[RFC PATCH] usb-serial/option: modem port types

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Many newer modems expose multiple AT-compatible TTYs, but often only one
of those TTYs will emit the unsolicited responses like CONNECT that are
necessary for operation.  Without this knowledge encapsulated in the
driver (which may in the future get port types from firmware), userspace
would require a large mapping table for devices, something which is
currently (badly) done with HAL .fdi files, an unmaintainable approach.

Following the general example of the 'hso' driver, this patch implements
modem port types using a 'port_type' attribute in the tty's sysfs
directory.  Port types for genuine Option NV devices are added by this
patch, but types for other manufacturers devices may follow.  I intend
to submit a patch to hso that adds these "standard" port types to that
driver as well.

To add the sysfs attribute, it was necessary to save the tty 'struct
device' created by the usb-serial layer, which previously was ignored.
Thus, two hooks are introduced which are called after
tty_register_device() and right before tty_unregister_device().

Signed-off-by: Dan Williams <dcbw@xxxxxxxxxx>

---
diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c
index 83bbb5b..5987033 100644
--- a/drivers/usb/serial/bus.c
+++ b/drivers/usb/serial/bus.c
@@ -78,11 +78,27 @@ static int usb_serial_device_probe(struct device *dev)
 		goto exit;
 
 	minor = port->number;
-	tty_register_device(usb_serial_tty_driver, minor, dev);
+	port->tty_dev = tty_register_device(usb_serial_tty_driver, minor, dev);
+	if (!port->tty_dev) {
+		dev_err(dev, "tty device creation failed, exiting\n");
+		goto exit;
+	}
+	port->tty_dev->driver_data = port;
+
 	dev_info(&port->serial->dev->dev,
 		 "%s converter now attached to ttyUSB%d\n",
 		 driver->description, minor);
 
+	if (driver->tty_registered) {
+		if (!try_module_get(driver->driver.owner)) {
+			dev_err(dev, "module get failed, exiting\n");
+			retval = -EIO;
+			goto exit;
+		}
+		retval = driver->tty_registered(port);
+		module_put(driver->driver.owner);
+	}
+
 exit:
 	return retval;
 }
@@ -101,13 +117,23 @@ static int usb_serial_device_remove(struct device *dev)
 	device_remove_file(&port->dev, &dev_attr_port_number);
 
 	driver = port->serial->type;
