Compare commits

..
6 Commits
Author SHA1 Message Date
Aaron 3a733b1559 feat: auto-mount boot drive to /boot; fix FAT32 deleted entry triple fault
continuous-integration/drone/push Build is passing
- Decode multiboot boot_device byte in storagemanager to identify boot device
- Add isBootDevice to TStorage_Device; propagate isBootDrive to volumes
- Create /boot VFS directory at init; mount boot volume there in auto_mount_volumes
- Fix getDirEntries skipping \-marked (deleted) FAT32 entries to prevent
  triple fault when navigating into a deleted directory via file picker
2026-03-07 18:58:32 +00:00
Aaron deb513c925 storage: fix VFS corruption, ISO9660 hang, OOM; add notepad, file picker, fdtable, GPT, async I/O
VFS fixes:
- GetDirectoryListing(otVDIRECTORY) now returns a heap-allocated snapshot
  instead of a raw live-tree pointer; FreeDirectoryListing no longer
  corrupts the VFS tree (fixes page fault on repeated navigation)
- volumeGetDirectories: guard LL_Size > 0 before loop to prevent uint32
  underflow (0-1 = 0xFFFFFFFF) causing 4B kalloc iterations -> OOM
- Free entry^.fileName and dirList in all branches (memory leak fix)

iso9660:
- equalsIgnoreCase: guard la=0 before loop (same uint32 underflow bug)
- readDirectoryEntries/resolvePath: add 65536/256-iter safety caps

storagemanager:
- Strip debug syslog calls that were allocating 6+ strings per I/O
  (hundreds of I/Os x 6 allocs exhausted the heap)

Process manager:
- Per-process FDTable and Cwd initialised on create, freed on reap
- proc_await: use pointer-indirection spin per lesson #6 (FPC caches
  non-volatile reads; raw while flag=0 returns immediately)
- graphicsrefresh: move LVGL handler from timer ISR (IF=0) to a
  dedicated process so LVGL callbacks can call blocking I/O

New units:
- fdtable.pas: per-process file descriptor table (fd_alloc/get/close)
- iorequest.pas: heap allocation helpers for TIORequest
- gpt.pas: GPT header/entry read + CRC validation
- filepicker.pas: reusable OS-level open/save file picker dialog
- notepad.pas: GUI text editor with selection, indent, save-as

