summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bochsrc58
-rw-r--r--.gitignore4
-rw-r--r--main.ha22
-rw-r--r--real/real.ha22
-rw-r--r--rt/+x86_64/gdt16.S27
-rw-r--r--rt/+x86_64/gdt32.S20
-rw-r--r--rt/+x86_64/gdt64.S19
-rw-r--r--rt/+x86_64/halt.s5
-rw-r--r--rt/+x86_64/realcall.S161
-rw-r--r--rt/+x86_64/stage0.s92
-rw-r--r--rt/+x86_64/stage0_a20.S146
-rw-r--r--rt/+x86_64/stage0_drive.S118
-rw-r--r--rt/+x86_64/stage1.S191
-rw-r--r--rt/abort.ha3
-rw-r--r--rt/halt.ha1
-rw-r--r--rt/hare.sc74
-rw-r--r--rt/memset.ha6
-rw-r--r--rt/start.ha24
-rw-r--r--rt/test.ha1
-rwxr-xr-xrun.sh22
-rw-r--r--term/term.ha116
-rw-r--r--vga/vga.ha86
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
diff --git a/main.ha b/main.ha
new file mode 100644
index 0000000..edd4607
--- /dev/null
+++ b/main.ha
@@ -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;
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..0e136e4
--- /dev/null
+++ b/run.sh
@@ -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;
+ };
+};