diff --git a/08-32bit-print/32bit-print.asm b/08-32bit-print/32bit-print.asm new file mode 100644 index 0000000..4169a17 --- /dev/null +++ b/08-32bit-print/32bit-print.asm @@ -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 diff --git a/08-32bit-print/README.md b/08-32bit-print/README.md new file mode 100644 index 0000000..111d599 --- /dev/null +++ b/08-32bit-print/README.md @@ -0,0 +1,29 @@ +*Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video +memory* + +**Goal: Print on the screen when on 32-bit protected mode** + +32-bit mode allows us to use 32 bit registers and memory addressing +, protected memory, virtual memory and other advangades, but we will lose +BIOS interrupts and we'll need to code the GDT (more on this later) + +In this lesson we will write a print string routine by directly manipulating +the VGA video memory instead of calling `int 0x10`. The VGA memory starts +at address `0xb8000` and it has a text mode which is useful to avoid +manipulating direct pixels. + +The formula for accessing a specific character on the 80x25 grid is: + +`0xb8000 + 2 * (row * 80 + col)` + +That is, every character uses 2 bytes (one for the ASCII, another for +color and such), and we see that the structure of the memory concatenates +rows. + +Open `32bit-print.asm` to see the code. It will always print the string +on the top left of the screen, but soon we'll write higher level routines +to replace it. + +Unfortunately we cannot yet call this routine from the bootloader, because +we still don't know how to write the GDT and enter protected mode. Once +you have understood the code, jump to the next lesson. diff --git a/09-32bit-gdt/32bit-gdt.asm b/09-32bit-gdt/32bit-gdt.asm new file mode 100644 index 0000000..e5db388 --- /dev/null +++ b/09-32bit-gdt/32bit-gdt.asm @@ -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 diff --git a/09-32bit-gdt/README.md b/09-32bit-gdt/README.md new file mode 100644 index 0000000..c728bf8 --- /dev/null +++ b/09-32bit-gdt/README.md @@ -0,0 +1,31 @@ +*Concepts you may want to Google beforehand: GDT* + +**Goals: program the GDT** + +Remember segmentation from lesson 6? The offset was left shifted +to address an extra level of indirection. + +In 32-bit mode, segmentation works differently. Now, the offset becomes an +index to a segment descriptor (SD) in the GDT. This descriptor defines +the base address (32 bits), the size (20 bits) and some flags, like +readonly, permissions, etc. To add confusion, the data structures are split, +so open the os-dev.pdf file and check out the figure on page 34 or the +Wikipedia page for the GDT. + +The easiest way to program the GDT is to define two segments, one for code +and another for data. These can overlap which means there is no memory protection, +but it's good enough to boot, we'll fix this later with a higher language. + +As a curiosity, the first GDT entry must be `0x00` to make sure that the +programmer didn't make any mistakes managing addresses. + +Furthermore, since the CPU needs to know how long the GDT is, we'll use +a meta structure called the "GDT descriptor" with the size (16b) and address +(32b) of our actual GDT. + +Let's directly jump to the GDT code in assembly. Again, to understand +all the segment flags, refer to the os-dev.pdf document. The theory for +this lesson is quite complex. + +In the next lesson we will make the switch to 32-bit protected mode +and test our code from these lessons. diff --git a/10-32bit-enter/32bit-main.asm b/10-32bit-enter/32bit-main.asm new file mode 100644 index 0000000..b84404e --- /dev/null +++ b/10-32bit-enter/32bit-main.asm @@ -0,0 +1,27 @@ +[org 0x7c00] ; bootloader offset + mov bp, 0x9000 ; set the stack + mov sp, bp + + mov bx, MSG_REAL_MODE + call print ; This will be written after the BIOS messages + + call switch_to_pm + jmp $ ; this will actually never be executed + +%include "../05-bootsector-functions-strings/boot_sect_print.asm" +%include "../09-32bit-gdt/32bit-gdt.asm" +%include "../08-32bit-print/32bit-print.asm" +%include "32bit-switch.asm" + +[bits 32] +BEGIN_PM: ; after the switch we will get here + mov ebx, MSG_PROT_MODE + call print_string_pm ; Note that this will be written at the top left corner + jmp $ + +MSG_REAL_MODE db "Started in 16-bit real mode", 0 +MSG_PROT_MODE db "Loaded 32-bit protected mode", 0 + +; bootsector +times 510-($-$$) db 0 +dw 0xaa55 diff --git a/10-32bit-enter/32bit-switch.asm b/10-32bit-enter/32bit-switch.asm new file mode 100644 index 0000000..9394da3 --- /dev/null +++ b/10-32bit-enter/32bit-switch.asm @@ -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 diff --git a/10-32bit-enter/README.md b/10-32bit-enter/README.md new file mode 100644 index 0000000..350fa93 --- /dev/null +++ b/10-32bit-enter/README.md @@ -0,0 +1,23 @@ +*Concepts you may want to Google beforehand: interrupts, pipelining* + +**Goal: Enter 32-bit protected mode and test our code from previous lessons** + +To jump into 32-bit mode: + +1. Disable interrupts +2. Load our GDT +3. Set a bit on the CPU control register `cr0` +4. Flush the CPU pipeline by issuing a carefully crafted far jump +5. Update all the segment registers +6. Update the stack +7. Call to a well-known label which contains the first useful code in 32 bits + +We will encapsulate this process on the file `32bit-switch.asm`. Open it +and take a look at the code. + +After entering 32-bit mode, we will call `BEGIN_PM` which is the entry point +for our actual useful code (e.g. kernel code, etc). You can read the code +at `32bit-main.asm`. Compile and run this last file and you will see the two +messages on the screen. + +Congratulations! Our next step will be to write a simple kernel