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:
Allocate a request.
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.
Wait for completion notification.
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
The following table lists the transfer functions that you can use for each
type of request.
Table 20-2 Request Transfer Setup
Use the following procedure to allocate and deallocate a request:
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.
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.
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.