Writing Device Drivers

Chapter 20 USB Drivers

This chapter describes how to write a client USB device driver using the USBA 2.0 framework for the Solaris environment. This chapter discusses the following topics:

USB in the Solaris Environment

The Solaris USB architecture includes the USBA 2.0 framework and USB client drivers.

USBA 2.0 Framework

The USBA 2.0 framework is a service layer that presents an abstract view of USB devices to USBA-compliant client drivers. The framework enables USBA-compliant client drivers to manage their USB devices. The USBA 2.0 framework supports the USB 2.0 specification except for high speed isochronous pipes. For information on the USB 2.0 specification, see http://www.usb.org/home.

The USBA 2.0 framework is platform-independent. The Solaris USB architecture is shown in the following figure. The USBA 2.0 framework is the USBA layer in the figure. This layer interfaces through a hardware-independent host controller driver interface to hardware-specific host controller drivers. The host controller drivers access the USB physical devices through the host controllers they manage.

Figure 20–1 Solaris USB Architecture

Diagram shows the flow of control from client and hub
drivers, through the USB Architecture Interfaces, to the controllers and devices.

USB Client Drivers

The USBA 2.0 framework is not a device driver itself. This chapter describes the client drivers shown in Figure 20–1 and Figure 20–2. The client drivers interact with various kinds of USB devices such as mass storage devices, printers, and human interface devices. The hub driver is a client driver that is also a nexus driver. The hub driver enumerates devices on its ports and creates devinfo nodes for those devices and then attaches the client drivers. This chapter does not describe how to write a hub driver.

USB drivers have the same structure as any other Solaris driver. USB drivers can be block drivers, character drivers, or STREAMS drivers. USB drivers follow the calling conventions and use the data structures and routines described in the Solaris OS section 9 man pages. See Intro(9E), Intro(9F), and Intro(9S).

The difference between USB drivers and other Solaris drivers is that USB drivers call USBA 2.0 framework functions to access the device instead of directly accessing the device. The USBA 2.0 framework supplements the standard Solaris DDI routines. See the following figure.

Figure 20–2 Driver and Controller Interfaces

Diagram shows DDI and USBAI functions, different versions
of the USBA framework, and different types of host controllers.

Figure 20–2 shows interfaces in more detail than Figure 20–1 does. Figure 20–2 shows that the USBA is a kernel subsystem into which a client driver can call, just as a client driver can call DDI functions.

Not all systems have all of the host controller interfaces shown in Figure 20–2. OHCI (Open Host Controller Interface) hardware is most prevalent on SPARC systems and third-party USB PCI cards. UHCI (Universal Host Controller Interface) hardware is most prevalent on x86 systems. However, both OHCI and UHCI hardware can be used on any system. When EHCI (Enhanced Host Controller Interface) hardware is present, the EHCI hardware is on the same card and shares the same ports with either OHCI or UHCI.

The host controllers, host controller drivers, and HCDI make up a transport layer that is commanded by the USBA. You cannot directly call into the OHCI, EHCI, or UHCI. You call into them indirectly through the platform-independent USBA interface.

Binding Client Drivers

This section discusses binding a driver to a device. It discusses compatible device names for devices with single interfaces and devices with multiple interfaces.

How USB Devices Appear to the System

A USB device can support multiple configurations. Only one configuration is active at any given time. The active configuration is called the current configuration.

A configuration can have more than one interface, possibly with intervening interface-associations that group two or more interfaces for a function. All interfaces of a configuration are active simultaneously. Different interfaces might be operated by different device drivers.

An interface can represent itself to the host system in different ways by using alternate settings. Only one alternate setting is active for any given interface.

Each alternate setting provides device access through endpoints. Each endpoint has a specific purpose. The host system communicates with the device by establishing a communication channel to an endpoint. This communication channel is called a pipe.

USB Devices and the Solaris Device Tree

If a USB device has one configuration, one interface, and device class zero, the device is represented as a single device node. If a USB device has multiple interfaces, the device is represented as a hierarchical device structure. In a hierarchical device structure, the device node for each interface is a child of the top-level device node. An example of a device with multiple interfaces is an audio device that presents simultaneously to the host computer both an audio control interface and an audio streaming interface. The audio control interface and the audio streaming interface each could be controlled by its own driver.

Compatible Device Names

The Solaris software builds an ordered list of compatible device names for USB binding based on identification information kept within each device. This information includes device class, subclass, vendor ID, product ID, revision, and protocol. See http://www.usb.org/home for a list of USB classes and subclasses.

This name hierarchy enables binding to a general driver if a more device-specific driver is not available. An example of a general driver is a class-specific driver. Device names that begin with usbif designate single interface devices. See Example 20–1 for examples. The USBA 2.0 framework defines all compatible names for a device. Use the prtconf command to display these device names, as shown in Example 20–2.

The following example shows an example of compatible device names for a USB mouse device. This mouse device represents a combined node entirely operated by a single driver. The USBA 2.0 framework gives this device node the names shown in the example, in the order shown.


Example 20–1 USB Mouse Compatible Device Names

  1. 'usb430,100.102'      Vendor 430, product 100, revision 102
  2. 'usb430,100'          Vendor 430, product 100
  3. 'usbif430,class3.1.2' Vendor 430, class 3, subclass 1, protocol 2
  4. 'usbif430,class3.1'   Vendor 430, class 3, subclass 1
  5. 'usbif430,class3'     Vendor 430, class 3
  6. 'usbif,class3.1.2'    Class 3, subclass 1, protocol 2
  7. 'usbif,class3.1'      Class 3, subclass 1
  8. 'usbif,class3'        Class 3

Note that the names in the above example progress from the most specific to the most general. Entry 1 binds only to a particular revision of a specific product from a particular vendor. Entries 3, 4, and 5 are for class 3 devices manufactured by vendor 430. Entries 6, 7, and 8 are for class 3 devices from any vendor. The binding process looks for a match on the name from the top name down. To bind, drivers must be added to the system with an alias that matches one of these names. To get a list of compatible device names to which to bind when you add your driver, check the compatible property of the device in the output from the prtconf -vp command.

