📜 ⬆️ ⬇️

USB support in KolibriOS: what's inside? Part 6: hub driver

The last piece of USB infrastructure is hubs. Although hubs are separate USB devices, they are closely related to other parts of the infrastructure so that the hub specification is part of the basic USB specification, and the support code is part of the kernel located in the bus / usb / hub.inc file .

Tasks hubs are as follows.
Traffic transmission without switching speed is completely transparent to the host. Split transactions are handled by the EHCI host controller, here it is important to only fill in the fields in the hardware of the channel structure that contain the address of the TT hub and the port in the TT hub — and, of course, take the time of the transactions into account when planning . The hub driver manages the rest of the task list.


Hubs have device class code 9, device subclass code 0, and three options 0, 1, 2 for the protocol code. According to the USB specification, the HighSpeed ​​hub is required to support the operation mode with a single TT for all its ports, and may additionally, but not necessarily, support the operation mode with a separate TT for each port. A typical case is that there is no mode with different TTs, then the protocol code is 0. If this mode is supported, there should be two variants of the interface descriptor with the same interface number in the configuration data . Then the protocol code 1 identifies the mode with a single TT, which should be adopted by default, and the protocol code 2 identifies the mode with different TT, which is activated by the command SET_INTERFACE . The existence in nature of hubs that support the mode with different TT is not confirmed, as well as the benefits of this mode, so the hub driver does not even try to detect and enable it and simply uses the single TT mode, which is enabled by default.
')
Upon detecting a class 9 interface, the logic device reads a usb_hub_callbacks structure containing pointers to the usb_hub_init and usb_hub_disconnect driver functions. The driver operation begins when the logic device level calls usb_hub_init , and ends when the channel support level causes usb_hub_disconnect in response to the device disconnecting.

Initialization


The hub initialization starts at usb_hub_init and continues in other functions as it usb_hub_init responses from the device. usb_hub_init opens a channel to the interrupt type endpoint, through which the hub will notify the driver about changes in the status of connected devices. Next, usb_hub_init sends a GET_DESCRIPTOR request to the hub handle .

The figure shows the hex dumps of the descriptors of three different hubs: keyboards with a built-in hub for two ports, two RMHs for 6 ports and 8 ports. A keyboard with a built-in hub is presented to the system as a hub, to one of whose ports a non-removable device is connected - the keyboard itself.
The hub handle contains the following fields:The handle has a variable size. The maximum size is obtained at 255 ports and is equal to 40 bytes. usb_hub_init saves the endpoint packet size — it will be needed later — and requests 40 bytes of the hub descriptor, allowing a short response. After receiving the hub handle, usb_hub_got_config allocates and fills enough memory to store interesting data, plus two variables for each port: a pointer to the connected device and the device connection time.

Before you can work with the hub ports, you need to turn them on SET_FEATURE(PORT_POWER) . At the input, the command accepts a port number, counting from 1. Physically, the hub can support powering on and off for each port or be limited to two states “there is no power on all ports” and “power is on all ports” - the organization can be learned from the two lower bits of the attributes hub, 00 corresponds to the total power, 01 - separate, 10 and 11 are reserved. But in both cases, the hub maintains a separate logical state for each port and will not report events on ports with a power-off logic, so regardless of the physical organization, you need to turn on the power on each port. usb_hub_got_config and usb_hub_port_powered consistently usb_hub_port_powered a power-on command for all ports.

After successful power-up on all ports, you should wait until the power stabilizes, the waiting time is specified in the hub descriptor. Like other actions that require a delayed reaction time, the function of waiting for power is handled by the usb_hub_process_deferred function. After the allotted interval usb_hub_process_deferred , usb_hub_process_deferred starts working with the hub.

Job


Briefly, the hub polling scheme is described in the following figure taken from the USB specification:

Working with the hub begins with a call to usb_hub_wait_change , which requests data from the interrupt type endpoint. While there are no events requiring attention with the hub, the request waits without demanding CPU resources: the host controller periodically polls the endpoint without any intervention from the CPU to determine if there is any data. I remind you that the polling interval is encoded by the last byte of the endpoint descriptor and is set when the channel is opened. When the state of the hub has changed, the hub returns a bit mask indicating the ports whose state has changed, plus the low-order bit indicating whether the state of the hub itself has changed. If at the initialization of the hub some devices have already been connected to it, the first time the hub requests it, it will immediately return a change with respect to the “zero” state.

The size of the significant part of the response in bits is equal to the number of ports plus one. In most cases, the full response size is obtained by adding up to an integer number of bytes. But tests show that sometimes the hub can add even insignificant bytes, so the actual response size should be taken as the maximum packet size from the endpoint descriptor - that’s why usb_hub_init saved it, not limited to transferring to the channel open function.

