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.
This commit is contained in:
2026-03-07 00:29:38 +00:00
parent 9fa1c859ac
commit 738e5828f6
13 changed files with 131 additions and 360 deletions
+1 -4
View File
@@ -29,14 +29,11 @@ unit contextswitcher;
interface
uses
idt, isrmanager, processmanager, proctypes, util, syslog, tracer;
idt, isrmanager, processmanager, proctypes, util, tracer;
{ Initialise: overwrite IDT gate 32 with our custom ISR. }
procedure init;
{ No idle ESP variable needed — idle is now a formal process (PID 0)
with its SavedESP managed like any other process. }
implementation
{ -----------------------------------------------------------------------
+4 -1
View File
@@ -957,11 +957,14 @@ end;
procedure usb_check_hotplug;
var
i : uint32;
count: uint32;
hc : PUSBHCDriver;
port : uint8;
begin
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));
if (hc <> nil) and hc^.PortChangePending then begin
hc^.PortChangePending := false;
+5 -1
View File
@@ -119,8 +119,12 @@ var
x32, y32 : sint32;
r : pchar;
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
b := mouse_read;
b := inb($60);
if Cycle = 0 then begin
if (b AND $08) = $08 then begin
Mouse_Byte[Cycle] := b;
+3 -3
View File
@@ -13,7 +13,7 @@
// 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]>)
}
@@ -37,7 +37,7 @@ var
Hooks : Array[1..MAX_HOOKS] of pp_hook_method;
Registered : boolean = false;
procedure Main; //IRQ0, 1024.19hz aprox
procedure Main; //IRQ0, ~8001hz
var
i : integer;
@@ -54,7 +54,7 @@ procedure register();
begin
if not registered then begin
asm
mov ax, 1193
mov ax, 149
out $40, al
mov al, ah
out $40, al
-54
View File
@@ -1,54 +0,0 @@
{
Driver->Timer->GraphicsRefresh - Timer-driven graphics refresh at ~120FPS.
Hooks into the 1024Hz timer (TMR_0_ISR) and calls the desktop, uidebug,
LVGL and video flush routines every ~9 ticks (1024/9 ≈ 113.8 FPS).
@author(Kieron Morris <[email protected]>)
}
unit graphicsrefresh;
interface
procedure init;
implementation
uses
util,
TMR_0_ISR,
desktop,
uidebug,
lvgl,
video,
windows,
syslog;
const
{ 1024 / 4 ≈ 256 FPS }
TICKS_PER_FRAME = 4;
var
TickCounter : uint32;
procedure on_tick(data : void);
begin
TickCounter := TickCounter + 1;
if TickCounter >= TICKS_PER_FRAME then begin
TickCounter := 0;
windows.reapOrphanedWindows;
desktop.update;
uidebug.update;
lvgl_handler;
video.Flush;
end;
end;
procedure init;
begin
TickCounter := 0;
TMR_0_ISR.hook(uint32(@on_tick));
syslog.logln('GFXREFRESH', 'Hooked into 1024Hz timer (~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
@@ -18,7 +18,7 @@ const
PROCESS_STACK_SIZE = 8192;
{ Base quantum multiplier: quantum = Priority * BASE_QUANTUM ticks }
BASE_QUANTUM = 8;
BASE_QUANTUM = 5;
type
{ Forward declaration }
+2 -6
View File
@@ -25,7 +25,7 @@ interface
uses
multiboot, bios_data_area,
util,
gdt, idt, isr, irq, tss,
gdt, idt, isr, irq,
TMR_0_ISR,
syslog, stdio,
keyboard, mouse,
@@ -33,7 +33,6 @@ uses
vmemorymanager, pmemorymanager, lmemorymanager,
tracer,
drivermanagement,
scheduler,
progmanager,
processmanager, contextswitcher,
testprocs,
@@ -67,7 +66,7 @@ uses
base64,
rand,
hashmap, vfs,
video, vesa, doublebuffer, color, lvgl, desktop, uidebug,
video, vesa, doublebuffer, color, lvgl, desktop, uidebug, windows,
vterminal,
graphicsrefresh, usbhotplug,
fifo, cfifo, cfifols, lifo, circ, minh, maxh, prio;
@@ -170,9 +169,6 @@ begin
stdio.init();
stdio.registerCommand('BSOD', @terminal_command_bsod, 'Force a Panic Screen.');
tss.init();
scheduler.init();
{ CPUID }
syslog.logln('CPU', 'Init begin');
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;
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;
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.