[net-next v7 7/8] net: wwan: hwsim: support NMEA port emulation

Slark Xiao posted 8 patches 3 weeks, 4 days ago
There is a newer version of this series
[net-next v7 7/8] net: wwan: hwsim: support NMEA port emulation
Posted by Slark Xiao 3 weeks, 4 days ago
From: Sergey Ryazanov <ryazanov.s.a@gmail.com>

Support NMEA port emulation for the WWAN core GNSS port testing purpose.
Emulator produces pair of GGA + RMC sentences every second what should
be enough to fool gpsd into believing it is working with a NMEA GNSS
receiver.

If the GNSS system is enabled then one NMEA port will be created
automatically for the simulated WWAN device. Manual NMEA port creation
is not supported at the moment.

Signed-off-by: Sergey Ryazanov <ryazanov.s.a@gmail.com>
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 drivers/net/wwan/wwan_hwsim.c | 128 +++++++++++++++++++++++++++++++++-
 1 file changed, 126 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
index 11d15dc39041..e4b1bbff9af2 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>
@@ -65,6 +67,9 @@ struct wwan_hwsim_port {
 				AT_PARSER_SKIP_LINE,
 			} pstate;
 		} at_emul;
+		struct {
+			struct timer_list timer;
+		} nmea_emul;
 	};
 };
 
@@ -193,6 +198,108 @@ static const struct wwan_port_ops wwan_hwsim_at_emul_port_ops = {
 	.tx = wwan_hwsim_at_emul_tx,
 };
 
+#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, GFP_KERNEL);	/* 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)
 {
@@ -203,6 +310,10 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev,
 
 	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);
 
@@ -478,9 +589,10 @@ 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) {
 			port = wwan_hwsim_port_new(dev, WWAN_PORT_AT);
 			if (IS_ERR(port))
@@ -490,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;
-- 
2.25.1
Re: [net-next,v7,7/8] net: wwan: hwsim: support NMEA port emulation
Posted by Jakub Kicinski 3 weeks, 4 days ago
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: wwan: hwsim: support NMEA port emulation

This commit adds NMEA port emulation to the WWAN hardware simulator for
GNSS port testing. The emulator generates GGA and RMC sentences every
second via a timer callback.

> diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
> index 11d15dc39041..e4b1bbff9af2 100644
> --- a/drivers/net/wwan/wwan_hwsim.c
> +++ b/drivers/net/wwan/wwan_hwsim.c

[ ... ]

> +#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' */

[ ... ]

> +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, GFP_KERNEL);	/* GGA + RMC */
                       ^^^^^^^^^^^^^^     ^^^^^^^^^^

Two questions about this allocation:

1. The buffer size appears to be insufficient. The allocation requests
NMEA_MAX_LEN * 2 = 164 bytes, but wwan_hwsim_nmea_skb_push_sentence()
is called twice and each call uses skb_put(skb, NMEA_MAX_LEN + 1) which
reserves 83 bytes per sentence. The total needed is 166 bytes, which
exceeds the 164 bytes allocated. Would this cause skb_over_panic() to
trigger on the second skb_put() call?

2. This function is a timer callback registered via timer_setup() in
wwan_hwsim_nmea_emul_start(). Timer callbacks run in softirq context
where sleeping is not permitted. GFP_KERNEL allocations can sleep when
memory pressure triggers reclaim. Should this be GFP_ATOMIC instead to
avoid "scheduling while atomic" warnings?

> +	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);
> +}

[ ... ]
Re:Re: [net-next,v7,7/8] net: wwan: hwsim: support NMEA port emulation
Posted by Slark Xiao 2 weeks, 5 days ago
At 2026-01-16 10:44:41, "Jakub Kicinski" <kuba@kernel.org> wrote:
>This is an AI-generated review of your patch. The human sending this
>email has considered the AI review valid, or at least plausible.
>
>For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
>---
>net: wwan: hwsim: support NMEA port emulation
>
>This commit adds NMEA port emulation to the WWAN hardware simulator for
>GNSS port testing. The emulator generates GGA and RMC sentences every
>second via a timer callback.
>
>> diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
>> index 11d15dc39041..e4b1bbff9af2 100644
>> --- a/drivers/net/wwan/wwan_hwsim.c
>> +++ b/drivers/net/wwan/wwan_hwsim.c
>
>[ ... ]
>
>> +#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' */
>
>[ ... ]
>
>> +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, GFP_KERNEL);	/* GGA + RMC */
>                       ^^^^^^^^^^^^^^     ^^^^^^^^^^
>
Hi Sergey, Loic,
Let's update it as :
+	skb = alloc_skb(NMEA_MAX_LEN * 2+2, GFP_ATOMIC);	/* GGA + RMC */

How do you think?


>Two questions about this allocation:
>
>1. The buffer size appears to be insufficient. The allocation requests
>NMEA_MAX_LEN * 2 = 164 bytes, but wwan_hwsim_nmea_skb_push_sentence()
>is called twice and each call uses skb_put(skb, NMEA_MAX_LEN + 1) which
>reserves 83 bytes per sentence. The total needed is 166 bytes, which
>exceeds the 164 bytes allocated. Would this cause skb_over_panic() to
>trigger on the second skb_put() call?
>
>2. This function is a timer callback registered via timer_setup() in
>wwan_hwsim_nmea_emul_start(). Timer callbacks run in softirq context
>where sleeping is not permitted. GFP_KERNEL allocations can sleep when
>memory pressure triggers reclaim. Should this be GFP_ATOMIC instead to
>avoid "scheduling while atomic" warnings?
>
>> +	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);
>> +}
>
>[ ... ]