From: Johan Hovold <johan@kernel.org>
To: Bertold Van den Bergh <vandenbergh@bertold.org>
Cc: Johan Hovold <johan@kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jonathan Corbet <corbet@lwn.net>,
linux-usb@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] usb-serial: support NAL Research Corporation Iridium modems
Date: Fri, 13 Nov 2020 14:17:16 +0100 [thread overview]
Message-ID: <20201113131716.GK4085@localhost> (raw)
In-Reply-To: <20201020155426.9919-1-vandenbergh@bertold.org>
On Tue, Oct 20, 2020 at 05:54:26PM +0200, Bertold Van den Bergh wrote:
> This driver is for NAL Reserach Corporation Iridium modems with USB.
"Research"
> There are different variants of the bus protocol, but the driver should
> detect this automatically.
Do you mean that this is something to be implemented?
> Signed-off-by: Bertold Van den Bergh <vandenbergh@bertold.org>
> ---
> Documentation/usb/usb-serial.rst | 15 ++
> drivers/usb/serial/Kconfig | 9 +
> drivers/usb/serial/Makefile | 1 +
> drivers/usb/serial/nal.c | 357 +++++++++++++++++++++++++++++++
> 4 files changed, 382 insertions(+)
> create mode 100644 drivers/usb/serial/nal.c
>
> diff --git a/Documentation/usb/usb-serial.rst b/Documentation/usb/usb-serial.rst
> index 8fa7dbd3d..fdc8c0c76 100644
> --- a/Documentation/usb/usb-serial.rst
> +++ b/Documentation/usb/usb-serial.rst
> @@ -494,6 +494,21 @@ Moschip MCS7720, MCS7715 driver
> with this driver with a simple addition to the usb_device_id table. I
> don't have one of these devices, so I can't say for sure.
>
> +NAL Research Corporation driver
> +-------------------------------
> +
> + This driver is for NAL Reserach Corporation Iridium modems with USB.
Same typo.
> + There are different variants of the bus protocol, but the driver should
> + detect this automatically.
> +
> + The manufacturer provided Windows driver attaches to all PIDs in the given
> + VID, so you may want to try this driver even if the PID doesn't match.
> +
> + The manufacturer's website: https://www.nalresearch.com/
> +
> + For any questions or problems with this driver, please contact:
> + Bertold Van den Bergh <vandenbergh@bertold.org>
> +
> Generic Serial driver
> ---------------------
>
> diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
> index 4007fa25a..f97c44068 100644
> --- a/drivers/usb/serial/Kconfig
> +++ b/drivers/usb/serial/Kconfig
> @@ -436,6 +436,15 @@ config USB_SERIAL_MXUPORT
> To compile this driver as a module, choose M here: the
> module will be called mxuport.
>
> +config USB_SERIAL_NAL
> + tristate "USB NAL Research Corporation Serial Bridge"
> + help
> + Say Y here if you want to use NAL Research Corporation
> + USB devices.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called nal.
> +
> config USB_SERIAL_NAVMAN
> tristate "USB Navman GPS device"
> help
> diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
> index 2d491e434..f3cbe972f 100644
> --- a/drivers/usb/serial/Makefile
> +++ b/drivers/usb/serial/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o
> obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o
> obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o
> obj-$(CONFIG_USB_SERIAL_MXUPORT) += mxuport.o
> +obj-$(CONFIG_USB_SERIAL_NAL) += nal.o
> obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o
> obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o
> obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o
> diff --git a/drivers/usb/serial/nal.c b/drivers/usb/serial/nal.c
> new file mode 100644
> index 000000000..e3272cd5e
> --- /dev/null
> +++ b/drivers/usb/serial/nal.c
> @@ -0,0 +1,357 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for NAL Research Corporation USB serial converter.
> + * Tested using A3LA-XG.
> + *
> + * Copyright (C) 2020 Bertold Van den Bergh (vandenbergh@bertold.org)
> + *
Please provide some details on the protocol here. What are the header
types, that you use a bulk pipe for modem control, etc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/errno.h>
> +#include <linux/slab.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/module.h>
> +#include <linux/usb.h>
> +#include <linux/usb/serial.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +
> +static const struct usb_device_id id_table[] = {
> + { USB_DEVICE(0x2017, 0x0001) },
> + { }
> +};
> +
> +MODULE_DEVICE_TABLE(usb, id_table);
> +
> +struct nal_serial_private {
> + struct usb_device *dev;
> + struct mutex cmd_mutex; //Mutex for sharing cmd_buf
Please don't use // comments and shorten to something like
/* protects cmd_buf */
> + unsigned char cmd_buf[64];
> +
> + spinlock_t lock; //Lock for sharing next three entries
Here too.
> + unsigned char control_get;
> + unsigned char control_put;
> + unsigned char header_type;
> +
> + wait_queue_head_t control_event;
> +
> + struct workqueue_struct *work_queue;
> + struct work_struct control_work;
> + struct work_struct data_work;
> +};
> +
> +static int nal_prepare_write_buffer(struct usb_serial_port *port,
> + void *buf, size_t count);
> +static void nal_process_read_urb(struct urb *urb);
> +static int nal_request(struct nal_serial_private *priv, int type);
> +static void nal_data_work(struct work_struct *work);
> +static int nal_tiocmget(struct tty_struct *tty);
> +static int nal_send_control(struct nal_serial_private *priv,
> + unsigned int set, unsigned int clear);
> +static int nal_tiocmset(struct tty_struct *tty,
> + unsigned int set, unsigned int clear);
> +static void nal_dtr_rts(struct usb_serial_port *port, int on);
> +static void nal_control_work(struct work_struct *work);
> +static int nal_port_probe(struct usb_serial_port *serial);
> +static int nal_port_remove(struct usb_serial_port *serial);
> +
> +static struct usb_serial_driver nal_device = {
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "nal",
> + },
> + .id_table = id_table,
> + .num_ports = 1,
> + .tiocmget = nal_tiocmget,
> + .tiocmset = nal_tiocmset,
> + .port_probe = nal_port_probe,
> + .port_remove = nal_port_remove,
> + .dtr_rts = nal_dtr_rts,
> + .process_read_urb = nal_process_read_urb,
> + .prepare_write_buffer = nal_prepare_write_buffer
> +};
> +
> +static struct usb_serial_driver * const serial_drivers[] = {
> + &nal_device, NULL
> +};
Move the driver structures to the end of the file and drop the function
prototypes (may need to reorder some functions).
> +
> +#define CONTROL_DSR (1)
> +#define CONTROL_CD (2)
> +#define CONTROL_RI (4)
> +#define CONTROL_CTS (8)
> +#define CONTROL_DTR (16)
> +#define CONTROL_RTS (32)
No need for parentheses, use BIT(n).
> +
> +static int nal_prepare_write_buffer(struct usb_serial_port *port,
> + void *buf, size_t count)
> +{
> + struct nal_serial_private *priv = usb_get_serial_port_data(port);
> + unsigned char *header = (unsigned char *)buf;
Cast not needed.
> + unsigned char header_type, cout;
> +
> + spin_lock(&priv->lock);
You need to disable interrupts also in the completion handlers (which
may run in soft-interrupt context); use spin_lock_irqsave().
> + header_type = priv->header_type;
> + spin_unlock(&priv->lock);
> +
> + count = min_t(size_t, count, 64 - header_type);
Please clarify how the max-packet size correlates with the header types
by using a helper function (and defines for the types).
> +
> + cout = kfifo_out_locked(&port->write_fifo, buf + header_type,
> + count, &port->lock) + header_type;
Is header type really a header length?
> +
> + header[0] = 5;
What's 5 here? Use a defines for magic constants throughout.
> +
> + if (!header_type) {
> + return 0;
You're throwing away data here.
> + } else if (header_type == 2) {
> + header[1] = count;
> + return 64;
What if there wasn't 64 bytes in the fifo? Should the stale buffer data
be cleared?
> + }
> +
> + return cout;
> +}
> +
> +static void nal_process_read_urb(struct urb *urb)
> +{
> + struct usb_serial_port *port = urb->context;
> + struct nal_serial_private *priv = usb_get_serial_port_data(port);
> + const unsigned char *buf = (const unsigned char *)urb->transfer_buffer;
Cast not needed.
> + unsigned char length;
> +
> + if (urb->actual_length < 1)
> + return;
> +
> + if (!priv->header_type && urb->actual_length < 64) {
> + spin_lock(&priv->lock);
> + priv->header_type = 1;
> + spin_unlock(&priv->lock);
> + }
Can you do the header type (protocol?) detection at probe somehow
instead? Send a request and read it back synchronously?
> +
> + if (priv->header_type != 1)
> + schedule_work(&priv->data_work);
What's going on here? Looks like you're polling for data, but that
should be made clear here (use a helper function, or at least add a
comment).
How does it depend on the header type?
> +
> + if (buf[0] == 5 && urb->actual_length >= 2) {
> + if (!priv->header_type) {
> + return;
Throwing away data if you haven't yet detected the protocol?
> + } else if (priv->header_type == 1) {
> + tty_insert_flip_string(&port->port, buf + 1,
> + urb->actual_length - 1);
> + } else {
> + length = buf[1];
> + if (length > urb->actual_length - 2)
> + length = urb->actual_length - 2;
> +
> + tty_insert_flip_string(&port->port, buf + 2, length);
> + }
> +
> + tty_flip_buffer_push(&port->port);
> + } else if (buf[0] == 0 && urb->actual_length >= 2) {
> + spin_lock(&priv->lock);
> + priv->control_get = 0x80 | buf[1];
What's going on here? What is command/reply "0"? Use a define.
What is 0x80? Use a define.
> + if (!priv->header_type && urb->actual_length == 64)
> + priv->header_type = 2;
> + spin_unlock(&priv->lock);
> +
> + wake_up(&priv->control_event);
> + } else if (buf[0] == 1) {
What's "1"? You get the point...
> + schedule_work(&priv->control_work);
And why do you need to set the control lines on a device request here?
> + } else {
> + dev_info(&priv->dev->dev, "Unsupported input (length=%u): %02x\n",
> + urb->actual_length, buf[0]);
Use &port->dev for messages, and this one should be demoted to
dev_dbg().
> + }
> +}
> +
> +static int nal_request(struct nal_serial_private *priv, int type)
> +{
> + int ret_val;
Please use the shorter "ret" throughout.
> +
> + mutex_lock(&priv->cmd_mutex);
> +
> + /* type==0: Request control lines
> + * type==1: Request application data
> + */
Use an enum or defines for the request types.
Comment style should be
/*
* ...
*/
> + priv->cmd_buf[0] = type ? 0x04 : 0x01;
> + priv->cmd_buf[1] = 0xFF;
Defines.
> +
> + ret_val = usb_bulk_msg(priv->dev, usb_sndbulkpipe(priv->dev, 1),
> + priv->cmd_buf, 64, NULL, HZ);
Timeout is specified in milliseconds and should not depend on HZ.
Note that cmd_buf must be allocated separately from priv as it may be
mapped for DMA.
> +
> + mutex_unlock(&priv->cmd_mutex);
> +
> + return ret_val;
> +}
> +
> +static void nal_data_work(struct work_struct *work)
> +{
> + struct nal_serial_private *priv =
> + container_of(work, struct nal_serial_private, data_work);
> +
> + nal_request(priv, 1);
> +}
> +
> +static int nal_tiocmget(struct tty_struct *tty)
> +{
> + struct usb_serial_port *port = tty->driver_data;
> + struct nal_serial_private *priv = usb_get_serial_port_data(port);
> + int ret_val, control = 0;
> +
> + spin_lock(&priv->lock);
You must disable interrupt when taking this lock (throughout).
> + priv->control_get = 0;
> + spin_unlock(&priv->lock);
> +
> + ret_val = nal_request(priv, 0);
> + if (ret_val)
> + return ret_val;
> +
> + while (!control) {
do-while
> + ret_val = wait_event_interruptible_timeout(priv->control_event,
> + priv->control_get > 0, HZ);
Looks like this could be simplified by using a struct completion and
calling wait_for_completion_interruptible_timeout() here instead. Just
store the latest modem control status in the completion handler and call
complete().
> + if (ret_val == 0)
> + ret_val = -ETIMEDOUT;
> + if (ret_val < 0)
> + return ret_val;
> +
> + spin_lock(&priv->lock);
> + control = priv->control_get;
> + spin_unlock(&priv->lock);
> + }
> +
> + ret_val = ((control & CONTROL_DSR) ? TIOCM_DSR : 0) |
> + ((control & CONTROL_CD) ? TIOCM_CD : 0) |
> + ((control & CONTROL_RI) ? TIOCM_RI : 0) |
> + ((control & CONTROL_CTS) ? TIOCM_CTS : 0);
> +
> + spin_lock(&priv->lock);
> + control = priv->control_put;
> + spin_unlock(&priv->lock);
Are DTR and RTS included in the bulk response you get? If so you may not
need to store control_put at all. Just return the latest values returned
by the device.
> +
> + ret_val |= ((control & CONTROL_DTR) ? TIOCM_DTR : 0) |
> + ((control & CONTROL_RTS) ? TIOCM_RTS : 0);
> +
> + return ret_val;
> +}
> +
> +static int nal_send_control(struct nal_serial_private *priv,
> + unsigned int set, unsigned int clear)
> +{
> + int ret_val, control;
> +
> + mutex_lock(&priv->cmd_mutex);
> +
> + spin_lock(&priv->lock);
> + if (set & TIOCM_RTS)
> + priv->control_put |= CONTROL_RTS;
> + if (set & TIOCM_DTR)
> + priv->control_put |= CONTROL_DTR;
> + if (clear & TIOCM_RTS)
> + priv->control_put &= ~CONTROL_RTS;
> + if (clear & TIOCM_DTR)
> + priv->control_put &= ~CONTROL_DTR;
> +
> + control = priv->control_put;
> + spin_unlock(&priv->lock);
> +
> + priv->cmd_buf[0] = 0x00;
> + priv->cmd_buf[1] = 0x0d | control;
> +
> + ret_val = usb_bulk_msg(priv->dev, usb_sndbulkpipe(priv->dev, 1),
> + priv->cmd_buf, 64, NULL, HZ);
> +
> + mutex_unlock(&priv->cmd_mutex);
> +
> + return ret_val;
> +}
> +
> +static int nal_tiocmset(struct tty_struct *tty,
> + unsigned int set, unsigned int clear)
> +{
> + struct usb_serial_port *port = tty->driver_data;
> + struct nal_serial_private *priv = usb_get_serial_port_data(port);
> +
> + return nal_send_control(priv, set, clear);
> +}
> +
> +static void nal_dtr_rts(struct usb_serial_port *port, int enable)
> +{
> + struct nal_serial_private *priv = usb_get_serial_port_data(port);
> +
> + if (enable)
> + nal_send_control(priv, TIOCM_RTS | TIOCM_DTR, 0);
> + else
> + nal_send_control(priv, 0, TIOCM_RTS | TIOCM_DTR);
> +}
> +
> +static void nal_control_work(struct work_struct *work)
> +{
> + struct nal_serial_private *priv =
> + container_of(work, struct nal_serial_private, control_work);
> +
> + nal_send_control(priv, 0, 0);
> +}
> +
> +static int nal_port_probe(struct usb_serial_port *serial)
> +{
> + struct nal_serial_private *priv;
> + int ret_val;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->dev = serial->serial->dev;
> + spin_lock_init(&priv->lock);
> + init_waitqueue_head(&priv->control_event);
> + mutex_init(&priv->cmd_mutex);
> +
> + priv->work_queue = alloc_workqueue("nal_wq", 0, 0);
> + if (!priv->work_queue) {
> + ret_val = -ENOMEM;
> + goto fail_queue;
> + }
You never use the work queue so remove it.
> +
> + usb_set_serial_port_data(serial, priv);
> +
> + INIT_WORK(&priv->control_work, nal_control_work);
> + INIT_WORK(&priv->data_work, nal_data_work);
> +
> + /* Used for header autodetect */
> + ret_val = nal_request(priv, 0);
Again, what is 0...
So this is used to trigger a reply that you read when the port is
opened?
> + if (ret_val < 0)
> + goto fail_probe;
> +
> + ret_val = nal_send_control(priv, TIOCM_RTS | TIOCM_DTR, 0);
> + if (ret_val < 0)
> + goto fail_probe;
This is taken care of by the dtr_rts callback on open().
> +
> + return 0;
> +
> +fail_probe:
> + cancel_work_sync(&priv->data_work);
> + cancel_work_sync(&priv->control_work);
These cannot possibly be scheduled yet, right? Can you cancel (or flush)
them on close instead?
> + destroy_workqueue(priv->work_queue);
> +fail_queue:
> + kfree(priv);
> + return ret_val;
Name labels after what they do rather than where you jump from (e.g.
"err_cancel_work" and "err_free").
> +}
> +
> +static int nal_port_remove(struct usb_serial_port *serial)
> +{
> + struct nal_serial_private *priv = usb_get_serial_port_data(serial);
> +
> + cancel_work_sync(&priv->data_work);
> + cancel_work_sync(&priv->control_work);
> + destroy_workqueue(priv->work_queue);
> + kfree(priv);
> +
> + return 0;
> +}
> +
> +module_usb_serial_driver(serial_drivers, id_table);
> +
> +#define AUTHOR "Bertold Van den Bergh <vandenbergh@bertold.org>"
> +#define DESC "Driver for NAL Research Corporation USB serial interface"
No need for these, just use the module macros directly.
> +MODULE_AUTHOR(AUTHOR);
> +MODULE_DESCRIPTION(DESC);
> +MODULE_LICENSE("GPL v2");
The shorter "GPL" means the same here.
Ok, I think I get the protocol now, but it really shouldn't take that
much effort. You need to go through the driver and add helper functions
and/or aptly named defines so that the protocol becomes more or less
obvious to someone looking at this code. And please add a short overview
of the protocol to the file header.
Johan
prev parent reply other threads:[~2020-11-13 13:17 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-10-20 15:54 [PATCH] usb-serial: support NAL Research Corporation Iridium modems Bertold Van den Bergh
2020-11-13 13:17 ` Johan Hovold [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201113131716.GK4085@localhost \
--to=johan@kernel.org \
--cc=corbet@lwn.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=vandenbergh@bertold.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.