mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2026-08-11 05:24:44 -05:00
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:
+229
-36
@@ -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
@@ -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.
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user