All data on USB bus is transferred under host control. Even though some transfers are called IN and some other OUT, they all start at the host. There are four types of transfers, the most interesting of which is control transfer. This type of transfer is used all the time in communication with every class of devices. In this article, I give a short description on programming control transfers.
Control transfer consists of three steps( or stages ): setup, data, and status. Some of them carry necessary data in setup packet itself and don’t have data stage at all. Good example of such short request is “Set address” – the information to transfer is just one byte and is sent by filling a certain setup packet field.
Even though USB specification defines several different destinations for the transfer, such as device, interface and endpoint, on the lowest level each packet address consists of two parts – device and endpoint. Some endpoints are IN or OUT only. Other, such as “default control pipe” AKA endpoint 0, are bi-directional. Before each transfer can be sent out, peripheral address must be loaded into MAX3421E PERADDR register.
The first step of every control transfers is called setup stage, during which host sends special packet called setup packet to the peripheral. Control transfer low-level bus signaling is complex, that’s why there is special FIFO register for setup packet, called SUDFIFO. Eight bytes of setup packet are loaded into this register. After that, endpoint number and setup token are loaded into HXFR register. Loading of this register starts the transfer. The setup packet is sent out and host checks for transfer results. If peripheral is busy, it returns NAK. If peripheral is very busy or dead, it won’t answer at all. There are other errors, but they usually mean that the firmware tries to send a wrong packet and needs to be fixed.
What happens on MAX3421E side after setup packet was sent out? As soon as it receives an answer from the peripheral, it sets HXFRDNIRQ (“Transfer done”) bit in HIRQ register. The result is loaded into HRSL register. Then it’s up to firmware to decide what to do next. Take a look at XferDispatchPkt function in transfer.c.
This function dispatches a packet of any type – setup, IN, or OUT. After sending a transfer request and getting a result, it checks if result is a NAK. NAK means the device is alive but not ready to answer. Receiving NAK during transfer is quite typical and is usually not a big deal, unless we get too many. There is no restriction on number of NAKs in USB specification. Device can send them for hours. Some devices are (incorrectly) programmed to return NAK instead of STALL, which is a condition when request can not be satisfied. For this reason, function counts the number of NAKs and returns if certain limit (in my code default is set to 200) is reached.
Another tracked situation is a timeout. After sending a packet, function waits certain time for HXFRDNIRQ to assert, then tries to resend the packet. Since it’s totally possible that peripheral device will never answer, function aborts after several (default 3) attempts are made.
Any other result code is returned as function return code. Result zero means success, non-zero means something is wrong.
Like I said before, some control transfers have data stage, and some don’t. In case data stage is present (Get_Descriptor request, for example), the next request after setup is ordinary bulk-IN request. There are also rare cases when bulk-OUT request is necessary.
The XferInTransfer function does just that. Before calling it in control transfer we need to tweak data toggle mechanism; since first packet sent to the endpoint was not a bulk one, the toggle value for this endpoint will still be zero. If we try to send the packet with this toggle value, the result code would be toggle error.
At present, the toggle value is set in XferCtrlReq function. Take a look at the beginning of big switch statement under cases for getting various descriptors. At the time of writing, USB data structures for connected devices have not been fully designed; the right way to do toggles is to store them in endpoint-related structure, load before transmission, and update after transmission is completed.
Let’s get back to XferInTransfer. The function sends IN request first, and then keeps reading receive FIFO checking several end transmission conditions on the way. It is crucial to know maximum packet size for the device beforehand; one of the indicators that device sent all the data is a packet which is less in size than the maximum packet size. This criteria, however important, is not the only one. Function also checks if the expected number of bytes has been received. In the ideal world, number of bytes requested in the setup packet will be equal to the number of bytes received, for example, during enumeration the very first packet is Get_descriptor request with number of bytes set to 8 and maximum packet size also set to 8; we shall expect to receive 8 bytes, however, I often see devices that return the whole descriptor( 18 bytes ) instead.
Last stage is status – a packet which finishes the control transfer. It is also an indicator for the device to stop transmitting during data stage. MAX3421E supports two special tokens for status packets – HS-IN and HS-OUT. HS are empty bulk transfers in opposite direction. HS-IN is used to complete CONTROL-WRITE requests, such as Set_Address. HS-OUT is used for CONTROL-READ requests like Get_Descriptor.
Status stage is very simple. All you need to do is load tokINHS or tokOUTHS and analyse the return code of XferDispatchPkt.
Control transfers are the most complex of all USB transfer types. Take a look at XferCtrlReq function to get better idea of data flow, different stages and status tokens.