The following example shows compatible property lists for a keyboard and a mouse. Use the prtconf -D command to display the bound driver.


Example 20–2 Compatible Device Names Shown by the Print Configuration Command


# prtconf -vD | grep compatible
            compatible: 'usb430,5.200' + 'usb430,5' + 'usbif430,class3.1.1'
+ 'usbif430,class3.1' + 'usbif430,class3' + 'usbif,class3.1.1' +
'usbif,class3.1' + 'usbif,class3'
            compatible: 'usb2222,2071.200' + 'usb2222,2071' +
'usbif2222,class3.1.2' + 'usbif2222,class3.1' + 'usbif2222,class3' +
'usbif,class3.1.2' + 'usbif,class3.1' + 'usbif,class3'

Use the most specific name you can to more accurately identify a driver for a device or group of devices. To bind drivers written for a specific revision of a specific product, use the most specific name match possible. For example, if you have a USB mouse driver written by vendor 430 for revision 102 of their product 100, use the following command to add that driver to the system:

add_drv -n -i '"usb430,100.102"' specific_mouse_driver

To add a driver written for any USB mouse (class 3, subclass 1, protocol 2) from vendor 430, use the following command:

add_drv -n -i '"usbif430,class3.1.2"' more_generic_mouse_driver

If you install both of these drivers and then connect a compatible device, the system binds the correct driver to the connected device. For example, if you install both of these drivers and then connect a vendor 430, model 100, revision 102 device, this device is bound to specific_mouse_driver. If you connect a vendor 430, model 98 device, this device is bound to more_generic_mouse_driver. If you connect a mouse from another vendor, this device also is bound to more_generic_mouse_driver. If multiple drivers are available for a specific device, the driver binding framework selects the driver with the first matching compatible name in the compatible names list.

Devices With Multiple Interfaces

Composite devices are devices that support multiple interfaces. Composite devices have a list of compatible names for each interface. This compatible names list ensures that the best available driver is bound to the interface. The most general multiple interface entry is usb,device.

For a USB audio composite device, the compatible names are as follows:

1. 'usb471,101.100'     Vendor 471, product 101, revision 100
2. 'usb471,101'         Vendor 471, product 101
3. 'usb,device'         Generic USB device

The name usb,device is a compatible name that represents any whole USB device. The usb_mid(7D) driver (USB multiple-interface driver) binds to the usb,device device node if no other driver has claimed the whole device. The usb_mid driver creates a child device node for each interface of the physical device. The usb_mid driver also generates a set of compatible names for each interface. Each of these generated compatible names begins with usbif. The system then uses these generated compatible names to find the best driver for each interface. In this way, different interfaces of one physical device can be bound to different drivers.

For example, the usb_mid driver binds to a multiple-interface audio device through the usb,device node name of that audio device. The usb_mid driver then creates interface-specific device nodes. Each of these interface-specific device nodes has its own compatible name list. For an audio control interface node, the compatible name list might look like the list shown in the following example.


Example 20–3 USB Audio Compatible Device Names

1. 'usbif471,101.100.config1.0' Vend 471, prod 101, rev 100, cnfg 1, iface 0
2. 'usbif471,101.config1.0'     Vend 471, product 101, config 1, interface 0
3. 'usbif471,class1.1.0'        Vend 471, class 1, subclass 1, protocol 0
4. 'usbif471,class1.1'          Vend 471, class 1, subclass 1
5. 'usbif471,class1'            Vend 471, class 1
6. 'usbif,class1.1.0'           Class 1, subclass 1, protocol 0
7. 'usbif,class1.1'             Class 1, subclass 1
8. 'usbif,class1'               Class 1

Use the following command to bind a vendor-specific, device-specific client driver named vendor_model_audio_usb to the vendor-specific, device-specific configuration 1, interface 0 interface compatible name shown in Example 20–3.

add_drv -n -i '"usbif471,101.config1.0"' vendor_model_audio_usb

Use the following command to bind a class driver named audio_class_usb_if_driver to the more general class 1, subclass 1 interface compatible name shown in Example 20–3:

add_drv -n -i '"usbif,class1.1"' audio_class_usb_if_driver

Use the prtconf -D command to show a list of devices and their drivers. In the following example, the prtconf -D command shows that the usb_mid driver manages the audio device. The usb_mid driver is splitting the audio device into interfaces. Each interface is indented under the audio device name. For each interface shown in the indented list, the prtconf -D command shows which driver manages the interface.

audio, instance #0 (driver name: usb_mid)
    sound-control, instance #2 (driver name: usb_ac)
    sound, instance #2 (driver name: usb_as)
    input, instance #8 (driver name: hid)

Checking Device Driver Bindings

The file /etc/driver_aliases contains entries for the bindings that already exist on a system. Each line of the /etc/driver_aliases file shows a driver name, followed by a space, followed by a device name. Use this file to check existing device driver bindings.


Note –

Do not edit the /etc/driver_aliases file manually. Use the add_drv(1M) command to establish a binding. Use the update_drv(1M) command to change a binding.


Basic Device Access

This section describes how to access a USB device and how to register a client driver. This section also discusses the descriptor tree.

Before the Client Driver Is Attached

The following events take place before the client driver is attached:

  1. The PROM (OBP/BIOS) and USBA framework gain access to the device before any client driver is attached.

  2. The hub driver probes devices on each of its hub's ports for identity and configuration.

  3. The default control pipe to each device is opened, and each device is probed for its device descriptor.

  4. Compatible names properties are constructed for each device, using the device and interface descriptors.

The compatible names properties define different parts of the device that can be individually bound to client drivers. Client drivers can bind either to the entire device or to just one interface. See Binding Client Drivers.

The Descriptor Tree

