|USBA_HCDI(9E)||Driver Entry Points||USBA_HCDI(9E)|
This describes private interfaces that are not part of the stable DDI. This may be removed or changed at any time.
hcdi drivers are part of the illumos USB Architecture (USBA). The usba(7D) driver provides support for many of the surrounding needs of an hcdi driver and requires that such drivers implement a specific operations vector, usba_hcdi_ops(9S). These functions cover everything from initialization to performing I/O to USB devices on behalf of client device drivers.
The latter description describes the maximum theoretical speed of a given device. For example, a super-speed device theoretically caps out around 5 Gbit/s, whereas a low-speed device caps out at 1.5 Mbit/s.
In general, each speed usually corresponds to a specific USB protocol generation. For example, all USB 3.0 devices are super-speed devices. All 'high-speed' devices are USB 2.x devices. Full-speed devices are special in that they can either be USB 1.x or USB 2.x devices. Low-speed devices are only a USB 1.x thing, they did not jump the fire line to USB 2.x.
USB 3.0 devices and ports generally have the wiring for both USB 2.0 and USB 3.0. When a USB 3.0 device is plugged into a USB 2.0 port or hub, then it will report its version as USB 2.1, to indicate that it is actually a USB 3.0 device.
A given USB device is made up of endpoints. A request, or transfer, is made to a specific USB endpoint. These endpoints can provide different services and have different expectations around the size of the data that'll be used in a given request and the periodicity of requests. Endpoints themselves are either used to make one-shot requests, for example, making requests to a mass storage device for a given sector, or for making periodic requests where you end up polling on the endpoint, for example, polling on a USB keyboard for keystrokes.
Each endpoint encodes two different pieces of information: a direction and a type. There are two different directions: IN and OUT. These refer to the general direction that data moves relative to the operating system. For example, an IN transfer transfers data in to the operating system, from the device. An OUT transfer transfers data from the operating system, out to the device.
There are four different kinds of endpoints:
To find out information about the endpoints, USB devices have a series of descriptors that cover different aspects of the device. For example, there are endpoint descriptors which cover the properties of endpoints such as the maximum packet size or polling interval.
Descriptors exist at all levels of USB. For example, there are general descriptors for every device. The USB device descriptor is described in usb_dev_descr(9S). Host controllers will look at these descriptors to ensure that they program the device correctly; however, they are more often used by client device drivers. There are also descriptors that exist at a class level. For example, the hub class has a class-specific descriptor which describes properties of the hub. That information is requested for and used by the hub driver.
All of the different descriptors are gathered by the system and placed into a tree, with device descriptors, configurations, endpoints, and more. Client device drivers gain access to this tree and then use them to then open endpoints, which are called pipes in USBA (and some revisions of the USB specification).
Each pipe gives access to a specific endpoint on the device which can be used to perform transfers of a specific type and direction. For example, a mass storage device often has three different endpoints, the default control endpoint (which every device has), a Bulk-IN endpoint, and a Bulk-OUT endpoint. The device driver ends up with three open pipes. One to the default control endpoint to configure the device, and then the other two are used to perform I/O.
These routines translate more or less directly into calls to a host controller driver. A request to open a pipe takes an endpoint descriptor that describes the properties of the pipe, and the host controller driver goes through and does any work necessary to allow the client device driver to access it. Once the pipe is open, it either makes one-shot transfers specific to the transfer type or it starts performing a periodic poll of an endpoint.
All of these different actions translate into requests to the host controller. The host controller driver itself is in charge of making sure that all of the required resources for polling are allocated with a request and then proceed to give the driver's periodic callbacks.
For each of the different operations described above, there is a corresponding entry in usba_hcdi_ops(9S). For example, open an endpoint, the host controller has to implement usba_hcdi_pipe_open(9E) and for each transfer type, there is a different transfer function. One example is usba_hcdi_pipe_bulk_xfer(9E). See usba_hcdi_ops(9S) for a full list of the different function endpoints.
To initialize a device driver with the USBA, it must first call usba_alloc_hcdi_ops(9F). This provides a device driver with the usba_hcdi_ops(9S) structure that it must fill out. Please see usba_hcdi_ops(9S) for instructions on how it should be filled out. Once filled out, the driver should call usba_hcdi_register(9F).
If the call to register fails for whatever reason, the device driver should fail its attach(9E) entry point. After this call successfully completes, the driver should assume that any of the functions it registered with the call to usba_hcdi_register(9F) will be called at this point.
This device descriptor should be a packed descriptor that is the same that would be read off of the device. The device descriptor should match a hub of a USB generation equivalent to the maximum speed of the device. For example, a USB 3.0 host controller would use a USB 3.0 hub's device descriptor. Similarly, a USB 2.0 host controller would use a USB 2.0 hub's device descriptor.
The descriptor first starts with a USB configuration descriptor, as defined in usb_cfg_descr(9S). It is then followed by an interface descriptor. The definition for it can be found in usb_if_descr(9S). Next is the endpoint descriptor for the single Interrupt-IN endpoint that all hubs have as defined in usb_ep_descr(9S). Finally, any required companion descriptors should be used. For example, a USB 3.x hub will have a usb_ep_ss_comp_descr(9S) appended to the structure.
Note, that the structure needs to be packed, as though it were read from a device. The structures types referenced in usb_cfg_descr(9S), usb_if_descr(9S), usb_ep_descr(9S), and usb_ep_ss_comp_descr(9S) are not packed for this purpose. They should not be used as they have gaps added by the compiler for alignment.
Once assembled, the device driver should call usba_hubdi_bind_root_hub(9F). This will cause an instance of the hubd(7D) driver to be attached and associated with the root controller. As such, driver writers need to ensure that all initialization is done prior to loading the root hub. Once successfully loaded, driver writers should assume that they'll get other calls into the driver's operation vector before the call to usba_hubdi_bind_root_hub(9F).
If the call to
failed for whatever reason, the driver should unregister from USBA (see the
next section), unwind all of the resources it has allocated, and return
Otherwise, at this point it's safe to assume that the instance of
the device has initialized successfully and the driver should return
To unbind the root hub, the instance of the driver should call
If for some reason that function does not return
USB_SUCCESS, then the device driver should fail the call
to detach(9E) and return
Once the root hub has been unbound, the device driver can continue by removing its hcdi registration with USBA. To do this, the driver should call usba_hcdi_unregister(9F). As this call always succeeds, at this point, it is safe for the driver to tear down all the rest of its resources and successfully detach.
hcdi drivers must not store any data with ddi_get_driver_private(9F). This private data is used by USBA. If it has been called before the device registers, then it will fail to register successfully with the USBA. However, setting it after that point will corrupt the state of the USBA and likely lead to data corruption and crashes.
Similarly, part of the minor number space is utilized to represent
various devices like the root hub. Whenever a device driver is presented
with a dev_t and it's trying to extract the minor
number, it must take into account the constant
HUBD_IS_ROOT_HUB. The following shows how to perform
this, given a dev_t called
minor_t minor = getminor(dev) & ~HUBD_IS_ROOT_HUB;
In the dev_ops(9S) structure, the following members have special significance:
The following members of the cb_ops(9S) operations vector must be implemented and set:
If the device driver wishes to have private ioctls, it may check the ioctl command before calling usba_hubdi_ioctl(9F). Because the usba_hubdi_ioctl(9F) function normally takes care of checking for the proper privileges, device drivers must verify that a caller has appropriate privileges before processing any private ioctls.
See usba_hcdi_cb_ioctl(9E) for more information.
Outside of that constraint, the device driver should perform locking of its data structures. It should assume that many of its entry points will be called in parallel across the many devices that exist.
There are certain occasions where a device driver may have to enter the p_mutex member of the usba_pipe_handle_data(9S) structure when duplicating isochronous or interrupt requests. The USBA should in general, not hold this lock across calls to the HCD driver, and in turn, the HCD driver should not hold this lock across any calls back to the USBA. As such, the HCD driver should make sure to incorporate the lock ordering of this mutex into its broader lock ordering and operational theory. Generally, the p_mutex mutex will be entered after any HCD-specific locks.
The final recommendation is that due to the fact that the host controller driver provides services to a multitude of USB devices at once, it should strive not to hold its own internal locks while waiting for I/O to complete, such as an issued command. This is particularly true if the device driver uses coarse grained locking. If the device driver does not pay attention to these conditions, it can easily lead to service stalls.
Each of the above entry points begins one-shot or periodic I/O. When the driver returns USB_SUCCESS from one of those functions, it is expected that it will later call usba_hcdi_cb(9F) when the I/O completes, whether successful or not. It is the driver's responsibility to keep track of these outstanding transfers and time them out. For more information on timeouts, see the section Endpoint Timeouts.
If for some reason, the driver fails to initialize the I/O transfer and indicates this by returning a value other than USB_SUCCESS from its entry point, then it must not call usba_hcdi_cb(9F) for that transfer.
In the host controller driver's usba_hcdi_pipe_open(9E) entry point, it already has to look at the pipe handle it's been given to determine the attributes of the endpoint it's looking at. However, before it does that it needs to look at the USB address of the device the handle corresponds to. If the device address matches the macro ROOT_HUB_ADDR, then this is a time where the USBA is opening one of the root hub's endpoints.
Because the root hub is generally not a real device, the driver will likely need to handle this in a different manner from traditional pipes.
The device driver will want to check for the presence of the device's address with the following major entry points and change its behavior as described:
The device driver needs to implement all of the major hub specific request types. It is recommended that driver writers see what existing host controller drivers implement and what the hub driver currently requires to implement this.
Aside from the fact that the request is not being issued to a specific USB device, a request to the root hub follows the normal rules for a transfer and the device driver will need to call usba_hcdi_cb(9F) to indicate that it has finished.
Otherwise, this represents a request to begin polling on the
status endpoint for a hub. This is a periodic request, see the section
Device Addressing Every USB
device has an address assigned to it. The addresses assigned to each
controller are independent. The root hub of a given controller always
has an address of
In general, addresses are assigned by the USBA and stored in the usb_addr member of a usba_device_t(9S). However, some controllers, such as xHCI, require that they control the device addressing themselves to facilitate their functionality. In such a case, the USBA still assigns every device an address; however, the actual address on the bus will be different and assigned by the HCD driver. An HCD driver that needs to address devices itself must implement the usba_hcdi_device_address(9E) entry point. Endpoint Polling more on the semantics of polling and periodic requests.
Here, the device driver will need to provide data and perform a callback whenever the state of one of the ports changes on its virtual hub. Different drivers have different ways to perform this. For example, some hardware will provide an interrupt to indicate that a change has occurred. Other hardware does not, so this must be simulated.
The way that the status data responses must be laid out is based in the USB specification. Generally, there is one bit per port and the driver sets the bit for the corresponding port that has had a change.
The primary request that was used to start polling should be returned, as with any other request to stop interrupt polling.
Polling operates in a different fashion from traditional transfers. With a traditional transfer, a single request is made and a single callback is made for it, no more and no less. With a polling request, things are different. A single transfer request comes in; however, the driver needs to keep ensuring that transfers are being made within the polling bounds until a request to stop polling comes in or a fatal error is encountered.
In many cases, as part of initializing the request, the driver will prepare several transfers such that there is always an active transfer, even if there is some additional latency in the system. This ensures that even if there is a momentary delay in the device driver processing a given transfer, I/O data will not be lost.
The driver must not use the original request structure until it is ready to return due to a request to stop polling or an error. To obtain new interrupt and isochronous request structures, the driver should use the usba_hcdi_dup_intr_req(9F) and usba_hcdi_dup_isoc_req(9F) functions. These functions also allocate the resulting message blocks that data should be copied into. Note, it is possible that memory will not be available to duplicate such a request. In this case, the driver should use the original request to return an error and stop polling.
Device drivers should not use desballoc(9F) to try and bind the memory used for DMA transfers to a message block nor should they bind the message block's read pointer to a DMA handle using ddi_dma_addr_bind_handle(9F).
While this isn't a strict rule, the general framework does not assume that there are going to be outstanding message blocks that may be in use by the controller or belong to the controller outside of the boundaries of a given call to one of the transfer functions and its corresponding callback.
The timeouts are specified in seconds in the request structures. For bulk timeouts, the request is in the bulk_timeout member of the usb_bulk_req(9S) structure. The interrupt and control transfers also have a similar member in their request structures, see usb_intr_req(9S) and usb_ctrl_req(9S). If any of the times is set to zero, the default USBA timeout should be used. In that case, drivers should set the value to the macro HCDI_DEFAULT_TIMEOUT, which is a time in seconds.
Isochronous-OUT transfers do not have a timeout defined on their request structure, the usb_isoc_req(9S). Due to the periodic nature of even outbound requests, it is less likely that a timeout will occur; however, driver writers are encouraged to still set up the default timeout, HCDI_DEFAULT_TIMEOUT, on those transfers.
The exact means of performing the timeout is best left to the driver writer as the way that hardware exposes scheduling of different endpoints will vary. One strategy to consider is to use the timeout(9F) function at a one second period while I/O is ongoing on a per-endpoint basis. Because the time is measured in seconds, a driver writer can decrement a counter for a given outstanding transfer once a second and then if it reaches zero, interject and stop the endpoint and clean up.
This has the added benefit that when no I/O is scheduled, then there will be no timer activity, reducing overall system load.
|November 18, 2016||OmniOS|