Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: wwan: add NMEA port support

Many WWAN modems come with embedded GNSS receiver inside and have a
dedicated port to output geopositioning data. On the one hand, the
GNSS receiver has little in common with WWAN modem and just shares a
host interface and should be exported using the GNSS subsystem. On the
other hand, GNSS receiver is not automatically activated and needs a
generic WWAN control port (AT, MBIM, etc.) to be turned on. And a user
space software needs extra information to find the control port.

Introduce the new type of WWAN port - NMEA. When driver asks to register
a NMEA port, the core allocates common parent WWAN device as usual, but
exports the NMEA port via the GNSS subsystem and acts as a proxy between
the device driver and the GNSS subsystem.

From the WWAN device driver perspective, a NMEA port is registered as a
regular WWAN port without any difference. And the driver interacts only
with the WWAN core. From the user space perspective, the NMEA port is a
GNSS device which parent can be used to enumerate and select the proper
control port for the GNSS receiver management.

CC: Slark Xiao <slark_xiao@163.com>
CC: Muhammad Nuzaihan <zaihan@unrealasia.net>
CC: Qiang Yu <quic_qianyu@quicinc.com>
CC: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
CC: Johan Hovold <johan@kernel.org>
Suggested-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
Signed-off-by: Sergey Ryazanov <ryazanov.s.a@gmail.com>
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
Link: https://patch.msgid.link/20260126062158.308598-6-slark_xiao@163.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Sergey Ryazanov and committed by
Jakub Kicinski
5b2e294e 0868ea34

