Files
Astral/kernel-src/io/usb/ctrl/xhci.c
T
2026-05-12 22:04:13 -03:00

763 lines
22 KiB
C

#include <arch/cpu.h>
#include <errno.h>
#include <kernel/alloc.h>
#include <kernel/init.h>
#include <kernel/interrupt.h>
#include <kernel/pci.h>
#include <kernel/pmm.h>
#include <kernel/usb.h>
#include <kernel/vmm.h>
#include <list.h>
#include <logging.h>
#include <util.h>
#include <kernel/xhci.h>
static uint32_t xhci_trb_type(xhci_trb_t *trb) {
return (trb->dw3 >> 10) & 0x3f;
}
static void xhci_signal_event_thread(xhci_ctrl_t *xhci) {
semaphore_signal_limit(&xhci->event_sem, 1);
}
static void xhci_signal_callback_thread(xhci_ctrl_t *xhci) {
semaphore_signal_limit(&xhci->callback_sem, 1);
}
static void xhci_release_submission_page(xhci_submission_t *submission) {
uint32_t cmd_type = xhci_trb_type(&submission->cmd_trb);
if ((cmd_type == TRB_NORMAL || cmd_type == TRB_DATA_STAGE) && submission->cmd_trb.parameters != 0)
pmm_release((void *)(submission->cmd_trb.parameters & ~(PAGE_SIZE - 1)));
}
static bool xhci_reclaim_submission_slot(xhci_submission_t *slot, xhci_submission_t *submission) {
bool valid = __atomic_exchange_n(&slot->valid, false, __ATOMIC_SEQ_CST);
__assert(valid);
xhci_release_submission_page(submission);
xhci_ring_unreserve(submission->ring, 1);
return true;
}
static void xhci_reclaim_submission_chain(xhci_submission_t *submissionp) {
while (submissionp != NULL) {
if (!__atomic_load_n(&submissionp->valid, __ATOMIC_SEQ_CST))
return;
xhci_submission_t submission = *submissionp;
xhci_submission_t *next = submission.next;
xhci_reclaim_submission_slot(submissionp, &submission);
submissionp = next;
}
}
static void xhci_enqueue_completion(xhci_ctrl_t *xhci, xhci_completion_t *completion) {
semaphore_wait(&xhci->callback_space_sem, false);
MUTEX_ACQUIRE(&xhci->callback_mutex);
size_t written = ringbuffer_write(&xhci->callback_ring, completion, sizeof(*completion));
__assert(written == sizeof(*completion));
MUTEX_RELEASE(&xhci->callback_mutex);
xhci_signal_callback_thread(xhci);
}
static void handle_submission_complete(xhci_ctrl_t *xhci, xhci_submission_t *submissionp) {
xhci_submission_t submission = *submissionp;
uint32_t cmd_type = xhci_trb_type(&submission.cmd_trb);
if (cmd_type == TRB_SETUP_STAGE) {
if (submission.usb_status == USB_STATUS_SUCCESS)
return;
} else if (cmd_type == TRB_NORMAL || cmd_type == TRB_DATA_STAGE) {
// Check if we need to wait for more TRBs to complete.
if (submission.usb_status == USB_STATUS_SUCCESS && (cmd_type == TRB_DATA_STAGE || (submission.cmd_trb.dw3 & XHCI_TRB_DW3_CH) != 0))
return;
}
// Complete the transfer.
size_t xfer_len;
uint32_t status = (submission.event_trb.dw2 >> 24) & 0xff;
if (status == TRB_SUCCESS) {
xfer_len = submission.data_offset + submission.data_length;
} else if (status == TRB_SHORT_PACKET) {
size_t remainder = submission.event_trb.dw2 & 0xffffff;
size_t trb_xfer_len = submission.data_length - min(remainder, submission.data_length);
xfer_len = submission.data_offset + trb_xfer_len;
} else {
xfer_len = 0;
}
if (cmd_type == TRB_DATA_STAGE && submission.usb_status == USB_STATUS_SHORT_PACKET) {
// surgically edit the status stage trb to return a short packet status with the final length
__assert(submission.next != NULL);
submission.next->usb_status = USB_STATUS_SHORT_PACKET;
submission.next->data_offset = xfer_len;
return;
}
// for ENABLE_SLOT commands, we use xfer_len for the slot id
if ((submission.cmd_trb.dw3 & XHCI_TRB_DW3_TYPE_MASK) == XHCI_TRB_DW3_TYPE(TRB_ENABLE_SLOT))
xfer_len = (submission.event_trb.dw3 >> 24) & 0xff;
xhci_submission_t *td_head = submission.td_head != NULL ? submission.td_head : submissionp;
xhci_reclaim_submission_chain(td_head);
if (submission.callback) {
xhci_completion_t completion = {
.device = submission.device,
.callback = submission.callback,
.callback_ctx = submission.callback_ctx,
.status = submission.usb_status,
.transferred = xfer_len,
};
xhci_enqueue_completion(xhci, &completion);
}
}
static xhci_submission_t *xhci_pop_completed_submission(xhci_ctrl_t *xhci) {
long ipl = spinlock_acquire_raise_ipl(&xhci->event_lock, IPL_USB);
xhci_submission_t *submission = (xhci_submission_t *)list_pop_front(&xhci->completion_list);
spinlock_release_lower_ipl(&xhci->event_lock, ipl);
return submission;
}
static int xhci_pop_port_change(xhci_ctrl_t *xhci) {
long ipl = spinlock_acquire_raise_ipl(&xhci->event_lock, IPL_USB);
int port = bitmap_find_first_set(&xhci->port_change_bitmap);
if (port != -1)
bitmap_set(&xhci->port_change_bitmap, port, 0);
spinlock_release_lower_ipl(&xhci->event_lock, ipl);
return port;
}
static bool xhci_pop_completion(xhci_ctrl_t *xhci, xhci_completion_t *completion) {
bool ret = false;
MUTEX_ACQUIRE(&xhci->callback_mutex);
if (RINGBUFFER_DATACOUNT(&xhci->callback_ring) >= sizeof(*completion)) {
size_t read = ringbuffer_read(&xhci->callback_ring, completion, sizeof(*completion));
__assert(read == sizeof(*completion));
ret = true;
}
MUTEX_RELEASE(&xhci->callback_mutex);
if (ret)
semaphore_signal(&xhci->callback_space_sem);
return ret;
}
static void xhci_event_thread(void) {
xhci_ctrl_t *xhci = current_thread()->kernelarg;
for (;;) {
semaphore_wait(&xhci->event_sem, false);
for (;;) {
xhci_submission_t *submission = xhci_pop_completed_submission(xhci);
if (submission == NULL)
break;
handle_submission_complete(xhci, submission);
}
}
}
static void xhci_callback_thread(void) {
xhci_ctrl_t *xhci = current_thread()->kernelarg;
for (;;) {
semaphore_wait(&xhci->callback_sem, false);
for (;;) {
bool did_work = false;
for (;;) {
int port = xhci_pop_port_change(xhci);
if (port == -1)
break;
xhci_handle_port_change_event(xhci, port + 1);
did_work = true;
}
for (;;) {
xhci_completion_t completion;
if (!xhci_pop_completion(xhci, &completion))
break;
completion.callback(completion.device, completion.callback_ctx, completion.status, completion.transferred);
did_work = true;
}
if (!did_work)
break;
}
}
}
static void xhci_dpc(context_t *, dpcarg_t dpcarg) {
xhci_ctrl_t *xhci = dpcarg;
volatile xhci_ir_t *ir = &xhci->rtregs->ir[0];
// Process events from the event ring.
bool signal_event_thread = false;
bool signal_callback_thread = false;
xhci_trb_t *r_trb;
while ((r_trb = xhci_ring_dequeue(&xhci->event_ring)) != NULL) {
uint32_t type = (r_trb->dw3 >> 10) & 0x3f;
if (type == TRB_COMMAND_COMPLETION_EVENT || type == TRB_XFER_COMPLETION_EVENT) {
uint32_t status = (r_trb->dw2 >> 24) & 0xff;
usb_status_t usb_status;
switch (status) {
case TRB_SUCCESS:
usb_status = USB_STATUS_SUCCESS;
break;
case TRB_SHORT_PACKET:
usb_status = USB_STATUS_SHORT_PACKET;
break;
case TRB_DATA_BUFFER_ERROR:
case TRB_BABBLE_DETECTED:
case TRB_TRANSACTION_ERROR:
case TRB_RESOURCE_ERROR:
case TRB_BANDWIDTH_ERROR:
case TRB_NO_SLOTS_AVAILABLE:
case TRB_INVALID_STREAM_TYPE:
case TRB_SLOT_NOT_ENABLED:
case TRB_ENDPOINT_NOT_ENABLED:
case TRB_BANDWIDTH_OVERRUN_ERROR:
case TRB_CONTEXT_STATE_ERROR:
case TRB_NO_PING_RESPONSE:
case TRB_INCOMPATIBLE_DEVICE_ERROR:
case TRB_COMMAND_RING_STOPPED:
case TRB_COMMAND_RING_ABORTED:
case TRB_STOPPED:
case TRB_STOPPED_LEN:
case TRB_STOPPED_SHORT:
case TRB_LATENCY_TOO_LARGE:
case TRB_ERROR_RESERVED:
case TRB_ERROR_UNDEFINED:
case TRB_INVALID_STREAM_ID:
case TRB_SECONDARY_BANDWIDTH_ERROR:
case TRB_SPLIT_TRANSACTION_ERROR:
usb_status = USB_STATUS_ERROR;
break;
case TRB_STALL:
usb_status = USB_STATUS_STALL;
break;
case TRB_BUFFER_OVERRUN:
case TRB_RING_UNDERRUN:
case TRB_RING_OVERRUN:
case TRB_MISSED_SERVICE:
usb_status = USB_STATUS_FLOW_ERROR;
break;
default:
printf("xhci: bad event: %lu (driver bug!)\n", status);
_panic("Driver bug", NULL);
break;
}
xhci_ring_t *ring;
if (type == TRB_COMMAND_COMPLETION_EVENT) {
ring = &xhci->command_ring;
} else {
uint32_t slot_id = (r_trb->dw3 >> 24) & 0xff;
__assert(slot_id > 0 && slot_id <= xhci->slot_count);
xhci_device_t *dev = xhci->slots[slot_id - 1];
__assert(dev);
uint8_t ep_index = (r_trb->dw3 >> 16) & 0x1f;
__assert(ep_index > 0);
ring = &dev->ep_rings[ep_index - 1];
}
xhci_trb_t *trb = MAKE_HHDM(r_trb->parameters);
uintptr_t trb_addr = (uintptr_t)trb;
uintptr_t ring_start = (uintptr_t)ring->ring;
uintptr_t ring_end = (uintptr_t)&ring->ring[ring->size - 1];
__assert(trb_addr >= ring_start && trb_addr < ring_end);
__assert((trb_addr - ring_start) % sizeof(xhci_trb_t) == 0);
size_t trb_index = (trb_addr - ring_start) / sizeof(xhci_trb_t);
xhci_submission_t *sub = &ring->submissions[trb_index];
__assert(__atomic_load_n(&sub->valid, __ATOMIC_SEQ_CST));
memcpy(&sub->event_trb, r_trb, sizeof(xhci_trb_t));
if (sub->usb_status == USB_STATUS_SHORT_PACKET) {
// this is a status stage trb and the data stage returned a short packet.
// the offset has been surgically edited to the shorter length as to
// return the proper transferred length to the callback.
uint32_t original_cmd = xhci_trb_type(&sub->cmd_trb);
__assert(original_cmd == TRB_STATUS_STAGE);
} else {
sub->usb_status = usb_status;
}
spinlock_acquire(&xhci->event_lock);
list_push_back(&xhci->completion_list, &sub->list_node);
spinlock_release(&xhci->event_lock);
signal_event_thread = true;
} else if (type == TRB_PORT_STATUS_CHANGE_EVENT) {
uint8_t port_id = (r_trb->dw0 >> 24) & 0xff;
spinlock_acquire(&xhci->event_lock);
bitmap_set(&xhci->port_change_bitmap, port_id - 1, 1);
spinlock_release(&xhci->event_lock);
signal_callback_thread = true;
}
// Update the Event Ring Dequeue Pointer.
uint64_t event_ring_phys = (uint64_t)xhci->event_ring.ring_phys;
event_ring_phys += xhci->event_ring.index * sizeof(xhci_trb_t);
ir->erdp = event_ring_phys | XHCI_ERDP_EHB;
}
if (signal_event_thread)
xhci_signal_event_thread(xhci);
if (signal_callback_thread)
xhci_signal_callback_thread(xhci);
}
static void xhci_isr(isr_t *isr, context_t *ctx) {
xhci_ctrl_t *xhci = isr->priv;
// Check if there is an interrupt pending.
volatile xhci_ir_t *ir = &xhci->rtregs->ir[0];
uint32_t iman = ir->iman;
uint32_t usbsts = xhci->opregs->usbsts;
uint64_t erdp = ir->erdp;
if ((erdp & XHCI_ERDP_EHB) == 0 && (iman & XHCI_IMAN_IP) == 0 && (usbsts & XHCI_USBSTS_EINT) == 0)
return;
// Clear the interrupt pending bit.
if (iman & XHCI_IMAN_IP)
ir->iman |= XHCI_IMAN_IP;
if (usbsts & XHCI_USBSTS_EINT)
xhci->opregs->usbsts = XHCI_USBSTS_EINT;
dpc_enqueue(&xhci->dpc, xhci);
}
static int xhci_halt(xhci_ctrl_t *ctrl) {
if ((ctrl->opregs->usbcmd & XHCI_USBCMD_RS) == 0)
return 0;
ctrl->opregs->usbcmd &= ~XHCI_USBCMD_RS;
timespec_t start = timekeeper_timefromboot();
while ((ctrl->opregs->usbsts & XHCI_USBSTS_HCH) == 0) {
sched_sleep_us(10 * 1000); // 10ms
timespec_t now = timekeeper_timefromboot();
if (timespec_diffms(now, start) > 1000)
return -1;
}
return 0;
}
static int xhci_reset(xhci_ctrl_t *ctrl) {
ctrl->opregs->usbcmd |= XHCI_USBCMD_HCRST;
timespec_t start = timekeeper_timefromboot();
while ((ctrl->opregs->usbcmd & XHCI_USBCMD_HCRST) != 0 || (ctrl->opregs->usbsts & XHCI_USBSTS_CNR) != 0) {
sched_sleep_us(10 * 1000); // 10ms
timespec_t now = timekeeper_timefromboot();
if (timespec_diffms(now, start) > 1000)
return -1;
}
return 0;
}
static int xhci_run(xhci_ctrl_t *ctrl) {
if ((ctrl->opregs->usbcmd & XHCI_USBCMD_RS))
return 0;
ctrl->opregs->usbcmd |= XHCI_USBCMD_RS | XHCI_USBCMD_INTE;
timespec_t start = timekeeper_timefromboot();
while ((ctrl->opregs->usbsts & XHCI_USBSTS_HCH) != 0 || (ctrl->opregs->usbsts & XHCI_USBSTS_CNR) != 0) {
sched_sleep_us(10 * 1000); // 10ms
timespec_t now = timekeeper_timefromboot();
if (timespec_diffms(now, start) > 1000)
return -1;
}
return 0;
}
static volatile uint32_t *xhci_ext_caps(pcibar_t bar0) {
volatile xhci_caps_t *caps = (xhci_caps_t *)bar0.address;
uint32_t xecp = (caps->hccparams1 >> 16) & 0xffff;
if (xecp == 0)
return NULL;
return (uint32_t *)(bar0.address + (xecp << 2));
}
static int xhci_handoff(pcibar_t bar0) {
volatile uint32_t *ext_caps = xhci_ext_caps(bar0);
if (ext_caps == NULL)
return 0;
for (;;) {
uint8_t cap_id = *ext_caps & 0xff;
uint8_t next_cap_off = (*ext_caps >> 8) & 0xff;
if (cap_id == 1) {
printf("xhci: found BIOS/OS ownership capability\n");
bool is_bios_owned = (*ext_caps >> 16) & 0x1;
if (is_bios_owned) {
printf("xhci: attempting to take ownership from BIOS\n");
// Set OS Owned Semaphore.
*ext_caps |= (1 << 24);
timespec_t start = timekeeper_timefromboot();
while (is_bios_owned) {
sched_sleep_us(500000); // 500ms
is_bios_owned = (*ext_caps >> 16) & 0x1;
timespec_t now = timekeeper_timefromboot();
if (timespec_diffms(now, start) > 5000) {
printf("xhci: BIOS ownership handoff timeout\n");
return ETIMEDOUT;
}
}
printf("xhci: took ownership of controller from BIOS\n");
}
}
if (next_cap_off == 0)
break;
ext_caps += next_cap_off;
}
return 0;
}
static int xhci_ctrl_start(usb_ctrl_t *ctrl) {
int ret = 0;
xhci_ctrl_t *xhci = container_of(ctrl, xhci_ctrl_t, ctrl);
pcibar_t bar0 = pci_getbar(xhci->pci_enum, 0);
ret = xhci_halt(xhci);
if (ret != 0) {
printf("xhci: controller halt timeout\n");
return ret;
}
ret = xhci_reset(xhci);
if (ret != 0) {
printf("xhci: controller reset timeout\n");
return ret;
}
// Enable MSI interrupts.
size_t intcount;
if (xhci->pci_enum->msix.exists) {
intcount = pci_initmsix(xhci->pci_enum);
} else if (xhci->pci_enum->msi.exists) {
intcount = pci_initmsi(xhci->pci_enum, 1);
} else {
printf("xhci: controller does not support MSI or MSI-X\n");
return ENODEV;
}
__assert(intcount > 0);
isr_t *isr = interrupt_allocate(xhci_isr, ARCH_EOI, IPL_USB);
__assert(isr != NULL);
isr->priv = ctrl;
if (xhci->pci_enum->msix.exists) {
pci_msixadd(xhci->pci_enum, 0, INTERRUPT_IDTOVECTOR(isr->id), 1, 0);
pci_msixsetmask(xhci->pci_enum, 0);
} else {
pci_msisetbase(xhci->pci_enum, INTERRUPT_IDTOVECTOR(isr->id), 1, 0);
pci_msisetmask(xhci->pci_enum, 0, 0);
}
uint32_t max_slots = xhci->caps->hcsparams1 & 0xff;
uint32_t max_ports = (xhci->caps->hcsparams1 >> 24) & 0xff;
printf("xhci: controller supports %u slots and %u ports\n", max_slots, max_ports);
xhci->port_count = max_ports;
xhci->ports = alloc(sizeof(xhci_port_protocol_t) * max_ports);
__assert(xhci->ports);
__assert(bitmap_init(&xhci->port_change_bitmap, max_ports) == 0);
for (uint32_t i = 0; i < max_ports; i++) {
xhci->ports[i].ver_major = 0;
xhci->ports[i].ver_minor = 0;
}
xhci->slot_count = max_slots;
xhci->slots = alloc(sizeof(xhci_device_t *) * max_slots);
__assert(xhci->slots);
xhci->ctx_stride = (xhci->caps->hccparams1 & XHCI_HCCPARAMS1_CSZ) != 0 ? 64 : 32;
volatile uint32_t *ext_caps = xhci_ext_caps(bar0);
if (ext_caps == NULL) {
printf("xhci: controller has no extended capabilities\n");
return ENODEV;
}
for (;;) {
uint8_t cap_id = *ext_caps & 0xff;
uint8_t next_cap_off = (*ext_caps >> 8) & 0xff;
if (cap_id == 2) {
// map ports to usb types
uint8_t ver_major = (*ext_caps >> 24) & 0xff;
uint8_t ver_minor = (*ext_caps >> 16) & 0xff;
uint32_t raw_port_start = ext_caps[2] & 0xff;
uint32_t port_count = (ext_caps[2] >> 8) & 0xff;
if (raw_port_start == 0 || port_count == 0 || raw_port_start - 1 >= max_ports || port_count > max_ports - (raw_port_start - 1)) {
printf("xhci: invalid usb %u.%u port range start=%u count=%u max=%u\n",
ver_major,
ver_minor,
raw_port_start,
port_count,
max_ports);
return EINVAL;
}
uint32_t port_start = raw_port_start - 1;
printf("xhci: %u usb %u.%u ports starting at port %u\n",
port_count, ver_major, ver_minor, port_start + 1);
for (uint32_t i = 0; i < port_count; i++) {
xhci->ports[port_start + i].ver_major = ver_major;
xhci->ports[port_start + i].ver_minor = ver_minor;
}
xhci_root_hub_t *hub = alloc(sizeof(xhci_root_hub_t));
__assert(hub);
snprintf(hub->hub.name, sizeof(hub->hub.name), "xhci-root-hub-%u.%u", ver_major, ver_minor);
hub->hub.ctrl = ctrl;
hub->hub.ops = &xhci_root_hub_ops;
hub->hub.device = NULL;
hub->hub.ports = alloc(sizeof(usb_hub_port_t) * port_count);
__assert(hub->hub.ports);
hub->hub.port_count = port_count;
hub->port_offset = port_start;
list_push_back(&xhci->hubs, &hub->list_node);
}
if (next_cap_off == 0)
break;
ext_caps += next_cap_off;
}
__assert(xhci_alloc_ring(xhci, &xhci->command_ring, false) == 0);
__assert(xhci_alloc_ring(xhci, &xhci->event_ring, true) == 0);
// Set up MaxSlotsEn field.
xhci->opregs->config = max_slots;
// Set up the Device Context Base Address Array.
void *dcbaa_phys = pmm_allocpage(PMM_SECTION_DEFAULT);
__assert(dcbaa_phys);
uint64_t *dcbaa_virt = MAKE_HHDM(dcbaa_phys);
memset(dcbaa_virt, 0, PAGE_SIZE);
// Set up scratchpad buffers if requested.
uint32_t max_scratchpads_hi = (xhci->caps->hcsparams2 >> 21) & 0x1f;
uint32_t max_scratchpads_lo = (xhci->caps->hcsparams2 >> 27) & 0x1f;
uint32_t max_scratchpads = max_scratchpads_lo | (max_scratchpads_hi << 5);
if (max_scratchpads > 0) {
printf("xhci: setting up %u scratchpad buffers\n", max_scratchpads);
__assert(max_scratchpads < PAGE_SIZE / sizeof(uint64_t));
void *scratchpad_array_phys = pmm_allocpage(PMM_SECTION_DEFAULT);
__assert(scratchpad_array_phys);
uint64_t *scratchpad_array_virt = MAKE_HHDM(scratchpad_array_phys);
for (uint32_t i = 0; i < max_scratchpads; i++) {
void *scratchpad_phys = pmm_allocpage(PMM_SECTION_DEFAULT);
__assert(scratchpad_phys);
memset(MAKE_HHDM(scratchpad_phys), 0, PAGE_SIZE);
scratchpad_array_virt[i] = (uint64_t)scratchpad_phys;
}
dcbaa_virt[0] = (uint64_t)scratchpad_array_phys;
}
xhci->dcbaa = dcbaa_virt;
xhci->opregs->dcbaap = (uint64_t)dcbaa_phys;
// Set up command rings.
xhci->opregs->crcr = (uint64_t)xhci->command_ring.ring_phys | XHCI_CRCR_RCS;
// Set up Event Ring Segment Table.
void *erst_phys = pmm_allocpage(PMM_SECTION_DEFAULT);
__assert(erst_phys);
xhci_erst_entry_t *erst_virt = MAKE_HHDM(erst_phys);
memset(erst_virt, 0, PAGE_SIZE);
erst_virt[0].ring_segment = (uint64_t)xhci->event_ring.ring_phys;
erst_virt[0].ring_segment_size = xhci->event_ring.size;
// Set up the interrupter.
volatile xhci_ir_t *ir = &xhci->rtregs->ir[0];
ir->iman = XHCI_IMAN_IE;
ir->erstsz = 1;
ir->erstba = (uint64_t)erst_phys;
ir->erdp = (uint64_t)xhci->event_ring.ring_phys | XHCI_ERDP_EHB;
// Start the controller.
ret = xhci_run(xhci);
if (ret != 0) {
printf("xhci: controller run timeout\n");
return ret;
}
// power on all ports
bool debounce_wait = false;
list_for_each (&xhci->hubs, node) {
xhci_root_hub_t *rh = container_of(node, xhci_root_hub_t, list_node);
usb_hub_t *hub = &rh->hub;
for (uint8_t port = 0; port < hub->port_count; port++) {
volatile xhci_port_regs_t *portregs = &xhci->portregs[rh->port_offset + port];
uint32_t portsc = portregs->portsc;
portregs->portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | (portsc & XHCI_PORTSC_CHANGE_BITS);
if ((portsc & XHCI_PORTSC_PP) == 0) {
portregs->portsc = (portsc & XHCI_PORTSC_CHANGE_BITS) | XHCI_PORTSC_PP;
debounce_wait = true;
}
}
}
if (debounce_wait)
sched_sleep_us(100000); // 100 ms
// reset all conected ports
list_for_each (&xhci->hubs, node) {
xhci_root_hub_t *rh = container_of(node, xhci_root_hub_t, list_node);
usb_hub_t *hub = &rh->hub;
for (uint8_t port = 0; port < hub->port_count; port++) {
volatile xhci_port_regs_t *portregs = &xhci->portregs[rh->port_offset + port];
// NOTE: while USB3 ports don't need this, we will still do it to simplify the path
uint32_t portsc = portregs->portsc;
if (portsc & XHCI_PORTSC_CCS)
portregs->portsc = (portsc & XHCI_PORTSC_PRESERVE_BITS) | XHCI_PORTSC_PR;
}
}
return 0;
}
static void init_ctrl(pcienum_t *e) {
printf("xhci: found controller at %02x:%02x.%x\n", e->bus, e->device, e->function);
pci_setcommand(e, PCI_COMMAND_MMIO, 1);
// perform handoff before changing anything else
pcibar_t bar0 = pci_getbar(e, 0);
if (xhci_handoff(bar0) != 0)
return;
pci_setcommand(e, PCI_COMMAND_IO, 0);
pci_setcommand(e, PCI_COMMAND_IRQDISABLE, 1);
pci_setcommand(e, PCI_COMMAND_BUSMASTER, 1);
volatile xhci_caps_t *caps = (xhci_caps_t *)bar0.address;
volatile xhci_opregs_t *opregs = (xhci_opregs_t *)(bar0.address + caps->caplength);
volatile xhci_rtregs_t *rtregs = (xhci_rtregs_t *)(bar0.address + caps->rtsoff);
// Only 64-bit-addressing controllers are supported for simplicity.
if ((caps->hccparams1 & XHCI_HCCPARAMS1_AC64) == 0) {
printf("xhci: controller does not support 64-bit addressing\n");
return;
}
// Make sure the controller supports 4K pages
if ((opregs->pagesize & (1 << 0)) == 0) {
printf("xhci: controller does not support 4K page size\n");
return;
}
// Register the controller with the USB subsystem
xhci_ctrl_t *ctrl = alloc(sizeof(xhci_ctrl_t));
__assert(ctrl);
ctrl->ctrl.ops = &xhci_ops;
SEMAPHORE_INIT(&ctrl->event_sem, 0);
SEMAPHORE_INIT(&ctrl->callback_sem, 0);
SEMAPHORE_INIT(&ctrl->callback_space_sem, XHCI_CALLBACK_RING_ENTRIES);
SPINLOCK_INIT(ctrl->event_lock);
MUTEX_INIT(&ctrl->callback_mutex);
list_init(&ctrl->completion_list);
__assert(ringbuffer_init(&ctrl->callback_ring, sizeof(xhci_completion_t) * XHCI_CALLBACK_RING_ENTRIES) == 0);
dpc_prepare(&ctrl->dpc, xhci_dpc);
list_init(&ctrl->hubs);
ctrl->pci_enum = e;
ctrl->caps = caps;
ctrl->opregs = opregs;
ctrl->rtregs = rtregs;
ctrl->portregs = (xhci_port_regs_t *)(bar0.address + caps->caplength + 0x400);
ctrl->dbs = (uint32_t *)(bar0.address + caps->dboff);
int res = xhci_ctrl_start(&ctrl->ctrl);
if (res) {
printf("xhci: controller init returned an error: %s\n", strerror(res));
return;
}
struct thread_t *event_thread = sched_newthread(xhci_event_thread, PAGE_SIZE * 4, 0, NULL, NULL);
__assert(event_thread != NULL);
event_thread->kernelarg = ctrl;
sched_queue(event_thread);
struct thread_t *callback_thread = sched_newthread(xhci_callback_thread, PAGE_SIZE * 4, 0, NULL, NULL);
__assert(callback_thread != NULL);
callback_thread->kernelarg = ctrl;
sched_queue(callback_thread);
}
void xhci_init(void) {
for (int i = 0;; ++i) {
pcienum_t *e = pci_getenum(PCI_CLASS_SERIAL_BUS, PCI_SUBCLASS_SERIAL_BUS_USB, PCI_PROGIF_USB_XHCI, -1, -1, -1, i);
if (e == NULL)
break;
init_ctrl(e);
}
}
INIT_ROUTINE_DEFINE(xhci, INIT_ROUTINE_FLAGS_NONE, xhci_init, acpi);