Add support for USB hubs.

This refactors the USB driver code into a more object-oriented design,
with usbkbd.c being the base class and ohci.c and xhci.c being subclasses.
This makes the code that performs USB device enumeration independent of
the host controller.
This commit is contained in:
Martin Whitaker
2022-01-08 23:00:28 +00:00
parent 84da9f7553
commit 52a87c5d40
7 changed files with 1335 additions and 759 deletions

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021 Martin Whitaker.
// Copyright (C) 2021-2022 Martin Whitaker.
#include <stdbool.h>
#include <stddef.h>
@@ -152,13 +152,12 @@
#define OHCI_MAX_INTERVAL 32
// Values specific to this implementation.
// Values specific to this driver.
#define MAX_KEYBOARDS 8 // per host controller
#define WS_ED_SIZE (1 + MAX_KEYBOARDS) // Endpoint Descriptors
#define WS_TD_SIZE (3 + MAX_KEYBOARDS) // Transfer Descriptors
#define WS_DATA_SIZE 1024 // bytes
#define WS_KC_BUFFER_SIZE 8 // keycodes
#define MILLISEC 1000 // in microseconds
@@ -221,25 +220,21 @@ typedef volatile struct {
// Data structures specific to this implementation.
typedef struct {
hcd_workspace_t base_ws;
// System memory data structures used by the host controller.
ohci_hcca_t hcca;
ohci_ed_t ed [WS_ED_SIZE];
ohci_td_t td [WS_TD_SIZE];
ohci_ed_t ed[WS_ED_SIZE];
ohci_td_t td[WS_TD_SIZE];
// Data transfer buffers.
union {
volatile uint8_t data [WS_DATA_SIZE];
hid_kbd_rpt_t kbd_rpt [MAX_KEYBOARDS];
};
// Keyboad data transfer buffers.
hid_kbd_rpt_t kbd_rpt[MAX_KEYBOARDS];
// Pointer to the host controller registers.
ohci_op_regs_t *op_regs;
// 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];
uint8_t kc_buffer[WS_KC_BUFFER_SIZE];
int kc_index_i;
int kc_index_o;
} workspace_t __attribute__ ((aligned (256)));
@@ -248,67 +243,61 @@ typedef struct {
// Private Functions
//------------------------------------------------------------------------------
static size_t min(size_t a, size_t b)
{
return (a < b) ? a : b;
}
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
static bool reset_ohci_port(ohci_op_regs_t *regs, int port_idx)
static bool reset_ohci_port(ohci_op_regs_t *op_regs, int port_idx)
{
// The OHCI reset lasts for 10ms, but the USB specification calls for 50ms (but not necessarily continuously).
// So do it 5 times.
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)) {
write32(&op_regs->rh_port_status[port_idx], OHCI_PORT_CONNECT_CHG | OHCI_PORT_RESET_CHG);
write32(&op_regs->rh_port_status[port_idx], OHCI_SET_PORT_RESET);
if (!wait_until_set(&op_regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG, 1000*MILLISEC)) {
return false;
}
}
write32(&regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG);
usleep(10*MILLISEC); // USB reset recovery time
// Check the port is now active.
uint32_t status = read32(&regs->rh_port_status[port_idx]);
if ( status & OHCI_PORT_RESETING) return false;
if (~status & OHCI_PORT_CONNECTED) return false;
if (~status & OHCI_PORT_ENABLED) return false;
write32(&op_regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG);
return true;
}
static ohci_td_t *get_ohci_done_head(workspace_t *ws)
static ohci_td_t *get_ohci_done_head(const workspace_t *ws)
{
if (!wait_until_set(&ws->op_regs->interrupt_status, OHCI_INTR_WDH, 10*MILLISEC)) {
ohci_op_regs_t *op_regs = ws->op_regs;
if (~read32(&op_regs->interrupt_status) & OHCI_INTR_WDH) {
return NULL;
}
uintptr_t done_head = ws->hcca.done_head & 0xfffffffe;
write32(&ws->op_regs->interrupt_status, OHCI_INTR_WDH);
write32(&op_regs->interrupt_status, OHCI_INTR_WDH);
return (ohci_td_t *)done_head;
}
static bool wait_for_ohci_done(workspace_t *ws, int td_expected)
static bool wait_for_ohci_done(const workspace_t *ws, int td_expected)
{
int td_completed = 0;
ohci_td_t *td = get_ohci_done_head(ws);
while (td != NULL) {
td_completed++;
if ((td->control & OHCI_TD_CC) != OHCI_TD_CC_NO_ERR) {
return false;
// Rely on the controller to timeout if the device doesn't respond.
while (true) {
ohci_td_t *td = get_ohci_done_head(ws);
while (td != NULL) {
td_completed++;
if ((td->control & OHCI_TD_CC) != OHCI_TD_CC_NO_ERR) {
return false;
}
td = (ohci_td_t *)((uintptr_t)td->next_td);
}
td = (ohci_td_t *)((uintptr_t)td->next_td);
if (td_completed == td_expected) break;
usleep(10);
}
return td_completed == td_expected;
return true;
}
static void build_ohci_td(ohci_td_t *td, uint32_t control, const volatile void *buffer, size_t length)
static void build_ohci_td(ohci_td_t *td, uint32_t control, const void *buffer, size_t length)
{
td->control = OHCI_TD_CC_NEW | control;
td->curr_buff = (uintptr_t)buffer;
@@ -326,19 +315,35 @@ static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, const ohci_td_t *head
write32(&ed->control, control);
}
static uint32_t ohci_ed_control(const usb_ep_info_t *ep)
static uint32_t ohci_ed_control(const usb_ep_t *ep)
{
uint32_t ed_speed = (ep->device_speed == USB_SPEED_LOW) ? OHCI_ED_SPD_LOW : OHCI_ED_SPD_FULL;
uint32_t control = OHCI_ED_FMT_GEN
| OHCI_ED_DIR_TD
| ep->device_speed
| ed_speed
| ep->max_packet_size << 16
| ep->endpoint_num << 7
| ep->device_addr;
| ep->device_id;
return control;
}
static bool send_setup_request(workspace_t *ws, const usb_ep_info_t *ep, const usb_setup_pkt_t *setup_pkt)
//------------------------------------------------------------------------------
// Driver Methods
//------------------------------------------------------------------------------
static bool reset_root_hub_port(const usb_hcd_t *hcd, int port_num)
{
const workspace_t *ws = (const workspace_t *)hcd->ws;
return reset_ohci_port(ws->op_regs, port_num - 1);
}
static bool setup_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt)
{
workspace_t *ws = (workspace_t *)hcd->ws;
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]);
@@ -346,9 +351,11 @@ static bool send_setup_request(workspace_t *ws, const usb_ep_info_t *ep, const u
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)
static bool get_data_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt,
const void *buffer, size_t length)
{
workspace_t *ws = (workspace_t *)hcd->ws;
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);
@@ -357,315 +364,9 @@ static bool send_get_data_request(workspace_t *ws, const usb_ep_info_t *ep, cons
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)
static uint8_t get_keycode(const usb_hcd_t *hcd)
{
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.
size_t fetch_length = 8;
goto fetch_device_descriptor;
set_address:
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(&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 (fetch_length == 8) {
usb_device_desc_t *device = (usb_device_desc_t *)ws->data;
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(ws->op_regs, port_idx)) {
return false;
}
}
fetch_length = sizeof(usb_device_desc_t);
goto set_address;
}
// 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
// more data.
fetch_length = sizeof(usb_config_desc_t);
fetch_config_descriptor:
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;
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->data_length = total_length;
return true;
}
static bool configure_keyboard(workspace_t *ws, const usb_ep_info_t *ep0, const usb_ep_info_t *kbd)
{
usb_setup_pkt_t setup_pkt;
// Set the device configuration.
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(&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(&setup_pkt, 0x21, 0x0b, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, ep0, &setup_pkt)) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void *ohci_init(uintptr_t base_addr)
{
ohci_op_regs_t *regs = (ohci_op_regs_t *)base_addr;
// Check the host controller revison.
if ((read32(&regs->revision) & 0xff) != 0x10) {
return NULL;
}
// Take ownership from the SMM if necessary.
if (read32(&regs->control) & OHCI_CTRL_IR) {
write32(&regs->interrupt_enable, OHCI_INTR_OC);
flush32(&regs->command_status, OHCI_CMD_OCR);
if (!wait_until_clr(&regs->control, OHCI_CTRL_IR, 1000*MILLISEC)) {
return NULL;
}
}
// Preserve the FM interval set by the SMM or BIOS.
// If not set, use the default value.
uint32_t fm_interval = read32(&regs->fm_interval) & 0x3fff;
if (fm_interval == 0) {
fm_interval = 0x2edf;
}
// Prepare for host controller setup (see section 5.1.1.3 of the OHCI spec.).
switch (read32(&regs->control) & OHCI_CTRL_HCFS) {
case OHCI_CTRL_HCFS_RST:
usleep(50*MILLISEC);
break;
case OHCI_CTRL_HCFS_SUS:
case OHCI_CTRL_HCFS_RES:
flush32(&regs->control, OHCI_CTRL_HCFS_SUS);
usleep(10*MILLISEC);
break;
default: // operational
break;
}
// Reset the host controller.
write32(&regs->command_status, OHCI_CMD_HCR);
if (!wait_until_clr(&regs->command_status, OHCI_CMD_HCR, 30)) {
return NULL;
}
// Check we are now in SUSPEND state.
if ((read32(&regs->control) & OHCI_CTRL_HCFS) != OHCI_CTRL_HCFS_SUS) {
return NULL;
}
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual
// memory, so allocate it in the first segment.
// TODO: check for segment overflow.
pm_map[0].end -= num_pages(sizeof(workspace_t));
uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT;
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
// Initialise the control list ED.
ws->ed[0].control = OHCI_ED_SKIP;
ws->ed[0].next_ed = 0;
// Initialise the pointer to the device registers.
ws->op_regs = regs;
// Initialise the keycode buffer.
ws->kc_index_i = 0;
ws->kc_index_o = 0;
// Initialise the host controller.
uint32_t max_packet_size = ((fm_interval - 210) * 6) / 7;
write32(&regs->fm_interval, 1 << 31 | max_packet_size << 16 | fm_interval);
write32(&regs->periodic_start, (fm_interval * 9) / 10);
write32(&regs->hcca, (uintptr_t)(&ws->hcca));
write32(&regs->ctrl_head_ed, (uintptr_t)(&ws->ed[0]));
write32(&regs->bulk_head_ed, 0);
write32(&regs->ctrl_current_ed, 0);
write32(&regs->bulk_current_ed, 0);
write32(&regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_CBSR0);
flush32(&regs->interrupt_status, ~0);
// Power up the ports.
uint32_t rh_descriptor_a = read32(&regs->rh_descriptor_a);
uint32_t rh_descriptor_b = read32(&regs->rh_descriptor_b);
if (~rh_descriptor_a & OHCI_RHDA_NPS) {
// If we have individual port power control, clear the port power control mask to allow us to power up all
// ports now.
if (rh_descriptor_a & OHCI_RHDA_PSM) {
write32(&regs->rh_descriptor_b, rh_descriptor_b & OHCI_RHDB_DR);
}
// Power up all ports.
flush32(&regs->rh_status, OHCI_RHS_LPSC);
int port_power_up_delay = (rh_descriptor_a >> 24) * 2*MILLISEC;
usleep(port_power_up_delay);
usleep(100*MILLISEC); // USB maximum device attach time.
}
// Scan the ports, looking for keyboards.
usb_ep_info_t keyboard_info[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 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++) {
if (num_keyboards >= MAX_KEYBOARDS) continue;
uint32_t port_status = read32(&regs->rh_port_status[port_idx]);
// Check the port is powered up.
if (~port_status & OHCI_PORT_POWERED) continue;
// Check if anything is connected to this port.
if (~port_status & OHCI_PORT_CONNECTED) continue;
num_devices++;
// Reset the port.
if (!reset_ohci_port(regs, port_idx)) continue;
// Now the port has been reset, we can determine the device speed.
port_status = read32(&regs->rh_port_status[port_idx]);
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.
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_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_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_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);
}
if (new_keyboards > 0) {
num_keyboards += new_keyboards;
continue;
}
// If we didn't find any keyboard interfaces, we can disable the port.
disable_port:
write32(&regs->rh_port_status[port_idx], OHCI_CLR_PORT_ENABLE);
}
print_usb_info(" Found %i device%s, %i keyboard%s",
num_devices, num_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "");
if (num_keyboards == 0) {
// Shut down the host controller and the root hub.
flush32(&regs->control, OHCI_CTRL_HCFS_RST);
// Delay to allow the controller to reset.
usleep(10);
// Deallocate the workspace for this controller.
pm_map[0].end += num_pages(sizeof(workspace_t));
return NULL;
}
// 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_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];
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), kbd_td+0, kbd_td+1);
kbd_ed->next_ed = (uintptr_t)last_kbd_ed;
last_kbd_ed = kbd_ed;
}
// Initialise the interrupt table.
for (int i = 0; i < OHCI_MAX_INTERVAL; i += min_interval) {
ws->hcca.intr_head_ed[i] = (uintptr_t)last_kbd_ed;
}
write32(&regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_PLE | OHCI_CTRL_CBSR0);
flush32(&regs->interrupt_status, ~0);
return ws;
}
uint8_t ohci_get_keycode(void *workspace)
{
workspace_t *ws = (workspace_t *)workspace;
workspace_t *ws = (workspace_t *)hcd->ws;
ohci_td_t *td = get_ohci_done_head(ws);
while (td != NULL) {
@@ -702,3 +403,222 @@ uint8_t ohci_get_keycode(void *workspace)
return 0;
}
//------------------------------------------------------------------------------
// Driver Method Table
//------------------------------------------------------------------------------
static const hcd_methods_t methods = {
.reset_root_hub_port = reset_root_hub_port,
.allocate_slot = NULL,
.release_slot = NULL,
.assign_address = assign_usb_address, // use the base implementation for this method
.configure_hub_ep = NULL,
.configure_kbd_ep = NULL,
.setup_request = setup_request,
.get_data_request = get_data_request,
.get_keycode = get_keycode
};
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
bool ohci_init(uintptr_t base_addr, usb_hcd_t *hcd)
{
ohci_op_regs_t *op_regs = (ohci_op_regs_t *)base_addr;
// Check the host controller revison.
if ((read32(&op_regs->revision) & 0xff) != 0x10) {
return false;
}
// Take ownership from the SMM if necessary.
if (read32(&op_regs->control) & OHCI_CTRL_IR) {
write32(&op_regs->interrupt_enable, OHCI_INTR_OC);
flush32(&op_regs->command_status, OHCI_CMD_OCR);
if (!wait_until_clr(&op_regs->control, OHCI_CTRL_IR, 1000*MILLISEC)) {
return false;
}
}
// Preserve the FM interval set by the SMM or BIOS.
// If not set, use the default value.
uint32_t fm_interval = read32(&op_regs->fm_interval) & 0x3fff;
if (fm_interval == 0) {
fm_interval = 0x2edf;
}
// Prepare for host controller setup (see section 5.1.1.3 of the OHCI spec.).
switch (read32(&op_regs->control) & OHCI_CTRL_HCFS) {
case OHCI_CTRL_HCFS_RST:
usleep(50*MILLISEC);
break;
case OHCI_CTRL_HCFS_SUS:
case OHCI_CTRL_HCFS_RES:
flush32(&op_regs->control, OHCI_CTRL_HCFS_SUS);
usleep(10*MILLISEC);
break;
default: // operational
break;
}
// Reset the host controller.
write32(&op_regs->command_status, OHCI_CMD_HCR);
if (!wait_until_clr(&op_regs->command_status, OHCI_CMD_HCR, 30)) {
return false;
}
// Check we are now in SUSPEND state.
if ((read32(&op_regs->control) & OHCI_CTRL_HCFS) != OHCI_CTRL_HCFS_SUS) {
return false;
}
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory,
// so allocate it in the first segment.
// TODO: check for segment overflow.
pm_map[0].end -= num_pages(sizeof(workspace_t));
uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT;
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
ws->op_regs = op_regs;
// Initialise the driver object for this controller.
hcd->methods = &methods;
hcd->ws = &ws->base_ws;
// Initialise the control list ED.
ws->ed[0].control = OHCI_ED_SKIP;
ws->ed[0].next_ed = 0;
// Initialise the host controller.
uint32_t max_packet_size = ((fm_interval - 210) * 6) / 7;
write32(&op_regs->fm_interval, 1 << 31 | max_packet_size << 16 | fm_interval);
write32(&op_regs->periodic_start, (fm_interval * 9) / 10);
write32(&op_regs->hcca, (uintptr_t)(&ws->hcca));
write32(&op_regs->ctrl_head_ed, (uintptr_t)(&ws->ed[0]));
write32(&op_regs->bulk_head_ed, 0);
write32(&op_regs->ctrl_current_ed, 0);
write32(&op_regs->bulk_current_ed, 0);
write32(&op_regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_CBSR0);
flush32(&op_regs->interrupt_status, ~0);
uint32_t rh_descriptor_a = read32(&op_regs->rh_descriptor_a);
uint32_t rh_descriptor_b = read32(&op_regs->rh_descriptor_b);
// Construct a hub descriptor for the root hub.
usb_hub_t root_hub;
root_hub.ep0 = NULL;
root_hub.level = 0;
root_hub.route = 0;
root_hub.num_ports = rh_descriptor_a & 0xf;
root_hub.power_up_delay = rh_descriptor_a >> 24;
// Power up all the ports.
if (~rh_descriptor_a & OHCI_RHDA_NPS) {
// If we have individual port power control, clear the port power control mask to allow us to power up all
// ports at once.
if (rh_descriptor_a & OHCI_RHDA_PSM) {
write32(&op_regs->rh_descriptor_b, rh_descriptor_b & OHCI_RHDB_DR);
}
// Power up all ports.
flush32(&op_regs->rh_status, OHCI_RHS_LPSC);
usleep(root_hub.power_up_delay * 2 * MILLISEC);
}
usleep(100*MILLISEC); // USB maximum device attach time.
// Scan the ports, looking for hubs and keyboards.
usb_ep_t keyboards[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 0;
for (int port_idx = 0; port_idx < root_hub.num_ports; port_idx++) {
// If we've filled the keyboard info table, abort now.
if (num_keyboards >= MAX_KEYBOARDS) break;
uint32_t port_status = read32(&op_regs->rh_port_status[port_idx]);
// Check the port is powered up.
if (~port_status & OHCI_PORT_POWERED) continue;
// Check if anything is connected to this port.
if (~port_status & OHCI_PORT_CONNECTED) continue;
// Reset the port.
if (!reset_ohci_port(op_regs, port_idx)) continue;
usleep(10*MILLISEC); // USB reset recovery time
port_status = read32(&op_regs->rh_port_status[port_idx]);
// Check the port is active.
if (~port_status & OHCI_PORT_CONNECTED) continue;
if (~port_status & OHCI_PORT_ENABLED) continue;
// Now the port has been enabled, we can determine the device speed.
usb_speed_t device_speed = (port_status & OHCI_PORT_LOW_SPEED) ? USB_SPEED_LOW : USB_SPEED_FULL;
num_devices++;
// Look for keyboards attached directly or indirectly to this port.
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, num_devices,
&num_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
continue;
}
// If we didn't find any keyboard interfaces, we can disable the port.
write32(&op_regs->rh_port_status[port_idx], OHCI_CLR_PORT_ENABLE);
}
print_usb_info(" Found %i device%s, %i keyboard%s",
num_devices, num_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "");
if (num_keyboards == 0) {
// Shut down the host controller and the root hub.
flush32(&op_regs->control, OHCI_CTRL_HCFS_RST);
// Delay to allow the controller to reset.
usleep(10);
// Deallocate the workspace for this controller.
pm_map[0].end += num_pages(sizeof(workspace_t));
return false;
}
// Initialise the interrupt ED and TD for each keyboard interface and find the minimum interval.
int min_interval = OHCI_MAX_INTERVAL;
ohci_ed_t *last_kbd_ed = NULL;
for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) {
usb_ep_t *kbd = &keyboards[kbd_idx];
ohci_ed_t *kbd_ed = &ws->ed[1 + kbd_idx];
ohci_td_t *kbd_td = &ws->td[3 + kbd_idx];
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), kbd_td+0, kbd_td+1);
kbd_ed->next_ed = (uintptr_t)last_kbd_ed;
last_kbd_ed = kbd_ed;
if (kbd->interval < min_interval) {
min_interval = kbd->interval;
}
}
// Initialise the interrupt table.
for (int i = 0; i < OHCI_MAX_INTERVAL; i += min_interval) {
ws->hcca.intr_head_ed[i] = (uintptr_t)last_kbd_ed;
}
write32(&op_regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_PLE | OHCI_CTRL_CBSR0);
flush32(&op_regs->interrupt_status, ~0);
return true;
}

