diff options
| author | Alejandro Sior <aho@sior.be> | 2022-05-13 20:50:39 +0200 |
|---|---|---|
| committer | Alejandro Sior <aho@sior.be> | 2022-05-13 20:50:39 +0200 |
| commit | e50cbe8eb763cf63fb44b047d5164f6fbf07eb39 (patch) | |
| tree | 8acb610a4f33462c843f54016861780ffadc010d | |
boot: longmode to realmode stub
This is the initial commit to the repo. It adds all the code.
The last proper thing that I got working before committing is
calling real mode functions from long mode using a stub.
Next up, I would like to implement a disk reader abstraction over
the BIOS in order to read form partitions (most notably FAT32).
| -rw-r--r-- | .bochsrc | 58 | ||||
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | main.ha | 22 | ||||
| -rw-r--r-- | real/real.ha | 22 | ||||
| -rw-r--r-- | rt/+x86_64/gdt16.S | 27 | ||||
| -rw-r--r-- | rt/+x86_64/gdt32.S | 20 | ||||
| -rw-r--r-- | rt/+x86_64/gdt64.S | 19 | ||||
| -rw-r--r-- | rt/+x86_64/halt.s | 5 | ||||
| -rw-r--r-- | rt/+x86_64/realcall.S | 161 | ||||
| -rw-r--r-- | rt/+x86_64/stage0.s | 92 | ||||
| -rw-r--r-- | rt/+x86_64/stage0_a20.S | 146 | ||||
| -rw-r--r-- | rt/+x86_64/stage0_drive.S | 118 | ||||
| -rw-r--r-- | rt/+x86_64/stage1.S | 191 | ||||
| -rw-r--r-- | rt/abort.ha | 3 | ||||
| -rw-r--r-- | rt/halt.ha | 1 | ||||
| -rw-r--r-- | rt/hare.sc | 74 | ||||
| -rw-r--r-- | rt/memset.ha | 6 | ||||
| -rw-r--r-- | rt/start.ha | 24 | ||||
| -rw-r--r-- | rt/test.ha | 1 | ||||
| -rwxr-xr-x | run.sh | 22 | ||||
| -rw-r--r-- | term/term.ha | 116 | ||||
| -rw-r--r-- | vga/vga.ha | 86 |
22 files changed, 1218 insertions, 0 deletions
diff --git a/.bochsrc b/.bochsrc new file mode 100644 index 0000000..394d356 --- /dev/null +++ b/.bochsrc @@ -0,0 +1,58 @@ +# configuration file generated by Bochs
+plugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, iodebug=true
+display_library: wx, options=gui_debug
+#display_library: options=cmdmode
+memory: host=32, guest=32
+romimage: file="/usr/local/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none
+vgaromimage: file="/usr/local/share/bochs/VGABIOS-lgpl-latest"
+boot: floppy
+floppy_bootsig_check: disabled=0
+floppya: type=1_44, 1_44="/home/aws/rep/boot/boot.bin", status=inserted, write_protected=0
+# no floppyb
+ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
+ata0-master: type=none
+ata0-slave: type=none
+ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15
+ata1-master: type=none
+ata1-slave: type=none
+ata2: enabled=false
+ata3: enabled=false
+optromimage1: file=none
+optromimage2: file=none
+optromimage3: file=none
+optromimage4: file=none
+optramimage1: file=none
+optramimage2: file=none
+optramimage3: file=none
+optramimage4: file=none
+pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none
+vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin
+cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
+#cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string=" Intel(R) Pentium(R) 4 CPU "
+#cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true
+#cpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, x86_64=true
+#cpuid: 1g_pages=false, pcid=false, fsgsbase=false, smep=false, smap=false, mwait=true
+#cpuid: vmx=1
+print_timestamps: enabled=0
+debugger_log: -
+magic_break: enabled=1
+port_e9_hack: enabled=0
+private_colormap: enabled=0
+clock: sync=none, time0=local, rtc_sync=0
+# no cmosimage
+log: -
+logprefix: %t%e%d
+debug: action=ignore
+info: action=report
+error: action=report
+panic: action=ask
+keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none
+mouse: type=ps2, enabled=false, toggle=ctrl+mbutton
+#sound: waveoutdrv=win, waveout=none, waveindrv=win, wavein=none, midioutdrv=win, midiout=none
+speaker: enabled=true, mode=sound, volume=15
+parport1: enabled=true, file=none
+parport2: enabled=false
+com1: enabled=true, mode=null
+com2: enabled=false
+com3: enabled=false
+com4: enabled=false
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e2ef1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/boot +/boot.bin + +/bx_enh_dbg.ini @@ -0,0 +1,22 @@ +use vga; +use term; +use rt; +use real; + +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!"); + 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); +}; diff --git a/real/real.ha b/real/real.ha new file mode 100644 index 0000000..ee42523 --- /dev/null +++ b/real/real.ha @@ -0,0 +1,22 @@ +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/gdt16.S b/rt/+x86_64/gdt16.S new file mode 100644 index 0000000..6b74819 --- /dev/null +++ b/rt/+x86_64/gdt16.S @@ -0,0 +1,27 @@ +.globl gdt16 +gdt16: + .quad 0 +.globl gdt16_code +gdt16_code: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10011010 + .byte 0b10001111 + .byte 0x00 +.globl gdt16_data +gdt16_data: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10010010 + .byte 0b10001111 + .byte 0x00 +.globl gdtr16 +gdtr16: + .short gdtr16 - gdt16 - 1 + .int gdt16 + + + + diff --git a/rt/+x86_64/gdt32.S b/rt/+x86_64/gdt32.S new file mode 100644 index 0000000..21ab796 --- /dev/null +++ b/rt/+x86_64/gdt32.S @@ -0,0 +1,20 @@ +gdt32: + .quad 0 +gdt32_code: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10011010 + .byte 0b11001111 + .byte 0x00 +gdt32_data: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10010010 + .byte 0b11001111 + .byte 0x00 +.globl gdtr32 +gdtr32: + .short gdtr32 - gdt32 - 1 + .int gdt32 diff --git a/rt/+x86_64/gdt64.S b/rt/+x86_64/gdt64.S new file mode 100644 index 0000000..c6166bf --- /dev/null +++ b/rt/+x86_64/gdt64.S @@ -0,0 +1,19 @@ +gdt64: + .quad 0 +gdt64_code: + .short 0xFFFF + .short 0x0000 + .byte 0x0 + .byte 0b10011010 + .byte 0b10101111 + .byte 0x0 +gdt64_data: + .short 0xFFFF + .short 0x0000 + .byte 0x0 + .byte 0b10010010 + .byte 0b11001111 + .byte 0x0 +gdtr64: + .short gdtr64 - gdt64 - 1 + .int gdt64 diff --git a/rt/+x86_64/halt.s b/rt/+x86_64/halt.s new file mode 100644 index 0000000..72404b3 --- /dev/null +++ b/rt/+x86_64/halt.s @@ -0,0 +1,5 @@ +.globl rt.halt +rt.halt: + cli + hlt + jmp rt.halt diff --git a/rt/+x86_64/realcall.S b/rt/+x86_64/realcall.S new file mode 100644 index 0000000..46745f8 --- /dev/null +++ b/rt/+x86_64/realcall.S @@ -0,0 +1,161 @@ +.code64 + +.globl real.regs +real.regs: +reax: + .int 0x0 +rebx: + .int 0x0 +recx: + .int 0x0 +redx: + .int 0x0 +redi: + .int 0x0 +resi: + .int 0x0 + + +# :real_call +# This function is intended to be called from long mode +# and calls another function in real mode. Note that precautions have +# to be made. Recall that real mode can only access below 0xFFFFF. +# So the target function, and objects of arguments passed to it cannot +# be located above that. A reasonable scheme is to put the result +# in a buffer and then, once back in long mode, copy its content to higher +# 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 + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + push %di + + cli + + + lgdt gdtr32 + pushq $(gdt32_code - gdt32) + pushq $real_call_to_pmode_down + retfq +.code32 +real_call_to_pmode_down: + # :Here, we are in compatibility mode (32 bits) + + # :Disable paging + mov %cr0, %eax + and $~(1 << 31), %eax + mov %eax, %cr0 + + # :Disabling long mode in msr + mov $0xc0000080, %ecx + rdmsr + and $~(1 << 8), %eax + wrmsr + + # :Here we are in true protected mode (32 bits) + # Let's continue our descent to real mode + # by switching our gdt to 16 bits + lgdt gdtr16 + ljmp $(gdt16_code - gdt16), $real_call_to_16bits_pmode_down +.code16 +real_call_to_16bits_pmode_down: + + # :Disable protected mode + mov %cr0, %eax + and $~1, %eax + mov %eax, %cr0 + + ljmp $0, $real_call_to_16bits_rmode_down +real_call_to_16bits_rmode_down: + mov $0, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + 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 + + cli + + # :Restore protected mode + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + + lgdt gdtr32 + ljmp $(gdt32_code - gdt32), $real_call_to_pmode_up +.code32 +real_call_to_pmode_up: + # :Restore PAE (probably unneeded) + mov %cr4, %eax + or $(1 << 5), %eax + mov %eax, %cr4 + + # :Restore long mode + mov $0xc0000080, %ecx + rdmsr + or $(1 << 8), %eax + wrmsr + + # :Restore paging + mov %cr0, %eax + or $1 << 31, %eax + mov %eax, %cr0 + + lgdt gdtr64 + ljmp $(gdt64_code - gdt64), $real_call_to_longmode_up +.code64 +real_call_to_longmode_up: + mov $(gdt64_data - gdt64), %ax + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + mov %ax, %es + mov %ax, %ds + +real_call_end: + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + ret + +.globl testt +testt: + mov $1, %al + mov $0x43, %bh + mov $0x6, %ah + mov $0, %ch + mov $0, %cl + mov $5, %dh + mov $5, %dl + int $0x10 + ret diff --git a/rt/+x86_64/stage0.s b/rt/+x86_64/stage0.s new file mode 100644 index 0000000..de0b0e4 --- /dev/null +++ b/rt/+x86_64/stage0.s @@ -0,0 +1,92 @@ +.org 0x7c00 +.code16 +.section boot.stage0, "aw" + +.globl _stage0 +_stage0: + # :When we enter this procedure, we are + # in 16-bit real mode. Our CPU essentially + # thinks it is a 8086 and we have to work our + # way through enabling Long Mode, from which + # we will jump to the Hare code + + # :Clear segment registers + # this is important if memory accesses are done later before proper segmentation + # as these registers can have arbitrary values + xor %ax, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + xchg %bx, %bx + + sidt bios_idtr + + mov $stack_top, %sp + + # :Initialize the drive + # handling routines + call drive_init + + # :Enable the A20 line + call a20_enable_bios + call a20_check + cmp $1, %ax + je a20_ok + + call a20_enable_kb + call a20_check + cmp $1, %ax + je a20_ok + + call a20_enable_fast_gate + call a20_check + cmp $1, %ax + je a20_ok +a20_fail: + + # :We can't do anything, hang + # XXX error message? + hlt +a20_ok: + + # :Now that the drive is initialized and A20 set up, we shall load + # the rest :) +load_stage1: + # :Start to read at the second sector + mov $0x1, %di + # :Place the result just after us + mov $boot_stage1_start, %esi +load_stage1_loop: + # :When stage1 has finished loaded, finish looping + cmp $boot_end, %esi + jge load_stage1_loop_end + + call drive_read_lba + + inc %di + add $0x200, %esi + jmp load_stage1_loop +load_stage1_loop_end: + # :We will need the last sector for later, for when + # we want to load the stage3 + # :XXX can probably work this out reliably by division + mov %di, previous_sector + + # :Jump to the newly loaded code + jmp _stage1_real + +.include "rt/+x86_64/stage0_drive.S" +.include "rt/+x86_64/stage0_a20.S" + +previous_sector: + .short 0 +bios_idtr: + .quad 0 + +.org 510 +.word 0xaa55 + + +.include "rt/+x86_64/stage1.S" diff --git a/rt/+x86_64/stage0_a20.S b/rt/+x86_64/stage0_a20.S new file mode 100644 index 0000000..048670e --- /dev/null +++ b/rt/+x86_64/stage0_a20.S @@ -0,0 +1,146 @@ +# :This file contains functions pertaining to enabling +# the A20 line. Segmentation allows to access up to +# 20 bits of address space in 16 bit real mode, however +# when the segment and the address specified in a memory +# access is set to something that would theoritically +# point to something above 20bits (such as 0xFFFF:0x0010) +# the CPU would wrap over to the beginning of the memory +# (in the example, 0x0000:0x0000) +# Since some programmers relied on this behavior, when Intel +# added the possibility of addressing more than 20 bits, it +# had to keep that sepecific line disabled by default to +# remain backwards compatible. + +# :a20_enable_bios +# Tries to enable the A20 line with BIOS functions +a20_enable_bios: + mov $0x2401, %ax + int $0x15 + ret + +# :a20_enable_kb +# Tries to enable the A20 line using the keyboard controller +# (the physical line is routed and managed by the keyboard +# controller for some reason (ask Intel)) +a20_enable_kb: + cli + + # :Disable the PS/2 port + call a20_wait_ready + mov $0xad, %al + out %al, $0x64 + + # :Tell keybord controller to send output port byte + call a20_wait_ready + mov $0xd0, %al + out %al, $0x64 + + # :Get the output port byte + call a20_wait_avail + in $0x60, %al + push %eax + + # :Tell keyboard controller we are going to send new port byte + call a20_wait_ready + mov $0xd1, %al + out %al, $0x64 + + # :Send new port byte to keyboard controller + call a20_wait_ready + pop %eax + # :Enable A20 line bit in port byte + or $2, %al + out %al, $0x60 + + # :Reenable the PS/2 port + call a20_wait_ready + mov $0xae, %al + out %al, $0x64 + + call a20_wait_ready + + sti + ret + +# :a20_wait_ready +# Lock until keyboard controller input buffer is ready +a20_wait_ready: + in $0x64, %al + # :Bit 1 indicates whether input buffer is full + test $2, %al + jnz a20_wait_ready + ret + +# :a20_wait_avail +# Lock until keyboard controller output buffer has data pending +a20_wait_avail: + in $0x64, %al + test $1, %al + jz a20_wait_avail + ret + +# :a20_enable_fast_gate +# Tries to enable the A20 gate using the Fast Gate method +a20_enable_fast_gate: + in $0x92, %al + or $2, %al + out %al, $0x92 + ret + +# :a20_check +# Check if the A20 line is enabled +# returns %ax := status (1 OK, 0 disabled) +a20_check: + pushf + push %ds + push %es + push %di + push %si + + cli + + # :Put first segment in %es + xor %ax, %ax + mov %ax, %es + + + # :Put last segment in %ds + not %ax + mov %ax, %ds + + # :Select addresses + mov $0x0500, %di + mov $0x0510, %si + + # :Save previous bytes at %es:%di and %ds:%si + mov %es:(%di), %al + push %ax + + mov %ds:(%si), %al + push %ax + + # :Set different bytes at %es:%di and %ds:%si + # and check if they are the same + movw $0x00, %es:(%di) + movw $0x01, %ds:(%si) + + cmpw $0x01, %es:(%di) + + # :Restore previous bytes at %es:%di and %ds:%si + pop %ax + mov %al, %ds:(%si) + + pop %ax + mov %al, %es:(%di) + + # :Note: the following conditional jump relates to the cmp above! + mov $0, %ax + je a20_check_end + mov $1, %ax +a20_check_end: + pop %si + pop %di + pop %es + pop %ds + popf + ret diff --git a/rt/+x86_64/stage0_drive.S b/rt/+x86_64/stage0_drive.S new file mode 100644 index 0000000..1b6a801 --- /dev/null +++ b/rt/+x86_64/stage0_drive.S @@ -0,0 +1,118 @@ +# :Boot drive number +drive_no: + .byte 0 + +# :Boot drive sectors per track +drive_spt: + .byte 0 + +# :Boot drive number of heads +drive_heads: + .byte 0 + +# :These two values are of importance here +# to calculate the CHS triplets of the sectors we +# want to call from the LBA. +# There are 2 main ways to address a sector: +# (1) CHS (cylinder, head, sector) +# (2) LBA ("sector id") +# Since LBA is more intuitive, we would want to use it, +# however, it is not garenteed that the BIOS has functions to +# manipulate drives in terms of LBA, so we are going to convert LBA to CHS +# using those numbers + +# :drive_init +# Perform the initialization of the values above +drive_init: + # :Save the drive number + # the bios puts it in %dl + mov %dl, drive_no + + # :Perform the BIOS disk info call + mov $0x8, %ah + mov drive_no, %dl + int $0x13 + + # :Get the number of heads + # The BIOS functions gives us the index + # of the last entry (not the amount) (hence the + # incrementation) + inc %dh + mov %dh, drive_heads + + # :Get the sector per track count + and $0x3f, %cl + mov %cl, drive_spt + + ret + +# :drive_read_lba +# Reads the drive at an LBA address and writes it at %esi +# in %esi := the destination address +# out %di := the LBA +.globl drive_read_lba +drive_read_lba: + push %di + push %esi + + call drive_addr_to_arg + # The conversion formula is pretty simple + # and is described in great details at + # https://wiki.osdev.org/Disk_access_using_the_BIOS_(INT_13h)#CHS + + # However the idea is that sectors are layed out on several tracks + # + # and these tracks are layed out + + # :Calculate sector: LBA % sector per track + 1 (because 1-indexed) + mov %di, %ax + mov drive_spt, %cl + div %cl + + # :Perform the +1 + mov %ah, %cl + inc %cl # Sector + + # :Perfom (LBA / sector per track) + xor %ah, %ah + mov drive_heads, %dl + div %dl + + # :Store modulo in %dh, quotient in %ch + mov %ah, %dh # Head + mov %al, %ch # Cylinder + + # :The CHS is complete, now perform the BIOS call + mov $0x2, %ah + mov $1, %al + mov drive_no, %dl + int $0x13 + + pop %esi + pop %di + ret + +# :drive_addr_to_arg +# Since, nowadays, we can access 32-bit registers in real mode +# we use them instead of the more traditional %es:%bx for +# practical purposes. +# This function converts %esi into %es:%bx +# in %esi := address +# out %es:%bx := segmented address +drive_addr_to_arg: + push %esi + + # :Put %esi in %dx:%ax to perform division + mov %si, %ax + shr $16, %esi + mov %si, %dx + + # :Divide %dx:%ax by 16 + mov $16, %si + div %si + + mov %ax, %es + mov %dx, %bx + + pop %esi + ret diff --git a/rt/+x86_64/stage1.S b/rt/+x86_64/stage1.S new file mode 100644 index 0000000..4f99d69 --- /dev/null +++ b/rt/+x86_64/stage1.S @@ -0,0 +1,191 @@ +.code16 +.section boot.stage1, "aw" + +.include "rt/+x86_64/gdt16.S" +.include "rt/+x86_64/gdt32.S" +.include "rt/+x86_64/gdt64.S" +.include "rt/+x86_64/realcall.S" + +.code16 +_stage1_real: + xchg %bx, %bx + # :At this point, we are in the newly loaded code, but still in 16 bits Real Mode, + # we will want to switch to 32 bits protected mode + # This is done by + # (1) Loading the 32 bits GDT + # (2) Enabling protected mode bit + # (3) Loading segment registers with the data and code segment offsets + # in the GDT + + # Load the 32 bits GDT by using lgdt on the gdtr32 structure + lgdt gdtr32 + + # :Enable the protected mode bit is bit 1 in %cr0 + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + + # :Longjumping to set the %cs segment register + # (the rest will be set there) + ljmp $gdt32_code - gdt32, $_stage1 + + +# :Tell the assembler to emit 32 bits code from now on in the file +.code32 + +_stage1: + # :We are now in 32 bits protected mode! + # Our goal now is to load the stage 3 and to set up the long mode + # in order to get to our sweet 64 bits + xchg %bx, %bx + + # :Load the rest of the 32 bits data segments (%cs was set by the longjump) + mov $gdt32_data - gdt32, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + +pages_clear: + # :We know that the pages are located in free memory (in the 0x500 -> 0x7BFF zone) + # However, they might contain garbage so we need to free it + + xchg %bx, %bx + mov $_p4, %edi + call pages_clear_page + + mov $_p3, %edi + call pages_clear_page + + mov $_p2, %edi + call pages_clear_page + + mov $_p1, %edi + call pages_clear_page + +pages_fill: + # :Set each page level's first entry to point to the next level + # in order to identity map the first 2 megabytes of memory + + # :Here, we or with 3 in order to enable the + # 'present' bit and the 'rw' bit on the page entry + mov $_p3, %edi + or $3, %edi + mov %edi, (_p4) + + mov $_p2, %edi + or $3, %edi + mov %edi, (_p3) + + mov $_p1, %edi + or $3, %edi + mov %edi, (_p2) + + + # :Now we are going to fill the first page + mov $0x3, %ebx + mov $512, %ecx + mov $_p1, %edi +pages_fill_p1_loop: + mov %ebx, (%edi) + add $0x1000, %ebx + add $8, %edi + loop pages_fill_p1_loop + + # :Now that the pages are prepared, we must set the %cr3 to point to the level 4 + # page table. This register contains the address of the level 4 page + mov $_p4, %edi + mov %edi, %cr3 + +pages_enable_pae: + # :Enable the physical address extension, extends the virtually addressable + # space above 4GB. Also, this is required for long mode + # The PAE bit is the 5th bit of the %cr4 register + mov %cr4, %eax + or $1 << 5, %eax + mov %eax, %cr4 + +pages_enable_longmode_bit: + # :Enable the longmode bit. It is the 8th bit of the model specific register + # identified with 0xC00000080 + mov $0xC0000080, %ecx + rdmsr + or $1 << 8, %eax + wrmsr + + # :We are now in Compatibility mode. This is a transitational mode between 32 bits protected + # and long mode. We still have 2 things to do before entering long mode: + # (1) Enabling paging + # (2) Loading a 64 bits GDT + +pages_enable_paging: + # :Enable the paging and also make sure that the protected mode is enabled + # Everything is in %cr0, paging is bit 31st and protected is bit 0th + mov %cr0, %eax + or $1 << 31 | 1 << 0, %eax + mov %eax, %cr0 + +reload_segments: + # :Last thing to do, reloading the segments + xchg %bx, %bx + + lgdt gdtr64 + + ljmp $gdt64_code - gdt64, $stage1_long + + +# :pages_clear_page: +# Clears the page located at address +# pointed by %edi +pages_clear_page: + push %ecx + push %edi + + mov $0x1000, %ecx +pages_clear_page_loop: + movb $0x0, (%edi) + inc %edi + loop pages_clear_page_loop + + pop %edi + pop %ecx + ret + +# :Tell the assembler to emit 64 bits code +.code64 + +stage1_long: + # :Finally! We are in long mode. We shall now setup the rest of the segments + # registers, as well as the stack, then jump to Hare + + xchg %bx, %bx + + mov $gdt64_data - gdt64, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + + # :Clear all the registers + # (QBE makes this assumption?) + xor %rax, %rax + xor %rbx, %rbx + xor %rcx, %rcx + xor %rdx, %rdx + xor %rdi, %rdi + xor %rsi, %rsi + xor %r8, %r8 + xor %r9, %r9 + xor %r10, %r10 + xor %r11, %r11 + xor %r12, %r12 + xor %r13, %r13 + xor %r14, %r14 + xor %r15, %r15 + xor %rbp, %rbp + + cli + + jmp _hare diff --git a/rt/abort.ha b/rt/abort.ha new file mode 100644 index 0000000..dbe5d76 --- /dev/null +++ b/rt/abort.ha @@ -0,0 +1,3 @@ +export @noreturn fn abort_fixed() void = { + halt(); +}; diff --git a/rt/halt.ha b/rt/halt.ha new file mode 100644 index 0000000..228beb8 --- /dev/null +++ b/rt/halt.ha @@ -0,0 +1 @@ +export @noreturn fn halt() void;
\ No newline at end of file diff --git a/rt/hare.sc b/rt/hare.sc new file mode 100644 index 0000000..0cb9c02 --- /dev/null +++ b/rt/hare.sc @@ -0,0 +1,74 @@ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_stage0) + +PHDRS { + headers PT_PHDR PHDRS; + text PT_LOAD FILEHDR PHDRS; + data PT_LOAD; +} + +SECTIONS { + . = 0x0; + + . = 0x500; + . = ALIGN(4096); + _p4 = .; + . += 4096; + _p3 = .; + . += 4096; + _p2 = .; + . += 4096; + _p1 = .; + . += 4096; + + . = 0x7c00; + stack_top = .; + + boot_start = .; + + boot : { + boot_stage0_start = .; + *(boot.stage0) + boot_stage0_end = .; + + boot_stage1_start = .; + *(boot.stage1) + boot_stage1_end = .; + + . = ALIGN(512); + } + + .text : { + KEEP (*(.text)) + *(.text.*) + } :text + .data : { + KEEP (*(.data)) + *(.data.*) + . = ALIGN(16); + KEEP (*(.exception_array)) + } :data + .init_array : { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } :data + .fini_array : { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } :data + .test_array : { + PROVIDE_HIDDEN (__test_array_start = .); + KEEP (*(.test_array)) + PROVIDE_HIDDEN (__test_array_end = .); + } :data + + .bss : { + KEEP (*(.bss)) + *(.bss.*) + } :data + + + boot_end = .; +} diff --git a/rt/memset.ha b/rt/memset.ha new file mode 100644 index 0000000..01b9946 --- /dev/null +++ b/rt/memset.ha @@ -0,0 +1,6 @@ +export fn memset(dest: *void, val: u8, amt: size) void = { + let a = dest: *[*]u8; + for (let i = 0z; i < amt; i += 1) { + a[i] = val; + }; +}; diff --git a/rt/start.ha b/rt/start.ha new file mode 100644 index 0000000..edbc5a5 --- /dev/null +++ b/rt/start.ha @@ -0,0 +1,24 @@ +@symbol("main") fn main() void; + +const @symbol("__init_array_start") init_start: [*]*fn() void; +const @symbol("__init_array_end") init_end: [*]*fn() void; +const @symbol("__fini_array_start") fini_start: [*]*fn() void; +const @symbol("__fini_array_end") fini_end: [*]*fn() void; + +export @noreturn @symbol("_hare") fn _hare() void = { + const ninit = (&init_end: uintptr - &init_start: uintptr): size + / size(*fn() void); + for (let i = 0z; i < ninit; i += 1) { + init_start[i](); + }; + + main(); + + const nfini = (&fini_end: uintptr - &fini_start: uintptr): size + / size(*fn() void); + for (let i = 0z; i < nfini; i += 1) { + fini_start[i](); + }; + + halt(); +}; diff --git a/rt/test.ha b/rt/test.ha new file mode 100644 index 0000000..a9a03a3 --- /dev/null +++ b/rt/test.ha @@ -0,0 +1 @@ +export @symbol("real_call") fn real_call() void; @@ -0,0 +1,22 @@ +#!/bin/sh -e + +# :Tell hare to use our custom runtime +# and linker scripts (this feature is so simple and neat :)) +export HAREPATH=. + +# :Tell the linker to add this argument to discard +# unused sections +export LDFLAGS="--gc-sections" + +# :Do the build +hare build -X^ -T+x86_64 + +# :Create a flat binary out of the elf file +# (while also removing the breaking gnu note) +objcopy \ + --remove-section .note.gnu.property \ + -I elf64-x86-64 -O binary \ + --binary-architecture=i386:x86-64 \ + boot boot.bin + +bochs diff --git a/term/term.ha b/term/term.ha new file mode 100644 index 0000000..3104e71 --- /dev/null +++ b/term/term.ha @@ -0,0 +1,116 @@ +// Defines the colors that a terminal is expected to implement. +// These are the classical VGA colors. +export type color = enum { + BLACK, + BLUE, + GREEN, + CYAN, + RED, + MAGENTA, + BROWN, + LGRAY, + DGRAY, + LBLUE, + LGREEN, + LCYAN, + LRED, + YELLOW, + LMAGENTA, + WHITE +}; + +export type vtable = struct { + setpos: *fn(_: *term, _: (size, size)) void, + getpos: *fn(_: *term) (size, size), + setcolors: *fn(_: *term, _: (color, color)) void, + getcolors: *fn(_: *term) (color, color), + putchar: *fn(_: *term, _: u8) void, + getdim: *fn(_: *term) (size, size), +}; + +export type term = *vtable; + +export fn setpos(ctrl: *term, pos: (size, size)) void = { + ctrl.setpos(ctrl, pos); +}; +export fn getpos(ctrl: *term) (size, size) = { + return ctrl.getpos(ctrl); +}; +export fn setcolors(ctrl: *term, colors: (color, color)) void = { + ctrl.setcolors(ctrl, colors); +}; +export fn getcolors(ctrl: *term) (color, color) = { + return ctrl.getcolors(ctrl); +}; +export fn putchar(ctrl: *term, c: u8) void = { + ctrl.putchar(ctrl, c); +}; +export fn getdim(ctrl: *term) (size, size) = { + return ctrl.getdim(ctrl); +}; + + +fn advancex(ctrl: *term) void = { + let dim = getdim(ctrl); + let pos = getpos(ctrl); + + pos.0 += 1; + if (pos.0 >= dim.0) { + newline(ctrl); + } else { + setpos(ctrl, pos); + }; +}; + +fn newline(ctrl: *term) void = { + let dim = getdim(ctrl); + let pos = getpos(ctrl); + + pos.0 = 0; + pos.1 += 1; + + if (pos.1 >= dim.1) { + clear(ctrl); + setpos(ctrl, (0, 0)); + } else { + setpos(ctrl, pos); + }; +}; + +fn carriage(ctrl: *term) void = { + let pos = getpos(ctrl); + + pos.0 = 0; + setpos(ctrl, pos); +}; + +export fn print(ctrl: *term, msg: str) void = { + let msg = *(&msg: *[]u8); + + for (let i = 0z; i < len(msg); i += 1) { + switch (msg[i]) { + case '\n' => newline(ctrl); + case '\r' => carriage(ctrl); + case => + putchar(ctrl, msg[i]); + advancex(ctrl); + }; + }; +}; + +export fn clear(ctrl: *term) void = { + let colors = getcolors(ctrl); + setcolors(ctrl, (colors.1, colors.1)); + let dim = getdim(ctrl); + let pos = getpos(ctrl); + + for (let j = 0z; j < dim.1; j += 1) { + for (let i = 0z; i < dim.0; i += 1) { + setpos(ctrl, (i, j)); + putchar(ctrl, ' '); + }; + }; + + setpos(ctrl, pos); + setcolors(ctrl, colors); +}; diff --git a/vga/vga.ha b/vga/vga.ha new file mode 100644 index 0000000..cf88221 --- /dev/null +++ b/vga/vga.ha @@ -0,0 +1,86 @@ +use term; + +export type vga_text = struct { + term: term::term, + address: *[*]u16, + pos: (size, size), + dim: (size, size), + colors: (term::color, term::color) +}; + +const vga_text_term: term::vtable = term::vtable { + setpos = &setpos, + getpos = &getpos, + setcolors = &setcolors, + getcolors = &getcolors, + putchar = &putchar, + getdim = &getdim +}; + +export fn attach(address: uintptr, xsize: size, ysize: size) vga_text = { + return vga_text { + term = &vga_text_term, + address = address: *[*]u16, + dim = (xsize, ysize), + pos = (0, 0), + colors = (term::color::LMAGENTA, term::color::BLACK) + }; +}; + +fn setpos(ctrl: *term::term, pos: (size, size)) void = { + let ctrl = ctrl: *vga_text; + + ctrl.pos = pos; +}; + +fn getpos(ctrl: *term::term) (size, size) = { + let ctrl = ctrl: *vga_text; + + return ctrl.pos; +}; + +fn setcolors(ctrl: *term::term, colors: (term::color, term::color)) void = { + let ctrl = ctrl: *vga_text; + + ctrl.colors = colors; +}; + +fn getcolors(ctrl: *term::term) (term::color, term::color) = { + let ctrl = ctrl: *vga_text; + + return ctrl.colors; +}; + +fn putchar(ctrl: *term::term, c: u8) void = { + let ctrl = ctrl: *vga_text; + + let attr: u16 = term_color_map(ctrl.colors.1) << 4 | term_color_map(ctrl.colors.0); + ctrl.address[ctrl.pos.1 * ctrl.dim.0 + ctrl.pos.0] = attr << 8 | c; +}; + +fn getdim(ctrl: *term::term) (size, size) = { + let ctrl = ctrl: *vga_text; + + return ctrl.dim; +}; + +fn term_color_map(col: term::color) u8 = { + switch (col) { + case term::color::BLACK => return 0x0; + case term::color::BLUE => return 0x1; + case term::color::GREEN => return 0x2; + case term::color::CYAN => return 0x3; + case term::color::RED => return 0x4; + case term::color::MAGENTA => return 0x5; + case term::color::BROWN => return 0x6; + case term::color::LGRAY => return 0x7; + case term::color::DGRAY => return 0x8; + case term::color::LBLUE => return 0x9; + case term::color::LGREEN => return 0xa; + case term::color::LCYAN => return 0xb; + case term::color::LRED => return 0xc; + case term::color::LMAGENTA => return 0xd; + case term::color::YELLOW => return 0xe; + case term::color::WHITE => return 0xf; + }; +}; |