Parsing descriptors involves aligning structure members at natural boundaries and converting the structure members to the endianness of the host CPU. Parsed standard USB configuration descriptors, interface descriptors, and endpoint descriptors are available to the client driver in the form of a hierarchical tree for each configuration. Any raw class-specific or vendor-specific descriptor information also is available to the client driver in the same hierarchical tree.

Call the usb_get_dev_data(9F) function to retrieve the hierarchical descriptor tree. The “SEE ALSO” section of the usb_get_dev_data(9F) man page lists the man pages for each standard USB descriptor. Use the usb_parse_data(9F) function to parse raw descriptor information.

A descriptor tree for a device with two configurations might look like the tree shown in the following figure.

Figure 20–3 A Hierarchical USB Descriptor Tree

Diagram shows a tree of pairs of descriptors for each
interface of a device with two configurations.

The dev_cfg array shown in the above figure contains nodes that correspond to configurations. Each node contains the following information:

The node that represents the second interface of the second indexed configuration is at dev_cfg[1].cfg_if[1] in the diagram. That node contains an array of nodes that represent the alternate settings for that interface. The hierarchy of USB descriptors propagates through the tree. ASCII strings from string descriptor data are attached where the USB specification says these strings exist.

The array of configurations is non-sparse and is indexed by the configuration index. The first valid configuration (configuration 1) is dev_cfg[0]. Interfaces and alternate settings have indices that align with their numbers. Endpoints of each alternate setting are indexed consecutively. The first endpoint of each alternate setting is at index 0.

This numbering scheme makes the tree easy to traverse. For example, the raw descriptor data of endpoint index 0, alternate 0, interface 1, configuration index 1 is at the node defined by the following path:

dev_cfg[1].cfg_if[1].if_alt[0].altif_ep[0].ep_descr

An alternative to using the descriptor tree directly is using the usb_lookup_ep_data(9F) function. The usb_lookup_ep_data(9F) function takes as arguments the interface, alternate, which endpoint, endpoint type, and direction. You can use the usb_lookup_ep_data(9F) function to traverse the descriptor tree to get a particular endpoint. See the usb_get_dev_data(9F) man page for more information.

Registering Drivers to Gain Device Access

Two of the first calls into the USBA 2.0 framework by a client driver are calls to the usb_client_attach(9F) function and the usb_get_dev_data(9F) function. These two calls come from the client driver's attach(9E) entry point. You must call the usb_client_attach(9F) function before you call the usb_get_dev_data(9F) function.

The usb_client_attach(9F) function registers a client driver with the USBA 2.0 framework. The usb_client_attach(9F) function enforces versioning. All client driver source files must start with the following lines:


#define USBDRV_MAJOR_VER        2
#define USBDRV_MINOR_VER        minor-version
#include <sys/usb/usba.h>

The value of minor-version must be less than or equal to USBA_MINOR_VER. The symbol USBA_MINOR_VER is defined in the <sys/usb/usbai.h> header file. The <sys/usb/usbai.h> header file is included by the <sys/usb/usba.h> header file.

USBDRV_VERSION is a macro that generates the version number from USBDRV_MAJOR_VERSION and USBDRV_MINOR_VERSION. The second argument to usb_client_attach() must be USBDRV_VERSION. The usb_client_attach() function fails if the second argument is not USBDRV_VERSION or if USBDRV_VERSION reflects an invalid version. This restriction ensures programming interface compatibility.

The usb_get_dev_data() function returns information that is required for proper USB device management. For example, the usb_get_dev_data() function returns the following information:

The call to the usb_get_dev_data() function is mandatory. Calling usb_get_dev_data() is the only way to retrieve the default control pipe and retrieve the iblock_cookie required for mutex initialization.

After calling usb_get_dev_data(), the client driver's attach(9E) routine typically copies the desired descriptors and data from the descriptor tree to the driver's soft state. Endpoint descriptors copied to the soft state are used later to open pipes to those endpoints. The attach(9E) routine usually calls usb_free_descr_tree(9F) to free the descriptor tree after copying descriptors. Alternatively, you might choose to keep the descriptor tree and not copy the descriptors.

Specify one of the following three parse levels to the usb_get_dev_data(9F) function to request the breadth of the descriptor tree you want returned. You need greater tree breadth if your driver needs to bind to more of the device.

The client driver's detach(9E) routine must call the usb_free_dev_data(9F) function to release all resources allocated by theusb_get_dev_data() function. The usb_free_dev_data() function accepts handles where the descriptor tree has already been freed with the usb_free_descr_tree() function. The client driver's detach() routine also must call the usb_client_detach(9F) function to release all resources allocated by the usb_client_attach(9F) function.

Device Communication

USB devices operate by passing requests through communication channels called pipes. Pipes must be open before you can submit requests. Pipes also can be flushed, queried, and closed. This section discusses pipes, data transfers and callbacks, and data requests.

USB Endpoints

The four kinds of pipes that communicate with the four kinds of USB endpoints are:

See Chapter 5 of the USB 2.0 specification or see Requests for more information on the transfer types that correspond to these endpoints.

The Default Pipe

Each USB device has a special control endpoint called the default endpoint. Its communication channel is called the default pipe. Most, if not all, device setup is done through this pipe. Many USB devices have this pipe as their only control pipe.

The usb_get_dev_data(9F) function provides the default control pipe to the client driver. This pipe is pre-opened to accommodate any special setup needed before opening other pipes. This default control pipe is special in the following ways:

Other pipes, including other control pipes, must be opened explicitly and are exclusive-open only.

Pipe States

Pipes are in one of the following states:

Call the usb_pipe_get_state(9F) function to retrieve the state of a pipe.

Opening Pipes

To open a pipe, pass to the usb_pipe_open(9F) function the endpoint descriptor that corresponds to the pipe you want to open. Use the usb_get_dev_data(9F) and usb_lookup_ep_data(9F) functions to retrieve the endpoint descriptor from the descriptor tree. The usb_pipe_open(9F) function returns a handle to the pipe.

