From 9c45a41abec6fd803ae5c24473f4ff77e032ea4a Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 13 May 2026 12:04:17 -0300 Subject: [PATCH] usb: hub class driver --- kernel-src/include/kernel/usb.h | 33 ++- kernel-src/io/usb/class/hid.c | 1 - kernel-src/io/usb/class/hub.c | 411 +++++++++++++++++++++++++++++ kernel-src/io/usb/core/hub.c | 105 +++++++- kernel-src/io/usb/core/usb.c | 14 +- kernel-src/io/usb/ctrl/xhci_ctrl.c | 50 +++- kernel-src/io/usb/ctrl/xhci_hub.c | 2 +- recipes/astral | 2 +- 8 files changed, 598 insertions(+), 20 deletions(-) create mode 100644 kernel-src/io/usb/class/hub.c diff --git a/kernel-src/include/kernel/usb.h b/kernel-src/include/kernel/usb.h index 076e7b9e..46ca338b 100644 --- a/kernel-src/include/kernel/usb.h +++ b/kernel-src/include/kernel/usb.h @@ -23,6 +23,8 @@ typedef struct usb_class_driver usb_class_driver_t; #define USB_DESCRIPTOR_TYPE_INTERFACE 4 #define USB_DESCRIPTOR_TYPE_ENDPOINT 5 #define USB_DESCRIPTOR_TYPE_SS_EP_COMPANION 48 +#define USB_DESCRIPTOR_TYPE_HUB 0x29 +#define USB_DESCRIPTOR_TYPE_SS_HUB 0x2A typedef struct { uint8_t bLength; @@ -30,6 +32,7 @@ typedef struct { } usb_desc_hdr_t; #define USB_MAX_CONFIG_DESC_SIZE 4096 +#define USB_CLASS_HUB 0x09 typedef struct { uint8_t bLength; @@ -103,6 +106,28 @@ typedef struct { uint16_t wBytesPerInterval; } __attribute__((packed)) usb_ss_ep_companion_desc_t; +#define USB_HUB_PROTOCOL_MULTI_TT 2 + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; +} __attribute__((packed)) usb_hub_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; + uint8_t bHubHdrDecLat; + uint16_t wHubDelay; +} __attribute__((packed)) usb_ss_hub_desc_t; + #define USB_REQUEST_RECIP_DEVICE 0x00 #define USB_REQUEST_RECIP_INTERFACE 0x01 #define USB_REQUEST_RECIP_ENDPOINT 0x02 @@ -246,6 +271,7 @@ struct usb_device { // Descriptors for device usb_device_desc_t desc; usb_config_desc_t *config_desc; + usb_desc_hdr_t *hub_desc; // Port number on the parent hub. uint8_t port_number; // Hub-port generation captured when enumeration started. @@ -313,9 +339,11 @@ typedef void (*usb_address_device_callback_t)(usb_hub_t *hub, uint8_t port, usb_ typedef struct { // Allocate, enable and address a USB device. - int (*address_device)(usb_ctrl_t *, usb_hub_t *, uint8_t port, usb_address_device_callback_t callback); + int (*address_device)(usb_ctrl_t *, usb_hub_t *, uint8_t port, usb_speed_t speed, usb_address_device_callback_t callback); // Deaddress, disable and free a USB device void (*deaddress_device)(usb_ctrl_t *, usb_device_t *); + // Mark a device as a hub in the controller + int (*mark_as_hub)(usb_ctrl_t *, usb_device_t *, usb_completion_callback_t callback, void *callback_ctx); // Configure an endpoint. int (*configure_ep)(usb_ctrl_t *, usb_device_t *, usb_endpoint_t *, usb_completion_callback_t callback, void *callback_ctx); // Execute a control or data transfer. @@ -330,13 +358,14 @@ int usb_submit_xfer(usb_device_t *dev, usb_xfer_t *xfer); int usb_control_xfer(usb_device_t *dev, usb_setup_t *setup, void *buffer, usb_completion_callback_t callback, void *ctx); int usb_get_device_descriptor(usb_device_t *dev, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx); +int usb_get_class_device_descriptor(usb_device_t *dev, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx); int usb_get_interface_descriptor(usb_device_t *dev, uint16_t interface_number, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx); int usb_get_endpoint_descriptor(usb_device_t *dev, uint16_t endpoint_address, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx); int usb_set_configuration(usb_device_t *dev, uint8_t config_value, usb_completion_callback_t callback); void usb_hub_event_connect(usb_hub_t *hub, int port); void usb_hub_event_disconnect(usb_hub_t *hub, int port); -void usb_hub_event_reset(usb_hub_t *hub, int port); +void usb_hub_event_reset(usb_hub_t *hub, int port, usb_speed_t speed); #define USB_DRIVER_SCORE_NONE 0 #define USB_DRIVER_SCORE_GENERIC 10 diff --git a/kernel-src/io/usb/class/hid.c b/kernel-src/io/usb/class/hid.c index 76a84248..fced168d 100644 --- a/kernel-src/io/usb/class/hid.c +++ b/kernel-src/io/usb/class/hid.c @@ -294,7 +294,6 @@ static int hid_attach(usb_device_t *dev, usb_interface_t *interface) { } static int hid_probe(usb_device_t *, usb_interface_t *interface) { - if (interface->desc->bInterfaceClass == USB_INTERFACE_CLASS_HID) return USB_DRIVER_SCORE_CLASS_MATCH; diff --git a/kernel-src/io/usb/class/hub.c b/kernel-src/io/usb/class/hub.c new file mode 100644 index 00000000..290f4a67 --- /dev/null +++ b/kernel-src/io/usb/class/hub.c @@ -0,0 +1,411 @@ +#include +#include +#include +#include +#include +#include + +#define USB_HUB_REQUEST_GET_STATUS 0 +#define USB_HUB_REQUEST_CLEAR_FEATURE 1 +#define USB_HUB_REQUEST_SET_FEATURE 3 + +#define USB_HUB_POWER_SWITCHING_MODE_MASK 0x0003 +#define USB_HUB_POWER_SWITCHING_MODE_NONE (1 << 1) + +typedef struct { + uint64_t in_buffer; + size_t in_bitmap_size; + usb_hub_t *usb_hub; + uint32_t *status_buffer; + usb_endpoint_t *interrupt_in_endpoint; + size_t ports_powered; + size_t ports_initialized; +} hub_driver_data_t; + +static int hub_port_control_xfer(usb_device_t *dev, uint8_t request, uint16_t value, int port, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { + usb_setup_t setup = {0}; + setup.bmRequestType = USB_REQUEST_RECIP_OTHER | USB_REQUEST_CLASS; + setup.bmRequestType |= buffer ? USB_REQUEST_DIR_TO_HOST : USB_REQUEST_DIR_TO_DEVICE; + setup.bRequest = request; + setup.wValue = value; + setup.wIndex = port + 1; + setup.wLength = length; + + return usb_control_xfer(dev, &setup, buffer, callback, callback_ctx); +} + +static int get_status_cmd(usb_device_t *dev, hub_driver_data_t *data, int port, usb_completion_callback_t callback, void *callback_ctx) { + if (port < 0 || port >= data->usb_hub->port_count) + return EINVAL; + + return hub_port_control_xfer(dev, USB_HUB_REQUEST_GET_STATUS, 0, port, &data->status_buffer[port], sizeof(data->status_buffer[port]), callback, callback_ctx); +} + +static hub_driver_data_t *hub_get_driver_data(usb_hub_t *hub) { + usb_device_t *dev = hub->device; + if (dev == NULL || dev->config_desc == NULL || dev->interfaces == NULL) + return NULL; + + for (int i = 0; i < dev->config_desc->bNumInterfaces; ++i) { + hub_driver_data_t *data = dev->interfaces[i].driver_data; + if (data != NULL && data->usb_hub == hub) + return data; + } + + return NULL; +} + +static int hub_get_port_status(usb_hub_t *hub, uint8_t port, uint16_t *status, uint16_t *change) { + if (port >= hub->port_count) + return EINVAL; + + hub_driver_data_t *data = hub_get_driver_data(hub); + if (data == NULL) + return ENODEV; + + uint32_t port_status = data->status_buffer[port]; + if (status) + *status = port_status & 0xffff; + if (change) + *change = port_status >> 16; + + return 0; +} + +static void reset_port_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + (void)dev; + (void)transferred; + + if (status != USB_STATUS_SUCCESS) + printf("hub: failed to reset port %lu\n", (uintptr_t)ctx); +} + +static int hub_set_port_feature(usb_hub_t *hub, uint8_t port, uint16_t feature) { + if (port >= hub->port_count) + return EINVAL; + + usb_completion_callback_t callback = NULL; + void *callback_ctx = NULL; + if (feature == USB_HUB_FEATURE_PORT_RESET) { + callback = reset_port_callback; + callback_ctx = (void *)(uintptr_t)(port + 1); + } + + return hub_port_control_xfer(hub->device, USB_HUB_REQUEST_SET_FEATURE, feature, port, NULL, 0, callback, callback_ctx); +} + +static int hub_clear_port_feature(usb_hub_t *hub, uint8_t port, uint16_t feature) { + if (port >= hub->port_count) + return EINVAL; + + return hub_port_control_xfer(hub->device, USB_HUB_REQUEST_CLEAR_FEATURE, feature, port, NULL, 0, NULL, NULL); +} + +typedef struct { + int port; + hub_driver_data_t *data; +} callback_ctx_t; + +static void clear_port_changes(hub_driver_data_t *data, int port, uint16_t port_change) { + if (port_change & USB_HUB_PORT_CHANGE_PORT_CONNECTION) { + if (data->usb_hub->ops->clear_port_feature(data->usb_hub, port, USB_HUB_FEATURE_C_PORT_CONNECTION)) + printf("hub: failed to clear connection change on port %d\n", port); + } + if (port_change & USB_HUB_PORT_CHANGE_PORT_ENABLE) { + if (data->usb_hub->ops->clear_port_feature(data->usb_hub, port, USB_HUB_FEATURE_C_PORT_ENABLE)) + printf("hub: failed to clear enable change on port %d\n", port); + } + if (port_change & USB_HUB_PORT_CHANGE_PORT_OVER_CURRENT) { + if (data->usb_hub->ops->clear_port_feature(data->usb_hub, port, USB_HUB_FEATURE_C_PORT_OVER_CURRENT)) + printf("hub: failed to clear over-current change on port %d\n", port); + } + if (port_change & USB_HUB_PORT_CHANGE_PORT_RESET) { + if (data->usb_hub->ops->clear_port_feature(data->usb_hub, port, USB_HUB_FEATURE_C_PORT_RESET)) + printf("hub: failed to clear reset change on port %d\n", port); + } +} + +static usb_speed_t speed_from_port_status(usb_hub_t *hub, uint16_t port_status) { + if (hub->device->speed == USB_SPEED_SUPER || hub->device->speed == USB_SPEED_SUPER_PLUS) + return USB_SPEED_SUPER; + + if (port_status & USB_HUB_PORT_STATUS_LOW_SPEED) + return USB_SPEED_LOW; + if (port_status & USB_HUB_PORT_STATUS_HIGH_SPEED) + return USB_SPEED_HIGH; + + return USB_SPEED_FULL; +} + +static void handle_port_change(hub_driver_data_t *data, int port, uint16_t port_status, uint16_t port_change) { + if (port_change & USB_HUB_PORT_CHANGE_PORT_CONNECTION) { + if (port_status & USB_HUB_PORT_STATUS_PORT_CONNECTION) { + if (data->usb_hub->ports[port].device != NULL) + usb_hub_event_disconnect(data->usb_hub, port); + usb_hub_event_connect(data->usb_hub, port); + } else { + usb_hub_event_disconnect(data->usb_hub, port); + } + } else if (port_change & USB_HUB_PORT_CHANGE_PORT_RESET) { + usb_hub_event_reset(data->usb_hub, port, speed_from_port_status(data->usb_hub, port_status)); + } +} + +static void get_status_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + callback_ctx_t *callback_ctx = ctx; + hub_driver_data_t *data = callback_ctx->data; + int port = callback_ctx->port; + + if (status != USB_STATUS_SUCCESS) { + printf("hub: failed to get status of port %d\n", port); + goto out; + } + + uint16_t port_status = data->status_buffer[port] & 0xffff; + uint16_t port_change = data->status_buffer[port] >> 16; + + handle_port_change(data, port, port_status, port_change); + clear_port_changes(data, port, port_change); + +out: + free(ctx); +} + +static int insert_in_transfer(usb_device_t *dev, hub_driver_data_t *data); + +static bool hub_change_bitmap_has_port(hub_driver_data_t *data, size_t transferred, int port) { + size_t bit = port + 1; + size_t byte = bit / 8; + + if (byte >= transferred || byte >= data->in_bitmap_size || byte >= sizeof(data->in_buffer)) + return false; + + uint8_t *bitmap = (uint8_t *)&data->in_buffer; + return (bitmap[byte] & (1 << (bit % 8))) != 0; +} + +static void interrupt_in_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + hub_driver_data_t *data = ctx; + if (status != USB_STATUS_SUCCESS) { + printf("hub: IN interrupt failed; retrying\n"); + } else { + for (int port = 0; port < data->usb_hub->port_count; ++port) { + if (!hub_change_bitmap_has_port(data, transferred, port)) + continue; + + callback_ctx_t *callback_ctx = alloc(sizeof(callback_ctx_t)); + if (callback_ctx == NULL) { + printf("hub: out of memory to do get_status\n"); + continue; + } + + callback_ctx->port = port; + callback_ctx->data = data; + + if (get_status_cmd(dev, data, port, get_status_callback, callback_ctx)) { + printf("hub: get_status_cmd() failed\n"); + free(callback_ctx); + } + } + } + + data->in_buffer = 0; + if (insert_in_transfer(dev, data)) { + printf("hub: failed to insert in trasnfer during callback. hub will not be functional.\n"); + } +} + +static int insert_in_transfer(usb_device_t *dev, hub_driver_data_t *data) { + usb_xfer_t xfer = { + .ep = data->interrupt_in_endpoint, + .flags = USB_XFER_FLAG_TO_HOST, + .type = USB_XFER_TYPE_INTERRUPT, + .data = &data->in_buffer, + .data_length = data->in_bitmap_size, + .completion = interrupt_in_callback, + .completion_ctx = data + }; + + int error = usb_submit_xfer(dev, &xfer); + if (error) { + printf("hub: failed to submit IN xfer: %s\n", strerror(error)); + } + return error; +} + +static usb_hub_ops_t hub_ops = { + .get_port_status = hub_get_port_status, + .set_port_feature = hub_set_port_feature, + .clear_port_feature = hub_clear_port_feature, +}; + +static void init_status_get_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + hub_driver_data_t *data = ctx; + size_t port = data->ports_initialized; + + if (status != USB_STATUS_SUCCESS) { + printf("hub: failed to get first status of port %lu\n", port); + return; + } + + uint16_t port_status = data->status_buffer[port] & 0xffff; + uint16_t port_change = data->status_buffer[port] >> 16; + + if (port_status & USB_HUB_PORT_STATUS_PORT_CONNECTION) + usb_hub_event_connect(data->usb_hub, port); + + clear_port_changes(data, port, port_change); + + if (++data->ports_initialized != data->usb_hub->port_count) { + if (get_status_cmd(dev, data, data->ports_initialized, init_status_get_callback, data)) { + printf("hub: get_status_cmd() failed\n"); + } + } +} + +static int power_port(usb_device_t *dev, int port, hub_driver_data_t *data); + +static void initial_status_scan(usb_device_t *dev, hub_driver_data_t *data) { + if (get_status_cmd(dev, data, 0, init_status_get_callback, data)) + printf("hub: failed to get status of first port\n"); +} + +static void power_on_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + hub_driver_data_t *data = ctx; + + if (status != USB_STATUS_SUCCESS) { + printf("hub: failed to power on port %lu\n", data->ports_powered); + return; + } + + if (++data->ports_powered == data->usb_hub->port_count) { + usb_hub_desc_t *desc = (usb_hub_desc_t *)dev->hub_desc; + size_t power_good_us = (size_t)desc->bPwrOn2PwrGood * 2000; + if (power_good_us) + sched_sleep_us(power_good_us); + initial_status_scan(dev, data); + } else if (power_port(dev, data->ports_powered, data)) { + printf("hub: power_port() failed\n"); + } +} + +static int power_port(usb_device_t *dev, int port, hub_driver_data_t *data) { + return hub_port_control_xfer(data->usb_hub->device, USB_HUB_REQUEST_SET_FEATURE, USB_HUB_FEATURE_PORT_POWER, port, NULL, 0, power_on_callback, data); +} + +static bool hub_advertises_port_power_switching(usb_device_t *dev) { + usb_hub_desc_t *desc = (usb_hub_desc_t *)dev->hub_desc; + uint16_t power_mode = desc->wHubCharacteristics & USB_HUB_POWER_SWITCHING_MODE_MASK; + + return (power_mode & USB_HUB_POWER_SWITCHING_MODE_NONE) == 0; +} + +static void get_hub_name(usb_device_t *dev, char *buffer, size_t buffer_size) { + uint8_t major = (dev->desc.bcdUSB >> 8) & 0x0f; + uint8_t minor_tens = (dev->desc.bcdUSB >> 4) & 0x0f; + uint8_t minor_ones = dev->desc.bcdUSB & 0x0f; + + if (minor_ones) + snprintf(buffer, buffer_size, "usb-hub-%u.%u%u", major, minor_tens, minor_ones); + else + snprintf(buffer, buffer_size, "usb-hub-%u.%u", major, minor_tens); +} + +static int hub_attach(usb_device_t *dev, usb_interface_t *interface) { + __assert(dev->hub_desc); + hub_driver_data_t *hub_driver_data = alloc(sizeof(hub_driver_data_t)); + if (hub_driver_data == NULL) + return ENOMEM; + + hub_driver_data->usb_hub = alloc(sizeof(usb_hub_t)); + if (hub_driver_data->usb_hub == NULL) { + free(hub_driver_data); + return ENOMEM; + } + + get_hub_name(dev, hub_driver_data->usb_hub->name, sizeof(hub_driver_data->usb_hub->name)); + hub_driver_data->usb_hub->ctrl = dev->hub->ctrl; + hub_driver_data->usb_hub->ops = &hub_ops; + hub_driver_data->usb_hub->device = dev; + hub_driver_data->usb_hub->port_count = ((usb_hub_desc_t *)dev->hub_desc)->bNbrPorts; + if (hub_driver_data->usb_hub->port_count == 0) { + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return EINVAL; + } + + hub_driver_data->usb_hub->ports = alloc(sizeof(usb_hub_port_t) * hub_driver_data->usb_hub->port_count); + if (hub_driver_data->usb_hub->ports == NULL) { + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return ENOMEM; + } + + hub_driver_data->status_buffer = alloc(sizeof(uint32_t) * hub_driver_data->usb_hub->port_count); + if (hub_driver_data->status_buffer == NULL) { + free(hub_driver_data->usb_hub->ports); + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return ENOMEM; + } + + hub_driver_data->in_bitmap_size = ROUND_UP(hub_driver_data->usb_hub->port_count + 1, 8) / 8; + if (hub_driver_data->in_bitmap_size > sizeof(hub_driver_data->in_buffer)) { + printf("hub: too many ports for interrupt bitmap\n"); + free(hub_driver_data->status_buffer); + free(hub_driver_data->usb_hub->ports); + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return EINVAL; + } + + for (int i = 0; i < interface->desc->bNumEndpoints; ++i) { + if (interface->endpoints[i].desc && (interface->endpoints[i].desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_DIR_IN) && (interface->endpoints[i].desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK) == USB_ENDPOINT_ATTRIB_TYPE_INTR) + hub_driver_data->interrupt_in_endpoint = &interface->endpoints[i]; + } + + if (hub_driver_data->interrupt_in_endpoint == NULL) { + printf("hub: no IN interrupt endpoint found\n"); + free(hub_driver_data->status_buffer); + free(hub_driver_data->usb_hub->ports); + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return EINVAL; + } + + interface->driver_data = hub_driver_data; + + int error = insert_in_transfer(dev, hub_driver_data); + if (error) { + printf("hub: failed to insert first IN transfer\n"); + interface->driver_data = NULL; + free(hub_driver_data->status_buffer); + free(hub_driver_data->usb_hub->ports); + free(hub_driver_data->usb_hub); + free(hub_driver_data); + return error; + } + + if (hub_advertises_port_power_switching(dev)) { + if (power_port(dev, 0, hub_driver_data)) + printf("hub: failed to power first port\n"); + } else { + initial_status_scan(dev, hub_driver_data); + } + + return 0; +} + +static int hub_probe(usb_device_t *dev, usb_interface_t *interface) { + if (interface->desc->bInterfaceClass == USB_CLASS_HUB) + return USB_DRIVER_SCORE_CLASS_MATCH; + + return USB_DRIVER_SCORE_NONE; +} + +DEFINE_USB_CLASS_DRIVER(hub_class, + .name = "USB Hub", + .attach = hub_attach, + .probe = hub_probe + // TODO detach +) diff --git a/kernel-src/io/usb/core/hub.c b/kernel-src/io/usb/core/hub.c index f9253c08..5ba23d39 100644 --- a/kernel-src/io/usb/core/hub.c +++ b/kernel-src/io/usb/core/hub.c @@ -50,6 +50,13 @@ static bool endpoint_requires_ss_companion(usb_device_t *dev, usb_endpoint_t *en return (endpoint->desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK) != USB_ENDPOINT_ATTRIB_TYPE_CONTROL; } +static uint8_t hub_descriptor_type(usb_device_t *dev) { + if (dev->speed == USB_SPEED_SUPER || dev->speed == USB_SPEED_SUPER_PLUS) + return USB_DESCRIPTOR_TYPE_SS_HUB; + + return USB_DESCRIPTOR_TYPE_HUB; +} + static bool device_port_stale(usb_device_t *dev) { return dev->hub->ports[dev->port_number].generation != dev->port_generation; } @@ -144,6 +151,8 @@ static void post_config_cleanup(usb_device_t *dev) { free(dev->interfaces[i].endpoints); } + if (dev->hub_desc) + free(dev->hub_desc); free(dev->interfaces); free(dev->config_desc); dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev); @@ -169,6 +178,76 @@ static void endpoint_configured(usb_device_t *dev, void *ctx, usb_status_t statu post_config_cleanup(dev); } +static void marked_as_hub(usb_device_t *dev, void *, usb_status_t status, size_t) { + if (status != USB_STATUS_SUCCESS) { + printf("usb: failed to mark device as hub\n"); + post_config_cleanup(dev); + return; + } + + if (device_port_stale(dev)) { + post_config_cleanup(dev); + return; + } + + if (configure_next_endpoint(dev, 0, 0)) { + printf("usb: failed to configure first endpoint\n"); + post_config_cleanup(dev); + return; + } +} + +static void get_hub_desc_done(usb_device_t *dev, void *, usb_status_t status, size_t) { + if (status != USB_STATUS_SUCCESS) { + printf("usb: failed to get full hub descriptor\n"); + post_config_cleanup(dev); + return; + } + + if (device_port_stale(dev)) { + post_config_cleanup(dev); + return; + } + + if (dev->hub->ctrl->ops->mark_as_hub) { + if (dev->hub->ctrl->ops->mark_as_hub(dev->hub->ctrl, dev, marked_as_hub, NULL)) { + printf("usb: mark_as_hub failed\n"); + post_config_cleanup(dev); + } + } else { + if (configure_next_endpoint(dev, 0, 0)) { + printf("usb: failed to configure first endpoint of device\n"); + post_config_cleanup(dev); + } + } +} + +static void get_hub_desc_header_done(usb_device_t *dev, void *, usb_status_t status, size_t transferred) { + if (status != USB_STATUS_SUCCESS) { + printf("usb: failed to get hub descriptor header\n"); + post_config_cleanup(dev); + return; + } + + if (device_port_stale(dev)) { + post_config_cleanup(dev); + return; + } + + void *p = realloc(dev->hub_desc, dev->hub_desc->bLength); + if (p == NULL) { + printf("usb: out of memory to get full hub descriptor\n"); + post_config_cleanup(dev); + return; + } + dev->hub_desc = p; + + if (usb_get_class_device_descriptor(dev, hub_descriptor_type(dev), 0, dev->hub_desc, dev->hub_desc->bLength, get_hub_desc_done, NULL)) { + printf("usb: usb_get_class_device_descriptor failed\n"); + post_config_cleanup(dev); + } +} + // allocate and populate interface and endpoint datastructures, then start configuring endpoints static void set_configuration_done(usb_device_t *dev, void *, usb_status_t status, size_t transferred) { if (status != USB_STATUS_SUCCESS) { @@ -192,6 +271,7 @@ static void set_configuration_done(usb_device_t *dev, void *, usb_status_t statu return; } + bool has_hub = dev->desc.bDeviceClass == USB_CLASS_HUB; // set up interface data usb_for_each_descriptor(dev->config_desc, desc) { // since this is the first pass over the configure descriptor, @@ -224,6 +304,9 @@ static void set_configuration_done(usb_device_t *dev, void *, usb_status_t statu goto interface_setup_error; } + if (interface_desc->bInterfaceClass == USB_CLASS_HUB) + has_hub = true; + interface->desc = interface_desc; if (interface_desc->bNumEndpoints > 0) { interface->endpoints = alloc(interface_desc->bNumEndpoints * sizeof(usb_endpoint_t)); @@ -322,8 +405,22 @@ static void set_configuration_done(usb_device_t *dev, void *, usb_status_t statu } } - if (configure_next_endpoint(dev, 0, 0) == 0) - return; // success, wait for endpoint to be configured. + if (has_hub) { + // to set up the context in xhci + dev->hub_desc = alloc(sizeof(usb_desc_hdr_t)); + if (dev->hub_desc == NULL) { + printf("usb: out of memory to get hub descriptor header\n"); + post_config_cleanup(dev); + return; + } + + if (usb_get_class_device_descriptor(dev, hub_descriptor_type(dev), 0, dev->hub_desc, sizeof(usb_desc_hdr_t), get_hub_desc_header_done, NULL) == 0) + return; + + printf("usb: usb_get_class_device_descriptor failed\n"); + } else if (configure_next_endpoint(dev, 0, 0) == 0) { + return; // success, wait for endpoint to be configured. + } interface_setup_error: post_config_cleanup(dev); @@ -435,8 +532,8 @@ static void address_done(usb_hub_t *hub, uint8_t port, usb_device_t *dev) { } // address device -void usb_hub_event_reset(usb_hub_t *hub, int port) { - if (hub->ctrl->ops->address_device(hub->ctrl, hub, port, address_done)) { +void usb_hub_event_reset(usb_hub_t *hub, int port, usb_speed_t speed) { + if (hub->ctrl->ops->address_device(hub->ctrl, hub, port, speed, address_done)) { printf("usb_hub %s: failed to address device\n", hub->name); } } diff --git a/kernel-src/io/usb/core/usb.c b/kernel-src/io/usb/core/usb.c index ee701dee..f5efc8bb 100644 --- a/kernel-src/io/usb/core/usb.c +++ b/kernel-src/io/usb/core/usb.c @@ -25,9 +25,9 @@ int usb_control_xfer(usb_device_t *dev, usb_setup_t *setup, void *buffer, usb_co return usb_submit_xfer(dev, &xfer); } -static int usb_get_descriptor_for_recipient(usb_device_t *dev, uint8_t recipient, uint16_t index, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { +static int usb_get_descriptor_for_recipient(usb_device_t *dev, uint8_t request_type, uint8_t recipient, uint16_t index, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { usb_setup_t setup = {0}; - setup.bmRequestType = recipient | USB_REQUEST_STANDARD | USB_REQUEST_DIR_TO_HOST; + setup.bmRequestType = recipient | request_type | USB_REQUEST_DIR_TO_HOST; setup.bRequest = USB_REQUEST_GET_DESCRIPTOR; setup.wValue = ((uint16_t)desc_type << 8) | desc_index; setup.wIndex = index; @@ -37,15 +37,19 @@ static int usb_get_descriptor_for_recipient(usb_device_t *dev, uint8_t recipient } int usb_get_device_descriptor(usb_device_t *dev, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { - return usb_get_descriptor_for_recipient(dev, USB_REQUEST_RECIP_DEVICE, 0, desc_type, desc_index, buffer, length, callback, callback_ctx); + return usb_get_descriptor_for_recipient(dev, USB_REQUEST_STANDARD, USB_REQUEST_RECIP_DEVICE, 0, desc_type, desc_index, buffer, length, callback, callback_ctx); +} + +int usb_get_class_device_descriptor(usb_device_t *dev, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { + return usb_get_descriptor_for_recipient(dev, USB_REQUEST_CLASS, USB_REQUEST_RECIP_DEVICE, 0, desc_type, desc_index, buffer, length, callback, callback_ctx); } int usb_get_interface_descriptor(usb_device_t *dev, uint16_t interface_number, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { - return usb_get_descriptor_for_recipient(dev, USB_REQUEST_RECIP_INTERFACE, interface_number, desc_type, desc_index, buffer, length, callback, callback_ctx); + return usb_get_descriptor_for_recipient(dev, USB_REQUEST_STANDARD, USB_REQUEST_RECIP_INTERFACE, interface_number, desc_type, desc_index, buffer, length, callback, callback_ctx); } int usb_get_endpoint_descriptor(usb_device_t *dev, uint16_t endpoint_address, uint8_t desc_type, uint8_t desc_index, void *buffer, uint16_t length, usb_completion_callback_t callback, void *callback_ctx) { - return usb_get_descriptor_for_recipient(dev, USB_REQUEST_RECIP_ENDPOINT, endpoint_address, desc_type, desc_index, buffer, length, callback, callback_ctx); + return usb_get_descriptor_for_recipient(dev, USB_REQUEST_STANDARD, USB_REQUEST_RECIP_ENDPOINT, endpoint_address, desc_type, desc_index, buffer, length, callback, callback_ctx); } int usb_set_configuration(usb_device_t *dev, uint8_t config_value, usb_completion_callback_t callback) { diff --git a/kernel-src/io/usb/ctrl/xhci_ctrl.c b/kernel-src/io/usb/ctrl/xhci_ctrl.c index 76a53e70..5121ed1e 100644 --- a/kernel-src/io/usb/ctrl/xhci_ctrl.c +++ b/kernel-src/io/usb/ctrl/xhci_ctrl.c @@ -90,6 +90,35 @@ static void xhci_update_input_context(xhci_ctrl_t *ctrl, xhci_device_t *dev) { memset(input_ctx, 0, sizeof(uint32_t) * 2); } +static int xhci_update_hub_slot_context(usb_device_t *dev, volatile xhci_slot_ctx_t *slot_ctx) { + if (dev->hub_desc == NULL) + return 0; + + slot_ctx->dw0.hub = 1; + + if (dev->hub_desc->bDescriptorType == USB_DESCRIPTOR_TYPE_SS_HUB) { + if (dev->hub_desc->bLength < sizeof(usb_ss_hub_desc_t)) + return EINVAL; + + usb_ss_hub_desc_t *hub_desc = (usb_ss_hub_desc_t *)dev->hub_desc; + slot_ctx->dw0.mtt = 0; + slot_ctx->dw1.number_of_ports = hub_desc->bNbrPorts; + return 0; + } + + if (dev->hub_desc->bDescriptorType == USB_DESCRIPTOR_TYPE_HUB) { + if (dev->hub_desc->bLength < sizeof(usb_hub_desc_t)) + return EINVAL; + + usb_hub_desc_t *hub_desc = (usb_hub_desc_t *)dev->hub_desc; + slot_ctx->dw0.mtt = dev->desc.bDeviceProtocol == USB_HUB_PROTOCOL_MULTI_TT; + slot_ctx->dw1.number_of_ports = hub_desc->bNbrPorts; + return 0; + } + + return EINVAL; +} + static void xhci_release_device_resources(xhci_ctrl_t *xhci, xhci_device_t *dev) { for (int i = 0; i < 31; ++i) { if (dev->ep_rings[i].ring != NULL) @@ -324,7 +353,7 @@ static void address_device_create_slot_callback(usb_device_t *device, void *ctx, xhci_ring_command_doorbell(xhci); } -static int xhci_ctrl_address_device(usb_ctrl_t *ctrl, usb_hub_t *hub, uint8_t port, usb_address_device_callback_t callback) { +static int xhci_ctrl_address_device(usb_ctrl_t *ctrl, usb_hub_t *hub, uint8_t port, usb_speed_t speed, usb_address_device_callback_t callback) { ++port; // convert to the format xhci expects xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); @@ -378,7 +407,6 @@ static int xhci_ctrl_address_device(usb_ctrl_t *ctrl, usb_hub_t *hub, uint8_t po xhci_dev->root_port_number = port; } - // TODO the device speed is gonna come from elsewhere and not from the (just now allocated) usb device if (hub->device == NULL) { // this is a root hub! we can just take the speed directly from the portsc register xhci_root_hub_t *rh = container_of(hub, xhci_root_hub_t, hub); @@ -386,10 +414,13 @@ static int xhci_ctrl_address_device(usb_ctrl_t *ctrl, usb_hub_t *hub, uint8_t po uint32_t port_speed = (portsc >> 10) & 0xf; xhci_dev->ctrl_speed = port_speed; xhci_dev->device.speed = xhci_speed_to_usb_speed(port_speed); - } else if (!usb_speed_to_xhci_speed(xhci_dev->device.speed, &xhci_dev->ctrl_speed)) { - // this is a super speed plus device behind a hub. - // we can't know its speed in advance, so set it as super speed and figure it out later. - xhci_dev->ctrl_speed = XHCI_PORT_SPEED_SUPER; + } else { + xhci_dev->device.speed = speed; + if (!usb_speed_to_xhci_speed(xhci_dev->device.speed, &xhci_dev->ctrl_speed)) { + // This is a super speed plus device behind a hub. + // We can't know its speed in advance, so set it as super speed and figure it out later. + xhci_dev->ctrl_speed = XHCI_PORT_SPEED_SUPER; + } } xhci_submission_t xhci_submission = { @@ -463,6 +494,13 @@ static int xhci_ctrl_configure_ep(usb_ctrl_t *ctrl, usb_device_t *dev, usb_endpo if (slot_ctx->dw0.ctx_entries < ep_index) slot_ctx->dw0.ctx_entries = ep_index; + int error = xhci_update_hub_slot_context(dev, slot_ctx); + if (error) { + free(configure_ctx); + xhci_release_ep_ring(xhci_dev, ep_index); + return error; + } + // Set up the endpoint context. uint8_t ep_type = ep->desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK; uint16_t max_packet_size = usb_endpoint_max_packet_size(ep->desc); diff --git a/kernel-src/io/usb/ctrl/xhci_hub.c b/kernel-src/io/usb/ctrl/xhci_hub.c index cceb92b6..430efd65 100644 --- a/kernel-src/io/usb/ctrl/xhci_hub.c +++ b/kernel-src/io/usb/ctrl/xhci_hub.c @@ -37,7 +37,7 @@ void xhci_handle_port_change_event(xhci_ctrl_t *xhci, int global_port) { usb_hub_event_disconnect(&hub->hub, local_port); usb_hub_event_connect(&hub->hub, local_port); } else if (reset_event) { - usb_hub_event_reset(&hub->hub, local_port); + usb_hub_event_reset(&hub->hub, local_port, USB_SPEED_UNKNOWN); } xhci->portregs[global_port - 1].portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | diff --git a/recipes/astral b/recipes/astral index 50bf39c2..6e3a54e1 100644 --- a/recipes/astral +++ b/recipes/astral @@ -1,5 +1,5 @@ name=astral -revision=4 +revision=5 from_source=astral imagedeps="build-essential nasm" hostdeps="xgcc xbinutils limine"