Add support for USB Mass Storage Devices (#607)

* Initial commit for USB Mass Storage Support

* Add support for SCSI_16 and various fixes by debrouxl

* Fix USB HCD registration to skip failed probes (when a mass storage device was already found)

* Fix xHCI bulk transfer completion matching & accept short packets for IN transfers

* Add USB BOT error recovery and match xHCI control events by slot/EP

* Add EHCI bulk timeout/size guard, fix per-controller MSD port handling, and make xHCI keyboard re-arm precise

* Harden FAT/GPT parsing (cluster validation & block-size check) and fix RTC read issue due to R/W race

* Fix report varargs on x86_64, truncate F6 drive label, and clean up failed FAT file writes

* Fix stale hub VID/PID read, refactor USB MSD detection to helper & tidy usbhcd/display/config layout

* Scan for USB drives on demand when saving a report
Keep EHCI/xHCI controllers registered when empty and rescan free root ports on F6.
Also fix xHCI keyboard re-arm losing a key release, dropping the next same-key press.
This commit is contained in:
Sam Demeulemeester
2026-07-11 11:49:41 +02:00
committed by GitHub
parent a557c9402c
commit 33a5eeeffa
23 changed files with 2438 additions and 92 deletions
+229 -36
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021-2022 Martin Whitaker.
// Copyright (C) 2026 Sam Demeulemeester.
#include <stdbool.h>
#include <stddef.h>
@@ -138,6 +139,8 @@
#define MAX_KEYBOARDS 8 // per host controller
#define EHCI_MAX_PORTS 15 // HCSPARAMS N_PORTS is a 4-bit field
#define WS_QHD_SIZE (1 + MAX_KEYBOARDS) // Queue Head Descriptors
#define WS_QTD_SIZE (3 + MAX_KEYBOARDS) // Queue Transfer Descriptors
@@ -202,6 +205,11 @@ typedef volatile struct {
// Data structures specific to this implementation.
typedef struct {
uintptr_t hs_parent_data;
uint8_t data_toggle;
} ehci_bulk_ep_t;
typedef struct {
hcd_workspace_t base_ws;
@@ -220,6 +228,12 @@ typedef struct {
// Number of keyboards detected.
int num_keyboards;
// State needed to rescan the root ports after initialisation.
int num_hs_devices;
uint8_t num_ports;
bool i_have_companions;
bool port_in_use[EHCI_MAX_PORTS];
} workspace_t __attribute__ ((aligned (256)));
//------------------------------------------------------------------------------
@@ -372,20 +386,25 @@ static void build_ehci_qhd(ehci_qhd_t *qhd, const ehci_qtd_t *qtd, const usb_ep_
static bool do_async_transfer(const workspace_t *ws, int num_tds)
{
// Rely on the controller to timeout if the device doesn't respond.
// The controller only detects device errors; a device that NAKs forever would
// hang us, so also enforce a software timeout.
bool ok = true;
enable_async_schedule(ws->op_regs);
for (int td_idx = 0; td_idx < num_tds; td_idx++) {
for (int td_idx = 0; td_idx < num_tds && ok; td_idx++) {
const ehci_qtd_t *qtd = &ws->qtd[td_idx];
int timer = 5000 * MILLISEC / 10;
while (qtd->status & EHCI_QTD_ACTIVE) {
if (timer-- == 0) {
ok = false;
break;
}
usleep(10);
}
if (qtd->status & (EHCI_QTD_HALTED | EHCI_QTD_DB_ERR | EHCI_QTD_BABBLE | EHCI_QTD_TR_ERR | EHCI_QTD_MMF | EHCI_QTD_PS)) {
ok = false;
break;
}
}
// This waits for the schedule to go idle, so it also stops a timed-out transfer.
disable_async_schedule(ws->op_regs);
return ok;
}
@@ -466,6 +485,164 @@ static void poll_keyboards(const usb_hcd_t *hcd)
}
}
static bool configure_bulk_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int ep_id, bool is_out)
{
workspace_t *ws = (workspace_t *)hcd->ws;
(void)ep_id;
(void)is_out;
// Allocate a small metadata struct to track HS parent info and data toggle.
// The actual QH/QTD are rebuilt in ws->qhd[0]/qtd[0] for each transfer
// (same slots used by control transfers), avoiding stale QH cache issues.
uintptr_t bulk_addr = heap_alloc(HEAP_TYPE_LM_1, sizeof(ehci_bulk_ep_t), 64);
if (bulk_addr == 0) return false;
ehci_bulk_ep_t *bulk_ep = (ehci_bulk_ep_t *)bulk_addr;
bulk_ep->hs_parent_data = ep->driver_data;
bulk_ep->data_toggle = 0;
// Store the pointer in data_buffer for the caller.
*(uintptr_t *)ws->base_ws.data_buffer = bulk_addr;
return true;
}
static bool bulk_transfer(const usb_hcd_t *hcd, const usb_ep_t *ep, void *buffer, size_t length, bool is_out)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ehci_bulk_ep_t *bulk_ep = (ehci_bulk_ep_t *)ep->driver_data;
// A single qTD can address at most 5 buffer pages; reject anything larger.
if (length > 5 * 0x1000 - ((uintptr_t)buffer & 0xFFF)) {
return false;
}
uint8_t pid = is_out ? EHCI_QTD_PID_OUT : EHCI_QTD_PID_IN;
// Build QTD in workspace slot 0, with software-tracked data toggle.
build_ehci_qtd(&ws->qtd[0], &ws->qtd[0], pid,
EHCI_QTD_DT(bulk_ep->data_toggle), buffer, length);
// Fill in additional buffer page pointers if the transfer spans page boundaries.
if (length > 0) {
uintptr_t start = (uintptr_t)buffer;
uintptr_t end = start + length - 1;
for (int i = 1; i < 5; i++) {
uintptr_t page = (start & ~0xFFFUL) + ((uintptr_t)i * 0x1000);
if (page > end) break;
ws->qtd[0].buffer_ptr[i] = page;
}
}
// Build a fresh QH in workspace slot 0 (same slot as control transfers).
// This avoids stale QH cache issues => the controller always reads a clean QH.
usb_ep_t tmp_ep = *ep;
tmp_ep.driver_data = bulk_ep->hs_parent_data;
build_ehci_qhd(&ws->qhd[0], &ws->qtd[0], &tmp_ep, false);
// Set the data toggle in the QH overlay (DTC=1 means HW uses overlay DT)
ws->qhd[0].data_length = EHCI_QTD_DT(bulk_ep->data_toggle);
// Execute via the existing async schedule
bool ok = do_async_transfer(ws, 1);
if (ok) {
// Read next data toggle from the QH overlay (updated by hardware)
bulk_ep->data_toggle = (ws->qhd[0].data_length >> 15) & 1;
}
return ok;
}
static bool reset_bulk_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int ep_id)
{
(void)hcd;
(void)ep_id;
// A cleared halt resets the device to DATA0; resync the software toggle.
ehci_bulk_ep_t *bulk_ep = (ehci_bulk_ep_t *)ep->driver_data;
bulk_ep->data_toggle = 0;
return true;
}
static bool scan_for_msd(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ehci_op_regs_t *op_regs = ws->op_regs;
// Record the heap state to allow us to free memory if the scan fails.
uintptr_t initial_heap_mark = heap_mark(HEAP_TYPE_LM_1);
// Construct a hub descriptor for the root hub.
usb_hub_t root_hub;
memset(&root_hub, 0, sizeof(root_hub));
root_hub.ep0 = NULL;
root_hub.num_ports = ws->num_ports;
root_hub.power_up_delay = 10; // 20ms
usleep(100*MILLISEC); // USB maximum device attach time
// Scan the ports that are not already in use, looking for a USB drive.
usb_ep_t keyboards[1];
for (int port_idx = 0; port_idx < ws->num_ports; port_idx++) {
uint32_t port_status = read32(&op_regs->port_sc[port_idx]);
// Check the port is powered up.
if (~port_status & EHCI_PORT_SC_PP) continue;
// Skip ports owned by a device found during a previous scan, unless it was unplugged.
if (ws->port_in_use[port_idx]) {
if ((port_status & (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) == (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) {
continue;
}
ws->port_in_use[port_idx] = false;
}
// Check if anything is connected to this port.
if (~port_status & EHCI_PORT_SC_CCS) continue;
// Low and full speed devices are handled by the companion controllers, which we don't rescan.
if ((port_status & EHCI_PORT_SC_LS_MASK) == EHCI_PORT_SC_LS_K) {
if (ws->i_have_companions) {
release_ehci_port(op_regs, port_idx);
}
continue;
}
// Reset the port.
if (!reset_ehci_port(op_regs, port_idx)) continue;
usleep(10*MILLISEC); // USB reset recovery time
port_status = read32(&op_regs->port_sc[port_idx]);
// Check for full speed device.
if (~port_status & EHCI_PORT_SC_PED) {
if (ws->i_have_companions) {
release_ehci_port(op_regs, port_idx);
}
continue;
}
ws->num_hs_devices++;
// With max_keyboards = 0 only the mass storage path can succeed, so a true
// return value means the USB drive was found on this port.
int num_keyboards = 0;
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, USB_SPEED_HIGH, ws->num_hs_devices,
&ws->num_hs_devices, keyboards, 0, &num_keyboards)) {
ws->port_in_use[port_idx] = true;
return true;
}
disable_ehci_port(op_regs, port_idx);
}
heap_rewind(HEAP_TYPE_LM_1, initial_heap_mark);
return false;
}
//------------------------------------------------------------------------------
// Driver Method Table
//------------------------------------------------------------------------------
@@ -479,7 +656,12 @@ static const hcd_methods_t methods = {
.configure_kbd_ep = NULL,
.setup_request = setup_request,
.get_data_request = get_data_request,
.poll_keyboards = poll_keyboards
.poll_keyboards = poll_keyboards,
.rearm_keyboards = NULL,
.configure_bulk_ep = configure_bulk_ep,
.bulk_transfer = bulk_transfer,
.reset_bulk_ep = reset_bulk_ep,
.scan_for_msd = scan_for_msd
};
//------------------------------------------------------------------------------
@@ -590,14 +772,19 @@ bool ehci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
bool i_have_companions = (num_ehci_companions(hcs_params) > 0);
// Record the state needed to rescan the root ports later.
ws->num_ports = root_hub.num_ports;
ws->i_have_companions = i_have_companions;
// Scan the ports, looking for hubs and keyboards.
usb_ep_t keyboards[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_ls_devices = 0;
int num_hs_devices = 0;
bool msd_found_before = usb_mass_storage_found;
for (int port_idx = 0; port_idx < root_hub.num_ports; port_idx++) {
// If we've filled the keyboard info table, abort now.
if (num_keyboards >= MAX_KEYBOARDS) break;
// If we've filled the keyboard info table and found a USB drive, abort now.
if (num_keyboards >= MAX_KEYBOARDS && usb_mass_storage_found) break;
uint32_t port_status = read32(&op_regs->port_sc[port_idx]);
@@ -634,58 +821,64 @@ bool ehci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
num_hs_devices++;
// Look for keyboards attached directly or indirectly to this port.
// Look for keyboards and USB drives attached directly or indirectly to this port.
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, USB_SPEED_HIGH, num_hs_devices,
&num_hs_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
ws->port_in_use[port_idx] = true;
continue;
}
// If we didn't find any keyboard interfaces, we can disable the port.
// If we didn't find any keyboard interfaces or a USB drive on this port
// (find_attached_usb_keyboards returns true for both), we can disable it.
disable_ehci_port(op_regs, port_idx);
}
print_usb_info(" Found %i low/full speed device%s, %i high speed device%s, %i keyboard%s",
// True only if the drive was found on this controller during the scan above.
bool msd_on_this_hcd = usb_mass_storage_found && !msd_found_before;
print_usb_info(" Found %i low/full speed device%s, %i high speed device%s, %i keyboard%s%s",
num_ls_devices, num_ls_devices != 1 ? "s" : "",
num_hs_devices, num_hs_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "");
num_keyboards, num_keyboards != 1 ? "s" : "",
msd_on_this_hcd ? ", 1 USB drive" : "");
if (num_ls_devices > 0 && i_have_companions) {
print_usb_info(" Handed over low/full speed devices to companion controllers");
}
if (num_keyboards == 0) {
(void)halt_host_controller(op_regs);
goto no_keyboards_found;
}
// Even if no device was found, keep the controller registered so its root ports
// can be rescanned later by usb_scan_for_msd().
ws->num_hs_devices = num_hs_devices;
ws->num_keyboards = num_keyboards;
ws->num_keyboards = num_keyboards;
if (num_keyboards > 0) {
// Initialise the interrupt QHD and QTD for each keyboard interface and find the minimum interval.
int min_interval = EHCI_MAX_PFL_LENGTH;
uint32_t first_qhd_ptr = EHCI_LP_TERMINATE;
for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) {
usb_ep_t *kbd = &keyboards[kbd_idx];
// Initialise the interrupt QHD and QTD for each keyboard interface and find the minimum interval.
int min_interval = EHCI_MAX_PFL_LENGTH;
uint32_t first_qhd_ptr = EHCI_LP_TERMINATE;
for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) {
usb_ep_t *kbd = &keyboards[kbd_idx];
ehci_qhd_t *kbd_qhd = &ws->qhd[1 + kbd_idx];
ehci_qtd_t *kbd_qtd = &ws->qtd[3 + kbd_idx];
ehci_qhd_t *kbd_qhd = &ws->qhd[1 + kbd_idx];
ehci_qtd_t *kbd_qtd = &ws->qtd[3 + kbd_idx];
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
build_ehci_qtd(kbd_qtd, kbd_qtd, EHCI_QTD_PID_IN, EHCI_QTD_DT(0), kbd_rpt, sizeof(hid_kbd_rpt_t));
build_ehci_qhd(kbd_qhd, kbd_qtd, kbd, true);
build_ehci_qtd(kbd_qtd, kbd_qtd, EHCI_QTD_PID_IN, EHCI_QTD_DT(0), kbd_rpt, sizeof(hid_kbd_rpt_t));
build_ehci_qhd(kbd_qhd, kbd_qtd, kbd, true);
kbd_qhd->next_qhd_ptr = first_qhd_ptr;
first_qhd_ptr = (uintptr_t)kbd_qhd | EHCI_LP_TYPE_QH;
kbd_qhd->next_qhd_ptr = first_qhd_ptr;
first_qhd_ptr = (uintptr_t)kbd_qhd | EHCI_LP_TYPE_QH;
if (kbd->interval < min_interval) {
min_interval = kbd->interval;
if (kbd->interval < min_interval) {
min_interval = kbd->interval;
}
}
}
// Initialise the periodic frame list and enable the periodic schedule.
for (int i = 0; i < EHCI_MAX_PFL_LENGTH; i += min_interval) {
pfl[i] = first_qhd_ptr;
// Initialise the periodic frame list and enable the periodic schedule.
for (int i = 0; i < EHCI_MAX_PFL_LENGTH; i += min_interval) {
pfl[i] = first_qhd_ptr;
}
enable_periodic_schedule(op_regs);
}
enable_periodic_schedule(op_regs);
return true;
+588
View File
@@ -0,0 +1,588 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2026 Sam Demeulemeester.
#include <stdbool.h>
#include <stdint.h>
#include "string.h"
#include "fat32.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define FAT_FREE 0x00000000
#define DIR_ENTRY_SIZE 32
#define DIR_ATTR_ARCHIVE 0x20
// MBR partition type IDs.
#define MBR_TYPE_FAT16_SMALL 0x04
#define MBR_TYPE_FAT16 0x06
#define MBR_TYPE_NTFS_EXFAT 0x07
#define MBR_TYPE_FAT32 0x0B
#define MBR_TYPE_FAT32_LBA 0x0C
#define MBR_TYPE_FAT16_LBA 0x0E
#define MBR_TYPE_GPT_PROTECTIVE 0xEE
// GPT header signature at sector 1.
static const char gpt_signature[8] = "EFI PART";
// "Basic Data Partition" GUID: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (mixed-endian)
static const uint8_t gpt_basic_data_guid[16] = {
0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44,
0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7
};
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static uint32_t eoc_value(const fat32_fs_t *fs)
{
if (fs->fat_type == 16) return 0xFFFF;
return 0x0FFFFFFF;
}
// Validates a cluster number read from the FAT before using it to address the disk;
// EOC marks and out-of-range values (corrupt FAT) both fail this check.
static bool is_valid_cluster(const fat32_fs_t *fs, uint32_t cluster)
{
return cluster >= 2 && cluster < fs->max_cluster;
}
static bool is_fat_partition(uint8_t type)
{
switch (type) {
case MBR_TYPE_FAT16_SMALL:
case MBR_TYPE_FAT16:
case MBR_TYPE_FAT16_LBA:
case MBR_TYPE_FAT32:
case MBR_TYPE_FAT32_LBA:
return true;
default:
return false;
}
}
static uint32_t cluster_to_lba(const fat32_fs_t *fs, uint32_t cluster)
{
return fs->data_start_lba + (cluster - 2) * fs->sectors_per_cluster;
}
static bool read_sector(fat32_fs_t *fs, uint32_t lba)
{
return msd_read_sectors(fs->msd, fs->partition_lba + lba, 1, fs->sector_buf);
}
static bool write_sector(fat32_fs_t *fs, uint32_t lba)
{
return msd_write_sectors(fs->msd, fs->partition_lba + lba, 1, fs->sector_buf);
}
static uint32_t fat_read_entry(fat32_fs_t *fs, uint32_t cluster)
{
if (fs->fat_type == 32) {
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + fat_offset / fs->bytes_per_sector;
uint32_t offset_in_sector = fat_offset % fs->bytes_per_sector;
if (!read_sector(fs, fat_sector)) return 0x0FFFFFFF;
uint32_t val;
memcpy(&val, fs->sector_buf + offset_in_sector, 4);
return val & 0x0FFFFFFF;
}
// FAT16.
uint32_t fat_offset = cluster * 2;
uint32_t fat_sector = fs->fat_start_lba + fat_offset / fs->bytes_per_sector;
uint32_t offset_in_sector = fat_offset % fs->bytes_per_sector;
if (!read_sector(fs, fat_sector)) return 0xFFFF;
uint16_t val;
memcpy(&val, fs->sector_buf + offset_in_sector, 2);
return val;
}
static bool fat_write_entry(fat32_fs_t *fs, uint32_t cluster, uint32_t value)
{
if (fs->fat_type == 32) {
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector_off = fat_offset / fs->bytes_per_sector;
uint32_t offset_in_sector = fat_offset % fs->bytes_per_sector;
for (int fat = 0; fat < fs->num_fats; fat++) {
uint32_t fat_sector = fs->fat_start_lba + fat * fs->sectors_per_fat + fat_sector_off;
if (!read_sector(fs, fat_sector)) return false;
// Preserve the upper 4 bits of the existing entry.
uint32_t existing;
memcpy(&existing, fs->sector_buf + offset_in_sector, 4);
uint32_t merged = (existing & 0xF0000000) | (value & 0x0FFFFFFF);
memcpy(fs->sector_buf + offset_in_sector, &merged, 4);
if (!write_sector(fs, fat_sector)) return false;
}
return true;
}
// FAT16.
uint32_t fat_offset = cluster * 2;
uint32_t fat_sector_off = fat_offset / fs->bytes_per_sector;
uint32_t offset_in_sector = fat_offset % fs->bytes_per_sector;
uint16_t val16 = (uint16_t)value;
for (int fat = 0; fat < fs->num_fats; fat++) {
uint32_t fat_sector = fs->fat_start_lba + fat * fs->sectors_per_fat + fat_sector_off;
if (!read_sector(fs, fat_sector)) return false;
memcpy(fs->sector_buf + offset_in_sector, &val16, 2);
if (!write_sector(fs, fat_sector)) return false;
}
return true;
}
static uint32_t fat_alloc_cluster(fat32_fs_t *fs)
{
// Linear scan from cluster 2 to find a free entry.
for (uint32_t cluster = 2; cluster < fs->max_cluster; cluster++) {
uint32_t entry = fat_read_entry(fs, cluster);
if (entry == FAT_FREE) {
if (!fat_write_entry(fs, cluster, eoc_value(fs))) return 0;
return cluster;
}
}
return 0; // Disk full.
}
// Frees a cluster chain (best effort - used to undo a failed file write).
static void fat_free_chain(fat32_fs_t *fs, uint32_t first_cluster)
{
uint32_t cluster = first_cluster;
for (uint32_t n = 0; n < fs->max_cluster && is_valid_cluster(fs, cluster); n++) {
uint32_t next = fat_read_entry(fs, cluster);
if (!fat_write_entry(fs, cluster, FAT_FREE)) return;
cluster = next;
}
}
// Scan the root directory for a free 32-byte entry.
// Returns the LBA and byte offset of the free entry using out parameters.
static bool find_free_dir_entry(fat32_fs_t *fs,
uint32_t *out_lba, uint32_t *out_offset)
{
// FAT12/16: fixed root directory area.
if (fs->fat_type != 32) {
for (uint32_t s = 0; s < fs->root_dir_sectors; s++) {
if (!read_sector(fs, fs->root_dir_lba + s)) return false;
for (uint32_t off = 0; off + DIR_ENTRY_SIZE <= fs->bytes_per_sector; off += DIR_ENTRY_SIZE) {
uint8_t first_byte = fs->sector_buf[off];
if (first_byte == 0x00 || first_byte == 0xE5) {
*out_lba = fs->root_dir_lba + s;
*out_offset = off;
return true;
}
}
}
return false;
}
// FAT32: root directory is a cluster chain. Validate each link and cap the
// walk length to guard against corrupt or cyclic FATs.
uint32_t cluster = fs->root_cluster;
for (uint32_t n = 0; n < fs->max_cluster && is_valid_cluster(fs, cluster); n++) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (int s = 0; s < fs->sectors_per_cluster; s++) {
if (!read_sector(fs, lba + s)) return false;
for (uint32_t off = 0; off + DIR_ENTRY_SIZE <= fs->bytes_per_sector; off += DIR_ENTRY_SIZE) {
uint8_t first_byte = fs->sector_buf[off];
if (first_byte == 0x00 || first_byte == 0xE5) {
*out_lba = lba + s;
*out_offset = off;
return true;
}
}
}
cluster = fat_read_entry(fs, cluster);
}
return false; // No free entry found.
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
// Try to parse the BPB from the sector already in buf.
// Returns true if it looks like a valid FAT16/32 BPB.
static bool parse_fat_bpb(fat32_fs_t *fs, const uint8_t *buf)
{
// Validate boot signature.
if (buf[510] != 0x55 || buf[511] != 0xAA) return false;
// Parse common BPB fields.
uint16_t tmp16;
memcpy(&tmp16, buf + 11, 2);
fs->bytes_per_sector = tmp16;
fs->sectors_per_cluster = buf[13];
memcpy(&tmp16, buf + 14, 2);
fs->reserved_sectors = tmp16;
fs->num_fats = buf[16];
if (fs->bytes_per_sector == 0 || fs->sectors_per_cluster == 0) return false;
if (fs->bytes_per_sector != fs->msd->block_size) return false;
uint16_t root_entry_count;
memcpy(&root_entry_count, buf + 17, 2);
// Read both sectors_per_fat fields (FAT12/16 at offset 22, FAT32 at offset 36).
uint16_t spf16;
memcpy(&spf16, buf + 22, 2);
uint32_t spf32;
memcpy(&spf32, buf + 36, 4);
if (spf16 != 0) {
fs->sectors_per_fat = spf16;
} else if (spf32 != 0) {
fs->sectors_per_fat = spf32;
} else {
return false;
}
// Compute root directory size (non-zero only for FAT12/16).
fs->root_dir_sectors = ((root_entry_count * DIR_ENTRY_SIZE) +
(fs->bytes_per_sector - 1)) / fs->bytes_per_sector;
// Derive layout (works for all FAT types: root_dir_sectors is 0 for FAT32).
fs->fat_start_lba = fs->reserved_sectors;
fs->root_dir_lba = fs->fat_start_lba + (uint32_t)fs->num_fats * fs->sectors_per_fat;
fs->data_start_lba = fs->root_dir_lba + fs->root_dir_sectors;
// Compute total sectors and determine FAT type from cluster count.
uint16_t total_sectors_16;
memcpy(&total_sectors_16, buf + 19, 2);
uint32_t total_sectors_32;
memcpy(&total_sectors_32, buf + 32, 4);
uint32_t total_sectors = total_sectors_16 ? total_sectors_16 : total_sectors_32;
if (total_sectors == 0) return false;
if (total_sectors <= fs->data_start_lba) return false;
uint32_t data_sectors = total_sectors - fs->data_start_lba;
uint32_t count_clusters = data_sectors / fs->sectors_per_cluster;
if (count_clusters < 4085) {
return false; // FAT12 not supported.
} else if (count_clusters < 65525) {
fs->fat_type = 16;
} else {
fs->fat_type = 32;
}
fs->max_cluster = count_clusters + 2;
// FAT32: root directory is a cluster chain.
if (fs->fat_type == 32) {
if (root_entry_count != 0) return false;
memcpy(&fs->root_cluster, buf + 44, 4);
} else {
if (root_entry_count == 0) return false;
fs->root_cluster = 0;
}
return true;
}
bool fat32_mount(fat32_fs_t *fs, usb_msd_t *msd, uint8_t *buf)
{
fs->msd = msd;
fs->sector_buf = buf;
fs->partition_lba = 0;
// Read sector 0.
if (!msd_read_sectors(msd, 0, 1, buf)) return false;
// First, try to parse sector 0 directly as a FAT VBR (unpartitioned drive).
if (parse_fat_bpb(fs, buf)) return true;
// Not a valid FAT VBR. Check if it's an MBR with a partition table.
if (buf[510] != 0x55 || buf[511] != 0xAA) return false;
// Check for GPT: MBR partition type 0xEE (protective MBR) in the first entry.
bool is_gpt = false;
for (int i = 0; i < 4; i++) {
if (buf[446 + i * 16 + 4] == MBR_TYPE_GPT_PROTECTIVE) {
is_gpt = true;
break;
}
}
if (is_gpt) {
// Read GPT header at sector 1.
if (!msd_read_sectors(msd, 1, 1, buf)) return false;
// Validate GPT signature "EFI PART".
if (memcmp(buf, gpt_signature, 8) != 0) return false;
// Parse GPT header fields.
uint64_t entry_lba;
uint32_t entry_count, entry_size;
memcpy(&entry_lba, buf + 72, 8);
memcpy(&entry_count, buf + 80, 4);
memcpy(&entry_size, buf + 84, 4);
if (entry_size < 128 || entry_count == 0) return false;
// Scan partition entries for a Basic Data Partition with a FAT VBR.
// The EFI System Partition is deliberately never used: writing to the
// ESP risks breaking the boot setup, so it is not an acceptable target.
// block_size is a power of two (validated in msd_init), so use shift/mask to
// avoid 64-bit division, which needs libgcc support on i586.
uint32_t bs_shift = 0;
while ((1u << bs_shift) < msd->block_size) bs_shift++;
uint32_t loaded_sector = 0;
bool loaded = false;
for (uint32_t i = 0; i < entry_count; i++) {
uint64_t byte_off = (uint64_t)i * entry_size;
uint32_t sector = (uint32_t)(entry_lba + (byte_off >> bs_shift));
uint32_t offset = (uint32_t)byte_off & (msd->block_size - 1);
// The fields we need (GUID + start LBA) must lie within the loaded sector.
if (offset + 40 > msd->block_size) continue;
if (!loaded || sector != loaded_sector) {
if (!msd_read_sectors(msd, sector, 1, buf)) break;
loaded_sector = sector;
loaded = true;
}
const uint8_t *ent = buf + offset;
// Check for Basic Data Partition GUID.
if (memcmp(ent, gpt_basic_data_guid, 16) != 0) continue;
// Get starting LBA (little-endian uint64_t at offset 32).
uint64_t start_lba;
memcpy(&start_lba, ent + 32, 8);
if (start_lba == 0 || start_lba > 0xFFFFFFFF) continue;
fs->partition_lba = (uint32_t)start_lba;
// Read the VBR and try to parse as FAT; this clobbers the entries in buf.
loaded = false;
if (!msd_read_sectors(msd, (uint32_t)start_lba, 1, buf)) continue;
if (parse_fat_bpb(fs, buf)) return true;
}
return false;
}
// Standard MBR: scan the 4 partition entries (at offsets 446, 462, 478, 494).
// Re-read sector 0 since buf may have been clobbered by GPT check above.
if (!msd_read_sectors(msd, 0, 1, buf)) return false;
for (int i = 0; i < 4; i++) {
uint8_t *entry = buf + 446 + i * 16;
uint8_t type = entry[4];
if (!is_fat_partition(type)) continue;
// Read the partition start LBA (little-endian uint32_t at offset 8).
uint32_t part_lba;
memcpy(&part_lba, entry + 8, 4);
if (part_lba == 0) continue;
fs->partition_lba = part_lba;
// Read the VBR from the partition.
if (!msd_read_sectors(msd, part_lba, 1, buf)) continue;
if (parse_fat_bpb(fs, buf)) return true;
}
return false;
}
bool fat32_write_file(fat32_fs_t *fs, const char *name_8_3, const void *data, uint32_t size)
{
if (size == 0) return false;
uint32_t cluster_size = fs->sectors_per_cluster * fs->bytes_per_sector;
uint32_t num_clusters = (size + cluster_size - 1) / cluster_size;
// Allocate clusters and chain them.
uint32_t first_cluster = 0;
uint32_t prev_cluster = 0;
for (uint32_t i = 0; i < num_clusters; i++) {
uint32_t cluster = fat_alloc_cluster(fs);
if (cluster == 0) goto fail;
if (first_cluster == 0) {
first_cluster = cluster;
}
// Chain to previous cluster.
if (prev_cluster != 0) {
if (!fat_write_entry(fs, prev_cluster, cluster)) goto fail;
}
prev_cluster = cluster;
}
// Write file data to the allocated clusters.
const uint8_t *src = (const uint8_t *)data;
uint32_t remaining = size;
uint32_t cluster = first_cluster;
while (remaining > 0 && is_valid_cluster(fs, cluster)) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (int s = 0; s < fs->sectors_per_cluster && remaining > 0; s++) {
uint32_t to_write = remaining < fs->bytes_per_sector ? remaining : fs->bytes_per_sector;
// If partial sector, clear the buffer first.
if (to_write < fs->bytes_per_sector) {
memset(fs->sector_buf, 0, fs->bytes_per_sector);
}
memcpy(fs->sector_buf, src, to_write);
if (!write_sector(fs, lba + s)) goto fail;
src += to_write;
remaining -= to_write;
}
cluster = fat_read_entry(fs, cluster);
}
// A chain shorter than expected (read error / corrupt FAT) must not be
// reported as success with a directory entry claiming the full size.
if (remaining != 0) goto fail;
// Create directory entry in root directory.
uint32_t entry_lba, entry_offset;
if (!find_free_dir_entry(fs, &entry_lba, &entry_offset)) {
goto fail;
}
// Read the sector containing the directory entry.
if (!read_sector(fs, entry_lba)) goto fail;
// Build the 32-byte directory entry.
uint8_t *entry = fs->sector_buf + entry_offset;
memset(entry, 0, DIR_ENTRY_SIZE);
// Filename (8 bytes name + 3 bytes extension).
memcpy(entry, name_8_3, 11);
// Attributes.
entry[11] = DIR_ATTR_ARCHIVE;
// First cluster high word (bytes 20-21).
entry[20] = (first_cluster >> 16) & 0xFF;
entry[21] = (first_cluster >> 24) & 0xFF;
// First cluster low word (bytes 26-27).
entry[26] = first_cluster & 0xFF;
entry[27] = (first_cluster >> 8) & 0xFF;
// File size (bytes 28-31, little-endian).
memcpy(entry + 28, &size, 4);
// Write back the directory sector.
if (!write_sector(fs, entry_lba)) goto fail;
return true;
fail:
// Release any clusters allocated for this file so they aren't leaked.
if (first_cluster != 0) {
fat_free_chain(fs, first_cluster);
}
return false;
}
// Check a directory entry against the MT86P_XX.TXT pattern and mark used slots.
static void check_dir_entry(const uint8_t *entry, bool *used)
{
if (memcmp(entry, "MT86P_", 6) == 0 && memcmp(entry + 8, "TXT", 3) == 0) {
int tens = entry[6] - '0';
int ones = entry[7] - '0';
if (tens >= 0 && tens <= 9 && ones >= 0 && ones <= 9) {
int num = tens * 10 + ones;
if (num >= 1 && num <= 99) {
used[num] = true;
}
}
}
}
bool fat32_next_filename(fat32_fs_t *fs, char *name_out)
{
// Scan root directory for existing MT86P_XX.TXT files.
bool used[100];
memset(used, 0, sizeof(used));
if (fs->fat_type != 32) {
// FAT12/16: fixed root directory area.
for (uint32_t s = 0; s < fs->root_dir_sectors; s++) {
if (!read_sector(fs, fs->root_dir_lba + s)) break;
for (uint32_t off = 0; off + DIR_ENTRY_SIZE <= fs->bytes_per_sector; off += DIR_ENTRY_SIZE) {
uint8_t *entry = fs->sector_buf + off;
if (entry[0] == 0x00) goto scan_done;
if (entry[0] == 0xE5) continue;
check_dir_entry(entry, used);
}
}
} else {
// FAT32: root directory is a cluster chain (validated and capped as above).
uint32_t cluster = fs->root_cluster;
for (uint32_t n = 0; n < fs->max_cluster && is_valid_cluster(fs, cluster); n++) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (int s = 0; s < fs->sectors_per_cluster; s++) {
if (!read_sector(fs, lba + s)) break;
for (uint32_t off = 0; off + DIR_ENTRY_SIZE <= fs->bytes_per_sector; off += DIR_ENTRY_SIZE) {
uint8_t *entry = fs->sector_buf + off;
if (entry[0] == 0x00) goto scan_done;
if (entry[0] == 0xE5) continue;
check_dir_entry(entry, used);
}
}
cluster = fat_read_entry(fs, cluster);
}
}
scan_done:
// Find the first unused number.
for (int n = 1; n <= 99; n++) {
if (!used[n]) {
// Build 8.3 name: "MT86P_NNTXT" (11 chars, no dot).
name_out[0] = 'M';
name_out[1] = 'T';
name_out[2] = '8';
name_out[3] = '6';
name_out[4] = 'P';
name_out[5] = '_';
name_out[6] = '0' + (n / 10);
name_out[7] = '0' + (n % 10);
name_out[8] = 'T';
name_out[9] = 'X';
name_out[10] = 'T';
name_out[11] = '\0';
return true;
}
}
return false; // All 99 slots used.
}
+76
View File
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef FAT32_H
#define FAT32_H
/**
* \file
*
* Provides a minimal write-only FAT16/32 filesystem for creating
* files in the root directory of a FAT-formatted USB drive.
*
*//*
* Copyright (C) 2026 Sam Demeulemeester.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "usbmsd.h"
/**
* FAT16/32 filesystem context.
*/
typedef struct {
usb_msd_t *msd;
uint32_t partition_lba; // LBA offset of the partition (0 if unpartitioned)
uint32_t bytes_per_sector;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t num_fats;
uint8_t fat_type; // 16 or 32
uint32_t sectors_per_fat;
uint32_t root_cluster; // FAT32: first cluster of root directory
uint32_t root_dir_lba; // FAT12/16: start LBA of fixed root directory
uint16_t root_dir_sectors; // FAT12/16: sector count of fixed root directory
uint32_t max_cluster; // highest valid cluster number (exclusive)
uint32_t fat_start_lba;
uint32_t data_start_lba;
uint8_t *sector_buf; // one sector buffer
} fat32_fs_t;
/**
* Mounts a FAT16/32 filesystem by reading the BPB from sector 0.
*
* \param fs - the filesystem context to populate.
* \param msd - the mass storage device to read from.
* \param buf - a sector buffer (must be at least msd->block_size bytes).
*
* \returns true if a valid FAT filesystem was found.
*/
bool fat32_mount(fat32_fs_t *fs, usb_msd_t *msd, uint8_t *buf);
/**
* Creates a new file in the root directory and writes its contents.
* The filename must be in 8.3 format padded with spaces (11 bytes).
*
* \param fs - the mounted filesystem context.
* \param name_8_3 - the filename in 8.3 format (exactly 11 characters, no dot).
* \param data - the file contents.
* \param size - the size of the file in bytes.
*
* \returns true if the file was created successfully.
*/
bool fat32_write_file(fat32_fs_t *fs, const char *name_8_3, const void *data, uint32_t size);
/**
* Generates the next available filename in the format "MT86P_NN.TXT"
* by scanning the root directory for existing files.
*
* \param fs - the mounted filesystem context.
* \param name_out - output buffer for the 8.3 name (at least 12 bytes).
*
* \returns true if a name was generated (false if all 99 slots are used).
*/
bool fat32_next_filename(fat32_fs_t *fs, char *name_out);
#endif // FAT32_H
+4
View File
@@ -207,6 +207,10 @@ int print_spd_startup_info(void)
for (spdidx = 0; spdidx < max_mc_nu * 2; spdidx++) {
parse_spd(&curspd, spdidx);
if (spdidx < MAX_SPD_SLOT) {
spd_slot_cache[spdidx] = curspd;
}
if (!curspd.isValid)
continue;
+28
View File
@@ -37,6 +37,14 @@ struct cpu_info *dmi_cpu_info;
struct mem_dev *dmi_memory_devices[MAX_DMI_MEM_DEVICES];
int dmi_num_memory_devices = 0;
// Cached copies of board manufacturer and product name, saved at boot
// before memory testing can overwrite the original SMBIOS table data.
#define DMI_STRING_MAX 64
static char dmi_board_manufacturer[DMI_STRING_MAX];
static char dmi_board_product[DMI_STRING_MAX];
static bool dmi_board_info_valid = false;
static char *get_tstruct_string(struct tstruct_header *header, uint16_t maxlen, int n)
{
if (n < 1)
@@ -348,6 +356,17 @@ int smbios_init(void)
return result;
}
void get_smbios_board_info(const char **manufacturer, const char **product)
{
if (dmi_board_info_valid) {
*manufacturer = dmi_board_manufacturer;
*product = dmi_board_product;
} else {
*manufacturer = NULL;
*product = NULL;
}
}
void print_smbios_startup_info(void)
{
// Use baseboard info (struct type 2) as primary source of information,
@@ -379,6 +398,15 @@ void print_smbios_startup_info(void)
dmicol = 40 - ((sl1 + sl2) / 2);
dmicol = prints(LINE_DMI, dmicol, sys_man);
prints(LINE_DMI, dmicol + 1, sys_sku);
// Cache copies for later use (memory tests overwrite SMBIOS data).
int len1 = sl1 < DMI_STRING_MAX - 1 ? sl1 : DMI_STRING_MAX - 1;
memcpy(dmi_board_manufacturer, sys_man, len1);
dmi_board_manufacturer[len1] = '\0';
int len2 = sl2 < DMI_STRING_MAX - 1 ? sl2 : DMI_STRING_MAX - 1;
memcpy(dmi_board_product, sys_sku, len2);
dmi_board_product[len2] = '\0';
dmi_board_info_valid = true;
}
}
}
+6
View File
@@ -186,6 +186,12 @@ extern struct cpu_info *dmi_cpu_info;
int smbios_init(void);
/**
* Retrieve board manufacturer and product name strings.
* Sets output pointers to NULL if unavailable.
*/
void get_smbios_board_info(const char **manufacturer, const char **product);
/**
* Print DMI
*/
+2 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2004-2025 Sam Demeulemeester
// Copyright (C) 2004-2026 Sam Demeulemeester
#include "stdbool.h"
#include "stdint.h"
@@ -21,6 +21,7 @@
ram_info_t ram = { 0, 0, 0, 0, 0, 0, "N/A"};
ram_slot_info_t ram_slot_info[MAX_SPD_SLOT];
spd_info spd_slot_cache[MAX_SPD_SLOT];
static inline uint8_t bcd_to_ui8(uint8_t bcd)
{
+2 -1
View File
@@ -7,7 +7,7 @@
*
* Provides access to SPD parsing and printing functions.
*
* Copyright (C) 2004-2025 Sam Demeulemeester.
* Copyright (C) 2004-2026 Sam Demeulemeester.
*/
#define MAX_SPD_SLOT 8
@@ -67,6 +67,7 @@ typedef struct {
extern ram_info_t ram;
extern ram_slot_info_t ram_slot_info[MAX_SPD_SLOT];
extern spd_info spd_slot_cache[MAX_SPD_SLOT];
void print_spdi(spd_info spdi, uint8_t lidx);
void parse_spd(spd_info *spdi, uint8_t slot_idx);
+13
View File
@@ -45,6 +45,10 @@
#define USB_GET_INTERFACE 10
#define USB_SET_INTERFACE 11
// Standard feature selectors.
#define USB_ENDPOINT_HALT 0
#define HID_GET_REPORT 1
#define HID_GET_IDLE 2
#define HID_GET_PROTOCOL 3
@@ -62,16 +66,25 @@
#define USB_DESC_DEVICE 1
#define USB_DESC_CONFIGURATION 2
#define USB_DESC_STRING 3
#define USB_DESC_INTERFACE 4
#define USB_DESC_ENDPOINT 5
#define HUB_DESC_DEVICE 0x29
#define USB_DESC_LANG_EN 0x0409
// Class codes.
#define USB_CLASS_HID 3
#define USB_CLASS_MASS_STORAGE 8
#define USB_CLASS_HUB 9
// Mass Storage subclass and protocol codes.
#define USB_MSC_SUBCLASS_SCSI 6
#define USB_MSC_PROTOCOL_BOT 0x50
// Hub feature selectors.
#define HUB_PORT_ENABLE 1
+265 -16
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021-2022 Martin Whitaker.
// Copyright (C) 2026 Sam Demeulemeester.
#include "keyboard.h"
#include "memrw.h"
@@ -15,6 +16,7 @@
#include "xhci.h"
#include "print.h"
#include "string.h"
#include "unistd.h"
#include "usbhcd.h"
@@ -68,7 +70,12 @@ static const hcd_methods_t methods = {
.configure_kbd_ep = NULL,
.setup_request = NULL,
.get_data_request = NULL,
.poll_keyboards = NULL
.poll_keyboards = NULL,
.rearm_keyboards = NULL,
.configure_bulk_ep = NULL,
.bulk_transfer = NULL,
.reset_bulk_ep = NULL,
.scan_for_msd = NULL
};
// All entries in this array must be initialised in order to generate the necessary relocation records.
@@ -85,15 +92,25 @@ static usb_hcd_t hcd_list[MAX_HCD] = {
static int num_hcd = 0;
static int num_usb_keyboards = 0;
static int print_row = 0;
static int print_col = 0;
static bool usb_runtime_scan = false;
static usb_msd_t usb_msd_info;
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
usb_init_options_t usb_init_options = USB_DEFAULT_INIT;
bool usb_mass_storage_found = false;
char usb_msd_name[64] = "";
//------------------------------------------------------------------------------
// Macro Functions
//------------------------------------------------------------------------------
@@ -168,9 +185,9 @@ static bool build_hub_info(const usb_hcd_t *hcd, const usb_hub_t *parent, int po
return true;
}
static void add_hub_quirks(const usb_device_desc_t *device, usb_hub_t *hub)
static void add_hub_quirks(uint16_t vendor_id, uint16_t product_id, usb_hub_t *hub)
{
if ((device->vendor_id == USB_VID_AMERICAN_MEGATRENDS) && (device->product_id == 0xff01)) {
if ((vendor_id == USB_VID_AMERICAN_MEGATRENDS) && (product_id == 0xff01)) {
// add quirk for AMI Virtual Hub - see issue #523
hub->quirks |= USB_HUB_DONT_DISABLE_PORTS;
}
@@ -264,6 +281,65 @@ static void get_keyboard_info_from_descriptors(const uint8_t *desc_buffer, int d
}
}
static bool get_msd_info_from_descriptors(const uint8_t *desc_buffer, int desc_length,
usb_ep_t *ep_in, usb_ep_t *ep_out,
uint8_t *alt_setting)
{
bool found_ifc = false;
bool found_in = false;
bool found_out = false;
const uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
const uint8_t *tail_ptr = desc_buffer + desc_length;
while (curr_ptr < tail_ptr) {
const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
const uint8_t *next_ptr = curr_ptr + header->length;
if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;
if (header->type == USB_DESC_INTERFACE && header->length == sizeof(usb_interface_desc_t)) {
const usb_interface_desc_t *ifc = (const usb_interface_desc_t *)curr_ptr;
if (ifc->class == USB_CLASS_MASS_STORAGE
&& ifc->subclass == USB_MSC_SUBCLASS_SCSI
&& ifc->protocol == USB_MSC_PROTOCOL_BOT) {
found_ifc = true;
found_in = false;
found_out = false;
ep_in->interface_num = ifc->interface_num;
ep_out->interface_num = ifc->interface_num;
*alt_setting = ifc->alt_setting;
} else {
// Stop collecting endpoints for non-BOT interfaces. UAS devices (protocol 0x62)
// often also expose a BOT fallback interface elsewhere in the descriptor list.
found_ifc = false;
}
} else if (found_ifc && header->type == USB_DESC_ENDPOINT
&& header->length == sizeof(usb_endpoint_desc_t)) {
usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
// Check for bulk endpoint (attributes bits 1:0 == 0x02)
if ((endpoint->attributes & 0x3) == 0x02) {
if (endpoint->address & 0x80) {
// Bulk IN
ep_in->endpoint_num = endpoint->address & 0xf;
ep_in->max_packet_size = endpoint->max_packet_size;
ep_in->interval = 0;
found_in = true;
} else {
// Bulk OUT
ep_out->endpoint_num = endpoint->address & 0xf;
ep_out->max_packet_size = endpoint->max_packet_size;
ep_out->interval = 0;
found_out = true;
}
}
// Once we have both bulk endpoints for a BOT interface, we're done.
if (found_in && found_out) return true;
}
curr_ptr = next_ptr;
}
return found_ifc && found_in && found_out;
}
static bool configure_device(const usb_hcd_t *hcd, const usb_ep_t *ep0, int config_num)
{
usb_setup_pkt_t setup_pkt;
@@ -291,6 +367,119 @@ static bool configure_keyboard(const usb_hcd_t *hcd, const usb_ep_t *ep0, int in
return true;
}
static void fetch_usb_string(const usb_hcd_t *hcd, const usb_ep_t *ep0, uint8_t str_index, char *out, int out_size)
{
out[0] = '\0';
if (str_index == 0 || out_size < 2) return;
usb_setup_pkt_t setup_pkt;
uint8_t *buf = hcd->ws->data_buffer;
// Fetch the string descriptor.
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR,
USB_DESC_STRING << 8 | str_index, USB_DESC_LANG_EN, HCD_DATA_BUFFER_SIZE);
if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, buf, HCD_DATA_BUFFER_SIZE)) {
return;
}
// String descriptor: buf[0] = length, buf[1] = type (3), buf[2..] = UTF-16LE chars.
int desc_len = buf[0];
if (desc_len < 4 || buf[1] != USB_DESC_STRING) return;
int num_chars = (desc_len - 2) / 2;
int j = 0;
for (int i = 0; i < num_chars && j < out_size - 1; i++) {
uint16_t ch = buf[2 + i * 2] | (buf[3 + i * 2] << 8);
if (ch >= 0x20 && ch < 0x7F) {
out[j++] = (char)ch;
}
}
// Trim trailing spaces.
while (j > 0 && out[j - 1] == ' ') j--;
out[j] = '\0';
}
static bool check_for_usb_msd(const usb_hcd_t *hcd, const usb_ep_t *ep0, usb_speed_t device_speed,
int device_id, int port_num, int config_num, uint8_t num_configs,
uint8_t product_str_index)
{
if (usb_mass_storage_found || hcd->methods->configure_bulk_ep == NULL) {
return false;
}
usb_ep_t ep_in, ep_out;
uint8_t msd_alt_setting = 0;
bool found_bot = false;
// Try all configurations looking for a BOT interface. UAS-only devices (protocol 0x62)
// may have BOT in a different configuration or as an alternate setting.
for (int cfg_idx = 0; cfg_idx < num_configs && !found_bot; cfg_idx++) {
if (cfg_idx > 0) {
config_num = get_configuration_descriptors(hcd, ep0, cfg_idx);
if (config_num == 0) continue;
}
memset(&ep_in, 0, sizeof(ep_in));
memset(&ep_out, 0, sizeof(ep_out));
msd_alt_setting = 0;
found_bot = get_msd_info_from_descriptors(hcd->ws->data_buffer, hcd->ws->data_length,
&ep_in, &ep_out, &msd_alt_setting);
}
if (!found_bot) {
return false;
}
if (!configure_device(hcd, ep0, config_num)) {
return false;
}
// If the BOT interface is an alternate setting (common on UAS devices that
// expose BOT as a fallback), select it.
if (msd_alt_setting != 0) {
usb_setup_pkt_t set_ifc;
build_setup_packet(&set_ifc, USB_REQ_TO_INTERFACE, USB_SET_INTERFACE,
msd_alt_setting, ep_in.interface_num, 0);
if (!hcd->methods->setup_request(hcd, ep0, &set_ifc)) {
return false;
}
usleep(1*MILLISEC);
}
ep_in.device_speed = device_speed;
ep_in.device_id = device_id;
ep_in.driver_data = ep0->driver_data;
ep_out.device_speed = device_speed;
ep_out.device_id = device_id;
ep_out.driver_data = ep0->driver_data;
int ep_in_id = 2 * ep_in.endpoint_num + 1;
if (!hcd->methods->configure_bulk_ep(hcd, &ep_in, ep_in_id, false)) {
return false;
}
ep_in.driver_data = *(uintptr_t *)hcd->ws->data_buffer;
int ep_out_id = 2 * ep_out.endpoint_num;
if (!hcd->methods->configure_bulk_ep(hcd, &ep_out, ep_out_id, true)) {
return false;
}
ep_out.driver_data = *(uintptr_t *)hcd->ws->data_buffer;
usb_msd_info.hcd = hcd;
usb_msd_info.ep0 = *ep0;
usb_msd_info.ep_in = ep_in;
usb_msd_info.ep_out = ep_out;
usb_msd_info.tag = 1;
usb_mass_storage_found = true;
fetch_usb_string(hcd, ep0, product_str_index, usb_msd_name, sizeof(usb_msd_name));
if (usb_msd_name[0]) {
print_usb_info(" USB drive found on port %i (%s)", port_num, usb_msd_name);
} else {
print_usb_info(" USB drive found on port %i", port_num);
}
return true;
}
static bool scan_hub_ports(const usb_hcd_t *hcd, const usb_hub_t *hub, int *num_devices,
usb_ep_t keyboards[], int max_keyboards, int *num_keyboards)
{
@@ -312,8 +501,8 @@ static bool scan_hub_ports(const usb_hcd_t *hcd, const usb_hub_t *hub, int *num_
// Scan the ports, looking for hubs and keyboards.
for (int port_num = 1; port_num <= hub->num_ports; port_num++) {
// If we've filled the keyboard info table, abort now.
if (*num_keyboards >= max_keyboards) break;
// If we've filled the keyboard info table and found a USB drive, abort now.
if (*num_keyboards >= max_keyboards && usb_mass_storage_found) break;
uint32_t port_status;
@@ -334,8 +523,11 @@ static bool scan_hub_ports(const usb_hcd_t *hcd, const usb_hub_t *hub, int *num_
if (~port_status & HUB_PORT_ENABLED) continue;
// Now the port has been enabled, we can determine the device speed.
// USB 3.0 hubs only carry SuperSpeed traffic (all downstream devices are SuperSpeed).
usb_speed_t device_speed;
if (port_status & HUB_PORT_LOW_SPEED) {
if (hub->ep0->device_speed == USB_SPEED_SUPER) {
device_speed = USB_SPEED_SUPER;
} else if (port_status & HUB_PORT_LOW_SPEED) {
device_speed = USB_SPEED_LOW;
} else if (port_status & HUB_PORT_HIGH_SPEED) {
device_speed = USB_SPEED_HIGH;
@@ -539,24 +731,27 @@ static void probe_usb_controller(hci_type_t controller_type, uintptr_t pm_base_a
print_usb_info("Probing %s controller at %08x", hci_name[controller_type], pm_base_addr);
// Probe the device according to its type.
bool keyboards_found = false;
bool registered = false;
switch (controller_type) {
case UHCI:
keyboards_found = uhci_probe(vm_base_addr, &hcd_list[num_hcd]);
registered = uhci_probe(vm_base_addr, &hcd_list[num_hcd]);
break;
case OHCI:
keyboards_found = ohci_probe(vm_base_addr, &hcd_list[num_hcd]);
registered = ohci_probe(vm_base_addr, &hcd_list[num_hcd]);
break;
case EHCI:
keyboards_found = ehci_probe(vm_base_addr, &hcd_list[num_hcd]);
registered = ehci_probe(vm_base_addr, &hcd_list[num_hcd]);
break;
case XHCI:
keyboards_found = xhci_probe(vm_base_addr, &hcd_list[num_hcd]);
registered = xhci_probe(vm_base_addr, &hcd_list[num_hcd]);
break;
default:
break;
}
if (keyboards_found) {
// Register only on probe success: a failed probe has freed its workspace. EHCI and
// xHCI probes succeed whenever the controller itself initialises, even if no device
// was found, so their root ports can be rescanned later by usb_scan_for_msd().
if (registered) {
num_hcd++;
}
}
@@ -616,6 +811,11 @@ bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_tim
void print_usb_info(const char *fmt, ...)
{
// During a runtime rescan the test display is live and must not be disturbed.
if (usb_runtime_scan) {
return;
}
if (print_row == SCREEN_HEIGHT) {
scroll_screen_region(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 1);
print_row--;
@@ -750,6 +950,10 @@ bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int
}
usb_device_desc_t *device = (usb_device_desc_t *)hcd->ws->data_buffer;
bool is_hub = (device->class == USB_CLASS_HUB);
uint8_t product_str_index = device->product_str;
uint8_t num_configs = device->num_configs;
uint16_t vendor_id = device->vendor_id;
uint16_t product_id = device->product_id;
// Fetch the descriptors for the first configuration into the data transfer buffer. In theory a keyboard device
// may have more than one configuration and may only support the boot protocol in another configuration, but
@@ -765,7 +969,7 @@ bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int
if (!build_hub_info(hcd, hub, port_num, &ep0, &new_hub, &ep1)) {
return false;
}
add_hub_quirks(device, &new_hub);
add_hub_quirks(vendor_id, product_id, &new_hub);
if (!configure_device(hcd, &ep0, config_num)) {
return false;
}
@@ -786,7 +990,9 @@ bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int
get_keyboard_info_from_descriptors(hcd->ws->data_buffer, hcd->ws->data_length,
keyboards, max_keyboards, &new_num_keyboards);
if (new_num_keyboards == old_num_keyboards) {
return false;
// No keyboard interfaces found, check for mass storage.
return check_for_usb_msd(hcd, &ep0, device_speed, device_id, port_num,
config_num, num_configs, product_str_index);
}
if (!configure_device(hcd, &ep0, config_num)) {
return false;
@@ -810,6 +1016,7 @@ bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int
keyboard_found = true;
*num_keyboards += 1;
num_usb_keyboards++;
}
}
@@ -854,7 +1061,7 @@ bool process_usb_keyboard_report(const usb_hcd_t *hcd, const hid_kbd_rpt_t *repo
void find_usb_keyboards(bool pause_if_none)
{
clear_screen();
print_usb_info("Scanning for USB keyboards...");
print_usb_info("Scanning for USB keyboards & Mass Storage Devices...");
hci_info_t hci_list[MAX_HCI];
@@ -893,7 +1100,7 @@ void find_usb_keyboards(bool pause_if_none)
if (usb_init_options & USB_DEBUG) {
print_usb_info("Press any key to continue...");
while (get_key() == 0) {}
} else if (pause_if_none && num_hcd == 0) {
} else if (pause_if_none && num_usb_keyboards == 0) {
for (int i = PAUSE_IF_NONE_TIME; i > 0; i--) {
print_usb_info("No USB keyboards found. Continuing in %i second%c ", i, i == 1 ? ' ' : 's');
sleep(1);
@@ -917,3 +1124,45 @@ uint8_t get_usb_keycode(void)
}
return 0;
}
void usb_rearm_keyboards(void)
{
for (int i = 0; i < num_hcd; i++) {
const usb_hcd_t *hcd = &hcd_list[i];
if (hcd->methods->rearm_keyboards != NULL) {
hcd->methods->rearm_keyboards(hcd);
}
}
}
bool find_usb_mass_storage(usb_msd_t *msd)
{
if (!usb_mass_storage_found) {
return false;
}
*msd = usb_msd_info;
msd->block_count = 0;
msd->block_size = 512;
return true;
}
bool usb_hcd_available(void)
{
return num_hcd > 0;
}
bool usb_scan_for_msd(void)
{
if (usb_mass_storage_found) {
return true;
}
usb_runtime_scan = true;
for (int i = 0; i < num_hcd && !usb_mass_storage_found; i++) {
const usb_hcd_t *hcd = &hcd_list[i];
if (hcd->methods->scan_for_msd != NULL) {
(void)hcd->methods->scan_for_msd(hcd);
}
}
usb_runtime_scan = false;
return usb_mass_storage_found;
}
+69 -1
View File
@@ -45,7 +45,8 @@ typedef enum __attribute__ ((packed)) {
USB_SPEED_UNKNOWN = 0,
USB_SPEED_LOW = 1,
USB_SPEED_FULL = 2,
USB_SPEED_HIGH = 3
USB_SPEED_HIGH = 3,
USB_SPEED_SUPER = 4
} usb_speed_t;
/**
@@ -111,6 +112,11 @@ typedef struct {
bool (*setup_request) (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *);
bool (*get_data_request) (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *, const void *, size_t);
void (*poll_keyboards) (usb_hcd_r);
void (*rearm_keyboards) (usb_hcd_r);
bool (*configure_bulk_ep) (usb_hcd_r, const usb_ep_t *, int, bool);
bool (*bulk_transfer) (usb_hcd_r, const usb_ep_t *, void *, size_t, bool);
bool (*reset_bulk_ep) (usb_hcd_r, const usb_ep_t *, int);
bool (*scan_for_msd) (usb_hcd_r);
} hcd_methods_t;
/**
@@ -181,6 +187,8 @@ static inline int default_max_packet_size(usb_speed_t device_speed)
return 64;
case USB_SPEED_HIGH:
return 64;
case USB_SPEED_SUPER:
return 512;
default:
return 0;
}
@@ -194,6 +202,8 @@ static inline int default_max_packet_size(usb_speed_t device_speed)
*/
static inline bool valid_usb_max_packet_size(int size, usb_speed_t speed)
{
// USB 3.0 encodes bMaxPacketSize0 as an exponent (9 = 2^9 = 512 bytes).
if (speed == USB_SPEED_SUPER) return (size == 9);
return (size == 8) || ((speed != USB_SPEED_LOW) && (size == 16 || size == 32 || size == 64));
}
@@ -328,4 +338,62 @@ void find_usb_keyboards(bool pause_if_none);
*/
uint8_t get_usb_keycode(void);
/**
* Re-arms keyboard interrupt TRBs on all active USB controllers.
* Must be called after bulk transfers to restore keyboard polling,
* as bulk transfer event handling may consume keyboard events.
*
* Used by reports.c after saving results to USB.
*/
void usb_rearm_keyboards(void);
/**
* A USB mass storage device descriptor returned by find_usb_mass_storage.
*/
typedef struct {
const usb_hcd_t *hcd;
usb_ep_t ep0;
usb_ep_t ep_in;
usb_ep_t ep_out;
uint32_t tag;
uint64_t block_count;
uint32_t block_size;
bool use_16; // true if device requires/needs 16-byte SCSI commands
} usb_msd_t;
/**
* Set to true during USB enumeration if a mass storage device was found.
*
* Used internally by the various HCI drivers.
*/
extern bool usb_mass_storage_found;
/**
* The product name of the USB mass storage device found during enumeration.
*/
extern char usb_msd_name[64];
/**
* Returns the mass storage device discovered during the initial USB scan.
* If found, populates msd and returns true.
*/
bool find_usb_mass_storage(usb_msd_t *msd);
/**
* Returns true if at least one USB host controller driver is active.
*
* Used by config.c to decide whether to offer saving results to USB.
*/
bool usb_hcd_available(void);
/**
* Rescans the root ports of the active host controllers for a newly attached
* mass storage device, allowing a USB drive to be plugged in after boot.
* Returns true if a mass storage device is available, whether found by this
* scan or by a previous one.
*
* Used by reports.c when the user requests a report save.
*/
bool usb_scan_for_msd(void);
#endif // USBHCD_H
+282
View File
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2026 Sam Demeulemeester.
#include <stdbool.h>
#include <stdint.h>
#include "string.h"
#include "unistd.h"
#include "usbmsd.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define CBW_SIGNATURE 0x43425355
#define CSW_SIGNATURE 0x53425355
#define CBW_FLAG_DATA_IN 0x80
#define CBW_FLAG_DATA_OUT 0x00
// Bulk-Only Mass Storage Reset class request.
#define BOT_RESET 0xFF
// CSW status values.
#define CSW_STATUS_PASSED 0
#define CSW_STATUS_FAILED 1
#define CSW_STATUS_PHASE_ERR 2
// SCSI command opcodes.
#define SCSI_TEST_UNIT_READY 0x00
#define SCSI_REQUEST_SENSE 0x03
#define SCSI_INQUIRY 0x12
#define SCSI_READ_CAPACITY_10 0x25
#define SCSI_READ_10 0x28
#define SCSI_WRITE_10 0x2A
#define SCSI_READ_16 0x88
#define SCSI_WRITE_16 0x8A
#define SCSI_READ_CAPACITY_16 0x9E
#define SCSI_SAI_READ_CAPACITY_16 0x10 // Service action for SCSI_READ_CAPACITY_16
// LBA returned by READ CAPACITY (10) when the disk is too large to address with 32 bits.
#define READ_CAP_10_OVERFLOW 0xFFFFFFFFu
#define MILLISEC 1000 // in microseconds
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef struct __attribute__((packed)) {
uint32_t signature;
uint32_t tag;
uint32_t data_transfer_length;
uint8_t flags;
uint8_t lun;
uint8_t cb_length;
uint8_t cb[16];
} usb_cbw_t;
typedef struct __attribute__((packed)) {
uint32_t signature;
uint32_t tag;
uint32_t data_residue;
uint8_t status;
} usb_csw_t;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
// Clears a halted bulk endpoint on both the device and the controller side.
static bool msd_clear_stall(usb_msd_t *msd, const usb_ep_t *ep, bool is_in)
{
const usb_hcd_t *hcd = msd->hcd;
usb_setup_pkt_t setup_pkt;
build_setup_packet(&setup_pkt, USB_REQ_TO_ENDPOINT, USB_CLR_FEATURE,
USB_ENDPOINT_HALT, ep->endpoint_num | (is_in ? 0x80 : 0), 0);
if (!hcd->methods->setup_request(hcd, &msd->ep0, &setup_pkt)) {
return false;
}
if (hcd->methods->reset_bulk_ep != NULL) {
int ep_id = 2 * ep->endpoint_num + (is_in ? 1 : 0);
return hcd->methods->reset_bulk_ep(hcd, ep, ep_id);
}
return true;
}
// BOT Reset Recovery (BOT spec 5.3.4): class reset, then clear both bulk endpoints.
static bool msd_reset_recovery(usb_msd_t *msd)
{
const usb_hcd_t *hcd = msd->hcd;
usb_setup_pkt_t setup_pkt;
build_setup_packet(&setup_pkt, USB_REQ_TO_INTERFACE | USB_REQ_CLASS, BOT_RESET,
0, msd->ep_in.interface_num, 0);
if (!hcd->methods->setup_request(hcd, &msd->ep0, &setup_pkt)) {
return false;
}
usleep(10 * MILLISEC);
bool ok = msd_clear_stall(msd, &msd->ep_in, true);
return msd_clear_stall(msd, &msd->ep_out, false) && ok;
}
static bool msd_bot_command(usb_msd_t *msd, const uint8_t *cdb, int cdb_len,
void *data, uint32_t data_len, bool data_in)
{
const usb_hcd_t *hcd = msd->hcd;
// Build Command Block Wrapper. cb[] not in the initializer is zero-padded.
usb_cbw_t cbw = {
.signature = CBW_SIGNATURE,
.tag = msd->tag++,
.data_transfer_length = data_len,
.flags = data_in ? CBW_FLAG_DATA_IN : CBW_FLAG_DATA_OUT,
.lun = 0,
.cb_length = cdb_len,
};
memcpy(cbw.cb, cdb, cdb_len);
// Send CBW via bulk OUT. A failure here means the transport is broken.
if (!hcd->methods->bulk_transfer(hcd, &msd->ep_out, &cbw, sizeof(cbw), true)) {
msd_reset_recovery(msd);
return false;
}
// Data phase (if any). On failure (usually a STALL on a rejected command),
// clear the endpoint so the CSW can still be read (BOT spec 6.7.2/6.7.3).
bool data_ok = true;
if (data_len > 0 && data != NULL) {
const usb_ep_t *ep = data_in ? &msd->ep_in : &msd->ep_out;
data_ok = hcd->methods->bulk_transfer(hcd, ep, data, data_len, !data_in);
if (!data_ok) {
msd_clear_stall(msd, ep, data_in);
}
}
// Receive CSW via bulk IN; retry once after clearing a stalled IN endpoint.
usb_csw_t csw;
if (!hcd->methods->bulk_transfer(hcd, &msd->ep_in, &csw, sizeof(csw), false)) {
if (!msd_clear_stall(msd, &msd->ep_in, true)
|| !hcd->methods->bulk_transfer(hcd, &msd->ep_in, &csw, sizeof(csw), false)) {
msd_reset_recovery(msd);
return false;
}
}
// Validate the CSW; a bad CSW or a phase error requires a full reset recovery.
if (csw.signature != CSW_SIGNATURE || csw.tag != cbw.tag || csw.status == CSW_STATUS_PHASE_ERR) {
msd_reset_recovery(msd);
return false;
}
return data_ok && csw.status == CSW_STATUS_PASSED;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
// The block size is device-reported and sizes host buffers; only accept sane values.
static bool valid_block_size(uint32_t size)
{
return size == 512 || size == 1024 || size == 2048 || size == 4096;
}
static bool read_capacity_16(usb_msd_t *msd)
{
uint8_t cdb[16] = {
SCSI_READ_CAPACITY_16,
SCSI_SAI_READ_CAPACITY_16,
0, 0, 0, 0, 0, 0, 0, 0, // 8-byte LBA (0 for service action 0x10)
0, 0, 0, 32, // allocation length = 32
0, 0
};
uint8_t cap_data[32];
if (!msd_bot_command(msd, cdb, 16, cap_data, sizeof(cap_data), true)) {
return false;
}
uint64_t last_lba = 0;
for (int i = 0; i < 8; i++) {
last_lba = (last_lba << 8) | cap_data[i];
}
msd->block_count = last_lba + 1;
msd->block_size = ((uint32_t)cap_data[8] << 24) | ((uint32_t)cap_data[9] << 16)
| ((uint32_t)cap_data[10] << 8) | (uint32_t)cap_data[11];
return valid_block_size(msd->block_size);
}
bool msd_init(usb_msd_t *msd)
{
msd->use_16 = false;
// TEST UNIT READY — retry a few times since the device may need time to spin up.
uint8_t cdb_tur[6] = { SCSI_TEST_UNIT_READY };
for (int retry = 0; retry < 5; retry++) {
if (msd_bot_command(msd, cdb_tur, 6, NULL, 0, false)) {
break;
}
usleep(500 * MILLISEC);
if (retry == 4) return false;
}
// READ CAPACITY (10) — returns 8 bytes: last LBA (4 bytes BE) + block size (4 bytes BE).
uint8_t cdb_cap[10] = { SCSI_READ_CAPACITY_10 };
uint8_t cap_data[8];
if (!msd_bot_command(msd, cdb_cap, 10, cap_data, 8, true)) {
// Some larger drives reject 10-byte commands; try the 16-byte variant.
if (!read_capacity_16(msd)) return false;
msd->use_16 = true;
return true;
}
uint32_t last_lba_10 = ((uint32_t)cap_data[0] << 24) | ((uint32_t)cap_data[1] << 16)
| ((uint32_t)cap_data[2] << 8) | (uint32_t)cap_data[3];
msd->block_size = ((uint32_t)cap_data[4] << 24) | ((uint32_t)cap_data[5] << 16)
| ((uint32_t)cap_data[6] << 8) | (uint32_t)cap_data[7];
if (!valid_block_size(msd->block_size)) return false;
// Drive >= 2 TiB: last LBA saturates to 0xFFFFFFFF; query READ CAPACITY (16) for the real value.
if (last_lba_10 == READ_CAP_10_OVERFLOW) {
if (!read_capacity_16(msd)) return false;
msd->use_16 = true;
} else {
msd->block_count = (uint64_t)last_lba_10 + 1;
}
return true;
}
bool msd_read_sectors(usb_msd_t *msd, uint64_t lba, uint32_t count, void *buffer)
{
if (msd->use_16 || (lba >> 32) != 0) {
uint8_t cdb[16] = {
SCSI_READ_16, 0,
(uint8_t)(lba >> 56), (uint8_t)(lba >> 48), (uint8_t)(lba >> 40), (uint8_t)(lba >> 32),
(uint8_t)(lba >> 24), (uint8_t)(lba >> 16), (uint8_t)(lba >> 8), (uint8_t)lba,
(uint8_t)(count >> 24), (uint8_t)(count >> 16), (uint8_t)(count >> 8), (uint8_t)count,
0, 0
};
return msd_bot_command(msd, cdb, 16, buffer, count * msd->block_size, true);
}
uint8_t cdb[10] = {
SCSI_READ_10, 0,
(uint8_t)(lba >> 24), (uint8_t)(lba >> 16), (uint8_t)(lba >> 8), (uint8_t)lba,
0,
(uint8_t)(count >> 8), (uint8_t)count, 0
};
return msd_bot_command(msd, cdb, 10, buffer, count * msd->block_size, true);
}
bool msd_write_sectors(usb_msd_t *msd, uint64_t lba, uint32_t count, const void *buffer)
{
if (msd->use_16 || (lba >> 32) != 0) {
uint8_t cdb[16] = {
SCSI_WRITE_16, 0,
(uint8_t)(lba >> 56), (uint8_t)(lba >> 48), (uint8_t)(lba >> 40), (uint8_t)(lba >> 32),
(uint8_t)(lba >> 24), (uint8_t)(lba >> 16), (uint8_t)(lba >> 8), (uint8_t)lba,
(uint8_t)(count >> 24), (uint8_t)(count >> 16), (uint8_t)(count >> 8), (uint8_t)count,
0, 0
};
return msd_bot_command(msd, cdb, 16, (void *)buffer, count * msd->block_size, false);
}
uint8_t cdb[10] = {
SCSI_WRITE_10, 0,
(uint8_t)(lba >> 24), (uint8_t)(lba >> 16), (uint8_t)(lba >> 8), (uint8_t)lba,
0,
(uint8_t)(count >> 8), (uint8_t)count, 0
};
return msd_bot_command(msd, cdb, 10, (void *)buffer, count * msd->block_size, false);
}
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef USBMSD_H
#define USBMSD_H
/**
* \file
*
* Provides USB Mass Storage (Bulk-Only Transport) support for reading and
* writing sectors on a USB drive.
*
*//*
* Copyright (C) 2026 Sam Demeulemeester.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "usbhcd.h"
/**
* Initialises the mass storage device by issuing TEST UNIT READY and
* READ CAPACITY commands. Populates msd->block_count and msd->block_size.
*
* \returns true if the device is ready and capacity was read successfully.
*/
bool msd_init(usb_msd_t *msd);
/**
* Reads one or more sectors from the mass storage device.
*
* \param msd - the mass storage device context.
* \param lba - the starting logical block address.
* \param count - the number of sectors to read.
* \param buffer - the destination buffer (must be at least count * block_size).
*
* \returns true if all sectors were read successfully.
*/
bool msd_read_sectors(usb_msd_t *msd, uint64_t lba, uint32_t count, void *buffer);
/**
* Writes one or more sectors to the mass storage device.
*
* \param msd - the mass storage device context.
* \param lba - the starting logical block address.
* \param count - the number of sectors to write.
* \param buffer - the source buffer (must be at least count * block_size).
*
* \returns true if all sectors were written successfully.
*/
bool msd_write_sectors(usb_msd_t *msd, uint64_t lba, uint32_t count, const void *buffer);
#endif // USBMSD_H
+2
View File
@@ -62,6 +62,8 @@ int print_spd_startup_info(void)
ram_slot_info[spdidx].isPopulated = curspd.isValid;
ram_slot_info[spdidx].hasTempSensor = false;
spd_slot_cache[spdidx] = curspd;
if (!curspd.isValid)
continue;
+327 -35
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021-2022 Martin Whitaker.
// Copyright (C) 2026 Sam Demeulemeester.
#include <stdbool.h>
#include <stdint.h>
@@ -87,6 +88,9 @@
#define XHCI_TRB_ADDRESS_DEVICE (11 << 10)
#define XHCI_TRB_CONFIGURE_ENDPOINT (12 << 10)
#define XHCI_TRB_EVALUATE_CONTEXT (13 << 10)
#define XHCI_TRB_RESET_ENDPOINT (14 << 10)
#define XHCI_TRB_STOP_ENDPOINT (15 << 10)
#define XHCI_TRB_SET_TR_DEQUEUE (16 << 10)
#define XHCI_TRB_NOOP (23 << 10)
#define XHCI_TRB_TRANSFER_EVENT (32 << 10)
#define XHCI_TRB_COMMAND_COMPLETE (33 << 10)
@@ -110,6 +114,7 @@
#define XHCI_FULL_SPEED 1
#define XHCI_LOW_SPEED 2
#define XHCI_HIGH_SPEED 3
#define XHCI_SUPER_SPEED 4
// Endpoint Type values
@@ -125,11 +130,13 @@
// Event Completion Code values
#define XHCI_EVENT_CC_SUCCESS 1
#define XHCI_EVENT_CC_SHORT_PACKET 13
#define XHCI_EVENT_CC_TIMEOUT 191 // specific to this driver
// Values specific to this driver.
#define PORT_TYPE_PST_MASK 0x1f // Protocol Slot Type mask
#define PORT_TYPE_IN_USE 0x20 // set when a device found by a scan owns the port
#define PORT_TYPE_USB2 0x40
#define PORT_TYPE_USB3 0x80
@@ -342,6 +349,20 @@ typedef struct {
// Keyboard endpoint ID lookup table
uint8_t kbd_ep_id [MAX_KEYBOARDS];
// Number of active keyboards
int num_keyboards;
// Keyboards whose interrupt TRB was consumed on behalf of another wait
// and must be re-issued by rearm_keyboards().
bool kbd_rearm_needed[MAX_KEYBOARDS];
// Raw xHCI port speed for the device currently being enumerated.
int port_speed;
// State needed to rescan the root ports after initialisation.
uint8_t num_ports;
uint8_t port_type[XHCI_MAX_PORTS];
} workspace_t __attribute__ ((aligned (64)));
//------------------------------------------------------------------------------
@@ -391,24 +412,13 @@ static usb_speed_t xhci_to_usb_speed(int xhci_speed)
case XHCI_HIGH_SPEED:
return USB_SPEED_HIGH;
default:
// Speed 4 = SuperSpeed (5 Gbps), speed 5+ = SuperSpeedPlus (10+ Gbps).
// Treat all SS variants the same for USB-level protocol purposes.
if (xhci_speed >= XHCI_SUPER_SPEED) return USB_SPEED_SUPER;
return USB_SPEED_UNKNOWN;
}
}
static int usb_to_xhci_speed(usb_speed_t usb_speed)
{
switch (usb_speed) {
case USB_SPEED_LOW:
return XHCI_LOW_SPEED;
case USB_SPEED_FULL:
return XHCI_FULL_SPEED;
case USB_SPEED_HIGH:
return XHCI_HIGH_SPEED;
default:
return 0;
}
}
static int xhci_ep_interval(int config_interval, usb_speed_t device_speed)
{
if (device_speed < USB_SPEED_HIGH) {
@@ -495,6 +505,20 @@ static int event_ep_id(const xhci_trb_t *event)
return (event->control >> 16) & 0x1f;
}
static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id);
// Records a keyboard completion consumed on behalf of another wait, so that
// rearm_keyboards() knows which endpoints need a fresh transfer.
static void note_discarded_event(workspace_t *ws, const xhci_trb_t *event)
{
if (event_type(event) != XHCI_TRB_TRANSFER_EVENT) return;
int kbd_idx = identify_keyboard(ws, event_slot_id(event), event_ep_id(event));
if (kbd_idx >= 0) {
ws->kbd_rearm_needed[kbd_idx] = true;
}
}
static uint32_t enqueue_trb(xhci_trb_t *trb_ring, uint32_t ring_size, uint32_t enqueue_state,
uint32_t control, uint64_t params1, uint32_t params2)
{
@@ -559,19 +583,48 @@ static bool get_xhci_event(workspace_t *ws, xhci_trb_t *event)
static uint32_t wait_for_xhci_event(workspace_t *ws, uint32_t wanted_type, int max_time, xhci_trb_t *event)
{
int timer = max_time >> 3;
while (!get_xhci_event(ws, event) || event_type(event) != wanted_type) {
while (true) {
while (get_xhci_event(ws, event)) {
if (event_type(event) == wanted_type) {
return event_cc(event);
}
note_discarded_event(ws, event);
}
if (timer == 0) return XHCI_EVENT_CC_TIMEOUT;
usleep(8);
timer--;
}
return event_cc(event);
}
// Waits for a transfer event from a specific endpoint, discarding unrelated events
// (e.g. keyboard interrupt completions, which share the same event ring).
static uint32_t wait_for_ep_transfer_event(workspace_t *ws, int slot_id, int ep_id, int max_time, xhci_trb_t *event)
{
int timer = max_time >> 3;
while (timer > 0) {
while (get_xhci_event(ws, event)) {
if (event_type(event) == XHCI_TRB_TRANSFER_EVENT
&& event_slot_id(event) == slot_id
&& event_ep_id(event) == ep_id) {
return event_cc(event);
}
note_discarded_event(ws, event);
}
usleep(8);
timer--;
}
return XHCI_EVENT_CC_TIMEOUT;
}
static void issue_setup_stage_trb(ep_tr_t *ep_tr, const usb_setup_pkt_t *setup_pkt)
{
uint64_t params1 = *(const uint64_t *)setup_pkt;
uint32_t params2 = sizeof(usb_setup_pkt_t);
uint32_t control = XHCI_TRB_SETUP_STAGE | XHCI_TRB_TRT_IN | XHCI_TRB_IDT;
// TRT must match the actual transfer: no-data OUT (0), OUT data (2), IN data (3).
uint32_t trt = (setup_pkt->length == 0) ? XHCI_TRB_TRT_NO_DATA
: (setup_pkt->type & 0x80) ? XHCI_TRB_TRT_IN
: XHCI_TRB_TRT_OUT;
uint32_t control = XHCI_TRB_SETUP_STAGE | trt | XHCI_TRB_IDT;
ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2);
}
@@ -612,7 +665,7 @@ static bool setup_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_se
issue_setup_stage_trb(ep_tr, setup_pkt);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN);
ring_device_doorbell(ws->db_regs, ep->device_id, 1);
return (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 5000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
return (wait_for_ep_transfer_event(ws, ep->device_id, 1, 5000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
}
static bool get_data_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt,
@@ -628,7 +681,7 @@ static bool get_data_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb
issue_data_stage_trb(ep_tr, buffer, XHCI_TRB_DIR_IN, length);
issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT);
ring_device_doorbell(ws->db_regs, ep->device_id, 1);
return (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 5000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
return (wait_for_ep_transfer_event(ws, ep->device_id, 1, 5000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
}
static bool reset_root_hub_port(const usb_hcd_t *hcd, int port_num)
@@ -724,7 +777,21 @@ static bool assign_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_
ctrl_context->add_context_flags = XHCI_CONTEXT_A(0) | XHCI_CONTEXT_A(1);
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + ws->context_size);
slot_context->params1 = 1 << 27 | usb_to_xhci_speed(device_speed) << 20;
// For root hub devices, use the raw port speed (preserves SS/SS+ variants).
// For hub-connected devices, derive from the USB speed enum.
int slot_speed;
if (hub->level == 0) {
slot_speed = ws->port_speed;
} else {
static const int speed_map[] = {
[USB_SPEED_LOW] = XHCI_LOW_SPEED,
[USB_SPEED_FULL] = XHCI_FULL_SPEED,
[USB_SPEED_HIGH] = XHCI_HIGH_SPEED,
[USB_SPEED_SUPER] = XHCI_SUPER_SPEED
};
slot_speed = (device_speed <= USB_SPEED_SUPER) ? speed_map[device_speed] : XHCI_SUPER_SPEED;
}
slot_context->params1 = 1 << 27 | slot_speed << 20;
if (hub->level > 0) {
uint32_t route = usb_route(hub, port_num);
slot_context->params1 |= route & 0xfffff;
@@ -787,7 +854,10 @@ static bool assign_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_
return false;
}
}
ep_context->max_packet_size = device->max_packet_size;
// For SS, bMaxPacketSize0 is an exponent (2^N bytes); expand it here.
ep_context->max_packet_size = (device_speed == USB_SPEED_SUPER)
? (1 << device->max_packet_size)
: device->max_packet_size;
ep_context->tr_dequeue_ptr += 3 * sizeof(xhci_trb_t);
fetch_length = sizeof(usb_device_desc_t);
@@ -855,6 +925,105 @@ static bool configure_kbd_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int kbd_i
return configure_interrupt_endpoint(ws, ep, 0, 0, 0, (uintptr_t)(&ws->kbd_tr[kbd_idx]), sizeof(hid_kbd_rpt_t));
}
static bool configure_bulk_endpoint(workspace_t *ws, const usb_ep_t *ep, int ep_id, bool is_out)
{
xhci_trb_t event;
int xhci_ep_type = is_out ? XHCI_EP_BULK_OUT : XHCI_EP_BULK_IN;
// Allocate a transfer ring for this endpoint.
uintptr_t tr_addr = heap_alloc(HEAP_TYPE_LM_1, sizeof(ep_tr_t), 64);
if (tr_addr == 0) return false;
ep_tr_t *ep_tr = (ep_tr_t *)tr_addr;
memset((void *)ep_tr, 0, sizeof(ep_tr_t));
ep_tr->enqueue_state = EP_TR_SIZE; // cycle = 1, index = 0
xhci_ctrl_context_t *ctrl_context = (xhci_ctrl_context_t *)ws->input_context_addr;
ctrl_context->add_context_flags = XHCI_CONTEXT_A(0) | XHCI_CONTEXT_A(ep_id);
xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + ws->context_size);
int current_max_ep_id = slot_context->params1 >> 27;
if (ep_id > current_max_ep_id) {
slot_context->params1 = (slot_context->params1 & 0x07ffffff) | (ep_id << 27);
}
xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + (1 + ep_id) * ws->context_size);
ep_context->params1 = 0;
ep_context->params2 = xhci_ep_type << 3 | 3 << 1; // EP Type | CErr
ep_context->interval = 0;
ep_context->max_burst_size = 0;
ep_context->max_packet_size = ep->max_packet_size;
ep_context->tr_dequeue_ptr = tr_addr | 1;
ep_context->average_trb_length = ep->max_packet_size;
ep_context->max_esit_payload_l = 0;
ep_context->max_esit_payload_h = 0;
enqueue_xhci_command(ws, XHCI_TRB_CONFIGURE_ENDPOINT | ep->device_id << 24, ws->input_context_addr, 0);
ring_host_controller_doorbell(ws->db_regs);
if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 1000*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) {
return false;
}
// Store the transfer ring address in the data_buffer so the caller can retrieve it.
*(uintptr_t *)ws->base_ws.data_buffer = tr_addr;
return true;
}
static bool configure_bulk_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int ep_id, bool is_out)
{
workspace_t *ws = (workspace_t *)hcd->ws;
return configure_bulk_endpoint(ws, ep, ep_id, is_out);
}
static bool bulk_transfer(const usb_hcd_t *hcd, const usb_ep_t *ep, void *buffer, size_t length, bool is_out)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ep_tr_t *ep_tr = (ep_tr_t *)ep->driver_data;
int ep_id = is_out ? (2 * ep->endpoint_num) : (2 * ep->endpoint_num + 1);
xhci_trb_t event;
// Issue a NORMAL TRB for the bulk transfer.
uint32_t dir = is_out ? XHCI_TRB_DIR_OUT : XHCI_TRB_DIR_IN;
issue_normal_trb(ep_tr, buffer, dir, length);
ring_device_doorbell(ws->db_regs, ep->device_id, ep_id);
uint32_t cc = wait_for_ep_transfer_event(ws, ep->device_id, ep_id, 5000*MILLISEC, &event);
if (cc == XHCI_EVENT_CC_SUCCESS) return true;
// A short packet is a valid completion for IN transfers (device sent less than requested).
return !is_out && cc == XHCI_EVENT_CC_SHORT_PACKET;
}
static bool reset_bulk_ep(const usb_hcd_t *hcd, const usb_ep_t *ep, int ep_id)
{
workspace_t *ws = (workspace_t *)hcd->ws;
ep_tr_t *ep_tr = (ep_tr_t *)ep->driver_data;
xhci_trb_t event;
// Stop then reset the endpoint. One of the two completes with a context state
// error depending on whether the endpoint was Running or Halted - ignore that.
enqueue_xhci_command(ws, XHCI_TRB_STOP_ENDPOINT | ep_id << 16 | ep->device_id << 24, 0, 0);
ring_host_controller_doorbell(ws->db_regs);
(void)wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 1000*MILLISEC, &event);
enqueue_xhci_command(ws, XHCI_TRB_RESET_ENDPOINT | ep_id << 16 | ep->device_id << 24, 0, 0);
ring_host_controller_doorbell(ws->db_regs);
(void)wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 1000*MILLISEC, &event);
// Re-sync the controller's dequeue pointer (and cycle state) with our enqueue state.
uint32_t cycle = ep_tr->enqueue_state / EP_TR_SIZE;
uint32_t index = ep_tr->enqueue_state % EP_TR_SIZE;
uint64_t dequeue_ptr = (uintptr_t)&ep_tr->tr[index] | cycle;
enqueue_xhci_command(ws, XHCI_TRB_SET_TR_DEQUEUE | ep_id << 16 | ep->device_id << 24, dequeue_ptr, 0);
ring_host_controller_doorbell(ws->db_regs);
return (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 1000*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS);
}
static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id)
{
for (int kbd_idx = 0; kbd_idx < MAX_KEYBOARDS; kbd_idx++) {
@@ -865,6 +1034,37 @@ static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id)
return -1;
}
static void rearm_keyboards(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
// Drain any stale events from the event ring, noting keyboard completions.
xhci_trb_t event;
while (get_xhci_event(ws, &event)) {
note_discarded_event(ws, &event);
}
// Re-issue a NORMAL TRB only where one was consumed; the others are still armed.
for (int kbd_idx = 0; kbd_idx < ws->num_keyboards; kbd_idx++) {
if (!ws->kbd_rearm_needed[kbd_idx]) continue;
ws->kbd_rearm_needed[kbd_idx] = false;
ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx];
hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx];
// The discarded transfer completed, so its report is already in the buffer.
// Process it, otherwise a key release goes unnoticed and the next press of
// the same key is treated as a repeat and dropped.
hid_kbd_rpt_t *prev_kbd_rpt = &ws->prev_kbd_rpt[kbd_idx];
if (process_usb_keyboard_report(hcd, kbd_rpt, prev_kbd_rpt)) {
*prev_kbd_rpt = *kbd_rpt;
}
issue_normal_trb(kbd_tr, kbd_rpt, XHCI_TRB_DIR_IN, sizeof(hid_kbd_rpt_t));
ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]);
}
}
static void poll_keyboards(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
@@ -890,6 +1090,77 @@ static void poll_keyboards(const usb_hcd_t *hcd)
}
}
static bool scan_for_msd(const usb_hcd_t *hcd)
{
workspace_t *ws = (workspace_t *)hcd->ws;
xhci_op_regs_t *op_regs = ws->op_regs;
// Record the heap state to allow us to free memory if the scan fails.
uintptr_t initial_heap_mark = heap_mark(HEAP_TYPE_LM_1);
// Construct a hub descriptor for the root hub.
usb_hub_t root_hub;
memset(&root_hub, 0, sizeof(root_hub));
root_hub.ep0 = NULL;
root_hub.num_ports = ws->num_ports;
usleep(100*MILLISEC); // USB maximum device attach time.
// Scan the ports that are not already in use, looking for a USB drive.
usb_ep_t keyboards[1];
for (int port_idx = 0; port_idx < ws->num_ports; port_idx++) {
// Skip ports that are neither USB2 or USB3.
if (!(ws->port_type[port_idx] & (PORT_TYPE_USB2 | PORT_TYPE_USB3))) continue;
uint32_t port_status = read32(&op_regs->port_regs[port_idx].sc);
// Skip ports owned by a device found during a previous scan, unless it was unplugged.
if (ws->port_type[port_idx] & PORT_TYPE_IN_USE) {
if (port_status & XHCI_PORT_SC_CCS) continue;
ws->port_type[port_idx] &= ~PORT_TYPE_IN_USE;
}
// Check if anything is connected to this port.
if (~port_status & XHCI_PORT_SC_CCS) continue;
// Reset the port.
if (!reset_xhci_port(op_regs, port_idx)) continue;
usleep(10*MILLISEC); // USB reset recovery time
port_status = read32(&op_regs->port_regs[port_idx].sc);
// Check the port is active.
if (~port_status & XHCI_PORT_SC_CCS) continue;
if (~port_status & XHCI_PORT_SC_PED) continue;
// Now the port has been enabled, we can determine the device speed.
ws->port_speed = get_xhci_device_speed(op_regs, port_idx);
usb_speed_t device_speed = xhci_to_usb_speed(ws->port_speed);
// Allocate a controller slot for this device.
int slot_id = allocate_slot(hcd);
if (slot_id == 0) break;
// With max_keyboards = 0 only the mass storage path can succeed, so a true
// return value means the USB drive was found on this port.
int num_devices = 0;
int num_keyboards = 0;
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, slot_id,
&num_devices, keyboards, 0, &num_keyboards)) {
ws->port_type[port_idx] |= PORT_TYPE_IN_USE;
return true;
}
disable_xhci_port(op_regs, port_idx);
release_slot(hcd, slot_id);
}
heap_rewind(HEAP_TYPE_LM_1, initial_heap_mark);
return false;
}
//------------------------------------------------------------------------------
// Driver Method Table
//------------------------------------------------------------------------------
@@ -903,7 +1174,12 @@ static const hcd_methods_t methods = {
.configure_kbd_ep = configure_kbd_ep,
.setup_request = setup_request,
.get_data_request = get_data_request,
.poll_keyboards = poll_keyboards
.poll_keyboards = poll_keyboards,
.rearm_keyboards = rearm_keyboards,
.configure_bulk_ep = configure_bulk_ep,
.bulk_transfer = bulk_transfer,
.reset_bulk_ep = reset_bulk_ep,
.scan_for_msd = scan_for_msd
};
//------------------------------------------------------------------------------
@@ -1085,6 +1361,9 @@ bool xhci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
ws->rt_regs = rt_regs;
ws->db_regs = db_regs;
// Record the port types to allow us to rescan the root ports later.
memcpy(ws->port_type, port_type, sizeof(ws->port_type));
ws->device_context_index = device_context_index;
ws->context_size = cap_regs->hcc_params1 & 0x4 ? 64 : 32;
@@ -1126,18 +1405,24 @@ bool xhci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
root_hub.ep0 = NULL;
root_hub.num_ports = cap_regs->hcs_params1 & 0xff;
ws->num_ports = root_hub.num_ports;
usleep(100*MILLISEC); // USB maximum device attach time.
// Scan the ports, looking for hubs and keyboards.
// Scan the ports, looking for hubs, keyboards, and mass storage devices.
usb_ep_t keyboards[MAX_KEYBOARDS];
int num_keyboards = 0;
int num_devices = 0;
bool msd_found_before = usb_mass_storage_found;
for (int port_idx = 0; port_idx < root_hub.num_ports; port_idx++) {
// If we've filled the keyboard info table, abort now.
if (num_keyboards >= MAX_KEYBOARDS) break;
if (num_keyboards >= MAX_KEYBOARDS && usb_mass_storage_found) break;
// We only expect to find keyboards on USB2 ports.
if (~port_type[port_idx] & PORT_TYPE_USB2) continue;
// Skip ports that are neither USB2 or USB3.
if (!(port_type[port_idx] & (PORT_TYPE_USB2 | PORT_TYPE_USB3))) continue;
// USB3 ports only need scanning for MSDs.
if ((port_type[port_idx] & PORT_TYPE_USB3) && usb_mass_storage_found) continue;
uint32_t port_status = read32(&op_regs->port_regs[port_idx].sc);
@@ -1156,7 +1441,9 @@ bool xhci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
if (~port_status & XHCI_PORT_SC_PED) continue;
// Now the port has been enabled, we can determine the device speed.
usb_speed_t device_speed = xhci_to_usb_speed(get_xhci_device_speed(ws->op_regs, port_idx));
int raw_speed = get_xhci_device_speed(ws->op_regs, port_idx);
ws->port_speed = raw_speed;
usb_speed_t device_speed = xhci_to_usb_speed(raw_speed);
num_devices++;
@@ -1167,24 +1454,29 @@ bool xhci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
// Look for keyboards attached directly or indirectly to this port.
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, slot_id,
&num_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
ws->port_type[port_idx] |= PORT_TYPE_IN_USE;
continue;
}
// If we didn't find any keyboard interfaces, we disable the port and free the slot.
// If we didn't find any keyboard interfaces or a USB drive on this port
// (find_attached_usb_keyboards returns true for both), disable it and free the slot.
disable_xhci_port(op_regs, port_idx);
release_slot(hcd, slot_id);
}
print_usb_info(" Found %i device%s, %i keyboard%s",
num_devices, num_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "");
// True only if the drive was found on this controller during the scan above.
bool msd_on_this_hcd = usb_mass_storage_found && !msd_found_before;
if (num_keyboards == 0) {
(void)halt_host_controller(op_regs);
goto no_keyboards_found;
}
print_usb_info(" Found %i device%s, %i keyboard%s%s",
num_devices, num_devices != 1 ? "s" : "",
num_keyboards, num_keyboards != 1 ? "s" : "",
msd_on_this_hcd ? ", 1 USB drive" : "");
// Even if no device was found, keep the controller registered so its root ports
// can be rescanned later by usb_scan_for_msd().
// Initialise the interrupt TRB ring for each keyboard interface.
ws->num_keyboards = num_keyboards;
for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) {
ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx];
kbd_tr->enqueue_state = EP_TR_SIZE; // cycle = 1, index = 0