You must specify a pipe policy when you open a pipe. The pipe policy contains an estimate of the number of concurrent asynchronous operations that require separate threads that will be needed for this pipe. An estimate of the number of threads is the number of parallel operations that could occur during a callback. The value of this estimate must be at least 2. See the usb_pipe_open(9F) man page for more information on pipe policy.

Closing Pipes

The driver must use the usb_pipe_close(9F) function to close pipes other than the default pipe. The usb_pipe_close(9F) function enables all remaining requests in the pipe to complete. The function then allows one second for all callbacks of those requests to complete.

Data Transfer

For all pipe types, the programming model is as follows:

  1. Allocate a request.

  2. Submit the request using one of the pipe transfer functions. See the usb_pipe_bulk_xfer(9F), usb_pipe_ctrl_xfer(9F), usb_pipe_intr_xfer(9F), and usb_pipe_isoc_xfer(9F) man pages.

  3. Wait for completion notification.

  4. Free the request.

See Requests for more information on requests. The following sections describe the features of different request types.

Synchronous and Asynchronous Transfers and Callbacks

Transfers are either synchronous or asynchronous. Synchronous transfers block until they complete. Asynchronous transfers callback into the client driver when they complete. Most transfer functions called with the USB_FLAGS_SLEEP flag set in the flags argument are synchronous.

Continuous transfers such as polling and isochronous transfers cannot be synchronous. Calls to transfer functions for continuous transfers made with the USB_FLAGS_SLEEP flag set block only to wait for resources before the transfer begins.

Synchronous transfers are the most simple transfers to set up because synchronous transfers do not require any callback functions. Synchronous transfer functions return a transfer start status, even though synchronous transfer functions block until the transfer is completed. Upon completion, you can find additional information about the transfer status in the completion reason field and callback flags field of the request. Completion reasons and callback flags fields are discussed below.

If the USB_FLAGS_SLEEP flag is not specified in the flags argument, that transfer operation is asynchronous. The exception to this rule are isochronous transfers. Asynchronous transfer operations set up and start the transfer, and then return before the transfer is complete. Asynchronous transfer operations return a transfer start status. The client driver receives transfer completion status through callback handlers.

Callback handlers are functions that are called when asynchronous transfers complete. Do not set up an asynchronous transfer without callbacks. The two types of callback handlers are normal completion handlers and exception handlers. You can specify one handler to be called in both of these cases.

Both completion handlers and exception handlers receive the transfer's request as an argument. Exception handlers use the completion reason and callback status in the request to find out what happened. The completion reason (usb_cr_t) indicates how the original transaction completed. For example, a completion reason of USB_CR_TIMEOUT indicates that the transfer timed out. As another example, if a USB device is removed while in use, client drivers might receive USB_CR_DEV_NOT_RESP as the completion reason on their outstanding requests. The callback status (usb_cb_flags_t) indicates what the USBA framework did to remedy the situation. For example, a callback status of USB_CB_STALL_CLEARED indicates that the USBA framework cleared a functional stall condition. See the usb_completion_reason(9S) man page for more information on completion reasons. See the usb_callback_flags(9S) man page for more information on callback status flags.

The context of the callback and the policy of the pipe on which the requests are run limit what you can do in the callback.

Requests

This section discusses request structures and allocating and deallocating different types of requests.

Request Allocation and Deallocation

Requests are implemented as initialized request structures. Each different endpoint type takes a different type of request. Each type of request has a different request structure type. The following table shows the structure type for each type of request. This table also lists the functions to use to allocate and free each type of structure.

Table 20–1 Request Initialization

Pipe or Endpoint Type 

Request Structure 

Request Structure Allocation Function 

Request Structure Free Function 

Control 

usb_ctrl_req_t (see the usb_ctrl_request(9S) man page)

usb_alloc_ctrl_req(9F)

usb_free_ctrl_req(9F)

Bulk 

usb_bulk_req_t (see the usb_bulk_request(9S) man page)

usb_alloc_bulk_req(9F)

usb_free_bulk_req(9F)

Interrupt 

usb_intr_req_t (see the usb_intr_request(9S) man page)

usb_alloc_intr_req(9F)

usb_free_intr_req(9F)

Isochronous 

usb_isoc_req_t (see the usb_isoc_request(9S) man page)

usb_alloc_isoc_req(9F)

usb_free_isoc_req(9F)

The following table lists the transfer functions that you can use for each type of request.

Table 20–2 Request Transfer Setup

Pipe or Endpoint Type 

Transfer Functions 

Control 

usb_pipe_ctrl_xfer(9F), usb_pipe_ctrl_xfer_wait(9F)

Bulk 

usb_pipe_bulk_xfer(9F)

Interrupt 

usb_pipe_intr_xfer(9F), usb_pipe_stop_intr_polling(9F)

Isochronous 

usb_pipe_isoc_xfer(9F), usb_pipe_stop_isoc_polling(9F)

Use the following procedure to allocate and deallocate a request:

  1. Use the appropriate allocation function to allocate a request structure for the type of request you need. The man pages for the request structure allocation functions are listed in Table 20–1.

  2. Initialize any fields you need in the structure. See Request Features and Fields or the appropriate request structure man page for more information. The man pages for the request structures are listed in Table 20–1.

  3. When the data transfer is complete, use the appropriate free function to free the request structure. The man pages for the request structure free functions are listed in Table 20–1.

Request Features and Fields

Data for all requests is passed in message blocks so that the data is handled uniformly whether the driver is a STREAMS, character, or block driver. The message block type, mblk_t, is described in the mblk(9S) man page. The DDI offers several routines for manipulating message blocks. Examples include allocb(9F) and freemsg(9F). To learn about other routines for manipulating message blocks, see the “SEE ALSO” sections of the allocb(9F) and freemsg(9F) man pages. Also see the STREAMS Programming Guide.

The following request fields are included in all transfer types. In each field name, the possible values for xxxx are: ctrl, bulk, intr, or isoc.

