move keyboard and mouse out of HAL into a standalone ps2.sys driver, implement usb stack with xhci controller discovery, hid keyboard/mouse, and bulk-only mass storage, add pci msi support with capability chain walking and software irq vector allocation (slots 16-23), rewrite user virtual memory as a USER_VA_REGION descriptor arena with per-process ownership tracking and guard page support, replacing the old bump allocator and top-down image region, add MmCreateUserRegion/MmDestroyAllUserRegions so process teardown reclaims all user va in one pass, allocate a pool-backed shadow framebuffer in hal_screen to fix cursor tearing and scroll corruption, reserve ap trampoline page below 1mb in uefi bootloader before boot services exit, rewrite memset/memcpy/memmove/memcmp with __stosq/__movsq intrinsics, promote driver flag literals to macros and add multi-tu STANDALONE_DRIVERS table to build.c, split rtl8139/rtl816x into header+source, rename KbReadScancode to KbReadKey, improve page fault output with MmDebugDescribeAddress and user region dump, fix %p to %016llX throughout.
This commit is contained in:
1 parent
b970d7f59c
commit
b838f8d83d
130 files changed
+8137
-2932
No files matched your search
@@ -1,436 +1,18 @@
|
||||
// ahci.c - AHCI driver, interrupt-driven with per-drive async IRP queue.
|
||||
// Supports SATA drives only (no ATAPI). Registers each found drive with the
|
||||
// kernel disk device registry via IoRegisterDiskDevice().
|
||||
//
|
||||
// Memory model:
|
||||
// MmAllocateContiguous(n) returns a physical address for n*4K pages.
|
||||
// hal->PhysToVirt(phys) converts to kernel virtual address.
|
||||
// DMA buffers are bounce-buffered (allocated via MmAllocateContiguous) to
|
||||
// avoid needing a VirtToPhys conversion for arbitrary kernel pointers.
|
||||
//
|
||||
// Async I/O model (IRP path):
|
||||
// Each drive has a dedicated kernel worker thread (AhciWorker) that blocks
|
||||
// on a per-drive counting semaphore. Callers submit IRPs through IoCallDriver,
|
||||
// which dispatches to AHCI and enqueues the IRP. The worker dequeues
|
||||
// one IRP at a time, runs the synchronous hardware transfer (blocking the
|
||||
// worker, NOT the submitting thread), then writes irp->Status and fires
|
||||
// irp->CompletionEvent / irp->CompletionFn.
|
||||
//
|
||||
// This gives true async I/O from the caller's perspective: IoSubmitIrp()
|
||||
// returns STATUS_PENDING immediately. Multiple callers can have outstanding
|
||||
// requests against different drives concurrently without stalling each other.
|
||||
//
|
||||
// NCQ extension (future work - see "NCQ TODO" comments):
|
||||
// To saturate modern SSDs, replace the serialised single-slot model with
|
||||
// Native Command Queuing:
|
||||
// • Allocate per-slot bounce buffers (NUM_NCQ_SLOTS × SLOT_BOUNCE_BYTES).
|
||||
// • Use ATA_CMD_READ_FPDMA_QUEUED / WRITE_FPDMA_QUEUED.
|
||||
// • Encode the slot tag in FIS.countl bits 7:3.
|
||||
// • Issue via port->sact (NCQ active) rather than port->ci.
|
||||
// • In AhciIsr: completed slots = prev_sact ^ port->sact; look up
|
||||
// slot_irp[slot] and fire each completion from within the ISR.
|
||||
// • The worker becomes a pure dispatcher: grab a free slot, build the
|
||||
// command, issue it, return immediately. No per-slot KeWaitForEvent.
|
||||
//
|
||||
// Interrupt model (unchanged):
|
||||
// GHC.IE and per-port PORT_IE are enabled after each port is initialised.
|
||||
// AhciIsr fires on the PCI IRQ line, captures/clears port status, and queues
|
||||
// a per-drive DPC. The DPC signals completionEvent outside hard IRQ context.
|
||||
// port_issue_cmd blocks the worker thread on KeWaitForEvent instead of
|
||||
// spinning on CI. port_issue_cmd_poll is kept for early init (IDENTIFY)
|
||||
// before the scheduler is live.
|
||||
// ahci.c - AHCI disk command path and DriverEntry.
|
||||
#include "ahci.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/ddi/io_driver.h>
|
||||
#include <kernel/mm_phys.h>
|
||||
#include <kernel/mm_virt.h>
|
||||
#include <kernel/ke_debug.h>
|
||||
#include <kernel/ke_event.h>
|
||||
#include <kernel/ke_semaphore.h>
|
||||
#include <kernel/ke_thread.h>
|
||||
#include <kernel/ke_irq.h>
|
||||
#include <kernel/ke_irp.h>
|
||||
#include <kernel/ke_dpc.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef enum {
|
||||
FIS_TYPE_REG_H2D = 0x27,
|
||||
FIS_TYPE_REG_D2H = 0x34,
|
||||
FIS_TYPE_DMA_ACT = 0x39,
|
||||
FIS_TYPE_DMA_SETUP = 0x41,
|
||||
FIS_TYPE_DATA = 0x46,
|
||||
FIS_TYPE_BIST = 0x58,
|
||||
FIS_TYPE_PIO_SETUP = 0x5F,
|
||||
FIS_TYPE_DEV_BITS = 0xA1,
|
||||
} FIS_TYPE;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// Register FIS - Host to Device (H2D)
|
||||
typedef struct {
|
||||
uint8_t fis_type; // FIS_TYPE_REG_H2D
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 3;
|
||||
uint8_t c : 1; // 1 = Command, 0 = Control
|
||||
uint8_t command;
|
||||
uint8_t featurel;
|
||||
// DW1
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
// DW2
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t featureh;
|
||||
// DW3
|
||||
uint8_t countl, counth;
|
||||
uint8_t icc;
|
||||
uint8_t control;
|
||||
// DW4
|
||||
uint8_t rsv1[4];
|
||||
} FIS_REG_H2D;
|
||||
|
||||
// Register FIS - Device to Host (D2H)
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 2;
|
||||
uint8_t i : 1;
|
||||
uint8_t rsv1 : 1;
|
||||
uint8_t status;
|
||||
uint8_t error;
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t rsv2;
|
||||
uint8_t countl, counth;
|
||||
uint8_t rsv3[2];
|
||||
uint8_t rsv4[4];
|
||||
} FIS_REG_D2H;
|
||||
|
||||
// DMA Setup FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t d : 1;
|
||||
uint8_t i : 1;
|
||||
uint8_t a : 1;
|
||||
uint8_t rsved[2];
|
||||
uint64_t DMAbufferID;
|
||||
uint32_t rsvd;
|
||||
uint32_t DMAbufOffset;
|
||||
uint32_t TransferCount;
|
||||
uint32_t resvd;
|
||||
} FIS_DMA_SETUP;
|
||||
|
||||
// PIO Setup FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t d : 1;
|
||||
uint8_t i : 1;
|
||||
uint8_t rsv1 : 1;
|
||||
uint8_t status;
|
||||
uint8_t error;
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t rsv2;
|
||||
uint8_t countl, counth;
|
||||
uint8_t rsv3;
|
||||
uint8_t e_status;
|
||||
uint16_t tc;
|
||||
uint8_t rsv4[2];
|
||||
} FIS_PIO_SETUP;
|
||||
|
||||
// Set Device Bits FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 2;
|
||||
uint8_t i : 1;
|
||||
uint8_t n : 1;
|
||||
uint8_t statusl : 4;
|
||||
uint8_t rsv1 : 4;
|
||||
uint8_t statush : 4;
|
||||
uint8_t rsv2 : 4;
|
||||
uint8_t error;
|
||||
uint32_t protocol;
|
||||
} FIS_DEV_BITS;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HBA structures (volatile - MMIO)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Received FIS structure in host memory
|
||||
typedef volatile struct {
|
||||
FIS_DMA_SETUP dsfis; // 0x00 DMA Setup FIS
|
||||
uint8_t pad0[4];
|
||||
FIS_PIO_SETUP psfis; // 0x20 PIO Setup FIS
|
||||
uint8_t pad1[12];
|
||||
FIS_REG_D2H rfis; // 0x40 Register D2H FIS
|
||||
uint8_t pad2[4];
|
||||
FIS_DEV_BITS sdbfis; // 0x58 Set Device Bits FIS
|
||||
uint8_t ufis[64]; // 0x60 Unknown FIS
|
||||
uint8_t rsv[0x100-0xA0];
|
||||
} HBA_FIS;
|
||||
|
||||
// Command List header (one entry = one slot)
|
||||
typedef struct {
|
||||
// DW0
|
||||
uint8_t cfl : 5; // Command FIS length in DWORDs (2..16)
|
||||
uint8_t a : 1; // ATAPI
|
||||
uint8_t w : 1; // Write (H2D=1, D2H=0)
|
||||
uint8_t p : 1; // Prefetchable
|
||||
uint8_t r : 1; // Reset
|
||||
uint8_t b : 1; // BIST
|
||||
uint8_t c : 1; // Clear busy on R_OK
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t pmp : 4; // Port multiplier port
|
||||
uint16_t prdtl; // PRDT length in entries
|
||||
// DW1
|
||||
volatile uint32_t prdbc; // PRDT byte count transferred
|
||||
// DW2,3
|
||||
uint32_t ctba; // Command table base address (128-byte aligned)
|
||||
uint32_t ctbau; // Upper 32 bits
|
||||
// DW4-7
|
||||
uint32_t rsv1[4];
|
||||
} HBA_CMD_HEADER;
|
||||
|
||||
// Physical Region Descriptor Table entry
|
||||
typedef struct {
|
||||
uint32_t dba; // Data base address
|
||||
uint32_t dbau; // Upper 32 bits
|
||||
uint32_t rsv0;
|
||||
uint32_t dbc : 22; // Byte count (0-based, so actual = dbc+1)
|
||||
uint32_t rsv1 : 9;
|
||||
uint32_t i : 1; // Interrupt on completion
|
||||
} HBA_PRDT_ENTRY;
|
||||
|
||||
// Command Table
|
||||
typedef struct {
|
||||
uint8_t cfis[64]; // Command FIS (up to 64 bytes)
|
||||
uint8_t acmd[16]; // ATAPI command (12 or 16 bytes)
|
||||
uint8_t rsv[48]; // Reserved
|
||||
HBA_PRDT_ENTRY prdt_entry[1]; // PRDT entries (variable length)
|
||||
} HBA_CMD_TBL;
|
||||
#pragma pack(pop)
|
||||
|
||||
// Port registers
|
||||
typedef volatile struct {
|
||||
uint32_t clb; // 0x00 Command list base (1K-aligned)
|
||||
uint32_t clbu; // 0x04 Command list base upper
|
||||
uint32_t fb; // 0x08 FIS base (256-byte aligned)
|
||||
uint32_t fbu; // 0x0C FIS base upper
|
||||
uint32_t is; // 0x10 Interrupt status
|
||||
uint32_t ie; // 0x14 Interrupt enable
|
||||
uint32_t cmd; // 0x18 Command and status
|
||||
uint32_t rsv0; // 0x1C Reserved
|
||||
uint32_t tfd; // 0x20 Task file data
|
||||
uint32_t sig; // 0x24 Signature
|
||||
uint32_t ssts; // 0x28 SATA status
|
||||
uint32_t sctl; // 0x2C SATA control
|
||||
uint32_t serr; // 0x30 SATA error
|
||||
uint32_t sact; // 0x34 SATA active
|
||||
uint32_t ci; // 0x38 Command issue
|
||||
uint32_t sntf; // 0x3C SATA notification
|
||||
uint32_t fbs; // 0x40 FIS-based switch control
|
||||
uint32_t rsv1[11];
|
||||
uint32_t vendor[4];
|
||||
} HBA_PORT;
|
||||
|
||||
// Generic Host Control registers (starts at ABAR)
|
||||
typedef volatile struct {
|
||||
uint32_t cap; // 0x00 Host capability
|
||||
uint32_t ghc; // 0x04 Global host control
|
||||
uint32_t is; // 0x08 Interrupt status
|
||||
uint32_t pi; // 0x0C Ports implemented
|
||||
uint32_t vs; // 0x10 Version
|
||||
uint32_t ccc_ctl; // 0x14 CCC control
|
||||
uint32_t ccc_pts; // 0x18 CCC ports
|
||||
uint32_t em_loc; // 0x1C Enclosure mgmt location
|
||||
uint32_t em_ctl; // 0x20 Enclosure mgmt control
|
||||
uint32_t cap2; // 0x24 Extended capabilities
|
||||
uint32_t bohc; // 0x28 BIOS/OS handoff control
|
||||
uint8_t rsv[0xA0-0x2C];
|
||||
uint8_t vendor[0x100-0xA0];
|
||||
HBA_PORT ports[32]; // 0x100 Port control registers
|
||||
} HBA_MEM;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// PCI
|
||||
#define PCI_AHCI_CLASS 0x01
|
||||
#define PCI_AHCI_SUBCLASS 0x06
|
||||
#define PCI_CMD_REG 0x04
|
||||
#define PCI_BAR5_REG 0x24
|
||||
#define PCI_CMD_BUSMASTER (1 << 2)
|
||||
#define PCI_CMD_MEMSPACE (1 << 1)
|
||||
|
||||
// GHC register bits
|
||||
#define GHC_AE (1u << 31) // AHCI Enable
|
||||
#define GHC_IE (1u << 1) // Interrupt Enable
|
||||
#define GHC_HR (1u << 0) // HBA Reset
|
||||
|
||||
// BOHC register bits
|
||||
#define BOHC_BOS (1u << 0) // BIOS-owned semaphore
|
||||
#define BOHC_OOS (1u << 1) // OS-owned semaphore
|
||||
#define BOHC_BB (1u << 4) // BIOS Busy
|
||||
|
||||
// CAP2 bits
|
||||
#define CAP2_BOH (1u << 0) // BIOS/OS handoff capable
|
||||
|
||||
// Port CMD register bits
|
||||
#define PORT_CMD_ST (1u << 0) // Start
|
||||
#define PORT_CMD_FRE (1u << 4) // FIS Receive Enable
|
||||
#define PORT_CMD_FR (1u << 14) // FIS Receive Running
|
||||
#define PORT_CMD_CR (1u << 15) // Command List Running
|
||||
#define PORT_CMD_POD (1u << 2) // Power On Device
|
||||
#define PORT_CMD_SUD (1u << 1) // Spin-Up Device
|
||||
#define PORT_CMD_ICC_ACTIVE (0x1u << 28)
|
||||
|
||||
// Port IS register bits (interrupt status)
|
||||
#define PORT_IS_TFES (1u << 30) // Task File Error Status
|
||||
#define PORT_IS_HBFS (1u << 29) // Host Bus Fatal Error
|
||||
#define PORT_IS_HBDS (1u << 28) // Host Bus Data Error
|
||||
#define PORT_IS_IFS (1u << 27) // Interface Fatal Error
|
||||
|
||||
// Port SSTS register
|
||||
#define PORT_DET_PRESENT 0x3
|
||||
#define PORT_IPM_ACTIVE 0x1
|
||||
|
||||
// Port signatures
|
||||
#define SATA_SIG_ATA 0x00000101
|
||||
#define SATA_SIG_ATAPI 0xEB140101
|
||||
#define SATA_SIG_SEMB 0xC33C0101
|
||||
#define SATA_SIG_PM 0x96690101
|
||||
|
||||
// ATA commands
|
||||
#define ATA_CMD_READ_DMA_EX 0x25
|
||||
#define ATA_CMD_WRITE_DMA_EX 0x35
|
||||
#define ATA_CMD_IDENTIFY 0xEC
|
||||
#define ATA_CMD_FLUSH_EXT 0xEA
|
||||
|
||||
// ATA status bits
|
||||
#define ATA_SR_BUSY 0x80
|
||||
#define ATA_SR_DRQ 0x08
|
||||
#define ATA_SR_ERR 0x01
|
||||
|
||||
// IDENTIFY word offsets
|
||||
#define ATA_IDENT_W83 83 // bit 10 = 48-bit LBA supported
|
||||
#define ATA_IDENT_W100 100 // 48-bit LBA sector count (4 words)
|
||||
#define ATA_IDENT_W60 60 // 28-bit LBA sector count (2 words)
|
||||
|
||||
// Layout constants (per port, single contiguous physical allocation)
|
||||
// Offset 0: Command List 1024 bytes (32 × 32-byte headers)
|
||||
// Offset 1024: Received FIS 256 bytes
|
||||
// Offset 2048: Command Tables 32 × 256 bytes = 8192 bytes
|
||||
// Total: 10240 bytes → 3 pages (12288 bytes)
|
||||
#define PORT_PAGES_STRUCT 3
|
||||
#define PORT_OFF_CMDLIST 0
|
||||
#define PORT_OFF_FIS 1024
|
||||
#define PORT_OFF_CMDTBLS 2048
|
||||
#define PORT_CMDTBL_SIZE 256 // 64 CFIS + 16 ACMD + 48 rsv + 8×16 PRDT
|
||||
#define PORT_PRDT_COUNT 8 // PRDT entries per command table
|
||||
|
||||
// Bounce buffer: 128 sectors × 512 bytes = 64 KB = 16 pages
|
||||
#define BOUNCE_SECTORS 128
|
||||
#define BOUNCE_BYTES (BOUNCE_SECTORS * 512)
|
||||
#define BOUNCE_PAGES (BOUNCE_BYTES / 4096)
|
||||
|
||||
// Polling timeout (spin iterations)
|
||||
#define POLL_TIMEOUT 1000000
|
||||
|
||||
#define MAX_AHCI_DRIVES 8
|
||||
#define NUM_CMD_SLOTS 32
|
||||
|
||||
// Map MMIO physical range into the physical window as uncacheable.
|
||||
// Must be called before any pointer dereference via PhysToVirt().
|
||||
// AHCI MMIO layout: 0x100 GHC + 32*0x80 ports = 0x1100 bytes → 2 pages minimum.
|
||||
// We map 8 pages (32KB) without probing the BAR size.
|
||||
#define AHCI_MMIO_PAGES 8
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driver-global state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const HAL_API *g_hal;
|
||||
static HBA_MEM *g_hba; // HBA MMIO base (set during init)
|
||||
static uint8_t g_irq; // PCI IRQ line for this HBA
|
||||
static DRIVER_OBJECT g_driver; // AHCI disk driver object
|
||||
|
||||
// Per-drive context
|
||||
typedef struct {
|
||||
HBA_PORT *port; // MMIO port registers (virtual)
|
||||
uint64_t sectorCount; // Total LBA sectors on device
|
||||
uint8_t portNo; // Physical port index (for ISR dispatch)
|
||||
|
||||
// Structures block (command list + FIS + cmd tables)
|
||||
uint64_t structPhys; // Physical base
|
||||
void *structVirt; // Virtual base
|
||||
|
||||
// Bounce buffer for DMA data
|
||||
uint64_t bouncePhys;
|
||||
void *bounceVirt;
|
||||
|
||||
// Signaled by the completion DPC when the port raises an interrupt.
|
||||
// Auto-reset: one Wait consumes one completion.
|
||||
EVENT *completionEvent;
|
||||
|
||||
// Last IS snapshot captured by the ISR (for error checking post-wait).
|
||||
volatile uint32_t lastIs;
|
||||
|
||||
// Bottom-half used to wake the worker outside hard IRQ/DIRQL context.
|
||||
KDPC completionDpc;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// IRP queue - async dispatch
|
||||
// -----------------------------------------------------------------------
|
||||
// AhciWorker blocks here waiting for work. AHCI dispatch enqueues an IRP
|
||||
// and signals the semaphore to wake the worker.
|
||||
IRP_QUEUE irpQueue;
|
||||
THREAD *workerThread;
|
||||
DEVICE_OBJECT *deviceObject;
|
||||
|
||||
// NCQ TODO: for multi-slot NCQ, add:
|
||||
// uint32_t slotMask; // bitmask of in-flight slots
|
||||
// IRP *slotIrp[NUM_CMD_SLOTS]; // slot → pending IRP
|
||||
// uint64_t slotBouncePhys[NUM_CMD_SLOTS];
|
||||
// void *slotBounceVirt[NUM_CMD_SLOTS];
|
||||
// volatile uint32_t prevSact; // previous port->sact snapshot
|
||||
} AHCI_DRIVE;
|
||||
|
||||
static AHCI_DRIVE g_drives[MAX_AHCI_DRIVES];
|
||||
static int g_driveCount;
|
||||
const HAL_API *g_hal;
|
||||
HBA_MEM *g_hba;
|
||||
uint8_t g_irq;
|
||||
DRIVER_OBJECT g_driver;
|
||||
AHCI_DRIVE g_drives[MAX_AHCI_DRIVES];
|
||||
int g_driveCount;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
static KSTATUS ahci_map_mmio(uint64_t physBase) {
|
||||
for (int i = 0; i < AHCI_MMIO_PAGES; i++) {
|
||||
uint64_t phys = physBase + (uint64_t)i * 4096;
|
||||
uintptr_t virt = (uintptr_t)g_hal->PhysToVirt(phys);
|
||||
uint64_t flags = PAGE_PRESENT | PAGE_WRITE | PAGE_NOCACHE;
|
||||
KSTATUS st = MmMapPage(virt, phys, flags);
|
||||
// Ignore STATUS_ALREADY_EXISTS - a prior mapping with different flags
|
||||
// is possible for the first page if the bootloader was overly generous.
|
||||
// Use MmSetPageProtect to force NOCACHE in that case.
|
||||
if (st == STATUS_ALREADY_EXISTS) {
|
||||
st = MmSetPageProtect(virt, 4096, flags);
|
||||
}
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: MmMapPage failed for page %d (phys=0x%llx, st=0x%x)\n",
|
||||
i, (unsigned long long)phys, st);
|
||||
return st;
|
||||
}
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Accessors for port structure memory at known offsets from structVirt/Phys
|
||||
static inline HBA_CMD_HEADER *cmd_list(AHCI_DRIVE *drv) {
|
||||
@@ -460,29 +42,7 @@ static inline uint64_t cmd_tbl_phys(AHCI_DRIVE *drv, int slot) {
|
||||
// Port engine start / stop (polling safe)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void port_stop(HBA_PORT *port) {
|
||||
// Clear ST (bit 0)
|
||||
port->cmd &= ~PORT_CMD_ST;
|
||||
// Wait for CR (bit 15) to clear (command list DMA stops)
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
}
|
||||
// Clear FRE (bit 4)
|
||||
port->cmd &= ~PORT_CMD_FRE;
|
||||
// Wait for FR (bit 14) to clear
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_FR)) break;
|
||||
}
|
||||
}
|
||||
|
||||
static void port_start(HBA_PORT *port) {
|
||||
// Wait until CR is clear before setting ST
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
}
|
||||
port->cmd |= PORT_CMD_FRE;
|
||||
port->cmd |= PORT_CMD_ST;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Port memory initialisation
|
||||
@@ -878,100 +438,16 @@ static void AhciWorker(uintptr_t arg) {
|
||||
// AHCI controller initialisation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static KSTATUS hba_reset(HBA_MEM *hba) {
|
||||
// BIOS/OS handoff (spec §10.6.3)
|
||||
if (hba->cap2 & CAP2_BOH) {
|
||||
hba->bohc |= BOHC_OOS;
|
||||
// Wait for BIOS to clear BOS
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(hba->bohc & BOHC_BOS)) break;
|
||||
}
|
||||
// Give the BIOS another moment to clear Busy
|
||||
for (int i = 0; i < 800000; i++) {
|
||||
if (!(hba->bohc & BOHC_BB)) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable AHCI mode before reset so the reset actually takes effect
|
||||
hba->ghc |= GHC_AE;
|
||||
|
||||
// HBA reset
|
||||
hba->ghc |= GHC_HR;
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(hba->ghc & GHC_HR)) break;
|
||||
}
|
||||
if (hba->ghc & GHC_HR) {
|
||||
DbgPrint("AHCI: HBA reset timed out\n");
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
// Re-enable AHCI mode (reset cleared it), then enable global interrupts.
|
||||
// GHC.IE lets the HBA assert its PCI interrupt line when a port fires.
|
||||
hba->ghc |= GHC_AE | GHC_IE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// Before memory init: only check physical link. Signature is not yet valid.
|
||||
static bool port_link_up(HBA_PORT *port) {
|
||||
uint32_t det = port->ssts & 0x0F;
|
||||
uint32_t ipm = (port->ssts >> 8) & 0x0F;
|
||||
return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE);
|
||||
}
|
||||
|
||||
// After COMRESET + FIS receive: check signature for SATA disk.
|
||||
static bool port_is_sata_disk(HBA_PORT *port) {
|
||||
return port->sig == SATA_SIG_ATA;
|
||||
}
|
||||
|
||||
static void ahci_delay_ms(uint32_t ms) {
|
||||
for (uint32_t i = 0; i < ms * 1000; i++)
|
||||
__outbyte(0x80, 0);
|
||||
}
|
||||
|
||||
// Issue a COMRESET on the port, wait for link re-establishment and D2H FIS.
|
||||
// FRE must already be set (port_init_memory does this) so the HBA can
|
||||
// capture the signature FIS the device sends after the reset.
|
||||
static KSTATUS port_comreset(AHCI_DRIVE *drv) {
|
||||
HBA_PORT *port = drv->port;
|
||||
|
||||
port->cmd &= ~PORT_CMD_ST;
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++)
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
|
||||
// Assert COMRESET: SControl.DET = 1, hold for ≥ 1ms
|
||||
port->sctl = (port->sctl & ~0x0Fu) | 0x1u;
|
||||
ahci_delay_ms(1);
|
||||
|
||||
// Deassert: DET = 0
|
||||
port->sctl = port->sctl & ~0x0Fu;
|
||||
|
||||
// Poll for DET = 3, up to 1 second
|
||||
bool link_ok = false;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if ((port->ssts & 0x0Fu) == PORT_DET_PRESENT) { link_ok = true; break; }
|
||||
ahci_delay_ms(1);
|
||||
}
|
||||
if (!link_ok) {
|
||||
DbgPrint("AHCI: COMRESET: link did not come back (ssts=0x%x)\n", port->ssts);
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
port->serr = (uint32_t)-1;
|
||||
port->is = (uint32_t)-1;
|
||||
port->cmd |= PORT_CMD_ST;
|
||||
|
||||
// Wait up to 500ms for a valid signature (D2H FIS from device)
|
||||
for (int i = 0; i < 500; i++) {
|
||||
uint32_t sig = port->sig;
|
||||
if (sig != 0xFFFFFFFFu && sig != 0x00000000u) break;
|
||||
ahci_delay_ms(1);
|
||||
}
|
||||
|
||||
DbgPrint("AHCI: port COMRESET done: sig=0x%08x ssts=0x%08x\n",
|
||||
port->sig, port->ssts);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// PORT_IE bits we care about:
|
||||
// DHRS (bit 0) - D2H Register FIS received - normal completion
|
||||
@@ -1032,7 +508,7 @@ KSTATUS AhciInitialize(const HAL_API *hal) {
|
||||
if (!KSUCCESS(st)) return st;
|
||||
|
||||
g_hba = (HBA_MEM *)hal->PhysToVirt(abarPhys);
|
||||
DbgPrint("AHCI: ABAR phys=0x%llx version=0x%08x\n",
|
||||
DbgPrint("AHCI: ABAR phys=0x%016llX version=0x%08x\n",
|
||||
(unsigned long long)abarPhys, g_hba->vs);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
// ahci.h - shared AHCI driver internals.
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/ddi/io_driver.h>
|
||||
#include <kernel/mm_phys.h>
|
||||
#include <kernel/mm_virt.h>
|
||||
#include <kernel/ke_debug.h>
|
||||
#include <kernel/ke_event.h>
|
||||
#include <kernel/ke_semaphore.h>
|
||||
#include <kernel/ke_thread.h>
|
||||
#include <kernel/ke_irq.h>
|
||||
#include <kernel/ke_irp.h>
|
||||
#include <kernel/ke_dpc.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef enum {
|
||||
FIS_TYPE_REG_H2D = 0x27,
|
||||
FIS_TYPE_REG_D2H = 0x34,
|
||||
FIS_TYPE_DMA_ACT = 0x39,
|
||||
FIS_TYPE_DMA_SETUP = 0x41,
|
||||
FIS_TYPE_DATA = 0x46,
|
||||
FIS_TYPE_BIST = 0x58,
|
||||
FIS_TYPE_PIO_SETUP = 0x5F,
|
||||
FIS_TYPE_DEV_BITS = 0xA1,
|
||||
} FIS_TYPE;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// Register FIS - Host to Device (H2D)
|
||||
typedef struct {
|
||||
uint8_t fis_type; // FIS_TYPE_REG_H2D
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 3;
|
||||
uint8_t c : 1; // 1 = Command, 0 = Control
|
||||
uint8_t command;
|
||||
uint8_t featurel;
|
||||
// DW1
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
// DW2
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t featureh;
|
||||
// DW3
|
||||
uint8_t countl, counth;
|
||||
uint8_t icc;
|
||||
uint8_t control;
|
||||
// DW4
|
||||
uint8_t rsv1[4];
|
||||
} FIS_REG_H2D;
|
||||
|
||||
// Register FIS - Device to Host (D2H)
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 2;
|
||||
uint8_t i : 1;
|
||||
uint8_t rsv1 : 1;
|
||||
uint8_t status;
|
||||
uint8_t error;
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t rsv2;
|
||||
uint8_t countl, counth;
|
||||
uint8_t rsv3[2];
|
||||
uint8_t rsv4[4];
|
||||
} FIS_REG_D2H;
|
||||
|
||||
// DMA Setup FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t d : 1;
|
||||
uint8_t i : 1;
|
||||
uint8_t a : 1;
|
||||
uint8_t rsved[2];
|
||||
uint64_t DMAbufferID;
|
||||
uint32_t rsvd;
|
||||
uint32_t DMAbufOffset;
|
||||
uint32_t TransferCount;
|
||||
uint32_t resvd;
|
||||
} FIS_DMA_SETUP;
|
||||
|
||||
// PIO Setup FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t d : 1;
|
||||
uint8_t i : 1;
|
||||
uint8_t rsv1 : 1;
|
||||
uint8_t status;
|
||||
uint8_t error;
|
||||
uint8_t lba0, lba1, lba2;
|
||||
uint8_t device;
|
||||
uint8_t lba3, lba4, lba5;
|
||||
uint8_t rsv2;
|
||||
uint8_t countl, counth;
|
||||
uint8_t rsv3;
|
||||
uint8_t e_status;
|
||||
uint16_t tc;
|
||||
uint8_t rsv4[2];
|
||||
} FIS_PIO_SETUP;
|
||||
|
||||
// Set Device Bits FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type;
|
||||
uint8_t pmport : 4;
|
||||
uint8_t rsv0 : 2;
|
||||
uint8_t i : 1;
|
||||
uint8_t n : 1;
|
||||
uint8_t statusl : 4;
|
||||
uint8_t rsv1 : 4;
|
||||
uint8_t statush : 4;
|
||||
uint8_t rsv2 : 4;
|
||||
uint8_t error;
|
||||
uint32_t protocol;
|
||||
} FIS_DEV_BITS;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HBA structures (volatile - MMIO)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Received FIS structure in host memory
|
||||
typedef volatile struct {
|
||||
FIS_DMA_SETUP dsfis; // 0x00 DMA Setup FIS
|
||||
uint8_t pad0[4];
|
||||
FIS_PIO_SETUP psfis; // 0x20 PIO Setup FIS
|
||||
uint8_t pad1[12];
|
||||
FIS_REG_D2H rfis; // 0x40 Register D2H FIS
|
||||
uint8_t pad2[4];
|
||||
FIS_DEV_BITS sdbfis; // 0x58 Set Device Bits FIS
|
||||
uint8_t ufis[64]; // 0x60 Unknown FIS
|
||||
uint8_t rsv[0x100-0xA0];
|
||||
} HBA_FIS;
|
||||
|
||||
// Command List header (one entry = one slot)
|
||||
typedef struct {
|
||||
// DW0
|
||||
uint8_t cfl : 5; // Command FIS length in DWORDs (2..16)
|
||||
uint8_t a : 1; // ATAPI
|
||||
uint8_t w : 1; // Write (H2D=1, D2H=0)
|
||||
uint8_t p : 1; // Prefetchable
|
||||
uint8_t r : 1; // Reset
|
||||
uint8_t b : 1; // BIST
|
||||
uint8_t c : 1; // Clear busy on R_OK
|
||||
uint8_t rsv0 : 1;
|
||||
uint8_t pmp : 4; // Port multiplier port
|
||||
uint16_t prdtl; // PRDT length in entries
|
||||
// DW1
|
||||
volatile uint32_t prdbc; // PRDT byte count transferred
|
||||
// DW2,3
|
||||
uint32_t ctba; // Command table base address (128-byte aligned)
|
||||
uint32_t ctbau; // Upper 32 bits
|
||||
// DW4-7
|
||||
uint32_t rsv1[4];
|
||||
} HBA_CMD_HEADER;
|
||||
|
||||
// Physical Region Descriptor Table entry
|
||||
typedef struct {
|
||||
uint32_t dba; // Data base address
|
||||
uint32_t dbau; // Upper 32 bits
|
||||
uint32_t rsv0;
|
||||
uint32_t dbc : 22; // Byte count (0-based, so actual = dbc+1)
|
||||
uint32_t rsv1 : 9;
|
||||
uint32_t i : 1; // Interrupt on completion
|
||||
} HBA_PRDT_ENTRY;
|
||||
|
||||
// Command Table
|
||||
typedef struct {
|
||||
uint8_t cfis[64]; // Command FIS (up to 64 bytes)
|
||||
uint8_t acmd[16]; // ATAPI command (12 or 16 bytes)
|
||||
uint8_t rsv[48]; // Reserved
|
||||
HBA_PRDT_ENTRY prdt_entry[1]; // PRDT entries (variable length)
|
||||
} HBA_CMD_TBL;
|
||||
#pragma pack(pop)
|
||||
|
||||
// Port registers
|
||||
typedef volatile struct {
|
||||
uint32_t clb; // 0x00 Command list base (1K-aligned)
|
||||
uint32_t clbu; // 0x04 Command list base upper
|
||||
uint32_t fb; // 0x08 FIS base (256-byte aligned)
|
||||
uint32_t fbu; // 0x0C FIS base upper
|
||||
uint32_t is; // 0x10 Interrupt status
|
||||
uint32_t ie; // 0x14 Interrupt enable
|
||||
uint32_t cmd; // 0x18 Command and status
|
||||
uint32_t rsv0; // 0x1C Reserved
|
||||
uint32_t tfd; // 0x20 Task file data
|
||||
uint32_t sig; // 0x24 Signature
|
||||
uint32_t ssts; // 0x28 SATA status
|
||||
uint32_t sctl; // 0x2C SATA control
|
||||
uint32_t serr; // 0x30 SATA error
|
||||
uint32_t sact; // 0x34 SATA active
|
||||
uint32_t ci; // 0x38 Command issue
|
||||
uint32_t sntf; // 0x3C SATA notification
|
||||
uint32_t fbs; // 0x40 FIS-based switch control
|
||||
uint32_t rsv1[11];
|
||||
uint32_t vendor[4];
|
||||
} HBA_PORT;
|
||||
|
||||
// Generic Host Control registers (starts at ABAR)
|
||||
typedef volatile struct {
|
||||
uint32_t cap; // 0x00 Host capability
|
||||
uint32_t ghc; // 0x04 Global host control
|
||||
uint32_t is; // 0x08 Interrupt status
|
||||
uint32_t pi; // 0x0C Ports implemented
|
||||
uint32_t vs; // 0x10 Version
|
||||
uint32_t ccc_ctl; // 0x14 CCC control
|
||||
uint32_t ccc_pts; // 0x18 CCC ports
|
||||
uint32_t em_loc; // 0x1C Enclosure mgmt location
|
||||
uint32_t em_ctl; // 0x20 Enclosure mgmt control
|
||||
uint32_t cap2; // 0x24 Extended capabilities
|
||||
uint32_t bohc; // 0x28 BIOS/OS handoff control
|
||||
uint8_t rsv[0xA0-0x2C];
|
||||
uint8_t vendor[0x100-0xA0];
|
||||
HBA_PORT ports[32]; // 0x100 Port control registers
|
||||
} HBA_MEM;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// PCI
|
||||
#define PCI_AHCI_CLASS 0x01
|
||||
#define PCI_AHCI_SUBCLASS 0x06
|
||||
#define PCI_CMD_REG 0x04
|
||||
#define PCI_BAR5_REG 0x24
|
||||
#define PCI_CMD_BUSMASTER (1 << 2)
|
||||
#define PCI_CMD_MEMSPACE (1 << 1)
|
||||
|
||||
// GHC register bits
|
||||
#define GHC_AE (1u << 31) // AHCI Enable
|
||||
#define GHC_IE (1u << 1) // Interrupt Enable
|
||||
#define GHC_HR (1u << 0) // HBA Reset
|
||||
|
||||
// BOHC register bits
|
||||
#define BOHC_BOS (1u << 0) // BIOS-owned semaphore
|
||||
#define BOHC_OOS (1u << 1) // OS-owned semaphore
|
||||
#define BOHC_BB (1u << 4) // BIOS Busy
|
||||
|
||||
// CAP2 bits
|
||||
#define CAP2_BOH (1u << 0) // BIOS/OS handoff capable
|
||||
|
||||
// Port CMD register bits
|
||||
#define PORT_CMD_ST (1u << 0) // Start
|
||||
#define PORT_CMD_FRE (1u << 4) // FIS Receive Enable
|
||||
#define PORT_CMD_FR (1u << 14) // FIS Receive Running
|
||||
#define PORT_CMD_CR (1u << 15) // Command List Running
|
||||
#define PORT_CMD_POD (1u << 2) // Power On Device
|
||||
#define PORT_CMD_SUD (1u << 1) // Spin-Up Device
|
||||
#define PORT_CMD_ICC_ACTIVE (0x1u << 28)
|
||||
|
||||
// Port IS register bits (interrupt status)
|
||||
#define PORT_IS_TFES (1u << 30) // Task File Error Status
|
||||
#define PORT_IS_HBFS (1u << 29) // Host Bus Fatal Error
|
||||
#define PORT_IS_HBDS (1u << 28) // Host Bus Data Error
|
||||
#define PORT_IS_IFS (1u << 27) // Interface Fatal Error
|
||||
|
||||
// Port SSTS register
|
||||
#define PORT_DET_PRESENT 0x3
|
||||
#define PORT_IPM_ACTIVE 0x1
|
||||
|
||||
// Port signatures
|
||||
#define SATA_SIG_ATA 0x00000101
|
||||
#define SATA_SIG_ATAPI 0xEB140101
|
||||
#define SATA_SIG_SEMB 0xC33C0101
|
||||
#define SATA_SIG_PM 0x96690101
|
||||
|
||||
// ATA commands
|
||||
#define ATA_CMD_READ_DMA_EX 0x25
|
||||
#define ATA_CMD_WRITE_DMA_EX 0x35
|
||||
#define ATA_CMD_IDENTIFY 0xEC
|
||||
#define ATA_CMD_FLUSH_EXT 0xEA
|
||||
|
||||
// ATA status bits
|
||||
#define ATA_SR_BUSY 0x80
|
||||
#define ATA_SR_DRQ 0x08
|
||||
#define ATA_SR_ERR 0x01
|
||||
|
||||
// IDENTIFY word offsets
|
||||
#define ATA_IDENT_W83 83 // bit 10 = 48-bit LBA supported
|
||||
#define ATA_IDENT_W100 100 // 48-bit LBA sector count (4 words)
|
||||
#define ATA_IDENT_W60 60 // 28-bit LBA sector count (2 words)
|
||||
|
||||
// Layout constants (per port, single contiguous physical allocation)
|
||||
// Offset 0: Command List 1024 bytes (32 × 32-byte headers)
|
||||
// Offset 1024: Received FIS 256 bytes
|
||||
// Offset 2048: Command Tables 32 × 256 bytes = 8192 bytes
|
||||
// Total: 10240 bytes → 3 pages (12288 bytes)
|
||||
#define PORT_PAGES_STRUCT 3
|
||||
#define PORT_OFF_CMDLIST 0
|
||||
#define PORT_OFF_FIS 1024
|
||||
#define PORT_OFF_CMDTBLS 2048
|
||||
#define PORT_CMDTBL_SIZE 256 // 64 CFIS + 16 ACMD + 48 rsv + 8×16 PRDT
|
||||
#define PORT_PRDT_COUNT 8 // PRDT entries per command table
|
||||
|
||||
// Bounce buffer: 128 sectors × 512 bytes = 64 KB = 16 pages
|
||||
#define BOUNCE_SECTORS 128
|
||||
#define BOUNCE_BYTES (BOUNCE_SECTORS * 512)
|
||||
#define BOUNCE_PAGES (BOUNCE_BYTES / 4096)
|
||||
|
||||
// Polling timeout (spin iterations)
|
||||
#define POLL_TIMEOUT 1000000
|
||||
|
||||
#define MAX_AHCI_DRIVES 8
|
||||
#define NUM_CMD_SLOTS 32
|
||||
|
||||
// Map MMIO physical range into the physical window as uncacheable.
|
||||
// Must be called before any pointer dereference via PhysToVirt().
|
||||
// AHCI MMIO layout: 0x100 GHC + 32*0x80 ports = 0x1100 bytes → 2 pages minimum.
|
||||
// We map 8 pages (32KB) without probing the BAR size.
|
||||
#define AHCI_MMIO_PAGES 8
|
||||
|
||||
// Per-drive context
|
||||
typedef struct {
|
||||
HBA_PORT *port; // MMIO port registers (virtual)
|
||||
uint64_t sectorCount; // Total LBA sectors on device
|
||||
uint8_t portNo; // Physical port index (for ISR dispatch)
|
||||
|
||||
// Structures block (command list + FIS + cmd tables)
|
||||
uint64_t structPhys; // Physical base
|
||||
void* structVirt; // Virtual base
|
||||
|
||||
// Bounce buffer for DMA data
|
||||
uint64_t bouncePhys;
|
||||
void* bounceVirt;
|
||||
|
||||
// Signaled by the completion DPC when the port raises an interrupt.
|
||||
// Auto-reset: one Wait consumes one completion.
|
||||
EVENT *completionEvent;
|
||||
|
||||
// Last IS snapshot captured by the ISR (for error checking post-wait).
|
||||
volatile uint32_t lastIs;
|
||||
|
||||
// Bottom-half used to wake the worker outside hard IRQ/DIRQL context.
|
||||
KDPC completionDpc;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// IRP queue - async dispatch
|
||||
// -----------------------------------------------------------------------
|
||||
// AhciWorker blocks here waiting for work. AHCI dispatch enqueues an IRP
|
||||
// and signals the semaphore to wake the worker.
|
||||
IRP_QUEUE irpQueue;
|
||||
THREAD* workerThread;
|
||||
DEVICE_OBJECT* deviceObject;
|
||||
|
||||
// NCQ TODO: for multi-slot NCQ, add:
|
||||
// uint32_t slotMask; // bitmask of in-flight slots
|
||||
// IRP* slotIrp[NUM_CMD_SLOTS]; // slot → pending IRP
|
||||
// uint64_t slotBouncePhys[NUM_CMD_SLOTS];
|
||||
// void* slotBounceVirt[NUM_CMD_SLOTS];
|
||||
// volatile uint32_t prevSact; // previous port->sact snapshot
|
||||
} AHCI_DRIVE;
|
||||
|
||||
|
||||
extern const HAL_API *g_hal;
|
||||
extern HBA_MEM *g_hba;
|
||||
extern uint8_t g_irq;
|
||||
extern DRIVER_OBJECT g_driver;
|
||||
extern AHCI_DRIVE g_drives[MAX_AHCI_DRIVES];
|
||||
extern int g_driveCount;
|
||||
|
||||
KSTATUS ahci_map_mmio(uint64_t physBase);
|
||||
void port_stop(HBA_PORT *port);
|
||||
void port_start(HBA_PORT *port);
|
||||
KSTATUS hba_reset(HBA_MEM *hba);
|
||||
bool port_link_up(HBA_PORT *port);
|
||||
bool port_is_sata_disk(HBA_PORT *port);
|
||||
void ahci_delay_ms(uint32_t ms);
|
||||
KSTATUS port_comreset(AHCI_DRIVE *drv);
|
||||
@@ -0,0 +1,143 @@
|
||||
// ahci_port.c - AHCI MMIO mapping, HBA reset, and port link bring-up helpers.
|
||||
#include "ahci.h"
|
||||
|
||||
KSTATUS ahci_map_mmio(uint64_t physBase) {
|
||||
for (int i = 0; i < AHCI_MMIO_PAGES; i++) {
|
||||
uint64_t phys = physBase + (uint64_t)i * 4096;
|
||||
uintptr_t virt = (uintptr_t)g_hal->PhysToVirt(phys);
|
||||
uint64_t flags = PAGE_PRESENT | PAGE_WRITE | PAGE_NOCACHE;
|
||||
KSTATUS st = MmMapPage(virt, phys, flags);
|
||||
// Ignore STATUS_ALREADY_EXISTS - a prior mapping with different flags
|
||||
// is possible for the first page if the bootloader was overly generous.
|
||||
// Use MmSetPageProtect to force NOCACHE in that case.
|
||||
if (st == STATUS_ALREADY_EXISTS) {
|
||||
st = MmSetPageProtect(virt, 4096, flags);
|
||||
}
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("AHCI: MmMapPage failed for page %d (phys=0x%016llX, st=0x%x)\n",
|
||||
i, (unsigned long long)phys, st);
|
||||
return st;
|
||||
}
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void port_stop(HBA_PORT *port) {
|
||||
// Clear ST (bit 0)
|
||||
port->cmd &= ~PORT_CMD_ST;
|
||||
// Wait for CR (bit 15) to clear (command list DMA stops)
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
}
|
||||
// Clear FRE (bit 4)
|
||||
port->cmd &= ~PORT_CMD_FRE;
|
||||
// Wait for FR (bit 14) to clear
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_FR)) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void port_start(HBA_PORT *port) {
|
||||
// Wait until CR is clear before setting ST
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
}
|
||||
port->cmd |= PORT_CMD_FRE;
|
||||
port->cmd |= PORT_CMD_ST;
|
||||
}
|
||||
|
||||
|
||||
KSTATUS hba_reset(HBA_MEM *hba) {
|
||||
// BIOS/OS handoff (spec §10.6.3)
|
||||
if (hba->cap2 & CAP2_BOH) {
|
||||
hba->bohc |= BOHC_OOS;
|
||||
// Wait for BIOS to clear BOS
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(hba->bohc & BOHC_BOS)) break;
|
||||
}
|
||||
// Give the BIOS another moment to clear Busy
|
||||
for (int i = 0; i < 800000; i++) {
|
||||
if (!(hba->bohc & BOHC_BB)) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable AHCI mode before reset so the reset actually takes effect
|
||||
hba->ghc |= GHC_AE;
|
||||
|
||||
// HBA reset
|
||||
hba->ghc |= GHC_HR;
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++) {
|
||||
if (!(hba->ghc & GHC_HR)) break;
|
||||
}
|
||||
if (hba->ghc & GHC_HR) {
|
||||
DbgPrint("AHCI: HBA reset timed out\n");
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
// Re-enable AHCI mode (reset cleared it), then enable global interrupts.
|
||||
// GHC.IE lets the HBA assert its PCI interrupt line when a port fires.
|
||||
hba->ghc |= GHC_AE | GHC_IE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
bool port_link_up(HBA_PORT *port) {
|
||||
uint32_t det = port->ssts & 0x0F;
|
||||
uint32_t ipm = (port->ssts >> 8) & 0x0F;
|
||||
return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
bool port_is_sata_disk(HBA_PORT *port) {
|
||||
return port->sig == SATA_SIG_ATA;
|
||||
}
|
||||
|
||||
|
||||
void ahci_delay_ms(uint32_t ms) {
|
||||
for (uint32_t i = 0; i < ms * 1000; i++)
|
||||
__outbyte(0x80, 0);
|
||||
}
|
||||
|
||||
|
||||
KSTATUS port_comreset(AHCI_DRIVE *drv) {
|
||||
HBA_PORT *port = drv->port;
|
||||
|
||||
port->cmd &= ~PORT_CMD_ST;
|
||||
for (int i = 0; i < POLL_TIMEOUT; i++)
|
||||
if (!(port->cmd & PORT_CMD_CR)) break;
|
||||
|
||||
// Assert COMRESET: SControl.DET = 1, hold for ≥ 1ms
|
||||
port->sctl = (port->sctl & ~0x0Fu) | 0x1u;
|
||||
ahci_delay_ms(1);
|
||||
|
||||
// Deassert: DET = 0
|
||||
port->sctl = port->sctl & ~0x0Fu;
|
||||
|
||||
// Poll for DET = 3, up to 1 second
|
||||
bool link_ok = false;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if ((port->ssts & 0x0Fu) == PORT_DET_PRESENT) { link_ok = true; break; }
|
||||
ahci_delay_ms(1);
|
||||
}
|
||||
if (!link_ok) {
|
||||
DbgPrint("AHCI: COMRESET: link did not come back (ssts=0x%x)\n", port->ssts);
|
||||
return STATUS_TIMEOUT;
|
||||
}
|
||||
|
||||
port->serr = (uint32_t)-1;
|
||||
port->is = (uint32_t)-1;
|
||||
port->cmd |= PORT_CMD_ST;
|
||||
|
||||
// Wait up to 500ms for a valid signature (D2H FIS from device)
|
||||
for (int i = 0; i < 500; i++) {
|
||||
uint32_t sig = port->sig;
|
||||
if (sig != 0xFFFFFFFFu && sig != 0x00000000u) break;
|
||||
ahci_delay_ms(1);
|
||||
}
|
||||
|
||||
DbgPrint("AHCI: port COMRESET done: sig=0x%08x ssts=0x%08x\n",
|
||||
port->sig, port->ssts);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
+19
-207
@@ -1,130 +1,26 @@
|
||||
// ide.c — IDE/ATA driver: PIO (sync fallback) + Bus Master DMA (async)
|
||||
//
|
||||
// Architecture
|
||||
// ------------
|
||||
// The dispatch path mirrors AHCI:
|
||||
// IoCallDriver → IdeDispatchReadWrite → IRP queue or inline PIO
|
||||
// IdeWorker → dequeues IRP, calls IdeDmaRw(), completes the IRP
|
||||
// IdeIsr → clears BM status, queues a DPC
|
||||
// IdeDpc → signals gDmaEvent to unblock the worker
|
||||
//
|
||||
// Graceful degradation: if Bus Master init fails (no PCI IDE controller,
|
||||
// BAR4 not assigned, PRDT allocation above 4 GB, etc.) the same device
|
||||
// object remains registered and dispatch completes IRPs inline through PIO.
|
||||
//
|
||||
// PRDT layout
|
||||
// -----------
|
||||
// One page allocated via MmAllocateContiguous. We build up to PRDT_MAX_ENTRIES
|
||||
// entries by walking the transfer buffer in page-sized chunks and looking up
|
||||
// each page's physical address via MmGetPhysicalAddress. Adjacent physical
|
||||
// pages are coalesced into a single PRD entry where possible.
|
||||
//
|
||||
// LBA48 vs LBA28
|
||||
// --------------
|
||||
// DMA commands exist in both modes: CMD_READ/WRITE_DMA_EXT (0x25/0x35) for
|
||||
// LBA48 and CMD_READ/WRITE_DMA (0xC8/0xCA) for LBA28. The existing
|
||||
// gSupportsLba48 flag picks the right variant.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <ntlite/ddi/io_driver.h>
|
||||
#include <ntlite/ddi/hal_api.h>
|
||||
#include <kernel/ke_debug.h>
|
||||
#include <kernel/ke_sched.h>
|
||||
#include <kernel/ke_irp.h>
|
||||
#include <kernel/ke_event.h>
|
||||
#include <kernel/ke_semaphore.h>
|
||||
#include <kernel/ke_thread.h>
|
||||
#include <kernel/ke_irq.h>
|
||||
#include <kernel/ke_dpc.h>
|
||||
#include <kernel/mm_phys.h>
|
||||
#include <kernel/mm_virt.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IDE register map (primary channel, base 0x1F0)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define IDE_DATA 0x1F0
|
||||
#define IDE_ERR 0x1F1
|
||||
#define IDE_SECCNT 0x1F2
|
||||
#define IDE_LBA0 0x1F3
|
||||
#define IDE_LBA1 0x1F4
|
||||
#define IDE_LBA2 0x1F5
|
||||
#define IDE_DRIVE 0x1F6
|
||||
#define IDE_STATUS 0x1F7
|
||||
#define IDE_CMD 0x1F7
|
||||
#define IDE_ALT 0x3F6
|
||||
|
||||
#define IDE_BUSY 0x80
|
||||
#define IDE_READY 0x40
|
||||
#define IDE_ERR_F 0x01
|
||||
#define IDE_DRQ 0x08
|
||||
|
||||
// ATA commands
|
||||
#define CMD_READ_EXT 0x24 // PIO 48-bit read
|
||||
#define CMD_WRITE_EXT 0x34 // PIO 48-bit write
|
||||
#define CMD_READ_DMA_EXT 0x25 // DMA 48-bit read
|
||||
#define CMD_WRITE_DMA_EXT 0x35 // DMA 48-bit write
|
||||
#define CMD_READ_DMA 0xC8 // DMA 28-bit read
|
||||
#define CMD_WRITE_DMA 0xCA // DMA 28-bit write
|
||||
#define CMD_IDENT 0xEC
|
||||
|
||||
// IDE primary IRQ
|
||||
#define IDE_IRQ_PRIMARY 14
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bus Master IDE register offsets (relative to gBmBase, primary channel)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define BM_CMD_REG 0x00 // command
|
||||
#define BM_STATUS_REG 0x02 // status
|
||||
#define BM_PRDT_REG 0x04 // PRDT physical address (32-bit)
|
||||
|
||||
// BM_CMD_REG bits
|
||||
#define BM_CMD_START 0x01 // start DMA transfer
|
||||
#define BM_CMD_READ 0x08 // 1 = disk→memory (read), 0 = memory→disk (write)
|
||||
|
||||
// BM_STATUS_REG bits
|
||||
#define BM_STATUS_ACTIVE 0x01 // DMA active
|
||||
#define BM_STATUS_ERROR 0x02 // DMA error (write 1 to clear)
|
||||
#define BM_STATUS_IRQ 0x04 // interrupt pending (write 1 to clear)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Physical Region Descriptor (PRDT entry, 8 bytes)
|
||||
// The PRDT itself must not cross a 64 KB boundary and must be 4-byte aligned.
|
||||
// We allocate a full page so both constraints are trivially satisfied.
|
||||
// ---------------------------------------------------------------------------
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t physAddr; // physical base of this memory region
|
||||
uint16_t byteCount; // byte count; 0 means 64 KB
|
||||
uint16_t flags; // bit 15: End Of Table
|
||||
} PRD;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define PRD_EOT 0x8000u
|
||||
// 512 entries × 8 bytes = 4096 bytes = exactly one page; supports 512×64 KB = 32 MB
|
||||
#define PRDT_MAX_ENTRIES 512
|
||||
// ide.c - IDE/ATA disk I/O path and DriverEntry.
|
||||
#include "ide.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module state
|
||||
// ---------------------------------------------------------------------------
|
||||
static const HAL_API *gHal;
|
||||
static DRIVER_OBJECT gDriver;
|
||||
static DEVICE_OBJECT *gDevice = NULL;
|
||||
static bool gDmaEnabled = false;
|
||||
static uint64_t gSectorCount = 0;
|
||||
static bool gSupportsLba48 = false;
|
||||
const HAL_API *gHal;
|
||||
DRIVER_OBJECT gDriver;
|
||||
DEVICE_OBJECT *gDevice = NULL;
|
||||
bool gDmaEnabled = false;
|
||||
uint64_t gSectorCount = 0;
|
||||
bool gSupportsLba48 = false;
|
||||
|
||||
// DMA hardware state
|
||||
static uint16_t gBmBase = 0; // Bus Master I/O base port (BAR4 & ~3)
|
||||
static PRD *gPrdtVirt = NULL; // kernel VA of PRDT page
|
||||
static uint32_t gPrdtPhys = 0; // 32-bit physical address of PRDT
|
||||
uint16_t gBmBase = 0; // Bus Master I/O base port (BAR4 & ~3)
|
||||
PRD *gPrdtVirt = NULL; // kernel VA of PRDT page
|
||||
uint32_t gPrdtPhys = 0; // 32-bit physical address of PRDT
|
||||
|
||||
// Async worker synchronisation
|
||||
static IRP_QUEUE gIrpQueue;
|
||||
static SEMAPHORE *gIrpSemaphore = NULL;
|
||||
static EVENT *gDmaEvent = NULL; // auto-reset; DPC signals after each xfer
|
||||
static KDPC gDmaDpc; // bottom half for DMA completion
|
||||
IRP_QUEUE gIrpQueue;
|
||||
SEMAPHORE *gIrpSemaphore = NULL;
|
||||
EVENT *gDmaEvent = NULL; // auto-reset; DPC signals after each xfer
|
||||
KDPC gDmaDpc; // bottom half for DMA completion
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bus Master I/O helpers
|
||||
@@ -255,7 +151,7 @@ static uint32_t BuildPrdt(void *virtBuf, uint32_t byteLen) {
|
||||
PHYSICAL_ADDRESS phys = MmGetPhysicalAddress(va);
|
||||
if (phys >> 32) {
|
||||
// Physical address above 4 GB — BM IDE's 32-bit PRDT can't reach it.
|
||||
DbgPrint("IDE DMA: buffer page at VA 0x%llx has phys 0x%llx > 4 GB\n",
|
||||
DbgPrint("IDE DMA: buffer page at VA 0x%016llX has phys 0x%016llX > 4 GB\n",
|
||||
(uint64_t)va, (uint64_t)phys);
|
||||
return 0;
|
||||
}
|
||||
@@ -294,7 +190,7 @@ static uint32_t BuildPrdt(void *virtBuf, uint32_t byteLen) {
|
||||
// IDE DMA completion DPC. The ISR owns hardware acknowledgement; this bottom
|
||||
// half wakes the worker outside hard IRQ/DIRQL context.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void IdeDmaCompletionDpc(KDPC *dpc, void *context,
|
||||
void IdeDmaCompletionDpc(KDPC *dpc, void *context,
|
||||
void *systemArgument1, void *systemArgument2) {
|
||||
(void)dpc;
|
||||
(void)context;
|
||||
@@ -312,7 +208,7 @@ static void IdeDmaCompletionDpc(KDPC *dpc, void *context,
|
||||
// interrupt bits here and queue a DPC to wake the worker thread.
|
||||
// The worker reads the final status and fires IRP completion hooks.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void IdeIsr(void) {
|
||||
void IdeIsr(void) {
|
||||
uint8_t bmSt = BmRead8(BM_STATUS_REG);
|
||||
if (!(bmSt & BM_STATUS_IRQ))
|
||||
return; // not our interrupt (spurious or secondary channel)
|
||||
@@ -417,7 +313,7 @@ static KSTATUS IdeDmaRw(uint64_t lba, uint32_t count, void *buf, bool write) {
|
||||
// Blocks on gIrpSemaphore. Each IrpQueueEnqueue increments it by one, so
|
||||
// the worker wakes once per submitted IRP. Mirrors AhciWorker exactly.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void IdeWorker(uintptr_t arg) {
|
||||
void IdeWorker(uintptr_t arg) {
|
||||
(void)arg;
|
||||
while (true) {
|
||||
KeWaitSemaphore(gIrpSemaphore);
|
||||
@@ -473,90 +369,6 @@ static KSTATUS IdeDispatchReadWrite(DEVICE_OBJECT *device, IRP *irp) {
|
||||
// Returns true on success. On any failure, logs a message and returns false;
|
||||
// dispatch will complete IRPs inline through PIO.
|
||||
// ---------------------------------------------------------------------------
|
||||
static bool IdeDmaInit(void) {
|
||||
// ---- Find PCI IDE controller (class 0x01, subclass 0x01) --------------
|
||||
uint8_t bus, dev, fn;
|
||||
if (!gHal->Pci.FindClass(0x01, 0x01, &bus, &dev, &fn)) {
|
||||
DbgPrint("IDE DMA: No PCI IDE controller found — using PIO\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Read BAR4 (Bus Master base, I/O space) ---------------------------
|
||||
// PCI config offset 0x20. Bit 0 must be 1 for I/O BAR.
|
||||
uint32_t bar4 = gHal->Pci.Read32(bus, dev, fn, 0x20);
|
||||
if (!(bar4 & 0x1)) {
|
||||
DbgPrint("IDE DMA: BAR4 is a memory BAR (0x%08x) — unexpected, using PIO\n", bar4);
|
||||
return false;
|
||||
}
|
||||
uint16_t bmBase = (uint16_t)(bar4 & 0xFFFC);
|
||||
if (bmBase == 0) {
|
||||
DbgPrint("IDE DMA: BAR4 is zero (firmware didn't assign it) — using PIO\n");
|
||||
return false;
|
||||
}
|
||||
DbgPrint("IDE DMA: Bus Master base = 0x%04X (bus %u, dev %u, fn %u)\n",
|
||||
bmBase, bus, dev, fn);
|
||||
|
||||
// ---- Enable Bus Mastering in PCI Command register (bit 2) ------------
|
||||
uint32_t pciCmd = gHal->Pci.Read32(bus, dev, fn, 0x04);
|
||||
gHal->Pci.Write32(bus, dev, fn, 0x04, pciCmd | (1u << 2));
|
||||
|
||||
// ---- Allocate PRDT page (must be 32-bit physical, 4-byte aligned) ----
|
||||
PHYSICAL_ADDRESS prdtPhys = MmAllocateContiguous(1);
|
||||
if (!prdtPhys) {
|
||||
DbgPrint("IDE DMA: Failed to allocate PRDT page\n");
|
||||
return false;
|
||||
}
|
||||
if (prdtPhys >> 32) {
|
||||
// MmAllocateContiguous returned memory above 4 GB — BM IDE can't
|
||||
// address it. This shouldn't happen on normal x86 systems with early
|
||||
// boot allocations but we check defensively.
|
||||
DbgPrint("IDE DMA: PRDT page is above 4 GB (0x%llx) — using PIO\n",
|
||||
(uint64_t)prdtPhys);
|
||||
return false;
|
||||
}
|
||||
void *prdtVirt = gHal->PhysToVirt(prdtPhys);
|
||||
DbgPrint("IDE DMA: PRDT at phys 0x%08x, virt %p\n", (uint32_t)prdtPhys, prdtVirt);
|
||||
|
||||
// ---- Create synchronisation objects ------------------------------------
|
||||
SEMAPHORE *sem = NULL;
|
||||
if (!KSUCCESS(KeSemaphoreCreate(0, 0x7FFFFFFF, &sem))) {
|
||||
DbgPrint("IDE DMA: Failed to create IRP semaphore\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
EVENT *dmaEv = NULL;
|
||||
if (!KSUCCESS(KeEventCreate(EventAutoReset, 0, &dmaEv))) {
|
||||
DbgPrint("IDE DMA: Failed to create DMA completion event\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Commit module state -----------------------------------------------
|
||||
gBmBase = bmBase;
|
||||
gPrdtPhys = (uint32_t)prdtPhys;
|
||||
gPrdtVirt = (PRD *)prdtVirt;
|
||||
gIrpSemaphore = sem;
|
||||
gDmaEvent = dmaEv;
|
||||
KeInitializeDpc(&gDmaDpc, IdeDmaCompletionDpc, (void *)0);
|
||||
|
||||
IrpQueueInit(&gIrpQueue, sem);
|
||||
|
||||
// ---- Register IRQ 14 --------------------------------------------------
|
||||
KeIrqRegister(IDE_IRQ_PRIMARY, IdeIsr);
|
||||
DbgPrint("IDE DMA: IRQ %u registered\n", IDE_IRQ_PRIMARY);
|
||||
|
||||
// ---- Spawn worker thread -----------------------------------------------
|
||||
KSTATUS st = KeCreateKernelThread((uintptr_t)IdeWorker, 0,
|
||||
PriorityHigh, (THREAD **)NULL);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("IDE DMA: Worker thread creation failed (0x%x)\n", st);
|
||||
// Not fatal for init — the PRDT/IRQ are live but async callers will
|
||||
// never get completions. Better to surface the failure clearly.
|
||||
return false;
|
||||
}
|
||||
|
||||
DbgPrint("IDE DMA: Init complete\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DriverEntry
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// ide.h - shared IDE/ATA driver internals.
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <ntlite/ddi/io_driver.h>
|
||||
#include <ntlite/ddi/hal_api.h>
|
||||
#include <kernel/ke_debug.h>
|
||||
#include <kernel/ke_sched.h>
|
||||
#include <kernel/ke_irp.h>
|
||||
#include <kernel/ke_event.h>
|
||||
#include <kernel/ke_semaphore.h>
|
||||
#include <kernel/ke_thread.h>
|
||||
#include <kernel/ke_irq.h>
|
||||
#include <kernel/ke_dpc.h>
|
||||
#include <kernel/mm_phys.h>
|
||||
#include <kernel/mm_virt.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IDE register map (primary channel, base 0x1F0)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define IDE_DATA 0x1F0
|
||||
#define IDE_ERR 0x1F1
|
||||
#define IDE_SECCNT 0x1F2
|
||||
#define IDE_LBA0 0x1F3
|
||||
#define IDE_LBA1 0x1F4
|
||||
#define IDE_LBA2 0x1F5
|
||||
#define IDE_DRIVE 0x1F6
|
||||
#define IDE_STATUS 0x1F7
|
||||
#define IDE_CMD 0x1F7
|
||||
#define IDE_ALT 0x3F6
|
||||
|
||||
#define IDE_BUSY 0x80
|
||||
#define IDE_READY 0x40
|
||||
#define IDE_ERR_F 0x01
|
||||
#define IDE_DRQ 0x08
|
||||
|
||||
// ATA commands
|
||||
#define CMD_READ_EXT 0x24 // PIO 48-bit read
|
||||
#define CMD_WRITE_EXT 0x34 // PIO 48-bit write
|
||||
#define CMD_READ_DMA_EXT 0x25 // DMA 48-bit read
|
||||
#define CMD_WRITE_DMA_EXT 0x35 // DMA 48-bit write
|
||||
#define CMD_READ_DMA 0xC8 // DMA 28-bit read
|
||||
#define CMD_WRITE_DMA 0xCA // DMA 28-bit write
|
||||
#define CMD_IDENT 0xEC
|
||||
|
||||
// IDE primary IRQ
|
||||
#define IDE_IRQ_PRIMARY 14
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bus Master IDE register offsets (relative to gBmBase, primary channel)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define BM_CMD_REG 0x00 // command
|
||||
#define BM_STATUS_REG 0x02 // status
|
||||
#define BM_PRDT_REG 0x04 // PRDT physical address (32-bit)
|
||||
|
||||
// BM_CMD_REG bits
|
||||
#define BM_CMD_START 0x01 // start DMA transfer
|
||||
#define BM_CMD_READ 0x08 // 1 = disk→memory (read), 0 = memory→disk (write)
|
||||
|
||||
// BM_STATUS_REG bits
|
||||
#define BM_STATUS_ACTIVE 0x01 // DMA active
|
||||
#define BM_STATUS_ERROR 0x02 // DMA error (write 1 to clear)
|
||||
#define BM_STATUS_IRQ 0x04 // interrupt pending (write 1 to clear)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Physical Region Descriptor (PRDT entry, 8 bytes)
|
||||
// The PRDT itself must not cross a 64 KB boundary and must be 4-byte aligned.
|
||||
// We allocate a full page so both constraints are trivially satisfied.
|
||||
// ---------------------------------------------------------------------------
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t physAddr; // physical base of this memory region
|
||||
uint16_t byteCount; // byte count; 0 means 64 KB
|
||||
uint16_t flags; // bit 15: End Of Table
|
||||
} PRD;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define PRD_EOT 0x8000u
|
||||
// 512 entries × 8 bytes = 4096 bytes = exactly one page; supports 512×64 KB = 32 MB
|
||||
#define PRDT_MAX_ENTRIES 512
|
||||
|
||||
|
||||
extern const HAL_API *gHal;
|
||||
extern DRIVER_OBJECT gDriver;
|
||||
extern DEVICE_OBJECT *gDevice;
|
||||
extern bool gDmaEnabled;
|
||||
extern uint64_t gSectorCount;
|
||||
extern bool gSupportsLba48;
|
||||
extern uint16_t gBmBase;
|
||||
extern PRD *gPrdtVirt;
|
||||
extern uint32_t gPrdtPhys;
|
||||
extern IRP_QUEUE gIrpQueue;
|
||||
extern SEMAPHORE *gIrpSemaphore;
|
||||
extern EVENT *gDmaEvent;
|
||||
extern KDPC gDmaDpc;
|
||||
|
||||
void IdeDmaCompletionDpc(KDPC *dpc, void *context, void *systemArgument1, void *systemArgument2);
|
||||
void IdeIsr(void);
|
||||
void IdeWorker(uintptr_t arg);
|
||||
bool IdeDmaInit(void);
|
||||
@@ -0,0 +1,87 @@
|
||||
// ide_bus.c - Bus-master IDE PCI/DMA initialisation.
|
||||
#include "ide.h"
|
||||
|
||||
bool IdeDmaInit(void) {
|
||||
// ---- Find PCI IDE controller (class 0x01, subclass 0x01) --------------
|
||||
uint8_t bus, dev, fn;
|
||||
if (!gHal->Pci.FindClass(0x01, 0x01, &bus, &dev, &fn)) {
|
||||
DbgPrint("IDE DMA: No PCI IDE controller found — using PIO\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Read BAR4 (Bus Master base, I/O space) ---------------------------
|
||||
// PCI config offset 0x20. Bit 0 must be 1 for I/O BAR.
|
||||
uint32_t bar4 = gHal->Pci.Read32(bus, dev, fn, 0x20);
|
||||
if (!(bar4 & 0x1)) {
|
||||
DbgPrint("IDE DMA: BAR4 is a memory BAR (0x%08x) — unexpected, using PIO\n", bar4);
|
||||
return false;
|
||||
}
|
||||
uint16_t bmBase = (uint16_t)(bar4 & 0xFFFC);
|
||||
if (bmBase == 0) {
|
||||
DbgPrint("IDE DMA: BAR4 is zero (firmware didn't assign it) — using PIO\n");
|
||||
return false;
|
||||
}
|
||||
DbgPrint("IDE DMA: Bus Master base = 0x%04X (bus %u, dev %u, fn %u)\n",
|
||||
bmBase, bus, dev, fn);
|
||||
|
||||
// ---- Enable Bus Mastering in PCI Command register (bit 2) ------------
|
||||
uint32_t pciCmd = gHal->Pci.Read32(bus, dev, fn, 0x04);
|
||||
gHal->Pci.Write32(bus, dev, fn, 0x04, pciCmd | (1u << 2));
|
||||
|
||||
// ---- Allocate PRDT page (must be 32-bit physical, 4-byte aligned) ----
|
||||
PHYSICAL_ADDRESS prdtPhys = MmAllocateContiguous(1);
|
||||
if (!prdtPhys) {
|
||||
DbgPrint("IDE DMA: Failed to allocate PRDT page\n");
|
||||
return false;
|
||||
}
|
||||
if (prdtPhys >> 32) {
|
||||
// MmAllocateContiguous returned memory above 4 GB — BM IDE can't
|
||||
// address it. This shouldn't happen on normal x86 systems with early
|
||||
// boot allocations but we check defensively.
|
||||
DbgPrint("IDE DMA: PRDT page is above 4 GB (0x%016llX) — using PIO\n",
|
||||
(uint64_t)prdtPhys);
|
||||
return false;
|
||||
}
|
||||
void *prdtVirt = gHal->PhysToVirt(prdtPhys);
|
||||
DbgPrint("IDE DMA: PRDT at phys 0x%08x, virt 0x%016llX\n", (uint32_t)prdtPhys, prdtVirt);
|
||||
|
||||
// ---- Create synchronisation objects ------------------------------------
|
||||
SEMAPHORE *sem = NULL;
|
||||
if (!KSUCCESS(KeSemaphoreCreate(0, 0x7FFFFFFF, &sem))) {
|
||||
DbgPrint("IDE DMA: Failed to create IRP semaphore\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
EVENT *dmaEv = NULL;
|
||||
if (!KSUCCESS(KeEventCreate(EventAutoReset, 0, &dmaEv))) {
|
||||
DbgPrint("IDE DMA: Failed to create DMA completion event\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Commit module state -----------------------------------------------
|
||||
gBmBase = bmBase;
|
||||
gPrdtPhys = (uint32_t)prdtPhys;
|
||||
gPrdtVirt = (PRD *)prdtVirt;
|
||||
gIrpSemaphore = sem;
|
||||
gDmaEvent = dmaEv;
|
||||
KeInitializeDpc(&gDmaDpc, IdeDmaCompletionDpc, (void *)0);
|
||||
|
||||
IrpQueueInit(&gIrpQueue, sem);
|
||||
|
||||
// ---- Register IRQ 14 --------------------------------------------------
|
||||
KeIrqRegister(IDE_IRQ_PRIMARY, IdeIsr);
|
||||
DbgPrint("IDE DMA: IRQ %u registered\n", IDE_IRQ_PRIMARY);
|
||||
|
||||
// ---- Spawn worker thread -----------------------------------------------
|
||||
KSTATUS st = KeCreateKernelThread((uintptr_t)IdeWorker, 0,
|
||||
PriorityHigh, (THREAD **)NULL);
|
||||
if (!KSUCCESS(st)) {
|
||||
DbgPrint("IDE DMA: Worker thread creation failed (0x%x)\n", st);
|
||||
// Not fatal for init — the PRDT/IRQ are live but async callers will
|
||||
// never get completions. Better to surface the failure clearly.
|
||||
return false;
|
||||
}
|
||||
|
||||
DbgPrint("IDE DMA: Init complete\n");
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user