Shell commands (diskcmd, partcmd, volcmd, diskutil):
- Route output to stdout_buf instead of syslog (lesson #4)
- diskutil: async add/remove/format partition; cache-based device display

Add Apache 2.0 license headers to storagemanager.pas, iso9660.pas,
fat32.pas, storagetest.pas

Lessons learnt: #9 VFS listing must return snapshot (not live pointer)
2026-03-07 18:58:32 +00:00
Aaron 789fb7971a getting ready 2026-03-07 18:57:38 +00:00
Aaron 4e3acd6af7 Storage system: AHCI, IDE rework, VFS, volume manager, flatfs, async IO, IOAPIC 2026-03-07 18:45:25 +00:00
admin 262d064b6d Merge pull request 'Fix: PS/2 mouse ISR blocking; convert USB polling & GFX refresh to processes; remove dead scheduler' (#48) from feature/ps2-mouse-fix into develop
continuous-integration/drone/push Build is passing
Reviewed-on: #48
2026-03-07 00:40:20 +00:00
t3hn3rd 738e5828f6 Fix PS/2 mouse ISR blocking; convert USB polling & GFX refresh to processes; remove dead scheduler
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
PS/2 Mouse:
- Fix ISR spin-blocking by reading port $60 directly with inb() instead
  of calling mouse_read(), which uses mouse_wait_long (100k iterations)
  and would stall the interrupt handler waiting for the next packet byte.

Timer (PIT):
- Increase PIT frequency from ~1024 Hz to ~8 kHz (divisor 1193 -> 149)
  for more granular timer ticks.

Graphics Refresh:
- Replace timer-hook-driven rendering (driver/timers/graphicsrefresh)
  with a dedicated 'gfxd' process that runs a continuous render loop,
  keeping the vCPU active to avoid NEM/Hyper-V pause-loop descheduling.
- Move uidebug from driver/video/ to prog/.
- Update FPS calculation to account for 8 kHz tick rate.

USB Hotplug:
- Replace timer-hook-driven polling (driver/timers/usbhotplug) with a
  dedicated 'usbd' daemon process that sleeps 1s between poll cycles.
- Add bounds check in usbcore.usb_check_hotplug for empty HC list.

Cleanup:
- Remove old scheduler unit (superseded by processmanager/contextswitcher).
- Remove tss unit and tss.init() call from kernel.
- Remove unused syslog import from contextswitcher.
- Reduce BASE_QUANTUM from 8 to 5.
2026-03-07 00:29:38 +00:00
13 changed files with 130 additions and 365 deletions

No files matched your search

+1 -4
View File
@@ -29,14 +29,11 @@ unit contextswitcher;
interface interface
uses uses
idt, isrmanager, processmanager, proctypes, util, syslog, tracer; idt, isrmanager, processmanager, proctypes, util, tracer;
{ Initialise: overwrite IDT gate 32 with our custom ISR. } { Initialise: overwrite IDT gate 32 with our custom ISR. }
procedure init; procedure init;
{ No idle ESP variable needed — idle is now a formal process (PID 0)
with its SavedESP managed like any other process. }
implementation implementation
{ ----------------------------------------------------------------------- { -----------------------------------------------------------------------
+4 -1
View File
@@ -957,11 +957,14 @@ end;
procedure usb_check_hotplug; procedure usb_check_hotplug;
var var
i : uint32; i : uint32;
count: uint32;
hc : PUSBHCDriver; hc : PUSBHCDriver;
port : uint8; port : uint8;
begin begin
if HCList = nil then exit; if HCList = nil then exit;
for i := 0 to LL_Size(HCList) - 1 do begin count := LL_Size(HCList);
if count = 0 then exit;
for i := 0 to count - 1 do begin
hc := PUSBHCDriver(LL_Get(HCList, i)); hc := PUSBHCDriver(LL_Get(HCList, i));
if (hc <> nil) and hc^.PortChangePending then begin if (hc <> nil) and hc^.PortChangePending then begin
hc^.PortChangePending := false; hc^.PortChangePending := false;
+5 -1
View File
@@ -119,8 +119,12 @@ var
x32, y32 : sint32; x32, y32 : sint32;
r : pchar; r : pchar;
begin begin
{ mouse_wait(0) checks OBF with a short timeout (100 iterations).
When it returns true, data is ready — read it directly with inb($60).
Do NOT call mouse_read here: it uses mouse_wait_long (100k iterations)
and would spin-block the ISR if the next packet byte hasn't arrived yet. }
while mouse_wait(0) do begin while mouse_wait(0) do begin
b := mouse_read; b := inb($60);
if Cycle = 0 then begin if Cycle = 0 then begin
if (b AND $08) = $08 then begin if (b AND $08) = $08 then begin
Mouse_Byte[Cycle] := b; Mouse_Byte[Cycle] := b;
+3 -3
View File
@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
{ {
Driver->Timer->TMR_0_ISR - 1024hz Timer Driver. Driver->Timer->TMR_0_ISR - 8khz Timer Driver.
@author(Kieron Morris <[email protected]>) @author(Kieron Morris <[email protected]>)
} }
@@ -37,7 +37,7 @@ var
Hooks : Array[0..MAX_HOOKS-1] of pp_hook_method; Hooks : Array[0..MAX_HOOKS-1] of pp_hook_method;
Registered : boolean = false; Registered : boolean = false;
procedure Main; //IRQ0, 1024.19hz aprox procedure Main; //IRQ0, ~8001hz
var var
i : integer; i : integer;
@@ -54,7 +54,7 @@ procedure register();
begin begin
if not registered then begin if not registered then begin
asm asm
mov ax, 1193 mov ax, 149
out $40, al out $40, al
mov al, ah mov al, ah
out $40, al out $40, al
-60
View File
@@ -1,60 +0,0 @@
{
Driver->Timer->GraphicsRefresh - Process-driven UI/graphics refresh at ~120FPS.
Spawns a dedicated UI process that calls desktop, uidebug, LVGL and video
flush routines in a loop (~8ms sleep ≈ 120 FPS).
Running LVGL from a process (not the timer ISR) is critical: LVGL event
callbacks (e.g. notepad save, disk format) may call blocking storage I/O
via submit_io_wait. Blocking I/O needs proc_yield → hlt, which requires
interrupts to be enabled (IF=1). Inside the timer ISR, IF=0, so hlt
would hang forever. A process runs with IF=1 between preemptions, so
blocking I/O works correctly.
LVGL's internal tick counter (tick_accumulator) is still driven by the
1024Hz timer ISR hook registered in lvgl_init — that just increments a
counter and is safe from ISR context. Only lv_timer_handler (event
dispatch) and video.Flush move to the process.
@author(Kieron Morris <[email protected]>)
}
unit graphicsrefresh;
interface
procedure init;
implementation
uses
proctypes,
processmanager,
desktop,
uidebug,
lvgl,
video,
windows,
syslog;
{ UI loop — runs as a dedicated process so LVGL event callbacks can safely
call blocking storage I/O (proc_yield → hlt needs IF=1). }
procedure ui_loop(ctx : PProcessContext);
begin
while true do begin
windows.reapOrphanedWindows;
desktop.update;
uidebug.update;
lvgl_handler;
video.Flush;
{ ~120 FPS: sleep 8ms between frames }
processmanager.proc_sleep_ms(8);
end;
end;
procedure init;
begin
processmanager.create('ui', @ui_loop, nil, 1);
syslog.logln('GFXREFRESH', 'UI process created (~120 FPS).');
end;
end.
-46
View File
@@ -1,46 +0,0 @@
{
Driver->Timer->USBHotplug - Timer-driven USB hotplug polling at ~1Hz.
Hooks into the 1024Hz timer (TMR_0_ISR) and calls
usbcore.usb_check_hotplug every 1024 ticks (approximately once per second).
@author(Kieron Morris <[email protected]>)
}
unit usbhotplug;
interface
procedure init;
implementation
uses
util,
TMR_0_ISR,
usbcore,
syslog;
const
{ 1024 ticks at 1024Hz ≈ 1 second }
TICKS_PER_POLL = 1024;
var
TickCounter : uint32;
procedure on_tick(data : void);
begin
TickCounter := TickCounter + 1;
if TickCounter >= TICKS_PER_POLL then begin
TickCounter := 0;
usbcore.usb_check_hotplug;
end;
end;
procedure init;
begin
TickCounter := 0;
TMR_0_ISR.hook(uint32(@on_tick));
syslog.logln('USBHOTPLUG', 'Hooked into 1024Hz timer (~1Hz polling).');
end;
end.
+1 -1
View File
@@ -19,7 +19,7 @@ const
PROCESS_STACK_SIZE = 8192; PROCESS_STACK_SIZE = 8192;
{ Base quantum multiplier: quantum = Priority * BASE_QUANTUM ticks } { Base quantum multiplier: quantum = Priority * BASE_QUANTUM ticks }
BASE_QUANTUM = 8; BASE_QUANTUM = 5;
type type
{ Forward declaration } { Forward declaration }
+1 -5
View File
@@ -77,7 +77,6 @@ uses
ps2_mouse, ps2_mouse,
rand, rand,
RTC, RTC,
scheduler,
serial, serial,
stdio, stdio,
storagemanager, storagemanager,
@@ -88,7 +87,6 @@ uses
testprocs, testprocs,
TMR_0_ISR, TMR_0_ISR,
tracer, tracer,
tss,
UHCI, UHCI,
uidebug, uidebug,
USB, USB,
@@ -107,6 +105,7 @@ uses
volcmd, volcmd,
volumemanager, volumemanager,
vterminal, vterminal,
Windows,
XHCI; XHCI;
procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: uint32); stdcall; procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: uint32); stdcall;
@@ -207,9 +206,6 @@ begin
stdio.init(); stdio.init();
stdio.registerCommand('BSOD', @terminal_command_bsod, 'Force a Panic Screen.'); stdio.registerCommand('BSOD', @terminal_command_bsod, 'Force a Panic Screen.');
tss.init();
scheduler.init();
{ CPUID } { CPUID }
syslog.logln('CPU', 'Init begin'); syslog.logln('CPU', 'Init begin');
cpu.init(); cpu.init();
+76
View File
@@ -0,0 +1,76 @@
{
prog->GraphicsRefresh - Continuous graphics rendering process.
Spawns a dedicated render process that loops indefinitely, driving the
desktop, LVGL and video-flush pipeline. The loop does real work every
iteration (especially the 7.3 MB SSE framebuffer copy in video.Flush),
which keeps the vCPU active so the NEM/Hyper-V back-end never
deschedules it and starves the PIT of interrupts.
@author(Kieron Morris <[email protected]>)
}
unit graphicsrefresh;
interface
procedure init;
implementation
uses
desktop,
uidebug,
lvgl,
video,
windows,
processmanager,
proctypes,
syslog,
TMR_0_ISR;
{ Render process entry point — runs as a normal scheduled process with
interrupts enabled. Continuously drives the full render pipeline so
the vCPU is always executing real work (no HLT / PAUSE spin-waits
that would trigger pause-loop exits under NEM/Hyper-V). }
const
TICKS_PER_FRAME = 32; { target ticks per frame; adjust as needed to balance refresh rate and CPU usage }
var
LAST_UPDATE : uint32 = 0;
CURRENT_TICK : uint32 = 0;
procedure render_loop(ctx : PProcessContext);
var
should_update : boolean;
begin
while true do begin
should_update := (CURRENT_TICK - LAST_UPDATE) >= TICKS_PER_FRAME;
should_update := should_update or (CURRENT_TICK < LAST_UPDATE); { handle tick counter wraparound }
if (should_update) then begin
LAST_UPDATE := CURRENT_TICK;
windows.reapOrphanedWindows;
desktop.update;
uidebug.update;
lvgl_handler;
video.Flush;
end;
end;
end;
procedure tick;
begin
{ This function is called on every timer tick (interrupt 32) via the ISR hooks.
It can be used to trigger periodic tasks without needing a dedicated process. }
CURRENT_TICK := CURRENT_TICK + 1;
end;
procedure init;
begin
TMR_0_ISR.hook(uint32(@tick)); { Register the tick handler to run on every timer interrupt }
processmanager.create('gfxd', @render_loop, nil, 5);
syslog.logln('gfxd', 'Render process spawned.');
end;
end.
@@ -365,7 +365,7 @@ begin
now_tick := lvgl_get_ticks; now_tick := lvgl_get_ticks;
if (now_tick - last_tick) >= FPS_INTERVAL then begin if (now_tick - last_tick) >= FPS_INTERVAL then begin
current_fps := safeDiv32(frame_count * 1000, now_tick - last_tick); current_fps := safeDiv32(frame_count * 8000, now_tick - last_tick);
frame_count := 0; frame_count := 0;
last_tick := now_tick; last_tick := now_tick;
+38
View File
@@ -0,0 +1,38 @@
{
Prog->USBHotplug - USB hotplug daemon process.
Spawns a dedicated 'usbd' process that polls for USB port changes
approximately once per second using processmanager.proc_sleep_ms.
@author(Kieron Morris <[email protected]>)
}
unit usbhotplug;
interface
procedure init;
implementation
uses
usbcore,
processmanager,
proctypes,
syslog;
{ USB daemon process entry point — polls for hotplug events once per second. }
procedure usbd_loop(ctx : PProcessContext);
begin
while true do begin
usbcore.usb_check_hotplug;
processmanager.proc_sleep_ms(1000);
end;
end;
procedure init;
begin
processmanager.create('usbd', @usbd_loop, nil, 2);
syslog.logln('usbd', 'USB hotplug daemon spawned.');
end;
end.
-132
View File
@@ -1,132 +0,0 @@
// Copyright 2021 Kieron Morris
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
{
Scheduler - Schedules Context Switches.
@author(Kieron Morris <[email protected]>)
}
unit scheduler;
interface
uses
syslog, stdio,
TMR_0_ISR,
lmemorymanager;
const
Quantum = 64;
type
TTaskState = packed record
//EAX, EDX,
end;
TScheduler_Entry = packed record
ThreadID : uint32;
Priority : uint8;
Delta : uint32;
Next : void;
end;
PScheduler_Entry = ^TScheduler_Entry;
var
Active : Boolean;
procedure init;
procedure add_task(priority : uint8);
implementation
var
Tick : uint32;
Root_Task : PScheduler_Entry = nil;
Current_Task : PScheduler_Entry = nil;
procedure context_switch();
begin
Current_Task:= PScheduler_Entry(Current_Task^.Next);
end;
procedure add_task(priority : uint8);
var
new_task : PScheduler_Entry;
task : PScheduler_Entry;
i : uint32;
begin
new_task:= PScheduler_Entry(kalloc(sizeof(TScheduler_Entry)));
new_task^.Priority:= priority;
new_task^.Delta:= Tick;
new_task^.Next:= void(Root_Task);
task:= Root_Task;
i:= 1;
while PScheduler_Entry(task^.next) <> Root_Task do begin
i:= i+1;
task:= PScheduler_Entry(task^.next);
end;
task^.next:= void(new_task);
new_task^.ThreadID:= i;
end;
procedure delta(data : void);
begin
If Active then begin
Tick:= Tick + 1;
If Tick = 0 then context_switch();
If (Current_Task^.Delta + (Current_Task^.Priority * Quantum)) <= Tick then context_switch();
end;
end;
procedure terminal_command_tasks(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf);
var
list : PScheduler_Entry;
begin
stdio.bufWriteStrLn(stdout_buf, 'ThreadID - Priority - Delta');
list:= Root_Task;
stdio.bufWriteInt(stdout_buf, list^.ThreadID);
stdio.bufWriteStr(stdout_buf, ' - ');
stdio.bufWriteInt(stdout_buf, list^.Priority);
stdio.bufWriteStr(stdout_buf, ' - ');
stdio.bufWriteIntLn(stdout_buf, list^.Delta);
list:= PScheduler_Entry(list^.Next);
while list <> Root_Task do begin
stdio.bufWriteInt(stdout_buf, list^.ThreadID);
stdio.bufWriteStr(stdout_buf, ' - ');
stdio.bufWriteInt(stdout_buf, list^.Priority);
stdio.bufWriteStr(stdout_buf, ' - ');
stdio.bufWriteIntLn(stdout_buf, list^.Delta);
list:= PScheduler_Entry(list^.Next);
end;
end;
procedure init;
begin
syslog.logln('SCHEDULER','INIT BEGIN.');
Root_Task:= PScheduler_Entry(kalloc(sizeof(TScheduler_Entry)));
Root_Task^.ThreadID:= 0;
Root_Task^.Priority:= 1;
Root_Task^.Delta:= 0;
Root_Task^.Next:= void(Root_Task);
Current_Task:= Root_Task;
Tick:= 0;
Active:= False;
TMR_0_ISR.hook(uint32(@delta));
//stdio.registerCommand('TASKS', @terminal_command_tasks, 'List Active Processes.');
syslog.logln('SCHEDULER','INIT END.');
end;
end.
-111
View File
@@ -1,111 +0,0 @@
// Copyright 2021 Kieron Morris
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
{
TSS - Task State Segment (stub).
@author(Kieron Morris <[email protected]>)
}
unit tss;
interface
uses
gdt,
vmemorymanager,
syslog;
type
TTaskStateSegment = packed record
link : uint16;
link_h : uint16;
esp0 : uint32;
ss0 : uint16;
ss0_h : uint16;
esp1 : uint32;
ss1 : uint16;
ss1_h : uint16;
esp2 : uint32;
ss2 : uint16;
ss2_h : uint16;
cr3 : uint32;
eip : uint32;
eflags : uint32;
eax : uint32;
ecx : uint32;
edx : uint32;
ebx : uint32;
esp : uint32;
ebp : uint32;
esi : uint32;
edi : uint32;
es : uint16;
es_h : uint16;
cs : uint16;
cs_h : uint16;
ss : uint16;
ss_h : uint16;
ds : uint16;
ds_h : uint16;
fs : uint16;
fs_h : uint16;
gs : uint16;
gs_h : uint16;
ldt : uint16;
ldt_h : uint16;
trap : uint16;
iomap : uint16;
end;
PTaskStateSegment = ^TTaskStateSegment;
var
TaskStateSegment : TTaskStateSegment;
ptrTaskStateSegment : PTaskStateSegment = @TaskStateSegment;
procedure init;
implementation
procedure init;
var
cESP : uint32;
cCR3 : uint32;
begin
syslog.logln('TSS','INIT BEGIN.');
ptrTaskStateSegment^.ss0:= $08;
ptrTaskStateSegment^.iomap:= sizeof(TTaskStateSegment)-1;
asm
MOV cESP, ESP
MOV EAX, CR3
MOV cCR3, EAX
end;
ptrTaskStateSegment^.esp0:= cESP;
ptrTaskStateSegment^.CR3:= cCR3;
gdt.set_gate($05, uint32(ptrTaskStateSegment) - KERNEL_VIRTUAL_BASE, sizeof(TTaskStateSegment) - 1, $89, $40); //OFFSET: 40
gdt.reload;
asm
mov AX, 40
ltr AX
end;
syslog.logln('TSS','INIT END.');
end;
end.