From 7a23528650bc3add8566e64015dd1f5431156d54 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 11 May 2026 01:24:24 -0300 Subject: [PATCH 1/8] util: log2_ceil --- kernel-src/include/util.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel-src/include/util.h b/kernel-src/include/util.h index acc4485b..5cd7ed42 100644 --- a/kernel-src/include/util.h +++ b/kernel-src/include/util.h @@ -23,6 +23,13 @@ static inline unsigned long log2(unsigned long x) { return sizeof(unsigned long) * 8 - __builtin_clzll(x) - 1; } +static inline unsigned long ceil_log2(unsigned long x) { + if (x <= 1) + return 0; + + return log2(x - 1) + 1; +} + #define min(x, y) (x > y ? y : x) #define max(x, y) (x < y ? y : x) From de54054142f125fc5874989fe27fc95251134d18 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 11 May 2026 01:24:38 -0300 Subject: [PATCH 2/8] iovec: fix page_remaining --- kernel-src/io/iovec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel-src/io/iovec.c b/kernel-src/io/iovec.c index 362678ef..c0f5e52c 100644 --- a/kernel-src/io/iovec.c +++ b/kernel-src/io/iovec.c @@ -7,6 +7,7 @@ static inline bool iovec_iterator_finished(iovec_iterator_t *iovec_iterator) { } bool iovec_user_check(iovec_t *iovec, size_t count) { + // TODO add size overflow check for (int i = 0; i < count; ++i) { // POSIX says that when len is zero, the addr can be an invalid buffer if (iovec[i].len && IS_USER_ADDRESS(iovec[i].addr) == false) @@ -250,7 +251,7 @@ int iovec_iterator_next_page(iovec_iterator_t *iovec_iterator, size_t *page_offs return EFAULT; *page_offset = offset_in_page; - *page_remaining = min(PAGE_SIZE, remaining); + *page_remaining = min(PAGE_SIZE - offset_in_page, remaining); *page = phys; iovec_iterator_skip(iovec_iterator, *page_remaining); return 0; From c4dbf30716e47fea11e98e2690b12fd407539aee Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 11 May 2026 12:09:07 -0300 Subject: [PATCH 3/8] resource_allocator: fix interrupt state leak --- kernel-src/lib/resource_allocator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel-src/lib/resource_allocator.c b/kernel-src/lib/resource_allocator.c index 9aecf0a9..c69087d8 100644 --- a/kernel-src/lib/resource_allocator.c +++ b/kernel-src/lib/resource_allocator.c @@ -39,6 +39,7 @@ size_t resource_allocate(resource_allocator_t *allocator, size_t count) { sched_prepare_sleep(false); spinlock_release(&allocator->spinlock); sched_yield(); + interrupt_set(status); return count; } From 9697ee777a5cba5f06ac2fcbd1e4d2bb01cfab9f Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 5 May 2026 00:35:11 -0300 Subject: [PATCH 4/8] kernel: initial xhci and usb implementation --- kernel-src/arch/x86-64/kernel.ld | 13 +- kernel-src/include/kernel/interrupt.h | 1 + kernel-src/include/kernel/pci.h | 3 + kernel-src/include/kernel/usb.h | 387 +++++++++++++ kernel-src/include/kernel/xhci.h | 450 +++++++++++++++ kernel-src/io/usb/core/hub.c | 477 ++++++++++++++++ kernel-src/io/usb/core/usb.c | 60 ++ kernel-src/io/usb/ctrl/xhci.c | 762 ++++++++++++++++++++++++++ kernel-src/io/usb/ctrl/xhci_ctrl.c | 583 ++++++++++++++++++++ kernel-src/io/usb/ctrl/xhci_hub.c | 153 ++++++ kernel-src/io/usb/ctrl/xhci_ring.c | 130 +++++ kernel-src/io/usb/ctrl/xhci_xfer.c | 288 ++++++++++ 12 files changed, 3304 insertions(+), 3 deletions(-) create mode 100644 kernel-src/include/kernel/usb.h create mode 100644 kernel-src/include/kernel/xhci.h create mode 100644 kernel-src/io/usb/core/hub.c create mode 100644 kernel-src/io/usb/core/usb.c create mode 100644 kernel-src/io/usb/ctrl/xhci.c create mode 100644 kernel-src/io/usb/ctrl/xhci_ctrl.c create mode 100644 kernel-src/io/usb/ctrl/xhci_hub.c create mode 100644 kernel-src/io/usb/ctrl/xhci_ring.c create mode 100644 kernel-src/io/usb/ctrl/xhci_xfer.c diff --git a/kernel-src/arch/x86-64/kernel.ld b/kernel-src/arch/x86-64/kernel.ld index 6f3f0c48..86c7921d 100644 --- a/kernel-src/arch/x86-64/kernel.ld +++ b/kernel-src/arch/x86-64/kernel.ld @@ -20,7 +20,7 @@ SECTIONS } :text . = ALIGN(4K); - _text_end = .; + _text_end = .; _rodata_start = .; .rodata : { *(.rodata .rodata.*); @@ -32,7 +32,14 @@ SECTIONS *(.timekeeper_sources); timekeeper_sources_end = .; } :rodata - + + . = ALIGN(8); + .usb_class_drivers : { + usb_class_drivers = .; + *(.usb_class_drivers); + usb_class_drivers_end = .; + } :rodata + . = ALIGN(8); .init_routines : { init_routines = .; @@ -60,7 +67,7 @@ SECTIONS *(COMMON) *(.bss .bss.*) } :data - + . = ALIGN(4K); _data_end = .; } diff --git a/kernel-src/include/kernel/interrupt.h b/kernel-src/include/kernel/interrupt.h index 30805a54..403736a9 100644 --- a/kernel-src/include/kernel/interrupt.h +++ b/kernel-src/include/kernel/interrupt.h @@ -19,6 +19,7 @@ #define IPL_DISK 500 #define IPL_SERIAL 550 #define IPL_INPUT 600 +#define IPL_USB 675 #define IPL_ACPI 700 #define IPL_DPC 900 #define IPL_NORMAL 1000 diff --git a/kernel-src/include/kernel/pci.h b/kernel-src/include/kernel/pci.h index 1d03c7a7..0c9e9129 100644 --- a/kernel-src/include/kernel/pci.h +++ b/kernel-src/include/kernel/pci.h @@ -22,11 +22,14 @@ #define PCI_CONFIG_REVISION 0x8 #define PCI_CONFIG_PROGIF 0x9 #define PCI_PROGIF_STORAGE_SATA_AHCI 0x1 + #define PCI_PROGIF_USB_XHCI 0x30 #define PCI_CONFIG_SUBCLASS 0xa #define PCI_SUBCLASS_STORAGE_NVM 0x8 #define PCI_SUBCLASS_STORAGE_SATA 0x6 + #define PCI_SUBCLASS_SERIAL_BUS_USB 0x3 #define PCI_CONFIG_CLASS 0xb #define PCI_CLASS_STORAGE 0x1 + #define PCI_CLASS_SERIAL_BUS 0xc #define PCI_CONFIG_HEADERTYPE 0xe #define PCI_HEADERTYPE_MULTIFUNCTION_MASK 0x80 #define PCI_HEADERTYPE_MASK 0x7f diff --git a/kernel-src/include/kernel/usb.h b/kernel-src/include/kernel/usb.h new file mode 100644 index 00000000..076e7b9e --- /dev/null +++ b/kernel-src/include/kernel/usb.h @@ -0,0 +1,387 @@ +#ifndef _USB_H +#define _USB_H + +#include +#include +#include + +#include +#include + +typedef struct usb_hub_port usb_hub_port_t; +typedef struct usb_hub usb_hub_t; +typedef struct usb_device usb_device_t; +typedef struct usb_interface usb_interface_t; +typedef struct usb_endpoint usb_endpoint_t; +typedef struct usb_xfer usb_xfer_t; +typedef struct usb_ctrl usb_ctrl_t; +typedef struct usb_class_driver usb_class_driver_t; + +#define USB_DESCRIPTOR_TYPE_DEVICE 1 +#define USB_DESCRIPTOR_TYPE_CONFIG 2 +#define USB_DESCRIPTOR_TYPE_STRING 3 +#define USB_DESCRIPTOR_TYPE_INTERFACE 4 +#define USB_DESCRIPTOR_TYPE_ENDPOINT 5 +#define USB_DESCRIPTOR_TYPE_SS_EP_COMPANION 48 + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; +} usb_desc_hdr_t; + +#define USB_MAX_CONFIG_DESC_SIZE 4096 + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} __attribute__((packed)) usb_device_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; +} __attribute__((packed)) usb_config_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} __attribute__((packed)) usb_interface_desc_t; + +#define USB_ENDPOINT_ADDRESS_NUM_MASK 0x0f +#define USB_ENDPOINT_ADDRESS_DIR_IN 0x80 + +#define USB_ENDPOINT_ATTRIB_TYPE_MASK 0x03 +#define USB_ENDPOINT_ATTRIB_TYPE_CONTROL 0x00 +#define USB_ENDPOINT_ATTRIB_TYPE_ISOCH 0x01 +#define USB_ENDPOINT_ATTRIB_TYPE_BULK 0x02 +#define USB_ENDPOINT_ATTRIB_TYPE_INTR 0x03 + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; +} __attribute__((packed)) usb_endpoint_desc_t; + +#define USB_ENDPOINT_MAX_PACKET_SIZE_MASK 0x7ff + +static inline uint16_t usb_endpoint_max_packet_size(usb_endpoint_desc_t *desc) { + return desc->wMaxPacketSize & USB_ENDPOINT_MAX_PACKET_SIZE_MASK; +} + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bMaxBurst; + uint8_t bmAttributes; + uint16_t wBytesPerInterval; +} __attribute__((packed)) usb_ss_ep_companion_desc_t; + +#define USB_REQUEST_RECIP_DEVICE 0x00 +#define USB_REQUEST_RECIP_INTERFACE 0x01 +#define USB_REQUEST_RECIP_ENDPOINT 0x02 +#define USB_REQUEST_RECIP_OTHER 0x03 + +#define USB_REQUEST_STANDARD 0x00 +#define USB_REQUEST_CLASS 0x20 +#define USB_REQUEST_VENDOR 0x40 + +#define USB_REQUEST_DIR_TO_DEVICE 0x00 +#define USB_REQUEST_DIR_TO_HOST 0x80 + +#define USB_REQUEST_GET_DESCRIPTOR 6 +#define USB_REQUEST_SET_CONFIGURATION 9 +#define USB_REQUEST_SET_INTERFACE 11 + +typedef struct { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} __attribute__((packed)) usb_setup_t; + +typedef enum { + USB_HUB_PORT_DISCONNECTED, + USB_HUB_PORT_RESETTING, + USB_HUB_PORT_ENABLED, +} usb_hub_port_status_t; + +struct usb_hub_port { + usb_device_t *device; + uint32_t generation; +}; + +#define USB_HUB_PORT_STATUS_PORT_CONNECTION (1 << 0) +#define USB_HUB_PORT_STATUS_PORT_ENABLE (1 << 1) +#define USB_HUB_PORT_STATUS_PORT_OVER_CURRENT (1 << 3) +#define USB_HUB_PORT_STATUS_PORT_RESET (1 << 4) +#define USB_HUB_PORT_STATUS_PORT_POWER (1 << 8) +#define USB_HUB_PORT_STATUS_LOW_SPEED (1 << 9) +#define USB_HUB_PORT_STATUS_HIGH_SPEED (1 << 10) + +#define USB_HUB_PORT_CHANGE_PORT_CONNECTION (1 << 0) +#define USB_HUB_PORT_CHANGE_PORT_ENABLE (1 << 1) +#define USB_HUB_PORT_CHANGE_PORT_OVER_CURRENT (1 << 3) +#define USB_HUB_PORT_CHANGE_PORT_RESET (1 << 4) + +#define USB_HUB_FEATURE_PORT_CONNECTION 0 +#define USB_HUB_FEATURE_PORT_ENABLE 1 +#define USB_HUB_FEATURE_PORT_RESET 4 +#define USB_HUB_FEATURE_PORT_POWER 8 +#define USB_HUB_FEATURE_PORT_LOW_SPEED 9 +#define USB_HUB_FEATURE_C_PORT_CONNECTION 16 +#define USB_HUB_FEATURE_C_PORT_ENABLE 17 +#define USB_HUB_FEATURE_C_PORT_OVER_CURRENT 19 +#define USB_HUB_FEATURE_C_PORT_RESET 20 + +typedef struct { + int (*get_port_status)(usb_hub_t *, uint8_t port, uint16_t *status, uint16_t *change); + int (*set_port_feature)(usb_hub_t *, uint8_t port, uint16_t feature); + int (*clear_port_feature)(usb_hub_t *, uint8_t port, uint16_t feature); +} usb_hub_ops_t; + +struct usb_hub { + char name[32]; + + // List node for linking hubs in the controller's hub list. + list_node_t node; + // Controller the hub is connected to. + usb_ctrl_t *ctrl; + // Operations for managing the hub. + usb_hub_ops_t *ops; + // Pointer to the device representing the hub itself. + // NULL if the hub is a root hub. + usb_device_t *device; + // Array of ports on the hub. + // Each port can have a pointer to the connected device. + usb_hub_port_t *ports; + uint32_t port_count; +}; + +struct usb_endpoint { + usb_endpoint_desc_t *desc; + usb_ss_ep_companion_desc_t *ss_companion; +}; + +struct usb_interface { + usb_interface_desc_t *desc; + usb_endpoint_t *endpoints; + // Pointer to the attached class driver (for cleanup on disconnect). + usb_class_driver_t *driver; + // Private data for the attached class driver. + void *driver_data; +}; + +typedef enum { + USB_SPEED_UNKNOWN, + USB_SPEED_LOW, + USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_SUPER, + USB_SPEED_SUPER_PLUS, +} usb_speed_t; + +static inline bool usb_device_desc_valid(usb_device_desc_t *desc, usb_speed_t speed, size_t transferred) { + if (transferred < sizeof(usb_device_desc_t)) + return false; + + if (desc->bLength != sizeof(usb_device_desc_t)) + return false; + + if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_DEVICE) + return false; + + if (desc->bNumConfigurations == 0) + return false; + + if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) + return desc->bMaxPacketSize0 == 9; + + if (speed == USB_SPEED_LOW) + return desc->bMaxPacketSize0 == 8; + + if (speed == USB_SPEED_HIGH) + return desc->bMaxPacketSize0 == 64; + + if (speed == USB_SPEED_FULL) { + return desc->bMaxPacketSize0 == 8 || + desc->bMaxPacketSize0 == 16 || + desc->bMaxPacketSize0 == 32 || + desc->bMaxPacketSize0 == 64; + } + + return false; +} + +struct usb_device { + // Pointer to the parent hub. + usb_hub_t *hub; + // Descriptors for device + usb_device_desc_t desc; + usb_config_desc_t *config_desc; + // Port number on the parent hub. + uint8_t port_number; + // Hub-port generation captured when enumeration started. + uint32_t port_generation; + // Read only USB address assigned to the device. + uint8_t address; + // Device speed. + usb_speed_t speed; + // Interfaces + usb_interface_t *interfaces; +}; + +typedef enum { + USB_XFER_FLAG_TO_DEVICE = (1 << 0), + USB_XFER_FLAG_TO_HOST = (1 << 1), + USB_XFER_FLAG_IOVEC = (1 << 2), + USB_XFER_FLAG_BUFFER_PHYSICAL = (1 << 3), +} usb_xfer_flag_t; + +typedef enum { + USB_XFER_TYPE_CONTROL, + USB_XFER_TYPE_BULK, + USB_XFER_TYPE_INTERRUPT, +} usb_xfer_type_t; + +typedef enum { + USB_STATUS_SUCCESS, + USB_STATUS_SHORT_PACKET, + USB_STATUS_ERROR, + USB_STATUS_STALL, + USB_STATUS_FLOW_ERROR, +} usb_status_t; + +typedef void (*usb_completion_callback_t)(usb_device_t *device, void *completion_ctx, usb_status_t status, size_t transferred); + +// Represents a USB control or data transfer. +struct usb_xfer { + // Target endpoint for the transfer. + // Should be NULL and will be ignored for control transfers. + usb_endpoint_t *ep; + + // Flags for the transfer. + usb_xfer_flag_t flags; + usb_xfer_type_t type; + + // Only valid for control transfers. + usb_setup_t *setup; + + // Buffer for the transfer. + union { + iovec_iterator_t *iov; + struct { + void *data; + size_t data_length; + }; + }; + + // Asynchronous completion callback. + usb_completion_callback_t completion; + usb_device_t *device; + void *completion_ctx; +}; + +typedef void (*usb_address_device_callback_t)(usb_hub_t *hub, uint8_t port, usb_device_t *device); + +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); + // Deaddress, disable and free a USB device + void (*deaddress_device)(usb_ctrl_t *, usb_device_t *); + // 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. + int (*xfer)(usb_ctrl_t *, usb_device_t *, usb_xfer_t *); +} usb_ctrl_ops_t; + +struct usb_ctrl { + usb_ctrl_ops_t *ops; +}; + +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_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); + +#define USB_DRIVER_SCORE_NONE 0 +#define USB_DRIVER_SCORE_GENERIC 10 +#define USB_DRIVER_SCORE_CLASS_MATCH 30 +#define USB_DRIVER_SCORE_SUBCLASS_MATCH 50 +#define USB_DRIVER_SCORE_PROTOCOL_MATCH 60 +#define USB_DRIVER_SCORE_VENDOR_MATCH 80 +#define USB_DRIVER_SCORE_PRODUCT_MATCH 90 +#define USB_DRIVER_SCORE_EXACT_MATCH 100 + +#define usb_for_each_descriptor(desc, item) \ + for (usb_desc_hdr_t *item = (usb_desc_hdr_t *)((void *)(desc) + desc->bLength); \ + (void *)item < (void *)(desc) + (desc)->wTotalLength; \ + item = (usb_desc_hdr_t *)((void *)item + item->bLength)) + +static inline bool usb_descriptor_malformed(usb_config_desc_t *desc, usb_desc_hdr_t *item) { + uint8_t *top = (uint8_t *)desc + desc->wTotalLength; + uint8_t *start = (uint8_t *)item; + + if (start + sizeof(*item) > top) + return true; + + if (item->bLength < sizeof(*item)) + return true; + + return start + item->bLength > top; +} + +#define USB_FOR_EACH_DESCRIPTOR_CHECK(desc, item) \ + if (usb_descriptor_malformed((desc), (item))) + +struct usb_class_driver { + const char *name; + // Probe function, returns score - (0 = won't handle, higher = better match). + int (*probe)(usb_device_t *dev, usb_interface_t *interface); + // Called when this driver is selected for the interface. + int (*attach)(usb_device_t *dev, usb_interface_t *interface); + // Called when device is disconnected. + void (*detach)(usb_device_t *dev, usb_interface_t *interface); +}; + +#define DEFINE_USB_CLASS_DRIVER(name, ...) \ + static usb_class_driver_t __usb_class_driver_##name = { \ + __VA_ARGS__ \ + }; \ + __attribute__((section(".usb_class_drivers"), used)) static usb_class_driver_t *usb_class_driver_##name = &__usb_class_driver_##name; + +#endif diff --git a/kernel-src/include/kernel/xhci.h b/kernel-src/include/kernel/xhci.h new file mode 100644 index 00000000..45648a18 --- /dev/null +++ b/kernel-src/include/kernel/xhci.h @@ -0,0 +1,450 @@ +#ifndef _XHCI_H +#define _XHCI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define XHCI_CALLBACK_RING_ENTRIES 256 + +typedef struct { + union { + uint64_t parameters; + + struct { + uint32_t dw0; + uint32_t dw1; + }; + }; + + uint32_t dw2; + uint32_t dw3; +} xhci_trb_t; + +typedef struct { + uint8_t caplength; + uint8_t reserved1; + uint16_t hciversion; + uint32_t hcsparams1; + uint32_t hcsparams2; + uint32_t hcsparams3; + uint32_t hccparams1; + uint32_t dboff; + uint32_t rtsoff; + uint32_t hccparams2; +} xhci_caps_t; + +typedef struct { + uint32_t portsc; + uint32_t portpmsc; + uint32_t portli; + uint32_t porthlpmc; +} xhci_port_regs_t; + +typedef struct { + uint32_t usbcmd; + uint32_t usbsts; + uint32_t pagesize; + uint32_t reserved1; + uint32_t reserved2; + uint32_t dnctrl; + uint64_t crcr; + uint32_t reserved3[4]; + uint64_t dcbaap; + uint32_t config; +} xhci_opregs_t; + +typedef struct { + uint32_t iman; + uint32_t imod; + uint32_t erstsz; + uint32_t reserved; + uint64_t erstba; + uint64_t erdp; +} xhci_ir_t; + +typedef struct { + uint32_t mfindex; + uint32_t reserved[7]; + xhci_ir_t ir[1024]; +} xhci_rtregs_t; + +typedef struct { + uint64_t ring_segment; + uint16_t ring_segment_size; + uint16_t reserved1; + uint32_t reserved2; +} xhci_erst_entry_t; + +typedef struct { + struct { + uint32_t route_string : 20; + uint8_t speed : 4; + uint8_t reserved1 : 1; + uint8_t mtt : 1; + uint8_t hub : 1; + uint8_t ctx_entries : 5; + } dw0; + + struct { + uint16_t max_exit_latency; + uint8_t root_hub_port_number; + uint8_t number_of_ports; + } dw1; + + struct { + uint8_t tt_hub_slot_id; + uint8_t tt_port_number; + uint16_t ttt : 2; + uint16_t reserved1 : 4; + uint16_t interrupter_target : 10; + } dw2; + + struct { + uint8_t usb_device_address; + uint8_t reserved1[2]; + uint8_t reserved2 : 3; + uint8_t slot_state : 5; + } dw3; + + uint32_t reserved[4]; +} xhci_slot_ctx_t; + +typedef struct { + struct { + uint8_t ep_state : 3; + uint8_t reserved1 : 5; + uint8_t mult : 2; + uint8_t max_pstreams : 5; + uint8_t lsa : 1; + uint8_t interval; + uint8_t max_esit_payload_hi; + } dw0; + + struct { + uint8_t reserved1 : 1; + uint8_t cerr : 2; + uint8_t ep_type : 3; + uint8_t reserved2 : 1; + uint8_t hid : 1; + uint8_t max_burst_size; + uint16_t max_packet_size; + } dw1; + + struct { + uint32_t dcs : 1; + uint32_t reserved1 : 3; + uint32_t tr_dequeue_pointer_lo : 28; + } dw2; + + struct { + uint32_t tr_dequeue_pointer_hi; + } dw3; + + struct { + uint16_t max_esit_payload_lo; + } dw4; + + uint32_t reserved[3]; +} xhci_ep_ctx_t; + +typedef struct { + uint32_t d; + uint32_t a; + uint32_t reserved[5]; + + struct { + uint8_t configuration_value; + uint8_t interface_number; + uint8_t alternate_setting; + uint8_t reserved1; + } dw7; +} xhci_input_ctx_t; + +#define XHCI_CONTEXT_SIZE 32 +_Static_assert(sizeof(xhci_slot_ctx_t) == XHCI_CONTEXT_SIZE, "xHCI slot context must be 32 bytes"); +_Static_assert(sizeof(xhci_ep_ctx_t) == XHCI_CONTEXT_SIZE, "xHCI endpoint context must be 32 bytes"); +_Static_assert(sizeof(xhci_input_ctx_t) == XHCI_CONTEXT_SIZE, "xHCI input control context must be 32 bytes"); + +#define XHCI_HCCPARAMS1_AC64 (1 << 0) +#define XHCI_HCCPARAMS1_CSZ (1 << 2) + +#define XHCI_USBCMD_RS (1 << 0) +#define XHCI_USBCMD_HCRST (1 << 1) +#define XHCI_USBCMD_INTE (1 << 2) + +#define XHCI_USBSTS_HCH (1 << 0) +#define XHCI_USBSTS_EINT (1 << 3) +#define XHCI_USBSTS_CNR (1 << 11) +#define XHCI_USBSTS_HCE (1 << 12) + +#define XHCI_IMAN_IP (1 << 0) +#define XHCI_IMAN_IE (1 << 1) + +#define XHCI_CRCR_RCS (1 << 0) + +#define XHCI_ERDP_EHB (1 << 3) + +#define XHCI_PORTSC_CCS (1 << 0) // current connection status +#define XHCI_PORTSC_PED (1 << 1) // port enabled +#define XHCI_PORTSC_OCA (1 << 3) // over-current active +#define XHCI_PORTSC_PR (1 << 4) // port reset +#define XHCI_PORTSC_PP (1 << 9) // port power +#define XHCI_PORTSC_CSC (1 << 17) // connect status change +#define XHCI_PORTSC_PEC (1 << 18) // port enabled change +#define XHCI_PORTSC_OCC (1 << 20) // over current change +#define XHCI_PORTSC_PRC (1 << 21) // port reset change + +#define XHCI_PORTSC_CHANGE_BITS (XHCI_PORTSC_CSC | XHCI_PORTSC_PEC | XHCI_PORTSC_OCC | XHCI_PORTSC_PRC) +#define XHCI_PORTSC_PRESERVE_BITS XHCI_PORTSC_PP + +#define XHCI_TRB_DW2_TR_LEN(LEN) (uint32_t)((LEN) & 0x1ffff) +#define XHCI_TRB_DW2_TD_SIZE(SIZE) ((uint32_t)((SIZE) & 0x1f) << 17) + +#define XHCI_TRB_DW3_C (1 << 0) +#define XHCI_TRB_DW3_TC (1 << 1) +#define XHCI_TRB_DW3_ISP (1 << 2) +#define XHCI_TRB_DW3_CH (1 << 4) +#define XHCI_TRB_DW3_IOC (1 << 5) +#define XHCI_TRB_DW3_IDT (1 << 6) +#define XHCI_TRB_DW3_TYPE_MASK (0x3f << 10) +#define XHCI_TRB_DW3_DIR (1 << 16) + +#define XHCI_TRB_DW3_TYPE(TYPE) (((TYPE) & 0x3f) << 10) +#define XHCI_TRB_DW3_TRT(TRT) (((TRT) & 0x3) << 16) + +typedef enum { + TRB_NORMAL = 1, + TRB_SETUP_STAGE = 2, + TRB_DATA_STAGE = 3, + TRB_STATUS_STAGE = 4, + TRB_LINK = 6, + TRB_ENABLE_SLOT = 9, + TRB_DISABLE_SLOT = 10, + TRB_ADDRESS_DEVICE = 11, + TRB_CONFIGURE_EP = 12, + TRB_EVALUATE_CTX = 13, + TRB_RESET_EP = 14, + TRB_STOP_EP = 15, + TRB_XFER_COMPLETION_EVENT = 32, + TRB_COMMAND_COMPLETION_EVENT = 33, + TRB_PORT_STATUS_CHANGE_EVENT = 34, +} xhci_trb_type_t; + +typedef enum { + TRB_INVALID = 0, + TRB_SUCCESS = 1, + TRB_DATA_BUFFER_ERROR = 2, + TRB_BABBLE_DETECTED = 3, + TRB_TRANSACTION_ERROR = 4, + TRB_TRB_ERROR = 5, + TRB_STALL = 6, + TRB_RESOURCE_ERROR = 7, + TRB_BANDWIDTH_ERROR = 8, + TRB_NO_SLOTS_AVAILABLE = 9, + TRB_INVALID_STREAM_TYPE = 10, + TRB_SLOT_NOT_ENABLED = 11, + TRB_ENDPOINT_NOT_ENABLED = 12, + TRB_SHORT_PACKET = 13, + TRB_RING_UNDERRUN = 14, + TRB_RING_OVERRUN = 15, + TRB_VF_EVENT_RING_FULL = 16, + TRB_PARAMETER_ERROR = 17, + TRB_BANDWIDTH_OVERRUN_ERROR = 18, + TRB_CONTEXT_STATE_ERROR = 19, + TRB_NO_PING_RESPONSE = 20, + TRB_EVENT_RING_FULL = 21, + TRB_INCOMPATIBLE_DEVICE_ERROR = 22, + TRB_MISSED_SERVICE = 23, + TRB_COMMAND_RING_STOPPED = 24, + TRB_COMMAND_RING_ABORTED = 25, + TRB_STOPPED = 26, + TRB_STOPPED_LEN = 27, + TRB_STOPPED_SHORT = 28, + TRB_LATENCY_TOO_LARGE = 29, + TRB_ERROR_RESERVED = 30, + TRB_BUFFER_OVERRUN = 31, + TRB_EVENT_LOST = 32, + TRB_ERROR_UNDEFINED = 33, + TRB_INVALID_STREAM_ID = 34, + TRB_SECONDARY_BANDWIDTH_ERROR = 35, + TRB_SPLIT_TRANSACTION_ERROR = 36, + + TRB_VENDOR_ERROR_START = 192, + TRB_VENDOR_ERROR_END = 223, + TRB_VENDOR_INFO_START = 224, + TRB_VENDOR_INFO_END = 255, +} xhci_trb_status_t; + +typedef enum { + XHCI_PORT_SPEED_FULL = 1, + XHCI_PORT_SPEED_LOW = 2, + XHCI_PORT_SPEED_HIGH = 3, + XHCI_PORT_SPEED_SUPER = 4, + XHCI_PORT_SPEED_SS_2X1 = 5, + XHCI_PORT_SPEED_SS_1X2 = 6, + XHCI_PORT_SPEED_SS_2X2 = 7, +} xhci_port_speed_t; + +typedef enum { + XHCI_EP_TYPE_ISOCH_OUT = 1, + XHCI_EP_TYPE_BULK_OUT = 2, + XHCI_EP_TYPE_INTR_OUT = 3, + XHCI_EP_TYPE_CTRL = 4, + XHCI_EP_TYPE_ISOCH_IN = 5, + XHCI_EP_TYPE_BULK_IN = 6, + XHCI_EP_TYPE_INTR_IN = 7, +} xhci_ep_type_t; + +typedef struct xhci_submission xhci_submission_t; +typedef struct xhci_device xhci_device_t; + +typedef struct { + uint8_t ver_major; + uint8_t ver_minor; +} xhci_port_protocol_t; + +struct xhci_submission { + list_node_t list_node; + // Set whenever a submission is queued + bool valid; + struct xhci_ring *ring; + + // The data associated with this submission + usb_device_t *device; + usb_completion_callback_t callback; + void *callback_ctx; + size_t data_length; + size_t data_offset; + xhci_submission_t *next; + xhci_submission_t *td_head; + + // event_trb is filled from the event ring; cmd_trb is initialized on submission. + xhci_trb_t event_trb; + xhci_trb_t cmd_trb; + usb_status_t usb_status; +}; + +typedef struct xhci_ring { + void *ring_phys; + xhci_trb_t *ring; + + // Mutex to protect the ring structure. + mutex_t lock; + + // Back-pointer to the controller. + struct xhci_ctrl *ctrl; + + // List of submissions on this ring. Will be NULL for event rings. + xhci_submission_t *submissions; + resource_allocator_t trb_allocator; + + // Ring size in TRBs. + size_t size; + + // For event rings, the index of the next event to process. + // For command and transfer rings, the index of the next free TRB. + size_t index; + + // Cycle bit for the ring. + bool cycle; +} xhci_ring_t; + +typedef struct { + usb_device_t *device; + usb_completion_callback_t callback; + void *callback_ctx; + usb_status_t status; + size_t transferred; +} xhci_completion_t; + +typedef struct xhci_ctrl { + usb_ctrl_t ctrl; + pcienum_t *pci_enum; + + list_t hubs; + + uint32_t port_count; + uint32_t slot_count; + + xhci_port_protocol_t *ports; + xhci_device_t **slots; + + xhci_ring_t command_ring; + xhci_ring_t event_ring; + + semaphore_t event_sem; + semaphore_t callback_sem; + semaphore_t callback_space_sem; + spinlock_t event_lock; + mutex_t callback_mutex; + bitmap_t port_change_bitmap; + list_t completion_list; + ringbuffer_t callback_ring; + dpc_t dpc; + + uint32_t ctx_stride; + + volatile xhci_caps_t *caps; + volatile xhci_opregs_t *opregs; + volatile xhci_rtregs_t *rtregs; + volatile xhci_port_regs_t *portregs; + + volatile uint64_t *dcbaa; + volatile uint32_t *dbs; +} xhci_ctrl_t; + +typedef struct { + usb_hub_t hub; + uint8_t port_offset; + list_node_t list_node; +} xhci_root_hub_t; + +struct xhci_device { + usb_device_t device; + xhci_ring_t ep_rings[31]; + + uint32_t route_string; + uint8_t device_tier; + uint8_t slot_id; + uint8_t ctrl_speed; + uint8_t root_port_number; + + void *device_ctx_phys; + void *device_ctx; + void *input_ctx_phys; + void *input_ctx; +}; + +void xhci_handle_port_change_event(xhci_ctrl_t *xhci, int global_port); +int xhci_data_xfer(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub); +int xhci_sg_data_xfer(xhci_ctrl_t *xhci, xhci_ring_t *ring, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub); +int xhci_control_xfer(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub); +xhci_trb_t *xhci_ring_dequeue(xhci_ring_t *r); +void xhci_ring_reserve(xhci_ring_t *r, size_t needed); +void xhci_ring_unreserve(xhci_ring_t *r, size_t needed); +void xhci_ring_submit(xhci_ring_t *r, xhci_trb_t *trb, xhci_submission_t *sub); +xhci_submission_t *xhci_ring_submit_locked(xhci_ring_t *r, xhci_trb_t *trb, xhci_submission_t *sub); +int xhci_alloc_ring(xhci_ctrl_t *ctrl, xhci_ring_t *r, bool event_ring); +void xhci_free_ring(xhci_ring_t *ring); + +static inline void xhci_ring_command_doorbell(xhci_ctrl_t *ctrl) { + ctrl->dbs[0] = 0; +} + +static inline void xhci_ring_endpoint_doorbell(xhci_ctrl_t *ctrl, xhci_device_t *device, int endpoint) { + ctrl->dbs[device->slot_id] = endpoint; +} + +extern usb_hub_ops_t xhci_root_hub_ops; +extern usb_ctrl_ops_t xhci_ops; + +#endif diff --git a/kernel-src/io/usb/core/hub.c b/kernel-src/io/usb/core/hub.c new file mode 100644 index 00000000..f9253c08 --- /dev/null +++ b/kernel-src/io/usb/core/hub.c @@ -0,0 +1,477 @@ +#include +#include +#include + +// 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 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); + } + + 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); +} + +// 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; + } + + // 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; + } + + 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 (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) { + if (hub->ctrl->ops->address_device(hub->ctrl, hub, port, 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); +} diff --git a/kernel-src/io/usb/core/usb.c b/kernel-src/io/usb/core/usb.c new file mode 100644 index 00000000..ee701dee --- /dev/null +++ b/kernel-src/io/usb/core/usb.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +extern usb_class_driver_t *usb_class_drivers; +extern usb_class_driver_t *usb_class_drivers_end; + +int usb_submit_xfer(usb_device_t *dev, usb_xfer_t *xfer) { + usb_ctrl_t *ctrl = dev->hub->ctrl; + xfer->device = dev; + return ctrl->ops->xfer(ctrl, dev, xfer); +} + +int usb_control_xfer(usb_device_t *dev, usb_setup_t *setup, void *buffer, usb_completion_callback_t callback, void *ctx) { + usb_xfer_t xfer = {0}; + xfer.flags = (setup->bmRequestType & USB_REQUEST_DIR_TO_HOST) ? USB_XFER_FLAG_TO_HOST : USB_XFER_FLAG_TO_DEVICE; + xfer.type = USB_XFER_TYPE_CONTROL; + xfer.setup = setup; + xfer.data = buffer; + xfer.data_length = setup->wLength; + xfer.completion = callback; + xfer.completion_ctx = ctx; + + 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) { + usb_setup_t setup = {0}; + setup.bmRequestType = recipient | USB_REQUEST_STANDARD | USB_REQUEST_DIR_TO_HOST; + setup.bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup.wValue = ((uint16_t)desc_type << 8) | desc_index; + setup.wIndex = index; + setup.wLength = length; + + return usb_control_xfer(dev, &setup, buffer, callback, callback_ctx ? callback_ctx : buffer); +} + +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); +} + +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); +} + +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); +} + +int usb_set_configuration(usb_device_t *dev, uint8_t config_value, usb_completion_callback_t callback) { + usb_setup_t setup = {0}; + setup.bmRequestType = USB_REQUEST_RECIP_DEVICE | USB_REQUEST_STANDARD | USB_REQUEST_DIR_TO_DEVICE; + setup.bRequest = USB_REQUEST_SET_CONFIGURATION; + setup.wValue = config_value; + setup.wIndex = 0; + setup.wLength = 0; + + return usb_control_xfer(dev, &setup, NULL, callback, NULL); +} diff --git a/kernel-src/io/usb/ctrl/xhci.c b/kernel-src/io/usb/ctrl/xhci.c new file mode 100644 index 00000000..18dd63da --- /dev/null +++ b/kernel-src/io/usb/ctrl/xhci.c @@ -0,0 +1,762 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t xhci_trb_type(xhci_trb_t *trb) { + return (trb->dw3 >> 10) & 0x3f; +} + +static void xhci_signal_event_thread(xhci_ctrl_t *xhci) { + semaphore_signal_limit(&xhci->event_sem, 1); +} + +static void xhci_signal_callback_thread(xhci_ctrl_t *xhci) { + semaphore_signal_limit(&xhci->callback_sem, 1); +} + +static void xhci_release_submission_page(xhci_submission_t *submission) { + uint32_t cmd_type = xhci_trb_type(&submission->cmd_trb); + + if ((cmd_type == TRB_NORMAL || cmd_type == TRB_DATA_STAGE) && submission->cmd_trb.parameters != 0) + pmm_release((void *)(submission->cmd_trb.parameters & ~(PAGE_SIZE - 1))); +} + +static bool xhci_reclaim_submission_slot(xhci_submission_t *slot, xhci_submission_t *submission) { + bool valid = __atomic_exchange_n(&slot->valid, false, __ATOMIC_SEQ_CST); + __assert(valid); + + xhci_release_submission_page(submission); + xhci_ring_unreserve(submission->ring, 1); + return true; +} + +static void xhci_reclaim_submission_chain(xhci_submission_t *submissionp) { + while (submissionp != NULL) { + if (!__atomic_load_n(&submissionp->valid, __ATOMIC_SEQ_CST)) + return; + + xhci_submission_t submission = *submissionp; + xhci_submission_t *next = submission.next; + xhci_reclaim_submission_slot(submissionp, &submission); + submissionp = next; + } +} + +static void xhci_enqueue_completion(xhci_ctrl_t *xhci, xhci_completion_t *completion) { + semaphore_wait(&xhci->callback_space_sem, false); + + MUTEX_ACQUIRE(&xhci->callback_mutex); + size_t written = ringbuffer_write(&xhci->callback_ring, completion, sizeof(*completion)); + __assert(written == sizeof(*completion)); + MUTEX_RELEASE(&xhci->callback_mutex); + + xhci_signal_callback_thread(xhci); +} + +static void handle_submission_complete(xhci_ctrl_t *xhci, xhci_submission_t *submissionp) { + xhci_submission_t submission = *submissionp; + + uint32_t cmd_type = xhci_trb_type(&submission.cmd_trb); + if (cmd_type == TRB_SETUP_STAGE) { + if (submission.usb_status == USB_STATUS_SUCCESS) + return; + } else if (cmd_type == TRB_NORMAL || cmd_type == TRB_DATA_STAGE) { + // Check if we need to wait for more TRBs to complete. + if (submission.usb_status == USB_STATUS_SUCCESS && (cmd_type == TRB_DATA_STAGE || (submission.cmd_trb.dw3 & XHCI_TRB_DW3_CH) != 0)) + return; + } + + // Complete the transfer. + size_t xfer_len; + uint32_t status = (submission.event_trb.dw2 >> 24) & 0xff; + if (status == TRB_SUCCESS) { + xfer_len = submission.data_offset + submission.data_length; + } else if (status == TRB_SHORT_PACKET) { + size_t remainder = submission.event_trb.dw2 & 0xffffff; + size_t trb_xfer_len = submission.data_length - min(remainder, submission.data_length); + xfer_len = submission.data_offset + trb_xfer_len; + } else { + xfer_len = 0; + } + + if (cmd_type == TRB_DATA_STAGE && submission.usb_status == USB_STATUS_SHORT_PACKET) { + // surgically edit the status stage trb to return a short packet status with the final length + __assert(submission.next != NULL); + submission.next->usb_status = USB_STATUS_SHORT_PACKET; + submission.next->data_offset = xfer_len; + return; + } + + // for ENABLE_SLOT commands, we use xfer_len for the slot id + if ((submission.cmd_trb.dw3 & XHCI_TRB_DW3_TYPE_MASK) == XHCI_TRB_DW3_TYPE(TRB_ENABLE_SLOT)) + xfer_len = (submission.event_trb.dw3 >> 24) & 0xff; + + xhci_submission_t *td_head = submission.td_head != NULL ? submission.td_head : submissionp; + xhci_reclaim_submission_chain(td_head); + + if (submission.callback) { + xhci_completion_t completion = { + .device = submission.device, + .callback = submission.callback, + .callback_ctx = submission.callback_ctx, + .status = submission.usb_status, + .transferred = xfer_len, + }; + + xhci_enqueue_completion(xhci, &completion); + } +} + +static xhci_submission_t *xhci_pop_completed_submission(xhci_ctrl_t *xhci) { + long ipl = spinlock_acquire_raise_ipl(&xhci->event_lock, IPL_USB); + xhci_submission_t *submission = (xhci_submission_t *)list_pop_front(&xhci->completion_list); + spinlock_release_lower_ipl(&xhci->event_lock, ipl); + return submission; +} + +static int xhci_pop_port_change(xhci_ctrl_t *xhci) { + long ipl = spinlock_acquire_raise_ipl(&xhci->event_lock, IPL_USB); + + int port = bitmap_find_first_set(&xhci->port_change_bitmap); + if (port != -1) + bitmap_set(&xhci->port_change_bitmap, port, 0); + + spinlock_release_lower_ipl(&xhci->event_lock, ipl); + return port; +} + +static bool xhci_pop_completion(xhci_ctrl_t *xhci, xhci_completion_t *completion) { + bool ret = false; + + MUTEX_ACQUIRE(&xhci->callback_mutex); + if (RINGBUFFER_DATACOUNT(&xhci->callback_ring) >= sizeof(*completion)) { + size_t read = ringbuffer_read(&xhci->callback_ring, completion, sizeof(*completion)); + __assert(read == sizeof(*completion)); + ret = true; + } + MUTEX_RELEASE(&xhci->callback_mutex); + + if (ret) + semaphore_signal(&xhci->callback_space_sem); + + return ret; +} + +static void xhci_event_thread(void) { + xhci_ctrl_t *xhci = current_thread()->kernelarg; + + for (;;) { + semaphore_wait(&xhci->event_sem, false); + + for (;;) { + xhci_submission_t *submission = xhci_pop_completed_submission(xhci); + if (submission == NULL) + break; + + handle_submission_complete(xhci, submission); + } + } +} + +static void xhci_callback_thread(void) { + xhci_ctrl_t *xhci = current_thread()->kernelarg; + + for (;;) { + semaphore_wait(&xhci->callback_sem, false); + + for (;;) { + bool did_work = false; + + for (;;) { + int port = xhci_pop_port_change(xhci); + if (port == -1) + break; + + xhci_handle_port_change_event(xhci, port + 1); + did_work = true; + } + + for (;;) { + xhci_completion_t completion; + if (!xhci_pop_completion(xhci, &completion)) + break; + + completion.callback(completion.device, completion.callback_ctx, completion.status, completion.transferred); + did_work = true; + } + + if (!did_work) + break; + } + } +} + + +static void xhci_dpc(context_t *, dpcarg_t dpcarg) { + xhci_ctrl_t *xhci = dpcarg; + volatile xhci_ir_t *ir = &xhci->rtregs->ir[0]; + + // Process events from the event ring. + bool signal_event_thread = false; + bool signal_callback_thread = false; + xhci_trb_t *r_trb; + while ((r_trb = xhci_ring_dequeue(&xhci->event_ring)) != NULL) { + uint32_t type = (r_trb->dw3 >> 10) & 0x3f; + if (type == TRB_COMMAND_COMPLETION_EVENT || type == TRB_XFER_COMPLETION_EVENT) { + uint32_t status = (r_trb->dw2 >> 24) & 0xff; + + usb_status_t usb_status; + switch (status) { + case TRB_SUCCESS: + usb_status = USB_STATUS_SUCCESS; + break; + case TRB_SHORT_PACKET: + usb_status = USB_STATUS_SHORT_PACKET; + break; + case TRB_DATA_BUFFER_ERROR: + case TRB_BABBLE_DETECTED: + case TRB_TRANSACTION_ERROR: + case TRB_RESOURCE_ERROR: + case TRB_BANDWIDTH_ERROR: + case TRB_NO_SLOTS_AVAILABLE: + case TRB_INVALID_STREAM_TYPE: + case TRB_SLOT_NOT_ENABLED: + case TRB_ENDPOINT_NOT_ENABLED: + case TRB_BANDWIDTH_OVERRUN_ERROR: + case TRB_CONTEXT_STATE_ERROR: + case TRB_NO_PING_RESPONSE: + case TRB_INCOMPATIBLE_DEVICE_ERROR: + case TRB_COMMAND_RING_STOPPED: + case TRB_COMMAND_RING_ABORTED: + case TRB_STOPPED: + case TRB_STOPPED_LEN: + case TRB_STOPPED_SHORT: + case TRB_LATENCY_TOO_LARGE: + case TRB_ERROR_RESERVED: + case TRB_ERROR_UNDEFINED: + case TRB_INVALID_STREAM_ID: + case TRB_SECONDARY_BANDWIDTH_ERROR: + case TRB_SPLIT_TRANSACTION_ERROR: + usb_status = USB_STATUS_ERROR; + break; + case TRB_STALL: + usb_status = USB_STATUS_STALL; + break; + case TRB_BUFFER_OVERRUN: + case TRB_RING_UNDERRUN: + case TRB_RING_OVERRUN: + case TRB_MISSED_SERVICE: + usb_status = USB_STATUS_FLOW_ERROR; + break; + default: + printf("xhci: bad event: %lu (driver bug!)\n", status); + _panic("Driver bug", NULL); + break; + } + + xhci_ring_t *ring; + if (type == TRB_COMMAND_COMPLETION_EVENT) { + ring = &xhci->command_ring; + } else { + uint32_t slot_id = (r_trb->dw3 >> 24) & 0xff; + __assert(slot_id > 0 && slot_id <= xhci->slot_count); + xhci_device_t *dev = xhci->slots[slot_id - 1]; + __assert(dev); + + uint8_t ep_index = (r_trb->dw3 >> 16) & 0x1f; + __assert(ep_index > 0); + ring = &dev->ep_rings[ep_index - 1]; + } + + xhci_trb_t *trb = MAKE_HHDM(r_trb->parameters); + uintptr_t trb_addr = (uintptr_t)trb; + uintptr_t ring_start = (uintptr_t)ring->ring; + uintptr_t ring_end = (uintptr_t)&ring->ring[ring->size - 1]; + __assert(trb_addr >= ring_start && trb_addr < ring_end); + __assert((trb_addr - ring_start) % sizeof(xhci_trb_t) == 0); + size_t trb_index = (trb_addr - ring_start) / sizeof(xhci_trb_t); + xhci_submission_t *sub = &ring->submissions[trb_index]; + __assert(__atomic_load_n(&sub->valid, __ATOMIC_SEQ_CST)); + + memcpy(&sub->event_trb, r_trb, sizeof(xhci_trb_t)); + + if (sub->usb_status == USB_STATUS_SHORT_PACKET) { + // this is a status stage trb and the data stage returned a short packet. + // the offset has been surgically edited to the shorter length as to + // return the proper transferred length to the callback. + uint32_t original_cmd = xhci_trb_type(&sub->cmd_trb); + __assert(original_cmd == TRB_STATUS_STAGE); + } else { + sub->usb_status = usb_status; + } + + spinlock_acquire(&xhci->event_lock); + list_push_back(&xhci->completion_list, &sub->list_node); + spinlock_release(&xhci->event_lock); + + signal_event_thread = true; + } else if (type == TRB_PORT_STATUS_CHANGE_EVENT) { + uint8_t port_id = (r_trb->dw0 >> 24) & 0xff; + + spinlock_acquire(&xhci->event_lock); + bitmap_set(&xhci->port_change_bitmap, port_id - 1, 1); + spinlock_release(&xhci->event_lock); + signal_callback_thread = true; + } + + // Update the Event Ring Dequeue Pointer. + uint64_t event_ring_phys = (uint64_t)xhci->event_ring.ring_phys; + event_ring_phys += xhci->event_ring.index * sizeof(xhci_trb_t); + ir->erdp = event_ring_phys | XHCI_ERDP_EHB; + } + + if (signal_event_thread) + xhci_signal_event_thread(xhci); + if (signal_callback_thread) + xhci_signal_callback_thread(xhci); +} + +static void xhci_isr(isr_t *isr, context_t *ctx) { + xhci_ctrl_t *xhci = isr->priv; + + // Check if there is an interrupt pending. + volatile xhci_ir_t *ir = &xhci->rtregs->ir[0]; + uint32_t iman = ir->iman; + uint32_t usbsts = xhci->opregs->usbsts; + uint64_t erdp = ir->erdp; + + if ((erdp & XHCI_ERDP_EHB) == 0 && (iman & XHCI_IMAN_IP) == 0 && (usbsts & XHCI_USBSTS_EINT) == 0) + return; + + // Clear the interrupt pending bit. + if (iman & XHCI_IMAN_IP) + ir->iman |= XHCI_IMAN_IP; + if (usbsts & XHCI_USBSTS_EINT) + xhci->opregs->usbsts = XHCI_USBSTS_EINT; + + dpc_enqueue(&xhci->dpc, xhci); +} + +static int xhci_halt(xhci_ctrl_t *ctrl) { + if ((ctrl->opregs->usbcmd & XHCI_USBCMD_RS) == 0) + return 0; + + ctrl->opregs->usbcmd &= ~XHCI_USBCMD_RS; + + timespec_t start = timekeeper_timefromboot(); + while ((ctrl->opregs->usbsts & XHCI_USBSTS_HCH) == 0) { + sched_sleep_us(10 * 1000); // 10ms + + timespec_t now = timekeeper_timefromboot(); + if (timespec_diffms(now, start) > 1000) + return -1; + } + + return 0; +} + +static int xhci_reset(xhci_ctrl_t *ctrl) { + ctrl->opregs->usbcmd |= XHCI_USBCMD_HCRST; + + timespec_t start = timekeeper_timefromboot(); + while ((ctrl->opregs->usbcmd & XHCI_USBCMD_HCRST) != 0 || (ctrl->opregs->usbsts & XHCI_USBSTS_CNR) != 0) { + sched_sleep_us(10 * 1000); // 10ms + + timespec_t now = timekeeper_timefromboot(); + if (timespec_diffms(now, start) > 1000) + return -1; + } + + return 0; +} + +static int xhci_run(xhci_ctrl_t *ctrl) { + if ((ctrl->opregs->usbcmd & XHCI_USBCMD_RS)) + return 0; + + ctrl->opregs->usbcmd |= XHCI_USBCMD_RS | XHCI_USBCMD_INTE; + + timespec_t start = timekeeper_timefromboot(); + while ((ctrl->opregs->usbsts & XHCI_USBSTS_HCH) != 0 || (ctrl->opregs->usbsts & XHCI_USBSTS_CNR) != 0) { + sched_sleep_us(10 * 1000); // 10ms + + timespec_t now = timekeeper_timefromboot(); + if (timespec_diffms(now, start) > 1000) + return -1; + } + + return 0; +} + + +static volatile uint32_t *xhci_ext_caps(pcibar_t bar0) { + volatile xhci_caps_t *caps = (xhci_caps_t *)bar0.address; + uint32_t xecp = (caps->hccparams1 >> 16) & 0xffff; + if (xecp == 0) + return NULL; + return (uint32_t *)(bar0.address + (xecp << 2)); +} + +static int xhci_handoff(pcibar_t bar0) { + volatile uint32_t *ext_caps = xhci_ext_caps(bar0); + if (ext_caps == NULL) + return 0; + + for (;;) { + uint8_t cap_id = *ext_caps & 0xff; + uint8_t next_cap_off = (*ext_caps >> 8) & 0xff; + + if (cap_id == 1) { + printf("xhci: found BIOS/OS ownership capability\n"); + + bool is_bios_owned = (*ext_caps >> 16) & 0x1; + if (is_bios_owned) { + printf("xhci: attempting to take ownership from BIOS\n"); + + // Set OS Owned Semaphore. + *ext_caps |= (1 << 24); + + timespec_t start = timekeeper_timefromboot(); + while (is_bios_owned) { + sched_sleep_us(500000); // 500ms + is_bios_owned = (*ext_caps >> 16) & 0x1; + timespec_t now = timekeeper_timefromboot(); + if (timespec_diffms(now, start) > 5000) { + printf("xhci: BIOS ownership handoff timeout\n"); + return ETIMEDOUT; + } + } + + printf("xhci: took ownership of controller from BIOS\n"); + } + } + + if (next_cap_off == 0) + break; + ext_caps += next_cap_off; + } + + return 0; +} + + +static int xhci_ctrl_start(usb_ctrl_t *ctrl) { + int ret = 0; + xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); + pcibar_t bar0 = pci_getbar(xhci->pci_enum, 0); + + ret = xhci_halt(xhci); + if (ret != 0) { + printf("xhci: controller halt timeout\n"); + return ret; + } + + ret = xhci_reset(xhci); + if (ret != 0) { + printf("xhci: controller reset timeout\n"); + return ret; + } + + // Enable MSI interrupts. + size_t intcount; + if (xhci->pci_enum->msix.exists) { + intcount = pci_initmsix(xhci->pci_enum); + } else if (xhci->pci_enum->msi.exists) { + intcount = pci_initmsi(xhci->pci_enum, 1); + } else { + printf("xhci: controller does not support MSI or MSI-X\n"); + return ENODEV; + } + + __assert(intcount > 0); + + isr_t *isr = interrupt_allocate(xhci_isr, ARCH_EOI, IPL_USB); + __assert(isr != NULL); + isr->priv = ctrl; + + if (xhci->pci_enum->msix.exists) { + pci_msixadd(xhci->pci_enum, 0, INTERRUPT_IDTOVECTOR(isr->id), 1, 0); + pci_msixsetmask(xhci->pci_enum, 0); + } else { + pci_msisetbase(xhci->pci_enum, INTERRUPT_IDTOVECTOR(isr->id), 1, 0); + pci_msisetmask(xhci->pci_enum, 0, 0); + } + + uint32_t max_slots = xhci->caps->hcsparams1 & 0xff; + uint32_t max_ports = (xhci->caps->hcsparams1 >> 24) & 0xff; + + printf("xhci: controller supports %u slots and %u ports\n", max_slots, max_ports); + + xhci->port_count = max_ports; + xhci->ports = alloc(sizeof(xhci_port_protocol_t) * max_ports); + __assert(xhci->ports); + __assert(bitmap_init(&xhci->port_change_bitmap, max_ports) == 0); + + for (uint32_t i = 0; i < max_ports; i++) { + xhci->ports[i].ver_major = 0; + xhci->ports[i].ver_minor = 0; + } + + xhci->slot_count = max_slots; + xhci->slots = alloc(sizeof(xhci_device_t *) * max_slots); + __assert(xhci->slots); + + xhci->ctx_stride = (xhci->caps->hccparams1 & XHCI_HCCPARAMS1_CSZ) != 0 ? 64 : 32; + + volatile uint32_t *ext_caps = xhci_ext_caps(bar0); + if (ext_caps == NULL) { + printf("xhci: controller has no extended capabilities\n"); + return ENODEV; + } + + for (;;) { + uint8_t cap_id = *ext_caps & 0xff; + uint8_t next_cap_off = (*ext_caps >> 8) & 0xff; + + if (cap_id == 2) { + // map ports to usb types + uint8_t ver_major = (*ext_caps >> 24) & 0xff; + uint8_t ver_minor = (*ext_caps >> 16) & 0xff; + + uint32_t raw_port_start = ext_caps[2] & 0xff; + uint32_t port_count = (ext_caps[2] >> 8) & 0xff; + + if (raw_port_start == 0 || port_count == 0 || raw_port_start - 1 >= max_ports || port_count > max_ports - (raw_port_start - 1)) { + printf("xhci: invalid usb %u.%u port range start=%u count=%u max=%u\n", + ver_major, + ver_minor, + raw_port_start, + port_count, + max_ports); + return EINVAL; + } + + uint32_t port_start = raw_port_start - 1; + + printf("xhci: %u usb %u.%u ports starting at port %u\n", + port_count, ver_major, ver_minor, port_start + 1); + + for (uint32_t i = 0; i < port_count; i++) { + xhci->ports[port_start + i].ver_major = ver_major; + xhci->ports[port_start + i].ver_minor = ver_minor; + } + + xhci_root_hub_t *hub = alloc(sizeof(xhci_root_hub_t)); + __assert(hub); + + snprintf(hub->hub.name, sizeof(hub->hub.name), "xhci-root-hub-%u.%u", ver_major, ver_minor); + + hub->hub.ctrl = ctrl; + hub->hub.ops = &xhci_root_hub_ops; + hub->hub.device = NULL; + + hub->hub.ports = alloc(sizeof(usb_hub_port_t) * port_count); + __assert(hub->hub.ports); + hub->hub.port_count = port_count; + + hub->port_offset = port_start; + + list_push_back(&xhci->hubs, &hub->list_node); + } + + if (next_cap_off == 0) + break; + + ext_caps += next_cap_off; + } + + __assert(xhci_alloc_ring(xhci, &xhci->command_ring, false) == 0); + __assert(xhci_alloc_ring(xhci, &xhci->event_ring, true) == 0); + + // Set up MaxSlotsEn field. + xhci->opregs->config = max_slots; + + // Set up the Device Context Base Address Array. + void *dcbaa_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + __assert(dcbaa_phys); + + uint64_t *dcbaa_virt = MAKE_HHDM(dcbaa_phys); + memset(dcbaa_virt, 0, PAGE_SIZE); + + // Set up scratchpad buffers if requested. + uint32_t max_scratchpads_hi = (xhci->caps->hcsparams2 >> 21) & 0x1f; + uint32_t max_scratchpads_lo = (xhci->caps->hcsparams2 >> 27) & 0x1f; + uint32_t max_scratchpads = max_scratchpads_lo | (max_scratchpads_hi << 5); + + if (max_scratchpads > 0) { + printf("xhci: setting up %u scratchpad buffers\n", max_scratchpads); + __assert(max_scratchpads < PAGE_SIZE / sizeof(uint64_t)); + + void *scratchpad_array_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + __assert(scratchpad_array_phys); + uint64_t *scratchpad_array_virt = MAKE_HHDM(scratchpad_array_phys); + + for (uint32_t i = 0; i < max_scratchpads; i++) { + void *scratchpad_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + __assert(scratchpad_phys); + memset(MAKE_HHDM(scratchpad_phys), 0, PAGE_SIZE); + scratchpad_array_virt[i] = (uint64_t)scratchpad_phys; + } + + dcbaa_virt[0] = (uint64_t)scratchpad_array_phys; + } + + xhci->dcbaa = dcbaa_virt; + xhci->opregs->dcbaap = (uint64_t)dcbaa_phys; + + // Set up command rings. + xhci->opregs->crcr = (uint64_t)xhci->command_ring.ring_phys | XHCI_CRCR_RCS; + + // Set up Event Ring Segment Table. + void *erst_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + __assert(erst_phys); + + xhci_erst_entry_t *erst_virt = MAKE_HHDM(erst_phys); + memset(erst_virt, 0, PAGE_SIZE); + + erst_virt[0].ring_segment = (uint64_t)xhci->event_ring.ring_phys; + erst_virt[0].ring_segment_size = xhci->event_ring.size; + + // Set up the interrupter. + volatile xhci_ir_t *ir = &xhci->rtregs->ir[0]; + ir->iman = XHCI_IMAN_IE; + ir->erstsz = 1; + ir->erstba = (uint64_t)erst_phys; + ir->erdp = (uint64_t)xhci->event_ring.ring_phys | XHCI_ERDP_EHB; + + // Start the controller. + ret = xhci_run(xhci); + if (ret != 0) { + printf("xhci: controller run timeout\n"); + return ret; + } + + // power on all ports + bool debounce_wait = false; + list_for_each (&xhci->hubs, node) { + xhci_root_hub_t *rh = container_of(node, xhci_root_hub_t, list_node); + usb_hub_t *hub = &rh->hub; + + for (uint8_t port = 0; port < hub->port_count; port++) { + volatile xhci_port_regs_t *portregs = &xhci->portregs[rh->port_offset + port]; + uint32_t portsc = portregs->portsc; + portregs->portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | (portsc & XHCI_PORTSC_CHANGE_BITS); + if ((portsc & XHCI_PORTSC_PP) == 0) { + portregs->portsc = (portsc & XHCI_PORTSC_CHANGE_BITS) | XHCI_PORTSC_PP; + debounce_wait = true; + } + } + } + + if (debounce_wait) + sched_sleep_us(100000); // 100 ms + + // reset all conected ports + list_for_each (&xhci->hubs, node) { + xhci_root_hub_t *rh = container_of(node, xhci_root_hub_t, list_node); + usb_hub_t *hub = &rh->hub; + + for (uint8_t port = 0; port < hub->port_count; port++) { + volatile xhci_port_regs_t *portregs = &xhci->portregs[rh->port_offset + port]; + // NOTE: while USB3 ports don't need this, we will still do it to simplify the path + uint32_t portsc = portregs->portsc; + if (portsc & XHCI_PORTSC_CCS) + portregs->portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | XHCI_PORTSC_PR; + } + } + + return 0; +} + + +static void init_ctrl(pcienum_t *e) { + printf("xhci: found controller at %02x:%02x.%x\n", e->bus, e->device, e->function); + + pci_setcommand(e, PCI_COMMAND_MMIO, 1); + + // perform handoff before changing anything else + pcibar_t bar0 = pci_getbar(e, 0); + if (xhci_handoff(bar0) != 0) + return; + + pci_setcommand(e, PCI_COMMAND_IO, 0); + pci_setcommand(e, PCI_COMMAND_IRQDISABLE, 1); + pci_setcommand(e, PCI_COMMAND_BUSMASTER, 1); + + volatile xhci_caps_t *caps = (xhci_caps_t *)bar0.address; + volatile xhci_opregs_t *opregs = (xhci_opregs_t *)(bar0.address + caps->caplength); + volatile xhci_rtregs_t *rtregs = (xhci_rtregs_t *)(bar0.address + caps->rtsoff); + + // Only 64-bit-addressing controllers are supported for simplicity. + if ((caps->hccparams1 & XHCI_HCCPARAMS1_AC64) == 0) { + printf("xhci: controller does not support 64-bit addressing\n"); + return; + } + + // Make sure the controller supports 4K pages + if ((opregs->pagesize & (1 << 0)) == 0) { + printf("xhci: controller does not support 4K page size\n"); + return; + } + + // Register the controller with the USB subsystem + xhci_ctrl_t *ctrl = alloc(sizeof(xhci_ctrl_t)); + __assert(ctrl); + + ctrl->ctrl.ops = &xhci_ops; + + SEMAPHORE_INIT(&ctrl->event_sem, 0); + SEMAPHORE_INIT(&ctrl->callback_sem, 0); + SEMAPHORE_INIT(&ctrl->callback_space_sem, XHCI_CALLBACK_RING_ENTRIES); + SPINLOCK_INIT(ctrl->event_lock); + MUTEX_INIT(&ctrl->callback_mutex); + list_init(&ctrl->completion_list); + __assert(ringbuffer_init(&ctrl->callback_ring, sizeof(xhci_completion_t) * XHCI_CALLBACK_RING_ENTRIES) == 0); + dpc_prepare(&ctrl->dpc, xhci_dpc); + + list_init(&ctrl->hubs); + + ctrl->pci_enum = e; + ctrl->caps = caps; + ctrl->opregs = opregs; + ctrl->rtregs = rtregs; + ctrl->portregs = (xhci_port_regs_t *)(bar0.address + caps->caplength + 0x400); + ctrl->dbs = (uint32_t *)(bar0.address + caps->dboff); + + int res = xhci_ctrl_start(&ctrl->ctrl); + if (res) { + printf("xhci: controller init returned an error: %s\n", strerror(res)); + return; + } + + struct thread_t *event_thread = sched_newthread(xhci_event_thread, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(event_thread != NULL); + event_thread->kernelarg = ctrl; + sched_queue(event_thread); + + struct thread_t *callback_thread = sched_newthread(xhci_callback_thread, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(callback_thread != NULL); + callback_thread->kernelarg = ctrl; + sched_queue(callback_thread); +} + +void xhci_init(void) { + for (int i = 0;; ++i) { + pcienum_t *e = pci_getenum(PCI_CLASS_SERIAL_BUS, PCI_SUBCLASS_SERIAL_BUS_USB, PCI_PROGIF_USB_XHCI, -1, -1, -1, i); + if (e == NULL) + break; + init_ctrl(e); + } +} + +INIT_ROUTINE_DEFINE(xhci, INIT_ROUTINE_FLAGS_NONE, xhci_init, acpi); diff --git a/kernel-src/io/usb/ctrl/xhci_ctrl.c b/kernel-src/io/usb/ctrl/xhci_ctrl.c new file mode 100644 index 00000000..76a53e70 --- /dev/null +++ b/kernel-src/io/usb/ctrl/xhci_ctrl.c @@ -0,0 +1,583 @@ +#include +#include +#include +#include +#include +#include + +static bool usb_speed_to_xhci_speed(usb_speed_t speed, uint8_t *out) { + switch (speed) { + case USB_SPEED_FULL: + *out = XHCI_PORT_SPEED_FULL; + return true; + case USB_SPEED_LOW: + *out = XHCI_PORT_SPEED_LOW; + return true; + case USB_SPEED_HIGH: + *out = XHCI_PORT_SPEED_HIGH; + return true; + case USB_SPEED_SUPER: + *out = XHCI_PORT_SPEED_SUPER; + return true; + default: + return false; + } +} + +static usb_speed_t xhci_speed_to_usb_speed(uint8_t speed) { + switch (speed) { + case XHCI_PORT_SPEED_FULL: + return USB_SPEED_FULL; + case XHCI_PORT_SPEED_LOW: + return USB_SPEED_LOW; + case XHCI_PORT_SPEED_HIGH: + return USB_SPEED_HIGH; + case XHCI_PORT_SPEED_SUPER: + return USB_SPEED_SUPER; + case XHCI_PORT_SPEED_SS_2X1: + case XHCI_PORT_SPEED_SS_1X2: + case XHCI_PORT_SPEED_SS_2X2: + return USB_SPEED_SUPER_PLUS; + default: + return USB_SPEED_UNKNOWN; + } +} + +static uint16_t xhci_ep0_max_packet_size(usb_device_t *device) { + if (device->speed == USB_SPEED_SUPER || device->speed == USB_SPEED_SUPER_PLUS) + return 1 << device->desc.bMaxPacketSize0; + return device->desc.bMaxPacketSize0; +} + +#define CTX_PTR(CTRL, CTX, INDEX) ((uintptr_t)(CTX) + ((CTRL)->ctx_stride * (INDEX))) + +static volatile xhci_input_ctx_t *xhci_get_input_ctrl_ctx(xhci_ctrl_t *xhci, xhci_device_t *dev) { + return (volatile xhci_input_ctx_t *)CTX_PTR(xhci, dev->input_ctx, 0); +} + +static volatile xhci_slot_ctx_t *xhci_get_input_slot_ctx(xhci_ctrl_t *xhci, xhci_device_t *dev) { + return (volatile xhci_slot_ctx_t *)CTX_PTR(xhci, dev->input_ctx, 1); +} + +static volatile xhci_slot_ctx_t *xhci_get_device_slot_ctx(xhci_ctrl_t *xhci, xhci_device_t *dev) { + return (volatile xhci_slot_ctx_t *)CTX_PTR(xhci, dev->device_ctx, 0); +} + +static volatile xhci_ep_ctx_t *xhci_get_input_ep_ctx(xhci_ctrl_t *xhci, xhci_device_t *dev, uint8_t ep) { + return (volatile xhci_ep_ctx_t *)CTX_PTR(xhci, dev->input_ctx, ep + 2); +} + +typedef struct { + usb_completion_callback_t callback; + void *callback_ctx; + uint32_t ep_index; +} xhci_configure_ep_ctx_t; + +static void xhci_release_ep_ring(xhci_device_t *dev, uint32_t ep_index) { + __assert(ep_index > 0 && ep_index <= 31); + xhci_ring_t *ring = &dev->ep_rings[ep_index - 1]; + xhci_free_ring(ring); + memset(ring, 0, sizeof(*ring)); +} + +static void xhci_update_input_context(xhci_ctrl_t *ctrl, xhci_device_t *dev) { + uint8_t *input_ctx = dev->input_ctx; + uint8_t *device_ctx = dev->device_ctx; + + // Copy over the slot context and all endpoint contexts. + memcpy(input_ctx + ctrl->ctx_stride, device_ctx, ctrl->ctx_stride * (1 + 31)); + // Clear input context header. + memset(input_ctx, 0, sizeof(uint32_t) * 2); +} + +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) + xhci_release_ep_ring(dev, i + 1); + } + + if (dev->slot_id > 0 && dev->slot_id <= xhci->slot_count) { + xhci->dcbaa[dev->slot_id] = 0; + if (xhci->slots[dev->slot_id - 1] == dev) + xhci->slots[dev->slot_id - 1] = NULL; + } + + if (dev->input_ctx_phys) + pmm_release(dev->input_ctx_phys); + if (dev->device_ctx_phys) + pmm_release(dev->device_ctx_phys); + + free(dev); +} + +static int xhci_disable_slot(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_completion_callback_t callback, void *callback_ctx) { + xhci_submission_t xhci_submission = { + .callback = callback, + .callback_ctx = callback_ctx, + .device = &dev->device, + }; + + xhci_trb_t trb = { + .dw3 = XHCI_TRB_DW3_TYPE(TRB_DISABLE_SLOT) | (dev->slot_id << 24) + }; + + xhci_ring_submit(&xhci->command_ring, &trb, &xhci_submission); + xhci_ring_command_doorbell(xhci); + return 0; +} + +static void address_device_failed_cleanup_callback(usb_device_t *device, void *ctx, usb_status_t status, size_t) { + usb_address_device_callback_t callback = ctx; + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + usb_hub_t *hub = device->hub; + uint8_t port = device->port_number; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: disable slot %u failed during address cleanup\n", xhci_dev->slot_id); + // If Disable Slot fails, the controller may still reference this device's + // contexts or transfer rings. Leak them for now instead of freeing memory + // that hardware may still own. + } else { + xhci_release_device_resources(xhci, xhci_dev); + } + + callback(hub, port, NULL); +} + +static void address_device_failed_cleanup(usb_device_t *device, usb_address_device_callback_t callback) { + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + xhci_disable_slot(xhci, xhci_dev, address_device_failed_cleanup_callback, callback); +} + +static void address_device_failed_before_slot(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_address_device_callback_t callback) { + usb_hub_t *hub = dev->device.hub; + uint8_t port = dev->device.port_number; + + xhci_release_device_resources(xhci, dev); + callback(hub, port, NULL); +} + +static void address_device_evaluate_ctx_callback(usb_device_t *device, void *ctx, usb_status_t status, size_t) { + usb_address_device_callback_t callback = ctx; + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: evaluate context failed on slot %u\n", xhci_dev->slot_id); + address_device_failed_cleanup(device, callback); + return; + } + // Update the input context again. + xhci_update_input_context(xhci, xhci_dev); + + // Tell the original caller that the device has been addresses + callback(xhci_dev->device.hub, xhci_dev->device.port_number, &xhci_dev->device); +} + +static void address_device_get_descriptor_callback(usb_device_t *device, void *ctx, usb_status_t status, size_t transferred) { + usb_address_device_callback_t callback = ctx; + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: failed to get device descriptor on slot %u\n", xhci_dev->slot_id); + address_device_failed_cleanup(device, callback); + return; + } + + if (!usb_device_desc_valid(&device->desc, device->speed, transferred)) { + printf("xhci: malformed device descriptor\n"); + address_device_failed_cleanup(device, callback); + return; + } + + // Update EP0 context with correct max packet size. + volatile xhci_ep_ctx_t *ep0_ctx = xhci_get_input_ep_ctx(xhci, xhci_dev, 0); + volatile xhci_input_ctx_t *input_ctx = xhci_get_input_ctrl_ctx(xhci, xhci_dev); + + ep0_ctx->dw1.max_packet_size = xhci_ep0_max_packet_size(device); + + input_ctx->a |= (1 << 1); + + // Submit an Evaluate Context command. + xhci_submission_t xhci_submission = { + .callback = address_device_evaluate_ctx_callback, + .callback_ctx = callback, + .device = &xhci_dev->device, + }; + + xhci_trb_t trb = { + .parameters = (uint64_t)xhci_dev->input_ctx_phys, + .dw3 = XHCI_TRB_DW3_TYPE(TRB_EVALUATE_CTX) | (xhci_dev->slot_id << 24) + }; + + xhci_ring_submit(&xhci->command_ring, &trb, &xhci_submission); + xhci_ring_command_doorbell(xhci); +} + +static void address_device_addressed_callback(usb_device_t *device, void *ctx, usb_status_t status, size_t) { + usb_address_device_callback_t callback = ctx; + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: address device failed on slot %d failed\n", xhci_dev->slot_id); + address_device_failed_cleanup(device, callback); + return; + } + + printf("xhci: addressed device on slot %u\n", xhci_dev->slot_id); + + volatile xhci_slot_ctx_t *slot_ctx = xhci_get_device_slot_ctx(xhci, xhci_dev); + + // Update the input context and set the device address. + xhci_dev->device.address = slot_ctx->dw3.usb_device_address; + xhci_update_input_context(xhci, xhci_dev); + + if (usb_get_device_descriptor( + &xhci_dev->device, + USB_DESCRIPTOR_TYPE_DEVICE, + 0, + &xhci_dev->device.desc, + sizeof(xhci_dev->device.desc), + address_device_get_descriptor_callback, + callback + )) { + printf("xhci: usb_get_device_descriptor failed\n"); + address_device_failed_cleanup(device, callback); + } +} + +static void address_device_create_slot_callback(usb_device_t *device, void *ctx, usb_status_t status, size_t slot_id) { + usb_address_device_callback_t callback = ctx; + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: create slot failed\n"); + address_device_failed_before_slot(xhci, xhci_dev, callback); + return; + } + + xhci_dev->slot_id = slot_id; + __assert(xhci_dev->slot_id > 0 && xhci_dev->slot_id <= xhci->slot_count); + printf("xhci: allocated slot %u for device %s:%u\n", xhci_dev->slot_id, xhci_dev->device.hub->name, xhci_dev->device.port_number); + + // Set up the Device Context Base Address Array entry. + xhci->dcbaa[xhci_dev->slot_id] = (uint64_t)xhci_dev->device_ctx_phys; + xhci->slots[xhci_dev->slot_id - 1] = xhci_dev; + + // Set up the input context and address the device. + volatile xhci_input_ctx_t *input_ctx = xhci_get_input_ctrl_ctx(xhci, xhci_dev); + volatile xhci_slot_ctx_t *slot_ctx = xhci_get_input_slot_ctx(xhci, xhci_dev); + volatile xhci_ep_ctx_t *ep0_ctx = xhci_get_input_ep_ctx(xhci, xhci_dev, 0); + + // Enable slot and EP0 contexts. + input_ctx->a |= (1 << 0) | (1 << 1); + + // Set up slot context. + + slot_ctx->dw0.route_string = xhci_dev->route_string; + slot_ctx->dw0.speed = xhci_dev->ctrl_speed; + slot_ctx->dw0.ctx_entries = 1; + slot_ctx->dw1.root_hub_port_number = xhci_dev->root_port_number; + if (device->hub->device != NULL && + (device->speed == USB_SPEED_LOW || device->speed == USB_SPEED_FULL) && + device->hub->device->speed == USB_SPEED_HIGH) { + xhci_device_t *parent = container_of(device->hub->device, xhci_device_t, device); + slot_ctx->dw2.tt_hub_slot_id = parent->slot_id; + slot_ctx->dw2.tt_port_number = device->port_number + 1; + slot_ctx->dw2.ttt = 0; + } + + // Set up EP0 context. + ep0_ctx->dw1.ep_type = XHCI_EP_TYPE_CTRL; + ep0_ctx->dw1.cerr = 3; + + if (xhci_dev->ctrl_speed == XHCI_PORT_SPEED_LOW || xhci_dev->ctrl_speed == XHCI_PORT_SPEED_FULL) + ep0_ctx->dw1.max_packet_size = 8; + else if (xhci_dev->ctrl_speed == XHCI_PORT_SPEED_HIGH) + ep0_ctx->dw1.max_packet_size = 64; + else + ep0_ctx->dw1.max_packet_size = 512; + + uint64_t ep0_ring_phys = (uint64_t)xhci_dev->ep_rings[0].ring_phys; + ep0_ctx->dw2.dcs = xhci_dev->ep_rings[0].cycle; + ep0_ctx->dw2.tr_dequeue_pointer_lo = (uint32_t)((ep0_ring_phys >> 4) & 0xffffffff); + ep0_ctx->dw3.tr_dequeue_pointer_hi = (uint32_t)(ep0_ring_phys >> 32); + + xhci_submission_t xhci_submission = { + .callback = address_device_addressed_callback, + .callback_ctx = callback, + .device = &xhci_dev->device + }; + + xhci_trb_t trb = { + .parameters = (uint64_t)xhci_dev->input_ctx_phys, + .dw3 = XHCI_TRB_DW3_TYPE(TRB_ADDRESS_DEVICE) | (xhci_dev->slot_id << 24) + }; + + xhci_ring_submit(&xhci->command_ring, &trb, &xhci_submission); + 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) { + ++port; // convert to the format xhci expects + + xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); + + xhci_device_t *xhci_dev = alloc(sizeof(xhci_device_t)); + if (xhci_dev == NULL) + return ENOMEM; + + void *device_ctx_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + if (device_ctx_phys == NULL) { + free(xhci_dev); + return ENOMEM; + } + + void *input_ctx_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + if (input_ctx_phys == NULL) { + pmm_release(device_ctx_phys); + free(xhci_dev); + return ENOMEM; + } + + if (xhci_alloc_ring(xhci, &xhci_dev->ep_rings[0], false)) { + pmm_release(input_ctx_phys); + pmm_release(device_ctx_phys); + free(xhci_dev); + return ENOMEM; + } + + xhci_dev->device_ctx_phys = device_ctx_phys; + xhci_dev->device_ctx = MAKE_HHDM(device_ctx_phys); + memset(xhci_dev->device_ctx, 0, PAGE_SIZE); + + xhci_dev->input_ctx_phys = input_ctx_phys; + xhci_dev->input_ctx = MAKE_HHDM(input_ctx_phys); + memset(xhci_dev->input_ctx, 0, PAGE_SIZE); + + xhci_dev->device.hub = hub; + xhci_dev->device.port_number = port - 1; + xhci_dev->device.port_generation = hub->ports[port - 1].generation; + + if (hub->device) { + xhci_device_t *parent = container_of(hub->device, xhci_device_t, device); + xhci_dev->device_tier = parent->device_tier + 1; + xhci_dev->route_string = parent->route_string | (port << (parent->device_tier * 4)); + + xhci_device_t *xhci_hub_device = (xhci_device_t *)hub->device; + xhci_dev->root_port_number = xhci_hub_device->root_port_number; + } else { + xhci_dev->device_tier = 0; + xhci_dev->route_string = 0; + 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); + uint32_t portsc = xhci->portregs[rh->port_offset + port - 1].portsc; + 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; + } + + xhci_submission_t xhci_submission = { + .callback = address_device_create_slot_callback, + .callback_ctx = callback, + .device = &xhci_dev->device + }; + + xhci_trb_t trb = { + .dw3 = XHCI_TRB_DW3_TYPE(TRB_ENABLE_SLOT) + }; + + xhci_ring_submit(&xhci->command_ring, &trb, &xhci_submission); + xhci_ring_command_doorbell(xhci); + return 0; +} + +static void configure_ep_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + xhci_configure_ep_ctx_t *configure_ctx = ctx; + usb_completion_callback_t callback = configure_ctx->callback; + void *callback_ctx = configure_ctx->callback_ctx; + + if (status != USB_STATUS_SUCCESS) { + xhci_device_t *xhci_dev = container_of(dev, xhci_device_t, device); + xhci_release_ep_ring(xhci_dev, configure_ctx->ep_index); + } + + free(configure_ctx); + + if (callback) + callback(dev, callback_ctx, status, transferred); +} + +static int xhci_ctrl_configure_ep(usb_ctrl_t *ctrl, usb_device_t *dev, usb_endpoint_t *ep, usb_completion_callback_t callback, void *callback_ctx) { + xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); + xhci_device_t *xhci_dev = container_of(dev, xhci_device_t, device); + + bool is_in = (ep->desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_DIR_IN) != 0; + + uint32_t ep_num = ep->desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_NUM_MASK; + uint32_t ep_index = ep_num ? ((ep_num << 1) | (is_in ? 1 : 0)) : 0; + + if (ep_index == 0 || ep_index > 31) + return EINVAL; + + if (xhci_dev->ep_rings[ep_index - 1].ring != NULL) { + printf("xhci: duplicate endpoint address 0x%02x\n", ep->desc->bEndpointAddress); + return EINVAL; + } + + xhci_configure_ep_ctx_t *configure_ctx = alloc(sizeof(xhci_configure_ep_ctx_t)); + if (configure_ctx == NULL) + return ENOMEM; + + if (xhci_alloc_ring(xhci, &xhci_dev->ep_rings[ep_index - 1], false)) { + free(configure_ctx); + return ENOMEM; + } + + xhci_update_input_context(xhci, xhci_dev); + + volatile xhci_input_ctx_t *input_ctx = xhci_get_input_ctrl_ctx(xhci, xhci_dev); + volatile xhci_slot_ctx_t *slot_ctx = xhci_get_input_slot_ctx(xhci, xhci_dev); + volatile xhci_ep_ctx_t *ep_ctx = xhci_get_input_ep_ctx(xhci, xhci_dev, ep_index - 1); + + memset((void *)ep_ctx, 0, sizeof(xhci_ep_ctx_t)); + + input_ctx->a |= 1 << 0; + input_ctx->a |= 1 << ep_index; + + if (slot_ctx->dw0.ctx_entries < ep_index) + slot_ctx->dw0.ctx_entries = ep_index; + + // 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); + uint8_t max_burst_size = 0; + uint16_t max_esit_payload = max_packet_size; + + if ((dev->speed == USB_SPEED_SUPER || dev->speed == USB_SPEED_SUPER_PLUS) && ep->ss_companion == NULL) { + free(configure_ctx); + xhci_release_ep_ring(xhci_dev, ep_index); + return EINVAL; + } + + if (ep->ss_companion != NULL) { + max_burst_size = ep->ss_companion->bMaxBurst; + max_esit_payload = ep->ss_companion->wBytesPerInterval; + } else if (dev->speed == USB_SPEED_HIGH) { + uint8_t transactions = ((ep->desc->wMaxPacketSize >> 11) & 0x3) + 1; + max_esit_payload = max_packet_size * transactions; + } + + if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_BULK) { + ep_ctx->dw1.ep_type = is_in ? XHCI_EP_TYPE_BULK_IN : XHCI_EP_TYPE_BULK_OUT; + } else if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_INTR) { + if (ep->desc->bInterval == 0) { + free(configure_ctx); + xhci_release_ep_ring(xhci_dev, ep_index); + return EINVAL; + } + + if (dev->speed == USB_SPEED_LOW || dev->speed == USB_SPEED_FULL) + ep_ctx->dw0.interval = ceil_log2(ep->desc->bInterval * 8); + else + ep_ctx->dw0.interval = ep->desc->bInterval - 1; + + ep_ctx->dw1.ep_type = is_in ? XHCI_EP_TYPE_INTR_IN : XHCI_EP_TYPE_INTR_OUT; + ep_ctx->dw4.max_esit_payload_lo = max_esit_payload; + } else { + free(configure_ctx); + xhci_release_ep_ring(xhci_dev, ep_index); + return EINVAL; + } + + ep_ctx->dw1.cerr = 3; + ep_ctx->dw1.max_packet_size = max_packet_size; + ep_ctx->dw1.max_burst_size = max_burst_size; + + // Set up the ring dequeue pointer. + uint64_t ep_ring_phys = (uint64_t)xhci_dev->ep_rings[ep_index - 1].ring_phys; + ep_ctx->dw2.dcs = xhci_dev->ep_rings[ep_index - 1].cycle; + ep_ctx->dw2.tr_dequeue_pointer_lo = (uint32_t)((ep_ring_phys >> 4) & 0xffffffff); + ep_ctx->dw3.tr_dequeue_pointer_hi = (uint32_t)(ep_ring_phys >> 32); + + configure_ctx->callback = callback; + configure_ctx->callback_ctx = callback_ctx; + configure_ctx->ep_index = ep_index; + + xhci_submission_t xhci_submission = { + .callback = configure_ep_callback, + .callback_ctx = configure_ctx, + .device = &xhci_dev->device, + }; + + xhci_trb_t trb = { + .parameters = (uint64_t)xhci_dev->input_ctx_phys, + .dw3 = XHCI_TRB_DW3_TYPE(TRB_CONFIGURE_EP) | (xhci_dev->slot_id << 24) + }; + + xhci_ring_submit(&xhci->command_ring, &trb, &xhci_submission); + xhci_ring_command_doorbell(xhci); + return 0; +} + +static int xhci_ctrl_xfer(usb_ctrl_t *ctrl, usb_device_t *dev, usb_xfer_t *xfer) { + xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); + xhci_device_t *xhci_dev = container_of(dev, xhci_device_t, device); + + int error = 0; + xhci_submission_t sub = { + .callback = xfer->completion, + .callback_ctx = xfer->completion_ctx, + .device = xfer->device, + }; + + if (xfer->type == USB_XFER_TYPE_CONTROL) + error = xhci_control_xfer(xhci, xhci_dev, xfer, &sub); + else + error = xhci_data_xfer(xhci, xhci_dev, xfer, &sub); + + return error; +} + +static void deaddress_device_callback(usb_device_t *device, void *, usb_status_t status, size_t) { + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + xhci_ctrl_t *xhci = (xhci_ctrl_t *)xhci_dev->device.hub->ctrl; + + if (status != USB_STATUS_SUCCESS) { + printf("xhci: disable slot %u failed during deaddress\n", xhci_dev->slot_id); + // If Disable Slot fails, the controller may still reference this device's + // contexts or transfer rings. Leak them for now instead of freeing memory + // that hardware may still own. + } else { + xhci_release_device_resources(xhci, xhci_dev); + } +} + +static void xhci_deaddress_device(usb_ctrl_t *ctrl, usb_device_t *device) { + xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl); + xhci_device_t *xhci_dev = container_of(device, xhci_device_t, device); + + xhci_disable_slot(xhci, xhci_dev, deaddress_device_callback, NULL); +} + +usb_ctrl_ops_t xhci_ops = { + .address_device = xhci_ctrl_address_device, + .configure_ep = xhci_ctrl_configure_ep, + .xfer = xhci_ctrl_xfer, + .deaddress_device = xhci_deaddress_device +}; diff --git a/kernel-src/io/usb/ctrl/xhci_hub.c b/kernel-src/io/usb/ctrl/xhci_hub.c new file mode 100644 index 00000000..cceb92b6 --- /dev/null +++ b/kernel-src/io/usb/ctrl/xhci_hub.c @@ -0,0 +1,153 @@ +#include +#include + +void xhci_handle_port_change_event(xhci_ctrl_t *xhci, int global_port) { + int local_port = -1; + xhci_root_hub_t *hub = NULL; + list_for_each(&xhci->hubs, i) { + hub = container_of(i, xhci_root_hub_t, list_node); + int port = global_port - 1 - hub->port_offset; + if (port >= 0 && port < hub->hub.port_count) { + local_port = port; + break; + } + } + + // getting an event on a non-existant hub is either a hardware bug or a driver bug + // either way, we probably don't want to continue + __assert(local_port != -1); + + uint32_t portsc = xhci->portregs[global_port - 1].portsc; + bool reset_event = false; + bool connect_event = false; + bool disconnect_event = false; + + if (portsc & XHCI_PORTSC_PRC) + reset_event = true; + + if (portsc & XHCI_PORTSC_CSC) { + connect_event = portsc & XHCI_PORTSC_CCS; + disconnect_event = !connect_event; + } + + if (disconnect_event) + usb_hub_event_disconnect(&hub->hub, local_port); + else if (connect_event) { + if (hub->hub.ports[local_port].device != NULL) + 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); + } + + xhci->portregs[global_port - 1].portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | + (portsc & XHCI_PORTSC_CHANGE_BITS); +} + +static int xhci_root_hub_get_port_status(usb_hub_t *hub, uint8_t port, uint16_t *status, uint16_t *change) { + xhci_root_hub_t *rh = container_of(hub, xhci_root_hub_t, hub); + xhci_ctrl_t *ctrl = container_of(hub->ctrl, xhci_ctrl_t, ctrl); + ++port; + + if (port == 0 || port > hub->port_count) + return EINVAL; + + uint32_t portsc = ctrl->portregs[rh->port_offset + port - 1].portsc; + uint32_t port_speed = (portsc >> 10) & 0xf; + + *status = 0; + *change = 0; + + if (port_speed == XHCI_PORT_SPEED_LOW) + *status |= USB_HUB_PORT_STATUS_LOW_SPEED; + else if (port_speed == XHCI_PORT_SPEED_HIGH) + *status |= USB_HUB_PORT_STATUS_HIGH_SPEED; + + if (portsc & XHCI_PORTSC_CCS) + *status |= USB_HUB_PORT_STATUS_PORT_CONNECTION; + if (portsc & XHCI_PORTSC_PED) + *status |= USB_HUB_PORT_STATUS_PORT_ENABLE; + if (portsc & XHCI_PORTSC_OCA) + *status |= USB_HUB_PORT_STATUS_PORT_OVER_CURRENT; + if (portsc & XHCI_PORTSC_PR) + *status |= USB_HUB_PORT_STATUS_PORT_RESET; + if (portsc & XHCI_PORTSC_PP) + *status |= USB_HUB_PORT_STATUS_PORT_POWER; + + if (portsc & XHCI_PORTSC_CSC) + *change |= USB_HUB_PORT_CHANGE_PORT_CONNECTION; + if (portsc & XHCI_PORTSC_PEC) + *change |= USB_HUB_PORT_CHANGE_PORT_ENABLE; + if (portsc & XHCI_PORTSC_OCC) + *change |= USB_HUB_PORT_CHANGE_PORT_OVER_CURRENT; + if (portsc & XHCI_PORTSC_PRC) + *change |= USB_HUB_PORT_CHANGE_PORT_RESET; + + return 0; +} + +static int xhci_root_hub_set_port_feature(usb_hub_t *hub, uint8_t port, uint16_t feature) { + xhci_root_hub_t *rh = container_of(hub, xhci_root_hub_t, hub); + xhci_ctrl_t *ctrl = container_of(hub->ctrl, xhci_ctrl_t, ctrl); + ++port; + + if (port == 0 || port > hub->port_count) + return EINVAL; + + volatile xhci_port_regs_t *portregs = &ctrl->portregs[rh->port_offset + port - 1]; + + uint32_t portsc = portregs->portsc; + portsc &= ~(XHCI_PORTSC_CCS | XHCI_PORTSC_PED | XHCI_PORTSC_OCA | XHCI_PORTSC_PR); + portsc &= ~XHCI_PORTSC_CHANGE_BITS; + + if (feature == USB_HUB_FEATURE_PORT_RESET) + portsc |= XHCI_PORTSC_PR; + else if (feature == USB_HUB_FEATURE_PORT_POWER) + portsc |= XHCI_PORTSC_PP; + else + return EINVAL; + + portregs->portsc = portsc; + return 0; +} + +static int xhci_root_hub_clear_port_feature(usb_hub_t *hub, uint8_t port, uint16_t feature) { + xhci_root_hub_t *rh = container_of(hub, xhci_root_hub_t, hub); + xhci_ctrl_t *ctrl = container_of(hub->ctrl, xhci_ctrl_t, ctrl); + ++port; + + if (port == 0 || port > hub->port_count) + return EINVAL; + + volatile xhci_port_regs_t *portregs = &ctrl->portregs[rh->port_offset + port - 1]; + + uint32_t portsc = portregs->portsc; + portsc &= ~(XHCI_PORTSC_CCS | XHCI_PORTSC_PED | XHCI_PORTSC_OCA | XHCI_PORTSC_PR); + portsc &= ~XHCI_PORTSC_CHANGE_BITS; + + if (feature == USB_HUB_FEATURE_PORT_ENABLE) + portsc &= ~XHCI_PORTSC_PED; + else if (feature == USB_HUB_FEATURE_PORT_RESET) + portsc &= ~XHCI_PORTSC_PR; + else if (feature == USB_HUB_FEATURE_PORT_POWER) + portsc &= ~XHCI_PORTSC_PP; + else if (feature == USB_HUB_FEATURE_C_PORT_CONNECTION) + portsc |= XHCI_PORTSC_CSC; + else if (feature == USB_HUB_FEATURE_C_PORT_ENABLE) + portsc |= XHCI_PORTSC_PEC; + else if (feature == USB_HUB_FEATURE_C_PORT_OVER_CURRENT) + portsc |= XHCI_PORTSC_OCC; + else if (feature == USB_HUB_FEATURE_C_PORT_RESET) + portsc |= XHCI_PORTSC_PRC; + else + return EINVAL; + + portregs->portsc = portsc; + return 0; +} + +usb_hub_ops_t xhci_root_hub_ops = { + .get_port_status = xhci_root_hub_get_port_status, + .set_port_feature = xhci_root_hub_set_port_feature, + .clear_port_feature = xhci_root_hub_clear_port_feature, +}; diff --git a/kernel-src/io/usb/ctrl/xhci_ring.c b/kernel-src/io/usb/ctrl/xhci_ring.c new file mode 100644 index 00000000..ff5e811d --- /dev/null +++ b/kernel-src/io/usb/ctrl/xhci_ring.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +int xhci_alloc_ring(xhci_ctrl_t *ctrl, xhci_ring_t *r, bool event_ring) { + void *ring_phys = pmm_allocpage(PMM_SECTION_DEFAULT); + if (ring_phys == NULL) + return ENOMEM; + + xhci_trb_t *ring = MAKE_HHDM(ring_phys); + memset(ring, 0, PAGE_SIZE); + + r->ring_phys = ring_phys; + r->ring = ring; + r->ctrl = ctrl; + r->size = PAGE_SIZE / sizeof(xhci_trb_t); + r->index = 0; + r->cycle = true; + + if (event_ring) { + r->submissions = NULL; + } else { + r->submissions = alloc(sizeof(xhci_submission_t) * r->size); + if (r->submissions == NULL) { + pmm_release(ring_phys); + return ENOMEM; + } + + resource_allocator_init(&r->trb_allocator, r->size - 1, r->size - 1); + } + + MUTEX_INIT(&r->lock); + + return 0; +} + +void xhci_free_ring(xhci_ring_t *ring) { + pmm_release(ring->ring_phys); + free(ring->submissions); +} + +void xhci_ring_reserve(xhci_ring_t *r, size_t needed) { + if (needed == 0) + return; + + __assert(needed <= r->size - 1) + + resource_allocate(&r->trb_allocator, needed); +} + +void xhci_ring_unreserve(xhci_ring_t *r, size_t needed) { + if (needed) + resource_free(&r->trb_allocator, needed); +} + +xhci_submission_t *xhci_ring_submit_locked(xhci_ring_t *r, xhci_trb_t *trb, xhci_submission_t *sub) { + // Cycle bit must be 0 in the TRB being submitted. + __assert((trb->dw3 & XHCI_TRB_DW3_C) == 0); + + if (r->index == r->size - 1) { + // If we're at the end of the ring, we need to set up a link TRB. + xhci_trb_t *link_trb = &r->ring[r->index]; + link_trb->parameters = (uint64_t)r->ring_phys; + link_trb->dw2 = 0; + link_trb->dw3 = XHCI_TRB_DW3_TYPE(TRB_LINK) | XHCI_TRB_DW3_TC | (r->cycle ? XHCI_TRB_DW3_C : 0); + + // Now wrap around to start of ring and flip cycle bit. + r->index = 0; + r->cycle ^= 1; + } + + size_t idx = r->index++; + xhci_trb_t *r_trb = &r->ring[idx]; + xhci_submission_t *r_sub = NULL; + + memcpy(r_trb, trb, sizeof(xhci_trb_t)); + + if (sub) { + r_sub = &r->submissions[idx]; + + bool old_valid = __atomic_exchange_n(&r_sub->valid, true, __ATOMIC_SEQ_CST); + __assert(!old_valid); + + r_sub->ring = r; + r_sub->device = sub->device; + r_sub->callback = sub->callback; + r_sub->callback_ctx = sub->callback_ctx; + r_sub->data_length = sub->data_length; + r_sub->data_offset = sub->data_offset; + r_sub->next = NULL; + r_sub->td_head = NULL; + memcpy(&r_sub->cmd_trb, trb, sizeof(xhci_trb_t)); + r_sub->usb_status = USB_STATUS_SUCCESS; // initialize this to a known value to allow the handling of short packets on control transfers + } + + // Set cycle bit appropriately. + if (r->cycle) + r_trb->dw3 |= XHCI_TRB_DW3_C; + + return r_sub; +} + +void xhci_ring_submit(xhci_ring_t *r, xhci_trb_t *trb, xhci_submission_t *sub) { + xhci_ring_reserve(r, 1); + + MUTEX_ACQUIRE(&r->lock); + xhci_ring_submit_locked(r, trb, sub); + MUTEX_RELEASE(&r->lock); +} + +xhci_trb_t *xhci_ring_dequeue(xhci_ring_t *r) { + xhci_trb_t *r_trb = &r->ring[r->index]; + + // Make sure the cycle bit matches. + if (((r_trb->dw3 & XHCI_TRB_DW3_C) != 0) != r->cycle) { + return NULL; + } + + // Advance offset and flip cycle bit if needed. + r->index++; + + if (r->index == r->size) { + r->index = 0; + r->cycle ^= 1; + } + + return r_trb; +} diff --git a/kernel-src/io/usb/ctrl/xhci_xfer.c b/kernel-src/io/usb/ctrl/xhci_xfer.c new file mode 100644 index 00000000..2ef373b7 --- /dev/null +++ b/kernel-src/io/usb/ctrl/xhci_xfer.c @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include +#include + +typedef struct { + xhci_trb_t trb; + void *page; + size_t data_length; + size_t data_offset; +} sg_trb_t; + +static int xhci_count_sg_trbs(iovec_iterator_t *iov, size_t total_size, size_t *trb_count) { + iovec_iterator_t iter = *iov; + size_t done = 0; + size_t count = 0; + + while (done < total_size) { + size_t page_offset, page_remaining; + void *page; + + int err = iovec_iterator_next_page(&iter, &page_offset, &page_remaining, &page); + if (err) + return err; + if (page == NULL) + return EFAULT; + + pmm_release(page); + + done += min(page_remaining, total_size - done); + count++; + } + + *trb_count = count; + return 0; +} + +int xhci_data_xfer(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub) { + __assert(xfer->ep != NULL); + __assert(xfer->setup == NULL); + + uint32_t ep_num = xfer->ep->desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_NUM_MASK; + uint32_t ep_index = (ep_num << 1) | (xfer->ep->desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_DIR_IN ? 1 : 0); + + xhci_ring_t *ring = &dev->ep_rings[ep_index - 1]; + int error = 0; + + if (xfer->flags & USB_XFER_FLAG_IOVEC) { + error = xhci_sg_data_xfer(xhci, ring, dev, xfer, sub); + } else { + if (xfer->data_length == 0) { + if (sub->callback) + sub->callback(sub->device, sub->callback_ctx, USB_STATUS_SUCCESS, 0); + return 0; + } + + xhci_ring_reserve(ring, 1); + + // Make sure the data transfer does not cross a page boundary. + uint64_t page_offset = (uint64_t)xfer->data & (PAGE_SIZE - 1); + __assert(page_offset + xfer->data_length <= PAGE_SIZE); + + xhci_trb_t trb = {0}; + if (xfer->flags & USB_XFER_FLAG_BUFFER_PHYSICAL) { + pmm_hold(xfer->data - page_offset); + trb.parameters = (uint64_t)xfer->data; + } else { + // This should never be called with user pages. Doing so is a bug. For user-fronting code, use + // the scatter-gather variant + __assert(!IS_USER_ADDRESS(xfer->data)); + void *page = vmm_getphysical(xfer->data - page_offset, VMM_GET_PHYSICAL_FLAGS_HOLD); + __assert(page != NULL); + trb.parameters = (uint64_t)page + page_offset; + } + + trb.dw2 = (uint32_t)xfer->data_length; + trb.dw3 = XHCI_TRB_DW3_TYPE(TRB_NORMAL) | XHCI_TRB_DW3_ISP | XHCI_TRB_DW3_IOC; + + sub->data_length = xfer->data_length; + sub->data_offset = 0; + + MUTEX_ACQUIRE(&ring->lock); + xhci_submission_t *current = xhci_ring_submit_locked(ring, &trb, sub); + current->td_head = current; + MUTEX_RELEASE(&ring->lock); + } + + if (error) + return error; + + // Ring the doorbell for the endpoint. + xhci_ring_endpoint_doorbell(xhci, dev, ep_index); + return error; +} + +int xhci_sg_data_xfer(xhci_ctrl_t *xhci, xhci_ring_t *ring, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub) { + uint16_t max_packet_size = usb_endpoint_max_packet_size(xfer->ep->desc); + uint32_t td_count = ROUND_UP(xfer->iov->total_size, max_packet_size) / max_packet_size; + uint32_t done = 0; + + if (xfer->iov->total_size == 0) { + if (sub->callback) + sub->callback(sub->device, sub->callback_ctx, USB_STATUS_SUCCESS, 0); + return 0; + } + + size_t trb_count; + int err = xhci_count_sg_trbs(xfer->iov, xfer->iov->total_size, &trb_count); + if (err) + return err; + + xhci_ring_reserve(ring, trb_count); + + sg_trb_t *prepared = alloc(sizeof(sg_trb_t) * trb_count); + if (prepared == NULL) { + xhci_ring_unreserve(ring, trb_count); + return ENOMEM; + } + memset(prepared, 0, sizeof(sg_trb_t) * trb_count); + + iovec_iterator_t iter = *xfer->iov; + + size_t prepared_count = 0; + while (done < xfer->iov->total_size) { + size_t page_offset, page_remaining; + void *page; + + err = iovec_iterator_next_page(&iter, &page_offset, &page_remaining, &page); + if (err) + goto fail; + if (page == NULL) { + err = EFAULT; + goto fail; + } + + size_t to_xfer = min(page_remaining, xfer->iov->total_size - done); + + uint8_t td_size = td_count - (done + to_xfer) / max_packet_size; + if (done + to_xfer == xfer->iov->total_size) + td_size = 0; + else if (td_size > 31) + td_size = 31; + + xhci_trb_t trb = {0}; + trb.parameters = (uint64_t)page + page_offset; + trb.dw2 = XHCI_TRB_DW2_TR_LEN(to_xfer) | XHCI_TRB_DW2_TD_SIZE(td_size); + trb.dw3 = XHCI_TRB_DW3_TYPE(TRB_NORMAL) | XHCI_TRB_DW3_ISP; + + if (to_xfer + done < xfer->iov->total_size) { + // More TRBs to come in this transfer, set the CH bit. + trb.dw3 |= XHCI_TRB_DW3_CH; + } else { + trb.dw3 |= XHCI_TRB_DW3_IOC; + } + + prepared[prepared_count].trb = trb; + prepared[prepared_count].page = page; + prepared[prepared_count].data_length = to_xfer; + prepared[prepared_count].data_offset = done; + prepared_count++; + done += to_xfer; + } + + *xfer->iov = iter; + + MUTEX_ACQUIRE(&ring->lock); + xhci_submission_t *prev = NULL; + for (size_t i = 0; i < prepared_count; ++i) { + sub->data_length = prepared[i].data_length; + sub->data_offset = prepared[i].data_offset; + xhci_submission_t *current = xhci_ring_submit_locked(ring, &prepared[i].trb, sub); + if (prev == NULL) { + current->td_head = current; + } else { + current->td_head = prev->td_head; + prev->next = current; + } + + prev = current; + } + MUTEX_RELEASE(&ring->lock); + + free(prepared); + return 0; + + fail: + for (size_t i = 0; i < prepared_count; ++i) + pmm_release(prepared[i].page); + free(prepared); + xhci_ring_unreserve(ring, trb_count); + return err; +} + +int xhci_control_xfer(xhci_ctrl_t *xhci, xhci_device_t *dev, usb_xfer_t *xfer, xhci_submission_t *sub) { + __assert(xfer->ep == NULL); + __assert(xfer->setup != NULL); + + // Why would anyone want to do scatter-gather control transfers??? + __assert(!(xfer->flags & USB_XFER_FLAG_IOVEC)); + + // Make sure direction matches request type and length matches buffer length. + usb_setup_t *setup = xfer->setup; + + { + bool flags_to_host = (xfer->flags & USB_XFER_FLAG_TO_HOST) != 0; + bool req_to_host = (setup->bmRequestType & USB_REQUEST_DIR_TO_HOST) != 0; + __assert(xfer->data_length == setup->wLength); + __assert(flags_to_host == req_to_host); + } + + bool has_data_stage = xfer->data != NULL && xfer->data_length > 0; + bool data_stage_in = has_data_stage && xfer->flags & USB_XFER_FLAG_TO_HOST; + + xhci_ring_t *ring = &dev->ep_rings[0]; + + size_t trb_count = has_data_stage ? 3 : 2; + xhci_ring_reserve(ring, trb_count); + + xhci_trb_t setup_trb = {0}; + xhci_trb_t data_trb = {0}; + xhci_trb_t status_trb = {0}; + + uint32_t trt = 0; + if (has_data_stage) + // 0 = no data stage, 2 = data out, 3 = data in + trt = data_stage_in ? 3 : 2; + + setup_trb.dw0 = (uint32_t)setup->bmRequestType | ((uint32_t)setup->bRequest << 8) | ((uint32_t)setup->wValue << 16); + setup_trb.dw1 = (uint32_t)setup->wIndex | ((uint32_t)setup->wLength << 16); + setup_trb.dw2 = 8; // Always 8 bytes for setup stage. + setup_trb.dw3 = XHCI_TRB_DW3_TYPE(TRB_SETUP_STAGE) | XHCI_TRB_DW3_TRT(trt) | XHCI_TRB_DW3_IDT; + + if (has_data_stage) { + // Make sure the data transfer does not cross a page boundary. + uint64_t page_offset = (uint64_t)xfer->data & (PAGE_SIZE - 1); + __assert(page_offset + xfer->data_length <= PAGE_SIZE); + + if (xfer->data_length) { + if (xfer->flags & USB_XFER_FLAG_BUFFER_PHYSICAL) { + pmm_hold(xfer->data - page_offset); + data_trb.parameters = (uint64_t)xfer->data; + } else { + void *page = vmm_getphysical(xfer->data - page_offset, VMM_GET_PHYSICAL_FLAGS_HOLD); + if (page == NULL) { + xhci_ring_unreserve(ring, trb_count); + return EFAULT; + } + data_trb.parameters = (uint64_t)page + page_offset; + } + } + + data_trb.dw2 = (uint32_t)xfer->data_length; + data_trb.dw3 = XHCI_TRB_DW3_TYPE(TRB_DATA_STAGE) | XHCI_TRB_DW3_ISP | (data_stage_in ? XHCI_TRB_DW3_DIR : 0); + } + + status_trb.dw3 = XHCI_TRB_DW3_TYPE(TRB_STATUS_STAGE) | XHCI_TRB_DW3_IOC | (data_stage_in ? 0 : XHCI_TRB_DW3_DIR); + + MUTEX_ACQUIRE(&ring->lock); + + sub->data_length = 0; + sub->data_offset = 0; + xhci_submission_t *prev = xhci_ring_submit_locked(ring, &setup_trb, sub); + xhci_submission_t *head = prev; + prev->td_head = head; + + if (has_data_stage) { + sub->data_length = xfer->data_length; + sub->data_offset = 0; + xhci_submission_t *current = xhci_ring_submit_locked(ring, &data_trb, sub); + current->td_head = head; + prev->next = current; + prev = current; + } + + sub->data_length = 0; + sub->data_offset = has_data_stage ? xfer->data_length : 0; + xhci_submission_t *status = xhci_ring_submit_locked(ring, &status_trb, sub); + status->td_head = head; + prev->next = status; + + MUTEX_RELEASE(&ring->lock); + + // Ring the doorbell for the control endpoint. + xhci_ring_endpoint_doorbell(xhci, dev, 1); + return 0; +} From 3d02764ca96b5fa7f9fdbef19086a3db97e7f528 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 May 2026 00:25:45 -0300 Subject: [PATCH 5/8] usb: initial hid implementation --- kernel-src/include/kernel/usb_hid.h | 100 +++ kernel-src/io/usb/class/hid.c | 309 ++++++++ kernel-src/io/usb/class/hid_parser.c | 1011 ++++++++++++++++++++++++++ 3 files changed, 1420 insertions(+) create mode 100644 kernel-src/include/kernel/usb_hid.h create mode 100644 kernel-src/io/usb/class/hid.c create mode 100644 kernel-src/io/usb/class/hid_parser.c diff --git a/kernel-src/include/kernel/usb_hid.h b/kernel-src/include/kernel/usb_hid.h new file mode 100644 index 00000000..d8e25ce9 --- /dev/null +++ b/kernel-src/include/kernel/usb_hid.h @@ -0,0 +1,100 @@ +#ifndef _USB_HID_H +#define _USB_HID_H + +#include +#include +#include + +#define HID_USAGE_PAGE_GENERIC_DESKTOP 0x01 +#define HID_USAGE_PAGE_SIMULATION 0x02 +#define HID_USAGE_PAGE_VR 0x03 +#define HID_USAGE_PAGE_SPORT 0x04 +#define HID_USAGE_PAGE_GAME 0x05 +#define HID_USAGE_PAGE_GENERIC_DEVICE 0x06 +#define HID_USAGE_PAGE_KEYBOARD_KEYPAD 0x07 +#define HID_USAGE_PAGE_LED 0x08 +#define HID_USAGE_PAGE_BUTTON 0x09 +#define HID_USAGE_PAGE_ORDINAL 0x0a +#define HID_USAGE_PAGE_TELEPHONY 0x0b +#define HID_USAGE_PAGE_CONSUMER 0x0c +#define HID_USAGE_PAGE_DIGITIZER 0x0d +#define HID_USAGE_PAGE_PID 0x0f +#define HID_USAGE_PAGE_UNICODE 0x10 +#define HID_USAGE_PAGE_SENSOR 0x20 +#define HID_USAGE_PAGE_VENDOR_MIN 0xff00 +#define HID_USAGE_PAGE_VENDOR_MAX 0xffff + +#define HID_GD_POINTER 0x01 +#define HID_GD_MOUSE 0x02 +#define HID_GD_JOYSTICK 0x04 +#define HID_GD_GAMEPAD 0x05 +#define HID_GD_KEYBOARD 0x06 +#define HID_GD_KEYPAD 0x07 +#define HID_GD_MULTI_AXIS_CONTROLLER 0x08 + +typedef struct { + uint32_t usage; + uint32_t minimum; + uint32_t maximum; +} hid_usage_t; + +#define USAGE_PAGE(x) (((x) >> 16) & 0xffff) +#define USAGE(x) ((x) & 0xffff) + +#define HID_INPUT_CONSTANT (1u << 0) +#define HID_INPUT_VARIABLE (1u << 1) +#define HID_INPUT_RELATIVE (1u << 2) +#define HID_INPUT_WRAP (1u << 3) +#define HID_INPUT_NON_LINEAR (1u << 4) +#define HID_INPUT_NO_PREFERRED (1u << 5) +#define HID_INPUT_NULL_STATE (1u << 6) +#define HID_INPUT_VOLATILE (1u << 7) +#define HID_INPUT_BUFFERED_BYTES (1u << 8) + +typedef struct { + unsigned int application_id; + + hid_usage_t *usages; + size_t usage_count; + long logical_maximum; + long logical_minimum; + uint16_t flags; + + size_t bit_offset; + size_t bit_size; + size_t count; +} hid_input_t; + +typedef struct { + unsigned int report_id; + hid_input_t *inputs; + size_t input_count; + size_t bit_size; +} hid_report_t; + +typedef struct { + uint32_t usage; + input_device_t *input_device; + uint64_t pressed_keys[4]; + uint64_t pressed_buttons; +} hid_application_t; + +typedef struct { + union { + hid_report_t *reports; + hid_input_t *inputs; + }; + size_t report_count; + size_t input_count; + size_t input_bit_size; + + hid_application_t *applications; + size_t application_count; +} hid_parser_t; + +int hid_parser_init(hid_parser_t *parser, void *data, size_t size); +hid_report_t *hid_get_report_from_id(hid_parser_t *parser, unsigned int report_id); +void hid_parse_report(hid_parser_t *parser, hid_report_t *report, uint8_t *data, size_t size); +void hid_advertise_events(hid_parser_t *parser); + +#endif diff --git a/kernel-src/io/usb/class/hid.c b/kernel-src/io/usb/class/hid.c new file mode 100644 index 00000000..76a84248 --- /dev/null +++ b/kernel-src/io/usb/class/hid.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include + +// TODO proper cleanup + +#define USB_INTERFACE_CLASS_HID 0x03 + +#define USB_DESC_TYPE_HID 0x21 +#define USB_DESC_TYPE_HID_REPORT 0x22 +#define USB_DESC_TYPE_HID_PHYSICAL 0x23 + +typedef struct { + uint8_t bDescriptorType; + uint16_t wDescriptorLength; +} __attribute__((packed)) usb_hid_class_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + usb_hid_class_desc_t descriptors[]; +} __attribute__((packed)) usb_hid_desc_t; + + +typedef struct { + uint8_t *report_descriptor; + size_t report_descriptor_length; + hid_parser_t parser; + usb_endpoint_t *interrupt_in_endpoint; + size_t interrupt_in_packet_size; + void *data_buffer; +} hid_driver_t; + +#define HID_GD_POINTER 0x01 +#define HID_GD_MOUSE 0x02 +#define HID_GD_JOYSTICK 0x04 +#define HID_GD_GAMEPAD 0x05 +#define HID_GD_KEYBOARD 0x06 +#define HID_GD_KEYPAD 0x07 +#define HID_GD_MULTI_AXIS_CONTROLLER 0x08 + +static inline const char *get_pretty_name_gd(uint32_t usage) { + switch (USAGE(usage)) { + case HID_GD_POINTER: + return "HID Pointer"; + case HID_GD_MOUSE: + return "HID Mouse"; + case HID_GD_JOYSTICK: + return "HID Joystick"; + case HID_GD_GAMEPAD: + return "HID Gamepad"; + case HID_GD_KEYBOARD: + return "HID Keyboard"; + case HID_GD_KEYPAD: + return "HID Keypad"; + case HID_GD_MULTI_AXIS_CONTROLLER: + return "HID Multi Axis Controller"; + } + + return "HID Device (GD)"; +} + +static usb_hid_desc_t *hid_find_descriptor(usb_device_t *dev, usb_interface_t *interface) { + bool in_interface = false; + + usb_for_each_descriptor(dev->config_desc, desc) { + USB_FOR_EACH_DESCRIPTOR_CHECK(dev->config_desc, desc) { + return NULL; + } + + if (desc == (usb_desc_hdr_t *)interface->desc) { + in_interface = true; + continue; + } + + if (!in_interface) + continue; + + if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) + break; + + if (desc->bDescriptorType != USB_DESC_TYPE_HID) + continue; + + if (desc->bLength < sizeof(usb_hid_desc_t)) + return NULL; + + usb_hid_desc_t *usb_hid_desc = (usb_hid_desc_t *)desc; + size_t expected_length = sizeof(usb_hid_desc_t) + usb_hid_desc->bNumDescriptors * sizeof(usb_hid_class_desc_t); + if (desc->bLength < expected_length) + return NULL; + + return usb_hid_desc; + } + + return NULL; +} + +hid_report_t *hid_get_report_from_id(hid_parser_t *parser, unsigned int report_id) { + hid_report_t *report = NULL; + + for (size_t i = 0; i < parser->report_count; ++i) { + if (parser->reports[i].report_id == report_id) { + report = &parser->reports[i]; + break; + } + } + + return report; +} + +static void interrupt_in_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + hid_driver_t *hid_driver = ctx; + + if (status != USB_STATUS_SUCCESS && status != USB_STATUS_SHORT_PACKET) { + printf("hid: interrupt transfer failed. retrying\n"); + } else { + if (transferred == 0) + goto resend; + + uint8_t *data = hid_driver->data_buffer; + hid_report_t *report = NULL; + if (hid_driver->parser.report_count) { + report = hid_get_report_from_id(&hid_driver->parser, data[0]); + if (report == NULL) { + printf("hid: got unknown report. ignoring\n"); + goto resend; + } + + ++data; + --transferred; + } + + hid_parse_report(&hid_driver->parser, report, data, transferred); + } + +resend: + usb_xfer_t xfer = { + .ep = hid_driver->interrupt_in_endpoint, + .flags = USB_XFER_FLAG_TO_HOST, + .type = USB_XFER_TYPE_INTERRUPT, + .data = hid_driver->data_buffer, + .data_length = hid_driver->interrupt_in_packet_size, + .completion = interrupt_in_callback, + .completion_ctx = hid_driver + }; + + int error = usb_submit_xfer(dev, &xfer); + if (error) { + printf("hid: failed to submit HID interrupt xfer: %s\n", strerror(error)); + } +} + +static void get_report_descriptor_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + if (status != USB_STATUS_SUCCESS) { + printf("hid: failed to get report descriptor\n"); + return; + } + + usb_interface_t *interface = ctx; + hid_driver_t *hid_driver = interface->driver_data; + + if (transferred != hid_driver->report_descriptor_length) { + printf("hid: transferred != hid_driver->report_descriptor_length\n"); + return; + } + + 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) + hid_driver->interrupt_in_endpoint = &interface->endpoints[i]; + } + + if (hid_driver->interrupt_in_endpoint == NULL) { + printf("hid: no IN interrupt endpoint found\n"); + return; + } + + int error = hid_parser_init(&hid_driver->parser, hid_driver->report_descriptor, hid_driver->report_descriptor_length); + if (error) { + printf("hid: failed to parse report descriptor\n"); + return; + } + + printf("hid: %lu applications with %lu %s%s\n", + hid_driver->parser.application_count, + hid_driver->parser.report_count ? hid_driver->parser.report_count : hid_driver->parser.input_count, + hid_driver->parser.report_count ? "report" : "anonymous input", + (hid_driver->parser.report_count + hid_driver->parser.input_count != 1) ? "s" : "" + ); + + hid_driver->interrupt_in_packet_size = usb_endpoint_max_packet_size(hid_driver->interrupt_in_endpoint->desc); + hid_driver->data_buffer = alloc(hid_driver->interrupt_in_packet_size); + if (hid_driver->data_buffer == NULL) { + printf("hid: out of memory\n"); + return; + } + + for (size_t i = 0; i < hid_driver->parser.application_count; ++i) { + hid_application_t *application = &hid_driver->parser.applications[i]; + const char *pretty_name = "HID Device"; + if (USAGE_PAGE(application->usage) == HID_USAGE_PAGE_GENERIC_DESKTOP) + pretty_name = get_pretty_name_gd(application->usage); + + application->input_device = input_new(); + if (application->input_device == NULL) { + printf("hid: failed to create new input device\n"); + return; + } + + application->input_device->id_bus = INPUT_DEVICE_BUS_USB; + application->input_device->id_vendor = dev->desc.idVendor; + application->input_device->id_product = dev->desc.idProduct; + snprintf(application->input_device->name, sizeof(application->input_device->name), pretty_name); + application->input_device->id_version = 1; + application->input_device->ver_major = 1; + application->input_device->ver_minor = 0; + application->input_device->ver_patch = 0; + } + + hid_advertise_events(&hid_driver->parser); + + usb_xfer_t xfer = { + .ep = hid_driver->interrupt_in_endpoint, + .flags = USB_XFER_FLAG_TO_HOST, + .type = USB_XFER_TYPE_INTERRUPT, + .data = hid_driver->data_buffer, + .data_length = hid_driver->interrupt_in_packet_size, + .completion = interrupt_in_callback, + .completion_ctx = hid_driver + }; + + error = usb_submit_xfer(dev, &xfer); + if (error) { + printf("hid: failed to submit initial hid xfer: %s\n", strerror(error)); + } +} + +static int hid_attach(usb_device_t *dev, usb_interface_t *interface) { + usb_hid_desc_t *usb_hid_desc = hid_find_descriptor(dev, interface); + if (usb_hid_desc == NULL) { + printf("hid: invalid interface descriptor\n"); + return EINVAL; + } + + usb_hid_class_desc_t *usb_hid_class_desc = NULL; + for (int i = 0; i < usb_hid_desc->bNumDescriptors; ++i) { + if (usb_hid_desc->descriptors[i].bDescriptorType != USB_DESC_TYPE_HID_REPORT) + continue; + + usb_hid_class_desc = &usb_hid_desc->descriptors[i]; + break; + } + + if (usb_hid_class_desc == NULL) { + printf("hid: no report descriptor\n"); + return EINVAL; + } + + hid_driver_t *hid_driver = alloc(sizeof(hid_driver_t)); + if (hid_driver == NULL) { + printf("hid: out of memory\n"); + return ENOMEM; + } + + hid_driver->report_descriptor_length = usb_hid_class_desc->wDescriptorLength; + if (hid_driver->report_descriptor_length == 0) { + printf("hid: invalid report descriptor length\n"); + free(hid_driver); + return EINVAL; + } + + hid_driver->report_descriptor = alloc(hid_driver->report_descriptor_length); + if (hid_driver->report_descriptor == NULL) { + printf("hid: out of memory\n"); + free(hid_driver); + return ENOMEM; + } + + interface->driver_data = hid_driver; + + int error = usb_get_interface_descriptor(dev, interface->desc->bInterfaceNumber, USB_DESC_TYPE_HID_REPORT, 0, hid_driver->report_descriptor, hid_driver->report_descriptor_length, get_report_descriptor_callback, interface); + if (error) { + interface->driver_data = NULL; + free(hid_driver->report_descriptor); + free(hid_driver); + } + + return error; +} + +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; + + return USB_DRIVER_SCORE_NONE; +} + +DEFINE_USB_CLASS_DRIVER(hid_driver, + .name = "USB Human Interface Device", + .probe = hid_probe, + .attach = hid_attach + // TODO detach +); diff --git a/kernel-src/io/usb/class/hid_parser.c b/kernel-src/io/usb/class/hid_parser.c new file mode 100644 index 00000000..0880ee82 --- /dev/null +++ b/kernel-src/io/usb/class/hid_parser.c @@ -0,0 +1,1011 @@ +#include +#include +#include +#include + +#define HID_ITEM_LONG_PREFIX 0xfe + +#define HID_ITEM_TYPE_MAIN 0x00 +#define HID_ITEM_TYPE_GLOBAL 0x01 +#define HID_ITEM_TYPE_LOCAL 0x02 +#define HID_ITEM_TYPE_RESERVED 0x03 + +#define HID_MAIN_ITEM_INPUT 0x08 +#define HID_MAIN_ITEM_OUTPUT 0x09 +#define HID_MAIN_ITEM_COLLECTION 0x0a +#define HID_MAIN_ITEM_FEATURE 0x0b +#define HID_MAIN_ITEM_END_COLLECTION 0x0c + +#define HID_GLOBAL_ITEM_USAGE_PAGE 0x00 +#define HID_GLOBAL_ITEM_LOGICAL_MIN 0x01 +#define HID_GLOBAL_ITEM_LOGICAL_MAX 0x02 +#define HID_GLOBAL_ITEM_PHYSICAL_MIN 0x03 +#define HID_GLOBAL_ITEM_PHYSICAL_MAX 0x04 +#define HID_GLOBAL_ITEM_UNIT_EXPONENT 0x05 +#define HID_GLOBAL_ITEM_UNIT 0x06 +#define HID_GLOBAL_ITEM_REPORT_SIZE 0x07 +#define HID_GLOBAL_ITEM_REPORT_ID 0x08 +#define HID_GLOBAL_ITEM_REPORT_COUNT 0x09 +#define HID_GLOBAL_ITEM_PUSH 0x0a +#define HID_GLOBAL_ITEM_POP 0x0b + +#define HID_LOCAL_ITEM_USAGE 0x00 +#define HID_LOCAL_ITEM_USAGE_MIN 0x01 +#define HID_LOCAL_ITEM_USAGE_MAX 0x02 +#define HID_LOCAL_ITEM_DESIGNATOR_INDEX 0x03 +#define HID_LOCAL_ITEM_DESIGNATOR_MIN 0x04 +#define HID_LOCAL_ITEM_DESIGNATOR_MAX 0x05 +#define HID_LOCAL_ITEM_STRING_INDEX 0x07 +#define HID_LOCAL_ITEM_STRING_MIN 0x08 +#define HID_LOCAL_ITEM_STRING_MAX 0x09 +#define HID_LOCAL_ITEM_DELIMITER 0x0a + +#define HID_COLLECTION_PHYSICAL 0x00 +#define HID_COLLECTION_APPLICATION 0x01 +#define HID_COLLECTION_LOGICAL 0x02 +#define HID_COLLECTION_REPORT 0x03 +#define HID_COLLECTION_NAMED_ARRAY 0x04 +#define HID_COLLECTION_USAGE_SWITCH 0x05 +#define HID_COLLECTION_USAGE_MODIFIER 0x06 + +#define HID_GD_X 0x30 +#define HID_GD_Y 0x31 +#define HID_GD_Z 0x32 +#define HID_GD_RX 0x33 +#define HID_GD_RY 0x34 +#define HID_GD_RZ 0x35 +#define HID_GD_SLIDER 0x36 +#define HID_GD_DIAL 0x37 +#define HID_GD_WHEEL 0x38 +#define HID_GD_HAT_SWITCH 0x39 + +#define HID_BUTTON_PRIMARY 0x01 +#define HID_BUTTON_SECONDARY 0x02 +#define HID_BUTTON_TERTIARY 0x03 +#define HID_BUTTON_SIDE 0x04 +#define HID_BUTTON_EXTRA 0x05 +#define HID_BUTTON_MAX HID_BUTTON_EXTRA + +#define HID_KEYBOARD_KEYPAD_MAX 0xe7 + +static inline void decode_item(uint8_t item, uint8_t *tag, uint8_t *type, uint8_t *size) { + *size = item & 0x3; + *type = (item >> 2) & 0x3; + *tag = (item >> 4) & 0xf; +} + +static inline uint32_t get_data_u(uint8_t *data, size_t size) { + switch (size) { + case 0: + return 0; + case 1: + return data[0]; + case 2: + return (uint32_t)data[0] | + ((uint32_t)data[1] << 8); + case 4: + return (uint32_t)data[0] | + ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | + ((uint32_t)data[3] << 24); + default: + __assert(!"Corruption in HID driver"); + } +} + +static inline int32_t get_data_i(uint8_t *data, size_t size) { + switch (size) { + case 0: + return 0; + case 1: + return (int32_t)(int8_t)data[0]; + case 2: + return (int32_t)(int16_t)((uint16_t)data[0] | + ((uint16_t)data[1] << 8)); + case 4: + return (int32_t)((uint32_t)data[0] | + ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | + ((uint32_t)data[3] << 24)); + default: + __assert(!"Corruption in HID driver"); + } +} + +static inline size_t translate_size(uint8_t size) { + switch (size) { + case 0: + case 1: + case 2: + return size; + case 3: + return 4; + default: + __assert(!"Corruption in HID driver"); + } +} + +typedef struct { + uint32_t usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + uint32_t unit; + uint32_t report_size; + uint32_t report_id; + uint32_t report_count; +} hid_global_state_t; + +static int handle_global(hid_global_state_t *global_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + int32_t idata = get_data_i(item + 1, actual_size); + + switch (tag) { + case HID_GLOBAL_ITEM_USAGE_PAGE: + global_state->usage_page = udata; + break; + case HID_GLOBAL_ITEM_LOGICAL_MIN: + global_state->logical_minimum = idata; + break; + case HID_GLOBAL_ITEM_LOGICAL_MAX: + global_state->logical_maximum = idata; + break; + case HID_GLOBAL_ITEM_PHYSICAL_MIN: + global_state->physical_minimum = idata; + break; + case HID_GLOBAL_ITEM_PHYSICAL_MAX: + global_state->physical_maximum = idata; + break; + case HID_GLOBAL_ITEM_UNIT_EXPONENT: + global_state->unit_exponent = idata; + break; + case HID_GLOBAL_ITEM_UNIT: + global_state->unit = udata; + break; + case HID_GLOBAL_ITEM_REPORT_SIZE: + global_state->report_size = udata; + break; + case HID_GLOBAL_ITEM_REPORT_ID: + if (udata == 0 || udata > 0xff) + return EINVAL; + + global_state->report_id = udata; + break; + case HID_GLOBAL_ITEM_REPORT_COUNT: + global_state->report_count = udata; + break; + default: + return EINVAL; + } + + return 0; +} +#define HID_LOCAL_USAGE_CAPACITY 128 + +typedef struct { + hid_usage_t usages[HID_LOCAL_USAGE_CAPACITY]; + size_t usage_count; + + bool has_usage_minimum; + uint32_t usage_minimum; + + bool has_designator_index; + bool has_designator_minimum; + bool has_designator_maximum; + uint32_t designator_index; + uint32_t designator_minimum; + uint32_t designator_maximum; + + bool has_string_index; + bool has_string_minimum; + bool has_string_maximum; + uint32_t string_index; + uint32_t string_minimum; + uint32_t string_maximum; +} hid_local_state_t; + +static inline hid_usage_t hid_make_usage(uint32_t current_page, uint32_t raw_usage, size_t actual_size) { + hid_usage_t usage = { + .usage = actual_size <= 2 ? ((current_page << 16) | raw_usage) : raw_usage, + + }; + + return usage; +} + +static inline hid_usage_t hid_make_usage_range(hid_global_state_t *global_state, uint32_t min, uint32_t max) { + hid_usage_t usage = { + .minimum = min, + .maximum = max, + .usage = global_state->usage_page << 16 + }; + + return usage; +} + +static int hid_local_add_usage(hid_local_state_t *local_state, hid_usage_t usage) { + if (local_state->usage_count >= HID_LOCAL_USAGE_CAPACITY) + return E2BIG; + + local_state->usages[local_state->usage_count++] = usage; + + return 0; +} + +static int handle_local(hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + + switch (tag) { + case HID_LOCAL_ITEM_USAGE: { + hid_usage_t usage = hid_make_usage(global_state->usage_page, udata, actual_size); + return hid_local_add_usage(local_state, usage); + } + case HID_LOCAL_ITEM_USAGE_MIN: + local_state->has_usage_minimum = true; + local_state->usage_minimum = udata; + break; + case HID_LOCAL_ITEM_USAGE_MAX: + if (local_state->has_usage_minimum == false) + return EINVAL; + + hid_usage_t usage = hid_make_usage_range(global_state, local_state->usage_minimum, udata); + local_state->has_usage_minimum = false; + return hid_local_add_usage(local_state, usage); + case HID_LOCAL_ITEM_DESIGNATOR_INDEX: + local_state->has_designator_index = true; + local_state->designator_index = udata; + break; + case HID_LOCAL_ITEM_DESIGNATOR_MIN: + local_state->has_designator_minimum = true; + local_state->designator_minimum = udata; + break; + case HID_LOCAL_ITEM_DESIGNATOR_MAX: + local_state->has_designator_maximum = true; + local_state->designator_maximum = udata; + break; + case HID_LOCAL_ITEM_STRING_INDEX: + local_state->has_string_index = true; + local_state->string_index = udata; + break; + case HID_LOCAL_ITEM_STRING_MIN: + local_state->has_string_minimum = true; + local_state->string_minimum = udata; + break; + case HID_LOCAL_ITEM_STRING_MAX: + local_state->has_string_maximum = true; + local_state->string_maximum = udata; + break; + case HID_LOCAL_ITEM_DELIMITER: + return ENOTSUP; + } + + return 0; +} + +typedef struct { + unsigned int current_depth; +} hid_collection_state_t; + +static int handle_collection(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t type) { + // only accept root application collections + if (collection_state->current_depth == 0 && type != HID_COLLECTION_APPLICATION) + return EINVAL; + + // currently ignore all non-root collections + if (collection_state->current_depth++ != 0) + return 0; + + // check if we have valid usage state before proceeding + if (local_state->usage_count != 1) + return EINVAL; + + // add a new application to the parser + void *p; + if (parser->applications == NULL) + p = alloc(sizeof(hid_application_t)); + else + p = realloc(parser->applications, (parser->application_count + 1) * sizeof(hid_application_t)); + + if (p == NULL) + return ENOMEM; + + parser->applications = p; + parser->applications[parser->application_count].usage = local_state->usages[0].usage; + ++parser->application_count; + return 0; +} + +static int handle_collection_end(hid_collection_state_t *collection_state) { + if (collection_state->current_depth == 0) + return EINVAL; + + --collection_state->current_depth; + + return 0; +} + +static int insert_report(hid_parser_t *parser, unsigned int id, hid_report_t **report) { + void *p = parser->reports ? realloc(parser->reports, sizeof(hid_report_t) * (parser->report_count + 1)) : alloc(sizeof(hid_report_t)); + if (p == NULL) + return ENOMEM; + + parser->reports = p; + *report = &parser->reports[parser->report_count++]; + + (*report)->report_id = id; + return 0; +} + +static int insert_input(hid_parser_t *parser, hid_input_t **inputp, size_t *input_count, size_t *report_bit_size, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) { + size_t field_bit_size = global_state->report_size * global_state->report_count; + if (global_state->report_count && field_bit_size / global_state->report_count != global_state->report_size) + return EOVERFLOW; + if (*report_bit_size + field_bit_size < *report_bit_size) + return EOVERFLOW; + + void *p = *inputp ? realloc(*inputp, sizeof(hid_input_t) * (*input_count + 1)) : alloc(sizeof(hid_input_t)); + if (p == NULL) + return ENOMEM; + + *inputp = p; + hid_input_t *input = &(*inputp)[(*input_count)++]; + input->application_id = parser->application_count - 1; + input->bit_offset = *report_bit_size; + if (local_state->usage_count) { + input->usages = alloc(local_state->usage_count * sizeof(hid_usage_t)); + if (input->usages == NULL) { + // TODO proper cleanup here + return ENOMEM; + } + memcpy(input->usages, local_state->usages, local_state->usage_count * sizeof(hid_usage_t)); + input->usage_count = local_state->usage_count; + } + input->logical_maximum = global_state->logical_maximum; + input->logical_minimum = global_state->logical_minimum; + input->bit_size = global_state->report_size; + input->count = global_state->report_count; + input->flags = flags; + *report_bit_size += field_bit_size; + + return 0; +} + +static int handle_input(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) { + if (collection_state->current_depth == 0) + return EINVAL; + + if (global_state->report_id == 0) { + // add it to the input list without being gated behind a report id + if (parser->report_count) + return EINVAL; + + return insert_input(parser, &parser->inputs, &parser->input_count, &parser->input_bit_size, collection_state, global_state, local_state, flags); + } + + if (parser->input_count) + return EINVAL; + + hid_report_t *report = hid_get_report_from_id(parser, global_state->report_id); + if (report == NULL) { + // we are guaranteed to have a valid report_id here + int error = insert_report(parser, global_state->report_id, &report); + if (error) + return error; + } + + return insert_input(parser, &report->inputs, &report->input_count, &report->bit_size, collection_state, global_state, local_state, flags); +} + +static int handle_main(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + + int error = 0; + switch (tag) { + case HID_MAIN_ITEM_INPUT: + error = handle_input(parser, collection_state, global_state, local_state, udata); + break; + case HID_MAIN_ITEM_COLLECTION: + error = handle_collection(parser, collection_state, global_state, local_state, udata); + break; + case HID_MAIN_ITEM_END_COLLECTION: + error = handle_collection_end(collection_state); + break; + } + + memset(local_state, 0, sizeof(hid_local_state_t)); + return error; +} + +int hid_parser_init(hid_parser_t *parser, void *data, size_t data_size) { + uint8_t *item = data; + size_t current_offset = 0; + uint8_t tag, type, size; + + hid_global_state_t globals = {0}; + hid_local_state_t locals = {0}; + hid_collection_state_t collection_state = {0}; + + // TODO support PUSH and POP operations + while (current_offset < data_size) { + // TODO: if item 0xfe, it is a long item and will be ignored for now. + decode_item(item[current_offset], &tag, &type, &size); + size_t actual_size = translate_size(size); + if (current_offset + 1 + actual_size > data_size) { + printf("hid: short item exceeds report descriptor length\n"); + return EINVAL; + } + + int error = 0; + switch (type) { + case HID_ITEM_TYPE_GLOBAL: + error = handle_global(&globals, tag, &item[current_offset], actual_size); + break; + case HID_ITEM_TYPE_LOCAL: + error = handle_local(&globals, &locals, tag, &item[current_offset], actual_size); + break; + case HID_ITEM_TYPE_MAIN: + error = handle_main(parser, &collection_state, &globals, &locals, tag, &item[current_offset], actual_size); + break; + } + + if (error) { + // TODO eventual cleanup + printf("hid: parsing returned an error: %s\n", strerror(error)); + return error; + } + + current_offset += 1 + actual_size; + } + + if (collection_state.current_depth != 0) { + printf("hid: unclosed collection in report descriptor\n"); + return EINVAL; + } + + return 0; +} + +#define EVENT_EMISSION_LIMIT 32 + +static const uint16_t hid_keyboard_keypad_to_input_key_table[HID_KEYBOARD_KEYPAD_MAX + 1] = { + [0x04] = INPUT_KEY_A, + [0x05] = INPUT_KEY_B, + [0x06] = INPUT_KEY_C, + [0x07] = INPUT_KEY_D, + [0x08] = INPUT_KEY_E, + [0x09] = INPUT_KEY_F, + [0x0a] = INPUT_KEY_G, + [0x0b] = INPUT_KEY_H, + [0x0c] = INPUT_KEY_I, + [0x0d] = INPUT_KEY_J, + [0x0e] = INPUT_KEY_K, + [0x0f] = INPUT_KEY_L, + [0x10] = INPUT_KEY_M, + [0x11] = INPUT_KEY_N, + [0x12] = INPUT_KEY_O, + [0x13] = INPUT_KEY_P, + [0x14] = INPUT_KEY_Q, + [0x15] = INPUT_KEY_R, + [0x16] = INPUT_KEY_S, + [0x17] = INPUT_KEY_T, + [0x18] = INPUT_KEY_U, + [0x19] = INPUT_KEY_V, + [0x1a] = INPUT_KEY_W, + [0x1b] = INPUT_KEY_X, + [0x1c] = INPUT_KEY_Y, + [0x1d] = INPUT_KEY_Z, + [0x1e] = INPUT_KEY_1, + [0x1f] = INPUT_KEY_2, + [0x20] = INPUT_KEY_3, + [0x21] = INPUT_KEY_4, + [0x22] = INPUT_KEY_5, + [0x23] = INPUT_KEY_6, + [0x24] = INPUT_KEY_7, + [0x25] = INPUT_KEY_8, + [0x26] = INPUT_KEY_9, + [0x27] = INPUT_KEY_0, + [0x28] = INPUT_KEY_ENTER, + [0x29] = INPUT_KEY_ESC, + [0x2a] = INPUT_KEY_BACKSPACE, + [0x2b] = INPUT_KEY_TAB, + [0x2c] = INPUT_KEY_SPACE, + [0x2d] = INPUT_KEY_MINUS, + [0x2e] = INPUT_KEY_EQUAL, + [0x2f] = INPUT_KEY_LEFTBRACE, + [0x30] = INPUT_KEY_RIGHTBRACE, + [0x31] = INPUT_KEY_BACKSLASH, + [0x33] = INPUT_KEY_SEMICOLON, + [0x34] = INPUT_KEY_APOSTROPHE, + [0x35] = INPUT_KEY_GRAVE, + [0x36] = INPUT_KEY_COMMA, + [0x37] = INPUT_KEY_DOT, + [0x38] = INPUT_KEY_SLASH, + [0x39] = INPUT_KEY_CAPSLOCK, + [0x3a] = INPUT_KEY_F1, + [0x3b] = INPUT_KEY_F2, + [0x3c] = INPUT_KEY_F3, + [0x3d] = INPUT_KEY_F4, + [0x3e] = INPUT_KEY_F5, + [0x3f] = INPUT_KEY_F6, + [0x40] = INPUT_KEY_F7, + [0x41] = INPUT_KEY_F8, + [0x42] = INPUT_KEY_F9, + [0x43] = INPUT_KEY_F10, + [0x44] = INPUT_KEY_F11, + [0x45] = INPUT_KEY_F12, + [0x47] = INPUT_KEY_SCROLLLOCK, + [0x49] = INPUT_KEY_INSERT, + [0x4a] = INPUT_KEY_HOME, + [0x4b] = INPUT_KEY_PAGEUP, + [0x4c] = INPUT_KEY_DELETE, + [0x4d] = INPUT_KEY_END, + [0x4e] = INPUT_KEY_PAGEDOWN, + [0x4f] = INPUT_KEY_RIGHT, + [0x50] = INPUT_KEY_LEFT, + [0x51] = INPUT_KEY_DOWN, + [0x52] = INPUT_KEY_UP, + [0x53] = INPUT_KEY_NUMLOCK, + [0x54] = INPUT_KEY_KPSLASH, + [0x55] = INPUT_KEY_KPASTERISK, + [0x56] = INPUT_KEY_KPMINUS, + [0x57] = INPUT_KEY_KPPLUS, + [0x58] = INPUT_KEY_KPENTER, + [0x59] = INPUT_KEY_KP1, + [0x5a] = INPUT_KEY_KP2, + [0x5b] = INPUT_KEY_KP3, + [0x5c] = INPUT_KEY_KP4, + [0x5d] = INPUT_KEY_KP5, + [0x5e] = INPUT_KEY_KP6, + [0x5f] = INPUT_KEY_KP7, + [0x60] = INPUT_KEY_KP8, + [0x61] = INPUT_KEY_KP9, + [0x62] = INPUT_KEY_KP0, + [0x63] = INPUT_KEY_KPDOT, + [0x68] = INPUT_KEY_F13, + [0x69] = INPUT_KEY_F14, + [0x6a] = INPUT_KEY_F15, + [0x6b] = INPUT_KEY_F16, + [0x6c] = INPUT_KEY_F17, + [0x6d] = INPUT_KEY_F18, + [0x6e] = INPUT_KEY_F19, + [0x6f] = INPUT_KEY_F20, + [0x70] = INPUT_KEY_F21, + [0x71] = INPUT_KEY_F22, + [0x72] = INPUT_KEY_F23, + [0x73] = INPUT_KEY_F24, + [0xe0] = INPUT_KEY_LEFTCTRL, + [0xe1] = INPUT_KEY_LEFTSHIFT, + [0xe2] = INPUT_KEY_LEFTALT, + [0xe3] = INPUT_KEY_LEFTMETA, + [0xe4] = INPUT_KEY_RIGHTCTRL, + [0xe5] = INPUT_KEY_RIGHTSHIFT, + [0xe6] = INPUT_KEY_RIGHTALT, + [0xe7] = INPUT_KEY_RIGHTMETA +}; + +static uint16_t hid_keyboard_keypad_to_input_key(uint32_t hid_key) { + if (hid_key > HID_KEYBOARD_KEYPAD_MAX) + return INPUT_KEY_RESERVED; + + return hid_keyboard_keypad_to_input_key_table[hid_key]; +} + +static void push_event(input_event_t event, input_event_t *events, size_t *events_emitted) { + if (*events_emitted >= EVENT_EMISSION_LIMIT) + return; + + events[*events_emitted] = event; + *events_emitted += 1; +} + +static inline void hid_key_set(uint64_t *keys, uint32_t usage) { + if (usage > HID_KEYBOARD_KEYPAD_MAX) + return; + + keys[usage / 64] |= 1ull << (usage % 64); +} + +static inline bool hid_key_is_modifier(uint32_t usage) { + return usage >= 0xe0 && usage <= 0xe7; +} + +static inline int64_t hid_input_value_signed(hid_input_t *input, uint64_t value) { + if (input->logical_minimum >= 0 || input->bit_size == 0 || input->bit_size >= 64) + return (int64_t)value; + + uint64_t sign_bit = 1ull << (input->bit_size - 1); + if (value & sign_bit) + value |= ~((1ull << input->bit_size) - 1); + + return (int64_t)value; +} + +static void hid_emit_event_keyboard_keypad(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (input->flags & HID_INPUT_VARIABLE) { + if (value == 0) + return; + + if (usage->minimum || usage->maximum) { + value = usage->minimum + idx; + if (value > usage->maximum) + return; + } else { + value = USAGE(usage->usage); + } + } + + hid_key_set(application->pressed_keys, value); +} + +static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage); + +static void hid_emit_event_generic_desktop(hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if ((input->flags & HID_INPUT_RELATIVE) == 0) + return; + + uint32_t hid_usage = USAGE(usage->usage); + if (usage->minimum || usage->maximum) { + hid_usage = usage->minimum + idx; + if (hid_usage > usage->maximum) + return; + } + + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code > INPUT_REL_MAX) + return; + + int64_t signed_value = hid_input_value_signed(input, value); + if (signed_value == 0) + return; + + input_event_t event = {0}; + event.type = INPUT_EV_REL; + event.code = code; + event.value = signed_value; + push_event(event, events, events_emitted); +} + +static inline void hid_button_set(hid_application_t *application, uint32_t button) { + if (button < HID_BUTTON_PRIMARY || button > HID_BUTTON_MAX) + return; + + application->pressed_buttons |= 1ull << (button - HID_BUTTON_PRIMARY); +} + +static void hid_emit_event_button(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (input->flags & HID_INPUT_VARIABLE) { + if (value == 0) + return; + + if (usage->minimum || usage->maximum) { + uint32_t button = usage->minimum + idx; + if (button > usage->maximum) + return; + + hid_button_set(application, button); + } else { + hid_button_set(application, USAGE(usage->usage)); + } + } else if (value) { + hid_button_set(application, value); + } +} + +static void hid_emit_event(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + switch (USAGE_PAGE(usage->usage)) { + case HID_USAGE_PAGE_GENERIC_DESKTOP: + hid_emit_event_generic_desktop(input, usage, idx, value, events, events_emitted); + break; + case HID_USAGE_PAGE_KEYBOARD_KEYPAD: + hid_emit_event_keyboard_keypad(application, input, usage, idx, value, events, events_emitted); + break; + case HID_USAGE_PAGE_BUTTON: + hid_emit_event_button(application, input, usage, idx, value); + break; + } +} + +static inline uint64_t get_bitfield_data_at_offset(uint8_t *data, size_t bit_count, size_t bit_offset) { + uint64_t value = 0; + + if (bit_count == 0) + return 0; + + __assert(bit_count <= 64); + + for (size_t i = 0; i < bit_count; i++) { + size_t src_bit = bit_offset + i; + uint8_t byte = data[src_bit / 8]; + uint8_t bit = (byte >> (src_bit % 8)) & 1; + + value |= (uint64_t)bit << i; + } + + return value; +} + +static void do_input(hid_parser_t *parser, hid_input_t *input, uint8_t *data) { + hid_application_t *application = &parser->applications[input->application_id]; + + if (input->bit_size > 64) { + printf("hid: bit sizes above 64-bit are currently a TODO.\n"); + return; + } + + input_event_t events[EVENT_EMISSION_LIMIT]; + size_t events_emitted = 0; + hid_usage_t *usage = NULL; + for (size_t i = 0; i < input->count; ++i) { + if (i < input->usage_count) + usage = &input->usages[i]; + + // no point in handling this if we are not gonna have an use for it + if (usage == NULL) + break; + + uint64_t value = get_bitfield_data_at_offset(data, input->bit_size, input->bit_offset + input->bit_size * i); + hid_emit_event(application, input, usage, i, value, events, &events_emitted); + } + + if (events_emitted) + input_queue_packet(application->input_device, events, events_emitted); +} + +typedef struct { + hid_input_t *inputs; + size_t input_count; + unsigned int next_input; +} parse_state_t; + +static bool consume_next_input(parse_state_t *parse_state, hid_input_t **current_input) { + if (parse_state->next_input >= parse_state->input_count) + return false; + + *current_input = &parse_state->inputs[parse_state->next_input]; + + ++parse_state->next_input; + return true; +} + +static void hid_handle_key_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_keys, size_t offset, bool modifiers) { + uint64_t changes = old_keys ^ application->pressed_keys[offset]; + while (changes) { + int bit = __builtin_ctzll(changes); + uint32_t value = offset * 64 + bit; + + if (hid_key_is_modifier(value) != modifiers) { + changes &= ~(1llu << bit); + continue; + } + + input_event_t event = {0}; + event.type = INPUT_EV_KEY; + event.code = hid_keyboard_keypad_to_input_key(value); + if (event.code != INPUT_KEY_RESERVED) { + event.value = (application->pressed_keys[offset] & (1ull << bit)) ? 1 : 0; + push_event(event, events, events_emitted); + } + + changes &= ~(1llu << bit); + } +} + +static uint16_t hid_button_to_input_key(uint32_t usage); + +static void hid_handle_button_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_buttons) { + uint64_t changes = old_buttons ^ application->pressed_buttons; + while (changes) { + int bit = __builtin_ctzll(changes); + uint32_t button = HID_BUTTON_PRIMARY + bit; + + input_event_t event = {0}; + event.type = INPUT_EV_KEY; + event.code = hid_button_to_input_key(button); + if (event.code != INPUT_KEY_RESERVED) { + event.value = (application->pressed_buttons & (1ull << bit)) ? 1 : 0; + push_event(event, events, events_emitted); + } + + changes &= ~(1llu << bit); + } +} + +static void do_report_parse(hid_parser_t *parser, hid_input_t *inputs, size_t input_count, uint8_t *data) { + parse_state_t parse_state = { + .inputs = inputs, + .input_count = input_count + }; + + hid_input_t *current_input; + + __assert(parser->application_count <= 64); + uint64_t applications_handled = 0; + uint64_t old_keys[4 * parser->application_count]; + uint64_t old_buttons[parser->application_count]; + while (consume_next_input(&parse_state, ¤t_input)) { + if ((applications_handled & (1llu << current_input->application_id)) == 0) { + memcpy(&old_keys[current_input->application_id * 4], parser->applications[current_input->application_id].pressed_keys, sizeof(parser->applications[current_input->application_id].pressed_keys)); + memset(parser->applications[current_input->application_id].pressed_keys, 0, sizeof(parser->applications[current_input->application_id].pressed_keys)); + old_buttons[current_input->application_id] = parser->applications[current_input->application_id].pressed_buttons; + parser->applications[current_input->application_id].pressed_buttons = 0; + applications_handled |= (1llu << current_input->application_id); + } + + do_input(parser, current_input, data); + } + + for (size_t i = 0; i < parser->application_count; ++i) { + if ((applications_handled & (1llu << i)) == 0) + continue; + + input_event_t events[EVENT_EMISSION_LIMIT]; + size_t events_emitted = 0; + + for (int j = 0; j < 4; ++j) + if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j]) + hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, true); + + for (int j = 0; j < 4; ++j) + if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j]) + hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, false); + + if (old_buttons[i] ^ parser->applications[i].pressed_buttons) + hid_handle_button_changes(&parser->applications[i], events, &events_emitted, old_buttons[i]); + + if (events_emitted) + input_queue_packet(parser->applications[i].input_device, events, events_emitted); + } +} + +void hid_parse_report(hid_parser_t *parser, hid_report_t *report, uint8_t *data, size_t size) { + if (report) { + // separated by reports + if (ROUND_UP(report->bit_size, 8) / 8 > size) + return; + + do_report_parse(parser, report->inputs, report->input_count, data); + } else { + // only anonymous inputs + if (ROUND_UP(parser->input_bit_size, 8) / 8 > size) + return; + + do_report_parse(parser, parser->inputs, parser->input_count, data); + } +} + +static void hid_advertise_keyboard_keypad(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_keyboard_keypad_to_input_key(USAGE(usage->usage)); + if (code != INPUT_KEY_RESERVED) + bitmap_set(&device->key_bits, code, 1); + } + + for (uint32_t hid_key = usage->minimum; hid_key <= usage->maximum && hid_key <= HID_KEYBOARD_KEYPAD_MAX; ++hid_key) { + uint16_t code = hid_keyboard_keypad_to_input_key(hid_key); + if (code != INPUT_KEY_RESERVED) + bitmap_set(&device->key_bits, code, 1); + } +} + +static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage) { + switch (usage) { + case HID_GD_X: + return INPUT_REL_X; + case HID_GD_Y: + return INPUT_REL_Y; + case HID_GD_WHEEL: + return INPUT_REL_WHEEL; + default: + return INPUT_REL_MAX + 1; + } +} + +static void hid_advertise_generic_desktop(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if ((input->flags & HID_INPUT_RELATIVE) == 0) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_generic_desktop_to_input_rel(USAGE(usage->usage)); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } + + for (uint32_t hid_usage = usage->minimum; hid_usage <= usage->maximum && hid_usage <= HID_GD_WHEEL; ++hid_usage) { + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } +} + +static uint16_t hid_button_to_input_key(uint32_t usage) { + switch (usage) { + case HID_BUTTON_PRIMARY: + return INPUT_KEY_BTN_LEFT; + case HID_BUTTON_SECONDARY: + return INPUT_KEY_BTN_RIGHT; + case HID_BUTTON_TERTIARY: + return INPUT_KEY_BTN_MIDDLE; + case HID_BUTTON_SIDE: + return INPUT_KEY_BTN_SIDE; + case HID_BUTTON_EXTRA: + return INPUT_KEY_BTN_EXTRA; + default: + return INPUT_KEY_RESERVED; + } +} + +static void hid_advertise_button(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_button_to_input_key(USAGE(usage->usage)); + if (code != INPUT_KEY_RESERVED) { + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + bitmap_set(&device->key_bits, code, 1); + } + } + + for (uint32_t button = usage->minimum; button <= usage->maximum && button <= HID_BUTTON_MAX; ++button) { + uint16_t code = hid_button_to_input_key(button); + if (code != INPUT_KEY_RESERVED) { + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + bitmap_set(&device->key_bits, code, 1); + } + } +} + +static void hid_advertise_input(hid_parser_t *parser, hid_input_t *input) { + hid_application_t *application = &parser->applications[input->application_id]; + input_device_t *device = application->input_device; + + for (size_t i = 0; i < input->usage_count; ++i) { + hid_usage_t *usage = &input->usages[i]; + switch (USAGE_PAGE(usage->usage)) { + case HID_USAGE_PAGE_GENERIC_DESKTOP: + hid_advertise_generic_desktop(device, input, usage); + break; + case HID_USAGE_PAGE_KEYBOARD_KEYPAD: + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + hid_advertise_keyboard_keypad(device, input, usage); + break; + case HID_USAGE_PAGE_BUTTON: + hid_advertise_button(device, input, usage); + break; + } + } +} + +void hid_advertise_events(hid_parser_t *parser) { + if (parser->report_count) { + for (size_t i = 0; i < parser->report_count; ++i) { + hid_report_t *report = &parser->reports[i]; + for (size_t j = 0; j < report->input_count; ++j) + hid_advertise_input(parser, &report->inputs[j]); + } + } else { + for (size_t i = 0; i < parser->input_count; ++i) + hid_advertise_input(parser, &parser->inputs[i]); + } +} From 885a6d8a9f1fbc97d62987729dc3d99aa3434dd9 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 May 2026 21:06:15 -0300 Subject: [PATCH 6/8] input: proper support for absolute events --- kernel-src/include/kernel/input.h | 1 + kernel-src/io/input.c | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/kernel-src/include/kernel/input.h b/kernel-src/include/kernel/input.h index 4cf69cde..4c06b822 100644 --- a/kernel-src/include/kernel/input.h +++ b/kernel-src/include/kernel/input.h @@ -212,6 +212,7 @@ struct input_device { bitmap_t key_bits; bitmap_t rel_bits; bitmap_t abs_bits; + input_absinfo_t abs_info[INPUT_ABS_CNT]; uint16_t id_bus; uint16_t id_vendor; diff --git a/kernel-src/io/input.c b/kernel-src/io/input.c index cbed55a3..46202865 100644 --- a/kernel-src/io/input.c +++ b/kernel-src/io/input.c @@ -334,14 +334,7 @@ static int input_listener_ioctl(int minor, unsigned long request, void *arg, int if (!bitmap_get(&lis->device->abs_bits, abs_code)) return EINVAL; - input_absinfo_t absinfo; - absinfo.value = -1; - absinfo.min = -1; - absinfo.max = -1; - absinfo.fuzz = 0; - absinfo.flat = 0; - absinfo.res = 0; - return USERCOPY_POSSIBLY_TO_USER(arg, &absinfo, sizeof(input_absinfo_t)); + return USERCOPY_POSSIBLY_TO_USER(arg, &lis->device->abs_info[abs_code], sizeof(input_absinfo_t)); } else { printf("input_ioctl: unhandled ioctl %lu (%u, %u, %u)\n", request, number, type, size); return ENOTTY; @@ -478,6 +471,11 @@ void input_queue_packet(input_device_t *dev, input_event_t *events, int count) { long ipl = spinlock_acquire_raise_ipl(&dev->lock, IPL_INPUT); + for (int i = 0; i < count; ++i) { + if (events[i].type == INPUT_EV_ABS && events[i].code < INPUT_ABS_CNT) + dev->abs_info[events[i].code].value = events[i].value; + } + list_for_each(&dev->listeners, node) { input_listener_t *lis = container_of(node, input_listener_t, node); input_queue_packet_internal(lis, events, count); From 62f8b8cddc3a0c8076715cc33ec7a5cfb66462c7 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 May 2026 21:06:47 -0300 Subject: [PATCH 7/8] hid: support absolute positions --- kernel-src/io/usb/class/hid_parser.c | 90 +++++++++++++++++++++------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/kernel-src/io/usb/class/hid_parser.c b/kernel-src/io/usb/class/hid_parser.c index 0880ee82..b280f117 100644 --- a/kernel-src/io/usb/class/hid_parser.c +++ b/kernel-src/io/usb/class/hid_parser.c @@ -643,14 +643,12 @@ static void hid_emit_event_keyboard_keypad(hid_application_t *application, hid_i } static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage); +static uint16_t hid_generic_desktop_to_input_abs(uint32_t usage); static void hid_emit_event_generic_desktop(hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { if (input->flags & HID_INPUT_CONSTANT) return; - if ((input->flags & HID_INPUT_RELATIVE) == 0) - return; - uint32_t hid_usage = USAGE(usage->usage); if (usage->minimum || usage->maximum) { hid_usage = usage->minimum + idx; @@ -658,18 +656,30 @@ static void hid_emit_event_generic_desktop(hid_input_t *input, hid_usage_t *usag return; } - uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); - if (code > INPUT_REL_MAX) - return; - int64_t signed_value = hid_input_value_signed(input, value); - if (signed_value == 0) - return; input_event_t event = {0}; - event.type = INPUT_EV_REL; - event.code = code; event.value = signed_value; + + if (input->flags & HID_INPUT_RELATIVE) { + if (signed_value == 0) + return; + + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code > INPUT_REL_MAX) + return; + + event.type = INPUT_EV_REL; + event.code = code; + } else { + uint16_t code = hid_generic_desktop_to_input_abs(hid_usage); + if (code > INPUT_ABS_MAX) + return; + + event.type = INPUT_EV_ABS; + event.code = code; + } + push_event(event, events, events_emitted); } @@ -914,26 +924,62 @@ static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage) { } } +static uint16_t hid_generic_desktop_to_input_abs(uint32_t usage) { + switch (usage) { + case HID_GD_X: + return INPUT_ABS_X; + case HID_GD_Y: + return INPUT_ABS_Y; + default: + return INPUT_ABS_MAX + 1; + } +} + +static int32_t hid_abs_initial_value(hid_input_t *input) { + if (input->logical_minimum > 0) + return input->logical_minimum; + if (input->logical_maximum < 0) + return input->logical_maximum; + return 0; +} + +static void hid_advertise_abs(input_device_t *device, hid_input_t *input, uint16_t code) { + if (code > INPUT_ABS_MAX) + return; + + bitmap_set(&device->ev_bits, INPUT_EV_ABS, 1); + bitmap_set(&device->abs_bits, code, 1); + device->abs_info[code].value = hid_abs_initial_value(input); + device->abs_info[code].min = input->logical_minimum; + device->abs_info[code].max = input->logical_maximum; +} + static void hid_advertise_generic_desktop(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { if (input->flags & HID_INPUT_CONSTANT) return; - if ((input->flags & HID_INPUT_RELATIVE) == 0) - return; - if (usage->minimum == 0 && usage->maximum == 0) { - uint16_t code = hid_generic_desktop_to_input_rel(USAGE(usage->usage)); - if (code <= INPUT_REL_MAX) { - bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); - bitmap_set(&device->rel_bits, code, 1); + uint32_t hid_usage = USAGE(usage->usage); + if (input->flags & HID_INPUT_RELATIVE) { + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } else { + hid_advertise_abs(device, input, hid_generic_desktop_to_input_abs(hid_usage)); } } for (uint32_t hid_usage = usage->minimum; hid_usage <= usage->maximum && hid_usage <= HID_GD_WHEEL; ++hid_usage) { - uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); - if (code <= INPUT_REL_MAX) { - bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); - bitmap_set(&device->rel_bits, code, 1); + if (input->flags & HID_INPUT_RELATIVE) { + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } else { + hid_advertise_abs(device, input, hid_generic_desktop_to_input_abs(hid_usage)); } } } From 3fda97332f5af01b26ecee8c1112dc0f0daf367d Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 May 2026 22:18:42 -0300 Subject: [PATCH 8/8] ps2: don't wait forever on machines with no controller --- kernel-src/arch/x86-64/ps2.c | 8 +++++++- kernel-src/include/x86-64/arch/ps2.h | 15 +++++++++++++++ recipes/astral | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/kernel-src/arch/x86-64/ps2.c b/kernel-src/arch/x86-64/ps2.c index 1c605279..0ebc87d7 100644 --- a/kernel-src/arch/x86-64/ps2.c +++ b/kernel-src/arch/x86-64/ps2.c @@ -132,7 +132,13 @@ bool ps2_init_device(int port, bool *mouse) { void arch_ps2_init() { // XXX actually check if the controller is there - ps2_write_command(PS2_CTLR_CMD_DISABLEP1); + bool t; + ps2_write_command_timeout(PS2_CTLR_CMD_DISABLEP1, 10, &t); + if (t) { + printf("ps2: no controller on this machine\n"); + return; + } + ps2_write_command(PS2_CTLR_CMD_DISABLEP2); // just to make sure diff --git a/kernel-src/include/x86-64/arch/ps2.h b/kernel-src/include/x86-64/arch/ps2.h index 4e19f964..ba6cce58 100644 --- a/kernel-src/include/x86-64/arch/ps2.h +++ b/kernel-src/include/x86-64/arch/ps2.h @@ -63,6 +63,21 @@ static inline void ps2_write_command(uint8_t cmd) { outb(PS2_PORT_COMMAND, cmd); } +static inline void ps2_write_command_timeout(uint8_t cmd, int ms, bool *timeout) { + timespec_t initial = timekeeper_timefromboot(); + + while (ps2_inbuffer_full()) { + timespec_t current = timekeeper_timefromboot(); + if (timespec_diffms(initial, current) >= ms) { + *timeout = true; + return; + } + } + + *timeout = false; + outb(PS2_PORT_COMMAND, cmd); +} + static inline void ps2_write_data(uint8_t cmd) { while (ps2_inbuffer_full()); outb(PS2_PORT_DATA, cmd); diff --git a/recipes/astral b/recipes/astral index ec616893..28fe005d 100644 --- a/recipes/astral +++ b/recipes/astral @@ -1,5 +1,5 @@ name=astral -revision=1 +revision=2 from_source=astral imagedeps="build-essential nasm" hostdeps="xgcc xbinutils limine"