Files
nt-lite/code/drivers/storage/ata/ata.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

949 lines
33 KiB
C

// ATA compatibility storage driver. This file owns legacy IDE probing and PIO disk access for older virtual hardware.
#include "ata.h"
const NTL_DDK* gAtaDdk;
static DRIVER_OBJECT gAtaDriver;
static ATA_CONTROLLER* gAtaControllers[ATA_MAX_CONTROLLERS];
static uint32_t gAtaControllerCount;
static const IO_DEVICE_ID gAtaIds[] = {
{ IoBusPci, { "PCI\\CC_0101", sizeof("PCI\\CC_0101") - 1 } },
};
static const IO_DRIVER_ID_TABLE gAtaIdTable = {
gAtaIds,
sizeof(gAtaIds) / sizeof(gAtaIds[0]),
};
// Implements the in8 operation.
static uint8_t AtaIn8(ATA_CHANNEL* ch, uint8_t reg) {
return __inbyte((unsigned short)(ch->CommandBase + reg));
}
// Implements the in16 operation.
static uint16_t AtaIn16(ATA_CHANNEL* ch, uint8_t reg) {
return __inword((unsigned short)(ch->CommandBase + reg));
}
// Implements the out8 operation.
static void AtaOut8(ATA_CHANNEL* ch, uint8_t reg, uint8_t value) {
__outbyte((unsigned short)(ch->CommandBase + reg), value);
}
// Implements the out16 operation.
static void AtaOut16(ATA_CHANNEL* ch, uint8_t reg, uint16_t value) {
__outword((unsigned short)(ch->CommandBase + reg), value);
}
// Implements the alt Status operation.
static uint8_t AtaAltStatus(ATA_CHANNEL* ch) {
return __inbyte((unsigned short)(ch->ControlBase + ATA_REG_ALT_STATUS));
}
// Implements the device Control operation.
static void AtaDeviceControl(ATA_CHANNEL* ch, uint8_t value) {
__outbyte((unsigned short)(ch->ControlBase + ATA_REG_DEVICE_CONTROL), value);
}
// Implements the delay400ns operation.
static void AtaDelay400ns(ATA_CHANNEL* ch) {
(void)AtaAltStatus(ch);
(void)AtaAltStatus(ch);
(void)AtaAltStatus(ch);
(void)AtaAltStatus(ch);
}
// Returns the monotonic DDK timer in microseconds.
static uint64_t AtaQueryTicks(void) {
return gAtaDdk->Timer->QueryTicks();
}
// Delays briefly between ATA status samples without involving the scheduler.
static void AtaPollDelay(void) {
gAtaDdk->Timer->Stall(ATA_POLL_INTERVAL_US);
}
// Returns whether a status byte represents an unimplemented/floating legacy channel.
static bool AtaStatusIsAbsent(uint8_t status) {
return status == 0x00u || status == 0xFFu;
}
// Waits until one microsecond deadline expires while polling alternate status.
static KSTATUS AtaWaitStatus(ATA_CHANNEL* ch, uint64_t timeoutUs, bool requireDrq, bool checkErrors,
uint8_t* finalStatusOut) {
uint64_t start = AtaQueryTicks();
uint64_t deadline = start > UINT64_MAX - timeoutUs ? UINT64_MAX : start + timeoutUs;
uint64_t pollBudget = timeoutUs / ATA_POLL_INTERVAL_US + 2u;
for (;;) {
uint8_t status = AtaAltStatus(ch);
if (finalStatusOut) *finalStatusOut = status;
if (AtaStatusIsAbsent(status)) return NTLSTATUS_NOT_FOUND;
if (!(status & ATA_STATUS_BSY)) {
if (checkErrors && (status & (ATA_STATUS_ERR | ATA_STATUS_DF)))
return NTLSTATUS_DEVICE_ERROR;
if (!requireDrq || (status & ATA_STATUS_DRQ))
return NTLSTATUS_SUCCESS;
}
if (AtaQueryTicks() >= deadline || pollBudget-- == 0u)
return NTLSTATUS_TIMEOUT;
AtaPollDelay();
}
}
// Waits boundedly for BSY to clear using elapsed hardware time rather than scheduler iterations.
static KSTATUS AtaWaitNotBusy(ATA_CHANNEL* ch) {
return AtaWaitStatus(ch, ATA_POLL_TIMEOUT_US, false, true, (uint8_t*)0);
}
// Waits boundedly for a data phase while rejecting absent and failed devices early.
static KSTATUS AtaWaitDrq(ATA_CHANNEL* ch) {
return AtaWaitStatus(ch, ATA_POLL_TIMEOUT_US, true, true, (uint8_t*)0);
}
// Emits the task-file state needed to distinguish selection, signature, and command failures.
static void AtaLogProbeState(const ATA_DEVICE* dev, const char* stage, KSTATUS result) {
if (!dev || !dev->Channel) return;
ATA_CHANNEL* ch = dev->Channel;
DbgPrint("ATA: channel %u %s identify %s result=0x%x status=0x%02x error=0x%02x lba1=0x%02x lba2=0x%02x\n",
ch->Index,
dev->Drive ? "slave" : "master",
stage ? stage : "stage",
result,
AtaAltStatus(ch),
AtaIn8(ch, ATA_REG_ERROR),
AtaIn8(ch, ATA_REG_LBA1),
AtaIn8(ch, ATA_REG_LBA2));
}
// Resets one legacy ATA channel before probing so firmware state cannot leak into IDENTIFY.
static KSTATUS AtaResetChannel(ATA_CHANNEL* ch) {
if (!ch) return NTLSTATUS_INVALID_PARAMETER;
uint8_t before = AtaAltStatus(ch);
DbgPrint("ATA: channel %u reset begin status=0x%02x\n", ch->Index, before);
AtaDeviceControl(ch, ATA_DEVICE_CONTROL_NIEN | ATA_DEVICE_CONTROL_SRST);
gAtaDdk->Timer->Stall(ATA_RESET_ASSERT_US);
AtaDeviceControl(ch, ATA_DEVICE_CONTROL_NIEN);
gAtaDdk->Timer->Stall(ATA_RESET_SETTLE_US);
uint8_t finalStatus = AtaAltStatus(ch);
if (AtaStatusIsAbsent(finalStatus)) {
DbgPrint("ATA: channel %u reset found no responding device status=0x%02x\n",
ch->Index, finalStatus);
return NTLSTATUS_NOT_FOUND;
}
KSTATUS status = AtaWaitStatus(ch, ATA_RESET_TIMEOUT_US, false, false, &finalStatus);
DbgPrint("ATA: channel %u reset complete result=0x%x status=0x%02x\n",
ch->Index, status, finalStatus);
return status;
}
// Implements the select Drive operation.
static void AtaSelectDrive(ATA_DEVICE* dev) {
ATA_CHANNEL* ch = dev->Channel;
AtaOut8(ch, ATA_REG_DRIVE_HEAD, (uint8_t)(0xA0u | (dev->Drive ? 0x10u : 0u)));
AtaDelay400ns(ch);
}
// Implements IDENTIFY with stage diagnostics and finite hardware deadlines.
static KSTATUS AtaIdentify(ATA_DEVICE* dev) {
ATA_CHANNEL* ch = dev->Channel;
uint16_t ident[256];
memset(ident, 0, sizeof(ident));
KSTATUS st = KeWaitSemaphore(ch->Lock, KE_WAIT_INFINITE);
if (!KSUCCESS(st))
return st;
DbgPrint("ATA: channel %u %s identify lock acquired\n",
ch->Index, dev->Drive ? "slave" : "master");
AtaSelectDrive(dev);
DbgPrint("ATA: channel %u %s identify selected status=0x%02x\n",
ch->Index, dev->Drive ? "slave" : "master", AtaAltStatus(ch));
st = AtaWaitStatus(ch, ATA_POLL_TIMEOUT_US, false, false, (uint8_t*)0);
if (!KSUCCESS(st)) {
AtaLogProbeState(dev, "pre-command wait", st);
KeSignalSemaphore(ch->Lock);
return st;
}
AtaOut8(ch, ATA_REG_SECTOR_COUNT, 0);
AtaOut8(ch, ATA_REG_LBA0, 0);
AtaOut8(ch, ATA_REG_LBA1, 0);
AtaOut8(ch, ATA_REG_LBA2, 0);
AtaOut8(ch, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_DEVICE);
AtaDelay400ns(ch);
uint8_t status = AtaAltStatus(ch);
DbgPrint("ATA: channel %u %s identify command issued status=0x%02x\n",
ch->Index, dev->Drive ? "slave" : "master", status);
if (AtaStatusIsAbsent(status)) {
AtaLogProbeState(dev, "absent after command", NTLSTATUS_NOT_FOUND);
KeSignalSemaphore(ch->Lock);
return NTLSTATUS_NOT_FOUND;
}
st = AtaWaitNotBusy(ch);
if (!KSUCCESS(st)) {
AtaLogProbeState(dev, "busy timeout/error", st);
KeSignalSemaphore(ch->Lock);
return st;
}
uint8_t lba1 = AtaIn8(ch, ATA_REG_LBA1);
uint8_t lba2 = AtaIn8(ch, ATA_REG_LBA2);
if (lba1 != 0 || lba2 != 0) {
AtaLogProbeState(dev, "non-ATA signature", NTLSTATUS_NOT_SUPPORTED);
KeSignalSemaphore(ch->Lock);
return NTLSTATUS_NOT_SUPPORTED;
}
st = AtaWaitDrq(ch);
if (!KSUCCESS(st)) {
AtaLogProbeState(dev, "DRQ timeout/error", st);
KeSignalSemaphore(ch->Lock);
return st;
}
for (uint32_t i = 0; i < 256; i++)
ident[i] = AtaIn16(ch, ATA_REG_DATA);
AtaLogProbeState(dev, "data complete", NTLSTATUS_SUCCESS);
KeSignalSemaphore(ch->Lock);
DbgPrint("ATA: channel %u %s identify lock released capabilities=0x%04x command_sets=0x%04x\n",
ch->Index, dev->Drive ? "slave" : "master",
ident[ATA_IDENT_CAPABILITIES], ident[ATA_IDENT_COMMAND_SETS]);
if (!(ident[ATA_IDENT_CAPABILITIES] & ATA_IDENT_LBA_SUPPORTED))
return NTLSTATUS_NOT_SUPPORTED;
dev->SupportsLba48 = (ident[ATA_IDENT_COMMAND_SETS] & ATA_IDENT_LBA48_SUPPORTED) != 0;
if (dev->SupportsLba48) {
uint64_t sectors = 0;
sectors |= (uint64_t)ident[100];
sectors |= (uint64_t)ident[101] << 16;
sectors |= (uint64_t)ident[102] << 32;
sectors |= (uint64_t)ident[103] << 48;
dev->SectorCount = sectors;
} else {
dev->SectorCount = ((uint32_t)ident[ATA_IDENT_LBA28_SECTORS_HI] << 16) |
ident[ATA_IDENT_LBA28_SECTORS_LO];
}
DbgPrint("ATA: channel %u %s identify parsed lba48=%u sectors=%llu\n",
ch->Index, dev->Drive ? "slave" : "master",
dev->SupportsLba48 ? 1u : 0u, (unsigned long long)dev->SectorCount);
return dev->SectorCount ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_FOUND;
}
// Implements the program Lba operation.
static KSTATUS AtaProgramLba(ATA_DEVICE* dev, uint64_t lba, uint16_t count, bool write) {
ATA_CHANNEL* ch = dev->Channel;
if (dev->SupportsLba48) {
AtaOut8(ch, ATA_REG_DRIVE_HEAD, (uint8_t)(0x40u | (dev->Drive ? 0x10u : 0u)));
AtaOut8(ch, ATA_REG_SECTOR_COUNT, (uint8_t)(count >> 8));
AtaOut8(ch, ATA_REG_LBA0, (uint8_t)(lba >> 24));
AtaOut8(ch, ATA_REG_LBA1, (uint8_t)(lba >> 32));
AtaOut8(ch, ATA_REG_LBA2, (uint8_t)(lba >> 40));
AtaOut8(ch, ATA_REG_SECTOR_COUNT, (uint8_t)count);
AtaOut8(ch, ATA_REG_LBA0, (uint8_t)lba);
AtaOut8(ch, ATA_REG_LBA1, (uint8_t)(lba >> 8));
AtaOut8(ch, ATA_REG_LBA2, (uint8_t)(lba >> 16));
AtaOut8(ch, ATA_REG_COMMAND, write ? ATA_CMD_WRITE_SECTORS_EXT : ATA_CMD_READ_SECTORS_EXT);
return NTLSTATUS_SUCCESS;
}
if (lba > 0x0FFFFFFFULL || count > 256)
return NTLSTATUS_INVALID_PARAMETER;
AtaOut8(ch, ATA_REG_DRIVE_HEAD, (uint8_t)(0xE0u | (dev->Drive ? 0x10u : 0u) | ((lba >> 24) & 0x0Fu)));
AtaOut8(ch, ATA_REG_SECTOR_COUNT, (uint8_t)count);
AtaOut8(ch, ATA_REG_LBA0, (uint8_t)lba);
AtaOut8(ch, ATA_REG_LBA1, (uint8_t)(lba >> 8));
AtaOut8(ch, ATA_REG_LBA2, (uint8_t)(lba >> 16));
AtaOut8(ch, ATA_REG_COMMAND, write ? ATA_CMD_WRITE_SECTORS : ATA_CMD_READ_SECTORS);
return NTLSTATUS_SUCCESS;
}
// Returns pio.
static KSTATUS AtaReadPio(ATA_DEVICE* dev, uint64_t lba, uint32_t count, void* buffer) {
if (!dev || !buffer)
return NTLSTATUS_INVALID_PARAMETER;
if (count == 0)
return NTLSTATUS_SUCCESS;
if (lba + count > dev->SectorCount)
return NTLSTATUS_INVALID_PARAMETER;
ATA_CHANNEL* ch = dev->Channel;
uint16_t* out = (uint16_t*)buffer;
KSTATUS st = KeWaitSemaphore(ch->Lock, KE_WAIT_INFINITE);
if (!KSUCCESS(st))
return st;
while (count > 0) {
uint16_t batch = (count > 256u) ? 256u : (uint16_t)count;
uint16_t encodedCount = (!dev->SupportsLba48 && batch == 256u) ? 0u : batch;
AtaSelectDrive(dev);
st = AtaWaitNotBusy(ch);
if (!KSUCCESS(st)) break;
st = AtaProgramLba(dev, lba, encodedCount, false);
if (!KSUCCESS(st)) break;
for (uint32_t s = 0; s < batch; s++) {
st = AtaWaitDrq(ch);
if (!KSUCCESS(st)) break;
for (uint32_t w = 0; w < ATA_SECTOR_SIZE / 2u; w++)
*out++ = AtaIn16(ch, ATA_REG_DATA);
}
if (!KSUCCESS(st)) break;
lba += batch;
count -= batch;
}
KeSignalSemaphore(ch->Lock);
return st;
}
// Flushes the requested operation.
static KSTATUS AtaFlush(ATA_DEVICE* dev) {
ATA_CHANNEL* ch = dev->Channel;
KSTATUS st = AtaWaitNotBusy(ch);
if (!KSUCCESS(st))
return st;
AtaOut8(ch, ATA_REG_COMMAND, dev->SupportsLba48 ? ATA_CMD_CACHE_FLUSH_EXT : ATA_CMD_CACHE_FLUSH);
return AtaWaitNotBusy(ch);
}
// Flushes one device while holding the shared channel lock.
static KSTATUS AtaFlushSerialized(ATA_DEVICE* dev) {
if (!dev || !dev->Channel)
return NTLSTATUS_INVALID_PARAMETER;
ATA_CHANNEL* ch = dev->Channel;
KSTATUS st = KeWaitSemaphore(ch->Lock, KE_WAIT_INFINITE);
if (!KSUCCESS(st))
return st;
AtaSelectDrive(dev);
st = AtaFlush(dev);
KeSignalSemaphore(ch->Lock);
return st;
}
// Updates pio.
static KSTATUS AtaWritePio(ATA_DEVICE* dev, uint64_t lba, uint32_t count, const void* buffer) {
if (!dev || !buffer)
return NTLSTATUS_INVALID_PARAMETER;
if (count == 0)
return NTLSTATUS_SUCCESS;
if (lba + count > dev->SectorCount)
return NTLSTATUS_INVALID_PARAMETER;
ATA_CHANNEL* ch = dev->Channel;
const uint16_t* in = (const uint16_t*)buffer;
KSTATUS st = KeWaitSemaphore(ch->Lock, KE_WAIT_INFINITE);
if (!KSUCCESS(st))
return st;
while (count > 0) {
uint16_t batch = (count > 256u) ? 256u : (uint16_t)count;
uint16_t encodedCount = (!dev->SupportsLba48 && batch == 256u) ? 0u : batch;
AtaSelectDrive(dev);
st = AtaWaitNotBusy(ch);
if (!KSUCCESS(st)) break;
st = AtaProgramLba(dev, lba, encodedCount, true);
if (!KSUCCESS(st)) break;
for (uint32_t s = 0; s < batch; s++) {
st = AtaWaitDrq(ch);
if (!KSUCCESS(st)) break;
for (uint32_t w = 0; w < ATA_SECTOR_SIZE / 2u; w++)
AtaOut16(ch, ATA_REG_DATA, *in++);
}
if (!KSUCCESS(st)) break;
st = AtaFlush(dev);
if (!KSUCCESS(st)) break;
lba += batch;
count -= batch;
}
KeSignalSemaphore(ch->Lock);
return st;
}
static KSTATUS AtaGetIrpReadWrite(IRP* irp, bool* outWrite, uint64_t* outLba,
uint32_t* outSectors, void** outBuffer) {
if (!irp || !outWrite || !outLba || !outSectors || !outBuffer)
return NTLSTATUS_INVALID_PARAMETER;
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
if (!ios) return NTLSTATUS_INVALID_PARAMETER;
if (ios->MajorFunction != IRP_MJ_READ && ios->MajorFunction != IRP_MJ_WRITE)
return NTLSTATUS_INVALID_PARAMETER;
if ((ios->Parameters.ReadWrite.ByteOffset & 511ull) ||
(ios->Parameters.ReadWrite.Length & 511u))
return NTLSTATUS_INVALID_PARAMETER;
if (!irp->UserBuffer || ios->Parameters.ReadWrite.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
*outWrite = (ios->MajorFunction == IRP_MJ_WRITE);
*outLba = ios->Parameters.ReadWrite.ByteOffset >> 9;
*outSectors = ios->Parameters.ReadWrite.Length >> 9;
*outBuffer = irp->UserBuffer;
return NTLSTATUS_SUCCESS;
}
// Implements the worker operation.
static void AtaWorker(uintptr_t arg) {
ATA_DEVICE* dev = (ATA_DEVICE*)arg;
if (!dev)
return;
DbgPrint("ATA: channel %u %s worker online\n",
dev->Channel ? dev->Channel->Index : 0u, dev->Drive ? "slave" : "master");
while (dev->WorkerRunning) {
KSTATUS waitStatus = KeWaitSemaphore(dev->IrpQueue.Semaphore, KE_WAIT_INFINITE);
if (!KSUCCESS(waitStatus))
continue;
if (!dev->WorkerRunning)
break;
IRP* irp = IrpQueueDequeue(&dev->IrpQueue);
if (!irp)
continue;
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
KSTATUS st = NTLSTATUS_INVALID_PARAMETER;
size_t information = 0;
if (ios && ios->MajorFunction == IRP_MJ_FLUSH_BUFFERS) {
st = AtaFlushSerialized(dev);
} else {
bool write = false;
uint64_t lba = 0;
uint32_t sectors = 0;
void* buffer = (void*)0;
st = AtaGetIrpReadWrite(irp, &write, &lba, &sectors, &buffer);
if (KSUCCESS(st)) {
if (lba + sectors > dev->SectorCount) {
st = NTLSTATUS_INVALID_PARAMETER;
} else {
st = write ? AtaWritePio(dev, lba, sectors, buffer)
: AtaReadPio(dev, lba, sectors, buffer);
if (KSUCCESS(st))
information = (size_t)sectors * ATA_SECTOR_SIZE;
}
}
}
IoReleaseRemoveLock(&dev->RemoveLock, irp);
IoCompleteRequest(irp, st, information);
}
DbgPrint("ATA: channel %u %s worker stopped\n",
dev->Channel ? dev->Channel->Index : 0u, dev->Drive ? "slave" : "master");
if (dev->WorkerFinished)
KeSignalEvent(dev->WorkerFinished);
}
// Stops one ATA disk worker and releases the worker-owned synchronization references.
static void AtaStopWorker(ATA_DEVICE* dev) {
if (!dev)
return;
dev->WorkerRunning = false;
if (dev->IrpQueue.Semaphore)
KeSignalSemaphore(dev->IrpQueue.Semaphore);
if (dev->WorkerThread && dev->WorkerFinished)
(void)KeWaitForEvent(dev->WorkerFinished, 0);
if (dev->WorkerThread) {
ObDereferenceObject((OBJECT*)dev->WorkerThread);
dev->WorkerThread = (THREAD*)0;
}
if (dev->WorkerFinished) {
ObDereferenceObject(&dev->WorkerFinished->Header);
dev->WorkerFinished = (EVENT*)0;
}
if (dev->IrpQueue.Semaphore) {
ObDereferenceObject(&dev->IrpQueue.Semaphore->Header);
dev->IrpQueue.Semaphore = (SEMAPHORE*)0;
}
}
// Starts one ATA disk worker before the disk is published to synchronous discovery consumers.
static KSTATUS AtaStartWorker(ATA_DEVICE* dev) {
if (!dev || !dev->IrpQueue.Semaphore)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KeEventCreate(EventManualReset, 0, &dev->WorkerFinished);
if (!KSUCCESS(status))
return status;
dev->WorkerRunning = true;
const KE_THREAD_CREATE_PARAMETERS workerParameters =
KE_THREAD_CREATE_NAMED_NORMAL(PriorityHigh, "ata.disk");
status = KeCreateKernelThread((uintptr_t)AtaWorker, (uintptr_t)dev,
&workerParameters, &dev->WorkerThread);
if (!KSUCCESS(status)) {
dev->WorkerRunning = false;
AtaStopWorker(dev);
return status;
}
return NTLSTATUS_SUCCESS;
}
// Dispatches read Write.
KSTATUS AtaDispatchReadWrite(DEVICE_OBJECT* device, IRP* irp) {
if (!device || !irp || !device->DeviceExtension)
return NTLSTATUS_INVALID_PARAMETER;
ATA_DEVICE* dev = (ATA_DEVICE*)device->DeviceExtension;
if (dev->Magic != ATA_DEVICE_MAGIC || !dev->Present)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS st = IoAcquireRemoveLock(&dev->RemoveLock, irp);
if (!KSUCCESS(st))
return st;
bool write = false;
uint64_t lba = 0;
uint32_t sectors = 0;
void* buffer = (void*)0;
st = AtaGetIrpReadWrite(irp, &write, &lba, &sectors, &buffer);
(void)write;
(void)buffer;
if (!KSUCCESS(st) || lba + sectors > dev->SectorCount) {
IoReleaseRemoveLock(&dev->RemoveLock, irp);
return KSUCCESS(st) ? NTLSTATUS_INVALID_PARAMETER : st;
}
irp->Status = NTLSTATUS_FAILURE;
irp->Information = 0;
IrpQueueEnqueue(&dev->IrpQueue, irp);
return NTLSTATUS_PENDING;
}
// Queues one cache flush on the device worker.
static KSTATUS AtaDispatchFlush(DEVICE_OBJECT* device, IRP* irp) {
if (!device || !irp || !device->DeviceExtension)
return NTLSTATUS_INVALID_PARAMETER;
ATA_DEVICE* dev = (ATA_DEVICE*)device->DeviceExtension;
if (dev->Magic != ATA_DEVICE_MAGIC || !dev->Present)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS st = IoAcquireRemoveLock(&dev->RemoveLock, irp);
if (!KSUCCESS(st))
return st;
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
if (!ios || ios->MajorFunction != IRP_MJ_FLUSH_BUFFERS) {
IoReleaseRemoveLock(&dev->RemoveLock, irp);
return NTLSTATUS_INVALID_PARAMETER;
}
irp->Status = NTLSTATUS_FAILURE;
irp->Information = 0;
IrpQueueEnqueue(&dev->IrpQueue, irp);
return NTLSTATUS_PENDING;
}
// Returns free Disk Index.
static uint8_t AtaFindFreeDiskIndex(void) {
for (uint8_t i = 0; i < 8; i++) {
if (!IoDiskDevicePresent(i))
return i;
}
return 0xFFu;
}
// Implements the bar Io Base operation.
static uint16_t AtaBarIoBase(ATA_CONTROLLER* ctl, uint8_t barIndex, uint16_t fallback) {
PCI_BAR bar;
KSTATUS st = PciGetBar(ctl->Pdo, barIndex, &bar);
if (KSUCCESS(st) && bar.Type == PciBarIo && bar.Base && bar.Base <= 0xFFFFu)
return (uint16_t)bar.Base;
return fallback;
}
// Initializes channel.
static KSTATUS AtaInitChannel(ATA_CONTROLLER* ctl, uint8_t index) {
ATA_CHANNEL* ch = &ctl->Channels[index];
memset(ch, 0, sizeof(*ch));
ch->Controller = ctl;
ch->Index = index;
if (index == 0) {
ch->CommandBase = AtaBarIoBase(ctl, 0, ATA_PRIMARY_LEGACY_CMD_BASE);
ch->ControlBase = AtaBarIoBase(ctl, 1, ATA_PRIMARY_LEGACY_CTL_BASE);
ch->Irq = ATA_PRIMARY_LEGACY_IRQ;
} else {
ch->CommandBase = AtaBarIoBase(ctl, 2, ATA_SECONDARY_LEGACY_CMD_BASE);
ch->ControlBase = AtaBarIoBase(ctl, 3, ATA_SECONDARY_LEGACY_CTL_BASE);
ch->Irq = ATA_SECONDARY_LEGACY_IRQ;
}
if (ctl->PciInfo.ProgIf & (index == 0 ? 0x01u : 0x04u)) {
uint8_t irq = 0xFFu;
if (KSUCCESS(PciGetInterrupt(ctl->Pdo, &irq)) && irq != 0xFFu)
ch->Irq = irq;
ch->NativeMode = true;
}
KSTATUS st = KeSemaphoreCreate(1, 1, &ch->Lock);
if (!KSUCCESS(st))
return st;
AtaDeviceControl(ch, ATA_DEVICE_CONTROL_NIEN); // nIEN: polled I/O for now.
DbgPrint("ATA: channel %u cmd=0x%04x ctl=0x%04x irq=%u mode=%s\n",
index,
ch->CommandBase,
ch->ControlBase,
ch->Irq,
ch->NativeMode ? "native" : "compat");
return NTLSTATUS_SUCCESS;
}
// Registers disk.
static KSTATUS AtaRegisterDisk(ATA_DEVICE* dev) {
DEVICE_OBJECT* disk = (DEVICE_OBJECT*)0;
KSTATUS st = IoCreateDevice(&gAtaDriver, 0, &disk);
if (!KSUCCESS(st))
return st;
disk->DeviceExtension = dev;
disk->u.Disk.SectorCount = dev->SectorCount;
disk->u.Disk.SectorSize = ATA_SECTOR_SIZE;
disk->AlignmentRequirement = ATA_SECTOR_SIZE;
(void)IoSetDeviceName(disk, SYS_SV_LITERAL("ata-disk"));
uint8_t index = AtaFindFreeDiskIndex();
if (index == 0xFFu) {
IoDeleteDevice(disk);
return NTLSTATUS_NO_MEMORY;
}
st = IoRegisterDiskDevice(index, disk);
if (!KSUCCESS(st)) {
IoDeleteDevice(disk);
return st;
}
dev->DeviceObject = disk;
dev->DiskIndex = index;
DbgPrint("ATA: drive %u registered channel=%u %s sectors=%llu (%llu MB)\n",
index,
dev->Channel->Index,
dev->Drive ? "slave" : "master",
(unsigned long long)dev->SectorCount,
(unsigned long long)(dev->SectorCount / 2048));
return NTLSTATUS_SUCCESS;
}
// Probes drive.
static KSTATUS AtaProbeDrive(ATA_CONTROLLER* ctl, ATA_CHANNEL* ch, uint8_t drive) {
ATA_DEVICE* dev = &ch->Devices[drive];
memset(dev, 0, sizeof(*dev));
dev->Magic = ATA_DEVICE_MAGIC;
dev->Controller = ctl;
dev->Channel = ch;
dev->Drive = drive;
dev->DiskIndex = 0xFFu;
KSTATUS st = AtaIdentify(dev);
if (!KSUCCESS(st))
return st;
st = IoInitializeRemoveLock(&dev->RemoveLock);
if (!KSUCCESS(st))
return st;
SEMAPHORE* sem = (SEMAPHORE*)0;
st = KeSemaphoreCreate(0, 0x7FFFFFFF, &sem);
if (!KSUCCESS(st))
return st;
IrpQueueInit(&dev->IrpQueue, sem);
DbgPrint("ATA: channel %u %s worker start before disk publication\n",
ch->Index, drive ? "slave" : "master");
st = AtaStartWorker(dev);
if (!KSUCCESS(st)) {
DbgPrint("ATA: channel %u %s worker start failed (0x%x)\n",
ch->Index, drive ? "slave" : "master", st);
AtaStopWorker(dev);
IoDestroyRemoveLock(&dev->RemoveLock);
return st;
}
dev->Present = true;
DbgPrint("ATA: channel %u %s publishing disk sectors=%llu lba48=%u\n",
ch->Index, drive ? "slave" : "master",
(unsigned long long)dev->SectorCount, dev->SupportsLba48 ? 1u : 0u);
st = AtaRegisterDisk(dev);
if (!KSUCCESS(st)) {
DbgPrint("ATA: channel %u %s disk publication failed (0x%x)\n",
ch->Index, drive ? "slave" : "master", st);
dev->Present = false;
AtaStopWorker(dev);
IoDestroyRemoveLock(&dev->RemoveLock);
return st;
}
DbgPrint("ATA: channel %u %s probe complete disk=%u\n",
ch->Index, drive ? "slave" : "master", dev->DiskIndex);
return NTLSTATUS_SUCCESS;
}
// Registers controller.
static KSTATUS AtaRegisterController(ATA_CONTROLLER* ctl) {
if (!ctl) return NTLSTATUS_INVALID_PARAMETER;
for (uint32_t i = 0; i < gAtaControllerCount; i++) {
if (gAtaControllers[i] == ctl)
return NTLSTATUS_SUCCESS;
}
if (gAtaControllerCount >= ATA_MAX_CONTROLLERS)
return NTLSTATUS_NO_MEMORY;
gAtaControllers[gAtaControllerCount++] = ctl;
return NTLSTATUS_SUCCESS;
}
// Starts controller.
static KSTATUS AtaStartController(ATA_CONTROLLER* ctl) {
if (!ctl || ctl->Magic != ATA_CONTROLLER_MAGIC || !ctl->Pdo || !ctl->Fdo)
return NTLSTATUS_INVALID_PARAMETER;
if (ctl->Started)
return NTLSTATUS_SUCCESS;
KSTATUS st = PciGetDeviceInfo(ctl->Pdo, &ctl->PciInfo);
if (!KSUCCESS(st)) {
DbgPrint("ATA: PciGetDeviceInfo failed: 0x%x\n", st);
return st;
}
DbgPrint("ATA: controller at PCI %02x:%02x.%x vendor=%04x device=%04x progif=%02x\n",
ctl->PciInfo.Address.Bus,
ctl->PciInfo.Address.Device,
ctl->PciInfo.Address.Function,
ctl->PciInfo.VendorId,
ctl->PciInfo.DeviceId,
ctl->PciInfo.ProgIf);
st = PciEnableDevice(ctl->Pdo, PCI_ENABLE_IO | PCI_ENABLE_INTX);
if (!KSUCCESS(st)) {
DbgPrint("ATA: PciEnableDevice failed: 0x%x\n", st);
return st;
}
st = AtaRegisterController(ctl);
if (!KSUCCESS(st))
return st;
for (uint8_t chIndex = 0; chIndex < ATA_CHANNEL_COUNT; chIndex++) {
ATA_CHANNEL* ch = &ctl->Channels[chIndex];
st = AtaInitChannel(ctl, chIndex);
if (!KSUCCESS(st)) {
DbgPrint("ATA: channel %u init failed (0x%x)\n", chIndex, st);
continue;
}
st = AtaResetChannel(ch);
if (!KSUCCESS(st))
DbgPrint("ATA: channel %u reset did not complete cleanly (0x%x); probing drives with bounded waits\n",
chIndex, st);
for (uint8_t drive = 0; drive < ATA_DRIVES_PER_CHANNEL; drive++) {
DbgPrint("ATA: channel %u probing %s\n", chIndex, drive ? "slave" : "master");
st = AtaProbeDrive(ctl, ch, drive);
if (KSUCCESS(st)) {
ctl->DeviceCount++;
} else if (st != NTLSTATUS_NOT_FOUND) {
DbgPrint("ATA: channel %u %s probe failed (0x%x)\n",
chIndex, drive ? "slave" : "master", st);
}
}
}
if (ctl->DeviceCount == 0)
DbgPrint("ATA: no ATA disks found on PCI %02x:%02x.%x\n",
ctl->PciInfo.Address.Bus,
ctl->PciInfo.Address.Device,
ctl->PciInfo.Address.Function);
ctl->Started = true;
ctl->Fdo->PnpState = DevicePnpStarted;
return NTLSTATUS_SUCCESS;
}
// Cancels release Remove Lock.
static void AtaCancelReleaseRemoveLock(IRP* irp, void* context) {
ATA_DEVICE* dev = (ATA_DEVICE*)context;
if (dev)
IoReleaseRemoveLock(&dev->RemoveLock, irp);
}
// Releases controller Devices.
static void AtaRemoveControllerDevices(ATA_CONTROLLER* ctl, IO_DEVICE_CHANGE change) {
if (!ctl) return;
for (uint8_t c = 0; c < ATA_CHANNEL_COUNT; c++) {
ATA_CHANNEL* ch = &ctl->Channels[c];
for (uint8_t d = 0; d < ATA_DRIVES_PER_CHANNEL; d++) {
ATA_DEVICE* dev = &ch->Devices[d];
if (!dev->Present)
continue;
dev->Present = false;
IrpQueueCancelAllWithCallback(&dev->IrpQueue, NTLSTATUS_OBJECT_DEAD, AtaCancelReleaseRemoveLock, dev);
AtaStopWorker(dev);
if (dev->DiskIndex != 0xFFu) {
if (change == IoDeviceSurpriseRemoved)
IoNotifyDiskSurpriseRemoved(dev->DiskIndex);
else
IoUnregisterDiskDevice(dev->DiskIndex);
dev->DiskIndex = 0xFFu;
}
}
}
}
// Dispatches pnp.
KSTATUS AtaDispatchPnp(DEVICE_OBJECT* device, IRP* irp) {
if (!device || !irp || !device->DeviceExtension)
return NTLSTATUS_INVALID_PARAMETER;
ATA_CONTROLLER* ctl = (ATA_CONTROLLER*)device->DeviceExtension;
if (ctl->Magic == ATA_CONTROLLER_MAGIC) {
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
if (!ios || ios->MajorFunction != IRP_MJ_PNP)
return NTLSTATUS_INVALID_PARAMETER;
switch ((IO_PNP_MINOR_FUNCTION)ios->MinorFunction) {
case IRP_MN_START_DEVICE:
return AtaStartController(ctl);
case IRP_MN_QUERY_REMOVE_DEVICE:
case IRP_MN_CANCEL_REMOVE_DEVICE:
return NTLSTATUS_SUCCESS;
case IRP_MN_SURPRISE_REMOVAL:
ctl->SurpriseRemoved = true;
device->PnpState = DevicePnpSurpriseRemoved;
AtaRemoveControllerDevices(ctl, IoDeviceSurpriseRemoved);
return NTLSTATUS_SUCCESS;
case IRP_MN_REMOVE_DEVICE:
ctl->Removed = true;
device->PnpState = DevicePnpDeleted;
AtaRemoveControllerDevices(ctl, IoDeviceRemoved);
IoDetachDevice(device);
IoDeleteDevice(device);
return NTLSTATUS_SUCCESS;
default:
return IoForwardIrpToLower(device, irp);
}
}
ATA_DEVICE* dev = (ATA_DEVICE*)device->DeviceExtension;
if (dev->Magic == ATA_DEVICE_MAGIC) {
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
if (!ios || ios->MajorFunction != IRP_MJ_PNP)
return NTLSTATUS_INVALID_PARAMETER;
switch ((IO_PNP_MINOR_FUNCTION)ios->MinorFunction) {
case IRP_MN_START_DEVICE:
device->PnpState = DevicePnpStarted;
return NTLSTATUS_SUCCESS;
case IRP_MN_QUERY_REMOVE_DEVICE:
case IRP_MN_CANCEL_REMOVE_DEVICE:
return NTLSTATUS_SUCCESS;
case IRP_MN_SURPRISE_REMOVAL:
device->PnpState = DevicePnpSurpriseRemoved;
dev->Present = false;
IrpQueueCancelAllWithCallback(&dev->IrpQueue, NTLSTATUS_OBJECT_DEAD, AtaCancelReleaseRemoveLock, dev);
AtaStopWorker(dev);
return NTLSTATUS_SUCCESS;
case IRP_MN_REMOVE_DEVICE:
device->PnpState = DevicePnpDeleted;
dev->Present = false;
IrpQueueCancelAllWithCallback(&dev->IrpQueue, NTLSTATUS_OBJECT_DEAD, AtaCancelReleaseRemoveLock, dev);
AtaStopWorker(dev);
IoReleaseRemoveLockAndWait(&dev->RemoveLock, irp);
IoDestroyRemoveLock(&dev->RemoveLock);
IoDeleteDevice(device);
dev->DeviceObject = (DEVICE_OBJECT*)0;
return NTLSTATUS_SUCCESS;
default:
return NTLSTATUS_NOT_SUPPORTED;
}
}
return NTLSTATUS_INVALID_PARAMETER;
}
// Implements the add Device operation.
KSTATUS AtaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) {
if (!driver || !physicalDeviceObject || physicalDeviceObject->Type != DeviceObjectPhysical)
return NTLSTATUS_INVALID_PARAMETER;
DEVICE_OBJECT* fdo = (DEVICE_OBJECT*)0;
KSTATUS st = IoCreateDevice(driver, sizeof(ATA_CONTROLLER), &fdo);
if (!KSUCCESS(st))
return st;
(void)IoSetDeviceName(fdo, SYS_SV_LITERAL("ata-controller"));
ATA_CONTROLLER* ctl = (ATA_CONTROLLER*)fdo->DeviceExtension;
memset(ctl, 0, sizeof(*ctl));
ctl->Magic = ATA_CONTROLLER_MAGIC;
ctl->Fdo = fdo;
ctl->Pdo = physicalDeviceObject;
ctl->LowerDevice = IoAttachDeviceToDeviceStack(fdo, physicalDeviceObject);
if (!ctl->LowerDevice) {
IoDeleteDevice(fdo);
return NTLSTATUS_FAILURE;
}
DbgPrint("ATA: FDO attached to %v\n", physicalDeviceObject->Name);
return NTLSTATUS_SUCCESS;
}
// Implements the driver Entry operation.
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
(void)init;
if (!ddk || !ddk->Timer || !ddk->Timer->QueryTicks || !ddk->Timer->Stall)
return NTLSTATUS_INVALID_PARAMETER;
DbgPrint("ATA: driver loading...\n");
gAtaDdk = ddk;
gAtaControllerCount = 0;
memset(&gAtaDriver, 0, sizeof(gAtaDriver));
IoInitializeDriverObject(&gAtaDriver, SYS_SV_LITERAL("ata"));
gAtaDriver.IdTable = &gAtaIdTable;
gAtaDriver.DriverAddDevice = AtaAddDevice;
gAtaDriver.MajorFunction[IRP_MJ_READ] = AtaDispatchReadWrite;
gAtaDriver.MajorFunction[IRP_MJ_WRITE] = AtaDispatchReadWrite;
gAtaDriver.MajorFunction[IRP_MJ_FLUSH_BUFFERS] = AtaDispatchFlush;
gAtaDriver.MajorFunction[IRP_MJ_PNP] = AtaDispatchPnp;
KSTATUS st = IoRegisterDriverObject(&gAtaDriver);
if (!KSUCCESS(st))
DbgPrint("ATA: IoRegisterDriverObject failed: 0x%x\n", st);
return st;
}