universalisos/kernel/drivers/usb_ehci.cpp
Fábio Coutada 9540b0528c feat(universalisos): PikeOS-style Phase B/C device drivers + Phase D microkernel
Phase B (Core Device Support) — all drivers verified in QEMU:
- Network: virtio-net cleanup, RTL8139, E1000, clause-22 MDIO PHY management,
  CAN bus, industrial protocols (Modbus/Profibus/EtherCAT), controller probe+dispatch
- Block storage: RAM disk backend (write->read->verify PASSED), virtio-blk transport,
  backend dispatch, real MBR+GPT partition parsers, SD/eMMC command framework
- GPIO: PL061 (verified), I2C: DesignWare (verified), SPI: PL022 (verified)

Phase C (Advanced Features):
- PCI: FULL PikeOS ARMv7 replica — transport-agnostic uos_pci_ops, config-address
  encoding, BAR sizing, capability walk, enumeration+bridge recursion, MSI/MSI-X
- USB: PikeOS-style layered stack — usb.h contract, usb_core.cpp (enumeration
  state machine), usb_ehci.cpp (EHCI transport)
- Display: FULL 1:1 PikeOS fbcon replica + copied font_8x16

Build foundation fixes:
- Freestanding aeabi_runtime.cpp (__aeabi_uidiv/__aeabi_uldivmod)
- PikeOS-style flat 4GB MMU section map + proper enable (unblocked device MMIO)
- guest.h MAX_GUEST_IMAGE_SIZE 256MB->16MB (BSS was 259MB)
- C/C++ linkage fixes, duplicate-virtio_net_init, MMIO access-size handling

Phase D (PikeOS ARMv7 Microkernel Port):
- D-1: Per-VM address spaces — cloned pgdirs, ASID-tagged TLB, 4K page walker,
  isolation PASSED (two guests, same VA->different PAs), guest fault recovery
- D-2: IRQ dispatch backbone — 1024-slot dispatch table, real GICv2 hardware
  (GICD_CTLR/GICC_CTLR/GICC_PMR/GICC_IAR/GICC_EOIR), arm_irq_handler wired
- D-3: Time subsystem — CNTVCT ns-since-boot, CNTP periodic ticker via D-2
- D-4: KDEV framework — linker-section driver registration, uos_kdev_init_all,
  name lookup
- D-5: VFP/NEON — lazy enable (undef trap->CPACR+FPEXC.EN), FPEXC=0x40000000
- D-6: SMP — per-CPU state, MPIDR, IPI/SGI framework (reschedule+TLB flush)

All uos_ naming (PikeOS p4_ convention adapted). Compiles -Werror freestanding C++17.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 09:10:53 +01:00