+154 -5
+1
drivers/net/wwan/Kconfig
··· 7 7 8 8 config WWAN 9 9 tristate "WWAN Driver Core" 10 + depends on GNSS || GNSS = n 10 11 help 11 12 Say Y here if you want to use the WWAN driver core. This driver 12 13 provides a common framework for WWAN drivers.
+151 -5
drivers/net/wwan/wwan_core.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0-only 2 - /* Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> */ 2 + /* WWAN Driver Core 3 + * 4 + * Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> 5 + * Copyright (c) 2025, Sergey Ryazanov <ryazanov.s.a@gmail.com> 6 + */ 3 7 4 8 #include <linux/bitmap.h> 5 9 #include <linux/err.h> ··· 20 16 #include <linux/types.h> 21 17 #include <linux/uaccess.h> 22 18 #include <linux/termios.h> 19 + #include <linux/gnss.h> 23 20 #include <linux/wwan.h> 24 21 #include <net/rtnetlink.h> 25 22 #include <uapi/linux/wwan.h> ··· 80 75 * @headroom_len: SKB reserved headroom size 81 76 * @frag_len: Length to fragment packet 82 77 * @at_data: AT port specific data 78 + * @gnss: Pointer to GNSS device associated with this port 83 79 */ 84 80 struct wwan_port { 85 81 enum wwan_port_type type; ··· 99 93 struct ktermios termios; 100 94 int mdmbits; 101 95 } at_data; 96 + struct gnss_device *gnss; 102 97 }; 103 98 }; 99 + 100 + static int wwan_port_op_start(struct wwan_port *port); 101 + static void wwan_port_op_stop(struct wwan_port *port); 102 + static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb, 103 + bool nonblock); 104 + static int wwan_wait_tx(struct wwan_port *port, bool nonblock); 104 105 105 106 static ssize_t index_show(struct device *dev, struct device_attribute *attr, char *buf) 106 107 { ··· 349 336 .name = "MIPC", 350 337 .devsuf = "mipc", 351 338 }, 339 + /* WWAN_PORT_NMEA is exported via the GNSS subsystem */ 352 340 }; 353 341 354 342 static ssize_t type_show(struct device *dev, struct device_attribute *attr, ··· 501 487 device_del(&port->dev); 502 488 } 503 489 490 + #if IS_ENABLED(CONFIG_GNSS) 491 + static int wwan_gnss_open(struct gnss_device *gdev) 492 + { 493 + return wwan_port_op_start(gnss_get_drvdata(gdev)); 494 + } 495 + 496 + static void wwan_gnss_close(struct gnss_device *gdev) 497 + { 498 + wwan_port_op_stop(gnss_get_drvdata(gdev)); 499 + } 500 + 501 + static int wwan_gnss_write(struct gnss_device *gdev, const unsigned char *buf, 502 + size_t count) 503 + { 504 + struct wwan_port *port = gnss_get_drvdata(gdev); 505 + struct sk_buff *skb, *head = NULL, *tail = NULL; 506 + size_t frag_len, remain = count; 507 + int ret; 508 + 509 + ret = wwan_wait_tx(port, false); 510 + if (ret) 511 + return ret; 512 + 513 + do { 514 + frag_len = min(remain, port->frag_len); 515 + skb = alloc_skb(frag_len + port->headroom_len, GFP_KERNEL); 516 + if (!skb) { 517 + ret = -ENOMEM; 518 + goto freeskb; 519 + } 520 + skb_reserve(skb, port->headroom_len); 521 + memcpy(skb_put(skb, frag_len), buf + count - remain, frag_len); 522 + 523 + if (!head) { 524 + head = skb; 525 + } else { 526 + if (!tail) 527 + skb_shinfo(head)->frag_list = skb; 528 + else 529 + tail->next = skb; 530 + 531 + tail = skb; 532 + head->data_len += skb->len; 533 + head->len += skb->len; 534 + head->truesize += skb->truesize; 535 + } 536 + } while (remain -= frag_len); 537 + 538 + ret = wwan_port_op_tx(port, head, false); 539 + if (!ret) 540 + return count; 541 + 542 + freeskb: 543 + kfree_skb(head); 544 + return ret; 545 + } 546 + 547 + static struct gnss_operations wwan_gnss_ops = { 548 + .open = wwan_gnss_open, 549 + .close = wwan_gnss_close, 550 + .write_raw = wwan_gnss_write, 551 + }; 552 + 553 + /* GNSS port specific device registration */ 554 + static int wwan_port_register_gnss(struct wwan_port *port) 555 + { 556 + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); 557 + struct gnss_device *gdev; 558 + int err; 559 + 560 + gdev = gnss_allocate_device(&wwandev->dev); 561 + if (!gdev) 562 + return -ENOMEM; 563 + 564 + /* NB: for now we support only NMEA WWAN port type, so hardcode 565 + * the GNSS port type. If more GNSS WWAN port types will be added, 566 + * then we should dynamically map WWAN port type to GNSS type. 567 + */ 568 + gdev->type = GNSS_TYPE_NMEA; 569 + gdev->ops = &wwan_gnss_ops; 570 + gnss_set_drvdata(gdev, port); 571 + 572 + port->gnss = gdev; 573 + 574 + err = gnss_register_device(gdev); 575 + if (err) { 576 + gnss_put_device(gdev); 577 + return err; 578 + } 579 + 580 + dev_info(&wwandev->dev, "port %s attached\n", dev_name(&gdev->dev)); 581 + 582 + return 0; 583 + } 584 + 585 + /* GNSS port specific device unregistration */ 586 + static void wwan_port_unregister_gnss(struct wwan_port *port) 587 + { 588 + struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); 589 + struct gnss_device *gdev = port->gnss; 590 + 591 + dev_info(&wwandev->dev, "port %s disconnected\n", dev_name(&gdev->dev)); 592 + 593 + gnss_deregister_device(gdev); 594 + gnss_put_device(gdev); 595 + } 596 + #else 597 + static int wwan_port_register_gnss(struct wwan_port *port) 598 + { 599 + return -EOPNOTSUPP; 600 + } 601 + 602 + static void wwan_port_unregister_gnss(struct wwan_port *port) 603 + { 604 + WARN_ON(1); /* This handler cannot be called */ 605 + } 606 + #endif 607 + 504 608 struct wwan_port *wwan_create_port(struct device *parent, 505 609 enum wwan_port_type type, 506 610 const struct wwan_port_ops *ops, ··· 659 527 dev_set_drvdata(&port->dev, drvdata); 660 528 device_initialize(&port->dev); 661 529 662 - err = wwan_port_register_wwan(port); 530 + if (port->type == WWAN_PORT_NMEA) 531 + err = wwan_port_register_gnss(port); 532 + else 533 + err = wwan_port_register_wwan(port); 534 + 663 535 if (err) 664 536 goto error_put_device; 665 537 ··· 693 557 wake_up_interruptible(&port->waitqueue); 694 558 skb_queue_purge(&port->rxq); 695 559 696 - wwan_port_unregister_wwan(port); 560 + if (port->type == WWAN_PORT_NMEA) 561 + wwan_port_unregister_gnss(port); 562 + else 563 + wwan_port_unregister_wwan(port); 697 564 698 565 put_device(&port->dev); 699 566 ··· 707 568 708 569 void wwan_port_rx(struct wwan_port *port, struct sk_buff *skb) 709 570 { 710 - skb_queue_tail(&port->rxq, skb); 711 - wake_up_interruptible(&port->waitqueue); 571 + if (port->type == WWAN_PORT_NMEA) { 572 + #if IS_ENABLED(CONFIG_GNSS) 573 + gnss_insert_raw(port->gnss, skb->data, skb->len); 574 + #endif 575 + consume_skb(skb); 576 + } else { 577 + skb_queue_tail(&port->rxq, skb); 578 + wake_up_interruptible(&port->waitqueue); 579 + } 712 580 } 713 581 EXPORT_SYMBOL_GPL(wwan_port_rx); 714 582
+2
include/linux/wwan.h
··· 19 19 * @WWAN_PORT_FASTBOOT: Fastboot protocol control 20 20 * @WWAN_PORT_ADB: ADB protocol control 21 21 * @WWAN_PORT_MIPC: MTK MIPC diagnostic interface 22 + * @WWAN_PORT_NMEA: embedded GNSS receiver with NMEA output 22 23 * 23 24 * @WWAN_PORT_MAX: Highest supported port types 24 25 * @WWAN_PORT_UNKNOWN: Special value to indicate an unknown port type ··· 35 34 WWAN_PORT_FASTBOOT, 36 35 WWAN_PORT_ADB, 37 36 WWAN_PORT_MIPC, 37 + WWAN_PORT_NMEA, 38 38 39 39 /* Add new port types above this line */ 40 40