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
+24 -2
View File
@@ -234,8 +234,29 @@ namespace Montauk {
buf->sectorSizePhys = bdev->SectorSize;
dl_strcpy(buf->model, bdev->Model, 41);
switch (bdev->Kind) {
case Drivers::Storage::BLOCK_KIND_SATA:
buf->type = 1;
break;
case Drivers::Storage::BLOCK_KIND_SATAPI:
buf->type = 2;
break;
case Drivers::Storage::BLOCK_KIND_NVME:
buf->type = 3;
buf->rpm = 1;
break;
case Drivers::Storage::BLOCK_KIND_USB_MSC:
buf->type = 4;
break;
default:
break;
}
// Try to fill AHCI-specific fields if this is an AHCI device
if (Drivers::Storage::Ahci::IsInitialized()) {
if ((bdev->Kind == Drivers::Storage::BLOCK_KIND_SATA ||
bdev->Kind == Drivers::Storage::BLOCK_KIND_SATAPI ||
bdev->Kind == Drivers::Storage::BLOCK_KIND_UNKNOWN) &&
Drivers::Storage::Ahci::IsInitialized()) {
for (int p = 0; p < 32; p++) {
auto* info = Drivers::Storage::Ahci::GetPortInfo(p);
if (!info) continue;
@@ -262,7 +283,8 @@ namespace Montauk {
}
// For NVMe devices: set type=3 (NVMe), rpm=1 (SSD)
if (buf->type == 0 && Drivers::Storage::Nvme::IsInitialized()) {
if ((buf->type == 0 || bdev->Kind == Drivers::Storage::BLOCK_KIND_NVME) &&
Drivers::Storage::Nvme::IsInitialized()) {
for (int ns = 0; ns < Drivers::Storage::Nvme::GetNamespaceCount(); ns++) {
auto* nsInfo = Drivers::Storage::Nvme::GetNamespaceInfo(ns);
if (!nsInfo) continue;
+1 -1
View File
@@ -174,7 +174,7 @@ namespace Montauk {
const char* entries[1];
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
} else {
Fs::Vfs::BackendFile file = {-1, -1};
Fs::Vfs::BackendFile file = {-1, -1, 0};
if (Fs::Vfs::OpenBackendFile(resolved, file) < 0) return -1;
Fs::Vfs::CloseBackendFile(file);
}
+3 -2
View File
@@ -62,7 +62,7 @@ namespace Montauk {
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
if (!dev) return -1;
if (lba + count > dev->SectorCount) return -1;
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return -1;
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, true)) return -1;
if (!dev->ReadSectors(dev->Ctx, lba, count, buffer)) return -1;
@@ -78,7 +78,7 @@ namespace Montauk {
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
if (!dev) return -1;
if (lba + count > dev->SectorCount) return -1;
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return -1;
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, false)) return -1;
if (!dev->WriteSectors(dev->Ctx, lba, count, buffer)) return -1;
@@ -88,6 +88,7 @@ namespace Montauk {
// Initialize a new GPT on a block device. Returns 0 on success, -1 on error.
static int64_t Sys_GptInit(int blockDev) {
Fs::FsProbe::UnmountPartitionsForBlockDevice(blockDev);
return (int64_t)Drivers::Storage::Gpt::InitializeGpt(blockDev);
}
+1 -1
View File
@@ -357,7 +357,7 @@ namespace Montauk {
struct DiskInfo {
uint8_t port; // block device index
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage
uint8_t sataGen; // SATA gen (1/2/3)
uint8_t _pad0;
uint64_t sectorCount; // Total user-addressable sectors
+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);
+126 -40
View File
@@ -13,6 +13,8 @@ namespace Fs::FsProbe {
static ProbeFn g_probes[MaxProbes] = {};
static int g_probeCount = 0;
static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {};
static int g_driveForPart[Drivers::Storage::Gpt::MaxPartitions] = {};
void Register(ProbeFn fn) {
if (g_probeCount < MaxProbes && fn) {
@@ -27,56 +29,93 @@ namespace Fs::FsProbe {
memcmp(a.Data4, b.Data4, 8) == 0;
}
static int TryMountPartitionAtLowestDrive(int partIndex, int firstDrive, bool efiLog) {
if (!Vfs::IsInitialized()) return 0;
if (partIndex < 0 || partIndex >= Drivers::Storage::Gpt::MaxPartitions) return 0;
if (g_mounted[partIndex]) return 0;
auto* part = Drivers::Storage::Gpt::GetPartition(partIndex);
if (!part) return 0;
int driveNum = Vfs::FindLowestAvailableDrive(firstDrive);
if (driveNum < 0) {
Kt::KernelLogStream(Kt::WARNING, "FsProbe") << "No free drive slot for partition "
<< partIndex;
return -1;
}
for (int p = 0; p < g_probeCount; p++) {
Vfs::FsDriver* driver = g_probes[p](
part->BlockDevIndex, part->StartLba, part->SectorCount);
if (!driver) continue;
if (Vfs::RegisterDrive(driveNum, driver) == 0) {
g_mounted[partIndex] = true;
g_driveForPart[partIndex] = driveNum;
Kt::KernelLogStream(Kt::OK, "FsProbe")
<< (efiLog ? "Mounted EFI partition " : "Mounted partition ")
<< partIndex << " as drive " << driveNum;
return 1;
}
return -1;
}
Kt::KernelLogStream(Kt::INFO, "FsProbe") << "No filesystem recognized on partition "
<< partIndex;
return 0;
}
void MountPartitions(int firstDrive) {
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
if (partCount == 0 || g_probeCount == 0) return;
int driveNum = firstDrive;
// First pass: mount non-EFI partitions so they get lower drive numbers
for (int i = 0; i < partCount && driveNum < Vfs::MaxDrives; i++) {
// First pass: mount non-EFI partitions so they get lower drive numbers.
for (int i = 0; i < partCount; i++) {
auto* part = Drivers::Storage::Gpt::GetPartition(i);
if (!part) continue;
if (IsEfiPartition(part)) continue;
for (int p = 0; p < g_probeCount; p++) {
Vfs::FsDriver* driver = g_probes[p](
part->BlockDevIndex, part->StartLba, part->SectorCount);
if (driver) {
Vfs::RegisterDrive(driveNum, driver);
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted partition "
<< i << " as drive " << driveNum;
driveNum++;
break;
}
}
if (!part || IsEfiPartition(part)) continue;
TryMountPartitionAtLowestDrive(i, firstDrive, false);
}
// Second pass: mount EFI system partitions after all others
for (int i = 0; i < partCount && driveNum < Vfs::MaxDrives; i++) {
// Second pass: mount EFI system partitions after all others.
for (int i = 0; i < partCount; i++) {
auto* part = Drivers::Storage::Gpt::GetPartition(i);
if (!part) continue;
if (!IsEfiPartition(part)) continue;
for (int p = 0; p < g_probeCount; p++) {
Vfs::FsDriver* driver = g_probes[p](
part->BlockDevIndex, part->StartLba, part->SectorCount);
if (driver) {
Vfs::RegisterDrive(driveNum, driver);
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted EFI partition "
<< i << " as drive " << driveNum;
driveNum++;
break;
}
}
if (!part || !IsEfiPartition(part)) continue;
TryMountPartitionAtLowestDrive(i, firstDrive, true);
}
}
int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive) {
if (!Vfs::IsInitialized() || g_probeCount == 0) return 0;
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
int mounted = 0;
for (int i = 0; i < partCount; i++) {
auto* part = Drivers::Storage::Gpt::GetPartition(i);
if (!part || part->BlockDevIndex != blockDevIndex || IsEfiPartition(part)) continue;
int result = TryMountPartitionAtLowestDrive(i, firstDrive, false);
if (result > 0) mounted += result;
}
for (int i = 0; i < partCount; i++) {
auto* part = Drivers::Storage::Gpt::GetPartition(i);
if (!part || part->BlockDevIndex != blockDevIndex || !IsEfiPartition(part)) continue;
int result = TryMountPartitionAtLowestDrive(i, firstDrive, true);
if (result > 0) mounted += result;
}
return mounted;
}
int MountPartition(int partIndex, int driveNum) {
if (driveNum < 0 || driveNum >= Vfs::MaxDrives) return -1;
if (g_probeCount == 0) return -1;
if (partIndex >= 0 && partIndex < Drivers::Storage::Gpt::MaxPartitions && g_mounted[partIndex]) return -1;
if (Vfs::IsDriveRegistered(driveNum)) return -1;
auto* part = Drivers::Storage::Gpt::GetPartition(partIndex);
if (!part) return -1;
@@ -86,10 +125,16 @@ namespace Fs::FsProbe {
part->BlockDevIndex, part->StartLba, part->SectorCount);
if (driver) {
Vfs::RegisterDrive(driveNum, driver);
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted partition "
<< partIndex << " as drive " << driveNum;
return 0;
if (Vfs::RegisterDrive(driveNum, driver) == 0) {
if (partIndex >= 0 && partIndex < Drivers::Storage::Gpt::MaxPartitions) {
g_mounted[partIndex] = true;
g_driveForPart[partIndex] = driveNum;
}
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted partition "
<< partIndex << " as drive " << driveNum;
return 0;
}
return -1;
}
}
@@ -97,4 +142,45 @@ namespace Fs::FsProbe {
return -1;
}
int UnmountPartitionsForBlockDevice(int blockDevIndex) {
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
int unmounted = 0;
int dst = 0;
for (int src = 0; src < partCount; src++) {
auto* part = Drivers::Storage::Gpt::GetPartition(src);
bool remove = part && part->BlockDevIndex == blockDevIndex;
if (remove) {
if (src < Drivers::Storage::Gpt::MaxPartitions &&
g_mounted[src] && g_driveForPart[src] >= 0) {
if (Vfs::UnregisterDrive(g_driveForPart[src]) == 0) {
unmounted++;
}
}
if (src < Drivers::Storage::Gpt::MaxPartitions) {
g_mounted[src] = false;
g_driveForPart[src] = -1;
}
continue;
}
if (src < Drivers::Storage::Gpt::MaxPartitions &&
dst < Drivers::Storage::Gpt::MaxPartitions) {
if (dst != src) {
g_mounted[dst] = g_mounted[src];
g_driveForPart[dst] = g_driveForPart[src];
}
dst++;
}
}
for (int i = dst; i < partCount && i < Drivers::Storage::Gpt::MaxPartitions; i++) {
g_mounted[i] = false;
g_driveForPart[i] = -1;
}
return unmounted;
}
};
+10 -2
View File
@@ -18,12 +18,20 @@ namespace Fs::FsProbe {
// Register a filesystem probe function (called once per FS driver at init).
void Register(ProbeFn fn);
// Probe all discovered GPT partitions and auto-mount recognized filesystems.
// Assigns VFS drive numbers starting from firstDrive.
// Probe all discovered storage partitions and auto-mount recognized filesystems.
// Assigns each partition the lowest available VFS drive number at or above firstDrive.
void MountPartitions(int firstDrive = 1);
// Probe discovered partitions for a single block device and mount recognized
// filesystems at the lowest currently available VFS drive numbers.
int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive = 0);
// Try to mount a single partition (by global partition index) as the given VFS drive.
// Returns 0 on success, -1 if no probe recognized the filesystem.
int MountPartition(int partIndex, int driveNum);
// Unmount every VFS drive backed by partitions on a block device and compact
// mount bookkeeping to match Gpt::RemovePartitionsForBlockDevice().
int UnmountPartitionsForBlockDevice(int blockDevIndex);
};
+146 -29
View File
@@ -11,10 +11,12 @@
namespace Fs::Vfs {
static FsDriver* driveTable[MaxDrives];
static bool driveActive[MaxDrives];
static uint32_t driveGeneration[MaxDrives];
static bool initialized = false;
// Protects handle table and driver dispatch from concurrent CPU access.
// Uses Mutex (not Spinlock) so interrupts stay enabled while held --
// VFS is never called from interrupt context.
// Protects driver dispatch from concurrent CPU access. Hot-unplug only
// flips driveActive so an interrupted dispatch never sees a null driver.
static kcp::Mutex vfsLock;
// Parse "N:/path" into drive number and local path.
@@ -42,106 +44,202 @@ namespace Fs::Vfs {
return true;
}
static void BumpDriveGeneration(int driveNumber) {
driveGeneration[driveNumber]++;
if (driveGeneration[driveNumber] == 0) {
driveGeneration[driveNumber] = 1;
}
}
void Initialize() {
for (int i = 0; i < MaxDrives; i++) {
driveTable[i] = nullptr;
driveActive[i] = false;
driveGeneration[i] = 1;
}
initialized = true;
Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)";
}
bool IsInitialized() {
return initialized;
}
int RegisterDrive(int driveNumber, FsDriver* driver) {
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
if (driver == nullptr) return -1;
if (!initialized) return -1;
vfsLock.Acquire();
if (driveActive[driveNumber]) {
vfsLock.Release();
return -1;
}
driveTable[driveNumber] = driver;
BumpDriveGeneration(driveNumber);
driveActive[driveNumber] = true;
vfsLock.Release();
Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
return 0;
}
int UnregisterDrive(int driveNumber) {
if (!initialized) return -1;
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
vfsLock.Acquire();
if (!driveActive[driveNumber]) {
vfsLock.Release();
return -1;
}
driveActive[driveNumber] = false;
BumpDriveGeneration(driveNumber);
vfsLock.Release();
Kt::KernelLogStream(Kt::OK, "VFS") << "Unregistered drive " << driveNumber;
return 0;
}
bool IsDriveRegistered(int driveNumber) {
if (driveNumber < 0 || driveNumber >= MaxDrives) return false;
vfsLock.Acquire();
bool registered = driveActive[driveNumber];
vfsLock.Release();
return registered;
}
int FindLowestAvailableDrive(int firstDrive) {
if (!initialized) return -1;
if (firstDrive < 0) firstDrive = 0;
vfsLock.Acquire();
for (int i = firstDrive; i < MaxDrives; i++) {
if (!driveActive[i]) {
vfsLock.Release();
return i;
}
}
vfsLock.Release();
return -1;
}
int OpenBackendFile(const char* path, BackendFile& outFile) {
outFile.driveNumber = -1;
outFile.localHandle = -1;
outFile.generation = 0;
int drive;
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
vfsLock.Acquire();
int localHandle = driveTable[drive]->Open(localPath);
FsDriver* driver = driveTable[drive];
uint32_t generation = driveGeneration[drive];
int localHandle = (driveActive[drive] && driver) ? driver->Open(localPath) : -1;
vfsLock.Release();
if (localHandle < 0) return -1;
outFile.driveNumber = drive;
outFile.localHandle = localHandle;
outFile.generation = generation;
return 0;
}
int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) {
vfsLock.Acquire();
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
? driveTable[file.driveNumber]
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release();
return -1;
}
int result = driveTable[file.driveNumber]->Read(file.localHandle, buffer, offset, size);
int result = driver->Read(file.localHandle, buffer, offset, size);
vfsLock.Release();
return result;
}
uint64_t GetBackendFileSize(const BackendFile& file) {
vfsLock.Acquire();
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
? driveTable[file.driveNumber]
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release();
return 0;
}
uint64_t result = driveTable[file.driveNumber]->GetSize(file.localHandle);
uint64_t result = driver->GetSize(file.localHandle);
vfsLock.Release();
return result;
}
bool BackendFileCanWrite(const BackendFile& file) {
vfsLock.Acquire();
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
? driveTable[file.driveNumber]
: nullptr;
bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives &&
driveTable[file.driveNumber] != nullptr &&
driveActive[file.driveNumber] &&
driver != nullptr &&
file.generation == driveGeneration[file.driveNumber] &&
file.localHandle >= 0 &&
driveTable[file.driveNumber]->Write != nullptr;
driver->Write != nullptr;
vfsLock.Release();
return canWrite;
}
void CloseBackendFile(BackendFile& file) {
vfsLock.Acquire();
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
? driveTable[file.driveNumber]
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release();
file.driveNumber = -1;
file.localHandle = -1;
file.generation = 0;
return;
}
driveTable[file.driveNumber]->Close(file.localHandle);
driver->Close(file.localHandle);
vfsLock.Release();
file.driveNumber = -1;
file.localHandle = -1;
file.generation = 0;
}
int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) {
vfsLock.Acquire();
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
? driveTable[file.driveNumber]
: nullptr;
if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
driver == nullptr ||
file.generation != driveGeneration[file.driveNumber] ||
file.localHandle < 0) {
vfsLock.Release();
return -1;
}
if (driveTable[file.driveNumber]->Write == nullptr) { vfsLock.Release(); return -1; }
int result = driveTable[file.driveNumber]->Write(file.localHandle, buffer, offset, size);
if (driver->Write == nullptr) { vfsLock.Release(); return -1; }
int result = driver->Write(file.localHandle, buffer, offset, size);
vfsLock.Release();
return result;
}
@@ -149,22 +247,27 @@ namespace Fs::Vfs {
int CreateBackendFile(const char* path, BackendFile& outFile) {
outFile.driveNumber = -1;
outFile.localHandle = -1;
outFile.generation = 0;
int drive;
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
if (driveTable[drive]->Create == nullptr) return -1;
vfsLock.Acquire();
int localHandle = driveTable[drive]->Create(localPath);
FsDriver* driver = driveTable[drive];
uint32_t generation = driveGeneration[drive];
int localHandle = (driveActive[drive] && driver && driver->Create)
? driver->Create(localPath)
: -1;
vfsLock.Release();
if (localHandle < 0) return -1;
outFile.driveNumber = drive;
outFile.localHandle = localHandle;
outFile.generation = generation;
return 0;
}
@@ -173,11 +276,14 @@ namespace Fs::Vfs {
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
if (driveTable[drive]->Delete == nullptr) return -1;
vfsLock.Acquire();
int result = driveTable[drive]->Delete(localPath);
FsDriver* driver = driveTable[drive];
int result = (driveActive[drive] && driver && driver->Delete)
? driver->Delete(localPath)
: -1;
vfsLock.Release();
return result;
}
@@ -187,11 +293,14 @@ namespace Fs::Vfs {
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
if (driveTable[drive]->Mkdir == nullptr) return -1;
vfsLock.Acquire();
int result = driveTable[drive]->Mkdir(localPath);
FsDriver* driver = driveTable[drive];
int result = (driveActive[drive] && driver && driver->Mkdir)
? driver->Mkdir(localPath)
: -1;
vfsLock.Release();
return result;
}
@@ -200,7 +309,7 @@ namespace Fs::Vfs {
vfsLock.Acquire();
int count = 0;
for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
if (driveTable[i] != nullptr) {
if (driveActive[i]) {
outDrives[count++] = i;
}
}
@@ -213,7 +322,8 @@ namespace Fs::Vfs {
outLabel[0] = '\0';
vfsLock.Acquire();
if (driveNumber < 0 || driveNumber >= MaxDrives || driveTable[driveNumber] == nullptr) {
if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] ||
driveTable[driveNumber] == nullptr) {
vfsLock.Release();
return -1;
}
@@ -250,11 +360,15 @@ namespace Fs::Vfs {
// Cross-drive rename not supported
if (oldDrive != newDrive) return -1;
if (oldDrive < 0 || oldDrive >= MaxDrives || driveTable[oldDrive] == nullptr) return -1;
if (oldDrive < 0 || oldDrive >= MaxDrives || !driveActive[oldDrive] ||
driveTable[oldDrive] == nullptr) return -1;
if (driveTable[oldDrive]->Rename == nullptr) return -1;
vfsLock.Acquire();
int result = driveTable[oldDrive]->Rename(oldLocal, newLocal);
FsDriver* driver = driveTable[oldDrive];
int result = (driveActive[oldDrive] && driver && driver->Rename)
? driver->Rename(oldLocal, newLocal)
: -1;
vfsLock.Release();
return result;
}
@@ -264,10 +378,13 @@ namespace Fs::Vfs {
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
vfsLock.Acquire();
int result = driveTable[drive]->ReadDir(localPath, outNames, maxEntries);
FsDriver* driver = driveTable[drive];
int result = (driveActive[drive] && driver && driver->ReadDir)
? driver->ReadDir(localPath, outNames, maxEntries)
: -1;
vfsLock.Release();
return result;
}
+5
View File
@@ -15,6 +15,7 @@ namespace Fs::Vfs {
struct BackendFile {
int driveNumber;
int localHandle;
uint32_t generation;
};
struct FsDriver {
@@ -32,7 +33,11 @@ namespace Fs::Vfs {
};
void Initialize();
bool IsInitialized();
int RegisterDrive(int driveNumber, FsDriver* driver);
int UnregisterDrive(int driveNumber);
bool IsDriveRegistered(int driveNumber);
int FindLowestAvailableDrive(int firstDrive = 0);
int OpenBackendFile(const char* path, BackendFile& outFile);
int CreateBackendFile(const char* path, BackendFile& outFile);
+1 -1
View File
@@ -1099,7 +1099,7 @@ namespace Ipc {
int OpenFileHandleForSlot(int slot, const char* path, bool create) {
if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1;
Fs::Vfs::BackendFile backend = {-1, -1};
Fs::Vfs::BackendFile backend = {-1, -1, 0};
int result = create ? Fs::Vfs::CreateBackendFile(path, backend)
: Fs::Vfs::OpenBackendFile(path, backend);
if (result < 0) return -1;
+2 -2
View File
@@ -414,7 +414,7 @@ namespace Sched {
}
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
Fs::Vfs::BackendFile file = {-1, -1};
Fs::Vfs::BackendFile file = {-1, -1, 0};
if (Fs::Vfs::OpenBackendFile(vfsPath, file) < 0) {
return 0;
}
@@ -512,7 +512,7 @@ namespace Sched {
}
uint64_t ElfLoadLib(const char* vfsPath, uint64_t pml4Phys, int slot) {
Fs::Vfs::BackendFile file = {-1, -1};
Fs::Vfs::BackendFile file = {-1, -1, 0};
if (Fs::Vfs::OpenBackendFile(vfsPath, file) < 0) {
return 0;
}
+9 -3
View File
@@ -105,9 +105,6 @@ namespace Timekeeping {
if (Drivers::Net::E1000E::RequiresPolling()) {
Drivers::Net::E1000E::Poll();
}
if (Drivers::USB::Xhci::HasDeferredWork()) {
Drivers::USB::Xhci::ProcessDeferredWork();
}
Drivers::USB::HidKeyboard::Tick();
}
@@ -246,6 +243,15 @@ namespace Timekeeping {
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
auto* cpu = Smp::GetCurrentCpuData();
// Drain USB hot-plug deferred work from any idle core, not just the BSP.
// ProcessDeferredWork uses an atomic CAS so only one core runs it at a
// time; the rest return immediately. This keeps hot-plug latency bounded
// by *any* core's idle gap instead of being pinned to BSP idle.
if (Drivers::USB::Xhci::HasDeferredWork()) {
Drivers::USB::Xhci::ProcessDeferredWork();
}
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
Hal::CpuIdle::Wait(BSP_TICK_INTERVAL_MS, hasMwait, monitorAddr);
return;
+1 -1
View File
@@ -291,7 +291,7 @@ namespace Montauk {
struct DiskInfo {
uint8_t port; // block device index
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage
uint8_t sataGen; // SATA gen (1/2/3)
uint8_t _pad0;
uint64_t sectorCount; // Total user-addressable sectors
+3 -1
View File
@@ -88,10 +88,12 @@ static void dd_draw_general(uint32_t* px, int bw, int bh, DiskDetailState* dd) {
const char* typeStr = "Unknown";
if (dd->info.type == 1) typeStr = "SATA";
else if (dd->info.type == 2) typeStr = "SATAPI";
else if (dd->info.type == 3) typeStr = "NVMe";
else if (dd->info.type == 4) typeStr = "USB Mass Storage";
table_row("Type", typeStr);
snprintf(line, sizeof(line), "%d", (int)dd->info.port);
table_row("AHCI Port", line);
table_row("Device Index", line);
px_hline(px, bw, bh, x + 8, y, w - 16, border);
y += 12;
+5 -3
View File
@@ -46,10 +46,11 @@ void open_newpart_dialog() {
dlg.hover_cancel = false;
Montauk::DiskInfo& disk = dt.disks[dt.selected_disk];
int blockDev = selected_block_dev();
char sz[24];
format_disk_size(sz, sizeof(sz), disk.sectorCount, disk.sectorSizeLog);
snprintf(dlg.disk_desc, sizeof(dlg.disk_desc), "Disk %d: %s (%s)",
dt.selected_disk, disk.model, sz);
blockDev, disk.model, sz);
if (dlg.will_init_gpt) {
snprintf(dlg.warn_line1, sizeof(dlg.warn_line1),
@@ -89,7 +90,8 @@ void newpart_dialog_confirm() {
auto& dlg = dt.np_dlg;
if (dlg.will_init_gpt) {
int r = montauk::gpt_init(dt.selected_disk);
int blockDev = selected_block_dev();
int r = montauk::gpt_init(blockDev);
if (r < 0) {
set_status("Failed to initialize GPT on disk");
close_newpart_dialog();
@@ -100,7 +102,7 @@ void newpart_dialog_confirm() {
Montauk::GptAddParams params;
montauk::memset(&params, 0, sizeof(params));
params.blockDev = dt.selected_disk;
params.blockDev = selected_block_dev();
params.startLba = 0;
params.endLba = 0;
params.typeGuid.Data1 = 0xEBD0A0A2;
+8 -3
View File
@@ -31,7 +31,7 @@ static constexpr int ITEM_H = 32;
static constexpr int MAP_H = 48;
static constexpr int MAP_PAD = 16;
static constexpr int MAX_PARTS = 32;
static constexpr int MAX_DISKS = 8;
static constexpr int MAX_DISKS = 32;
static constexpr int STATUS_H = 44;
static constexpr int TB_BTN_Y = 7;
@@ -120,8 +120,11 @@ extern const Color part_colors[NUM_PART_COLORS];
// ============================================================================
inline int disk_button_width(int idx) {
char label[8];
snprintf(label, sizeof(label), "Disk %d", idx);
char label[16];
int blockDev = (idx >= 0 && idx < g_state.disk_count)
? (int)g_state.disks[idx].port
: idx;
snprintf(label, sizeof(label), "Disk %d", blockDev);
return text_width(label) + 20;
}
@@ -192,6 +195,8 @@ inline Rect dialog_secondary_button_rect(int dialog_w, int dialog_h) {
// ============================================================================
void set_status(const char* msg);
int disk_block_dev(int diskIndex);
int selected_block_dev();
int get_disk_parts(int* indices, int max);
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize);
+13 -1
View File
@@ -40,10 +40,22 @@ void set_status(const char* msg) {
g_state.status_time = montauk::get_milliseconds();
}
int disk_block_dev(int diskIndex) {
if (diskIndex < 0 || diskIndex >= g_state.disk_count) return -1;
return (int)g_state.disks[diskIndex].port;
}
int selected_block_dev() {
return disk_block_dev(g_state.selected_disk);
}
int get_disk_parts(int* indices, int max) {
int blockDev = selected_block_dev();
if (blockDev < 0) return 0;
int count = 0;
for (int i = 0; i < g_state.part_count && count < max; i++) {
if (g_state.parts[i].blockDev == g_state.selected_disk) {
if (g_state.parts[i].blockDev == blockDev) {
indices[count++] = i;
}
}
+2 -2
View File
@@ -93,8 +93,8 @@ static void render_toolbar(Canvas& c, const mtk::Theme& theme) {
for (int i = 0; i < dt.disk_count; i++) {
Rect button = toolbar_disk_button_rect(i);
char label[8];
snprintf(label, sizeof(label), "Disk %d", i);
char label[16];
snprintf(label, sizeof(label), "Disk %d", disk_block_dev(i));
mtk::draw_button(c, button, label, mtk::BUTTON_SECONDARY,
toolbar_button_state(button, true, i == dt.selected_disk), theme);
}
+2 -2
View File
@@ -649,14 +649,14 @@ static void install_single_fat32(int disk) {
void do_install() {
auto& st = g_state;
int disk = st.selected_disk;
if (disk < 0 || disk >= st.disk_count) {
if (st.selected_disk < 0 || st.selected_disk >= st.disk_count) {
st.step = STEP_ERROR;
add_log("No disk selected");
flush_ui();
return;
}
int disk = (int)st.disks[st.selected_disk].port;
g_files_copied = 0;
g_dirs_created = 0;
+1 -1
View File
@@ -28,7 +28,7 @@ static constexpr int INIT_H = 440;
static constexpr int TOOLBAR_H = 36;
static constexpr int STEP_BAR_H = 32;
static constexpr int CONTENT_TOP = TOOLBAR_H + STEP_BAR_H;
static constexpr int MAX_DISKS = 8;
static constexpr int MAX_DISKS = 32;
static constexpr int MAX_PARTS = 32;
static constexpr int STATUS_H = 26;
+4 -2
View File
@@ -207,7 +207,8 @@ static void render_select_disk(uint32_t* px) {
char info[64], sz[24];
format_disk_size(sz, sizeof(sz), st.disks[i].sectorCount, st.disks[i].sectorSizeLog);
const char* dtype = st.disks[i].rpm == 1 ? "SSD" : "HDD";
snprintf(info, sizeof(info), "%s %s (Disk %d)", sz, dtype, i);
snprintf(info, sizeof(info), "%s %s (Disk %d)", sz, dtype,
(int)st.disks[i].port);
px_text(px, g_win_w, g_win_h, item_x + 32, y + 6 + fh + 2, info, DIM_TEXT, FONT_SM);
y += item_h + 4;
@@ -301,7 +302,8 @@ static void render_confirm(uint32_t* px) {
format_disk_size(sz, sizeof(sz), st.disks[st.selected_disk].sectorCount,
st.disks[st.selected_disk].sectorSizeLog);
snprintf(desc, sizeof(desc), "Disk %d: %s (%s)",
st.selected_disk, st.disks[st.selected_disk].model, sz);
(int)st.disks[st.selected_disk].port,
st.disks[st.selected_disk].model, sz);
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(desc)) / 2, y, desc, TEXT_COLOR);
y += fh + 16;