├── .gitignore
├── 00-environment
└── README.md
├── 01-bootsector-barebones
├── README.md
└── boot_sect_simple.asm
├── 02-bootsector-print
├── README.md
└── boot_sect_hello.asm
├── 03-bootsector-memory
├── README.md
├── boot_sect_memory.asm
└── boot_sect_memory_org.asm
├── 04-bootsector-stack
├── README.md
└── boot_sect_stack.asm
├── 05-bootsector-functions-strings
├── README.md
├── boot_sect_main.asm
├── boot_sect_print.asm
└── boot_sect_print_hex.asm
├── 06-bootsector-segmentation
├── README.md
└── boot_sect_segmentation.asm
├── 07-bootsector-disk
├── README.md
├── boot_sect_disk.asm
└── boot_sect_main.asm
├── 08-32bit-print
├── 32bit-print.asm
└── README.md
├── 09-32bit-gdt
├── 32bit-gdt.asm
└── README.md
├── 10-32bit-enter
├── 32bit-main.asm
├── 32bit-switch.asm
└── README.md
├── 11-kernel-crosscompiler
└── README.md
├── 12-kernel-c
├── README.md
├── function.c
├── functioncalls.c
├── localvars.c
└── pointers.c
├── 13-kernel-barebones
├── Makefile
├── README.md
├── bootsect.asm
├── kernel.c
└── kernel_entry.asm
├── 14-checkpoint
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
└── kernel
│ └── kernel.c
├── 15-video-ports
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
├── drivers
│ ├── ports.c
│ └── ports.h
└── kernel
│ └── kernel.c
├── 16-video-driver
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
├── drivers
│ ├── ports.c
│ ├── ports.h
│ ├── screen.c
│ └── screen.h
└── kernel
│ └── kernel.c
├── 17-video-scroll
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
├── drivers
│ ├── ports.c
│ ├── ports.h
│ ├── screen.c
│ └── screen.h
└── kernel
│ ├── kernel.c
│ ├── util.c
│ └── util.h
├── 18-interrupts
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ └── types.h
├── drivers
│ ├── ports.c
│ ├── ports.h
│ ├── screen.c
│ └── screen.h
└── kernel
│ ├── kernel.c
│ ├── util.c
│ └── util.h
├── 19-interrupts-irqs
├── Makefile
├── README.md
├── boot
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ └── types.h
├── drivers
└── kernel
│ ├── kernel.c
│ ├── util.c
│ └── util.h
├── 20-interrupts-timer
├── Makefile
├── README.md
├── boot
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ ├── timer.c
│ ├── timer.h
│ └── types.h
├── drivers
│ ├── keyboard.c
│ ├── keyboard.h
│ ├── ports.c
│ ├── ports.h
│ ├── screen.c
│ └── screen.h
└── kernel
│ ├── kernel.c
│ ├── util.c
│ └── util.h
├── 21-shell
├── Makefile
├── README.md
├── boot
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ ├── ports.c
│ ├── ports.h
│ ├── timer.c
│ ├── timer.h
│ └── types.h
├── drivers
│ ├── keyboard.c
│ ├── keyboard.h
│ ├── screen.c
│ └── screen.h
├── kernel
│ ├── kernel.c
│ └── kernel.h
└── libc
│ ├── function.h
│ ├── mem.c
│ ├── mem.h
│ ├── string.c
│ └── string.h
├── 22-malloc
├── Makefile
├── README.md
├── boot
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ ├── ports.c
│ ├── ports.h
│ ├── timer.c
│ ├── timer.h
│ └── type.h
├── drivers
│ ├── keyboard.c
│ ├── keyboard.h
│ ├── screen.c
│ └── screen.h
├── kernel
│ ├── kernel.c
│ └── kernel.h
└── libc
│ ├── function.h
│ ├── mem.c
│ ├── mem.h
│ ├── string.c
│ └── string.h
├── 23-fixes
├── Makefile
├── README.md
├── boot
│ ├── 32bit_print.asm
│ ├── bootsect.asm
│ ├── disk.asm
│ ├── gdt.asm
│ ├── kernel_entry.asm
│ ├── print.asm
│ ├── print_hex.asm
│ └── switch_pm.asm
├── cpu
│ ├── idt.c
│ ├── idt.h
│ ├── interrupt.asm
│ ├── isr.c
│ ├── isr.h
│ ├── ports.c
│ ├── ports.h
│ ├── timer.c
│ ├── timer.h
│ └── type.h
├── drivers
│ ├── keyboard.c
│ ├── keyboard.h
│ ├── screen.c
│ └── screen.h
├── kernel
│ ├── kernel.c
│ └── kernel.h
└── libc
│ ├── function.h
│ ├── mem.c
│ ├── mem.h
│ ├── string.c
│ └── string.h
├── 24-el-capitan
├── Makefile
├── README.md
├── boot
├── cpu
├── drivers
├── kernel
└── libc
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bin
2 | *.o
3 | *.swp
4 | *.dis
5 | *.elf
6 | *.sym
7 | .DS_STORE
8 |
--------------------------------------------------------------------------------
/00-environment/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: linux, mac, terminal, compiler, emulator, nasm, qemu*
2 |
3 | **Goal: Install the software required to run this tutorial**
4 |
5 | I'm working on a Mac, though Linux is better because it will have all the standard tools already
6 | available for you.
7 |
8 | On a mac, [install Homebrew](http://brew.sh) and then `brew install qemu nasm`
9 |
10 | Don't use the Xcode developer tools `nasm` if you have them installed, they won't work for the most cases. Always use `/usr/local/bin/nasm`
11 |
12 | On some systems qemu is split into multiple binaries. You may want
13 | to call `qemu-system-x86_64 binfile`
14 |
--------------------------------------------------------------------------------
/01-bootsector-barebones/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: assembler, BIOS*
2 |
3 | **Goal: Create a file which the BIOS interprets as a bootable disk**
4 |
5 | This is very exciting, we're going to create our own boot sector!
6 |
7 | Theory
8 | ------
9 |
10 | When the computer boots, the BIOS doesn't know how to load the OS, so it
11 | delegates that task to the boot sector. Thus, the boot sector must be
12 | placed in a known, standard location. That location is the first sector
13 | of the disk (cylinder 0, head 0, sector 0) and it takes 512 bytes.
14 |
15 | To make sure that the "disk is bootable", the BIOS checks that bytes
16 | 511 and 512 of the alleged boot sector are bytes `0xAA55`.
17 |
18 | This is the simplest boot sector ever:
19 |
20 | ```
21 | e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00
22 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
23 | [ 29 more lines with sixteen zero-bytes each ]
24 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
25 | ```
26 |
27 | It is basically all zeros, ending with the 16-bit value
28 | `0xAA55` (beware of endianness, x86 is little-endian).
29 | The first three bytes perform an infinite jump
30 |
31 | Simplest boot sector ever
32 | -------------------------
33 |
34 | You can either write the above 512 bytes
35 | with a binary editor, or just write a very
36 | simple assembler code:
37 |
38 | ```nasm
39 | ; Infinite loop (e9 fd ff)
40 | loop:
41 | jmp loop
42 |
43 | ; Fill with 510 zeros minus the size of the previous code
44 | times 510-($-$$) db 0
45 | ; Magic number
46 | dw 0xaa55
47 | ```
48 |
49 | To compile:
50 | `nasm -f bin boot_sect_simple.asm -o boot_sect_simple.bin`
51 |
52 | > OSX warning: if this drops an error, read chapter 00 again
53 |
54 | I know you're anxious to try it out (I am!), so let's do it:
55 |
56 | `qemu boot_sect_simple.bin`
57 |
58 | > On some systems, you may have to run `qemu-system-x86_64 boot_sect_simple.bin` If this gives an SDL error, try passing the --nographic and/or --curses flag(s).
59 |
60 | You will see a window open which says "Booting from Hard Disk..." and
61 | nothing else. When was the last time you were so excited to see an infinite
62 | loop? ;-)
63 |
--------------------------------------------------------------------------------
/01-bootsector-barebones/boot_sect_simple.asm:
--------------------------------------------------------------------------------
1 | ; A simple boot sector program that loops forever
2 | loop:
3 | jmp loop
4 |
5 | times 510-($-$$) db 0
6 | dw 0xaa55
7 |
--------------------------------------------------------------------------------
/02-bootsector-print/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: interrupts, CPU
2 | registers*
3 |
4 | **Goal: Make our previously silent boot sector print some text**
5 |
6 | We will improve a bit on our infinite-loop boot sector and print
7 | something on the screen. We will raise an interrupt for this.
8 |
9 | On this example we are going to write each character of the "Hello"
10 | word into the register `al` (lower part of `ax`), the bytes `0x0e`
11 | into `ah` (the higher part of `ax`) and raise interrupt `0x10` which
12 | is a general interrupt for video services.
13 |
14 | `0x0e` on `ah` tells the video interrupt that the actual function
15 | we want to run is to 'write the contents of `al` in tty mode'.
16 |
17 | We will set tty mode only once though in the real world we
18 | cannot be sure that the contents of `ah` are constant. Some other
19 | process may run on the CPU while we are sleeping, not clean
20 | up properly and leave garbage data on `ah`.
21 |
22 | For this example we don't need to take care of that since we are
23 | the only thing running on the CPU.
24 |
25 | Our new boot sector looks like this:
26 | ```nasm
27 | mov ah, 0x0e ; tty mode
28 | mov al, 'H'
29 | int 0x10
30 | mov al, 'e'
31 | int 0x10
32 | mov al, 'l'
33 | int 0x10
34 | int 0x10 ; 'l' is still on al, remember?
35 | mov al, 'o'
36 | int 0x10
37 |
38 | jmp $ ; jump to current address = infinite loop
39 |
40 | ; padding and magic number
41 | times 510 - ($-$$) db 0
42 | dw 0xaa55
43 | ```
44 |
45 | You can examine the binary data with `xxd file.bin`
46 |
47 | Anyway, you know the drill:
48 |
49 | `nasm -fbin boot_sect_hello.asm -o boot_sect_hello.bin`
50 |
51 | `qemu boot_sect_hello.bin`
52 |
53 | Your boot sector will say 'Hello' and hang on an infinite loop
54 |
--------------------------------------------------------------------------------
/02-bootsector-print/boot_sect_hello.asm:
--------------------------------------------------------------------------------
1 | mov ah, 0x0e ; tty mode
2 | mov al, 'H'
3 | int 0x10
4 | mov al, 'e'
5 | int 0x10
6 | mov al, 'l'
7 | int 0x10
8 | int 0x10 ; 'l' is still on al, remember?
9 | mov al, 'o'
10 | int 0x10
11 |
12 | jmp $ ; jump to current address = infinite loop
13 |
14 | ; padding and magic number
15 | times 510 - ($-$$) db 0
16 | dw 0xaa55
17 |
--------------------------------------------------------------------------------
/03-bootsector-memory/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: memory offsets, pointers*
2 |
3 | **Goal: Learn how the computer memory is organized**
4 |
5 | Please open page 14 [of this document](
6 | http://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf)1
7 | and look at the figure with the memory layout.
8 |
9 | The only goal of this lesson is to learn where the boot sector is stored
10 |
11 | I could just bluntly tell you that the BIOS places it at `0x7C00` and
12 | get it done with, but an example with wrong solutions will make things clearer.
13 |
14 | We want to print an X on screen. We will try 4 different strategies
15 | and see which ones work and why.
16 |
17 | **Open the file `boot_sect_memory.asm`**
18 |
19 | First, we will define the X as data, with a label:
20 | ```nasm
21 | the_secret:
22 | db "X"
23 | ```
24 |
25 | Then we will try to access `the_secret` in many different ways:
26 |
27 | 1. `mov al, the_secret`
28 | 2. `mov al, [the_secret]`
29 | 3. `mov al, the_secret + 0x7C00`
30 | 4. `mov al, 2d + 0x7C00`, where `2d` is the actual position of the 'X' byte in the binary
31 |
32 | Take a look at the code and read the comments.
33 |
34 | Compile and run the code. You should see a string similar to `1[2¢3X4X`, where
35 | the bytes following 1 and 2 are just random garbage.
36 |
37 | If you add or remove instructions, remember to compute the new offset of the X
38 | by counting the bytes, and replace `0x2d` with the new one.
39 |
40 | Please don't continue onto the next section unless you have 100% understood
41 | the boot sector offset and memory addressing.
42 |
43 |
44 | The global offset
45 | -----------------
46 |
47 | Now, since offsetting `0x7c00` everywhere is very inconvenient, assemblers let
48 | us define a "global offset" for every memory location, with the `org` command:
49 |
50 | ```nasm
51 | [org 0x7c00]
52 | ```
53 |
54 | Go ahead and **open `boot_sect_memory_org.asm`** and you will see the canonical
55 | way to print data with the boot sector, which is now attempt 2. Compile the code
56 | and run it, and you will see how the `org` command affects each previous solution.
57 |
58 | Read the comments for a full explanation of the changes with and without `org`
59 |
60 | -----
61 |
62 | [1] This whole tutorial is heavily inspired on that document. Please read the
63 | root-level README for more information on that.
64 |
--------------------------------------------------------------------------------
/03-bootsector-memory/boot_sect_memory.asm:
--------------------------------------------------------------------------------
1 | mov ah, 0x0e
2 |
3 | ; attempt 1
4 | ; Fails because it tries to print the memory address (i.e. pointer)
5 | ; not its actual contents
6 | mov al, "1"
7 | int 0x10
8 | mov al, the_secret
9 | int 0x10
10 |
11 | ; attempt 2
12 | ; It tries to print the memory address of 'the_secret' which is the correct approach.
13 | ; However, BIOS places our bootsector binary at address 0x7c00
14 | ; so we need to add that padding beforehand. We'll do that in attempt 3
15 | mov al, "2"
16 | int 0x10
17 | mov al, [the_secret]
18 | int 0x10
19 |
20 | ; attempt 3
21 | ; Add the BIOS starting offset 0x7c00 to the memory address of the X
22 | ; and then dereference the contents of that pointer.
23 | ; We need the help of a different register 'bx' because 'mov al, [ax]' is illegal.
24 | ; A register can't be used as source and destination for the same command.
25 | mov al, "3"
26 | int 0x10
27 | mov bx, the_secret
28 | add bx, 0x7c00
29 | mov al, [bx]
30 | int 0x10
31 |
32 | ; attempt 4
33 | ; We try a shortcut since we know that the X is stored at byte 0x2d in our binary
34 | ; That's smart but ineffective, we don't want to be recounting label offsets
35 | ; every time we change the code
36 | mov al, "4"
37 | int 0x10
38 | mov al, [0x7c2d]
39 | int 0x10
40 |
41 |
42 | jmp $ ; infinite loop
43 |
44 | the_secret:
45 | ; ASCII code 0x58 ('X') is stored just before the zero-padding.
46 | ; On this code that is at byte 0x2d (check it out using 'xxd file.bin')
47 | db "X"
48 |
49 | ; zero padding and magic bios number
50 | times 510-($-$$) db 0
51 | dw 0xaa55
52 |
--------------------------------------------------------------------------------
/03-bootsector-memory/boot_sect_memory_org.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00]
2 | mov ah, 0x0e
3 |
4 | ; attempt 1
5 | ; Will fail again regardless of 'org' because we are still addressing the pointer
6 | ; and not the data it points to
7 | mov al, "1"
8 | int 0x10
9 | mov al, the_secret
10 | int 0x10
11 |
12 | ; attempt 2
13 | ; Having solved the memory offset problem with 'org', this is now the correct answer
14 | mov al, "2"
15 | int 0x10
16 | mov al, [the_secret]
17 | int 0x10
18 |
19 | ; attempt 3
20 | ; As you expected, we are adding 0x7c00 twice, so this is not going to work
21 | mov al, "3"
22 | int 0x10
23 | mov bx, the_secret
24 | add bx, 0x7c00
25 | mov al, [bx]
26 | int 0x10
27 |
28 | ; attempt 4
29 | ; This still works because there are no memory references to pointers, so
30 | ; the 'org' mode never applies. Directly addressing memory by counting bytes
31 | ; is always going to work, but it's inconvenient
32 | mov al, "4"
33 | int 0x10
34 | mov al, [0x7c2d]
35 | int 0x10
36 |
37 |
38 | jmp $ ; infinite loop
39 |
40 | the_secret:
41 | ; ASCII code 0x58 ('X') is stored just before the zero-padding.
42 | ; On this code that is at byte 0x2d (check it out using 'xxd file.bin')
43 | db "X"
44 |
45 | ; zero padding and magic bios number
46 | times 510-($-$$) db 0
47 | dw 0xaa55
48 |
--------------------------------------------------------------------------------
/04-bootsector-stack/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: stack*
2 |
3 | **Goal: Learn how to use the stack**
4 |
5 | The usage of the stack is important, so we'll write yet another boot sector
6 | with an example.
7 |
8 | Remember that the `bp` register stores the base address (i.e. bottom) of the stack,
9 | and `sp` stores the top, and that the stack grows downwards from `bp` (i.e. `sp` gets
10 | decremented)
11 |
12 | This lesson is quite straightforward, so jump ahead to the code.
13 |
14 | I suggest that you try accessing in-stack memory addresses by yourself,
15 | at different points in the code, and see what happens.
16 |
--------------------------------------------------------------------------------
/04-bootsector-stack/boot_sect_stack.asm:
--------------------------------------------------------------------------------
1 | mov ah, 0x0e ; tty mode
2 |
3 | mov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten
4 | mov sp, bp ; if the stack is empty then sp points to bp
5 |
6 | push 'A'
7 | push 'B'
8 | push 'C'
9 |
10 | ; to show how the stack grows downwards
11 | mov al, [0x7ffe] ; 0x8000 - 2
12 | int 0x10
13 |
14 | ; however, don't try to access [0x8000] now, because it won't work
15 | ; you can only access the stack top so, at this point, only 0x7ffe (look above)
16 | mov al, [0x8000]
17 | int 0x10
18 |
19 |
20 | ; recover our characters using the standard procedure: 'pop'
21 | ; We can only pop full words so we need an auxiliary register to manipulate
22 | ; the lower byte
23 | pop bx
24 | mov al, bl
25 | int 0x10 ; prints C
26 |
27 | pop bx
28 | mov al, bl
29 | int 0x10 ; prints B
30 |
31 | pop bx
32 | mov al, bl
33 | int 0x10 ; prints A
34 |
35 | ; data that has been pop'd from the stack is garbage now
36 | mov al, [0x8000]
37 | int 0x10
38 |
39 |
40 | jmp $
41 | times 510-($-$$) db 0
42 | dw 0xaa55
43 |
--------------------------------------------------------------------------------
/05-bootsector-functions-strings/boot_sect_main.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00] ; tell the assembler that our offset is bootsector code
2 |
3 | ; The main routine makes sure the parameters are ready and then calls the function
4 | mov bx, HELLO
5 | call print
6 |
7 | call print_nl
8 |
9 | mov bx, GOODBYE
10 | call print
11 |
12 | call print_nl
13 |
14 | mov dx, 0x12fe
15 | call print_hex
16 |
17 | ; that's it! we can hang now
18 | jmp $
19 |
20 | ; remember to include subroutines below the hang
21 | %include "boot_sect_print.asm"
22 | %include "boot_sect_print_hex.asm"
23 |
24 |
25 | ; data
26 | HELLO:
27 | db 'Hello, World', 0
28 |
29 | GOODBYE:
30 | db 'Goodbye', 0
31 |
32 | ; padding and magic number
33 | times 510-($-$$) db 0
34 | dw 0xaa55
35 |
--------------------------------------------------------------------------------
/05-bootsector-functions-strings/boot_sect_print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/05-bootsector-functions-strings/boot_sect_print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/06-bootsector-segmentation/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: segmentation*
2 |
3 | **Goal: learn how to address memory with 16-bit real mode segmentation**
4 |
5 | If you are comfortable with segmentation, skip this lesson.
6 |
7 | We did segmentation
8 | with `[org]` on lesson 3. Segmentation means that you can specify
9 | an offset to all the data you refer to.
10 |
11 | This is done by using special registers: `cs`, `ds`, `ss` and `es`, for
12 | Code, Data, Stack and Extra (i.e. user-defined)
13 |
14 | Beware: they are *implicitly* used by the CPU, so once you set some
15 | value for, say, `ds`, then all your memory access will be offset by `ds`.
16 | [Read more here](http://wiki.osdev.org/Segmentation)
17 |
18 | Furthermore, to compute the real address we don't just join the two
19 | addresses, but we *overlap* them: `segment << 4 + address`. For example,
20 | if `ds` is `0x4d`, then `[0x20]` actually refers to `0x4d0 + 0x20 = 0x4f0`
21 |
22 | Enough theory. Have a look at the code and play with it a bit.
23 |
24 | Hint: We cannot `mov` literals to those registers, we have to
25 | use a general purpose register before.
26 |
--------------------------------------------------------------------------------
/06-bootsector-segmentation/boot_sect_segmentation.asm:
--------------------------------------------------------------------------------
1 | mov ah, 0x0e ; tty
2 |
3 | mov al, [the_secret]
4 | int 0x10 ; we already saw this doesn't work, right?
5 |
6 | mov bx, 0x7c0 ; remember, the segment is automatically <<4 for you
7 | mov ds, bx
8 | ; WARNING: from now on all memory references will be offset by 'ds' implicitly
9 | mov al, [the_secret]
10 | int 0x10
11 |
12 | mov al, [es:the_secret]
13 | int 0x10 ; doesn't look right... isn't 'es' currently 0x000?
14 |
15 | mov bx, 0x7c0
16 | mov es, bx
17 | mov al, [es:the_secret]
18 | int 0x10
19 |
20 |
21 | jmp $
22 |
23 | the_secret:
24 | db "X"
25 |
26 | times 510 - ($-$$) db 0
27 | dw 0xaa55
28 |
--------------------------------------------------------------------------------
/07-bootsector-disk/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: hard disk, cylinder, head, sector,
2 | carry bit*
3 |
4 | **Goal: Let the bootsector load data from disk in order to boot the kernel**
5 |
6 | Our OS won't fit inside the bootsector 512 bytes, so we need to read data from
7 | a disk in order to run the kernel.
8 |
9 | Thankfully, we don't have to deal with turning spinning platters on and off,
10 | we can just call some BIOS routines, like we did to print characters on the screen.
11 | To do so, we set `al` to `0x02` (and other registers with the required cylinder, head
12 | and sector) and raise `int 0x13`
13 |
14 | You can access [a detailed int 13h guide here](http://stanislavs.org/helppc/int_13-2.html)
15 |
16 | On this lesson we will use for the first time the *carry bit*, which is an extra bit
17 | present on each register which stores when an operation has overflowed its current
18 | capacity:
19 |
20 | ```nasm
21 | mov ax, 0xFFFF
22 | add ax, 1 ; ax = 0x0000 and carry = 1
23 | ```
24 |
25 | The carry isn't accessed directly but used as a control structure by other operators,
26 | like `jc` (jump if the carry bit is set)
27 |
28 | The BIOS also sets `al` to the number of sectors read, so always compare it
29 | to the expected number.
30 |
31 |
32 | Code
33 | ----
34 |
35 | Open and examine `boot_sect_disk.asm` for the complete routine that
36 | reads from disk.
37 |
38 | `boot_sect_main.asm` prepares the parameters for disk read and calls `disk_load`.
39 | Notice how we write some extra data which does not actually belong to the boot
40 | sector, since it is outside the 512 bits mark.
41 |
42 | The boot sector is actually sector 1 (the first one, sectors start at 1)
43 | of cylinder 0 of head 0 of hdd 0.
44 |
45 | Thus, any bytes after byte 512 correspond to sector 2 of cylinder 0 of head 0 of hdd 0
46 |
47 | The main routine will fill it with sample data and then let the bootsector
48 | read it.
49 |
50 | **Note: if you keep getting errors and your code seems fine, make sure that qemu
51 | is booting from the right drive and set the drive on `dl` accordingly**
52 |
53 | The BIOS sets `dl` to the drive number before calling the bootloader. However,
54 | I found some problems with qemu when booting from the hdd.
55 |
56 | There are two quick options:
57 |
58 | 1. Try the flag `-fda` for example, `qemu -fda boot_sect_main.bin` which will set `dl`
59 | as `0x00`, it seems to work fine then.
60 | 2. Explicitly use the flag `-boot`, e.g. `qemu boot_sect_main.bin -boot c` which
61 | automatically sets `dl` as `0x80` and lets the bootloader read data
62 |
63 |
64 |
--------------------------------------------------------------------------------
/07-bootsector-disk/boot_sect_disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/07-bootsector-disk/boot_sect_main.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00]
2 | mov bp, 0x8000 ; set the stack safely away from us
3 | mov sp, bp
4 |
5 | mov bx, 0x9000 ; es:bx = 0x0000:0x9000 = 0x09000
6 | mov dh, 2 ; read 2 sectors
7 | ; the bios sets 'dl' for our boot disk number
8 | ; if you have trouble, use the '-fda' flag: 'qemu -fda file.bin'
9 | call disk_load
10 |
11 | mov dx, [0x9000] ; retrieve the first loaded word, 0xdada
12 | call print_hex
13 |
14 | call print_nl
15 |
16 | mov dx, [0x9000 + 512] ; first word from second loaded sector, 0xface
17 | call print_hex
18 |
19 | jmp $
20 |
21 | %include "../05-bootsector-functions-strings/boot_sect_print.asm"
22 | %include "../05-bootsector-functions-strings/boot_sect_print_hex.asm"
23 | %include "boot_sect_disk.asm"
24 |
25 | ; Magic number
26 | times 510 - ($-$$) db 0
27 | dw 0xaa55
28 |
29 | ; boot sector = sector 1 of cyl 0 of head 0 of hdd 0
30 | ; from now on = sector 2 ...
31 | times 256 dw 0xdada ; sector 2 = 512 bytes
32 | times 256 dw 0xface ; sector 3 = 512 bytes
33 |
--------------------------------------------------------------------------------
/08-32bit-print/32bit-print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_ON_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_ON_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/08-32bit-print/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video
2 | memory*
3 |
4 | **Goal: Print on the screen when on 32-bit protected mode**
5 |
6 | 32-bit mode allows us to use 32 bit registers and memory addressing,
7 | protected memory, virtual memory and other advantages, but we will lose
8 | BIOS interrupts and we'll need to code the GDT (more on this later)
9 |
10 | In this lesson we will write a new print string routine which works in
11 | 32-bit mode, where we don't have BIOS interrupts, by directly manipulating
12 | the VGA video memory instead of calling `int 0x10`. The VGA memory starts
13 | at address `0xb8000` and it has a text mode which is useful to avoid
14 | manipulating direct pixels.
15 |
16 |
17 | The formula for accessing a specific character on the 80x25 grid is:
18 |
19 | `0xb8000 + 2 * (row * 80 + col)`
20 |
21 | That is, every character uses 2 bytes (one for the ASCII, another for
22 | color and such), and we see that the structure of the memory concatenates
23 | rows.
24 |
25 | Open `32bit-print.asm` to see the code. It will always print the string
26 | on the top left of the screen, but soon we'll write higher level routines
27 | to replace it.
28 |
29 | Unfortunately we cannot yet call this routine from the bootloader, because
30 | we still don't know how to write the GDT and enter protected mode. Once
31 | you have understood the code, jump to the next lesson.
32 |
--------------------------------------------------------------------------------
/09-32bit-gdt/32bit-gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/09-32bit-gdt/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: GDT*
2 |
3 | **Goal: program the GDT**
4 |
5 | Remember segmentation from lesson 6? The offset was left shifted
6 | to address an extra level of indirection.
7 |
8 | In 32-bit mode, segmentation works differently. Now, the offset becomes an
9 | index to a segment descriptor (SD) in the GDT. This descriptor defines
10 | the base address (32 bits), the size (20 bits) and some flags, like
11 | readonly, permissions, etc. To add confusion, the data structures are split,
12 | so open the os-dev.pdf file and check out the figure on page 34 or the
13 | Wikipedia page for the GDT.
14 |
15 | The easiest way to program the GDT is to define two segments, one for code
16 | and another for data. These can overlap which means there is no memory protection,
17 | but it's good enough to boot, we'll fix this later with a higher language.
18 |
19 | As a curiosity, the first GDT entry must be `0x00` to make sure that the
20 | programmer didn't make any mistakes managing addresses.
21 |
22 | Furthermore, the CPU can't directly load the GDT address, but it requires
23 | a meta structure called the "GDT descriptor" with the size (16b) and address
24 | (32b) of our actual GDT. It is loaded with the `lgdt` operation.
25 |
26 | Let's directly jump to the GDT code in assembly. Again, to understand
27 | all the segment flags, refer to the os-dev.pdf document. The theory for
28 | this lesson is quite complex.
29 |
30 | In the next lesson we will make the switch to 32-bit protected mode
31 | and test our code from these lessons.
32 |
--------------------------------------------------------------------------------
/10-32bit-enter/32bit-main.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00] ; bootloader offset
2 | mov bp, 0x9000 ; set the stack
3 | mov sp, bp
4 |
5 | mov bx, MSG_REAL_MODE
6 | call print ; This will be written after the BIOS messages
7 |
8 | call switch_to_pm
9 | jmp $ ; this will actually never be executed
10 |
11 | %include "../05-bootsector-functions-strings/boot_sect_print.asm"
12 | %include "../09-32bit-gdt/32bit-gdt.asm"
13 | %include "../08-32bit-print/32bit-print.asm"
14 | %include "32bit-switch.asm"
15 |
16 | [bits 32]
17 | BEGIN_PM: ; after the switch we will get here
18 | mov ebx, MSG_PROT_MODE
19 | call print_string_pm ; Note that this will be written at the top left corner
20 | jmp $
21 |
22 | MSG_REAL_MODE db "Started in 16-bit real mode", 0
23 | MSG_PROT_MODE db "Loaded 32-bit protected mode", 0
24 |
25 | ; bootsector
26 | times 510-($-$$) db 0
27 | dw 0xaa55
28 |
--------------------------------------------------------------------------------
/10-32bit-enter/32bit-switch.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/10-32bit-enter/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: interrupts, pipelining*
2 |
3 | **Goal: Enter 32-bit protected mode and test our code from previous lessons**
4 |
5 | To jump into 32-bit mode:
6 |
7 | 1. Disable interrupts
8 | 2. Load our GDT
9 | 3. Set a bit on the CPU control register `cr0`
10 | 4. Flush the CPU pipeline by issuing a carefully crafted far jump
11 | 5. Update all the segment registers
12 | 6. Update the stack
13 | 7. Call to a well-known label which contains the first useful code in 32 bits
14 |
15 | We will encapsulate this process on the file `32bit-switch.asm`. Open it
16 | and take a look at the code.
17 |
18 | After entering 32-bit mode, we will call `BEGIN_PM` which is the entry point
19 | for our actual useful code (e.g. kernel code, etc). You can read the code
20 | at `32bit-main.asm`. Compile and run this last file and you will see the two
21 | messages on the screen.
22 |
23 | Congratulations! Our next step will be to write a simple kernel
24 |
--------------------------------------------------------------------------------
/11-kernel-crosscompiler/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: cross-compiler*
2 |
3 | **Goal: Create a development environment to build your kernel**
4 |
5 | If you're using a Mac, you will need to do this process right away. Otherwise, it could have waited
6 | for a few more lessons. Anyway, you will need a cross-compiler once we jump to developing in a higher
7 | language, that is, C. [Read why](http://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler%3F)
8 |
9 | I'll be adapting the instructions [at the OSDev wiki](http://wiki.osdev.org/GCC_Cross-Compiler).
10 |
11 |
12 | Required packages
13 | -----------------
14 |
15 | First, install the required packages. On linux, use your package distribution. On a Mac, [install brew](http://brew.sh/) if
16 | you didn't do it on lesson 00, and get those packages with `brew install`
17 |
18 | - gmp
19 | - mpfr
20 | - libmpc
21 | - gcc
22 |
23 | Yes, we will need `gcc` to build our cross-compiled `gcc`, especially on a Mac where gcc has been deprecated for `clang`
24 |
25 | Once installed, find where your packaged gcc is (remember, not clang) and export it. For example:
26 |
27 | ```
28 | export CC=/usr/local/bin/gcc-4.9
29 | export LD=/usr/local/bin/gcc-4.9
30 | ```
31 |
32 | We will need to build binutils and a cross-compiled gcc, and we will put them into `/usr/local/i386elfgcc`, so
33 | let's export some paths now. Feel free to change them to your liking.
34 |
35 | ```
36 | export PREFIX="/usr/local/i386elfgcc"
37 | export TARGET=i386-elf
38 | export PATH="$PREFIX/bin:$PATH"
39 | ```
40 |
41 | binutils
42 | --------
43 |
44 | Remember: always be careful before pasting walls of text from the internet. I recommend copying line by line.
45 |
46 | ```sh
47 | mkdir /tmp/src
48 | cd /tmp/src
49 | curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz # If the link 404's, look for a more recent version
50 | tar xf binutils-2.24.tar.gz
51 | mkdir binutils-build
52 | cd binutils-build
53 | ../binutils-2.24/configure --target=$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix=$PREFIX 2>&1 | tee configure.log
54 | make all install 2>&1 | tee make.log
55 | ```
56 |
57 | gcc
58 | ---
59 | ```sh
60 | cd /tmp/src
61 | curl -O https://ftp.gnu.org/gnu/gcc/gcc-4.9.1/gcc-4.9.1.tar.bz2
62 | tar xf gcc-4.9.1.tar.bz2
63 | mkdir gcc-build
64 | cd gcc-build
65 | ../gcc-4.9.1/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
66 | make all-gcc
67 | make all-target-libgcc
68 | make install-gcc
69 | make install-target-libgcc
70 | ```
71 |
72 | That's it! You should have all the GNU binutils and the compiler at `/usr/local/i386elfgcc/bin`, prefixed by `i386-elf-` to avoid
73 | collisions with your system's compiler and binutils.
74 |
75 | You may want to add the `$PATH` to your `.bashrc`. From now on, on this tutorial, we will explicitly use the prefixes when using
76 | the cross-compiled gcc.
77 |
--------------------------------------------------------------------------------
/12-kernel-c/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: C, object code, linker, disassemble*
2 |
3 | **Goal: Learn to write the same low-level code as we did with assembler, but in C**
4 |
5 |
6 | Compile
7 | -------
8 |
9 | Let's see how the C compiler compiles our code and compare it to the machine code
10 | generated with the assembler.
11 |
12 | We will start writing a simple program which contains a function, `function.c`.
13 | Open the file and examine it.
14 |
15 | To compile system-independent code, we need the flag `-ffreestanding`, so compile
16 | `function.c` in this fashion:
17 |
18 | `i386-elf-gcc -ffreestanding -c function.c -o function.o`
19 |
20 | Let's examine the machine code generated by the compiler:
21 |
22 | `i386-elf-objdump -d function.o`
23 |
24 | Now that is something we recognize, isn't it?
25 |
26 |
27 | Link
28 | ----
29 |
30 | Finally, to produce a binary file, we will use the linker. An important part of this
31 | step is to learn how high level languages call function labels. Which is the offset
32 | where our function will be placed in memory? We don't actually know. For this
33 | example, we'll place the offset at `0x0` and use the `binary` format which
34 | generates machine code without any labels and/or metadata
35 |
36 | `i386-elf-ld -o function.bin -Ttext 0x0 --oformat binary function.o`
37 |
38 | *Note: a warning may appear when linking, disregard it*
39 |
40 | Now examine both "binary" files, `function.o` and `function.bin` using `xxd`. You
41 | will see that the `.bin` file is machine code, while the `.o` file has a lot
42 | of debugging information, labels, etc.
43 |
44 |
45 | Decompile
46 | ---------
47 |
48 | As a curiosity, we will examine the machine code.
49 |
50 | `ndisasm -b 32 function.bin`
51 |
52 |
53 | More
54 | ----
55 |
56 | I encourage you to write more small programs, which feature:
57 |
58 | - Local variables `localvars.c`
59 | - Function calls `functioncalls.c`
60 | - Pointers `pointers.c`
61 |
62 | Then compile and disassemble them, and examine the resulting machine code. Follow
63 | the os-guide.pdf for explanations. Try to answer this question: why does the
64 | disassemblement of `pointers.c` not resemble what you would expect? Where is
65 | the ASCII `0x48656c6c6f` for "Hello"?
66 |
--------------------------------------------------------------------------------
/12-kernel-c/function.c:
--------------------------------------------------------------------------------
1 | int my_function() {
2 | return 0xbaba;
3 | }
4 |
--------------------------------------------------------------------------------
/12-kernel-c/functioncalls.c:
--------------------------------------------------------------------------------
1 | void caller() {
2 | my_func(0xdede);
3 | }
4 |
5 | int my_func(int arg) {
6 | return arg;
7 | }
8 |
--------------------------------------------------------------------------------
/12-kernel-c/localvars.c:
--------------------------------------------------------------------------------
1 | int my_function() {
2 | int my_var = 0xbaba;
3 | return my_var;
4 | }
5 |
--------------------------------------------------------------------------------
/12-kernel-c/pointers.c:
--------------------------------------------------------------------------------
1 | void func() {
2 | char* string = "Hello";
3 | }
4 |
--------------------------------------------------------------------------------
/13-kernel-barebones/Makefile:
--------------------------------------------------------------------------------
1 | # $@ = target file
2 | # $< = first dependency
3 | # $^ = all dependencies
4 |
5 | # First rule is the one executed when no parameters are fed to the Makefile
6 | all: run
7 |
8 | # Notice how dependencies are built as needed
9 | kernel.bin: kernel_entry.o kernel.o
10 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
11 |
12 | kernel_entry.o: kernel_entry.asm
13 | nasm $< -f elf -o $@
14 |
15 | kernel.o: kernel.c
16 | i386-elf-gcc -ffreestanding -c $< -o $@
17 |
18 | # Rule to disassemble the kernel - may be useful to debug
19 | kernel.dis: kernel.bin
20 | ndisasm -b 32 $< > $@
21 |
22 | bootsect.bin: bootsect.asm
23 | nasm $< -f bin -o $@
24 |
25 | os-image.bin: bootsect.bin kernel.bin
26 | cat $^ > $@
27 |
28 | run: os-image.bin
29 | qemu-system-i386 -fda $<
30 |
31 | clean:
32 | rm *.bin *.o *.dis
33 |
--------------------------------------------------------------------------------
/13-kernel-barebones/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: kernel, ELF format, makefile*
2 |
3 | **Goal: Create a simple kernel and a bootsector capable of booting it**
4 |
5 | The kernel
6 | ----------
7 |
8 | Our C kernel will just print an 'X' on the top left corner of the screen. Go ahead
9 | and open `kernel.c`.
10 |
11 | You will notice a dummy function that does nothing. That function will force us
12 | to create a kernel entry routine which does not point to byte 0x0 in our kernel, but
13 | to an actual label which we know that launches it. In our case, function `main()`.
14 |
15 | `i386-elf-gcc -ffreestanding -c kernel.c -o kernel.o`
16 |
17 | That routine is coded on `kernel_entry.asm`. Read it and you will learn how to
18 | use `[extern]` declarations in assembly. To compile this file, instead of generating
19 | a binary, we will generate an `elf` format file which will be linked with `kernel.o`
20 |
21 | `nasm kernel_entry.asm -f elf -o kernel_entry.o`
22 |
23 |
24 | The linker
25 | ----------
26 |
27 | A linker is a very powerful tool and we only started to benefit from it.
28 |
29 | To link both object files into a single binary kernel and resolve label references,
30 | run:
31 |
32 | `i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary`
33 |
34 | Notice how our kernel will be placed not at `0x0` in memory, but at `0x1000`. The
35 | bootsector will need to know this address too.
36 |
37 |
38 | The bootsector
39 | --------------
40 |
41 | It is very similar to the one in lesson 10. Open `bootsect.asm` and examine the code.
42 | Actually, if you remove all the lines used to print messages on the screen, it accounts
43 | to a couple dozen lines.
44 |
45 | Compile it with `nasm bootsect.asm -f bin -o bootsect.bin`
46 |
47 |
48 | Putting it all together
49 | -----------------------
50 |
51 | Now what? We have two separate files for the bootsector and the kernel?
52 |
53 | Can't we just "link" them together into a single file? Yes, we can, and it's easy,
54 | just concatenate them:
55 |
56 | `cat bootsect.bin kernel.bin > os-image.bin`
57 |
58 |
59 | Run!
60 | ----
61 |
62 | You can now run `os-image.bin` with qemu.
63 |
64 | Remember that if you find disk load errors you may need to play with the disk numbers
65 | or qemu parameters (floppy = `0x0`, hdd = `0x80`). I usually use `qemu-system-i386 -fda os-image.bin`
66 |
67 | You will see four messages:
68 |
69 | - "Started in 16-bit Real Mode"
70 | - "Loading kernel into memory"
71 | - (Top left) "Landed in 32-bit Protected Mode"
72 | - (Top left, overwriting previous message) "X"
73 |
74 | Congratulations!
75 |
76 |
77 | Makefile
78 | --------
79 |
80 | As a last step, we will tidy up the compilation process with a Makefile. Open the `Makefile`
81 | script and examine its contents. If you don't know what a Makefile is, now is a good time
82 | to Google and learn it, as this will save us a lot of time in the future.
83 |
--------------------------------------------------------------------------------
/13-kernel-barebones/bootsect.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00]
2 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
3 |
4 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
5 | mov bp, 0x9000
6 | mov sp, bp
7 |
8 | mov bx, MSG_REAL_MODE
9 | call print
10 | call print_nl
11 |
12 | call load_kernel ; read the kernel from disk
13 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
14 | jmp $ ; Never executed
15 |
16 | %include "../05-bootsector-functions-strings/boot_sect_print.asm"
17 | %include "../05-bootsector-functions-strings/boot_sect_print_hex.asm"
18 | %include "../07-bootsector-disk/boot_sect_disk.asm"
19 | %include "../09-32bit-gdt/32bit-gdt.asm"
20 | %include "../08-32bit-print/32bit-print.asm"
21 | %include "../10-32bit-enter/32bit-switch.asm"
22 |
23 | [bits 16]
24 | load_kernel:
25 | mov bx, MSG_LOAD_KERNEL
26 | call print
27 | call print_nl
28 |
29 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
30 | mov dh, 2
31 | mov dl, [BOOT_DRIVE]
32 | call disk_load
33 | ret
34 |
35 | [bits 32]
36 | BEGIN_PM:
37 | mov ebx, MSG_PROT_MODE
38 | call print_string_pm
39 | call KERNEL_OFFSET ; Give control to the kernel
40 | jmp $ ; Stay here when the kernel returns control to us (if ever)
41 |
42 |
43 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
44 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
45 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
46 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
47 |
48 | ; padding
49 | times 510 - ($-$$) db 0
50 | dw 0xaa55
51 |
--------------------------------------------------------------------------------
/13-kernel-barebones/kernel.c:
--------------------------------------------------------------------------------
1 | /* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */
2 | void dummy_test_entrypoint() {
3 | }
4 |
5 | void main() {
6 | char* video_memory = (char*) 0xb8000;
7 | *video_memory = 'X';
8 | }
9 |
--------------------------------------------------------------------------------
/13-kernel-barebones/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/14-checkpoint/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
47 |
--------------------------------------------------------------------------------
/14-checkpoint/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: monolithic kernel, microkernel, debugger, gdb*
2 |
3 | **Goal: Pause and organize our code a little bit. Then learn how to debug the kernel with gdb**
4 |
5 | Maybe you didn't realize it, but you already have your own kernel
6 | running!
7 |
8 | However, it does very little, just print an 'X'. Now is the time to stop for
9 | a moment and organize the code into folders, create a scalable Makefile for future code,
10 | and think on a strategy.
11 |
12 | Take a look at the new folder structure. Most of the files have been symlinked
13 | from previous lessons, so if we have to change them at some point, it will be
14 | a better idea to remove the symlink and create a new file.
15 |
16 | Furthermore, since from now on we will use mostly C to code, we'll take advantage of qemu's
17 | ability to open a connection to gdb. First, let's install a cross-compiled `gdb` since
18 | OSX uses `lldb` which is not compatible with the ELF file format (neither is the `gdb` available
19 | on Homebrew's repos)
20 |
21 | ```sh
22 | cd /tmp/src
23 | curl -O http://ftp.rediris.es/mirror/GNU/gdb/gdb-7.8.tar.gz
24 | tar xf gdb-7.8.tar.gz
25 | mkdir gdb-build
26 | cd gdb-build
27 | export PREFIX="/usr/local/i386elfgcc"
28 | export TARGET=i386-elf
29 | ../gdb-7.8/configure --target="$TARGET" --prefix="$PREFIX" --program-prefix=i386-elf-
30 | make
31 | make install
32 | ```
33 |
34 | Check out the Makefile target `make debug`. This target uses builds `kernel.elf`, which
35 | is an object file (not binary) with all the symbols we generated on the kernel, thanks to
36 | the `-g` flag on gcc. Please examine it with `xxd` and you'll see some strings. Actually,
37 | the correct way to examine the strings in an object file is by `strings kernel.elf`
38 |
39 | We can take advantage of this cool qemu feature. Type `make debug` and, on the gdb shell:
40 |
41 | - Set up a breakpoint in `kernel.c:main()`: `b main`
42 | - Run the OS: `continue`
43 | - Run two steps into the code: `next` then `next`. You will see that we are just about to set
44 | the 'X' on the screen, but it isn't there yet (check out the qemu screen)
45 | - Let's see what's in the video memory: `print *video_memory`. There is the 'L' from "Landed in
46 | 32-bit Protected Mode"
47 | - Hmmm, let's make sure that `video_memory` points to the correct address: `print video_memory`
48 | - `next` to put there our 'X'
49 | - Let's make sure: `print *video_memory` and look at the qemu screen. It's definitely there.
50 |
51 | Now is a good time to read some tutorial on `gdb` and learn super useful things like `info registers`
52 | which will save us a lot of time in the future!
53 |
54 |
55 | You may notice that, since this is a tutorial, we haven't yet discussed which kind
56 | of kernel we will write. It will probably be a monolithic one since they are easier
57 | to design and implement, and after all this is our first OS. Maybe in the future
58 | we'll add a lesson "15-b" with a microkernel design. Who knows.
59 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 16 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 |
49 | ; padding
50 | times 510 - ($-$$) db 0
51 | dw 0xaa55
52 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/14-checkpoint/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/14-checkpoint/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | /* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */
2 | void dummy_test_entrypoint() {
3 | }
4 |
5 | void main() {
6 | char* video_memory = (char*) 0xb8000;
7 | *video_memory = 'X';
8 | }
9 |
--------------------------------------------------------------------------------
/15-video-ports/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
47 |
--------------------------------------------------------------------------------
/15-video-ports/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: I/O ports*
2 |
3 | **Goal: Learn how to use the VGA card data ports**
4 |
5 | We will use C to communicate with devices via I/O registers and ports.
6 |
7 | Open `drivers/ports.c` and examine the inline C assembler syntax. It has
8 | some differences, like the order of the source and destination operands,
9 | and the funny syntax to assign variables to operands.
10 |
11 | When you understand the concepts, open `kernel/kernel.c` for an example
12 | of use.
13 |
14 | In this example we will examine the I/O ports which map the screen cursor
15 | position. Specifically, we will query port `0x3d4` with value `14` to request
16 | the cursor position high byte, and the same port with `15` for the low byte.
17 |
18 | When this port is queried, it saves the result in port `0x3d5`
19 |
20 | Don't miss the opportunity to use `gdb` to inspect the value of C variables,
21 | since we still can't print them on the screen. To do so, set a breakpoint
22 | for a specific line, `breakpoint kernel.c:21` and use the `print` command
23 | to examine variables. Aren't you glad now that we invested some time in
24 | compiling the cross-compiled gdb? ;)
25 |
26 | Finally, we will use the queried cursor position to write a character
27 | at that location.
28 |
--------------------------------------------------------------------------------
/15-video-ports/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/15-video-ports/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 16 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 |
49 | ; padding
50 | times 510 - ($-$$) db 0
51 | dw 0xaa55
52 |
--------------------------------------------------------------------------------
/15-video-ports/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/15-video-ports/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/15-video-ports/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/15-video-ports/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/15-video-ports/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/15-video-ports/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/15-video-ports/drivers/ports.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Read a byte from the specified port
3 | */
4 | unsigned char port_byte_in (unsigned short port) {
5 | unsigned char result;
6 | /* Inline assembler syntax
7 | * !! Notice how the source and destination registers are switched from NASM !!
8 | *
9 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
10 | * '"d" (port)': map the C variable '(port)' into e'd'x register
11 | *
12 | * Inputs and outputs are separated by colons
13 | */
14 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
15 | return result;
16 | }
17 |
18 | void port_byte_out (unsigned short port, unsigned char data) {
19 | /* Notice how here both registers are mapped to C variables and
20 | * nothing is returned, thus, no equals '=' in the asm syntax
21 | * However we see a comma since there are two variables in the input area
22 | * and none in the 'return' area
23 | */
24 | __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
25 | }
26 |
27 | unsigned short port_word_in (unsigned short port) {
28 | unsigned short result;
29 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
30 | return result;
31 | }
32 |
33 | void port_word_out (unsigned short port, unsigned short data) {
34 | __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
35 | }
36 |
--------------------------------------------------------------------------------
/15-video-ports/drivers/ports.h:
--------------------------------------------------------------------------------
1 | unsigned char port_byte_in (unsigned short port);
2 | void port_byte_out (unsigned short port, unsigned char data);
3 | unsigned short port_word_in (unsigned short port);
4 | void port_word_out (unsigned short port, unsigned short data);
5 |
--------------------------------------------------------------------------------
/15-video-ports/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../drivers/ports.h"
2 |
3 | void main() {
4 | /* Screen cursor position: ask VGA control register (0x3d4) for bytes
5 | * 14 = high byte of cursor and 15 = low byte of cursor. */
6 | port_byte_out(0x3d4, 14); /* Requesting byte 14: high byte of cursor pos */
7 | /* Data is returned in VGA data register (0x3d5) */
8 | int position = port_byte_in(0x3d5);
9 | position = position << 8; /* high byte */
10 |
11 | port_byte_out(0x3d4, 15); /* requesting low byte */
12 | position += port_byte_in(0x3d5);
13 |
14 | /* VGA 'cells' consist of the character and its control data
15 | * e.g. 'white on black background', 'red text on white bg', etc */
16 | int offset_from_vga = position * 2;
17 |
18 | /* Now you can examine both variables using gdb, since we still
19 | * don't know how to print strings on screen. Run 'make debug' and
20 | * on the gdb console:
21 | * breakpoint kernel.c:21
22 | * continue
23 | * print position
24 | * print offset_from_vga
25 | */
26 |
27 | /* Let's write on the current cursor position, we already know how
28 | * to do that */
29 | char *vga = 0xb8000;
30 | vga[offset_from_vga] = 'X';
31 | vga[offset_from_vga+1] = 0x0f; /* White text on black background */
32 | }
33 |
--------------------------------------------------------------------------------
/16-video-driver/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
47 |
--------------------------------------------------------------------------------
/16-video-driver/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: VGA character cells, screen offset*
2 |
3 | **Goal: Write strings on the screen**
4 |
5 | Finally, we are going to be able to output text on the screen. This lesson contains
6 | a bit more code than usual, so let's go step by step.
7 |
8 | Open `drivers/screen.h` and you'll see that we have defined some constants for the VGA
9 | card driver and three public functions, one to clear the screen and another couple
10 | to write strings, the famously named `kprint` for "kernel print"
11 |
12 | Now open `drivers/screen.c`. It starts with the declaration of private helper functions
13 | that we will use to aid our `kprint` kernel API.
14 |
15 | There are the two I/O port access routines that we learned in the previous lesson,
16 | `get` and `set_cursor_offset()`.
17 |
18 | Then there is the routine that directly manipulates the video memory, `print_char()`
19 |
20 | Finally, there are three small helper functions to transform rows and columns into offsets
21 | and vice versa.
22 |
23 |
24 | kprint_at
25 | ---------
26 |
27 | `kprint_at` may be called with a `-1` value for `col` and `row`, which indicates that
28 | we will print the string at the current cursor position.
29 |
30 | It first sets three variables for the col/row and the offset. Then it iterates through
31 | the `char*` and calls `print_char()` with the current coordinates.
32 |
33 | Note that `print_char` itself returns the offset of the next cursor position, and we reuse
34 | it for the next loop.
35 |
36 | `kprint` is basically a wrapper for `kprint_at`
37 |
38 |
39 |
40 | print_char
41 | ----------
42 |
43 | Like `kprint_at`, `print_char` allows cols/rows to be `-1`. In that case it retrieves
44 | the cursor position from the hardware, using the `ports.c` routines.
45 |
46 | `print_char` also handles newlines. In that case, we will position the cursor offset
47 | to column 0 of the next row.
48 |
49 | Remember that the VGA cells take two bytes, one for the character itself and another one
50 | for the attribute.
51 |
52 |
53 | kernel.c
54 | --------
55 |
56 | Our new kernel is finally able to print strings.
57 |
58 | It tests correct character positioning, spanning through multiple lines, line breaks,
59 | and finally it tries to write outside of the screen bounds. What happens then?
60 |
61 | In the next lesson we will learn how to scroll the screen.
62 |
--------------------------------------------------------------------------------
/16-video-driver/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/16-video-driver/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 16 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 |
49 | ; padding
50 | times 510 - ($-$$) db 0
51 | dw 0xaa55
52 |
--------------------------------------------------------------------------------
/16-video-driver/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/16-video-driver/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/16-video-driver/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/16-video-driver/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/16-video-driver/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/16-video-driver/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/16-video-driver/drivers/ports.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Read a byte from the specified port
3 | */
4 | unsigned char port_byte_in (unsigned short port) {
5 | unsigned char result;
6 | /* Inline assembler syntax
7 | * !! Notice how the source and destination registers are switched from NASM !!
8 | *
9 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
10 | * '"d" (port)': map the C variable '(port)' into e'd'x register
11 | *
12 | * Inputs and outputs are separated by colons
13 | */
14 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
15 | return result;
16 | }
17 |
18 | void port_byte_out (unsigned short port, unsigned char data) {
19 | /* Notice how here both registers are mapped to C variables and
20 | * nothing is returned, thus, no equals '=' in the asm syntax
21 | * However we see a comma since there are two variables in the input area
22 | * and none in the 'return' area
23 | */
24 | __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
25 | }
26 |
27 | unsigned short port_word_in (unsigned short port) {
28 | unsigned short result;
29 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
30 | return result;
31 | }
32 |
33 | void port_word_out (unsigned short port, unsigned short data) {
34 | __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
35 | }
36 |
--------------------------------------------------------------------------------
/16-video-driver/drivers/ports.h:
--------------------------------------------------------------------------------
1 | unsigned char port_byte_in (unsigned short port);
2 | void port_byte_out (unsigned short port, unsigned char data);
3 | unsigned short port_word_in (unsigned short port);
4 | void port_word_out (unsigned short port, unsigned short data);
5 |
--------------------------------------------------------------------------------
/16-video-driver/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #define VIDEO_ADDRESS 0xb8000
2 | #define MAX_ROWS 25
3 | #define MAX_COLS 80
4 | #define WHITE_ON_BLACK 0x0f
5 | #define RED_ON_WHITE 0xf4
6 |
7 | /* Screen i/o ports */
8 | #define REG_SCREEN_CTRL 0x3d4
9 | #define REG_SCREEN_DATA 0x3d5
10 |
11 | /* Public kernel API */
12 | void clear_screen();
13 | void kprint_at(char *message, int col, int row);
14 | void kprint(char *message);
15 |
--------------------------------------------------------------------------------
/16-video-driver/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../drivers/screen.h"
2 |
3 | void main() {
4 | clear_screen();
5 | kprint_at("X", 1, 6);
6 | kprint_at("This text spans multiple lines", 75, 10);
7 | kprint_at("There is a line\nbreak", 0, 20);
8 | kprint("There is a line\nbreak");
9 | kprint_at("What happens when we run out of space?", 45, 24);
10 | }
11 |
--------------------------------------------------------------------------------
/17-video-scroll/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
47 |
--------------------------------------------------------------------------------
/17-video-scroll/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: scroll*
2 |
3 | **Goal: Scroll the screen when the text reaches the bottom**
4 |
5 | For this short lesson, open `drivers/screen.c` and note that at the
6 | bottom of `print_char` there is a new section (line 84) which checks
7 | if the current offset is over the screen size and scrolls the text.
8 |
9 | The actual scrolling is handled by a new function, `memory_copy`. It is
10 | a simpler version of the standard `memcpy` but we named it differently
11 | to avoid namespace collisions, at least for now. Open `kernel/util.c` to
12 | see its implementation.
13 |
14 | To help visualize scrolling, we will also implement a function to
15 | convert integers to text, `int_to_ascii`. Again, it is a quick implementation
16 | of the standard `itoa`. Notice that for integers which have double digits
17 | or more, they are printed in reverse. This is intended. On future lessons
18 | we will extend our helper functions, but that is not the point for now.
19 |
20 | Finally, open `kernel/kernel.c`. Initially, each line displays its line
21 | number. You can set a breakpoint on line 14 to confirm this. Then,
22 | the following `kprint`s force the kernel to scroll down.
23 |
24 | This lesson ends the coverage for the os-dev.pdf document. From now on, we'll
25 | follow [the OSDev wiki](http://wiki.osdev.org/Meaty_Skeleton) and
26 | other sources and examples. Thanks Prof. Blundell for that great document!
27 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 16 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 |
49 | ; padding
50 | times 510 - ($-$$) db 0
51 | dw 0xaa55
52 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/17-video-scroll/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/17-video-scroll/drivers/ports.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Read a byte from the specified port
3 | */
4 | unsigned char port_byte_in (unsigned short port) {
5 | unsigned char result;
6 | /* Inline assembler syntax
7 | * !! Notice how the source and destination registers are switched from NASM !!
8 | *
9 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
10 | * '"d" (port)': map the C variable '(port)' into e'd'x register
11 | *
12 | * Inputs and outputs are separated by colons
13 | */
14 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
15 | return result;
16 | }
17 |
18 | void port_byte_out (unsigned short port, unsigned char data) {
19 | /* Notice how here both registers are mapped to C variables and
20 | * nothing is returned, thus, no equals '=' in the asm syntax
21 | * However we see a comma since there are two variables in the input area
22 | * and none in the 'return' area
23 | */
24 | __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
25 | }
26 |
27 | unsigned short port_word_in (unsigned short port) {
28 | unsigned short result;
29 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
30 | return result;
31 | }
32 |
33 | void port_word_out (unsigned short port, unsigned short data) {
34 | __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
35 | }
36 |
--------------------------------------------------------------------------------
/17-video-scroll/drivers/ports.h:
--------------------------------------------------------------------------------
1 | unsigned char port_byte_in (unsigned short port);
2 | void port_byte_out (unsigned short port, unsigned char data);
3 | unsigned short port_word_in (unsigned short port);
4 | void port_word_out (unsigned short port, unsigned short data);
5 |
--------------------------------------------------------------------------------
/17-video-scroll/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #define VIDEO_ADDRESS 0xb8000
2 | #define MAX_ROWS 25
3 | #define MAX_COLS 80
4 | #define WHITE_ON_BLACK 0x0f
5 | #define RED_ON_WHITE 0xf4
6 |
7 | /* Screen i/o ports */
8 | #define REG_SCREEN_CTRL 0x3d4
9 | #define REG_SCREEN_DATA 0x3d5
10 |
11 | /* Public kernel API */
12 | void clear_screen();
13 | void kprint_at(char *message, int col, int row);
14 | void kprint(char *message);
15 |
--------------------------------------------------------------------------------
/17-video-scroll/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../drivers/screen.h"
2 | #include "util.h"
3 |
4 | void main() {
5 | clear_screen();
6 |
7 | /* Fill up the screen */
8 | int i = 0;
9 | for (i = 0; i < 24; i++) {
10 | char str[255];
11 | int_to_ascii(i, str);
12 | kprint_at(str, 0, i);
13 | }
14 |
15 | kprint_at("This text forces the kernel to scroll. Row 0 will disappear. ", 60, 24);
16 | kprint("And with this text, the kernel will scroll again, and row 1 will disappear too!");
17 | }
18 |
--------------------------------------------------------------------------------
/17-video-scroll/kernel/util.c:
--------------------------------------------------------------------------------
1 | void memory_copy(char *source, char *dest, int nbytes) {
2 | int i;
3 | for (i = 0; i < nbytes; i++) {
4 | *(dest + i) = *(source + i);
5 | }
6 | }
7 |
8 | /**
9 | * K&R implementation
10 | */
11 | void int_to_ascii(int n, char str[]) {
12 | int i, sign;
13 | if ((sign = n) < 0) n = -n;
14 | i = 0;
15 | do {
16 | str[i++] = n % 10 + '0';
17 | } while ((n /= 10) > 0);
18 |
19 | if (sign < 0) str[i++] = '-';
20 | str[i] = '\0';
21 |
22 | /* TODO: implement "reverse" */
23 | }
24 |
--------------------------------------------------------------------------------
/17-video-scroll/kernel/util.h:
--------------------------------------------------------------------------------
1 | void memory_copy(char *source, char *dest, int nbytes);
2 | void int_to_ascii(int n, char str[]);
3 |
--------------------------------------------------------------------------------
/18-interrupts/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
47 |
--------------------------------------------------------------------------------
/18-interrupts/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/18-interrupts/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 31 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 | MSG_RETURNED_KERNEL db "Returned from kernel. Error?", 0
49 |
50 | ; padding
51 | times 510 - ($-$$) db 0
52 | dw 0xaa55
53 |
--------------------------------------------------------------------------------
/18-interrupts/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/18-interrupts/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/18-interrupts/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/18-interrupts/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/18-interrupts/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/18-interrupts/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/18-interrupts/cpu/idt.c:
--------------------------------------------------------------------------------
1 | #include "idt.h"
2 | #include "../kernel/util.h"
3 |
4 | void set_idt_gate(int n, u32 handler) {
5 | idt[n].low_offset = low_16(handler);
6 | idt[n].sel = KERNEL_CS;
7 | idt[n].always0 = 0;
8 | idt[n].flags = 0x8E;
9 | idt[n].high_offset = high_16(handler);
10 | }
11 |
12 | void set_idt() {
13 | idt_reg.base = (u32) &idt;
14 | idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
15 | /* Don't make the mistake of loading &idt -- always load &idt_reg */
16 | __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
17 | }
18 |
--------------------------------------------------------------------------------
/18-interrupts/cpu/idt.h:
--------------------------------------------------------------------------------
1 | #ifndef IDT_H
2 | #define IDT_H
3 |
4 | #include "types.h"
5 |
6 | /* Segment selectors */
7 | #define KERNEL_CS 0x08
8 |
9 | /* How every interrupt gate (handler) is defined */
10 | typedef struct {
11 | u16 low_offset; /* Lower 16 bits of handler function address */
12 | u16 sel; /* Kernel segment selector */
13 | u8 always0;
14 | /* First byte
15 | * Bit 7: "Interrupt is present"
16 | * Bits 6-5: Privilege level of caller (0=kernel..3=user)
17 | * Bit 4: Set to 0 for interrupt gates
18 | * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19 | u8 flags;
20 | u16 high_offset; /* Higher 16 bits of handler function address */
21 | } __attribute__((packed)) idt_gate_t ;
22 |
23 | /* A pointer to the array of interrupt handlers.
24 | * Assembly instruction 'lidt' will read it */
25 | typedef struct {
26 | u16 limit;
27 | u32 base;
28 | } __attribute__((packed)) idt_register_t;
29 |
30 | #define IDT_ENTRIES 256
31 | idt_gate_t idt[IDT_ENTRIES];
32 | idt_register_t idt_reg;
33 |
34 |
35 | /* Functions implemented in idt.c */
36 | void set_idt_gate(int n, u32 handler);
37 | void set_idt();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/18-interrupts/cpu/isr.c:
--------------------------------------------------------------------------------
1 | #include "isr.h"
2 | #include "idt.h"
3 | #include "../drivers/screen.h"
4 | #include "../kernel/util.h"
5 |
6 | /* Can't do this with a loop because we need the address
7 | * of the function names */
8 | void isr_install() {
9 | set_idt_gate(0, (u32)isr0);
10 | set_idt_gate(1, (u32)isr1);
11 | set_idt_gate(2, (u32)isr2);
12 | set_idt_gate(3, (u32)isr3);
13 | set_idt_gate(4, (u32)isr4);
14 | set_idt_gate(5, (u32)isr5);
15 | set_idt_gate(6, (u32)isr6);
16 | set_idt_gate(7, (u32)isr7);
17 | set_idt_gate(8, (u32)isr8);
18 | set_idt_gate(9, (u32)isr9);
19 | set_idt_gate(10, (u32)isr10);
20 | set_idt_gate(11, (u32)isr11);
21 | set_idt_gate(12, (u32)isr12);
22 | set_idt_gate(13, (u32)isr13);
23 | set_idt_gate(14, (u32)isr14);
24 | set_idt_gate(15, (u32)isr15);
25 | set_idt_gate(16, (u32)isr16);
26 | set_idt_gate(17, (u32)isr17);
27 | set_idt_gate(18, (u32)isr18);
28 | set_idt_gate(19, (u32)isr19);
29 | set_idt_gate(20, (u32)isr20);
30 | set_idt_gate(21, (u32)isr21);
31 | set_idt_gate(22, (u32)isr22);
32 | set_idt_gate(23, (u32)isr23);
33 | set_idt_gate(24, (u32)isr24);
34 | set_idt_gate(25, (u32)isr25);
35 | set_idt_gate(26, (u32)isr26);
36 | set_idt_gate(27, (u32)isr27);
37 | set_idt_gate(28, (u32)isr28);
38 | set_idt_gate(29, (u32)isr29);
39 | set_idt_gate(30, (u32)isr30);
40 | set_idt_gate(31, (u32)isr31);
41 |
42 | set_idt(); // Load with ASM
43 | }
44 |
45 | /* To print the message which defines every exception */
46 | char *exception_messages[] = {
47 | "Division By Zero",
48 | "Debug",
49 | "Non Maskable Interrupt",
50 | "Breakpoint",
51 | "Into Detected Overflow",
52 | "Out of Bounds",
53 | "Invalid Opcode",
54 | "No Coprocessor",
55 |
56 | "Double Fault",
57 | "Coprocessor Segment Overrun",
58 | "Bad TSS",
59 | "Segment Not Present",
60 | "Stack Fault",
61 | "General Protection Fault",
62 | "Page Fault",
63 | "Unknown Interrupt",
64 |
65 | "Coprocessor Fault",
66 | "Alignment Check",
67 | "Machine Check",
68 | "Reserved",
69 | "Reserved",
70 | "Reserved",
71 | "Reserved",
72 | "Reserved",
73 |
74 | "Reserved",
75 | "Reserved",
76 | "Reserved",
77 | "Reserved",
78 | "Reserved",
79 | "Reserved",
80 | "Reserved",
81 | "Reserved"
82 | };
83 |
84 | void isr_handler(registers_t r) {
85 | kprint("received interrupt: ");
86 | char s[3];
87 | int_to_ascii(r.int_no, s);
88 | kprint(s);
89 | kprint("\n");
90 | kprint(exception_messages[r.int_no]);
91 | kprint("\n");
92 | }
93 |
--------------------------------------------------------------------------------
/18-interrupts/cpu/isr.h:
--------------------------------------------------------------------------------
1 | #ifndef ISR_H
2 | #define ISR_H
3 |
4 | #include "types.h"
5 |
6 | /* ISRs reserved for CPU exceptions */
7 | extern void isr0();
8 | extern void isr1();
9 | extern void isr2();
10 | extern void isr3();
11 | extern void isr4();
12 | extern void isr5();
13 | extern void isr6();
14 | extern void isr7();
15 | extern void isr8();
16 | extern void isr9();
17 | extern void isr10();
18 | extern void isr11();
19 | extern void isr12();
20 | extern void isr13();
21 | extern void isr14();
22 | extern void isr15();
23 | extern void isr16();
24 | extern void isr17();
25 | extern void isr18();
26 | extern void isr19();
27 | extern void isr20();
28 | extern void isr21();
29 | extern void isr22();
30 | extern void isr23();
31 | extern void isr24();
32 | extern void isr25();
33 | extern void isr26();
34 | extern void isr27();
35 | extern void isr28();
36 | extern void isr29();
37 | extern void isr30();
38 | extern void isr31();
39 |
40 | /* Struct which aggregates many registers */
41 | typedef struct {
42 | u32 ds; /* Data segment selector */
43 | u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
44 | u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
45 | u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
46 | } registers_t;
47 |
48 | void isr_install();
49 | void isr_handler(registers_t r);
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/18-interrupts/cpu/types.h:
--------------------------------------------------------------------------------
1 | #ifndef TYPES_H
2 | #define TYPES_H
3 |
4 | /* Instead of using 'chars' to allocate non-character bytes,
5 | * we will use these new type with no semantic meaning */
6 | typedef unsigned int u32;
7 | typedef int s32;
8 | typedef unsigned short u16;
9 | typedef short s16;
10 | typedef unsigned char u8;
11 | typedef char s8;
12 |
13 | #define low_16(address) (u16)((address) & 0xFFFF)
14 | #define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/18-interrupts/drivers/ports.c:
--------------------------------------------------------------------------------
1 | #include "ports.h"
2 |
3 | /**
4 | * Read a byte from the specified port
5 | */
6 | u8 port_byte_in (u16 port) {
7 | u8 result;
8 | /* Inline assembler syntax
9 | * !! Notice how the source and destination registers are switched from NASM !!
10 | *
11 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
12 | * '"d" (port)': map the C variable '(port)' into e'd'x register
13 | *
14 | * Inputs and outputs are separated by colons
15 | */
16 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
17 | return result;
18 | }
19 |
20 | void port_byte_out (u16 port, u8 data) {
21 | /* Notice how here both registers are mapped to C variables and
22 | * nothing is returned, thus, no equals '=' in the asm syntax
23 | * However we see a comma since there are two variables in the input area
24 | * and none in the 'return' area
25 | */
26 | __asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
27 | }
28 |
29 | u16 port_word_in (u16 port) {
30 | u16 result;
31 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
32 | return result;
33 | }
34 |
35 | void port_word_out (u16 port, u16 data) {
36 | __asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
37 | }
38 |
--------------------------------------------------------------------------------
/18-interrupts/drivers/ports.h:
--------------------------------------------------------------------------------
1 | #ifndef PORTS_H
2 | #define PORTS_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | unsigned char port_byte_in (u16 port);
7 | void port_byte_out (u16 port, u8 data);
8 | unsigned short port_word_in (u16 port);
9 | void port_word_out (u16 port, u16 data);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/18-interrupts/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #define VIDEO_ADDRESS 0xb8000
5 | #define MAX_ROWS 25
6 | #define MAX_COLS 80
7 | #define WHITE_ON_BLACK 0x0f
8 | #define RED_ON_WHITE 0xf4
9 |
10 | /* Screen i/o ports */
11 | #define REG_SCREEN_CTRL 0x3d4
12 | #define REG_SCREEN_DATA 0x3d5
13 |
14 | /* Public kernel API */
15 | void clear_screen();
16 | void kprint_at(char *message, int col, int row);
17 | void kprint(char *message);
18 |
19 | #endif
20 |
--------------------------------------------------------------------------------
/18-interrupts/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../drivers/screen.h"
2 | #include "util.h"
3 | #include "../cpu/isr.h"
4 | #include "../cpu/idt.h"
5 |
6 | void main() {
7 | isr_install();
8 | /* Test the interrupts */
9 | __asm__ __volatile__("int $2");
10 | __asm__ __volatile__("int $3");
11 | }
12 |
--------------------------------------------------------------------------------
/18-interrupts/kernel/util.c:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | void memory_copy(char *source, char *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(u8 *dest, u8 val, u32 len) {
11 | u8 *temp = (u8 *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
15 | /**
16 | * K&R implementation
17 | */
18 | void int_to_ascii(int n, char str[]) {
19 | int i, sign;
20 | if ((sign = n) < 0) n = -n;
21 | i = 0;
22 | do {
23 | str[i++] = n % 10 + '0';
24 | } while ((n /= 10) > 0);
25 |
26 | if (sign < 0) str[i++] = '-';
27 | str[i] = '\0';
28 |
29 | /* TODO: implement "reverse" */
30 | }
31 |
--------------------------------------------------------------------------------
/18-interrupts/kernel/util.h:
--------------------------------------------------------------------------------
1 | #ifndef UTIL_H
2 | #define UTIL_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | void memory_copy(char *source, char *dest, int nbytes);
7 | void memory_set(u8 *dest, u8 val, u32 len);
8 | void int_to_ascii(int n, char str[]);
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
47 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: IRQs, PIC, polling*
2 |
3 | **Goal: Finish the interrupts implementation and CPU timer**
4 |
5 | When the CPU boots, the PIC maps IRQs 0-7 to INT 0x8-0xF
6 | and IRQs 8-15 to INT 0x70-0x77. This conflicts with the ISRs
7 | we programmed last lesson. Since we programmed ISRs 0-31,
8 | it is standard to remap the IRQs to ISRs 32-47.
9 |
10 | The PICs are communicated with via I/O ports (see lesson 15).
11 | The Master PIC has command 0x20 and data 0x21, while the slave has
12 | command 0xA0 and data 0xA1.
13 |
14 | The code for remapping the PICs is weird and includes
15 | some masks, so check
16 | [this article](http://www.osdev.org/wiki/PIC) if you're curious.
17 | Otherwise, just look at `cpu/isr.c`, new code after we set the IDT
18 | gates for the ISRs. After that, we add the IDT gates for IRQs.
19 |
20 | Now we jump to assembler, at `interrupt.asm`. The first task is to
21 | add global definitions for the IRQ symbols we just used in the C code.
22 | Look at the end of the `global` statements.
23 |
24 | Then, add the IRQ handlers. Same `interrupt.asm`, at the bottom. Notice
25 | how they jump to a new common stub: `irq_common_stub` (next step)
26 |
27 | We then create this `irq_common_stub` which is very similar to the ISR one.
28 | It is located at the top of `interrupt.asm`, and it also defines
29 | a new `[extern irq_handler]`
30 |
31 | Now back to C code, to write the `irq_handler()` in `isr.c`. It sends some
32 | EOIs to the PICs and calls the appropriate handler, which is stored in an array
33 | named `interrupt_handlers` and defined at the top of the file. The new structs
34 | are defined in `isr.h`. We will also use a simple function to register
35 | the interrupt handlers.
36 |
37 | That was a lot of work, but now we can define our first IRQ handler!
38 |
39 | There are no changes in `kernel.c`, so there is nothing new to run and see.
40 | Please move on to the next lesson to check those shiny new IRQs.
41 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/boot:
--------------------------------------------------------------------------------
1 | ../18-interrupts/boot/
--------------------------------------------------------------------------------
/19-interrupts-irqs/cpu/idt.c:
--------------------------------------------------------------------------------
1 | #include "idt.h"
2 | #include "../kernel/util.h"
3 |
4 | void set_idt_gate(int n, u32 handler) {
5 | idt[n].low_offset = low_16(handler);
6 | idt[n].sel = KERNEL_CS;
7 | idt[n].always0 = 0;
8 | idt[n].flags = 0x8E;
9 | idt[n].high_offset = high_16(handler);
10 | }
11 |
12 | void set_idt() {
13 | idt_reg.base = (u32) &idt;
14 | idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
15 | /* Don't make the mistake of loading &idt -- always load &idt_reg */
16 | __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
17 | }
18 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/cpu/idt.h:
--------------------------------------------------------------------------------
1 | #ifndef IDT_H
2 | #define IDT_H
3 |
4 | #include "types.h"
5 |
6 | /* Segment selectors */
7 | #define KERNEL_CS 0x08
8 |
9 | /* How every interrupt gate (handler) is defined */
10 | typedef struct {
11 | u16 low_offset; /* Lower 16 bits of handler function address */
12 | u16 sel; /* Kernel segment selector */
13 | u8 always0;
14 | /* First byte
15 | * Bit 7: "Interrupt is present"
16 | * Bits 6-5: Privilege level of caller (0=kernel..3=user)
17 | * Bit 4: Set to 0 for interrupt gates
18 | * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19 | u8 flags;
20 | u16 high_offset; /* Higher 16 bits of handler function address */
21 | } __attribute__((packed)) idt_gate_t ;
22 |
23 | /* A pointer to the array of interrupt handlers.
24 | * Assembly instruction 'lidt' will read it */
25 | typedef struct {
26 | u16 limit;
27 | u32 base;
28 | } __attribute__((packed)) idt_register_t;
29 |
30 | #define IDT_ENTRIES 256
31 | idt_gate_t idt[IDT_ENTRIES];
32 | idt_register_t idt_reg;
33 |
34 |
35 | /* Functions implemented in idt.c */
36 | void set_idt_gate(int n, u32 handler);
37 | void set_idt();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/cpu/isr.h:
--------------------------------------------------------------------------------
1 | #ifndef ISR_H
2 | #define ISR_H
3 |
4 | #include "types.h"
5 |
6 | /* ISRs reserved for CPU exceptions */
7 | extern void isr0();
8 | extern void isr1();
9 | extern void isr2();
10 | extern void isr3();
11 | extern void isr4();
12 | extern void isr5();
13 | extern void isr6();
14 | extern void isr7();
15 | extern void isr8();
16 | extern void isr9();
17 | extern void isr10();
18 | extern void isr11();
19 | extern void isr12();
20 | extern void isr13();
21 | extern void isr14();
22 | extern void isr15();
23 | extern void isr16();
24 | extern void isr17();
25 | extern void isr18();
26 | extern void isr19();
27 | extern void isr20();
28 | extern void isr21();
29 | extern void isr22();
30 | extern void isr23();
31 | extern void isr24();
32 | extern void isr25();
33 | extern void isr26();
34 | extern void isr27();
35 | extern void isr28();
36 | extern void isr29();
37 | extern void isr30();
38 | extern void isr31();
39 | /* IRQ definitions */
40 | extern void irq0();
41 | extern void irq1();
42 | extern void irq2();
43 | extern void irq3();
44 | extern void irq4();
45 | extern void irq5();
46 | extern void irq6();
47 | extern void irq7();
48 | extern void irq8();
49 | extern void irq9();
50 | extern void irq10();
51 | extern void irq11();
52 | extern void irq12();
53 | extern void irq13();
54 | extern void irq14();
55 | extern void irq15();
56 |
57 | #define IRQ0 32
58 | #define IRQ1 33
59 | #define IRQ2 34
60 | #define IRQ3 35
61 | #define IRQ4 36
62 | #define IRQ5 37
63 | #define IRQ6 38
64 | #define IRQ7 39
65 | #define IRQ8 40
66 | #define IRQ9 41
67 | #define IRQ10 42
68 | #define IRQ11 43
69 | #define IRQ12 44
70 | #define IRQ13 45
71 | #define IRQ14 46
72 | #define IRQ15 47
73 |
74 | /* Struct which aggregates many registers */
75 | typedef struct {
76 | u32 ds; /* Data segment selector */
77 | u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
78 | u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
79 | u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
80 | } registers_t;
81 |
82 | void isr_install();
83 | void isr_handler(registers_t r);
84 |
85 | typedef void (*isr_t)(registers_t);
86 | void register_interrupt_handler(u8 n, isr_t handler);
87 |
88 | #endif
89 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/cpu/types.h:
--------------------------------------------------------------------------------
1 | #ifndef TYPES_H
2 | #define TYPES_H
3 |
4 | /* Instead of using 'chars' to allocate non-character bytes,
5 | * we will use these new type with no semantic meaning */
6 | typedef unsigned int u32;
7 | typedef int s32;
8 | typedef unsigned short u16;
9 | typedef short s16;
10 | typedef unsigned char u8;
11 | typedef char s8;
12 |
13 | #define low_16(address) (u16)((address) & 0xFFFF)
14 | #define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/drivers:
--------------------------------------------------------------------------------
1 | ../18-interrupts/drivers/
--------------------------------------------------------------------------------
/19-interrupts-irqs/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../drivers/screen.h"
2 | #include "util.h"
3 | #include "../cpu/isr.h"
4 | #include "../cpu/idt.h"
5 |
6 | void main() {
7 | isr_install();
8 | /* Test the interrupts */
9 | __asm__ __volatile__("int $2");
10 | __asm__ __volatile__("int $3");
11 | }
12 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/kernel/util.c:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | void memory_copy(char *source, char *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(u8 *dest, u8 val, u32 len) {
11 | u8 *temp = (u8 *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
15 | /**
16 | * K&R implementation
17 | */
18 | void int_to_ascii(int n, char str[]) {
19 | int i, sign;
20 | if ((sign = n) < 0) n = -n;
21 | i = 0;
22 | do {
23 | str[i++] = n % 10 + '0';
24 | } while ((n /= 10) > 0);
25 |
26 | if (sign < 0) str[i++] = '-';
27 | str[i] = '\0';
28 |
29 | /* TODO: implement "reverse" */
30 | }
31 |
--------------------------------------------------------------------------------
/19-interrupts-irqs/kernel/util.h:
--------------------------------------------------------------------------------
1 | #ifndef UTIL_H
2 | #define UTIL_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | void memory_copy(char *source, char *dest, int nbytes);
7 | void memory_set(u8 *dest, u8 val, u32 len);
8 | void int_to_ascii(int n, char str[]);
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/20-interrupts-timer/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
47 |
--------------------------------------------------------------------------------
/20-interrupts-timer/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: CPU timer, keyboard interrupts, scancode*
2 |
3 | **Goal: Implement our first IRQ handlers: the CPU timer and the keyboard**
4 |
5 | Everything is now ready to test our hardware interrupts.
6 |
7 | Timer
8 | -----
9 |
10 | The timer is easy to configure. First we'll declare an `init_timer()` on `cpu/timer.h` and
11 | implement it on `cpu/timer.c`. It is just a matter of computing the clock frequency and
12 | sending the bytes to the appropriate ports.
13 |
14 | We will now fix `kernel/utils.c int_to_ascii()` to print the numbers in the correct order.
15 | For that, we need to implement `reverse()` and `strlen()`.
16 |
17 | Finally, go back to the `kernel/kernel.c` and do two things. Enable interrupts again
18 | (very important!) and then initialize the timer interrupt.
19 |
20 | Go `make run` and you'll see the clock ticking!
21 |
22 |
23 | Keyboard
24 | --------
25 |
26 | The keyboard is even easier, with a drawback. The PIC does not send us the ASCII code
27 | for the pressed key, but the scancode for the key-down and the key-up events, so we
28 | will need to translate those.
29 |
30 | Check out `drivers/keyboard.c` where there are two functions: the callback and
31 | the initialization which configures the interrupt callback. A new `keyboard.h` was
32 | created with the definitions.
33 |
34 | `keyboard.c` also has a long table to translate scancodes to ASCII keys. For the time
35 | being, we will only implement a simple subset of the US keyboard. You can read
36 | more [about scancodes here](http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html)
37 |
38 | I don't know about you, but I'm thrilled! We are very close to building a simple shell.
39 | In the next chapter, we will expand a little bit on keyboard input
40 |
--------------------------------------------------------------------------------
/20-interrupts-timer/boot:
--------------------------------------------------------------------------------
1 | ../19-interrupts-irqs/boot
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/idt.c:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/idt.c
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/idt.h:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/idt.h
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/interrupt.asm:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/interrupt.asm
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/isr.c:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/isr.c
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/isr.h:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/isr.h
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 | #include "../drivers/screen.h"
3 | #include "../kernel/util.h"
4 | #include "isr.h"
5 |
6 | u32 tick = 0;
7 |
8 | static void timer_callback(registers_t regs) {
9 | tick++;
10 | kprint("Tick: ");
11 |
12 | char tick_ascii[256];
13 | int_to_ascii(tick, tick_ascii);
14 | kprint(tick_ascii);
15 | kprint("\n");
16 | }
17 |
18 | void init_timer(u32 freq) {
19 | /* Install the function we just wrote */
20 | register_interrupt_handler(IRQ0, timer_callback);
21 |
22 | /* Get the PIT value: hardware clock at 1193180 Hz */
23 | u32 divisor = 1193180 / freq;
24 | u8 low = (u8)(divisor & 0xFF);
25 | u8 high = (u8)( (divisor >> 8) & 0xFF);
26 | /* Send the command */
27 | port_byte_out(0x43, 0x36); /* Command port */
28 | port_byte_out(0x40, low);
29 | port_byte_out(0x40, high);
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_H
2 | #define TIMER_H
3 |
4 | #include "../kernel/util.h"
5 |
6 | void init_timer(u32 freq);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/20-interrupts-timer/cpu/types.h:
--------------------------------------------------------------------------------
1 | ../../19-interrupts-irqs/cpu/types.h
--------------------------------------------------------------------------------
/20-interrupts-timer/drivers/keyboard.h:
--------------------------------------------------------------------------------
1 | #include "../cpu/types.h"
2 |
3 | void init_keyboard();
4 |
--------------------------------------------------------------------------------
/20-interrupts-timer/drivers/ports.c:
--------------------------------------------------------------------------------
1 | #include "ports.h"
2 |
3 | /**
4 | * Read a byte from the specified port
5 | */
6 | u8 port_byte_in (u16 port) {
7 | u8 result;
8 | /* Inline assembler syntax
9 | * !! Notice how the source and destination registers are switched from NASM !!
10 | *
11 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
12 | * '"d" (port)': map the C variable '(port)' into e'd'x register
13 | *
14 | * Inputs and outputs are separated by colons
15 | */
16 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
17 | return result;
18 | }
19 |
20 | void port_byte_out (u16 port, u8 data) {
21 | /* Notice how here both registers are mapped to C variables and
22 | * nothing is returned, thus, no equals '=' in the asm syntax
23 | * However we see a comma since there are two variables in the input area
24 | * and none in the 'return' area
25 | */
26 | __asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
27 | }
28 |
29 | u16 port_word_in (u16 port) {
30 | u16 result;
31 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
32 | return result;
33 | }
34 |
35 | void port_word_out (u16 port, u16 data) {
36 | __asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
37 | }
38 |
--------------------------------------------------------------------------------
/20-interrupts-timer/drivers/ports.h:
--------------------------------------------------------------------------------
1 | #ifndef PORTS_H
2 | #define PORTS_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | unsigned char port_byte_in (u16 port);
7 | void port_byte_out (u16 port, u8 data);
8 | unsigned short port_word_in (u16 port);
9 | void port_word_out (u16 port, u16 data);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/20-interrupts-timer/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #define VIDEO_ADDRESS 0xb8000
5 | #define MAX_ROWS 25
6 | #define MAX_COLS 80
7 | #define WHITE_ON_BLACK 0x0f
8 | #define RED_ON_WHITE 0xf4
9 |
10 | /* Screen i/o ports */
11 | #define REG_SCREEN_CTRL 0x3d4
12 | #define REG_SCREEN_DATA 0x3d5
13 |
14 | /* Public kernel API */
15 | void clear_screen();
16 | void kprint_at(char *message, int col, int row);
17 | void kprint(char *message);
18 |
19 | #endif
20 |
--------------------------------------------------------------------------------
/20-interrupts-timer/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../cpu/isr.h"
2 | #include "../cpu/timer.h"
3 | #include "../drivers/keyboard.h"
4 |
5 | void main() {
6 | isr_install();
7 |
8 | asm volatile("sti");
9 | init_timer(50);
10 | /* Comment out the timer IRQ handler to read
11 | * the keyboard IRQs easier */
12 | init_keyboard();
13 | }
14 |
--------------------------------------------------------------------------------
/20-interrupts-timer/kernel/util.c:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | void memory_copy(char *source, char *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(u8 *dest, u8 val, u32 len) {
11 | u8 *temp = (u8 *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
15 | /**
16 | * K&R implementation
17 | */
18 | void int_to_ascii(int n, char str[]) {
19 | int i, sign;
20 | if ((sign = n) < 0) n = -n;
21 | i = 0;
22 | do {
23 | str[i++] = n % 10 + '0';
24 | } while ((n /= 10) > 0);
25 |
26 | if (sign < 0) str[i++] = '-';
27 | str[i] = '\0';
28 |
29 | reverse(str);
30 | }
31 |
32 | /* K&R */
33 | void reverse(char s[]) {
34 | int c, i, j;
35 | for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
36 | c = s[i];
37 | s[i] = s[j];
38 | s[j] = c;
39 | }
40 | }
41 |
42 | /* K&R */
43 | int strlen(char s[]) {
44 | int i = 0;
45 | while (s[i] != '\0') ++i;
46 | return i;
47 | }
48 |
--------------------------------------------------------------------------------
/20-interrupts-timer/kernel/util.h:
--------------------------------------------------------------------------------
1 | #ifndef UTIL_H
2 | #define UTIL_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | void memory_copy(char *source, char *dest, int nbytes);
7 | void memory_set(u8 *dest, u8 val, u32 len);
8 | void int_to_ascii(int n, char str[]);
9 | void reverse(char s[]);
10 | int strlen(char s[]);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/21-shell/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs \
11 | -Wall -Wextra -Werror
12 |
13 | # First rule is run by default
14 | os-image.bin: boot/bootsect.bin kernel.bin
15 | cat $^ > os-image.bin
16 |
17 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
18 | # to 'strip' them manually on this case
19 | kernel.bin: boot/kernel_entry.o ${OBJ}
20 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
21 |
22 | # Used for debugging purposes
23 | kernel.elf: boot/kernel_entry.o ${OBJ}
24 | i386-elf-ld -o $@ -Ttext 0x1000 $^
25 |
26 | run: os-image.bin
27 | qemu-system-i386 -fda os-image.bin
28 |
29 | # Open the connection to qemu and load our kernel-object file with symbols
30 | debug: os-image.bin kernel.elf
31 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
32 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
33 |
34 | # Generic rules for wildcards
35 | # To make an object, always compile from its .c
36 | %.o: %.c ${HEADERS}
37 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
38 |
39 | %.o: %.asm
40 | nasm $< -f elf -o $@
41 |
42 | %.bin: %.asm
43 | nasm $< -f bin -o $@
44 |
45 | clean:
46 | rm -rf *.bin *.dis *.o os-image.bin *.elf
47 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o
48 |
--------------------------------------------------------------------------------
/21-shell/README.md:
--------------------------------------------------------------------------------
1 |
2 | **Goal: Clean the code a bit and parse user input**
3 |
4 | In this lesson we will do two things. First, we will clean up the code a bit, so it is ready
5 | for further lessons. During the previous ones I tried to put things in the most predictable places,
6 | but it is also a good exercise to know when the code base is growing and adapt it to current
7 | and further needs.
8 |
9 |
10 | Code cleaning
11 | -------------
12 |
13 | First of all, we will quickly start to need more utility functions
14 | for handling strings and so on. In a regular OS, this is called the C library,
15 | or libc for short.
16 |
17 | Right now we have a `utils.c` which we will split into `mem.c` and `string.c`, with their respective headers.
18 |
19 | Second, we will create a new function `irq_install()` so that the kernel
20 | only needs to perform one call to initialize all the IRQs. That function
21 | is akin to `isr_install()` and placed on the same `irq.c`.
22 | While we're here, we will disable the `kprint()` on `timer_callback()`
23 | to avoid filling the screen with junk, now that we know that it works
24 | properly.
25 |
26 | There is not a clear distinction between `cpu/` and `drivers/`.
27 | Keep in mind that I'm
28 | creating this tutorial while following many others, and each of them
29 | has a distinct folder structure. The only change we will do for now is to
30 | move `drivers/ports.*` into `cpu/` since it is clearly cpu-dependent code.
31 | `boot/` is also CPU-dependent code, but we will not mess with it until
32 | we implement the boot sequence for a different machine.
33 |
34 | There are more switches for the `CFLAGS` on the `Makefile`, since we will now
35 | start creating higher-level functions for our C library and we don't want
36 | the compiler to include any external code if we make a mistake with a declaration.
37 | We also added some flags to turn warnings into errors, since an apparently minor mistake
38 | converting pointers can blow up later on. This also forced us to modify some misc pointer
39 | declarations in our code.
40 |
41 | Finally, we'll add a macro to avoid warning-errors on unused parameters on `libc/function.h`
42 |
43 | Keyboard characters
44 | -------------------
45 |
46 | How to access the typed characters, then?
47 |
48 | - When a key is pressed, the callback gets the ASCII code via a new
49 | arrays which are defined at the beginning of `keyboard.c`
50 | - The callback then appends that character to a buffer, `key_buffer`
51 | - It is also printed on the screen
52 | - When the OS wants to read user input, it calls `libc/io.c:readline()`
53 |
54 | `keyboard.c` also parses backspace, by removing the last element
55 | of the key buffer, and deleting it from the screen, by calling
56 | `screen.c:kprint_backspace()`. For this we needed to modify a bit
57 | `print_char()` to not advance the offset when printing a backspace
58 |
59 |
60 | Responding to user input
61 | ------------------------
62 |
63 | The keyboard callback checks for a newline, and then calls the kernel,
64 | telling it that the user has input something. Out final libc function
65 | is `strcmp()`, which compares two strings and returns 0 if they
66 | are equal. If the user inputs "END", we halt the CPU.
67 |
68 | This is the most basic shell ever, but you should be proud, because
69 | we implemented it from scratch. Do you realize how cool this is?
70 |
71 | If you want to, expand `kernel.c` to parse more stuff. In the future,
72 | when we have a filesystem, we will allow the user to run some basic commands.
73 |
--------------------------------------------------------------------------------
/21-shell/boot:
--------------------------------------------------------------------------------
1 | ../20-interrupts-timer/boot
--------------------------------------------------------------------------------
/21-shell/cpu/idt.c:
--------------------------------------------------------------------------------
1 | #include "idt.h"
2 |
3 | void set_idt_gate(int n, u32 handler) {
4 | idt[n].low_offset = low_16(handler);
5 | idt[n].sel = KERNEL_CS;
6 | idt[n].always0 = 0;
7 | idt[n].flags = 0x8E;
8 | idt[n].high_offset = high_16(handler);
9 | }
10 |
11 | void set_idt() {
12 | idt_reg.base = (u32) &idt;
13 | idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
14 | /* Don't make the mistake of loading &idt -- always load &idt_reg */
15 | __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
16 | }
17 |
--------------------------------------------------------------------------------
/21-shell/cpu/idt.h:
--------------------------------------------------------------------------------
1 | #ifndef IDT_H
2 | #define IDT_H
3 |
4 | #include "types.h"
5 |
6 | /* Segment selectors */
7 | #define KERNEL_CS 0x08
8 |
9 | /* How every interrupt gate (handler) is defined */
10 | typedef struct {
11 | u16 low_offset; /* Lower 16 bits of handler function address */
12 | u16 sel; /* Kernel segment selector */
13 | u8 always0;
14 | /* First byte
15 | * Bit 7: "Interrupt is present"
16 | * Bits 6-5: Privilege level of caller (0=kernel..3=user)
17 | * Bit 4: Set to 0 for interrupt gates
18 | * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19 | u8 flags;
20 | u16 high_offset; /* Higher 16 bits of handler function address */
21 | } __attribute__((packed)) idt_gate_t ;
22 |
23 | /* A pointer to the array of interrupt handlers.
24 | * Assembly instruction 'lidt' will read it */
25 | typedef struct {
26 | u16 limit;
27 | u32 base;
28 | } __attribute__((packed)) idt_register_t;
29 |
30 | #define IDT_ENTRIES 256
31 | idt_gate_t idt[IDT_ENTRIES];
32 | idt_register_t idt_reg;
33 |
34 |
35 | /* Functions implemented in idt.c */
36 | void set_idt_gate(int n, u32 handler);
37 | void set_idt();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/21-shell/cpu/isr.h:
--------------------------------------------------------------------------------
1 | #ifndef ISR_H
2 | #define ISR_H
3 |
4 | #include "types.h"
5 |
6 | /* ISRs reserved for CPU exceptions */
7 | extern void isr0();
8 | extern void isr1();
9 | extern void isr2();
10 | extern void isr3();
11 | extern void isr4();
12 | extern void isr5();
13 | extern void isr6();
14 | extern void isr7();
15 | extern void isr8();
16 | extern void isr9();
17 | extern void isr10();
18 | extern void isr11();
19 | extern void isr12();
20 | extern void isr13();
21 | extern void isr14();
22 | extern void isr15();
23 | extern void isr16();
24 | extern void isr17();
25 | extern void isr18();
26 | extern void isr19();
27 | extern void isr20();
28 | extern void isr21();
29 | extern void isr22();
30 | extern void isr23();
31 | extern void isr24();
32 | extern void isr25();
33 | extern void isr26();
34 | extern void isr27();
35 | extern void isr28();
36 | extern void isr29();
37 | extern void isr30();
38 | extern void isr31();
39 | /* IRQ definitions */
40 | extern void irq0();
41 | extern void irq1();
42 | extern void irq2();
43 | extern void irq3();
44 | extern void irq4();
45 | extern void irq5();
46 | extern void irq6();
47 | extern void irq7();
48 | extern void irq8();
49 | extern void irq9();
50 | extern void irq10();
51 | extern void irq11();
52 | extern void irq12();
53 | extern void irq13();
54 | extern void irq14();
55 | extern void irq15();
56 |
57 | #define IRQ0 32
58 | #define IRQ1 33
59 | #define IRQ2 34
60 | #define IRQ3 35
61 | #define IRQ4 36
62 | #define IRQ5 37
63 | #define IRQ6 38
64 | #define IRQ7 39
65 | #define IRQ8 40
66 | #define IRQ9 41
67 | #define IRQ10 42
68 | #define IRQ11 43
69 | #define IRQ12 44
70 | #define IRQ13 45
71 | #define IRQ14 46
72 | #define IRQ15 47
73 |
74 | /* Struct which aggregates many registers */
75 | typedef struct {
76 | u32 ds; /* Data segment selector */
77 | u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
78 | u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
79 | u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
80 | } registers_t;
81 |
82 | void isr_install();
83 | void isr_handler(registers_t r);
84 | void irq_install();
85 |
86 | typedef void (*isr_t)(registers_t);
87 | void register_interrupt_handler(u8 n, isr_t handler);
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/21-shell/cpu/ports.c:
--------------------------------------------------------------------------------
1 | ../../20-interrupts-timer/drivers/ports.c
--------------------------------------------------------------------------------
/21-shell/cpu/ports.h:
--------------------------------------------------------------------------------
1 | ../../20-interrupts-timer/drivers/ports.h
--------------------------------------------------------------------------------
/21-shell/cpu/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 | #include "isr.h"
3 | #include "ports.h"
4 | #include "../libc/function.h"
5 |
6 | u32 tick = 0;
7 |
8 | static void timer_callback(registers_t regs) {
9 | tick++;
10 | UNUSED(regs);
11 | }
12 |
13 | void init_timer(u32 freq) {
14 | /* Install the function we just wrote */
15 | register_interrupt_handler(IRQ0, timer_callback);
16 |
17 | /* Get the PIT value: hardware clock at 1193180 Hz */
18 | u32 divisor = 1193180 / freq;
19 | u8 low = (u8)(divisor & 0xFF);
20 | u8 high = (u8)( (divisor >> 8) & 0xFF);
21 | /* Send the command */
22 | port_byte_out(0x43, 0x36); /* Command port */
23 | port_byte_out(0x40, low);
24 | port_byte_out(0x40, high);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/21-shell/cpu/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_H
2 | #define TIMER_H
3 |
4 | #include "types.h"
5 |
6 | void init_timer(u32 freq);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/21-shell/cpu/types.h:
--------------------------------------------------------------------------------
1 | #ifndef TYPES_H
2 | #define TYPES_H
3 |
4 | /* Instead of using 'chars' to allocate non-character bytes,
5 | * we will use these new type with no semantic meaning */
6 | typedef unsigned int u32;
7 | typedef int s32;
8 | typedef unsigned short u16;
9 | typedef short s16;
10 | typedef unsigned char u8;
11 | typedef char s8;
12 |
13 | #define low_16(address) (u16)((address) & 0xFFFF)
14 | #define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/21-shell/drivers/keyboard.c:
--------------------------------------------------------------------------------
1 | #include "keyboard.h"
2 | #include "../cpu/ports.h"
3 | #include "../cpu/isr.h"
4 | #include "screen.h"
5 | #include "../libc/string.h"
6 | #include "../libc/function.h"
7 | #include "../kernel/kernel.h"
8 |
9 | #define BACKSPACE 0x0E
10 | #define ENTER 0x1C
11 |
12 | static char key_buffer[256];
13 |
14 | #define SC_MAX 57
15 | const char *sc_name[] = { "ERROR", "Esc", "1", "2", "3", "4", "5", "6",
16 | "7", "8", "9", "0", "-", "=", "Backspace", "Tab", "Q", "W", "E",
17 | "R", "T", "Y", "U", "I", "O", "P", "[", "]", "Enter", "Lctrl",
18 | "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "`",
19 | "LShift", "\\", "Z", "X", "C", "V", "B", "N", "M", ",", ".",
20 | "/", "RShift", "Keypad *", "LAlt", "Spacebar"};
21 | const char sc_ascii[] = { '?', '?', '1', '2', '3', '4', '5', '6',
22 | '7', '8', '9', '0', '-', '=', '?', '?', 'Q', 'W', 'E', 'R', 'T', 'Y',
23 | 'U', 'I', 'O', 'P', '[', ']', '?', '?', 'A', 'S', 'D', 'F', 'G',
24 | 'H', 'J', 'K', 'L', ';', '\'', '`', '?', '\\', 'Z', 'X', 'C', 'V',
25 | 'B', 'N', 'M', ',', '.', '/', '?', '?', '?', ' '};
26 |
27 | static void keyboard_callback(registers_t regs) {
28 | /* The PIC leaves us the scancode in port 0x60 */
29 | u8 scancode = port_byte_in(0x60);
30 |
31 | if (scancode > SC_MAX) return;
32 | if (scancode == BACKSPACE) {
33 | backspace(key_buffer);
34 | kprint_backspace();
35 | } else if (scancode == ENTER) {
36 | kprint("\n");
37 | user_input(key_buffer); /* kernel-controlled function */
38 | key_buffer[0] = '\0';
39 | } else {
40 | char letter = sc_ascii[(int)scancode];
41 | /* Remember that kprint only accepts char[] */
42 | char str[2] = {letter, '\0'};
43 | append(key_buffer, letter);
44 | kprint(str);
45 | }
46 | UNUSED(regs);
47 | }
48 |
49 | void init_keyboard() {
50 | register_interrupt_handler(IRQ1, keyboard_callback);
51 | }
52 |
--------------------------------------------------------------------------------
/21-shell/drivers/keyboard.h:
--------------------------------------------------------------------------------
1 | #include "../cpu/types.h"
2 |
3 | void init_keyboard();
4 |
--------------------------------------------------------------------------------
/21-shell/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | #define VIDEO_ADDRESS 0xb8000
7 | #define MAX_ROWS 25
8 | #define MAX_COLS 80
9 | #define WHITE_ON_BLACK 0x0f
10 | #define RED_ON_WHITE 0xf4
11 |
12 | /* Screen i/o ports */
13 | #define REG_SCREEN_CTRL 0x3d4
14 | #define REG_SCREEN_DATA 0x3d5
15 |
16 | /* Public kernel API */
17 | void clear_screen();
18 | void kprint_at(char *message, int col, int row);
19 | void kprint(char *message);
20 | void kprint_backspace();
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/21-shell/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../cpu/isr.h"
2 | #include "../drivers/screen.h"
3 | #include "kernel.h"
4 | #include "../libc/string.h"
5 |
6 | void main() {
7 | isr_install();
8 | irq_install();
9 |
10 | kprint("Type something, it will go through the kernel\n"
11 | "Type END to halt the CPU\n> ");
12 | }
13 |
14 | void user_input(char *input) {
15 | if (strcmp(input, "END") == 0) {
16 | kprint("Stopping the CPU. Bye!\n");
17 | asm volatile("hlt");
18 | }
19 | kprint("You said: ");
20 | kprint(input);
21 | kprint("\n> ");
22 | }
23 |
--------------------------------------------------------------------------------
/21-shell/kernel/kernel.h:
--------------------------------------------------------------------------------
1 | #ifndef KERNEL_H
2 | #define KERNEL_H
3 |
4 | void user_input(char *input);
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/21-shell/libc/function.h:
--------------------------------------------------------------------------------
1 | #ifndef FUNCTION_H
2 | #define FUNCTION_H
3 |
4 | /* Sometimes we want to keep parameters to a function for later use
5 | * and this is a solution to avoid the 'unused parameter' compiler warning */
6 | #define UNUSED(x) (void)(x)
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/21-shell/libc/mem.c:
--------------------------------------------------------------------------------
1 | #include "mem.h"
2 |
3 | void memory_copy(u8 *source, u8 *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(u8 *dest, u8 val, u32 len) {
11 | u8 *temp = (u8 *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
--------------------------------------------------------------------------------
/21-shell/libc/mem.h:
--------------------------------------------------------------------------------
1 | #ifndef MEM_H
2 | #define MEM_H
3 |
4 | #include "../cpu/types.h"
5 |
6 | void memory_copy(u8 *source, u8 *dest, int nbytes);
7 | void memory_set(u8 *dest, u8 val, u32 len);
8 |
9 | #endif
10 |
--------------------------------------------------------------------------------
/21-shell/libc/string.c:
--------------------------------------------------------------------------------
1 | #include "string.h"
2 |
3 | /**
4 | * K&R implementation
5 | */
6 | void int_to_ascii(int n, char str[]) {
7 | int i, sign;
8 | if ((sign = n) < 0) n = -n;
9 | i = 0;
10 | do {
11 | str[i++] = n % 10 + '0';
12 | } while ((n /= 10) > 0);
13 |
14 | if (sign < 0) str[i++] = '-';
15 | str[i] = '\0';
16 |
17 | reverse(str);
18 | }
19 |
20 | /* K&R */
21 | void reverse(char s[]) {
22 | int c, i, j;
23 | for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
24 | c = s[i];
25 | s[i] = s[j];
26 | s[j] = c;
27 | }
28 | }
29 |
30 | /* K&R */
31 | int strlen(char s[]) {
32 | int i = 0;
33 | while (s[i] != '\0') ++i;
34 | return i;
35 | }
36 |
37 | void append(char s[], char n) {
38 | int len = strlen(s);
39 | s[len] = n;
40 | s[len+1] = '\0';
41 | }
42 |
43 | void backspace(char s[]) {
44 | int len = strlen(s);
45 | s[len-1] = '\0';
46 | }
47 |
48 | /* K&R
49 | * Returns <0 if s10 if s1>s2 */
50 | int strcmp(char s1[], char s2[]) {
51 | int i;
52 | for (i = 0; s1[i] == s2[i]; i++) {
53 | if (s1[i] == '\0') return 0;
54 | }
55 | return s1[i] - s2[i];
56 | }
57 |
--------------------------------------------------------------------------------
/21-shell/libc/string.h:
--------------------------------------------------------------------------------
1 | #ifndef STRINGS_H
2 | #define STRINGS_H
3 |
4 | void int_to_ascii(int n, char str[]);
5 | void reverse(char s[]);
6 | int strlen(char s[]);
7 | void backspace(char s[]);
8 | void append(char s[], char n);
9 | int strcmp(char s1[], char s2[]);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/22-malloc/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs \
11 | -Wall -Wextra -Werror
12 |
13 | # First rule is run by default
14 | os-image.bin: boot/bootsect.bin kernel.bin
15 | cat $^ > os-image.bin
16 |
17 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
18 | # to 'strip' them manually on this case
19 | kernel.bin: boot/kernel_entry.o ${OBJ}
20 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
21 |
22 | # Used for debugging purposes
23 | kernel.elf: boot/kernel_entry.o ${OBJ}
24 | i386-elf-ld -o $@ -Ttext 0x1000 $^
25 |
26 | run: os-image.bin
27 | qemu-system-i386 -fda os-image.bin
28 |
29 | # Open the connection to qemu and load our kernel-object file with symbols
30 | debug: os-image.bin kernel.elf
31 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
32 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
33 |
34 | # Generic rules for wildcards
35 | # To make an object, always compile from its .c
36 | %.o: %.c ${HEADERS}
37 | ${CC} ${CFLAGS} -ffreestanding -c $< -o $@
38 |
39 | %.o: %.asm
40 | nasm $< -f elf -o $@
41 |
42 | %.bin: %.asm
43 | nasm $< -f bin -o $@
44 |
45 | clean:
46 | rm -rf *.bin *.dis *.o os-image.bin *.elf
47 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o
48 |
--------------------------------------------------------------------------------
/22-malloc/README.md:
--------------------------------------------------------------------------------
1 | *Concepts you may want to Google beforehand: malloc*
2 |
3 | **Goal: Implement a memory allocator**
4 |
5 | We will add a kernel memory allocator to `libc/mem.c`. It is
6 | implemented as a simple pointer to free memory, which keeps
7 | growing.
8 |
9 | The `kmalloc()` function can be used to request an aligned page,
10 | and it will also return the real, physical address, for later use.
11 |
12 | We'll change the `kernel.c` leaving all the "shell" code there,
13 | Let's just try out the new `kmalloc()`, and check out that
14 | our first page starts at 0x10000 (as hardcoded on `mem.c`) and
15 | subsequent `kmalloc()`'s produce a new address which is
16 | aligned 4096 bytes or 0x1000 from the previous one.
17 |
18 | Note that we added a new `strings.c:hex_to_ascii()` for
19 | nicer printing of hex numbers.
20 |
21 | Another cosmetic modification is to rename `types.c` to
22 | `type.c` for language consistency.
23 |
24 | The rest of the files are unchanged from last lesson.
25 |
--------------------------------------------------------------------------------
/22-malloc/boot:
--------------------------------------------------------------------------------
1 | ../21-shell/boot
--------------------------------------------------------------------------------
/22-malloc/cpu/idt.c:
--------------------------------------------------------------------------------
1 | #include "idt.h"
2 |
3 | void set_idt_gate(int n, u32 handler) {
4 | idt[n].low_offset = low_16(handler);
5 | idt[n].sel = KERNEL_CS;
6 | idt[n].always0 = 0;
7 | idt[n].flags = 0x8E;
8 | idt[n].high_offset = high_16(handler);
9 | }
10 |
11 | void set_idt() {
12 | idt_reg.base = (u32) &idt;
13 | idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
14 | /* Don't make the mistake of loading &idt -- always load &idt_reg */
15 | __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
16 | }
17 |
--------------------------------------------------------------------------------
/22-malloc/cpu/idt.h:
--------------------------------------------------------------------------------
1 | #ifndef IDT_H
2 | #define IDT_H
3 |
4 | #include "type.h"
5 |
6 | /* Segment selectors */
7 | #define KERNEL_CS 0x08
8 |
9 | /* How every interrupt gate (handler) is defined */
10 | typedef struct {
11 | u16 low_offset; /* Lower 16 bits of handler function address */
12 | u16 sel; /* Kernel segment selector */
13 | u8 always0;
14 | /* First byte
15 | * Bit 7: "Interrupt is present"
16 | * Bits 6-5: Privilege level of caller (0=kernel..3=user)
17 | * Bit 4: Set to 0 for interrupt gates
18 | * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19 | u8 flags;
20 | u16 high_offset; /* Higher 16 bits of handler function address */
21 | } __attribute__((packed)) idt_gate_t ;
22 |
23 | /* A pointer to the array of interrupt handlers.
24 | * Assembly instruction 'lidt' will read it */
25 | typedef struct {
26 | u16 limit;
27 | u32 base;
28 | } __attribute__((packed)) idt_register_t;
29 |
30 | #define IDT_ENTRIES 256
31 | idt_gate_t idt[IDT_ENTRIES];
32 | idt_register_t idt_reg;
33 |
34 |
35 | /* Functions implemented in idt.c */
36 | void set_idt_gate(int n, u32 handler);
37 | void set_idt();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/22-malloc/cpu/isr.h:
--------------------------------------------------------------------------------
1 | #ifndef ISR_H
2 | #define ISR_H
3 |
4 | #include "type.h"
5 |
6 | /* ISRs reserved for CPU exceptions */
7 | extern void isr0();
8 | extern void isr1();
9 | extern void isr2();
10 | extern void isr3();
11 | extern void isr4();
12 | extern void isr5();
13 | extern void isr6();
14 | extern void isr7();
15 | extern void isr8();
16 | extern void isr9();
17 | extern void isr10();
18 | extern void isr11();
19 | extern void isr12();
20 | extern void isr13();
21 | extern void isr14();
22 | extern void isr15();
23 | extern void isr16();
24 | extern void isr17();
25 | extern void isr18();
26 | extern void isr19();
27 | extern void isr20();
28 | extern void isr21();
29 | extern void isr22();
30 | extern void isr23();
31 | extern void isr24();
32 | extern void isr25();
33 | extern void isr26();
34 | extern void isr27();
35 | extern void isr28();
36 | extern void isr29();
37 | extern void isr30();
38 | extern void isr31();
39 | /* IRQ definitions */
40 | extern void irq0();
41 | extern void irq1();
42 | extern void irq2();
43 | extern void irq3();
44 | extern void irq4();
45 | extern void irq5();
46 | extern void irq6();
47 | extern void irq7();
48 | extern void irq8();
49 | extern void irq9();
50 | extern void irq10();
51 | extern void irq11();
52 | extern void irq12();
53 | extern void irq13();
54 | extern void irq14();
55 | extern void irq15();
56 |
57 | #define IRQ0 32
58 | #define IRQ1 33
59 | #define IRQ2 34
60 | #define IRQ3 35
61 | #define IRQ4 36
62 | #define IRQ5 37
63 | #define IRQ6 38
64 | #define IRQ7 39
65 | #define IRQ8 40
66 | #define IRQ9 41
67 | #define IRQ10 42
68 | #define IRQ11 43
69 | #define IRQ12 44
70 | #define IRQ13 45
71 | #define IRQ14 46
72 | #define IRQ15 47
73 |
74 | /* Struct which aggregates many registers */
75 | typedef struct {
76 | u32 ds; /* Data segment selector */
77 | u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
78 | u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
79 | u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
80 | } registers_t;
81 |
82 | void isr_install();
83 | void isr_handler(registers_t r);
84 | void irq_install();
85 |
86 | typedef void (*isr_t)(registers_t);
87 | void register_interrupt_handler(u8 n, isr_t handler);
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/22-malloc/cpu/ports.c:
--------------------------------------------------------------------------------
1 | #include "ports.h"
2 |
3 | /**
4 | * Read a byte from the specified port
5 | */
6 | u8 port_byte_in (u16 port) {
7 | u8 result;
8 | /* Inline assembler syntax
9 | * !! Notice how the source and destination registers are switched from NASM !!
10 | *
11 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
12 | * '"d" (port)': map the C variable '(port)' into e'd'x register
13 | *
14 | * Inputs and outputs are separated by colons
15 | */
16 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
17 | return result;
18 | }
19 |
20 | void port_byte_out (u16 port, u8 data) {
21 | /* Notice how here both registers are mapped to C variables and
22 | * nothing is returned, thus, no equals '=' in the asm syntax
23 | * However we see a comma since there are two variables in the input area
24 | * and none in the 'return' area
25 | */
26 | __asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
27 | }
28 |
29 | u16 port_word_in (u16 port) {
30 | u16 result;
31 | __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
32 | return result;
33 | }
34 |
35 | void port_word_out (u16 port, u16 data) {
36 | __asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
37 | }
38 |
--------------------------------------------------------------------------------
/22-malloc/cpu/ports.h:
--------------------------------------------------------------------------------
1 | #ifndef PORTS_H
2 | #define PORTS_H
3 |
4 | #include "../cpu/type.h"
5 |
6 | unsigned char port_byte_in (u16 port);
7 | void port_byte_out (u16 port, u8 data);
8 | unsigned short port_word_in (u16 port);
9 | void port_word_out (u16 port, u16 data);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/22-malloc/cpu/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 | #include "isr.h"
3 | #include "ports.h"
4 | #include "../libc/function.h"
5 |
6 | u32 tick = 0;
7 |
8 | static void timer_callback(registers_t regs) {
9 | tick++;
10 | UNUSED(regs);
11 | }
12 |
13 | void init_timer(u32 freq) {
14 | /* Install the function we just wrote */
15 | register_interrupt_handler(IRQ0, timer_callback);
16 |
17 | /* Get the PIT value: hardware clock at 1193180 Hz */
18 | u32 divisor = 1193180 / freq;
19 | u8 low = (u8)(divisor & 0xFF);
20 | u8 high = (u8)( (divisor >> 8) & 0xFF);
21 | /* Send the command */
22 | port_byte_out(0x43, 0x36); /* Command port */
23 | port_byte_out(0x40, low);
24 | port_byte_out(0x40, high);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/22-malloc/cpu/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_H
2 | #define TIMER_H
3 |
4 | #include "type.h"
5 |
6 | void init_timer(u32 freq);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/22-malloc/cpu/type.h:
--------------------------------------------------------------------------------
1 | #ifndef TYPE_H
2 | #define TYPE_H
3 |
4 | /* Instead of using 'chars' to allocate non-character bytes,
5 | * we will use these new type with no semantic meaning */
6 | typedef unsigned int u32;
7 | typedef int s32;
8 | typedef unsigned short u16;
9 | typedef short s16;
10 | typedef unsigned char u8;
11 | typedef char s8;
12 |
13 | #define low_16(address) (u16)((address) & 0xFFFF)
14 | #define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/22-malloc/drivers/keyboard.c:
--------------------------------------------------------------------------------
1 | #include "keyboard.h"
2 | #include "../cpu/ports.h"
3 | #include "../cpu/isr.h"
4 | #include "screen.h"
5 | #include "../libc/string.h"
6 | #include "../libc/function.h"
7 | #include "../kernel/kernel.h"
8 |
9 | #define BACKSPACE 0x0E
10 | #define ENTER 0x1C
11 |
12 | static char key_buffer[256];
13 |
14 | #define SC_MAX 57
15 | const char *sc_name[] = { "ERROR", "Esc", "1", "2", "3", "4", "5", "6",
16 | "7", "8", "9", "0", "-", "=", "Backspace", "Tab", "Q", "W", "E",
17 | "R", "T", "Y", "U", "I", "O", "P", "[", "]", "Enter", "Lctrl",
18 | "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "`",
19 | "LShift", "\\", "Z", "X", "C", "V", "B", "N", "M", ",", ".",
20 | "/", "RShift", "Keypad *", "LAlt", "Spacebar"};
21 | const char sc_ascii[] = { '?', '?', '1', '2', '3', '4', '5', '6',
22 | '7', '8', '9', '0', '-', '=', '?', '?', 'Q', 'W', 'E', 'R', 'T', 'Y',
23 | 'U', 'I', 'O', 'P', '[', ']', '?', '?', 'A', 'S', 'D', 'F', 'G',
24 | 'H', 'J', 'K', 'L', ';', '\'', '`', '?', '\\', 'Z', 'X', 'C', 'V',
25 | 'B', 'N', 'M', ',', '.', '/', '?', '?', '?', ' '};
26 |
27 | static void keyboard_callback(registers_t regs) {
28 | /* The PIC leaves us the scancode in port 0x60 */
29 | u8 scancode = port_byte_in(0x60);
30 |
31 | if (scancode > SC_MAX) return;
32 | if (scancode == BACKSPACE) {
33 | backspace(key_buffer);
34 | kprint_backspace();
35 | } else if (scancode == ENTER) {
36 | kprint("\n");
37 | user_input(key_buffer); /* kernel-controlled function */
38 | key_buffer[0] = '\0';
39 | } else {
40 | char letter = sc_ascii[(int)scancode];
41 | /* Remember that kprint only accepts char[] */
42 | char str[2] = {letter, '\0'};
43 | append(key_buffer, letter);
44 | kprint(str);
45 | }
46 | UNUSED(regs);
47 | }
48 |
49 | void init_keyboard() {
50 | register_interrupt_handler(IRQ1, keyboard_callback);
51 | }
52 |
--------------------------------------------------------------------------------
/22-malloc/drivers/keyboard.h:
--------------------------------------------------------------------------------
1 | #include "../cpu/type.h"
2 |
3 | void init_keyboard();
4 |
--------------------------------------------------------------------------------
/22-malloc/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #include "../cpu/type.h"
5 |
6 | #define VIDEO_ADDRESS 0xb8000
7 | #define MAX_ROWS 25
8 | #define MAX_COLS 80
9 | #define WHITE_ON_BLACK 0x0f
10 | #define RED_ON_WHITE 0xf4
11 |
12 | /* Screen i/o ports */
13 | #define REG_SCREEN_CTRL 0x3d4
14 | #define REG_SCREEN_DATA 0x3d5
15 |
16 | /* Public kernel API */
17 | void clear_screen();
18 | void kprint_at(char *message, int col, int row);
19 | void kprint(char *message);
20 | void kprint_backspace();
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/22-malloc/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../cpu/isr.h"
2 | #include "../drivers/screen.h"
3 | #include "kernel.h"
4 | #include "../libc/string.h"
5 | #include "../libc/mem.h"
6 |
7 | void main() {
8 | isr_install();
9 | irq_install();
10 |
11 | kprint("Type something, it will go through the kernel\n"
12 | "Type END to halt the CPU or PAGE to request a kmalloc()\n> ");
13 | }
14 |
15 | void user_input(char *input) {
16 | if (strcmp(input, "END") == 0) {
17 | kprint("Stopping the CPU. Bye!\n");
18 | asm volatile("hlt");
19 | } else if (strcmp(input, "PAGE") == 0) {
20 | /* Lesson 22: Code to test kmalloc, the rest is unchanged */
21 | u32 phys_addr;
22 | u32 page = kmalloc(1000, 1, &phys_addr);
23 | char page_str[16] = "";
24 | hex_to_ascii(page, page_str);
25 | char phys_str[16] = "";
26 | hex_to_ascii(phys_addr, phys_str);
27 | kprint("Page: ");
28 | kprint(page_str);
29 | kprint(", physical address: ");
30 | kprint(phys_str);
31 | kprint("\n");
32 | }
33 | kprint("You said: ");
34 | kprint(input);
35 | kprint("\n> ");
36 | }
37 |
--------------------------------------------------------------------------------
/22-malloc/kernel/kernel.h:
--------------------------------------------------------------------------------
1 | #ifndef KERNEL_H
2 | #define KERNEL_H
3 |
4 | void user_input(char *input);
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/22-malloc/libc/function.h:
--------------------------------------------------------------------------------
1 | #ifndef FUNCTION_H
2 | #define FUNCTION_H
3 |
4 | /* Sometimes we want to keep parameters to a function for later use
5 | * and this is a solution to avoid the 'unused parameter' compiler warning */
6 | #define UNUSED(x) (void)(x)
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/22-malloc/libc/mem.c:
--------------------------------------------------------------------------------
1 | #include "mem.h"
2 |
3 | void memory_copy(u8 *source, u8 *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(u8 *dest, u8 val, u32 len) {
11 | u8 *temp = (u8 *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
15 | /* This should be computed at link time, but a hardcoded
16 | * value is fine for now. Remember that our kernel starts
17 | * at 0x1000 as defined on the Makefile */
18 | u32 free_mem_addr = 0x10000;
19 | /* Implementation is just a pointer to some free memory which
20 | * keeps growing */
21 | u32 kmalloc(u32 size, int align, u32 *phys_addr) {
22 | /* Pages are aligned to 4K, or 0x1000 */
23 | if (align == 1 && (free_mem_addr & 0xFFFFF000)) {
24 | free_mem_addr &= 0xFFFFF000;
25 | free_mem_addr += 0x1000;
26 | }
27 | /* Save also the physical address */
28 | if (phys_addr) *phys_addr = free_mem_addr;
29 |
30 | u32 ret = free_mem_addr;
31 | free_mem_addr += size; /* Remember to increment the pointer */
32 | return ret;
33 | }
34 |
--------------------------------------------------------------------------------
/22-malloc/libc/mem.h:
--------------------------------------------------------------------------------
1 | #ifndef MEM_H
2 | #define MEM_H
3 |
4 | #include "../cpu/type.h"
5 |
6 | void memory_copy(u8 *source, u8 *dest, int nbytes);
7 | void memory_set(u8 *dest, u8 val, u32 len);
8 |
9 | /* At this stage there is no 'free' implemented. */
10 | u32 kmalloc(u32 size, int align, u32 *phys_addr);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/22-malloc/libc/string.c:
--------------------------------------------------------------------------------
1 | #include "string.h"
2 | #include "../cpu/type.h"
3 |
4 | /**
5 | * K&R implementation
6 | */
7 | void int_to_ascii(int n, char str[]) {
8 | int i, sign;
9 | if ((sign = n) < 0) n = -n;
10 | i = 0;
11 | do {
12 | str[i++] = n % 10 + '0';
13 | } while ((n /= 10) > 0);
14 |
15 | if (sign < 0) str[i++] = '-';
16 | str[i] = '\0';
17 |
18 | reverse(str);
19 | }
20 |
21 | void hex_to_ascii(int n, char str[]) {
22 | append(str, '0');
23 | append(str, 'x');
24 | char zeros = 0;
25 |
26 | s32 tmp;
27 | int i;
28 | for (i = 28; i > 0; i -= 4) {
29 | tmp = (n >> i) & 0xF;
30 | if (tmp == 0 && zeros == 0) continue;
31 | zeros = 1;
32 | if (tmp > 0xA) append(str, tmp - 0xA + 'a');
33 | else append(str, tmp + '0');
34 | }
35 |
36 | tmp = n & 0xF;
37 | if (tmp >= 0xA) append(str, tmp - 0xA + 'a');
38 | else append(str, tmp + '0');
39 | }
40 |
41 | /* K&R */
42 | void reverse(char s[]) {
43 | int c, i, j;
44 | for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
45 | c = s[i];
46 | s[i] = s[j];
47 | s[j] = c;
48 | }
49 | }
50 |
51 | /* K&R */
52 | int strlen(char s[]) {
53 | int i = 0;
54 | while (s[i] != '\0') ++i;
55 | return i;
56 | }
57 |
58 | void append(char s[], char n) {
59 | int len = strlen(s);
60 | s[len] = n;
61 | s[len+1] = '\0';
62 | }
63 |
64 | void backspace(char s[]) {
65 | int len = strlen(s);
66 | s[len-1] = '\0';
67 | }
68 |
69 | /* K&R
70 | * Returns <0 if s10 if s1>s2 */
71 | int strcmp(char s1[], char s2[]) {
72 | int i;
73 | for (i = 0; s1[i] == s2[i]; i++) {
74 | if (s1[i] == '\0') return 0;
75 | }
76 | return s1[i] - s2[i];
77 | }
78 |
--------------------------------------------------------------------------------
/22-malloc/libc/string.h:
--------------------------------------------------------------------------------
1 | #ifndef STRINGS_H
2 | #define STRINGS_H
3 |
4 | void int_to_ascii(int n, char str[]);
5 | void hex_to_ascii(int n, char str[]);
6 | void reverse(char s[]);
7 | int strlen(char s[]);
8 | void backspace(char s[]);
9 | void append(char s[], char n);
10 | int strcmp(char s1[], char s2[]);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/23-fixes/Makefile:
--------------------------------------------------------------------------------
1 | C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
2 | HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
3 | # Nice syntax for file extension replacement
4 | OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5 |
6 | # Change this if your cross-compiler is somewhere else
7 | CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8 | GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9 | # -g: Use debugging symbols in gcc
10 | CFLAGS = -g -ffreestanding -Wall -Wextra -fno-exceptions -m32
11 |
12 | # First rule is run by default
13 | os-image.bin: boot/bootsect.bin kernel.bin
14 | cat $^ > os-image.bin
15 |
16 | # '--oformat binary' deletes all symbols as a collateral, so we don't need
17 | # to 'strip' them manually on this case
18 | kernel.bin: boot/kernel_entry.o ${OBJ}
19 | i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20 |
21 | # Used for debugging purposes
22 | kernel.elf: boot/kernel_entry.o ${OBJ}
23 | i386-elf-ld -o $@ -Ttext 0x1000 $^
24 |
25 | run: os-image.bin
26 | qemu-system-i386 -fda os-image.bin
27 |
28 | # Open the connection to qemu and load our kernel-object file with symbols
29 | debug: os-image.bin kernel.elf
30 | qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
31 | ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32 |
33 | # Generic rules for wildcards
34 | # To make an object, always compile from its .c
35 | %.o: %.c ${HEADERS}
36 | ${CC} ${CFLAGS} -c $< -o $@
37 |
38 | %.o: %.asm
39 | nasm $< -f elf -o $@
40 |
41 | %.bin: %.asm
42 | nasm $< -f bin -o $@
43 |
44 | clean:
45 | rm -rf *.bin *.dis *.o os-image.bin *.elf
46 | rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o
47 |
--------------------------------------------------------------------------------
/23-fixes/boot/32bit_print.asm:
--------------------------------------------------------------------------------
1 | [bits 32] ; using 32-bit protected mode
2 |
3 | ; this is how constants are defined
4 | VIDEO_MEMORY equ 0xb8000
5 | WHITE_OB_BLACK equ 0x0f ; the color byte for each character
6 |
7 | print_string_pm:
8 | pusha
9 | mov edx, VIDEO_MEMORY
10 |
11 | print_string_pm_loop:
12 | mov al, [ebx] ; [ebx] is the address of our character
13 | mov ah, WHITE_OB_BLACK
14 |
15 | cmp al, 0 ; check if end of string
16 | je print_string_pm_done
17 |
18 | mov [edx], ax ; store character + attribute in video memory
19 | add ebx, 1 ; next char
20 | add edx, 2 ; next video memory position
21 |
22 | jmp print_string_pm_loop
23 |
24 | print_string_pm_done:
25 | popa
26 | ret
27 |
--------------------------------------------------------------------------------
/23-fixes/boot/bootsect.asm:
--------------------------------------------------------------------------------
1 | ; Identical to lesson 13's boot sector, but the %included files have new paths
2 | [org 0x7c00]
3 | KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
4 |
5 | mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
6 | mov bp, 0x9000
7 | mov sp, bp
8 |
9 | mov bx, MSG_REAL_MODE
10 | call print
11 | call print_nl
12 |
13 | call load_kernel ; read the kernel from disk
14 | call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
15 | jmp $ ; Never executed
16 |
17 | %include "boot/print.asm"
18 | %include "boot/print_hex.asm"
19 | %include "boot/disk.asm"
20 | %include "boot/gdt.asm"
21 | %include "boot/32bit_print.asm"
22 | %include "boot/switch_pm.asm"
23 |
24 | [bits 16]
25 | load_kernel:
26 | mov bx, MSG_LOAD_KERNEL
27 | call print
28 | call print_nl
29 |
30 | mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31 | mov dh, 31 ; Our future kernel will be larger, make this big
32 | mov dl, [BOOT_DRIVE]
33 | call disk_load
34 | ret
35 |
36 | [bits 32]
37 | BEGIN_PM:
38 | mov ebx, MSG_PROT_MODE
39 | call print_string_pm
40 | call KERNEL_OFFSET ; Give control to the kernel
41 | jmp $ ; Stay here when the kernel returns control to us (if ever)
42 |
43 |
44 | BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
45 | MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
46 | MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
47 | MSG_LOAD_KERNEL db "Loading kernel into memory", 0
48 | MSG_RETURNED_KERNEL db "Returned from kernel. Error?", 0
49 |
50 | ; padding
51 | times 510 - ($-$$) db 0
52 | dw 0xaa55
53 |
--------------------------------------------------------------------------------
/23-fixes/boot/disk.asm:
--------------------------------------------------------------------------------
1 | ; load 'dh' sectors from drive 'dl' into ES:BX
2 | disk_load:
3 | pusha
4 | ; reading from disk requires setting specific values in all registers
5 | ; so we will overwrite our input parameters from 'dx'. Let's save it
6 | ; to the stack for later use.
7 | push dx
8 |
9 | mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
10 | mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
11 | mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
12 | ; 0x01 is our boot sector, 0x02 is the first 'available' sector
13 | mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
14 | ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
15 | ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
16 | mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
17 |
18 | ; [es:bx] <- pointer to buffer where the data will be stored
19 | ; caller sets it up for us, and it is actually the standard location for int 13h
20 | int 0x13 ; BIOS interrupt
21 | jc disk_error ; if error (stored in the carry bit)
22 |
23 | pop dx
24 | cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
25 | jne sectors_error
26 | popa
27 | ret
28 |
29 |
30 | disk_error:
31 | mov bx, DISK_ERROR
32 | call print
33 | call print_nl
34 | mov dh, ah ; ah = error code, dl = disk drive that dropped the error
35 | call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
36 | jmp disk_loop
37 |
38 | sectors_error:
39 | mov bx, SECTORS_ERROR
40 | call print
41 |
42 | disk_loop:
43 | jmp $
44 |
45 | DISK_ERROR: db "Disk read error", 0
46 | SECTORS_ERROR: db "Incorrect number of sectors read", 0
47 |
--------------------------------------------------------------------------------
/23-fixes/boot/gdt.asm:
--------------------------------------------------------------------------------
1 | gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
2 | ; the GDT starts with a null 8-byte
3 | dd 0x0 ; 4 byte
4 | dd 0x0 ; 4 byte
5 |
6 | ; GDT for code segment. base = 0x00000000, length = 0xfffff
7 | ; for flags, refer to os-dev.pdf document, page 36
8 | gdt_code:
9 | dw 0xffff ; segment length, bits 0-15
10 | dw 0x0 ; segment base, bits 0-15
11 | db 0x0 ; segment base, bits 16-23
12 | db 10011010b ; flags (8 bits)
13 | db 11001111b ; flags (4 bits) + segment length, bits 16-19
14 | db 0x0 ; segment base, bits 24-31
15 |
16 | ; GDT for data segment. base and length identical to code segment
17 | ; some flags changed, again, refer to os-dev.pdf
18 | gdt_data:
19 | dw 0xffff
20 | dw 0x0
21 | db 0x0
22 | db 10010010b
23 | db 11001111b
24 | db 0x0
25 |
26 | gdt_end:
27 |
28 | ; GDT descriptor
29 | gdt_descriptor:
30 | dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
31 | dd gdt_start ; address (32 bit)
32 |
33 | ; define some constants for later use
34 | CODE_SEG equ gdt_code - gdt_start
35 | DATA_SEG equ gdt_data - gdt_start
36 |
--------------------------------------------------------------------------------
/23-fixes/boot/kernel_entry.asm:
--------------------------------------------------------------------------------
1 | global _start;
2 | [bits 32]
3 |
4 | _start:
5 | [extern kernel_main] ; Define calling point. Must have same name as kernel.c 'main' function
6 | call kernel_main ; Calls the C function. The linker will know where it is placed in memory
7 | jmp $
8 |
--------------------------------------------------------------------------------
/23-fixes/boot/print.asm:
--------------------------------------------------------------------------------
1 | print:
2 | pusha
3 |
4 | ; keep this in mind:
5 | ; while (string[i] != 0) { print string[i]; i++ }
6 |
7 | ; the comparison for string end (null byte)
8 | start:
9 | mov al, [bx] ; 'bx' is the base address for the string
10 | cmp al, 0
11 | je done
12 |
13 | ; the part where we print with the BIOS help
14 | mov ah, 0x0e
15 | int 0x10 ; 'al' already contains the char
16 |
17 | ; increment pointer and do next loop
18 | add bx, 1
19 | jmp start
20 |
21 | done:
22 | popa
23 | ret
24 |
25 |
26 |
27 | print_nl:
28 | pusha
29 |
30 | mov ah, 0x0e
31 | mov al, 0x0a ; newline char
32 | int 0x10
33 | mov al, 0x0d ; carriage return
34 | int 0x10
35 |
36 | popa
37 | ret
38 |
--------------------------------------------------------------------------------
/23-fixes/boot/print_hex.asm:
--------------------------------------------------------------------------------
1 | ; receiving the data in 'dx'
2 | ; For the examples we'll assume that we're called with dx=0x1234
3 | print_hex:
4 | pusha
5 |
6 | mov cx, 0 ; our index variable
7 |
8 | ; Strategy: get the last char of 'dx', then convert to ASCII
9 | ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
10 | ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
11 | ; Then, move the ASCII byte to the correct position on the resulting string
12 | hex_loop:
13 | cmp cx, 4 ; loop 4 times
14 | je end
15 |
16 | ; 1. convert last char of 'dx' to ascii
17 | mov ax, dx ; we will use 'ax' as our working register
18 | and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
19 | add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
20 | cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
21 | jle step2
22 | add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
23 |
24 | step2:
25 | ; 2. get the correct position of the string to place our ASCII char
26 | ; bx <- base address + string length - index of char
27 | mov bx, HEX_OUT + 5 ; base + length
28 | sub bx, cx ; our index variable
29 | mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
30 | ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
31 |
32 | ; increment index and loop
33 | add cx, 1
34 | jmp hex_loop
35 |
36 | end:
37 | ; prepare the parameter and call the function
38 | ; remember that print receives parameters in 'bx'
39 | mov bx, HEX_OUT
40 | call print
41 |
42 | popa
43 | ret
44 |
45 | HEX_OUT:
46 | db '0x0000',0 ; reserve memory for our new string
47 |
--------------------------------------------------------------------------------
/23-fixes/boot/switch_pm.asm:
--------------------------------------------------------------------------------
1 | [bits 16]
2 | switch_to_pm:
3 | cli ; 1. disable interrupts
4 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
5 | mov eax, cr0
6 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
7 | mov cr0, eax
8 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
9 |
10 | [bits 32]
11 | init_pm: ; we are now using 32-bit instructions
12 | mov ax, DATA_SEG ; 5. update the segment registers
13 | mov ds, ax
14 | mov ss, ax
15 | mov es, ax
16 | mov fs, ax
17 | mov gs, ax
18 |
19 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
20 | mov esp, ebp
21 |
22 | call BEGIN_PM ; 7. Call a well-known label with useful code
23 |
--------------------------------------------------------------------------------
/23-fixes/cpu/idt.c:
--------------------------------------------------------------------------------
1 | #include "idt.h"
2 | #include "type.h"
3 |
4 | void set_idt_gate(int n, uint32_t handler) {
5 | idt[n].low_offset = low_16(handler);
6 | idt[n].sel = KERNEL_CS;
7 | idt[n].always0 = 0;
8 | idt[n].flags = 0x8E;
9 | idt[n].high_offset = high_16(handler);
10 | }
11 |
12 | void set_idt() {
13 | idt_reg.base = (uint32_t) &idt;
14 | idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
15 | /* Don't make the mistake of loading &idt -- always load &idt_reg */
16 | asm volatile("lidtl (%0)" : : "r" (&idt_reg));
17 | }
18 |
--------------------------------------------------------------------------------
/23-fixes/cpu/idt.h:
--------------------------------------------------------------------------------
1 | #ifndef IDT_H
2 | #define IDT_H
3 |
4 | #include
5 |
6 | /* Segment selectors */
7 | #define KERNEL_CS 0x08
8 |
9 | /* How every interrupt gate (handler) is defined */
10 | typedef struct {
11 | uint16_t low_offset; /* Lower 16 bits of handler function address */
12 | uint16_t sel; /* Kernel segment selector */
13 | uint8_t always0;
14 | /* First byte
15 | * Bit 7: "Interrupt is present"
16 | * Bits 6-5: Privilege level of caller (0=kernel..3=user)
17 | * Bit 4: Set to 0 for interrupt gates
18 | * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19 | uint8_t flags;
20 | uint16_t high_offset; /* Higher 16 bits of handler function address */
21 | } __attribute__((packed)) idt_gate_t ;
22 |
23 | /* A pointer to the array of interrupt handlers.
24 | * Assembly instruction 'lidt' will read it */
25 | typedef struct {
26 | uint16_t limit;
27 | uint32_t base;
28 | } __attribute__((packed)) idt_register_t;
29 |
30 | #define IDT_ENTRIES 256
31 | idt_gate_t idt[IDT_ENTRIES];
32 | idt_register_t idt_reg;
33 |
34 |
35 | /* Functions implemented in idt.c */
36 | void set_idt_gate(int n, uint32_t handler);
37 | void set_idt();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/23-fixes/cpu/isr.h:
--------------------------------------------------------------------------------
1 | #ifndef ISR_H
2 | #define ISR_H
3 |
4 | #include
5 |
6 | /* ISRs reserved for CPU exceptions */
7 | extern void isr0();
8 | extern void isr1();
9 | extern void isr2();
10 | extern void isr3();
11 | extern void isr4();
12 | extern void isr5();
13 | extern void isr6();
14 | extern void isr7();
15 | extern void isr8();
16 | extern void isr9();
17 | extern void isr10();
18 | extern void isr11();
19 | extern void isr12();
20 | extern void isr13();
21 | extern void isr14();
22 | extern void isr15();
23 | extern void isr16();
24 | extern void isr17();
25 | extern void isr18();
26 | extern void isr19();
27 | extern void isr20();
28 | extern void isr21();
29 | extern void isr22();
30 | extern void isr23();
31 | extern void isr24();
32 | extern void isr25();
33 | extern void isr26();
34 | extern void isr27();
35 | extern void isr28();
36 | extern void isr29();
37 | extern void isr30();
38 | extern void isr31();
39 | /* IRQ definitions */
40 | extern void irq0();
41 | extern void irq1();
42 | extern void irq2();
43 | extern void irq3();
44 | extern void irq4();
45 | extern void irq5();
46 | extern void irq6();
47 | extern void irq7();
48 | extern void irq8();
49 | extern void irq9();
50 | extern void irq10();
51 | extern void irq11();
52 | extern void irq12();
53 | extern void irq13();
54 | extern void irq14();
55 | extern void irq15();
56 |
57 | #define IRQ0 32
58 | #define IRQ1 33
59 | #define IRQ2 34
60 | #define IRQ3 35
61 | #define IRQ4 36
62 | #define IRQ5 37
63 | #define IRQ6 38
64 | #define IRQ7 39
65 | #define IRQ8 40
66 | #define IRQ9 41
67 | #define IRQ10 42
68 | #define IRQ11 43
69 | #define IRQ12 44
70 | #define IRQ13 45
71 | #define IRQ14 46
72 | #define IRQ15 47
73 |
74 | /* Struct which aggregates many registers.
75 | * It matches exactly the pushes on interrupt.asm. From the bottom:
76 | * - Pushed by the processor automatically
77 | * - `push byte`s on the isr-specific code: error code, then int number
78 | * - All the registers by pusha
79 | * - `push eax` whose lower 16-bits contain DS
80 | */
81 | typedef struct {
82 | uint32_t ds; /* Data segment selector */
83 | uint32_t edi, esi, ebp, useless, ebx, edx, ecx, eax; /* Pushed by pusha. */
84 | uint32_t int_no, err_code; /* Interrupt number and error code (if applicable) */
85 | uint32_t eip, cs, eflags, esp, ss; /* Pushed by the processor automatically */
86 | } registers_t;
87 |
88 | void isr_install();
89 | void isr_handler(registers_t *r);
90 | void irq_install();
91 |
92 | typedef void (*isr_t)(registers_t*);
93 | void register_interrupt_handler(uint8_t n, isr_t handler);
94 |
95 | #endif
96 |
--------------------------------------------------------------------------------
/23-fixes/cpu/ports.c:
--------------------------------------------------------------------------------
1 | #include "ports.h"
2 |
3 | /**
4 | * Read a byte from the specified port
5 | */
6 | uint8_t port_byte_in (uint16_t port) {
7 | uint8_t result;
8 | /* Inline assembler syntax
9 | * !! Notice how the source and destination registers are switched from NASM !!
10 | *
11 | * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
12 | * '"d" (port)': map the C variable '(port)' into e'd'x register
13 | *
14 | * Inputs and outputs are separated by colons
15 | */
16 | asm("in %%dx, %%al" : "=a" (result) : "d" (port));
17 | return result;
18 | }
19 |
20 | void port_byte_out (uint16_t port, uint8_t data) {
21 | /* Notice how here both registers are mapped to C variables and
22 | * nothing is returned, thus, no equals '=' in the asm syntax
23 | * However we see a comma since there are two variables in the input area
24 | * and none in the 'return' area
25 | */
26 | asm volatile("out %%al, %%dx" : : "a" (data), "d" (port));
27 | }
28 |
29 | uint16_t port_word_in (uint16_t port) {
30 | uint16_t result;
31 | asm("in %%dx, %%ax" : "=a" (result) : "d" (port));
32 | return result;
33 | }
34 |
35 | void port_word_out (uint16_t port, uint16_t data) {
36 | asm volatile("out %%ax, %%dx" : : "a" (data), "d" (port));
37 | }
38 |
--------------------------------------------------------------------------------
/23-fixes/cpu/ports.h:
--------------------------------------------------------------------------------
1 | #ifndef PORTS_H
2 | #define PORTS_H
3 |
4 | #include
5 |
6 | unsigned char port_byte_in (uint16_t port);
7 | void port_byte_out (uint16_t port, uint8_t data);
8 | unsigned short port_word_in (uint16_t port);
9 | void port_word_out (uint16_t port, uint16_t data);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/23-fixes/cpu/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 | #include "isr.h"
3 | #include "ports.h"
4 | #include "../libc/function.h"
5 |
6 | uint32_t tick = 0;
7 |
8 | static void timer_callback(registers_t *regs) {
9 | tick++;
10 | UNUSED(regs);
11 | }
12 |
13 | void init_timer(uint32_t freq) {
14 | /* Install the function we just wrote */
15 | register_interrupt_handler(IRQ0, timer_callback);
16 |
17 | /* Get the PIT value: hardware clock at 1193180 Hz */
18 | uint32_t divisor = 1193180 / freq;
19 | uint8_t low = (uint8_t)(divisor & 0xFF);
20 | uint8_t high = (uint8_t)( (divisor >> 8) & 0xFF);
21 | /* Send the command */
22 | port_byte_out(0x43, 0x36); /* Command port */
23 | port_byte_out(0x40, low);
24 | port_byte_out(0x40, high);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/23-fixes/cpu/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_H
2 | #define TIMER_H
3 |
4 | #include
5 |
6 | void init_timer(uint32_t freq);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/23-fixes/cpu/type.h:
--------------------------------------------------------------------------------
1 | #ifndef TYPE_H
2 | #define TYPE_H
3 |
4 | #include
5 |
6 | #define low_16(address) (uint16_t)((address) & 0xFFFF)
7 | #define high_16(address) (uint16_t)(((address) >> 16) & 0xFFFF)
8 |
9 | #endif
10 |
--------------------------------------------------------------------------------
/23-fixes/drivers/keyboard.c:
--------------------------------------------------------------------------------
1 | #include "keyboard.h"
2 | #include "../cpu/ports.h"
3 | #include "../cpu/isr.h"
4 | #include "screen.h"
5 | #include "../libc/string.h"
6 | #include "../libc/function.h"
7 | #include "../kernel/kernel.h"
8 | #include
9 |
10 | #define BACKSPACE 0x0E
11 | #define ENTER 0x1C
12 |
13 | static char key_buffer[256];
14 |
15 | #define SC_MAX 57
16 | const char *sc_name[] = { "ERROR", "Esc", "1", "2", "3", "4", "5", "6",
17 | "7", "8", "9", "0", "-", "=", "Backspace", "Tab", "Q", "W", "E",
18 | "R", "T", "Y", "U", "I", "O", "P", "[", "]", "Enter", "Lctrl",
19 | "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "`",
20 | "LShift", "\\", "Z", "X", "C", "V", "B", "N", "M", ",", ".",
21 | "/", "RShift", "Keypad *", "LAlt", "Spacebar"};
22 | const char sc_ascii[] = { '?', '?', '1', '2', '3', '4', '5', '6',
23 | '7', '8', '9', '0', '-', '=', '?', '?', 'Q', 'W', 'E', 'R', 'T', 'Y',
24 | 'U', 'I', 'O', 'P', '[', ']', '?', '?', 'A', 'S', 'D', 'F', 'G',
25 | 'H', 'J', 'K', 'L', ';', '\'', '`', '?', '\\', 'Z', 'X', 'C', 'V',
26 | 'B', 'N', 'M', ',', '.', '/', '?', '?', '?', ' '};
27 |
28 | static void keyboard_callback(registers_t *regs) {
29 | /* The PIC leaves us the scancode in port 0x60 */
30 | uint8_t scancode = port_byte_in(0x60);
31 |
32 | if (scancode > SC_MAX) return;
33 | if (scancode == BACKSPACE) {
34 | backspace(key_buffer);
35 | kprint_backspace();
36 | } else if (scancode == ENTER) {
37 | kprint("\n");
38 | user_input(key_buffer); /* kernel-controlled function */
39 | key_buffer[0] = '\0';
40 | } else {
41 | char letter = sc_ascii[(int)scancode];
42 | /* Remember that kprint only accepts char[] */
43 | char str[2] = {letter, '\0'};
44 | append(key_buffer, letter);
45 | kprint(str);
46 | }
47 | UNUSED(regs);
48 | }
49 |
50 | void init_keyboard() {
51 | register_interrupt_handler(IRQ1, keyboard_callback);
52 | }
53 |
--------------------------------------------------------------------------------
/23-fixes/drivers/keyboard.h:
--------------------------------------------------------------------------------
1 | void init_keyboard();
2 |
--------------------------------------------------------------------------------
/23-fixes/drivers/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #define VIDEO_ADDRESS 0xb8000
5 | #define MAX_ROWS 25
6 | #define MAX_COLS 80
7 | #define WHITE_ON_BLACK 0x0f
8 | #define RED_ON_WHITE 0xf4
9 |
10 | /* Screen i/o ports */
11 | #define REG_SCREEN_CTRL 0x3d4
12 | #define REG_SCREEN_DATA 0x3d5
13 |
14 | /* Public kernel API */
15 | void clear_screen();
16 | void kprint_at(char *message, int col, int row);
17 | void kprint(char *message);
18 | void kprint_backspace();
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/23-fixes/kernel/kernel.c:
--------------------------------------------------------------------------------
1 | #include "../cpu/isr.h"
2 | #include "../drivers/screen.h"
3 | #include "kernel.h"
4 | #include "../libc/string.h"
5 | #include "../libc/mem.h"
6 | #include
7 |
8 | void kernel_main() {
9 | isr_install();
10 | irq_install();
11 |
12 | asm("int $2");
13 | asm("int $3");
14 |
15 | kprint("Type something, it will go through the kernel\n"
16 | "Type END to halt the CPU or PAGE to request a kmalloc()\n> ");
17 | }
18 |
19 | void user_input(char *input) {
20 | if (strcmp(input, "END") == 0) {
21 | kprint("Stopping the CPU. Bye!\n");
22 | asm volatile("hlt");
23 | } else if (strcmp(input, "PAGE") == 0) {
24 | /* Lesson 22: Code to test kmalloc, the rest is unchanged */
25 | uint32_t phys_addr;
26 | uint32_t page = kmalloc(1000, 1, &phys_addr);
27 | char page_str[16] = "";
28 | hex_to_ascii(page, page_str);
29 | char phys_str[16] = "";
30 | hex_to_ascii(phys_addr, phys_str);
31 | kprint("Page: ");
32 | kprint(page_str);
33 | kprint(", physical address: ");
34 | kprint(phys_str);
35 | kprint("\n");
36 | }
37 | kprint("You said: ");
38 | kprint(input);
39 | kprint("\n> ");
40 | }
41 |
--------------------------------------------------------------------------------
/23-fixes/kernel/kernel.h:
--------------------------------------------------------------------------------
1 | #ifndef KERNEL_H
2 | #define KERNEL_H
3 |
4 | void user_input(char *input);
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/23-fixes/libc/function.h:
--------------------------------------------------------------------------------
1 | #ifndef FUNCTION_H
2 | #define FUNCTION_H
3 |
4 | /* Sometimes we want to keep parameters to a function for later use
5 | * and this is a solution to avoid the 'unused parameter' compiler warning */
6 | #define UNUSED(x) (void)(x)
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/23-fixes/libc/mem.c:
--------------------------------------------------------------------------------
1 | #include "mem.h"
2 |
3 | void memory_copy(uint8_t *source, uint8_t *dest, int nbytes) {
4 | int i;
5 | for (i = 0; i < nbytes; i++) {
6 | *(dest + i) = *(source + i);
7 | }
8 | }
9 |
10 | void memory_set(uint8_t *dest, uint8_t val, uint32_t len) {
11 | uint8_t *temp = (uint8_t *)dest;
12 | for ( ; len != 0; len--) *temp++ = val;
13 | }
14 |
15 | /* This should be computed at link time, but a hardcoded
16 | * value is fine for now. Remember that our kernel starts
17 | * at 0x1000 as defined on the Makefile */
18 | uint32_t free_mem_addr = 0x10000;
19 | /* Implementation is just a pointer to some free memory which
20 | * keeps growing */
21 | uint32_t kmalloc(size_t size, int align, uint32_t *phys_addr) {
22 | /* Pages are aligned to 4K, or 0x1000 */
23 | if (align == 1 && (free_mem_addr & 0xFFFFF000)) {
24 | free_mem_addr &= 0xFFFFF000;
25 | free_mem_addr += 0x1000;
26 | }
27 | /* Save also the physical address */
28 | if (phys_addr) *phys_addr = free_mem_addr;
29 |
30 | uint32_t ret = free_mem_addr;
31 | free_mem_addr += size; /* Remember to increment the pointer */
32 | return ret;
33 | }
34 |
--------------------------------------------------------------------------------
/23-fixes/libc/mem.h:
--------------------------------------------------------------------------------
1 | #ifndef MEM_H
2 | #define MEM_H
3 |
4 | #include
5 | #include
6 |
7 | void memory_copy(uint8_t *source, uint8_t *dest, int nbytes);
8 | void memory_set(uint8_t *dest, uint8_t val, uint32_t len);
9 |
10 | /* At this stage there is no 'free' implemented. */
11 | uint32_t kmalloc(size_t size, int align, uint32_t *phys_addr);
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/23-fixes/libc/string.c:
--------------------------------------------------------------------------------
1 | #include "string.h"
2 | #include
3 |
4 | /**
5 | * K&R implementation
6 | */
7 | void int_to_ascii(int n, char str[]) {
8 | int i, sign;
9 | if ((sign = n) < 0) n = -n;
10 | i = 0;
11 | do {
12 | str[i++] = n % 10 + '0';
13 | } while ((n /= 10) > 0);
14 |
15 | if (sign < 0) str[i++] = '-';
16 | str[i] = '\0';
17 |
18 | reverse(str);
19 | }
20 |
21 | void hex_to_ascii(int n, char str[]) {
22 | append(str, '0');
23 | append(str, 'x');
24 | char zeros = 0;
25 |
26 | int32_t tmp;
27 | int i;
28 | for (i = 28; i > 0; i -= 4) {
29 | tmp = (n >> i) & 0xF;
30 | if (tmp == 0 && zeros == 0) continue;
31 | zeros = 1;
32 | if (tmp > 0xA) append(str, tmp - 0xA + 'a');
33 | else append(str, tmp + '0');
34 | }
35 |
36 | tmp = n & 0xF;
37 | if (tmp >= 0xA) append(str, tmp - 0xA + 'a');
38 | else append(str, tmp + '0');
39 | }
40 |
41 | /* K&R */
42 | void reverse(char s[]) {
43 | int c, i, j;
44 | for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
45 | c = s[i];
46 | s[i] = s[j];
47 | s[j] = c;
48 | }
49 | }
50 |
51 | /* K&R */
52 | int strlen(char s[]) {
53 | int i = 0;
54 | while (s[i] != '\0') ++i;
55 | return i;
56 | }
57 |
58 | void append(char s[], char n) {
59 | int len = strlen(s);
60 | s[len] = n;
61 | s[len+1] = '\0';
62 | }
63 |
64 | void backspace(char s[]) {
65 | int len = strlen(s);
66 | s[len-1] = '\0';
67 | }
68 |
69 | /* K&R
70 | * Returns <0 if s10 if s1>s2 */
71 | int strcmp(char s1[], char s2[]) {
72 | int i;
73 | for (i = 0; s1[i] == s2[i]; i++) {
74 | if (s1[i] == '\0') return 0;
75 | }
76 | return s1[i] - s2[i];
77 | }
78 |
--------------------------------------------------------------------------------
/23-fixes/libc/string.h:
--------------------------------------------------------------------------------
1 | #ifndef STRINGS_H
2 | #define STRINGS_H
3 |
4 | void int_to_ascii(int n, char str[]);
5 | void hex_to_ascii(int n, char str[]);
6 | void reverse(char s[]);
7 | int strlen(char s[]);
8 | void backspace(char s[]);
9 | void append(char s[], char n);
10 | int strcmp(char s1[], char s2[]);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/24-el-capitan/Makefile:
--------------------------------------------------------------------------------
1 | ../23-fixes/Makefile
--------------------------------------------------------------------------------
/24-el-capitan/README.md:
--------------------------------------------------------------------------------
1 | **Goal: Update our build system to El Capitan**
2 |
3 | If you were following this guide from the beginning and upgraded to El Capitan only
4 | to find that Makefiles don't compile anymore, follow these instructions to upgrade
5 | your cross-compiler.
6 |
7 | Otherwise, move on to the next lesson
8 |
9 | Upgrading the cross-compiler
10 | ----------------------------
11 |
12 | We will follow the same instructions as in lesson 11, more or less.
13 |
14 | First, run `brew upgrade` and you will get your gcc upgraded to version 5.0 (at the time this guide was written)
15 |
16 | Then run `xcode-select --install` to update OSX commandline tools
17 |
18 | Once installed, find where your packaged gcc is (remember, not clang) and export it. For example:
19 |
20 | ```
21 | export CC=/usr/local/bin/gcc-5
22 | export LD=/usr/local/bin/gcc-5
23 | ```
24 |
25 | We will need to recompile binutils and our cross-compiled gcc. Export the targets and prefix:
26 |
27 | ```
28 | export PREFIX="/usr/local/i386elfgcc"
29 | export TARGET=i386-elf
30 | export PATH="$PREFIX/bin:$PATH"
31 | ```
32 |
33 | binutils
34 | --------
35 |
36 | Remember: always be careful before pasting walls of text from the internet. I recommend copying line by line.
37 |
38 | ```sh
39 | mkdir /tmp/src
40 | cd /tmp/src
41 | curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz # If the link 404's, look for a more recent version
42 | tar xf binutils-2.24.tar.gz
43 | mkdir binutils-build
44 | cd binutils-build
45 | ../binutils-2.24/configure --target=$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix=$PREFIX 2>&1 | tee configure.log
46 | make all install 2>&1 | tee make.log
47 | ```
48 |
49 |
50 | gcc
51 | ---
52 | ```sh
53 | cd /tmp/src
54 | curl -O http://mirror.bbln.org/gcc/releases/gcc-4.9.1/gcc-4.9.1.tar.bz2
55 | tar xf gcc-4.9.1.tar.bz2
56 | mkdir gcc-build
57 | cd gcc-build
58 | ../gcc-4.9.1/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
59 | make all-gcc
60 | make all-target-libgcc
61 | make install-gcc
62 | make install-target-libgcc
63 | ```
64 |
65 |
66 | Now try to type `make` on this lesson's folder and check that everything compiles smoothly
67 |
--------------------------------------------------------------------------------
/24-el-capitan/boot:
--------------------------------------------------------------------------------
1 | ../23-fixes/boot
--------------------------------------------------------------------------------
/24-el-capitan/cpu:
--------------------------------------------------------------------------------
1 | ../23-fixes/cpu
--------------------------------------------------------------------------------
/24-el-capitan/drivers:
--------------------------------------------------------------------------------
1 | ../23-fixes/drivers
--------------------------------------------------------------------------------
/24-el-capitan/kernel:
--------------------------------------------------------------------------------
1 | ../23-fixes/kernel
--------------------------------------------------------------------------------
/24-el-capitan/libc:
--------------------------------------------------------------------------------
1 | ../23-fixes/libc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2018, Carlos Fenollosa
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------