feat: support for USB mass storage devices, hotplugging

This commit is contained in:
2026-05-18 17:47:12 +02:00
parent f8e8af1d78
commit 41d2841cc9
36 changed files with 1537 additions and 130 deletions
+3
View File
@@ -748,6 +748,9 @@ namespace Drivers::Storage::Ahci {
bdev.Ctx = (void*)(uintptr_t)i;
bdev.SectorCount = g_ports[i].SectorCount;
bdev.SectorSize = g_ports[i].SectorSizeLog;
bdev.Kind = (type == PortType::Satapi)
? Storage::BLOCK_KIND_SATAPI
: Storage::BLOCK_KIND_SATA;
memcpy(bdev.Model, g_ports[i].Model, 41);
Storage::RegisterBlockDevice(bdev);
}
+29 -6
View File
@@ -9,21 +9,44 @@
namespace Drivers::Storage {
static BlockDevice g_devices[MaxBlockDevices] = {};
static int g_deviceCount = 0;
static bool g_active[MaxBlockDevices] = {};
static int g_deviceHighWater = 0;
int RegisterBlockDevice(const BlockDevice& dev) {
if (g_deviceCount >= MaxBlockDevices) return -1;
g_devices[g_deviceCount] = dev;
return g_deviceCount++;
for (int i = 0; i < MaxBlockDevices; i++) {
if (g_active[i]) continue;
g_devices[i] = dev;
g_active[i] = true;
if (i >= g_deviceHighWater) {
g_deviceHighWater = i + 1;
}
return i;
}
return -1;
}
int UnregisterBlockDevice(int index) {
if (index < 0 || index >= MaxBlockDevices || !g_active[index]) return -1;
g_active[index] = false;
g_devices[index] = {};
while (g_deviceHighWater > 0 && !g_active[g_deviceHighWater - 1]) {
g_deviceHighWater--;
}
return 0;
}
const BlockDevice* GetBlockDevice(int index) {
if (index < 0 || index >= g_deviceCount) return nullptr;
if (index < 0 || index >= MaxBlockDevices || !g_active[index]) return nullptr;
return &g_devices[index];
}
int GetBlockDeviceCount() {
return g_deviceCount;
return g_deviceHighWater;
}
};
+16 -2
View File
@@ -11,22 +11,36 @@ namespace Drivers::Storage {
static constexpr int MaxBlockDevices = 32;
enum BlockDeviceKind : uint8_t {
BLOCK_KIND_UNKNOWN = 0,
BLOCK_KIND_SATA = 1,
BLOCK_KIND_SATAPI = 2,
BLOCK_KIND_NVME = 3,
BLOCK_KIND_USB_MSC = 4,
};
struct BlockDevice {
bool (*ReadSectors)(void* ctx, uint64_t lba, uint32_t count, void* buffer);
bool (*WriteSectors)(void* ctx, uint64_t lba, uint32_t count, const void* buffer);
void* Ctx;
uint64_t SectorCount;
uint16_t SectorSize;
uint8_t Kind;
uint8_t Reserved;
char Model[41];
};
// Register a block device. Returns the assigned index, or -1 on failure.
// Register a block device in the lowest free slot. Returns the assigned
// index, or -1 on failure.
int RegisterBlockDevice(const BlockDevice& dev);
// Unregister a block device by index. Returns 0 on success, -1 on failure.
int UnregisterBlockDevice(int index);
// Get a registered block device by index. Returns nullptr if invalid.
const BlockDevice* GetBlockDevice(int index);
// Get the number of registered block devices.
// Get the high-water count for registered block-device slots.
int GetBlockDeviceCount();
};
+177 -2
View File
@@ -62,6 +62,18 @@ namespace Drivers::Storage::Gpt {
a.Data4[6] == b.Data4[6] && a.Data4[7] == b.Data4[7];
}
static int FindExistingPartition(int blockDevIndex, uint64_t startLba, uint64_t endLba) {
for (int i = 0; i < g_partitionCount; i++) {
if (g_partitions[i].BlockDevIndex == blockDevIndex &&
g_partitions[i].StartLba == startLba &&
g_partitions[i].EndLba == endLba) {
return i;
}
}
return -1;
}
// -------------------------------------------------------------------------
// UTF-16LE to ASCII narrowing
// -------------------------------------------------------------------------
@@ -76,6 +88,53 @@ namespace Drivers::Storage::Gpt {
dst[j] = '\0';
}
static void CopyAsciiName(const char* src, char* dst, int dstMax) {
int i = 0;
for (; src && src[i] && i < dstMax - 1; i++) {
dst[i] = src[i];
}
dst[i] = '\0';
}
static bool IsExtendedMbrType(uint8_t type) {
return type == 0x05 || type == 0x0F || type == 0x85;
}
static Guid GuidForMbrType(uint8_t type) {
switch (type) {
case 0x82:
return GUID_LINUX_SWAP;
case 0x83:
return GUID_LINUX_FS;
default:
return GUID_BASIC_DATA;
}
}
static int AppendMbrPartition(int blockDevIndex, uint64_t diskSectorCount,
uint64_t startLba, uint64_t sectorCount,
uint8_t type, const char* name) {
if (sectorCount == 0 || g_partitionCount >= MaxPartitions) return 0;
if (startLba >= diskSectorCount || sectorCount > diskSectorCount - startLba) return 0;
uint64_t endLba = startLba + sectorCount - 1;
if (endLba < startLba) return 0;
if (FindExistingPartition(blockDevIndex, startLba, endLba) >= 0) return 0;
PartitionInfo& part = g_partitions[g_partitionCount];
part.BlockDevIndex = blockDevIndex;
part.StartLba = startLba;
part.EndLba = endLba;
part.SectorCount = sectorCount;
part.TypeGuid = GuidForMbrType(type);
part.UniqueGuid = GUID_UNUSED;
part.Attributes = 0;
CopyAsciiName(name, part.Name, 72);
g_partitionCount++;
return 1;
}
// -------------------------------------------------------------------------
// Validate protective MBR
// -------------------------------------------------------------------------
@@ -237,6 +296,7 @@ namespace Drivers::Storage::Gpt {
if (GuidIsZero(entry->TypeGuid)) continue;
if (entry->StartingLba == 0 || entry->EndingLba == 0) continue;
if (entry->StartingLba > entry->EndingLba) continue;
if (FindExistingPartition(blockDevIndex, entry->StartingLba, entry->EndingLba) >= 0) continue;
PartitionInfo& part = g_partitions[g_partitionCount];
part.BlockDevIndex = blockDevIndex;
@@ -258,6 +318,88 @@ namespace Drivers::Storage::Gpt {
return found;
}
static int ParseExtendedMbr(const BlockDevice* dev, int blockDevIndex,
uint64_t extendedBase, int& logicalNumber) {
int found = 0;
uint64_t ebrLba = extendedBase;
for (int guard = 0; guard < 32 && g_partitionCount < MaxPartitions; guard++) {
uint8_t ebr[512];
if (!dev->ReadSectors(dev->Ctx, ebrLba, 1, ebr)) break;
const ProtectiveMbr* mbr = (const ProtectiveMbr*)ebr;
if (mbr->Signature != 0xAA55) break;
const MbrPartitionEntry& logical = mbr->Partitions[0];
if (logical.Type != 0 && logical.SectorCount != 0 &&
!IsExtendedMbrType(logical.Type)) {
char name[16] = {};
name[0] = 'M'; name[1] = 'B'; name[2] = 'R';
int n = logicalNumber++;
name[3] = (char)('0' + ((n / 10) % 10));
name[4] = (char)('0' + (n % 10));
name[5] = '\0';
found += AppendMbrPartition(blockDevIndex,
dev->SectorCount,
ebrLba + logical.LbaFirst,
logical.SectorCount,
logical.Type,
name);
}
const MbrPartitionEntry& next = mbr->Partitions[1];
if (next.Type == 0 || next.SectorCount == 0 || !IsExtendedMbrType(next.Type)) {
break;
}
ebrLba = extendedBase + next.LbaFirst;
}
return found;
}
static int ParseMbrPartitions(const BlockDevice* dev, const uint8_t* sector0,
int blockDevIndex) {
const ProtectiveMbr* mbr = (const ProtectiveMbr*)sector0;
if (mbr->Signature != 0xAA55) {
KernelLogStream(INFO, "MBR") << "No partition table on device "
<< blockDevIndex << " (LBA 0 signature absent)";
return 0;
}
int found = 0;
int logicalNumber = 5;
for (int i = 0; i < 4 && g_partitionCount < MaxPartitions; i++) {
const MbrPartitionEntry& entry = mbr->Partitions[i];
if (entry.Type == 0 || entry.SectorCount == 0) continue;
if (IsExtendedMbrType(entry.Type)) {
found += ParseExtendedMbr(dev, blockDevIndex, entry.LbaFirst, logicalNumber);
continue;
}
char name[8] = {};
name[0] = 'M'; name[1] = 'B'; name[2] = 'R';
name[3] = (char)('1' + i);
name[4] = '\0';
found += AppendMbrPartition(blockDevIndex, dev->SectorCount, entry.LbaFirst,
entry.SectorCount, entry.Type, name);
}
if (found > 0) {
KernelLogStream(OK, "MBR") << "Found " << found
<< " partition(s) on device " << blockDevIndex;
} else {
KernelLogStream(INFO, "MBR") << "Device " << blockDevIndex
<< " has MBR signature but no partition entries";
}
return found;
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
@@ -281,12 +423,15 @@ namespace Drivers::Storage::Gpt {
uint8_t sectorBuf[1024];
if (!dev->ReadSectors(dev->Ctx, 0, 2, sectorBuf)) {
KernelLogStream(ERROR, "GPT") << "Failed to read LBA 0-1 from device "
<< blockDevIndex;
return 0;
}
// Validate protective MBR
// Validate protective MBR. If this is a legacy-partitioned disk instead
// of GPT, keep using the same partition registry for MBR entries.
if (!ValidateProtectiveMbr(sectorBuf)) {
return 0;
return ParseMbrPartitions(dev, sectorBuf, blockDevIndex);
}
// Validate primary GPT header (LBA 1)
@@ -375,6 +520,36 @@ namespace Drivers::Storage::Gpt {
return &g_partitions[index];
}
int RemovePartitionsForBlockDevice(int blockDevIndex) {
int removed = 0;
int dst = 0;
for (int src = 0; src < g_partitionCount; src++) {
if (g_partitions[src].BlockDevIndex == blockDevIndex) {
removed++;
continue;
}
if (dst != src) {
g_partitions[dst] = g_partitions[src];
}
dst++;
}
for (int i = dst; i < g_partitionCount; i++) {
g_partitions[i] = {};
}
g_partitionCount = dst;
if (removed > 0) {
KernelLogStream(INFO, "GPT") << "Removed " << removed
<< " partition record(s) for device " << blockDevIndex;
}
return removed;
}
// -------------------------------------------------------------------------
// ASCII to UTF-16LE for partition names
// -------------------------------------------------------------------------
+7 -3
View File
@@ -110,11 +110,11 @@ namespace Drivers::Storage::Gpt {
// Public API
// =========================================================================
// Probe a block device for a GPT. Returns the number of partitions found,
// or 0 if no valid GPT was detected.
// Probe a block device for GPT, with a legacy MBR fallback. Returns the
// number of new partitions found, or 0 if no valid partition table exists.
int ProbeDevice(int blockDevIndex);
// Probe all registered block devices for GPT.
// Probe all registered block devices for partitions.
void ProbeAll();
// Get total number of discovered partitions (across all devices).
@@ -123,6 +123,10 @@ namespace Drivers::Storage::Gpt {
// Get partition info by global index.
const PartitionInfo* GetPartition(int index);
// Remove all in-memory partition records for a block device. Used when a
// hot-pluggable block device disappears.
int RemovePartitionsForBlockDevice(int blockDevIndex);
// Look up the human-readable name for a partition type GUID.
const char* GetTypeName(const Guid& typeGuid);
+1
View File
@@ -668,6 +668,7 @@ namespace Drivers::Storage::Nvme {
bdev.Ctx = (void*)(uintptr_t)i;
bdev.SectorCount = g_namespaces[i].SectorCount;
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
bdev.Kind = Storage::BLOCK_KIND_NVME;
memcpy(bdev.Model, g_namespaces[i].Model, 41);
Storage::RegisterBlockDevice(bdev);
}
+34
View File
@@ -261,6 +261,40 @@ namespace Drivers::USB::HidKeyboard {
KernelLogStream(OK, "USB/KB") << "Registered HID keyboard on slot " << (uint64_t)slotId;
}
void UnregisterDevice(uint8_t slotId) {
if (g_SlotId != slotId) return;
uint8_t modifiers = g_PrevModifiers;
for (int i = 0; i < 6; i++) {
uint8_t key = g_PrevKeys[i];
if (key == 0) continue;
uint8_t scancode = g_HidToScancode[key];
if (scancode != 0) {
InjectKey(scancode, false, modifiers, IsNonCharKey(key));
}
g_PrevKeys[i] = 0;
}
if (modifiers & MOD_LEFT_CTRL) InjectModifierKey(SC_LEFT_CTRL, false, 0);
if (modifiers & MOD_LEFT_SHIFT) InjectModifierKey(SC_LEFT_SHIFT, false, 0);
if (modifiers & MOD_LEFT_ALT) InjectModifierKey(SC_LEFT_ALT, false, 0);
if (modifiers & MOD_LEFT_GUI) InjectModifierKey(SC_LEFT_GUI, false, 0);
if (modifiers & MOD_RIGHT_CTRL) InjectModifierKey(SC_RIGHT_CTRL, false, 0);
if (modifiers & MOD_RIGHT_SHIFT) InjectModifierKey(SC_RIGHT_SHIFT, false, 0);
if (modifiers & MOD_RIGHT_ALT) InjectModifierKey(SC_RIGHT_ALT, false, 0);
if (modifiers & MOD_RIGHT_GUI) InjectModifierKey(SC_RIGHT_GUI, false, 0);
g_SlotId = 0;
g_PrevModifiers = 0;
g_RepeatKey = 0;
g_RepeatStartMs = 0;
g_LastRepeatMs = 0;
KernelLogStream(INFO, "USB/KB") << "Unregistered HID keyboard on slot "
<< (uint64_t)slotId;
}
void ProcessReport(const uint8_t* data, uint16_t length) {
if (length < 8) return;
+3
View File
@@ -12,6 +12,9 @@ namespace Drivers::USB::HidKeyboard {
// Register a keyboard device by slot ID
void RegisterDevice(uint8_t slotId);
// Unregister a keyboard device and release any held keys/modifiers.
void UnregisterDevice(uint8_t slotId);
// Process an 8-byte boot protocol keyboard report
void ProcessReport(const uint8_t* data, uint16_t length);
+12
View File
@@ -195,6 +195,18 @@ namespace Drivers::USB::HidMouse {
KernelLogStream(OK, "USB/Mouse") << "Registered HID mouse on slot " << (uint64_t)slotId;
}
void UnregisterDevice(uint8_t slotId) {
if (g_SlotId != slotId) return;
Drivers::PS2::Mouse::InjectMouseReport(0, 0, 0, 0);
g_SlotId = 0;
g_Format = {};
g_FormatValid = false;
KernelLogStream(INFO, "USB/Mouse") << "Unregistered HID mouse on slot "
<< (uint64_t)slotId;
}
void ProcessReport(const uint8_t* data, uint16_t length) {
if (length < 3) return;
+3
View File
@@ -26,6 +26,9 @@ namespace Drivers::USB::HidMouse {
// Register a mouse device by slot ID
void RegisterDevice(uint8_t slotId);
// Unregister a mouse device and release any pressed buttons.
void UnregisterDevice(uint8_t slotId);
// Parse a HID Report Descriptor to determine the report layout.
// Must be called before ProcessReport for Report Protocol mice.
void ParseReportDescriptor(const uint8_t* desc, uint16_t length);
+670
View File
@@ -0,0 +1,670 @@
/*
* MassStorage.cpp
* USB Mass Storage Bulk-Only Transport driver
* Copyright (c) 2026 Daniel Hammer
*/
#include "MassStorage.hpp"
#include "Xhci.hpp"
#include "UsbDevice.hpp"
#include <Drivers/Storage/BlockDevice.hpp>
#include <Drivers/Storage/Gpt.hpp>
#include <Fs/FsProbe.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Libraries/Memory.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <atomic>
using namespace Kt;
namespace Drivers::USB::MassStorage {
// -------------------------------------------------------------------------
// USB Mass Storage Bulk-Only Transport constants
// -------------------------------------------------------------------------
constexpr uint32_t CBW_SIGNATURE = 0x43425355; // "USBC"
constexpr uint32_t CSW_SIGNATURE = 0x53425355; // "USBS"
constexpr uint8_t REQ_BULK_ONLY_RESET = 0xFF;
constexpr uint8_t REQ_GET_MAX_LUN = 0xFE;
constexpr uint8_t CBW_FLAG_DATA_IN = 0x80;
constexpr uint8_t CSW_STATUS_PASSED = 0x00;
constexpr uint8_t CSW_STATUS_FAILED = 0x01;
constexpr uint8_t CSW_STATUS_PHASE_ERROR = 0x02;
constexpr uint8_t SCSI_TEST_UNIT_READY = 0x00;
constexpr uint8_t SCSI_REQUEST_SENSE = 0x03;
constexpr uint8_t SCSI_INQUIRY = 0x12;
constexpr uint8_t SCSI_READ_CAPACITY10 = 0x25;
constexpr uint8_t SCSI_READ10 = 0x28;
constexpr uint8_t SCSI_WRITE10 = 0x2A;
constexpr uint8_t SCSI_SERVICE_ACTION = 0x9E;
constexpr uint8_t SCSI_READ16 = 0x88;
constexpr uint8_t SCSI_WRITE16 = 0x8A;
constexpr uint8_t SERVICE_READ_CAPACITY16 = 0x10;
constexpr uint32_t MAX_BULK_TRANSFER_BYTES = 0x10000;
constexpr int MAX_MSC_DEVICES = 16;
struct CommandBlockWrapper {
uint32_t Signature;
uint32_t Tag;
uint32_t DataTransferLength;
uint8_t Flags;
uint8_t Lun;
uint8_t CommandBlockLength;
uint8_t CommandBlock[16];
} __attribute__((packed));
static_assert(sizeof(CommandBlockWrapper) == 31);
struct CommandStatusWrapper {
uint32_t Signature;
uint32_t Tag;
uint32_t DataResidue;
uint8_t Status;
} __attribute__((packed));
static_assert(sizeof(CommandStatusWrapper) == 13);
struct Device {
bool Active;
uint8_t SlotId;
uint8_t Lun;
uint8_t InterfaceNumber;
uint32_t Tag;
uint64_t BlockCount;
uint32_t BlockSize;
char Model[41];
int BlockDevIndex;
uint8_t* CbwBuf;
uint64_t CbwPhys;
uint8_t* CswBuf;
uint64_t CswPhys;
uint8_t* ScratchBuf;
uint64_t ScratchPhys;
};
static Device g_devices[MAX_MSC_DEVICES] = {};
static std::atomic<bool> g_transportBusy{false};
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
static uint32_t ReadBe32(const uint8_t* p) {
return ((uint32_t)p[0] << 24)
| ((uint32_t)p[1] << 16)
| ((uint32_t)p[2] << 8)
| p[3];
}
static uint64_t ReadBe64(const uint8_t* p) {
return ((uint64_t)ReadBe32(p) << 32) | ReadBe32(p + 4);
}
static void WriteBe16(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)(v >> 8);
p[1] = (uint8_t)v;
}
static void WriteBe32(uint8_t* p, uint32_t v) {
p[0] = (uint8_t)(v >> 24);
p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8);
p[3] = (uint8_t)v;
}
static void WriteBe64(uint8_t* p, uint64_t v) {
WriteBe32(p, (uint32_t)(v >> 32));
WriteBe32(p + 4, (uint32_t)v);
}
static void PollWaitMs(uint32_t ms) {
uint64_t flags;
asm volatile("pushfq; pop %0" : "=r"(flags));
if ((flags & (1 << 9)) == 0) {
for (uint64_t i = 0; i < (uint64_t)ms * 1000; i++) {
Xhci::PollEvents();
asm volatile("outb %%al, $0x80" ::: "memory");
}
return;
}
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < ms) {
Xhci::PollEvents();
asm volatile("pause" ::: "memory");
}
}
static void TrimTrailingSpaces(char* s, int len) {
if (!s || len <= 0) return;
s[len - 1] = '\0';
for (int i = len - 2; i >= 0; i--) {
if (s[i] == ' ' || s[i] == '\0') {
s[i] = '\0';
} else {
break;
}
}
}
static void AppendField(char* dst, int dstMax, const uint8_t* src, int srcLen) {
int pos = 0;
while (pos < dstMax - 1 && dst[pos] != '\0') pos++;
for (int i = 0; i < srcLen && pos < dstMax - 1; i++) {
char c = (char)src[i];
if (c < 32 || c > 126) c = ' ';
dst[pos++] = c;
}
dst[pos] = '\0';
}
static void BuildInquiryModel(Device& dev, const uint8_t* inquiry) {
memset(dev.Model, 0, sizeof(dev.Model));
AppendField(dev.Model, sizeof(dev.Model), inquiry + 8, 8);
int pos = 0;
while (pos < 40 && dev.Model[pos] != '\0') pos++;
if (pos < 40) dev.Model[pos++] = ' ';
dev.Model[pos] = '\0';
AppendField(dev.Model, sizeof(dev.Model), inquiry + 16, 16);
TrimTrailingSpaces(dev.Model, sizeof(dev.Model));
if (dev.Model[0] == '\0') {
const char fallback[] = "USB Mass Storage";
memcpy(dev.Model, fallback, sizeof(fallback));
}
}
static int AllocateDevice() {
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
if (g_devices[i].Active) continue;
if (g_devices[i].CbwBuf == nullptr) {
g_devices[i].CbwBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_devices[i].CbwPhys = Memory::SubHHDM(g_devices[i].CbwBuf);
g_devices[i].CswBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_devices[i].CswPhys = Memory::SubHHDM(g_devices[i].CswBuf);
g_devices[i].ScratchBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_devices[i].ScratchPhys = Memory::SubHHDM(g_devices[i].ScratchBuf);
}
return i;
}
return -1;
}
static bool IsGoodCompletion(uint32_t cc) {
return cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET;
}
bool IsTransportBusy() {
return g_transportBusy.load(std::memory_order_acquire);
}
static void AcquireTransport() {
bool expected = false;
while (!g_transportBusy.compare_exchange_weak(expected, true,
std::memory_order_acquire,
std::memory_order_relaxed)) {
expected = false;
asm volatile("pause" ::: "memory");
}
}
static void ReleaseTransport() {
g_transportBusy.store(false, std::memory_order_release);
}
// -------------------------------------------------------------------------
// BOT transport
// -------------------------------------------------------------------------
static bool BulkOnlyResetTransport(uint8_t slotId, uint8_t interfaceNumber) {
uint32_t cc = Xhci::ControlTransfer(slotId,
UsbDevice::REQTYPE_CLASS_IFACE,
REQ_BULK_ONLY_RESET,
0,
interfaceNumber,
0,
nullptr,
false);
if (cc != Xhci::CC_SUCCESS) {
KernelLogStream(WARNING, "USB-MSC") << "Bulk-only reset failed, cc="
<< (uint64_t)cc;
return false;
}
PollWaitMs(50);
return true;
}
static bool BulkOnlyReset(Device& dev) {
return BulkOnlyResetTransport(dev.SlotId, dev.InterfaceNumber);
}
static uint8_t GetMaxLun(uint8_t slotId, uint8_t interfaceNumber) {
uint8_t maxLun = 0;
uint32_t cc = Xhci::ControlTransfer(slotId,
0xA1, // Device-to-host, class, interface
REQ_GET_MAX_LUN,
0,
interfaceNumber,
1,
&maxLun,
true);
if (cc != Xhci::CC_SUCCESS && cc != Xhci::CC_SHORT_PACKET) {
return 0;
}
if (maxLun > 15) maxLun = 15;
return maxLun;
}
static bool ScsiCommand(Device& dev, const uint8_t* cdb, uint8_t cdbLen,
void* data, uint64_t dataPhys, uint32_t dataLen,
bool dataIn) {
if (cdbLen == 0 || cdbLen > 16) return false;
if (dataLen > MAX_BULK_TRANSFER_BYTES) return false;
if (dataLen > 0 && data == nullptr) return false;
AcquireTransport();
auto* cbw = (CommandBlockWrapper*)dev.CbwBuf;
memset(cbw, 0, sizeof(CommandBlockWrapper));
cbw->Signature = CBW_SIGNATURE;
cbw->Tag = ++dev.Tag;
cbw->DataTransferLength = dataLen;
cbw->Flags = dataIn ? CBW_FLAG_DATA_IN : 0;
cbw->Lun = dev.Lun;
cbw->CommandBlockLength = cdbLen;
memcpy(cbw->CommandBlock, cdb, cdbLen);
uint32_t actual = 0;
uint32_t cc = Xhci::BulkTransfer(dev.SlotId, false, dev.CbwBuf, dev.CbwPhys,
sizeof(CommandBlockWrapper), &actual);
if (!IsGoodCompletion(cc) || actual != sizeof(CommandBlockWrapper)) {
ReleaseTransport();
return false;
}
if (dataLen > 0) {
cc = Xhci::BulkTransfer(dev.SlotId, dataIn, data, dataPhys, dataLen, &actual);
if (!IsGoodCompletion(cc)) {
ReleaseTransport();
return false;
}
}
memset(dev.CswBuf, 0, 0x1000);
cc = Xhci::BulkTransfer(dev.SlotId, true, dev.CswBuf, dev.CswPhys,
sizeof(CommandStatusWrapper), &actual);
if (!IsGoodCompletion(cc) || actual < sizeof(CommandStatusWrapper)) {
ReleaseTransport();
return false;
}
auto* csw = (CommandStatusWrapper*)dev.CswBuf;
bool ok = csw->Signature == CSW_SIGNATURE &&
csw->Tag == cbw->Tag &&
csw->Status == CSW_STATUS_PASSED;
if (csw->Status == CSW_STATUS_PHASE_ERROR) {
BulkOnlyReset(dev);
}
ReleaseTransport();
return ok;
}
// -------------------------------------------------------------------------
// SCSI commands
// -------------------------------------------------------------------------
static bool RequestSense(Device& dev) {
uint8_t cdb[6] = {};
cdb[0] = SCSI_REQUEST_SENSE;
cdb[4] = 18;
memset(dev.ScratchBuf, 0, 18);
bool ok = ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true);
if (ok) {
uint8_t senseKey = dev.ScratchBuf[2] & 0x0F;
uint8_t asc = dev.ScratchBuf[12];
uint8_t ascq = dev.ScratchBuf[13];
KernelLogStream(INFO, "USB-MSC") << "REQUEST SENSE: key="
<< (uint64_t)senseKey << " asc=" << base::hex << (uint64_t)asc
<< " ascq=" << (uint64_t)ascq << base::dec;
}
return ok;
}
static bool TestUnitReady(Device& dev) {
uint8_t cdb[6] = {};
cdb[0] = SCSI_TEST_UNIT_READY;
for (int i = 0; i < 20; i++) {
if (ScsiCommand(dev, cdb, 6, nullptr, 0, 0, false)) return true;
RequestSense(dev);
PollWaitMs(100);
}
return false;
}
static bool Inquiry(Device& dev) {
uint8_t cdb[6] = {};
cdb[0] = SCSI_INQUIRY;
cdb[4] = 36;
memset(dev.ScratchBuf, 0, 36);
if (!ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 36, true)) {
return false;
}
BuildInquiryModel(dev, dev.ScratchBuf);
return true;
}
static bool ReadCapacity16(Device& dev) {
uint8_t cdb[16] = {};
cdb[0] = SCSI_SERVICE_ACTION;
cdb[1] = SERVICE_READ_CAPACITY16;
cdb[13] = 32;
memset(dev.ScratchBuf, 0, 32);
if (!ScsiCommand(dev, cdb, 16, dev.ScratchBuf, dev.ScratchPhys, 32, true)) {
return false;
}
uint64_t lastLba = ReadBe64(dev.ScratchBuf);
uint32_t blockLen = ReadBe32(dev.ScratchBuf + 8);
if (blockLen == 0) return false;
dev.BlockCount = lastLba + 1;
dev.BlockSize = blockLen;
return dev.BlockCount != 0;
}
static bool ReadCapacity(Device& dev) {
uint8_t cdb[10] = {};
cdb[0] = SCSI_READ_CAPACITY10;
memset(dev.ScratchBuf, 0, 8);
if (!ScsiCommand(dev, cdb, 10, dev.ScratchBuf, dev.ScratchPhys, 8, true)) {
return ReadCapacity16(dev);
}
uint32_t lastLba = ReadBe32(dev.ScratchBuf);
uint32_t blockLen = ReadBe32(dev.ScratchBuf + 4);
if (lastLba == 0xFFFFFFFF) {
return ReadCapacity16(dev);
}
if (blockLen == 0) return false;
dev.BlockCount = (uint64_t)lastLba + 1;
dev.BlockSize = blockLen;
return dev.BlockCount != 0;
}
static bool ReadBlocks(Device& dev, uint64_t lba, uint32_t blocks,
void* buffer, uint64_t bufferPhys) {
uint8_t cdb[16] = {};
if (lba > 0xFFFFFFFFULL) {
cdb[0] = SCSI_READ16;
WriteBe64(cdb + 2, lba);
WriteBe32(cdb + 10, blocks);
return ScsiCommand(dev, cdb, 16, buffer, bufferPhys,
blocks * dev.BlockSize, true);
}
cdb[0] = SCSI_READ10;
WriteBe32(cdb + 2, (uint32_t)lba);
WriteBe16(cdb + 7, (uint16_t)blocks);
return ScsiCommand(dev, cdb, 10, buffer, bufferPhys,
blocks * dev.BlockSize, true);
}
static bool WriteBlocks(Device& dev, uint64_t lba, uint32_t blocks,
void* buffer, uint64_t bufferPhys) {
uint8_t cdb[16] = {};
if (lba > 0xFFFFFFFFULL) {
cdb[0] = SCSI_WRITE16;
WriteBe64(cdb + 2, lba);
WriteBe32(cdb + 10, blocks);
return ScsiCommand(dev, cdb, 16, buffer, bufferPhys,
blocks * dev.BlockSize, false);
}
cdb[0] = SCSI_WRITE10;
WriteBe32(cdb + 2, (uint32_t)lba);
WriteBe16(cdb + 7, (uint16_t)blocks);
return ScsiCommand(dev, cdb, 10, buffer, bufferPhys,
blocks * dev.BlockSize, false);
}
// -------------------------------------------------------------------------
// Block device callbacks
// -------------------------------------------------------------------------
static bool ReadSectors(void* ctx, uint64_t lba, uint32_t count, void* buffer) {
auto* dev = (Device*)ctx;
if (!dev || !dev->Active || !buffer || count == 0) return false;
if (lba >= dev->BlockCount || count > dev->BlockCount - lba) return false;
uint32_t maxBlocks = MAX_BULK_TRANSFER_BYTES / dev->BlockSize;
if (maxBlocks == 0) maxBlocks = 1;
uint8_t* dst = (uint8_t*)buffer;
uint32_t remaining = count;
uint64_t curLba = lba;
while (remaining > 0) {
uint32_t chunk = remaining > maxBlocks ? maxBlocks : remaining;
uint32_t bytes = chunk * dev->BlockSize;
int pages = (bytes + 0xFFF) / 0x1000;
uint64_t dmaPhys;
uint8_t* dma = (uint8_t*)Memory::g_pfa->ReallocConsecutive(nullptr, pages);
memset(dma, 0, pages * 0x1000);
dmaPhys = Memory::SubHHDM(dma);
bool ok = ReadBlocks(*dev, curLba, chunk, dma, dmaPhys);
if (ok) {
memcpy(dst, dma, bytes);
}
Memory::g_pfa->Free(dma, pages);
if (!ok) return false;
dst += bytes;
curLba += chunk;
remaining -= chunk;
}
return true;
}
static bool WriteSectors(void* ctx, uint64_t lba, uint32_t count, const void* buffer) {
auto* dev = (Device*)ctx;
if (!dev || !dev->Active || !buffer || count == 0) return false;
if (lba >= dev->BlockCount || count > dev->BlockCount - lba) return false;
uint32_t maxBlocks = MAX_BULK_TRANSFER_BYTES / dev->BlockSize;
if (maxBlocks == 0) maxBlocks = 1;
const uint8_t* src = (const uint8_t*)buffer;
uint32_t remaining = count;
uint64_t curLba = lba;
while (remaining > 0) {
uint32_t chunk = remaining > maxBlocks ? maxBlocks : remaining;
uint32_t bytes = chunk * dev->BlockSize;
int pages = (bytes + 0xFFF) / 0x1000;
uint64_t dmaPhys;
uint8_t* dma = (uint8_t*)Memory::g_pfa->ReallocConsecutive(nullptr, pages);
memset(dma, 0, pages * 0x1000);
memcpy(dma, src, bytes);
dmaPhys = Memory::SubHHDM(dma);
bool ok = WriteBlocks(*dev, curLba, chunk, dma, dmaPhys);
Memory::g_pfa->Free(dma, pages);
if (!ok) return false;
src += bytes;
curLba += chunk;
remaining -= chunk;
}
return true;
}
// -------------------------------------------------------------------------
// Registration
// -------------------------------------------------------------------------
static void RegisterBlockDevice(Device& dev) {
Storage::BlockDevice bdev = {};
bdev.ReadSectors = ReadSectors;
bdev.WriteSectors = WriteSectors;
bdev.Ctx = &dev;
bdev.SectorCount = dev.BlockCount;
bdev.SectorSize = (uint16_t)dev.BlockSize;
bdev.Kind = Storage::BLOCK_KIND_USB_MSC;
memcpy(bdev.Model, dev.Model, 41);
int blockIndex = Storage::RegisterBlockDevice(bdev);
if (blockIndex < 0) {
KernelLogStream(ERROR, "USB-MSC") << "Failed to register block device";
return;
}
dev.BlockDevIndex = blockIndex;
uint64_t sizeMB = (dev.BlockCount * dev.BlockSize) / (1024 * 1024);
uint64_t sizeGB = sizeMB / 1024;
if (sizeGB > 0) {
KernelLogStream(OK, "USB-MSC") << "LUN " << (uint64_t)dev.Lun
<< ": " << dev.Model << " (" << sizeGB << " GiB, drive "
<< blockIndex << ")";
} else {
KernelLogStream(OK, "USB-MSC") << "LUN " << (uint64_t)dev.Lun
<< ": " << dev.Model << " (" << sizeMB << " MiB, drive "
<< blockIndex << ")";
}
Storage::Gpt::ProbeDevice(blockIndex);
Fs::FsProbe::MountPartitionsForBlockDevice(blockIndex);
}
void RegisterDevice(uint8_t slotId) {
auto* usbDev = Xhci::GetDevice(slotId);
if (!usbDev || !usbDev->BulkInEpNum || !usbDev->BulkOutEpNum) {
KernelLogStream(ERROR, "USB-MSC") << "Missing bulk endpoints on slot "
<< (uint64_t)slotId;
return;
}
uint8_t maxLun = GetMaxLun(slotId, usbDev->InterfaceNumber);
KernelLogStream(INFO, "USB-MSC") << "Slot " << (uint64_t)slotId
<< ": max LUN " << (uint64_t)maxLun;
BulkOnlyResetTransport(slotId, usbDev->InterfaceNumber);
for (uint8_t lun = 0; lun <= maxLun; lun++) {
int idx = AllocateDevice();
if (idx < 0) {
KernelLogStream(ERROR, "USB-MSC") << "No free device slots";
return;
}
Device& dev = g_devices[idx];
dev.Active = true;
dev.SlotId = slotId;
dev.Lun = lun;
dev.InterfaceNumber = usbDev->InterfaceNumber;
dev.Tag = 0x4D544B00u + idx;
dev.BlockCount = 0;
dev.BlockSize = 0;
dev.BlockDevIndex = -1;
memset(dev.Model, 0, sizeof(dev.Model));
if (!Inquiry(dev)) {
KernelLogStream(WARNING, "USB-MSC") << "INQUIRY failed for LUN "
<< (uint64_t)lun;
dev.Active = false;
continue;
}
if (!TestUnitReady(dev)) {
KernelLogStream(WARNING, "USB-MSC") << "LUN " << (uint64_t)lun
<< " not ready";
dev.Active = false;
continue;
}
if (!ReadCapacity(dev)) {
KernelLogStream(WARNING, "USB-MSC") << "READ CAPACITY failed for LUN "
<< (uint64_t)lun;
dev.Active = false;
continue;
}
if (dev.BlockSize == 0 || dev.BlockSize > 0xFFFF ||
dev.BlockSize > MAX_BULK_TRANSFER_BYTES) {
KernelLogStream(WARNING, "USB-MSC") << "Unsupported block size "
<< (uint64_t)dev.BlockSize;
dev.Active = false;
continue;
}
RegisterBlockDevice(dev);
}
}
void UnregisterDevice(uint8_t slotId) {
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
Device& dev = g_devices[i];
if (!dev.Active || dev.SlotId != slotId) continue;
int blockIndex = dev.BlockDevIndex;
if (blockIndex >= 0) {
Fs::FsProbe::UnmountPartitionsForBlockDevice(blockIndex);
Storage::Gpt::RemovePartitionsForBlockDevice(blockIndex);
Storage::UnregisterBlockDevice(blockIndex);
KernelLogStream(INFO, "USB-MSC") << "Unregistered block device "
<< blockIndex << " from slot " << (uint64_t)slotId;
}
dev.Active = false;
dev.SlotId = 0;
dev.Lun = 0;
dev.InterfaceNumber = 0;
dev.Tag = 0;
dev.BlockCount = 0;
dev.BlockSize = 0;
dev.BlockDevIndex = -1;
dev.Model[0] = '\0';
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* MassStorage.hpp
* USB Mass Storage Bulk-Only Transport driver
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::USB::MassStorage {
// Register a USB Mass Storage interface after xHCI has configured its
// bulk IN and bulk OUT endpoints.
void RegisterDevice(uint8_t slotId);
// Tear down every mass-storage LUN associated with an xHCI slot.
void UnregisterDevice(uint8_t slotId);
// True while a bulk-only transport command is in flight.
bool IsTransportBusy();
}
+32 -8
View File
@@ -8,6 +8,7 @@
#include "Xhci.hpp"
#include "HidKeyboard.hpp"
#include "HidMouse.hpp"
#include "MassStorage.hpp"
#include "Bluetooth/Bluetooth.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -129,6 +130,7 @@ namespace Drivers::USB::UsbDevice {
// Step 2: Allocate device output context and set DCBAA entry
// -----------------------------------------------------------------
auto* dev = Xhci::GetDevice(slotId);
*dev = {};
dev->Active = true;
dev->PortId = portId;
dev->Speed = speed;
@@ -341,6 +343,8 @@ namespace Drivers::USB::UsbDevice {
uint16_t offset = 0;
bool foundHid = false;
bool foundBt = false;
bool foundMsc = false;
bool currentMsc = false;
bool foundEp = false;
bool foundBulkIn = false;
bool foundBulkOut = false;
@@ -356,6 +360,7 @@ namespace Drivers::USB::UsbDevice {
// Reset at each new interface boundary
foundHid = false;
foundBt = false;
currentMsc = false;
if (!foundEp &&
iface->bInterfaceClass == CLASS_HID &&
@@ -363,6 +368,7 @@ namespace Drivers::USB::UsbDevice {
dev->InterfaceClass = iface->bInterfaceClass;
dev->InterfaceSubClass = iface->bInterfaceSubClass;
dev->InterfaceProtocol = iface->bInterfaceProtocol;
dev->InterfaceNumber = iface->bInterfaceNumber;
foundHid = true;
}
@@ -374,9 +380,22 @@ namespace Drivers::USB::UsbDevice {
dev->InterfaceClass = iface->bInterfaceClass;
dev->InterfaceSubClass = iface->bInterfaceSubClass;
dev->InterfaceProtocol = iface->bInterfaceProtocol;
dev->InterfaceNumber = iface->bInterfaceNumber;
}
foundBt = true;
}
// USB Mass Storage Bulk-Only Transport with SCSI transparent commands
if (iface->bInterfaceClass == CLASS_MASS_STORAGE &&
iface->bInterfaceSubClass == SUBCLASS_SCSI &&
iface->bInterfaceProtocol == PROTOCOL_BULK_ONLY) {
dev->InterfaceClass = iface->bInterfaceClass;
dev->InterfaceSubClass = iface->bInterfaceSubClass;
dev->InterfaceProtocol = iface->bInterfaceProtocol;
dev->InterfaceNumber = iface->bInterfaceNumber;
currentMsc = true;
foundMsc = true;
}
}
// HID descriptor (0x21): extract report descriptor length
@@ -398,8 +417,8 @@ namespace Drivers::USB::UsbDevice {
foundEp = true;
}
// Bluetooth endpoints
if (foundBt) {
// Bluetooth and Mass Storage bulk endpoints
if (foundBt || currentMsc) {
if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) {
// HCI event pipe (interrupt IN)
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
@@ -407,12 +426,10 @@ namespace Drivers::USB::UsbDevice {
dev->InterruptInterval = ep->bInterval;
foundEp = true;
} else if (isIn && xferType == EP_XFER_BULK && !foundBulkIn) {
// ACL data IN pipe (bulk IN)
dev->BulkInEpNum = ep->bEndpointAddress & 0x0F;
dev->BulkInMaxPacket = ep->wMaxPacketSize & 0x7FF;
foundBulkIn = true;
} else if (!isIn && xferType == EP_XFER_BULK && !foundBulkOut) {
// ACL data OUT pipe (bulk OUT)
dev->BulkOutEpNum = ep->bEndpointAddress & 0x0F;
dev->BulkOutMaxPacket = ep->wMaxPacketSize & 0x7FF;
foundBulkOut = true;
@@ -585,7 +602,7 @@ namespace Drivers::USB::UsbDevice {
// HidMouse parses the HID Report Descriptor to handle variable formats.
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL,
0, 0, 0, nullptr, false);
0, dev->InterfaceNumber, 0, nullptr, false);
if (cc != Xhci::CC_SUCCESS) {
KernelLogStream(WARNING, "USB") << "SET_PROTOCOL(Boot) failed, cc=" << (uint64_t)cc;
// Non-fatal: some devices only support boot protocol anyway
@@ -601,7 +618,7 @@ namespace Drivers::USB::UsbDevice {
if (rdLen > 256) rdLen = 256;
cc = Xhci::ControlTransfer(slotId, REQTYPE_STD_IFACE_IN, REQ_GET_DESCRIPTOR,
(DESC_HID_REPORT << 8), 0, rdLen,
(DESC_HID_REPORT << 8), dev->InterfaceNumber, rdLen,
rdBuf, true);
if (cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) {
HidMouse::ParseReportDescriptor(rdBuf, rdLen);
@@ -616,7 +633,7 @@ namespace Drivers::USB::UsbDevice {
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
// wValue upper byte = duration (0 = indefinite), lower byte = report ID
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
(0 << 8), 0, 0, nullptr, false);
(0 << 8), dev->InterfaceNumber, 0, nullptr, false);
if (cc != Xhci::CC_SUCCESS) {
KernelLogStream(WARNING, "USB") << "SET_IDLE(0) failed, cc=" << (uint64_t)cc;
// Non-fatal: not all devices support SET_IDLE
@@ -627,7 +644,7 @@ namespace Drivers::USB::UsbDevice {
// Step 12: Queue first interrupt transfer (HID only)
// Bluetooth manages its own interrupt/bulk transfers via StartEventPipe()
// -----------------------------------------------------------------
if (foundEp && !foundBt) {
if (foundEp && dev->InterfaceClass == CLASS_HID) {
Xhci::QueueInterruptTransfer(slotId);
}
@@ -647,6 +664,13 @@ namespace Drivers::USB::UsbDevice {
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter"
<< " VID:" << base::hex << (uint64_t)dev->VendorId
<< " PID:" << (uint64_t)dev->ProductId << base::dec;
} else if (dev->InterfaceClass == CLASS_MASS_STORAGE &&
dev->InterfaceSubClass == SUBCLASS_SCSI &&
dev->InterfaceProtocol == PROTOCOL_BULK_ONLY &&
foundMsc && foundBulkIn && foundBulkOut) {
MassStorage::RegisterDevice(slotId);
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId
<< ": USB Mass Storage";
} else if (foundEp) {
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
<< ": USB device, class=" << (uint64_t)dev->InterfaceClass
+5
View File
@@ -85,6 +85,11 @@ namespace Drivers::USB::UsbDevice {
constexpr uint8_t SUBCLASS_RF = 0x01;
constexpr uint8_t PROTOCOL_BLUETOOTH = 0x01;
// Mass Storage class (Bulk-Only Transport + SCSI transparent command set)
constexpr uint8_t CLASS_MASS_STORAGE = 0x08;
constexpr uint8_t SUBCLASS_SCSI = 0x06;
constexpr uint8_t PROTOCOL_BULK_ONLY = 0x50;
// USB standard requests (bRequest)
constexpr uint8_t REQ_GET_DESCRIPTOR = 0x06;
constexpr uint8_t REQ_SET_CONFIGURATION = 0x09;
+149 -9
View File
@@ -8,6 +8,7 @@
#include "UsbDevice.hpp"
#include "HidKeyboard.hpp"
#include "HidMouse.hpp"
#include "MassStorage.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -17,6 +18,7 @@
#include <Libraries/Memory.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <atomic>
using namespace Kt;
@@ -50,7 +52,7 @@ namespace Drivers::USB::Xhci {
// Hot-plug deferred work
static volatile bool g_hotplugPending[MAX_PORTS] = {};
static volatile bool g_deferredWorkPending = false;
static bool g_hotplugProcessing = false;
static std::atomic<bool> g_hotplugProcessing{false};
// MMIO region pointers
static volatile uint8_t* g_mmioBase = nullptr;
@@ -92,6 +94,16 @@ namespace Drivers::USB::Xhci {
static volatile bool g_xferCompleted = false;
static volatile uint32_t g_xferCompletionCode = 0;
// Synchronous bulk transfer tracking. Only one storage-style blocking bulk
// transfer is active at a time.
static volatile bool g_syncBulkActive = false;
static volatile bool g_syncBulkCompleted = false;
static volatile uint8_t g_syncBulkSlotId = 0;
static volatile uint8_t g_syncBulkEpDci = 0;
static volatile uint32_t g_syncBulkLength = 0;
static volatile uint32_t g_syncBulkResidual = 0;
static volatile uint32_t g_syncBulkCompletionCode = 0;
// Per-device info
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
@@ -300,7 +312,7 @@ namespace Drivers::USB::Xhci {
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
// Defer enumeration to ProcessDeferredWork (called from timer tick)
// Defer enumeration to ProcessDeferredWork outside interrupt context.
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
g_hotplugPending[portId - 1] = true;
g_deferredWorkPending = true;
@@ -312,6 +324,7 @@ namespace Drivers::USB::Xhci {
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
uint32_t slotId = (evt.Control >> 24) & 0xFF;
uint32_t epDci = (evt.Control >> 16) & 0x1F;
uint32_t residual = evt.Status & 0x00FFFFFF;
if (epDci == 1) {
// EP0 (DCI 1) - control transfer completion
@@ -320,10 +333,17 @@ namespace Drivers::USB::Xhci {
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
UsbDeviceInfo& dev = g_devices[slotId];
if (g_syncBulkActive &&
slotId == g_syncBulkSlotId &&
epDci == g_syncBulkEpDci) {
g_syncBulkResidual = residual;
g_syncBulkCompletionCode = completionCode;
g_syncBulkCompleted = true;
break;
}
if (completionCode == CC_SUCCESS || completionCode == CC_SHORT_PACKET) {
// Compute actual transfer length from residual
uint32_t residual = evt.Status & 0x00FFFFFF;
// Check if this is a bulk IN endpoint completion
uint8_t bulkInDci = dev.BulkInEpNum ? (dev.BulkInEpNum * 2 + 1) : 0;
uint8_t bulkOutDci = dev.BulkOutEpNum ? (dev.BulkOutEpNum * 2) : 0;
@@ -688,6 +708,91 @@ namespace Drivers::USB::Xhci {
WriteDoorbell(slotId, target);
}
// -------------------------------------------------------------------------
// BulkTransfer - blocking bulk IN/OUT transfer
// -------------------------------------------------------------------------
uint32_t BulkTransfer(uint8_t slotId, bool dirIn, void* data, uint64_t dataPhys,
uint32_t length, uint32_t* actualLength) {
if (actualLength) *actualLength = 0;
if (length == 0) return CC_SUCCESS;
if (length > 0x10000) return 0xFF;
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return 0xFF;
if (data == nullptr) return 0xFF;
if (g_syncBulkActive) return 0xFF;
UsbDeviceInfo& dev = g_devices[slotId];
if (dataPhys == 0) dataPhys = Memory::SubHHDM(data);
uint8_t target = 0;
if (dirIn) {
if (!dev.BulkInRing || dev.BulkInEpNum == 0) return 0xFF;
TRB& trb = dev.BulkInRing[dev.BulkInRingEnqueue];
trb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
trb.Parameter1 = (uint32_t)(dataPhys >> 32);
trb.Status = length;
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC | TRB_ISP;
if (dev.BulkInRingCCS) control |= TRB_CYCLE_BIT;
trb.Control = control;
AdvanceBulkInRing(dev);
target = dev.BulkInEpNum * 2 + 1;
} else {
if (!dev.BulkOutRing || dev.BulkOutEpNum == 0) return 0xFF;
TRB& trb = dev.BulkOutRing[dev.BulkOutRingEnqueue];
trb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
trb.Parameter1 = (uint32_t)(dataPhys >> 32);
trb.Status = length;
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC;
if (dev.BulkOutRingCCS) control |= TRB_CYCLE_BIT;
trb.Control = control;
AdvanceBulkOutRing(dev);
target = dev.BulkOutEpNum * 2;
}
g_syncBulkSlotId = slotId;
g_syncBulkEpDci = target;
g_syncBulkLength = length;
g_syncBulkResidual = length;
g_syncBulkCompletionCode = 0;
g_syncBulkCompleted = false;
g_syncBulkActive = true;
WriteDoorbell(slotId, target);
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 5000) {
PollEvents();
if (g_syncBulkCompleted) {
uint32_t cc = g_syncBulkCompletionCode;
uint32_t residual = g_syncBulkResidual;
g_syncBulkActive = false;
if (actualLength) {
*actualLength = (residual < g_syncBulkLength)
? (g_syncBulkLength - residual)
: 0;
}
return cc;
}
for (int j = 0; j < 100; j++) {
asm volatile("" ::: "memory");
}
}
g_syncBulkActive = false;
KernelLogStream(WARNING, "xHCI") << "Bulk transfer timeout on slot "
<< base::dec << (uint64_t)slotId << " ep " << (uint64_t)target;
return 0xFF;
}
// -------------------------------------------------------------------------
// RegisterTransferCallback
// -------------------------------------------------------------------------
@@ -729,15 +834,45 @@ namespace Drivers::USB::Xhci {
return g_initialized && g_deferredWorkPending;
}
static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) {
if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
MassStorage::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
HidKeyboard::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
dev.InterfaceProtocol == UsbDevice::PROTOCOL_MOUSE) {
HidMouse::UnregisterDevice(slotId);
}
}
static void DisableSlot(uint8_t slotId) {
TRB trb = {};
trb.Control = (TRB_DISABLE_SLOT << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24);
uint32_t cc = SendCommand(trb);
if (cc != CC_SUCCESS) {
KernelLogStream(WARNING, "xHCI") << "Disable Slot failed for slot "
<< base::dec << (uint64_t)slotId << " cc=" << (uint64_t)cc;
}
}
// -------------------------------------------------------------------------
// ProcessDeferredWork - handle hot-plug outside interrupt context
// Called from timer tick (same pattern as E1000E::Poll)
// ProcessDeferredWork - handle hot-plug outside interrupt context.
// Called from the BSP idle path so class-driver probing can wait on timers.
// -------------------------------------------------------------------------
void ProcessDeferredWork() {
if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return;
if (g_hotplugProcessing) return;
g_hotplugProcessing = true;
// CAS claim: any idle core may call this, but only one runs at a time.
// AcquireTransport in MassStorage handles the SCSI-in-flight case itself.
bool expected = false;
if (!g_hotplugProcessing.compare_exchange_strong(expected, true,
std::memory_order_acquire, std::memory_order_relaxed)) {
return;
}
g_deferredWorkPending = false;
for (uint32_t port = 0; port < g_maxPorts; port++) {
@@ -802,7 +937,12 @@ namespace Drivers::USB::Xhci {
// Device disconnected — deactivate its slot
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
UnregisterClassDriver(s, g_devices[s]);
g_devices[s].Active = false;
g_transferCallbacks[s] = nullptr;
DisableSlot(s);
g_dcbaa[s] = 0;
g_devices[s] = {};
KernelLogStream(INFO, "xHCI") << "Hot-unplug: slot "
<< base::dec << (uint64_t)s << " (port "
<< (uint64_t)(port + 1) << ") deactivated";
@@ -812,7 +952,7 @@ namespace Drivers::USB::Xhci {
}
}
g_hotplugProcessing = false;
g_hotplugProcessing.store(false, std::memory_order_release);
}
// -------------------------------------------------------------------------
+7 -1
View File
@@ -238,6 +238,7 @@ namespace Drivers::USB::Xhci {
uint8_t InterfaceClass;
uint8_t InterfaceSubClass;
uint8_t InterfaceProtocol;
uint8_t InterfaceNumber;
uint8_t DeviceClass; // bDeviceClass from device descriptor
// Interrupt IN endpoint
@@ -295,7 +296,7 @@ namespace Drivers::USB::Xhci {
bool IsInitialized();
bool HasDeferredWork();
// Deferred hot-plug processing (call from timer tick, not interrupt context)
// Deferred hot-plug processing (call outside interrupt context)
void ProcessDeferredWork();
// Send a command on the command ring, wait for completion.
@@ -318,6 +319,11 @@ namespace Drivers::USB::Xhci {
void QueueBulkInTransfer(uint8_t slotId, uint8_t* data, uint64_t dataPhys, uint32_t length);
void QueueBulkOutTransfer(uint8_t slotId, uint8_t* data, uint64_t dataPhys, uint32_t length);
// Queue a bulk transfer and wait for its completion. Used by storage-style
// class drivers that need command/response ordering on bulk pipes.
uint32_t BulkTransfer(uint8_t slotId, bool dirIn, void* data, uint64_t dataPhys,
uint32_t length, uint32_t* actualLength = nullptr);
// Register a transfer callback for a specific slot (used by non-HID class drivers)
void RegisterTransferCallback(uint8_t slotId, TransferCallback cb);