feat: support for USB mass storage devices, hotplugging
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user