Compare commits

..
5 Commits
Author SHA1 Message Date
t3hn3rd e5548c878b Rebase: Changes made to conform with rebase on develop
continuous-integration/drone/push Build is passing
2026-03-07 13:32:10 +00:00
t3hn3rd 33985afea1 fix: show live GPU/resolution info in System Information
- desktop: Resolution/Graphics fields now read from video frontbuffer
  and gpu.activeDriverName instead of static multiboot/hardcoded values
- gpu: markAvailable sets ActiveName when no driver is active yet, so
  the initial VBE boot driver is reported before any setMode call
- kernel: whitespace cleanup
2026-03-07 10:38:14 +00:00
t3hn3rd a3b766464b feature: GPU driver framework with BGA, V86 monitor, and runtime resolution switching
- GPU Framework (gpu.pas): Priority-ordered GPU driver registry with
  mode-change callback system. Drivers register with a priority (lower =
  tried first). After successful setMode, fires registered callbacks in
  order (video → LVGL → desktop) for fully decoupled resolution updates.

- BGA Driver (bga.pas): Bochs Graphics Adapter via drivermanagement PCI
  auto-detection (class=$03 subclass=$00 wildcard). Programs display via
  I/O ports $01CE/$01CF, reads framebuffer from PCI BARs with VMware SVGA
  fallback. Supports arbitrary resolutions (1920x1080, 2560x1440, etc).

- SETRES Command (setres.pas): Runtime resolution switching — just calls
  gpu.setMode; video reinit, LVGL buffer realloc, and desktop relayout
  all fire automatically via GPU mode-change callbacks.

- VESA/VBE: Registered as GPU fallback driver (priority 50), uses V86
  INT 10h for VBE mode switching.

- TSS: Fix SS0 from $08 (code segment) to $10 (data segment); add
  set_esp0/get_esp0 helpers for V86 ring-0 stack management.

- VMM: Add map_page_user() for identity-mapping first 4MB with User bit
  (required for V86 IVT/BDA/video ROM access).

- Desktop: relayout procedure + GPU callback registration for automatic
  dock/watermark repositioning on resolution change.

- V86 Monitor (v86.pas): Full Virtual 8086 mode monitor for executing
  real-mode BIOS interrupts from protected mode. Naked GPF handler with
  instruction emulation (INT, IRET, CLI, STI, PUSHF/POPF, IN/OUT, HLT,
  0x66 prefix for 32-bit variants). Thunk-based entry via IRET into VM86.

- Kernel init order: gpu.init → video.init → vesa.init → bga.init;
  drivermanagement.init moved earlier (before video) so PCI scan detects
  BGA before first use.
