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