Files
Astral/kernel-src/io/usb/core/hub.c
T
2026-05-13 23:48:29 -03:00

575 lines
17 KiB
C

#include <kernel/usb.h>
#include <logging.h>
#include <kernel/alloc.h>
// TODO support using other configurations (for simplicity, we will just pick the first one for now)
extern usb_class_driver_t *usb_class_drivers;
extern usb_class_driver_t *usb_class_drivers_end;
static void endpoint_configured(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred);
static bool endpoint_desc_valid(usb_device_t *dev, usb_endpoint_desc_t *desc) {
uint8_t ep_num = desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_NUM_MASK;
uint8_t ep_type = desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK;
uint16_t max_packet_size = usb_endpoint_max_packet_size(desc);
uint8_t high_speed_transactions = (desc->wMaxPacketSize >> 11) & 0x3;
if (ep_num == 0)
return false;
if (max_packet_size == 0)
return false;
if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_BULK) {
if (dev->speed == USB_SPEED_LOW)
return false;
if (dev->speed == USB_SPEED_HIGH && high_speed_transactions != 0)
return false;
} else if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_INTR) {
if (desc->bInterval == 0)
return false;
if ((dev->speed == USB_SPEED_HIGH ||
dev->speed == USB_SPEED_SUPER ||
dev->speed == USB_SPEED_SUPER_PLUS) &&
desc->bInterval > 16)
return false;
if (dev->speed == USB_SPEED_HIGH && high_speed_transactions == 3)
return false;
} else {
return false;
}
return true;
}
static bool endpoint_requires_ss_companion(usb_device_t *dev, usb_endpoint_t *endpoint) {
if (dev->speed != USB_SPEED_SUPER && dev->speed != USB_SPEED_SUPER_PLUS)
return false;
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;
}
static int attach_interface_to_driver(usb_device_t *device, usb_interface_t *interface) {
int best_score = 0;
usb_class_driver_t *best_driver = NULL;
for (usb_class_driver_t **it = &usb_class_drivers; it < &usb_class_drivers_end; ++it) {
usb_class_driver_t *driver = *it;
int score = driver->probe(device, interface);
if (score > best_score) {
best_score = score;
best_driver = driver;
}
}
if (best_driver == NULL) {
printf("usb: no driver found for interface %u\n", interface->desc->bInterfaceNumber);
return 0;
}
if (best_driver->attach(device, interface)) {
printf("usb: attaching %s port %u addr %u interface %u to driver '%s' failed\n",
device->hub->name,
device->port_number + 1,
device->address,
interface->desc->bInterfaceNumber,
best_driver->name);
return 0;
}
interface->driver = best_driver;
printf("usb: %s port %u addr %u device %04x:%04x interface %u attached to driver '%s' (score: %d)\n",
device->hub->name,
device->port_number + 1,
device->address,
device->desc.idVendor,
device->desc.idProduct,
interface->desc->bInterfaceNumber,
best_driver->name,
best_score);
return 0;
}
// device plug ininitialization is complete, pass control for each interface to a driver
static int attach_to_drivers(usb_device_t *device) {
if (device_port_stale(device))
return ENODEV;
device->hub->ports[device->port_number].device = device;
for (int i = 0; i < device->config_desc->bNumInterfaces; ++i) {
usb_interface_t *interface = &device->interfaces[i];
if (interface->desc == NULL)
return EINVAL;
if (attach_interface_to_driver(device, interface))
return EINVAL;
}
return 0;
}
static int configure_next_endpoint(usb_device_t *dev, uint8_t interface_idx, uint8_t endpoint_idx) {
if (device_port_stale(dev))
return ENODEV;
for (; interface_idx < dev->config_desc->bNumInterfaces; interface_idx++, endpoint_idx = 0) {
usb_interface_t *interface = &dev->interfaces[interface_idx];
if (interface->desc == NULL)
return EINVAL;
if (endpoint_idx < interface->desc->bNumEndpoints) {
usb_endpoint_t *endpoint = &interface->endpoints[endpoint_idx];
if (endpoint->desc == NULL)
return EINVAL;
uintptr_t id = endpoint_idx | (interface_idx << 8);
return dev->hub->ctrl->ops->configure_ep(dev->hub->ctrl, dev, endpoint, endpoint_configured, (void *)id);
}
}
return attach_to_drivers(dev);
}
static void post_config_cleanup(usb_device_t *dev) {
for (int i = 0; i < dev->config_desc->bNumInterfaces; ++i) {
if (dev->interfaces[i].endpoints)
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);
}
// configure all the endpoints and attach to driver if the last endpoint was configured
static void endpoint_configured(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) {
if (status != USB_STATUS_SUCCESS)
goto error;
if (device_port_stale(dev))
goto error;
uintptr_t id = (uintptr_t)ctx;
uint8_t endpoint_idx = (id & 0xff) + 1;
uint8_t interface_idx = (id >> 8) & 0xff;
if (configure_next_endpoint(dev, interface_idx, endpoint_idx) == 0)
return; // success, wait for endpoint to be configured.
error:
printf("usb: endpoint config failed\n");
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) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: failed to set configuration\n");
return;
}
if (device_port_stale(dev)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
return;
}
dev->interfaces = alloc(dev->config_desc->bNumInterfaces * sizeof(usb_interface_t));
if (dev->interfaces == NULL) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: out of memory\n");
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,
// we will verify if it is valid and fail the enumeration if not
USB_FOR_EACH_DESCRIPTOR_CHECK(dev->config_desc, desc) {
printf("usb: malformed config description\n");
goto interface_setup_error;
}
if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_INTERFACE)
continue;
if (desc->bLength < sizeof(usb_interface_desc_t)) {
printf("usb: malformed interface description\n");
goto interface_setup_error;
}
usb_interface_desc_t *interface_desc = (usb_interface_desc_t *)desc;
if (interface_desc->bAlternateSetting)
continue;
if (interface_desc->bInterfaceNumber >= dev->config_desc->bNumInterfaces) {
printf("usb: malformed interface description\n");
goto interface_setup_error;
}
usb_interface_t *interface = &dev->interfaces[interface_desc->bInterfaceNumber];
if (interface->desc != NULL) {
printf("usb: duplicate interface description\n");
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));
if (interface->endpoints == NULL) {
printf("usb: out of memory\n");
goto interface_setup_error;
}
}
}
// check for missing interfaces
for (int i = 0; i < dev->config_desc->bNumInterfaces; ++i) {
if (dev->interfaces[i].desc == NULL) {
printf("usb: missing interface description\n");
goto interface_setup_error;
}
}
// set up endpoint data
usb_interface_t *current_interface = NULL;
size_t current_endpoint = 0;
usb_for_each_descriptor(dev->config_desc, desc) {
USB_FOR_EACH_DESCRIPTOR_CHECK(dev->config_desc, desc) {
printf("usb: malformed config description\n");
goto interface_setup_error;
}
if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) {
if (desc->bLength < sizeof(usb_interface_desc_t)) {
printf("usb: malformed interface description\n");
goto interface_setup_error;
}
usb_interface_desc_t *interface_desc = (usb_interface_desc_t *)desc;
if (interface_desc->bInterfaceNumber >= dev->config_desc->bNumInterfaces) {
printf("usb: malformed interface description\n");
goto interface_setup_error;
}
if (interface_desc->bAlternateSetting) {
current_endpoint = 0;
current_interface = NULL;
continue;
}
current_endpoint = 0;
current_interface = &dev->interfaces[interface_desc->bInterfaceNumber];
continue;
}
if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_ENDPOINT) {
// skip any endpoints on an alternate setting
if (current_interface == NULL)
continue;
if (desc->bLength < sizeof(usb_endpoint_desc_t)) {
printf("usb: malformed endpoint description\n");
goto interface_setup_error;
}
if (current_endpoint >= current_interface->desc->bNumEndpoints) {
printf("usb: malformed interface description\n");
goto interface_setup_error;
}
usb_endpoint_desc_t *endpoint_desc = (usb_endpoint_desc_t *)desc;
if (!endpoint_desc_valid(dev, endpoint_desc)) {
printf("usb: malformed endpoint description\n");
goto interface_setup_error;
}
current_interface->endpoints[current_endpoint++].desc = endpoint_desc;
} else if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_SS_EP_COMPANION) {
if (current_interface == NULL || current_endpoint == 0 || desc->bLength < sizeof(usb_ss_ep_companion_desc_t)) {
printf("usb: malformed endpoint companion description\n");
goto interface_setup_error;
}
current_interface->endpoints[current_endpoint - 1].ss_companion = (usb_ss_ep_companion_desc_t *)desc;
}
}
// verify if we got all advertised endpoints
for (int i = 0; i < dev->config_desc->bNumInterfaces; ++i) {
usb_interface_t *interface = &dev->interfaces[i];
for (int j = 0; j < interface->desc->bNumEndpoints; ++j) {
if (interface->endpoints[j].desc == NULL) {
printf("usb: missing endpoint description\n");
goto interface_setup_error;
}
if (endpoint_requires_ss_companion(dev, &interface->endpoints[j]) && interface->endpoints[j].ss_companion == NULL) {
printf("usb: missing endpoint companion description\n");
goto interface_setup_error;
}
}
}
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);
}
// set default configuration
static void get_configuration_done(usb_device_t *dev, void *, usb_status_t status, size_t transferred) {
if (status != USB_STATUS_SUCCESS) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: failed to get config descriptor\n");
return;
}
if (device_port_stale(dev)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
return;
}
if (usb_set_configuration(dev, dev->config_desc->bConfigurationValue, set_configuration_done)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: usb_set_configuration failed\n");
}
}
static bool config_desc_header_valid(usb_config_desc_t *desc, size_t transferred) {
if (transferred < sizeof(usb_config_desc_t))
return false;
if (desc->bLength != sizeof(usb_config_desc_t))
return false;
if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_CONFIG)
return false;
if (desc->wTotalLength < desc->bLength)
return false;
if (desc->wTotalLength > USB_MAX_CONFIG_DESC_SIZE)
return false;
return true;
}
// get the rest of configuration
static void get_configuration_header_done(usb_device_t *dev, void *, usb_status_t status, size_t transferred) {
if (status != USB_STATUS_SUCCESS) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: usb_set_configuration failed\n");
return;
}
if (device_port_stale(dev)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
return;
}
if (!config_desc_header_valid(dev->config_desc, transferred)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: malformed config descriptor header\n");
return;
}
void *p = realloc(dev->config_desc, dev->config_desc->wTotalLength);
if (p == NULL) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: out of memory to get configuration\n");
return;
}
dev->config_desc = p;
if (usb_get_device_descriptor(dev, USB_DESCRIPTOR_TYPE_CONFIG, 0, dev->config_desc, dev->config_desc->wTotalLength, get_configuration_done, NULL)) {
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
printf("usb: usb_get_device_descriptor failed\n");
}
}
// get configuration descriptor size
static void address_done(usb_hub_t *hub, uint8_t port, usb_device_t *dev) {
if (dev == NULL) {
printf("usb: failed to address device in plug-in path\n");
return;
}
if (device_port_stale(dev)) {
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
return;
}
dev->config_desc = alloc(sizeof(usb_config_desc_t));
if (dev->config_desc == NULL) {
printf("usb: out of memory to get configuration\n");
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
return;
}
if (usb_get_device_descriptor(dev, USB_DESCRIPTOR_TYPE_CONFIG, 0, dev->config_desc, sizeof(usb_config_desc_t), get_configuration_header_done, NULL)) {
printf("usb: usb_get_device_descriptor failed\n");
free(dev->config_desc);
dev->hub->ctrl->ops->deaddress_device(dev->hub->ctrl, dev);
}
}
// address device
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);
}
}
// reset device
void usb_hub_event_connect(usb_hub_t *hub, int port) {
__assert(port < hub->port_count);
hub->ports[port].generation++;
if (hub->ops->set_port_feature(hub, port, USB_HUB_FEATURE_PORT_RESET)) {
printf("usb_hub %s: port reset failed\n", hub->name);
}
}
void usb_hub_event_disconnect(usb_hub_t* hub, int port) {
__assert(port < hub->port_count);
hub->ports[port].generation++;
usb_device_t *device = hub->ports[port].device;
if (device == NULL) {
printf("usb: unplugged device during device init\n");
return;
}
hub->ports[port].device = NULL;
// TODO in the case of a hub driver this would also cause a sort of "recursive disconnect" from there
for (int i = 0; i < device->config_desc->bNumInterfaces; ++i) {
usb_interface_t *interface = &device->interfaces[i];
if (interface->driver && interface->driver->detach)
interface->driver->detach(device, interface);
}
// TODO block new transfers
// TODO stop transfers
post_config_cleanup(device);
}