-	if (driver->port_remove) {
+	if (driver->tty_unregister || driver->port_remove) {
 		if (!try_module_get(driver->driver.owner)) {
 			dev_err(dev, "module get failed, exiting\n");
 			retval = -EIO;
 			goto exit;
 		}
-		retval = driver->port_remove(port);
+
+		if (port->tty_dev) {
+			if (driver->tty_unregister)
+				retval = driver->tty_unregister(port);
+			port->tty_dev->driver_data = NULL;
+			port->tty_dev = NULL;
+		}
+
+		if ((retval == 0) && driver->port_remove)
+			retval = driver->port_remove(port);
+
 		module_put(driver->driver.owner);
 	}
 exit:
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index b7c132b..67f64a9 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -43,6 +43,8 @@
 #include <linux/usb/serial.h>
 
 /* Function prototypes */
+static int option_tty_registered(struct usb_serial_port *port);
+static int option_tty_unregister(struct usb_serial_port *port);
 static int  option_open(struct tty_struct *tty, struct usb_serial_port *port,
 							struct file *filp);
 static void option_close(struct tty_struct *tty, struct usb_serial_port *port,
@@ -542,6 +544,8 @@ static struct usb_serial_driver option_1port_device = {
 	.usb_driver        = &option_driver,
 	.id_table          = option_ids,
 	.num_ports         = 1,
+	.tty_registered    = option_tty_registered,
+	.tty_unregister    = option_tty_unregister,
 	.open              = option_open,
 	.close             = option_close,
 	.write             = option_write,
@@ -564,6 +568,13 @@ static int debug;
 #define IN_BUFLEN 4096
 #define OUT_BUFLEN 4096
 
+#define OPTION_PORT_UNHANDLED	0x0
+#define OPTION_PORT_UNKNOWN	0x1
+#define OPTION_PORT_MODEM	0x2
+#define OPTION_PORT_DIAG	0x3
+#define OPTION_PORT_APP		0x4
+#define OPTION_PORT_PCSC	0x5
+
 struct option_port_private {
 	/* Input endpoints and buffer for this port */
 	struct urb *in_urbs[N_IN_URB];
@@ -582,6 +593,9 @@ struct option_port_private {
 	int ri_state;
 
 	unsigned long tx_start_time[N_OUT_URB];
+
+	/* Port type: OPTION_PORT_* */
+	u32 port_type;
 };
 
 /* Functions used by new usb-serial code. */
@@ -1051,15 +1065,108 @@ static int option_send_setup(struct tty_struct *tty,
 	return 0;
 }
 
+/* Sysfs attribute */
+static ssize_t option_sysfs_show_porttype(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	struct usb_serial_port *port = to_usb_serial_port_from_tty_dev(dev);
+	struct option_port_private *portdata;
+	char *port_type;
+
+	if (!port)
+		return -1;
+
+	portdata = usb_get_serial_port_data(port);
+
+	switch (portdata->port_type) {
+	case OPTION_PORT_MODEM:
+		port_type = "modem";
+		break;
+	case OPTION_PORT_DIAG:
+		port_type = "diagnostic";
+		break;
+	case OPTION_PORT_APP:
+		port_type = "application";
+		break;
+	case OPTION_PORT_PCSC:
+		port_type = "pcsc";
+		break;
+	default:
+		port_type = "unknown";
+		break;
+	}
+
+	return sprintf(buf, "%s\n", port_type);
+}
+static DEVICE_ATTR(port_type, S_IRUGO, option_sysfs_show_porttype, NULL);
+
+/* Many modems expose multiple serial ports, but often only one of those
+ * ports will emit unsolicited responses (like CONNECT xxxx) necessary
+ * for meaningful operation of the device.  These port types are only known
+ * to the firmware and the driver, and there isn't a way to determine what
+ * the port types are via AT commands.  So that userspace doesn't have to
+ * maintain its own huge device table, handle port types in the driver.
+ */
+static int option_get_port_type(struct usb_serial *serial)
+{
+	u8 ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+	u16 vendor = le16_to_cpu(serial->dev->descriptor.idVendor);
+
+	if (vendor == OPTION_VENDOR_ID) {
+		/* Per mail from Option engineers, port types for genuine
+		 * Option devices are as follows:
+		 *
+		 * 0 = Modem port
+		 * 1 = Diagnostic port
+		 * 2 = Application port (GPS, SMS, whatever)
+		 * 3 = smart-card port (optional)
+		 */
+		if (ifnum == 0)
+			return OPTION_PORT_MODEM;
+		else if (ifnum == 1)
+			return OPTION_PORT_DIAG;
+		else if (ifnum == 2)
+			return OPTION_PORT_APP;
+		else if (ifnum == 3)
+			return OPTION_PORT_PCSC;
+		return OPTION_PORT_UNKNOWN;
+	}
+
+	return OPTION_PORT_UNHANDLED;
+}
+
+static int option_tty_registered(struct usb_serial_port *port)
+{
+	struct option_port_private *portdata = usb_get_serial_port_data(port);
+
+	if (portdata->port_type != OPTION_PORT_UNHANDLED)
+		return device_create_file(port->tty_dev, &dev_attr_port_type);
+
+	return 0;
+}
+
+static int option_tty_unregister(struct usb_serial_port *port)
+{
+	struct option_port_private *portdata = usb_get_serial_port_data(port);
+
+	if (portdata->port_type != OPTION_PORT_UNHANDLED)
+		device_remove_file(port->tty_dev, &dev_attr_port_type);
+
+	return 0;
+}
+
 static int option_startup(struct usb_serial *serial)
 {
-	int i, j, err;
+	int i, j, err, port_type;
 	struct usb_serial_port *port;
 	struct option_port_private *portdata;
 	u8 *buffer;
 
 	dbg("%s", __func__);
 
+	port_type = option_get_port_type(serial);
+
 	/* Now setup per port private data */
 	for (i = 0; i < serial->num_ports; i++) {
 		port = serial->port[i];
@@ -1084,6 +1191,7 @@ static int option_startup(struct usb_serial *serial)
 			portdata->out_buffer[j] = buffer;
 		}
 
+		portdata->port_type = port_type;
 		usb_set_serial_port_data(port, portdata);
 
 		if (!port->interrupt_in_urb)
diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h
index 0b8617a..406d140 100644
--- a/include/linux/usb/serial.h
+++ b/include/linux/usb/serial.h
@@ -29,7 +29,7 @@
 /**
  * usb_serial_port: structure for the specific ports of a device.
  * @serial: pointer back to the struct usb_serial owner of this port.
- * @tty: pointer to the corresponding tty for this port.
+ * @port: pointer to the corresponding struct tty_port for this port.
  * @lock: spinlock to grab when updating portions of this structure.
  * @mutex: mutex used to synchronize serial_open() and serial_close()
  *	access for this port.
@@ -57,6 +57,7 @@
  * @open_count: number of times this port has been opened.
  * @throttled: nonzero if the read urb is inactive to throttle the device
  * @throttle_req: nonzero if the tty wants to throttle us
+ * @tty_dev: pointer to the tty device for this port.
  *
  * This structure is used by the usb-serial core and drivers for the specific
  * ports of a device.
@@ -93,9 +94,11 @@ struct usb_serial_port {
 	char			throttled;
 	char			throttle_req;
 	char			console;
+	struct device *		tty_dev;
 	struct device		dev;
 };
 #define to_usb_serial_port(d) container_of(d, struct usb_serial_port, dev)
+#define to_usb_serial_port_from_tty_dev(d) (d->driver_data)
 
 /* get and set the port private data pointer helper functions */
 static inline void *usb_get_serial_port_data(struct usb_serial_port *port)
@@ -177,6 +180,10 @@ static inline void usb_set_serial_data(struct usb_serial *serial, void *data)
  *	This will be called when the struct usb_serial structure is fully set
  *	set up.  Do any local initialization of the device, or any private
  *	memory structure allocation at this point in time.
+ * @tty_registered: pointer to the driver's tty_registered function.  This will
+ *	be called after the port's tty device has been registered.
+ * @tty_unregister: pointer to the driver's tty_unregister function.  This will
+ *	be called before the port's tty device has been unregistered.
  * @shutdown: pointer to the driver's shutdown function.  This will be
  *	called when the device is removed from the system.
  * @usb_driver: pointer to the struct usb_driver that controls this
@@ -213,6 +220,9 @@ struct usb_serial_driver {
 	int (*port_probe)(struct usb_serial_port *port);
 	int (*port_remove)(struct usb_serial_port *port);
 
+	int (*tty_registered)(struct usb_serial_port *port);
+	int (*tty_unregister)(struct usb_serial_port *port);
+
 	int (*suspend)(struct usb_serial *serial, pm_message_t message);
 	int (*resume)(struct usb_serial *serial);
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux