diff --git a/sys/include/usb/descriptor.h b/sys/include/usb/descriptor.h index 886e6a44dd5c..b0ed512c04f9 100644 --- a/sys/include/usb/descriptor.h +++ b/sys/include/usb/descriptor.h @@ -58,6 +58,15 @@ extern "C" { #define USB_TYPE_DESCRIPTOR_INTERFACE_ASSOC 0x0b /**< Interface association */ /** @} */ +/** + * @name USB standard feature selectors + * @{ + */ +#define USB_FEATURE_ENDPOINT_HALT 0x00 /**< Endpoint halt */ +#define USB_FEATURE_DEVICE_REMOTE_WAKEUP 0x01 /**< Device remote wakeup */ +#define USB_FEATURE_TEST_MODE 0x02 /**< Test mode feature */ +/** @} */ + /** * @name USB configuration attributes * @anchor USB_CONF_ATTR diff --git a/sys/include/usb/usbus.h b/sys/include/usb/usbus.h index 3a4657501220..ad0bb06b81aa 100644 --- a/sys/include/usb/usbus.h +++ b/sys/include/usb/usbus.h @@ -327,6 +327,7 @@ typedef struct usbus_endpoint { uint8_t interval; /**< Poll interval for interrupt endpoints */ bool active; /**< If the endpoint should be activated after reset */ + bool halted; /**< Endpoint is halted */ } usbus_endpoint_t; /** @@ -676,6 +677,22 @@ void usbus_urb_submit(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *u */ int usbus_urb_cancel(usbus_t *usbus, usbus_endpoint_t *endpoint, usbus_urb_t *urb); +/** + * @brief Set the halt condition on an endpoint. + * + * The endpoint will respond with stall to all packets and must explicitly be + * cleared by the host by clearing the halt condition or switching interfaces + */ +void usbus_endpoint_halt(usbus_endpoint_t *ep); + +/** + * @brief Clear the halt condition on an endpoint + * + * @note Must only be used when the endpoint is halted and when the host issues + * a SetInterface request on the interface containing the endpoint + */ +void usbus_endpoint_clear_halt(usbus_endpoint_t *ep); + /** * @brief Enable an endpoint * diff --git a/sys/usb/usbus/usbus.c b/sys/usb/usbus/usbus.c index abba08e8090f..77bdf906c36d 100644 --- a/sys/usb/usbus/usbus.c +++ b/sys/usb/usbus/usbus.c @@ -470,6 +470,34 @@ static void *_usbus_thread(void *args) return NULL; } +void usbus_endpoint_halt(usbus_endpoint_t *ep) +{ + assert(ep->ep->num != 0); /* Not valid for endpoint 0 */ + DEBUG("Endpoint %u halted\n", ep->ep->num); + ep->halted = 1; + usbdev_ep_stall(ep->ep, true); +} + +void usbus_endpoint_clear_halt(usbus_endpoint_t *ep) +{ + assert(ep->ep->num != 0); /* Not valid for endpoint 0 */ + DEBUG("Endpoint %u unhalted\n", ep->ep->num); + ep->halted = 0; + usbdev_ep_stall(ep->ep, false); +} + +/** + * @brief Reset the halted status on USB reset condition + */ +static void _usbus_endpoint_reset_halt(usbus_t *usbus) +{ + /* Clear halted state. No need to notify usbdev, USB reset already resets those */ + for (size_t i = 0; i < USBDEV_NUM_ENDPOINTS; i++) { + usbus->ep_out[i].halted = 0; + usbus->ep_in[i].halted = 0; + } +} + /* USB event callback */ static void _event_cb(usbdev_t *usbdev, usbdev_event_t event) { @@ -487,6 +515,7 @@ static void _event_cb(usbdev_t *usbdev, usbdev_event_t event) usbus->addr = 0; usbdev_set(usbus->dev, USBOPT_ADDRESS, &usbus->addr, sizeof(uint8_t)); + _usbus_endpoint_reset_halt(usbus); flag = USBUS_HANDLER_FLAG_RESET; msg = USBUS_EVENT_USB_RESET; DEBUG("usbus: USB reset detected\n"); diff --git a/sys/usb/usbus/usbus_control.c b/sys/usb/usbus/usbus_control.c index 96290f12d104..77e5ca4f99dc 100644 --- a/sys/usb/usbus/usbus_control.c +++ b/sys/usb/usbus/usbus_control.c @@ -105,13 +105,39 @@ static usbus_string_t *_get_descriptor(usbus_t *usbus, uint16_t idx) static int _req_status(usbus_t *usbus) { - uint8_t status[2]; + /* Signal self powered */ + uint16_t status = (CONFIG_USB_SELF_POWERED) ? 1 : 0; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&status, sizeof(status)); + return sizeof(status); +} + +static int _req_iface_status(usbus_t *usbus) +{ + uint16_t status = 0; /* always zero */ + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&status, sizeof(status)); + return sizeof(status); +} - memset(status, 0, sizeof(status)); - usbus_control_slicer_put_bytes(usbus, status, sizeof(status)); +static int _req_endpoint_status(usbus_t *usbus, usbus_endpoint_t *ep) +{ + uint16_t status = ep->halted ? 1 : 0; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&status, sizeof(status)); return sizeof(status); } +static int _req_endpoint_feature(usbus_endpoint_t *ep, uint16_t feature, bool enable) +{ + switch (feature) { + case USB_FEATURE_ENDPOINT_HALT: + enable ? usbus_endpoint_halt(ep) : usbus_endpoint_clear_halt(ep); + break; + default: + DEBUG("usbus: unknown endpoint feature request: %u\n", feature); + return -1; + } + return 1; +} + static int _req_str(usbus_t *usbus, uint16_t idx) { /* Return an error condition by default */ @@ -238,6 +264,11 @@ static int _recv_interface_setup(usbus_t *usbus, usb_setup_t *pkt) (usbus_control_handler_t *)usbus->control; uint16_t destination = pkt->index & 0x0f; + /* Globally handle the iface get status request */ + if (pkt->request == USB_SETUP_REQ_GET_STATUS) { + return _req_iface_status(usbus); + } + /* Find interface handler */ for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) { if (destination == iface->idx && @@ -251,6 +282,26 @@ static int _recv_interface_setup(usbus_t *usbus, usb_setup_t *pkt) return -1; } +static int _recv_endpoint_setup(usbus_t *usbus, usb_setup_t *pkt) +{ + uint8_t destination = pkt->index & 0x0f; + bool in = pkt->index & (1 << 7); /* Bit seven is 1 for IN, 0 for OUT */ + usbus_endpoint_t *ep = in ? &usbus->ep_in[destination] : + &usbus->ep_out[destination]; + + switch (pkt->request) { + case USB_SETUP_REQ_GET_STATUS: + return _req_endpoint_status(usbus, ep); + case USB_SETUP_REQ_SET_FEATURE: + return _req_endpoint_feature(ep, pkt->value, true); + case USB_SETUP_REQ_CLEAR_FEATURE: + return _req_endpoint_feature(ep, pkt->value, false); + default: + DEBUG("usbus: Unknown endpoint request %u\n", pkt->request); + return -1; + } +} + static void _recv_setup(usbus_t *usbus, usbus_control_handler_t *handler) { usb_setup_t *pkt = &handler->setup; @@ -272,6 +323,9 @@ static void _recv_setup(usbus_t *usbus, usbus_control_handler_t *handler) case USB_SETUP_REQUEST_RECIPIENT_INTERFACE: res = _recv_interface_setup(usbus, pkt); break; + case USB_SETUP_REQUEST_RECIPIENT_ENDPOINT: + res = _recv_endpoint_setup(usbus, pkt); + break; default: DEBUG("usbus_control: Unhandled setup request\n"); }