xxxx_client_private

This field value is a pointer that is intended for internal data to be passed around the client driver along with the request. This pointer is not used to transfer data to the device. 

xxxx_attributes

This field value is a set of transfer attributes. While this field is common to all request structures, the initialization of this field is somewhat different for each transfer type. See the appropriate request structure man page for more information. These man pages are listed in Table 20–1. See also the usb_request_attributes(9S) man page.

xxxx_cb

This field value is a callback function for normal transfer completion. This function is called when an asynchronous transfer completes without error. 

xxxx_exc_cb

This field value is a callback function for error handling. This function is called only when asynchronous transfers complete with errors. 

xxxx_completion_reason

This field holds the completion status of the transfer itself. If an error occurred, this field shows what went wrong. See the usb_completion_reason(9S) man page for more information. This field is updated by the USBA 2.0 framework.

xxxx_cb_flags

This field lists the recovery actions that were taken by the USBA 2.0 framework before calling the callback handler. The USB_CB_INTR_CONTEXT flag indicates whether a callback is running in interrupt context. See the usb_callback_flags(9S) man page for more information. This field is updated by the USBA 2.0 framework.

The following sections describe the request fields that are different for the four different transfer types. These sections describe how to initialize these structure fields. These sections also describe the restrictions on various combinations of attributes and parameters.

Control Requests

Use control requests to initiate message transfers down a control pipe. You can set up transfers manually, as described below. You can also set up and send synchronous transfers using the usb_pipe_ctrl_xfer_wait(9F) wrapper function.

The client driver must initialize the ctrl_bmRequestType, ctrl_bRequest, ctrl_wValue, ctrl_wIndex, and ctrl_wLength fields as described in the USB 2.0 specification.

The ctrl_data field of the request must be initialized to point to a data buffer. The usb_alloc_ctrl_req(9F) function initializes this field when you pass a positive value as the buffer len. The buffer must, of course, be initialized for any outbound transfers. In all cases, the client driver must free the request when the transfer is complete.

Multiple control requests can be queued. Queued requests can be a combination of synchronous and asynchronous requests.

The ctrl_timeout field defines the maximum wait time for the request to be processed, excluding wait time on the queue. This field applies to both synchronous and asynchronous requests. The ctrl_timeout field is specified in seconds.

The ctrl_exc_cb field accepts the address of a function to call if an exception occurs. The arguments of this exception handler are specified in the usb_ctrl_request(9S) man page. The second argument of the exception handler is the usb_ctrl_req_t structure. Passing the request structure as an argument allows the exception handler to check the ctrl_completion_reason and ctrl_cb_flags fields of the request to determine the best recovery action.

The USB_ATTRS_ONE_XFER and USB_ATTRS_ISOC_* flags are invalid attributes for all control requests. The USB_ATTRS_SHORT_XFER_OK flag is valid only for host-bound requests.

Bulk Requests

Use bulk requests to send data that is not time-critical. Bulk requests can take several USB frames to complete, depending on overall bus load.

All requests must receive an initialized message block. See the mblk(9S) man page for a description of the mblk_t message block type. This message block either supplies the data or stores the data, depending on the transfer direction. Refer to the usb_bulk_request(9S) man page for more details.

The USB_ATTRS_ONE_XFER and USB_ATTRS_ISOC_* flags are invalid attributes for all bulk requests. The USB_ATTRS_SHORT_XFER_OK flag is valid only for host-bound requests.

The usb_pipe_get_max_bulk_transfer_size(9F) function specifies the maximum number of bytes per request. The value retrieved can be the maximum value used in the client driver's minphys(9F) routine.

Multiple bulk requests can be queued.

Interrupt Requests

Interrupt requests typically are for periodic inbound data. Interrupt requests periodically poll the device for data. However, the USBA 2.0 framework supports one-time inbound interrupt data requests, as well as outbound interrupt data requests. All interrupt requests can take advantage of the USB interrupt transfer features of timeliness and retry.

The USB_ATTRS_ISOC_* flags are invalid attributes for all interrupt requests. The USB_ATTRS_SHORT_XFER_OK and USB_ATTRS_ONE_XFER flags are valid only for host-bound requests.

Only one-time polls can be done as synchronous interrupt transfers. Specifying the USB_ATTRS_ONE_XFER attribute in the request results in a one-time poll.

Periodic polling is started as an asynchronous interrupt transfer. An original interrupt request is passed to usb_pipe_intr_xfer(9F). When polling finds new data to return, a new usb_intr_req_t structure is cloned from the original and is populated with an initialized data block. When allocating the request, specify zero for the len argument to the usb_alloc_intr_req(9F) function. The len argument is zero because the USBA 2.0 framework allocates and fills in a new request with each callback. After you allocate the request structure, fill in the intr_len field to specify the number of bytes you want the framework to allocate with each poll. Data beyond intr_len bytes is not returned.

The client driver must free each request it receives. If the message block is sent upstream, decouple the message block from the request before you send the message block upstream. To decouple the message block from the request, set the data pointer of the request to NULL. Setting the data pointer of the request to NULL prevents the message block from being freed when the request is deallocated.

Call the usb_pipe_stop_intr_polling(9F) function to cancel periodic polling. When polling is stopped or the pipe is closed, the original request structure is returned through an exception callback. This returned request structure has its completion reason set to USB_CR_STOPPED_POLLING.

Do not start polling while polling is already in progress. Do not start polling while a call to usb_pipe_stop_intr_polling(9F) is in progress.

Isochronous Requests

Isochronous requests are for streaming, constant-rate, time-relevant data. Retries are not made on errors. Isochronous requests have the following request-specific fields:

isoc_frame_no

Specify this field when the overall transfer must start from a specific frame number. The value of this field must be greater than the current frame number. Use usb_get_current_frame_number(9F) to find the current frame number. Note that the current frame number is a moving target. For low-speed and full-speed buses, the current frame is new each millisecond. For high-speed buses, the current frame is new each 0.125 millisecond. Set the USB_ATTR_ISOC_START_FRAME attribute so that the isoc_frame_no field is recognized.

