diff --git a/bootstrap.asm b/bootstrap.asm index 5ed6b82..66ceb45 100644 --- a/bootstrap.asm +++ b/bootstrap.asm @@ -1,55 +1,35 @@ +; start indicates starting point of the execution start: - ; When a the boot sector is loaded by BIOS. It will be loaded on the location 07C0h in the memory. - ; What we do here is setting the register "ds" to the value "07C0" which is the same of the memory address we are on now. - ; Our variable "hello_string" will be loaded as a part of the bootloader, when we use it in the line "mov si, hello_string" - ; we're actually loading the memory address of the first character, let's assume that this memory address is (1000). Because - ; it is considered as a "data" by the processor. This offset (the memory address of "hello_string") will be added to the data segment - ; selector which resides in the register "ds". According to "Protected Mode Software Architecture", the initial value of "ds" is 0000h, - ; so, if we keep "ds" as it is, the CPU is going to load the data from the memory 00001000, BUT we are actually on 07C0, so the data - ; segment selector should be 07C0h so we can get the correct address of "hello_string" which is 07C01000 and not 00001000. - ; For backward compatibility, when the bootloader starts, the execution environment will be in "Real Mode", 16-bit. + + ; we want to move 07C0h to ds (why? it will be explained in later segment) + ; we cannot mov ds, 0x08C0 directly because ds (segment register) cannot be loaded with value directly in x86 + ; further details: https://stackoverflow.com/questions/19074666/8086-why-cant-we-move-an-immediate-data-into-segment-register mov ax, 07C0h mov ds, ax - ; ... ; - - ; == Printing == ; + ; define si to be title_string or message_string + ; call print_string which prints to screen the values saved in si mov si, title_string call print_string ; "CALL" is used instead of "JMP" because we would like to return to the next instruction after "print_string" finishes. mov si, message_string call print_string - ; ... ; - - ; === Loading the Kernel === ; call load_kernel_from_disk - ; If the loading has been performed correctly. Jump to the kernel's code which resides on 0900h:0000 according - ; to ES:BX values before calling (int 13h). - ; - ; There is a difference between "JMP" and "CALL" inctructions. The first one doesn't store returning information - ; in the stack while the later does. Because we are not going to return from kernel to the bootloader, we don't - ; need to store return information. - + ; gives control to kernel by jumping to the start of the kernel + ; 0900h is the same value we have loaded in the register es in the beginning of load_kernel_from_disk + ; 0000 is the offset we have specified in the register bx in load_kernel_from_disk before calling 02h:13h jmp 0900h:0000 -; ... ; -; ... ; - +; BIOS service 13h:02h loads the required sector into the memory address es:bx +; So, the value 0900h which has been set to es in the code above will be the starting memory address of the kernel +; Previously, it was enough to set bx = 0 since we only load 1 sector -> the code will reside from offset 0 to 511 +; Now we are loading > 1 sector so we need to increment bx load_kernel_from_disk: - ; BIOS provides disk services through intrrupt 13h. The value of the register "ah" decides which disk service that we are requesting from BIOS. - - ; The Disk Service "02h" loads sectors to the memory. The memory location that we would like to load our kernel in should be decided - ; before calling "int 13h". There are two parts of this location. The first part (segment selector) resides in the register "ES" while - ; the other part (offset) resides in the register "BX". - ; - ; x86 Segment Registers: CS: for code segment. - ; DS, ES, FS, and GS: for 4 data segments. - ; SS: for stack segment. - ; They are 16-bit registers that hold "segment selectors" + ; increment bx by 1 sector to load the next sector mov ax, [curr_sector_to_load] sub ax, 2 mov bx, 512d @@ -58,24 +38,34 @@ load_kernel_from_disk: mov ax, 0900h mov es, ax - - mov ah, 02h ; Requesting the service of reading disk sectors - mov al, 1h ; Number of sectors to read (How many sectors to read?) - mov ch, 0h ; Track number - mov cl, [curr_sector_to_load] ; Sector number - mov dh, 0h ; Head number + + ; service number 02h is specified to register ah + ; it reads sectors from the hard disk and loads them into the memory + mov ah, 02h + + ; 01h is the number of sector we ants to read (since simple_kernel.asm is < 512 bytes, 1 sector is enough) + mov al, 1h + + ; 0h is the track number we would like to read (track 0) + mov ch, 0h + + ; 02h is the sector number we would like to read (sector 2) + mov cl, [curr_sector_to_load] + + ; 0h is the head number (of the disk) + ; specifies the type of disk + ; 0h means floppy disk, 80h means hard disk #0, 81h means hard disk #1, etc. + ; the value of bx is the memory address that the content will be loaded into + ; we are reading one sector and the content will be stored on memory address 0h + mov dh, 0h mov dl, 80h ; Drive to read from. (0 = Floppy. 80h = Drive #0. 81h = Drive #1) int 13h ; BIOS Disk Services - - ; The instruction "jc" jumps to a memory location when CF = 1 (jc = jump if carry). - ; CF (or carry flag) is the first bit of EFLAG register in x86. The BIOS service (13h,02h) - ; clear CF (that is, put 0 in CF) when everything is fine. But if there is some error - ; it's going to set CF (that is, put 1 in CF). Error code will be in "ah". - ; - ; If any error happens in loading our kernel. The bootloader is going to jump to the label "kernel_load_error". + ; when the content is loaded successfully, BIOS service 13h:02h set carry flag to 0, otherwise 1 and stores error code in ax + ; if cf is 1 jc will jump jc kernel_load_error - + + ; increment curr_sector_to_load, decrement number_of_sectors_to_load sub byte [number_of_sectors_to_load], 1 add byte [curr_sector_to_load], 1 cmp byte [number_of_sectors_to_load], 0 @@ -103,17 +93,12 @@ kernel_load_error: ; For an example of using service "0Eh" to print "Hello" character by character, please refer to "../examples/bootstrap/1/". print_string: + ; specify function for printing character mov ah, 0Eh print_char: - ; [MQH] 26 Nov 2019 - ; It's known that a byte = 8 bits. In x86 there are more two units: a "word" = 16 bits and "doubleword" = 32 bits. Some x86 instructions have multiple variants that work - ; with a specific aforementioned unit. "lods" is an example of these instructions, there is "lodsb" which works with a byte, "lodsw" which works with a word and "lodsd" which - ; works with doubleword. - ; - ; The instruction "lods" uses the value which is stored in register "si" (when using lodsb or loadsw) or register "esi" (when using lodsw) and assume that this - ; value as memory location. Then according to the used unit a (b)yte (8-bits), (w)ord (16-bits) or (d)oubleword (32-bit) will be read from the memory location DS:(E)SI - ; and store these bits in al, ax or eax according to the size of read data. + ; char to print is passed via si + ; lodsb will transfer first character of the string to the register al and increase value of si by 1 byte lodsb cmp al, 0 @@ -124,47 +109,30 @@ print_char: jmp print_char printing_finished: - mov al, 10d ; Print new line + mov al, 10d ; print new line 10d = ENDL int 10h - ; Move the cursor to the beginning - mov ah, 03h + ; reading current cursor position + mov ah, 03h ; specify service for reading current position of cursor mov bh, 0 int 10h - mov ah, 02h - mov dl, 0 + ; move the cursor to the beginning + mov ah, 02h ; specify service for setting cursor to position 0 + mov dl, 0 ; position to be set is passed to dl int 10h ; print_string is a procedure (or function), therefore we should return. It is called by using "CALL" instead of "JMP". ret -; ... ; - -; "DB" is one of pseudo-instructions which is provided by NASM. A source line in NASM follows the following format -; label: instruction operands ; comment -; The label is optional and even the colon after the label is optional. -; "DB" is used to declare initialized data. The "B" in "DB" means byte. -; the part ", 0" means that the last byte will be 0. In some way resembles -; nul character in C strings. -; -; Put the string in the output of this assembly file. Byte by byte, and the address of -; this string will be in the label, so we can reach the string inside the assembly source code. title_string db 'The Bootloader of 539kernel.', 0 message_string db 'The kernel is loading...', 0 load_error_string db 'The kernel cannot be loaded', 0 number_of_sectors_to_load db 10d ; 255 sectors = 127.5KB ; [MQH] NEW 4 July 2021 curr_sector_to_load db 2d -; "TIMES" is an NASM pseudo-instruction which repeats an instruction a number of specific times. -; The first operand of "TIMES" is the number of repetitions. -; As we mentioned before "$" means the starting address (or assembly position?) of current instruction. -; "$$" is another special expression which means the starting address (or assembly position?) of current section. -; So, we know that the size of bootstrap should be 512 byte. Two bytes are represent the magic code in the last line -; so 510 bytes remains for us. Our code starts from position $$ and we reached position $. Therefore, $ - $$ gives -; us the size of our code. So, 510 - ( $ - $$ ) gives us the remaining size of the 512 bytes and fills it with zeros -; by using "DB". +; write the magic signature 0xAA55 times 510-($-$$) db 0 -; Put the magic code of bootloader in the end of assembly file's output. "W" means word. +; pad with 0 so magic signature is at loc 510-511 (0-based) dw 0xAA55 diff --git a/gdt.asm b/gdt.asm index 4d6ee63..e9fbf0b 100644 --- a/gdt.asm +++ b/gdt.asm @@ -1,15 +1,22 @@ ; The values of the decriptors from Basekernel (kernelcode.S) (https://github.com/dthain/basekernel) gdt: + + ; in x86, first entry is null descriptor null_descriptor : dw 0, 0, 0, 0 + + ; code and data segment of kernel kernel_code_descriptor : dw 0xffff, 0x0000, 9a00h, 0x00cf kernel_data_descriptor : dw 0xffff, 0x0000, 0x9200, 0x00cf + + ; code and data segment of user application userspace_code_descriptor : dw 0xffff, 0x0000, 0xfa00, 0x00cf userspace_data_descriptor : dw 0xffff, 0x0000, 0xf200, 0x00cf + ; DON'T FORGET TO CHANGE THE SIZE OF THE GDT - ; TODO: I think the limit isn't correct tss_descriptor : dw tss + 3, tss, 0x8900, 0x0000 gdtr: + ; size: 6 entries, each 8 byte gdt_size_in_bytes : dw ( 6 * 8 ) ;= 28h gdt_base_address : dd gdt diff --git a/idt.asm b/idt.asm index e7a18a8..063d2cc 100644 --- a/idt.asm +++ b/idt.asm @@ -1,5 +1,6 @@ isr_0: cli + ; push number of current interrupt into the stack push 0 jmp isr_basic @@ -294,9 +295,10 @@ isr_48: jmp irq_basic isr_basic: -; cli + ; call C function interrput_handler call interrupt_handler + ; clean stack frame: pop stack frame and save popped value in eax (chosen arbitrarily) pop eax sti iret @@ -321,6 +323,7 @@ irq_basic: ; The value of the flags from Basekernel (kernelcode.S) (https://github.com/dthain/basekernel) idt: + ; handler_name, segment_selector, present, privilege_level, descriptor_size, gate_type (in this case interrupt) dw isr_0, 8, 0x8e00, 0x0000 dw isr_1, 8, 0x8e00, 0x0000 dw isr_2, 8, 0x8e00, 0x0000 diff --git a/main.c b/main.c index bbd4a6f..50487f1 100644 --- a/main.c +++ b/main.c @@ -19,16 +19,12 @@ void kernel_main() { process_t p1, p2, p3, p4; - // ... // - heap_init(); paging_init(); screen_init(); process_init(); scheduler_init(); - // ... // - // video[0] = 'A'; // video[2] = 'B' why not video[1]? this is because the byte after char contains color information // Each memory location corresponds to Cartesian coordinate [k * 2] = (k % max_x, k / max_x) e.g. [0] = (0, 0), [2] = (1, 0) @@ -37,8 +33,6 @@ void kernel_main() print( "We are now in Protected-mode" ); println(); - // ... // - process_create( &processA ); process_create( &processB ); process_create( &processC ); diff --git a/paging.c b/paging.c index e091d73..24164d5 100644 --- a/paging.c +++ b/paging.c @@ -1,7 +1,5 @@ #include "paging.h" - unsigned int *page_directory; - int create_page_entry( int base_address, char present, char writable, char privilege_level, char cache_enabled, char write_through_cache, char accessed, char page_size, char dirty ) { diff --git a/starter.asm b/starter.asm index 4f74cf1..a011c67 100644 --- a/starter.asm +++ b/starter.asm @@ -1,4 +1,14 @@ +; runs in 16 bits +; to prepare proper environment for main kernel by: +; - init and load GDT +; - set interrupts +; - set video mode +; - change 16 bit real mode to 32 bit protected mode + +; tell NASM to generate 16 bit code since we will be using 16 bit real mode +; in the bootloader don't need to do this because: for flat binary the default is 16 bit but we use ELF32 bits 16 +; extern tells NASM that there is a function/ variable define outside this code, it will be figured out by the linker extern kernel_main extern interrupt_handler extern scheduler @@ -9,10 +19,8 @@ global load_page_directory global enable_paging start: - mov ax, cs + mov ax, cs ; set proper memory address of ds depending on the value of cs mov ds, ax - - ; --- ; call load_gdt call init_video_mode @@ -20,29 +28,35 @@ start: call setup_interrupts call load_task_register - ; --- ; - call 08h:start_kernel load_gdt: - cli - lgdt [gdtr - start] - - ret - + ; turn off interrupts (recommended by x86) + cli + ; substract address of start from gdtr, take this resulting address and load the content there + ; why gdtr - start instead of gdtr? + ; in real mode, gdtr -> is seen as offset from segment register instead of full address + lgdt [gdtr - start] + + ret + setup_interrupts: call remap_pic call load_idt ret - + +; cannot use BIOS to print to screen anymore, so we need to use vga init_video_mode: - ;; Set Video Mode + ; Set Video Mode + ; call BIOS service 10h:0h to set video mode to number passed in register al mov ah, 0h - mov al, 03h ; 03h For Text Mode. 13h For Graphics Mode. + + ; 03h -> text mode with 16 colors, 13h is graphic mode with 256 colors + mov al, 03h int 10h - ;; Disable Text Cursor + ; call BIOS service 10h:01h to disable text cursor mov ah, 01h mov cx, 2000h int 10h @@ -50,7 +64,10 @@ init_video_mode: ret enter_protected_mode: + ; x86 has cr0-cr7 control register mov eax, cr0 + + ; last bit of cr0 -> 0: disable protected mode, 1: enable protected mode or eax, 1 mov cr0, eax @@ -59,6 +76,7 @@ enter_protected_mode: remap_pic: mov al, 11h + ; out port_number, value send_init_cmd_to_pic_master: out 0x20, al