// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2021, Linaro Ltd */ #include #include #include #include #include #include #include #include #include #include #include #include #define WWAN_MAX_MINORS 256 /* 256 minors allowed with register_chrdev() */ static DEFINE_MUTEX(wwan_register_lock); /* WWAN device create|remove lock */ static DEFINE_IDA(minors); /* minors for WWAN port chardevs */ static DEFINE_IDA(wwan_dev_ids); /* for unique WWAN device IDs */ static struct class *wwan_class; static int wwan_major; #define to_wwan_dev(d) container_of(d, struct wwan_device, dev) #define to_wwan_port(d) container_of(d, struct wwan_port, dev) /* WWAN port flags */ #define WWAN_PORT_TX_OFF 0 /** * struct wwan_device - The structure that defines a WWAN device * * @id: WWAN device unique ID. * @dev: Underlying device. * @port_id: Current available port ID to pick. */ struct wwan_device { unsigned int id; struct device dev; atomic_t port_id; }; /** * struct wwan_port - The structure that defines a WWAN port * @type: Port type * @start_count: Port start counter * @flags: Store port state and capabilities * @ops: Pointer to WWAN port operations * @ops_lock: Protect port ops * @dev: Underlying device * @rxq: Buffer inbound queue * @waitqueue: The waitqueue for port fops (read/write/poll) */ struct wwan_port { enum wwan_port_type type; unsigned int start_count; unsigned long flags; const struct wwan_port_ops *ops; struct mutex ops_lock; /* Serialize ops + protect against removal */ struct device dev; struct sk_buff_head rxq; wait_queue_head_t waitqueue; }; static void wwan_dev_destroy(struct device *dev) { struct wwan_device *wwandev = to_wwan_dev(dev); ida_free(&wwan_dev_ids, wwandev->id); kfree(wwandev); } static const struct device_type wwan_dev_type = { .name = "wwan_dev", .release = wwan_dev_destroy, }; static int wwan_dev_parent_match(struct device *dev, const void *parent) { return (dev->type == &wwan_dev_type && dev->parent == parent); } static struct wwan_device *wwan_dev_get_by_parent(struct device *parent) { struct device *dev; dev = class_find_device(wwan_class, NULL, parent, wwan_dev_parent_match); if (!dev) return ERR_PTR(-ENODEV); return to_wwan_dev(dev); } /* This function allocates and registers a new WWAN device OR if a WWAN device * already exist for the given parent, it gets a reference and return it. * This function is not exported (for now), it is called indirectly via * wwan_create_port(). */ static struct wwan_device *wwan_create_dev(struct device *parent) { struct wwan_device *wwandev; int err, id; /* The 'find-alloc-register' operation must be protected against * concurrent execution, a WWAN device is possibly shared between * multiple callers or concurrently unregistered from wwan_remove_dev(). */ mutex_lock(&wwan_register_lock); /* If wwandev already exists, return it */ wwandev = wwan_dev_get_by_parent(parent); if (!IS_ERR(wwandev)) goto done_unlock; id = ida_alloc(&wwan_dev_ids, GFP_KERNEL); if (id < 0) goto done_unlock; wwandev = kzalloc(sizeof(*wwandev), GFP_KERNEL); if (!wwandev) { ida_free(&wwan_dev_ids, id); goto done_unlock; } wwandev->dev.parent = parent; wwandev->dev.class = wwan_class; wwandev->dev.type = &wwan_dev_type; wwandev->id = id; dev_set_name(&wwandev->dev, "wwan%d", wwandev->id); err = device_register(&wwandev->dev); if (err) { put_device(&wwandev->dev); wwandev = NULL; } done_unlock: mutex_unlock(&wwan_register_lock); return wwandev; } static int is_wwan_child(struct device *dev, void *data) { return dev->class == wwan_class; } static void wwan_remove_dev(struct wwan_device *wwandev) { int ret; /* Prevent concurrent picking from wwan_create_dev */ mutex_lock(&wwan_register_lock); /* WWAN device is created and registered (get+add) along with its first * child port, and subsequent port registrations only grab a reference * (get). The WWAN device must then be unregistered (del+put) along with * its latest port, and reference simply dropped (put) otherwise. */ ret = device_for_each_child(&wwandev->dev, NULL, is_wwan_child); if (!ret) device_unregister(&wwandev->dev); else put_device(&wwandev->dev); mutex_unlock(&wwan_register_lock); } /* ------- WWAN port management ------- */ static void wwan_port_destroy(struct device *dev) { struct wwan_port *port = to_wwan_port(dev); ida_free(&minors, MINOR(port->dev.devt)); skb_queue_purge(&port->rxq); mutex_destroy(&port->ops_lock); kfree(port); } static const struct device_type wwan_port_dev_type = { .name = "wwan_port", .release = wwan_port_destroy, }; static int wwan_port_minor_match(struct device *dev, const void *minor) { return (dev->type == &wwan_port_dev_type && MINOR(dev->devt) == *(unsigned int *)minor); } static struct wwan_port *wwan_port_get_by_minor(unsigned int minor) { struct device *dev; dev = class_find_device(wwan_class, NULL, &minor, wwan_port_minor_match); if (!dev) return ERR_PTR(-ENODEV); return to_wwan_port(dev); } /* Keep aligned with wwan_port_type enum */ static const char * const wwan_port_type_str[] = { "AT", "MBIM", "QMI", "QCDM", "FIREHOSE" }; struct wwan_port *wwan_create_port(struct device *parent, enum wwan_port_type type, const struct wwan_port_ops *ops, void *drvdata) { struct wwan_device *wwandev; struct wwan_port *port; int minor, err = -ENOMEM; if (type >= WWAN_PORT_MAX || !ops) return ERR_PTR(-EINVAL); /* A port is always a child of a WWAN device, retrieve (allocate or * pick) the WWAN device based on the provided parent device. */ wwandev = wwan_create_dev(parent); if (IS_ERR(wwandev)) return ERR_CAST(wwandev); /* A port is exposed as character device, get a minor */ minor = ida_alloc_range(&minors, 0, WWAN_MAX_MINORS - 1, GFP_KERNEL); if (minor < 0) goto error_wwandev_remove; port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) { ida_free(&minors, minor); goto error_wwandev_remove; } port->type = type; port->ops = ops; mutex_init(&port->ops_lock); skb_queue_head_init(&port->rxq); init_waitqueue_head(&port->waitqueue); port->dev.parent = &wwandev->dev; port->dev.class = wwan_class; port->dev.type = &wwan_port_dev_type; port->dev.devt = MKDEV(wwan_major, minor); dev_set_drvdata(&port->dev, drvdata); /* create unique name based on wwan device id, port index and type */ dev_set_name(&port->dev, "wwan%up%u%s", wwandev->id, atomic_inc_return(&wwandev->port_id), wwan_port_type_str[port->type]); err = device_register(&port->dev); if (err) goto error_put_device; return port; error_put_device: put_device(&port->dev); error_wwandev_remove: wwan_remove_dev(wwandev); return ERR_PTR(err); } EXPORT_SYMBOL_GPL(wwan_create_port); void wwan_remove_port(struct wwan_port *port) { struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); mutex_lock(&port->ops_lock); if (port->start_count) port->ops->stop(port); port->ops = NULL; /* Prevent any new port operations (e.g. from fops) */ mutex_unlock(&port->ops_lock); wake_up_interruptible(&port->waitqueue); skb_queue_purge(&port->rxq); dev_set_drvdata(&port->dev, NULL); device_unregister(&port->dev); /* Release related wwan device */ wwan_remove_dev(wwandev); } EXPORT_SYMBOL_GPL(wwan_remove_port); void wwan_port_rx(struct wwan_port *port, struct sk_buff *skb) { skb_queue_tail(&port->rxq, skb); wake_up_interruptible(&port->waitqueue); } EXPORT_SYMBOL_GPL(wwan_port_rx); void wwan_port_txon(struct wwan_port *port) { clear_bit(WWAN_PORT_TX_OFF, &port->flags); wake_up_interruptible(&port->waitqueue); } EXPORT_SYMBOL_GPL(wwan_port_txon); void wwan_port_txoff(struct wwan_port *port) { set_bit(WWAN_PORT_TX_OFF, &port->flags); } EXPORT_SYMBOL_GPL(wwan_port_txoff); void *wwan_port_get_drvdata(struct wwan_port *port) { return dev_get_drvdata(&port->dev); } EXPORT_SYMBOL_GPL(wwan_port_get_drvdata); static int wwan_port_op_start(struct wwan_port *port) { int ret = 0; mutex_lock(&port->ops_lock); if (!port->ops) { /* Port got unplugged */ ret = -ENODEV; goto out_unlock; } /* If port is already started, don't start again */ if (!port->start_count) ret = port->ops->start(port); if (!ret) port->start_count++; out_unlock: mutex_unlock(&port->ops_lock); return ret; } static void wwan_port_op_stop(struct wwan_port *port) { mutex_lock(&port->ops_lock); port->start_count--; if (port->ops && !port->start_count) port->ops->stop(port); mutex_unlock(&port->ops_lock); } static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb) { int ret; mutex_lock(&port->ops_lock); if (!port->ops) { /* Port got unplugged */ ret = -ENODEV; goto out_unlock; } ret = port->ops->tx(port, skb); out_unlock: mutex_unlock(&port->ops_lock); return ret; } static bool is_read_blocked(struct wwan_port *port) { return skb_queue_empty(&port->rxq) && port->ops; } static bool is_write_blocked(struct wwan_port *port) { return test_bit(WWAN_PORT_TX_OFF, &port->flags) && port->ops; } static int wwan_wait_rx(struct wwan_port *port, bool nonblock) { if (!is_read_blocked(port)) return 0; if (nonblock) return -EAGAIN; if (wait_event_interruptible(port->waitqueue, !is_read_blocked(port))) return -ERESTARTSYS; return 0; } static int wwan_wait_tx(struct wwan_port *port, bool nonblock) { if (!is_write_blocked(port)) return 0; if (nonblock) return -EAGAIN; if (wait_event_interruptible(port->waitqueue, !is_write_blocked(port))) return -ERESTARTSYS; return 0; } static int wwan_port_fops_open(struct inode *inode, struct file *file) { struct wwan_port *port; int err = 0; port = wwan_port_get_by_minor(iminor(inode)); if (IS_ERR(port)) return PTR_ERR(port); file->private_data = port; stream_open(inode, file); err = wwan_port_op_start(port); if (err) put_device(&port->dev); return err; } static int wwan_port_fops_release(struct inode *inode, struct file *filp) { struct wwan_port *port = filp->private_data; wwan_port_op_stop(port); put_device(&port->dev); return 0; } static ssize_t wwan_port_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct wwan_port *port = filp->private_data; struct sk_buff *skb; size_t copied; int ret; ret = wwan_wait_rx(port, !!(filp->f_flags & O_NONBLOCK)); if (ret) return ret; skb = skb_dequeue(&port->rxq); if (!skb) return -EIO; copied = min_t(size_t, count, skb->len); if (copy_to_user(buf, skb->data, copied)) { kfree_skb(skb); return -EFAULT; } skb_pull(skb, copied); /* skb is not fully consumed, keep it in the queue */ if (skb->len) skb_queue_head(&port->rxq, skb); else consume_skb(skb); return copied; } static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp) { struct wwan_port *port = filp->private_data; struct sk_buff *skb; int ret; ret = wwan_wait_tx(port, !!(filp->f_flags & O_NONBLOCK)); if (ret) return ret; skb = alloc_skb(count, GFP_KERNEL); if (!skb) return -ENOMEM; if (copy_from_user(skb_put(skb, count), buf, count)) { kfree_skb(skb); return -EFAULT; } ret = wwan_port_op_tx(port, skb); if (ret) { kfree_skb(skb); return ret; } return count; } static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait) { struct wwan_port *port = filp->private_data; __poll_t mask = 0; poll_wait(filp, &port->waitqueue, wait); if (!is_write_blocked(port)) mask |= EPOLLOUT | EPOLLWRNORM; if (!is_read_blocked(port)) mask |= EPOLLIN | EPOLLRDNORM; if (!port->ops) mask |= EPOLLHUP | EPOLLERR; return mask; } static const struct file_operations wwan_port_fops = { .owner = THIS_MODULE, .open = wwan_port_fops_open, .release = wwan_port_fops_release, .read = wwan_port_fops_read, .write = wwan_port_fops_write, .poll = wwan_port_fops_poll, .llseek = noop_llseek, }; static int __init wwan_init(void) { wwan_class = class_create(THIS_MODULE, "wwan"); if (IS_ERR(wwan_class)) return PTR_ERR(wwan_class); /* chrdev used for wwan ports */ wwan_major = register_chrdev(0, "wwan_port", &wwan_port_fops); if (wwan_major < 0) { class_destroy(wwan_class); return wwan_major; } return 0; } static void __exit wwan_exit(void) { unregister_chrdev(wwan_major, "wwan_port"); class_destroy(wwan_class); } module_init(wwan_init); module_exit(wwan_exit); MODULE_AUTHOR("Loic Poulain "); MODULE_DESCRIPTION("WWAN core"); MODULE_LICENSE("GPL v2");