View File

@@ -4,23 +4,20 @@
/*
* Provides support for USB keyboards connected via an OHCI controller.
*
* Copyright (C) 2021 Martin Whitaker.
* Copyright (C) 2021-2022 Martin Whitaker.
*/
#include <stdint.h>
#include "usbkbd.h"
/*
* Initialises the OHCI device found at base_addr, scans all the attached USB
* devices, and configures any HID USB keyboard devices it finds to generate
* periodic interrupt transfers that report key presses.
* periodic interrupt transfers that report key presses. Initialises hcd and
* returns true if the device was successfully initialised and one or more
* keyboards were found.
*/
void *ohci_init(uintptr_t base_addr);
/*
* Polls the completed periodic interrupt transfers, stores the keycodes from
* any new key press events in an internal queue, and if the keycode queue is
* not empty, pops and returns the keycode from the front of the queue.
*/
uint8_t ohci_get_keycode(void *ws);
bool ohci_init(uintptr_t base_addr, usb_hcd_t *hcd);
#endif // OHCI_H

View File

@@ -3,20 +3,86 @@
#define USB_H
/*
* Provides definitions of various values and data structures defined by the
* USB specification.
* USB specifications.
*
* Copyright (C) 2021 Martin Whitaker.
* Copyright (C) 2021-2022 Martin Whitaker.
*/
#include <stdbool.h>
#include <stdint.h>
#define USB_DESC_DEVICE 0x01
#define USB_DESC_CONFIG 0x02
#define USB_DESC_INTERFACE 0x04
#define USB_DESC_ENDPOINT 0x05
// Request types.
typedef volatile struct __attribute__((packed)) {
#define USB_REQ_TO_DEVICE 0x00
#define USB_REQ_TO_INTERFACE 0x01
#define USB_REQ_TO_ENDPOINT 0x02
#define USB_REQ_TO_HUB_PORT 0x03
#define USB_REQ_FROM_DEVICE 0x80
#define USB_REQ_FROM_INTERFACE 0x81
#define USB_REQ_FROM_ENDPOINT 0x82
#define USB_REQ_FROM_HUB_PORT 0x83
#define USB_REQ_CLASS 0x20
// Request codes.
#define USB_GET_STATUS 0
#define USB_CLR_FEATURE 1
#define USB_SET_FEATURE 3
#define USB_SET_ADDRESS 5
#define USB_GET_DESCRIPTOR 6
#define USB_SET_DESCRIPTOR 7
#define USB_GET_CONFIGURATION 8
#define USB_SET_CONFIGURATION 9
#define USB_GET_INTERFACE 10
#define USB_SET_INTERFACE 11
#define HID_GET_REPORT 1
#define HID_GET_IDLE 2
#define HID_GET_PROTOCOL 3
#define HID_SET_REPORT 9
#define HID_SET_IDLE 10
#define HID_SET_PROTOCOL 11
#define HUB_GET_STATUS 0
#define HUB_CLR_FEATURE 1
#define HUB_SET_FEATURE 3
#define HUB_GET_DESCRIPTOR 6
#define HUB_SET_DESCRIPTOR 7
// Descriptor types.
#define USB_DESC_DEVICE 1
#define USB_DESC_CONFIGURATION 2
#define USB_DESC_INTERFACE 4
#define USB_DESC_ENDPOINT 5
#define HUB_DESC_DEVICE 0x29
// Class codes.
#define USB_CLASS_HID 3
#define USB_CLASS_HUB 9
// Hub feature selectors.
#define HUB_PORT_ENABLE 1
#define HUB_PORT_RESET 4
#define HUB_PORT_POWER 8
// Hub port status.
#define HUB_PORT_CONNECTED 0x00000001
#define HUB_PORT_ENABLED 0x00000002
#define HUB_PORT_RESETTING 0x00000010
#define HUB_PORT_POWERED 0x00000100
#define HUB_PORT_LOW_SPEED 0x00000200
#define HUB_PORT_HIGH_SPEED 0x00000400
// Data structures.
typedef struct __attribute__((packed)) {
uint8_t type;
uint8_t request;
uint16_t value;
@@ -24,12 +90,12 @@ typedef volatile struct __attribute__((packed)) {
uint16_t length;
} usb_setup_pkt_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
} usb_desc_header_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t usb_minor;
@@ -48,7 +114,7 @@ typedef volatile struct __attribute__((packed)) {
uint8_t num_configs;
} usb_device_desc_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint16_t total_length;
@@ -59,7 +125,7 @@ typedef volatile struct __attribute__((packed)) {
uint8_t max_power;
} usb_config_desc_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t interface_num;
@@ -71,7 +137,7 @@ typedef volatile struct __attribute__((packed)) {
uint8_t interface_str;
} usb_interface_desc_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t address;
@@ -80,7 +146,17 @@ typedef volatile struct __attribute__((packed)) {
uint8_t interval;
} usb_endpoint_desc_t;
typedef volatile struct __attribute__((packed)) {
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t num_ports;
uint16_t characteristics;
uint8_t power_up_delay;
uint8_t controller_current;
uint8_t port_flags[];
} usb_hub_desc_t;
typedef struct __attribute__((packed)) {
uint8_t modifiers;
uint8_t reserved;
uint8_t key_code[6];

View File

@@ -1,7 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021 Martin Whitaker.
#include <stddef.h>
// Copyright (C) 2021-2022 Martin Whitaker.
#include "keyboard.h"
#include "memrw32.h"
@@ -22,7 +20,9 @@
// Constants
//------------------------------------------------------------------------------
#define MAX_USB_CONTROLLERS 8
#define MAX_USB_CONTROLLERS 8 // an arbitrary limit - must match the initialisation of usb_controllers
#define MILLISEC 1000 // in microseconds
//------------------------------------------------------------------------------
// Types
@@ -36,22 +36,40 @@ typedef enum {
MAX_HCI_TYPE = 4
} hci_type_t;
typedef struct {
hci_type_t type;
void *workspace;
} usb_controller_info_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static const char *hci_type_str[MAX_HCI_TYPE] = { "UHCI", "OHCI", "EHCI", "XHCI" };
static const char *hci_name[MAX_HCI_TYPE] = { "UHCI", "OHCI", "EHCI", "XHCI" };
static usb_controller_info_t usb_controllers[MAX_USB_CONTROLLERS];
static const hcd_methods_t methods = {
.reset_root_hub_port = NULL,
.allocate_slot = NULL,
.release_slot = NULL,
.assign_address = NULL,
.configure_hub_ep = NULL,
.configure_kbd_ep = NULL,
.setup_request = NULL,
.get_data_request = NULL,
.get_keycode = NULL
};
// All entries in this array must be initialised in order to generate the necessary relocation records.
static usb_hcd_t usb_controllers[MAX_USB_CONTROLLERS] = {
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL },
{ &methods, NULL }
};
static int num_usb_controllers = 0;
static int print_row = 0;
static int print_col = 0;
//------------------------------------------------------------------------------
// Public Variables
@@ -60,9 +78,285 @@ static int print_row = 0;
usb_init_options_t usb_init_options = USB_DEFAULT_INIT;
//------------------------------------------------------------------------------
// Shared Functions (used by controller drivers)
// Macro Functions
//------------------------------------------------------------------------------
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static usb_endpoint_desc_t *find_hub_endpoint_descriptor(const uint8_t *desc_buffer, int desc_length)
{
const uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
const uint8_t *tail_ptr = desc_buffer + desc_length;
while (curr_ptr < tail_ptr) {
const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
const uint8_t *next_ptr = curr_ptr + header->length;
// Basic checks for validity.
if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;
if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) {
usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
#if 0
print_usb_info("endpoint addr 0x%02x attr 0x%02x",
(uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes);
#endif
if ((endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) {
return endpoint;
}
}
curr_ptr = next_ptr;
}
return NULL;
}
static bool build_hub_info(const usb_hcd_t *hcd, const usb_hub_t *parent, int port_num, const usb_ep_t *ep0,
usb_hub_t *hub, usb_ep_t *ep1)
{
usb_setup_pkt_t setup_pkt;
usb_hub_desc_t hub_desc;
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE | USB_REQ_CLASS, HUB_GET_DESCRIPTOR,
HUB_DESC_DEVICE << 8, 0, sizeof(hub_desc));
if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, &hub_desc, sizeof(hub_desc))) {
return false;
}
hub->ep0 = ep0;
hub->level = parent->level + 1;
hub->route = usb_route(parent, port_num);
hub->num_ports = hub_desc.num_ports;
hub->tt_think_time = hub_desc.characteristics & 0x0060 >> 5;
hub->power_up_delay = hub_desc.power_up_delay;
usb_endpoint_desc_t *ep1_desc = find_hub_endpoint_descriptor(hcd->ws->data_buffer, hcd->ws->data_length);
if (ep1_desc == NULL) {
return false;
}
ep1->device_speed = ep0->device_speed;
ep1->device_id = ep0->device_id;
ep1->interface_num = 0;
ep1->endpoint_num = ep1_desc->address & 0xf;
ep1->max_packet_size = ep1_desc->max_packet_size;
ep1->interval = ep1_desc->interval;
return true;
}
static bool get_hub_port_status(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num, uint32_t *port_status)
{
usb_setup_pkt_t setup_pkt;
build_setup_packet(&setup_pkt, USB_REQ_FROM_HUB_PORT | USB_REQ_CLASS, HUB_GET_STATUS,
0, port_num, sizeof(uint32_t));
return hcd->methods->get_data_request(hcd, hub->ep0, &setup_pkt, port_status, sizeof(uint32_t));
}
static int get_configuration_descriptors(const usb_hcd_t *hcd, const usb_ep_t *ep0, int config_idx)
{
// Fetch the descriptors for the specified configuration. Start by requesting just the configuration descriptor.
// Then read the descriptor to determine how much more data we need to fetch.
usb_setup_pkt_t setup_pkt;
uint8_t *data_buffer = hcd->ws->data_buffer;
size_t fetch_length = sizeof(usb_config_desc_t);
get_descriptor:
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR,
USB_DESC_CONFIGURATION << 8 | config_idx, 0, fetch_length);
if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, data_buffer, fetch_length)
|| !valid_usb_config_descriptor(data_buffer)) {
return 0;
}
usb_config_desc_t *config = (usb_config_desc_t *)data_buffer;
size_t total_length = MIN(config->total_length, HCD_DATA_BUFFER_SIZE);
if (total_length > fetch_length) {
fetch_length = total_length;
goto get_descriptor;
}
hcd->ws->data_length = fetch_length;
return config->config_num;
}
static void get_keyboard_info_from_descriptors(const uint8_t *desc_buffer, int desc_length, usb_ep_t keyboards[],
int max_keyboards, int *num_keyboards)
{
usb_ep_t *kbd = NULL;
const uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
const uint8_t *tail_ptr = desc_buffer + desc_length;
while (curr_ptr < tail_ptr) {
// If we've filled the keyboard info table, abort now.
if (*num_keyboards >= max_keyboards) break;
const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
const uint8_t *next_ptr = curr_ptr + header->length;
// Basic checks for validity.
if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;
if (header->type == USB_DESC_INTERFACE && header->length == sizeof(usb_interface_desc_t)) {
const usb_interface_desc_t *ifc = (const usb_interface_desc_t *)curr_ptr;
#if 0
print_usb_info("interface %i class %i subclass %i protocol %i",
ifc->interface_num, ifc->class, ifc->subclass, ifc->protocol);
#endif
if (ifc->class == 3 && ifc->subclass == 1 && ifc->protocol == 1) {
kbd = &keyboards[*num_keyboards];
kbd->interface_num = ifc->interface_num;
} else {
kbd = NULL;
}
} else if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) {
usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
#if 0
print_usb_info("endpoint addr 0x%02x attr 0x%02x",
(uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes);
#endif
if (kbd && (endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) {
kbd->endpoint_num = endpoint->address & 0xf;
kbd->max_packet_size = endpoint->max_packet_size;
kbd->interval = endpoint->interval;
kbd = NULL;
*num_keyboards += 1;
}
}
curr_ptr = next_ptr;
}
}
static bool configure_device(const usb_hcd_t *hcd, const usb_ep_t *ep0, int config_num)
{
usb_setup_pkt_t setup_pkt;
build_setup_packet(&setup_pkt, USB_REQ_TO_DEVICE, USB_SET_CONFIGURATION, config_num, 0, 0);
return hcd->methods->setup_request(hcd, ep0, &setup_pkt);
}
static bool configure_keyboard(const usb_hcd_t *hcd, const usb_ep_t *ep0, int interface_num)
{
usb_setup_pkt_t setup_pkt;
// Set the idle duration to infinite.
build_setup_packet(&setup_pkt, USB_REQ_TO_INTERFACE | USB_REQ_CLASS, HID_SET_IDLE, 0, interface_num, 0);
if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
return false;
}
// Select the boot protocol.
build_setup_packet(&setup_pkt, USB_REQ_TO_INTERFACE | USB_REQ_CLASS, HID_SET_PROTOCOL, 0, interface_num, 0);
if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
return false;
}
return true;
}
static bool scan_hub_ports(const usb_hcd_t *hcd, const usb_hub_t *hub, int *num_devices,
usb_ep_t keyboards[], int max_keyboards, int *num_keyboards)
{
bool keyboard_found = false;
usb_setup_pkt_t setup_pkt;
// Power up all the ports.
build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_SET_FEATURE, HUB_PORT_POWER, 0, 0);
for (int port_num = 1; port_num <= hub->num_ports; port_num++) {
setup_pkt.index = port_num;
if (!hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt)) {
return false;
}
}
usleep(hub->power_up_delay * 2 * MILLISEC);
usleep(100*MILLISEC); // USB maximum device attach time.
// Scan the ports, looking for hubs and keyboards.
for (int port_num = 1; port_num <= hub->num_ports; port_num++) {
// If we've filled the keyboard info table, abort now.
if (*num_keyboards >= max_keyboards) break;
uint32_t port_status;
get_hub_port_status(hcd, hub, port_num, &port_status);
// Check the port is powered up.
if (~port_status & HUB_PORT_POWERED) continue;
// Check if anything is connected to this port.
if (~port_status & HUB_PORT_CONNECTED) continue;
if (!reset_usb_hub_port(hcd, hub, port_num)) continue;
get_hub_port_status(hcd, hub, port_num, &port_status);
// Check the port is active.
if (~port_status & HUB_PORT_CONNECTED) continue;
if (~port_status & HUB_PORT_ENABLED) continue;
// Now the port has been enabled, we can determine the device speed.
usb_speed_t device_speed;
if (port_status & HUB_PORT_LOW_SPEED) {
device_speed = USB_SPEED_LOW;
} else if (port_status & HUB_PORT_HIGH_SPEED) {
device_speed = USB_SPEED_HIGH;
} else {
device_speed = USB_SPEED_FULL;
}
*num_devices += 1;
// By default, using the incrementing count of devices as the device ID.
int device_id = *num_devices;
// Allocate a controller slot for this device (only needed for some controllers).
if (hcd->methods->allocate_slot) {
device_id = hcd->methods->allocate_slot(hcd);
if (device_id == 0) break;
}
// Look for keyboards attached directly or indirectly to this port.
if (find_attached_usb_keyboards(hcd, hub, port_num, device_speed, device_id, num_devices,
keyboards, max_keyboards, num_keyboards)) {
keyboard_found = true;
continue;
}
// If we didn't find any keyboards, we can disable the port and release the slot.
build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_CLR_FEATURE, HUB_PORT_ENABLE, port_num, 0);
(void)hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt);
if (hcd->methods->release_slot) {
(void)hcd->methods->release_slot(hcd, device_id);
}
}
return keyboard_found;
}
//------------------------------------------------------------------------------
// Shared Functions (used by all drivers)
//------------------------------------------------------------------------------
uint32_t usb_route(const usb_hub_t *hub, int port_num)
{
if (hub->level == 0) {
return port_num << 24;
}
if (hub->level > 5) {
port_num = 0;
} else if (port_num > 15) {
port_num = 15;
}
return hub->route | (port_num << (4 * (hub->level - 1)));
}
bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_time)
{
int timer = max_time >> 3;
@@ -85,54 +379,6 @@ bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_tim
return true;
}
int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length,
usb_ep_info_t keyboard_info[], int keyboard_info_size)
{
int num_keyboards = 0;
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) {
// If we've filled the keyboard info table, abort now.
if (num_keyboards >= keyboard_info_size) break;
const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
const volatile uint8_t *next_ptr = curr_ptr + header->length;
// Basic checks for validity.
if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;
if (header->type == USB_DESC_INTERFACE && header->length == sizeof(usb_interface_desc_t)) {
const usb_interface_desc_t *ifc = (const usb_interface_desc_t *)curr_ptr;
#if 0
print_usb_info("interface %i class %i subclass %i protocol %i",
ifc->interface_num, ifc->class, ifc->subclass, ifc->protocol);
#endif
if (ifc->class == 3 && ifc->subclass == 1 && ifc->protocol == 1) {
kbd = &keyboard_info[num_keyboards];
kbd->interface_num = ifc->interface_num;
} else {
kbd = NULL;
}
} else if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) {
usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
#if 0
print_usb_info("endpoint addr 0x%02x attr 0x%02x",
(uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes);
#endif
if (kbd && (endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) {
kbd->endpoint_num = endpoint->address & 0xf;
kbd->max_packet_size = endpoint->max_packet_size;
kbd->interval = endpoint->interval;
num_keyboards++;
kbd = NULL;
}
}
curr_ptr = next_ptr;
}
return num_keyboards;
}
void print_usb_info(const char *fmt, ...)
{
if (print_row == SCREEN_HEIGHT) {
@@ -143,10 +389,188 @@ void print_usb_info(const char *fmt, ...)
va_list args;
va_start(args, fmt);
(void)vprintf(print_row++, 0, fmt, args);
(void)vprintf(print_row++, print_col, fmt, args);
va_end(args);
}
bool reset_usb_hub_port(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num)
{
usb_setup_pkt_t setup_pkt;
if (hub->level > 0) {
build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_SET_FEATURE,
HUB_PORT_RESET, port_num, 0);
if (!hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt)) {
return false;
}
int timer = 200;
uint32_t port_status;
do {
usleep(1000);
if (--timer == 0) return false;
if (!get_hub_port_status(hcd, hub, port_num, &port_status)) {
return false;
}
} while (port_status & HUB_PORT_RESETTING);
} else {
if (!hcd->methods->reset_root_hub_port(hcd, port_num)) {
return false;
}
}
usleep(10*MILLISEC); // USB reset recovery time
return true;
}
bool assign_usb_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
usb_speed_t device_speed, int device_id, usb_ep_t *ep0)
{
usb_setup_pkt_t setup_pkt;
uint8_t *data_buffer = hcd->ws->data_buffer;
// Initialise the control endpoint descriptor.
ep0->device_speed = device_speed;
ep0->device_id = 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 Address state, and fetch the full device descriptor.
size_t fetch_length = 8;
goto fetch_descriptor;
set_address:
build_setup_packet(&setup_pkt, USB_REQ_TO_DEVICE, USB_SET_ADDRESS, device_id, 0, 0);
if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
return false;
}
ep0->device_id = device_id;
usleep(2*MILLISEC + 1*MILLISEC); // USB set address recovery time (plus a bit).
fetch_descriptor:
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR, USB_DESC_DEVICE << 8, 0, fetch_length);
if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, data_buffer, fetch_length)
|| !valid_usb_device_descriptor(data_buffer)) {
return false;
}
#if 0
print_usb_info("%02x %02x %02x %02x %02x %02x %02x %02x",
(uintptr_t)data_buffer[0],
(uintptr_t)data_buffer[1],
(uintptr_t)data_buffer[2],
(uintptr_t)data_buffer[3],
(uintptr_t)data_buffer[4],
(uintptr_t)data_buffer[5],
(uintptr_t)data_buffer[6],
(uintptr_t)data_buffer[7]);
#endif
if (fetch_length == 8) {
usb_device_desc_t *device = (usb_device_desc_t *)data_buffer;
ep0->max_packet_size = device->max_packet_size;
if (!valid_usb_max_packet_size(device->max_packet_size, device_speed)) {
return false;
}
if (usb_init_options & USB_EXTRA_RESET) {
if (!reset_usb_hub_port(hcd, hub, port_num)) {
return false;
}
}
fetch_length = sizeof(usb_device_desc_t);
goto set_address;
}
hcd->ws->data_length = fetch_length;
return true;
}
bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
usb_speed_t device_speed, int device_id, int *num_devices,
usb_ep_t keyboards[], int max_keyboards, int *num_keyboards)
{
bool keyboard_found = false;
// Set the USB device address. If successful, this also fills in the descriptor for the default control endpoint
// (ep0) and leaves the device descriptor in the data transfer buffer.
usb_ep_t ep0;
if (!hcd->methods->assign_address(hcd, hub, port_num, device_speed, device_id, &ep0)) {
return false;
}
usb_device_desc_t *device = (usb_device_desc_t *)hcd->ws->data_buffer;
bool is_hub = (device->class == USB_CLASS_HUB);
// Fetch the descriptors for the first configuration into the data transfer buffer. In theory a keyboard device
// may have more than one configuration and may only support the boot protocol in another configuration, but
// this seems unlikely in practice. A hub should only ever have one configuration.
int config_num = get_configuration_descriptors(hcd, &ep0, 0);
if (config_num == 0) {
return false;
}
if (is_hub) {
usb_hub_t new_hub;
usb_ep_t ep1;
if (!build_hub_info(hcd, hub, port_num, &ep0, &new_hub, &ep1)) {
return false;
}
if (!configure_device(hcd, &ep0, config_num)) {
return false;
}
if (hcd->methods->configure_hub_ep) {
if (!hcd->methods->configure_hub_ep(hcd, &ep1, &new_hub)) {
return false;
}
}
print_usb_info(" %i port hub found on port %i", new_hub.num_ports, port_num);
print_col += 1;
keyboard_found = scan_hub_ports(hcd, &new_hub, num_devices, keyboards, max_keyboards, num_keyboards);
print_col -= 1;
} else {
// Scan the configuration to see if this device has one or more interfaces that implement the keyboard
// boot protocol and if so, record that information in the keyboard info table and configure the device.
int old_num_keyboards = *num_keyboards;
int new_num_keyboards = *num_keyboards;
get_keyboard_info_from_descriptors(hcd->ws->data_buffer, hcd->ws->data_length,
keyboards, max_keyboards, &new_num_keyboards);
if (new_num_keyboards == old_num_keyboards) {
return false;
}
if (!configure_device(hcd, &ep0, config_num)) {
return false;
}
// Complete the new entries in the keyboard info table and configure the keyboard interfaces.
for (int kbd_idx = old_num_keyboards; kbd_idx < new_num_keyboards; kbd_idx++) {
usb_ep_t *kbd = &keyboards[kbd_idx];
kbd->device_speed = device_speed;
kbd->device_id = device_id;
if (hcd->methods->configure_kbd_ep) {
if (!hcd->methods->configure_kbd_ep(hcd, kbd, kbd_idx)) {
return false;
}
}
if (!configure_keyboard(hcd, &ep0, kbd->interface_num)) break;
print_usb_info(" Keyboard found on port %i interface %i endpoint %i",
port_num, kbd->interface_num, kbd->endpoint_num);
keyboard_found = true;
*num_keyboards += 1;
}
}
return keyboard_found;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
@@ -197,12 +621,12 @@ void find_usb_keyboards(bool pause_at_end)
mmio_size &= ~(uintptr_t)0xf;
mmio_size = ~mmio_size + 1;
print_usb_info("Found %s controller %04x:%04x at %08x size %08x", hci_type_str[controller_type],
print_usb_info("Found %s controller %04x:%04x at %08x size %08x", hci_name[controller_type],
(uintptr_t)vendor_id, (uintptr_t)device_id, base_addr, mmio_size);
base_addr = map_device(base_addr, mmio_size);
if (base_addr == 0) {
print_usb_info(" Failed to map device into virtual memory");
print_usb_info(" Failed to map device into virtual memory");
break;
}
@@ -230,23 +654,20 @@ void find_usb_keyboards(bool pause_at_end)
pci_config_write16(bus, dev, func, 0x04, control | 0x0007);
// Initialise the device according to its type.
usb_controller_info_t *new_controller = &usb_controllers[num_usb_controllers];
new_controller->type = controller_type;
new_controller->workspace = NULL;
bool keyboards_found = false;
if (controller_type == UHCI) {
print_usb_info(" This controller type is not supported yet");
print_usb_info(" This controller type is not supported yet");
}
if (controller_type == OHCI) {
new_controller->workspace = ohci_init(base_addr);
keyboards_found = ohci_init(base_addr, &usb_controllers[num_usb_controllers]);
}
if (controller_type == EHCI) {
print_usb_info(" This controller type is not supported yet");
print_usb_info(" This controller type is not supported yet");
}
if (controller_type == XHCI) {
new_controller->workspace = xhci_init(base_addr);
keyboards_found = xhci_init(base_addr, &usb_controllers[num_usb_controllers]);
}
if (new_controller->workspace != NULL) {
if (keyboards_found) {
num_usb_controllers++;
// If we've filled the controller table, abort now.
if (num_usb_controllers == MAX_USB_CONTROLLERS) {
@@ -277,17 +698,7 @@ void find_usb_keyboards(bool pause_at_end)
uint8_t get_usb_keycode(void)
{
for (int i = 0; i < num_usb_controllers; i++) {
uint8_t keycode = 0;
switch (usb_controllers[i].type) {
case OHCI:
keycode = ohci_get_keycode(usb_controllers[i].workspace);
break;
case XHCI:
keycode = xhci_get_keycode(usb_controllers[i].workspace);
break;
default:
break;
}
uint8_t keycode = usb_controllers[i].methods->get_keycode(&usb_controllers[i]);
if (keycode != 0) return keycode;
}
return 0;

View File

@@ -2,27 +2,107 @@
#ifndef USBKBD_H
#define USBKBD_H
/*
* Provides low-level support for USB keyboards.
* Provides the base USB host controller driver for USB keyboard support.
*
* Copyright (C) 2021 Martin Whitaker.
* This is an object-oriented design. The hcd_methods_t structure defines
* a set of virtual methods that will be implemented by the subclasses.
* The hcd_workspace_t structure defines the base class properties. The
* usb_hcd_t structure represents a driver object. The non-virtual and
* default base class methods are defined as separate functions, taking
* a usb_hcd_t pointer as their first parameter.
*
* The find_usb_keyboards function instantiates a driver object of the
* appropriate subclass for each USB controller it finds and stores it
* in the private usb_controllers table, where it can subsequently used
* to poll the keyboards for key presses.
*
* Copyright (C) 2021-2022 Martin Whitaker.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <usb.h>
#include "usb.h"
/*
* A USB endpoint descriptor used internally by the various HCI drivers.
* The size of the data transfer buffer in a host controller driver workspace.
* This aligns the start of the driver private data to a 1024 byte boundary.
*/
#define HCD_DATA_BUFFER_SIZE (1024 - sizeof(size_t))
/*
* A USB device speed (used internally by the various HCI drivers).
*/
typedef enum __attribute__ ((packed)) {
USB_SPEED_UNKNOWN = 0,
USB_SPEED_LOW = 1,
USB_SPEED_FULL = 2,
USB_SPEED_HIGH = 3
} usb_speed_t;
/*
* A USB endpoint descriptor (used internally by the various HCI drivers).
*/
typedef struct __attribute__ ((packed)) {
void *driver_data;
usb_speed_t device_speed;
uint8_t device_id;
uint8_t interface_num;
uint8_t endpoint_num;
uint16_t max_packet_size;
uint8_t interval;
uint8_t reserved;
} usb_ep_t;
/*
* A USB hub descriptor (used internally by the various HCI drivers).
*/
typedef struct __attribute__ ((packed)) {
const usb_ep_t *ep0;
uint32_t route;
uint8_t level;
uint8_t num_ports;
uint8_t tt_think_time;
uint8_t power_up_delay;
} usb_hub_t;
/*
* A USB host controller driver object reference.
*/
typedef const struct usb_hcd_s *usb_hcd_r;
/*
* A USB host controller driver method table.
*/
typedef struct {
int device_speed;
int device_addr;
int interface_num;
int endpoint_num;
int max_packet_size;
int interval;
} usb_ep_info_t;
bool (*reset_root_hub_port) (usb_hcd_r, int);
int (*allocate_slot) (usb_hcd_r);
bool (*release_slot) (usb_hcd_r, int);
bool (*assign_address) (usb_hcd_r, const usb_hub_t *, int, usb_speed_t, int, usb_ep_t *);
bool (*configure_hub_ep) (usb_hcd_r, const usb_ep_t *, const usb_hub_t *);
bool (*configure_kbd_ep) (usb_hcd_r, const usb_ep_t *, int);
bool (*setup_request) (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *);
bool (*get_data_request) (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *, const void *, size_t);
uint8_t (*get_keycode) (usb_hcd_r);
} hcd_methods_t;
/*
* A USB host controller driver workspace. This is extended by each HCI driver
* to append its private data.
*/
typedef struct __attribute__((packed)) {
uint8_t data_buffer[HCD_DATA_BUFFER_SIZE];
size_t data_length;
} hcd_workspace_t;
/*
* A USB host controller driver object.
*/
typedef struct usb_hcd_s {
const hcd_methods_t *methods;
hcd_workspace_t *ws;
} usb_hcd_t;
/*
* A set of USB device initialisation options.
@@ -55,13 +135,13 @@ static inline void build_setup_packet(usb_setup_pkt_t *pkt, int type, int reques
/*
* Returns true if size is a valid value for the maximum packet size for a
* low speed or full speed USB device or endpoint.
* USB device running at the given speed.
*
* Used internally by the various HCI drivers.
*/
static inline bool valid_usb_max_packet_size(int size, bool is_low_speed)
static inline bool valid_usb_max_packet_size(int size, usb_speed_t speed)
{
return (size == 8) || (!is_low_speed && (size == 16 || size == 32 || size == 64));
return (size == 8) || ((speed != USB_SPEED_LOW) && (size == 16 || size == 32 || size == 64));
}
/*
@@ -69,7 +149,7 @@ static inline bool valid_usb_max_packet_size(int size, bool is_low_speed)
*
* Used internally by the various HCI drivers.
*/
static inline bool valid_usb_device_descriptor(volatile uint8_t *buffer)
static inline bool valid_usb_device_descriptor(const uint8_t *buffer)
{
usb_desc_header_t *desc = (usb_desc_header_t *)buffer;
@@ -77,17 +157,27 @@ static inline bool valid_usb_device_descriptor(volatile uint8_t *buffer)
}
/*
* Returns true if buffer appears to contain a valid USB configuration descriptor.
* Returns true if buffer appears to contain a valid USB configuration
* descriptor.
*
* Used internally by the various HCI drivers.
*/
static inline bool valid_usb_config_descriptor(volatile uint8_t *buffer)
static inline bool valid_usb_config_descriptor(const uint8_t *buffer)
{
usb_desc_header_t *desc = (usb_desc_header_t *)buffer;
return desc->length == sizeof(usb_config_desc_t) && desc->type == USB_DESC_CONFIG;
return desc->length == sizeof(usb_config_desc_t) && desc->type == USB_DESC_CONFIGURATION;
}
/*
* Returns the USB route to the device attached to the hub port specified by
* hub and port_num. The top 8 bits of the returned value contain the root
* port number and the bottom 20 bits contain the USB3 route string.
*
* Used internally by the various HCI drivers.
*/
uint32_t usb_route(const usb_hub_t *hub, int port_num);
/*
* Waits for all the bits set in bit_mask to be cleared in the register pointed
* to by reg or for max_time microseconds to elapse.
@@ -104,20 +194,6 @@ bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_tim
*/
bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_time);
/*
* Scans the descriptors obtained from a USB GET_CONFIGURATION request and
* stored in the buffer defined by desc_buffer and desc_length. Adds an entry
* in the table defined by keyboard_info and keyboard_info_size for each
* endpoint it finds that identifies as a HID keyboard device, filling in
* the interface_num, endpoint_num, max_packet_size, and interval fields.
* Returns the number of entries added. The scan is terminated early if the
* table is full.
*
* Used internally by the various HCI drivers.
*/
int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length,
usb_ep_info_t keyboard_info[], int keyboard_info_size);
/*
* Displays an informational message, scrolling the screen if necessary.
* Takes the same arguments as the printf function.
@@ -126,6 +202,39 @@ int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer,
*/
void print_usb_info(const char *fmt, ...);
/*
* Resets the specified USB hub port.
*
* Used internally by the various HCI drivers.
*/
bool reset_usb_hub_port(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num);
/*
* Sets the device address for the device attached to the specified hub port
* (thus moving the device to Address state), fills in the descriptor for the
* device's default control endpoint (ep0), and leaves the device descriptor
* in the driver's data transfer buffer. Returns true if all actions are
* successfully completed.
*
* This is the default implementation of the HCD assign_address method.
*/
bool assign_usb_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
usb_speed_t device_speed, int device_id, usb_ep_t *ep0);
/*
* Scans the specified USB device to detect whether it has any HID keyboards
* attached to it (directly or indirectly). If so, the keyboard device(s)
* are initialised and configured, as are any intermediate USB hubs, and the
* table defined by keyboards and max_keyboards is updated accordingly and
* num_keyboards is updated to match. Returns true if any keyboards were found
* and added to the table.
*
* Used internally by the various HCI drivers.
*/
bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
usb_speed_t device_speed, int device_id, int *num_devices,
usb_ep_t keyboards[], int max_keyboards, int *num_keyboards);
/*
* Scans the attached USB devices and initialises all HID keyboard devices
* it finds (subject to implementation limits on the number of devices).

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021 Martin Whitaker.
// Copyright (C) 2021-2022 Martin Whitaker.
#include <stdbool.h>
#include <stdint.h>
@@ -98,6 +98,9 @@
#define XHCI_TRB_DIR_OUT (0 << 16) // Direction (Data/Status Stage TRB)
#define XHCI_TRB_DIR_IN (1 << 16) // Direction (Data/Status Stage TRB)
#define XHCI_TRB_USB2_SLOT (0 << 16) // Slot Type (Enable Slot TRB)
#define XHCI_TRB_USB3_SLOT (0 << 16) // Slot Type (Enable Slot TRB)
// Add Context flags
#define XHCI_CONTEXT_A0 (1 << 0)
@@ -123,9 +126,9 @@
// Event Completion Code values
#define XHCI_EVENT_CC_SUCCESS 1
#define XHCI_EVENT_CC_TIMEOUT 191 // specific to this implementation
#define XHCI_EVENT_CC_TIMEOUT 191 // specific to this driver
// Values specific to this implementation.
// Values specific to this driver.
#define PORT_TYPE_PST_MASK 0x1f // Protocol Slot Type mask
#define PORT_TYPE_USB2 0x40
@@ -136,13 +139,14 @@
#define WS_CR_SIZE 8 // TRBs (multiple of 4 to maintain 64 byte alignment)
#define WS_ER_SIZE 16 // TRBs (multiple of 4 to maintain 64 byte alignment)
#define WS_ERST_SIZE 4 // entries (multiple of 4 to maintain 64 byte alignment)
#define WS_DATA_SIZE 1024 // bytes
#define WS_KC_BUFFER_SIZE 8 // keycodes
#define EP_TR_SIZE 8 // TRBs (multiple of 4 to maintain 64 byte alignment)
#define MILLISEC 1000 // in microseconds
#define DEVICE_WS_SIZE (XHCI_MAX_OP_CONTEXT_SIZE + 2 * sizeof(ep_tr_t))
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
@@ -246,8 +250,8 @@ typedef struct {
uint16_t max_exit_latency;
uint8_t root_hub_port_num;
uint8_t num_ports;
uint8_t tt_hub_slot_id;
uint8_t tt_port_num;
uint8_t parent_slot_id;
uint8_t parent_port_num;
uint16_t params2;
uint8_t usb_dev_addr;
uint8_t reserved1;
@@ -289,29 +293,28 @@ typedef volatile struct {
uint32_t reserved2;
} xhci_erst_entry_t __attribute__ ((aligned (16)));
// Data structures specific to this implementation.
// Data structures specific to this driver.
typedef volatile struct {
xhci_trb_t tr [EP_TR_SIZE];
xhci_trb_t tr[EP_TR_SIZE];
uint32_t enqueue_state;
uint32_t padding[15];
} ep_tr_t __attribute__ ((aligned (64)));
typedef struct {
hcd_workspace_t base_ws;
// System memory data structures used by the host controller.
xhci_trb_t cr [WS_CR_SIZE]; // command ring
xhci_trb_t er [WS_ER_SIZE]; // event ring
xhci_erst_entry_t erst [WS_ERST_SIZE]; // event ring segment table
// Data transfer buffers.
union {
volatile uint8_t data [WS_DATA_SIZE];
hid_kbd_rpt_t kbd_rpt [MAX_KEYBOARDS];
};
// Keyboard data transfer rings
ep_tr_t kbd_tr [MAX_KEYBOARDS];
// Keyboard data transfer buffers.
hid_kbd_rpt_t kbd_rpt [MAX_KEYBOARDS];
// Pointers to the host controller registers.
xhci_op_regs_t *op_regs;
xhci_rt_regs_t *rt_regs;
@@ -321,15 +324,15 @@ typedef struct {
uint64_t *device_context_index;
size_t context_size;
// Host controller TRB ring enqueue cycle and index.
// Host controller TRB ring state (cycle and index).
uint32_t cr_enqueue_state;
uint32_t er_dequeue_state;
// 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;
uintptr_t interrupt_ep_tr_addr;
// Keyboard slot ID lookup table
uint8_t kbd_slot_id [MAX_KEYBOARDS];
@@ -338,10 +341,9 @@ typedef struct {
uint8_t kbd_ep_id [MAX_KEYBOARDS];
// Circular buffer for received keycodes.
uint8_t kc_buffer [WS_KC_BUFFER_SIZE];
uint8_t kc_buffer[WS_KC_BUFFER_SIZE];
int32_t kc_index_i;
int32_t kc_index_o;
} workspace_t __attribute__ ((aligned (64)));
//------------------------------------------------------------------------------
@@ -359,11 +361,6 @@ static int heap_segment = -1;
// Private Functions
//------------------------------------------------------------------------------
static size_t min(size_t a, size_t b)
{
return (a < b) ? a : b;
}
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
@@ -402,23 +399,51 @@ static void memcpy32(void *dst, const void *src, size_t size)
}
#endif
static int default_max_packet_size(int device_speed)
static usb_speed_t xhci_to_usb_speed(int xhci_speed)
{
switch (device_speed) {
switch (xhci_speed) {
case XHCI_LOW_SPEED:
return 8;
return USB_SPEED_LOW;
case XHCI_FULL_SPEED:
return 64;
return USB_SPEED_FULL;
case XHCI_HIGH_SPEED:
return 64;
return USB_SPEED_HIGH;
default:
return 512;
return USB_SPEED_UNKNOWN;
}
}
static int xhci_ep_interval(int config_interval, int device_speed)
static int usb_to_xhci_speed(usb_speed_t usb_speed)
{
if (device_speed < XHCI_HIGH_SPEED) {
switch (usb_speed) {
case USB_SPEED_LOW:
return XHCI_LOW_SPEED;
case USB_SPEED_FULL:
return XHCI_FULL_SPEED;
case USB_SPEED_HIGH:
return XHCI_HIGH_SPEED;
default:
return 0;
}
}
static int default_max_packet_size(usb_speed_t device_speed)
{
switch (device_speed) {
case USB_SPEED_LOW:
return 8;
case USB_SPEED_FULL:
return 64;
case USB_SPEED_HIGH:
return 64;
default:
return 0;
}
}
static int xhci_ep_interval(int config_interval, usb_speed_t device_speed)
{
if (device_speed < USB_SPEED_HIGH) {
int log2_interval = 7;
while ((1 << log2_interval) > config_interval) {
log2_interval--;
@@ -446,13 +471,13 @@ static bool reset_host_controller(xhci_op_regs_t *op_regs)
static bool start_host_controller(xhci_op_regs_t *op_regs)
{
write32(&op_regs->usb_command, read32(&op_regs->usb_command) | XHCI_USBCMD_R_S);
return wait_until_clr(&op_regs->usb_status, XHCI_USBSTS_HCH, 20*MILLISEC);
return wait_until_clr(&op_regs->usb_status, XHCI_USBSTS_HCH, 1000*MILLISEC);
}
static bool halt_host_controller(xhci_op_regs_t *op_regs)
{
write32(&op_regs->usb_command, read32(&op_regs->usb_command) & ~XHCI_USBCMD_R_S);
return wait_until_set(&op_regs->usb_status, XHCI_USBSTS_HCH, 20*MILLISEC);
return wait_until_set(&op_regs->usb_status, XHCI_USBSTS_HCH, 1000*MILLISEC);
}
static int get_xhci_device_speed(xhci_op_regs_t *op_regs, int port_idx)
@@ -460,15 +485,16 @@ 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;
}
static void reset_xhci_port(xhci_op_regs_t *op_regs, int port_idx)
static bool reset_xhci_port(xhci_op_regs_t *op_regs, int port_idx)
{
write32(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PP | XHCI_PORT_SC_PR);
return wait_until_clr(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PR, 1000*MILLISEC);
}
static void disable_xhci_port(xhci_op_regs_t *op_regs, int port_idx)
{
write32(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PP | XHCI_PORT_SC_PED);
wait_until_clr(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PED, 20*MILLISEC);
(void)wait_until_clr(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PED, 1000*MILLISEC);
}
static void ring_host_controller_doorbell(xhci_db_reg_t *db_regs)
@@ -581,7 +607,7 @@ static void issue_setup_stage_trb(ep_tr_t *ep_tr, const usb_setup_pkt_t *setup_p
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 void *buffer, uint32_t dir, size_t transfer_length)
static void issue_data_stage_trb(ep_tr_t *ep_tr, const void *buffer, uint32_t dir, size_t transfer_length)
{
uint64_t params1 = (uintptr_t)buffer;
uint32_t params2 = transfer_length;
@@ -595,7 +621,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, size_t transfer_length)
static void issue_normal_trb(ep_tr_t *ep_tr, const void *buffer, uint32_t dir, size_t transfer_length)
{
uint64_t params1 = (uintptr_t)buffer;
uint32_t params2 = transfer_length;
@@ -603,84 +629,156 @@ 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;
//------------------------------------------------------------------------------
// Driver Methods
//------------------------------------------------------------------------------
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
static bool setup_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ep_tr_t *ep_tr = (ep_tr_t *)ep->driver_data;
xhci_trb_t event;
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;
ring_device_doorbell(ws->db_regs, ep->device_id, 1);
return (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 5000*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)
static bool get_data_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt,
const void *buffer, size_t length)
{
xhci_trb_t event;
workspace_t *ws = (workspace_t *)hcd->ws;
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
ep_tr_t *ep_tr = (ep_tr_t *)ep->driver_data;
xhci_trb_t event;
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;
ring_device_doorbell(ws->db_regs, ep->device_id, 1);
return (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 5000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
}
static bool disable_xhci_slot(workspace_t *ws, int slot_id)
static bool reset_root_hub_port(const usb_hcd_t *hcd, int port_num)
{
xhci_trb_t event;
const workspace_t *ws = (const workspace_t *)hcd->ws;
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) {
return false;
}
return true;
return reset_xhci_port(ws->op_regs, port_num - 1);
}
static int initialise_device(workspace_t *ws, int port_idx, int slot_type)
static int allocate_slot(const usb_hcd_t *hcd)
{
usb_setup_pkt_t setup_pkt;
workspace_t *ws = (workspace_t *)hcd->ws;
xhci_trb_t event;
// Get the port speed.
// Allocate and initialise a private workspace for this device.
// TODO: check for heap overflow.
int device_speed = get_xhci_device_speed(ws->op_regs, port_idx);
pm_map[heap_segment].end -= num_pages(DEVICE_WS_SIZE);
uintptr_t device_workspace_addr = pm_map[heap_segment].end << PAGE_SHIFT;
ws->output_context_addr = device_workspace_addr;
ws->control_ep_tr_addr = device_workspace_addr + XHCI_MAX_OP_CONTEXT_SIZE;
ws->interrupt_ep_tr_addr = device_workspace_addr + XHCI_MAX_OP_CONTEXT_SIZE + sizeof(ep_tr_t);
memset((void *)device_workspace_addr, 0, DEVICE_WS_SIZE);
// Temporarily allocate and initialise the input context data structure on the heap.
// As we only use this temporarily, there's no need to adjust pm_map.
ws->input_context_addr = device_workspace_addr - XHCI_MAX_IP_CONTEXT_SIZE;
memset((void *)ws->input_context_addr, 0, XHCI_MAX_IP_CONTEXT_SIZE);
// Allocate a device slot and set up its output context.
enqueue_xhci_command(ws, XHCI_TRB_ENABLE_SLOT | slot_type << 16, 0, 0);
enqueue_xhci_command(ws, XHCI_TRB_ENABLE_SLOT | XHCI_TRB_USB2_SLOT, 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, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
pm_map[heap_segment].end += num_pages(DEVICE_WS_SIZE);
return 0;
}
int slot_id = event_slot_id(&event);
write64(&ws->device_context_index[slot_id], ws->output_context_addr);
return slot_id;
}
static bool release_slot(const usb_hcd_t *hcd, int slot_id)
{
workspace_t *ws = (workspace_t *)hcd->ws;
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, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
return false;
}
write64(&ws->device_context_index[slot_id], 0);
pm_map[heap_segment].end += num_pages(DEVICE_WS_SIZE);
return true;
}
static bool assign_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
usb_speed_t device_speed, int device_id, usb_ep_t *ep0)
{
workspace_t *ws = (workspace_t *)hcd->ws;
usb_setup_pkt_t setup_pkt;
uint8_t *data_buffer = hcd->ws->data_buffer;
xhci_trb_t event;
// Initialise the control endpoint descriptor. With the XHCI, we never need to know the USB address of the
// device, so device_id actually contains the slot ID.
ep0->device_speed = device_speed;
ep0->device_id = device_id;
ep0->interface_num = 0;
ep0->endpoint_num = 0;
ep0->max_packet_size = 0;
ep0->interval = 0;
// 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 + ws->context_size);
slot_context->root_hub_port_num = 1 + port_idx;
slot_context->params1 = 1 << 27 | device_speed << 20;
slot_context->params1 = 1 << 27 | usb_to_xhci_speed(device_speed) << 20;
if (hub->level > 0) {
uint32_t route = usb_route(hub, port_num);
slot_context->params1 |= route & 0xfffff;
slot_context->root_hub_port_num = route >> 24;
if (device_speed < USB_SPEED_HIGH && hub->ep0->device_speed == USB_SPEED_HIGH) {
slot_context->parent_slot_id = hub->ep0->device_id;
slot_context->parent_port_num = port_num;
}
} else {
slot_context->root_hub_port_num = port_num;
}
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(device_speed);
ep_context->tr_dequeue_ptr = ws->control_ep_tr_addr | 1;
ep_context->average_trb_length = 8;
// Initialise the control endpoint transfer ring.
ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr;
ep_tr->enqueue_state = EP_TR_SIZE; // cycle = 1, index = 0
ep0->driver_data = (void *)ep_tr;
// Set the device address. For full speed devices we need to read the first 8 bytes of the device descriptor
// to determine the maximum packet size the device supports and update the device context accordingly. For
@@ -689,33 +787,36 @@ static int initialise_device(workspace_t *ws, int port_idx, int slot_type)
size_t fetch_length = sizeof(usb_device_desc_t);
uint32_t command_flags = 0;
if (device_speed < XHCI_HIGH_SPEED) {
if (device_speed < USB_SPEED_HIGH) {
fetch_length = 8;
command_flags = XHCI_TRB_BSR;
}
set_address:
enqueue_xhci_command(ws, XHCI_TRB_ADDRESS_DEVICE | command_flags | slot_id << 24, ws->input_context_addr, 0);
enqueue_xhci_command(ws, XHCI_TRB_ADDRESS_DEVICE | command_flags | device_id << 24, ws->input_context_addr, 0);
ring_host_controller_doorbell(ws->db_regs);
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
goto disable_slot;
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 5000*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
return false;
}
if (command_flags == 0) {
usleep(2*MILLISEC); // USB set address recovery time.
usleep(2*MILLISEC + 1*MILLISEC); // USB set address recovery time (plus a bit).
}
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;
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR, USB_DESC_DEVICE << 8, 0, fetch_length);
if (!get_data_request(hcd, ep0, &setup_pkt, data_buffer, fetch_length)
|| !valid_usb_device_descriptor(data_buffer)) {
return false;
}
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, device_speed == XHCI_LOW_SPEED)) {
goto disable_slot;
if (command_flags != 0) {
usb_device_desc_t *device = (usb_device_desc_t *)data_buffer;
ep0->max_packet_size = device->max_packet_size;
if (!valid_usb_max_packet_size(device->max_packet_size, device_speed)) {
return false;
}
if (usb_init_options & USB_EXTRA_RESET) {
reset_xhci_port(ws->op_regs, port_idx);
if (!reset_usb_hub_port(hcd, hub, port_num)) {
return false;
}
}
ep_context->max_packet_size = device->max_packet_size;
ep_context->tr_dequeue_ptr += 3 * sizeof(xhci_trb_t);
@@ -725,46 +826,18 @@ static int initialise_device(workspace_t *ws, int port_idx, int slot_type)
goto set_address;
}
// 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 more data.
hcd->ws->data_length = fetch_length;
fetch_length = sizeof(usb_config_desc_t);
fetch_config_descriptor:
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;
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->data_length = total_length;
return slot_id;
disable_slot:
if (disable_xhci_slot(ws, slot_id)) {
write64(&ws->device_context_index[slot_id], 0);
}
return 0;
return true;
}
static bool configure_keyboard(workspace_t *ws, int slot_id, int kbd_idx, usb_ep_info_t *kbd)
static bool configure_interrupt_endpoint(workspace_t *ws, const usb_ep_t *ep, int hub_flag, int num_ports,
int tt_think_time, uintptr_t tr_addr, size_t rpt_size)
{
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.
int ep_id = 2 * kbd->endpoint_num + 1; // EP <N> IN
// Fill in the lookup tables in the workspace.
ws->kbd_slot_id[kbd_idx] = slot_id;
ws->kbd_ep_id [kbd_idx] = ep_id;
int ep_id = 2 * ep->endpoint_num + 1; // EP <N> IN
// The input context has already been initialised, so we just need to change the values used by the
// CONFIGURE_ENDPOINT command before issuing the command.
@@ -773,46 +846,44 @@ static bool configure_keyboard(workspace_t *ws, int slot_id, int kbd_idx, usb_ep
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 + ws->context_size);
slot_context->params1 = ep_id << 27 | kbd->device_speed << 20;
slot_context->params1 = ep_id << 27 | hub_flag << 26 | (slot_context->params1 & 0x00ffffff);
slot_context->num_ports = num_ports;
slot_context->params2 = tt_think_time;
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;
ep_context->interval = xhci_ep_interval(ep->interval, ep->device_speed);
ep_context->max_burst_size = 0;
ep_context->max_packet_size = kbd->max_packet_size;
ep_context->tr_dequeue_ptr = (uintptr_t)(&ws->kbd_tr[kbd_idx]) | 1;
ep_context->average_trb_length = sizeof(hid_kbd_rpt_t);
ep_context->max_esit_payload_l = kbd->max_packet_size;
ep_context->max_packet_size = ep->max_packet_size;
ep_context->tr_dequeue_ptr = tr_addr | 1;
ep_context->average_trb_length = rpt_size;
ep_context->max_esit_payload_l = ep->max_packet_size;
ep_context->max_esit_payload_h = 0;
enqueue_xhci_command(ws, XHCI_TRB_CONFIGURE_ENDPOINT | slot_id << 24, ws->input_context_addr, 0);
enqueue_xhci_command(ws, XHCI_TRB_CONFIGURE_ENDPOINT | ep->device_id << 24, ws->input_context_addr, 0);
ring_host_controller_doorbell(ws->db_regs);
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
return false;
}
return (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 100*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
}
// Now configure the device itself.
static bool configure_hub_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_hub_t *hub)
{
workspace_t *ws = (workspace_t *)hcd->ws;
// Set the device configuration.
build_setup_packet(&setup_pkt, 0x00, 0x09, 1, 0, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
return configure_interrupt_endpoint(ws, ep, 1, hub->num_ports, hub->tt_think_time,
ws->interrupt_ep_tr_addr, (hub->num_ports + 7) / 8);
}
// Set the idle duration to infinite.
build_setup_packet(&setup_pkt, 0x21, 0x0a, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
static bool configure_kbd_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int kbd_idx)
{
workspace_t *ws = (workspace_t *)hcd->ws;
// Select the boot protocol.
build_setup_packet(&setup_pkt, 0x21, 0x0b, 0, kbd->interface_num, 0);
if (!send_setup_request(ws, slot_id, &setup_pkt)) {
return false;
}
// Fill in the lookup tables in the workspace.
ws->kbd_slot_id[kbd_idx] = ep->device_id;
ws->kbd_ep_id [kbd_idx] = 2 * ep->endpoint_num + 1; // EP <N> IN
return true;
// Configure the controller.
return configure_interrupt_endpoint(ws, ep, 0, 0, 0, (uintptr_t)(&ws->kbd_tr[kbd_idx]), sizeof(hid_kbd_rpt_t));
}
static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id)
@@ -825,6 +896,44 @@ static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id)
return -1;
}
static uint8_t get_keycode(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
xhci_trb_t event;
while (get_xhci_event(ws, &event)) {
if (event_type(&event) != XHCI_TRB_TRANSFER_EVENT || event_cc(&event) != XHCI_EVENT_CC_SUCCESS) continue;
int kbd_idx = identify_keyboard(ws, event_slot_id(&event), event_ep_id(&event));
if (kbd_idx < 0) continue;
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
uint8_t keycode = kbd_rpt->key_code[0];
if (keycode != 0) {
int kc_index_i = ws->kc_index_i;
int kc_index_n = (kc_index_i + 1) % WS_KC_BUFFER_SIZE;
if (kc_index_n != ws->kc_index_o) {
ws->kc_buffer[kc_index_i] = keycode;
ws->kc_index_i = kc_index_n;
}
}
ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx];
issue_normal_trb(kbd_tr, kbd_rpt, XHCI_TRB_DIR_IN, sizeof(hid_kbd_rpt_t));
ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]);
}
int kc_index_o = ws->kc_index_o;
if (kc_index_o != ws->kc_index_i) {
ws->kc_index_o = (kc_index_o + 1) % WS_KC_BUFFER_SIZE;
return ws->kc_buffer[kc_index_o];
}
return 0;
}
static bool set_heap_segment(void)
{
// Use the largest 32-bit addressable physical memory segment for the heap.
@@ -839,14 +948,30 @@ static bool set_heap_segment(void)
return max_segment_size > 0;
}
//------------------------------------------------------------------------------
// Driver Method Table
//------------------------------------------------------------------------------
static const hcd_methods_t methods = {
.reset_root_hub_port = reset_root_hub_port,
.allocate_slot = allocate_slot,
.release_slot = release_slot,
.assign_address = assign_address,
.configure_hub_ep = configure_hub_ep,
.configure_kbd_ep = configure_kbd_ep,
.setup_request = setup_request,
.get_data_request = get_data_request,
.get_keycode = get_keycode
};
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void *xhci_init(uintptr_t base_addr)
bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
{
if (heap_segment < 0) {
if (!set_heap_segment()) return NULL;
if (!set_heap_segment()) return false;
}
uintptr_t heap_segment_end = pm_map[heap_segment].end;
@@ -880,7 +1005,7 @@ void *xhci_init(uintptr_t base_addr)
int timer = 1000;
while (legacy_support->bios_owns & 0x1) {
legacy_support->host_owns = 0x1;
if (timer == 0) return NULL;
if (timer == 0) return false;
usleep(1*MILLISEC);
timer--;
}
@@ -923,9 +1048,9 @@ void *xhci_init(uintptr_t base_addr)
xhci_db_reg_t *db_regs = (xhci_db_reg_t *)(base_addr + cap_regs->db_offset);
// Ensure the controller is halted and then reset it.
if (!halt_host_controller(op_regs)) return NULL;
if (!halt_host_controller(op_regs)) return false;
if (!reset_host_controller(op_regs)) return NULL;
if (!reset_host_controller(op_regs)) return false;
// Record the controller page size.
uintptr_t xhci_page_size = (read32(&op_regs->page_size) & 0xffff) << 12;
@@ -968,8 +1093,8 @@ void *xhci_init(uintptr_t base_addr)
}
}
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual
// memory, so allocate it in the first segment.
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory,
// so allocate it in the first segment.
// TODO: check for segment overflow.
pm_map[0].end -= num_pages(sizeof(workspace_t));
uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT;
@@ -988,6 +1113,10 @@ void *xhci_init(uintptr_t base_addr)
ws->cr_enqueue_state = WS_CR_SIZE; // cycle = 1, index = 0
ws->er_dequeue_state = WS_ER_SIZE; // cycle = 1, index = 0
// Initialise the driver object for this controller.
hcd->methods = &methods;
hcd->ws = &ws->base_ws;
// Initialise the ERST for the primary interrupter. We only use the first segment.
ws->erst[0].segment_addr = (uintptr_t)(&ws->er);
ws->erst[0].segment_size = WS_ER_SIZE;
@@ -1002,93 +1131,67 @@ void *xhci_init(uintptr_t base_addr)
write32(&op_regs->config, (read32(&op_regs->config) & 0xfffffc00) | max_slots);
if (!start_host_controller(op_regs)) {
pm_map[heap_segment].end = heap_segment_end;
return NULL;
return false;
}
// Construct a hub descriptor for the root hub.
usb_hub_t root_hub;
root_hub.ep0 = NULL;
root_hub.level = 0;
root_hub.route = 0;
root_hub.num_ports = cap_regs->hcs_params1 & 0xff;
root_hub.power_up_delay = 0;
usleep(100*MILLISEC); // USB maximum device attach time.
// Scan the ports, looking for keyboards.
usb_ep_info_t keyboard_info[MAX_KEYBOARDS];
// Scan the ports, looking for hubs and keyboards.
usb_ep_t keyboards[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 0;
int num_ports = cap_regs->hcs_params1 & 0xff;
for (int port_idx = 0; port_idx < num_ports; port_idx++) {
if (num_keyboards >= MAX_KEYBOARDS) continue;
for (int port_idx = 0; port_idx < root_hub.num_ports; port_idx++) {
// If we've filled the keyboard info table, abort now.
if (num_keyboards >= MAX_KEYBOARDS) break;
// We only expect to find keyboards on USB2 ports.
if (~port_type[port_idx] & PORT_TYPE_USB2) continue;
// Check if anything is connected to this port.
uint32_t port_status = read32(&op_regs->port_regs[port_idx].sc);
// Check if anything is connected to this port.
if (~port_status & XHCI_PORT_SC_CCS) continue;
// Reset the port.
if (!reset_xhci_port(op_regs, port_idx)) continue;
usleep(10*MILLISEC); // USB reset recovery time
port_status = read32(&op_regs->port_regs[port_idx].sc);
// Check the port is active.
if (~port_status & XHCI_PORT_SC_CCS) continue;
if (~port_status & XHCI_PORT_SC_PED) continue;
// Now the port has been enabled, we can determine the device speed.
usb_speed_t device_speed = xhci_to_usb_speed(get_xhci_device_speed(ws->op_regs, port_idx));
num_devices++;
// Reset the port (USB2 only).
reset_xhci_port(op_regs, port_idx);
// Allocate a controller slot for this device.
int slot_id = allocate_slot(hcd);
if (slot_id == 0) break;
// Wait for the device to be enabled.
if (!wait_until_set(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PED, 500*MILLISEC)) continue;
// Allocate and initialise a private workspace for this device.
// TODO: check for heap overflow.
const size_t device_ws_size = XHCI_MAX_OP_CONTEXT_SIZE + sizeof(ep_tr_t);
pm_map[heap_segment].end -= num_pages(device_ws_size);
uintptr_t device_workspace_addr = pm_map[heap_segment].end << PAGE_SHIFT;
ws->output_context_addr = device_workspace_addr;
ws->control_ep_tr_addr = device_workspace_addr + XHCI_MAX_OP_CONTEXT_SIZE;
memset((void *)device_workspace_addr, 0, device_ws_size);
// Temporarily allocate and initialise the input context data structure on the heap.
// As we only need this for the lifetime of this function, there's no need to adjust pm_map.
ws->input_context_addr = device_workspace_addr - XHCI_MAX_IP_CONTEXT_SIZE;
memset((void *)ws->input_context_addr, 0, XHCI_MAX_IP_CONTEXT_SIZE);
// Retrieve the protocol slot type.
int slot_type = port_type[port_idx] & PORT_TYPE_PST_MASK;
// Initialise the device. If successful, this leaves a set of configuration descriptors in the workspace
// data buffer.
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_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_ep_info_t *kbd = &new_keyboard_info[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);
}
if (new_keyboards > 0) {
num_keyboards += new_keyboards;
// Look for keyboards attached directly or indirectly to this port.
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, slot_id,
&num_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
continue;
}
// 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(&ws->device_context_index[slot_id], 0);
}
disable_port:
// If we didn't find any keyboard interfaces, we disable the port and free the slot.
disable_xhci_port(op_regs, port_idx);
pm_map[heap_segment].end += num_pages(device_ws_size);
release_slot(hcd, slot_id);
}
print_usb_info(" Found %i device%s, %i keyboard%s",
print_usb_info(" Found %i device%s, %i keyboard%s",
num_devices, num_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "");
@@ -1102,7 +1205,7 @@ void *xhci_init(uintptr_t base_addr)
// Free the pages we allocated in the heap segment.
pm_map[heap_segment].end = heap_segment_end;
return NULL;
return false;
}
// Initialise the interrupt TRB ring for each keyboard interface.
@@ -1115,42 +1218,5 @@ void *xhci_init(uintptr_t base_addr)
ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]);
}
return ws;
}
uint8_t xhci_get_keycode(void *workspace)
{
workspace_t *ws = (workspace_t *)workspace;
xhci_trb_t event;
while (get_xhci_event(ws, &event)) {
if (event_type(&event) != XHCI_TRB_TRANSFER_EVENT || event_cc(&event) != XHCI_EVENT_CC_SUCCESS) continue;
int kbd_idx = identify_keyboard(ws, event_slot_id(&event), event_ep_id(&event));
if (kbd_idx < 0) continue;
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
uint8_t keycode = kbd_rpt->key_code[0];
if (keycode != 0) {
int kc_index_i = ws->kc_index_i;
int kc_index_n = (kc_index_i + 1) % WS_KC_BUFFER_SIZE;
if (kc_index_n != ws->kc_index_o) {
ws->kc_buffer[kc_index_i] = keycode;
ws->kc_index_i = kc_index_n;
}
}
ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx];
issue_normal_trb(kbd_tr, kbd_rpt, XHCI_TRB_DIR_IN, sizeof(hid_kbd_rpt_t));
ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]);
}
int kc_index_o = ws->kc_index_o;
if (kc_index_o != ws->kc_index_i) {
ws->kc_index_o = (kc_index_o + 1) % WS_KC_BUFFER_SIZE;
return ws->kc_buffer[kc_index_o];
}
return 0;
return true;
}

View File

@@ -4,23 +4,20 @@
/*
* Provides support for USB keyboards connected via an XHCI controller.
*
* Copyright (C) 2021 Martin Whitaker.
* Copyright (C) 2021-2022 Martin Whitaker.
*/
#include <stdint.h>
#include "usbkbd.h"
/*
* Initialises the XHCI device found at base_addr, scans all the attached USB
* devices, and configures any HID USB keyboard devices it finds to generate
* periodic interrupt transfers that report key presses.
* periodic interrupt transfers that report key presses. Initialises hcd and
* returns true if the device was successfully initialised and one or more
* keyboards were found.
*/
void *xhci_init(uintptr_t base_addr);
/*
* Polls the completed periodic interrupt transfers, stores the keycodes from
* any new key press events in an internal queue, and if the keycode queue is
* not empty, pops and returns the keycode from the front of the queue.
*/
uint8_t xhci_get_keycode(void *ws);
bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd);
#endif // XHCI_H