2026-03-07 10:38:13 +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
14 changed files with 186 additions and 384 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[1..MAX_HOOKS] of pp_hook_method; Hooks : Array[1..MAX_HOOKS] 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
-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; 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 }
+23 -10
View File
@@ -25,7 +25,7 @@ interface
uses uses
multiboot, bios_data_area, multiboot, bios_data_area,
util, util,
gdt, idt, isr, irq, tss, v86, gdt, idt, isr, irq,
TMR_0_ISR, TMR_0_ISR,
syslog, stdio, syslog, stdio,
keyboard, mouse, keyboard, mouse,
@@ -33,7 +33,6 @@ uses
vmemorymanager, pmemorymanager, lmemorymanager, vmemorymanager, pmemorymanager, lmemorymanager,
tracer, tracer,
drivermanagement, drivermanagement,
scheduler,
progmanager, progmanager,
processmanager, contextswitcher, processmanager, contextswitcher,
testprocs, testprocs,
@@ -66,12 +65,29 @@ uses
md5, md5,
base64, base64,
rand, rand,
hashmap, vfs, hashmap,
video, vesa, doublebuffer, color, lvgl, desktop, uidebug, vfs,
gpu, bga, video,
vesa,
doublebuffer,
color,
lvgl,
desktop,
uidebug,
gpu,
bga,
vterminal, vterminal,
graphicsrefresh, usbhotplug, graphicsrefresh,
fifo, cfifo, cfifols, lifo, circ, minh, maxh, prio; usbhotplug,
fifo,
cfifo,
cfifols,
lifo,
circ,
minh,
maxh,
prio,
v86;
procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: uint32); stdcall; procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: uint32); stdcall;
@@ -174,9 +190,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.
-123
View File
@@ -1,123 +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;
procedure set_esp0(esp0 : uint32);
function get_esp0 : uint32;
implementation
procedure set_esp0(esp0 : uint32);
begin
ptrTaskStateSegment^.esp0 := esp0;
end;
function get_esp0 : uint32;
begin
get_esp0 := ptrTaskStateSegment^.esp0;
end;
procedure init;
var
cESP : uint32;
cCR3 : uint32;
begin
syslog.logln('TSS','INIT BEGIN.');
ptrTaskStateSegment^.ss0:= $10;
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.
+34 -8
View File
@@ -20,7 +20,7 @@ unit v86;
interface interface
uses uses
util, syslog, tracer, vmemorymanager, tss, idt, isr_types; util, syslog, tracer, vmemorymanager, gdt, idt, isr_types;
const const
{ V86 EFLAGS: VM=1 ($20000), IOPL=3 ($3000), IF=0 } { V86 EFLAGS: VM=1 ($20000), IOPL=3 ($3000), IF=0 }
@@ -34,6 +34,12 @@ const
V86_EXIT_INT = $FF; V86_EXIT_INT = $FF;
{ Size of the ring-0 stack used for V86 GPF handling } { Size of the ring-0 stack used for V86 GPF handling }
V86_R0_STACK_SIZE = 4096; V86_R0_STACK_SIZE = 4096;
{ GDT gate index for the TSS descriptor }
V86_TSS_GDT_GATE = 5;
{ TSS segment selector: gate 5 * 8 bytes per descriptor }
V86_TSS_SELECTOR = V86_TSS_GDT_GATE * 8;
{ Minimum x86 TSS size (104 bytes / $68) }
V86_TSS_SIZE = 104;
type type
{ Register state for V86 BIOS calls } { Register state for V86 BIOS calls }
@@ -103,6 +109,11 @@ var
{ Ring-0 stack for V86 exception handling } { Ring-0 stack for V86 exception handling }
V86Ring0Stack : array[0..V86_R0_STACK_SIZE - 1] of uint8; V86Ring0Stack : array[0..V86_R0_STACK_SIZE - 1] of uint8;
{ Minimal TSS — only ESP0 (offset 4) and SS0 (offset 8) matter.
We allocate the full 104-byte minimum so the CPU is happy. }
V86TSS : array[0..V86_TSS_SIZE - 1] of uint8;
V86TSSPtr : PUInt32 = nil;
{ ========================================================================= } { ========================================================================= }
{ V86 real-mode memory helpers (access via kernel mapping at 0xC0000000) } { V86 real-mode memory helpers (access via kernel mapping at 0xC0000000) }
{ ========================================================================= } { ========================================================================= }
@@ -608,8 +619,8 @@ asm
TSS ESP0 is restored by the exit handler so that normal interrupts TSS ESP0 is restored by the exit handler so that normal interrupts
use the correct ring-0 stack again. } use the correct ring-0 stack again. }
mov eax, dword ptr [V86OldESP0] mov eax, dword ptr [V86OldESP0]
mov ebx, dword ptr [ptrTaskStateSegment] mov ebx, dword ptr [V86TSSPtr]
mov dword ptr [ebx + 4], eax { TTaskStateSegment.esp0 is at offset 4 } mov dword ptr [ebx + 4], eax { TSS esp0 is at offset 4 }
{ Restore kernel stack frame and jump back into v86_int } { Restore kernel stack frame and jump back into v86_int }
mov esp, dword ptr [V86SavedESP] mov esp, dword ptr [V86SavedESP]
@@ -646,11 +657,9 @@ begin
V86ShadowIF := true; V86ShadowIF := true;
V86RegsPtr := @regs; V86RegsPtr := @regs;
{ Save current TSS ESP0 } { Save current TSS ESP0 and point it to our dedicated ring-0 stack }
V86OldESP0 := tss.get_esp0; V86OldESP0 := PUInt32(uint32(@V86TSS) + 4)^;
PUInt32(uint32(@V86TSS) + 4)^ := uint32(@V86Ring0Stack) + V86_R0_STACK_SIZE;
{ Set TSS ESP0 to our dedicated ring-0 stack for V86 traps }
tss.set_esp0(uint32(@V86Ring0Stack) + V86_R0_STACK_SIZE);
{ Write thunk code into physical memory at 0x7C00 } { Write thunk code into physical memory at 0x7C00 }
setup_thunk(intNo); setup_thunk(intNo);
@@ -730,10 +739,27 @@ end;
{ ========================================================================= } { ========================================================================= }
procedure init; procedure init;
var
tss_base : uint32;
begin begin
push_trace('v86.init'); push_trace('v86.init');
syslog.logln('V86', 'INIT BEGIN.'); syslog.logln('V86', 'INIT BEGIN.');
{ Set up a minimal TSS so the CPU can find ESP0 on V86 ring 3→0 traps }
memset(uint32(@V86TSS[0]), 0, V86_TSS_SIZE);
tss_base := uint32(@V86TSS[0]);
V86TSSPtr := PUInt32(tss_base);
{ SS0 at offset 8 = kernel data segment $10 }
PUInt16(tss_base + 8)^ := $10;
{ Install TSS descriptor in GDT gate 5 (access $89 = present, DPL 0, TSS available) }
gdt.set_gate(V86_TSS_GDT_GATE, tss_base, V86_TSS_SIZE - 1, $89, $00);
gdt.reload;
{ Load the task register }
asm
mov ax, V86_TSS_SELECTOR
ltr ax
end;
{ Identity-map the first 4MB at virtual 0x00000000 with User bit. { Identity-map the first 4MB at virtual 0x00000000 with User bit.
This allows V86 code to access the IVT, BDA, video ROM, and This allows V86 code to access the IVT, BDA, video ROM, and
VGA memory as real-mode linear addresses go through paging. } VGA memory as real-mode linear addresses go through paging. }