diff --git a/kernel/src/Api/Device.hpp b/kernel/src/Api/Device.hpp index e51d011..d0ea0e8 100644 --- a/kernel/src/Api/Device.hpp +++ b/kernel/src/Api/Device.hpp @@ -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; diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index 6025505..a331659 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -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); } diff --git a/kernel/src/Api/Storage.hpp b/kernel/src/Api/Storage.hpp index a70de83..5394306 100644 --- a/kernel/src/Api/Storage.hpp +++ b/kernel/src/Api/Storage.hpp @@ -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); } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index c40d980..9410e4d 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -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 diff --git a/kernel/src/Drivers/Storage/Ahci.cpp b/kernel/src/Drivers/Storage/Ahci.cpp index f705512..0f4ae98 100644 --- a/kernel/src/Drivers/Storage/Ahci.cpp +++ b/kernel/src/Drivers/Storage/Ahci.cpp @@ -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); } diff --git a/kernel/src/Drivers/Storage/BlockDevice.cpp b/kernel/src/Drivers/Storage/BlockDevice.cpp index 0bd83be..8f60f58 100644 --- a/kernel/src/Drivers/Storage/BlockDevice.cpp +++ b/kernel/src/Drivers/Storage/BlockDevice.cpp @@ -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; } }; diff --git a/kernel/src/Drivers/Storage/BlockDevice.hpp b/kernel/src/Drivers/Storage/BlockDevice.hpp index b8ef8d8..24c7841 100644 --- a/kernel/src/Drivers/Storage/BlockDevice.hpp +++ b/kernel/src/Drivers/Storage/BlockDevice.hpp @@ -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(); }; diff --git a/kernel/src/Drivers/Storage/Gpt.cpp b/kernel/src/Drivers/Storage/Gpt.cpp index 5089414..a7a2428 100644 --- a/kernel/src/Drivers/Storage/Gpt.cpp +++ b/kernel/src/Drivers/Storage/Gpt.cpp @@ -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 // ------------------------------------------------------------------------- diff --git a/kernel/src/Drivers/Storage/Gpt.hpp b/kernel/src/Drivers/Storage/Gpt.hpp index 2e2d547..9216720 100644 --- a/kernel/src/Drivers/Storage/Gpt.hpp +++ b/kernel/src/Drivers/Storage/Gpt.hpp @@ -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); diff --git a/kernel/src/Drivers/Storage/Nvme.cpp b/kernel/src/Drivers/Storage/Nvme.cpp index e0fcd13..82f1404 100644 --- a/kernel/src/Drivers/Storage/Nvme.cpp +++ b/kernel/src/Drivers/Storage/Nvme.cpp @@ -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); } diff --git a/kernel/src/Drivers/USB/HidKeyboard.cpp b/kernel/src/Drivers/USB/HidKeyboard.cpp index d79ab25..37db15e 100644 --- a/kernel/src/Drivers/USB/HidKeyboard.cpp +++ b/kernel/src/Drivers/USB/HidKeyboard.cpp @@ -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; diff --git a/kernel/src/Drivers/USB/HidKeyboard.hpp b/kernel/src/Drivers/USB/HidKeyboard.hpp index 99137de..47377c0 100644 --- a/kernel/src/Drivers/USB/HidKeyboard.hpp +++ b/kernel/src/Drivers/USB/HidKeyboard.hpp @@ -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); diff --git a/kernel/src/Drivers/USB/HidMouse.cpp b/kernel/src/Drivers/USB/HidMouse.cpp index d7f1209..42d7517 100644 --- a/kernel/src/Drivers/USB/HidMouse.cpp +++ b/kernel/src/Drivers/USB/HidMouse.cpp @@ -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; diff --git a/kernel/src/Drivers/USB/HidMouse.hpp b/kernel/src/Drivers/USB/HidMouse.hpp index a5767b1..42c365d 100644 --- a/kernel/src/Drivers/USB/HidMouse.hpp +++ b/kernel/src/Drivers/USB/HidMouse.hpp @@ -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); diff --git a/kernel/src/Drivers/USB/MassStorage.cpp b/kernel/src/Drivers/USB/MassStorage.cpp new file mode 100644 index 0000000..151515a --- /dev/null +++ b/kernel/src/Drivers/USB/MassStorage.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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'; + } + } + +} diff --git a/kernel/src/Drivers/USB/MassStorage.hpp b/kernel/src/Drivers/USB/MassStorage.hpp new file mode 100644 index 0000000..f519d9c --- /dev/null +++ b/kernel/src/Drivers/USB/MassStorage.hpp @@ -0,0 +1,22 @@ +/* + * MassStorage.hpp + * USB Mass Storage Bulk-Only Transport driver + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +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(); + +} diff --git a/kernel/src/Drivers/USB/UsbDevice.cpp b/kernel/src/Drivers/USB/UsbDevice.cpp index d709f8f..4051b95 100644 --- a/kernel/src/Drivers/USB/UsbDevice.cpp +++ b/kernel/src/Drivers/USB/UsbDevice.cpp @@ -8,6 +8,7 @@ #include "Xhci.hpp" #include "HidKeyboard.hpp" #include "HidMouse.hpp" +#include "MassStorage.hpp" #include "Bluetooth/Bluetooth.hpp" #include #include @@ -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 diff --git a/kernel/src/Drivers/USB/UsbDevice.hpp b/kernel/src/Drivers/USB/UsbDevice.hpp index b5aedfb..345520e 100644 --- a/kernel/src/Drivers/USB/UsbDevice.hpp +++ b/kernel/src/Drivers/USB/UsbDevice.hpp @@ -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; diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 1c370ed..a0287f7 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -8,6 +8,7 @@ #include "UsbDevice.hpp" #include "HidKeyboard.hpp" #include "HidMouse.hpp" +#include "MassStorage.hpp" #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include 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 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); } // ------------------------------------------------------------------------- diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp index c9fd05f..c01824f 100644 --- a/kernel/src/Drivers/USB/Xhci.hpp +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -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); diff --git a/kernel/src/Fs/FsProbe.cpp b/kernel/src/Fs/FsProbe.cpp index ea7866d..b962eb1 100644 --- a/kernel/src/Fs/FsProbe.cpp +++ b/kernel/src/Fs/FsProbe.cpp @@ -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; + } + }; diff --git a/kernel/src/Fs/FsProbe.hpp b/kernel/src/Fs/FsProbe.hpp index 9fc70d5..72cc3d8 100644 --- a/kernel/src/Fs/FsProbe.hpp +++ b/kernel/src/Fs/FsProbe.hpp @@ -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); + }; diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index d94843e..e3985dd 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -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; } diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp index 7f46594..67763e2 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -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); diff --git a/kernel/src/Ipc/Ipc.cpp b/kernel/src/Ipc/Ipc.cpp index 25ede95..3543a32 100644 --- a/kernel/src/Ipc/Ipc.cpp +++ b/kernel/src/Ipc/Ipc.cpp @@ -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; diff --git a/kernel/src/Sched/ElfLoader.cpp b/kernel/src/Sched/ElfLoader.cpp index 91185aa..7c7b569 100644 --- a/kernel/src/Sched/ElfLoader.cpp +++ b/kernel/src/Sched/ElfLoader.cpp @@ -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; } diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index e0fb85e..fc153c3 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -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; diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 81de880..ca75747 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -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 diff --git a/programs/src/devexplorer/diskdetail.cpp b/programs/src/devexplorer/diskdetail.cpp index e3de4ff..524327e 100644 --- a/programs/src/devexplorer/diskdetail.cpp +++ b/programs/src/devexplorer/diskdetail.cpp @@ -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; diff --git a/programs/src/disks/actions.cpp b/programs/src/disks/actions.cpp index 8075d52..af4e584 100644 --- a/programs/src/disks/actions.cpp +++ b/programs/src/disks/actions.cpp @@ -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(¶ms, 0, sizeof(params)); - params.blockDev = dt.selected_disk; + params.blockDev = selected_block_dev(); params.startLba = 0; params.endLba = 0; params.typeGuid.Data1 = 0xEBD0A0A2; diff --git a/programs/src/disks/disks.h b/programs/src/disks/disks.h index 481f2ce..c25df46 100644 --- a/programs/src/disks/disks.h +++ b/programs/src/disks/disks.h @@ -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); diff --git a/programs/src/disks/main.cpp b/programs/src/disks/main.cpp index 73f83af..ca0b727 100644 --- a/programs/src/disks/main.cpp +++ b/programs/src/disks/main.cpp @@ -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; } } diff --git a/programs/src/disks/render.cpp b/programs/src/disks/render.cpp index e2fa01e..641f650 100644 --- a/programs/src/disks/render.cpp +++ b/programs/src/disks/render.cpp @@ -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); } diff --git a/programs/src/installer/actions.cpp b/programs/src/installer/actions.cpp index 51e2191..59dedaa 100644 --- a/programs/src/installer/actions.cpp +++ b/programs/src/installer/actions.cpp @@ -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; diff --git a/programs/src/installer/installer.h b/programs/src/installer/installer.h index 39d7a1e..5cca57a 100644 --- a/programs/src/installer/installer.h +++ b/programs/src/installer/installer.h @@ -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; diff --git a/programs/src/installer/render.cpp b/programs/src/installer/render.cpp index 22de464..616af01 100644 --- a/programs/src/installer/render.cpp +++ b/programs/src/installer/render.cpp @@ -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;