[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20201020155426.9919-1-vandenbergh@bertold.org>
Date: Tue, 20 Oct 2020 17:54:26 +0200
From: Bertold Van den Bergh <vandenbergh@...told.org>
To: unlisted-recipients:; (no To-header on input)
Cc: Bertold Van den Bergh <vandenbergh@...told.org>,
Johan Hovold <johan@...nel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Jonathan Corbet <corbet@....net>, linux-usb@...r.kernel.org,
linux-doc@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [PATCH] usb-serial: support NAL Research Corporation Iridium modems
This driver is for NAL Reserach Corporation Iridium modems with USB.
There are different variants of the bus protocol, but the driver should
detect this automatically.
Signed-off-by: Bertold Van den Bergh <vandenbergh@...told.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.
+ 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@...told.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@...told.org)
+ *
+ */
+
+#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
+ unsigned char cmd_buf[64];
+
+ spinlock_t lock; //Lock for sharing next three entries
+ 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
+};
+
+#define CONTROL_DSR (1)
+#define CONTROL_CD (2)
+#define CONTROL_RI (4)
+#define CONTROL_CTS (8)
+#define CONTROL_DTR (16)
+#define CONTROL_RTS (32)
+
+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;
+ unsigned char header_type, cout;
+
+ spin_lock(&priv->lock);
+ header_type = priv->header_type;
+ spin_unlock(&priv->lock);
+
+ count = min_t(size_t, count, 64 - header_type);
+
+ cout = kfifo_out_locked(&port->write_fifo, buf + header_type,
+ count, &port->lock) + header_type;
+
+ header[0] = 5;
+
+ if (!header_type) {
+ return 0;
+ } else if (header_type == 2) {
+ header[1] = count;
+ return 64;
+ }
+
+ 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;
+ 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);
+ }
+
+ if (priv->header_type != 1)
+ schedule_work(&priv->data_work);
+
+ if (buf[0] == 5 && urb->actual_length >= 2) {
+ if (!priv->header_type) {
+ return;
+ } 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];
+ 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) {
+ schedule_work(&priv->control_work);
+ } else {
+ dev_info(&priv->dev->dev, "Unsupported input (length=%u): %02x\n",
+ urb->actual_length, buf[0]);
+ }
+}
+
+static int nal_request(struct nal_serial_private *priv, int type)
+{
+ int ret_val;
+
+ mutex_lock(&priv->cmd_mutex);
+
+ /* type==0: Request control lines
+ * type==1: Request application data
+ */
+ priv->cmd_buf[0] = type ? 0x04 : 0x01;
+ priv->cmd_buf[1] = 0xFF;
+
+ 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 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);
+ priv->control_get = 0;
+ spin_unlock(&priv->lock);
+
+ ret_val = nal_request(priv, 0);
+ if (ret_val)
+ return ret_val;
+
+ while (!control) {
+ ret_val = wait_event_interruptible_timeout(priv->control_event,
+ priv->control_get > 0, HZ);
+ 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);
+
+ 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;
+ }
+
+ 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);
+ 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;
+
+ return 0;
+
+fail_probe:
+ cancel_work_sync(&priv->data_work);
+ cancel_work_sync(&priv->control_work);
+ destroy_workqueue(priv->work_queue);
+fail_queue:
+ kfree(priv);
+ return ret_val;
+}
+
+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@...told.org>"
+#define DESC "Driver for NAL Research Corporation USB serial interface"
+MODULE_AUTHOR(AUTHOR);
+MODULE_DESCRIPTION(DESC);
+MODULE_LICENSE("GPL v2");
--
2.20.1
Powered by blists - more mailing lists