lessons 8, 9, 10, entering 32-bit mode

pull/6/head
Carlos Fenollosa 10 years ago
parent 085510f3c8
commit afa376d2b6

@ -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

@ -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.

@ -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

@ -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.

@ -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

@ -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

@ -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…
Cancel
Save