The hub tracks changes in the status of ports. The method of notifying the host of these changes is somewhat confusing. The state of the port contains several different bits: the device is connected / disconnected, traffic to the device is broadcast / not broadcast, the device is paused / running, overcurrent is registered / not, the reset process is running on the port / no. For each of the bits listed, the hub supports a pair of state changes; the hub sets the bit automatically for any change in state, with subsequent changes the bit remains set. The hub also supports two GET_STATUS , returning both the current status bits and the change bits, and CLEAR_FEATURE , one way to use which is to reset the specified change bit. Due to the fact that these are two different teams, the following situation is possible:Therefore, when processing a state change after the GET_STATUS command, in GET_STATUS to find out what happened, and the CLEAR_FEATURE to clear the changes, you need to send the GET_STATUS command GET_STATUS to get the current status. It is possible that the second GET_STATUS command GET_STATUS again report changes in state after CLEAR_FEATURE ; although stopping processing at this point is correct - the next time the data is read from the end point of the interrupt type, the hub will immediately report a new change - more efficiently, since the host has learned about the change, update the host’s view of the world behind the hub and do a cycle: send a second CLEAR_FEATURE command and the third GET_STATUS and so on, until GET_STATUS reports no new changes.

Data from the endpoint of the interrupt type, arriving when the state of the hub or any of its ports changes, is processed by the usb_hub_changed function. It looks at the state of which ports has changed, for each port it launches the above-described request-status-and-confirm-change cycle, accumulating incoming changes. The response to most changes is the corresponding debug print. It is necessary to process only device connection / disconnection notifications.

When the device is connected, usb_hub_changed remembers the connection time, after which usb_hub_process_deferred plans to wake up in 100 milliseconds. If within 100 milliseconds a signal arrives to disconnect a device on the same port, further actions are canceled. Unfortunately, the absence of such a signal does not mean that the device has been connected for all these 100 milliseconds - perhaps the polling interval is quite large and the signal has not reached yet. For reliability, after a 100 millisecond interval, usb_hub_process_deferred sends an explicit GET_STATUS request. If it shows that there was no change in the connection status, then further initialization is enabled. You cannot reset two devices on the same bus in parallel; if there is a reset of some other device connected to the same controller - not necessarily the same hub - then first the device is waiting for its turn to reset. The reset is activated by the SET_FEATURE(PORT_RESET) command SET_FEATURE(PORT_RESET) and is turned off by the hub automatically after 10 ms of the specified specification, which sets the reset status bit, which may or may not be noticed in time when reading the end point depending on the polling interval. Because of this uncertainty, usb_hub_process_deferred sends usb_hub_process_deferred independently after a set time and CLEAR_FEATURE , and the change processing code, if it still sees a change in the reset state, simply ignores it. The end of the reset in the hub at the same time includes broadcast traffic to the device. After resetting according to the usb_hub_process_deferred specification, another 10 milliseconds usb_hub_process_deferred , during which the device can adapt, and then passes control to the host controllers support code by calling usb_hardware_func.NewDevice . The device speed required for usb_hardware_func.NewDevice determined by the hub during the reset process and returned along with the other state of the device in response to GET_STATUS . A pointer to the connected device returned from usb_hardware_func.NewDevice is stored in the structure of the hub.

When the usb_hub_changed device is disconnected, usb_hub_changed calls the usb_device_disconnected channel support function, passing the previously stored pointer from NewDevice . If the device was in the process of initialization, the process is terminated.

Disconnect


The usb_hub_disconnected handler, called when the hub is disconnected, calls usb_device_disconnected for all connected devices, advances the queue of devices waiting to be reset — if the device was reset when one of the devices was disconnected and frees the allocated memory for the hub structure.

What's next?


The hub driver is the last part of the USB infrastructure. After the implementation of everything I wrote about in the first 6 parts of the series - ending with this article - we can talk about the support of the USB system. Unfortunately, this is only important for driver programmers, and so far the user has little noticeable.


Then begins the world of infinitely diverse drivers. Fortunately, with all the variety of devices, many fall into the classes, for all devices within one class the same driver can be suitable. Hubs are the first example of a class. Two other common classes for which KolibriOS provides a class driver are HID devices, typical representatives of which are mice and keyboards, and Mass Storage devices, typical representatives of which are flash drives and endangered external USB floppy drives. Next time I will talk about HID devices.

All articles of the series


Part 1: general scheme
Part 2: Basics of working with host controllers
Part 3: Host Controller Support Code
Part 4: Channel Support Level
Part 5: logic level
Part 6: hub driver

Source: https://habr.com/ru/post/203918/


All Articles