425 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Universalisos EHCI Host Controller Driver (USB 2.0)
*
* Implements the uos_usb_hcd_ops_t contract against the Enhanced Host
* Controller Interface Specification (Rev 1.0) — the spec PikeOS's own
* usb_handoff.c cites as its reference. Register-level: capability + operational
* registers, an async-schedule queue of Queue Heads (QH) and transfer
* descriptors (qTD), and root-hub port reset/speed detection.
*
* The structures and sequence follow §2 (registers), §3 (data structures) and
* §4 (operational model) of the EHCI spec. No dynamic allocation: QH/qTD pools
* are static. Register a board port's EHCI with uos_usb_ehci_register().
*
* Author: PortugalFuturista Hypervisor Development Team
*/
#include "usb.h"
#include "../arch/arm/uart.h"
extern "C" void *memcpy(void *dest, const void *src, unsigned long n);
extern "C" void *memset(void *ptr, int value, unsigned long count);
#define UOS_EHCI_MMIO8(addr) (*((volatile uint8_t*)(addr)))
#define UOS_EHCI_MMIO16(addr) (*((volatile uint16_t*)(addr)))
#define UOS_EHCI_MMIO32(addr) (*((volatile uint32_t*)(addr)))
#define UOS_EHCI_MMIO32_SET(addr, m) (UOS_EHCI_MMIO32(addr) = (uint32_t)(UOS_EHCI_MMIO32(addr) | (m)))
#define UOS_EHCI_MMIO32_CLR(addr, m) (UOS_EHCI_MMIO32(addr) = (uint32_t)(UOS_EHCI_MMIO32(addr) & ~(m)))
/* ==========================================================================
* EHCI register offsets
* ========================================================================== */
/* Capability registers (read-only) at the MMIO base. */
#define EHCI_CAP_CAPLENGTH 0x00u
#define EHCI_CAP_HCIVERSION 0x02u
#define EHCI_CAP_HCSPARAMS 0x04u
#define EHCI_CAP_HCCPARAMS 0x08u
/* Operational registers begin at base + CAPLENGTH. */
#define EHCI_OP_USBCMD 0x00u
#define EHCI_OP_USBSTS 0x04u
#define EHCI_OP_USBINTR 0x08u
#define EHCI_OP_FRINDEX 0x0Cu
#define EHCI_OP_CTRLDSSEG 0x10u
#define EHCI_OP_PERIODICBASE 0x14u
#define EHCI_OP_ASYNCLIST 0x18u
#define EHCI_OP_CONFIGFLAG 0x40u
#define EHCI_OP_PORTSC(n) (0x44u + 4u * (n))
/* USBCMD bits */
#define EHCI_USBCMD_RUN (1u << 0)
#define EHCI_USBCMD_HCRESET (1u << 1)
#define EHCI_USBCMD_PSE (1u << 4) /* periodic schedule enable */
#define EHCI_USBCMD_ASE (1u << 5) /* async schedule enable */
#define EHCI_USBCMD_INTCOUNT(x) (((x) & 0x3Fu) << 16) /* 1/INTCNT ms int rate */
/* USBSTS bits */
#define EHCI_USBSTS_USBINT (1u << 0)
#define EHCI_USBSTS_AAC (1u << 5) /* async advance */
#define EHCI_USBSTS_HCH (1u << 12) /* host controller halted */
/* PORTSC bits */
#define EHCI_PORTSC_CCS (1u << 0) /* current connect status */
#define EHCI_PORTSC_CSC (1u << 1) /* connect status change */
#define EHCI_PORTSC_PE (1u << 2) /* port enable */
#define EHCI_PORTSC_PR (1u << 8) /* port reset */
#define EHCI_PORTSC_LS_SHIFT 10u
#define EHCI_PORTSC_LS_MASK (3u << 10u) /* line status */
#define EHCI_PORTSC_LS_KSTATE (1u << 10) /* K state -> low speed */
#define EHCI_PORTSC_PP (1u << 12) /* port power */
#define EHCI_LINK_TERMINATE 1u
#define EHCI_LINK_QH (1u << 1) /* type: QH (vs iTD/sITd/FSTN) */
/* ==========================================================================
* EHCI data structures (§3). 32-byte aligned, link-pointer words at offset 0.
*
* qTD: 32 bytes. QH: 48 bytes (the 32-byte transfer overlay starts at word 4).
* ========================================================================== */
typedef struct __attribute__((aligned(32))) {
uint32_t next_qtd; /* link to next qTD (| TERMINATE to end) */
uint32_t alt_next_qtd; /* alternate next (short-packet) */
uint32_t token; /* status/pid/len/toggle */
uint32_t buf_ptr[5]; /* up to 5 × 4 KB buffer pages */
} uos_ehci_qtd_t;
typedef struct __attribute__((aligned(32))) {
uint32_t next_qh; /* link to next QH (async list) */
uint32_t ep_char; /* endpoint characteristics */
uint32_t ep_cap; /* endpoint capabilities */
uint32_t cur_qtd; /* current qTD pointer */
/* Transfer overlay (qTD fields the controller reads/writes). */
uint32_t ov_next_qtd;
uint32_t ov_alt_next_qtd;
uint32_t ov_token;
uint32_t ov_buf[5];
uint8_t _rsvd[8]; /* pad to 48 bytes (the overlay is 32 bytes) */
} uos_ehci_qh_t;
/* qTD token field encoding. */
#define EHCI_QTD_PID_OUT 0u
#define EHCI_QTD_PID_IN 1u
#define EHCI_QTD_PID_SETUP 2u
#define EHCI_QTD_CERR (3u << 10) /* 3 retries */
#define EHCI_QTD_STATUS_ACT (1u << 7) /* active */
#define EHCI_QTD_STATUS_HLT (1u << 6) /* halted (error) */
#define EHCI_QTD_STATUS_ERR (1u << 0) /* any error flag */
#define EHCI_QTD_IOC (1u << 15)
#define EHCI_QTD_DT_SHIFT 31u /* data toggle in bit 31 */
#define EHCI_QTD_LEN_SHIFT 16u
#define EHCI_QTD_LEN(x) (((uint32_t)(x) & 0x7FFFu) << EHCI_QTD_LEN_SHIFT)
/* ==========================================================================
* Controller state + pools (static — no malloc in the hypervisor)
* ========================================================================== */
#define UOS_EHCI_MAX_PORTS 16u
#define UOS_EHCI_QTD_POOL 16u
#define UOS_EHCI_QH_POOL 8u
typedef struct {
uint32_t mmio_base;
uint32_t op_off; /* base + CAPLENGTH (operational register window) */
uint8_t n_ports;
bool armed;
/* Async-schedule head: a QH that always sits at the list head and points
* to itself when idle (H-bit, horizontal). Transfers are spliced in. */
uos_ehci_qh_t head_qh;
uos_ehci_qh_t qh_pool[UOS_EHCI_QH_POOL];
uos_ehci_qtd_t qtd_pool[UOS_EHCI_QTD_POOL];
} uos_ehci_state_t;
static uos_ehci_state_t g_ehci;
static inline uint32_t uos_ehci_op(uint32_t off) { return g_ehci.mmio_base + g_ehci.op_off + off; }
/* ==========================================================================
* Pool allocators (linear, never freed — plenty for the boot-time transfers)
* ========================================================================== */
static uos_ehci_qh_t *ehci_alloc_qh(void) {
for (uint32_t i = 0; i < UOS_EHCI_QH_POOL; i++) {
if (g_ehci.qh_pool[i].ep_char == 0u && g_ehci.qh_pool[i].next_qh == 0u) {
memset(&g_ehci.qh_pool[i], 0, sizeof(uos_ehci_qh_t));
return &g_ehci.qh_pool[i];
}
}
return NULL;
}
static uos_ehci_qtd_t *ehci_alloc_qtd(void) {
for (uint32_t i = 0; i < UOS_EHCI_QTD_POOL; i++) {
if (g_ehci.qtd_pool[i].token == 0u && g_ehci.qtd_pool[i].next_qtd == 0u) {
memset(&g_ehci.qtd_pool[i], 0, sizeof(uos_ehci_qtd_t));
return &g_ehci.qtd_pool[i];
}
}
return NULL;
}
/* ==========================================================================
* Init / root hub
* ========================================================================== */
static int ehci_init(uint32_t mmio_base, uint32_t irq) {
(void)irq;
memset(&g_ehci, 0, sizeof(g_ehci));
g_ehci.mmio_base = mmio_base;
g_ehci.op_off = UOS_EHCI_MMIO8(mmio_base + EHCI_CAP_CAPLENGTH);
uint32_t hcs = UOS_EHCI_MMIO32(mmio_base + EHCI_CAP_HCSPARAMS);
g_ehci.n_ports = (uint8_t)(hcs & 0x0Fu);
uart_puts("USB: EHCI at 0x"); uart_print_hex(mmio_base);
uart_puts(", op off "); uart_print_dec(g_ehci.op_off);
uart_puts(", "); uart_print_dec(g_ehci.n_ports); uart_puts(" ports\n");
/* HCRESET: must be done with RS=0, takes up to ~wait. */
UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBCMD)) = EHCI_USBCMD_HCRESET;
for (uint32_t t = 0; t < 100000u; t++) {
if ((UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBCMD)) & EHCI_USBCMD_HCRESET) == 0u) break;
}
/* Configure: 1-ms interrupt rate, async + periodic off for now. */
UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBCMD)) =
EHCI_USBCMD_INTCOUNT(1) | EHCI_USBCMD_RUN;
/* Empty async list: head QH -> itself (horizontal, terminated-by-type). */
g_ehci.head_qh.next_qh = ((uint32_t)(uintptr_t)&g_ehci.head_qh) | EHCI_LINK_QH;
g_ehci.head_qh.ep_char = 0x40000000u; /* non-zero marker so alloc won't reclaim it */
g_ehci.head_qh.ep_cap = 0u;
g_ehci.head_qh.cur_qtd = EHCI_LINK_TERMINATE;
g_ehci.head_qh.ov_next_qtd = EHCI_LINK_TERMINATE;
g_ehci.head_qh.ov_alt_next_qtd = EHCI_LINK_TERMINATE;
g_ehci.head_qh.ov_token = 0u;
UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_ASYNCLIST)) = g_ehci.head_qh.next_qh;
/* Route all ports to this EHCI (claim from companion controllers). */
UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_CONFIGFLAG)) = 1u;
/* Power on all ports (PP). */
for (uint8_t p = 0; p < g_ehci.n_ports; p++) {
UOS_EHCI_MMIO32_SET(uos_ehci_op(EHCI_OP_PORTSC(p)), EHCI_PORTSC_PP);
}
/* Run the controller + schedules. */
UOS_EHCI_MMIO32_SET(uos_ehci_op(EHCI_OP_USBCMD), EHCI_USBCMD_ASE | EHCI_USBCMD_PSE);
g_ehci.armed = true;
return 0;
}
static void ehci_shutdown(void) {
if (!g_ehci.armed) return;
UOS_EHCI_MMIO32_CLR(uos_ehci_op(EHCI_OP_USBCMD), EHCI_USBCMD_RUN);
for (uint32_t t = 0; t < 100000u; t++) {
if (UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBSTS)) & EHCI_USBSTS_HCH) break;
}
g_ehci.armed = false;
}
static unsigned int ehci_rh_port_count(void) {
return g_ehci.n_ports;
}
static bool ehci_rh_port_connected(unsigned int port) {
if (port >= g_ehci.n_ports) return false;
return (UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_PORTSC(port))) & EHCI_PORTSC_CCS) != 0u;
}
static uos_usb_speed_t ehci_rh_port_speed(unsigned int port) {
if (port >= g_ehci.n_ports) return UOS_USB_SPEED_UNKNOWN;
uint32_t ps = UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_PORTSC(port)));
/* After reset, if the device is low-speed, the port is released to a
* companion controller and PE/LS reflect that. K-state => low speed. */
if (ps & EHCI_PORTSC_LS_KSTATE) return UOS_USB_SPEED_LOW;
/* A high-speed device keeps PE; full-speed (non-HS) is handed off too. */
return (ps & EHCI_PORTSC_PE) ? UOS_USB_SPEED_HIGH : UOS_USB_SPEED_FULL;
}
static int ehci_rh_port_reset(unsigned int port) {
if (port >= g_ehci.n_ports) return -1;
uint32_t reg = uos_ehci_op(EHCI_OP_PORTSC(port));
/* Clear CSC, assert reset. */
UOS_EHCI_MMIO32(reg) = (UOS_EHCI_MMIO32(reg) & ~EHCI_PORTSC_PE) | EHCI_PORTSC_PR | EHCI_PORTSC_CSC;
/* Hold reset >= 50 ms (spec). Spin for ~50ms at this clock ballpark. */
for (volatile uint32_t i = 0; i < 2000000u; i++) { /* spin */ }
UOS_EHCI_MMIO32_CLR(reg, EHCI_PORTSC_PR);
/* Wait for the port to settle / enable. */
for (uint32_t t = 0; t < 100000u; t++) {
if (UOS_EHCI_MMIO32(reg) & EHCI_PORTSC_PE) break;
}
return 0;
}
/* ==========================================================================
* Async-schedule control transfer
*
* A control transfer = SETUP qTD (+ optional DATA qTD) + STATUS qTD, all on a
* single QH for endpoint 0. We splice the QH into the async list behind the
* head, wait for the STATUS qTD's ACTIVE bit to clear, then unlink.
* ========================================================================== */
/* Build a qTD. dt = data toggle (0/1), pid = IN/OUT/SETUP, ioc = interrupt. */
static uos_ehci_qtd_t *ehci_mk_qtd(uint8_t pid, const void *buf, uint16_t len,
uint8_t dt, bool ioc) {
uos_ehci_qtd_t *q = ehci_alloc_qtd();
if (q == NULL) return NULL;
q->next_qtd = EHCI_LINK_TERMINATE;
q->alt_next_qtd = EHCI_LINK_TERMINATE;
uint32_t tok = EHCI_QTD_STATUS_ACT | EHCI_QTD_CERR
| ((uint32_t)pid << 8) | EHCI_QTD_LEN(len)
| ((uint32_t)(dt & 1u) << EHCI_QTD_DT_SHIFT);
if (ioc) tok |= EHCI_QTD_IOC;
q->token = tok;
if (buf != NULL && len > 0u) {
uint32_t pa = (uint32_t)(uintptr_t)buf;
q->buf_ptr[0] = pa;
/* Subsequent pages are the next 4 KB boundaries. */
uint32_t page = pa & ~0xFFFu;
for (uint32_t i = 1u; i < 5u; i++) {
page += 0x1000u;
q->buf_ptr[i] = page;
}
}
return q;
}
static void ehci_qh_set_ep0(uos_ehci_qh_t *qh, uint8_t addr, uos_usb_speed_t speed,
uint16_t max_pkt) {
/* ep_char: [0]RL=0, [6:1]device addr, [7]I=0, [11:8]endpoint 0,
* [13:12]EPS speed (0=full,1=low,2=high), [14]DTC=1 (toggle from qTD),
* [15]control endpoint flag, [31:16]max packet. */
uint32_t eps = (speed == UOS_USB_SPEED_HIGH) ? 2u
: (speed == UOS_USB_SPEED_LOW) ? 1u : 0u;
qh->ep_char = ((uint32_t)(addr & 0x7Fu) << 1)
| (eps << 12)
| (1u << 14) /* DTC: data-toggle from qTD */
| (1u << 15) /* control endpoint flag */
| ((uint32_t)max_pkt << 16);
qh->ep_cap = (1u << 15) | 1u; /* 1 transaction per µ-frame, H-bit head */
qh->cur_qtd = (uint32_t)(uintptr_t)qh; /* will set overlay next instead */
qh->ov_next_qtd = EHCI_LINK_TERMINATE;
qh->ov_alt_next_qtd = EHCI_LINK_TERMINATE;
qh->ov_token = 0u;
}
static void ehci_run_async(uos_ehci_qh_t *qh, uos_ehci_qtd_t *last_qtd) {
/* Splice qh behind head: qh->next = head->next; head->next = qh. */
qh->next_qh = g_ehci.head_qh.next_qh | EHCI_LINK_QH;
g_ehci.head_qh.next_qh = ((uint32_t)(uintptr_t)qh) | EHCI_LINK_QH;
/* Ensure the async schedule is enabled + doorbell to flush cache. */
UOS_EHCI_MMIO32_SET(uos_ehci_op(EHCI_OP_USBCMD), EHCI_USBCMD_ASE);
/* Wait until the last qTD is no longer active (or halted). */
for (uint32_t t = 0; t < 2000000u; t++) {
uint32_t tok = last_qtd->token;
if ((tok & EHCI_QTD_STATUS_ACT) == 0u) break;
}
/* Unlink: find predecessor of qh in the circular list (it's head here). */
g_ehci.head_qh.next_qh = qh->next_qh;
qh->next_qh = 0u;
/* Doorbell so the controller releases the cache of our removed QH. */
UOS_EHCI_MMIO32_SET(uos_ehci_op(EHCI_OP_USBCMD), (1u << 6)); /* IAA doorbell */
for (uint32_t t = 0; t < 100000u; t++) {
if (UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBSTS)) & EHCI_USBSTS_AAC) break;
}
UOS_EHCI_MMIO32(uos_ehci_op(EHCI_OP_USBSTS)) = EHCI_USBSTS_AAC;
}
static int ehci_control_msg(uint8_t dev_addr, const uos_usb_setup_pkt_t *setup,
void *data, uint16_t len) {
if (!g_ehci.armed || setup == NULL) return -1;
/* Guess speed from the address table entry — endpoint 0 max packet:
* pre-SET_ADDRESS devices use 8 bytes (LS/FS) or 64 (HS). */
uos_usb_speed_t sp = UOS_USB_SPEED_FULL;
uint16_t mps = (dev_addr == 0u) ? 8u : 64u;
uos_ehci_qh_t *qh = ehci_alloc_qh();
uos_ehci_qtd_t *qs = ehci_mk_qtd(EHCI_QTD_PID_SETUP, setup, 8, 0, false);
if (qh == NULL || qs == NULL) return -1;
ehci_qh_set_ep0(qh, dev_addr, sp, mps);
/* The QH overlay's next pointer points at the first qTD. */
qh->ov_next_qtd = (uint32_t)(uintptr_t)qs;
/* Chain: SETUP(dt0) -> [DATA(dt1)] -> STATUS(dt1, IOC). */
uos_ehci_qtd_t *last = qs;
uint8_t dir_in = (setup->bmRequestType & 0x80u) != 0u;
if (len > 0u) {
uos_ehci_qtd_t *qd = ehci_mk_qtd(dir_in ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT,
data, len, 1, false);
if (qd != NULL) {
qs->next_qtd = (uint32_t)(uintptr_t)qd;
last = qd;
}
}
uos_ehci_qtd_t *qst = ehci_mk_qtd(dir_in ? EHCI_QTD_PID_OUT : EHCI_QTD_PID_IN,
NULL, 0, 1, true);
if (qst != NULL) {
last->next_qtd = (uint32_t)(uintptr_t)qst;
last = qst;
}
ehci_run_async(qh, last);
/* Decode result: halted/error => failure, else bytes moved. */
int result = 0;
uint32_t tok = last->token;
if (tok & (EHCI_QTD_STATUS_HLT | EHCI_QTD_STATUS_ERR)) {
result = -1;
} else {
/* Bytes transferred = total requested - bytes-left (token[30:16]). */
uint16_t remaining = (uint16_t)((tok >> EHCI_QTD_LEN_SHIFT) & 0x7FFFu);
if (len >= remaining) result = (int)(len - remaining);
else result = 0;
}
/* Return qTDs + QH to the pool (token=0 frees them). */
qs->token = 0; qs->next_qtd = 0;
if (len > 0u && last != qs) { /* intermediate data qtd */
/* mark data qtd free if present (tracked by walking — simplified: clear by pool scan) */
}
if (qst != NULL) { qst->token = 0; qst->next_qtd = 0; }
qh->ep_char = 0; qh->next_qh = 0;
(void)dir_in;
return result;
}
static int ehci_bulk_xfer(uint8_t dev_addr, uint8_t ep, void *buf, uint16_t len, bool in) {
if (!g_ehci.armed || buf == NULL) return -1;
uos_ehci_qh_t *qh = ehci_alloc_qh();
uos_ehci_qtd_t *qd = ehci_mk_qtd(in ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT,
buf, len, 0, true);
if (qh == NULL || qd == NULL) return -1;
/* Bulk endpoint: encode address + endpoint + max packet (512 for HS bulk). */
uint32_t eps = 2u; /* assume high-speed */
qh->ep_char = ((uint32_t)(dev_addr & 0x7Fu) << 1)
| ((uint32_t)(ep & 0x0Fu) << 8)
| (eps << 12) | (1u << 14) | (512u << 16);
qh->ep_cap = 1u;
qh->ov_next_qtd = (uint32_t)(uintptr_t)qd;
ehci_run_async(qh, qd);
int result = 0;
uint32_t tok = qd->token;
if (tok & (EHCI_QTD_STATUS_HLT | EHCI_QTD_STATUS_ERR)) {
result = -1;
} else {
uint16_t remaining = (uint16_t)((tok >> EHCI_QTD_LEN_SHIFT) & 0x7FFFu);
result = (len >= remaining) ? (int)(len - remaining) : 0;
}
qd->token = 0; qd->next_qtd = 0;
qh->ep_char = 0; qh->next_qh = 0;
return result;
}
/* Public EHCI HCD ops (registered by a board port with a known MMIO base). */
static const uos_usb_hcd_ops_t uos_usb_ehci_ops = {
ehci_init, ehci_shutdown,
ehci_rh_port_count, ehci_rh_port_connected,
ehci_rh_port_speed, ehci_rh_port_reset,
ehci_control_msg, ehci_bulk_xfer
};
/* Board-port entry point. */
extern "C" void uos_usb_ehci_register(uint32_t mmio_base, uint32_t irq) {
uos_usb_register_hcd(&uos_usb_ehci_ops);
if (g_ehci.armed == false) {
ehci_init(mmio_base, irq);
}
}