diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-01-31 05:27:02 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-01-31 05:27:03 +0300 |
| commit | eec90ea4ef946d40d82286878477d8becb7cb450 (patch) | |
| tree | 84d7629b5bc97263768e1075dde33f5faab65bdd | |
| parent | db2733d4c435e09832b5d664b4472d012868886f (diff) | |
| parent | 209111d694ddd7d04961e43a3abeb626cc587b16 (diff) | |
| download | linux-eec90ea4ef946d40d82286878477d8becb7cb450.tar.xz | |
Merge branch 'net-wwan-add-nmea-port-type-support'
Slark Xiao says:
====================
net: wwan: add NMEA port type support
The series introduces a long discussed NMEA port type support for the
WWAN subsystem. There are two goals. From the WWAN driver perspective,
NMEA exported as any other port type (e.g. AT, MBIM, QMI, etc.). From
user space software perspective, the exported chardev belongs to the
GNSS class what makes it easy to distinguish desired port and the WWAN
device common to both NMEA and control (AT, MBIM, etc.) ports makes it
easy to locate a control port for the GNSS receiver activation.
Done by exporting the NMEA port via the GNSS subsystem with the WWAN
core acting as proxy between the WWAN modem driver and the GNSS
subsystem.
The series starts from a cleanup patch. Then three patches prepares the
WWAN core for the proxy style operation. Followed by a patch introding a
new WWNA port type, integration with the GNSS subsystem and demux. The
series ends with a couple of patches that introduce emulated EMEA port
to the WWAN HW simulator.
The series is the product of the discussion with Loic about the pros and
cons of possible models and implementation. Also Muhammad and Slark did
a great job defining the problem, sharing the code and pushing me to
finish the implementation. Daniele has caught an issue on driver
unloading and suggested an investigation direction. What was concluded
by Loic. Many thanks.
====================
Link: https://patch.msgid.link/20260126062158.308598-1-slark_xiao@163.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | drivers/net/wwan/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/net/wwan/mhi_wwan_ctrl.c | 1 | ||||
| -rw-r--r-- | drivers/net/wwan/wwan_core.c | 278 | ||||
| -rw-r--r-- | drivers/net/wwan/wwan_hwsim.c | 201 | ||||
| -rw-r--r-- | include/linux/wwan.h | 2 |
5 files changed, 395 insertions, 88 deletions
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 <loic.poulain@linaro.org> */ +/* WWAN Driver Core + * + * Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> + * Copyright (c) 2025, Sergey Ryazanov <ryazanov.s.a@gmail.com> + */ #include <linux/bitmap.h> #include <linux/err.h> @@ -16,6 +20,7 @@ #include <linux/types.h> #include <linux/uaccess.h> #include <linux/termios.h> +#include <linux/gnss.h> #include <linux/wwan.h> #include <net/rtnetlink.h> #include <uapi/linux/wwan.h> @@ -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 @@ done_unlock: 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 <ryazanov.s.a@gmail.com> + * Copyright (c) 2021, 2025, Sergey Ryazanov <ryazanov.s.a@gmail.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -12,8 +12,10 @@ #include <linux/slab.h> #include <linux/device.h> #include <linux/spinlock.h> +#include <linux/time.h> #include <linux/list.h> #include <linux/skbuff.h> +#include <linux/timer.h> #include <linux/netdevice.h> #include <linux/wwan.h> #include <linux/debugfs.h> @@ -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 + <CR><LF> */ +#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 */ |
