├── .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 | --------------------------------------------------------------------------------