
The story about the level of interaction with host controllers stretched out into
two articles and still left some details behind the scenes - which, I hope, the interested reader can fill in directly from the
sources . The channel support level is much simpler and mostly busy in that it translates API calls for higher levels into the necessary sequence of actions, including locks, with the necessary host controller.
Channel opening
The
USBOpenPipe
function from the API, called
usb_open_pipe
in the
pipe.inc code, opens a new channel according to the specified channel characteristics and the “parent” channel, where the device characteristics are recorded. For this, she:
- selects a pair of
*hci_pipe+usb_pipe
structures describing the channel and aligned with the controller-specific boundary by calling the usb_hardware_func.AllocPipe
controller-specific function; - selects a pair of
*hci_gtd+usb_gtd
, describing an empty transfer descriptor and aligned to the controller-specific border, by calling the controller-specific function usb_hardware_func.AllocTD
; - fills in pointers: in the channel structure copies the pointer to the controller structure and the pointer to device data common to all channels from the “parent” channel; between the structure of the channel and the structure of the empty descriptor fills pointers back and forth; the empty descriptor structure makes it the only element of the doubly linked channel list;
- initializes a mutex that will guard all operations with this channel. Although all event handling from USB controllers takes place in the USB stream, one cannot say the same about API calls: an application reading a file from a USB flash drive initiates the transfer setting — and not even one — in the queue in the context of the application flow. So that the new transfer does not prevent the USB stream from processing the completion of the old transfer, and this mutex is needed;
- captures the device channel set mutex and makes sure that the device is not yet disabled;
- causes controller-specific initialization of
usb_hardware_func.InitPipe
, protected by a mutex global to the controller; - adds a new channel to the device channel set and releases the channel set mutex;
- in case of an error at one of the stages, all previous stages are rolled back. Since it is the most difficult to roll back controller-specific initialization, it is done at the last stage, after which there can be no errors.
Controller-specific initialization with the last action adds a new channel to the appropriate list. For control channels, as well as for channels of data arrays, there is only one list, but for interrupt channels you need to choose one of several options.

