Real mode setup complete. Groundwork for GDT/protected mode
This commit is contained in:
parent
86c0c87425
commit
aa830de9c2
|
@ -5,37 +5,122 @@
|
|||
[org 0x7c00] ; Address where the code expects to be loaded in. The BIOS always loads us here
|
||||
[bits 16] ; All x86 CPUs start in 16 bit (aka "real") mode, so we tell nasm to emit 16-bit code
|
||||
|
||||
; We print a simple startup message using the ISRs
|
||||
; from the BIOS
|
||||
mov si, startup_msg
|
||||
call print
|
||||
|
||||
start:
|
||||
; Copied from https://github.com/limine-bootloader/limine/blob/trunk/stage1/hdd/bootsect.asm.
|
||||
; Quote:
|
||||
; Some BIOSes will do a funny and decide to overwrite bytes of code in
|
||||
; the section where a FAT BPB would be, potentially overwriting
|
||||
; bootsector code. Avoid that by filling the BPB area with dummy values.
|
||||
; Some of the values have to be set to certain values in order to boot
|
||||
; on even quirkier machines.
|
||||
; Source: https://github.com/freebsd/freebsd-src/blob/82a21151cf1d7a3e9e95b9edbbf74ac10f386d6a/stand/i386/boot2/boot1.S
|
||||
jmp skip_bpb
|
||||
nop
|
||||
bpb:
|
||||
times 3-($-$$) db 0
|
||||
.bpb_oem_id: db "TSOS "
|
||||
.bpb_sector_size: dw 512
|
||||
.bpb_sects_per_cluster: db 0
|
||||
.bpb_reserved_sects: dw 0
|
||||
.bpb_fat_count: db 0
|
||||
.bpb_root_dir_entries: dw 0
|
||||
.bpb_sector_count: dw 0
|
||||
.bpb_media_type: db 0
|
||||
.bpb_sects_per_fat: dw 0
|
||||
.bpb_sects_per_track: dw 18
|
||||
.bpb_heads_count: dw 2
|
||||
.bpb_hidden_sects: dd 0
|
||||
.bpb_sector_count_big: dd 0
|
||||
.bpb_drive_num: db 0
|
||||
.bpb_reserved: db 0
|
||||
.bpb_signature: db 0
|
||||
.bpb_volume_id: dd 0
|
||||
.bpb_volume_label: db "TSOS "
|
||||
.bpb_filesystem_type: times 8 db 0
|
||||
; The skip_bpb and initialize_cs code is adapted
|
||||
; from the Limine bootloader as well
|
||||
skip_bpb:
|
||||
cli
|
||||
cld
|
||||
jmp 0x0000:initialise_cs
|
||||
initialise_cs:
|
||||
xor si, si
|
||||
mov ds, si
|
||||
mov es, si
|
||||
mov ss, si
|
||||
sti
|
||||
; We're not made for floppy disks, these are dead anyways.
|
||||
; So if the value the BIOS passed is <0x80, just assume it has passed
|
||||
; an incorrect value.
|
||||
cmp dl, 0x80
|
||||
jb invalid_boot_device
|
||||
; Values above 0x8f are dubious so we assume we weren't booted properly
|
||||
; for those either
|
||||
cmp dl, 0x8f
|
||||
ja invalid_boot_device
|
||||
continue:
|
||||
; First off, we setup the stack by setting the
|
||||
; base pointer to address 0x8000. The address
|
||||
; itself doesn't matter as long as it's far
|
||||
; enough away from memory already in use by
|
||||
; the BIOS
|
||||
mov bp, 0x8000
|
||||
mov sp, bp ; The stack starts out empty, so sp == bp
|
||||
|
||||
; Now we setup the stack by setting the
|
||||
; base pointer to address 0x8000. The
|
||||
; address itself doesn't matter as long
|
||||
; as it's far enough away from memory already
|
||||
; in use by the BIOS
|
||||
mov bp, 0x8000
|
||||
mov sp, bp ; The stack starts out empty, so sp == bp
|
||||
; Since we have a stack, we can now call functions,
|
||||
; so we print a simple startup message using BIOS
|
||||
; routines
|
||||
mov si, startup_msg
|
||||
call bios_print
|
||||
; We also inform the user we're loading a few
|
||||
; sectors from the boot drive
|
||||
mov si, disk_read_info
|
||||
and dx, 0xff ; Gets rid of the high 8 bits of dx so
|
||||
; we only print the hex value of dh,
|
||||
; which is the type of storage device
|
||||
; we're booting from (0 = floppy,
|
||||
; 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||
call bios_print
|
||||
call bios_printh
|
||||
call bios_newline
|
||||
|
||||
; We load data from the current boot drive. The data is copied
|
||||
; to memory starting at address 0x9000
|
||||
mov bx, 0x9000
|
||||
mov dh, 2 ; Read 3 sectors (2 for our dummy sectors, 1 for our variables)
|
||||
; The dl register is already set by the BIOS
|
||||
call load_disk
|
||||
|
||||
jmp $ ; Keeps jumping at the current address (loops forever)
|
||||
; Now we retrieve the test data we placed in the other
|
||||
; sectors and print it in hexadecimal format
|
||||
mov dx, [0x9000]
|
||||
call bios_printh ; Should print 0xDADA
|
||||
call bios_newline
|
||||
mov dx, [0x9000 + 512]
|
||||
call bios_printh ; Should print OxFACE
|
||||
|
||||
; These two lines never execute (for now), but they might once
|
||||
; we jump into the kernel, so we disable interrupts and halt the
|
||||
; CPU, just in case
|
||||
cli
|
||||
hlt
|
||||
endless_loop:
|
||||
jmp $ ; Keeps jumping at the current address (loops forever)
|
||||
|
||||
invalid_boot_device:
|
||||
mov si, invalid_boot_device_msg
|
||||
call bios_println
|
||||
|
||||
; Now we include our "function definitions" (after the halting, so
|
||||
; they're never executed unless explicitly called)
|
||||
%include "src/bootloader/util/tty.s"
|
||||
; Now we include our "function definitions" (after the
|
||||
; loop, so they're never executed unless explicitly called)
|
||||
%include "src/bootloader/util/disk.s"
|
||||
%include "src/bootloader/util/io.s"
|
||||
|
||||
; Here we define our variables: They need to be defined after the
|
||||
; halting because otherwise they will be executed as code!
|
||||
startup_msg: db "TSOS - Bootloader: Starting up", 0xA, 0xD, 0x0
|
||||
; Here we define our variables: They need to be defined after the
|
||||
; halting because otherwise they will be executed as code
|
||||
startup_msg: db "TSOS is starting up", 0xA, 0xD, 0
|
||||
invalid_boot_device_msg: db "Invalid boot device", 0
|
||||
|
||||
; padding and magic number
|
||||
times 510 - ($-$$) db 0
|
||||
dw 0xaa55
|
||||
dw 0xaa55
|
||||
|
||||
; We add more sectors to our binary so we can read them
|
||||
times 256 dw 0xdada ; sector 2 = 512 bytes
|
||||
times 256 dw 0xface ; sector 3 = 512 bytes
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
; Utilities to read data from the disk. Used to load the kernel
|
||||
; into main memory
|
||||
|
||||
|
||||
load_disk:
|
||||
; Loads 'dh' sectors from drive 'dl' (this
|
||||
; register is set by the BIOS before calling
|
||||
; the bootloader) into es:bx
|
||||
pusha
|
||||
push dx
|
||||
mov ah, 0x2 ; Perform a read operation
|
||||
mov al, dh ; Number of sectors
|
||||
mov cl, 0x2 ; 0x1 is us, so 0x2 is the first sector we want
|
||||
mov ch, 0x0 ; Cylinder 0
|
||||
mov dh, 0x0 ; Head position is 0 too
|
||||
int 0x13 ; Once this returns, the data will be in es:bx
|
||||
jc disk_error ; If an error occurs, the carry bit is set
|
||||
pop dx
|
||||
; The BIOS tells us how many sectors have been
|
||||
; actually read. If the desired and actual value
|
||||
; don't match, an error occurred
|
||||
cmp al, dh
|
||||
jne sectors_error
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
disk_error:
|
||||
mov si, disk_read_error_msg
|
||||
call bios_print
|
||||
mov dh, ah ; Error code is in ah
|
||||
call bios_printh
|
||||
call bios_newline
|
||||
jmp disk_loop
|
||||
|
||||
|
||||
sectors_error:
|
||||
mov si, disk_sectors_error_msg
|
||||
call bios_println
|
||||
|
||||
|
||||
disk_loop:
|
||||
jmp $
|
||||
|
||||
|
||||
disk_read_info: db "Booting from disk type ", 0
|
||||
disk_read_error_msg: db "Read error: ", 0
|
||||
disk_sectors_error_msg: db "Read error (wrong number of sectors)", 0
|
|
@ -0,0 +1,192 @@
|
|||
; Some utilities to deal with the TTY using the BIOS
|
||||
; during real mode and writing directly to VGA memory
|
||||
; once we switch to protected mode
|
||||
[bits 16]
|
||||
|
||||
; I/O routines based on BIOS interrupt 13
|
||||
|
||||
bios_newline:
|
||||
; Points the TTY cursor to the next
|
||||
; line
|
||||
pusha
|
||||
; Source: http://www.techhelpmanual.com/118-int_10h_03h__query_cursor_position_and_size.html
|
||||
mov ah, 0x3 ; Get cursor position
|
||||
mov bh, 0x0 ; Page 0
|
||||
int 0x10
|
||||
; Go to the next row
|
||||
; Source: http://www.techhelpmanual.com/117-int_10h_02h__set_cursor_position.html
|
||||
mov ah, 0x2 ; Set cursor position
|
||||
mov bh, 0x0 ; Page 0
|
||||
xor dl, dl ; Goes to column 0 (i.e. start of the line)
|
||||
inc dh ; Goes to the next row
|
||||
int 0x10
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
bios_print:
|
||||
; Prints a null-terminated string whose address
|
||||
; is located in the si register
|
||||
pusha
|
||||
mov ah, 0xe ; Set the screen in TTY mode
|
||||
bios_print_loop:
|
||||
mov al, [si]
|
||||
int 0x10 ; Writes the content of al to the screen
|
||||
inc si
|
||||
cmp byte [si], 0x0 ; If we got to the null byte, we're done
|
||||
jne bios_print_loop ; Otherwise, we run this again
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
bios_println:
|
||||
; Prints a null-terminated string whose address
|
||||
; is located in the si register and sets the TTY
|
||||
; cursor to the next line
|
||||
pusha
|
||||
call bios_print
|
||||
call bios_newline
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
bios_printh:
|
||||
; Prints the value of dx in hexadecimal format
|
||||
pusha
|
||||
xor cx, cx ; This serves as our index and loop variable
|
||||
bios_printh_loop:
|
||||
cmp cx, 4 ; loop 4 times
|
||||
je bios_printh_end
|
||||
|
||||
; Here we extract the last digit from dx using
|
||||
; a bitmask, with ax as our working register,
|
||||
; and convert it to ASCII by adding 30 to it if
|
||||
; it's less than 9 (meaning it's a digit) or 37
|
||||
; if if it's a letter (that's because letters and
|
||||
; numbers are 7 digits apart in the ASCII table)
|
||||
mov ax, dx
|
||||
and ax, 0xf
|
||||
add al, 0x30
|
||||
cmp al, 0x39
|
||||
jle bios_printh_step2
|
||||
add al, 7
|
||||
|
||||
bios_printh_step2:
|
||||
; Now we start filling our string variable (starting from the
|
||||
; back, since we are extracting digits from the end of the
|
||||
; number) and then rotate the number by 4 bits to access the
|
||||
; next digit. This works and is the same as the more common
|
||||
; modulo division because each hexadecimal digits represents
|
||||
; exactly 4 bits and we can take advantage of the CPU's much
|
||||
; faster bitwise operations rather than burden ourselves with
|
||||
; a costly modulo 10 division (which would take hundreds of
|
||||
; clock cycles, as opposed to it only taking one for a rotate
|
||||
; operation)
|
||||
mov si, HEX_OUT + 5 ; We skip the '0x' part and jump to the last digit
|
||||
sub si, cx ; We subtract si by cx so we land on the right digit
|
||||
mov [si], al ; We copy the character in al to the character pointed to by si
|
||||
ror dx, 4 ; With an example input: 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||
inc cx
|
||||
jmp bios_printh_loop
|
||||
|
||||
bios_printh_end:
|
||||
; Since we have a string now, we just call our
|
||||
; handy routine to print it out. We're done!
|
||||
mov si, HEX_OUT
|
||||
call bios_print
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
HEX_OUT: db '0x0000', 0 ; reserve memory for our new string
|
||||
|
||||
[bits 32]
|
||||
|
||||
; I/O routines that directly manipulate video memory
|
||||
VMEM_START: equ 0xb8000 ; Video memory always starts at this address
|
||||
; A character on the screen in VGA text mode is composed of 2 bytes:
|
||||
; the first byte is the ASCII codepoint to be printed, while the next
|
||||
; octet represents additional formatting information (color, blink,
|
||||
; underline, etc.). More info: https://en.wikipedia.org/wiki/VGA_text_mode
|
||||
WHITE_ON_BLACK: equ 0x0f
|
||||
|
||||
|
||||
vga_print:
|
||||
; Prints a null-terminated string located
|
||||
; on the esi register
|
||||
pusha
|
||||
mov edx, VMEM_START
|
||||
|
||||
vga_print_loop:
|
||||
mov al, [esi]
|
||||
mov ah, WHITE_ON_BLACK
|
||||
cmp al, 0
|
||||
je vga_print_done ; If we're at the null byte, we exit
|
||||
mov [edx], ax ; Write the 2-byte character to video memory
|
||||
inc esi ; Go to the next character in the string
|
||||
add ebx, 2 ; Go to the next character in video memory
|
||||
jmp vga_print_loop
|
||||
|
||||
vga_print_done:
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
vga_println:
|
||||
; Prints a null-terminated string located on the
|
||||
; esi register and terminates it with a newline
|
||||
pusha
|
||||
call vga_print
|
||||
mov esi, NEWLINE
|
||||
call vga_print
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
vga_printh:
|
||||
; Prints the value of edx in hexadecimal format
|
||||
pusha
|
||||
xor ecx, ecx ; This serves as our index and loop counter
|
||||
vga_printh_loop:
|
||||
cmp ecx, 8 ; loop 8 times
|
||||
je vga_printh_end
|
||||
; Here we extract the last digit from edx using
|
||||
; a bitmask, with eax as our working register,
|
||||
; and convert it to ASCII by adding 30 to it if
|
||||
; it's less than 9 (meaning it's a digit) or 37
|
||||
; if if it's a letter (that's because letters and
|
||||
; numbers are 7 digits apart in the ASCII table)
|
||||
mov eax, edx
|
||||
and eax, 0xf
|
||||
add ax, 0x30
|
||||
cmp ax, 0x39
|
||||
jle vga_printh_step2
|
||||
add ax, 7
|
||||
|
||||
vga_printh_step2:
|
||||
; Now we start filling our string variable (starting from the
|
||||
; back, since we are extracting digits from the end of the
|
||||
; number) and then rotate the number by 4 bits to access the
|
||||
; next digit. This works and is the same as the more common
|
||||
; modulo division because each hexadecimal digits represents
|
||||
; exactly 4 bits and we can take advantage of the CPU's much
|
||||
; faster bitwise operations rather than burden ourselves with
|
||||
; a costly modulo 10 division (which would take hundreds of
|
||||
; clock cycles, as opposed to it only taking one for a rotate
|
||||
; operation)
|
||||
mov esi, HEX_OUT_LONG + 9 ; We skip the '0x' part and jump to the last digit
|
||||
sub esi, ecx ; We subtract esi by ecx so we land on the right digit
|
||||
mov [esi], ax ; We copy the character in ax to the character pointed to by esi
|
||||
ror edx, 4 ; With an example input: 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||
inc ecx
|
||||
jmp vga_printh_loop
|
||||
|
||||
vga_printh_end:
|
||||
mov esi, HEX_OUT_LONG
|
||||
call vga_print
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
NEWLINE: db 0xA, 0xD
|
||||
HEX_OUT_LONG: db '0x00000000', 0
|
|
@ -1,44 +0,0 @@
|
|||
; Some utilities to deal with the TTY using the BIOS
|
||||
; during real mode
|
||||
|
||||
next_line:
|
||||
; Points the TTY cursor to the next
|
||||
; line
|
||||
pusha
|
||||
; Source: http://www.techhelpmanual.com/118-int_10h_03h__query_cursor_position_and_size.html
|
||||
mov ah, 0x3 ; Get cursor position
|
||||
mov bh, 0x0 ; Page 0
|
||||
int 0x10
|
||||
; Go to the next row
|
||||
; Source: http://www.techhelpmanual.com/117-int_10h_02h__set_cursor_position.html
|
||||
mov ah, 0x2 ; Set cursor position
|
||||
mov bh, 0x0 ; Page 0
|
||||
xor dl, dl ; Goes to column 0 (i.e. start of the line)
|
||||
inc dh ; Goes to the next row
|
||||
int 0x10
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
print:
|
||||
; Prints a null-terminated string whose address
|
||||
; is located in the si register
|
||||
pusha
|
||||
mov ah, 0xe ; Set the screen in TTY mode
|
||||
print_loop:
|
||||
mov al, [si]
|
||||
int 0x10 ; Writes the content of al to the screen
|
||||
inc si
|
||||
cmp byte [si], 0x0 ; If we got to the null byte, we're done
|
||||
jne print_loop ; Otherwise, we run this again
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
println:
|
||||
; Prints a null-terminated string whose address
|
||||
; is located in the si register and sets the TTY
|
||||
; cursor to the next line
|
||||
call print
|
||||
call next_line
|
||||
ret
|
Loading…
Reference in New Issue