USB improvements (#116)

* Add new heap manager.

* Convert OHCI driver to use new heap manager.

* Convert UHCI driver to use new heap manager.

* Convert EHCI driver to use new heap manager.

* Convert XHCI driver to use new heap manager.

* Convert SMP to use new heap manager.

* Add a "usbinit" boot option to handle various buggy USB devices.

This replaces the "keyboard=buggy-usb" option, and adds a second
workaround to handle the problem seen in issue #107.
This commit is contained in:
martinwhitaker 2022-07-16 12:34:08 +01:00 committed by GitHub
parent 89e2643de4
commit e6e0f0c8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 355 additions and 164 deletions

View File

@ -132,7 +132,13 @@ recognised:
* where *type* is one of
* legacy
* usb
* buggy-usb
* usbdebug
* pauses after probing for USB keyboards
* usbinit=*mode*
* where *mode* is one of
* 1 = use the two-step init sequence for high speed devices
* 2 = add a second USB reset in the init sequence
* 3 = the combination of modes 1 and 2
* console=ttyS*x*,*y*
* activate serial/tty console output, where *x* is one of the following IO port
* 0 = 0x3F8
@ -152,9 +158,7 @@ recognised:
Memtest86+ supports both the legacy keyboard interface (using I/O ports 0x60
and 0x64) and USB keyboards (using its own USB device drivers). One or the
other can be selected via the boot command line, If neither is selected, the
default is to use both. An additional option on the boot command line is
`buggy-usb` which enables the longer initialisation sequence required by some
older USB devices.
default is to use both.
Older BIOSs usually support USB legacy keyboard emulation, which makes USB
keyboards act like legacy keyboards connected to ports 0x60 and 0x64. This
@ -170,6 +174,10 @@ emulation and add `keyboard=legacy` on the boot command line.
you enable the Compatibility System Module (CSM) in the BIOS setup. Others
only support it when actually booting in legacy mode.
Many USB devices don't fully conform to the USB specification. If the USB
keyboard probe hangs or fails to detect your keyboard, try the various
workarounds provided by the "usbinit" boot option.
**NOTE**: Memtest86+'s USB device drivers are work in progress. Not all USB
devices are supported yet, and there may be problems on some hardware.

View File

@ -177,9 +177,6 @@ static void parse_option(const char *option, const char *params)
keyboard_types = KT_LEGACY;
} else if (strncmp(params, "usb", 4) == 0) {
keyboard_types = KT_USB;
} else if (strncmp(params, "buggy-usb", 10) == 0) {
keyboard_types = KT_USB;
usb_init_options |= USB_EXTRA_RESET;
}
} else if (strncmp(option, "powersave", 10) == 0) {
if (strncmp(params, "off", 4) == 0) {
@ -203,6 +200,14 @@ static void parse_option(const char *option, const char *params)
enable_trace = true;
} else if (strncmp(option, "usbdebug", 9) == 0) {
usb_init_options |= USB_DEBUG;
} else if (strncmp(option, "usbinit", 8) == 0) {
if (strncmp(params, "1", 2) == 0) {
usb_init_options |= USB_2_STEP_INIT;
} else if (strncmp(params, "2", 2) == 0) {
usb_init_options |= USB_EXTRA_RESET;
} else if (strncmp(params, "3", 2) == 0) {
usb_init_options |= USB_2_STEP_INIT|USB_EXTRA_RESET;
}
} else if (strncmp(option, "nosm", 5) == 0) {
enable_sm = false;
}

View File

@ -22,6 +22,7 @@
#include "cache.h"
#include "cpuid.h"
#include "cpuinfo.h"
#include "heap.h"
#include "hwctrl.h"
#include "hwquirks.h"
#include "io.h"
@ -216,6 +217,8 @@ static void global_init(void)
pmem_init();
heap_init();
pci_init();
quirks_init();

View File

@ -20,6 +20,7 @@ SYS_OBJS = system/acpi.o \
system/cpulocal.o \
system/ehci.o \
system/font.o \
system/heap.o \
system/hwctrl.o \
system/hwquirks.o \
system/keyboard.o \

View File

@ -21,6 +21,7 @@ SYS_OBJS = system/acpi.o \
system/ehci.o \
system/font.o \
system/hwctrl.o \
system/heap.o \
system/hwquirks.o \
system/keyboard.o \
system/ohci.o \

View File

@ -5,10 +5,10 @@
#include <stddef.h>
#include <stdint.h>
#include "heap.h"
#include "memrw32.h"
#include "memsize.h"
#include "pci.h"
#include "pmem.h"
#include "usb.h"
#include "string.h"
@ -226,11 +226,6 @@ typedef struct {
// Private Functions
//------------------------------------------------------------------------------
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
static int usb_to_ehci_speed(usb_speed_t usb_speed)
{
switch (usb_speed) {
@ -519,20 +514,26 @@ bool ehci_init(int bus, int dev, int func, uintptr_t base_addr, usb_hcd_t *hcd)
if (!halt_host_controller(op_regs)) return false;
if (!reset_host_controller(op_regs)) return false;
// Record the heap state to allow us to free memory.
uintptr_t initial_heap_mark = lm_heap_mark();
// Allocate and initialise a periodic frame list. This needs to be aligned on a 4K page boundary. Some controllers
// don't support a programmable list length, so we just use the default length.
pm_map[0].end -= num_pages(EHCI_MAX_PFL_LENGTH * sizeof(uint32_t));
uintptr_t pfl_addr = pm_map[0].end << PAGE_SHIFT;
uintptr_t pfl_addr = lm_heap_alloc(EHCI_MAX_PFL_LENGTH * sizeof(uint32_t), PAGE_SIZE);
if (pfl_addr == 0) {
goto no_keyboards_found;
}
uint32_t *pfl = (uint32_t *)pfl_addr;
for (int i = 0; i < EHCI_MAX_PFL_LENGTH; i++) {
pfl[i] = EHCI_LP_TERMINATE;
}
// 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;
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory.
uintptr_t workspace_addr = lm_heap_alloc(sizeof(workspace_t), PAGE_SIZE);
if (workspace_addr == 0) {
goto no_keyboards_found;
}
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
@ -549,9 +550,7 @@ bool ehci_init(int bus, int dev, int func, uintptr_t base_addr, usb_hcd_t *hcd)
write32(&op_regs->periodic_list_base, pfl_addr);
write32(&op_regs->async_list_addr, (uintptr_t)(ws->qhd));
if (!start_host_controller(op_regs)) {
pm_map[0].end += num_pages(sizeof(workspace_t));
pm_map[0].end += num_pages(EHCI_MAX_PFL_LENGTH * sizeof(uint32_t));
return false;
goto no_keyboards_found;
}
flush32(&op_regs->config_flag, 1);
@ -647,17 +646,9 @@ bool ehci_init(int bus, int dev, int func, uintptr_t base_addr, usb_hcd_t *hcd)
}
if (num_keyboards == 0) {
// Halt the host controller.
(void)halt_host_controller(op_regs);
(void)reset_host_controller(op_regs);
// Deallocate the workspace for this controller.
pm_map[0].end += num_pages(sizeof(workspace_t));
// Deallocate the periodic frame list.
pm_map[0].end += num_pages(EHCI_MAX_PFL_LENGTH * sizeof(uint32_t));
return false;
goto no_keyboards_found;
}
ws->num_keyboards = num_keyboards;
@ -691,4 +682,8 @@ bool ehci_init(int bus, int dev, int func, uintptr_t base_addr, usb_hcd_t *hcd)
enable_periodic_schedule(op_regs);
return true;
no_keyboards_found:
lm_heap_rewind(initial_heap_mark);
return false;
}

128
system/heap.c Normal file
View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2022 Martin Whitaker.
#include <stdint.h>
#include "boot.h"
#include "memsize.h"
#include "pmem.h"
#include "heap.h"
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef struct {
int segment;
uintptr_t start;
uintptr_t end;
} heap_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static heap_t lm_heap = { .segment = -1, .start = 0, .end = 0 };
static heap_t hm_heap = { .segment = -1, .start = 0, .end = 0 };
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
static uintptr_t heap_alloc(const heap_t *heap, size_t size, uintptr_t alignment)
{
if (heap->segment < 0) {
return 0;
}
uintptr_t addr = pm_map[heap->segment].end - num_pages(size);
addr &= ~((alignment - 1) >> PAGE_SHIFT);
if (addr < heap->start) {
return 0;
}
pm_map[heap->segment].end = addr;
return addr << PAGE_SHIFT;
}
static uintptr_t heap_mark(const heap_t *heap)
{
if (heap->segment < 0) {
return 0;
}
return pm_map[heap->segment].end;
}
static void heap_rewind(const heap_t *heap, uintptr_t mark)
{
if (heap->segment >= 0 && mark > pm_map[heap->segment].end && mark <= heap->end) {
pm_map[heap->segment].end = mark;
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void heap_init(void)
{
// Use the largest 20-bit addressable physical memory segment for the low-memory heap.
// Use the largest 32-bit addressable physical memory segment for the high-memory heap.
// Exclude memory occupied by the program or below it in that segment.
uintptr_t program_start = (uintptr_t)_start >> PAGE_SHIFT;
uintptr_t program_end = program_start + num_pages(_end - _start);
uintptr_t max_segment_size = 0;
for (int i = 0; i < pm_map_size && pm_map[i].end <= PAGE_C(4,GB); i++) {
uintptr_t try_heap_start = pm_map[i].start;
uintptr_t try_heap_end = pm_map[i].end;
if (program_start >= try_heap_start && program_end <= try_heap_end) {
try_heap_start = program_end;
}
uintptr_t segment_size = try_heap_end - try_heap_start;
if (segment_size >= max_segment_size) {
max_segment_size = segment_size;
if (try_heap_end <= PAGE_C(1,MB)) {
lm_heap.segment = i;
lm_heap.start = try_heap_start;
lm_heap.end = try_heap_end;
}
hm_heap.segment = i;
hm_heap.start = try_heap_start;
hm_heap.end = try_heap_end;
}
}
}
uintptr_t lm_heap_alloc(size_t size, uintptr_t alignment)
{
return heap_alloc(&lm_heap, size, alignment);
}
uintptr_t lm_heap_mark(void)
{
return heap_mark(&lm_heap);
}
void lm_heap_rewind(uintptr_t mark)
{
heap_rewind(&lm_heap, mark);
}
uintptr_t hm_heap_alloc(size_t size, uintptr_t alignment)
{
return heap_alloc(&hm_heap, size, alignment);
}
uintptr_t hm_heap_mark(void)
{
return heap_mark(&hm_heap);
}
void hm_heap_rewind(uintptr_t mark)
{
heap_rewind(&hm_heap, mark);
}

86
system/heap.h Normal file
View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef HEAP_H
#define HEAP_H
/**
* \file
*
* Provides functions to allocate and free chunks of physical memory that will
* be excluded from the memory tests. Two separate heaps are supported, one in
* low (20-bit addressable) memory, the other in high (32-bit addressable)
* memory.
*
*//*
* Copyright (C) 2022 Martin Whitaker.
*/
#include <stddef.h>
#include <stdint.h>
/**
* Initialises the heaps.
*/
void heap_init(void);
/**
* Allocates a chunk of physical memory below 1MB. The allocated region will
* be at least the requested size with the requested alignment. This memory
* is always mapped to the identical address in virtual memory.
*
* \param size - the requested size in bytes.
* \param alignment - the requested byte alignment (must be a power of 2).
*
* \returns
* On success, the allocated address in physical memory. On failure, 0.
*/
uintptr_t lm_heap_alloc(size_t size, uintptr_t alignment);
/**
* Returns a value indicating the current allocation state of the low-memory
* heap. This value may be passed to lm_heap_rewind() to free any low memory
* allocated after this call.
*
* \returns
* An opaque value indicating the current allocation state.
*/
uintptr_t lm_heap_mark(void);
/**
* Frees any low memory allocated since the specified mark was obtained from
* a call to lm_heap_mark().
*
* \param mark - the mark that indicates how much memory to free.
*/
void lm_heap_rewind(uintptr_t mark);
/**
* Allocates a chunk of physical memory below 4GB. The allocated region will
* be at least the requested size with the requested alignment. The caller is
* responsible for mapping it into virtual memory if required.
*
* \param size - the requested size in bytes.
* \param alignment - the requested byte alignment (must be a power of 2).
*
* \returns
* On success, the allocated address in physical memory. On failure, 0.
*/
uintptr_t hm_heap_alloc(size_t size, uintptr_t alignment);
/**
* Returns a value indicating the current allocation state of the high-memory
* heap. This value may be passed to hm_heap_rewind() to free any high memory
* allocated after this call.
*
* \returns
* An opaque value indicating the current allocation state.
*/
uintptr_t hm_heap_mark(void);
/**
* Frees any high memory allocated since the specified mark was obtained from
* a call to hm_heap_mark().
*
* \param mark - the mark that indicates how much memory to free.
*/
void hm_heap_rewind(uintptr_t mark);
#endif // HEAP_H

View File

@ -5,9 +5,9 @@
#include <stddef.h>
#include <stdint.h>
#include "heap.h"
#include "memrw32.h"
#include "memsize.h"
#include "pmem.h"
#include "usb.h"
#include "string.h"
@ -243,11 +243,6 @@ typedef struct {
// 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).
@ -461,11 +456,14 @@ bool ohci_init(uintptr_t base_addr, usb_hcd_t *hcd)
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;
// Record the heap state to allow us to free memory.
uintptr_t initial_heap_mark = lm_heap_mark();
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory.
uintptr_t workspace_addr = lm_heap_alloc(sizeof(workspace_t), PAGE_SIZE);
if (workspace_addr == 0) {
goto no_keyboards_found;
}
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
@ -574,10 +572,7 @@ bool ohci_init(uintptr_t base_addr, usb_hcd_t *hcd)
// 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;
goto no_keyboards_found;
}
@ -611,4 +606,8 @@ bool ohci_init(uintptr_t base_addr, usb_hcd_t *hcd)
flush32(&op_regs->interrupt_status, ~0);
return true;
no_keyboards_found:
lm_heap_rewind(initial_heap_mark);
return false;
}

View File

@ -20,10 +20,10 @@
#include "efi.h"
#include "cpuid.h"
#include "heap.h"
#include "memrw32.h"
#include "memsize.h"
#include "msr.h"
#include "pmem.h"
#include "string.h"
#include "unistd.h"
#include "vmem.h"
@ -548,9 +548,9 @@ void smp_init(bool smp_enable)
apic_id_to_cpu_num[cpu_num_to_apic_id[i]] = i;
}
// Reserve last page of first segment for AP trampoline and sync objects.
// Allocate a page of low memory for AP trampoline and sync objects.
// These need to remain pinned in place during relocation.
smp_heap_page = --pm_map[0].end;
smp_heap_page = lm_heap_alloc(PAGE_SIZE, PAGE_SIZE) >> PAGE_SHIFT;
ap_startup_addr = (uintptr_t)startup;

View File

@ -5,11 +5,11 @@
#include <stddef.h>
#include <stdint.h>
#include "heap.h"
#include "io.h"
#include "memrw32.h"
#include "memsize.h"
#include "pci.h"
#include "pmem.h"
#include "usb.h"
#include "string.h"
@ -195,11 +195,6 @@ typedef struct {
// Private Functions
//------------------------------------------------------------------------------
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
static bool io_wait_until_clr(uint16_t io_reg, uint16_t bit_mask, int max_time)
{
int timer = max_time >> 3;
@ -434,16 +429,21 @@ bool uhci_init(int bus, int dev, int func, uint16_t io_base, usb_hcd_t *hcd)
if (!halt_host_controller(io_base)) return false;
if (!reset_host_controller(io_base)) return false;
// Allocate and initialise the frame list. This needs to be aligned on a 4K page boundary.
pm_map[0].end -= num_pages(UHCI_FL_LENGTH * sizeof(uint32_t));
uintptr_t fl_addr = pm_map[0].end << PAGE_SHIFT;
// Record the heap state to allow us to free memory.
uintptr_t initial_heap_mark = lm_heap_mark();
// Allocate the frame list. This needs to be aligned on a 4K page boundary.
uintptr_t fl_addr = lm_heap_alloc(UHCI_FL_LENGTH * sizeof(uint32_t), PAGE_SIZE);
if (fl_addr == 0) {
goto no_keyboards_found;
}
uint32_t *fl = (uint32_t *)fl_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.
// TODO: check for segment overflow.
pm_map[0].end -= num_pages(sizeof(workspace_t));
uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT;
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory.
uintptr_t workspace_addr = lm_heap_alloc(sizeof(workspace_t), PAGE_SIZE);
if (workspace_addr == 0) {
goto no_keyboards_found;
}
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
@ -469,9 +469,7 @@ bool uhci_init(int bus, int dev, int func, uint16_t io_base, usb_hcd_t *hcd)
outl(fl_addr, UHCI_FLBASE);
outb(UHCI_SOF_DEFAULT, UHCI_SOF);
if (!start_host_controller(io_base)) {
pm_map[0].end += num_pages(sizeof(workspace_t));
pm_map[0].end += num_pages(UHCI_FL_LENGTH * sizeof(uint32_t));
return false;
goto no_keyboards_found;
}
// Construct a hub descriptor for the root hub.
@ -535,16 +533,8 @@ bool uhci_init(int bus, int dev, int func, uint16_t io_base, usb_hcd_t *hcd)
num_keyboards, num_keyboards != 1 ? "s" : "");
if (num_keyboards == 0) {
// Halt the host controller.
(void)halt_host_controller(io_base);
// Deallocate the workspace for this controller.
pm_map[0].end += num_pages(sizeof(workspace_t));
// Deallocate the periodic frame list.
pm_map[0].end += num_pages(UHCI_FL_LENGTH * sizeof(uint32_t));
return false;
goto no_keyboards_found;
}
ws->num_keyboards = num_keyboards;
@ -582,4 +572,8 @@ bool uhci_init(int bus, int dev, int func, uint16_t io_base, usb_hcd_t *hcd)
}
return true;
no_keyboards_found:
lm_heap_rewind(initial_heap_mark);
return false;
}

View File

@ -543,12 +543,12 @@ bool assign_usb_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num
ep0->max_packet_size = default_max_packet_size(device_speed);
ep0->interval = 0;
// The device should currently be in Default state. For loww and full speed devices, We first fetch the first
// The device should currently be in Default state. For low and full speed devices, 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 = sizeof(usb_device_desc_t);
if (device_speed < USB_SPEED_HIGH) {
if (device_speed < USB_SPEED_HIGH || usb_init_options & USB_2_STEP_INIT) {
fetch_length = 8;
goto fetch_descriptor;
}

View File

@ -121,7 +121,8 @@ typedef enum {
USB_DEFAULT_INIT = 0,
USB_EXTRA_RESET = 1 << 0,
USB_IGNORE_EHCI = 1 << 1,
USB_DEBUG = 1 << 2
USB_2_STEP_INIT = 1 << 2,
USB_DEBUG = 1 << 3
} usb_init_options_t;
/**

View File

@ -4,9 +4,9 @@
#include <stdbool.h>
#include <stdint.h>
#include "heap.h"
#include "memrw32.h"
#include "memsize.h"
#include "pmem.h"
#include "usb.h"
#include "vmem.h"
@ -329,9 +329,11 @@ typedef struct {
uint32_t cr_enqueue_state;
uint32_t er_dequeue_state;
// Transient values used during device enumeration and configuration.
// Input context for controller commands.
uintptr_t input_context_addr;
uintptr_t output_context_addr;
// Transient values used during device enumeration and configuration.
uintptr_t initial_heap_mark;
uintptr_t control_ep_tr_addr;
uintptr_t interrupt_ep_tr_addr;
@ -342,26 +344,10 @@ typedef struct {
uint8_t kbd_ep_id [MAX_KEYBOARDS];
} workspace_t __attribute__ ((aligned (64)));
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
// The heap segment of the physical memory map is used when allocating memory
// to the controller that we don't need to access during normal operation.
// Any memory we do need to access during normal operation is allocated from
// segment 0, which is permanently mapped into the virtual memory address space.
static int heap_segment = -1;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static size_t num_pages(size_t size)
{
return (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
static size_t round_up(size_t size, size_t alignment)
{
return (size + alignment - 1) & ~(alignment - 1);
@ -658,37 +644,36 @@ static int allocate_slot(const usb_hcd_t *hcd)
xhci_trb_t event;
// Allocate and initialise a private workspace for this device.
// TODO: check for heap overflow.
// Record the heap state to allow us to free memory.
ws->initial_heap_mark = lm_heap_mark();
pm_map[0].end -= num_pages(DEVICE_WS_SIZE);
uintptr_t device_workspace_addr = pm_map[0].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);
// Allocate and initialise a private workspace for this device.
uintptr_t device_workspace_addr = lm_heap_alloc(DEVICE_WS_SIZE, PAGE_SIZE);
if (device_workspace_addr == 0) {
goto free_memory;
}
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);
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);
// Allocate a device slot and set up its output context.
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, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
pm_map[0].end += num_pages(DEVICE_WS_SIZE);
return 0;
goto free_memory;
}
int slot_id = event_slot_id(&event);
write64(&ws->device_context_index[slot_id], ws->output_context_addr);
write64(&ws->device_context_index[slot_id], device_workspace_addr);
return slot_id;
free_memory:
lm_heap_rewind(ws->initial_heap_mark);
return 0;
}
static bool release_slot(const usb_hcd_t *hcd, int slot_id)
@ -705,7 +690,7 @@ static bool release_slot(const usb_hcd_t *hcd, int slot_id)
write64(&ws->device_context_index[slot_id], 0);
pm_map[0].end += num_pages(DEVICE_WS_SIZE);
lm_heap_rewind(ws->initial_heap_mark);
return true;
}
@ -903,20 +888,6 @@ static void poll_keyboards(const usb_hcd_t *hcd)
}
}
static bool set_heap_segment(void)
{
// Use the largest 32-bit addressable physical memory segment for the heap.
uintptr_t max_segment_size = 0;
for (int i = 0; i < pm_map_size && pm_map[i].end <= PAGE_C(4,GB); i++) {
uintptr_t segment_size = pm_map[i].end - pm_map[i].start;
if (segment_size >= max_segment_size) {
max_segment_size = segment_size;
heap_segment = i;
}
}
return max_segment_size > 0;
}
//------------------------------------------------------------------------------
// Driver Method Table
//------------------------------------------------------------------------------
@ -939,11 +910,6 @@ static const hcd_methods_t methods = {
bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
{
if (heap_segment < 0) {
if (!set_heap_segment()) return false;
}
uintptr_t heap_segment_end = pm_map[heap_segment].end;
uint8_t port_type[XHCI_MAX_PORTS];
memset(port_type, 0, sizeof(port_type));
@ -1018,12 +984,14 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
// Ensure the controller is halted and then reset it.
if (!halt_host_controller(op_regs)) return false;
if (!reset_host_controller(op_regs)) return false;
// Record the heap states to allow us to free memory.
uintptr_t initial_lm_heap_mark = lm_heap_mark();
uintptr_t initial_hm_heap_mark = hm_heap_mark();
// Record the controller page size.
uintptr_t xhci_page_size = (read32(&op_regs->page_size) & 0xffff) << 12;
uintptr_t xhci_page_mask = xhci_page_size - 1;
// Find the maximum number of device slots the controller supports.
int max_slots = cap_regs->hcs_params1 & 0xff;
@ -1033,31 +1001,30 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
| ((cap_regs->hcs_params2 >> 27) & 0x1f);
// Allocate and clear the scratchpad memory on the heap. This must be aligned to the controller page size.
// TODO: check for heap overflow.
uintptr_t scratchpad_size = num_scratchpad_buffers * xhci_page_size;
pm_map[heap_segment].end -= num_pages(scratchpad_size);
pm_map[heap_segment].end &= ~(xhci_page_mask >> PAGE_SHIFT);
uintptr_t scratchpad_paddr = pm_map[heap_segment].end << PAGE_SHIFT;
uintptr_t scratchpad_paddr = hm_heap_alloc(scratchpad_size, xhci_page_size);
if (scratchpad_paddr == 0) {
goto no_keyboards_found;
}
uintptr_t scratchpad_vaddr = map_region(scratchpad_paddr, scratchpad_size, true);
if (scratchpad_vaddr == 0) {
pm_map[heap_segment].end = heap_segment_end;
return false;
goto no_keyboards_found;
}
memset((void *)scratchpad_vaddr, 0, scratchpad_size);
// Allocate and initialise the device context base address and scratchpad buffer arrays on the heap.
// Both need to be aligned on a 64 byte boundary.
// TODO: check for heap overflow.
uintptr_t device_context_index_size = (1 + max_slots) * sizeof(uint64_t);
uintptr_t scratchpad_buffer_index_offs = round_up(device_context_index_size, 64);
uintptr_t scratchpad_buffer_index_size = num_scratchpad_buffers * sizeof(uint64_t);
pm_map[heap_segment].end -= num_pages(scratchpad_buffer_index_offs + scratchpad_buffer_index_size);
uintptr_t device_context_index_paddr = pm_map[heap_segment].end << PAGE_SHIFT;
uintptr_t device_context_index_paddr = hm_heap_alloc(scratchpad_buffer_index_offs + scratchpad_buffer_index_size, 64);
if (device_context_index_paddr == 0) {
goto no_keyboards_found;
}
uintptr_t device_context_index_vaddr = map_region(device_context_index_paddr, scratchpad_buffer_index_offs + scratchpad_buffer_index_size, true);
if (device_context_index_vaddr == 0) {
pm_map[heap_segment].end = heap_segment_end;
return false;
goto no_keyboards_found;
}
memset((void *)device_context_index_vaddr, 0, device_context_index_size);
@ -1073,11 +1040,11 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
}
}
// 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;
// Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory.
uintptr_t workspace_addr = lm_heap_alloc(sizeof(workspace_t), PAGE_SIZE);
if (workspace_addr == 0) {
goto no_keyboards_found;
}
workspace_t *ws = (workspace_t *)workspace_addr;
memset(ws, 0, sizeof(workspace_t));
@ -1093,6 +1060,14 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
ws->cr_enqueue_state = WS_CR_SIZE; // cycle = 1, index = 0
ws->er_dequeue_state = WS_ER_SIZE; // cycle = 1, index = 0
// Allocate and initialise the input context data structure. This needs to be contained within a single page.
ws->input_context_addr = lm_heap_alloc(XHCI_MAX_IP_CONTEXT_SIZE, PAGE_SIZE);
if (ws->input_context_addr == 0) {
goto no_keyboards_found;
}
memset((void *)ws->input_context_addr, 0, XHCI_MAX_IP_CONTEXT_SIZE);
// Initialise the driver object for this controller.
hcd->methods = &methods;
hcd->ws = &ws->base_ws;
@ -1110,9 +1085,7 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
write64(&op_regs->dcbaap, device_context_index_paddr);
write32(&op_regs->config, (read32(&op_regs->config) & 0xfffffc00) | max_slots);
if (!start_host_controller(op_regs)) {
pm_map[0].end += num_pages(sizeof(workspace_t));
pm_map[heap_segment].end = heap_segment_end;
return false;
goto no_keyboards_found;
}
// Construct a hub descriptor for the root hub.
@ -1177,16 +1150,8 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
num_keyboards, num_keyboards != 1 ? "s" : "");
if (num_keyboards == 0) {
// Halt the host controller.
(void)halt_host_controller(op_regs);
// Free the pages we allocated in segment 0.
pm_map[0].end += num_pages(sizeof(workspace_t));
// Free the pages we allocated in the heap segment.
pm_map[heap_segment].end = heap_segment_end;
return false;
goto no_keyboards_found;
}
// Initialise the interrupt TRB ring for each keyboard interface.
@ -1200,4 +1165,9 @@ bool xhci_init(uintptr_t base_addr, usb_hcd_t *hcd)
}
return true;
no_keyboards_found:
lm_heap_rewind(initial_lm_heap_mark);
hm_heap_rewind(initial_hm_heap_mark);
return false;
}