From 776a3710652f78b44718450406d0683974aef72a Mon Sep 17 00:00:00 2001 From: Alejandro Sior Date: Thu, 19 May 2022 08:45:26 +0200 Subject: bios: stub calls interrupt number; simplified bios.ha main interface Instead of having the bios.call stub calling a function, make it directly call an interrupt that is changed dynamically by modifying the code at runtime. Simplify the bios.ha interface and document it. Next up: implementing a high level drive interface for the BIOS using BIOS interrupts --- bios/bios.ha | 41 +++++++++++++++++++++++++ bios/drive/+x86_64/drive.s | 7 +++++ bios/drive/drive.ha | 5 ++++ drive/drive.ha | 20 +++++++++++++ main.ha | 17 ++++++----- real/real.ha | 22 -------------- rt/+x86_64/realcall.S | 74 ++++++++++++++++++++++++++++++++-------------- rt/+x86_64/stage0.s | 1 + rt/+x86_64/stage0_drive.S | 3 ++ rt/hare.sc | 2 ++ 10 files changed, 139 insertions(+), 53 deletions(-) create mode 100644 bios/bios.ha create mode 100644 bios/drive/+x86_64/drive.s create mode 100644 bios/drive/drive.ha create mode 100644 drive/drive.ha delete mode 100644 real/real.ha diff --git a/bios/bios.ha b/bios/bios.ha new file mode 100644 index 0000000..afae0f6 --- /dev/null +++ b/bios/bios.ha @@ -0,0 +1,41 @@ +// Struct defining the contents of the register state used +// for bios calls +export type state = struct { + @offset(0) eax: u32, + @offset(4) ebx: u32, + @offset(8) ecx: u32, + @offset(12) edx: u32, + @offset(16) edi: u32, + @offset(20) esi: u32, + @offset(24) ds: u16, + @offset(26) es: u16, + @offset(28) ss: u16, + @offset(30) gs: u16, + @offset(32) fs: u16 +}; + +// The set real mode registers +export let regs: state; + +// The address of a 512 bytes workspace that is located below 0xFFFFF and can be used to store the results +// of various bios calls +export const @symbol("_ws") ws: u16; + +// The boot drive number as given by the BIOS at boot time +export const @symbol("drive_no") drive_number: u8; + +// The amount of sectors per track that the boot drive has +export const @symbol("drive_spt") drive_sectors_per_track: u8; + +// The amount of heads that the boot drive has +export const @symbol("drive_heads") drive_heads: u8; + +// Clears the BIOS mode registers +export fn clearregs() void = { + regs = state { + ... + }; +}; + +// Call a BIOS interrupt +export fn call(intno: u8) void; diff --git a/bios/drive/+x86_64/drive.s b/bios/drive/+x86_64/drive.s new file mode 100644 index 0000000..1f3828e --- /dev/null +++ b/bios/drive/+x86_64/drive.s @@ -0,0 +1,7 @@ +.code16 + +.globl bios.drive.read +bios.drive.read: + mov $0x2, %ah + int $0x13 + ret diff --git a/bios/drive/drive.ha b/bios/drive/drive.ha new file mode 100644 index 0000000..35cccca --- /dev/null +++ b/bios/drive/drive.ha @@ -0,0 +1,5 @@ +export let @symbol("drive_no") drive_no: u8; +export let @symbol("drive_spt") drive_spt: u8; +export let @symbol("drive_heads") drive_heads: u8; + +export fn read() void; diff --git a/drive/drive.ha b/drive/drive.ha new file mode 100644 index 0000000..42cef43 --- /dev/null +++ b/drive/drive.ha @@ -0,0 +1,20 @@ +use bios; +use bios::drive; + +fn lba_to_chs(lba: u16) (u16, u16, u16) = { + let temp = lba / bios::drive::drive_spt; + let sector = (lba % bios::drive::drive_spt) + 1; + let head = temp % bios::drive::drive_heads; + let cylinder = temp / bios::drive::drive_heads; + + return (cylinder, head, sector); +}; + +export fn read(sector: u16, dest: uintptr) void = { + let chs = lba_to_chs(sector); + + bios::regs.eax = 1 | 0x2 << 8; + bios::regs.ebx = ws; + bios::regs.ecx = chs.2 | chs.0 << 8; + bios::regs.edx = bios::drive::drive_no | chs.1 << 8; +}; diff --git a/main.ha b/main.ha index edd4607..e5b2e7c 100644 --- a/main.ha +++ b/main.ha @@ -1,22 +1,23 @@ use vga; use term; use rt; -use real; +use bios; +use bios::drive; +use drive; export fn main() void = { let text = vga::attach(0xb8000, 80, 25); term::clear(&text); term::setcolors(&text, (term::color::LMAGENTA, term::color::BLACK)); - term::print(&text, "hello hare world!"); + term::print(&text, "hello hare world!\n"); for (let i = 0z; i < 10; i += 1) { term::print(&text, "a"); - for (let j = 0z; j < 10000000; j += 1) { - yield; - }; }; - real::regs.edi = 0; - real::regs.esi = 0x7c00; - real::call((&real::testt): uintptr: u16); + bios::regs.eax = 6 << 8 | 1; + bios::regs.ebx = 0x43 << 8; + bios::regs.ecx = 0; + bios::regs.edx = 5 << 8 | 5; + bios::call(0x10); }; diff --git a/real/real.ha b/real/real.ha deleted file mode 100644 index ee42523..0000000 --- a/real/real.ha +++ /dev/null @@ -1,22 +0,0 @@ -export type state = struct { - @offset(0) eax: u32, - @offset(4) ebx: u32, - @offset(8) ecx: u32, - @offset(12) edx: u32, - @offset(16) edi: u32, - @offset(20) esi: u32 -}; - -export let regs: state; - - -export fn clearregs() void = { - regs = state { - ... - }; -}; - -export @symbol("real.call") fn call(addr: u16) void; - -export @symbol("testt") fn testt() void; -export @symbol("drive_read_lba") fn drive_read_lba() void; diff --git a/rt/+x86_64/realcall.S b/rt/+x86_64/realcall.S index 46745f8..9e9afb4 100644 --- a/rt/+x86_64/realcall.S +++ b/rt/+x86_64/realcall.S @@ -1,7 +1,7 @@ .code64 -.globl real.regs -real.regs: +.globl bios.regs +bios.regs: reax: .int 0x0 rebx: @@ -14,6 +14,17 @@ redi: .int 0x0 resi: .int 0x0 +rds: + .short 0x0 +res: + .short 0x0 +rss: + .short 0x0 +rgs: + .short 0x0 +rfs: + .short 0x0 + # :real_call @@ -26,9 +37,9 @@ resi: # above. # The procedure to go from Long Mode back to Real Mode is explained in much details # in the AMD64 Programmer's Manual Vol. 2, in particular, the figure 1-6. in section 1.3. -.globl real.call -real.call: - # :Push all the segments registers +.globl bios.call +bios.call: + xchg %bx, %bx push %rbx push %r12 push %r13 @@ -80,27 +91,44 @@ real_call_to_16bits_rmode_down: mov %ax, %gs mov %ax, %fs - # :real mode, yay - lidt bios_idtr - sti - mov rebx, %ebx - mov recx, %ecx - mov redx, %edx - mov redi, %edi - mov resi, %esi - - pop %ax - call *%ax - - mov %eax, reax - mov %ebx, rebx - mov %ecx, recx - mov %edx, redx - mov %edi, redi - mov %esi, resi + # :Self modifying code to call arbitrary interrupts + pop %ax + mov $real_call_int, %bx + mov %al, 1(%bx) + + push %ds + + # :Load registers + mov (reax), %eax + mov (rebx), %ebx + mov (recx), %ecx + mov (redx), %edx + mov (redi), %edi + mov (resi), %esi + mov %ds:(res), %es + mov %ds:(rss), %ss + mov %ds:(rgs), %gs + mov %ds:(rfs), %fs + mov %ds:(rds), %ds + xchg %bx, %bx + +real_call_int: + int $0x0 + + pop %ds + mov %ds:(rfs), %fs + mov %ds:(rgs), %gs + mov %ds:(rss), %ss + mov %ds:(res), %es + mov %eax, (reax) + mov %ebx, (rebx) + mov %ecx, (recx) + mov %edx, (redx) + mov %edi, (redi) + mov %esi, (resi) cli diff --git a/rt/+x86_64/stage0.s b/rt/+x86_64/stage0.s index de0b0e4..e17c06b 100644 --- a/rt/+x86_64/stage0.s +++ b/rt/+x86_64/stage0.s @@ -13,6 +13,7 @@ _stage0: # :Clear segment registers # this is important if memory accesses are done later before proper segmentation # as these registers can have arbitrary values + xchg %bx, %bx xor %ax, %ax mov %ax, %ds mov %ax, %es diff --git a/rt/+x86_64/stage0_drive.S b/rt/+x86_64/stage0_drive.S index 1b6a801..c376b6b 100644 --- a/rt/+x86_64/stage0_drive.S +++ b/rt/+x86_64/stage0_drive.S @@ -1,12 +1,15 @@ # :Boot drive number +.globl drive_no drive_no: .byte 0 # :Boot drive sectors per track +.globl drive_spt drive_spt: .byte 0 # :Boot drive number of heads +.globl drive_heads drive_heads: .byte 0 diff --git a/rt/hare.sc b/rt/hare.sc index 0cb9c02..3de964c 100644 --- a/rt/hare.sc +++ b/rt/hare.sc @@ -20,6 +20,8 @@ SECTIONS { . += 4096; _p1 = .; . += 4096; + _ws = .; + . += 512; . = 0x7c00; stack_top = .; -- cgit v1.2.3