diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 410b0245114e..88df55d78d90 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -7,6 +7,7 @@ menu "Wireless WAN" config WWAN tristate "WWAN Driver Core" + depends on GNSS || GNSS = n help Say Y here if you want to use the WWAN driver core. This driver provides a common framework for WWAN drivers. diff --git a/drivers/net/wwan/mhi_wwan_ctrl.c b/drivers/net/wwan/mhi_wwan_ctrl.c index e9f979d2d851..e13c0b078175 100644 --- a/drivers/net/wwan/mhi_wwan_ctrl.c +++ b/drivers/net/wwan/mhi_wwan_ctrl.c @@ -263,6 +263,7 @@ static const struct mhi_device_id mhi_wwan_ctrl_match_table[] = { { .chan = "QMI", .driver_data = WWAN_PORT_QMI }, { .chan = "DIAG", .driver_data = WWAN_PORT_QCDM }, { .chan = "FIREHOSE", .driver_data = WWAN_PORT_FIREHOSE }, + { .chan = "NMEA", .driver_data = WWAN_PORT_NMEA }, {}, }; MODULE_DEVICE_TABLE(mhi, mhi_wwan_ctrl_match_table); diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index 63a47d420bc5..015213b3d687 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -1,5 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2021, Linaro Ltd */ +/* WWAN Driver Core + * + * Copyright (c) 2021, Linaro Ltd + * Copyright (c) 2025, Sergey Ryazanov + */ #include #include @@ -16,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -42,16 +47,18 @@ static struct dentry *wwan_debugfs_dir; * struct wwan_device - The structure that defines a WWAN device * * @id: WWAN device unique ID. + * @refcount: Reference count of this WWAN device. When this refcount reaches + * zero, the device is deleted. NB: access is protected by global + * wwan_register_lock mutex. * @dev: Underlying device. - * @port_id: Current available port ID to pick. * @ops: wwan device ops * @ops_ctxt: context to pass to ops * @debugfs_dir: WWAN device debugfs dir */ struct wwan_device { unsigned int id; + int refcount; struct device dev; - atomic_t port_id; const struct wwan_ops *ops; void *ops_ctxt; #ifdef CONFIG_WWAN_DEBUGFS @@ -73,6 +80,7 @@ struct wwan_device { * @headroom_len: SKB reserved headroom size * @frag_len: Length to fragment packet * @at_data: AT port specific data + * @gnss: Pointer to GNSS device associated with this port */ struct wwan_port { enum wwan_port_type type; @@ -91,9 +99,16 @@ struct wwan_port { struct ktermios termios; int mdmbits; } at_data; + struct gnss_device *gnss; }; }; +static int wwan_port_op_start(struct wwan_port *port); +static void wwan_port_op_stop(struct wwan_port *port); +static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb, + bool nonblock); +static int wwan_wait_tx(struct wwan_port *port, bool nonblock); + static ssize_t index_show(struct device *dev, struct device_attribute *attr, char *buf) { struct wwan_device *wwan = to_wwan_dev(dev); @@ -224,8 +239,10 @@ static struct wwan_device *wwan_create_dev(struct device *parent) /* If wwandev already exists, return it */ wwandev = wwan_dev_get_by_parent(parent); - if (!IS_ERR(wwandev)) + if (!IS_ERR(wwandev)) { + wwandev->refcount++; goto done_unlock; + } id = ida_alloc(&wwan_dev_ids, GFP_KERNEL); if (id < 0) { @@ -244,6 +261,7 @@ static struct wwan_device *wwan_create_dev(struct device *parent) wwandev->dev.class = &wwan_class; wwandev->dev.type = &wwan_dev_type; wwandev->id = id; + wwandev->refcount = 1; dev_set_name(&wwandev->dev, "wwan%d", wwandev->id); err = device_register(&wwandev->dev); @@ -265,30 +283,18 @@ static struct wwan_device *wwan_create_dev(struct device *parent) 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 last port, and reference simply dropped (put) otherwise. In the - * same fashion, we must not unregister it when the ops are still there. - */ - if (wwandev->ops) - ret = 1; - else - ret = device_for_each_child(&wwandev->dev, NULL, is_wwan_child); + if (--wwandev->refcount <= 0) { + struct device *child = device_find_any_child(&wwandev->dev); + + put_device(child); + if (WARN_ON(wwandev->ops || child)) /* Paranoid */ + goto out_unlock; - if (!ret) { #ifdef CONFIG_WWAN_DEBUGFS debugfs_remove_recursive(wwandev->debugfs_dir); #endif @@ -297,6 +303,7 @@ static void wwan_remove_dev(struct wwan_device *wwandev) put_device(&wwandev->dev); } +out_unlock: mutex_unlock(&wwan_register_lock); } @@ -342,6 +349,7 @@ static const struct { .name = "MIPC", .devsuf = "mipc", }, + /* WWAN_PORT_NMEA is exported via the GNSS subsystem */ }; static ssize_t type_show(struct device *dev, struct device_attribute *attr, @@ -363,7 +371,8 @@ static void wwan_port_destroy(struct device *dev) { struct wwan_port *port = to_wwan_port(dev); - ida_free(&minors, MINOR(port->dev.devt)); + if (dev->class == &wwan_class) + ida_free(&minors, MINOR(dev->devt)); mutex_destroy(&port->data_lock); mutex_destroy(&port->ops_lock); kfree(port); @@ -442,6 +451,174 @@ static int __wwan_port_dev_assign_name(struct wwan_port *port, const char *fmt) return dev_set_name(&port->dev, "%s", buf); } +/* Register a regular WWAN port device (e.g. AT, MBIM, etc.) */ +static int wwan_port_register_wwan(struct wwan_port *port) +{ + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); + char namefmt[0x20]; + int minor, err; + + /* 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) + return minor; + + port->dev.class = &wwan_class; + port->dev.devt = MKDEV(wwan_major, minor); + + /* allocate unique name based on wwan device id, port type and number */ + snprintf(namefmt, sizeof(namefmt), "wwan%u%s%%d", wwandev->id, + wwan_port_types[port->type].devsuf); + + /* Serialize ports registration */ + mutex_lock(&wwan_register_lock); + + __wwan_port_dev_assign_name(port, namefmt); + err = device_add(&port->dev); + + mutex_unlock(&wwan_register_lock); + + if (err) { + ida_free(&minors, minor); + port->dev.class = NULL; + return err; + } + + dev_info(&wwandev->dev, "port %s attached\n", dev_name(&port->dev)); + + return 0; +} + +/* Unregister a regular WWAN port (e.g. AT, MBIM, etc) */ +static void wwan_port_unregister_wwan(struct wwan_port *port) +{ + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); + + dev_set_drvdata(&port->dev, NULL); + + dev_info(&wwandev->dev, "port %s disconnected\n", dev_name(&port->dev)); + + device_del(&port->dev); +} + +#if IS_ENABLED(CONFIG_GNSS) +static int wwan_gnss_open(struct gnss_device *gdev) +{ + return wwan_port_op_start(gnss_get_drvdata(gdev)); +} + +static void wwan_gnss_close(struct gnss_device *gdev) +{ + wwan_port_op_stop(gnss_get_drvdata(gdev)); +} + +static int wwan_gnss_write(struct gnss_device *gdev, const unsigned char *buf, + size_t count) +{ + struct wwan_port *port = gnss_get_drvdata(gdev); + struct sk_buff *skb, *head = NULL, *tail = NULL; + size_t frag_len, remain = count; + int ret; + + ret = wwan_wait_tx(port, false); + if (ret) + return ret; + + do { + frag_len = min(remain, port->frag_len); + skb = alloc_skb(frag_len + port->headroom_len, GFP_KERNEL); + if (!skb) { + ret = -ENOMEM; + goto freeskb; + } + skb_reserve(skb, port->headroom_len); + memcpy(skb_put(skb, frag_len), buf + count - remain, frag_len); + + if (!head) { + head = skb; + } else { + if (!tail) + skb_shinfo(head)->frag_list = skb; + else + tail->next = skb; + + tail = skb; + head->data_len += skb->len; + head->len += skb->len; + head->truesize += skb->truesize; + } + } while (remain -= frag_len); + + ret = wwan_port_op_tx(port, head, false); + if (!ret) + return count; + +freeskb: + kfree_skb(head); + return ret; +} + +static struct gnss_operations wwan_gnss_ops = { + .open = wwan_gnss_open, + .close = wwan_gnss_close, + .write_raw = wwan_gnss_write, +}; + +/* GNSS port specific device registration */ +static int wwan_port_register_gnss(struct wwan_port *port) +{ + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); + struct gnss_device *gdev; + int err; + + gdev = gnss_allocate_device(&wwandev->dev); + if (!gdev) + return -ENOMEM; + + /* NB: for now we support only NMEA WWAN port type, so hardcode + * the GNSS port type. If more GNSS WWAN port types will be added, + * then we should dynamically map WWAN port type to GNSS type. + */ + gdev->type = GNSS_TYPE_NMEA; + gdev->ops = &wwan_gnss_ops; + gnss_set_drvdata(gdev, port); + + port->gnss = gdev; + + err = gnss_register_device(gdev); + if (err) { + gnss_put_device(gdev); + return err; + } + + dev_info(&wwandev->dev, "port %s attached\n", dev_name(&gdev->dev)); + + return 0; +} + +/* GNSS port specific device unregistration */ +static void wwan_port_unregister_gnss(struct wwan_port *port) +{ + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); + struct gnss_device *gdev = port->gnss; + + dev_info(&wwandev->dev, "port %s disconnected\n", dev_name(&gdev->dev)); + + gnss_deregister_device(gdev); + gnss_put_device(gdev); +} +#else +static int wwan_port_register_gnss(struct wwan_port *port) +{ + return -EOPNOTSUPP; +} + +static void wwan_port_unregister_gnss(struct wwan_port *port) +{ + WARN_ON(1); /* This handler cannot be called */ +} +#endif + struct wwan_port *wwan_create_port(struct device *parent, enum wwan_port_type type, const struct wwan_port_ops *ops, @@ -450,8 +627,7 @@ struct wwan_port *wwan_create_port(struct device *parent, { struct wwan_device *wwandev; struct wwan_port *port; - char namefmt[0x20]; - int minor, err; + int err; if (type > WWAN_PORT_MAX || !ops) return ERR_PTR(-EINVAL); @@ -463,17 +639,9 @@ struct wwan_port *wwan_create_port(struct device *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) { - err = minor; - goto error_wwandev_remove; - } - port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) { err = -ENOMEM; - ida_free(&minors, minor); goto error_wwandev_remove; } @@ -487,27 +655,18 @@ struct wwan_port *wwan_create_port(struct device *parent, mutex_init(&port->data_lock); 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); + device_initialize(&port->dev); - /* allocate unique name based on wwan device id, port type and number */ - snprintf(namefmt, sizeof(namefmt), "wwan%u%s%%d", wwandev->id, - wwan_port_types[port->type].devsuf); - - /* Serialize ports registration */ - mutex_lock(&wwan_register_lock); - - __wwan_port_dev_assign_name(port, namefmt); - err = device_register(&port->dev); - - mutex_unlock(&wwan_register_lock); + if (port->type == WWAN_PORT_NMEA) + err = wwan_port_register_gnss(port); + else + err = wwan_port_register_wwan(port); if (err) goto error_put_device; - dev_info(&wwandev->dev, "port %s attached\n", dev_name(&port->dev)); return port; error_put_device: @@ -524,18 +683,22 @@ 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) + if (port->start_count) { port->ops->stop(port); + port->start_count = 0; + } 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); - dev_info(&wwandev->dev, "port %s disconnected\n", dev_name(&port->dev)); - device_unregister(&port->dev); + if (port->type == WWAN_PORT_NMEA) + wwan_port_unregister_gnss(port); + else + wwan_port_unregister_wwan(port); + + put_device(&port->dev); /* Release related wwan device */ wwan_remove_dev(wwandev); @@ -544,8 +707,15 @@ 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); + if (port->type == WWAN_PORT_NMEA) { +#if IS_ENABLED(CONFIG_GNSS) + gnss_insert_raw(port->gnss, skb->data, skb->len); +#endif + consume_skb(skb); + } else { + skb_queue_tail(&port->rxq, skb); + wake_up_interruptible(&port->waitqueue); + } } EXPORT_SYMBOL_GPL(wwan_port_rx); diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c index 733688cd4607..8541bd58e831 100644 --- a/drivers/net/wwan/wwan_hwsim.c +++ b/drivers/net/wwan/wwan_hwsim.c @@ -2,7 +2,7 @@ /* * WWAN device simulator for WWAN framework testing. * - * Copyright (c) 2021, Sergey Ryazanov + * Copyright (c) 2021, 2025, Sergey Ryazanov */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -56,12 +58,19 @@ struct wwan_hwsim_port { struct wwan_port *wwan; struct work_struct del_work; struct dentry *debugfs_topdir; - enum { /* AT command parser state */ - AT_PARSER_WAIT_A, - AT_PARSER_WAIT_T, - AT_PARSER_WAIT_TERM, - AT_PARSER_SKIP_LINE, - } pstate; + union { + struct { + enum { /* AT command parser state */ + AT_PARSER_WAIT_A, + AT_PARSER_WAIT_T, + AT_PARSER_WAIT_TERM, + AT_PARSER_SKIP_LINE, + } pstate; + } at_emul; + struct { + struct timer_list timer; + } nmea_emul; + }; }; static const struct file_operations wwan_hwsim_debugfs_portdestroy_fops; @@ -101,16 +110,16 @@ static const struct wwan_ops wwan_hwsim_wwan_rtnl_ops = { .setup = wwan_hwsim_netdev_setup, }; -static int wwan_hwsim_port_start(struct wwan_port *wport) +static int wwan_hwsim_at_emul_start(struct wwan_port *wport) { struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport); - port->pstate = AT_PARSER_WAIT_A; + port->at_emul.pstate = AT_PARSER_WAIT_A; return 0; } -static void wwan_hwsim_port_stop(struct wwan_port *wport) +static void wwan_hwsim_at_emul_stop(struct wwan_port *wport) { } @@ -120,7 +129,7 @@ static void wwan_hwsim_port_stop(struct wwan_port *wport) * * Be aware that this processor is not fully V.250 compliant. */ -static int wwan_hwsim_port_tx(struct wwan_port *wport, struct sk_buff *in) +static int wwan_hwsim_at_emul_tx(struct wwan_port *wport, struct sk_buff *in) { struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport); struct sk_buff *out; @@ -142,17 +151,17 @@ static int wwan_hwsim_port_tx(struct wwan_port *wport, struct sk_buff *in) for (i = 0, s = 0; i < in->len; ++i) { char c = in->data[i]; - if (port->pstate == AT_PARSER_WAIT_A) { + if (port->at_emul.pstate == AT_PARSER_WAIT_A) { if (c == 'A' || c == 'a') - port->pstate = AT_PARSER_WAIT_T; + port->at_emul.pstate = AT_PARSER_WAIT_T; else if (c != '\n') /* Ignore formating char */ - port->pstate = AT_PARSER_SKIP_LINE; - } else if (port->pstate == AT_PARSER_WAIT_T) { + port->at_emul.pstate = AT_PARSER_SKIP_LINE; + } else if (port->at_emul.pstate == AT_PARSER_WAIT_T) { if (c == 'T' || c == 't') - port->pstate = AT_PARSER_WAIT_TERM; + port->at_emul.pstate = AT_PARSER_WAIT_TERM; else - port->pstate = AT_PARSER_SKIP_LINE; - } else if (port->pstate == AT_PARSER_WAIT_TERM) { + port->at_emul.pstate = AT_PARSER_SKIP_LINE; + } else if (port->at_emul.pstate == AT_PARSER_WAIT_TERM) { if (c != '\r') continue; /* Consume the trailing formatting char as well */ @@ -162,11 +171,11 @@ static int wwan_hwsim_port_tx(struct wwan_port *wport, struct sk_buff *in) skb_put_data(out, &in->data[s], n);/* Echo */ skb_put_data(out, "\r\nOK\r\n", 6); s = i + 1; - port->pstate = AT_PARSER_WAIT_A; - } else if (port->pstate == AT_PARSER_SKIP_LINE) { + port->at_emul.pstate = AT_PARSER_WAIT_A; + } else if (port->at_emul.pstate == AT_PARSER_SKIP_LINE) { if (c != '\r') continue; - port->pstate = AT_PARSER_WAIT_A; + port->at_emul.pstate = AT_PARSER_WAIT_A; } } @@ -183,18 +192,131 @@ static int wwan_hwsim_port_tx(struct wwan_port *wport, struct sk_buff *in) return 0; } -static const struct wwan_port_ops wwan_hwsim_port_ops = { - .start = wwan_hwsim_port_start, - .stop = wwan_hwsim_port_stop, - .tx = wwan_hwsim_port_tx, +static const struct wwan_port_ops wwan_hwsim_at_emul_port_ops = { + .start = wwan_hwsim_at_emul_start, + .stop = wwan_hwsim_at_emul_stop, + .tx = wwan_hwsim_at_emul_tx, }; -static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev) +#if IS_ENABLED(CONFIG_GNSS) +#define NMEA_MAX_LEN 82 /* Max sentence length */ +#define NMEA_TRAIL_LEN 5 /* '*' + Checksum + */ +#define NMEA_MAX_DATA_LEN (NMEA_MAX_LEN - NMEA_TRAIL_LEN) + +static __printf(2, 3) +void wwan_hwsim_nmea_skb_push_sentence(struct sk_buff *skb, + const char *fmt, ...) { + unsigned char *s, *p; + va_list ap; + u8 cs = 0; + int len; + + s = skb_put(skb, NMEA_MAX_LEN + 1); /* +'\0' */ + if (!s) + return; + + va_start(ap, fmt); + len = vsnprintf(s, NMEA_MAX_DATA_LEN + 1, fmt, ap); + va_end(ap); + if (WARN_ON_ONCE(len > NMEA_MAX_DATA_LEN))/* No space for trailer */ + return; + + for (p = s + 1; *p != '\0'; ++p)/* Skip leading '$' or '!' */ + cs ^= *p; + p += snprintf(p, 5 + 1, "*%02X\r\n", cs); + + len = (p - s) - (NMEA_MAX_LEN + 1); /* exp. vs real length diff */ + skb->tail += len; /* Adjust tail to real length */ + skb->len += len; +} + +static void wwan_hwsim_nmea_emul_timer(struct timer_list *t) +{ + /* 43.74754722298909 N 11.25759835922875 E in DMM format */ + static const unsigned int coord[4 * 2] = { 43, 44, 8528, 0, + 11, 15, 4559, 0 }; + struct wwan_hwsim_port *port = timer_container_of(port, t, nmea_emul.timer); + struct sk_buff *skb; + struct tm tm; + + time64_to_tm(ktime_get_real_seconds(), 0, &tm); + + mod_timer(&port->nmea_emul.timer, jiffies + HZ); /* 1 second */ + + skb = alloc_skb(NMEA_MAX_LEN * 2 + 2, GFP_ATOMIC); /* GGA + RMC */ + if (!skb) + return; + + wwan_hwsim_nmea_skb_push_sentence(skb, + "$GPGGA,%02u%02u%02u.000,%02u%02u.%04u,%c,%03u%02u.%04u,%c,1,7,1.03,176.2,M,55.2,M,,", + tm.tm_hour, tm.tm_min, tm.tm_sec, + coord[0], coord[1], coord[2], + coord[3] ? 'S' : 'N', + coord[4], coord[5], coord[6], + coord[7] ? 'W' : 'E'); + + wwan_hwsim_nmea_skb_push_sentence(skb, + "$GPRMC,%02u%02u%02u.000,A,%02u%02u.%04u,%c,%03u%02u.%04u,%c,0.02,31.66,%02u%02u%02u,,,A", + tm.tm_hour, tm.tm_min, tm.tm_sec, + coord[0], coord[1], coord[2], + coord[3] ? 'S' : 'N', + coord[4], coord[5], coord[6], + coord[7] ? 'W' : 'E', + tm.tm_mday, tm.tm_mon + 1, + (unsigned int)tm.tm_year - 100); + + wwan_port_rx(port->wwan, skb); +} + +static int wwan_hwsim_nmea_emul_start(struct wwan_port *wport) +{ + struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport); + + timer_setup(&port->nmea_emul.timer, wwan_hwsim_nmea_emul_timer, 0); + wwan_hwsim_nmea_emul_timer(&port->nmea_emul.timer); + + return 0; +} + +static void wwan_hwsim_nmea_emul_stop(struct wwan_port *wport) +{ + struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport); + + timer_delete_sync(&port->nmea_emul.timer); +} + +static int wwan_hwsim_nmea_emul_tx(struct wwan_port *wport, struct sk_buff *in) +{ + consume_skb(in); + + return 0; +} + +static const struct wwan_port_ops wwan_hwsim_nmea_emul_port_ops = { + .start = wwan_hwsim_nmea_emul_start, + .stop = wwan_hwsim_nmea_emul_stop, + .tx = wwan_hwsim_nmea_emul_tx, +}; +#endif + +static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev, + enum wwan_port_type type) +{ + const struct wwan_port_ops *ops; struct wwan_hwsim_port *port; char name[0x10]; int err; + if (type == WWAN_PORT_AT) + ops = &wwan_hwsim_at_emul_port_ops; +#if IS_ENABLED(CONFIG_GNSS) + else if (type == WWAN_PORT_NMEA) + ops = &wwan_hwsim_nmea_emul_port_ops; +#endif + else + return ERR_PTR(-EINVAL); + port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) return ERR_PTR(-ENOMEM); @@ -205,9 +327,7 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev) port->id = dev->port_idx++; spin_unlock(&dev->ports_lock); - port->wwan = wwan_create_port(&dev->dev, WWAN_PORT_AT, - &wwan_hwsim_port_ops, - NULL, port); + port->wwan = wwan_create_port(&dev->dev, type, ops, NULL, port); if (IS_ERR(port->wwan)) { err = PTR_ERR(port->wwan); goto err_free_port; @@ -392,7 +512,7 @@ static ssize_t wwan_hwsim_debugfs_portcreate_write(struct file *file, struct wwan_hwsim_dev *dev = file->private_data; struct wwan_hwsim_port *port; - port = wwan_hwsim_port_new(dev); + port = wwan_hwsim_port_new(dev, WWAN_PORT_AT); if (IS_ERR(port)) return PTR_ERR(port); @@ -459,6 +579,8 @@ static int __init wwan_hwsim_init_devs(void) int i, j; for (i = 0; i < wwan_hwsim_devsnum; ++i) { + struct wwan_hwsim_port *port; + dev = wwan_hwsim_dev_new(); if (IS_ERR(dev)) return PTR_ERR(dev); @@ -467,13 +589,12 @@ static int __init wwan_hwsim_init_devs(void) list_add_tail(&dev->list, &wwan_hwsim_devs); spin_unlock(&wwan_hwsim_devs_lock); - /* Create a couple of ports per each device to accelerate + /* Create a few various ports per each device to accelerate * the simulator readiness time. */ - for (j = 0; j < 2; ++j) { - struct wwan_hwsim_port *port; - port = wwan_hwsim_port_new(dev); + for (j = 0; j < 2; ++j) { + port = wwan_hwsim_port_new(dev, WWAN_PORT_AT); if (IS_ERR(port)) return PTR_ERR(port); @@ -481,6 +602,18 @@ static int __init wwan_hwsim_init_devs(void) list_add_tail(&port->list, &dev->ports); spin_unlock(&dev->ports_lock); } + +#if IS_ENABLED(CONFIG_GNSS) + port = wwan_hwsim_port_new(dev, WWAN_PORT_NMEA); + if (IS_ERR(port)) { + dev_warn(&dev->dev, "failed to create initial NMEA port: %d\n", + (int)PTR_ERR(port)); + } else { + spin_lock(&dev->ports_lock); + list_add_tail(&port->list, &dev->ports); + spin_unlock(&dev->ports_lock); + } +#endif } return 0; diff --git a/include/linux/wwan.h b/include/linux/wwan.h index a4d6cc0c9f68..1e0e2cb53579 100644 --- a/include/linux/wwan.h +++ b/include/linux/wwan.h @@ -19,6 +19,7 @@ * @WWAN_PORT_FASTBOOT: Fastboot protocol control * @WWAN_PORT_ADB: ADB protocol control * @WWAN_PORT_MIPC: MTK MIPC diagnostic interface + * @WWAN_PORT_NMEA: embedded GNSS receiver with NMEA output * * @WWAN_PORT_MAX: Highest supported port types * @WWAN_PORT_UNKNOWN: Special value to indicate an unknown port type @@ -34,6 +35,7 @@ enum wwan_port_type { WWAN_PORT_FASTBOOT, WWAN_PORT_ADB, WWAN_PORT_MIPC, + WWAN_PORT_NMEA, /* Add new port types above this line */