summaryrefslogtreecommitdiff
path: root/rt/+x86_64
diff options
context:
space:
mode:
Diffstat (limited to 'rt/+x86_64')
-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
9 files changed, 779 insertions, 0 deletions
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