Refactor USB driver code to prepare for supporting USB hubs.

This commit is contained in:
Martin Whitaker 2021-12-29 18:17:46 +00:00
parent a4c62cae97
commit 84da9f7553
4 changed files with 192 additions and 170 deletions

View File

@ -235,9 +235,8 @@ typedef struct {
// Pointer to the host controller registers.
ohci_op_regs_t *op_regs;
// Values shared between functions during USB device initialisation.
int ep0_max_packet_size;
int config_total_length;
// Transient values used during device enumeration and configuration.
size_t data_length;
// Circular buffer for received keycodes.
uint8_t kc_buffer [WS_KC_BUFFER_SIZE];
@ -249,7 +248,10 @@ typedef struct {
// Private Functions
//------------------------------------------------------------------------------
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static size_t min(size_t a, size_t b)
{
return (a < b) ? a : b;
}
static size_t num_pages(size_t size)
{
@ -263,7 +265,9 @@ static bool reset_ohci_port(ohci_op_regs_t *regs, int port_idx)
for (int i = 0; i < 5; i++) {
write32(&regs->rh_port_status[port_idx], OHCI_PORT_CONNECT_CHG | OHCI_PORT_RESET_CHG);
write32(&regs->rh_port_status[port_idx], OHCI_SET_PORT_RESET);
if (!wait_until_set(&regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG, 500*MILLISEC)) return false;
if (!wait_until_set(&regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG, 500*MILLISEC)) {
return false;
}
}
write32(&regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG);
@ -304,7 +308,7 @@ static bool wait_for_ohci_done(workspace_t *ws, int td_expected)
return td_completed == td_expected;
}
static void build_ohci_td(ohci_td_t *td, uint32_t control, volatile void *buffer, size_t length)
static void build_ohci_td(ohci_td_t *td, uint32_t control, const volatile void *buffer, size_t length)
{
td->control = OHCI_TD_CC_NEW | control;
td->curr_buff = (uintptr_t)buffer;
@ -312,7 +316,7 @@ static void build_ohci_td(ohci_td_t *td, uint32_t control, volatile void *buffer
td->next_td = (uintptr_t)(td + 1);
}
static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, ohci_td_t *head_td, ohci_td_t *tail_td)
static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, const ohci_td_t *head_td, const ohci_td_t *tail_td)
{
// Set the skip flag before modifying the head and tail pointers, in case we are modifying an active ED.
// Use write32() to make sure the compiler doesn't reorder the writes.
@ -322,69 +326,85 @@ static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, ohci_td_t *head_td, o
write32(&ed->control, control);
}
static uint32_t ohci_ed_control(int device_idx, int endpoint_idx, int max_packet_size, bool is_low_speed)
static uint32_t ohci_ed_control(const usb_ep_info_t *ep)
{
uint32_t control = OHCI_ED_FMT_GEN | OHCI_ED_DIR_TD | max_packet_size << 16 | endpoint_idx << 7 | device_idx;
if (is_low_speed) {
control |= OHCI_ED_SPD_LOW;
} else {
control |= OHCI_ED_SPD_FULL;
}
uint32_t control = OHCI_ED_FMT_GEN
| OHCI_ED_DIR_TD
| ep->device_speed
| ep->max_packet_size << 16
| ep->endpoint_num << 7
| ep->device_addr;
return control;
}
static bool initialise_device(workspace_t *ws, int port_idx, int device_num, bool is_low_speed)
static bool send_setup_request(workspace_t *ws, const usb_ep_info_t *ep, const usb_setup_pkt_t *setup_pkt)
{
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, setup_pkt, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(ep), &ws->td[0], &ws->td[2]);
write32(&ws->op_regs->command_status, OHCI_CMD_CLF);
return wait_for_ohci_done(ws, 2);
}
static bool send_get_data_request(workspace_t *ws, const usb_ep_info_t *ep, const usb_setup_pkt_t *setup_pkt,
const volatile void *buffer, size_t length)
{
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, setup_pkt, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_INT, buffer, length);
build_ohci_td(&ws->td[2], OHCI_TD_DP_OUT | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(ep), &ws->td[0], &ws->td[3]);
write32(&ws->op_regs->command_status, OHCI_CMD_CLF);
return wait_for_ohci_done(ws, 3);
}
static bool initialise_device(workspace_t *ws, int port_idx, int device_speed, int device_addr, usb_ep_info_t *ep0)
{
usb_setup_pkt_t setup_pkt;
// Initialise the endpoint descriptor for the default control pipe (endpoint 0).
ep0->device_speed = device_speed;
ep0->device_addr = 0;
ep0->interface_num = 0;
ep0->endpoint_num = 0;
ep0->max_packet_size = 8;
ep0->interval = 0;
// The device should currently be in Default state. We first fetch the first 8 bytes of the device descriptor to
// discover the maximum packet size for the control endpoint. We then set the device address, which moves the
// device into Addressed state, and fetch the full device descriptor. We don't currently make use of any of the
// other fields of the device descriptor, but some USB devices may not work correctly if we don't fetch it.
ohci_op_regs_t *regs = ws->op_regs;
int usb_address = 0;
int fetch_length = 8;
int max_packet_size = 8;
size_t fetch_length = 8;
goto fetch_device_descriptor;
set_address:
build_setup_packet(ws->data, 0x00, 0x05, usb_address, 0, 0);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(0, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[2]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 2)) {
build_setup_packet(&setup_pkt, 0x00, 0x05, device_addr, 0, 0);
if (!send_setup_request(ws, ep0, &setup_pkt)) {
return false;
}
ep0->device_addr = device_addr;
usleep(2*MILLISEC); // USB set address recovery time.
fetch_device_descriptor:
build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_INT, ws->data, fetch_length);
build_ohci_td(&ws->td[2], OHCI_TD_DP_OUT | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(usb_address, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[3]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 3) || !valid_usb_device_descriptor(ws->data)) {
build_setup_packet(&setup_pkt, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length);
if (!send_get_data_request(ws, ep0, &setup_pkt, ws->data, fetch_length)
|| !valid_usb_device_descriptor(ws->data)) {
return false;
}
if (usb_address == 0) {
if (fetch_length == 8) {
usb_device_desc_t *device = (usb_device_desc_t *)ws->data;
max_packet_size = device->max_packet_size;
if (!valid_usb_max_packet_size(max_packet_size, is_low_speed)) {
ep0->max_packet_size = device->max_packet_size;
if (!valid_usb_max_packet_size(ep0->max_packet_size, ep0->device_speed == OHCI_ED_SPD_LOW)) {
return false;
}
if (usb_init_options & USB_EXTRA_RESET) {
if (!reset_ohci_port(regs, port_idx)) {
if (!reset_ohci_port(ws->op_regs, port_idx)) {
return false;
}
}
usb_address = device_num;
fetch_length = sizeof(usb_device_desc_t);
goto set_address;
}
ws->ep0_max_packet_size = max_packet_size;
// Fetch the first configuration descriptor and the associated interface and endpoint descriptors. Start by
// requesting just the configuration descriptor. Then read the descriptor to determine whether we need to fetch
@ -392,57 +412,41 @@ static bool initialise_device(workspace_t *ws, int port_idx, int device_num, boo
fetch_length = sizeof(usb_config_desc_t);
fetch_config_descriptor:
build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_INT, ws->data, fetch_length);
build_ohci_td(&ws->td[2], OHCI_TD_DP_OUT | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(usb_address, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[3]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 3) || !valid_usb_config_descriptor(ws->data)) {
build_setup_packet(&setup_pkt, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length);
if (!send_get_data_request(ws, ep0, &setup_pkt, ws->data, fetch_length)
|| !valid_usb_config_descriptor(ws->data)) {
return false;
}
usb_config_desc_t *config = (usb_config_desc_t *)ws->data;
int total_length = MIN(config->total_length, WS_DATA_SIZE);
size_t total_length = min(config->total_length, WS_DATA_SIZE);
if (total_length > fetch_length) {
fetch_length = total_length;
goto fetch_config_descriptor;
}
ws->config_total_length = total_length;
ws->data_length = total_length;
return true;
}
static bool configure_interface(workspace_t *ws, usb_keyboard_info_t *kbd)
static bool configure_keyboard(workspace_t *ws, const usb_ep_info_t *ep0, const usb_ep_info_t *kbd)
{
ohci_op_regs_t *regs = ws->op_regs;
usb_setup_pkt_t setup_pkt;
// Set the device configuration.
build_setup_packet(ws->data, 0x00, 0x09, 1, 0, 0);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 2)) {
build_setup_packet(&setup_pkt, 0x00, 0x09, 1, 0, 0);
if (!send_setup_request(ws, ep0, &setup_pkt)) {
return false;
}
// Set the idle duration to infinite.
build_setup_packet(ws->data, 0x21, 0x0a, 0, kbd->interface_num, 0);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 2)) {
build_setup_packet(&setup_pkt, 0x21, 0x0a, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, ep0, &setup_pkt)) {
return false;
}
// Select the boot protocol.
build_setup_packet(ws->data, 0x21, 0x0b, 0, kbd->interface_num, 0);
build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t));
build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0);
build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]);
write32(&regs->command_status, OHCI_CMD_CLF);
if (!wait_for_ohci_done(ws, 2)) {
build_setup_packet(&setup_pkt, 0x21, 0x0b, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, ep0, &setup_pkt)) {
return false;
}
@ -554,10 +558,10 @@ void *ohci_init(uintptr_t base_addr)
}
// Scan the ports, looking for keyboards.
usb_keyboard_info_t keyboard_info[MAX_KEYBOARDS];
usb_ep_info_t keyboard_info[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 0;
int device_num = 0;
int device_addr = 0;
int min_interval = OHCI_MAX_INTERVAL;
int num_ports = rh_descriptor_a & 0xf;
for (int port_idx = 0; port_idx < num_ports; port_idx++) {
@ -577,29 +581,30 @@ void *ohci_init(uintptr_t base_addr)
// Now the port has been reset, we can determine the device speed.
port_status = read32(&regs->rh_port_status[port_idx]);
bool is_low_speed = port_status & OHCI_PORT_LOW_SPEED;
uint32_t device_speed = (port_status & OHCI_PORT_LOW_SPEED) ? OHCI_ED_SPD_LOW : OHCI_ED_SPD_FULL;
// Initialise the USB device. If successful, this leaves a set of configuration descriptors in the workspace
// data buffer.
if (!initialise_device(ws, port_idx, ++device_num, is_low_speed)) {
usb_ep_info_t ep0;
if (!initialise_device(ws, port_idx, device_speed, ++device_addr, &ep0)) {
goto disable_port;
}
// Scan the descriptors to see if this device has one or more keyboard interfaces and if so, record that
// information in the keyboard info table.
usb_keyboard_info_t *new_keyboard_info = &keyboard_info[num_keyboards];
int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->config_total_length, new_keyboard_info,
usb_ep_info_t *new_keyboard_info = &keyboard_info[num_keyboards];
int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->data_length, new_keyboard_info,
MAX_KEYBOARDS - num_keyboards);
// Complete the new entries in the keyboard info table and configure the keyboard interfaces.
for (int kbd_idx = 0; kbd_idx < new_keyboards; kbd_idx++) {
usb_keyboard_info_t *kbd = &new_keyboard_info[kbd_idx];
kbd->port_speed = is_low_speed;
kbd->device_num = device_num;
usb_ep_info_t *kbd = &new_keyboard_info[kbd_idx];
kbd->device_speed = device_speed;
kbd->device_addr = device_addr;
if (kbd->interval < min_interval) {
min_interval = kbd->interval;
}
configure_interface(ws, kbd);
configure_keyboard(ws, &ep0, kbd);
print_usb_info(" Keyboard found on port %i interface %i endpoint %i",
1 + port_idx, kbd->interface_num, kbd->endpoint_num);
@ -634,7 +639,7 @@ void *ohci_init(uintptr_t base_addr)
// Initialise the interrupt ED and TD for each keyboard interface.
ohci_ed_t *last_kbd_ed = NULL;
for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) {
usb_keyboard_info_t *kbd = &keyboard_info[kbd_idx];
usb_ep_info_t *kbd = &keyboard_info[kbd_idx];
ohci_ed_t *kbd_ed = &ws->ed[1 + kbd_idx];
ohci_td_t *kbd_td = &ws->td[3 + kbd_idx];
@ -642,7 +647,7 @@ void *ohci_init(uintptr_t base_addr)
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
build_ohci_td(kbd_td, OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_DLY, kbd_rpt, sizeof(hid_kbd_rpt_t));
build_ohci_ed(kbd_ed, ohci_ed_control(kbd->device_num, kbd->endpoint_num, kbd->max_packet_size, kbd->port_speed), kbd_td+0, kbd_td+1);
build_ohci_ed(kbd_ed, ohci_ed_control(kbd), kbd_td+0, kbd_td+1);
kbd_ed->next_ed = (uintptr_t)last_kbd_ed;
last_kbd_ed = kbd_ed;

View File

@ -86,10 +86,10 @@ bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_tim
}
int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length,
usb_keyboard_info_t keyboard_info[], int keyboard_info_size)
usb_ep_info_t keyboard_info[], int keyboard_info_size)
{
int num_keyboards = 0;
usb_keyboard_info_t *kbd = NULL;
usb_ep_info_t *kbd = NULL;
const volatile uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
const volatile uint8_t *tail_ptr = desc_buffer + desc_length;
while (curr_ptr < tail_ptr) {

View File

@ -13,16 +13,16 @@
#include <usb.h>
/*
* A USB keyboard device descriptor used internally by the various HCI drivers.
* A USB endpoint descriptor used internally by the various HCI drivers.
*/
typedef struct {
int port_speed;
int device_num;
int device_speed;
int device_addr;
int interface_num;
int endpoint_num;
int max_packet_size;
int interval;
} usb_keyboard_info_t;
} usb_ep_info_t;
/*
* A set of USB device initialisation options.
@ -40,14 +40,12 @@ typedef enum {
extern usb_init_options_t usb_init_options;
/*
* Constructs a USB setup packet in buffer using the provided values/
* Constructs a USB setup packet in buffer using the provided values.
*
* Used internally by the various HCI drivers.
*/
static inline void build_setup_packet(volatile void *buffer, int type, int request, int value, int index, int length)
static inline void build_setup_packet(usb_setup_pkt_t *pkt, int type, int request, int value, int index, int length)
{
usb_setup_pkt_t *pkt = (usb_setup_pkt_t *)buffer;
pkt->type = type;
pkt->request = request;
pkt->value = value;
@ -118,7 +116,7 @@ bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_tim
* Used internally by the various HCI drivers.
*/
int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length,
usb_keyboard_info_t keyboard_info[], int keyboard_info_size);
usb_ep_info_t keyboard_info[], int keyboard_info_size);
/*
* Displays an informational message, scrolling the screen if necessary.

View File

@ -317,21 +317,25 @@ typedef struct {
xhci_rt_regs_t *rt_regs;
xhci_db_reg_t *db_regs;
// Device context information.
uint64_t *device_context_index;
size_t context_size;
// Host controller TRB ring enqueue cycle and index.
uint32_t cr_enqueue_state;
uint32_t er_dequeue_state;
// Values shared between functions during USB device initialisation.
int32_t config_total_length;
// Transient values used during device enumeration and configuration.
size_t data_length;
uintptr_t input_context_addr;
uintptr_t output_context_addr;
uintptr_t control_ep_tr_addr;
// Keyboard slot ID lookup table
int32_t kbd_slot_id [MAX_KEYBOARDS];
uint8_t kbd_slot_id [MAX_KEYBOARDS];
// Keyboard endpoint ID lookup table
int32_t kbd_ep_id [MAX_KEYBOARDS];
uint8_t kbd_ep_id [MAX_KEYBOARDS];
// Circular buffer for received keycodes.
uint8_t kc_buffer [WS_KC_BUFFER_SIZE];
@ -355,7 +359,10 @@ static int heap_segment = -1;
// Private Functions
//------------------------------------------------------------------------------
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static size_t min(size_t a, size_t b)
{
return (a < b) ? a : b;
}
static size_t num_pages(size_t size)
{
@ -395,9 +402,9 @@ static void memcpy32(void *dst, const void *src, size_t size)
}
#endif
static int default_max_packet_size(int port_speed)
static int default_max_packet_size(int device_speed)
{
switch (port_speed) {
switch (device_speed) {
case XHCI_LOW_SPEED:
return 8;
case XHCI_FULL_SPEED:
@ -409,9 +416,9 @@ static int default_max_packet_size(int port_speed)
}
}
static int xhci_ep_interval(int config_interval, int port_speed)
static int xhci_ep_interval(int config_interval, int device_speed)
{
if (port_speed < XHCI_HIGH_SPEED) {
if (device_speed < XHCI_HIGH_SPEED) {
int log2_interval = 7;
while ((1 << log2_interval) > config_interval) {
log2_interval--;
@ -448,7 +455,7 @@ static bool halt_host_controller(xhci_op_regs_t *op_regs)
return wait_until_set(&op_regs->usb_status, XHCI_USBSTS_HCH, 20*MILLISEC);
}
static int get_xhci_port_speed(xhci_op_regs_t *op_regs, int port_idx)
static int get_xhci_device_speed(xhci_op_regs_t *op_regs, int port_idx)
{
return (read32(&op_regs->port_regs[port_idx].sc) & XHCI_PORT_SC_PS) >> XHCI_PORT_SC_PS_OFFSET;
}
@ -566,15 +573,15 @@ static uint32_t wait_for_xhci_event(workspace_t *ws, uint32_t wanted_type, int m
return event_cc(event);
}
static void issue_setup_stage_trb(ep_tr_t *ep_tr, const volatile uint8_t *packet, int transfer_length)
static void issue_setup_stage_trb(ep_tr_t *ep_tr, const usb_setup_pkt_t *setup_pkt)
{
uint64_t params1 = *(const uint64_t *)packet;
uint32_t params2 = transfer_length;
uint64_t params1 = *(const uint64_t *)setup_pkt;
uint32_t params2 = sizeof(usb_setup_pkt_t);
uint32_t control = XHCI_TRB_SETUP_STAGE | XHCI_TRB_TRT_IN | XHCI_TRB_IDT;
ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2);
}
static void issue_data_stage_trb(ep_tr_t *ep_tr, const volatile uint8_t *buffer, uint32_t dir, int transfer_length)
static void issue_data_stage_trb(ep_tr_t *ep_tr, const volatile void *buffer, uint32_t dir, size_t transfer_length)
{
uint64_t params1 = (uintptr_t)buffer;
uint32_t params2 = transfer_length;
@ -588,7 +595,7 @@ static void issue_status_stage_trb(ep_tr_t *ep_tr, uint32_t dir)
ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, 0, 0);
}
static void issue_normal_trb(ep_tr_t *ep_tr, const volatile void *buffer, uint32_t dir, int transfer_length)
static void issue_normal_trb(ep_tr_t *ep_tr, const volatile void *buffer, uint32_t dir, size_t transfer_length)
{
uint64_t params1 = (uintptr_t)buffer;
uint32_t params2 = transfer_length;
@ -596,25 +603,53 @@ static void issue_normal_trb(ep_tr_t *ep_tr, const volatile void *buffer, uint32
ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2);
}
static bool send_setup_request(workspace_t *ws, int slot_id, const usb_setup_pkt_t *setup_pkt)
{
xhci_trb_t event;
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
issue_setup_stage_trb(ep_tr, setup_pkt);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN);
ring_device_doorbell(ws->db_regs, slot_id, 1);
return wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS;
}
static bool send_get_data_request(workspace_t *ws, int slot_id, const usb_setup_pkt_t *setup_pkt,
const volatile void *buffer, size_t length)
{
xhci_trb_t event;
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
issue_setup_stage_trb(ep_tr, setup_pkt);
issue_data_stage_trb(ep_tr, buffer, XHCI_TRB_DIR_IN, length);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT);
ring_device_doorbell(ws->db_regs, slot_id, 1);
return wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS;
}
static bool disable_xhci_slot(workspace_t *ws, int slot_id)
{
xhci_trb_t event;
enqueue_xhci_command(ws, XHCI_TRB_DISABLE_SLOT | slot_id << 24, 0, 0);
ring_host_controller_doorbell(ws->db_regs);
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 10*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS) {
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 10*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
return false;
}
return true;
}
static int init_device(workspace_t *ws, int port_idx, int slot_type, int context_size, uint64_t device_context_index[])
static int initialise_device(workspace_t *ws, int port_idx, int slot_type)
{
usb_setup_pkt_t setup_pkt;
xhci_trb_t event;
// Get the port speed.
int port_speed = get_xhci_port_speed(ws->op_regs, port_idx);
int device_speed = get_xhci_device_speed(ws->op_regs, port_idx);
// Allocate a device slot and set up its output context.
@ -625,21 +660,21 @@ static int init_device(workspace_t *ws, int port_idx, int slot_type, int context
}
int slot_id = event_slot_id(&event);
write64(&device_context_index[slot_id], ws->output_context_addr);
write64(&ws->device_context_index[slot_id], ws->output_context_addr);
// Prepare the input context for the ADDRESS_DEVICE command.
xhci_ctrl_context_t *ctrl_context = (xhci_ctrl_context_t *)ws->input_context_addr;
ctrl_context->add_context_flags = XHCI_CONTEXT_A0 | XHCI_CONTEXT_A1;
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + context_size);
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + ws->context_size);
slot_context->root_hub_port_num = 1 + port_idx;
slot_context->params1 = 1 << 27 | port_speed << 20;
slot_context->params1 = 1 << 27 | device_speed << 20;
xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + 2 * context_size);
xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + 2 * ws->context_size);
ep_context->params2 = XHCI_EP_CONTROL << 3 | 3 << 1; // EP Type | CErr
ep_context->max_burst_size = 0;
ep_context->max_packet_size = default_max_packet_size(port_speed);
ep_context->max_packet_size = default_max_packet_size(device_speed);
ep_context->tr_dequeue_ptr = ws->control_ep_tr_addr | 1;
// Initialise the control endpoint transfer ring.
@ -652,9 +687,9 @@ static int init_device(workspace_t *ws, int port_idx, int slot_type, int context
// compatibility with some older USB devices we need to read the first 8 bytes of the device descriptor before
// actually setting the address. We can conveniently combine both these requirements.
int fetch_length = sizeof(usb_device_desc_t);
size_t fetch_length = sizeof(usb_device_desc_t);
uint32_t command_flags = 0;
if (port_speed < XHCI_HIGH_SPEED) {
if (device_speed < XHCI_HIGH_SPEED) {
fetch_length = 8;
command_flags = XHCI_TRB_BSR;
}
@ -668,20 +703,16 @@ static int init_device(workspace_t *ws, int port_idx, int slot_type, int context
usleep(2*MILLISEC); // USB set address recovery time.
}
build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length);
issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t));
issue_data_stage_trb(ep_tr, ws->data, XHCI_TRB_DIR_IN, fetch_length);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT);
ring_device_doorbell(ws->db_regs, slot_id, 1);
if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS
|| !valid_usb_device_descriptor(ws->data)) {
build_setup_packet(&setup_pkt, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length);
if (!send_get_data_request(ws, slot_id, &setup_pkt, ws->data, fetch_length)
|| !valid_usb_device_descriptor(ws->data)) {
goto disable_slot;
}
if (command_flags == XHCI_TRB_BSR) {
if (fetch_length == 8) {
usb_device_desc_t *device = (usb_device_desc_t *)ws->data;
if (!valid_usb_max_packet_size(device->max_packet_size, port_speed == XHCI_LOW_SPEED)) {
return false;
if (!valid_usb_max_packet_size(device->max_packet_size, device_speed == XHCI_LOW_SPEED)) {
goto disable_slot;
}
if (usb_init_options & USB_EXTRA_RESET) {
reset_xhci_port(ws->op_regs, port_idx);
@ -700,34 +731,32 @@ static int init_device(workspace_t *ws, int port_idx, int slot_type, int context
fetch_length = sizeof(usb_config_desc_t);
fetch_config_descriptor:
build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length);
issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t));
issue_data_stage_trb(ep_tr, ws->data, XHCI_TRB_DIR_IN, fetch_length);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT);
ring_device_doorbell(ws->db_regs, slot_id, 1);
if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS
build_setup_packet(&setup_pkt, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length);
if (!send_get_data_request(ws, slot_id, &setup_pkt, ws->data, fetch_length)
|| !valid_usb_config_descriptor(ws->data)) {
goto disable_slot;
}
usb_config_desc_t *config = (usb_config_desc_t *)ws->data;
int total_length = MIN(config->total_length, WS_DATA_SIZE);
size_t total_length = min(config->total_length, WS_DATA_SIZE);
if (total_length > fetch_length) {
fetch_length = total_length;
goto fetch_config_descriptor;
}
ws->config_total_length = total_length;
ws->data_length = total_length;
return slot_id;
disable_slot:
if (disable_xhci_slot(ws, slot_id)) {
write64(&device_context_index[slot_id], 0);
write64(&ws->device_context_index[slot_id], 0);
}
return 0;
}
static bool configure_interface(workspace_t *ws, int slot_id, int context_size, usb_keyboard_info_t *kbd, int kbd_idx)
static bool configure_keyboard(workspace_t *ws, int slot_id, int kbd_idx, usb_ep_info_t *kbd)
{
usb_setup_pkt_t setup_pkt;
xhci_trb_t event;
// Calculate the endpoint ID. This is used both to select an endpoint context and as a doorbell target.
@ -743,10 +772,10 @@ static bool configure_interface(workspace_t *ws, int slot_id, int context_size,
xhci_ctrl_context_t *ctrl_context = (xhci_ctrl_context_t *)ws->input_context_addr;
ctrl_context->add_context_flags = XHCI_CONTEXT_A0 | 1 << ep_id;
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + context_size);
slot_context->params1 = ep_id << 27 | kbd->port_speed << 20;
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + ws->context_size);
slot_context->params1 = ep_id << 27 | kbd->device_speed << 20;
xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + (1 + ep_id) * context_size);
xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + (1 + ep_id) * ws->context_size);
ep_context->params1 = 0;
ep_context->params2 = XHCI_EP_INTERRUPT_IN << 3 | 3 << 1; // EP Type | CErr
ep_context->interval = kbd->interval;
@ -765,32 +794,21 @@ static bool configure_interface(workspace_t *ws, int slot_id, int context_size,
// Now configure the device itself.
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
// Set the device configuration.
build_setup_packet(ws->data, 0x00, 0x09, 1, 0, 0);
issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t));
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN);
ring_device_doorbell(ws->db_regs, slot_id, 1);
if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
build_setup_packet(&setup_pkt, 0x00, 0x09, 1, 0, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
// Set the idle duration to infinite.
build_setup_packet(ws->data, 0x21, 0x0a, 0, kbd->interface_num, 0);
issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t));
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN);
ring_device_doorbell(ws->db_regs, slot_id, 1);
if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
build_setup_packet(&setup_pkt, 0x21, 0x0a, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
// Select the boot protocol.
build_setup_packet(ws->data, 0x21, 0x0b, 0, kbd->interface_num, 0);
issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t));
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN);
ring_device_doorbell(ws->db_regs, slot_id, 1);
if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
build_setup_packet(&setup_pkt, 0x21, 0x0b, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
@ -904,7 +922,7 @@ void *xhci_init(uintptr_t base_addr)
xhci_rt_regs_t *rt_regs = (xhci_rt_regs_t *)(base_addr + cap_regs->rts_offset);
xhci_db_reg_t *db_regs = (xhci_db_reg_t *)(base_addr + cap_regs->db_offset);
// Ensure the controller is halted before resetting it.
// Ensure the controller is halted and then reset it.
if (!halt_host_controller(op_regs)) return NULL;
if (!reset_host_controller(op_regs)) return NULL;
@ -963,6 +981,10 @@ void *xhci_init(uintptr_t base_addr)
ws->rt_regs = rt_regs;
ws->db_regs = db_regs;
ws->device_context_index = device_context_index;
ws->context_size = cap_regs->hcc_params1 & 0x4 ? 64 : 32;
ws->cr_enqueue_state = WS_CR_SIZE; // cycle = 1, index = 0
ws->er_dequeue_state = WS_ER_SIZE; // cycle = 1, index = 0
@ -984,11 +1006,8 @@ void *xhci_init(uintptr_t base_addr)
}
usleep(100*MILLISEC); // USB maximum device attach time.
// Record the controller context size.
uint32_t context_size = cap_regs->hcc_params1 & 0x4 ? 64 : 32;
// Scan the ports, looking for keyboards.
usb_keyboard_info_t keyboard_info[MAX_KEYBOARDS];
usb_ep_info_t keyboard_info[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 0;
int num_ports = cap_regs->hcs_params1 & 0xff;
@ -1030,24 +1049,24 @@ void *xhci_init(uintptr_t base_addr)
// Initialise the device. If successful, this leaves a set of configuration descriptors in the workspace
// data buffer.
int slot_id = init_device(ws, port_idx, slot_type, context_size, device_context_index);
int slot_id = initialise_device(ws, port_idx, slot_type);
if (slot_id == 0) {
goto disable_port;
}
// Scan the descriptors to see if this device has one or more keyboard interfaces and if so, record that
// information in the keyboard info table.
usb_keyboard_info_t *new_keyboard_info = &keyboard_info[num_keyboards];
int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->config_total_length, new_keyboard_info,
usb_ep_info_t *new_keyboard_info = &keyboard_info[num_keyboards];
int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->data_length, new_keyboard_info,
MAX_KEYBOARDS - num_keyboards);
// Complete the new entries in the keyboard info table and configure the keyboard interfaces.
for (int kbd_idx = 0; kbd_idx < new_keyboards; kbd_idx++) {
usb_keyboard_info_t *kbd = &new_keyboard_info[kbd_idx];
usb_ep_info_t *kbd = &new_keyboard_info[kbd_idx];
kbd->port_speed = get_xhci_port_speed(op_regs, port_idx);
kbd->interval = xhci_ep_interval(kbd->interval, kbd->port_speed);
configure_interface(ws, slot_id, context_size, kbd, num_keyboards + kbd_idx);
kbd->device_speed = get_xhci_device_speed(op_regs, port_idx);
kbd->interval = xhci_ep_interval(kbd->interval, kbd->device_speed);
configure_keyboard(ws, slot_id, num_keyboards + kbd_idx, kbd);
print_usb_info(" Keyboard found on port %i interface %i endpoint %i",
1 + port_idx, kbd->interface_num, kbd->endpoint_num);
@ -1061,7 +1080,7 @@ void *xhci_init(uintptr_t base_addr)
// If we didn't find any keyboard interfaces, we can free the allocated resources and disable the port.
if (disable_xhci_slot(ws, slot_id)) {
write64(&device_context_index[slot_id], 0);
write64(&ws->device_context_index[slot_id], 0);
}
disable_port: