fix(ahci): harden command submission, DMA ownership, and timeout recovery
This commit is contained in:
1 parent
d8d51ba5df
commit
3d4f846a8f
6 files changed
+471
-520
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -21,6 +21,7 @@ unit driver.storage.ctl.ahci.types;
|
|||||||
interface
|
interface
|
||||||
uses
|
uses
|
||||||
io.syslog,
|
io.syslog,
|
||||||
|
driver.storage.types,
|
||||||
driver.mgr,
|
driver.mgr,
|
||||||
driver.types,
|
driver.types,
|
||||||
driver.storage.ctl.ide.types,
|
driver.storage.ctl.ide.types,
|
||||||
@@ -251,11 +252,13 @@ type
|
|||||||
{ Callback signature fired from IRQ context when an async I/O command completes.
|
{ Callback signature fired from IRQ context when an async I/O command completes.
|
||||||
Must be very short — set a flag or post to a queue.
|
Must be very short — set a flag or post to a queue.
|
||||||
Never call kalloc/kfree/VFS from within this callback. }
|
Never call kalloc/kfree/VFS from within this callback. }
|
||||||
TIOCompletion = procedure(success : boolean; userdata : puint32);
|
TIOCompletion = procedure(error : TError; userdata : puint32);
|
||||||
|
|
||||||
{ Per-slot pending operation — one entry per HBA command slot }
|
{ Per-slot pending operation — one entry per HBA command slot }
|
||||||
TPendingOp = record
|
TPendingOp = record
|
||||||
inUse : boolean;
|
inUse : boolean;
|
||||||
|
issued : boolean;
|
||||||
|
started : uint32;
|
||||||
completion : TIOCompletion;
|
completion : TIOCompletion;
|
||||||
userdata : puint32;
|
userdata : puint32;
|
||||||
end;
|
end;
|
||||||
@@ -267,6 +270,11 @@ type
|
|||||||
}
|
}
|
||||||
TAHCI_Device = record
|
TAHCI_Device = record
|
||||||
port : PHBA_Port;
|
port : PHBA_Port;
|
||||||
|
owner : pointer; { stable PAHCI_Controller }
|
||||||
|
offline : boolean;
|
||||||
|
recovering : boolean;
|
||||||
|
recoveryTick : uint32;
|
||||||
|
recoveryError: TError;
|
||||||
device_type : TDeviceType;
|
device_type : TDeviceType;
|
||||||
ata_info : TIdentResponse;
|
ata_info : TIdentResponse;
|
||||||
supportsNCQ : boolean;
|
supportsNCQ : boolean;
|
||||||
@@ -288,6 +296,7 @@ type
|
|||||||
}
|
}
|
||||||
TAHCI_Controller = record
|
TAHCI_Controller = record
|
||||||
pci_device : PPCI_Device;
|
pci_device : PPCI_Device;
|
||||||
|
offline : boolean;
|
||||||
mio: PHBA_Memory;
|
mio: PHBA_Memory;
|
||||||
ata_info : TIdentResponse;
|
ata_info : TIdentResponse;
|
||||||
devices : array[0..31] of TAHCI_Device;
|
devices : array[0..31] of TAHCI_Device;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type
|
|||||||
PMock = ^TMock;
|
PMock = ^TMock;
|
||||||
TMock = record
|
TMock = record
|
||||||
Data : puint8;
|
Data : puint8;
|
||||||
Writes, FailWrite, Callbacks : uint32;
|
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
||||||
Error : TError;
|
Error : TError;
|
||||||
OutOfBounds, FailRead : boolean;
|
OutOfBounds, FailRead : boolean;
|
||||||
end;
|
end;
|
||||||
@@ -27,7 +27,10 @@ begin
|
|||||||
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
||||||
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
||||||
end;
|
end;
|
||||||
if (request^.RequestType <> ioWrite) and m^.FailRead then begin complete_io(request, false, eIOError); exit; end;
|
if (request^.RequestType <> ioWrite) and m^.FailRead then begin
|
||||||
|
Inc(m^.Depth); if m^.Depth > m^.MaxDepth then m^.MaxDepth := m^.Depth;
|
||||||
|
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
||||||
|
end;
|
||||||
offset := request^.LBA*512; size := request^.SectorCount*512;
|
offset := request^.LBA*512; size := request^.SectorCount*512;
|
||||||
if request^.RequestType = ioWrite then begin
|
if request^.RequestType = ioWrite then begin
|
||||||
Inc(m^.Writes);
|
Inc(m^.Writes);
|
||||||
@@ -81,7 +84,17 @@ begin
|
|||||||
device^.controllerId0 := uint32(mock); device^.sectorSize := 512;
|
device^.controllerId0 := uint32(mock); device^.sectorSize := 512;
|
||||||
device^.maxSectorCount := 256; device^.writable := true; device^.maxActive := 1;
|
device^.maxSectorCount := 256; device^.writable := true; device^.maxActive := 1;
|
||||||
device^.dispatchRead := @dispatch; device^.dispatchWrite := @dispatch;
|
device^.dispatchRead := @dispatch; device^.dispatchWrite := @dispatch;
|
||||||
device^.requestQueue := CFIFO_New(sizeof(Pointer),4);
|
device^.requestQueue := CFIFO_New(sizeof(Pointer),8);
|
||||||
|
device^.maxActive := 0; mock^.FailRead := true;
|
||||||
|
for i := 1 to 4 do
|
||||||
|
Assert(storage_read_async(device,0,1,puint32(mock^.Data),@completion,mock)=eNone,'queued fault request accepted');
|
||||||
|
Assert(mock^.Callbacks=0,'queued requests wait for dispatch');
|
||||||
|
device^.maxActive := 1;
|
||||||
|
Assert(storage_read_async(device,0,1,puint32(mock^.Data),@completion,mock)=eNone,'fault queue drain accepted');
|
||||||
|
Assert((mock^.Callbacks=5) and (mock^.Error=eIOError),'every queued error completes once');
|
||||||
|
Assert((mock^.MaxDepth=1) and (device^.activeCount=0) and (CFIFO_Size(device^.requestQueue)=0),
|
||||||
|
'inline failures drain without recursive driver dispatch');
|
||||||
|
mock^.FailRead := false; mock^.Callbacks := 0;
|
||||||
device^.cachedMBR := kalloc(512);
|
device^.cachedMBR := kalloc(512);
|
||||||
mbr := PMaster_Boot_Record(device^.cachedMBR);
|
mbr := PMaster_Boot_Record(device^.cachedMBR);
|
||||||
memset(uint32(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678;
|
memset(uint32(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678;
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ begin
|
|||||||
device^.removed := false;
|
device^.removed := false;
|
||||||
device^.requestQueue := CFIFO_New(SizeOf(PIORequest), 128);
|
device^.requestQueue := CFIFO_New(SizeOf(PIORequest), 128);
|
||||||
device^.activeCount := 0;
|
device^.activeCount := 0;
|
||||||
|
device^.dispatching := false;
|
||||||
if device^.maxActive = 0 then device^.maxActive := 1;
|
if device^.maxActive = 0 then device^.maxActive := 1;
|
||||||
device^.cachedMBR := nil;
|
device^.cachedMBR := nil;
|
||||||
device^.metadataBusy := false;
|
device^.metadataBusy := false;
|
||||||
@@ -416,6 +417,8 @@ procedure dispatch_next(device : PStorage_Device);
|
|||||||
var
|
var
|
||||||
heapReq : PIORequest;
|
heapReq : PIORequest;
|
||||||
begin
|
begin
|
||||||
|
if device^.dispatching then exit;
|
||||||
|
device^.dispatching := true;
|
||||||
{ Dispatch queued requests up to maxActive hardware slots }
|
{ Dispatch queued requests up to maxActive hardware slots }
|
||||||
while device^.activeCount < device^.maxActive do begin
|
while device^.activeCount < device^.maxActive do begin
|
||||||
if not CFIFO_Dequeue(device^.requestQueue, @heapReq) then break; { queue empty }
|
if not CFIFO_Dequeue(device^.requestQueue, @heapReq) then break; { queue empty }
|
||||||
@@ -441,12 +444,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
device^.dispatching := false;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function submit_io(request : PIORequest) : TError;
|
function submit_io(request : PIORequest) : TError;
|
||||||
var
|
var
|
||||||
device : PStorage_Device;
|
device : PStorage_Device;
|
||||||
heapReq : PIORequest;
|
heapReq : PIORequest;
|
||||||
|
flags : uint32;
|
||||||
begin
|
begin
|
||||||
{ Pre-checks — fail fast before touching the queue }
|
{ Pre-checks — fail fast before touching the queue }
|
||||||
if request = nil then begin submit_io := eInvalidArgument; exit; end;
|
if request = nil then begin submit_io := eInvalidArgument; exit; end;
|
||||||
@@ -477,38 +482,16 @@ begin
|
|||||||
|
|
||||||
{ Enqueue and attempt dispatch — must be ISR-safe and bounded.
|
{ Enqueue and attempt dispatch — must be ISR-safe and bounded.
|
||||||
Reject once the fixed queue is full instead of growing with CLI held. }
|
Reject once the fixed queue is full instead of growing with CLI held. }
|
||||||
asm pushf; cli end;
|
asm pushfd; cli; pop flags end;
|
||||||
if (device^.activeCount < device^.maxActive) and (CFIFO_Size(device^.requestQueue) = 0) then begin
|
|
||||||
device^.activeCount := device^.activeCount + 1;
|
|
||||||
heapReq^.State := iosDispatched;
|
|
||||||
asm popf end;
|
|
||||||
case heapReq^.RequestType of
|
|
||||||
ioRead: begin
|
|
||||||
if device^.dispatchRead <> nil then
|
|
||||||
device^.dispatchRead(device, heapReq)
|
|
||||||
else
|
|
||||||
complete_io(heapReq, false, eDeviceNotFound);
|
|
||||||
end;
|
|
||||||
ioWrite: begin
|
|
||||||
if device^.dispatchWrite <> nil then
|
|
||||||
device^.dispatchWrite(device, heapReq)
|
|
||||||
else
|
|
||||||
complete_io(heapReq, false, eDeviceNotFound);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
submit_io := eNone;
|
|
||||||
exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if CFIFO_Size(device^.requestQueue) >= CFIFO_Capacity(device^.requestQueue) then begin
|
if CFIFO_Size(device^.requestQueue) >= CFIFO_Capacity(device^.requestQueue) then begin
|
||||||
asm popf end;
|
if (flags and $200) <> 0 then asm sti end;
|
||||||
ioreq_free(heapReq);
|
ioreq_free(heapReq);
|
||||||
submit_io := eQueueFull;
|
submit_io := eQueueFull;
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
CFIFO_Enqueue(device^.requestQueue, @heapReq);
|
CFIFO_Enqueue(device^.requestQueue, @heapReq);
|
||||||
dispatch_next(device);
|
dispatch_next(device);
|
||||||
asm popf end;
|
if (flags and $200) <> 0 then asm sti end;
|
||||||
|
|
||||||
submit_io := eNone;
|
submit_io := eNone;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ type
|
|||||||
dispatchRead : TDriverDispatch; { nil if driver not yet migrated }
|
dispatchRead : TDriverDispatch; { nil if driver not yet migrated }
|
||||||
dispatchWrite : TDriverDispatch; { nil if driver not yet migrated }
|
dispatchWrite : TDriverDispatch; { nil if driver not yet migrated }
|
||||||
requestQueue : PCFIFOQueue; { per-device I/O request CFIFO }
|
requestQueue : PCFIFOQueue; { per-device I/O request CFIFO }
|
||||||
|
dispatching : boolean; { prevents recursive synchronous-error queue draining }
|
||||||
activeCount : uint32; { number of commands currently dispatched to HW }
|
activeCount : uint32; { number of commands currently dispatched to HW }
|
||||||
maxActive : uint32; { max concurrent commands (NCQ depth) }
|
maxActive : uint32; { max concurrent commands (NCQ depth) }
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ uses
|
|||||||
driver.storage.vfs,
|
driver.storage.vfs,
|
||||||
driver.storage.test,
|
driver.storage.test,
|
||||||
driver.storage.metatest,
|
driver.storage.metatest,
|
||||||
|
driver.storage.ctl.ahci,
|
||||||
core.enc.fnv1a,
|
core.enc.fnv1a,
|
||||||
core.enc.djb2,
|
core.enc.djb2,
|
||||||
core.ds.bloom,
|
core.ds.bloom,
|
||||||
@@ -70,6 +71,7 @@ begin
|
|||||||
driver.storage.vfs.UnitTest;
|
driver.storage.vfs.UnitTest;
|
||||||
driver.storage.test.UnitTest;
|
driver.storage.test.UnitTest;
|
||||||
driver.storage.metatest.UnitTest;
|
driver.storage.metatest.UnitTest;
|
||||||
|
driver.storage.ctl.ahci.UnitTest;
|
||||||
core.enc.fnv1a.UnitTest;
|
core.enc.fnv1a.UnitTest;
|
||||||
core.enc.djb2.UnitTest;
|
core.enc.djb2.UnitTest;
|
||||||
core.ds.bloom.UnitTest;
|
core.ds.bloom.UnitTest;
|
||||||
|
|||||||
Reference in new issue
Block a user