This series adds a generic driver for GNSS receivers with a USB
interface and a first device id for the Sierra Wireless XM1210 receiver.
Johan
Johan Hovold (2):
gnss: add USB support
gnss: usb: add support for Sierra Wireless XM1210
drivers/gnss/Kconfig | 11 +++
drivers/gnss/Makefile | 3 +
drivers/gnss/usb.c | 210 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 224 insertions(+)
create mode 100644 drivers/gnss/usb.c
--
2.32.0
Add support for the USB interface of the Sierra Wireless XM1210
receiver.
Note that the device only supports NMEA.
Bus 002 Device 003: ID 1199:b000 Sierra Wireless, Inc. Sierra Wireless_GNSS
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x1199 Sierra Wireless, Inc.
idProduct 0xb000
bcdDevice 0.01
iManufacturer 1 Sierra-wireless
iProduct 2 Sierra Wireless_GNSS
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0020
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xc0
Self Powered
MaxPower 50mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 0
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 255
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 255
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0001
Self Powered
Reported-by: Marc Ferland <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Johan Hovold <[email protected]>
---
drivers/gnss/usb.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/gnss/usb.c b/drivers/gnss/usb.c
index 5c0251034def..792235a688ea 100644
--- a/drivers/gnss/usb.c
+++ b/drivers/gnss/usb.c
@@ -17,6 +17,7 @@
#define GNSS_USB_WRITE_TIMEOUT 1000
static const struct usb_device_id gnss_usb_id_table[] = {
+ { USB_DEVICE(0x1199, 0xb000) }, /* Sierra Wireless XM1210 */
{ }
};
MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
--
2.32.0
Add a generic driver for GNSS receivers with a USB interface with two
bulk endpoints.
The driver currently assumes that the device protocol is NMEA (only) but
this can be generalised later as needed.
Signed-off-by: Johan Hovold <[email protected]>
---
drivers/gnss/Kconfig | 11 +++
drivers/gnss/Makefile | 3 +
drivers/gnss/usb.c | 209 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 223 insertions(+)
create mode 100644 drivers/gnss/usb.c
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index bd12e3d57baa..d7fe265c2869 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -54,4 +54,15 @@ config GNSS_UBX_SERIAL
If unsure, say N.
+config GNSS_USB
+ tristate "USB GNSS receiver support"
+ depends on USB
+ help
+ Say Y here if you have a GNSS receiver which uses a USB interface.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gnss-usb.
+
+ If unsure, say N.
+
endif # GNSS
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
index 451f11401ecc..bb2cbada3435 100644
--- a/drivers/gnss/Makefile
+++ b/drivers/gnss/Makefile
@@ -17,3 +17,6 @@ gnss-sirf-y := sirf.o
obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o
gnss-ubx-y := ubx.o
+
+obj-$(CONFIG_GNSS_USB) += gnss-usb.o
+gnss-usb-y := usb.o
diff --git a/drivers/gnss/usb.c b/drivers/gnss/usb.c
new file mode 100644
index 000000000000..5c0251034def
--- /dev/null
+++ b/drivers/gnss/usb.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic USB GNSS receiver driver
+ *
+ * Copyright (C) 2021 Johan Hovold <[email protected]>
+ */
+
+#include <linux/errno.h>
+#include <linux/gnss.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#define GNSS_USB_READ_BUF_LEN 512
+#define GNSS_USB_WRITE_TIMEOUT 1000
+
+static const struct usb_device_id gnss_usb_id_table[] = {
+ { }
+};
+MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
+
+struct gnss_usb {
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ struct gnss_device *gdev;
+ struct urb *read_urb;
+ unsigned int write_pipe;
+};
+
+static void gnss_usb_rx_complete(struct urb *urb)
+{
+ struct gnss_usb *gusb = urb->context;
+ struct gnss_device *gdev = gusb->gdev;
+ int status = urb->status;
+ int len;
+ int ret;
+
+ switch (status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&gdev->dev, "urb stopped: %d\n", status);
+ return;
+ case -EPIPE:
+ dev_err(&gdev->dev, "urb stopped: %d\n", status);
+ return;
+ default:
+ dev_dbg(&gdev->dev, "nonzero urb status: %d\n", status);
+ goto resubmit;
+ }
+
+ len = urb->actual_length;
+ if (len == 0)
+ goto resubmit;
+
+ ret = gnss_insert_raw(gdev, urb->transfer_buffer, len);
+ if (ret < len)
+ dev_dbg(&gdev->dev, "dropped %d bytes\n", len - ret);
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret && ret != -EPERM && ret != -ENODEV)
+ dev_err(&gdev->dev, "failed to resubmit urb: %d\n", ret);
+}
+
+static int gnss_usb_open(struct gnss_device *gdev)
+{
+ struct gnss_usb *gusb = gnss_get_drvdata(gdev);
+ int ret;
+
+ ret = usb_submit_urb(gusb->read_urb, GFP_KERNEL);
+ if (ret) {
+ if (ret != -EPERM && ret != -ENODEV)
+ dev_err(&gdev->dev, "failed to submit urb: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void gnss_usb_close(struct gnss_device *gdev)
+{
+ struct gnss_usb *gusb = gnss_get_drvdata(gdev);
+
+ usb_kill_urb(gusb->read_urb);
+}
+
+static int gnss_usb_write_raw(struct gnss_device *gdev,
+ const unsigned char *buf, size_t count)
+{
+ struct gnss_usb *gusb = gnss_get_drvdata(gdev);
+ void *tbuf;
+ int ret;
+
+ tbuf = kmemdup(buf, count, GFP_KERNEL);
+ if (!tbuf)
+ return -ENOMEM;
+
+ ret = usb_bulk_msg(gusb->udev, gusb->write_pipe, tbuf, count, NULL,
+ GNSS_USB_WRITE_TIMEOUT);
+ kfree(tbuf);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static const struct gnss_operations gnss_usb_gnss_ops = {
+ .open = gnss_usb_open,
+ .close = gnss_usb_close,
+ .write_raw = gnss_usb_write_raw,
+};
+
+static int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct usb_endpoint_descriptor *in, *out;
+ struct gnss_device *gdev;
+ struct gnss_usb *gusb;
+ struct urb *urb;
+ size_t buf_len;
+ void *buf;
+ int ret;
+
+ ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
+ NULL);
+ if (ret)
+ return ret;
+
+ gusb = kzalloc(sizeof(*gusb), GFP_KERNEL);
+ if (!gusb)
+ return -ENOMEM;
+
+ gdev = gnss_allocate_device(&intf->dev);
+ if (!gdev) {
+ ret = -ENOMEM;
+ goto err_free_gusb;
+ }
+
+ gdev->ops = &gnss_usb_gnss_ops;
+ gdev->type = GNSS_TYPE_NMEA;
+ gnss_set_drvdata(gdev, gusb);
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ goto err_put_gdev;
+
+ buf_len = max(usb_endpoint_maxp(in), GNSS_USB_READ_BUF_LEN);
+
+ buf = kzalloc(buf_len, GFP_KERNEL);
+ if (!buf)
+ goto err_free_urb;
+
+ usb_fill_bulk_urb(urb, udev,
+ usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
+ buf, buf_len, gnss_usb_rx_complete, gusb);
+
+ gusb->intf = intf;
+ gusb->udev = udev;
+ gusb->gdev = gdev;
+ gusb->read_urb = urb;
+ gusb->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
+
+ ret = gnss_register_device(gdev);
+ if (ret)
+ goto err_free_buf;
+
+ usb_set_intfdata(intf, gusb);
+
+ return 0;
+
+err_free_buf:
+ kfree(buf);
+err_free_urb:
+ usb_free_urb(urb);
+err_put_gdev:
+ gnss_put_device(gdev);
+err_free_gusb:
+ kfree(gusb);
+
+ return ret;
+}
+
+static void gnss_usb_disconnect(struct usb_interface *intf)
+{
+ struct gnss_usb *gusb = usb_get_intfdata(intf);
+
+ gnss_deregister_device(gusb->gdev);
+
+ kfree(gusb->read_urb->transfer_buffer);
+ usb_free_urb(gusb->read_urb);
+ gnss_put_device(gusb->gdev);
+ kfree(gusb);
+}
+
+static struct usb_driver gnss_usb_driver = {
+ .name = "gnss-usb",
+ .probe = gnss_usb_probe,
+ .disconnect = gnss_usb_disconnect,
+ .id_table = gnss_usb_id_table,
+};
+module_usb_driver(gnss_usb_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("Generic USB GNSS receiver driver");
+MODULE_LICENSE("GPL v2");
--
2.32.0
On Mon, Dec 20, 2021 at 12:18:59PM +0100, Johan Hovold wrote:
> This series adds a generic driver for GNSS receivers with a USB
> interface and a first device id for the Sierra Wireless XM1210 receiver.
>
> Johan
>
>
> Johan Hovold (2):
> gnss: add USB support
> gnss: usb: add support for Sierra Wireless XM1210
>
> drivers/gnss/Kconfig | 11 +++
> drivers/gnss/Makefile | 3 +
> drivers/gnss/usb.c | 210 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 224 insertions(+)
> create mode 100644 drivers/gnss/usb.c
>
> --
> 2.32.0
>
Reviewed-by: Greg Kroah-Hartman <[email protected]>
On 20.12.21 12:19, Johan Hovold wrote:
> +static int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
> +{
> + struct usb_device *udev = interface_to_usbdev(intf);
> + struct usb_endpoint_descriptor *in, *out;
> + struct gnss_device *gdev;
> + struct gnss_usb *gusb;
> + struct urb *urb;
> + size_t buf_len;
> + void *buf;
> + int ret;
> +
> + ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
> + NULL);
> + if (ret)
> + return ret;
> +
> + gusb = kzalloc(sizeof(*gusb), GFP_KERNEL);
> + if (!gusb)
> + return -ENOMEM;
> +
> + gdev = gnss_allocate_device(&intf->dev);
> + if (!gdev) {
> + ret = -ENOMEM;
> + goto err_free_gusb;
> + }
> +
> + gdev->ops = &gnss_usb_gnss_ops;
> + gdev->type = GNSS_TYPE_NMEA;
> + gnss_set_drvdata(gdev, gusb);
> +
> + urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (!urb)
> + goto err_put_gdev;
The value of 'ret' here is the result of
usb_find_common_endpoints(), hence 0 due to the prior test.
> +
> + buf_len = max(usb_endpoint_maxp(in), GNSS_USB_READ_BUF_LEN);
> +
> + buf = kzalloc(buf_len, GFP_KERNEL);
> + if (!buf)
> + goto err_free_urb;
> +
> + usb_fill_bulk_urb(urb, udev,
> + usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
> + buf, buf_len, gnss_usb_rx_complete, gusb);
> +
> + gusb->intf = intf;
> + gusb->udev = udev;
> + gusb->gdev = gdev;
> + gusb->read_urb = urb;
> + gusb->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
> +
> + ret = gnss_register_device(gdev);
> + if (ret)
> + goto err_free_buf;
> +
> + usb_set_intfdata(intf, gusb);
> +
> + return 0;
> +
> +err_free_buf:
> + kfree(buf);
> +err_free_urb:
> + usb_free_urb(urb);
> +err_put_gdev:
> + gnss_put_device(gdev);
> +err_free_gusb:
> + kfree(gusb);
> +
> + return ret;
Yet you return it in the error case and subsequent error cases..
HTH
Oliver
On Mon, Dec 20, 2021 at 01:49:23PM +0100, Oliver Neukum wrote:
>
> On 20.12.21 12:19, Johan Hovold wrote:
> > +static int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
> > + urb = usb_alloc_urb(0, GFP_KERNEL);
> > + if (!urb)
> > + goto err_put_gdev;
>
> The value of 'ret' here is the result of
> usb_find_common_endpoints(), hence 0 due to the prior test.
> > +err_free_buf:
> > + kfree(buf);
> > +err_free_urb:
> > + usb_free_urb(urb);
> > +err_put_gdev:
> > + gnss_put_device(gdev);
> > +err_free_gusb:
> > + kfree(gusb);
> > +
> > + return ret;
> Yet you return it in the error case and subsequent error cases..
Thanks for spotting that! I'll fix it up before applying.
Johan
On Mon, Dec 20, 2021 at 01:15:43PM +0100, Greg Kroah-Hartman wrote:
> On Mon, Dec 20, 2021 at 12:18:59PM +0100, Johan Hovold wrote:
> > This series adds a generic driver for GNSS receivers with a USB
> > interface and a first device id for the Sierra Wireless XM1210 receiver.
> Reviewed-by: Greg Kroah-Hartman <[email protected]>
Thanks for reviewing.
Johan
On Mon, Dec 20, 2021 at 6:19 AM Johan Hovold <[email protected]> wrote:
>
> Add support for the USB interface of the Sierra Wireless XM1210
> receiver.
>
> Note that the device only supports NMEA.
>
> Bus 002 Device 003: ID 1199:b000 Sierra Wireless, Inc. Sierra Wireless_GNSS
> Device Descriptor:
> bLength 18
> bDescriptorType 1
> bcdUSB 1.00
> bDeviceClass 0
> bDeviceSubClass 0
> bDeviceProtocol 0
> bMaxPacketSize0 64
> idVendor 0x1199 Sierra Wireless, Inc.
> idProduct 0xb000
> bcdDevice 0.01
> iManufacturer 1 Sierra-wireless
> iProduct 2 Sierra Wireless_GNSS
> iSerial 0
> bNumConfigurations 1
> Configuration Descriptor:
> bLength 9
> bDescriptorType 2
> wTotalLength 0x0020
> bNumInterfaces 1
> bConfigurationValue 1
> iConfiguration 0
> bmAttributes 0xc0
> Self Powered
> MaxPower 50mA
> Interface Descriptor:
> bLength 9
> bDescriptorType 4
> bInterfaceNumber 0
> bAlternateSetting 0
> bNumEndpoints 2
> bInterfaceClass 0
> bInterfaceSubClass 0
> bInterfaceProtocol 0
> iInterface 0
> Endpoint Descriptor:
> bLength 7
> bDescriptorType 5
> bEndpointAddress 0x81 EP 1 IN
> bmAttributes 2
> Transfer Type Bulk
> Synch Type None
> Usage Type Data
> wMaxPacketSize 0x0040 1x 64 bytes
> bInterval 255
> Endpoint Descriptor:
> bLength 7
> bDescriptorType 5
> bEndpointAddress 0x01 EP 1 OUT
> bmAttributes 2
> Transfer Type Bulk
> Synch Type None
> Usage Type Data
> wMaxPacketSize 0x0040 1x 64 bytes
> bInterval 255
> can't get debug descriptor: Resource temporarily unavailable
> Device Status: 0x0001
> Self Powered
>
> Reported-by: Marc Ferland <[email protected]>
> Link: https://lore.kernel.org/r/[email protected]
> Signed-off-by: Johan Hovold <[email protected]>
> ---
> drivers/gnss/usb.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/gnss/usb.c b/drivers/gnss/usb.c
> index 5c0251034def..792235a688ea 100644
> --- a/drivers/gnss/usb.c
> +++ b/drivers/gnss/usb.c
> @@ -17,6 +17,7 @@
> #define GNSS_USB_WRITE_TIMEOUT 1000
>
> static const struct usb_device_id gnss_usb_id_table[] = {
> + { USB_DEVICE(0x1199, 0xb000) }, /* Sierra Wireless XM1210 */
> { }
> };
> MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
> --
> 2.32.0
>
Thank you Johan! Much appreciated!
With your patches applied on my platform (featuring the XM1210) , I get:
# lsmod | grep gnss
gnss_usb 16384 0 - Live 0xffffffffc011b000
gnss 16384 3 gnss_usb, Live 0xffffffffc0082000
# dmesg | grep gnss
gnss: GNSS driver registered with major 244
usbcore: registered new interface driver gnss-usb
# ls -l /dev/gnss0
crw-rw---- 1 root root 244, 0 Apr 8 08:39 /dev/gnss0
I also tested with gpsd and everything is working as expected, hence:
Tested-by: Marc Ferland <[email protected]>
Marc
On Mon, Dec 20, 2021 at 02:04:49PM -0500, Marc Ferland wrote:
> On Mon, Dec 20, 2021 at 6:19 AM Johan Hovold <[email protected]> wrote:
> >
> > Add support for the USB interface of the Sierra Wireless XM1210
> > receiver.
> >
> > Note that the device only supports NMEA.
> > Reported-by: Marc Ferland <[email protected]>
> > Link: https://lore.kernel.org/r/[email protected]
> > Signed-off-by: Johan Hovold <[email protected]>
> > ---
> > drivers/gnss/usb.c | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/drivers/gnss/usb.c b/drivers/gnss/usb.c
> > index 5c0251034def..792235a688ea 100644
> > --- a/drivers/gnss/usb.c
> > +++ b/drivers/gnss/usb.c
> > @@ -17,6 +17,7 @@
> > #define GNSS_USB_WRITE_TIMEOUT 1000
> >
> > static const struct usb_device_id gnss_usb_id_table[] = {
> > + { USB_DEVICE(0x1199, 0xb000) }, /* Sierra Wireless XM1210 */
> > { }
> > };
> > MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
> > --
> > 2.32.0
> >
> Thank you Johan! Much appreciated!
>
> With your patches applied on my platform (featuring the XM1210) , I get:
>
> # lsmod | grep gnss
> gnss_usb 16384 0 - Live 0xffffffffc011b000
> gnss 16384 3 gnss_usb, Live 0xffffffffc0082000
>
> # dmesg | grep gnss
> gnss: GNSS driver registered with major 244
> usbcore: registered new interface driver gnss-usb
>
> # ls -l /dev/gnss0
> crw-rw---- 1 root root 244, 0 Apr 8 08:39 /dev/gnss0
>
> I also tested with gpsd and everything is working as expected, hence:
>
> Tested-by: Marc Ferland <[email protected]>
Thanks for testing, Marc!
I've applied this series now after adding your Tested-by tag to both
patches.
Johan