feat: Ahci, ACPI shutdown
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* AmlParser.cpp
|
||||
* Primitive AML bytecode parser for extracting ACPI sleep state values
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "AmlParser.hpp"
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace AML {
|
||||
|
||||
// Decode a PkgLength field and return its value.
|
||||
// Advances *pos past the PkgLength bytes.
|
||||
static uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
|
||||
uint8_t lead = aml[*pos];
|
||||
uint32_t byteCount = (lead >> 6) & 0x03;
|
||||
|
||||
if (byteCount == 0) {
|
||||
// Single byte encoding: bits 0-5 are the length
|
||||
(*pos)++;
|
||||
return lead & 0x3F;
|
||||
}
|
||||
|
||||
// Multi-byte: lead bits 0-3 are low nibble, followed by byteCount bytes
|
||||
uint32_t length = lead & 0x0F;
|
||||
(*pos)++;
|
||||
|
||||
for (uint32_t i = 0; i < byteCount; i++) {
|
||||
length |= (uint32_t)aml[*pos] << (4 + 8 * i);
|
||||
(*pos)++;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
// Decode an AML integer at position *pos.
|
||||
// Handles ZeroOp, OneOp, OnesOp, BytePrefix, WordPrefix, DWordPrefix.
|
||||
// Returns the decoded value and advances *pos.
|
||||
static uint32_t DecodeInteger(const uint8_t* aml, uint32_t* pos) {
|
||||
uint8_t op = aml[*pos];
|
||||
|
||||
switch (op) {
|
||||
case ZeroOp:
|
||||
(*pos)++;
|
||||
return 0;
|
||||
case OneOp:
|
||||
(*pos)++;
|
||||
return 1;
|
||||
case OnesOp:
|
||||
(*pos)++;
|
||||
return 0xFFFFFFFF;
|
||||
case BytePrefix: {
|
||||
(*pos)++;
|
||||
uint8_t val = aml[*pos];
|
||||
(*pos)++;
|
||||
return val;
|
||||
}
|
||||
case WordPrefix: {
|
||||
(*pos)++;
|
||||
uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8);
|
||||
*pos += 2;
|
||||
return val;
|
||||
}
|
||||
case DWordPrefix: {
|
||||
(*pos)++;
|
||||
uint32_t val = aml[*pos]
|
||||
| ((uint32_t)aml[*pos + 1] << 8)
|
||||
| ((uint32_t)aml[*pos + 2] << 16)
|
||||
| ((uint32_t)aml[*pos + 3] << 24);
|
||||
*pos += 4;
|
||||
return val;
|
||||
}
|
||||
default:
|
||||
// Unknown encoding — treat as zero and skip
|
||||
(*pos)++;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
S5Object FindS5(void* dsdtData) {
|
||||
S5Object result{};
|
||||
result.Valid = false;
|
||||
|
||||
auto* header = (ACPI::CommonSDTHeader*)dsdtData;
|
||||
|
||||
if (!ACPI::TestChecksum(header)) {
|
||||
KernelLogStream(ERROR, "AML") << "DSDT checksum failed";
|
||||
return result;
|
||||
}
|
||||
|
||||
// The AML bytecode starts right after the CommonSDTHeader
|
||||
const uint8_t* aml = (const uint8_t*)dsdtData;
|
||||
uint32_t amlLength = header->Length;
|
||||
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
|
||||
|
||||
// Scan for the \_S5_ name in the AML stream.
|
||||
// We look for the 4-byte sequence '_S5_' preceded by a NameOp (0x08)
|
||||
// or preceded by a scope path like '\' (0x5C).
|
||||
for (uint32_t i = dataStart; i + 4 < amlLength; i++) {
|
||||
if (aml[i] == '_' && aml[i+1] == 'S' && aml[i+2] == '5' && aml[i+3] == '_') {
|
||||
// Verify a valid AML context: either NameOp before it,
|
||||
// or '\' + NameOp pattern, or just the name in a scope
|
||||
bool validContext = false;
|
||||
|
||||
if (i >= 1 && aml[i-1] == NameOp) {
|
||||
validContext = true;
|
||||
} else if (i >= 2 && aml[i-2] == NameOp && aml[i-1] == '\\') {
|
||||
validContext = true;
|
||||
}
|
||||
|
||||
if (!validContext)
|
||||
continue;
|
||||
|
||||
KernelLogStream(OK, "AML") << "Found \\_S5_ object at offset " << base::hex << (uint64_t)i;
|
||||
|
||||
// Move past the name
|
||||
uint32_t pos = i + 4;
|
||||
|
||||
// Expect PackageOp
|
||||
if (pos >= amlLength || aml[pos] != PackageOp) {
|
||||
KernelLogStream(ERROR, "AML") << "Expected PackageOp after \\_S5_, got " << base::hex << (uint64_t)aml[pos];
|
||||
continue;
|
||||
}
|
||||
pos++;
|
||||
|
||||
// Decode package length (we don't actually need the value,
|
||||
// but must advance past it)
|
||||
DecodePkgLength(aml, &pos);
|
||||
|
||||
// Number of elements in the package
|
||||
if (pos >= amlLength) continue;
|
||||
uint8_t numElements = aml[pos];
|
||||
pos++;
|
||||
|
||||
if (numElements < 1) {
|
||||
KernelLogStream(ERROR, "AML") << "\\_S5_ package has no elements";
|
||||
continue;
|
||||
}
|
||||
|
||||
// First element: SLP_TYPa
|
||||
result.SLP_TYPa = (uint16_t)DecodeInteger(aml, &pos);
|
||||
|
||||
// Second element: SLP_TYPb (if present)
|
||||
if (numElements >= 2 && pos < amlLength) {
|
||||
result.SLP_TYPb = (uint16_t)DecodeInteger(aml, &pos);
|
||||
} else {
|
||||
result.SLP_TYPb = 0;
|
||||
}
|
||||
|
||||
result.Valid = true;
|
||||
|
||||
KernelLogStream(OK, "AML") << "SLP_TYPa=" << base::hex << (uint64_t)result.SLP_TYPa
|
||||
<< " SLP_TYPb=" << base::hex << (uint64_t)result.SLP_TYPb;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(ERROR, "AML") << "\\_S5_ object not found in DSDT";
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* AmlParser.hpp
|
||||
* Primitive AML bytecode parser for extracting ACPI sleep state values
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
namespace AML {
|
||||
|
||||
// AML opcodes used during \_S5_ parsing
|
||||
static constexpr uint8_t NameOp = 0x08;
|
||||
static constexpr uint8_t PackageOp = 0x12;
|
||||
static constexpr uint8_t ZeroOp = 0x00;
|
||||
static constexpr uint8_t OneOp = 0x01;
|
||||
static constexpr uint8_t OnesOp = 0xFF;
|
||||
static constexpr uint8_t BytePrefix = 0x0A;
|
||||
static constexpr uint8_t WordPrefix = 0x0B;
|
||||
static constexpr uint8_t DWordPrefix = 0x0C;
|
||||
|
||||
struct S5Object {
|
||||
uint16_t SLP_TYPa;
|
||||
uint16_t SLP_TYPb;
|
||||
bool Valid;
|
||||
};
|
||||
|
||||
// Parse a DSDT (or SSDT) AML block to find the \_S5_ object.
|
||||
// dsdtData points to the CommonSDTHeader of the DSDT (HHDM-mapped).
|
||||
// Returns the parsed S5 values on success.
|
||||
S5Object FindS5(void* dsdtData);
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* AcpiShutdown.cpp
|
||||
* ACPI S5 (soft-off) shutdown via PM1 control registers
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "AcpiShutdown.hpp"
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiShutdown {
|
||||
|
||||
static constexpr uint16_t SLP_EN = 1 << 13;
|
||||
|
||||
static bool g_available = false;
|
||||
static uint32_t g_pm1aControlBlock = 0;
|
||||
static uint32_t g_pm1bControlBlock = 0;
|
||||
static uint16_t g_slpTypA = 0;
|
||||
static uint16_t g_slpTypB = 0;
|
||||
|
||||
void Initialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
FADT::ParsedFADT fadt{};
|
||||
if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) {
|
||||
KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT — ACPI shutdown unavailable";
|
||||
return;
|
||||
}
|
||||
|
||||
// Map and parse the DSDT to find \_S5_
|
||||
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
|
||||
AML::S5Object s5 = AML::FindS5(dsdt);
|
||||
|
||||
if (!s5.Valid) {
|
||||
KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT — ACPI shutdown unavailable";
|
||||
return;
|
||||
}
|
||||
|
||||
g_pm1aControlBlock = fadt.PM1aControlBlock;
|
||||
g_pm1bControlBlock = fadt.PM1bControlBlock;
|
||||
g_slpTypA = s5.SLP_TYPa;
|
||||
g_slpTypB = s5.SLP_TYPb;
|
||||
g_available = true;
|
||||
|
||||
KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex
|
||||
<< (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")";
|
||||
}
|
||||
|
||||
bool IsAvailable() {
|
||||
return g_available;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!g_available) return;
|
||||
|
||||
KernelLogStream(INFO, "ACPI") << "Performing ACPI S5 shutdown...";
|
||||
|
||||
// Write SLP_TYPa | SLP_EN to PM1a control register
|
||||
Io::Out16((g_slpTypA << 10) | SLP_EN, (uint16_t)g_pm1aControlBlock);
|
||||
|
||||
// If PM1b exists, write to it as well
|
||||
if (g_pm1bControlBlock != 0) {
|
||||
Io::Out16((g_slpTypB << 10) | SLP_EN, (uint16_t)g_pm1bControlBlock);
|
||||
}
|
||||
|
||||
// If we're still here, the shutdown didn't work immediately.
|
||||
// Halt and wait for the hardware to power off.
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* AcpiShutdown.hpp
|
||||
* ACPI S5 (soft-off) shutdown via PM1 control registers
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <ACPI/ACPI.hpp>
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiShutdown {
|
||||
|
||||
// Parse the FADT and DSDT from the XSDT to prepare shutdown values.
|
||||
// Must be called during boot before any shutdown attempt.
|
||||
void Initialize(ACPI::CommonSDTHeader* xsdt);
|
||||
|
||||
// Returns true if ACPI shutdown is available (Initialize succeeded).
|
||||
bool IsAvailable();
|
||||
|
||||
// Perform an ACPI S5 shutdown by writing to PM1a/PM1b control registers.
|
||||
// Does not return on success.
|
||||
void Shutdown();
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* FADT.cpp
|
||||
* Fixed ACPI Description Table parsing
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "FADT.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace FADT {
|
||||
|
||||
static bool SignatureMatch(const char* sig, const char* target, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (sig[i] != target[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Table* FindFADTInXSDT(ACPI::CommonSDTHeader* xsdt) {
|
||||
uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8;
|
||||
uint64_t* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader));
|
||||
|
||||
for (uint32_t i = 0; i < entryCount; i++) {
|
||||
auto* header = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
|
||||
if (SignatureMatch(header->Signature, "FACP", 4)) {
|
||||
return (Table*)header;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Parse(ACPI::CommonSDTHeader* xsdt, ParsedFADT& result) {
|
||||
result.Valid = false;
|
||||
|
||||
auto* fadt = FindFADTInXSDT(xsdt);
|
||||
if (fadt == nullptr) {
|
||||
KernelLogStream(ERROR, "FADT") << "FADT table not found in XSDT";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ACPI::TestChecksum(&fadt->Header)) {
|
||||
KernelLogStream(ERROR, "FADT") << "FADT checksum failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "FADT") << "Found FADT table";
|
||||
|
||||
// Prefer X_Dsdt (64-bit) if available, otherwise fall back to 32-bit Dsdt
|
||||
if (fadt->Header.Length >= offsetof(Table, X_Dsdt) + 8 && fadt->X_Dsdt != 0) {
|
||||
result.DsdtAddress = fadt->X_Dsdt;
|
||||
} else {
|
||||
result.DsdtAddress = fadt->Dsdt;
|
||||
}
|
||||
|
||||
result.PM1aControlBlock = fadt->PM1aControlBlock;
|
||||
result.PM1bControlBlock = fadt->PM1bControlBlock;
|
||||
result.SMI_CommandPort = fadt->SMI_CommandPort;
|
||||
result.AcpiEnable = fadt->AcpiEnable;
|
||||
result.AcpiDisable = fadt->AcpiDisable;
|
||||
result.Valid = true;
|
||||
|
||||
KernelLogStream(INFO, "FADT") << "DSDT at " << base::hex << result.DsdtAddress;
|
||||
KernelLogStream(INFO, "FADT") << "PM1a_CNT_BLK: " << base::hex << (uint64_t)result.PM1aControlBlock;
|
||||
|
||||
if (result.PM1bControlBlock != 0) {
|
||||
KernelLogStream(INFO, "FADT") << "PM1b_CNT_BLK: " << base::hex << (uint64_t)result.PM1bControlBlock;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* FADT.hpp
|
||||
* Fixed ACPI Description Table parsing
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <ACPI/ACPI.hpp>
|
||||
|
||||
namespace Hal {
|
||||
namespace FADT {
|
||||
|
||||
struct Table {
|
||||
ACPI::CommonSDTHeader Header;
|
||||
uint32_t FirmwareCtrl;
|
||||
uint32_t Dsdt;
|
||||
uint8_t Reserved0;
|
||||
uint8_t PreferredPMProfile;
|
||||
uint16_t SCI_Interrupt;
|
||||
uint32_t SMI_CommandPort;
|
||||
uint8_t AcpiEnable;
|
||||
uint8_t AcpiDisable;
|
||||
uint8_t S4BIOS_REQ;
|
||||
uint8_t PSTATE_Control;
|
||||
uint32_t PM1aEventBlock;
|
||||
uint32_t PM1bEventBlock;
|
||||
uint32_t PM1aControlBlock;
|
||||
uint32_t PM1bControlBlock;
|
||||
uint32_t PM2ControlBlock;
|
||||
uint32_t PMTimerBlock;
|
||||
uint32_t GPE0Block;
|
||||
uint32_t GPE1Block;
|
||||
uint8_t PM1EventLength;
|
||||
uint8_t PM1ControlLength;
|
||||
uint8_t PM2ControlLength;
|
||||
uint8_t PMTimerLength;
|
||||
uint8_t GPE0Length;
|
||||
uint8_t GPE1Length;
|
||||
uint8_t GPE1Base;
|
||||
uint8_t CStateControl;
|
||||
uint16_t WorstC2Latency;
|
||||
uint16_t WorstC3Latency;
|
||||
uint16_t FlushSize;
|
||||
uint16_t FlushStride;
|
||||
uint8_t DutyOffset;
|
||||
uint8_t DutyWidth;
|
||||
uint8_t DayAlarm;
|
||||
uint8_t MonthAlarm;
|
||||
uint8_t Century;
|
||||
uint16_t BootArchitectureFlags;
|
||||
uint8_t Reserved1;
|
||||
uint32_t Flags;
|
||||
// Generic Address Structure for RESET_REG (12 bytes)
|
||||
uint8_t ResetReg[12];
|
||||
uint8_t ResetValue;
|
||||
uint16_t ArmBootArchFlags;
|
||||
uint8_t FADTMinorVersion;
|
||||
// Extended fields (ACPI 2.0+)
|
||||
uint64_t X_FirmwareControl;
|
||||
uint64_t X_Dsdt;
|
||||
uint8_t X_PM1aEventBlock[12];
|
||||
uint8_t X_PM1bEventBlock[12];
|
||||
uint8_t X_PM1aControlBlock[12];
|
||||
uint8_t X_PM1bControlBlock[12];
|
||||
uint8_t X_PM2ControlBlock[12];
|
||||
uint8_t X_PMTimerBlock[12];
|
||||
uint8_t X_GPE0Block[12];
|
||||
uint8_t X_GPE1Block[12];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ParsedFADT {
|
||||
uint64_t DsdtAddress;
|
||||
uint32_t PM1aControlBlock;
|
||||
uint32_t PM1bControlBlock;
|
||||
uint32_t SMI_CommandPort;
|
||||
uint8_t AcpiEnable;
|
||||
uint8_t AcpiDisable;
|
||||
bool Valid;
|
||||
};
|
||||
|
||||
bool Parse(ACPI::CommonSDTHeader* xsdt, ParsedFADT& result);
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/Storage/Ahci.hpp>
|
||||
#include <Pci/Pci.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
@@ -128,7 +129,30 @@ namespace Montauk {
|
||||
}
|
||||
}
|
||||
|
||||
// PCI devices (category 7)
|
||||
// Storage (category 7)
|
||||
if (Drivers::Storage::Ahci::IsInitialized()) {
|
||||
for (int port = 0; port < 32 && count < maxCount; port++) {
|
||||
auto* info = Drivers::Storage::Ahci::GetPortInfo(port);
|
||||
if (!info) continue;
|
||||
uint64_t sectors = info->SectorCount;
|
||||
uint64_t sizeMB = (sectors * 512) / (1024 * 1024);
|
||||
uint64_t sizeGB = sizeMB / 1024;
|
||||
char detail[48];
|
||||
int p = 0;
|
||||
if (sizeGB > 0) {
|
||||
p = dl_append_dec(detail, p, (int)sizeGB, 48);
|
||||
p = dl_append(detail, p, " GiB, SATA port ", 48);
|
||||
} else {
|
||||
p = dl_append_dec(detail, p, (int)sizeMB, 48);
|
||||
p = dl_append(detail, p, " MiB, SATA port ", 48);
|
||||
}
|
||||
p = dl_append_dec(detail, p, port, 48);
|
||||
add(7, info->Model, detail);
|
||||
buf[count - 1]._pad[0] = (uint8_t)port; // stash port index
|
||||
}
|
||||
}
|
||||
|
||||
// PCI devices (category 8)
|
||||
auto& pciDevs = Pci::GetDevices();
|
||||
for (int i = 0; i < (int)pciDevs.size() && count < maxCount; i++) {
|
||||
auto& d = pciDevs[i];
|
||||
@@ -144,9 +168,37 @@ namespace Montauk {
|
||||
p = dl_append_hex(detail, p, d.VendorId, 4, 48);
|
||||
p = dl_append(detail, p, ":", 48);
|
||||
p = dl_append_hex(detail, p, d.DeviceId, 4, 48);
|
||||
add(7, className, detail);
|
||||
add(8, className, detail);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int Sys_DiskInfo(DiskInfo* buf, int port) {
|
||||
if (buf == nullptr) return -1;
|
||||
if (!Drivers::Storage::Ahci::IsInitialized()) return -1;
|
||||
|
||||
auto* info = Drivers::Storage::Ahci::GetPortInfo(port);
|
||||
if (!info) return -1;
|
||||
|
||||
buf->port = info->PortIndex;
|
||||
buf->type = (uint8_t)info->Type;
|
||||
buf->sataGen = (uint8_t)info->SataGen;
|
||||
buf->sectorCount = info->SectorCount;
|
||||
buf->sectorSizeLog = info->SectorSizeLog;
|
||||
buf->sectorSizePhys = info->SectorSizePhys;
|
||||
buf->rpm = info->Rpm;
|
||||
buf->ncqDepth = info->NcqDepth;
|
||||
buf->supportsLba48 = info->SupportsLba48 ? 1 : 0;
|
||||
buf->supportsNcq = info->SupportsNcq ? 1 : 0;
|
||||
buf->supportsTrim = info->SupportsTrim ? 1 : 0;
|
||||
buf->supportsSmart = info->SupportsSmart ? 1 : 0;
|
||||
buf->supportsWriteCache = info->SupportsWriteCache ? 1 : 0;
|
||||
buf->supportsReadAhead = info->SupportsReadAhead ? 1 : 0;
|
||||
dl_strcpy(buf->model, info->Model, 41);
|
||||
dl_strcpy(buf->serial, info->Serial, 21);
|
||||
dl_strcpy(buf->firmware, info->Firmware, 9);
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <Efi/UEFI.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <ACPI/AcpiShutdown.hpp>
|
||||
|
||||
namespace Montauk {
|
||||
|
||||
@@ -25,13 +26,18 @@ namespace Montauk {
|
||||
}
|
||||
|
||||
static void Sys_Shutdown() {
|
||||
/* Primary: ACPI S5 shutdown via PM1 control registers */
|
||||
if (Hal::AcpiShutdown::IsAvailable()) {
|
||||
Hal::AcpiShutdown::Shutdown();
|
||||
}
|
||||
|
||||
/* Fallback: EFI ResetSystem */
|
||||
if (Efi::g_ResetSystem) {
|
||||
/* Switch to kernel PML4 which has identity-mapped UEFI runtime regions */
|
||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
||||
Efi::g_ResetSystem(Efi::EfiResetShutdown, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
/* No fallback for shutdown; halt the CPU */
|
||||
/* Last resort: halt the CPU */
|
||||
asm volatile("cli; hlt");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
@@ -209,6 +209,8 @@ namespace Montauk {
|
||||
}
|
||||
case SYS_DEVLIST:
|
||||
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_DISKINFO:
|
||||
return (int64_t)Sys_DiskInfo((DiskInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_WINSETSCALE:
|
||||
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
||||
case SYS_WINGETSCALE:
|
||||
|
||||
@@ -132,6 +132,7 @@ namespace Montauk {
|
||||
|
||||
/* Device.hpp */
|
||||
static constexpr uint64_t SYS_DEVLIST = 63;
|
||||
static constexpr uint64_t SYS_DISKINFO = 69;
|
||||
|
||||
/* MemInfo.hpp */
|
||||
static constexpr uint64_t SYS_MEMSTATS = 67;
|
||||
@@ -224,12 +225,35 @@ namespace Montauk {
|
||||
};
|
||||
|
||||
struct DevInfo {
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI
|
||||
uint8_t _pad[3];
|
||||
char name[48];
|
||||
char detail[48];
|
||||
};
|
||||
|
||||
struct DiskInfo {
|
||||
uint8_t port; // AHCI port index
|
||||
uint8_t type; // 0=none, 1=SATA, 2=SATAPI
|
||||
uint8_t sataGen; // SATA gen (1/2/3)
|
||||
uint8_t _pad0;
|
||||
uint64_t sectorCount; // Total user-addressable sectors
|
||||
uint16_t sectorSizeLog; // Logical sector size (bytes)
|
||||
uint16_t sectorSizePhys; // Physical sector size (bytes)
|
||||
uint16_t rpm; // 0=unknown, 1=SSD, else RPM
|
||||
uint16_t ncqDepth; // 0 if no NCQ
|
||||
uint8_t supportsLba48;
|
||||
uint8_t supportsNcq;
|
||||
uint8_t supportsTrim;
|
||||
uint8_t supportsSmart;
|
||||
uint8_t supportsWriteCache;
|
||||
uint8_t supportsReadAhead;
|
||||
uint8_t _pad1[2];
|
||||
char model[41];
|
||||
char serial[21];
|
||||
char firmware[9];
|
||||
char _pad2[1];
|
||||
};
|
||||
|
||||
struct ProcInfo {
|
||||
int32_t pid;
|
||||
int32_t parentPid;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/Storage/Ahci.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#include <Net/Net.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
@@ -59,6 +60,10 @@ namespace Drivers {
|
||||
return Net::E1000E::Probe(dev);
|
||||
}
|
||||
|
||||
static bool ProbeAhci(const Pci::PciDevice& dev) {
|
||||
return Storage::Ahci::Probe(dev);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Driver table
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -108,6 +113,18 @@ namespace Drivers {
|
||||
Pci::ProbePhase::Normal,
|
||||
ProbeE1000E,
|
||||
},
|
||||
// Order 5: AHCI — Normal phase, match class=0x01/0x06/0x01 (SATA AHCI)
|
||||
{
|
||||
"AHCI",
|
||||
0, // VendorId (any)
|
||||
0x01, // ClassCode (Mass Storage)
|
||||
0x06, // SubClass (SATA)
|
||||
0x01, // ProgIf (AHCI 1.0)
|
||||
nullptr,
|
||||
0,
|
||||
Pci::ProbePhase::Normal,
|
||||
ProbeAhci,
|
||||
},
|
||||
};
|
||||
|
||||
static constexpr uint16_t g_driverTableCount = sizeof(g_driverTable) / sizeof(g_driverTable[0]);
|
||||
@@ -139,4 +156,10 @@ namespace Drivers {
|
||||
::Net::Initialize();
|
||||
}
|
||||
|
||||
void InitializeStorage() {
|
||||
// AHCI driver registered SATA devices during ProbeNormal().
|
||||
// Nothing else to do here for now — the VFS registration
|
||||
// is handled in Main.cpp after this call.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,13 @@ namespace Drivers {
|
||||
// Post-probe: wire up GPU framebuffer to cursor subsystem.
|
||||
void InitializeGraphics();
|
||||
|
||||
// Probe PCI devices for Normal-phase drivers (xHCI, E1000, E1000E).
|
||||
// Probe PCI devices for Normal-phase drivers (xHCI, E1000, E1000E, AHCI).
|
||||
void ProbeNormal();
|
||||
|
||||
// Post-probe: initialize network stack.
|
||||
void InitializeNetwork();
|
||||
|
||||
// Post-probe: register SATA drives with VFS.
|
||||
void InitializeStorage();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,833 @@
|
||||
/*
|
||||
* Ahci.cpp
|
||||
* AHCI (Advanced Host Controller Interface) SATA driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Ahci.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Storage::Ahci {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Driver state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool g_initialized = false;
|
||||
static volatile uint8_t* g_mmioBase = nullptr;
|
||||
static uint32_t g_portsImplemented = 0;
|
||||
static int g_activePortCount = 0;
|
||||
static PortInfo g_ports[MAX_PORTS] = {};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Register access
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void WriteReg(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_mmioBase + reg) = value;
|
||||
}
|
||||
|
||||
static uint32_t ReadReg(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_mmioBase + reg);
|
||||
}
|
||||
|
||||
static void WritePortReg(int port, uint32_t reg, uint32_t value) {
|
||||
WriteReg(PORT_BASE + port * PORT_SIZE + reg, value);
|
||||
}
|
||||
|
||||
static uint32_t ReadPortReg(int port, uint32_t reg) {
|
||||
return ReadReg(PORT_BASE + port * PORT_SIZE + reg);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DMA buffer allocation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void* AllocateDmaBuffer(uint64_t& outPhys, int pages = 1) {
|
||||
void* virt;
|
||||
if (pages == 1) {
|
||||
virt = Memory::g_pfa->AllocateZeroed();
|
||||
} else {
|
||||
virt = Memory::g_pfa->ReallocConsecutive(nullptr, pages);
|
||||
memset(virt, 0, pages * 0x1000);
|
||||
}
|
||||
outPhys = Memory::SubHHDM(virt);
|
||||
return virt;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BIOS/OS Handoff
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void PerformBiosHandoff() {
|
||||
uint32_t cap2 = ReadReg(REG_CAP2);
|
||||
if (!(cap2 & (1u << 0))) {
|
||||
return; // BOH not supported
|
||||
}
|
||||
|
||||
uint32_t bohc = ReadReg(REG_BOHC);
|
||||
if (!(bohc & BOHC_BOS)) {
|
||||
return; // BIOS doesn't own it
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "AHCI") << "Performing BIOS/OS handoff...";
|
||||
|
||||
// Set OS Owned Semaphore
|
||||
WriteReg(REG_BOHC, bohc | BOHC_OOS);
|
||||
|
||||
// Wait for BIOS to release (up to 25ms per spec)
|
||||
for (int i = 0; i < 250000; i++) {
|
||||
bohc = ReadReg(REG_BOHC);
|
||||
if (!(bohc & BOHC_BOS)) {
|
||||
KernelLogStream(OK, "AHCI") << "BIOS handoff complete";
|
||||
return;
|
||||
}
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
// If BIOS Busy, wait another 2 seconds
|
||||
if (ReadReg(REG_BOHC) & BOHC_BB) {
|
||||
for (int i = 0; i < 2000000; i++) {
|
||||
if (!(ReadReg(REG_BOHC) & BOHC_BB)) break;
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "AHCI") << "BIOS handoff timed out, proceeding anyway";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Port engine control
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void StopPort(int port) {
|
||||
uint32_t cmd = ReadPortReg(port, PORT_CMD);
|
||||
|
||||
// Clear ST (Stop command engine)
|
||||
if (cmd & PORT_CMD_ST) {
|
||||
cmd &= ~PORT_CMD_ST;
|
||||
WritePortReg(port, PORT_CMD, cmd);
|
||||
}
|
||||
|
||||
// Clear FRE (Stop FIS receive)
|
||||
if (cmd & PORT_CMD_FRE) {
|
||||
cmd &= ~PORT_CMD_FRE;
|
||||
WritePortReg(port, PORT_CMD, cmd);
|
||||
}
|
||||
|
||||
// Wait for CR and FR to clear
|
||||
for (int i = 0; i < 500000; i++) {
|
||||
cmd = ReadPortReg(port, PORT_CMD);
|
||||
if (!(cmd & PORT_CMD_CR) && !(cmd & PORT_CMD_FR)) {
|
||||
return;
|
||||
}
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "AHCI") << "Port " << port << " stop timed out";
|
||||
}
|
||||
|
||||
static void StartPort(int port) {
|
||||
// Wait for CR to clear before starting
|
||||
for (int i = 0; i < 500000; i++) {
|
||||
if (!(ReadPortReg(port, PORT_CMD) & PORT_CMD_CR)) break;
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
uint32_t cmd = ReadPortReg(port, PORT_CMD);
|
||||
cmd |= PORT_CMD_FRE;
|
||||
WritePortReg(port, PORT_CMD, cmd);
|
||||
|
||||
cmd |= PORT_CMD_ST;
|
||||
WritePortReg(port, PORT_CMD, cmd);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Port detection and classification
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static PortType ClassifyPort(int port) {
|
||||
uint32_t ssts = ReadPortReg(port, PORT_SSTS);
|
||||
uint32_t det = ssts & SSTS_DET_MASK;
|
||||
|
||||
if (det != SSTS_DET_PRESENT) {
|
||||
return PortType::None;
|
||||
}
|
||||
|
||||
uint32_t sig = ReadPortReg(port, PORT_SIG);
|
||||
switch (sig) {
|
||||
case SIG_ATA: return PortType::Sata;
|
||||
case SIG_ATAPI: return PortType::Satapi;
|
||||
case SIG_SEMB: return PortType::Semb;
|
||||
case SIG_PM: return PortType::PortMultiplier;
|
||||
default: return PortType::Sata; // Default to SATA for unknown signatures
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Port initialization (allocate command list, FIS area, command tables)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void InitPort(int port) {
|
||||
StopPort(port);
|
||||
|
||||
// Allocate Command List (1 KiB, 1024-byte aligned)
|
||||
// and FIS area (256 bytes, 256-byte aligned)
|
||||
// Both fit in one 4 KiB page
|
||||
uint64_t clPhys;
|
||||
void* clVirt = AllocateDmaBuffer(clPhys);
|
||||
g_ports[port].CmdList = (CommandHeader*)clVirt;
|
||||
g_ports[port].CmdListPhys = clPhys;
|
||||
|
||||
// FIS area goes in the second half of the same page
|
||||
uint64_t fbPhys = clPhys + 1024;
|
||||
void* fbVirt = (void*)((uint8_t*)clVirt + 1024);
|
||||
g_ports[port].FisArea = fbVirt;
|
||||
g_ports[port].FisAreaPhys = fbPhys;
|
||||
|
||||
// Set CLB and FB in port registers
|
||||
WritePortReg(port, PORT_CLB, (uint32_t)(clPhys & 0xFFFFFFFF));
|
||||
WritePortReg(port, PORT_CLBU, (uint32_t)(clPhys >> 32));
|
||||
WritePortReg(port, PORT_FB, (uint32_t)(fbPhys & 0xFFFFFFFF));
|
||||
WritePortReg(port, PORT_FBU, (uint32_t)(fbPhys >> 32));
|
||||
|
||||
// Allocate command tables (one page each, 128-byte aligned)
|
||||
CommandHeader* headers = g_ports[port].CmdList;
|
||||
for (int i = 0; i < CMD_HEADER_COUNT; i++) {
|
||||
uint64_t ctPhys;
|
||||
void* ctVirt = AllocateDmaBuffer(ctPhys);
|
||||
g_ports[port].CmdTables[i] = (CommandTable*)ctVirt;
|
||||
g_ports[port].CmdTablesPhys[i] = ctPhys;
|
||||
|
||||
headers[i].CtbaLow = (uint32_t)(ctPhys & 0xFFFFFFFF);
|
||||
headers[i].CtbaHigh = (uint32_t)(ctPhys >> 32);
|
||||
headers[i].PrdtLength = 0;
|
||||
headers[i].PrdByteCount = 0;
|
||||
}
|
||||
|
||||
// Clear interrupt status and error
|
||||
WritePortReg(port, PORT_SERR, 0xFFFFFFFF);
|
||||
WritePortReg(port, PORT_IS, 0xFFFFFFFF);
|
||||
|
||||
// Enable interrupts for this port
|
||||
WritePortReg(port, PORT_IE,
|
||||
PORT_IS_DHRS | PORT_IS_PSS | PORT_IS_DSS |
|
||||
PORT_IS_SDBS | PORT_IS_TFES);
|
||||
|
||||
// Power on and spin up if needed
|
||||
uint32_t cmd = ReadPortReg(port, PORT_CMD);
|
||||
cmd |= PORT_CMD_POD | PORT_CMD_SUD;
|
||||
WritePortReg(port, PORT_CMD, cmd);
|
||||
|
||||
StartPort(port);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue a command and wait for completion
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool IssueCommand(int port, int slot) {
|
||||
// Wait for the slot to be free
|
||||
for (int i = 0; i < 500000; i++) {
|
||||
if (!(ReadPortReg(port, PORT_CI) & (1u << slot))) break;
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
// Check that the port isn't in error
|
||||
uint32_t tfd = ReadPortReg(port, PORT_TFD);
|
||||
if (tfd & (PORT_TFD_BSY | PORT_TFD_DRQ)) {
|
||||
// Device is busy, wait
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
tfd = ReadPortReg(port, PORT_TFD);
|
||||
if (!(tfd & (PORT_TFD_BSY | PORT_TFD_DRQ))) break;
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
if (tfd & (PORT_TFD_BSY | PORT_TFD_DRQ)) {
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port << " device busy before command issue";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Issue the command
|
||||
WritePortReg(port, PORT_CI, 1u << slot);
|
||||
|
||||
// Wait for completion
|
||||
for (int i = 0; i < 5000000; i++) {
|
||||
uint32_t ci = ReadPortReg(port, PORT_CI);
|
||||
if (!(ci & (1u << slot))) {
|
||||
// Check for errors
|
||||
uint32_t is = ReadPortReg(port, PORT_IS);
|
||||
if (is & PORT_IS_TFES) {
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port << " task file error";
|
||||
WritePortReg(port, PORT_IS, PORT_IS_TFES);
|
||||
return false;
|
||||
}
|
||||
// Clear interrupt status
|
||||
WritePortReg(port, PORT_IS, is);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for fatal error during wait
|
||||
uint32_t is = ReadPortReg(port, PORT_IS);
|
||||
if (is & PORT_IS_TFES) {
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port
|
||||
<< " task file error during command, TFD=" << base::hex << (uint64_t)ReadPortReg(port, PORT_TFD);
|
||||
WritePortReg(port, PORT_IS, PORT_IS_TFES);
|
||||
return false;
|
||||
}
|
||||
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port << " command timeout";
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Build a command FIS for a DMA read/write
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static int FindFreeSlot(int port) {
|
||||
uint32_t slots = ReadPortReg(port, PORT_SACT) | ReadPortReg(port, PORT_CI);
|
||||
for (int i = 0; i < CMD_HEADER_COUNT; i++) {
|
||||
if (!(slots & (1u << i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void BuildReadWriteCommand(int port, int slot, uint64_t lba, uint32_t count,
|
||||
uint64_t bufferPhys, bool write) {
|
||||
CommandHeader* hdr = &g_ports[port].CmdList[slot];
|
||||
CommandTable* tbl = g_ports[port].CmdTables[slot];
|
||||
|
||||
// Clear the command table
|
||||
memset(tbl, 0, sizeof(CommandTable) + sizeof(PrdtEntry) * MAX_PRDT_ENTRIES);
|
||||
|
||||
// Build FIS
|
||||
FisRegH2D* fis = (FisRegH2D*)tbl->CommandFis;
|
||||
fis->FisType = (uint8_t)FisType::RegH2D;
|
||||
fis->CmdCtl = 1; // Command
|
||||
fis->Command = write ? ATA_CMD_WRITE_DMA_EX : ATA_CMD_READ_DMA_EX;
|
||||
fis->Device = (1 << 6); // LBA mode
|
||||
|
||||
fis->Lba0 = (uint8_t)(lba);
|
||||
fis->Lba1 = (uint8_t)(lba >> 8);
|
||||
fis->Lba2 = (uint8_t)(lba >> 16);
|
||||
fis->Lba3 = (uint8_t)(lba >> 24);
|
||||
fis->Lba4 = (uint8_t)(lba >> 32);
|
||||
fis->Lba5 = (uint8_t)(lba >> 40);
|
||||
|
||||
fis->Count = (uint16_t)count;
|
||||
|
||||
// Build PRDT entries
|
||||
uint32_t bytesRemaining = count * SECTOR_SIZE;
|
||||
int prdtIdx = 0;
|
||||
uint64_t currentPhys = bufferPhys;
|
||||
|
||||
while (bytesRemaining > 0 && prdtIdx < MAX_PRDT_ENTRIES) {
|
||||
uint32_t chunkSize = bytesRemaining;
|
||||
// Each PRDT entry can transfer up to 4 MiB (aligned to word boundary)
|
||||
if (chunkSize > 0x400000) {
|
||||
chunkSize = 0x400000;
|
||||
}
|
||||
|
||||
tbl->PrdtEntries[prdtIdx].DataBaseLow = (uint32_t)(currentPhys & 0xFFFFFFFF);
|
||||
tbl->PrdtEntries[prdtIdx].DataBaseHigh = (uint32_t)(currentPhys >> 32);
|
||||
tbl->PrdtEntries[prdtIdx].Reserved = 0;
|
||||
// Byte count is (actual bytes - 1), bit 31 = interrupt on completion for last entry
|
||||
tbl->PrdtEntries[prdtIdx].ByteCount = (chunkSize - 1);
|
||||
if (bytesRemaining <= chunkSize) {
|
||||
tbl->PrdtEntries[prdtIdx].ByteCount |= (1u << 31); // IOC
|
||||
}
|
||||
|
||||
currentPhys += chunkSize;
|
||||
bytesRemaining -= chunkSize;
|
||||
prdtIdx++;
|
||||
}
|
||||
|
||||
// Set up command header
|
||||
// CFL = FIS length in dwords (5 for FisRegH2D = 20 bytes / 4)
|
||||
hdr->CflPmpA = 5; // CFL bits [4:0]
|
||||
hdr->Flags = write ? CMDHDR_WRITE : 0;
|
||||
hdr->PrdtLength = (uint16_t)prdtIdx;
|
||||
hdr->PrdByteCount = 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IDENTIFY DEVICE
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void SwapStringBytes(char* str, int len) {
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
char tmp = str[i];
|
||||
str[i] = str[i + 1];
|
||||
str[i + 1] = tmp;
|
||||
}
|
||||
// Trim trailing spaces
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
if (str[i] == ' ' || str[i] == '\0') {
|
||||
str[i] = '\0';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IdentifyDevice(int port) {
|
||||
int slot = FindFreeSlot(port);
|
||||
if (slot < 0) {
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port << " no free command slot for IDENTIFY";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate a page for IDENTIFY data (512 bytes)
|
||||
uint64_t identPhys;
|
||||
uint16_t* identData = (uint16_t*)AllocateDmaBuffer(identPhys);
|
||||
|
||||
CommandHeader* hdr = &g_ports[port].CmdList[slot];
|
||||
CommandTable* tbl = g_ports[port].CmdTables[slot];
|
||||
|
||||
memset(tbl, 0, sizeof(CommandTable) + sizeof(PrdtEntry));
|
||||
|
||||
FisRegH2D* fis = (FisRegH2D*)tbl->CommandFis;
|
||||
fis->FisType = (uint8_t)FisType::RegH2D;
|
||||
fis->CmdCtl = 1;
|
||||
fis->Command = ATA_CMD_IDENTIFY;
|
||||
fis->Device = 0;
|
||||
|
||||
tbl->PrdtEntries[0].DataBaseLow = (uint32_t)(identPhys & 0xFFFFFFFF);
|
||||
tbl->PrdtEntries[0].DataBaseHigh = (uint32_t)(identPhys >> 32);
|
||||
tbl->PrdtEntries[0].ByteCount = 511 | (1u << 31); // 512 bytes, IOC
|
||||
|
||||
hdr->CflPmpA = 5;
|
||||
hdr->Flags = 0;
|
||||
hdr->PrdtLength = 1;
|
||||
hdr->PrdByteCount = 0;
|
||||
|
||||
if (!IssueCommand(port, slot)) {
|
||||
KernelLogStream(ERROR, "AHCI") << "Port " << port << " IDENTIFY failed";
|
||||
Memory::g_pfa->Free(identData);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse IDENTIFY data
|
||||
// Word 100-103: Total number of user addressable sectors (48-bit LBA)
|
||||
uint64_t sectors = (uint64_t)identData[100]
|
||||
| ((uint64_t)identData[101] << 16)
|
||||
| ((uint64_t)identData[102] << 32)
|
||||
| ((uint64_t)identData[103] << 48);
|
||||
|
||||
if (sectors == 0) {
|
||||
// Fallback to 28-bit LBA sector count (words 60-61)
|
||||
sectors = (uint64_t)identData[60] | ((uint64_t)identData[61] << 16);
|
||||
}
|
||||
|
||||
g_ports[port].SectorCount = sectors;
|
||||
g_ports[port].PortIndex = (uint8_t)port;
|
||||
|
||||
// Model string: words 27-46 (40 bytes)
|
||||
memcpy(g_ports[port].Model, &identData[27], 40);
|
||||
g_ports[port].Model[40] = '\0';
|
||||
SwapStringBytes(g_ports[port].Model, 40);
|
||||
|
||||
// Serial number: words 10-19 (20 bytes)
|
||||
memcpy(g_ports[port].Serial, &identData[10], 20);
|
||||
g_ports[port].Serial[20] = '\0';
|
||||
SwapStringBytes(g_ports[port].Serial, 20);
|
||||
|
||||
// Firmware revision: words 23-26 (8 bytes)
|
||||
memcpy(g_ports[port].Firmware, &identData[23], 8);
|
||||
g_ports[port].Firmware[8] = '\0';
|
||||
SwapStringBytes(g_ports[port].Firmware, 8);
|
||||
|
||||
// Word 49: capabilities — bit 9 = LBA supported
|
||||
// Word 83: command set supported — bit 10 = 48-bit LBA
|
||||
uint16_t cmdSet83 = identData[83];
|
||||
g_ports[port].SupportsLba48 = (cmdSet83 & (1 << 10)) != 0;
|
||||
|
||||
// Word 82: command set supported
|
||||
uint16_t cmdSet82 = identData[82];
|
||||
g_ports[port].SupportsSmart = (cmdSet82 & (1 << 0)) != 0;
|
||||
g_ports[port].SupportsWriteCache = (cmdSet82 & (1 << 5)) != 0;
|
||||
g_ports[port].SupportsReadAhead = (cmdSet82 & (1 << 6)) != 0;
|
||||
|
||||
// Word 84: command/feature set supported ext — bit 1 = SMART self-test
|
||||
uint16_t cmdSet84 = identData[84];
|
||||
g_ports[port].SupportsSmartSelfTest = (cmdSet84 & (1 << 1)) != 0;
|
||||
|
||||
// Word 75: NCQ queue depth (bits 4:0 = max depth - 1)
|
||||
uint16_t word75 = identData[75];
|
||||
uint16_t sataCapabilities = identData[76]; // Word 76: SATA capabilities
|
||||
g_ports[port].SupportsNcq = (sataCapabilities & (1 << 8)) != 0;
|
||||
g_ports[port].NcqDepth = g_ports[port].SupportsNcq ? (uint16_t)((word75 & 0x1F) + 1) : 0;
|
||||
|
||||
// Word 76: SATA capabilities — bits 3:1 = supported SATA generations
|
||||
g_ports[port].SataGen = 0;
|
||||
if (sataCapabilities & (1 << 3)) g_ports[port].SataGen = 3; // 6 Gbps
|
||||
else if (sataCapabilities & (1 << 2)) g_ports[port].SataGen = 2; // 3 Gbps
|
||||
else if (sataCapabilities & (1 << 1)) g_ports[port].SataGen = 1; // 1.5 Gbps
|
||||
|
||||
// Word 169: TRIM support (Data Set Management)
|
||||
uint16_t word169 = identData[169];
|
||||
g_ports[port].SupportsTrim = (word169 & (1 << 0)) != 0;
|
||||
|
||||
// Word 106: Physical/logical sector size
|
||||
uint16_t word106 = identData[106];
|
||||
if (word106 & (1 << 14)) {
|
||||
// Valid field
|
||||
g_ports[port].SectorSizePhys = (word106 & (1 << 12))
|
||||
? (uint16_t)(512 * (1 << (word106 & 0x0F))) : 512;
|
||||
g_ports[port].SectorSizeLog = (word106 & (1 << 13)) ? 0 : 512;
|
||||
// If logical sector size specified in words 117-118
|
||||
if (word106 & (1 << 13)) {
|
||||
g_ports[port].SectorSizeLog = (uint16_t)(
|
||||
((uint32_t)identData[117] | ((uint32_t)identData[118] << 16)) * 2);
|
||||
}
|
||||
} else {
|
||||
g_ports[port].SectorSizeLog = 512;
|
||||
g_ports[port].SectorSizePhys = 512;
|
||||
}
|
||||
if (g_ports[port].SectorSizeLog == 0) g_ports[port].SectorSizeLog = 512;
|
||||
|
||||
// Word 217: Nominal Media Rotation Rate
|
||||
// 0000h = not reported, 0001h = non-rotating (SSD), else RPM
|
||||
g_ports[port].Rpm = identData[217];
|
||||
|
||||
Memory::g_pfa->Free(identData);
|
||||
|
||||
uint64_t sizeBytes = sectors * SECTOR_SIZE;
|
||||
uint64_t sizeMB = sizeBytes / (1024 * 1024);
|
||||
uint64_t sizeGB = sizeMB / 1024;
|
||||
|
||||
if (sizeGB > 0) {
|
||||
KernelLogStream(OK, "AHCI") << "Port " << port << ": " << g_ports[port].Model
|
||||
<< " (" << sizeGB << " GiB)";
|
||||
} else {
|
||||
KernelLogStream(OK, "AHCI") << "Port " << port << ": " << g_ports[port].Model
|
||||
<< " (" << sizeMB << " MiB)";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Interrupt handler
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void HandleInterrupt(uint8_t irq) {
|
||||
(void)irq;
|
||||
|
||||
uint32_t is = ReadReg(REG_IS);
|
||||
if (is == 0) return;
|
||||
|
||||
// Acknowledge each port's interrupt
|
||||
for (int i = 0; i < MAX_PORTS; i++) {
|
||||
if (is & (1u << i)) {
|
||||
uint32_t portIs = ReadPortReg(i, PORT_IS);
|
||||
WritePortReg(i, PORT_IS, portIs);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear global interrupt status
|
||||
WriteReg(REG_IS, is);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MSI setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
|
||||
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
|
||||
if (cap == 0) {
|
||||
KernelLogStream(INFO, "AHCI") << "MSI capability not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2);
|
||||
bool is64bit = (msgCtrl & (1 << 7)) != 0;
|
||||
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE);
|
||||
|
||||
if (is64bit) {
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 8, 0);
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR);
|
||||
} else {
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR);
|
||||
}
|
||||
|
||||
msgCtrl &= ~(0x70); // Single message
|
||||
msgCtrl |= (1 << 0); // MSI Enable
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl);
|
||||
|
||||
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND);
|
||||
pciCmd |= Pci::PCI_CMD_INTX_DISABLE;
|
||||
Pci::LegacyWrite16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND, pciCmd);
|
||||
|
||||
Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt);
|
||||
|
||||
KernelLogStream(OK, "AHCI") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HBA reset
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool ResetHba() {
|
||||
// Set AHCI Enable
|
||||
uint32_t ghc = ReadReg(REG_GHC);
|
||||
ghc |= GHC_AE;
|
||||
WriteReg(REG_GHC, ghc);
|
||||
|
||||
// Perform HBA reset
|
||||
ghc = ReadReg(REG_GHC);
|
||||
ghc |= GHC_HR;
|
||||
WriteReg(REG_GHC, ghc);
|
||||
|
||||
// Wait for reset to complete (HR should self-clear within 1 second)
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
if (!(ReadReg(REG_GHC) & GHC_HR)) {
|
||||
// Re-enable AHCI after reset
|
||||
ghc = ReadReg(REG_GHC);
|
||||
ghc |= GHC_AE;
|
||||
WriteReg(REG_GHC, ghc);
|
||||
return true;
|
||||
}
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
KernelLogStream(ERROR, "AHCI") << "HBA reset timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Probe (PCI driver entry point)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool Probe(const Pci::PciDevice& dev) {
|
||||
if (g_initialized) return false;
|
||||
|
||||
KernelLogStream(OK, "AHCI") << "Found AHCI controller at PCI "
|
||||
<< base::hex << (uint64_t)dev.Bus << ":"
|
||||
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function
|
||||
<< " (" << (uint64_t)dev.VendorId << ":" << (uint64_t)dev.DeviceId << ")";
|
||||
|
||||
// AHCI uses BAR5 (ABAR) for its MMIO registers.
|
||||
// BAR5 is at PCI config offset 0x24.
|
||||
uint32_t abarLow = Pci::LegacyRead32(dev.Bus, dev.Device, dev.Function, 0x24);
|
||||
uint64_t mmioPhys = abarLow & 0xFFFFFFF0u;
|
||||
|
||||
// Check for 64-bit BAR
|
||||
if ((abarLow & 0x06) == 0x04) {
|
||||
uint32_t abarHigh = Pci::LegacyRead32(dev.Bus, dev.Device, dev.Function, 0x28);
|
||||
mmioPhys |= ((uint64_t)abarHigh << 32);
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "AHCI") << "ABAR (BAR5) physical: " << base::hex << mmioPhys;
|
||||
|
||||
// Map MMIO region (AHCI spec says up to 8 KiB for GHC + 32 ports)
|
||||
// Map 16 KiB to be safe
|
||||
constexpr uint64_t MmioSize = 0x4000;
|
||||
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
|
||||
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
|
||||
// Enable bus mastering and memory space
|
||||
Pci::EnableBusMaster(dev.Bus, dev.Device, dev.Function);
|
||||
|
||||
// BIOS/OS handoff
|
||||
PerformBiosHandoff();
|
||||
|
||||
// Reset HBA
|
||||
if (!ResetHba()) {
|
||||
KernelLogStream(ERROR, "AHCI") << "HBA reset failed, aborting";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read version
|
||||
uint32_t vs = ReadReg(REG_VS);
|
||||
uint32_t vsMajor = (vs >> 16) & 0xFFFF;
|
||||
uint32_t vsMinor = vs & 0xFFFF;
|
||||
KernelLogStream(INFO, "AHCI") << "Version: " << base::dec
|
||||
<< (uint64_t)vsMajor << "." << (uint64_t)vsMinor;
|
||||
|
||||
// Read capabilities
|
||||
uint32_t cap = ReadReg(REG_CAP);
|
||||
uint32_t numPorts = (cap & 0x1F) + 1;
|
||||
uint32_t numSlots = ((cap >> 8) & 0x1F) + 1;
|
||||
bool supports64bit = (cap & CAP_S64A) != 0;
|
||||
|
||||
KernelLogStream(INFO, "AHCI") << "Ports: " << (uint64_t)numPorts
|
||||
<< ", Command slots: " << (uint64_t)numSlots
|
||||
<< ", 64-bit: " << (supports64bit ? "yes" : "no");
|
||||
|
||||
// Read which ports are implemented
|
||||
g_portsImplemented = ReadReg(REG_PI);
|
||||
KernelLogStream(INFO, "AHCI") << "Ports implemented: " << base::hex << (uint64_t)g_portsImplemented;
|
||||
|
||||
// Set up MSI (or fall back to legacy IRQ)
|
||||
bool hasMsi = SetupMsi(dev.Bus, dev.Device, dev.Function);
|
||||
|
||||
if (!hasMsi) {
|
||||
uint8_t irqLine = Pci::LegacyRead8(dev.Bus, dev.Device, dev.Function,
|
||||
(uint8_t)Pci::PCI_REG_INTERRUPT);
|
||||
if (irqLine != 0xFF) {
|
||||
KernelLogStream(INFO, "AHCI") << "Using legacy IRQ " << base::dec << (uint64_t)irqLine;
|
||||
Hal::RegisterIrqHandler(irqLine, HandleInterrupt);
|
||||
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(irqLine));
|
||||
}
|
||||
}
|
||||
|
||||
// Enable global interrupts
|
||||
uint32_t ghc = ReadReg(REG_GHC);
|
||||
ghc |= GHC_IE;
|
||||
WriteReg(REG_GHC, ghc);
|
||||
|
||||
// Initialize each implemented port
|
||||
g_activePortCount = 0;
|
||||
for (int i = 0; i < MAX_PORTS; i++) {
|
||||
if (!(g_portsImplemented & (1u << i))) continue;
|
||||
|
||||
PortType type = ClassifyPort(i);
|
||||
g_ports[i].Type = type;
|
||||
|
||||
if (type == PortType::None) continue;
|
||||
|
||||
const char* typeStr = "Unknown";
|
||||
switch (type) {
|
||||
case PortType::Sata: typeStr = "SATA"; break;
|
||||
case PortType::Satapi: typeStr = "SATAPI"; break;
|
||||
case PortType::Semb: typeStr = "SEMB"; break;
|
||||
case PortType::PortMultiplier: typeStr = "PM"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "AHCI") << "Port " << base::dec << (uint64_t)i
|
||||
<< ": " << typeStr << " device detected";
|
||||
|
||||
InitPort(i);
|
||||
|
||||
if (type == PortType::Sata) {
|
||||
if (IdentifyDevice(i)) {
|
||||
g_ports[i].Active = true;
|
||||
g_activePortCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
KernelLogStream(OK, "AHCI") << "Initialization complete: "
|
||||
<< base::dec << (uint64_t)g_activePortCount << " SATA device(s) ready";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool IsInitialized() {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
int GetPortCount() {
|
||||
return g_activePortCount;
|
||||
}
|
||||
|
||||
const PortInfo* GetPortInfo(int port) {
|
||||
if (port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||
return nullptr;
|
||||
}
|
||||
return &g_ports[port];
|
||||
}
|
||||
|
||||
uint64_t GetSectorCount(int port) {
|
||||
if (port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||
return 0;
|
||||
}
|
||||
return g_ports[port].SectorCount;
|
||||
}
|
||||
|
||||
bool ReadSectors(int port, uint64_t lba, uint32_t count, void* buffer) {
|
||||
if (!g_initialized || port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||
return false;
|
||||
}
|
||||
if (count == 0 || buffer == nullptr) return false;
|
||||
|
||||
// Limit to 128 sectors per command (64 KiB)
|
||||
if (count > 128) {
|
||||
KernelLogStream(ERROR, "AHCI") << "ReadSectors: count " << count << " exceeds max 128";
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = FindFreeSlot(port);
|
||||
if (slot < 0) {
|
||||
KernelLogStream(ERROR, "AHCI") << "ReadSectors: no free command slot";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate DMA buffer (we need physically contiguous memory for DMA)
|
||||
uint32_t totalBytes = count * SECTOR_SIZE;
|
||||
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
|
||||
uint64_t dmaPhys;
|
||||
void* dmaVirt = AllocateDmaBuffer(dmaPhys, pagesNeeded);
|
||||
|
||||
BuildReadWriteCommand(port, slot, lba, count, dmaPhys, false);
|
||||
|
||||
bool ok = IssueCommand(port, slot);
|
||||
if (ok) {
|
||||
memcpy(buffer, dmaVirt, totalBytes);
|
||||
}
|
||||
|
||||
Memory::g_pfa->Free(dmaVirt, pagesNeeded);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer) {
|
||||
if (!g_initialized || port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||
return false;
|
||||
}
|
||||
if (count == 0 || buffer == nullptr) return false;
|
||||
|
||||
if (count > 128) {
|
||||
KernelLogStream(ERROR, "AHCI") << "WriteSectors: count " << count << " exceeds max 128";
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = FindFreeSlot(port);
|
||||
if (slot < 0) {
|
||||
KernelLogStream(ERROR, "AHCI") << "WriteSectors: no free command slot";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t totalBytes = count * SECTOR_SIZE;
|
||||
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
|
||||
uint64_t dmaPhys;
|
||||
void* dmaVirt = AllocateDmaBuffer(dmaPhys, pagesNeeded);
|
||||
|
||||
memcpy(dmaVirt, buffer, totalBytes);
|
||||
|
||||
BuildReadWriteCommand(port, slot, lba, count, dmaPhys, true);
|
||||
|
||||
bool ok = IssueCommand(port, slot);
|
||||
|
||||
Memory::g_pfa->Free(dmaVirt, pagesNeeded);
|
||||
return ok;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Ahci.hpp
|
||||
* AHCI (Advanced Host Controller Interface) SATA driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Pci/Pci.hpp>
|
||||
|
||||
namespace Drivers::Storage::Ahci {
|
||||
|
||||
// =========================================================================
|
||||
// AHCI Generic Host Control registers (HBA memory, offset from BAR5/ABAR)
|
||||
// =========================================================================
|
||||
|
||||
constexpr uint32_t REG_CAP = 0x00; // Host Capabilities
|
||||
constexpr uint32_t REG_GHC = 0x04; // Global Host Control
|
||||
constexpr uint32_t REG_IS = 0x08; // Interrupt Status
|
||||
constexpr uint32_t REG_PI = 0x0C; // Ports Implemented
|
||||
constexpr uint32_t REG_VS = 0x10; // Version
|
||||
constexpr uint32_t REG_CAP2 = 0x24; // Host Capabilities Extended
|
||||
constexpr uint32_t REG_BOHC = 0x28; // BIOS/OS Handoff Control
|
||||
|
||||
// GHC register bits
|
||||
constexpr uint32_t GHC_HR = (1u << 0); // HBA Reset
|
||||
constexpr uint32_t GHC_IE = (1u << 1); // Interrupt Enable
|
||||
constexpr uint32_t GHC_AE = (1u << 31); // AHCI Enable
|
||||
|
||||
// CAP register bits
|
||||
constexpr uint32_t CAP_S64A = (1u << 31); // Supports 64-bit Addressing
|
||||
constexpr uint32_t CAP_SSS = (1u << 27); // Supports Staggered Spin-up
|
||||
|
||||
// BOHC register bits
|
||||
constexpr uint32_t BOHC_BOS = (1u << 0); // BIOS Owned Semaphore
|
||||
constexpr uint32_t BOHC_OOS = (1u << 1); // OS Owned Semaphore
|
||||
constexpr uint32_t BOHC_BB = (1u << 4); // BIOS Busy
|
||||
|
||||
// =========================================================================
|
||||
// Per-port registers (base = 0x100 + port * 0x80)
|
||||
// =========================================================================
|
||||
|
||||
constexpr uint32_t PORT_BASE = 0x100;
|
||||
constexpr uint32_t PORT_SIZE = 0x80;
|
||||
|
||||
// Port register offsets (relative to port base)
|
||||
constexpr uint32_t PORT_CLB = 0x00; // Command List Base Address (low)
|
||||
constexpr uint32_t PORT_CLBU = 0x04; // Command List Base Address (high)
|
||||
constexpr uint32_t PORT_FB = 0x08; // FIS Base Address (low)
|
||||
constexpr uint32_t PORT_FBU = 0x0C; // FIS Base Address (high)
|
||||
constexpr uint32_t PORT_IS = 0x10; // Interrupt Status
|
||||
constexpr uint32_t PORT_IE = 0x14; // Interrupt Enable
|
||||
constexpr uint32_t PORT_CMD = 0x18; // Command and Status
|
||||
constexpr uint32_t PORT_TFD = 0x20; // Task File Data
|
||||
constexpr uint32_t PORT_SIG = 0x24; // Signature
|
||||
constexpr uint32_t PORT_SSTS = 0x28; // SATA Status (SCR0: SStatus)
|
||||
constexpr uint32_t PORT_SCTL = 0x2C; // SATA Control (SCR2: SControl)
|
||||
constexpr uint32_t PORT_SERR = 0x30; // SATA Error (SCR1: SError)
|
||||
constexpr uint32_t PORT_SACT = 0x34; // SATA Active
|
||||
constexpr uint32_t PORT_CI = 0x38; // Command Issue
|
||||
|
||||
// PORT_CMD bits
|
||||
constexpr uint32_t PORT_CMD_ST = (1u << 0); // Start
|
||||
constexpr uint32_t PORT_CMD_SUD = (1u << 1); // Spin-Up Device
|
||||
constexpr uint32_t PORT_CMD_POD = (1u << 2); // Power On Device
|
||||
constexpr uint32_t PORT_CMD_FRE = (1u << 4); // FIS Receive Enable
|
||||
constexpr uint32_t PORT_CMD_FR = (1u << 14); // FIS Receive Running
|
||||
constexpr uint32_t PORT_CMD_CR = (1u << 15); // Command List Running
|
||||
constexpr uint32_t PORT_CMD_ICC_ACTIVE = (1u << 28); // Interface Comm Control: Active
|
||||
|
||||
// PORT_TFD bits
|
||||
constexpr uint32_t PORT_TFD_BSY = (1u << 7); // Busy
|
||||
constexpr uint32_t PORT_TFD_DRQ = (1u << 3); // Data Request
|
||||
constexpr uint32_t PORT_TFD_ERR = (1u << 0); // Error
|
||||
|
||||
// PORT_IS bits (interrupt status)
|
||||
constexpr uint32_t PORT_IS_DHRS = (1u << 0); // Device to Host Register FIS
|
||||
constexpr uint32_t PORT_IS_PSS = (1u << 1); // PIO Setup FIS
|
||||
constexpr uint32_t PORT_IS_DSS = (1u << 2); // DMA Setup FIS
|
||||
constexpr uint32_t PORT_IS_SDBS = (1u << 3); // Set Device Bits
|
||||
constexpr uint32_t PORT_IS_TFES = (1u << 30); // Task File Error Status
|
||||
|
||||
// PORT_SSTS (SStatus) fields
|
||||
constexpr uint32_t SSTS_DET_MASK = 0x0F; // Device Detection
|
||||
constexpr uint32_t SSTS_DET_PRESENT = 0x03; // Phy communication established
|
||||
|
||||
// Device signatures
|
||||
constexpr uint32_t SIG_ATA = 0x00000101; // SATA drive
|
||||
constexpr uint32_t SIG_ATAPI = 0xEB140101; // SATAPI drive
|
||||
constexpr uint32_t SIG_SEMB = 0xC33C0101; // Enclosure management bridge
|
||||
constexpr uint32_t SIG_PM = 0x96690101; // Port multiplier
|
||||
|
||||
// =========================================================================
|
||||
// FIS (Frame Information Structure) types
|
||||
// =========================================================================
|
||||
|
||||
enum class FisType : uint8_t {
|
||||
RegH2D = 0x27, // Register FIS - Host to Device
|
||||
RegD2H = 0x34, // Register FIS - Device to Host
|
||||
DmaActivate = 0x39,
|
||||
DmaSetup = 0x41,
|
||||
Data = 0x46,
|
||||
BistActivate = 0x58,
|
||||
PioSetup = 0x5F,
|
||||
DevBits = 0xA1,
|
||||
};
|
||||
|
||||
// Register FIS - Host to Device (used for issuing ATA commands)
|
||||
struct FisRegH2D {
|
||||
uint8_t FisType; // FisType::RegH2D (0x27)
|
||||
uint8_t PmPort : 4; // Port multiplier
|
||||
uint8_t Reserved0 : 3;
|
||||
uint8_t CmdCtl : 1; // 1 = Command, 0 = Control
|
||||
uint8_t Command; // ATA command
|
||||
uint8_t FeatureLow; // Feature register (7:0)
|
||||
uint8_t Lba0; // LBA (7:0)
|
||||
uint8_t Lba1; // LBA (15:8)
|
||||
uint8_t Lba2; // LBA (23:16)
|
||||
uint8_t Device; // Device register
|
||||
uint8_t Lba3; // LBA (31:24)
|
||||
uint8_t Lba4; // LBA (39:32)
|
||||
uint8_t Lba5; // LBA (47:40)
|
||||
uint8_t FeatureHigh; // Feature register (15:8)
|
||||
uint16_t Count; // Count
|
||||
uint8_t Icc; // Isochronous command completion
|
||||
uint8_t Control; // Control register
|
||||
uint32_t Reserved1;
|
||||
} __attribute__((packed));
|
||||
|
||||
// =========================================================================
|
||||
// Command structures
|
||||
// =========================================================================
|
||||
|
||||
// Physical Region Descriptor Table entry (16 bytes)
|
||||
struct PrdtEntry {
|
||||
uint32_t DataBaseLow; // Data base address (low)
|
||||
uint32_t DataBaseHigh; // Data base address (high)
|
||||
uint32_t Reserved;
|
||||
uint32_t ByteCount; // Byte count (bit 31 = Interrupt on Completion)
|
||||
} __attribute__((packed));
|
||||
|
||||
// Command Table (pointed to by command header, variable size)
|
||||
struct CommandTable {
|
||||
uint8_t CommandFis[64]; // Command FIS (up to 64 bytes)
|
||||
uint8_t AtapiCommand[16]; // ATAPI command (12 or 16 bytes)
|
||||
uint8_t Reserved[48]; // Reserved
|
||||
PrdtEntry PrdtEntries[]; // PRDT entries (variable length)
|
||||
} __attribute__((packed));
|
||||
|
||||
// Command Header (32 bytes each, 32 entries in Command List)
|
||||
struct CommandHeader {
|
||||
uint8_t CflPmpA; // Command FIS length (bits 4:0), PMP (bits 11:8), etc.
|
||||
uint8_t Flags; // Flags (Write bit 6, Prefetchable bit 7, etc.)
|
||||
uint16_t PrdtLength; // PRDT entry count
|
||||
uint32_t PrdByteCount; // PRD byte count transferred
|
||||
uint32_t CtbaLow; // Command table base address (low)
|
||||
uint32_t CtbaHigh; // Command table base address (high)
|
||||
uint32_t Reserved[4];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Command header flag bits
|
||||
constexpr uint8_t CMDHDR_WRITE = (1u << 6); // Write direction (in Flags byte)
|
||||
constexpr uint8_t CMDHDR_PREFETCH = (1u << 1); // Prefetchable (in Flags byte)
|
||||
constexpr uint8_t CMDHDR_CLR_BSY = (1u << 2); // Clear Busy upon R_OK (in Flags byte)
|
||||
|
||||
// =========================================================================
|
||||
// ATA commands
|
||||
// =========================================================================
|
||||
|
||||
constexpr uint8_t ATA_CMD_IDENTIFY = 0xEC;
|
||||
constexpr uint8_t ATA_CMD_READ_DMA_EX = 0x25; // READ DMA EXT (48-bit LBA)
|
||||
constexpr uint8_t ATA_CMD_WRITE_DMA_EX = 0x35; // WRITE DMA EXT (48-bit LBA)
|
||||
|
||||
// =========================================================================
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
constexpr int MAX_PORTS = 32;
|
||||
constexpr int CMD_HEADER_COUNT = 32; // 32 command slots per port
|
||||
constexpr int MAX_PRDT_ENTRIES = 8; // Max PRDT entries per command
|
||||
constexpr int SECTOR_SIZE = 512;
|
||||
|
||||
// MSI configuration
|
||||
constexpr uint8_t MSI_IRQ = 25; // IRQ slot 25 = vector 57
|
||||
constexpr uint32_t MSI_VECTOR = 57;
|
||||
constexpr uint32_t MSI_ADDR_BASE = 0xFEE00000;
|
||||
|
||||
// =========================================================================
|
||||
// Port info
|
||||
// =========================================================================
|
||||
|
||||
enum class PortType : uint8_t {
|
||||
None,
|
||||
Sata,
|
||||
Satapi,
|
||||
Semb,
|
||||
PortMultiplier,
|
||||
};
|
||||
|
||||
struct PortInfo {
|
||||
bool Active;
|
||||
PortType Type;
|
||||
uint64_t SectorCount; // Total sectors (from IDENTIFY)
|
||||
char Model[41]; // Model string (from IDENTIFY)
|
||||
char Serial[21]; // Serial number (from IDENTIFY)
|
||||
char Firmware[9]; // Firmware revision (from IDENTIFY)
|
||||
uint8_t PortIndex; // AHCI port number
|
||||
|
||||
// Feature flags (from IDENTIFY)
|
||||
bool SupportsLba48;
|
||||
bool SupportsNcq;
|
||||
bool SupportsTrim;
|
||||
bool SupportsSmartSelfTest;
|
||||
bool SupportsSmart;
|
||||
bool SupportsWriteCache;
|
||||
bool SupportsReadAhead;
|
||||
|
||||
uint16_t SataGen; // SATA generation (1/2/3)
|
||||
uint16_t NcqDepth; // NCQ queue depth (0 if no NCQ)
|
||||
uint16_t SectorSizeLog; // Logical sector size (bytes, usually 512)
|
||||
uint16_t SectorSizePhys; // Physical sector size (bytes, 512 or 4096)
|
||||
uint16_t Rpm; // Nominal RPM (0 = unknown, 1 = SSD/non-rotating)
|
||||
|
||||
// DMA structures (physical + virtual)
|
||||
CommandHeader* CmdList; // Command list (1 KiB, 32 headers)
|
||||
uint64_t CmdListPhys;
|
||||
void* FisArea; // Received FIS area (256 bytes)
|
||||
uint64_t FisAreaPhys;
|
||||
CommandTable* CmdTables[CMD_HEADER_COUNT]; // Command tables
|
||||
uint64_t CmdTablesPhys[CMD_HEADER_COUNT];
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Public API
|
||||
// =========================================================================
|
||||
|
||||
// Probe a PCI device (called by driver matching framework)
|
||||
bool Probe(const Pci::PciDevice& dev);
|
||||
|
||||
// Check if the driver was initialized
|
||||
bool IsInitialized();
|
||||
|
||||
// Get number of active ports with SATA devices
|
||||
int GetPortCount();
|
||||
|
||||
// Read sectors from a SATA device
|
||||
// port: port index (0-31), lba: starting LBA, count: sector count (max 128)
|
||||
// buffer: destination buffer (must be large enough for count * 512 bytes)
|
||||
// Returns true on success
|
||||
bool ReadSectors(int port, uint64_t lba, uint32_t count, void* buffer);
|
||||
|
||||
// Write sectors to a SATA device
|
||||
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer);
|
||||
|
||||
// Get info about a specific port
|
||||
const PortInfo* GetPortInfo(int port);
|
||||
|
||||
// Get the total sector count for a port
|
||||
uint64_t GetSectorCount(int port);
|
||||
|
||||
};
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <ACPI/AcpiShutdown.hpp>
|
||||
#include <Hal/Apic/ApicInit.hpp>
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
@@ -136,6 +137,8 @@ extern "C" void kmain() {
|
||||
|
||||
#if defined (__x86_64__)
|
||||
if (g_acpi.GetXSDT() != nullptr) {
|
||||
Hal::AcpiShutdown::Initialize(g_acpi.GetXSDT());
|
||||
|
||||
Hal::ApicInitialize(g_acpi.GetXSDT());
|
||||
|
||||
Pci::Initialize(g_acpi.GetXSDT());
|
||||
@@ -151,6 +154,7 @@ extern "C" void kmain() {
|
||||
|
||||
Drivers::ProbeNormal();
|
||||
Drivers::InitializeNetwork();
|
||||
Drivers::InitializeStorage();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace Montauk {
|
||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||
static constexpr uint64_t SYS_KILL = 62;
|
||||
static constexpr uint64_t SYS_DEVLIST = 63;
|
||||
static constexpr uint64_t SYS_DISKINFO = 69;
|
||||
|
||||
// Kernel introspection syscalls
|
||||
static constexpr uint64_t SYS_MEMSTATS = 67;
|
||||
@@ -168,12 +169,35 @@ namespace Montauk {
|
||||
};
|
||||
|
||||
struct DevInfo {
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
|
||||
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI
|
||||
uint8_t _pad[3];
|
||||
char name[48];
|
||||
char detail[48];
|
||||
};
|
||||
|
||||
struct DiskInfo {
|
||||
uint8_t port; // AHCI port index
|
||||
uint8_t type; // 0=none, 1=SATA, 2=SATAPI
|
||||
uint8_t sataGen; // SATA gen (1/2/3)
|
||||
uint8_t _pad0;
|
||||
uint64_t sectorCount; // Total user-addressable sectors
|
||||
uint16_t sectorSizeLog; // Logical sector size (bytes)
|
||||
uint16_t sectorSizePhys; // Physical sector size (bytes)
|
||||
uint16_t rpm; // 0=unknown, 1=SSD, else RPM
|
||||
uint16_t ncqDepth; // 0 if no NCQ
|
||||
uint8_t supportsLba48;
|
||||
uint8_t supportsNcq;
|
||||
uint8_t supportsTrim;
|
||||
uint8_t supportsSmart;
|
||||
uint8_t supportsWriteCache;
|
||||
uint8_t supportsReadAhead;
|
||||
uint8_t _pad1[2];
|
||||
char model[41];
|
||||
char serial[21];
|
||||
char firmware[9];
|
||||
char _pad2[1];
|
||||
};
|
||||
|
||||
struct ProcInfo {
|
||||
int32_t pid;
|
||||
int32_t parentPid;
|
||||
|
||||
@@ -294,6 +294,9 @@ namespace montauk {
|
||||
inline int devlist(Montauk::DevInfo* buf, int max) {
|
||||
return (int)syscall2(Montauk::SYS_DEVLIST, (uint64_t)buf, (uint64_t)max);
|
||||
}
|
||||
inline int diskinfo(Montauk::DiskInfo* buf, int port) {
|
||||
return (int)syscall2(Montauk::SYS_DISKINFO, (uint64_t)buf, (uint64_t)port);
|
||||
}
|
||||
|
||||
// Kernel introspection
|
||||
inline void memstats(Montauk::MemStats* out) { syscall1(Montauk::SYS_MEMSTATS, (uint64_t)out); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* app_devexplorer.cpp
|
||||
* MontaukOS Desktop - Device Explorer (lists hardware detected by the kernel)
|
||||
* MontaukOS Desktop - Device Explorer
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -26,9 +26,10 @@ static const char* category_names[] = {
|
||||
"USB", // 4
|
||||
"Network", // 5
|
||||
"Display", // 6
|
||||
"PCI", // 7
|
||||
"Storage", // 7
|
||||
"PCI", // 8
|
||||
};
|
||||
static constexpr int NUM_CATEGORIES = 8;
|
||||
static constexpr int NUM_CATEGORIES = 9;
|
||||
|
||||
static Color category_colors[] = {
|
||||
Color::from_rgb(0x33, 0x66, 0xCC), // CPU - blue
|
||||
@@ -38,6 +39,7 @@ static Color category_colors[] = {
|
||||
Color::from_rgb(0x00, 0x88, 0x88), // USB - teal
|
||||
Color::from_rgb(0xCC, 0x55, 0x22), // Network - orange
|
||||
Color::from_rgb(0x44, 0x66, 0xCC), // Display - indigo
|
||||
Color::from_rgb(0x99, 0x55, 0x00), // Storage - brown
|
||||
Color::from_rgb(0x66, 0x66, 0x66), // PCI - gray
|
||||
};
|
||||
|
||||
@@ -49,6 +51,10 @@ struct DevExplorerState {
|
||||
int selected_row; // index into visible display rows (-1 = none)
|
||||
int scroll_y; // scroll offset in display rows
|
||||
uint64_t last_poll_ms;
|
||||
|
||||
// Double-click tracking
|
||||
int last_click_row;
|
||||
uint64_t last_click_ms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -305,6 +311,313 @@ static void devexplorer_on_draw(Window* win, Framebuffer& fb) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Disk Detail Window
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int DD_TAB_BAR_H = 32;
|
||||
static constexpr int DD_TAB_COUNT = 2;
|
||||
static const char* dd_tab_labels[DD_TAB_COUNT] = { "General", "Features" };
|
||||
|
||||
struct DiskDetailState {
|
||||
DesktopState* desktop;
|
||||
Montauk::DiskInfo info;
|
||||
int active_tab;
|
||||
};
|
||||
|
||||
// Helper: format uint64 to string (snprintf %d can't handle 64-bit)
|
||||
static void fmt_u64(char* buf, int bufsize, uint64_t v) {
|
||||
if (v == 0) { buf[0] = '0'; buf[1] = '\0'; return; }
|
||||
char tmp[24]; int i = 0;
|
||||
while (v > 0) { tmp[i++] = '0' + (int)(v % 10); v /= 10; }
|
||||
int j = 0;
|
||||
while (i > 0 && j < bufsize - 1) buf[j++] = tmp[--i];
|
||||
buf[j] = '\0';
|
||||
}
|
||||
|
||||
// Draw a two-column table row
|
||||
static void dd_table_row(Canvas& c, int x, int y, int col1_w, int row_h,
|
||||
const char* key, const char* value, Color key_col, Color val_col) {
|
||||
int fh = system_font_height();
|
||||
int ty = y + (row_h - fh) / 2;
|
||||
c.text(x + 8, ty, key, key_col);
|
||||
c.text(x + col1_w + 8, ty, value, val_col);
|
||||
}
|
||||
|
||||
// Draw a feature row with colored status indicator
|
||||
static void dd_feature_row(Canvas& c, int x, int y, int w, int row_h,
|
||||
const char* label, bool supported, const char* extra) {
|
||||
int fh = system_font_height();
|
||||
int ty = y + (row_h - fh) / 2;
|
||||
|
||||
// Status dot
|
||||
int dot_y = y + (row_h - 8) / 2;
|
||||
Color dot_col = supported ? Color::from_rgb(0x22, 0x88, 0x22)
|
||||
: Color::from_rgb(0xBB, 0xBB, 0xBB);
|
||||
c.fill_rounded_rect(x + 10, dot_y, 8, 8, 4, dot_col);
|
||||
|
||||
// Label
|
||||
Color text_col = supported ? colors::TEXT_COLOR : Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
c.text(x + 26, ty, label, text_col);
|
||||
|
||||
// Extra info on right
|
||||
if (extra && extra[0]) {
|
||||
int ew = text_width(extra);
|
||||
c.text(x + w - ew - 12, ty, extra, Color::from_rgb(0x66, 0x66, 0x66));
|
||||
}
|
||||
}
|
||||
|
||||
static void diskdetail_draw_general(Canvas& c, DiskDetailState* dd) {
|
||||
char line[128];
|
||||
int fh = system_font_height();
|
||||
int x = 0;
|
||||
int y = 12;
|
||||
int w = c.w;
|
||||
int col1_w = 110;
|
||||
int row_h = fh + 8;
|
||||
Color key_col = Color::from_rgb(0x66, 0x66, 0x66);
|
||||
Color val_col = colors::TEXT_COLOR;
|
||||
Color hdr_col = colors::ACCENT;
|
||||
Color border = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
|
||||
// --- Identification section ---
|
||||
c.text(x + 8, y, "Identification", hdr_col);
|
||||
y += fh + 8;
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
// Table rows with alternating backgrounds
|
||||
auto row_bg = [&](int ry) {
|
||||
static int rowIdx = 0;
|
||||
if (rowIdx++ % 2 == 0)
|
||||
c.fill_rect(x + 8, ry, w - 16, row_h, Color::from_rgb(0xF7, 0xF7, 0xF7));
|
||||
};
|
||||
|
||||
int rowIdx = 0;
|
||||
auto table_row = [&](const char* key, const char* val) {
|
||||
if (rowIdx++ % 2 == 0)
|
||||
c.fill_rect(x + 8, y, w - 16, row_h, Color::from_rgb(0xF7, 0xF7, 0xF7));
|
||||
dd_table_row(c, x, y, col1_w, row_h, key, val, key_col, val_col);
|
||||
y += row_h;
|
||||
};
|
||||
|
||||
table_row("Model", dd->info.model);
|
||||
table_row("Serial", dd->info.serial);
|
||||
table_row("Firmware", dd->info.firmware);
|
||||
|
||||
const char* typeStr = "Unknown";
|
||||
if (dd->info.type == 1) typeStr = "SATA";
|
||||
else if (dd->info.type == 2) typeStr = "SATAPI";
|
||||
table_row("Type", typeStr);
|
||||
|
||||
snprintf(line, sizeof(line), "%d", (int)dd->info.port);
|
||||
table_row("AHCI Port", line);
|
||||
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// --- Capacity section ---
|
||||
c.text(x + 8, y, "Capacity", hdr_col);
|
||||
y += fh + 8;
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
rowIdx = 0;
|
||||
uint64_t totalBytes = dd->info.sectorCount * (uint64_t)dd->info.sectorSizeLog;
|
||||
uint64_t totalMB = totalBytes / (1024 * 1024);
|
||||
uint64_t totalGB = totalMB / 1024;
|
||||
if (totalGB > 0) {
|
||||
int fracGB = (int)((totalMB % 1024) * 10 / 1024);
|
||||
snprintf(line, sizeof(line), "%d.%d GiB", (int)totalGB, fracGB);
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "%d MiB", (int)totalMB);
|
||||
}
|
||||
table_row("Size", line);
|
||||
|
||||
char secbuf[24];
|
||||
fmt_u64(secbuf, sizeof(secbuf), dd->info.sectorCount);
|
||||
table_row("Sectors", secbuf);
|
||||
|
||||
snprintf(line, sizeof(line), "%d bytes", (int)dd->info.sectorSizeLog);
|
||||
table_row("Logical", line);
|
||||
|
||||
snprintf(line, sizeof(line), "%d bytes", (int)dd->info.sectorSizePhys);
|
||||
table_row("Physical", line);
|
||||
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// --- Interface section ---
|
||||
c.text(x + 8, y, "Interface", hdr_col);
|
||||
y += fh + 8;
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 1;
|
||||
|
||||
rowIdx = 0;
|
||||
const char* sataSpeed = "Unknown";
|
||||
if (dd->info.sataGen == 1) sataSpeed = "SATA I (1.5 Gb/s)";
|
||||
else if (dd->info.sataGen == 2) sataSpeed = "SATA II (3.0 Gb/s)";
|
||||
else if (dd->info.sataGen == 3) sataSpeed = "SATA III (6.0 Gb/s)";
|
||||
table_row("Link Speed", sataSpeed);
|
||||
|
||||
if (dd->info.rpm == 0)
|
||||
table_row("Media", "Not reported");
|
||||
else if (dd->info.rpm == 1)
|
||||
table_row("Media", "Solid State (SSD)");
|
||||
else {
|
||||
snprintf(line, sizeof(line), "%d RPM", (int)dd->info.rpm);
|
||||
table_row("Media", line);
|
||||
}
|
||||
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
}
|
||||
|
||||
static void diskdetail_draw_features(Canvas& c, DiskDetailState* dd) {
|
||||
char line[64];
|
||||
int fh = system_font_height();
|
||||
int x = 0;
|
||||
int y = 12;
|
||||
int w = c.w;
|
||||
int row_h = fh + 10;
|
||||
Color hdr_col = colors::ACCENT;
|
||||
Color border = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
||||
|
||||
c.text(x + 8, y, "Supported Features", hdr_col);
|
||||
y += fh + 8;
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 4;
|
||||
|
||||
dd_feature_row(c, x, y, w, row_h, "48-bit LBA", dd->info.supportsLba48, nullptr);
|
||||
y += row_h;
|
||||
|
||||
if (dd->info.supportsNcq) {
|
||||
snprintf(line, sizeof(line), "Depth: %d", (int)dd->info.ncqDepth);
|
||||
dd_feature_row(c, x, y, w, row_h, "Native Command Queuing (NCQ)", true, line);
|
||||
} else {
|
||||
dd_feature_row(c, x, y, w, row_h, "Native Command Queuing (NCQ)", false, nullptr);
|
||||
}
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(c, x, y, w, row_h, "TRIM (Data Set Management)", dd->info.supportsTrim, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(c, x, y, w, row_h, "S.M.A.R.T.", dd->info.supportsSmart, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(c, x, y, w, row_h, "Write Cache", dd->info.supportsWriteCache, nullptr);
|
||||
y += row_h;
|
||||
|
||||
dd_feature_row(c, x, y, w, row_h, "Read Look-Ahead", dd->info.supportsReadAhead, nullptr);
|
||||
y += row_h;
|
||||
|
||||
y += 4;
|
||||
c.hline(x + 8, y, w - 16, border);
|
||||
y += 12;
|
||||
|
||||
// Legend
|
||||
Color legend_col = Color::from_rgb(0x88, 0x88, 0x88);
|
||||
int ly = y + (fh - 8) / 2;
|
||||
c.fill_rounded_rect(x + 10, ly, 8, 8, 4, Color::from_rgb(0x22, 0x88, 0x22));
|
||||
c.text(x + 26, y, "Supported", legend_col);
|
||||
|
||||
c.fill_rounded_rect(x + 120, ly, 8, 8, 4, Color::from_rgb(0xBB, 0xBB, 0xBB));
|
||||
c.text(x + 136, y, "Not supported", legend_col);
|
||||
}
|
||||
|
||||
static void diskdetail_on_draw(Window* win, Framebuffer& fb) {
|
||||
DiskDetailState* dd = (DiskDetailState*)win->app_data;
|
||||
if (!dd) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int sfh = system_font_height();
|
||||
Color accent = colors::ACCENT;
|
||||
|
||||
// --- Tab bar ---
|
||||
c.fill_rect(0, 0, c.w, DD_TAB_BAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, DD_TAB_BAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
int tab_w = c.w / DD_TAB_COUNT;
|
||||
for (int i = 0; i < DD_TAB_COUNT; i++) {
|
||||
int tx = i * tab_w;
|
||||
bool active = (i == dd->active_tab);
|
||||
|
||||
if (active) {
|
||||
c.fill_rect(tx, 0, tab_w, DD_TAB_BAR_H, colors::WINDOW_BG);
|
||||
c.fill_rect(tx + 4, DD_TAB_BAR_H - 3, tab_w - 8, 3, accent);
|
||||
}
|
||||
|
||||
int tw = text_width(dd_tab_labels[i]);
|
||||
Color tc = active ? accent : Color::from_rgb(0x66, 0x66, 0x66);
|
||||
c.text(tx + (tab_w - tw) / 2, (DD_TAB_BAR_H - sfh) / 2, dd_tab_labels[i], tc);
|
||||
}
|
||||
|
||||
// --- Tab content ---
|
||||
Canvas content(win->content + DD_TAB_BAR_H * win->content_w,
|
||||
win->content_w, win->content_h - DD_TAB_BAR_H);
|
||||
|
||||
switch (dd->active_tab) {
|
||||
case 0: diskdetail_draw_general(content, dd); break;
|
||||
case 1: diskdetail_draw_features(content, dd); break;
|
||||
}
|
||||
}
|
||||
|
||||
static void diskdetail_on_mouse(Window* win, MouseEvent& ev) {
|
||||
DiskDetailState* dd = (DiskDetailState*)win->app_data;
|
||||
if (!dd || !ev.left_pressed()) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int mx = ev.x - cr.x;
|
||||
int my = ev.y - cr.y;
|
||||
|
||||
// Tab bar click
|
||||
if (my >= 0 && my < DD_TAB_BAR_H) {
|
||||
int tab_w = win->content_w / DD_TAB_COUNT;
|
||||
int tab = mx / tab_w;
|
||||
if (tab >= 0 && tab < DD_TAB_COUNT) {
|
||||
dd->active_tab = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void diskdetail_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
montauk::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void open_disk_detail(DesktopState* ds, int port, const char* model) {
|
||||
char title[64];
|
||||
int i = 0;
|
||||
const char* prefix = "Disk: ";
|
||||
while (prefix[i]) { title[i] = prefix[i]; i++; }
|
||||
int j = 0;
|
||||
while (model[j] && i < 62) { title[i++] = model[j++]; }
|
||||
title[i] = '\0';
|
||||
|
||||
int idx = desktop_create_window(ds, title, 180, 90, 440, 420);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
DiskDetailState* dd = (DiskDetailState*)montauk::malloc(sizeof(DiskDetailState));
|
||||
montauk::memset(dd, 0, sizeof(DiskDetailState));
|
||||
|
||||
dd->desktop = ds;
|
||||
dd->active_tab = 0;
|
||||
montauk::diskinfo(&dd->info, port);
|
||||
|
||||
win->app_data = dd;
|
||||
win->on_draw = diskdetail_on_draw;
|
||||
win->on_mouse = diskdetail_on_mouse;
|
||||
win->on_close = diskdetail_on_close;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void devexplorer_on_mouse(Window* win, MouseEvent& ev) {
|
||||
DevExplorerState* de = (DevExplorerState*)win->app_data;
|
||||
if (!de) return;
|
||||
@@ -340,6 +653,7 @@ static void devexplorer_on_mouse(Window* win, MouseEvent& ev) {
|
||||
int row_count = build_display_rows(de, rows);
|
||||
|
||||
// Walk rows from scroll offset, accumulating y
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
int cur_y = list_y;
|
||||
for (int i = de->scroll_y; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
@@ -350,8 +664,23 @@ static void devexplorer_on_mouse(Window* win, MouseEvent& ev) {
|
||||
int cat = rows[i].category;
|
||||
de->collapsed[cat] = !de->collapsed[cat];
|
||||
de->selected_row = -1;
|
||||
de->last_click_row = -1;
|
||||
} else {
|
||||
de->selected_row = i;
|
||||
// Check for double-click on storage device
|
||||
int di = rows[i].dev_index;
|
||||
bool is_double = (de->last_click_row == i)
|
||||
&& (now - de->last_click_ms < 400);
|
||||
|
||||
if (is_double && de->devs[di].category == 7) {
|
||||
// Storage device — open detail window
|
||||
int port = (int)de->devs[di]._pad[0];
|
||||
open_disk_detail(de->desktop, port, de->devs[di].name);
|
||||
de->last_click_row = -1;
|
||||
} else {
|
||||
de->selected_row = i;
|
||||
de->last_click_row = i;
|
||||
de->last_click_ms = now;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -360,6 +689,7 @@ static void devexplorer_on_mouse(Window* win, MouseEvent& ev) {
|
||||
}
|
||||
// Clicked below all rows
|
||||
de->selected_row = -1;
|
||||
de->last_click_row = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,6 +788,8 @@ void open_devexplorer(DesktopState* ds) {
|
||||
de->selected_row = -1;
|
||||
de->scroll_y = 0;
|
||||
de->last_poll_ms = 0;
|
||||
de->last_click_row = -1;
|
||||
de->last_click_ms = 0;
|
||||
|
||||
// All categories start expanded
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++)
|
||||
|
||||
Reference in New Issue
Block a user