19a44c1ccSLoic Poulain // SPDX-License-Identifier: GPL-2.0-only 29a44c1ccSLoic Poulain /* Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> */ 39a44c1ccSLoic Poulain 49a44c1ccSLoic Poulain #include <linux/err.h> 59a44c1ccSLoic Poulain #include <linux/errno.h> 69a44c1ccSLoic Poulain #include <linux/fs.h> 79a44c1ccSLoic Poulain #include <linux/init.h> 89a44c1ccSLoic Poulain #include <linux/idr.h> 99a44c1ccSLoic Poulain #include <linux/kernel.h> 109a44c1ccSLoic Poulain #include <linux/module.h> 119a44c1ccSLoic Poulain #include <linux/poll.h> 129a44c1ccSLoic Poulain #include <linux/skbuff.h> 139a44c1ccSLoic Poulain #include <linux/slab.h> 149a44c1ccSLoic Poulain #include <linux/types.h> 15e263c5b2SSergey Ryazanov #include <linux/termios.h> 169a44c1ccSLoic Poulain #include <linux/wwan.h> 179a44c1ccSLoic Poulain 1872eedfc4SSergey Ryazanov /* Maximum number of minors in use */ 1972eedfc4SSergey Ryazanov #define WWAN_MAX_MINORS (1 << MINORBITS) 209a44c1ccSLoic Poulain 219a44c1ccSLoic Poulain static DEFINE_MUTEX(wwan_register_lock); /* WWAN device create|remove lock */ 229a44c1ccSLoic Poulain static DEFINE_IDA(minors); /* minors for WWAN port chardevs */ 239a44c1ccSLoic Poulain static DEFINE_IDA(wwan_dev_ids); /* for unique WWAN device IDs */ 249a44c1ccSLoic Poulain static struct class *wwan_class; 259a44c1ccSLoic Poulain static int wwan_major; 269a44c1ccSLoic Poulain 279a44c1ccSLoic Poulain #define to_wwan_dev(d) container_of(d, struct wwan_device, dev) 289a44c1ccSLoic Poulain #define to_wwan_port(d) container_of(d, struct wwan_port, dev) 299a44c1ccSLoic Poulain 309a44c1ccSLoic Poulain /* WWAN port flags */ 31b8c55ce2SLoic Poulain #define WWAN_PORT_TX_OFF 0 329a44c1ccSLoic Poulain 339a44c1ccSLoic Poulain /** 349a44c1ccSLoic Poulain * struct wwan_device - The structure that defines a WWAN device 359a44c1ccSLoic Poulain * 369a44c1ccSLoic Poulain * @id: WWAN device unique ID. 379a44c1ccSLoic Poulain * @dev: Underlying device. 389a44c1ccSLoic Poulain */ 399a44c1ccSLoic Poulain struct wwan_device { 409a44c1ccSLoic Poulain unsigned int id; 419a44c1ccSLoic Poulain struct device dev; 429a44c1ccSLoic Poulain }; 439a44c1ccSLoic Poulain 449a44c1ccSLoic Poulain /** 459a44c1ccSLoic Poulain * struct wwan_port - The structure that defines a WWAN port 469a44c1ccSLoic Poulain * @type: Port type 479a44c1ccSLoic Poulain * @start_count: Port start counter 489a44c1ccSLoic Poulain * @flags: Store port state and capabilities 499a44c1ccSLoic Poulain * @ops: Pointer to WWAN port operations 509a44c1ccSLoic Poulain * @ops_lock: Protect port ops 519a44c1ccSLoic Poulain * @dev: Underlying device 529a44c1ccSLoic Poulain * @rxq: Buffer inbound queue 539a44c1ccSLoic Poulain * @waitqueue: The waitqueue for port fops (read/write/poll) 54c230035cSSergey Ryazanov * @data_lock: Port specific data access serialization 55c230035cSSergey Ryazanov * @at_data: AT port specific data 569a44c1ccSLoic Poulain */ 579a44c1ccSLoic Poulain struct wwan_port { 589a44c1ccSLoic Poulain enum wwan_port_type type; 599a44c1ccSLoic Poulain unsigned int start_count; 609a44c1ccSLoic Poulain unsigned long flags; 619a44c1ccSLoic Poulain const struct wwan_port_ops *ops; 629a44c1ccSLoic Poulain struct mutex ops_lock; /* Serialize ops + protect against removal */ 639a44c1ccSLoic Poulain struct device dev; 649a44c1ccSLoic Poulain struct sk_buff_head rxq; 659a44c1ccSLoic Poulain wait_queue_head_t waitqueue; 66c230035cSSergey Ryazanov struct mutex data_lock; /* Port specific data access serialization */ 67c230035cSSergey Ryazanov union { 68c230035cSSergey Ryazanov struct { 69c230035cSSergey Ryazanov struct ktermios termios; 70c230035cSSergey Ryazanov int mdmbits; 71c230035cSSergey Ryazanov } at_data; 72c230035cSSergey Ryazanov }; 739a44c1ccSLoic Poulain }; 749a44c1ccSLoic Poulain 75e4e92ee7SLoic Poulain static ssize_t index_show(struct device *dev, struct device_attribute *attr, char *buf) 76e4e92ee7SLoic Poulain { 77e4e92ee7SLoic Poulain struct wwan_device *wwan = to_wwan_dev(dev); 78e4e92ee7SLoic Poulain 79e4e92ee7SLoic Poulain return sprintf(buf, "%d\n", wwan->id); 80e4e92ee7SLoic Poulain } 81e4e92ee7SLoic Poulain static DEVICE_ATTR_RO(index); 82e4e92ee7SLoic Poulain 83e4e92ee7SLoic Poulain static struct attribute *wwan_dev_attrs[] = { 84e4e92ee7SLoic Poulain &dev_attr_index.attr, 85e4e92ee7SLoic Poulain NULL, 86e4e92ee7SLoic Poulain }; 87e4e92ee7SLoic Poulain ATTRIBUTE_GROUPS(wwan_dev); 88e4e92ee7SLoic Poulain 899a44c1ccSLoic Poulain static void wwan_dev_destroy(struct device *dev) 909a44c1ccSLoic Poulain { 919a44c1ccSLoic Poulain struct wwan_device *wwandev = to_wwan_dev(dev); 929a44c1ccSLoic Poulain 939a44c1ccSLoic Poulain ida_free(&wwan_dev_ids, wwandev->id); 949a44c1ccSLoic Poulain kfree(wwandev); 959a44c1ccSLoic Poulain } 969a44c1ccSLoic Poulain 979a44c1ccSLoic Poulain static const struct device_type wwan_dev_type = { 989a44c1ccSLoic Poulain .name = "wwan_dev", 999a44c1ccSLoic Poulain .release = wwan_dev_destroy, 100e4e92ee7SLoic Poulain .groups = wwan_dev_groups, 1019a44c1ccSLoic Poulain }; 1029a44c1ccSLoic Poulain 1039a44c1ccSLoic Poulain static int wwan_dev_parent_match(struct device *dev, const void *parent) 1049a44c1ccSLoic Poulain { 1059a44c1ccSLoic Poulain return (dev->type == &wwan_dev_type && dev->parent == parent); 1069a44c1ccSLoic Poulain } 1079a44c1ccSLoic Poulain 1089a44c1ccSLoic Poulain static struct wwan_device *wwan_dev_get_by_parent(struct device *parent) 1099a44c1ccSLoic Poulain { 1109a44c1ccSLoic Poulain struct device *dev; 1119a44c1ccSLoic Poulain 1129a44c1ccSLoic Poulain dev = class_find_device(wwan_class, NULL, parent, wwan_dev_parent_match); 1139a44c1ccSLoic Poulain if (!dev) 1149a44c1ccSLoic Poulain return ERR_PTR(-ENODEV); 1159a44c1ccSLoic Poulain 1169a44c1ccSLoic Poulain return to_wwan_dev(dev); 1179a44c1ccSLoic Poulain } 1189a44c1ccSLoic Poulain 1199a44c1ccSLoic Poulain /* This function allocates and registers a new WWAN device OR if a WWAN device 1209a44c1ccSLoic Poulain * already exist for the given parent, it gets a reference and return it. 1219a44c1ccSLoic Poulain * This function is not exported (for now), it is called indirectly via 1229a44c1ccSLoic Poulain * wwan_create_port(). 1239a44c1ccSLoic Poulain */ 1249a44c1ccSLoic Poulain static struct wwan_device *wwan_create_dev(struct device *parent) 1259a44c1ccSLoic Poulain { 1269a44c1ccSLoic Poulain struct wwan_device *wwandev; 1279a44c1ccSLoic Poulain int err, id; 1289a44c1ccSLoic Poulain 1299a44c1ccSLoic Poulain /* The 'find-alloc-register' operation must be protected against 1309a44c1ccSLoic Poulain * concurrent execution, a WWAN device is possibly shared between 1319a44c1ccSLoic Poulain * multiple callers or concurrently unregistered from wwan_remove_dev(). 1329a44c1ccSLoic Poulain */ 1339a44c1ccSLoic Poulain mutex_lock(&wwan_register_lock); 1349a44c1ccSLoic Poulain 1359a44c1ccSLoic Poulain /* If wwandev already exists, return it */ 1369a44c1ccSLoic Poulain wwandev = wwan_dev_get_by_parent(parent); 1379a44c1ccSLoic Poulain if (!IS_ERR(wwandev)) 1389a44c1ccSLoic Poulain goto done_unlock; 1399a44c1ccSLoic Poulain 1409a44c1ccSLoic Poulain id = ida_alloc(&wwan_dev_ids, GFP_KERNEL); 1419a44c1ccSLoic Poulain if (id < 0) 1429a44c1ccSLoic Poulain goto done_unlock; 1439a44c1ccSLoic Poulain 1449a44c1ccSLoic Poulain wwandev = kzalloc(sizeof(*wwandev), GFP_KERNEL); 1459a44c1ccSLoic Poulain if (!wwandev) { 1469a44c1ccSLoic Poulain ida_free(&wwan_dev_ids, id); 1479a44c1ccSLoic Poulain goto done_unlock; 1489a44c1ccSLoic Poulain } 1499a44c1ccSLoic Poulain 1509a44c1ccSLoic Poulain wwandev->dev.parent = parent; 1519a44c1ccSLoic Poulain wwandev->dev.class = wwan_class; 1529a44c1ccSLoic Poulain wwandev->dev.type = &wwan_dev_type; 1539a44c1ccSLoic Poulain wwandev->id = id; 1549a44c1ccSLoic Poulain dev_set_name(&wwandev->dev, "wwan%d", wwandev->id); 1559a44c1ccSLoic Poulain 1569a44c1ccSLoic Poulain err = device_register(&wwandev->dev); 1579a44c1ccSLoic Poulain if (err) { 1589a44c1ccSLoic Poulain put_device(&wwandev->dev); 1599a44c1ccSLoic Poulain wwandev = NULL; 1609a44c1ccSLoic Poulain } 1619a44c1ccSLoic Poulain 1629a44c1ccSLoic Poulain done_unlock: 1639a44c1ccSLoic Poulain mutex_unlock(&wwan_register_lock); 1649a44c1ccSLoic Poulain 1659a44c1ccSLoic Poulain return wwandev; 1669a44c1ccSLoic Poulain } 1679a44c1ccSLoic Poulain 1689a44c1ccSLoic Poulain static int is_wwan_child(struct device *dev, void *data) 1699a44c1ccSLoic Poulain { 1709a44c1ccSLoic Poulain return dev->class == wwan_class; 1719a44c1ccSLoic Poulain } 1729a44c1ccSLoic Poulain 1739a44c1ccSLoic Poulain static void wwan_remove_dev(struct wwan_device *wwandev) 1749a44c1ccSLoic Poulain { 1759a44c1ccSLoic Poulain int ret; 1769a44c1ccSLoic Poulain 1779a44c1ccSLoic Poulain /* Prevent concurrent picking from wwan_create_dev */ 1789a44c1ccSLoic Poulain mutex_lock(&wwan_register_lock); 1799a44c1ccSLoic Poulain 1809a44c1ccSLoic Poulain /* WWAN device is created and registered (get+add) along with its first 1819a44c1ccSLoic Poulain * child port, and subsequent port registrations only grab a reference 1829a44c1ccSLoic Poulain * (get). The WWAN device must then be unregistered (del+put) along with 1839a44c1ccSLoic Poulain * its latest port, and reference simply dropped (put) otherwise. 1849a44c1ccSLoic Poulain */ 1859a44c1ccSLoic Poulain ret = device_for_each_child(&wwandev->dev, NULL, is_wwan_child); 1869a44c1ccSLoic Poulain if (!ret) 1879a44c1ccSLoic Poulain device_unregister(&wwandev->dev); 1889a44c1ccSLoic Poulain else 1899a44c1ccSLoic Poulain put_device(&wwandev->dev); 1909a44c1ccSLoic Poulain 1919a44c1ccSLoic Poulain mutex_unlock(&wwan_register_lock); 1929a44c1ccSLoic Poulain } 1939a44c1ccSLoic Poulain 1949a44c1ccSLoic Poulain /* ------- WWAN port management ------- */ 1959a44c1ccSLoic Poulain 196392c26f7SSergey Ryazanov static const struct { 197392c26f7SSergey Ryazanov const char * const name; /* Port type name */ 198392c26f7SSergey Ryazanov const char * const devsuf; /* Port devce name suffix */ 199392c26f7SSergey Ryazanov } wwan_port_types[WWAN_PORT_MAX + 1] = { 200392c26f7SSergey Ryazanov [WWAN_PORT_AT] = { 201392c26f7SSergey Ryazanov .name = "AT", 202392c26f7SSergey Ryazanov .devsuf = "at", 203392c26f7SSergey Ryazanov }, 204392c26f7SSergey Ryazanov [WWAN_PORT_MBIM] = { 205392c26f7SSergey Ryazanov .name = "MBIM", 206392c26f7SSergey Ryazanov .devsuf = "mbim", 207392c26f7SSergey Ryazanov }, 208392c26f7SSergey Ryazanov [WWAN_PORT_QMI] = { 209392c26f7SSergey Ryazanov .name = "QMI", 210392c26f7SSergey Ryazanov .devsuf = "qmi", 211392c26f7SSergey Ryazanov }, 212392c26f7SSergey Ryazanov [WWAN_PORT_QCDM] = { 213392c26f7SSergey Ryazanov .name = "QCDM", 214392c26f7SSergey Ryazanov .devsuf = "qcdm", 215392c26f7SSergey Ryazanov }, 216392c26f7SSergey Ryazanov [WWAN_PORT_FIREHOSE] = { 217392c26f7SSergey Ryazanov .name = "FIREHOSE", 218392c26f7SSergey Ryazanov .devsuf = "firehose", 219392c26f7SSergey Ryazanov }, 220b3e22e10SLoic Poulain }; 221b3e22e10SLoic Poulain 222b3e22e10SLoic Poulain static ssize_t type_show(struct device *dev, struct device_attribute *attr, 223b3e22e10SLoic Poulain char *buf) 224b3e22e10SLoic Poulain { 225b3e22e10SLoic Poulain struct wwan_port *port = to_wwan_port(dev); 226b3e22e10SLoic Poulain 227392c26f7SSergey Ryazanov return sprintf(buf, "%s\n", wwan_port_types[port->type].name); 228b3e22e10SLoic Poulain } 229b3e22e10SLoic Poulain static DEVICE_ATTR_RO(type); 230b3e22e10SLoic Poulain 231b3e22e10SLoic Poulain static struct attribute *wwan_port_attrs[] = { 232b3e22e10SLoic Poulain &dev_attr_type.attr, 233b3e22e10SLoic Poulain NULL, 234b3e22e10SLoic Poulain }; 235b3e22e10SLoic Poulain ATTRIBUTE_GROUPS(wwan_port); 236b3e22e10SLoic Poulain 2379a44c1ccSLoic Poulain static void wwan_port_destroy(struct device *dev) 2389a44c1ccSLoic Poulain { 2399a44c1ccSLoic Poulain struct wwan_port *port = to_wwan_port(dev); 2409a44c1ccSLoic Poulain 2419a44c1ccSLoic Poulain ida_free(&minors, MINOR(port->dev.devt)); 242c230035cSSergey Ryazanov mutex_destroy(&port->data_lock); 2439a44c1ccSLoic Poulain mutex_destroy(&port->ops_lock); 2449a44c1ccSLoic Poulain kfree(port); 2459a44c1ccSLoic Poulain } 2469a44c1ccSLoic Poulain 2479a44c1ccSLoic Poulain static const struct device_type wwan_port_dev_type = { 2489a44c1ccSLoic Poulain .name = "wwan_port", 2499a44c1ccSLoic Poulain .release = wwan_port_destroy, 250b3e22e10SLoic Poulain .groups = wwan_port_groups, 2519a44c1ccSLoic Poulain }; 2529a44c1ccSLoic Poulain 2539a44c1ccSLoic Poulain static int wwan_port_minor_match(struct device *dev, const void *minor) 2549a44c1ccSLoic Poulain { 2559a44c1ccSLoic Poulain return (dev->type == &wwan_port_dev_type && 2569a44c1ccSLoic Poulain MINOR(dev->devt) == *(unsigned int *)minor); 2579a44c1ccSLoic Poulain } 2589a44c1ccSLoic Poulain 2599a44c1ccSLoic Poulain static struct wwan_port *wwan_port_get_by_minor(unsigned int minor) 2609a44c1ccSLoic Poulain { 2619a44c1ccSLoic Poulain struct device *dev; 2629a44c1ccSLoic Poulain 2639a44c1ccSLoic Poulain dev = class_find_device(wwan_class, NULL, &minor, wwan_port_minor_match); 2649a44c1ccSLoic Poulain if (!dev) 2659a44c1ccSLoic Poulain return ERR_PTR(-ENODEV); 2669a44c1ccSLoic Poulain 2679a44c1ccSLoic Poulain return to_wwan_port(dev); 2689a44c1ccSLoic Poulain } 2699a44c1ccSLoic Poulain 270f458709fSSergey Ryazanov /* Allocate and set unique name based on passed format 271f458709fSSergey Ryazanov * 272f458709fSSergey Ryazanov * Name allocation approach is highly inspired by the __dev_alloc_name() 273f458709fSSergey Ryazanov * function. 274f458709fSSergey Ryazanov * 275f458709fSSergey Ryazanov * To avoid names collision, the caller must prevent the new port device 276f458709fSSergey Ryazanov * registration as well as concurrent invocation of this function. 277f458709fSSergey Ryazanov */ 278f458709fSSergey Ryazanov static int __wwan_port_dev_assign_name(struct wwan_port *port, const char *fmt) 279f458709fSSergey Ryazanov { 280f458709fSSergey Ryazanov struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); 281f458709fSSergey Ryazanov const unsigned int max_ports = PAGE_SIZE * 8; 282f458709fSSergey Ryazanov struct class_dev_iter iter; 283f458709fSSergey Ryazanov unsigned long *idmap; 284f458709fSSergey Ryazanov struct device *dev; 285f458709fSSergey Ryazanov char buf[0x20]; 286f458709fSSergey Ryazanov int id; 287f458709fSSergey Ryazanov 288f458709fSSergey Ryazanov idmap = (unsigned long *)get_zeroed_page(GFP_KERNEL); 289f458709fSSergey Ryazanov if (!idmap) 290f458709fSSergey Ryazanov return -ENOMEM; 291f458709fSSergey Ryazanov 292f458709fSSergey Ryazanov /* Collect ids of same name format ports */ 293f458709fSSergey Ryazanov class_dev_iter_init(&iter, wwan_class, NULL, &wwan_port_dev_type); 294f458709fSSergey Ryazanov while ((dev = class_dev_iter_next(&iter))) { 295f458709fSSergey Ryazanov if (dev->parent != &wwandev->dev) 296f458709fSSergey Ryazanov continue; 297f458709fSSergey Ryazanov if (sscanf(dev_name(dev), fmt, &id) != 1) 298f458709fSSergey Ryazanov continue; 299f458709fSSergey Ryazanov if (id < 0 || id >= max_ports) 300f458709fSSergey Ryazanov continue; 301f458709fSSergey Ryazanov set_bit(id, idmap); 302f458709fSSergey Ryazanov } 303f458709fSSergey Ryazanov class_dev_iter_exit(&iter); 304f458709fSSergey Ryazanov 305f458709fSSergey Ryazanov /* Allocate unique id */ 306f458709fSSergey Ryazanov id = find_first_zero_bit(idmap, max_ports); 307f458709fSSergey Ryazanov free_page((unsigned long)idmap); 308f458709fSSergey Ryazanov 309f458709fSSergey Ryazanov snprintf(buf, sizeof(buf), fmt, id); /* Name generation */ 310f458709fSSergey Ryazanov 311f458709fSSergey Ryazanov dev = device_find_child_by_name(&wwandev->dev, buf); 312f458709fSSergey Ryazanov if (dev) { 313f458709fSSergey Ryazanov put_device(dev); 314f458709fSSergey Ryazanov return -ENFILE; 315f458709fSSergey Ryazanov } 316f458709fSSergey Ryazanov 317f458709fSSergey Ryazanov return dev_set_name(&port->dev, buf); 318f458709fSSergey Ryazanov } 319f458709fSSergey Ryazanov 3209a44c1ccSLoic Poulain struct wwan_port *wwan_create_port(struct device *parent, 3219a44c1ccSLoic Poulain enum wwan_port_type type, 3229a44c1ccSLoic Poulain const struct wwan_port_ops *ops, 3239a44c1ccSLoic Poulain void *drvdata) 3249a44c1ccSLoic Poulain { 3259a44c1ccSLoic Poulain struct wwan_device *wwandev; 3269a44c1ccSLoic Poulain struct wwan_port *port; 3279a44c1ccSLoic Poulain int minor, err = -ENOMEM; 328f458709fSSergey Ryazanov char namefmt[0x20]; 3299a44c1ccSLoic Poulain 330b64d76b7SSergey Ryazanov if (type > WWAN_PORT_MAX || !ops) 3319a44c1ccSLoic Poulain return ERR_PTR(-EINVAL); 3329a44c1ccSLoic Poulain 3339a44c1ccSLoic Poulain /* A port is always a child of a WWAN device, retrieve (allocate or 3349a44c1ccSLoic Poulain * pick) the WWAN device based on the provided parent device. 3359a44c1ccSLoic Poulain */ 3369a44c1ccSLoic Poulain wwandev = wwan_create_dev(parent); 3379a44c1ccSLoic Poulain if (IS_ERR(wwandev)) 3389a44c1ccSLoic Poulain return ERR_CAST(wwandev); 3399a44c1ccSLoic Poulain 3409a44c1ccSLoic Poulain /* A port is exposed as character device, get a minor */ 3419a44c1ccSLoic Poulain minor = ida_alloc_range(&minors, 0, WWAN_MAX_MINORS - 1, GFP_KERNEL); 3429a44c1ccSLoic Poulain if (minor < 0) 3439a44c1ccSLoic Poulain goto error_wwandev_remove; 3449a44c1ccSLoic Poulain 3459a44c1ccSLoic Poulain port = kzalloc(sizeof(*port), GFP_KERNEL); 3469a44c1ccSLoic Poulain if (!port) { 3479a44c1ccSLoic Poulain ida_free(&minors, minor); 3489a44c1ccSLoic Poulain goto error_wwandev_remove; 3499a44c1ccSLoic Poulain } 3509a44c1ccSLoic Poulain 3519a44c1ccSLoic Poulain port->type = type; 3529a44c1ccSLoic Poulain port->ops = ops; 3539a44c1ccSLoic Poulain mutex_init(&port->ops_lock); 3549a44c1ccSLoic Poulain skb_queue_head_init(&port->rxq); 3559a44c1ccSLoic Poulain init_waitqueue_head(&port->waitqueue); 356c230035cSSergey Ryazanov mutex_init(&port->data_lock); 3579a44c1ccSLoic Poulain 3589a44c1ccSLoic Poulain port->dev.parent = &wwandev->dev; 3599a44c1ccSLoic Poulain port->dev.class = wwan_class; 3609a44c1ccSLoic Poulain port->dev.type = &wwan_port_dev_type; 3619a44c1ccSLoic Poulain port->dev.devt = MKDEV(wwan_major, minor); 3629a44c1ccSLoic Poulain dev_set_drvdata(&port->dev, drvdata); 3639a44c1ccSLoic Poulain 364f458709fSSergey Ryazanov /* allocate unique name based on wwan device id, port type and number */ 365f458709fSSergey Ryazanov snprintf(namefmt, sizeof(namefmt), "wwan%u%s%%d", wwandev->id, 366392c26f7SSergey Ryazanov wwan_port_types[port->type].devsuf); 3679a44c1ccSLoic Poulain 368f458709fSSergey Ryazanov /* Serialize ports registration */ 369f458709fSSergey Ryazanov mutex_lock(&wwan_register_lock); 370f458709fSSergey Ryazanov 371f458709fSSergey Ryazanov __wwan_port_dev_assign_name(port, namefmt); 3729a44c1ccSLoic Poulain err = device_register(&port->dev); 373f458709fSSergey Ryazanov 374f458709fSSergey Ryazanov mutex_unlock(&wwan_register_lock); 375f458709fSSergey Ryazanov 3769a44c1ccSLoic Poulain if (err) 3779a44c1ccSLoic Poulain goto error_put_device; 3789a44c1ccSLoic Poulain 3799a44c1ccSLoic Poulain return port; 3809a44c1ccSLoic Poulain 3819a44c1ccSLoic Poulain error_put_device: 3829a44c1ccSLoic Poulain put_device(&port->dev); 3839a44c1ccSLoic Poulain error_wwandev_remove: 3849a44c1ccSLoic Poulain wwan_remove_dev(wwandev); 3859a44c1ccSLoic Poulain 3869a44c1ccSLoic Poulain return ERR_PTR(err); 3879a44c1ccSLoic Poulain } 3889a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_create_port); 3899a44c1ccSLoic Poulain 3909a44c1ccSLoic Poulain void wwan_remove_port(struct wwan_port *port) 3919a44c1ccSLoic Poulain { 3929a44c1ccSLoic Poulain struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); 3939a44c1ccSLoic Poulain 3949a44c1ccSLoic Poulain mutex_lock(&port->ops_lock); 3959a44c1ccSLoic Poulain if (port->start_count) 3969a44c1ccSLoic Poulain port->ops->stop(port); 3979a44c1ccSLoic Poulain port->ops = NULL; /* Prevent any new port operations (e.g. from fops) */ 3989a44c1ccSLoic Poulain mutex_unlock(&port->ops_lock); 3999a44c1ccSLoic Poulain 4009a44c1ccSLoic Poulain wake_up_interruptible(&port->waitqueue); 4019a44c1ccSLoic Poulain 4029a44c1ccSLoic Poulain skb_queue_purge(&port->rxq); 4039a44c1ccSLoic Poulain dev_set_drvdata(&port->dev, NULL); 4049a44c1ccSLoic Poulain device_unregister(&port->dev); 4059a44c1ccSLoic Poulain 4069a44c1ccSLoic Poulain /* Release related wwan device */ 4079a44c1ccSLoic Poulain wwan_remove_dev(wwandev); 4089a44c1ccSLoic Poulain } 4099a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_remove_port); 4109a44c1ccSLoic Poulain 4119a44c1ccSLoic Poulain void wwan_port_rx(struct wwan_port *port, struct sk_buff *skb) 4129a44c1ccSLoic Poulain { 4139a44c1ccSLoic Poulain skb_queue_tail(&port->rxq, skb); 4149a44c1ccSLoic Poulain wake_up_interruptible(&port->waitqueue); 4159a44c1ccSLoic Poulain } 4169a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_port_rx); 4179a44c1ccSLoic Poulain 4189a44c1ccSLoic Poulain void wwan_port_txon(struct wwan_port *port) 4199a44c1ccSLoic Poulain { 4209a44c1ccSLoic Poulain clear_bit(WWAN_PORT_TX_OFF, &port->flags); 4219a44c1ccSLoic Poulain wake_up_interruptible(&port->waitqueue); 4229a44c1ccSLoic Poulain } 4239a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_port_txon); 4249a44c1ccSLoic Poulain 4259a44c1ccSLoic Poulain void wwan_port_txoff(struct wwan_port *port) 4269a44c1ccSLoic Poulain { 4279a44c1ccSLoic Poulain set_bit(WWAN_PORT_TX_OFF, &port->flags); 4289a44c1ccSLoic Poulain } 4299a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_port_txoff); 4309a44c1ccSLoic Poulain 4319a44c1ccSLoic Poulain void *wwan_port_get_drvdata(struct wwan_port *port) 4329a44c1ccSLoic Poulain { 4339a44c1ccSLoic Poulain return dev_get_drvdata(&port->dev); 4349a44c1ccSLoic Poulain } 4359a44c1ccSLoic Poulain EXPORT_SYMBOL_GPL(wwan_port_get_drvdata); 4369a44c1ccSLoic Poulain 4379a44c1ccSLoic Poulain static int wwan_port_op_start(struct wwan_port *port) 4389a44c1ccSLoic Poulain { 4399a44c1ccSLoic Poulain int ret = 0; 4409a44c1ccSLoic Poulain 4419a44c1ccSLoic Poulain mutex_lock(&port->ops_lock); 4429a44c1ccSLoic Poulain if (!port->ops) { /* Port got unplugged */ 4439a44c1ccSLoic Poulain ret = -ENODEV; 4449a44c1ccSLoic Poulain goto out_unlock; 4459a44c1ccSLoic Poulain } 4469a44c1ccSLoic Poulain 4479a44c1ccSLoic Poulain /* If port is already started, don't start again */ 4489a44c1ccSLoic Poulain if (!port->start_count) 4499a44c1ccSLoic Poulain ret = port->ops->start(port); 4509a44c1ccSLoic Poulain 4519a44c1ccSLoic Poulain if (!ret) 4529a44c1ccSLoic Poulain port->start_count++; 4539a44c1ccSLoic Poulain 4549a44c1ccSLoic Poulain out_unlock: 4559a44c1ccSLoic Poulain mutex_unlock(&port->ops_lock); 4569a44c1ccSLoic Poulain 4579a44c1ccSLoic Poulain return ret; 4589a44c1ccSLoic Poulain } 4599a44c1ccSLoic Poulain 4609a44c1ccSLoic Poulain static void wwan_port_op_stop(struct wwan_port *port) 4619a44c1ccSLoic Poulain { 4629a44c1ccSLoic Poulain mutex_lock(&port->ops_lock); 4639a44c1ccSLoic Poulain port->start_count--; 464*50467203SSergey Ryazanov if (!port->start_count) { 465*50467203SSergey Ryazanov if (port->ops) 4669a44c1ccSLoic Poulain port->ops->stop(port); 467*50467203SSergey Ryazanov skb_queue_purge(&port->rxq); 468*50467203SSergey Ryazanov } 4699a44c1ccSLoic Poulain mutex_unlock(&port->ops_lock); 4709a44c1ccSLoic Poulain } 4719a44c1ccSLoic Poulain 4729a44c1ccSLoic Poulain static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb) 4739a44c1ccSLoic Poulain { 4749a44c1ccSLoic Poulain int ret; 4759a44c1ccSLoic Poulain 4769a44c1ccSLoic Poulain mutex_lock(&port->ops_lock); 4779a44c1ccSLoic Poulain if (!port->ops) { /* Port got unplugged */ 4789a44c1ccSLoic Poulain ret = -ENODEV; 4799a44c1ccSLoic Poulain goto out_unlock; 4809a44c1ccSLoic Poulain } 4819a44c1ccSLoic Poulain 4829a44c1ccSLoic Poulain ret = port->ops->tx(port, skb); 4839a44c1ccSLoic Poulain 4849a44c1ccSLoic Poulain out_unlock: 4859a44c1ccSLoic Poulain mutex_unlock(&port->ops_lock); 4869a44c1ccSLoic Poulain 4879a44c1ccSLoic Poulain return ret; 4889a44c1ccSLoic Poulain } 4899a44c1ccSLoic Poulain 4909a44c1ccSLoic Poulain static bool is_read_blocked(struct wwan_port *port) 4919a44c1ccSLoic Poulain { 4929a44c1ccSLoic Poulain return skb_queue_empty(&port->rxq) && port->ops; 4939a44c1ccSLoic Poulain } 4949a44c1ccSLoic Poulain 4959a44c1ccSLoic Poulain static bool is_write_blocked(struct wwan_port *port) 4969a44c1ccSLoic Poulain { 4979a44c1ccSLoic Poulain return test_bit(WWAN_PORT_TX_OFF, &port->flags) && port->ops; 4989a44c1ccSLoic Poulain } 4999a44c1ccSLoic Poulain 5009a44c1ccSLoic Poulain static int wwan_wait_rx(struct wwan_port *port, bool nonblock) 5019a44c1ccSLoic Poulain { 5029a44c1ccSLoic Poulain if (!is_read_blocked(port)) 5039a44c1ccSLoic Poulain return 0; 5049a44c1ccSLoic Poulain 5059a44c1ccSLoic Poulain if (nonblock) 5069a44c1ccSLoic Poulain return -EAGAIN; 5079a44c1ccSLoic Poulain 5089a44c1ccSLoic Poulain if (wait_event_interruptible(port->waitqueue, !is_read_blocked(port))) 5099a44c1ccSLoic Poulain return -ERESTARTSYS; 5109a44c1ccSLoic Poulain 5119a44c1ccSLoic Poulain return 0; 5129a44c1ccSLoic Poulain } 5139a44c1ccSLoic Poulain 5149a44c1ccSLoic Poulain static int wwan_wait_tx(struct wwan_port *port, bool nonblock) 5159a44c1ccSLoic Poulain { 5169a44c1ccSLoic Poulain if (!is_write_blocked(port)) 5179a44c1ccSLoic Poulain return 0; 5189a44c1ccSLoic Poulain 5199a44c1ccSLoic Poulain if (nonblock) 5209a44c1ccSLoic Poulain return -EAGAIN; 5219a44c1ccSLoic Poulain 5229a44c1ccSLoic Poulain if (wait_event_interruptible(port->waitqueue, !is_write_blocked(port))) 5239a44c1ccSLoic Poulain return -ERESTARTSYS; 5249a44c1ccSLoic Poulain 5259a44c1ccSLoic Poulain return 0; 5269a44c1ccSLoic Poulain } 5279a44c1ccSLoic Poulain 5289a44c1ccSLoic Poulain static int wwan_port_fops_open(struct inode *inode, struct file *file) 5299a44c1ccSLoic Poulain { 5309a44c1ccSLoic Poulain struct wwan_port *port; 5319a44c1ccSLoic Poulain int err = 0; 5329a44c1ccSLoic Poulain 5339a44c1ccSLoic Poulain port = wwan_port_get_by_minor(iminor(inode)); 5349a44c1ccSLoic Poulain if (IS_ERR(port)) 5359a44c1ccSLoic Poulain return PTR_ERR(port); 5369a44c1ccSLoic Poulain 5379a44c1ccSLoic Poulain file->private_data = port; 5389a44c1ccSLoic Poulain stream_open(inode, file); 5399a44c1ccSLoic Poulain 5409a44c1ccSLoic Poulain err = wwan_port_op_start(port); 5419a44c1ccSLoic Poulain if (err) 5429a44c1ccSLoic Poulain put_device(&port->dev); 5439a44c1ccSLoic Poulain 5449a44c1ccSLoic Poulain return err; 5459a44c1ccSLoic Poulain } 5469a44c1ccSLoic Poulain 5479a44c1ccSLoic Poulain static int wwan_port_fops_release(struct inode *inode, struct file *filp) 5489a44c1ccSLoic Poulain { 5499a44c1ccSLoic Poulain struct wwan_port *port = filp->private_data; 5509a44c1ccSLoic Poulain 5519a44c1ccSLoic Poulain wwan_port_op_stop(port); 5529a44c1ccSLoic Poulain put_device(&port->dev); 5539a44c1ccSLoic Poulain 5549a44c1ccSLoic Poulain return 0; 5559a44c1ccSLoic Poulain } 5569a44c1ccSLoic Poulain 5579a44c1ccSLoic Poulain static ssize_t wwan_port_fops_read(struct file *filp, char __user *buf, 5589a44c1ccSLoic Poulain size_t count, loff_t *ppos) 5599a44c1ccSLoic Poulain { 5609a44c1ccSLoic Poulain struct wwan_port *port = filp->private_data; 5619a44c1ccSLoic Poulain struct sk_buff *skb; 5629a44c1ccSLoic Poulain size_t copied; 5639a44c1ccSLoic Poulain int ret; 5649a44c1ccSLoic Poulain 5659a44c1ccSLoic Poulain ret = wwan_wait_rx(port, !!(filp->f_flags & O_NONBLOCK)); 5669a44c1ccSLoic Poulain if (ret) 5679a44c1ccSLoic Poulain return ret; 5689a44c1ccSLoic Poulain 5699a44c1ccSLoic Poulain skb = skb_dequeue(&port->rxq); 5709a44c1ccSLoic Poulain if (!skb) 5719a44c1ccSLoic Poulain return -EIO; 5729a44c1ccSLoic Poulain 5739a44c1ccSLoic Poulain copied = min_t(size_t, count, skb->len); 5749a44c1ccSLoic Poulain if (copy_to_user(buf, skb->data, copied)) { 5759a44c1ccSLoic Poulain kfree_skb(skb); 5769a44c1ccSLoic Poulain return -EFAULT; 5779a44c1ccSLoic Poulain } 5789a44c1ccSLoic Poulain skb_pull(skb, copied); 5799a44c1ccSLoic Poulain 5809a44c1ccSLoic Poulain /* skb is not fully consumed, keep it in the queue */ 5819a44c1ccSLoic Poulain if (skb->len) 5829a44c1ccSLoic Poulain skb_queue_head(&port->rxq, skb); 5839a44c1ccSLoic Poulain else 5849a44c1ccSLoic Poulain consume_skb(skb); 5859a44c1ccSLoic Poulain 5869a44c1ccSLoic Poulain return copied; 5879a44c1ccSLoic Poulain } 5889a44c1ccSLoic Poulain 5899a44c1ccSLoic Poulain static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf, 5909a44c1ccSLoic Poulain size_t count, loff_t *offp) 5919a44c1ccSLoic Poulain { 5929a44c1ccSLoic Poulain struct wwan_port *port = filp->private_data; 5939a44c1ccSLoic Poulain struct sk_buff *skb; 5949a44c1ccSLoic Poulain int ret; 5959a44c1ccSLoic Poulain 5969a44c1ccSLoic Poulain ret = wwan_wait_tx(port, !!(filp->f_flags & O_NONBLOCK)); 5979a44c1ccSLoic Poulain if (ret) 5989a44c1ccSLoic Poulain return ret; 5999a44c1ccSLoic Poulain 6009a44c1ccSLoic Poulain skb = alloc_skb(count, GFP_KERNEL); 6019a44c1ccSLoic Poulain if (!skb) 6029a44c1ccSLoic Poulain return -ENOMEM; 6039a44c1ccSLoic Poulain 6049a44c1ccSLoic Poulain if (copy_from_user(skb_put(skb, count), buf, count)) { 6059a44c1ccSLoic Poulain kfree_skb(skb); 6069a44c1ccSLoic Poulain return -EFAULT; 6079a44c1ccSLoic Poulain } 6089a44c1ccSLoic Poulain 6099a44c1ccSLoic Poulain ret = wwan_port_op_tx(port, skb); 6109a44c1ccSLoic Poulain if (ret) { 6119a44c1ccSLoic Poulain kfree_skb(skb); 6129a44c1ccSLoic Poulain return ret; 6139a44c1ccSLoic Poulain } 6149a44c1ccSLoic Poulain 6159a44c1ccSLoic Poulain return count; 6169a44c1ccSLoic Poulain } 6179a44c1ccSLoic Poulain 6189a44c1ccSLoic Poulain static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait) 6199a44c1ccSLoic Poulain { 6209a44c1ccSLoic Poulain struct wwan_port *port = filp->private_data; 6219a44c1ccSLoic Poulain __poll_t mask = 0; 6229a44c1ccSLoic Poulain 6239a44c1ccSLoic Poulain poll_wait(filp, &port->waitqueue, wait); 6249a44c1ccSLoic Poulain 6259a44c1ccSLoic Poulain if (!is_write_blocked(port)) 6269a44c1ccSLoic Poulain mask |= EPOLLOUT | EPOLLWRNORM; 6279a44c1ccSLoic Poulain if (!is_read_blocked(port)) 6289a44c1ccSLoic Poulain mask |= EPOLLIN | EPOLLRDNORM; 62957e22247SLoic Poulain if (!port->ops) 63057e22247SLoic Poulain mask |= EPOLLHUP | EPOLLERR; 6319a44c1ccSLoic Poulain 6329a44c1ccSLoic Poulain return mask; 6339a44c1ccSLoic Poulain } 6349a44c1ccSLoic Poulain 635c230035cSSergey Ryazanov /* Implements minimalistic stub terminal IOCTLs support */ 636c230035cSSergey Ryazanov static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, 637c230035cSSergey Ryazanov unsigned long arg) 638c230035cSSergey Ryazanov { 639c230035cSSergey Ryazanov int ret = 0; 640c230035cSSergey Ryazanov 641c230035cSSergey Ryazanov mutex_lock(&port->data_lock); 642c230035cSSergey Ryazanov 643c230035cSSergey Ryazanov switch (cmd) { 644c230035cSSergey Ryazanov case TCFLSH: 645c230035cSSergey Ryazanov break; 646c230035cSSergey Ryazanov 647c230035cSSergey Ryazanov case TCGETS: 648c230035cSSergey Ryazanov if (copy_to_user((void __user *)arg, &port->at_data.termios, 649c230035cSSergey Ryazanov sizeof(struct termios))) 650c230035cSSergey Ryazanov ret = -EFAULT; 651c230035cSSergey Ryazanov break; 652c230035cSSergey Ryazanov 653c230035cSSergey Ryazanov case TCSETS: 654c230035cSSergey Ryazanov case TCSETSW: 655c230035cSSergey Ryazanov case TCSETSF: 656c230035cSSergey Ryazanov if (copy_from_user(&port->at_data.termios, (void __user *)arg, 657c230035cSSergey Ryazanov sizeof(struct termios))) 658c230035cSSergey Ryazanov ret = -EFAULT; 659c230035cSSergey Ryazanov break; 660c230035cSSergey Ryazanov 661c230035cSSergey Ryazanov #ifdef TCGETS2 662c230035cSSergey Ryazanov case TCGETS2: 663c230035cSSergey Ryazanov if (copy_to_user((void __user *)arg, &port->at_data.termios, 664c230035cSSergey Ryazanov sizeof(struct termios2))) 665c230035cSSergey Ryazanov ret = -EFAULT; 666c230035cSSergey Ryazanov break; 667c230035cSSergey Ryazanov 668c230035cSSergey Ryazanov case TCSETS2: 669c230035cSSergey Ryazanov case TCSETSW2: 670c230035cSSergey Ryazanov case TCSETSF2: 671c230035cSSergey Ryazanov if (copy_from_user(&port->at_data.termios, (void __user *)arg, 672c230035cSSergey Ryazanov sizeof(struct termios2))) 673c230035cSSergey Ryazanov ret = -EFAULT; 674c230035cSSergey Ryazanov break; 675c230035cSSergey Ryazanov #endif 676c230035cSSergey Ryazanov 677c230035cSSergey Ryazanov case TIOCMGET: 678c230035cSSergey Ryazanov ret = put_user(port->at_data.mdmbits, (int __user *)arg); 679c230035cSSergey Ryazanov break; 680c230035cSSergey Ryazanov 681c230035cSSergey Ryazanov case TIOCMSET: 682c230035cSSergey Ryazanov case TIOCMBIC: 683c230035cSSergey Ryazanov case TIOCMBIS: { 684c230035cSSergey Ryazanov int mdmbits; 685c230035cSSergey Ryazanov 686c230035cSSergey Ryazanov if (copy_from_user(&mdmbits, (int __user *)arg, sizeof(int))) { 687c230035cSSergey Ryazanov ret = -EFAULT; 688c230035cSSergey Ryazanov break; 689c230035cSSergey Ryazanov } 690c230035cSSergey Ryazanov if (cmd == TIOCMBIC) 691c230035cSSergey Ryazanov port->at_data.mdmbits &= ~mdmbits; 692c230035cSSergey Ryazanov else if (cmd == TIOCMBIS) 693c230035cSSergey Ryazanov port->at_data.mdmbits |= mdmbits; 694c230035cSSergey Ryazanov else 695c230035cSSergey Ryazanov port->at_data.mdmbits = mdmbits; 696c230035cSSergey Ryazanov break; 697c230035cSSergey Ryazanov } 698c230035cSSergey Ryazanov 699c230035cSSergey Ryazanov default: 700c230035cSSergey Ryazanov ret = -ENOIOCTLCMD; 701c230035cSSergey Ryazanov } 702c230035cSSergey Ryazanov 703c230035cSSergey Ryazanov mutex_unlock(&port->data_lock); 704c230035cSSergey Ryazanov 705c230035cSSergey Ryazanov return ret; 706c230035cSSergey Ryazanov } 707c230035cSSergey Ryazanov 708e263c5b2SSergey Ryazanov static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd, 709e263c5b2SSergey Ryazanov unsigned long arg) 710e263c5b2SSergey Ryazanov { 711e263c5b2SSergey Ryazanov struct wwan_port *port = filp->private_data; 712c230035cSSergey Ryazanov int res; 713c230035cSSergey Ryazanov 714c230035cSSergey Ryazanov if (port->type == WWAN_PORT_AT) { /* AT port specific IOCTLs */ 715c230035cSSergey Ryazanov res = wwan_port_fops_at_ioctl(port, cmd, arg); 716c230035cSSergey Ryazanov if (res != -ENOIOCTLCMD) 717c230035cSSergey Ryazanov return res; 718c230035cSSergey Ryazanov } 719e263c5b2SSergey Ryazanov 720e263c5b2SSergey Ryazanov switch (cmd) { 721e263c5b2SSergey Ryazanov case TIOCINQ: { /* aka SIOCINQ aka FIONREAD */ 722e263c5b2SSergey Ryazanov unsigned long flags; 723e263c5b2SSergey Ryazanov struct sk_buff *skb; 724e263c5b2SSergey Ryazanov int amount = 0; 725e263c5b2SSergey Ryazanov 726e263c5b2SSergey Ryazanov spin_lock_irqsave(&port->rxq.lock, flags); 727e263c5b2SSergey Ryazanov skb_queue_walk(&port->rxq, skb) 728e263c5b2SSergey Ryazanov amount += skb->len; 729e263c5b2SSergey Ryazanov spin_unlock_irqrestore(&port->rxq.lock, flags); 730e263c5b2SSergey Ryazanov 731e263c5b2SSergey Ryazanov return put_user(amount, (int __user *)arg); 732e263c5b2SSergey Ryazanov } 733e263c5b2SSergey Ryazanov 734e263c5b2SSergey Ryazanov default: 735e263c5b2SSergey Ryazanov return -ENOIOCTLCMD; 736e263c5b2SSergey Ryazanov } 737e263c5b2SSergey Ryazanov } 738e263c5b2SSergey Ryazanov 7399a44c1ccSLoic Poulain static const struct file_operations wwan_port_fops = { 7409a44c1ccSLoic Poulain .owner = THIS_MODULE, 7419a44c1ccSLoic Poulain .open = wwan_port_fops_open, 7429a44c1ccSLoic Poulain .release = wwan_port_fops_release, 7439a44c1ccSLoic Poulain .read = wwan_port_fops_read, 7449a44c1ccSLoic Poulain .write = wwan_port_fops_write, 7459a44c1ccSLoic Poulain .poll = wwan_port_fops_poll, 746e263c5b2SSergey Ryazanov .unlocked_ioctl = wwan_port_fops_ioctl, 747e263c5b2SSergey Ryazanov #ifdef CONFIG_COMPAT 748e263c5b2SSergey Ryazanov .compat_ioctl = compat_ptr_ioctl, 749e263c5b2SSergey Ryazanov #endif 7509a44c1ccSLoic Poulain .llseek = noop_llseek, 7519a44c1ccSLoic Poulain }; 7529a44c1ccSLoic Poulain 7539a44c1ccSLoic Poulain static int __init wwan_init(void) 7549a44c1ccSLoic Poulain { 7559a44c1ccSLoic Poulain wwan_class = class_create(THIS_MODULE, "wwan"); 7569a44c1ccSLoic Poulain if (IS_ERR(wwan_class)) 7579a44c1ccSLoic Poulain return PTR_ERR(wwan_class); 7589a44c1ccSLoic Poulain 7599a44c1ccSLoic Poulain /* chrdev used for wwan ports */ 76072eedfc4SSergey Ryazanov wwan_major = __register_chrdev(0, 0, WWAN_MAX_MINORS, "wwan_port", 76172eedfc4SSergey Ryazanov &wwan_port_fops); 7629a44c1ccSLoic Poulain if (wwan_major < 0) { 7639a44c1ccSLoic Poulain class_destroy(wwan_class); 7649a44c1ccSLoic Poulain return wwan_major; 7659a44c1ccSLoic Poulain } 7669a44c1ccSLoic Poulain 7679a44c1ccSLoic Poulain return 0; 7689a44c1ccSLoic Poulain } 7699a44c1ccSLoic Poulain 7709a44c1ccSLoic Poulain static void __exit wwan_exit(void) 7719a44c1ccSLoic Poulain { 77272eedfc4SSergey Ryazanov __unregister_chrdev(wwan_major, 0, WWAN_MAX_MINORS, "wwan_port"); 7739a44c1ccSLoic Poulain class_destroy(wwan_class); 7749a44c1ccSLoic Poulain } 7759a44c1ccSLoic Poulain 7769a44c1ccSLoic Poulain module_init(wwan_init); 7779a44c1ccSLoic Poulain module_exit(wwan_exit); 7789a44c1ccSLoic Poulain 7799a44c1ccSLoic Poulain MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>"); 7809a44c1ccSLoic Poulain MODULE_DESCRIPTION("WWAN core"); 7819a44c1ccSLoic Poulain MODULE_LICENSE("GPL v2"); 782