To ignore this frame number field and start as soon as possible, set the USB_ATTR_ISOC_XFER_ASAP flag.

isoc_pkts_count

This field is the number of packets in the request. This value is bounded by the value returned by the usb_get_max_pkts_per_isoc_request(9F) function and by the size of the isoc_pkt_descr array (see below). The number of bytes transferable with this request is equal to the product of this isoc_pkts_count value and the wMaxPacketSize value of the endpoint.

isoc_pkts_length

This field is the sum of the lengths of all packets of the request. This value is set by the initiator. This value should be set to zero so that the sum of isoc_pkts_length in the isoc_pkt_descr list will be used automatically and no check will be applied to this element.

isoc_error_count

This field is the number of packets that completed with errors. This value is set by the USBA 2.0 framework. 

isoc_pkt_descr

This field points to an array of packet descriptors that define how much data to transfer per packet. For an outgoing request, this value defines a private queue of sub-requests to process. For an incoming request, this value describes how the data arrived in pieces. The client driver allocates these descriptors for outgoing requests. The framework allocates and initializes these descriptors for incoming requests. Descriptors in this array contain framework-initialized fields that hold the number of bytes actually transferred and the status of the transfer. See the usb_isoc_request(9S) man page for more details.

All requests must receive an initialized message block. This message block either supplies the data or stores the data. See the mblk(9S) man page for a description of the mblk_t message block type.

The USB_ATTR_ONE_XFER flag is an illegal attribute because the system decides how to vary the amounts of data through available packets. The USB_ATTR_SHORT_XFER_OK flag is valid only on host-bound data.

The usb_pipe_isoc_xfer(9F) function makes all isochronous transfers asynchronous, regardless of whether the USB_FLAGS_SLEEP flag is set. All isochronous input requests start polling.

Call the usb_pipe_stop_isoc_polling(9F) function to cancel periodic polling. When polling is stopped or the pipe is closed, the original request structure is returned through an exception callback. This returned request structure has its completion reason set to USB_CR_STOPPED_POLLING.

Polling continues until one of the following events occurs:

Flushing Pipes

You might need to clean up a pipe after errors, or you might want to wait for a pipe to clear. Use one of the following methods to flush or clear pipes:

Device State Management

Managing a USB device includes accounting for hotplugging, system power management (checkpoint and resume), and device power management. All client drivers should implement the basic state machine shown in the following figure. For more information, see /usr/include/sys/usb/usbai.h.

Figure 20–4 USB Device State Machine

Diagram shows what state the device goes to after each
of seven different events.

This state machine and its four states can be augmented with driver-specific states. Device states 0x80 to 0xff can be defined and used only by client drivers.

Hotplugging USB Devices

USB devices support hotplugging. A USB device can be inserted or removed at any time. The client driver must handle removal and reinsertion of an open device. Use hotplug callbacks to handle open devices. Insertion and removal of closed devices is handled by the attach(9E) and detach(9E) entry points.

Hotplug Callbacks

The USBA 2.0 framework supports the following event notifications:

Client drivers must call usb_register_hotplug_cbs(9F) in their attach(9E) routine to register for event callbacks. Drivers must call usb_unregister_hotplug_cbs(9F) in their detach(9E) routine before dismantling.

Hot Insertion

The sequence of events for hot insertion of a USB device is as follows:

  1. The hub driver, hubd(7D), waits for a port connect status change.

  2. The hubd driver detects a port connect.

  3. The hubd driver enumerates the device, creates child device nodes, and attaches client drivers. Refer to Binding Client Drivers for compatible names definitions.

  4. The client driver manages the device. The driver is in the ONLINE state.

Hot Removal

The sequence of events for hot removal of a USB device is as follows:

  1. The hub driver, hubd(7D), waits for a port connect status change.

  2. The hubd driver detects a port disconnect.

  3. The hubd driver sends a disconnect event to the child client driver. If the child client driver is the hubd driver or the usb_mid(7D) multi-interface driver, then the child client driver propagates the event to its children.

  4. The client driver receives the disconnect event notification in kernel thread context. Kernel thread context enables the driver's disconnect handler to block.

  5. The client driver moves to the DISCONNECTED state. Outstanding I/O transfers fail with the completion reason of device not responding. All new I/O transfers and attempts to open the device node also fail. The client driver is not required to close pipes. The driver is required to save the device and driver context that needs to be restored if the device is reconnected.

  6. The hubd driver attempts to offline the OS device node and its children in bottom-up order.

The following events take place if the device node is not open when the hubd driver attempts to offline the device node:

  1. The client driver's detach(9E) entry point is called.

  2. The device node is destroyed.

  3. The port becomes available for a new device.

  4. The hotplug sequence of events starts over. The hubd driver waits for a port connect status change.

The following events take place if the device node is open when the hubd driver attempts to offline the device node:

  1. The hubd driver puts the offline request in the periodic offline retry queue.

  2. The port remains unavailable for a new device.

If the device node was open when the hubd driver attempted to offline the device node and the user later closes the device node, the hubd driver periodic offlining of that device node succeeds and the following events take place:

  1. The client driver's detach(9E) entry point is called.

  2. The device node is destroyed.

  3. The port becomes available for a new device.

  4. The hotplug sequence of events starts over. The hubd driver waits for a port connect status change.

If the user closes all applications that use the device, the port becomes available again. If the application does not terminate or does not close the device, the port remains unavailable.

Hot Reinsertion

The following events take place if a previously-removed device is reinserted into the same port while the device node of the device is still open:

  1. The hub driver, hubd(7D), detects a port connect.

  2. The hubd driver restores the bus address and the device configuration.

  3. The hubd driver cancels the offline retry request.

  4. The hubd driver sends a connect event to the client driver.

  5. The client driver receives the connect event.

  6. The client driver determines whether the new device is the same as the device that was previously connected. The client driver makes this determination first by comparing device descriptors. The client driver might also compare serial numbers and configuration descriptor clouds.

