mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2025-02-25 18:55:23 -06:00
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:
parent
89e2643de4
commit
e6e0f0c8e7
16
README.md
16
README.md
@ -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.
|
||||
|
||||
|
11
app/config.c
11
app/config.c
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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
128
system/heap.c
Normal 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
86
system/heap.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
140
system/xhci.c
140
system/xhci.c
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user