Monday November 21, 2005 | Artem's Weblog |
|
[Part 1] The generic serial driver (GSD) hides a great deal of termio(7I) complexity from the USB serial driver writers. Another major benefit is that it ensures compliance with UNIX standards, such as Single UNIX specification (see chapter 11, General Terminal Interfaces). VSX-PCTS test suite includes terminal interface tests that all USB serial drivers should pass. Today I am going to discuss some aspects of GSD implementation. Open Open(2) implementation for serial devices is quite complicated, there a many rules to follow. Recall that there are two types of device nodes per serial port: /dev/term (aka tty lines) and /dev/cua (aka dial-out lines). When an application attempts to open a tty line, the open(2) system call should block until Carrier Detect signal is asserted. Dial-out opens do not block and succeed immediately. Now recall that there can be multiple applications attempting to open the same device simultaneously. For example, while one application is blocked in open(2) waiting for Carrier Detect, another opens the corresponding dial-out line; in this case, the dial-out open should succeed and the first application's open(2) should unblock and fail. Open behavior also varies depending on the O_NONBLOCK/O_NDELAY flags, soft carrier setting and "ignore-cd" device properties. usbser_open_setup() function accounts for all possible scenarios through the used of a state machine. Close Quite a few things should happen before a serial device can be closed:
This is done in usbser_close(). Threads The driver uses two threads, one each for read and write message processing. Strictly speaking, separate threads are not needed in Solaris 9 and up since the STREAMS scheduler was improved with dynamic task queues. However, GSD was first written for Solaris 8, and at that time is was problematic using blocking USB requests in the STREAMS context. So instead of doing processing in the STREAMS service routine, this is done in usbser_wq_thread and usbser_rq_thread, which sleep until woken for new requests. Look for calls to usbser_thr_wake(). Due to asynchronous nature of USB requests, operations that usually happen atomically, take several steps. To prevent multiple operations from stepping on each other, some of them are serialized using usbser_serialize_port_act() and usbser_release_port_act(). The core of write-side processing is usbser_wmsg: it dequeues one message at a time and dispatches it depending on type, such as M_STOP, M_DATA or M_IOCTL. Ioctl usbser_ioctl() handles various terminal ioctls, passed via M_IOCTL messages. Setting port parameters, sending break, draining and flushing data, enabling internal loopback mode (great for testing), getting and setting port signals - all this is done here by calling down to DSD. Not all ioctls are handled by GSD, however, some of them are passed to the ttycommon_ioctl() function, which is a generic terminal ioctl implementation used by all Solaris serial drivers. Status changes When the DSD detects modem status line changes, such as CTS or CD, it notifies GSD with the status callback. usbser_status_cb(), the callback handler, does not process this request immediately. Instead, it sets a special flag, USBSER_FL_STATUS_CB, and wakes up the write thread. usbser_wq_thread() checks the flag and if it's set, calls the actual handler, usbser_status_proc_cb(). Because the status callback is called from DSD, and the callback handler may need to call other DSD functions, we avoid recursive calls into DSD by delegating status handling to the write thread. Back in Solaris 8, the DSD callback was called in the interrupt context, so a recursive call into DSD could lead to a deadlock. Outgoing data usbser_data() simply takes an M_DATA message and passes it on to the DSD by calling USBSER_DS_TX() for transmission. The DSD can't refuse the message, if it needs to postpone transmission, it has to maintain its own queue. When DSD is done transmitting the data, is would call back into GSD. usbser_tx_cb() callback handler simply wakes up the write thread to check for any new data to transfer. Incoming data When DSD receives new data, it lets GSD know via RX callback. See usbser_rx_cb() how it retrieves the data from DSD's queue by calling USBSER_DS_RX(), processes it and sends upstream. Data processing is the interesting part. If data was received without errors, then it's just a linked list of message blocks (mblk_t), which will be put on the stream's read queue after being processed by usbser_rx_massage_data(). This extra step is required for standards compliance: under certain conditions, a received character '\377' (0xFF) should be read by application as two such characters, i.e. '\377' followed by another '\377'. If a character was received with a framing or parity error, the DSD must pass it it to GSD as an M_BREAK message. The first byte should contain the error code, with the character in the second byte. The GSD then does the right thing for termio in usbser_rx_massage_mbreak(). Flow control Flow control is needed when a receive buffer on either end of communication becomes full and it signals the transmitting end to suspend transmission temporarily, until signalled to resume. There are two types of signalling:
Both types of flow control can be implemented in hardware. However, not all devices support flow control, and because GSD should work with any device, it implements both types in software. Inbound flow control happens when our local queue becomes full (reaches the high watermark) and we want to ask the device to stop transmission. Both hardware and software type of flow control are done in usbser_inbound_flow_ctl(). Outbound flow control happens when the other end asks us to stop transmission. For the hardware type,the GSD detects CTS transition through the status callback, see usbser_status_proc_cb(). When CTS turns 0, an M_STOP message is put on GSD's local write queue; when CTS turns 1, an M_START message is put. usbser_wmsg() then calls usbser_stop() and usbser_start() respectively, to suspend or resume data transmittion. Next time - DSDI. Tags:
Solaris
OpenSolaris
USB
Introduction USB serial adapter is a USB device that tunnels asynchronous serial protocols, such as RS-232 and RS-485, through USB. It allows to add serial lines to computers with insufficient number of built-in serial ports. The most common need for these devices is on laptops to connect other machines' serial consoles, GPS devices, PDAs, etc. Traditionally Solaris supported class devices only, such as printer class or mass storage class. A class driver can support devices from various vendors as long as they follow the class specification. Unfortunately, USB serial devices do not belong to any class: every device vendor has to invent a unique, often proprietary protocol, and therefore requires a separate driver. Eventually a project was funded to support one type of USB serial adapters. We selected the Edgeport series from Inside-Out Networks. After a long break, drivers for Keyspan devices and those based on the Prolific PL2303 chip are coming soon to the next Solaris Express build. Due to the proprietary nature of device protocols, it may take some time for the driver sources to propagate to OpenSolaris. This is the first in a series of articles about writing USB serial drivers for Solaris. Devices A typical USB serial device consists of a USB interface IC, firmware executed by an embedded CPU, and one or more UARTs (Universal Asynchronous Receiver-Transmitter). Firmware can be downloadable or upgradable by the driver. Firmware is responsible for bridging the two hardware interfaces, encoding outgoing and decoding incoming USB packets. At the other end, the driver similarly decodes/encodes USB packets and provides applications with the standard serial port API.
Interfaces While each protocol is unique, functionality is largely the same: set serial parameters, such as baud rate and flow control, get modem status, set control lines, transmit and receive data. All these functions are available to applications via the standard UNIX termio(7I) programming interface. The Solaris terminal subsystem, including the termio interface, is based on the STREAMS framework. STREAMS code is not easy to write, especially serial drivers, due to extreme asynchronicity and everything that it entails. It took some time for se(7D) and su(7D) to stabilize and we're still finding bugs in them once in a while. It comes as no surprise that we decided to put USB termio implementation into a common module used by all USB serial drivers. We call it generic serial driver, GSD. The bottom part of the driver that implements the vendor-specific USB protocol is called device-specific driver, DSD. The interface between these two parts is well-defined, we call it DSDI. DSD in turn talks to the kernel USB framework, also known as USBA. These layers are illustrated by the following diagram:
Note that GSD does not interact with USBA directly. In fact, GSD turned out generic enough to write any kind of serial driver, not just USB: it is possible, for example, to reimplement se(7D) and su(7D) using GSD. The only USB-specific features utilized in GSD are logging and hotplug events. Files
Tags:
Solaris
OpenSolaris
USB
UPDATE 19-Jul-2006: It looks like this blog entry is still getting hits from search engines and the USB FAQ, so here's an update. Since I wrote the entry, the project I said was under way has now been integrated in Nevada Build 36, Solaris Express 4/06 and Solaris 10 6/06 Release. It is mentioned in Dan's what's new blog as: * Hotpluggable drives are now better able to accomodate EFI-labels and device IDs, both of which are very important to supporting ZFS on USB and Firewire disk drives. [6348407] If you use one of these releases or later, the hack described below is not necessary (and as Brian noted, may cause some error messages). END UPDATE ZFS is awesome. I'd bust some zeerleading moves for y'all, but I misplaced my pom poms and it wasn't going to be pretty anyway. Instead I will tell you how to try ZFS on your laptop (or any computer without a spare fixed disk). There is no supported way to create ZFS pools on USB disks yet, although a project is under way to rectify this (you can have limited ZFS functionality using lofi(7D)). What follows is a hack, use it at your own risk. Before we proceed, you should get vold(1M) out of the way: # svcadm disable volfs An important thing to know in this context is that you can't create a pool on removable media devices, i.e. the ones whose storage media can be removed. DVD drives and flash readers are removable media, hard disks are not. So you'll need one or more USB hard disks. Most USB hard disks and IDE-to-USB enclosures should work. Thumb drives are not likely to work. In this experiment I'm using two USB 2.0 disks: a 20GB LaCie and a 40GB IOGEAR. For the reasons that are beyond this blog entry, the Solaris USB driver presents any USB storage device as removable media. A simple command to list removable and non-removable disks is format(1M). Quoting the man page:
Run format with and without -e and notice the difference:
# format
Searching for disks...done
AVAILABLE DISK SELECTIONS:
0. c1d0
c4t0d0 and c5t0d0 are USB disks. What we're going to do is tell the USB driver not to treat hard disks as removable media. This can be done by appending the following line to /kernel/drv/scsa2usb.conf file: attribute-override-list = "vid=* removable=false"; Reboot for these changes to take effect. Now all your USB hard disks are going to be treated as fixed (but you can still hotplug them). You can verify that by running format without -e option - if it still doesn't list your USB disk, most likely it's one of the rare samples that pretends to be removable media and that's a hack for another day. Now you can use these disks just like any fixed disk:
# zpool create usbpool mirror c4t0d0 c5t0d0
# zpool list
NAME SIZE USED AVAIL CAP HEALTH ALTROOT
usbpool 27.8G 33.0K 27.7G 0% ONLINE -
# zpool status
pool: usbpool
state: ONLINE
scrub: none requested
config:
NAME STATE READ WRITE CKSUM
usbpool ONLINE 0 0 0
mirror ONLINE 0 0 0
c4t0d0 ONLINE 0 0 0
c5t0d0 ONLINE 0 0 0
# zfs list
NAME USED AVAIL REFER MOUNTPOINT
usbpool 32K 27.5G 8K /usbpool
Tags:
Solaris
OpenSolaris
USB
ZFS
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||