The following events might take place if the client driver determines that the current device is not the same as the device that was previously connected:

  1. The client driver might issue a warning message to the console.

  2. The user might remove the device again. If the user removes the device again, the hot remove sequence of events starts over. The hubd driver detects a port disconnect. If the user does not remove the device again, the following events take place:

    1. The client driver remains in the DISCONNECTED state, failing all requests and opens.

    2. The port remains unavailable. The user must close and disconnect the device to free the port.

    3. The hotplug sequence of events starts over when the port is freed. The hubd driver waits for a port connect status change.

The following events might take place if the client driver determines that the current device is the same as the device that was previously connected:

  1. The client driver might restore its state and continue normal operation. This policy is up to the client driver. Audio speakers are a good example where the client driver should continue.

  2. If it is safe to continue using the reconnected device, the hotplug sequence of events starts over. The hubd driver waits for a port connect status change. The device is in service once again.

Power Management

This section discusses device power management and system power management.

Device power management manages individual USB devices depending on their I/O activity or idleness.

System power management uses checkpoint and resume to checkpoint the state of the system into a file and shut down the system completely. (Checkpoint is sometimes called “system suspend.”) The system is resumed to its pre-suspend state when the system is powered up again.

Device Power Management

The following summary lists what your driver needs to do to power manage a USB device. A more detailed description of power management follows this summary.

  1. Create power management components during attach(9E). See the usb_create_pm_components(9F) man page.

  2. Implement the power(9E) entry point.

  3. Call pm_busy_component(9F) and pm_raise_power(9F) before accessing the device.

  4. Call pm_idle_component(9F) when finished accessing the device.

The USBA 2.0 framework supports four power levels as specified by the USB interface power management specification. See /usr/include/sys/usb/usbai.h for information on mapping USB power levels to operating system power levels.

The hubd driver suspends the port when the device goes to the USB_DEV_OS_PWR_OFF state. The hubd driver resumes the port when the device goes to the USB_DEV_OS_PWR_1 state and above. Note that port suspend is different from system suspend. In port suspend, only the USB port is shut off. System suspend is defined in System Power Management.

The client driver might choose to enable remote wakeup on the device. See the usb_handle_remote_wakeup(9F) man page. When the hubd driver sees a remote wakeup on a port, the hubd driver completes the wakeup operation and calls pm_raise_power(9F) to notify the child.

The following figure shows the relationship between the different pieces of power management.

Figure 20–5 USB Power Management

Diagram shows when to employ two different power management
schemes.

The driver can implement one of the two power management schemes described at the bottom of Figure 20–5. The passive scheme is simpler than the active scheme because the passive scheme does not do power management during device transfers.

Active Power Management

This section describes the functions you need to use to implement the active power management scheme.

Do the following work in the attach(9E) entry point for your driver:

  1. Call usb_create_pm_components(9F).

  2. Optionally call usb_handle_remote_wakeup(9F) with USB_REMOTE_WAKEUP_ENABLE as the second argument to enable a remote wakeup on the device.

  3. Call pm_busy_component(9F).

  4. Call pm_raise_power(9F) to take power to the USB_DEV_OS_FULL_PWR level.

  5. Communicate with the device to initialize the device.

  6. Call pm_idle_component(9F).

Do the following work in the detach(9E) entry point for your driver:

  1. Call pm_busy_component(9F).

  2. Call pm_raise_power(9F) to take power to the USB_DEV_OS_FULL_PWR level.

  3. If you called the usb_handle_remote_wakeup(9F) function in your attach(9E) entry point, call usb_handle_remote_wakeup(9F) here with USB_REMOTE_WAKEUP_DISABLE as the second argument.

  4. Communicate with the device to cleanly shut down the device.

  5. Call pm_lower_power(9F) to take power to the USB_DEV_OS_PWR_OFF level.

    This is the only time a client driver calls pm_lower_power(9F).

  6. Call pm_idle_component(9F).

When a driver thread wants to start I/O to the device, that thread does the following tasks:

  1. Call pm_busy_component(9F).

  2. Call pm_raise_power(9F) to take power to the USB_DEV_OS_FULL_PWR level.

  3. Begin the I/O transfer.

The driver calls pm_idle_component(9F) when the driver receives notice that an I/O transfer has completed.

In the power(9E) entry point for your driver, check whether the power level to which you are transitioning is valid. You might also need to account for different threads calling into power(9E) at the same time.

The power(9E) routine might be called to take the device to the USB_DEV_OS_PWR_OFF state if the device has been idle for some time or the system is shutting down. This state corresponds to the PWRED_DWN state shown in Figure 20–4. If the device is going to the USB_DEV_OS_PWR_OFF state, do the following work in your power(9E) routine:

  1. Put all open pipes into the idle state. For example, stop polling on the interrupt pipe.

  2. Save any device or driver context that needs to be saved.

    The port to which the device is connected is suspended after the call to power(9E) completes.

The power(9E) routine might be called to power on the device when either a device-initiated remote wakeup or a system-initiated wakeup is received. Wakeup notices occur after the device has been powered down due to extended idle time or system suspend. If the device is going to the USB_DEV_OS_PWR_1 state or above, do the following work in your power(9E) routine:

  1. Restore any needed device and driver context.

  2. Restart activity on the pipe that is appropriate to the specified power level. For example, start polling on the interrupt pipe.

If the port to which the device is connected was previously suspended, that port is resumed before power(9E) is called.

Passive Power Management

The passive power management scheme is simpler than the active power management scheme described above. In this passive scheme, no power management is done during transfers. To implement this passive scheme, call pm_busy_component(9F) and pm_raise_power(9F) when you open the device. Then call pm_idle_component(9F) when you close the device.

System Power Management

