The USB Serial Driver Layer

Understanding the USB-to-serial layer and how to get devices into it.
struct usb_serial_device_type Explained

To register with the USB serial core, the usb_serial_device_type structure must be filled. This structure can be found in drivers/usb/serial/usb-serial.h and is defined as the following:

struct usb_serial_device_type {
  struct module *owner;
  char *name;
  const struct usb_device_id *id_table;
  char num_interrupt_in;
  char num_bulk_in;
  char num_bulk_out;
  char num_ports;
  struct list_head driver_list;
  int (*probe) (struct usb_serial *serial);
  int (*attach) (struct usb_serial *serial);
  int (*calc_num_ports) (struct usb_serial *serial);
  void (*shutdown) (struct usb_serial *serial);
  int  (*open) (struct usb_serial_port *port,
                struct file * filp);
  void (*close) (struct usb_serial_port *port,
                 struct file * filp);
  int  (*write) (struct usb_serial_port *port,
                 int from_user,
                 const unsigned char *buf,
                 int count);
  int  (*write_room) (struct usb_serial_port *port);
  int  (*ioctl) (struct usb_serial_port *port,
                 struct file * file,
                 unsigned int cmd,
                 unsigned long arg);
  void (*set_termios) (struct usb_serial_port *port,
                       struct termios * old);
  void (*break_ctl) (struct usb_serial_port *port,
                     int break_state);
  int  (*chars_in_buffer)
         (struct usb_serial_port *port);
  void (*throttle) (struct usb_serial_port *port);
  void (*unthrottle) (struct usb_serial_port *port);
  void (*read_int_callback)(struct urb *urb);
  void (*read_bulk_callback)(struct urb *urb);
  void (*write_bulk_callback)(struct urb *urb);
};

This is a rather large structure, but it's still smaller than either the tty layer's structure or the combination of the serial layer's structures, both of which are alternatives to using the USB serial layer.

Let us describe what all of these fields are used for and whether they are necessary. The owner field is a pointer to the module that owns this device. It should be set to the THIS_MODULE macro. When this is set, the module reference count logic is handled by the USB serial core, which is much safer than trying to do it on your own.

The name field is a pointer to a string that describes this device. This string is used in the syslog messages when a device is inserted or removed. It is also used in the /proc/tty/driver/usb-serial file to show what device is connected to what port.

The /proc/tty/driver/usb-serial File

The id_table field is a pointer to a list of usb_device_id structures that define all of the devices this structure can support. This field can be identical to the pointer that is passed to the USB core. If your driver needs to do different things for different types of devices, however, you can set up different structures describing these devices. An example of this is the Keyspan driver, which handles all of the Keyspan USB serial devices and needs different functions to be called for different devices.

The num_interrupt_in field is the expected number of interrupt in endpoints this device will have. An endpoint is a USB term, defined in the USB spec (www.usb.org). If you do not care about having the USB serial core check for this value (matching it up against any seen devices), use the NUM_DONT_CARE macro defined in usb-serial.h.

The num_bulk_in and num_bulk_out fields state the number of bulk in and bulk out endpoints this device will have. Again, the NUM_DONT_CARE macro can be used here if you do not want the core to pay attention to this value.

The num_ports field indicates the number of different ports this device will have. A single USB serial device can contain many different physical serial ports.

The driver_list field is used by the USB serial core to keep track of all the different drivers registered with it; it should not be used by the individual drivers.

The rest of the fields in the structure are all optional function pointers. If a field is not set, the generic USB serial driver's related function will be called. This allows a driver to be written with a minimal amount of code, if it happens to work the same way as the generic driver does. If not, almost all of these functions will need to be defined. These function pointers are divided into three groups: USB life-cycle pointers, tty life-cycle pointers and urb callback pointers.

The Generic USB Serial Driver

USB life-cycle function pointers consist of probe, calc_num_ports, attach and shut down. They are all called at different points in time as a USB device is initialized and shutdown. The probe function is called when a device matching one of the the id_table devices is inserted into the system. This call happens before the device has been fully initialized by the USB serial core. It can be used to download any needed firmware to the device. In addition, any other early-initialize commands that the device needs can be sent at this time. If 0 is returned, the USB serial core continues on with the initialization sequence. Any other value will abort the call and notify the USB core that this device is not claimed by any drivers.

The calc_num_ports function is called to determine how many ports this device has. This should be used only by devices that can dynamically determine their ports. Any return value overrides the num_ports field in the usb_serial_device_type structure. It is called after the probe function is called but before the attach function is called.

The attach function is called when the struct usb_serial structure is fully set up. Any local initialization of the device or any private memory structure allocation can be done in this function. The shutdown function is called when the device has been removed from the system. Any local memory allocated for this device should be freed up at this time.

tty layer function pointers consist of open, close, write, write_room, ioctl, set_termios, break_ctl, chars_in_buffer, throttle and unthrottle. If you recall the article on the tty layer [“The tty Layer”, LJ August 2002],the these match up with the tty layer function call of the same name, with a few twists. First off, they all pass in a pointer to the specific usb_serial_port structure that is being operated on, and some of the functions are only called when something needs to happen.

The open function is called the first time open() is called on a port, but not for any subsequent calls to open(). Any urb submission the driver needs to do to start receiving data, or any device-specific messages that should be sent, should be done at this time. If any errors occur, they should be returned; otherwise, return 0 to signal success.

The close function is called for the last close() call, which is called from user space. Any running urbs should be shut down, and any device-specific commands that are needed should be sent now.

The write function is called exactly like the tty layer write function is called. The data passed to the function needs to be sent to the specified port. The number of bytes sent to the device should be returned. Remember, the device does not have to send all of the data that the user wants it to; a short write can happen, as long as the driver notifies user space that this has happened. This allows the driver logic to be much simpler. If an error happens, it should be returned as a negative number.

The write_room and chars_in_buffer functions are closely related. The write_room function is called by the tty layer to ask how many bytes the driver can accept to be written out at this time. The chars_in_buffer function is called to find out the number of bytes still left to be sent to the device.

The ioctl function is called with a wide range of tty ioctl values. If the driver cannot handle the specific value, -ENOIOCTLCMD should be returned. This will allow the tty layer to try to provide a default function. Some of the more common values asked for by user space are documented in the tty driver article previously mentioned.

The set_termios function is called to set terminal settings for a specific port. This includes baud rate, flow control, data bits and other line settings. The break_ctl function is called to set the BREAK value for the port. A state of -1 means that the BREAK status should be turned on, and a status of 0 means it should be turned off. The throttle and unthrottle functions are used to stop and resume data being received from the serial port.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Interesting article, when

Anonymous's picture

Interesting article, when searching for Linux compatible USB serial adapters
this is a good source.

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix