usb: short control IN might need a ZLP
Control transfers can transfer less than was requested by the host in the wLength field. if this short transfer is a multiple of the endpoint's packet size, a zero length packet must be sent. Adds tests for a range of control transfer IN requests, and properly supports this in the core. Based heavily on work by Kuldeep Dhaka. See https://github.com/libopencm3/libopencm3/pull/505 and https://github.com/libopencm3/libopencm3/pull/194 for original discussion. Tested with stm32f4, stm32f103 and stm32l053.
This commit is contained in:
@@ -50,6 +50,24 @@ static void stall_transaction(usbd_device *usbd_dev)
|
||||
usbd_dev->control_state.state = IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're replying with _some_ data, but less than the host is expecting,
|
||||
* then we normally just do a short transfer. But if it's short, but a
|
||||
* multiple of the endpoint max packet size, we need an explicit ZLP.
|
||||
* @param len how much data we want to transfer
|
||||
* @param wLength how much the host asked for
|
||||
* @param ep_size
|
||||
* @return
|
||||
*/
|
||||
static bool needs_zlp(uint16_t len, uint16_t wLength, uint8_t ep_size) {
|
||||
if (len < wLength) {
|
||||
if (len && (len % ep_size == 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Register application callback function for handling USB control requests. */
|
||||
int usbd_register_control_callback(usbd_device *usbd_dev, uint8_t type,
|
||||
uint8_t type_mask,
|
||||
@@ -89,7 +107,10 @@ static void usb_control_send_chunk(usbd_device *usbd_dev)
|
||||
usbd_ep_write_packet(usbd_dev, 0,
|
||||
usbd_dev->control_state.ctrl_buf,
|
||||
usbd_dev->control_state.ctrl_len);
|
||||
usbd_dev->control_state.state = LAST_DATA_IN;
|
||||
|
||||
usbd_dev->control_state.state =
|
||||
usbd_dev->control_state.needs_zlp ? DATA_IN : LAST_DATA_IN;
|
||||
usbd_dev->control_state.needs_zlp = false;
|
||||
usbd_dev->control_state.ctrl_len = 0;
|
||||
usbd_dev->control_state.ctrl_buf = NULL;
|
||||
}
|
||||
@@ -153,7 +174,11 @@ static void usb_control_setup_read(usbd_device *usbd_dev,
|
||||
usbd_dev->control_state.ctrl_len = req->wLength;
|
||||
|
||||
if (usb_control_request_dispatch(usbd_dev, req)) {
|
||||
if (usbd_dev->control_state.ctrl_len) {
|
||||
if (req->wLength) {
|
||||
usbd_dev->control_state.needs_zlp =
|
||||
needs_zlp(usbd_dev->control_state.ctrl_len,
|
||||
req->wLength,
|
||||
usbd_dev->desc->bMaxPacketSize0);
|
||||
/* Go to data out stage if handled. */
|
||||
usb_control_send_chunk(usbd_dev);
|
||||
} else {
|
||||
|
||||
@@ -74,6 +74,7 @@ struct _usbd_device {
|
||||
uint8_t *ctrl_buf;
|
||||
uint16_t ctrl_len;
|
||||
usbd_control_complete_callback complete;
|
||||
bool needs_zlp;
|
||||
} control_state;
|
||||
|
||||
struct user_control_callback {
|
||||
|
||||
Reference in New Issue
Block a user