Writing Device Drivers
Previous Next

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:

  • Control. Control pipes are used primarily to send commands and retrieve status. Control pipes are intended for non-periodic, host-initiated request and response communication of small-sized structured data. Control pipes are bidirectional. The default pipe is a control pipe. See The Default Pipe.

  • Bulk. Bulk pipes are used primarily for data transfer. Bulk pipes offer reliable transportation of large amounts of data. Bulk pipes do not necessarily deliver the data in a timely manner. Bulk pipes are unidirectional.

  • Interrupt. Interrupt pipes offer timely, reliable communication of small amounts of unstructured data. Periodic polling often is started on interrupt-IN pipes. Interrupt-IN pipes return data to the host when the data becomes present on the device. Some devices have interrupt-OUT pipes. Interrupt-OUT pipes transfer data to the device with the same timely, reliable “interrupt pipe” characteristics of interrupt-IN pipes. Interrupt pipes are unidirectional.

  • Isochronous. Isochronous pipes offer a channel for transferring constant-rate, time-relevant data, such as for audio devices. Data is not retried on error. Isochronous pipes are unidirectional.

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:

  • This pipe is shared. Drivers that are operating other interfaces of the same device use the same default control pipe. The USBA 2.0 framework arbitrates this pipe among the different drivers.

  • This pipe cannot be opened, closed, or reset by the client driver. This restriction exists because the pipe is shared.

  • The pipe is autocleared on an exception.

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:

  • USB_PIPE_STATE_IDLE

    • All control and bulk pipes, interrupt-OUT pipes, and isochronous-OUT pipes: No request is in progress.

    • Interrupt-IN and isochronous-IN pipes: No polling is in progress.

  • USB_PIPE_STATE_ACTIVE

    • All control and bulk pipes, interrupt-OUT pipes, and isochronous-OUT pipes: The pipe is transferring data or an I/O request is active.

    • Interrupt-IN and isochronous-IN pipes: Polling is active.

  • USB_PIPE_STATE_ERROR. An error occurred. If this pipe is not the default pipe and if autoclearing is not enabled, then the client driver must call the usb_pipe_reset(9F) function.

  • USB_PIPE_STATE_CLOSING. The pipe is being closed.

  • USB_PIPE_STATE_CLOSED. The pipe is closed.

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.

  • Normal completion. A normal completion callback handler is called to notify of a normally completed transfer.

  • Exception. An exception callback handler is called to notify of an abnormally completed transfer and to process its errors.

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.

  • Callback context. Most callbacks execute in kernel context and usually can block. Some callbacks execute in interrupt context and cannot block. The USB_CB_INTR_CONTEXT flag is set in the callback flags to denote interrupt context. See the usb_callback_flags(9S) man page for more information on callback context and details on blocking.

  • Pipe policy. The pipe policy's hint on concurrent asynchronous operations limits the number of operations that can be run in parallel, including those executed from a callback handler. Blocking on a synchronous operation counts as one operation. See the usb_pipe_open(9F) man page for more information on pipe policy.

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:

  • A usb_pipe_stop_isoc_polling(9F) call is received.

  • A device disconnect is reported through an exception callback.

  • A usb_pipe_close(9F) call is received.

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:

  • The usb_pipe_reset(9F) function resets the pipe and flushes all of its requests. Do this for pipes that are in an error state if autoclearing is not enabled on those pipes. Use usb_pipe_get_state(9F) to determine the state of a pipe.

  • The usb_pipe_drain_reqs(9F) function blocks waiting for all pending requests to complete before continuing. This function can wait indefinitely, or it can time-out after a specified period of time. The usb_pipe_drain_reqs(9F) function neither closes nor flushes the pipe.

Previous Next