mirror of
https://github.com/cfenollosa/os-tutorial.git
synced 2024-10-27 20:34:19 +00:00
lessons 8, 9, 10, entering 32-bit mode
This commit is contained in:
parent
085510f3c8
commit
afa376d2b6
26
08-32bit-print/32bit-print.asm
Normal file
26
08-32bit-print/32bit-print.asm
Normal 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
|
29
08-32bit-print/README.md
Normal file
29
08-32bit-print/README.md
Normal file
@ -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.
|
35
09-32bit-gdt/32bit-gdt.asm
Normal file
35
09-32bit-gdt/32bit-gdt.asm
Normal 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
|
31
09-32bit-gdt/README.md
Normal file
31
09-32bit-gdt/README.md
Normal file
@ -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.
|
27
10-32bit-enter/32bit-main.asm
Normal file
27
10-32bit-enter/32bit-main.asm
Normal file
@ -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
|
22
10-32bit-enter/32bit-switch.asm
Normal file
22
10-32bit-enter/32bit-switch.asm
Normal 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
|
23
10-32bit-enter/README.md
Normal file
23
10-32bit-enter/README.md
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user