Uncertain about finishing control requests

153 views
Skip to first unread message

Christoph Redecker

unread,
Sep 3, 2011, 11:10:09 AM9/3/11
to lufa-s...@googlegroups.com
Hi list,

I'm uncertain about how exactly a control request has to be finished. I
am using libusb to send a control request to my board:

int iResult = usb_control_msg(_ptUsbHandle,
USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
CMD_GET,
0, // value
0, // index
cBuf, // destination*
2, // size
500); // timeout

This control request reads a status. The board should return the status
as a data packet that consists of the CMD that was issued (CMD_GET) and
the status byte. On the board side, I now have this:

char buf[2];
buf[0] = CMD_GET; // return in byte 0 the command that was issued
buf[1] = status;
Endpoint_ClearSETUP();
Endpoint_Write_Control_Stream_LE(buf, USB_ControlRequest.wLength);
Endpoint_ClearOUT();

which works without errors. I basically used the code in the low-level
mouse demo EVENT_USB_Device_ControlRequest(), case HID_REQ_GetReport,
and adopted it to my needs. However, it is - from the host's point of
view - an IN operation, so shouldn't I need to use ClearIN()? I tried
that, and got an error on the host side (usb_control_msg(...) returns
zero, which means zero bytes read).

Reading the low level mouse demo, I don't quite understand how he
different types of control requests and the LUFA functions come
together. There are (in EVENT_USB_Device_ControlRequest())

case HID_REQ_GetReport: <- "get"
Endpoint_ClearSETUP()
Endpoint_Write_Control_Stream_LE(...)
Endpoint_ClearOUT()

case HID_REQ_GetProtocol: <- again, "get"
Endpoint_ClearSETUP()
Endpoint_Write_8 <- again the device writes to the ctrl endpoint
Endpoint_ClearIN() <- why now that one?
Endpoint_ClearStatusStage() <- and why is this added?

case HID_REQ_SetProtocol:
Endpoint_ClearSETUP()
Endpoint_ClearStatusStage()

case HID_REQ_SetIdle:
same as HID_REQ_SetProtocol

case HID_REQ_GetIdle:
same as HID_REQ_GetProtocol

I was curious about what

Endpoint_ClearSETUP();
Endpoint_Write_Control_Stream_LE(buf, USB_ControlRequest.wLength);
Endpoint_ClearIN();
Endpoint_ClearStatusStage();

would result in, and got no error and the desired behavior. I'm puzzled!
My impression is that ClearIN() and ClearStatusStage() have the same
effect as ClearOUT(), but I'm sure I missed something important about
these functions.

Regards

Christoph

Dean Camera

unread,
Sep 5, 2011, 6:38:43 AM9/5/11
to LUFA Library Support List
Christoph ,

> which works without errors. I basically used the code in the low-level
> mouse demo EVENT_USB_Device_ControlRequest(), case HID_REQ_GetReport,
> and adopted it to my needs. However, it is - from the host's point of
> view - an IN operation, so shouldn't I need to use ClearIN()? I tried
> that, and got an error on the host side (usb_control_msg(...) returns
> zero, which means zero bytes read).

You are correct in that it is an IN operation (from device to host -
even on the device side, the requests are "orientated" in the
direction of the host). A control request involves three stages:

1) a SETUP packet from the host to the device
2) Data from sender to received (can be in either direction)
3) Status stage packet sent from receiver to sender

All of which must be processed correctly. For the SETUP stage, you
acknowledge the request with a Endpoint_ClearSETUP(). In the data
stage, the sender must send the request data to the receiver - if the
device is sending to the host, each data packet must be sent via a
call to Endpoint_ClearIN(). If the data is flowing from the host to
the device, the device must acknowledge the data with an
Endpoint_ClearOUT(). Clear so far?

The odd bit is the status stage - this is acknowledged by sending or
acknowledging a packet in the opposite direction of the data. If the
device is sending data to the host, you acknowledge data itself with
an Endpoint_ClearOUT(), and the end of the data by sending a packet to
the host via a call to Endpoint_ClearIN(). Requests in the opposite
direction.have the two calls reversed.


If you are managing the control data explicitly packet by packet, you
need to follow this structure for Host->Device transfers:

Endpoint_ClearSETUP(); // ACK SETUP packet
while (Still_Data_to_Receive())
{
while (!(Endpoint_IsOUTReceived())); // Wait for data
// Process data here
Endpoint_ClearOUT(); // ACK data packet
}
while (!(Endpoint_IsINReady())); // Wait for host ready to receive
STATUS ACK
Endpoint_ClearIN(); // Send STATUS ACK


And for Device->Host transfers:

Endpoint_ClearSETUP(); // ACK SETUP packet
while (Still_Data_to_Send())
{
while (!(Endpoint_IsINReady())); // Wait for host ready
// Process data here
Endpoint_ClearIN(); // Send data packet
}
while (!(Endpoint_IsOUTReceived())); // Wait for host to send STATUS
ACK
Endpoint_ClearOUT(); // Clear host STATUS ACK


Because the STATUS ACK portion of requests is a common operation, I've
instead provided a helper function called Endpont_ClearStatusStage()
which you can call instead of the last explicit loop-and-clear part of
the transfer. This function will examine the request header to
determine the data direction, and perform the correct loop-and-clear
operation to send or ACK the request status stage. This would then
result in the following code:

Endpoint_ClearSETUP(); // ACK SETUP packet
while (Still_Data_to_Receive())
{
while (!(Endpoint_IsOUTReceived())); // Wait for data
// Process data here
Endpoint_ClearOUT(); // ACK data packet
}
Endpoint_ClearStatusStage(); // Send STATUS ACK


(I'll skip the opposite direction here, as you can extrapolate from
the code above). As another convenience, I provide the
Endpoint_Write_Control_Stream_LE() and
Endpoint_Read_Control_Stream_LE() which replace the middle data stage
loop code in the request processing sequence. When these return, you
only need to ACK the status stage:


Endpoint_ClearSETUP(); // ACK SETUP packet
Endpoint_Read_Control_Stream_LE(Buffer, Length) // Read request data
Endpoint_ClearStatusStage(); // Send STATUS ACK


However, the Endpoint_ClearStatusStage() call is reasonably expensive.
Since the Control request read/write routines perform the first busy-
wait for the status stage to become ready, we can instead directly
call Endpoint_ClearIN() and Endpoint_ClearOUT() to send or ACK the
STATUS part of the request as soon as the control stream functions
return. Since, as you can see from the original code, the STATUS ACK
is in the opposite direction to the transfer, we end up with the clear
being in the opposite direction to what you would expect:

Endpoint_ClearSETUP(); // ACK SETUP packet
Endpoint_Read_Control_Stream_LE(Buffer, Length) // Read request data
Endpoint_ClearIN(); // Send STATUS ACK


This is done to tighten the code somewhat. You can still replace it
with a call to Endpoint_ClearStatusStage() if that's your fancy.


Hope that helps!
- Dean

On Sep 4, 1:10 am, Christoph Redecker <redec...@avrbeginners.net>
wrote:

rede...@avrbeginners.net

unread,
Sep 5, 2011, 6:57:12 AM9/5/11
to lufa-s...@googlegroups.com
Dean,

thank you for your explanation! Together with the USB spec and "USB in a
nutshell" from beyondlogic.org, I have a bigger picture now. I've read
your explanation and the source code side by side, which worked pretty
well. It is now clear to me how the different Clear...() functions can
replace each other.

Should I write a tutorial about this? Do you think anyone would be
interested?

Regards

Christoph

Reply all
Reply to author
Forward
0 new messages