This is where
scheduler.inc comes into play. He just chooses one of the lists of interrupt channels, and also makes sure that there is “enough space” for the new channel. I remind you that in every frame of FullSpeed-bus for periodic transmissions you can not use more than 90% of the time, and in every microframe HighSpeed-bus - more than 80% of the time.
Here I must note that if for some reason you are writing a USB implementation that should
work in your environment, you can save a lot on the scheduler. In one form or another, you will have to implement everything else that is described in this series of articles, but in the absence of a large load, instead of a full tree, you can do with just one list of interrupt channels processed by each frame / microframe. A slightly more economical scheme, not too complicating the implementation, is one list of channels for each processing interval of 1, 2, 4, 8, 16, 32 frames. While it is not necessary to simultaneously process more than one device with large traffic to a single host controller, this approach is not inferior to a full-fledged scheduler. A simple scheme will “break” in some specific configurations with two or more isochronous channels of FullSpeed devices or three or more isochronous channels of HighSpeed devices, but perhaps no one will run your implementation in such specific conditions?
')
If you are writing a USB implementation that should work
everywhere and always , you will also have to write a scheduler.
Transaction time evaluation
Data for calculations and the internal structure of transactionsOne bit at FullSpeed speed is nominally transmitted for 1/12000 part of the frame, which gives a speed of 12 megabits / s. In other words, the “size” of a single frame, as measured by the host controller, is 12,000 bits. Both on the host and on the device, the timer ticking off 1/12000 milliseconds is ticking; according to the timer counts, the host or device starts to send the next bit. The accuracy requirements of the host timer are quite stringent, and the calculations can be considered the host timer accurate. For external FullSpeed devices, the specification allows a timer error of ± 0.25%, which means that the reception time of 400 bits from the device can correspond to a time from 399 to 401 "nominal FS-bits". One bit at a speed of LowSpeed is nominally transmitted 8 times longer than at FullSpeed speed, which gives a speed of 1.5 megabits / s. LowSpeed was conceived as a weakened mode for simple devices like a mouse / keyboard, and the error of the LowSpeed device’s timer should be within ± 1.5%: the reception time of 50 bits from the device can correspond to the time from 394 to 406 "nominal FS-bits".
One bit at the speed of HighSpeed is nominally transmitted for 1/60000 part of the microframe, which gives a speed of 480 megabits / s. The accuracy requirements of the HighSpeed devices are raised to ± 0.05%, so that when planning transactions, the resulting error due to the discrepancy between the timers can be neglected.
Transactions have their own internal structure. Let's leave aside split transactions for now. Normal transactions consist of several packets following the USB bus strictly sequentially, not alternating with other packets: a packet with a token (Token), an optional data packet (Data), an optional feedback packet (Handshake). Before the packets sent from the host to the LowSpeed device, there is a separate special PRE packet. The PRE package and the pause after it with a minimum of 4 “nominal FS bits” are needed for the hubs on the bus to have time to unblock the ports to which the LowSpeed devices are connected. Normal FullSpeed traffic is not transmitted to such ports.
Each packet starts with a SYNC sync of 8 bits = 1 byte on Low / Full-Speed and 32 bits = 4 bytes on HighSpeed. Each packet, except for the special PRE packet, ends with a 3-bit EOP (end of packet) sequence for Low / Full-Speed and 8 bits for HighSpeed.
The token determines the action, direction of transmission, address and end point of the device, receiving / transmitting data. In normal transactions, three tokens are possible: IN, OUT, and SETUP for receiving data, sending data, and the first stage of the control transfer, respectively. A packet with a token occupies 3 bytes, not counting SYNC + EOP: 8 bits for the packet type, 7 bits of the device address, 4 bits of the end point and 5 bits of the CRC, confirming that there are no errors in the transmission of the device address and the end point.
The data packet contains the actual data sent or received from the device, as well as 3 additional bytes, not counting the SYNC + EOP: 8 bits for the packet type and 16 bits of the CRC data.
The feedback packet consists of one byte, not counting the SYNC + EOP: 8 bits for the packet type. The packet is sent in the opposite direction to the previous packet. An ACK indicates that all data has been successfully received. In IN transactions, the NAK packet is sent by the device instead of the data packet and means that there is no data yet. For example, the mouse will respond this way while the controller polls it regularly, but the state has not changed since the last poll. In OUT transactions, the NAK packet is sent by the device after the data packet and means that while the device is busy with internal affairs, the host must try again later. NAK is not an error. To signal an error, the device may not respond at all, respond with an incorrect package, or respond with a STALL package. In the first two cases, the controller will consider this an error somewhere on the bus and will try again up to three times, after which it will surrender and report an error. In the latter case, the controller will report an error immediately.
There is no feedback package in isochronous transactions. In an interrupt transaction, a feedback packet is required.
In the USB data, the zero bit is inserted after every six single bits. Single bits are encoded so that the bus state does not change when a single bit is used, a timer is used to select individual bits. The zero bit is inserted so that the tolerable discrepancy in the timers does not create problems. Therefore, in the worst case, the package delivery time must be multiplied by 7/6. The multiplier does not apply to SYNC and EOP: they are encoded in a special way, generating guaranteed changes in the state of the bus.
If the host controller sends two packets in a row, just a small pause (inter-packet delay) is sufficient, corresponding to transmission of 2 bits in the case of FullSpeed and LowSpeed and 88 bits in the case of HighSpeed. If the controller has accepted the packet and must send a reply packet, the pause is reduced to 8 bits in the case of HighSpeed and the same 2 bits in the case of FullSpeed and LowSpeed. If the host controller has sent a packet and is waiting for a reply packet, then it is necessary to take into account the delay in passing the packet to the device and the response packet from the device (turn-around time). For FullSpeed and LowSpeed tires, the specification defines the maximum delay, including the passage of the signal in both directions and the response of the device, as a transmission time of 18 bits with an appropriate speed. For HighSpeed, the maximum delay is equivalent to a transfer time of 736 bits.
The host controller hides in itself the implementation of all these transaction details, for planning it is enough just to know how long the transaction will take. The time depends on the type of transaction, the direction of the transaction and the speed of the device.
- The interrupt read transaction consists of a packet with a token to the device, waiting for a response from the device, a packet with data from the device, a pause after a received packet, a feedback packet to the device, a pause after the sent packet.
- HighSpeed-bus: 68 bits in the first packet, 736 wait bits, 40+ (7/6) * 8 * (data size + 3) bits in the second packet, 8 pause bits, 49 bits in the last packet, 88 more pause bits - total 989 + 8 * (7/6) * (data size + 3) bits maximum.
- FullSpeed bus: 39 bits in the first packet, 18 bits of waiting, (401/400) * (11+ (7/6) * 8 * (data size + 3)) bits in the second packet, 2 bits of pause, 20 bits in the last packet, 2 more pause bits - a total of 93 + 2807/300 * (data size + 3) bits maximum.
- LowSpeed bus: 16 FS-bits of the preamble to the first packet and 4 FS-bits for the hub reaction, 8 * 39 FS-bits in the first packet, 8 * 18 FS-bits of waiting, (406/50) * (11+ (7 / 6) * 8 * (data size + 3)) FS-bits in the second packet, 8 * 2 FS-bits of the pause, 16 FS-bits of the preamble to the last packet and 4 FS-bits for the response of the hubs, 8 * 20 FS- bits in the last packet, another 8 * 2 bits of the pause - total 778 + 5684/75 * (data size + 3) FS-bit maximum.
- The interrupt recording transaction consists of a packet with a token to the device, a pause after the sent packet, a packet with data to the device, waiting for a response from the device, a feedback packet from the device, a pause after the received packet. Difference from reading, not counting the order of the terms, only in the direction of transmission.
- HighSpeed-bus: the same 989 + 8 * (7/6) * (data size + 3) bits maximum.
- FullSpeed bus: The 401/400 multiplier “moves” from the data packet to the feedback packet, for a total of 93 + 28/3 * (data size + 3) bits maximum.
- LowSpeed-bus: 406/50 and 8 multipliers in two data packets and feedback are swapped, for a total of 778 + 224/3 * (data size + 3) FS-bit maximum.
- An isochronous read transaction consists of a packet with a token to the device, waiting for a response from the device, a data packet from the device, a pause after the received packet
- Highspeed-bus: 68 bits in the first packet, 736 wait bits, 40+ (7/6) * 8 * (data size + 3) bits in the second packet, 8 pause bits - total 852 + 8 * (7/6) * (data size + 3) bits maximum.
- FullSpeed bus: 39 bits in the first packet, 18 bits of waiting, (401/400) * (11+ (7/6) * 8 * (data size + 3)) bits in the second packet, 2 bits of pause - total 71 + 2807/300 * (data size + 3) bits maximum.
- On a LowSpeed bus, isochronous transactions are prohibited by specification.
- An isochronous write transaction consists of a packet with a token to the device, a pause after the sent packet, a packet with data to the device, another pause after the sent packet.
- Highspeed-bus: 68 bits in the first packet, 88 bits of the pause, 40+ (7/6) * 8 * (data size + 3) bits in the second packet, another 88 bits of the pause - total 284 + 8 * (7/6) * (data size + 3) bits maximum.
- FullSpeed bus: 39 bits in the first packet, 2 bits of the pause, 11+ (7/6) * 8 * (data size + 3) bits in the second packet, another 2 bits of the pause - a total of 54 + 8 * (7/6) * (data size + 3) bits maximum.
- On a LowSpeed bus, isochronous transactions are prohibited by specification.
Split transactions contain three types of “elementary” transactions on two buses: the Start-Split transaction on the HighSpeed bus between the host and the TT, the normal transaction on the FullSpeed / LowSpeed bus between the TT and the device, the Complete Split transaction on the HighSpeed bus between the host and TT. The transaction time in the middle differs from the time of the same transaction without TT only by the additional pause introduced by the TT and described in the hub descriptor with the TT. Start-Split and Complete-Split transactions begin with a special SPLIT packet of 4 bytes in size, not counting SYNC + EOP.
- Split read interrupt transaction and isochronous read transaction. The Start-Split transaction consists of a SPLIT packet, a pause after the sent packet, a packet with a token, a pause after the sent packet — a total of 321 bits. The Complete-Split transaction consists of a SPLIT packet, a pause after a sent packet, a packet with a token, waiting for a response from the device, a data packet, a pause after a received packet — total 1017 + 8 * (7/6) * (data size + 3) bits .
- Split interrupt write transaction. The Start-Split transaction consists of a SPLIT packet, a pause after the sent packet, a packet with a token, a pause after the sent packet, a data packet, a pause after the sent packet — a total of 449 + 8 * (7/6) * (data size + 3) bits . The Complete-Split transaction consists of a SPLIT packet, a pause after the sent packet, a packet with a token, waiting for the device to respond, a packet with feedback, a pause after the received packet — a total of 1026 bits.
- Split isochronous write transaction. The Start-Split transaction has the same structure as in the case of an interrupt write transaction, 449 + 8 * (7/6) * (data size + 3) bits. The Complete-Split transaction is missing.
Scheduler
On a FullSpeed / LowSpeed bus, a single frame can have no more than one transaction on a single channel, transfers from more than one transaction are broken up into several frames. On the HighSpeed bus, the maximum number of transactions per microframe can go up to 3 and is one of the channel characteristics along with the maximum transaction size.
When opening a channel, the scheduler must reserve a portion of the 90% of the frame / 80% of the microframe for the channel, based on the worst case of channel use - assuming the maximum possible number of longest-running transactions. The transaction duration is described in the previous section. If you cannot reserve a part of the channel because everything is already occupied by other channels, the scheduler should return an error. The driver, having detected an error, may, for example, try to agree with the device about reducing traffic due to something or inform the user (through the control program) that it is impossible to work under such conditions.
Split transactions add complexity. First, you need to reserve and keep records on two tires. Secondly, microframes appear on the FullSpeed / LowSpeed bus: if the Start-Split transaction from host to TT comes in the N microframe, then TT can start this transaction only in the N + 1 microframe, and return the results in the Complete-Split transaction - not earlier microframe N + 2. Thirdly, although the maximum of 90% of the frame for all periodic transactions remains at worst, planning on the FS / LS bus should proceed from an optimistic estimate without a 7/6 multiplier due to the insertion of bits, the USB2 specification in the face of section 11.18 “Periodic Split Transaction Pipelining and Buffer Management calls this assessment “best-case budget” - this reduces the chances that the FS / LS bus will be idle due to the fact that one periodic transaction was completed before the calculated one, the next periodic transaction could not start before the next microframe, because for her The data of the Start-Split transaction did not come, and there is not enough time for the next non-periodic transaction in the rest of the current microframe. Finally, the host does not know when the transaction will end exactly, and the TT buffers for storing the results are not rubber, so the Complete-Split transaction needs to be scheduled several times — one in each microframe following the microframe in which the transaction can complete. Specific requirements are voiced in the same section 11.18: as part of an interruption transaction starting from the microframe N, one Start-Split transaction in the N-1 microframe and three Complete-Split transactions in the N + 1, N + 2 microframes should be scheduled. N + 3. Isochronous read transactions differ only in that they can occupy several N, ..., L microframes in the budget, because of which the Complete-Split transactions should be planned in microframes from N + 1 to L + 3 inclusive. There are no Complete-Split transactions in the isochronous transactions, but there can be several Start-Split transactions: there is less than 188 bytes on the FS-bus in one microframe, and if there is more data, they will be split into several Start-Split transactions with a limit of 188 bytes in one transaction.
The KolibriOS Scheduler tries to achieve as even distribution of the reserved parts as possible in different frames / microframes, trying to ensure that the phrase “X or more free time is in each frame / microframe” contains the X number as much as possible. If a channel later appears requiring attention of each frame / microframe, a large X value is necessary for successful backup. If it does not appear, time is useful for non-periodic broadcasts.
First, the scheduler selects the real interval at which the host controller will poll the channel. This is the easy part of the problem: choose from the numbers 1, 2, 4, 8, 16, 32 maximum, not more than the given one. . USB1 8 . 8 : , 8k+0, ..., , 8k+7. 32 ; 8k+0 4 0,8,16,24, . , , , 24, , 32k+24, 16k+8, 8k+0, 4k+0, 2k+0, . 0,8,16,24, 8k+0. , , 8k+1, ..., 8k+7. — , , , , . , , 90% , .
HighSpeed- USB2 USB1 : 8 ; , , , .
USB2 , . , , . FS- . , . , HS-, Start-Split Complete-Split .
USBClosePipe
API,
usb_close_pipe
pipe.inc, . , « »,
usb_close_pipe_nolock
, USB-, .
-
usb_device_disconnected
, , ,
usb_close_pipe_nolock
, , , . USB- , , .
usb_close_pipe_nolock
:
- (, );
- , , « » ( — );
- ;
- ;
- -
usb_hardware_func.UnlinkPipe
, , scheduler.inc .
- , , -
usb_pipe_closed
, :
- , callback- « » ;
- « » , , ;
- « » , , ;
- ,
DeviceDisconnected
, , , , , , , , , .
,
USBNormalTransferAsync
API,
usb_normal_transfer_async
pipe.inc, : , , , -
usb_hardware_func.AllocTransfer
, , ,
usb_hardware_func.InsertTransfer
.
, ,
USBControlTransferAsync
API,
usb_control_async
pipe.inc,
USBNormalTransferAsync
.

-
usb_hardware_func.AllocTransfer
, . . , — Toggle. USB « » , - . : Toggle=0, Toggle=1 . Toggle , .
Part 1: general schemePart 2: Basics of working with host controllersPart 3: Host Controller Support Code4:
Part 5: logic level6: