====== Differences ====== This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
soc:2008:balajirrao:notes:usb_outline [2008/05/19 15:57] mdc |
soc:2008:balajirrao:notes:usb_outline [2008/05/25 13:04] (current) mdc |
||
---|---|---|---|
Line 45: | Line 45: | ||
Finally, once we know how TDs and QHs are arranged in memory, we'll describe how the Host Controller processes them. The Frame Counter register is incremented every millisecond. It indexes into the Frame list and selects a frame. From the frame, TDs one by one are executed, and their status marked in the TD itself. The TDs can be optionally marked to generate Interrupts on Completion (IOCs). This goes on and on. When interrupts are not available for use, we can poll for their status and recognize their completion. | Finally, once we know how TDs and QHs are arranged in memory, we'll describe how the Host Controller processes them. The Frame Counter register is incremented every millisecond. It indexes into the Frame list and selects a frame. From the frame, TDs one by one are executed, and their status marked in the TD itself. The TDs can be optionally marked to generate Interrupts on Completion (IOCs). This goes on and on. When interrupts are not available for use, we can poll for their status and recognize their completion. | ||
- | |||
===== How do we use this in a USB NIC ===== | ===== How do we use this in a USB NIC ===== | ||
- | //Packet Transmission// is fairly straight forward. First, we wrap the packet to be transmitted in a Device specific header and create a TD for it. Then we put it in the host controller schedule and we can signal the higher layers about its status in the //poll()// method. | + | === Transmitting Packets === |
+ | |||
+ | Now we're given a buffer of certain length to transmit. The first step is creating TDs for that buffer. Each TD can transfer up to a max packet size defined by the endpoint. Hence, we need to split up our buffer into several TDs. The structure of a TD is shown above. The 'Buffer Pointer' is set to the beginning of the buffer and the length pointer is set to the size of a maximum packet except for a last TD. The 'Link pointer' field is updated properly to point to the next TD. A dummy TD is introduced at the end indicating end of the TD chain. | ||
+ | |||
+ | Now we have a chain of TDs which must be introduced into the schedule as shown in the figure above, i.e the 'Queue Element pointer' of a some QH must point to the beginning of our TD chain. Finding this QH will be simple if we make a rule that all TDs of a certain endpoint are anchored to one particular QH thats unique to that endpoint. | ||
+ | |||
+ | Now, this QH has to be introduced into the schedule, i.e it must be linked to the Frame List as shown above. The approach used by Linux Kernel solves this problem elegantly. It creates a skeleton of QHs for various types of transfers, the addresses of which can be stored in the Host Controller descriptor data structure. | ||
+ | |||
+ | To summarize, when we are asked to transmit data, we create a chain of TDs containing the data. We know from the network device descriptor the USB device descriptor which contains the endpoint numbers of the transmitting and receiving endpoints. We can now obtain the QH to which the TD chain should be anchored to. We introduce the QH into the schedule if it has not been done already (happens when the endpoint is being used for the first time). This requires that we know the appropriate skeleton QH's address, which can be obtained from the Host Controller Descriptor whose address is given by the USB Device descriptor of our device (which is known at device configuration time) | ||
+ | |||
+ | == Skeleton QHs == | ||
+ | |||
+ | As mentioned above, several skeleton QHs are created for each type of transfer. Their addresses are appropriately put in the Frame List which is just a page (4K) of memory. Once this is done, we put the address of this page into a register called 'Frame List Base address register - which is described above. | ||
+ | |||
+ | === Receiving Packets === | ||
+ | |||
+ | First, we allocate empty buffers for incoming packets. Next, we need to instruct the device to put data into that buffer. To do this, we create a chain of TDs in a way similar to the method used for transmitting packets. The host controller can figure out the direction of data transfer from the endpoint we've addressed the TDs to since each endpoint is a simplex channel. When our TDs get 'executed' in the schedule, they will have data in them. | ||
+ | |||
+ | When the device is initialized we create many empty buffers and ask the USB controller to insert it into the schedule. In the poll method, we check for filled buffers. We hand away received packets to upper layers and add buffers in the schedule for receiving data. This process continues as long as the device is open. | ||
+ | |||
+ | Also, in the poll method, we handle completion of tx packets by signalling (un)successful transmission of data to the upper layers. | ||
- | //Packet Reception// is not very straight forward. One way I think this can be done is, in the poll method, create as many IN TDs (for receiving data) as the rx queue of the device can accumulate frames. When poll is called again we check for exectued TDs and pass the packet to higher layers, and again we create TDs upto the brim. | ||
- | For reading registers related to the NIC itself, we can use the control endpoint with device specific command and wait for them to finish, i.e, we deal with them synchronously. |