Merge pull request #29 from Mathewnd/xhci-work

Xhci work
This commit is contained in:
Matheus
2026-05-12 22:19:17 -03:00
committed by GitHub
23 changed files with 4810 additions and 14 deletions
+10 -3
View File
@@ -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 = .;
}
+7 -1
View File
@@ -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
+1
View File
@@ -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;
+1
View File
@@ -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
+3
View File
@@ -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
+387
View File
@@ -0,0 +1,387 @@
#ifndef _USB_H
#define _USB_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <kernel/iovec.h>
#include <list.h>
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
+100
View File
@@ -0,0 +1,100 @@
#ifndef _USB_HID_H
#define _USB_HID_H
#include <stdint.h>
#include <stddef.h>
#include <kernel/input.h>
#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
+450
View File
@@ -0,0 +1,450 @@
#ifndef _XHCI_H
#define _XHCI_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <kernel/usb.h>
#include <kernel/pci.h>
#include <kernel/dpc.h>
#include <bitmap.h>
#include <mutex.h>
#include <resource_allocator.h>
#include <ringbuffer.h>
#include <spinlock.h>
#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
+7
View File
@@ -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)
+15
View File
@@ -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);
+6 -8
View File
@@ -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);
+2 -1
View File
@@ -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;
+309
View File
@@ -0,0 +1,309 @@
#include <kernel/usb.h>
#include <kernel/alloc.h>
#include <errno.h>
#include <logging.h>
#include <kernel/usb_hid.h>
// 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
);
File diff suppressed because it is too large Load Diff
+477
View File
@@ -0,0 +1,477 @@
#include <kernel/usb.h>
#include <logging.h>
#include <kernel/alloc.h>
// TODO support using other configurations (for simplicity, we will just pick the first one for now)
extern usb_class_driver_t *usb_class_drivers;
extern usb_class_driver_t *usb_class_drivers_end;
static void endpoint_configured(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred);
static bool endpoint_desc_valid(usb_device_t *dev, usb_endpoint_desc_t *desc) {
uint8_t ep_num = desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_NUM_MASK;
uint8_t ep_type = desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK;
uint16_t max_packet_size = usb_endpoint_max_packet_size(desc);
uint8_t high_speed_transactions = (desc->wMaxPacketSize >> 11) & 0x3;
if (ep_num == 0)
return false;
if (max_packet_size == 0)
return false;
if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_BULK) {
if (dev->speed == USB_SPEED_LOW)
return false;
if (dev->speed == USB_SPEED_HIGH && high_speed_transactions != 0)
return false;
} else if (ep_type == USB_ENDPOINT_ATTRIB_TYPE_INTR) {
if (desc->bInterval == 0)
return false;
if ((dev->speed == USB_SPEED_HIGH ||
dev->speed == USB_SPEED_SUPER ||
dev->speed == USB_SPEED_SUPER_PLUS) &&
desc->bInterval > 16)
return false;
if (dev->speed == USB_SPEED_HIGH && high_speed_transactions == 3)
return false;
} else {
return false;
}
return true;
}
static bool endpoint_requires_ss_companion(usb_device_t *dev, usb_endpoint_t *endpoint) {
if (dev->speed != USB_SPEED_SUPER && dev->speed != USB_SPEED_SUPER_PLUS)
return false;
return (endpoint->desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK) != USB_ENDPOINT_ATTRIB_TYPE_CONTROL;
}
static 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);
}
+60
View File
@@ -0,0 +1,60 @@
#include <kernel/alloc.h>
#include <kernel/pmm.h>
#include <kernel/usb.h>
#include <logging.h>
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);
}
+762
View File
@@ -0,0 +1,762 @@
#include <arch/cpu.h>
#include <errno.h>
#include <kernel/alloc.h>
#include <kernel/init.h>
#include <kernel/interrupt.h>
#include <kernel/pci.h>
#include <kernel/pmm.h>
#include <kernel/usb.h>
#include <kernel/vmm.h>
#include <list.h>
#include <logging.h>
#include <util.h>
#include <kernel/xhci.h>
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);
+583
View File
@@ -0,0 +1,583 @@
#include <kernel/xhci.h>
#include <kernel/alloc.h>
#include <errno.h>
#include <kernel/pmm.h>
#include <logging.h>
#include <util.h>
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
};
+153
View File
@@ -0,0 +1,153 @@
#include <kernel/xhci.h>
#include <logging.h>
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,
};
+130
View File
@@ -0,0 +1,130 @@
#include <kernel/xhci.h>
#include <kernel/alloc.h>
#include <kernel/pmm.h>
#include <errno.h>
#include <logging.h>
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;
}
+288
View File
@@ -0,0 +1,288 @@
#include <kernel/xhci.h>
#include <errno.h>
#include <kernel/alloc.h>
#include <kernel/vmm.h>
#include <kernel/pmm.h>
#include <logging.h>
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;
}
+1
View File
@@ -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;
}
+1 -1
View File
@@ -1,5 +1,5 @@
name=astral
revision=1
revision=2
from_source=astral
imagedeps="build-essential nasm"
hostdeps="xgcc xbinutils limine"