Extracted provided zip.
This commit is contained in:
parent
18802a2957
commit
f3bdacdd2c
BIN
Part1/.DS_Store
vendored
BIN
Part1/.DS_Store
vendored
Binary file not shown.
51
Part1/A4/01_gdb/Makefile
Executable file
51
Part1/A4/01_gdb/Makefile
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
gdb:
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
|
26
Part1/A4/01_gdb/boot/32bit_print.asm
Executable file
26
Part1/A4/01_gdb/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/01_gdb/boot/bootsect.asm
Executable file
51
Part1/A4/01_gdb/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/01_gdb/boot/disk.asm
Executable file
46
Part1/A4/01_gdb/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/01_gdb/boot/gdt.asm
Executable file
35
Part1/A4/01_gdb/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/01_gdb/boot/kernel_entry.asm
Executable file
4
Part1/A4/01_gdb/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/01_gdb/boot/print.asm
Executable file
37
Part1/A4/01_gdb/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/01_gdb/boot/print_hex.asm
Executable file
46
Part1/A4/01_gdb/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/01_gdb/boot/switch_pm.asm
Executable file
22
Part1/A4/01_gdb/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
8
Part1/A4/01_gdb/kernel/kernel.c
Executable file
8
Part1/A4/01_gdb/kernel/kernel.c
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
/* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */
|
||||||
|
void dummy_test_entrypoint() {
|
||||||
|
}
|
||||||
|
// use _start instead of main, kinda like begin for arduino
|
||||||
|
void _start() {
|
||||||
|
char* video_memory = (char*) 0xb8000;
|
||||||
|
*video_memory = 'X';
|
||||||
|
}
|
52
Part1/A4/02_driver/Makefile
Executable file
52
Part1/A4/02_driver/Makefile
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
all: run
|
||||||
|
|
||||||
|
# concatanate the binary files to make kernel image
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
|
26
Part1/A4/02_driver/boot/32bit_print.asm
Executable file
26
Part1/A4/02_driver/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/02_driver/boot/bootsect.asm
Executable file
51
Part1/A4/02_driver/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/02_driver/boot/disk.asm
Executable file
46
Part1/A4/02_driver/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/02_driver/boot/gdt.asm
Executable file
35
Part1/A4/02_driver/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/02_driver/boot/kernel_entry.asm
Executable file
4
Part1/A4/02_driver/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/02_driver/boot/print.asm
Executable file
37
Part1/A4/02_driver/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/02_driver/boot/print_hex.asm
Executable file
46
Part1/A4/02_driver/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/02_driver/boot/switch_pm.asm
Executable file
22
Part1/A4/02_driver/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
35
Part1/A4/02_driver/drivers/ports.c
Executable file
35
Part1/A4/02_driver/drivers/ports.c
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Read a byte from the specified port
|
||||||
|
*/
|
||||||
|
unsigned char port_byte_in (unsigned short port) {
|
||||||
|
unsigned char result;
|
||||||
|
/* Inline assembler syntax
|
||||||
|
* !! Notice how the source and destination registers are switched from NASM !!
|
||||||
|
*
|
||||||
|
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
|
||||||
|
* '"d" (port)': map the C variable '(port)' into e'd'x register
|
||||||
|
*
|
||||||
|
* Inputs and outputs are separated by colons
|
||||||
|
*/
|
||||||
|
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data) {
|
||||||
|
/* Notice how here both registers are mapped to C variables and
|
||||||
|
* nothing is returned, thus, no equals '=' in the asm syntax
|
||||||
|
* However we see a comma since there are two variables in the input area
|
||||||
|
* and none in the 'return' area
|
||||||
|
*/
|
||||||
|
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short port_word_in (unsigned short port) {
|
||||||
|
unsigned short result;
|
||||||
|
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_word_out (unsigned short port, unsigned short data) {
|
||||||
|
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
4
Part1/A4/02_driver/drivers/ports.h
Executable file
4
Part1/A4/02_driver/drivers/ports.h
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
unsigned char port_byte_in (unsigned short port);
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data);
|
||||||
|
unsigned short port_word_in (unsigned short port);
|
||||||
|
void port_word_out (unsigned short port, unsigned short data);
|
32
Part1/A4/02_driver/kernel/kernel.c
Executable file
32
Part1/A4/02_driver/kernel/kernel.c
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#include "../drivers/ports.h"
|
||||||
|
|
||||||
|
void _start() {
|
||||||
|
/* Screen cursor position: ask VGA control register (0x3d4) for bytes
|
||||||
|
* 14 = high byte of cursor and 15 = low byte of cursor. */
|
||||||
|
port_byte_out(0x3d4, 14); /* Requesting byte 14: high byte of cursor pos */
|
||||||
|
/* Data is returned in VGA data register (0x3d5) */
|
||||||
|
int position = port_byte_in(0x3d5);
|
||||||
|
position = position << 8; /* high byte */
|
||||||
|
|
||||||
|
port_byte_out(0x3d4, 15); /* requesting low byte */
|
||||||
|
position += port_byte_in(0x3d5);
|
||||||
|
|
||||||
|
/* VGA 'cells' consist of the character and its control data
|
||||||
|
* e.g. 'white on black background', 'red text on white bg', etc */
|
||||||
|
int offset_from_vga = position * 2;
|
||||||
|
|
||||||
|
/* Now you can examine both variables using gdb, since we still
|
||||||
|
* don't know how to print strings on screen. Run 'make debug' and
|
||||||
|
* on the gdb console:
|
||||||
|
* breakpoint kernel.c:21
|
||||||
|
* continue
|
||||||
|
* print position
|
||||||
|
* print offset_from_vga
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Let's write on the current cursor position, we already know how
|
||||||
|
* to do that */
|
||||||
|
char *vga = (char *) 0xb8000;
|
||||||
|
vga[offset_from_vga] = 'X';
|
||||||
|
vga[offset_from_vga+1] = 0x0f; /* White text on black background */
|
||||||
|
}
|
49
Part1/A4/03_strings/Makefile
Executable file
49
Part1/A4/03_strings/Makefile
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
|
26
Part1/A4/03_strings/boot/32bit_print.asm
Executable file
26
Part1/A4/03_strings/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/03_strings/boot/bootsect.asm
Executable file
51
Part1/A4/03_strings/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/03_strings/boot/disk.asm
Executable file
46
Part1/A4/03_strings/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/03_strings/boot/gdt.asm
Executable file
35
Part1/A4/03_strings/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/03_strings/boot/kernel_entry.asm
Executable file
4
Part1/A4/03_strings/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/03_strings/boot/print.asm
Executable file
37
Part1/A4/03_strings/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/03_strings/boot/print_hex.asm
Executable file
46
Part1/A4/03_strings/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/03_strings/boot/switch_pm.asm
Executable file
22
Part1/A4/03_strings/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
35
Part1/A4/03_strings/drivers/ports.c
Executable file
35
Part1/A4/03_strings/drivers/ports.c
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Read a byte from the specified port
|
||||||
|
*/
|
||||||
|
unsigned char port_byte_in (unsigned short port) {
|
||||||
|
unsigned char result;
|
||||||
|
/* Inline assembler syntax
|
||||||
|
* !! Notice how the source and destination registers are switched from NASM !!
|
||||||
|
*
|
||||||
|
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
|
||||||
|
* '"d" (port)': map the C variable '(port)' into e'd'x register
|
||||||
|
*
|
||||||
|
* Inputs and outputs are separated by colons
|
||||||
|
*/
|
||||||
|
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data) {
|
||||||
|
/* Notice how here both registers are mapped to C variables and
|
||||||
|
* nothing is returned, thus, no equals '=' in the asm syntax
|
||||||
|
* However we see a comma since there are two variables in the input area
|
||||||
|
* and none in the 'return' area
|
||||||
|
*/
|
||||||
|
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short port_word_in (unsigned short port) {
|
||||||
|
unsigned short result;
|
||||||
|
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_word_out (unsigned short port, unsigned short data) {
|
||||||
|
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
4
Part1/A4/03_strings/drivers/ports.h
Executable file
4
Part1/A4/03_strings/drivers/ports.h
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
unsigned char port_byte_in (unsigned short port);
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data);
|
||||||
|
unsigned short port_word_in (unsigned short port);
|
||||||
|
void port_word_out (unsigned short port, unsigned short data);
|
122
Part1/A4/03_strings/drivers/screen.c
Executable file
122
Part1/A4/03_strings/drivers/screen.c
Executable file
@ -0,0 +1,122 @@
|
|||||||
|
#include "screen.h"
|
||||||
|
#include "ports.h"
|
||||||
|
|
||||||
|
/* Declaration of private functions */
|
||||||
|
int get_cursor_offset();
|
||||||
|
void set_cursor_offset(int offset);
|
||||||
|
int print_char(char c, int col, int row, char attr);
|
||||||
|
int get_offset(int col, int row);
|
||||||
|
int get_offset_row(int offset);
|
||||||
|
int get_offset_col(int offset);
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Public Kernel API functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a message on the specified location
|
||||||
|
* If col, row, are negative, we will use the current offset
|
||||||
|
*/
|
||||||
|
void kprint_at(char *message, int col, int row) {
|
||||||
|
/* Set cursor if col/row are negative */
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0)
|
||||||
|
offset = get_offset(col, row);
|
||||||
|
else {
|
||||||
|
offset = get_cursor_offset();
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop through message and print it */
|
||||||
|
int i = 0;
|
||||||
|
while (message[i] != 0) {
|
||||||
|
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
|
||||||
|
/* Compute row/col for next iteration */
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kprint(char *message) {
|
||||||
|
kprint_at(message, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Private kernel functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Innermost print function for our kernel, directly accesses the video memory
|
||||||
|
*
|
||||||
|
* If 'col' and 'row' are negative, we will print at current cursor location
|
||||||
|
* If 'attr' is zero it will use 'white on black' as default
|
||||||
|
* Returns the offset of the next character
|
||||||
|
* Sets the video cursor to the returned offset
|
||||||
|
*/
|
||||||
|
int print_char(char c, int col, int row, char attr) {
|
||||||
|
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
|
||||||
|
if (!attr) attr = WHITE_ON_BLACK;
|
||||||
|
|
||||||
|
/* Error control: print a red 'E' if the coords aren't right */
|
||||||
|
if (col >= MAX_COLS || row >= MAX_ROWS) {
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
|
||||||
|
return get_offset(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0) offset = get_offset(col, row);
|
||||||
|
else offset = get_cursor_offset();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
offset = get_offset(0, row+1);
|
||||||
|
} else {
|
||||||
|
vidmem[offset] = c;
|
||||||
|
vidmem[offset+1] = attr;
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
set_cursor_offset(offset);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_cursor_offset() {
|
||||||
|
/* Use the VGA ports to get the current cursor position
|
||||||
|
* 1. Ask for high byte of the cursor offset (data 14)
|
||||||
|
* 2. Ask for low byte (data 15)
|
||||||
|
*/
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
offset += port_byte_in(REG_SCREEN_DATA);
|
||||||
|
return offset * 2; /* Position * size of character cell */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor_offset(int offset) {
|
||||||
|
/* Similar to get_cursor_offset, but instead of reading we write data */
|
||||||
|
offset /= 2;
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_screen() {
|
||||||
|
int screen_size = MAX_COLS * MAX_ROWS;
|
||||||
|
int i;
|
||||||
|
char *screen = (char *) VIDEO_ADDRESS;
|
||||||
|
|
||||||
|
for (i = 0; i < screen_size; i++) {
|
||||||
|
screen[i*2] = ' ';
|
||||||
|
screen[i*2+1] = WHITE_ON_BLACK;
|
||||||
|
}
|
||||||
|
set_cursor_offset(get_offset(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
|
||||||
|
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
|
||||||
|
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
|
14
Part1/A4/03_strings/drivers/screen.h
Executable file
14
Part1/A4/03_strings/drivers/screen.h
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#define VIDEO_ADDRESS 0xb8000
|
||||||
|
#define MAX_ROWS 25
|
||||||
|
#define MAX_COLS 80
|
||||||
|
#define WHITE_ON_BLACK 0x0f
|
||||||
|
#define RED_ON_WHITE 0xf4
|
||||||
|
|
||||||
|
/* Screen i/o ports */
|
||||||
|
#define REG_SCREEN_CTRL 0x3d4
|
||||||
|
#define REG_SCREEN_DATA 0x3d5
|
||||||
|
|
||||||
|
/* Public kernel API */
|
||||||
|
void clear_screen();
|
||||||
|
void kprint_at(char *message, int col, int row);
|
||||||
|
void kprint(char *message);
|
10
Part1/A4/03_strings/kernel/kernel.c
Executable file
10
Part1/A4/03_strings/kernel/kernel.c
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#include "../drivers/screen.h"
|
||||||
|
|
||||||
|
void _start() {
|
||||||
|
clear_screen();
|
||||||
|
kprint_at("X", 1, 6);
|
||||||
|
kprint_at("This text spans multiple lines", 75, 10);
|
||||||
|
kprint_at("There is a line\nbreak", 0, 20);
|
||||||
|
kprint("There is a line\nbreak");
|
||||||
|
kprint_at("What happens when we run out of space?", 45, 24);
|
||||||
|
}
|
49
Part1/A4/04_scroll/Makefile
Executable file
49
Part1/A4/04_scroll/Makefile
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
|
26
Part1/A4/04_scroll/boot/32bit_print.asm
Executable file
26
Part1/A4/04_scroll/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/04_scroll/boot/bootsect.asm
Executable file
51
Part1/A4/04_scroll/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/04_scroll/boot/disk.asm
Executable file
46
Part1/A4/04_scroll/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/04_scroll/boot/gdt.asm
Executable file
35
Part1/A4/04_scroll/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/04_scroll/boot/kernel_entry.asm
Executable file
4
Part1/A4/04_scroll/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/04_scroll/boot/print.asm
Executable file
37
Part1/A4/04_scroll/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/04_scroll/boot/print_hex.asm
Executable file
46
Part1/A4/04_scroll/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/04_scroll/boot/switch_pm.asm
Executable file
22
Part1/A4/04_scroll/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
35
Part1/A4/04_scroll/drivers/ports.c
Executable file
35
Part1/A4/04_scroll/drivers/ports.c
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Read a byte from the specified port
|
||||||
|
*/
|
||||||
|
unsigned char port_byte_in (unsigned short port) {
|
||||||
|
unsigned char result;
|
||||||
|
/* Inline assembler syntax
|
||||||
|
* !! Notice how the source and destination registers are switched from NASM !!
|
||||||
|
*
|
||||||
|
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
|
||||||
|
* '"d" (port)': map the C variable '(port)' into e'd'x register
|
||||||
|
*
|
||||||
|
* Inputs and outputs are separated by colons
|
||||||
|
*/
|
||||||
|
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data) {
|
||||||
|
/* Notice how here both registers are mapped to C variables and
|
||||||
|
* nothing is returned, thus, no equals '=' in the asm syntax
|
||||||
|
* However we see a comma since there are two variables in the input area
|
||||||
|
* and none in the 'return' area
|
||||||
|
*/
|
||||||
|
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short port_word_in (unsigned short port) {
|
||||||
|
unsigned short result;
|
||||||
|
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_word_out (unsigned short port, unsigned short data) {
|
||||||
|
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
4
Part1/A4/04_scroll/drivers/ports.h
Executable file
4
Part1/A4/04_scroll/drivers/ports.h
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
unsigned char port_byte_in (unsigned short port);
|
||||||
|
void port_byte_out (unsigned short port, unsigned char data);
|
||||||
|
unsigned short port_word_in (unsigned short port);
|
||||||
|
void port_word_out (unsigned short port, unsigned short data);
|
139
Part1/A4/04_scroll/drivers/screen.c
Executable file
139
Part1/A4/04_scroll/drivers/screen.c
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#include "screen.h"
|
||||||
|
#include "ports.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
/* Declaration of private functions */
|
||||||
|
int get_cursor_offset();
|
||||||
|
void set_cursor_offset(int offset);
|
||||||
|
int print_char(char c, int col, int row, char attr);
|
||||||
|
int get_offset(int col, int row);
|
||||||
|
int get_offset_row(int offset);
|
||||||
|
int get_offset_col(int offset);
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Public Kernel API functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a message on the specified location
|
||||||
|
* If col, row, are negative, we will use the current offset
|
||||||
|
*/
|
||||||
|
void kprint_at(char *message, int col, int row) {
|
||||||
|
/* Set cursor if col/row are negative */
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0)
|
||||||
|
offset = get_offset( col, row);
|
||||||
|
else {
|
||||||
|
offset = get_cursor_offset();
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop through message and print it */
|
||||||
|
int i = 0;
|
||||||
|
while (message[i] != 0) {
|
||||||
|
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
|
||||||
|
/* Compute row/col for next iteration */
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kprint(char *message) {
|
||||||
|
kprint_at(message, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Private kernel functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Innermost print function for our kernel, directly accesses the video memory
|
||||||
|
*
|
||||||
|
* If 'col' and 'row' are negative, we will print at current cursor location
|
||||||
|
* If 'attr' is zero it will use 'white on black' as default
|
||||||
|
* Returns the offset of the next character
|
||||||
|
* Sets the video cursor to the returned offset
|
||||||
|
*/
|
||||||
|
int print_char(char c, int col, int row, char attr) {
|
||||||
|
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
|
||||||
|
if (!attr) attr = WHITE_ON_BLACK;
|
||||||
|
|
||||||
|
/* Error control: print a red 'E' if the coords aren't right */
|
||||||
|
if (col >= MAX_COLS || row >= MAX_ROWS) {
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
|
||||||
|
return get_offset(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0) offset = get_offset(col, row);
|
||||||
|
else offset = get_cursor_offset();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
offset = get_offset(0, row+1);
|
||||||
|
} else {
|
||||||
|
vidmem[offset] = c;
|
||||||
|
vidmem[offset+1] = attr;
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the offset is over screen size and scroll */
|
||||||
|
if (offset >= MAX_ROWS * MAX_COLS * 2) {
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < MAX_ROWS; i++)
|
||||||
|
memory_copy( (char *) (get_offset(0, i) + VIDEO_ADDRESS),
|
||||||
|
(char *) (get_offset(0, i-1) + VIDEO_ADDRESS),
|
||||||
|
MAX_COLS * 2);
|
||||||
|
|
||||||
|
/* Blank last line */
|
||||||
|
char *last_line = (char *) (get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS);
|
||||||
|
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
|
||||||
|
|
||||||
|
offset -= 2 * MAX_COLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cursor_offset(offset);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_cursor_offset() {
|
||||||
|
/* Use the VGA ports to get the current cursor position
|
||||||
|
* 1. Ask for high byte of the cursor offset (data 14)
|
||||||
|
* 2. Ask for low byte (data 15)
|
||||||
|
*/
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
offset += port_byte_in(REG_SCREEN_DATA);
|
||||||
|
return offset * 2; /* Position * size of character cell */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor_offset(int offset) {
|
||||||
|
/* Similar to get_cursor_offset, but instead of reading we write data */
|
||||||
|
offset /= 2;
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_screen() {
|
||||||
|
int screen_size = MAX_COLS * MAX_ROWS;
|
||||||
|
int i;
|
||||||
|
char *screen = (char *) VIDEO_ADDRESS;
|
||||||
|
|
||||||
|
for (i = 0; i < screen_size; i++) {
|
||||||
|
screen[i*2] = ' ';
|
||||||
|
screen[i*2+1] = WHITE_ON_BLACK;
|
||||||
|
}
|
||||||
|
set_cursor_offset(get_offset(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
|
||||||
|
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
|
||||||
|
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
|
14
Part1/A4/04_scroll/drivers/screen.h
Executable file
14
Part1/A4/04_scroll/drivers/screen.h
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#define VIDEO_ADDRESS 0xb8000
|
||||||
|
#define MAX_ROWS 25
|
||||||
|
#define MAX_COLS 80
|
||||||
|
#define WHITE_ON_BLACK 0x0f
|
||||||
|
#define RED_ON_WHITE 0xf4
|
||||||
|
|
||||||
|
/* Screen i/o ports */
|
||||||
|
#define REG_SCREEN_CTRL 0x3d4
|
||||||
|
#define REG_SCREEN_DATA 0x3d5
|
||||||
|
|
||||||
|
/* Public kernel API */
|
||||||
|
void clear_screen();
|
||||||
|
void kprint_at(char *message, int col, int row);
|
||||||
|
void kprint(char *message);
|
17
Part1/A4/04_scroll/kernel/kernel.c
Executable file
17
Part1/A4/04_scroll/kernel/kernel.c
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#include "../drivers/screen.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
void _start() {
|
||||||
|
clear_screen();
|
||||||
|
|
||||||
|
/* Fill up the screen */
|
||||||
|
int i = 0;
|
||||||
|
for (i = 0; i < 24; i++) {
|
||||||
|
char str[255];
|
||||||
|
int_to_ascii(i, str);
|
||||||
|
kprint_at(str, 0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
kprint_at("This text forces the kernel to scroll. Row 0 will disappear. ", 60, 24);
|
||||||
|
kprint("And with this text, the kernel will scroll again, and row 1 will disappear too!");
|
||||||
|
}
|
23
Part1/A4/04_scroll/kernel/util.c
Executable file
23
Part1/A4/04_scroll/kernel/util.c
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
void memory_copy(char *source, char *dest, int nbytes) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < nbytes; i++) {
|
||||||
|
*(dest + i) = *(source + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K&R implementation
|
||||||
|
*/
|
||||||
|
void int_to_ascii(int n, char str[]) {
|
||||||
|
int i, sign;
|
||||||
|
if ((sign = n) < 0) n = -n;
|
||||||
|
i = 0;
|
||||||
|
do {
|
||||||
|
str[i++] = n % 10 + '0';
|
||||||
|
} while ((n /= 10) > 0);
|
||||||
|
|
||||||
|
if (sign < 0) str[i++] = '-';
|
||||||
|
str[i] = '\0';
|
||||||
|
|
||||||
|
/* TODO: implement "reverse" */
|
||||||
|
}
|
2
Part1/A4/04_scroll/kernel/util.h
Executable file
2
Part1/A4/04_scroll/kernel/util.h
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
void memory_copy(char *source, char *dest, int nbytes);
|
||||||
|
void int_to_ascii(int n, char str[]);
|
49
Part1/A4/05_basic_int/Makefile
Executable file
49
Part1/A4/05_basic_int/Makefile
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw -d guest_errors,int &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
|
26
Part1/A4/05_basic_int/boot/32bit_print.asm
Executable file
26
Part1/A4/05_basic_int/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/05_basic_int/boot/bootsect.asm
Executable file
51
Part1/A4/05_basic_int/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/05_basic_int/boot/disk.asm
Executable file
46
Part1/A4/05_basic_int/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/05_basic_int/boot/gdt.asm
Executable file
35
Part1/A4/05_basic_int/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/05_basic_int/boot/kernel_entry.asm
Executable file
4
Part1/A4/05_basic_int/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/05_basic_int/boot/print.asm
Executable file
37
Part1/A4/05_basic_int/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/05_basic_int/boot/print_hex.asm
Executable file
46
Part1/A4/05_basic_int/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/05_basic_int/boot/switch_pm.asm
Executable file
22
Part1/A4/05_basic_int/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
17
Part1/A4/05_basic_int/cpu/idt.c
Executable file
17
Part1/A4/05_basic_int/cpu/idt.c
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#include "idt.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
void set_idt_gate(int n, u32 handler) {
|
||||||
|
idt[n].low_offset = low_16(handler);
|
||||||
|
idt[n].sel = KERNEL_CS;
|
||||||
|
idt[n].always0 = 0;
|
||||||
|
idt[n].flags = 0x8E;
|
||||||
|
idt[n].high_offset = high_16(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_idt() {
|
||||||
|
idt_reg.base = (u32) &idt;
|
||||||
|
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
|
||||||
|
/* Don't make the mistake of loading &idt -- always load &idt_reg */
|
||||||
|
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
|
||||||
|
}
|
39
Part1/A4/05_basic_int/cpu/idt.h
Executable file
39
Part1/A4/05_basic_int/cpu/idt.h
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef IDT_H
|
||||||
|
#define IDT_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/* Segment selectors */
|
||||||
|
#define KERNEL_CS 0x08
|
||||||
|
|
||||||
|
/* How every interrupt gate (handler) is defined */
|
||||||
|
typedef struct {
|
||||||
|
u16 low_offset; /* Lower 16 bits of handler function address */
|
||||||
|
u16 sel; /* Kernel segment selector */
|
||||||
|
u8 always0;
|
||||||
|
/* First byte
|
||||||
|
* Bit 7: "Interrupt is present"
|
||||||
|
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
|
||||||
|
* Bit 4: Set to 0 for interrupt gates
|
||||||
|
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
|
||||||
|
u8 flags;
|
||||||
|
u16 high_offset; /* Higher 16 bits of handler function address */
|
||||||
|
} __attribute__((packed)) idt_gate_t ;
|
||||||
|
|
||||||
|
/* A pointer to the array of interrupt handlers.
|
||||||
|
* Assembly instruction 'lidt' will read it */
|
||||||
|
typedef struct {
|
||||||
|
u16 limit;
|
||||||
|
u32 base;
|
||||||
|
} __attribute__((packed)) idt_register_t;
|
||||||
|
|
||||||
|
#define IDT_ENTRIES 256
|
||||||
|
idt_gate_t idt[IDT_ENTRIES];
|
||||||
|
idt_register_t idt_reg;
|
||||||
|
|
||||||
|
|
||||||
|
/* Functions implemented in idt.c */
|
||||||
|
void set_idt_gate(int n, u32 handler);
|
||||||
|
void set_idt();
|
||||||
|
|
||||||
|
#endif
|
287
Part1/A4/05_basic_int/cpu/interrupt.asm
Executable file
287
Part1/A4/05_basic_int/cpu/interrupt.asm
Executable file
@ -0,0 +1,287 @@
|
|||||||
|
; Defined in isr.c
|
||||||
|
[extern isr_handler]
|
||||||
|
|
||||||
|
; Common ISR code
|
||||||
|
isr_common_stub:
|
||||||
|
; 1. Save CPU state
|
||||||
|
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
|
||||||
|
mov ax, ds ; Lower 16-bits of eax = ds.
|
||||||
|
push eax ; save the data segment descriptor
|
||||||
|
mov ax, 0x10 ; kernel data segment descriptor
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
; 2. Call C handler
|
||||||
|
call isr_handler
|
||||||
|
|
||||||
|
; 3. Restore state
|
||||||
|
pop eax
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
popa
|
||||||
|
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
|
||||||
|
sti
|
||||||
|
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
|
||||||
|
|
||||||
|
; We don't get information about which interrupt was caller
|
||||||
|
; when the handler is run, so we will need to have a different handler
|
||||||
|
; for every interrupt.
|
||||||
|
; Furthermore, some interrupts push an error code onto the stack but others
|
||||||
|
; don't, so we will push a dummy error code for those which don't, so that
|
||||||
|
; we have a consistent stack for all of them.
|
||||||
|
|
||||||
|
; First make the ISRs global
|
||||||
|
global isr0
|
||||||
|
global isr1
|
||||||
|
global isr2
|
||||||
|
global isr3
|
||||||
|
global isr4
|
||||||
|
global isr5
|
||||||
|
global isr6
|
||||||
|
global isr7
|
||||||
|
global isr8
|
||||||
|
global isr9
|
||||||
|
global isr10
|
||||||
|
global isr11
|
||||||
|
global isr12
|
||||||
|
global isr13
|
||||||
|
global isr14
|
||||||
|
global isr15
|
||||||
|
global isr16
|
||||||
|
global isr17
|
||||||
|
global isr18
|
||||||
|
global isr19
|
||||||
|
global isr20
|
||||||
|
global isr21
|
||||||
|
global isr22
|
||||||
|
global isr23
|
||||||
|
global isr24
|
||||||
|
global isr25
|
||||||
|
global isr26
|
||||||
|
global isr27
|
||||||
|
global isr28
|
||||||
|
global isr29
|
||||||
|
global isr30
|
||||||
|
global isr31
|
||||||
|
|
||||||
|
; 0: Divide By Zero Exception
|
||||||
|
isr0:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 0
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 1: Debug Exception
|
||||||
|
isr1:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 1
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 2: Non Maskable Interrupt Exception
|
||||||
|
isr2:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 2
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 3: Int 3 Exception
|
||||||
|
isr3:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 3
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 4: INTO Exception
|
||||||
|
isr4:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 4
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 5: Out of Bounds Exception
|
||||||
|
isr5:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 5
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 6: Invalid Opcode Exception
|
||||||
|
isr6:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 6
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 7: Coprocessor Not Available Exception
|
||||||
|
isr7:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 7
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 8: Double Fault Exception (With Error Code!)
|
||||||
|
isr8:
|
||||||
|
cli
|
||||||
|
push byte 8
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 9: Coprocessor Segment Overrun Exception
|
||||||
|
isr9:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 9
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 10: Bad TSS Exception (With Error Code!)
|
||||||
|
isr10:
|
||||||
|
cli
|
||||||
|
push byte 10
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 11: Segment Not Present Exception (With Error Code!)
|
||||||
|
isr11:
|
||||||
|
cli
|
||||||
|
push byte 11
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 12: Stack Fault Exception (With Error Code!)
|
||||||
|
isr12:
|
||||||
|
cli
|
||||||
|
push byte 12
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 13: General Protection Fault Exception (With Error Code!)
|
||||||
|
isr13:
|
||||||
|
cli
|
||||||
|
push byte 13
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 14: Page Fault Exception (With Error Code!)
|
||||||
|
isr14:
|
||||||
|
cli
|
||||||
|
push byte 14
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 15: Reserved Exception
|
||||||
|
isr15:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 15
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 16: Floating Point Exception
|
||||||
|
isr16:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 16
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 17: Alignment Check Exception
|
||||||
|
isr17:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 17
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 18: Machine Check Exception
|
||||||
|
isr18:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 18
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 19: Reserved
|
||||||
|
isr19:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 19
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 20: Reserved
|
||||||
|
isr20:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 20
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 21: Reserved
|
||||||
|
isr21:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 21
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 22: Reserved
|
||||||
|
isr22:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 22
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 23: Reserved
|
||||||
|
isr23:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 23
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 24: Reserved
|
||||||
|
isr24:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 24
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 25: Reserved
|
||||||
|
isr25:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 25
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 26: Reserved
|
||||||
|
isr26:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 26
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 27: Reserved
|
||||||
|
isr27:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 27
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 28: Reserved
|
||||||
|
isr28:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 28
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 29: Reserved
|
||||||
|
isr29:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 29
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 30: Reserved
|
||||||
|
isr30:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 30
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 31: Reserved
|
||||||
|
isr31:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 31
|
||||||
|
jmp isr_common_stub
|
92
Part1/A4/05_basic_int/cpu/isr.c
Executable file
92
Part1/A4/05_basic_int/cpu/isr.c
Executable file
@ -0,0 +1,92 @@
|
|||||||
|
#include "isr.h"
|
||||||
|
#include "idt.h"
|
||||||
|
#include "../drivers/screen.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
/* Can't do this with a loop because we need the address
|
||||||
|
* of the function names */
|
||||||
|
void isr_install() {
|
||||||
|
set_idt_gate(0, (u32)isr0);
|
||||||
|
set_idt_gate(1, (u32)isr1);
|
||||||
|
set_idt_gate(2, (u32)isr2);
|
||||||
|
set_idt_gate(3, (u32)isr3);
|
||||||
|
set_idt_gate(4, (u32)isr4);
|
||||||
|
set_idt_gate(5, (u32)isr5);
|
||||||
|
set_idt_gate(6, (u32)isr6);
|
||||||
|
set_idt_gate(7, (u32)isr7);
|
||||||
|
set_idt_gate(8, (u32)isr8);
|
||||||
|
set_idt_gate(9, (u32)isr9);
|
||||||
|
set_idt_gate(10, (u32)isr10);
|
||||||
|
set_idt_gate(11, (u32)isr11);
|
||||||
|
set_idt_gate(12, (u32)isr12);
|
||||||
|
set_idt_gate(13, (u32)isr13);
|
||||||
|
set_idt_gate(14, (u32)isr14);
|
||||||
|
set_idt_gate(15, (u32)isr15);
|
||||||
|
set_idt_gate(16, (u32)isr16);
|
||||||
|
set_idt_gate(17, (u32)isr17);
|
||||||
|
set_idt_gate(18, (u32)isr18);
|
||||||
|
set_idt_gate(19, (u32)isr19);
|
||||||
|
set_idt_gate(20, (u32)isr20);
|
||||||
|
set_idt_gate(21, (u32)isr21);
|
||||||
|
set_idt_gate(22, (u32)isr22);
|
||||||
|
set_idt_gate(23, (u32)isr23);
|
||||||
|
set_idt_gate(24, (u32)isr24);
|
||||||
|
set_idt_gate(25, (u32)isr25);
|
||||||
|
set_idt_gate(26, (u32)isr26);
|
||||||
|
set_idt_gate(27, (u32)isr27);
|
||||||
|
set_idt_gate(28, (u32)isr28);
|
||||||
|
set_idt_gate(29, (u32)isr29);
|
||||||
|
set_idt_gate(30, (u32)isr30);
|
||||||
|
set_idt_gate(31, (u32)isr31);
|
||||||
|
|
||||||
|
set_idt(); // Load with ASM
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To print the message which defines every exception */
|
||||||
|
char *exception_messages[] = {
|
||||||
|
"Division By Zero",
|
||||||
|
"Debug",
|
||||||
|
"Non Maskable Interrupt",
|
||||||
|
"Breakpoint",
|
||||||
|
"Into Detected Overflow",
|
||||||
|
"Out of Bounds",
|
||||||
|
"Invalid Opcode",
|
||||||
|
"No Coprocessor",
|
||||||
|
|
||||||
|
"Double Fault",
|
||||||
|
"Coprocessor Segment Overrun",
|
||||||
|
"Bad TSS",
|
||||||
|
"Segment Not Present",
|
||||||
|
"Stack Fault",
|
||||||
|
"General Protection Fault",
|
||||||
|
"Page Fault",
|
||||||
|
"Unknown Interrupt",
|
||||||
|
|
||||||
|
"Coprocessor Fault",
|
||||||
|
"Alignment Check",
|
||||||
|
"Machine Check",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved"
|
||||||
|
};
|
||||||
|
|
||||||
|
void isr_handler(registers_t r) {
|
||||||
|
kprint("received interrupt: ");
|
||||||
|
char s[3];
|
||||||
|
int_to_ascii(r.int_no, s);
|
||||||
|
kprint(s);
|
||||||
|
kprint("\n");
|
||||||
|
kprint(exception_messages[r.int_no]);
|
||||||
|
kprint("\n");
|
||||||
|
}
|
51
Part1/A4/05_basic_int/cpu/isr.h
Executable file
51
Part1/A4/05_basic_int/cpu/isr.h
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef ISR_H
|
||||||
|
#define ISR_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/* ISRs reserved for CPU exceptions */
|
||||||
|
extern void isr0();
|
||||||
|
extern void isr1();
|
||||||
|
extern void isr2();
|
||||||
|
extern void isr3();
|
||||||
|
extern void isr4();
|
||||||
|
extern void isr5();
|
||||||
|
extern void isr6();
|
||||||
|
extern void isr7();
|
||||||
|
extern void isr8();
|
||||||
|
extern void isr9();
|
||||||
|
extern void isr10();
|
||||||
|
extern void isr11();
|
||||||
|
extern void isr12();
|
||||||
|
extern void isr13();
|
||||||
|
extern void isr14();
|
||||||
|
extern void isr15();
|
||||||
|
extern void isr16();
|
||||||
|
extern void isr17();
|
||||||
|
extern void isr18();
|
||||||
|
extern void isr19();
|
||||||
|
extern void isr20();
|
||||||
|
extern void isr21();
|
||||||
|
extern void isr22();
|
||||||
|
extern void isr23();
|
||||||
|
extern void isr24();
|
||||||
|
extern void isr25();
|
||||||
|
extern void isr26();
|
||||||
|
extern void isr27();
|
||||||
|
extern void isr28();
|
||||||
|
extern void isr29();
|
||||||
|
extern void isr30();
|
||||||
|
extern void isr31();
|
||||||
|
|
||||||
|
/* Struct which aggregates many registers */
|
||||||
|
typedef struct {
|
||||||
|
u32 ds; /* Data segment selector */
|
||||||
|
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
|
||||||
|
u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
|
||||||
|
u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
|
||||||
|
} registers_t;
|
||||||
|
|
||||||
|
void isr_install();
|
||||||
|
void isr_handler(registers_t r);
|
||||||
|
|
||||||
|
#endif
|
16
Part1/A4/05_basic_int/cpu/types.h
Executable file
16
Part1/A4/05_basic_int/cpu/types.h
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef TYPES_H
|
||||||
|
#define TYPES_H
|
||||||
|
|
||||||
|
/* Instead of using 'chars' to allocate non-character bytes,
|
||||||
|
* we will use these new type with no semantic meaning */
|
||||||
|
typedef unsigned int u32;
|
||||||
|
typedef int s32;
|
||||||
|
typedef unsigned short u16;
|
||||||
|
typedef short s16;
|
||||||
|
typedef unsigned char u8;
|
||||||
|
typedef char s8;
|
||||||
|
|
||||||
|
#define low_16(address) (u16)((address) & 0xFFFF)
|
||||||
|
#define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
|
||||||
|
|
||||||
|
#endif
|
37
Part1/A4/05_basic_int/drivers/ports.c
Executable file
37
Part1/A4/05_basic_int/drivers/ports.c
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#include "ports.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte from the specified port
|
||||||
|
*/
|
||||||
|
u8 port_byte_in (u16 port) {
|
||||||
|
u8 result;
|
||||||
|
/* Inline assembler syntax
|
||||||
|
* !! Notice how the source and destination registers are switched from NASM !!
|
||||||
|
*
|
||||||
|
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
|
||||||
|
* '"d" (port)': map the C variable '(port)' into e'd'x register
|
||||||
|
*
|
||||||
|
* Inputs and outputs are separated by colons
|
||||||
|
*/
|
||||||
|
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_byte_out (u16 port, u8 data) {
|
||||||
|
/* Notice how here both registers are mapped to C variables and
|
||||||
|
* nothing is returned, thus, no equals '=' in the asm syntax
|
||||||
|
* However we see a comma since there are two variables in the input area
|
||||||
|
* and none in the 'return' area
|
||||||
|
*/
|
||||||
|
__asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 port_word_in (u16 port) {
|
||||||
|
u16 result;
|
||||||
|
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_word_out (u16 port, u16 data) {
|
||||||
|
__asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
11
Part1/A4/05_basic_int/drivers/ports.h
Executable file
11
Part1/A4/05_basic_int/drivers/ports.h
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef PORTS_H
|
||||||
|
#define PORTS_H
|
||||||
|
|
||||||
|
#include "../cpu/types.h"
|
||||||
|
|
||||||
|
unsigned char port_byte_in (u16 port);
|
||||||
|
void port_byte_out (u16 port, u8 data);
|
||||||
|
unsigned short port_word_in (u16 port);
|
||||||
|
void port_word_out (u16 port, u16 data);
|
||||||
|
|
||||||
|
#endif
|
139
Part1/A4/05_basic_int/drivers/screen.c
Executable file
139
Part1/A4/05_basic_int/drivers/screen.c
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#include "screen.h"
|
||||||
|
#include "ports.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
/* Declaration of private functions */
|
||||||
|
int get_cursor_offset();
|
||||||
|
void set_cursor_offset(int offset);
|
||||||
|
int print_char(char c, int col, int row, char attr);
|
||||||
|
int get_offset(int col, int row);
|
||||||
|
int get_offset_row(int offset);
|
||||||
|
int get_offset_col(int offset);
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Public Kernel API functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a message on the specified location
|
||||||
|
* If col, row, are negative, we will use the current offset
|
||||||
|
*/
|
||||||
|
void kprint_at(char *message, int col, int row) {
|
||||||
|
/* Set cursor if col/row are negative */
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0)
|
||||||
|
offset = get_offset(col, row);
|
||||||
|
else {
|
||||||
|
offset = get_cursor_offset();
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop through message and print it */
|
||||||
|
int i = 0;
|
||||||
|
while (message[i] != 0) {
|
||||||
|
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
|
||||||
|
/* Compute row/col for next iteration */
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kprint(char *message) {
|
||||||
|
kprint_at(message, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Private kernel functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Innermost print function for our kernel, directly accesses the video memory
|
||||||
|
*
|
||||||
|
* If 'col' and 'row' are negative, we will print at current cursor location
|
||||||
|
* If 'attr' is zero it will use 'white on black' as default
|
||||||
|
* Returns the offset of the next character
|
||||||
|
* Sets the video cursor to the returned offset
|
||||||
|
*/
|
||||||
|
int print_char(char c, int col, int row, char attr) {
|
||||||
|
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
|
||||||
|
if (!attr) attr = WHITE_ON_BLACK;
|
||||||
|
|
||||||
|
/* Error control: print a red 'E' if the coords aren't right */
|
||||||
|
if (col >= MAX_COLS || row >= MAX_ROWS) {
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
|
||||||
|
return get_offset(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0) offset = get_offset(col, row);
|
||||||
|
else offset = get_cursor_offset();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
offset = get_offset(0, row+1);
|
||||||
|
} else {
|
||||||
|
vidmem[offset] = c;
|
||||||
|
vidmem[offset+1] = attr;
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the offset is over screen size and scroll */
|
||||||
|
if (offset >= MAX_ROWS * MAX_COLS * 2) {
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < MAX_ROWS; i++)
|
||||||
|
memory_copy((char *) (get_offset(0, i) + VIDEO_ADDRESS),
|
||||||
|
(char *) (get_offset(0, i-1) + VIDEO_ADDRESS),
|
||||||
|
MAX_COLS * 2);
|
||||||
|
|
||||||
|
/* Blank last line */
|
||||||
|
char *last_line = (char *) get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
|
||||||
|
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
|
||||||
|
|
||||||
|
offset -= 2 * MAX_COLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cursor_offset(offset);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_cursor_offset() {
|
||||||
|
/* Use the VGA ports to get the current cursor position
|
||||||
|
* 1. Ask for high byte of the cursor offset (data 14)
|
||||||
|
* 2. Ask for low byte (data 15)
|
||||||
|
*/
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
offset += port_byte_in(REG_SCREEN_DATA);
|
||||||
|
return offset * 2; /* Position * size of character cell */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor_offset(int offset) {
|
||||||
|
/* Similar to get_cursor_offset, but instead of reading we write data */
|
||||||
|
offset /= 2;
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_screen() {
|
||||||
|
int screen_size = MAX_COLS * MAX_ROWS;
|
||||||
|
int i;
|
||||||
|
char *screen = (char *) VIDEO_ADDRESS;
|
||||||
|
|
||||||
|
for (i = 0; i < screen_size; i++) {
|
||||||
|
screen[i*2] = ' ';
|
||||||
|
screen[i*2+1] = WHITE_ON_BLACK;
|
||||||
|
}
|
||||||
|
set_cursor_offset(get_offset(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
|
||||||
|
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
|
||||||
|
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
|
19
Part1/A4/05_basic_int/drivers/screen.h
Executable file
19
Part1/A4/05_basic_int/drivers/screen.h
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef SCREEN_H
|
||||||
|
#define SCREEN_H
|
||||||
|
|
||||||
|
#define VIDEO_ADDRESS 0xb8000
|
||||||
|
#define MAX_ROWS 25
|
||||||
|
#define MAX_COLS 80
|
||||||
|
#define WHITE_ON_BLACK 0x0f
|
||||||
|
#define RED_ON_WHITE 0xf4
|
||||||
|
|
||||||
|
/* Screen i/o ports */
|
||||||
|
#define REG_SCREEN_CTRL 0x3d4
|
||||||
|
#define REG_SCREEN_DATA 0x3d5
|
||||||
|
|
||||||
|
/* Public kernel API */
|
||||||
|
void clear_screen();
|
||||||
|
void kprint_at(char *message, int col, int row);
|
||||||
|
void kprint(char *message);
|
||||||
|
|
||||||
|
#endif
|
11
Part1/A4/05_basic_int/kernel/kernel.c
Executable file
11
Part1/A4/05_basic_int/kernel/kernel.c
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#include "../drivers/screen.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "../cpu/isr.h"
|
||||||
|
#include "../cpu/idt.h"
|
||||||
|
|
||||||
|
void _start() {
|
||||||
|
isr_install();
|
||||||
|
/* Test the interrupts */
|
||||||
|
__asm__ __volatile__("int $2");
|
||||||
|
__asm__ __volatile__("int $3");
|
||||||
|
}
|
30
Part1/A4/05_basic_int/kernel/util.c
Executable file
30
Part1/A4/05_basic_int/kernel/util.c
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
void memory_copy(char *source, char *dest, int nbytes) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < nbytes; i++) {
|
||||||
|
*(dest + i) = *(source + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_set(u8 *dest, u8 val, u32 len) {
|
||||||
|
u8 *temp = (u8 *)dest;
|
||||||
|
for ( ; len != 0; len--) *temp++ = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K&R implementation
|
||||||
|
*/
|
||||||
|
void int_to_ascii(int n, char str[]) {
|
||||||
|
int i, sign;
|
||||||
|
if ((sign = n) < 0) n = -n;
|
||||||
|
i = 0;
|
||||||
|
do {
|
||||||
|
str[i++] = n % 10 + '0';
|
||||||
|
} while ((n /= 10) > 0);
|
||||||
|
|
||||||
|
if (sign < 0) str[i++] = '-';
|
||||||
|
str[i] = '\0';
|
||||||
|
|
||||||
|
/* TODO: implement "reverse" */
|
||||||
|
}
|
10
Part1/A4/05_basic_int/kernel/util.h
Executable file
10
Part1/A4/05_basic_int/kernel/util.h
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef UTIL_H
|
||||||
|
#define UTIL_H
|
||||||
|
|
||||||
|
#include "../cpu/types.h"
|
||||||
|
|
||||||
|
void memory_copy(char *source, char *dest, int nbytes);
|
||||||
|
void memory_set(u8 *dest, u8 val, u32 len);
|
||||||
|
void int_to_ascii(int n, char str[]);
|
||||||
|
|
||||||
|
#endif
|
49
Part1/A4/06_irq/Makefile
Executable file
49
Part1/A4/06_irq/Makefile
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw -d guest_errors,int &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
|
26
Part1/A4/06_irq/boot/32bit_print.asm
Executable file
26
Part1/A4/06_irq/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/06_irq/boot/bootsect.asm
Executable file
51
Part1/A4/06_irq/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
46
Part1/A4/06_irq/boot/disk.asm
Executable file
46
Part1/A4/06_irq/boot/disk.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||||
|
disk_load:
|
||||||
|
pusha
|
||||||
|
; reading from disk requires setting specific values in all registers
|
||||||
|
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||||
|
; to the stack for later use.
|
||||||
|
push dx
|
||||||
|
|
||||||
|
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||||
|
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||||
|
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||||
|
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||||
|
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||||
|
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||||
|
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||||
|
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||||
|
|
||||||
|
; [es:bx] <- pointer to buffer where the data will be stored
|
||||||
|
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||||
|
int 0x13 ; BIOS interrupt
|
||||||
|
jc disk_error ; if error (stored in the carry bit)
|
||||||
|
|
||||||
|
pop dx
|
||||||
|
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||||
|
jne sectors_error
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov bx, DISK_ERROR
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||||
|
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||||
|
jmp disk_loop
|
||||||
|
|
||||||
|
sectors_error:
|
||||||
|
mov bx, SECTORS_ERROR
|
||||||
|
call print
|
||||||
|
|
||||||
|
disk_loop:
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
DISK_ERROR: db "Disk read error", 0
|
||||||
|
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part1/A4/06_irq/boot/gdt.asm
Executable file
35
Part1/A4/06_irq/boot/gdt.asm
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||||
|
; the GDT starts with a null 8-byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
dd 0x0 ; 4 byte
|
||||||
|
|
||||||
|
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||||
|
; for flags, refer to os-dev.pdf document, page 36
|
||||||
|
gdt_code:
|
||||||
|
dw 0xffff ; segment length, bits 0-15
|
||||||
|
dw 0x0 ; segment base, bits 0-15
|
||||||
|
db 0x0 ; segment base, bits 16-23
|
||||||
|
db 10011010b ; flags (8 bits)
|
||||||
|
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||||
|
db 0x0 ; segment base, bits 24-31
|
||||||
|
|
||||||
|
; GDT for data segment. base and length identical to code segment
|
||||||
|
; some flags changed, again, refer to os-dev.pdf
|
||||||
|
gdt_data:
|
||||||
|
dw 0xffff
|
||||||
|
dw 0x0
|
||||||
|
db 0x0
|
||||||
|
db 10010010b
|
||||||
|
db 11001111b
|
||||||
|
db 0x0
|
||||||
|
|
||||||
|
gdt_end:
|
||||||
|
|
||||||
|
; GDT descriptor
|
||||||
|
gdt_descriptor:
|
||||||
|
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||||
|
dd gdt_start ; address (32 bit)
|
||||||
|
|
||||||
|
; define some constants for later use
|
||||||
|
CODE_SEG equ gdt_code - gdt_start
|
||||||
|
DATA_SEG equ gdt_data - gdt_start
|
4
Part1/A4/06_irq/boot/kernel_entry.asm
Executable file
4
Part1/A4/06_irq/boot/kernel_entry.asm
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
[bits 32]
|
||||||
|
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||||
|
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||||
|
jmp $
|
37
Part1/A4/06_irq/boot/print.asm
Executable file
37
Part1/A4/06_irq/boot/print.asm
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
print:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
; keep this in mind:
|
||||||
|
; while (string[i] != 0) { print string[i]; i++ }
|
||||||
|
|
||||||
|
; the comparison for string end (null byte)
|
||||||
|
start:
|
||||||
|
mov al, [bx] ; 'bx' is the base address for the string
|
||||||
|
cmp al, 0
|
||||||
|
je done
|
||||||
|
|
||||||
|
; the part where we print with the BIOS help
|
||||||
|
mov ah, 0x0e
|
||||||
|
int 0x10 ; 'al' already contains the char
|
||||||
|
|
||||||
|
; increment pointer and do next loop
|
||||||
|
add bx, 1
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
done:
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print_nl:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov ah, 0x0e
|
||||||
|
mov al, 0x0a ; newline char
|
||||||
|
int 0x10
|
||||||
|
mov al, 0x0d ; carriage return
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
46
Part1/A4/06_irq/boot/print_hex.asm
Executable file
46
Part1/A4/06_irq/boot/print_hex.asm
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
; receiving the data in 'dx'
|
||||||
|
; For the examples we'll assume that we're called with dx=0x1234
|
||||||
|
print_hex:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
mov cx, 0 ; our index variable
|
||||||
|
|
||||||
|
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||||
|
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||||
|
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||||
|
; Then, move the ASCII byte to the correct position on the resulting string
|
||||||
|
hex_loop:
|
||||||
|
cmp cx, 4 ; loop 4 times
|
||||||
|
je end
|
||||||
|
|
||||||
|
; 1. convert last char of 'dx' to ascii
|
||||||
|
mov ax, dx ; we will use 'ax' as our working register
|
||||||
|
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||||
|
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||||
|
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||||
|
jle step2
|
||||||
|
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||||
|
|
||||||
|
step2:
|
||||||
|
; 2. get the correct position of the string to place our ASCII char
|
||||||
|
; bx <- base address + string length - index of char
|
||||||
|
mov bx, HEX_OUT + 5 ; base + length
|
||||||
|
sub bx, cx ; our index variable
|
||||||
|
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||||
|
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||||
|
|
||||||
|
; increment index and loop
|
||||||
|
add cx, 1
|
||||||
|
jmp hex_loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
; prepare the parameter and call the function
|
||||||
|
; remember that print receives parameters in 'bx'
|
||||||
|
mov bx, HEX_OUT
|
||||||
|
call print
|
||||||
|
|
||||||
|
popa
|
||||||
|
ret
|
||||||
|
|
||||||
|
HEX_OUT:
|
||||||
|
db '0x0000',0 ; reserve memory for our new string
|
22
Part1/A4/06_irq/boot/switch_pm.asm
Executable file
22
Part1/A4/06_irq/boot/switch_pm.asm
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
[bits 16]
|
||||||
|
switch_to_pm:
|
||||||
|
cli ; 1. disable interrupts
|
||||||
|
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||||
|
mov cr0, eax
|
||||||
|
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
init_pm: ; we are now using 32-bit instructions
|
||||||
|
mov ax, DATA_SEG ; 5. update the segment registers
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
call BEGIN_PM ; 7. Call a well-known label with useful code
|
17
Part1/A4/06_irq/cpu/idt.c
Executable file
17
Part1/A4/06_irq/cpu/idt.c
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#include "idt.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
void set_idt_gate(int n, u32 handler) {
|
||||||
|
idt[n].low_offset = low_16(handler);
|
||||||
|
idt[n].sel = KERNEL_CS;
|
||||||
|
idt[n].always0 = 0;
|
||||||
|
idt[n].flags = 0x8E;
|
||||||
|
idt[n].high_offset = high_16(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_idt() {
|
||||||
|
idt_reg.base = (u32) &idt;
|
||||||
|
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
|
||||||
|
/* Don't make the mistake of loading &idt -- always load &idt_reg */
|
||||||
|
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
|
||||||
|
}
|
39
Part1/A4/06_irq/cpu/idt.h
Executable file
39
Part1/A4/06_irq/cpu/idt.h
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef IDT_H
|
||||||
|
#define IDT_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/* Segment selectors */
|
||||||
|
#define KERNEL_CS 0x08
|
||||||
|
|
||||||
|
/* How every interrupt gate (handler) is defined */
|
||||||
|
typedef struct {
|
||||||
|
u16 low_offset; /* Lower 16 bits of handler function address */
|
||||||
|
u16 sel; /* Kernel segment selector */
|
||||||
|
u8 always0;
|
||||||
|
/* First byte
|
||||||
|
* Bit 7: "Interrupt is present"
|
||||||
|
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
|
||||||
|
* Bit 4: Set to 0 for interrupt gates
|
||||||
|
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
|
||||||
|
u8 flags;
|
||||||
|
u16 high_offset; /* Higher 16 bits of handler function address */
|
||||||
|
} __attribute__((packed)) idt_gate_t ;
|
||||||
|
|
||||||
|
/* A pointer to the array of interrupt handlers.
|
||||||
|
* Assembly instruction 'lidt' will read it */
|
||||||
|
typedef struct {
|
||||||
|
u16 limit;
|
||||||
|
u32 base;
|
||||||
|
} __attribute__((packed)) idt_register_t;
|
||||||
|
|
||||||
|
#define IDT_ENTRIES 256
|
||||||
|
idt_gate_t idt[IDT_ENTRIES];
|
||||||
|
idt_register_t idt_reg;
|
||||||
|
|
||||||
|
|
||||||
|
/* Functions implemented in idt.c */
|
||||||
|
void set_idt_gate(int n, u32 handler);
|
||||||
|
void set_idt();
|
||||||
|
|
||||||
|
#endif
|
425
Part1/A4/06_irq/cpu/interrupt.asm
Executable file
425
Part1/A4/06_irq/cpu/interrupt.asm
Executable file
@ -0,0 +1,425 @@
|
|||||||
|
; Defined in isr.c
|
||||||
|
[extern isr_handler]
|
||||||
|
[extern irq_handler]
|
||||||
|
|
||||||
|
; Common ISR code
|
||||||
|
isr_common_stub:
|
||||||
|
; 1. Save CPU state
|
||||||
|
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
|
||||||
|
mov ax, ds ; Lower 16-bits of eax = ds.
|
||||||
|
push eax ; save the data segment descriptor
|
||||||
|
mov ax, 0x10 ; kernel data segment descriptor
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
; 2. Call C handler
|
||||||
|
call isr_handler
|
||||||
|
|
||||||
|
; 3. Restore state
|
||||||
|
pop eax
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
popa
|
||||||
|
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
|
||||||
|
sti
|
||||||
|
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
|
||||||
|
|
||||||
|
; Common IRQ code. Identical to ISR code except for the 'call'
|
||||||
|
; and the 'pop ebx'
|
||||||
|
irq_common_stub:
|
||||||
|
pusha
|
||||||
|
mov ax, ds
|
||||||
|
push eax
|
||||||
|
mov ax, 0x10
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
call irq_handler ; Different than the ISR code
|
||||||
|
pop ebx ; Different than the ISR code
|
||||||
|
mov ds, bx
|
||||||
|
mov es, bx
|
||||||
|
mov fs, bx
|
||||||
|
mov gs, bx
|
||||||
|
popa
|
||||||
|
add esp, 8
|
||||||
|
sti
|
||||||
|
iret
|
||||||
|
|
||||||
|
; We don't get information about which interrupt was caller
|
||||||
|
; when the handler is run, so we will need to have a different handler
|
||||||
|
; for every interrupt.
|
||||||
|
; Furthermore, some interrupts push an error code onto the stack but others
|
||||||
|
; don't, so we will push a dummy error code for those which don't, so that
|
||||||
|
; we have a consistent stack for all of them.
|
||||||
|
|
||||||
|
; First make the ISRs global
|
||||||
|
global isr0
|
||||||
|
global isr1
|
||||||
|
global isr2
|
||||||
|
global isr3
|
||||||
|
global isr4
|
||||||
|
global isr5
|
||||||
|
global isr6
|
||||||
|
global isr7
|
||||||
|
global isr8
|
||||||
|
global isr9
|
||||||
|
global isr10
|
||||||
|
global isr11
|
||||||
|
global isr12
|
||||||
|
global isr13
|
||||||
|
global isr14
|
||||||
|
global isr15
|
||||||
|
global isr16
|
||||||
|
global isr17
|
||||||
|
global isr18
|
||||||
|
global isr19
|
||||||
|
global isr20
|
||||||
|
global isr21
|
||||||
|
global isr22
|
||||||
|
global isr23
|
||||||
|
global isr24
|
||||||
|
global isr25
|
||||||
|
global isr26
|
||||||
|
global isr27
|
||||||
|
global isr28
|
||||||
|
global isr29
|
||||||
|
global isr30
|
||||||
|
global isr31
|
||||||
|
; IRQs
|
||||||
|
global irq0
|
||||||
|
global irq1
|
||||||
|
global irq2
|
||||||
|
global irq3
|
||||||
|
global irq4
|
||||||
|
global irq5
|
||||||
|
global irq6
|
||||||
|
global irq7
|
||||||
|
global irq8
|
||||||
|
global irq9
|
||||||
|
global irq10
|
||||||
|
global irq11
|
||||||
|
global irq12
|
||||||
|
global irq13
|
||||||
|
global irq14
|
||||||
|
global irq15
|
||||||
|
|
||||||
|
; 0: Divide By Zero Exception
|
||||||
|
isr0:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 0
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 1: Debug Exception
|
||||||
|
isr1:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 1
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 2: Non Maskable Interrupt Exception
|
||||||
|
isr2:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 2
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 3: Int 3 Exception
|
||||||
|
isr3:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 3
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 4: INTO Exception
|
||||||
|
isr4:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 4
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 5: Out of Bounds Exception
|
||||||
|
isr5:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 5
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 6: Invalid Opcode Exception
|
||||||
|
isr6:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 6
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 7: Coprocessor Not Available Exception
|
||||||
|
isr7:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 7
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 8: Double Fault Exception (With Error Code!)
|
||||||
|
isr8:
|
||||||
|
cli
|
||||||
|
push byte 8
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 9: Coprocessor Segment Overrun Exception
|
||||||
|
isr9:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 9
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 10: Bad TSS Exception (With Error Code!)
|
||||||
|
isr10:
|
||||||
|
cli
|
||||||
|
push byte 10
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 11: Segment Not Present Exception (With Error Code!)
|
||||||
|
isr11:
|
||||||
|
cli
|
||||||
|
push byte 11
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 12: Stack Fault Exception (With Error Code!)
|
||||||
|
isr12:
|
||||||
|
cli
|
||||||
|
push byte 12
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 13: General Protection Fault Exception (With Error Code!)
|
||||||
|
isr13:
|
||||||
|
cli
|
||||||
|
push byte 13
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 14: Page Fault Exception (With Error Code!)
|
||||||
|
isr14:
|
||||||
|
cli
|
||||||
|
push byte 14
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 15: Reserved Exception
|
||||||
|
isr15:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 15
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 16: Floating Point Exception
|
||||||
|
isr16:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 16
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 17: Alignment Check Exception
|
||||||
|
isr17:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 17
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 18: Machine Check Exception
|
||||||
|
isr18:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 18
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 19: Reserved
|
||||||
|
isr19:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 19
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 20: Reserved
|
||||||
|
isr20:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 20
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 21: Reserved
|
||||||
|
isr21:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 21
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 22: Reserved
|
||||||
|
isr22:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 22
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 23: Reserved
|
||||||
|
isr23:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 23
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 24: Reserved
|
||||||
|
isr24:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 24
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 25: Reserved
|
||||||
|
isr25:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 25
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 26: Reserved
|
||||||
|
isr26:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 26
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 27: Reserved
|
||||||
|
isr27:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 27
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 28: Reserved
|
||||||
|
isr28:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 28
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 29: Reserved
|
||||||
|
isr29:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 29
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 30: Reserved
|
||||||
|
isr30:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 30
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; 31: Reserved
|
||||||
|
isr31:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 31
|
||||||
|
jmp isr_common_stub
|
||||||
|
|
||||||
|
; IRQ handlers
|
||||||
|
irq0:
|
||||||
|
cli
|
||||||
|
push byte 0
|
||||||
|
push byte 32
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq1:
|
||||||
|
cli
|
||||||
|
push byte 1
|
||||||
|
push byte 33
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq2:
|
||||||
|
cli
|
||||||
|
push byte 2
|
||||||
|
push byte 34
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq3:
|
||||||
|
cli
|
||||||
|
push byte 3
|
||||||
|
push byte 35
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq4:
|
||||||
|
cli
|
||||||
|
push byte 4
|
||||||
|
push byte 36
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq5:
|
||||||
|
cli
|
||||||
|
push byte 5
|
||||||
|
push byte 37
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq6:
|
||||||
|
cli
|
||||||
|
push byte 6
|
||||||
|
push byte 38
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq7:
|
||||||
|
cli
|
||||||
|
push byte 7
|
||||||
|
push byte 39
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq8:
|
||||||
|
cli
|
||||||
|
push byte 8
|
||||||
|
push byte 40
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq9:
|
||||||
|
cli
|
||||||
|
push byte 9
|
||||||
|
push byte 41
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq10:
|
||||||
|
cli
|
||||||
|
push byte 10
|
||||||
|
push byte 42
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq11:
|
||||||
|
cli
|
||||||
|
push byte 11
|
||||||
|
push byte 43
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq12:
|
||||||
|
cli
|
||||||
|
push byte 12
|
||||||
|
push byte 44
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq13:
|
||||||
|
cli
|
||||||
|
push byte 13
|
||||||
|
push byte 45
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq14:
|
||||||
|
cli
|
||||||
|
push byte 14
|
||||||
|
push byte 46
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
||||||
|
irq15:
|
||||||
|
cli
|
||||||
|
push byte 15
|
||||||
|
push byte 47
|
||||||
|
jmp irq_common_stub
|
||||||
|
|
142
Part1/A4/06_irq/cpu/isr.c
Executable file
142
Part1/A4/06_irq/cpu/isr.c
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
#include "isr.h"
|
||||||
|
#include "idt.h"
|
||||||
|
#include "../drivers/screen.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
#include "../drivers/ports.h"
|
||||||
|
|
||||||
|
isr_t interrupt_handlers[256];
|
||||||
|
|
||||||
|
/* Can't do this with a loop because we need the address
|
||||||
|
* of the function names */
|
||||||
|
void isr_install() {
|
||||||
|
set_idt_gate(0, (u32)isr0);
|
||||||
|
set_idt_gate(1, (u32)isr1);
|
||||||
|
set_idt_gate(2, (u32)isr2);
|
||||||
|
set_idt_gate(3, (u32)isr3);
|
||||||
|
set_idt_gate(4, (u32)isr4);
|
||||||
|
set_idt_gate(5, (u32)isr5);
|
||||||
|
set_idt_gate(6, (u32)isr6);
|
||||||
|
set_idt_gate(7, (u32)isr7);
|
||||||
|
set_idt_gate(8, (u32)isr8);
|
||||||
|
set_idt_gate(9, (u32)isr9);
|
||||||
|
set_idt_gate(10, (u32)isr10);
|
||||||
|
set_idt_gate(11, (u32)isr11);
|
||||||
|
set_idt_gate(12, (u32)isr12);
|
||||||
|
set_idt_gate(13, (u32)isr13);
|
||||||
|
set_idt_gate(14, (u32)isr14);
|
||||||
|
set_idt_gate(15, (u32)isr15);
|
||||||
|
set_idt_gate(16, (u32)isr16);
|
||||||
|
set_idt_gate(17, (u32)isr17);
|
||||||
|
set_idt_gate(18, (u32)isr18);
|
||||||
|
set_idt_gate(19, (u32)isr19);
|
||||||
|
set_idt_gate(20, (u32)isr20);
|
||||||
|
set_idt_gate(21, (u32)isr21);
|
||||||
|
set_idt_gate(22, (u32)isr22);
|
||||||
|
set_idt_gate(23, (u32)isr23);
|
||||||
|
set_idt_gate(24, (u32)isr24);
|
||||||
|
set_idt_gate(25, (u32)isr25);
|
||||||
|
set_idt_gate(26, (u32)isr26);
|
||||||
|
set_idt_gate(27, (u32)isr27);
|
||||||
|
set_idt_gate(28, (u32)isr28);
|
||||||
|
set_idt_gate(29, (u32)isr29);
|
||||||
|
set_idt_gate(30, (u32)isr30);
|
||||||
|
set_idt_gate(31, (u32)isr31);
|
||||||
|
|
||||||
|
// Remap the PIC
|
||||||
|
port_byte_out(0x20, 0x11);
|
||||||
|
port_byte_out(0xA0, 0x11);
|
||||||
|
port_byte_out(0x21, 0x20);
|
||||||
|
port_byte_out(0xA1, 0x28);
|
||||||
|
port_byte_out(0x21, 0x04);
|
||||||
|
port_byte_out(0xA1, 0x02);
|
||||||
|
port_byte_out(0x21, 0x01);
|
||||||
|
port_byte_out(0xA1, 0x01);
|
||||||
|
port_byte_out(0x21, 0x0);
|
||||||
|
port_byte_out(0xA1, 0x0);
|
||||||
|
|
||||||
|
// Install the IRQs
|
||||||
|
set_idt_gate(32, (u32)irq0);
|
||||||
|
set_idt_gate(33, (u32)irq1);
|
||||||
|
set_idt_gate(34, (u32)irq2);
|
||||||
|
set_idt_gate(35, (u32)irq3);
|
||||||
|
set_idt_gate(36, (u32)irq4);
|
||||||
|
set_idt_gate(37, (u32)irq5);
|
||||||
|
set_idt_gate(38, (u32)irq6);
|
||||||
|
set_idt_gate(39, (u32)irq7);
|
||||||
|
set_idt_gate(40, (u32)irq8);
|
||||||
|
set_idt_gate(41, (u32)irq9);
|
||||||
|
set_idt_gate(42, (u32)irq10);
|
||||||
|
set_idt_gate(43, (u32)irq11);
|
||||||
|
set_idt_gate(44, (u32)irq12);
|
||||||
|
set_idt_gate(45, (u32)irq13);
|
||||||
|
set_idt_gate(46, (u32)irq14);
|
||||||
|
set_idt_gate(47, (u32)irq15);
|
||||||
|
|
||||||
|
set_idt(); // Load with ASM
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To print the message which defines every exception */
|
||||||
|
char *exception_messages[] = {
|
||||||
|
"Division By Zero",
|
||||||
|
"Debug",
|
||||||
|
"Non Maskable Interrupt",
|
||||||
|
"Breakpoint",
|
||||||
|
"Into Detected Overflow",
|
||||||
|
"Out of Bounds",
|
||||||
|
"Invalid Opcode",
|
||||||
|
"No Coprocessor",
|
||||||
|
|
||||||
|
"Double Fault",
|
||||||
|
"Coprocessor Segment Overrun",
|
||||||
|
"Bad TSS",
|
||||||
|
"Segment Not Present",
|
||||||
|
"Stack Fault",
|
||||||
|
"General Protection Fault",
|
||||||
|
"Page Fault",
|
||||||
|
"Unknown Interrupt",
|
||||||
|
|
||||||
|
"Coprocessor Fault",
|
||||||
|
"Alignment Check",
|
||||||
|
"Machine Check",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved"
|
||||||
|
};
|
||||||
|
|
||||||
|
void isr_handler(registers_t r) {
|
||||||
|
kprint("received interrupt: ");
|
||||||
|
char s[3];
|
||||||
|
int_to_ascii(r.int_no, s);
|
||||||
|
kprint(s);
|
||||||
|
kprint("\n");
|
||||||
|
kprint(exception_messages[r.int_no]);
|
||||||
|
kprint("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_interrupt_handler(u8 n, isr_t handler) {
|
||||||
|
interrupt_handlers[n] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void irq_handler(registers_t r) {
|
||||||
|
/* After every interrupt we need to send an EOI to the PICs
|
||||||
|
* or they will not send another interrupt again */
|
||||||
|
if (r.int_no >= 40) port_byte_out(0xA0, 0x20); /* slave */
|
||||||
|
port_byte_out(0x20, 0x20); /* master */
|
||||||
|
|
||||||
|
/* Handle the interrupt in a more modular way */
|
||||||
|
if (interrupt_handlers[r.int_no] != 0) {
|
||||||
|
isr_t handler = interrupt_handlers[r.int_no];
|
||||||
|
handler(r);
|
||||||
|
}
|
||||||
|
}
|
88
Part1/A4/06_irq/cpu/isr.h
Executable file
88
Part1/A4/06_irq/cpu/isr.h
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
#ifndef ISR_H
|
||||||
|
#define ISR_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/* ISRs reserved for CPU exceptions */
|
||||||
|
extern void isr0();
|
||||||
|
extern void isr1();
|
||||||
|
extern void isr2();
|
||||||
|
extern void isr3();
|
||||||
|
extern void isr4();
|
||||||
|
extern void isr5();
|
||||||
|
extern void isr6();
|
||||||
|
extern void isr7();
|
||||||
|
extern void isr8();
|
||||||
|
extern void isr9();
|
||||||
|
extern void isr10();
|
||||||
|
extern void isr11();
|
||||||
|
extern void isr12();
|
||||||
|
extern void isr13();
|
||||||
|
extern void isr14();
|
||||||
|
extern void isr15();
|
||||||
|
extern void isr16();
|
||||||
|
extern void isr17();
|
||||||
|
extern void isr18();
|
||||||
|
extern void isr19();
|
||||||
|
extern void isr20();
|
||||||
|
extern void isr21();
|
||||||
|
extern void isr22();
|
||||||
|
extern void isr23();
|
||||||
|
extern void isr24();
|
||||||
|
extern void isr25();
|
||||||
|
extern void isr26();
|
||||||
|
extern void isr27();
|
||||||
|
extern void isr28();
|
||||||
|
extern void isr29();
|
||||||
|
extern void isr30();
|
||||||
|
extern void isr31();
|
||||||
|
/* IRQ definitions */
|
||||||
|
extern void irq0();
|
||||||
|
extern void irq1();
|
||||||
|
extern void irq2();
|
||||||
|
extern void irq3();
|
||||||
|
extern void irq4();
|
||||||
|
extern void irq5();
|
||||||
|
extern void irq6();
|
||||||
|
extern void irq7();
|
||||||
|
extern void irq8();
|
||||||
|
extern void irq9();
|
||||||
|
extern void irq10();
|
||||||
|
extern void irq11();
|
||||||
|
extern void irq12();
|
||||||
|
extern void irq13();
|
||||||
|
extern void irq14();
|
||||||
|
extern void irq15();
|
||||||
|
|
||||||
|
#define IRQ0 32
|
||||||
|
#define IRQ1 33
|
||||||
|
#define IRQ2 34
|
||||||
|
#define IRQ3 35
|
||||||
|
#define IRQ4 36
|
||||||
|
#define IRQ5 37
|
||||||
|
#define IRQ6 38
|
||||||
|
#define IRQ7 39
|
||||||
|
#define IRQ8 40
|
||||||
|
#define IRQ9 41
|
||||||
|
#define IRQ10 42
|
||||||
|
#define IRQ11 43
|
||||||
|
#define IRQ12 44
|
||||||
|
#define IRQ13 45
|
||||||
|
#define IRQ14 46
|
||||||
|
#define IRQ15 47
|
||||||
|
|
||||||
|
/* Struct which aggregates many registers */
|
||||||
|
typedef struct {
|
||||||
|
u32 ds; /* Data segment selector */
|
||||||
|
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
|
||||||
|
u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
|
||||||
|
u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
|
||||||
|
} registers_t;
|
||||||
|
|
||||||
|
void isr_install();
|
||||||
|
void isr_handler(registers_t r);
|
||||||
|
|
||||||
|
typedef void (*isr_t)(registers_t);
|
||||||
|
void register_interrupt_handler(u8 n, isr_t handler);
|
||||||
|
|
||||||
|
#endif
|
16
Part1/A4/06_irq/cpu/types.h
Executable file
16
Part1/A4/06_irq/cpu/types.h
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef TYPES_H
|
||||||
|
#define TYPES_H
|
||||||
|
|
||||||
|
/* Instead of using 'chars' to allocate non-character bytes,
|
||||||
|
* we will use these new type with no semantic meaning */
|
||||||
|
typedef unsigned int u32;
|
||||||
|
typedef int s32;
|
||||||
|
typedef unsigned short u16;
|
||||||
|
typedef short s16;
|
||||||
|
typedef unsigned char u8;
|
||||||
|
typedef char s8;
|
||||||
|
|
||||||
|
#define low_16(address) (u16)((address) & 0xFFFF)
|
||||||
|
#define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
|
||||||
|
|
||||||
|
#endif
|
37
Part1/A4/06_irq/drivers/ports.c
Executable file
37
Part1/A4/06_irq/drivers/ports.c
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#include "ports.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte from the specified port
|
||||||
|
*/
|
||||||
|
u8 port_byte_in (u16 port) {
|
||||||
|
u8 result;
|
||||||
|
/* Inline assembler syntax
|
||||||
|
* !! Notice how the source and destination registers are switched from NASM !!
|
||||||
|
*
|
||||||
|
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
|
||||||
|
* '"d" (port)': map the C variable '(port)' into e'd'x register
|
||||||
|
*
|
||||||
|
* Inputs and outputs are separated by colons
|
||||||
|
*/
|
||||||
|
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_byte_out (u16 port, u8 data) {
|
||||||
|
/* Notice how here both registers are mapped to C variables and
|
||||||
|
* nothing is returned, thus, no equals '=' in the asm syntax
|
||||||
|
* However we see a comma since there are two variables in the input area
|
||||||
|
* and none in the 'return' area
|
||||||
|
*/
|
||||||
|
__asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 port_word_in (u16 port) {
|
||||||
|
u16 result;
|
||||||
|
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void port_word_out (u16 port, u16 data) {
|
||||||
|
__asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||||
|
}
|
11
Part1/A4/06_irq/drivers/ports.h
Executable file
11
Part1/A4/06_irq/drivers/ports.h
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef PORTS_H
|
||||||
|
#define PORTS_H
|
||||||
|
|
||||||
|
#include "../cpu/types.h"
|
||||||
|
|
||||||
|
unsigned char port_byte_in (u16 port);
|
||||||
|
void port_byte_out (u16 port, u8 data);
|
||||||
|
unsigned short port_word_in (u16 port);
|
||||||
|
void port_word_out (u16 port, u16 data);
|
||||||
|
|
||||||
|
#endif
|
139
Part1/A4/06_irq/drivers/screen.c
Executable file
139
Part1/A4/06_irq/drivers/screen.c
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#include "screen.h"
|
||||||
|
#include "ports.h"
|
||||||
|
#include "../kernel/util.h"
|
||||||
|
|
||||||
|
/* Declaration of private functions */
|
||||||
|
int get_cursor_offset();
|
||||||
|
void set_cursor_offset(int offset);
|
||||||
|
int print_char(char c, int col, int row, char attr);
|
||||||
|
int get_offset(int col, int row);
|
||||||
|
int get_offset_row(int offset);
|
||||||
|
int get_offset_col(int offset);
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Public Kernel API functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a message on the specified location
|
||||||
|
* If col, row, are negative, we will use the current offset
|
||||||
|
*/
|
||||||
|
void kprint_at(char *message, int col, int row) {
|
||||||
|
/* Set cursor if col/row are negative */
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0)
|
||||||
|
offset = get_offset(col, row);
|
||||||
|
else {
|
||||||
|
offset = get_cursor_offset();
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop through message and print it */
|
||||||
|
int i = 0;
|
||||||
|
while (message[i] != 0) {
|
||||||
|
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
|
||||||
|
/* Compute row/col for next iteration */
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
col = get_offset_col(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kprint(char *message) {
|
||||||
|
kprint_at(message, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Private kernel functions *
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Innermost print function for our kernel, directly accesses the video memory
|
||||||
|
*
|
||||||
|
* If 'col' and 'row' are negative, we will print at current cursor location
|
||||||
|
* If 'attr' is zero it will use 'white on black' as default
|
||||||
|
* Returns the offset of the next character
|
||||||
|
* Sets the video cursor to the returned offset
|
||||||
|
*/
|
||||||
|
int print_char(char c, int col, int row, char attr) {
|
||||||
|
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
|
||||||
|
if (!attr) attr = WHITE_ON_BLACK;
|
||||||
|
|
||||||
|
/* Error control: print a red 'E' if the coords aren't right */
|
||||||
|
if (col >= MAX_COLS || row >= MAX_ROWS) {
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
|
||||||
|
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
|
||||||
|
return get_offset(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
if (col >= 0 && row >= 0) offset = get_offset(col, row);
|
||||||
|
else offset = get_cursor_offset();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
row = get_offset_row(offset);
|
||||||
|
offset = get_offset(0, row+1);
|
||||||
|
} else {
|
||||||
|
vidmem[offset] = c;
|
||||||
|
vidmem[offset+1] = attr;
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the offset is over screen size and scroll */
|
||||||
|
if (offset >= MAX_ROWS * MAX_COLS * 2) {
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < MAX_ROWS; i++)
|
||||||
|
memory_copy((char *) (get_offset(0, i) + VIDEO_ADDRESS),
|
||||||
|
(char *) (get_offset(0, i-1) + VIDEO_ADDRESS),
|
||||||
|
MAX_COLS * 2);
|
||||||
|
|
||||||
|
/* Blank last line */
|
||||||
|
char *last_line = (char *) get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
|
||||||
|
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
|
||||||
|
|
||||||
|
offset -= 2 * MAX_COLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cursor_offset(offset);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_cursor_offset() {
|
||||||
|
/* Use the VGA ports to get the current cursor position
|
||||||
|
* 1. Ask for high byte of the cursor offset (data 14)
|
||||||
|
* 2. Ask for low byte (data 15)
|
||||||
|
*/
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
offset += port_byte_in(REG_SCREEN_DATA);
|
||||||
|
return offset * 2; /* Position * size of character cell */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor_offset(int offset) {
|
||||||
|
/* Similar to get_cursor_offset, but instead of reading we write data */
|
||||||
|
offset /= 2;
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
|
||||||
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
||||||
|
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_screen() {
|
||||||
|
int screen_size = MAX_COLS * MAX_ROWS;
|
||||||
|
int i;
|
||||||
|
char *screen = (char *) VIDEO_ADDRESS;
|
||||||
|
|
||||||
|
for (i = 0; i < screen_size; i++) {
|
||||||
|
screen[i*2] = ' ';
|
||||||
|
screen[i*2+1] = WHITE_ON_BLACK;
|
||||||
|
}
|
||||||
|
set_cursor_offset(get_offset(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
|
||||||
|
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
|
||||||
|
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
|
19
Part1/A4/06_irq/drivers/screen.h
Executable file
19
Part1/A4/06_irq/drivers/screen.h
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef SCREEN_H
|
||||||
|
#define SCREEN_H
|
||||||
|
|
||||||
|
#define VIDEO_ADDRESS 0xb8000
|
||||||
|
#define MAX_ROWS 25
|
||||||
|
#define MAX_COLS 80
|
||||||
|
#define WHITE_ON_BLACK 0x0f
|
||||||
|
#define RED_ON_WHITE 0xf4
|
||||||
|
|
||||||
|
/* Screen i/o ports */
|
||||||
|
#define REG_SCREEN_CTRL 0x3d4
|
||||||
|
#define REG_SCREEN_DATA 0x3d5
|
||||||
|
|
||||||
|
/* Public kernel API */
|
||||||
|
void clear_screen();
|
||||||
|
void kprint_at(char *message, int col, int row);
|
||||||
|
void kprint(char *message);
|
||||||
|
|
||||||
|
#endif
|
11
Part1/A4/06_irq/kernel/kernel.c
Executable file
11
Part1/A4/06_irq/kernel/kernel.c
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#include "../drivers/screen.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "../cpu/isr.h"
|
||||||
|
#include "../cpu/idt.h"
|
||||||
|
|
||||||
|
void _start() {
|
||||||
|
isr_install();
|
||||||
|
/* Test the interrupts */
|
||||||
|
__asm__ __volatile__("int $2");
|
||||||
|
__asm__ __volatile__("int $3");
|
||||||
|
}
|
30
Part1/A4/06_irq/kernel/util.c
Executable file
30
Part1/A4/06_irq/kernel/util.c
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
void memory_copy(char *source, char *dest, int nbytes) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < nbytes; i++) {
|
||||||
|
*(dest + i) = *(source + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_set(u8 *dest, u8 val, u32 len) {
|
||||||
|
u8 *temp = (u8 *)dest;
|
||||||
|
for ( ; len != 0; len--) *temp++ = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K&R implementation
|
||||||
|
*/
|
||||||
|
void int_to_ascii(int n, char str[]) {
|
||||||
|
int i, sign;
|
||||||
|
if ((sign = n) < 0) n = -n;
|
||||||
|
i = 0;
|
||||||
|
do {
|
||||||
|
str[i++] = n % 10 + '0';
|
||||||
|
} while ((n /= 10) > 0);
|
||||||
|
|
||||||
|
if (sign < 0) str[i++] = '-';
|
||||||
|
str[i] = '\0';
|
||||||
|
|
||||||
|
/* TODO: implement "reverse" */
|
||||||
|
}
|
10
Part1/A4/06_irq/kernel/util.h
Executable file
10
Part1/A4/06_irq/kernel/util.h
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef UTIL_H
|
||||||
|
#define UTIL_H
|
||||||
|
|
||||||
|
#include "../cpu/types.h"
|
||||||
|
|
||||||
|
void memory_copy(char *source, char *dest, int nbytes);
|
||||||
|
void memory_set(u8 *dest, u8 val, u32 len);
|
||||||
|
void int_to_ascii(int n, char str[]);
|
||||||
|
|
||||||
|
#endif
|
49
Part1/A4/07_timers/Makefile
Executable file
49
Part1/A4/07_timers/Makefile
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
|
||||||
|
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
|
||||||
|
# Nice syntax for file extension replacement
|
||||||
|
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
|
||||||
|
|
||||||
|
# Change this if your cross-compiler is somewhere else
|
||||||
|
CC = /usr/bin/gcc
|
||||||
|
GDB = /usr/bin/gdb
|
||||||
|
# -g: Use debugging symbols in gcc
|
||||||
|
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||||
|
|
||||||
|
# First rule is run by default
|
||||||
|
os-image.bin: boot/bootsect.bin kernel.bin
|
||||||
|
cat $^ > os-image.bin
|
||||||
|
|
||||||
|
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||||
|
# to 'strip' them manually on this case
|
||||||
|
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||||
|
|
||||||
|
# Used for debugging purposes
|
||||||
|
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||||
|
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||||
|
|
||||||
|
run: os-image.bin
|
||||||
|
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
curses: os-image.bin
|
||||||
|
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||||
|
|
||||||
|
# Open the connection to qemu and load our kernel-object file with symbols
|
||||||
|
debug: os-image.bin kernel.elf
|
||||||
|
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw -d guest_errors,int &
|
||||||
|
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||||
|
|
||||||
|
# Generic rules for wildcards
|
||||||
|
# To make an object, always compile from its .c
|
||||||
|
%.o: %.c ${HEADERS}
|
||||||
|
${CC} ${CFLAGS} -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.asm
|
||||||
|
nasm $< -f elf -o $@
|
||||||
|
|
||||||
|
%.bin: %.asm
|
||||||
|
nasm $< -f bin -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||||
|
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
|
26
Part1/A4/07_timers/boot/32bit_print.asm
Executable file
26
Part1/A4/07_timers/boot/32bit_print.asm
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
[bits 32] ; using 32-bit protected mode
|
||||||
|
|
||||||
|
; this is how constants are defined
|
||||||
|
VIDEO_MEMORY equ 0xb8000
|
||||||
|
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||||
|
|
||||||
|
print_string_pm:
|
||||||
|
pusha
|
||||||
|
mov edx, VIDEO_MEMORY
|
||||||
|
|
||||||
|
print_string_pm_loop:
|
||||||
|
mov al, [ebx] ; [ebx] is the address of our character
|
||||||
|
mov ah, WHITE_OB_BLACK
|
||||||
|
|
||||||
|
cmp al, 0 ; check if end of string
|
||||||
|
je print_string_pm_done
|
||||||
|
|
||||||
|
mov [edx], ax ; store character + attribute in video memory
|
||||||
|
add ebx, 1 ; next char
|
||||||
|
add edx, 2 ; next video memory position
|
||||||
|
|
||||||
|
jmp print_string_pm_loop
|
||||||
|
|
||||||
|
print_string_pm_done:
|
||||||
|
popa
|
||||||
|
ret
|
51
Part1/A4/07_timers/boot/bootsect.asm
Executable file
51
Part1/A4/07_timers/boot/bootsect.asm
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||||
|
[org 0x7c00]
|
||||||
|
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||||
|
|
||||||
|
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||||
|
mov bp, 0x9000
|
||||||
|
mov sp, bp
|
||||||
|
|
||||||
|
mov bx, MSG_REAL_MODE
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
call load_kernel ; read the kernel from disk
|
||||||
|
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||||
|
jmp $ ; Never executed
|
||||||
|
|
||||||
|
%include "boot/print.asm"
|
||||||
|
%include "boot/print_hex.asm"
|
||||||
|
%include "boot/disk.asm"
|
||||||
|
%include "boot/gdt.asm"
|
||||||
|
%include "boot/32bit_print.asm"
|
||||||
|
%include "boot/switch_pm.asm"
|
||||||
|
|
||||||
|
[bits 16]
|
||||||
|
load_kernel:
|
||||||
|
mov bx, MSG_LOAD_KERNEL
|
||||||
|
call print
|
||||||
|
call print_nl
|
||||||
|
|
||||||
|
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||||
|
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||||
|
mov dl, [BOOT_DRIVE]
|
||||||
|
call disk_load
|
||||||
|
ret
|
||||||
|
|
||||||
|
[bits 32]
|
||||||
|
BEGIN_PM:
|
||||||
|
mov ebx, MSG_PROT_MODE
|
||||||
|
call print_string_pm
|
||||||
|
call KERNEL_OFFSET ; Give control to the kernel
|
||||||
|
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||||
|
|
||||||
|
|
||||||
|
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||||
|
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||||
|
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||||
|
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||||
|
|
||||||
|
; padding
|
||||||
|
times 510 - ($-$$) db 0
|
||||||
|
dw 0xaa55
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user