feat: support for USB mass storage devices, hotplugging
This commit is contained in:
@@ -234,8 +234,29 @@ namespace Montauk {
|
|||||||
buf->sectorSizePhys = bdev->SectorSize;
|
buf->sectorSizePhys = bdev->SectorSize;
|
||||||
dl_strcpy(buf->model, bdev->Model, 41);
|
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
|
// 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++) {
|
for (int p = 0; p < 32; p++) {
|
||||||
auto* info = Drivers::Storage::Ahci::GetPortInfo(p);
|
auto* info = Drivers::Storage::Ahci::GetPortInfo(p);
|
||||||
if (!info) continue;
|
if (!info) continue;
|
||||||
@@ -262,7 +283,8 @@ namespace Montauk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For NVMe devices: set type=3 (NVMe), rpm=1 (SSD)
|
// 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++) {
|
for (int ns = 0; ns < Drivers::Storage::Nvme::GetNamespaceCount(); ns++) {
|
||||||
auto* nsInfo = Drivers::Storage::Nvme::GetNamespaceInfo(ns);
|
auto* nsInfo = Drivers::Storage::Nvme::GetNamespaceInfo(ns);
|
||||||
if (!nsInfo) continue;
|
if (!nsInfo) continue;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ namespace Montauk {
|
|||||||
const char* entries[1];
|
const char* entries[1];
|
||||||
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
|
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
|
||||||
} else {
|
} else {
|
||||||
Fs::Vfs::BackendFile file = {-1, -1};
|
Fs::Vfs::BackendFile file = {-1, -1, 0};
|
||||||
if (Fs::Vfs::OpenBackendFile(resolved, file) < 0) return -1;
|
if (Fs::Vfs::OpenBackendFile(resolved, file) < 0) return -1;
|
||||||
Fs::Vfs::CloseBackendFile(file);
|
Fs::Vfs::CloseBackendFile(file);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Montauk {
|
|||||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
||||||
if (!dev) return -1;
|
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 (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, true)) return -1;
|
||||||
|
|
||||||
if (!dev->ReadSectors(dev->Ctx, lba, count, buffer)) return -1;
|
if (!dev->ReadSectors(dev->Ctx, lba, count, buffer)) return -1;
|
||||||
@@ -78,7 +78,7 @@ namespace Montauk {
|
|||||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
||||||
if (!dev) return -1;
|
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 (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, false)) return -1;
|
||||||
|
|
||||||
if (!dev->WriteSectors(dev->Ctx, lba, count, buffer)) 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.
|
// Initialize a new GPT on a block device. Returns 0 on success, -1 on error.
|
||||||
static int64_t Sys_GptInit(int blockDev) {
|
static int64_t Sys_GptInit(int blockDev) {
|
||||||
|
Fs::FsProbe::UnmountPartitionsForBlockDevice(blockDev);
|
||||||
return (int64_t)Drivers::Storage::Gpt::InitializeGpt(blockDev);
|
return (int64_t)Drivers::Storage::Gpt::InitializeGpt(blockDev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ namespace Montauk {
|
|||||||
|
|
||||||
struct DiskInfo {
|
struct DiskInfo {
|
||||||
uint8_t port; // block device index
|
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 sataGen; // SATA gen (1/2/3)
|
||||||
uint8_t _pad0;
|
uint8_t _pad0;
|
||||||
uint64_t sectorCount; // Total user-addressable sectors
|
uint64_t sectorCount; // Total user-addressable sectors
|
||||||
|
|||||||
@@ -748,6 +748,9 @@ namespace Drivers::Storage::Ahci {
|
|||||||
bdev.Ctx = (void*)(uintptr_t)i;
|
bdev.Ctx = (void*)(uintptr_t)i;
|
||||||
bdev.SectorCount = g_ports[i].SectorCount;
|
bdev.SectorCount = g_ports[i].SectorCount;
|
||||||
bdev.SectorSize = g_ports[i].SectorSizeLog;
|
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);
|
memcpy(bdev.Model, g_ports[i].Model, 41);
|
||||||
Storage::RegisterBlockDevice(bdev);
|
Storage::RegisterBlockDevice(bdev);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,44 @@
|
|||||||
namespace Drivers::Storage {
|
namespace Drivers::Storage {
|
||||||
|
|
||||||
static BlockDevice g_devices[MaxBlockDevices] = {};
|
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) {
|
int RegisterBlockDevice(const BlockDevice& dev) {
|
||||||
if (g_deviceCount >= MaxBlockDevices) return -1;
|
for (int i = 0; i < MaxBlockDevices; i++) {
|
||||||
g_devices[g_deviceCount] = dev;
|
if (g_active[i]) continue;
|
||||||
return g_deviceCount++;
|
|
||||||
|
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) {
|
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];
|
return &g_devices[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetBlockDeviceCount() {
|
int GetBlockDeviceCount() {
|
||||||
return g_deviceCount;
|
return g_deviceHighWater;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,22 +11,36 @@ namespace Drivers::Storage {
|
|||||||
|
|
||||||
static constexpr int MaxBlockDevices = 32;
|
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 {
|
struct BlockDevice {
|
||||||
bool (*ReadSectors)(void* ctx, uint64_t lba, uint32_t count, void* buffer);
|
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);
|
bool (*WriteSectors)(void* ctx, uint64_t lba, uint32_t count, const void* buffer);
|
||||||
void* Ctx;
|
void* Ctx;
|
||||||
uint64_t SectorCount;
|
uint64_t SectorCount;
|
||||||
uint16_t SectorSize;
|
uint16_t SectorSize;
|
||||||
|
uint8_t Kind;
|
||||||
|
uint8_t Reserved;
|
||||||
char Model[41];
|
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);
|
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.
|
// Get a registered block device by index. Returns nullptr if invalid.
|
||||||
const BlockDevice* GetBlockDevice(int index);
|
const BlockDevice* GetBlockDevice(int index);
|
||||||
|
|
||||||
// Get the number of registered block devices.
|
// Get the high-water count for registered block-device slots.
|
||||||
int GetBlockDeviceCount();
|
int GetBlockDeviceCount();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ namespace Drivers::Storage::Gpt {
|
|||||||
a.Data4[6] == b.Data4[6] && a.Data4[7] == b.Data4[7];
|
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
|
// UTF-16LE to ASCII narrowing
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -76,6 +88,53 @@ namespace Drivers::Storage::Gpt {
|
|||||||
dst[j] = '\0';
|
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
|
// Validate protective MBR
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -237,6 +296,7 @@ namespace Drivers::Storage::Gpt {
|
|||||||
if (GuidIsZero(entry->TypeGuid)) continue;
|
if (GuidIsZero(entry->TypeGuid)) continue;
|
||||||
if (entry->StartingLba == 0 || entry->EndingLba == 0) continue;
|
if (entry->StartingLba == 0 || entry->EndingLba == 0) continue;
|
||||||
if (entry->StartingLba > entry->EndingLba) continue;
|
if (entry->StartingLba > entry->EndingLba) continue;
|
||||||
|
if (FindExistingPartition(blockDevIndex, entry->StartingLba, entry->EndingLba) >= 0) continue;
|
||||||
|
|
||||||
PartitionInfo& part = g_partitions[g_partitionCount];
|
PartitionInfo& part = g_partitions[g_partitionCount];
|
||||||
part.BlockDevIndex = blockDevIndex;
|
part.BlockDevIndex = blockDevIndex;
|
||||||
@@ -258,6 +318,88 @@ namespace Drivers::Storage::Gpt {
|
|||||||
return found;
|
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
|
// Public API
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -281,12 +423,15 @@ namespace Drivers::Storage::Gpt {
|
|||||||
uint8_t sectorBuf[1024];
|
uint8_t sectorBuf[1024];
|
||||||
|
|
||||||
if (!dev->ReadSectors(dev->Ctx, 0, 2, sectorBuf)) {
|
if (!dev->ReadSectors(dev->Ctx, 0, 2, sectorBuf)) {
|
||||||
|
KernelLogStream(ERROR, "GPT") << "Failed to read LBA 0-1 from device "
|
||||||
|
<< blockDevIndex;
|
||||||
return 0;
|
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)) {
|
if (!ValidateProtectiveMbr(sectorBuf)) {
|
||||||
return 0;
|
return ParseMbrPartitions(dev, sectorBuf, blockDevIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate primary GPT header (LBA 1)
|
// Validate primary GPT header (LBA 1)
|
||||||
@@ -375,6 +520,36 @@ namespace Drivers::Storage::Gpt {
|
|||||||
return &g_partitions[index];
|
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
|
// ASCII to UTF-16LE for partition names
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -110,11 +110,11 @@ namespace Drivers::Storage::Gpt {
|
|||||||
// Public API
|
// Public API
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
// Probe a block device for a GPT. Returns the number of partitions found,
|
// Probe a block device for GPT, with a legacy MBR fallback. Returns the
|
||||||
// or 0 if no valid GPT was detected.
|
// number of new partitions found, or 0 if no valid partition table exists.
|
||||||
int ProbeDevice(int blockDevIndex);
|
int ProbeDevice(int blockDevIndex);
|
||||||
|
|
||||||
// Probe all registered block devices for GPT.
|
// Probe all registered block devices for partitions.
|
||||||
void ProbeAll();
|
void ProbeAll();
|
||||||
|
|
||||||
// Get total number of discovered partitions (across all devices).
|
// Get total number of discovered partitions (across all devices).
|
||||||
@@ -123,6 +123,10 @@ namespace Drivers::Storage::Gpt {
|
|||||||
// Get partition info by global index.
|
// Get partition info by global index.
|
||||||
const PartitionInfo* GetPartition(int 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.
|
// Look up the human-readable name for a partition type GUID.
|
||||||
const char* GetTypeName(const Guid& typeGuid);
|
const char* GetTypeName(const Guid& typeGuid);
|
||||||
|
|
||||||
|
|||||||
@@ -668,6 +668,7 @@ namespace Drivers::Storage::Nvme {
|
|||||||
bdev.Ctx = (void*)(uintptr_t)i;
|
bdev.Ctx = (void*)(uintptr_t)i;
|
||||||
bdev.SectorCount = g_namespaces[i].SectorCount;
|
bdev.SectorCount = g_namespaces[i].SectorCount;
|
||||||
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
|
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
|
||||||
|
bdev.Kind = Storage::BLOCK_KIND_NVME;
|
||||||
memcpy(bdev.Model, g_namespaces[i].Model, 41);
|
memcpy(bdev.Model, g_namespaces[i].Model, 41);
|
||||||
Storage::RegisterBlockDevice(bdev);
|
Storage::RegisterBlockDevice(bdev);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,6 +261,40 @@ namespace Drivers::USB::HidKeyboard {
|
|||||||
KernelLogStream(OK, "USB/KB") << "Registered HID keyboard on slot " << (uint64_t)slotId;
|
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) {
|
void ProcessReport(const uint8_t* data, uint16_t length) {
|
||||||
if (length < 8) return;
|
if (length < 8) return;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ namespace Drivers::USB::HidKeyboard {
|
|||||||
// Register a keyboard device by slot ID
|
// Register a keyboard device by slot ID
|
||||||
void RegisterDevice(uint8_t slotId);
|
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
|
// Process an 8-byte boot protocol keyboard report
|
||||||
void ProcessReport(const uint8_t* data, uint16_t length);
|
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;
|
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) {
|
void ProcessReport(const uint8_t* data, uint16_t length) {
|
||||||
if (length < 3) return;
|
if (length < 3) return;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ namespace Drivers::USB::HidMouse {
|
|||||||
// Register a mouse device by slot ID
|
// Register a mouse device by slot ID
|
||||||
void RegisterDevice(uint8_t slotId);
|
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.
|
// Parse a HID Report Descriptor to determine the report layout.
|
||||||
// Must be called before ProcessReport for Report Protocol mice.
|
// Must be called before ProcessReport for Report Protocol mice.
|
||||||
void ParseReportDescriptor(const uint8_t* desc, uint16_t length);
|
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 "Xhci.hpp"
|
||||||
#include "HidKeyboard.hpp"
|
#include "HidKeyboard.hpp"
|
||||||
#include "HidMouse.hpp"
|
#include "HidMouse.hpp"
|
||||||
|
#include "MassStorage.hpp"
|
||||||
#include "Bluetooth/Bluetooth.hpp"
|
#include "Bluetooth/Bluetooth.hpp"
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -129,6 +130,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
// Step 2: Allocate device output context and set DCBAA entry
|
// Step 2: Allocate device output context and set DCBAA entry
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
auto* dev = Xhci::GetDevice(slotId);
|
auto* dev = Xhci::GetDevice(slotId);
|
||||||
|
*dev = {};
|
||||||
dev->Active = true;
|
dev->Active = true;
|
||||||
dev->PortId = portId;
|
dev->PortId = portId;
|
||||||
dev->Speed = speed;
|
dev->Speed = speed;
|
||||||
@@ -341,6 +343,8 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
uint16_t offset = 0;
|
uint16_t offset = 0;
|
||||||
bool foundHid = false;
|
bool foundHid = false;
|
||||||
bool foundBt = false;
|
bool foundBt = false;
|
||||||
|
bool foundMsc = false;
|
||||||
|
bool currentMsc = false;
|
||||||
bool foundEp = false;
|
bool foundEp = false;
|
||||||
bool foundBulkIn = false;
|
bool foundBulkIn = false;
|
||||||
bool foundBulkOut = false;
|
bool foundBulkOut = false;
|
||||||
@@ -356,6 +360,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
// Reset at each new interface boundary
|
// Reset at each new interface boundary
|
||||||
foundHid = false;
|
foundHid = false;
|
||||||
foundBt = false;
|
foundBt = false;
|
||||||
|
currentMsc = false;
|
||||||
|
|
||||||
if (!foundEp &&
|
if (!foundEp &&
|
||||||
iface->bInterfaceClass == CLASS_HID &&
|
iface->bInterfaceClass == CLASS_HID &&
|
||||||
@@ -363,6 +368,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
dev->InterfaceClass = iface->bInterfaceClass;
|
dev->InterfaceClass = iface->bInterfaceClass;
|
||||||
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
||||||
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
||||||
|
dev->InterfaceNumber = iface->bInterfaceNumber;
|
||||||
foundHid = true;
|
foundHid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,9 +380,22 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
dev->InterfaceClass = iface->bInterfaceClass;
|
dev->InterfaceClass = iface->bInterfaceClass;
|
||||||
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
||||||
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
||||||
|
dev->InterfaceNumber = iface->bInterfaceNumber;
|
||||||
}
|
}
|
||||||
foundBt = true;
|
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
|
// HID descriptor (0x21): extract report descriptor length
|
||||||
@@ -398,8 +417,8 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
foundEp = true;
|
foundEp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bluetooth endpoints
|
// Bluetooth and Mass Storage bulk endpoints
|
||||||
if (foundBt) {
|
if (foundBt || currentMsc) {
|
||||||
if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) {
|
if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) {
|
||||||
// HCI event pipe (interrupt IN)
|
// HCI event pipe (interrupt IN)
|
||||||
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
|
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
|
||||||
@@ -407,12 +426,10 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
dev->InterruptInterval = ep->bInterval;
|
dev->InterruptInterval = ep->bInterval;
|
||||||
foundEp = true;
|
foundEp = true;
|
||||||
} else if (isIn && xferType == EP_XFER_BULK && !foundBulkIn) {
|
} else if (isIn && xferType == EP_XFER_BULK && !foundBulkIn) {
|
||||||
// ACL data IN pipe (bulk IN)
|
|
||||||
dev->BulkInEpNum = ep->bEndpointAddress & 0x0F;
|
dev->BulkInEpNum = ep->bEndpointAddress & 0x0F;
|
||||||
dev->BulkInMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
dev->BulkInMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
||||||
foundBulkIn = true;
|
foundBulkIn = true;
|
||||||
} else if (!isIn && xferType == EP_XFER_BULK && !foundBulkOut) {
|
} else if (!isIn && xferType == EP_XFER_BULK && !foundBulkOut) {
|
||||||
// ACL data OUT pipe (bulk OUT)
|
|
||||||
dev->BulkOutEpNum = ep->bEndpointAddress & 0x0F;
|
dev->BulkOutEpNum = ep->bEndpointAddress & 0x0F;
|
||||||
dev->BulkOutMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
dev->BulkOutMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
||||||
foundBulkOut = true;
|
foundBulkOut = true;
|
||||||
@@ -585,7 +602,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
// HidMouse parses the HID Report Descriptor to handle variable formats.
|
// HidMouse parses the HID Report Descriptor to handle variable formats.
|
||||||
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL,
|
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) {
|
if (cc != Xhci::CC_SUCCESS) {
|
||||||
KernelLogStream(WARNING, "USB") << "SET_PROTOCOL(Boot) failed, cc=" << (uint64_t)cc;
|
KernelLogStream(WARNING, "USB") << "SET_PROTOCOL(Boot) failed, cc=" << (uint64_t)cc;
|
||||||
// Non-fatal: some devices only support boot protocol anyway
|
// Non-fatal: some devices only support boot protocol anyway
|
||||||
@@ -601,7 +618,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
if (rdLen > 256) rdLen = 256;
|
if (rdLen > 256) rdLen = 256;
|
||||||
|
|
||||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_STD_IFACE_IN, REQ_GET_DESCRIPTOR,
|
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);
|
rdBuf, true);
|
||||||
if (cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) {
|
if (cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) {
|
||||||
HidMouse::ParseReportDescriptor(rdBuf, rdLen);
|
HidMouse::ParseReportDescriptor(rdBuf, rdLen);
|
||||||
@@ -616,7 +633,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
if (foundEp && dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||||
// wValue upper byte = duration (0 = indefinite), lower byte = report ID
|
// wValue upper byte = duration (0 = indefinite), lower byte = report ID
|
||||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
|
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) {
|
if (cc != Xhci::CC_SUCCESS) {
|
||||||
KernelLogStream(WARNING, "USB") << "SET_IDLE(0) failed, cc=" << (uint64_t)cc;
|
KernelLogStream(WARNING, "USB") << "SET_IDLE(0) failed, cc=" << (uint64_t)cc;
|
||||||
// Non-fatal: not all devices support SET_IDLE
|
// Non-fatal: not all devices support SET_IDLE
|
||||||
@@ -627,7 +644,7 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
// Step 12: Queue first interrupt transfer (HID only)
|
// Step 12: Queue first interrupt transfer (HID only)
|
||||||
// Bluetooth manages its own interrupt/bulk transfers via StartEventPipe()
|
// Bluetooth manages its own interrupt/bulk transfers via StartEventPipe()
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
if (foundEp && !foundBt) {
|
if (foundEp && dev->InterfaceClass == CLASS_HID) {
|
||||||
Xhci::QueueInterruptTransfer(slotId);
|
Xhci::QueueInterruptTransfer(slotId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,6 +664,13 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter"
|
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter"
|
||||||
<< " VID:" << base::hex << (uint64_t)dev->VendorId
|
<< " VID:" << base::hex << (uint64_t)dev->VendorId
|
||||||
<< " PID:" << (uint64_t)dev->ProductId << base::dec;
|
<< " 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) {
|
} else if (foundEp) {
|
||||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||||
<< ": USB device, class=" << (uint64_t)dev->InterfaceClass
|
<< ": USB device, class=" << (uint64_t)dev->InterfaceClass
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ namespace Drivers::USB::UsbDevice {
|
|||||||
constexpr uint8_t SUBCLASS_RF = 0x01;
|
constexpr uint8_t SUBCLASS_RF = 0x01;
|
||||||
constexpr uint8_t PROTOCOL_BLUETOOTH = 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)
|
// USB standard requests (bRequest)
|
||||||
constexpr uint8_t REQ_GET_DESCRIPTOR = 0x06;
|
constexpr uint8_t REQ_GET_DESCRIPTOR = 0x06;
|
||||||
constexpr uint8_t REQ_SET_CONFIGURATION = 0x09;
|
constexpr uint8_t REQ_SET_CONFIGURATION = 0x09;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "UsbDevice.hpp"
|
#include "UsbDevice.hpp"
|
||||||
#include "HidKeyboard.hpp"
|
#include "HidKeyboard.hpp"
|
||||||
#include "HidMouse.hpp"
|
#include "HidMouse.hpp"
|
||||||
|
#include "MassStorage.hpp"
|
||||||
#include <Pci/Pci.hpp>
|
#include <Pci/Pci.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Hal/Apic/Interrupts.hpp>
|
#include <Hal/Apic/Interrupts.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ namespace Drivers::USB::Xhci {
|
|||||||
// Hot-plug deferred work
|
// Hot-plug deferred work
|
||||||
static volatile bool g_hotplugPending[MAX_PORTS] = {};
|
static volatile bool g_hotplugPending[MAX_PORTS] = {};
|
||||||
static volatile bool g_deferredWorkPending = false;
|
static volatile bool g_deferredWorkPending = false;
|
||||||
static bool g_hotplugProcessing = false;
|
static std::atomic<bool> g_hotplugProcessing{false};
|
||||||
|
|
||||||
// MMIO region pointers
|
// MMIO region pointers
|
||||||
static volatile uint8_t* g_mmioBase = nullptr;
|
static volatile uint8_t* g_mmioBase = nullptr;
|
||||||
@@ -92,6 +94,16 @@ namespace Drivers::USB::Xhci {
|
|||||||
static volatile bool g_xferCompleted = false;
|
static volatile bool g_xferCompleted = false;
|
||||||
static volatile uint32_t g_xferCompletionCode = 0;
|
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
|
// Per-device info
|
||||||
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
|
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
|
||||||
|
|
||||||
@@ -300,7 +312,7 @@ namespace Drivers::USB::Xhci {
|
|||||||
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
|
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
|
||||||
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
|
(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) {
|
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
|
||||||
g_hotplugPending[portId - 1] = true;
|
g_hotplugPending[portId - 1] = true;
|
||||||
g_deferredWorkPending = true;
|
g_deferredWorkPending = true;
|
||||||
@@ -312,6 +324,7 @@ namespace Drivers::USB::Xhci {
|
|||||||
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
|
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
|
||||||
uint32_t slotId = (evt.Control >> 24) & 0xFF;
|
uint32_t slotId = (evt.Control >> 24) & 0xFF;
|
||||||
uint32_t epDci = (evt.Control >> 16) & 0x1F;
|
uint32_t epDci = (evt.Control >> 16) & 0x1F;
|
||||||
|
uint32_t residual = evt.Status & 0x00FFFFFF;
|
||||||
|
|
||||||
if (epDci == 1) {
|
if (epDci == 1) {
|
||||||
// EP0 (DCI 1) - control transfer completion
|
// 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) {
|
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
|
||||||
UsbDeviceInfo& dev = g_devices[slotId];
|
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) {
|
if (completionCode == CC_SUCCESS || completionCode == CC_SHORT_PACKET) {
|
||||||
// Compute actual transfer length from residual
|
// Compute actual transfer length from residual
|
||||||
uint32_t residual = evt.Status & 0x00FFFFFF;
|
|
||||||
|
|
||||||
// Check if this is a bulk IN endpoint completion
|
// Check if this is a bulk IN endpoint completion
|
||||||
uint8_t bulkInDci = dev.BulkInEpNum ? (dev.BulkInEpNum * 2 + 1) : 0;
|
uint8_t bulkInDci = dev.BulkInEpNum ? (dev.BulkInEpNum * 2 + 1) : 0;
|
||||||
uint8_t bulkOutDci = dev.BulkOutEpNum ? (dev.BulkOutEpNum * 2) : 0;
|
uint8_t bulkOutDci = dev.BulkOutEpNum ? (dev.BulkOutEpNum * 2) : 0;
|
||||||
@@ -688,6 +708,91 @@ namespace Drivers::USB::Xhci {
|
|||||||
WriteDoorbell(slotId, target);
|
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
|
// RegisterTransferCallback
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -729,15 +834,45 @@ namespace Drivers::USB::Xhci {
|
|||||||
return g_initialized && g_deferredWorkPending;
|
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
|
// ProcessDeferredWork - handle hot-plug outside interrupt context.
|
||||||
// Called from timer tick (same pattern as E1000E::Poll)
|
// Called from the BSP idle path so class-driver probing can wait on timers.
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
void ProcessDeferredWork() {
|
void ProcessDeferredWork() {
|
||||||
if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return;
|
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;
|
g_deferredWorkPending = false;
|
||||||
|
|
||||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||||
@@ -802,7 +937,12 @@ namespace Drivers::USB::Xhci {
|
|||||||
// Device disconnected — deactivate its slot
|
// Device disconnected — deactivate its slot
|
||||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
||||||
|
UnregisterClassDriver(s, g_devices[s]);
|
||||||
g_devices[s].Active = false;
|
g_devices[s].Active = false;
|
||||||
|
g_transferCallbacks[s] = nullptr;
|
||||||
|
DisableSlot(s);
|
||||||
|
g_dcbaa[s] = 0;
|
||||||
|
g_devices[s] = {};
|
||||||
KernelLogStream(INFO, "xHCI") << "Hot-unplug: slot "
|
KernelLogStream(INFO, "xHCI") << "Hot-unplug: slot "
|
||||||
<< base::dec << (uint64_t)s << " (port "
|
<< base::dec << (uint64_t)s << " (port "
|
||||||
<< (uint64_t)(port + 1) << ") deactivated";
|
<< (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 InterfaceClass;
|
||||||
uint8_t InterfaceSubClass;
|
uint8_t InterfaceSubClass;
|
||||||
uint8_t InterfaceProtocol;
|
uint8_t InterfaceProtocol;
|
||||||
|
uint8_t InterfaceNumber;
|
||||||
uint8_t DeviceClass; // bDeviceClass from device descriptor
|
uint8_t DeviceClass; // bDeviceClass from device descriptor
|
||||||
|
|
||||||
// Interrupt IN endpoint
|
// Interrupt IN endpoint
|
||||||
@@ -295,7 +296,7 @@ namespace Drivers::USB::Xhci {
|
|||||||
bool IsInitialized();
|
bool IsInitialized();
|
||||||
bool HasDeferredWork();
|
bool HasDeferredWork();
|
||||||
|
|
||||||
// Deferred hot-plug processing (call from timer tick, not interrupt context)
|
// Deferred hot-plug processing (call outside interrupt context)
|
||||||
void ProcessDeferredWork();
|
void ProcessDeferredWork();
|
||||||
|
|
||||||
// Send a command on the command ring, wait for completion.
|
// 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 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);
|
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)
|
// Register a transfer callback for a specific slot (used by non-HID class drivers)
|
||||||
void RegisterTransferCallback(uint8_t slotId, TransferCallback cb);
|
void RegisterTransferCallback(uint8_t slotId, TransferCallback cb);
|
||||||
|
|
||||||
|
|||||||
+120
-34
@@ -13,6 +13,8 @@ namespace Fs::FsProbe {
|
|||||||
|
|
||||||
static ProbeFn g_probes[MaxProbes] = {};
|
static ProbeFn g_probes[MaxProbes] = {};
|
||||||
static int g_probeCount = 0;
|
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) {
|
void Register(ProbeFn fn) {
|
||||||
if (g_probeCount < MaxProbes && fn) {
|
if (g_probeCount < MaxProbes && fn) {
|
||||||
@@ -27,56 +29,93 @@ namespace Fs::FsProbe {
|
|||||||
memcmp(a.Data4, b.Data4, 8) == 0;
|
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) {
|
void MountPartitions(int firstDrive) {
|
||||||
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
|
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
|
||||||
if (partCount == 0 || g_probeCount == 0) return;
|
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; i++) {
|
||||||
// First pass: mount non-EFI partitions so they get lower drive numbers
|
|
||||||
for (int i = 0; i < partCount && driveNum < Vfs::MaxDrives; i++) {
|
|
||||||
auto* part = Drivers::Storage::Gpt::GetPartition(i);
|
auto* part = Drivers::Storage::Gpt::GetPartition(i);
|
||||||
if (!part) continue;
|
if (!part || IsEfiPartition(part)) continue;
|
||||||
if (IsEfiPartition(part)) continue;
|
TryMountPartitionAtLowestDrive(i, firstDrive, false);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: mount EFI system partitions after all others
|
// Second pass: mount EFI system partitions after all others.
|
||||||
for (int i = 0; i < partCount && driveNum < Vfs::MaxDrives; i++) {
|
for (int i = 0; i < partCount; i++) {
|
||||||
auto* part = Drivers::Storage::Gpt::GetPartition(i);
|
auto* part = Drivers::Storage::Gpt::GetPartition(i);
|
||||||
if (!part) continue;
|
if (!part || !IsEfiPartition(part)) continue;
|
||||||
if (!IsEfiPartition(part)) continue;
|
TryMountPartitionAtLowestDrive(i, firstDrive, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int p = 0; p < g_probeCount; p++) {
|
int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive) {
|
||||||
Vfs::FsDriver* driver = g_probes[p](
|
if (!Vfs::IsInitialized() || g_probeCount == 0) return 0;
|
||||||
part->BlockDevIndex, part->StartLba, part->SectorCount);
|
|
||||||
|
|
||||||
if (driver) {
|
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
|
||||||
Vfs::RegisterDrive(driveNum, driver);
|
int mounted = 0;
|
||||||
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted EFI partition "
|
|
||||||
<< i << " as drive " << driveNum;
|
for (int i = 0; i < partCount; i++) {
|
||||||
driveNum++;
|
auto* part = Drivers::Storage::Gpt::GetPartition(i);
|
||||||
break;
|
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) {
|
int MountPartition(int partIndex, int driveNum) {
|
||||||
if (driveNum < 0 || driveNum >= Vfs::MaxDrives) return -1;
|
if (driveNum < 0 || driveNum >= Vfs::MaxDrives) return -1;
|
||||||
if (g_probeCount == 0) 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);
|
auto* part = Drivers::Storage::Gpt::GetPartition(partIndex);
|
||||||
if (!part) return -1;
|
if (!part) return -1;
|
||||||
@@ -86,15 +125,62 @@ namespace Fs::FsProbe {
|
|||||||
part->BlockDevIndex, part->StartLba, part->SectorCount);
|
part->BlockDevIndex, part->StartLba, part->SectorCount);
|
||||||
|
|
||||||
if (driver) {
|
if (driver) {
|
||||||
Vfs::RegisterDrive(driveNum, driver);
|
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 "
|
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Mounted partition "
|
||||||
<< partIndex << " as drive " << driveNum;
|
<< partIndex << " as drive " << driveNum;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::WARNING, "FsProbe") << "No filesystem recognized on partition " << partIndex;
|
Kt::KernelLogStream(Kt::WARNING, "FsProbe") << "No filesystem recognized on partition " << partIndex;
|
||||||
return -1;
|
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).
|
// Register a filesystem probe function (called once per FS driver at init).
|
||||||
void Register(ProbeFn fn);
|
void Register(ProbeFn fn);
|
||||||
|
|
||||||
// Probe all discovered GPT partitions and auto-mount recognized filesystems.
|
// Probe all discovered storage partitions and auto-mount recognized filesystems.
|
||||||
// Assigns VFS drive numbers starting from firstDrive.
|
// Assigns each partition the lowest available VFS drive number at or above firstDrive.
|
||||||
void MountPartitions(int firstDrive = 1);
|
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.
|
// 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.
|
// Returns 0 on success, -1 if no probe recognized the filesystem.
|
||||||
int MountPartition(int partIndex, int driveNum);
|
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 {
|
namespace Fs::Vfs {
|
||||||
|
|
||||||
static FsDriver* driveTable[MaxDrives];
|
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.
|
// Protects driver dispatch from concurrent CPU access. Hot-unplug only
|
||||||
// Uses Mutex (not Spinlock) so interrupts stay enabled while held --
|
// flips driveActive so an interrupted dispatch never sees a null driver.
|
||||||
// VFS is never called from interrupt context.
|
|
||||||
static kcp::Mutex vfsLock;
|
static kcp::Mutex vfsLock;
|
||||||
|
|
||||||
// Parse "N:/path" into drive number and local path.
|
// Parse "N:/path" into drive number and local path.
|
||||||
@@ -42,106 +44,202 @@ namespace Fs::Vfs {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void BumpDriveGeneration(int driveNumber) {
|
||||||
|
driveGeneration[driveNumber]++;
|
||||||
|
if (driveGeneration[driveNumber] == 0) {
|
||||||
|
driveGeneration[driveNumber] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize() {
|
void Initialize() {
|
||||||
for (int i = 0; i < MaxDrives; i++) {
|
for (int i = 0; i < MaxDrives; i++) {
|
||||||
driveTable[i] = nullptr;
|
driveTable[i] = nullptr;
|
||||||
|
driveActive[i] = false;
|
||||||
|
driveGeneration[i] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)";
|
Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsInitialized() {
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
int RegisterDrive(int driveNumber, FsDriver* driver) {
|
int RegisterDrive(int driveNumber, FsDriver* driver) {
|
||||||
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
|
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
|
||||||
if (driver == nullptr) return -1;
|
if (driver == nullptr) return -1;
|
||||||
|
if (!initialized) return -1;
|
||||||
|
|
||||||
|
vfsLock.Acquire();
|
||||||
|
if (driveActive[driveNumber]) {
|
||||||
|
vfsLock.Release();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
driveTable[driveNumber] = driver;
|
driveTable[driveNumber] = driver;
|
||||||
|
BumpDriveGeneration(driveNumber);
|
||||||
|
driveActive[driveNumber] = true;
|
||||||
|
vfsLock.Release();
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
|
Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
|
||||||
return 0;
|
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) {
|
int OpenBackendFile(const char* path, BackendFile& outFile) {
|
||||||
outFile.driveNumber = -1;
|
outFile.driveNumber = -1;
|
||||||
outFile.localHandle = -1;
|
outFile.localHandle = -1;
|
||||||
|
outFile.generation = 0;
|
||||||
|
|
||||||
int drive;
|
int drive;
|
||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|
||||||
if (!ParsePath(path, drive, localPath)) return -1;
|
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();
|
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();
|
vfsLock.Release();
|
||||||
if (localHandle < 0) return -1;
|
if (localHandle < 0) return -1;
|
||||||
|
|
||||||
outFile.driveNumber = drive;
|
outFile.driveNumber = drive;
|
||||||
outFile.localHandle = localHandle;
|
outFile.localHandle = localHandle;
|
||||||
|
outFile.generation = generation;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||||
vfsLock.Acquire();
|
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) {
|
file.localHandle < 0) {
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = driveTable[file.driveNumber]->Read(file.localHandle, buffer, offset, size);
|
int result = driver->Read(file.localHandle, buffer, offset, size);
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t GetBackendFileSize(const BackendFile& file) {
|
uint64_t GetBackendFileSize(const BackendFile& file) {
|
||||||
vfsLock.Acquire();
|
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) {
|
file.localHandle < 0) {
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t result = driveTable[file.driveNumber]->GetSize(file.localHandle);
|
uint64_t result = driver->GetSize(file.localHandle);
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BackendFileCanWrite(const BackendFile& file) {
|
bool BackendFileCanWrite(const BackendFile& file) {
|
||||||
vfsLock.Acquire();
|
vfsLock.Acquire();
|
||||||
|
FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
|
||||||
|
? driveTable[file.driveNumber]
|
||||||
|
: nullptr;
|
||||||
bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives &&
|
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 &&
|
file.localHandle >= 0 &&
|
||||||
driveTable[file.driveNumber]->Write != nullptr;
|
driver->Write != nullptr;
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return canWrite;
|
return canWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CloseBackendFile(BackendFile& file) {
|
void CloseBackendFile(BackendFile& file) {
|
||||||
vfsLock.Acquire();
|
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) {
|
file.localHandle < 0) {
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
file.driveNumber = -1;
|
file.driveNumber = -1;
|
||||||
file.localHandle = -1;
|
file.localHandle = -1;
|
||||||
|
file.generation = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
driveTable[file.driveNumber]->Close(file.localHandle);
|
driver->Close(file.localHandle);
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
|
|
||||||
file.driveNumber = -1;
|
file.driveNumber = -1;
|
||||||
file.localHandle = -1;
|
file.localHandle = -1;
|
||||||
|
file.generation = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) {
|
int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||||
vfsLock.Acquire();
|
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) {
|
file.localHandle < 0) {
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (driveTable[file.driveNumber]->Write == nullptr) { vfsLock.Release(); return -1; }
|
if (driver->Write == nullptr) { vfsLock.Release(); return -1; }
|
||||||
int result = driveTable[file.driveNumber]->Write(file.localHandle, buffer, offset, size);
|
int result = driver->Write(file.localHandle, buffer, offset, size);
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -149,22 +247,27 @@ namespace Fs::Vfs {
|
|||||||
int CreateBackendFile(const char* path, BackendFile& outFile) {
|
int CreateBackendFile(const char* path, BackendFile& outFile) {
|
||||||
outFile.driveNumber = -1;
|
outFile.driveNumber = -1;
|
||||||
outFile.localHandle = -1;
|
outFile.localHandle = -1;
|
||||||
|
outFile.generation = 0;
|
||||||
|
|
||||||
int drive;
|
int drive;
|
||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|
||||||
if (!ParsePath(path, drive, localPath)) return -1;
|
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;
|
if (driveTable[drive]->Create == nullptr) return -1;
|
||||||
|
|
||||||
vfsLock.Acquire();
|
vfsLock.Acquire();
|
||||||
|
FsDriver* driver = driveTable[drive];
|
||||||
int localHandle = driveTable[drive]->Create(localPath);
|
uint32_t generation = driveGeneration[drive];
|
||||||
|
int localHandle = (driveActive[drive] && driver && driver->Create)
|
||||||
|
? driver->Create(localPath)
|
||||||
|
: -1;
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
if (localHandle < 0) return -1;
|
if (localHandle < 0) return -1;
|
||||||
|
|
||||||
outFile.driveNumber = drive;
|
outFile.driveNumber = drive;
|
||||||
outFile.localHandle = localHandle;
|
outFile.localHandle = localHandle;
|
||||||
|
outFile.generation = generation;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +276,14 @@ namespace Fs::Vfs {
|
|||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|
||||||
if (!ParsePath(path, drive, localPath)) return -1;
|
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;
|
if (driveTable[drive]->Delete == nullptr) return -1;
|
||||||
|
|
||||||
vfsLock.Acquire();
|
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();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -187,11 +293,14 @@ namespace Fs::Vfs {
|
|||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|
||||||
if (!ParsePath(path, drive, localPath)) return -1;
|
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;
|
if (driveTable[drive]->Mkdir == nullptr) return -1;
|
||||||
|
|
||||||
vfsLock.Acquire();
|
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();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -200,7 +309,7 @@ namespace Fs::Vfs {
|
|||||||
vfsLock.Acquire();
|
vfsLock.Acquire();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
|
for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
|
||||||
if (driveTable[i] != nullptr) {
|
if (driveActive[i]) {
|
||||||
outDrives[count++] = i;
|
outDrives[count++] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +322,8 @@ namespace Fs::Vfs {
|
|||||||
outLabel[0] = '\0';
|
outLabel[0] = '\0';
|
||||||
|
|
||||||
vfsLock.Acquire();
|
vfsLock.Acquire();
|
||||||
if (driveNumber < 0 || driveNumber >= MaxDrives || driveTable[driveNumber] == nullptr) {
|
if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] ||
|
||||||
|
driveTable[driveNumber] == nullptr) {
|
||||||
vfsLock.Release();
|
vfsLock.Release();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -250,11 +360,15 @@ namespace Fs::Vfs {
|
|||||||
|
|
||||||
// Cross-drive rename not supported
|
// Cross-drive rename not supported
|
||||||
if (oldDrive != newDrive) return -1;
|
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;
|
if (driveTable[oldDrive]->Rename == nullptr) return -1;
|
||||||
|
|
||||||
vfsLock.Acquire();
|
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();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -264,10 +378,13 @@ namespace Fs::Vfs {
|
|||||||
const char* localPath;
|
const char* localPath;
|
||||||
|
|
||||||
if (!ParsePath(path, drive, localPath)) return -1;
|
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();
|
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();
|
vfsLock.Release();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace Fs::Vfs {
|
|||||||
struct BackendFile {
|
struct BackendFile {
|
||||||
int driveNumber;
|
int driveNumber;
|
||||||
int localHandle;
|
int localHandle;
|
||||||
|
uint32_t generation;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FsDriver {
|
struct FsDriver {
|
||||||
@@ -32,7 +33,11 @@ namespace Fs::Vfs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
bool IsInitialized();
|
||||||
int RegisterDrive(int driveNumber, FsDriver* driver);
|
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 OpenBackendFile(const char* path, BackendFile& outFile);
|
||||||
int CreateBackendFile(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) {
|
int OpenFileHandleForSlot(int slot, const char* path, bool create) {
|
||||||
if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1;
|
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)
|
int result = create ? Fs::Vfs::CreateBackendFile(path, backend)
|
||||||
: Fs::Vfs::OpenBackendFile(path, backend);
|
: Fs::Vfs::OpenBackendFile(path, backend);
|
||||||
if (result < 0) return -1;
|
if (result < 0) return -1;
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
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) {
|
if (Fs::Vfs::OpenBackendFile(vfsPath, file) < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -512,7 +512,7 @@ namespace Sched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ElfLoadLib(const char* vfsPath, uint64_t pml4Phys, int slot) {
|
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) {
|
if (Fs::Vfs::OpenBackendFile(vfsPath, file) < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,9 +105,6 @@ namespace Timekeeping {
|
|||||||
if (Drivers::Net::E1000E::RequiresPolling()) {
|
if (Drivers::Net::E1000E::RequiresPolling()) {
|
||||||
Drivers::Net::E1000E::Poll();
|
Drivers::Net::E1000E::Poll();
|
||||||
}
|
}
|
||||||
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
|
||||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
|
||||||
}
|
|
||||||
Drivers::USB::HidKeyboard::Tick();
|
Drivers::USB::HidKeyboard::Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +243,15 @@ namespace Timekeeping {
|
|||||||
|
|
||||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||||
auto* cpu = Smp::GetCurrentCpuData();
|
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) {
|
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
|
||||||
Hal::CpuIdle::Wait(BSP_TICK_INTERVAL_MS, hasMwait, monitorAddr);
|
Hal::CpuIdle::Wait(BSP_TICK_INTERVAL_MS, hasMwait, monitorAddr);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ namespace Montauk {
|
|||||||
|
|
||||||
struct DiskInfo {
|
struct DiskInfo {
|
||||||
uint8_t port; // block device index
|
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 sataGen; // SATA gen (1/2/3)
|
||||||
uint8_t _pad0;
|
uint8_t _pad0;
|
||||||
uint64_t sectorCount; // Total user-addressable sectors
|
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";
|
const char* typeStr = "Unknown";
|
||||||
if (dd->info.type == 1) typeStr = "SATA";
|
if (dd->info.type == 1) typeStr = "SATA";
|
||||||
else if (dd->info.type == 2) typeStr = "SATAPI";
|
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);
|
table_row("Type", typeStr);
|
||||||
|
|
||||||
snprintf(line, sizeof(line), "%d", (int)dd->info.port);
|
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);
|
px_hline(px, bw, bh, x + 8, y, w - 16, border);
|
||||||
y += 12;
|
y += 12;
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ void open_newpart_dialog() {
|
|||||||
dlg.hover_cancel = false;
|
dlg.hover_cancel = false;
|
||||||
|
|
||||||
Montauk::DiskInfo& disk = dt.disks[dt.selected_disk];
|
Montauk::DiskInfo& disk = dt.disks[dt.selected_disk];
|
||||||
|
int blockDev = selected_block_dev();
|
||||||
char sz[24];
|
char sz[24];
|
||||||
format_disk_size(sz, sizeof(sz), disk.sectorCount, disk.sectorSizeLog);
|
format_disk_size(sz, sizeof(sz), disk.sectorCount, disk.sectorSizeLog);
|
||||||
snprintf(dlg.disk_desc, sizeof(dlg.disk_desc), "Disk %d: %s (%s)",
|
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) {
|
if (dlg.will_init_gpt) {
|
||||||
snprintf(dlg.warn_line1, sizeof(dlg.warn_line1),
|
snprintf(dlg.warn_line1, sizeof(dlg.warn_line1),
|
||||||
@@ -89,7 +90,8 @@ void newpart_dialog_confirm() {
|
|||||||
auto& dlg = dt.np_dlg;
|
auto& dlg = dt.np_dlg;
|
||||||
|
|
||||||
if (dlg.will_init_gpt) {
|
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) {
|
if (r < 0) {
|
||||||
set_status("Failed to initialize GPT on disk");
|
set_status("Failed to initialize GPT on disk");
|
||||||
close_newpart_dialog();
|
close_newpart_dialog();
|
||||||
@@ -100,7 +102,7 @@ void newpart_dialog_confirm() {
|
|||||||
|
|
||||||
Montauk::GptAddParams params;
|
Montauk::GptAddParams params;
|
||||||
montauk::memset(¶ms, 0, sizeof(params));
|
montauk::memset(¶ms, 0, sizeof(params));
|
||||||
params.blockDev = dt.selected_disk;
|
params.blockDev = selected_block_dev();
|
||||||
params.startLba = 0;
|
params.startLba = 0;
|
||||||
params.endLba = 0;
|
params.endLba = 0;
|
||||||
params.typeGuid.Data1 = 0xEBD0A0A2;
|
params.typeGuid.Data1 = 0xEBD0A0A2;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ static constexpr int ITEM_H = 32;
|
|||||||
static constexpr int MAP_H = 48;
|
static constexpr int MAP_H = 48;
|
||||||
static constexpr int MAP_PAD = 16;
|
static constexpr int MAP_PAD = 16;
|
||||||
static constexpr int MAX_PARTS = 32;
|
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 STATUS_H = 44;
|
||||||
|
|
||||||
static constexpr int TB_BTN_Y = 7;
|
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) {
|
inline int disk_button_width(int idx) {
|
||||||
char label[8];
|
char label[16];
|
||||||
snprintf(label, sizeof(label), "Disk %d", idx);
|
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;
|
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);
|
void set_status(const char* msg);
|
||||||
|
int disk_block_dev(int diskIndex);
|
||||||
|
int selected_block_dev();
|
||||||
int get_disk_parts(int* indices, int max);
|
int get_disk_parts(int* indices, int max);
|
||||||
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize);
|
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();
|
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 get_disk_parts(int* indices, int max) {
|
||||||
|
int blockDev = selected_block_dev();
|
||||||
|
if (blockDev < 0) return 0;
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < g_state.part_count && count < max; i++) {
|
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;
|
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++) {
|
for (int i = 0; i < dt.disk_count; i++) {
|
||||||
Rect button = toolbar_disk_button_rect(i);
|
Rect button = toolbar_disk_button_rect(i);
|
||||||
char label[8];
|
char label[16];
|
||||||
snprintf(label, sizeof(label), "Disk %d", i);
|
snprintf(label, sizeof(label), "Disk %d", disk_block_dev(i));
|
||||||
mtk::draw_button(c, button, label, mtk::BUTTON_SECONDARY,
|
mtk::draw_button(c, button, label, mtk::BUTTON_SECONDARY,
|
||||||
toolbar_button_state(button, true, i == dt.selected_disk), theme);
|
toolbar_button_state(button, true, i == dt.selected_disk), theme);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,14 +649,14 @@ static void install_single_fat32(int disk) {
|
|||||||
|
|
||||||
void do_install() {
|
void do_install() {
|
||||||
auto& st = g_state;
|
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;
|
st.step = STEP_ERROR;
|
||||||
add_log("No disk selected");
|
add_log("No disk selected");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int disk = (int)st.disks[st.selected_disk].port;
|
||||||
|
|
||||||
g_files_copied = 0;
|
g_files_copied = 0;
|
||||||
g_dirs_created = 0;
|
g_dirs_created = 0;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ static constexpr int INIT_H = 440;
|
|||||||
static constexpr int TOOLBAR_H = 36;
|
static constexpr int TOOLBAR_H = 36;
|
||||||
static constexpr int STEP_BAR_H = 32;
|
static constexpr int STEP_BAR_H = 32;
|
||||||
static constexpr int CONTENT_TOP = TOOLBAR_H + STEP_BAR_H;
|
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 MAX_PARTS = 32;
|
||||||
static constexpr int STATUS_H = 26;
|
static constexpr int STATUS_H = 26;
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,8 @@ static void render_select_disk(uint32_t* px) {
|
|||||||
char info[64], sz[24];
|
char info[64], sz[24];
|
||||||
format_disk_size(sz, sizeof(sz), st.disks[i].sectorCount, st.disks[i].sectorSizeLog);
|
format_disk_size(sz, sizeof(sz), st.disks[i].sectorCount, st.disks[i].sectorSizeLog);
|
||||||
const char* dtype = st.disks[i].rpm == 1 ? "SSD" : "HDD";
|
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);
|
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;
|
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,
|
format_disk_size(sz, sizeof(sz), st.disks[st.selected_disk].sectorCount,
|
||||||
st.disks[st.selected_disk].sectorSizeLog);
|
st.disks[st.selected_disk].sectorSizeLog);
|
||||||
snprintf(desc, sizeof(desc), "Disk %d: %s (%s)",
|
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);
|
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(desc)) / 2, y, desc, TEXT_COLOR);
|
||||||
y += fh + 16;
|
y += fh + 16;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user