memtest86plus/system/ohci.c
Martin Whitaker 0bd19d9d91 ohci: rename variable used to preserve the frame interval.
For code clarity. No functional change.
2022-04-23 08:49:30 +01:00

631 lines
24 KiB
C

// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021-2022 Martin Whitaker.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "memrw32.h"
#include "memsize.h"
#include "pmem.h"
#include "usb.h"
#include "string.h"
#include "unistd.h"
#include "ohci.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// Values defined by the OHCI specification.
// HcControl register
#define OHCI_CTRL_CBSR 0x00000003 // Control Bulk Service Ratio
#define OHCI_CTRL_CBSR0 0x00000000 // Control Bulk Service Ratio 0
#define OHCI_CTRL_CBSR1 0x00000001 // Control Bulk Service Ratio 1
#define OHCI_CTRL_CBSR2 0x00000002 // Control Bulk Service Ratio 2
#define OHCI_CTRL_CBSR3 0x00000003 // Control Bulk Service Ratio 2
#define OHCI_CTRL_PLE 0x00000004 // Periodic List Enable
#define OHCI_CTRL_IE 0x00000008 // Isochronous Enable
#define OHCI_CTRL_CLE 0x00000010 // Control List Enable
#define OHCI_CTRL_BLE 0x00000020 // Bulk List Enable
#define OHCI_CTRL_HCFS 0x000000c0 // Host Controller Functional State
#define OHCI_CTRL_HCFS_RST 0x00000000 // Host Controller Functional State is Reset
#define OHCI_CTRL_HCFS_RES 0x00000040 // Host Controller Functional State is Resume
#define OHCI_CTRL_HCFS_RUN 0x00000080 // Host Controller Functional State is Run
#define OHCI_CTRL_HCFS_SUS 0x000000c0 // Host Controller Functional State is Suspend
#define OHCI_CTRL_IR 0x00000100 // Interrupt Routing
#define OHCI_CTRL_RWC 0x00000200 // Remote Wakeup Connected
#define OHCI_CTRL_RWE 0x00000400 // Remote Wakeup Enable
// HcCommandStatus register
#define OHCI_CMD_HCR 0x00000001 // Host Controller Reset
#define OHCI_CMD_CLF 0x00000002 // Control List Filled
#define OHCI_CMD_BLF 0x00000004 // Bulk List Filled
#define OHCI_CMD_OCR 0x00000008 // Ownership Change Request
// HcInterruptStatus register
#define OHCI_INTR_SC 0x00000001 // Scheduling Overrun
#define OHCI_INTR_WDH 0x00000002 // Writeback Done Head
#define OHCI_INTR_SOF 0x00000004 // Start of Frame
#define OHCI_INTR_RD 0x00000008 // Resume Detected
#define OHCI_INTR_UE 0x00000010 // Unrecoverable Error
#define OHCI_INTR_FNO 0x00000020 // Frame Number Overflow
#define OHCI_INTR_RHSC 0x00000040 // Root Hub Status Change
#define OHCI_INTR_OC 0x40000000 // Ownership Change
#define OHCI_INTR_MIE 0x80000000 // Master Interrupt Enable
// HcFmIntervalRegister
#define OHCI_FIT 0x80000000 // Frame Interval Toggle
// HcRhDescriptorA register
#define OHCI_RHDA_PSM 0x00000100 // Power Switching Mode
#define OHCI_RHDA_NPS 0x00000200 // No Power Switching
#define OHCI_RHDA_OCPM 0x00000800 // Over Current Protection Mode
#define OHCI_RHDA_NOCP 0x00001000 // No Over Current Protection
// HcRhDescriptorB register
#define OHCI_RHDB_DR 0x0000ffff // Device Removable
#define OHCI_RHDB_PPCM 0xffff0000 // Port Power Control Mask
// HcRhStatus register
#define OHCI_RHS_LPS 0x00000001 // Local Power Status
#define OHCI_RHS_OCI 0x00000002 // Over-Current Indicator
#define OHCI_RHS_DRWE 0x00008000 // Device Remote Wakeup Enable
#define OHCI_RHS_LPSC 0x00010000 // Local Power Status Change
#define OHCI_RHS_OCIC 0x00020000 // Over-Current Indicator Change
#define OHCI_RHS_CRWE 0x80000000 // Clear Remote Wakeup Enable
#define OHCI_SET_GLOBAL_POWER 0x00010000
#define OHCI_CLR_GLOBAL_POWER 0x00000001
// HcRhPortStatus registers
#define OHCI_PORT_CONNECTED 0x00000001
#define OHCI_PORT_ENABLED 0x00000002
#define OHCI_PORT_SUSPENDED 0x00000004
#define OHCI_PORT_OCI 0x00000008
#define OHCI_PORT_RESETING 0x00000010
#define OHCI_PORT_POWERED 0x00000100
#define OHCI_PORT_LOW_SPEED 0x00000200
#define OHCI_PORT_CONNECT_CHG 0x00010000
#define OHCI_PORT_ENABLE_CHG 0x00020000
#define OHCI_PORT_SUSPEND_CHG 0x00040000
#define OHCI_PORT_OCI_CHG 0x00080000
#define OHCI_PORT_RESET_CHG 0x00100000
#define OHCI_CLR_PORT_ENABLE 0x00000001
#define OHCI_SET_PORT_ENABLE 0x00000002
#define OHCI_SET_PORT_SUSPEND 0x00000004
#define OHCI_CLR_PORT_SUSPEND 0x00000008
#define OHCI_SET_PORT_RESET 0x00000010
#define OHCI_SET_PORT_POWER 0x00000100
#define OHCI_CLR_PORT_POWER 0x00000200
// Endpoint Descriptor data structure
#define OHCI_ED_FA 0x0000007f // Function Address
#define OHCI_ED_EN 0x00000780 // Endpoint Number
#define OHCI_ED_DIR 0x00001800 // Direction
#define OHCI_ED_DIR_TD 0x00000000 // Direction is From TD
#define OHCI_ED_DIR_OUT 0x00000800 // Direction is OUT
#define OHCI_ED_DIR_IN 0x00001000 // Direction is IN
#define OHCI_ED_SPD 0x00002000 // Speed
#define OHCI_ED_SPD_FULL 0x00000000 // Speed is Full Speed
#define OHCI_ED_SPD_LOW 0x00002000 // Speed is Low Speed
#define OHCI_ED_SKIP 0x00004000 // Skip
#define OHCI_ED_FMT 0x00008000 // Format
#define OHCI_ED_FMT_GEN 0x00000000 // Format is General TD
#define OHCI_ED_FMT_ISO 0x00008000 // Format is Isochronous TD
#define OHCI_ED_MPS 0x07ff0000 // Max Packet Size
#define OHCI_ED_HALTED 0x00000001 // Halted flag
#define OHCI_ED_TOGGLE 0x00000002 // Toggle carry bit
// Transfer Descriptor data structure
#define OHCI_TD_BR 0x00040000 // Buffer Rounding
#define OHCI_TD_DP 0x00180000 // Direction/PID
#define OHCI_TD_DP_SETUP 0x00000000 // Direction/PID is SETUP
#define OHCI_TD_DP_OUT 0x00080000 // Direction/PID is OUT
#define OHCI_TD_DP_IN 0x00100000 // Direction/PID is IN
#define OHCI_TD_DI 0x00e00000 // Delay Interrupt
#define OHCI_TD_DI_NO_DLY 0x00000000 // Delay Interrupt is 0 (no delay)
#define OHCI_TD_DI_NO_INT 0x00e00000 // Delay Interrupt is 7 (no interrupt)
#define OHCI_TD_DT 0x03000000 // Data Toggle
#define OHCI_TD_DT_0 0x00000000 // Data Toggle LSB is 0
#define OHCI_TD_DT_1 0x01000000 // Data Toggle LSB is 1
#define OHCI_TD_DT_USE_ED 0x00000000 // Data Toggle MSB is 0
#define OHCI_TD_DT_USE_TD 0x02000000 // Data Toggle MSB is 1
#define OHCI_TD_EC 0x0c000000 // Error Count
#define OHCI_TD_CC 0xf0000000 // Condition Code
#define OHCI_TD_CC_NO_ERR 0x00000000 // Condition Code is No Error
#define OHCI_TD_CC_NEW 0xe0000000 // Condition Code is Not Accessed
// Miscellaneous values
#define OHCI_MAX_INTERVAL 32
// 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_KC_BUFFER_SIZE 8 // keycodes
#define MILLISEC 1000 // in microseconds
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
// Register sets defined by the OHCI specification.
typedef volatile struct {
uint32_t revision;
uint32_t control;
uint32_t command_status;
uint32_t interrupt_status;
uint32_t interrupt_enable;
uint32_t interrupt_disable;
uint32_t hcca;
uint32_t period_current_ed;
uint32_t ctrl_head_ed;
uint32_t ctrl_current_ed;
uint32_t bulk_head_ed;
uint32_t bulk_current_ed;
uint32_t done_head;
uint32_t fm_interval;
uint32_t fm_remaining;
uint32_t fm_number;
uint32_t periodic_start;
uint32_t ls_threshold;
uint32_t rh_descriptor_a;
uint32_t rh_descriptor_b;
uint32_t rh_status;
uint32_t rh_port_status[];
} ohci_op_regs_t;
// Data structures defined by the OHCI specification.
typedef volatile struct {
uint32_t intr_head_ed[32];
uint16_t frame_num;
uint16_t pad;
uint32_t done_head;
uint32_t reserved[30];
} ohci_hcca_t __attribute__ ((aligned (256)));
typedef volatile struct {
uint32_t control;
uint32_t tail_ptr;
uint32_t head_ptr;
uint32_t next_ed;
} ohci_ed_t __attribute__ ((aligned (16)));
typedef volatile struct {
uint32_t control;
uint32_t curr_buff;
uint32_t next_td;
uint32_t buff_end;
} ohci_td_t __attribute__ ((aligned (16)));
// 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];
// Keyboad data transfer buffers.
hid_kbd_rpt_t kbd_rpt[MAX_KEYBOARDS];
// Pointer to the host controller registers.
ohci_op_regs_t *op_regs;
// Circular buffer for received keycodes.
uint8_t kc_buffer[WS_KC_BUFFER_SIZE];
int kc_index_i;
int kc_index_o;
} workspace_t __attribute__ ((aligned (256)));
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
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(&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(&op_regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG);
return true;
}
static ohci_td_t *get_ohci_done_head(const workspace_t *ws)
{
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(&op_regs->interrupt_status, OHCI_INTR_WDH);
return (ohci_td_t *)done_head;
}
static bool wait_for_ohci_done(const workspace_t *ws, int td_expected)
{
int td_completed = 0;
// 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);
}
if (td_completed == td_expected) break;
usleep(10);
}
return true;
}
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;
td->buff_end = (uintptr_t)buffer + length - 1;
td->next_td = (uintptr_t)(td + 1);
}
static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, const ohci_td_t *head_td, const ohci_td_t *tail_td)
{
// Set the skip flag before modifying the head and tail pointers, in case we are modifying an active ED.
// Use write32() to make sure the compiler doesn't reorder the writes.
write32(&ed->control, OHCI_ED_SKIP);
ed->head_ptr = (uintptr_t)head_td;
ed->tail_ptr = (uintptr_t)tail_td;
write32(&ed->control, control);
}
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
| ed_speed
| ep->max_packet_size << 16
| ep->endpoint_num << 7
| ep->device_id;
return control;
}
//------------------------------------------------------------------------------
// 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]);
write32(&ws->op_regs->command_status, OHCI_CMD_CLF);
return wait_for_ohci_done(ws, 2);
}
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);
build_ohci_ed(&ws->ed[0], ohci_ed_control(ep), &ws->td[0], &ws->td[3]);
write32(&ws->op_regs->command_status, OHCI_CMD_CLF);
return wait_for_ohci_done(ws, 3);
}
static uint8_t get_keycode(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ohci_td_t *td = get_ohci_done_head(ws);
while (td != NULL) {
int kbd_idx = td - ws->td - 3;
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
if ((td->control & OHCI_TD_CC) == OHCI_TD_CC_NO_ERR) {
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;
}
}
}
ohci_td_t *next_td = (ohci_td_t *)((uintptr_t)td->next_td);
ohci_ed_t *ed = &ws->ed[1 + kbd_idx];
build_ohci_td(td, td->control & ~OHCI_TD_CC, kbd_rpt, sizeof(hid_kbd_rpt_t));
build_ohci_ed(ed, ed->control, td+0, td+1);
td = next_td;
}
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;
}
//------------------------------------------------------------------------------
// 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 frame interval set by the SMM or BIOS.
// If not set, use the default value.
uint32_t frame_interval = read32(&op_regs->fm_interval) & 0x3fff;
if (frame_interval == 0) {
frame_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.
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);
// Some controllers ignore writes to these registers when in suspend state, so write them now.
uint32_t max_packet_size = ((frame_interval - 210) * 6) / 7;
uint32_t frame_interval_toggle = (read32(&op_regs->fm_interval) & OHCI_FIT) ^ OHCI_FIT;
write32(&op_regs->fm_interval, frame_interval_toggle | max_packet_size << 16 | frame_interval);
write32(&op_regs->periodic_start, (frame_interval * 9) / 10);
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;
uint32_t intr_head_ed = 0;
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 = intr_head_ed;
intr_head_ed = (uintptr_t)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] = intr_head_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;
}