System power management consists of turning off the entire system after saving its state, and restoring the state after the system is turned back on. This process is called CPR (checkpoint and resume). USB client drivers operate the same way that other client drivers operate with respect to CPR. To suspend a device, the driver's detach(9E) entry point is called with a cmd argument of DDI_SUSPEND. To resume a device, the driver's attach(9E) entry point is called with a cmd argument of DDI_RESUME. When you handle the DDI_SUSPEND command in your detach(9E) routine, clean up device state and clean up driver state as much as necessary for a clean resume later. (Note that this corresponds to the SUSPENDED state in Figure 20–4.) When you handle the DDI_RESUME command in your attach(9E) routine, always take the device to full power to put the system in sync with the device.

For USB devices, suspend and resume are handled similarly to a hotplug disconnect and reconnect (see Hotplugging USB Devices). An important difference between CPR and hotplugging is that with CPR the driver can fail the checkpoint process if the device is not in a state from which it can be suspended. For example, the device cannot be suspended if the device has an error recovery in progress. The device also cannot be suspended if the device is busy and cannot be stopped safely.

Serialization

In general, a driver should not call USBA functions while the driver is holding a mutex. Therefore, race conditions in a client driver can be difficult to prevent.

Do not allow normal operational code to run simultaneously with the processing of asynchronous events such as a disconnect or CPR. These types of asynchronous events normally clean up and dismantle pipes and could disrupt the normal operational code.

One way to manage race conditions and protect normal operational code is to write a serialization facility that can acquire and release an exclusive-access synchronization object. You can write the serialization facility in such a way that the synchronization object is safe to hold through calls to USBA functions. The usbskel sample driver demonstrates this technique. See Sample USB Device Driver for information on the usbskel driver.

Utility Functions

This section describes several functions that are of general use.

Device Configuration Facilities

This section describes functions related to device configuration.

Getting Interface Numbers

If you are using a multiple-interface device where the usb_mid(7D) driver is making only one of its interfaces available to the calling driver, you might need to know the number of the interface to which the calling driver is bound. Use the usb_get_if_number(9F) function to do any of the following tasks:

Managing Entire Devices

If a driver manages an entire composite device, that driver can bind to the entire device by using a compatible name that contains vendor ID, product ID, and revision ID. A driver that is bound to an entire composite device must manage all the interfaces of that device as a nexus driver would. In general, you should not bind your driver to an entire composite device. Instead, you should use the generic multiple-interface driver usb_mid(7D).

Use the usb_owns_device(9F) function to determine whether a driver owns an entire device. The device might be a composite device. The usb_owns_device(9F) function returns TRUE if the driver owns the entire device.

Multiple-Configuration Devices

USB devices make only a single configuration available to the host at any particular time. Most devices support only a single configuration. However, a few USB devices support multiple configurations.

Any device that has multiple configurations is placed into the first configuration for which a driver is available. When seeking a match, device configurations are considered in numeric order. If no matching driver is found, the device is set to the first configuration. In this case, the usb_mid driver takes over the device and splits the device into interface nodes. Use the usb_get_cfg(9F) function to return the current configuration of a device.

You can use either of the following two methods to request a different configuration. Using either of these two methods to modify the device configuration ensures that the USBA module remains in sync with the device.


Caution – Caution –

Do not change the device configuration by doing a SET_CONFIGURATION USB request manually. Using a SET_CONFIGURATION request to change the configuration is not supported.


Modifying or Getting the Alternate Setting

A client driver can call the usb_set_alt_if(9F) function to change the selected alternate setting of the currently selected interface. Be sure to close all pipes that were opened explicitly. When switching alternate settings, the usb_set_alt_if(9F) function verifies that only the default pipe is open. Be sure the device is settled before you call usb_set_alt_if(9F).

Changing the alternate setting can affect which endpoints and which class-specific and vendor-specific descriptors are available to the driver. See The Descriptor Tree for more information about endpoints and descriptors.

Call the usb_get_alt_if(9F) function to retrieve the number of the current alternate setting.


Note –

When you request a new alternate setting, a new configuration, or a new interface, all pipes except the default pipe to the device must be closed. This is because changing an alternate setting, a configuration, or an interface changes the mode of operation of the device. Also, changing an alternate setting, a configuration, or an interface changes the device's presentation to the system.


Other Utility Functions

This section describes other functions that are useful in USB device drivers.

Retrieving a String Descriptor

Call the usb_get_string_descr(9F) function to retrieve a string descriptor given its index. Some configuration, interface, or device descriptors have string IDs associated with them. Such descriptors contain string index fields with nonzero values. Pass a string index field value to the usb_get_string_descr(9F) to retrieve the corresponding string.

Pipe Private Data Facility

Each pipe has one pointer of space set aside for the client driver's private use. Use the usb_pipe_set_private(9F) function to install a value. Use the usb_pipe_get_private(9F) function to retrieve the value. This facility is useful in callbacks, when pipes might need to bring their own client-defined state to the callback for specific processing.

Clearing a USB Condition

Use the usb_clr_feature(9F) function to do the following tasks:

Getting Device, Interface, or Endpoint Status

Use the usb_get_status(9F) function to issue a USB GET_STATUS request to retrieve the status of a device, interface, or endpoint.

Getting the Bus Address of a Device

Use the usb_get_addr(9F) function to get the USB bus address of a device for debugging purposes. This address maps to a particular USB port.

Sample USB Device Driver

This section describes a template USB device driver that uses the USBA 2.0 framework for the Solaris environment. This driver demonstrates many of the features discussed in this chapter. This template or skeleton driver is named usbskel.

The usbskel driver is a template that you can use to start your own USB device driver. The usbskel driver demonstrates the following features:

This usbskel driver is available on Sun's web site at http://www.sun.com/bigadmin/software/usbskel/.

For source for additional USB drivers, see the OpenSolaris web site. Go to http://hub.opensolaris.org/bin/view/Main/, and click “Source Browser” in the menu on the left side of the page.