Async IO, IOAPIC and improvements
This commit is contained in:
1 parent
81d1b97cd0
commit
64a83a6c83
15 files changed
+782
-348
No files matched your search
+262
-277
File diff suppressed because it is too large.
Load diff
@@ -248,24 +248,42 @@ type
|
|||||||
PM
|
PM
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{ 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.
|
||||||
|
Never call kalloc/kfree/VFS from within this callback. }
|
||||||
|
TIOCompletion = procedure(success : boolean; userdata : puint32);
|
||||||
|
|
||||||
|
{ Per-slot pending operation — one entry per HBA command slot }
|
||||||
|
TPendingOp = record
|
||||||
|
inUse : boolean;
|
||||||
|
completion : TIOCompletion;
|
||||||
|
userdata : puint32;
|
||||||
|
end;
|
||||||
|
|
||||||
{
|
{
|
||||||
AHCI device reference
|
AHCI device reference.
|
||||||
|
Declared as a plain record (not bitpacked) so that procedure-pointer fields
|
||||||
|
in TPendingOp are safe to store and call.
|
||||||
}
|
}
|
||||||
TAHCI_Device = bitpacked record
|
TAHCI_Device = record
|
||||||
port : PHBA_Port;
|
port : PHBA_Port;
|
||||||
device_type : TDeviceType;
|
device_type : TDeviceType;
|
||||||
ata_info : TIdentResponse;
|
ata_info : TIdentResponse;
|
||||||
command_list : PHBA_CMD_HEADER;
|
command_list : PHBA_CMD_HEADER;
|
||||||
fis : PHBA_FIS;
|
fis : PHBA_FIS;
|
||||||
command_table : PHBA_CMD_TABLE;
|
command_table: PHBA_CMD_TABLE;
|
||||||
|
{ One pending-operation slot per HBA command slot (AHCI supports up to 32) }
|
||||||
|
pending : array[0..31] of TPendingOp;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
PAHCI_Device = ^TAHCI_Device;
|
PAHCI_Device = ^TAHCI_Device;
|
||||||
|
|
||||||
{
|
{
|
||||||
controller reference
|
controller reference
|
||||||
|
Declared as a plain record (not bitpacked) because it embeds TAHCI_Device
|
||||||
|
which contains procedure-pointer fields.
|
||||||
}
|
}
|
||||||
TAHCI_Controller = bitpacked record
|
TAHCI_Controller = record
|
||||||
pci_device : PPCI_Device;
|
pci_device : PPCI_Device;
|
||||||
mio: PHBA_Memory;
|
mio: PHBA_Memory;
|
||||||
ata_info : TIdentResponse;
|
ata_info : TIdentResponse;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# AHCI Driver Notes
|
||||||
|
|
||||||
|
## Pre-scheduling / Process Kill Hazards
|
||||||
|
|
||||||
|
### 1. Dangling DMA Buffer (critical)
|
||||||
|
When a process is killed, its `kalloc`'d buffers get freed. But the AHCI hardware
|
||||||
|
is still DMA'ing into that physical memory. The DMA write lands on whatever now
|
||||||
|
occupies that memory, silently corrupting the kernel heap or another process.
|
||||||
|
|
||||||
|
**Fix:** Before freeing a process's memory, either:
|
||||||
|
- Wait for all its pending AHCI slots to drain (`cmd_issue` bits clear), or
|
||||||
|
- Issue a port reset to abort in-flight commands, *then* free memory.
|
||||||
|
|
||||||
|
### 2. Dangling Completion Callback / Userdata Pointer (critical)
|
||||||
|
In `storage_read`/`storage_write`, the completion callback writes to `@done` —
|
||||||
|
a **stack-local variable**. If the scheduler preempts or kills the process while
|
||||||
|
it's in the `hlt` loop, that stack frame is gone. The ISR fires and writes to a
|
||||||
|
dangling stack address, corrupting whatever now uses that stack page.
|
||||||
|
|
||||||
|
**Fix:** The `done` flag and completion context must be heap-allocated (or in a
|
||||||
|
kernel-owned structure that outlives the process). A per-request struct like:
|
||||||
|
```pascal
|
||||||
|
type TPendingIO = record
|
||||||
|
done : uint32;
|
||||||
|
ownerPID : uint32; { skip delivery if process is dead }
|
||||||
|
end;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Command Slot Leak
|
||||||
|
If a process is killed with pending I/O, `pending[slot].inUse` stays `true`
|
||||||
|
forever. Eventually all 32 slots fill up and the device becomes unusable.
|
||||||
|
|
||||||
|
**Fix:** When killing a process, scan all AHCI devices for `pending[]` entries
|
||||||
|
belonging to that process, and either:
|
||||||
|
- Let the hardware finish but null out the completion callback (ISR just frees slot), or
|
||||||
|
- Abort the command (port reset) and clear the slot.
|
||||||
|
|
||||||
|
### 4. Interrupt-Disabled Preemption
|
||||||
|
The `asm sti` before the `hlt` loop is fine, but if the timer fires during setup
|
||||||
|
(between `readCallbackAsync(...)` and `asm sti`), the process could be switched
|
||||||
|
out with interrupts in an unexpected state. `hlt` is also not preemption-safe.
|
||||||
|
|
||||||
|
**Fix:** Move to a proper wait-queue/sleep mechanism instead of `hlt`.
|
||||||
|
|
||||||
|
## Recommended Path Forward
|
||||||
|
Implement a small **I/O request queue** owned by the kernel (not the process stack):
|
||||||
|
1. Allocate a request struct
|
||||||
|
2. Put it on a per-device queue
|
||||||
|
3. ISR completes it
|
||||||
|
4. Process kill walks the queue and cancels entries for that PID
|
||||||
|
|
||||||
|
## Other Bugs Fixed (2026-03-03)
|
||||||
|
- **vtop mask bug:** `$FFFFFF` (24-bit) → `$3FFFFF` (22-bit) for 4MB page offset
|
||||||
|
- **find_cmd_slot:** Now checks `pending[].inUse` in addition to `cmd_issue` bit
|
||||||
|
- **Missing lba5:** Both `send_read_dma_async` and `send_write_dma_async` now write all 48 LBA bits
|
||||||
|
- **IOAPIC routing:** PCI interrupts routed through IOAPIC so AHCI interrupts actually reach the CPU
|
||||||
|
- **LAPIC EOI:** `ISR_N` now sends LAPIC EOI for IOAPIC-delivered interrupts
|
||||||
|
- **PxIS pre-clear removed:** No longer blanket-clears port interrupt status before issuing commands (was eating other slots' completions)
|
||||||
@@ -28,6 +28,7 @@ uses
|
|||||||
rtc,
|
rtc,
|
||||||
serial,
|
serial,
|
||||||
storagemanagement,
|
storagemanagement,
|
||||||
|
storagemanager,
|
||||||
strings,
|
strings,
|
||||||
terminal,
|
terminal,
|
||||||
tracer,
|
tracer,
|
||||||
@@ -100,7 +101,7 @@ begin
|
|||||||
filesystemRecord^.endOfData := filesystemRecord.sectorsPerTable;
|
filesystemRecord^.endOfData := filesystemRecord.sectorsPerTable;
|
||||||
//filesystemRecord.volumeLabel := config^
|
//filesystemRecord.volumeLabel := config^
|
||||||
|
|
||||||
disk^.writecallback(disk, start, 1, buffer);
|
storagemanager.storage_write(disk, start, 1, buffer);
|
||||||
memset(uint32(buffer), 0, disk^.sectorSize);
|
memset(uint32(buffer), 0, disk^.sectorSize);
|
||||||
|
|
||||||
fileEntry := PFileEntry(buffer)[0];
|
fileEntry := PFileEntry(buffer)[0];
|
||||||
@@ -113,7 +114,7 @@ begin
|
|||||||
fileEntry^.attributes := 0;
|
fileEntry^.attributes := 0;
|
||||||
fileEntry^.location := 1;
|
fileEntry^.location := 1;
|
||||||
|
|
||||||
disk^.writecallback(disk, start + 1, 1, buffer);
|
storagemanager.storage_write(disk, start + 1, 1, buffer);
|
||||||
|
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,7 @@ uses
|
|||||||
filesystemmanager,
|
filesystemmanager,
|
||||||
lists,
|
lists,
|
||||||
lmemorymanager,
|
lmemorymanager,
|
||||||
|
storagemanager,
|
||||||
storagetypes,
|
storagetypes,
|
||||||
strings,
|
strings,
|
||||||
tracer,
|
tracer,
|
||||||
@@ -88,7 +89,7 @@ var
|
|||||||
begin
|
begin
|
||||||
buf := puint32(kalloc(count * device^.sectorSize));
|
buf := puint32(kalloc(count * device^.sectorSize));
|
||||||
memset(uint32(buf), 0, count * device^.sectorSize);
|
memset(uint32(buf), 0, count * device^.sectorSize);
|
||||||
device^.readCallback(device, lba, count, buf);
|
storagemanager.storage_read(device, lba, count, buf);
|
||||||
readSectors := buf;
|
readSectors := buf;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -324,7 +325,7 @@ var
|
|||||||
begin
|
begin
|
||||||
identify_volume := false;
|
identify_volume := false;
|
||||||
if volume^.device = nil then exit;
|
if volume^.device = nil then exit;
|
||||||
if volume^.device^.readCallback = nil then exit;
|
if (volume^.device^.readCallback = nil) and (volume^.device^.readCallbackAsync = nil) then exit;
|
||||||
|
|
||||||
buf := readSectors(volume^.device, volume^.sectorStart + PVD_SECTOR, 1);
|
buf := readSectors(volume^.device, volume^.sectorStart + PVD_SECTOR, 1);
|
||||||
identify_volume := isPVD(buf);
|
identify_volume := isPVD(buf);
|
||||||
@@ -339,7 +340,7 @@ var
|
|||||||
volume : PStorage_Volume;
|
volume : PStorage_Volume;
|
||||||
begin
|
begin
|
||||||
if disk = nil then exit;
|
if disk = nil then exit;
|
||||||
if disk^.readCallback = nil then exit;
|
if (disk^.readCallback = nil) and (disk^.readCallbackAsync = nil) then exit;
|
||||||
|
|
||||||
buf := readSectors(disk, PVD_SECTOR, 1);
|
buf := readSectors(disk, PVD_SECTOR, 1);
|
||||||
if not isPVD(buf) then begin
|
if not isPVD(buf) then begin
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ function controller_type_2_string(controllerType : TControllerType) : pchar;
|
|||||||
function read_mbr(device : PStorage_Device) : PMaster_Boot_Record;
|
function read_mbr(device : PStorage_Device) : PMaster_Boot_Record;
|
||||||
procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record);
|
procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record);
|
||||||
|
|
||||||
|
{ Block I/O helpers — prefer the async path (interrupt-driven wait) when
|
||||||
|
available, falling back to the legacy sync callback for non-AHCI devices.
|
||||||
|
These are meant as a transitional bridge; callers should migrate to the
|
||||||
|
async hooks with their own completion callbacks over time. }
|
||||||
|
procedure storage_read(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
||||||
|
procedure storage_write(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
procedure init();
|
procedure init();
|
||||||
@@ -83,14 +90,56 @@ function read_mbr(device : PStorage_Device) : PMaster_Boot_Record;
|
|||||||
begin
|
begin
|
||||||
push_trace('StorageManager.read_mbr');
|
push_trace('StorageManager.read_mbr');
|
||||||
read_mbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record)));
|
read_mbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record)));
|
||||||
device^.readCallback(device, 0, 1, puint32(read_mbr));
|
storage_read(device, 0, 1, puint32(read_mbr));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record);
|
procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record);
|
||||||
begin
|
begin
|
||||||
push_trace('StorageManager.write_mbr');
|
push_trace('StorageManager.write_mbr');
|
||||||
if not device^.writable then exit;
|
if not device^.writable then exit;
|
||||||
device^.writeCallback(device, 0, 1, puint32(mbr));
|
storage_write(device, 0, 1, puint32(mbr));
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Completion callback for storage_read/storage_write — sets flag so hlt loop exits }
|
||||||
|
procedure storage_sync_done(success : boolean; userdata : puint32);
|
||||||
|
begin
|
||||||
|
userdata^ := 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure storage_read(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
||||||
|
var
|
||||||
|
done : uint32;
|
||||||
|
begin
|
||||||
|
if device^.readCallbackAsync <> nil then begin
|
||||||
|
done := 0;
|
||||||
|
device^.readCallbackAsync(device, addr, sectors, buffer, @storage_sync_done, @done);
|
||||||
|
asm sti end;
|
||||||
|
while done = 0 do begin
|
||||||
|
asm hlt end; { sleep until next interrupt }
|
||||||
|
{ Fallback: if the hardware IRQ was not delivered through the PIC,
|
||||||
|
poll the driver inline so the completion fires. }
|
||||||
|
if (done = 0) and (device^.pollCallback <> nil) then
|
||||||
|
device^.pollCallback();
|
||||||
|
end;
|
||||||
|
end else if device^.readCallback <> nil then
|
||||||
|
device^.readCallback(device, addr, sectors, buffer);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure storage_write(device : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32);
|
||||||
|
var
|
||||||
|
done : uint32;
|
||||||
|
begin
|
||||||
|
if device^.writeCallbackAsync <> nil then begin
|
||||||
|
done := 0;
|
||||||
|
device^.writeCallbackAsync(device, addr, sectors, buffer, @storage_sync_done, @done);
|
||||||
|
asm sti end;
|
||||||
|
while done = 0 do begin
|
||||||
|
asm hlt end; { sleep until next interrupt }
|
||||||
|
if (done = 0) and (device^.pollCallback <> nil) then
|
||||||
|
device^.pollCallback();
|
||||||
|
end;
|
||||||
|
end else if device^.writeCallback <> nil then
|
||||||
|
device^.writeCallback(device, addr, sectors, buffer);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
@@ -70,6 +70,19 @@ type
|
|||||||
PPHIOHook = procedure(drive : PStorage_device; addr : uint32; sectors : uint32; buffer : puint32);
|
PPHIOHook = procedure(drive : PStorage_device; addr : uint32; sectors : uint32; buffer : puint32);
|
||||||
PPHIOHook_ = procedure;
|
PPHIOHook_ = procedure;
|
||||||
|
|
||||||
|
{ Completion callback fired from IRQ context after async block I/O.
|
||||||
|
Must be short — set a flag or post to a queue.
|
||||||
|
Never call kalloc/kfree/VFS from within this callback. }
|
||||||
|
TStorageCompletion = procedure(success : boolean; userdata : puint32);
|
||||||
|
|
||||||
|
{ Async block I/O callback — issues the command and returns immediately;
|
||||||
|
calls completion from IRQ context when done. }
|
||||||
|
PPHIOHookAsync = procedure(drive : PStorage_Device; addr : uint32; sectors : uint32; buffer : puint32; completion : TStorageCompletion; userdata : puint32);
|
||||||
|
|
||||||
|
{ Poll callback — called by sync bridge after each hlt wake-up to process
|
||||||
|
completions when the hardware interrupt isn't delivered through the PIC. }
|
||||||
|
PPPollHook = procedure;
|
||||||
|
|
||||||
{ Filesystem callback types }
|
{ Filesystem callback types }
|
||||||
PPWriteHook = procedure(volume : PStorage_volume; directory : pchar; entry : PDirectory_Entry; byteCount : uint32; buffer : puint32; statusOut : puint32);
|
PPWriteHook = procedure(volume : PStorage_volume; directory : pchar; entry : PDirectory_Entry; byteCount : uint32; buffer : puint32; statusOut : puint32);
|
||||||
PPReadHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; buffer : puint32; bytecount : puint32) : uint32;
|
PPReadHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; buffer : puint32; bytecount : puint32) : uint32;
|
||||||
@@ -80,6 +93,11 @@ type
|
|||||||
PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32);
|
PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32);
|
||||||
PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32);
|
PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32);
|
||||||
PPIdentifyHook = function(volume : PStorage_Volume) : boolean;
|
PPIdentifyHook = function(volume : PStorage_Volume) : boolean;
|
||||||
|
{ Offset-based read: reads byteCount bytes starting at byte offset into the file.
|
||||||
|
Returns the number of bytes actually read.
|
||||||
|
Filesystems that do not implement this leave the field nil and VFS falls back
|
||||||
|
to the load-all readCallback behaviour. }
|
||||||
|
PPReadOffsetHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; offset : uint32; buffer : puint32; byteCount : uint32) : uint32;
|
||||||
|
|
||||||
byteArray8 = array[0..7] of char;
|
byteArray8 = array[0..7] of char;
|
||||||
PByteArray8 = ^byteArray8;
|
PByteArray8 = ^byteArray8;
|
||||||
@@ -93,6 +111,13 @@ type
|
|||||||
volumes : PDList;
|
volumes : PDList;
|
||||||
writeCallback : PPHIOHook;
|
writeCallback : PPHIOHook;
|
||||||
readCallback : PPHIOHook;
|
readCallback : PPHIOHook;
|
||||||
|
{ Async block I/O callbacks — nil if the driver does not support async }
|
||||||
|
readCallbackAsync : PPHIOHookAsync;
|
||||||
|
writeCallbackAsync : PPHIOHookAsync;
|
||||||
|
{ Poll callback — processes pending completions inline when the
|
||||||
|
hardware interrupt is not delivered. Called from storage_read/write
|
||||||
|
sync bridge after each hlt wake-up. nil if not needed. }
|
||||||
|
pollCallback : PPPollHook;
|
||||||
maxSectorCount : uint32;
|
maxSectorCount : uint32;
|
||||||
sectorSize : uint32; //in bytes
|
sectorSize : uint32; //in bytes
|
||||||
start : uint32; //start of device in sectors
|
start : uint32; //start of device in sectors
|
||||||
@@ -111,6 +136,8 @@ type
|
|||||||
deleteFileCallback : PPDeleteFileHook;
|
deleteFileCallback : PPDeleteFileHook;
|
||||||
deleteDirCallback : PPDeleteDirHook;
|
deleteDirCallback : PPDeleteDirHook;
|
||||||
identifyCallback : PPIdentifyHook;
|
identifyCallback : PPIdentifyHook;
|
||||||
|
{ Offset-based read for streaming mode — nil if not implemented }
|
||||||
|
readOffsetCallback : PPReadOffsetHook;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Generic storage volume }
|
{ Generic storage volume }
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ uses
|
|||||||
tracer;
|
tracer;
|
||||||
|
|
||||||
type
|
type
|
||||||
TOpenMode = (omReadOnly, omWriteOnly, omReadWrite);
|
TOpenMode = (omReadOnly, omWriteOnly, omReadWrite, omStream);
|
||||||
TWriteMode = (wmRewrite, wmAppend, wmNew);
|
TWriteMode = (wmRewrite, wmAppend, wmNew);
|
||||||
TIsPathValid = (pvInvalid, pvFile, pvDirectory);
|
TIsPathValid = (pvInvalid, pvFile, pvDirectory);
|
||||||
TRegError = (pvUnknown, pvNotRegistered, pvRegistered, pvUnregistered);
|
TRegError = (pvUnknown, pvNotRegistered, pvRegistered, pvUnregistered);
|
||||||
@@ -486,9 +486,11 @@ type
|
|||||||
fileName : pchar; { Full filename including extension, e.g. 'README.TXT' }
|
fileName : pchar; { Full filename including extension, e.g. 'README.TXT' }
|
||||||
openMode : TOpenMode;
|
openMode : TOpenMode;
|
||||||
writeMode : TWriteMode;
|
writeMode : TWriteMode;
|
||||||
dataBuffer : puint32; { Pointer to loaded data }
|
dataBuffer : puint32; { Pointer to loaded data (nil for omStream) }
|
||||||
dataSize : uint32; { Size of loaded data in bytes }
|
dataSize : uint32; { Size of loaded data in bytes }
|
||||||
loaded : boolean; { True if data has been read from disk }
|
loaded : boolean; { True if data has been read from disk }
|
||||||
|
{ Current byte position for omStream mode; 0 on open, advanced by each ReadFile call }
|
||||||
|
streamOffset : uint32;
|
||||||
end;
|
end;
|
||||||
POpenFileEntry = ^TOpenFileEntry;
|
POpenFileEntry = ^TOpenFileEntry;
|
||||||
|
|
||||||
@@ -656,6 +658,7 @@ begin
|
|||||||
OpenFiles[slot].dataBuffer := nil;
|
OpenFiles[slot].dataBuffer := nil;
|
||||||
OpenFiles[slot].dataSize := 0;
|
OpenFiles[slot].dataSize := 0;
|
||||||
OpenFiles[slot].loaded := false;
|
OpenFiles[slot].loaded := false;
|
||||||
|
OpenFiles[slot].streamOffset := 0;
|
||||||
|
|
||||||
{ If reading, load the file data now }
|
{ If reading, load the file data now }
|
||||||
if (OpenMode = omReadOnly) or (OpenMode = omReadWrite) then begin
|
if (OpenMode = omReadOnly) or (OpenMode = omReadWrite) then begin
|
||||||
@@ -691,6 +694,7 @@ begin
|
|||||||
kfree(puint32(dataSize));
|
kfree(puint32(dataSize));
|
||||||
end;
|
end;
|
||||||
end else begin
|
end else begin
|
||||||
|
{ Write-only or streaming mode — no pre-load }
|
||||||
if Error <> nil then Error^ := eNone;
|
if Error <> nil then Error^ := eNone;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -767,6 +771,21 @@ begin
|
|||||||
entry := @OpenFiles[idx];
|
entry := @OpenFiles[idx];
|
||||||
|
|
||||||
if not entry^.inUse then exit;
|
if not entry^.inUse then exit;
|
||||||
|
|
||||||
|
{ Streaming mode: call readOffsetCallback on demand — no pre-loaded buffer needed }
|
||||||
|
if entry^.openMode = omStream then begin
|
||||||
|
if Buffer = nil then exit;
|
||||||
|
if entry^.volume = nil then exit;
|
||||||
|
if entry^.volume^.filesystem = nil then exit;
|
||||||
|
if entry^.volume^.filesystem^.readOffsetCallback = nil then exit;
|
||||||
|
ReadFile := entry^.volume^.filesystem^.readOffsetCallback(
|
||||||
|
entry^.volume, entry^.directory, entry^.fileName,
|
||||||
|
entry^.streamOffset, puint32(Buffer), Length);
|
||||||
|
entry^.streamOffset := entry^.streamOffset + ReadFile;
|
||||||
|
tracer.push_trace('vfs.ReadFile.exit');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
if not entry^.loaded then exit;
|
if not entry^.loaded then exit;
|
||||||
if entry^.dataBuffer = nil then exit;
|
if entry^.dataBuffer = nil then exit;
|
||||||
if Buffer = nil then exit;
|
if Buffer = nil then exit;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
ISR->IOAPIC - I/O APIC and Local APIC minimal support.
|
||||||
|
|
||||||
|
On systems where VirtualBox (or real hardware) routes PCI interrupts
|
||||||
|
through the I/O APIC instead of the legacy 8259 PIC, we must program
|
||||||
|
the IOAPIC redirection entry for each PCI IRQ we want to receive.
|
||||||
|
|
||||||
|
ISA interrupts (timer, keyboard) continue to work through the 8259
|
||||||
|
PIC via Virtual Wire mode (PIC -> LINT0 -> Local APIC -> CPU).
|
||||||
|
|
||||||
|
@author(Aaron Hance <[email protected]>)
|
||||||
|
}
|
||||||
|
unit ioapic;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
util,
|
||||||
|
lmemorymanager,
|
||||||
|
console;
|
||||||
|
|
||||||
|
{ Initialise IOAPIC/LAPIC support — maps MMIO regions, ensures LAPIC
|
||||||
|
is enabled. Safe to call even if the hardware doesn't have an IOAPIC
|
||||||
|
(will detect and bail out). }
|
||||||
|
procedure init();
|
||||||
|
|
||||||
|
{ Route a PCI interrupt through the IOAPIC.
|
||||||
|
irq — PCI IRQ number (the value from PCI config interrupt_line)
|
||||||
|
vector — IDT vector to deliver (typically irq + 32) }
|
||||||
|
procedure route_pci_irq(irq : uint8; vector : uint8);
|
||||||
|
|
||||||
|
{ Send End-of-Interrupt to the Local APIC.
|
||||||
|
Must be called for every IOAPIC-delivered (Fixed mode) interrupt.
|
||||||
|
Safe no-op if IOAPIC support was not initialised. }
|
||||||
|
procedure lapic_eoi();
|
||||||
|
|
||||||
|
{ True after init() succeeds — ISR_N checks this to know whether LAPIC
|
||||||
|
EOI is needed. }
|
||||||
|
var
|
||||||
|
ioapic_present : boolean;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
const
|
||||||
|
IOAPIC_PHYS = $FEC00000;
|
||||||
|
LAPIC_PHYS = $FEE00000;
|
||||||
|
|
||||||
|
{ IOAPIC register offsets (indirect via IOREGSEL / IOWIN) }
|
||||||
|
IOREGSEL = $00;
|
||||||
|
IOWIN = $10;
|
||||||
|
|
||||||
|
{ IOAPIC registers }
|
||||||
|
IOAPIC_ID = $00;
|
||||||
|
IOAPIC_VER = $01;
|
||||||
|
IOAPIC_REDTBL = $10; { base — each entry is 2 dwords }
|
||||||
|
|
||||||
|
{ LAPIC register offsets }
|
||||||
|
LAPIC_SVR = $F0; { Spurious Interrupt Vector Register }
|
||||||
|
LAPIC_EOI_REG = $B0; { End-of-Interrupt register }
|
||||||
|
|
||||||
|
{ ---- low-level IOAPIC register access ---- }
|
||||||
|
|
||||||
|
procedure ioapic_write(reg : uint32; val : uint32);
|
||||||
|
begin
|
||||||
|
puint32(IOAPIC_PHYS + IOREGSEL)^ := reg;
|
||||||
|
puint32(IOAPIC_PHYS + IOWIN)^ := val;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function ioapic_read(reg : uint32) : uint32;
|
||||||
|
begin
|
||||||
|
puint32(IOAPIC_PHYS + IOREGSEL)^ := reg;
|
||||||
|
ioapic_read := puint32(IOAPIC_PHYS + IOWIN)^;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ ---- public API ---- }
|
||||||
|
|
||||||
|
procedure init();
|
||||||
|
var
|
||||||
|
ver : uint32;
|
||||||
|
svr : uint32;
|
||||||
|
begin
|
||||||
|
ioapic_present := false;
|
||||||
|
|
||||||
|
{ Map the IOAPIC and LAPIC MMIO pages so we can access them }
|
||||||
|
kpalloc(IOAPIC_PHYS);
|
||||||
|
kpalloc(LAPIC_PHYS);
|
||||||
|
|
||||||
|
{ Probe: read IOAPIC version register — should return non-zero/non-$FF }
|
||||||
|
ver := ioapic_read(IOAPIC_VER);
|
||||||
|
if (ver = 0) or (ver = $FFFFFFFF) then begin
|
||||||
|
console.outputln('IOAPIC', 'Not detected — PCI interrupts rely on 8259 PIC.');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
console.outputln('IOAPIC', 'Detected.');
|
||||||
|
|
||||||
|
{ Ensure the Local APIC is software-enabled (bit 8 of SVR).
|
||||||
|
We set the spurious vector to $FF which is unused. }
|
||||||
|
svr := puint32(LAPIC_PHYS + LAPIC_SVR)^;
|
||||||
|
if (svr and $100) = 0 then begin
|
||||||
|
svr := svr or $100; { Enable APIC }
|
||||||
|
svr := (svr and $FFFFFF00) or $FF; { Spurious vector = 0xFF }
|
||||||
|
puint32(LAPIC_PHYS + LAPIC_SVR)^ := svr;
|
||||||
|
console.outputln('IOAPIC', 'LAPIC software-enabled.');
|
||||||
|
end;
|
||||||
|
|
||||||
|
ioapic_present := true;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure route_pci_irq(irq : uint8; vector : uint8);
|
||||||
|
var
|
||||||
|
lo : uint32;
|
||||||
|
begin
|
||||||
|
if not ioapic_present then exit;
|
||||||
|
|
||||||
|
{ Build low dword of redirection entry:
|
||||||
|
bits 7:0 — delivery vector
|
||||||
|
bits 10:8 — delivery mode (000 = Fixed)
|
||||||
|
bit 11 — destination mode (0 = Physical)
|
||||||
|
bit 13 — pin polarity (1 = active-low for PCI)
|
||||||
|
bit 15 — trigger mode (1 = level for PCI)
|
||||||
|
bit 16 — mask (0 = not masked) }
|
||||||
|
lo := uint32(vector);
|
||||||
|
lo := lo or (1 shl 13); { active-low }
|
||||||
|
lo := lo or (1 shl 15); { level-triggered }
|
||||||
|
|
||||||
|
{ Write low dword first, then high dword (dest APIC = 0, the BSP) }
|
||||||
|
ioapic_write(IOAPIC_REDTBL + uint32(irq) * 2, lo);
|
||||||
|
ioapic_write(IOAPIC_REDTBL + uint32(irq) * 2 + 1, 0);
|
||||||
|
|
||||||
|
console.output('IOAPIC', 'Routed IRQ ');
|
||||||
|
console.writeint(irq);
|
||||||
|
console.writestring(' -> vector ');
|
||||||
|
console.writeint(vector);
|
||||||
|
console.writestringln('');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure lapic_eoi();
|
||||||
|
begin
|
||||||
|
if ioapic_present then
|
||||||
|
puint32(LAPIC_PHYS + LAPIC_EOI_REG)^ := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -34,7 +34,7 @@ procedure registerISR(INT_N : uint8; callback : TISRHook);
|
|||||||
|
|
||||||
implementation
|
implementation
|
||||||
uses
|
uses
|
||||||
console;
|
console, ioapic;
|
||||||
|
|
||||||
var
|
var
|
||||||
Hooks : TISRHookArray;
|
Hooks : TISRHookArray;
|
||||||
@@ -68,6 +68,10 @@ begin
|
|||||||
for i:=0 to MAX_HOOKS do begin
|
for i:=0 to MAX_HOOKS do begin
|
||||||
if Hooks[INT_N][i] <> nil then Hooks[INT_N][i]();
|
if Hooks[INT_N][i] <> nil then Hooks[INT_N][i]();
|
||||||
end;
|
end;
|
||||||
|
{ Send EOI to Local APIC (needed for IOAPIC-delivered interrupts).
|
||||||
|
Safe no-op when IOAPIC is not present. }
|
||||||
|
ioapic.lapic_eoi();
|
||||||
|
{ Send EOI to 8259 PIC (slave then master) }
|
||||||
if (INT_N >= 40) then outb($A0, $20);
|
if (INT_N >= 40) then outb($A0, $20);
|
||||||
outb($20, $20);
|
outb($20, $20);
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ begin
|
|||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if device^.writeCallback = nil then begin
|
if (device^.writeCallback = nil) and (device^.writeCallbackAsync = nil) then begin
|
||||||
println('Error: device has no write support.');
|
println('Error: device has no write support.');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
@@ -257,7 +257,7 @@ begin
|
|||||||
else
|
else
|
||||||
batch := remain;
|
batch := remain;
|
||||||
|
|
||||||
device^.writeCallback(device, sector, batch, buf);
|
storagemanager.storage_write(device, sector, batch, buf);
|
||||||
sector := sector + batch;
|
sector := sector + batch;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ begin
|
|||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if device^.readCallback = nil then begin
|
if (device^.readCallback = nil) and (device^.readCallbackAsync = nil) then begin
|
||||||
println('Error: device has no read support.');
|
println('Error: device has no read support.');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
@@ -276,7 +276,7 @@ begin
|
|||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if device^.writeCallback = nil then begin
|
if (device^.writeCallback = nil) and (device^.writeCallbackAsync = nil) then begin
|
||||||
println('Error: device has no write support.');
|
println('Error: device has no write support.');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
@@ -392,7 +392,7 @@ begin
|
|||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if device^.writeCallback = nil then begin
|
if (device^.writeCallback = nil) and (device^.writeCallbackAsync = nil) then begin
|
||||||
println('Error: device has no write support.');
|
println('Error: device has no write support.');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ begin
|
|||||||
push_trace('vmemorymanager.vtop');
|
push_trace('vmemorymanager.vtop');
|
||||||
idx:= address SHR 22;
|
idx:= address SHR 22;
|
||||||
paddress:= uint32(KERNEL_PAGE_DIRECTORY^[idx].address) SHL 12;
|
paddress:= uint32(KERNEL_PAGE_DIRECTORY^[idx].address) SHL 12;
|
||||||
loadd:= address AND $FFFFFF;
|
loadd:= address AND $3FFFFF;
|
||||||
vtop:= paddress + loadd;
|
vtop:= paddress + loadd;
|
||||||
pop_trace;
|
pop_trace;
|
||||||
end;
|
end;
|
||||||
|
|||||||
Reference in new issue
Block a user