Showing preview only (304K chars total). Download the full file or copy to clipboard to get everything.
Repository: cfenollosa/os-tutorial
Branch: master
Commit: 8002382ee56f
Files: 219
Total size: 262.2 KB
Directory structure:
gitextract_elg482xn/
├── .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
│ ├── cpu/
│ │ ├── idt.c
│ │ ├── idt.h
│ │ ├── interrupt.asm
│ │ ├── isr.c
│ │ ├── isr.h
│ │ └── types.h
│ └── kernel/
│ ├── kernel.c
│ ├── util.c
│ └── util.h
├── 20-interrupts-timer/
│ ├── Makefile
│ ├── README.md
│ ├── cpu/
│ │ ├── timer.c
│ │ └── timer.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
│ ├── cpu/
│ │ ├── idt.c
│ │ ├── idt.h
│ │ ├── interrupt.asm
│ │ ├── isr.c
│ │ ├── isr.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
│ ├── 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/
│ └── README.md
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.bin
*.o
*.swp
*.dis
*.elf
*.sym
.DS_STORE
================================================
FILE: 00-environment/README.md
================================================
*Concepts you may want to Google beforehand: linux, mac, terminal, compiler, emulator, nasm, qemu*
**Goal: Install the software required to run this tutorial**
I'm working on a Mac, though Linux is better because it will have all the standard tools already
available for you.
On a mac, [install Homebrew](http://brew.sh) and then `brew install qemu nasm`
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`
On some systems qemu is split into multiple binaries. You may want
to call `qemu-system-x86_64 binfile`
================================================
FILE: 01-bootsector-barebones/README.md
================================================
*Concepts you may want to Google beforehand: assembler, BIOS*
**Goal: Create a file which the BIOS interprets as a bootable disk**
This is very exciting, we're going to create our own boot sector!
Theory
------
When the computer boots, the BIOS doesn't know how to load the OS, so it
delegates that task to the boot sector. Thus, the boot sector must be
placed in a known, standard location. That location is the first sector
of the disk (cylinder 0, head 0, sector 0) and it takes 512 bytes.
To make sure that the "disk is bootable", the BIOS checks that bytes
511 and 512 of the alleged boot sector are bytes `0xAA55`.
This is the simplest boot sector ever:
```
e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 29 more lines with sixteen zero-bytes each ]
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
```
It is basically all zeros, ending with the 16-bit value
`0xAA55` (beware of endianness, x86 is little-endian).
The first three bytes perform an infinite jump
Simplest boot sector ever
-------------------------
You can either write the above 512 bytes
with a binary editor, or just write a very
simple assembler code:
```nasm
; Infinite loop (e9 fd ff)
loop:
jmp loop
; Fill with 510 zeros minus the size of the previous code
times 510-($-$$) db 0
; Magic number
dw 0xaa55
```
To compile:
`nasm -f bin boot_sect_simple.asm -o boot_sect_simple.bin`
> OSX warning: if this drops an error, read chapter 00 again
I know you're anxious to try it out (I am!), so let's do it:
`qemu boot_sect_simple.bin`
> 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).
You will see a window open which says "Booting from Hard Disk..." and
nothing else. When was the last time you were so excited to see an infinite
loop? ;-)
================================================
FILE: 01-bootsector-barebones/boot_sect_simple.asm
================================================
; A simple boot sector program that loops forever
loop:
jmp loop
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 02-bootsector-print/README.md
================================================
*Concepts you may want to Google beforehand: interrupts, CPU
registers*
**Goal: Make our previously silent boot sector print some text**
We will improve a bit on our infinite-loop boot sector and print
something on the screen. We will raise an interrupt for this.
On this example we are going to write each character of the "Hello"
word into the register `al` (lower part of `ax`), the bytes `0x0e`
into `ah` (the higher part of `ax`) and raise interrupt `0x10` which
is a general interrupt for video services.
`0x0e` on `ah` tells the video interrupt that the actual function
we want to run is to 'write the contents of `al` in tty mode'.
We will set tty mode only once though in the real world we
cannot be sure that the contents of `ah` are constant. Some other
process may run on the CPU while we are sleeping, not clean
up properly and leave garbage data on `ah`.
For this example we don't need to take care of that since we are
the only thing running on the CPU.
Our new boot sector looks like this:
```nasm
mov ah, 0x0e ; tty mode
mov al, 'H'
int 0x10
mov al, 'e'
int 0x10
mov al, 'l'
int 0x10
int 0x10 ; 'l' is still on al, remember?
mov al, 'o'
int 0x10
jmp $ ; jump to current address = infinite loop
; padding and magic number
times 510 - ($-$$) db 0
dw 0xaa55
```
You can examine the binary data with `xxd file.bin`
Anyway, you know the drill:
`nasm -fbin boot_sect_hello.asm -o boot_sect_hello.bin`
`qemu boot_sect_hello.bin`
Your boot sector will say 'Hello' and hang on an infinite loop
================================================
FILE: 02-bootsector-print/boot_sect_hello.asm
================================================
mov ah, 0x0e ; tty mode
mov al, 'H'
int 0x10
mov al, 'e'
int 0x10
mov al, 'l'
int 0x10
int 0x10 ; 'l' is still on al, remember?
mov al, 'o'
int 0x10
jmp $ ; jump to current address = infinite loop
; padding and magic number
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 03-bootsector-memory/README.md
================================================
*Concepts you may want to Google beforehand: memory offsets, pointers*
**Goal: Learn how the computer memory is organized**
Please open page 14 [of this document](
http://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf)<sup>1</sup>
and look at the figure with the memory layout.
The only goal of this lesson is to learn where the boot sector is stored
I could just bluntly tell you that the BIOS places it at `0x7C00` and
get it done with, but an example with wrong solutions will make things clearer.
We want to print an X on screen. We will try 4 different strategies
and see which ones work and why.
**Open the file `boot_sect_memory.asm`**
First, we will define the X as data, with a label:
```nasm
the_secret:
db "X"
```
Then we will try to access `the_secret` in many different ways:
1. `mov al, the_secret`
2. `mov al, [the_secret]`
3. `mov al, the_secret + 0x7C00`
4. `mov al, 2d + 0x7C00`, where `2d` is the actual position of the 'X' byte in the binary
Take a look at the code and read the comments.
Compile and run the code. You should see a string similar to `1[2¢3X4X`, where
the bytes following 1 and 2 are just random garbage.
If you add or remove instructions, remember to compute the new offset of the X
by counting the bytes, and replace `0x2d` with the new one.
Please don't continue onto the next section unless you have 100% understood
the boot sector offset and memory addressing.
The global offset
-----------------
Now, since offsetting `0x7c00` everywhere is very inconvenient, assemblers let
us define a "global offset" for every memory location, with the `org` command:
```nasm
[org 0x7c00]
```
Go ahead and **open `boot_sect_memory_org.asm`** and you will see the canonical
way to print data with the boot sector, which is now attempt 2. Compile the code
and run it, and you will see how the `org` command affects each previous solution.
Read the comments for a full explanation of the changes with and without `org`
-----
[1] This whole tutorial is heavily inspired on that document. Please read the
root-level README for more information on that.
================================================
FILE: 03-bootsector-memory/boot_sect_memory.asm
================================================
mov ah, 0x0e
; attempt 1
; Fails because it tries to print the memory address (i.e. pointer)
; not its actual contents
mov al, "1"
int 0x10
mov al, the_secret
int 0x10
; attempt 2
; It tries to print the memory address of 'the_secret' which is the correct approach.
; However, BIOS places our bootsector binary at address 0x7c00
; so we need to add that padding beforehand. We'll do that in attempt 3
mov al, "2"
int 0x10
mov al, [the_secret]
int 0x10
; attempt 3
; Add the BIOS starting offset 0x7c00 to the memory address of the X
; and then dereference the contents of that pointer.
; We need the help of a different register 'bx' because 'mov al, [ax]' is illegal.
; A register can't be used as source and destination for the same command.
mov al, "3"
int 0x10
mov bx, the_secret
add bx, 0x7c00
mov al, [bx]
int 0x10
; attempt 4
; We try a shortcut since we know that the X is stored at byte 0x2d in our binary
; That's smart but ineffective, we don't want to be recounting label offsets
; every time we change the code
mov al, "4"
int 0x10
mov al, [0x7c2d]
int 0x10
jmp $ ; infinite loop
the_secret:
; ASCII code 0x58 ('X') is stored just before the zero-padding.
; On this code that is at byte 0x2d (check it out using 'xxd file.bin')
db "X"
; zero padding and magic bios number
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 03-bootsector-memory/boot_sect_memory_org.asm
================================================
[org 0x7c00]
mov ah, 0x0e
; attempt 1
; Will fail again regardless of 'org' because we are still addressing the pointer
; and not the data it points to
mov al, "1"
int 0x10
mov al, the_secret
int 0x10
; attempt 2
; Having solved the memory offset problem with 'org', this is now the correct answer
mov al, "2"
int 0x10
mov al, [the_secret]
int 0x10
; attempt 3
; As you expected, we are adding 0x7c00 twice, so this is not going to work
mov al, "3"
int 0x10
mov bx, the_secret
add bx, 0x7c00
mov al, [bx]
int 0x10
; attempt 4
; This still works because there are no memory references to pointers, so
; the 'org' mode never applies. Directly addressing memory by counting bytes
; is always going to work, but it's inconvenient
mov al, "4"
int 0x10
mov al, [0x7c2d]
int 0x10
jmp $ ; infinite loop
the_secret:
; ASCII code 0x58 ('X') is stored just before the zero-padding.
; On this code that is at byte 0x2d (check it out using 'xxd file.bin')
db "X"
; zero padding and magic bios number
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 04-bootsector-stack/README.md
================================================
*Concepts you may want to Google beforehand: stack*
**Goal: Learn how to use the stack**
The usage of the stack is important, so we'll write yet another boot sector
with an example.
Remember that the `bp` register stores the base address (i.e. bottom) of the stack,
and `sp` stores the top, and that the stack grows downwards from `bp` (i.e. `sp` gets
decremented)
This lesson is quite straightforward, so jump ahead to the code.
I suggest that you try accessing in-stack memory addresses by yourself,
at different points in the code, and see what happens.
================================================
FILE: 04-bootsector-stack/boot_sect_stack.asm
================================================
mov ah, 0x0e ; tty mode
mov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten
mov sp, bp ; if the stack is empty then sp points to bp
push 'A'
push 'B'
push 'C'
; to show how the stack grows downwards
mov al, [0x7ffe] ; 0x8000 - 2
int 0x10
; however, don't try to access [0x8000] now, because it won't work
; you can only access the stack top so, at this point, only 0x7ffe (look above)
mov al, [0x8000]
int 0x10
; recover our characters using the standard procedure: 'pop'
; We can only pop full words so we need an auxiliary register to manipulate
; the lower byte
pop bx
mov al, bl
int 0x10 ; prints C
pop bx
mov al, bl
int 0x10 ; prints B
pop bx
mov al, bl
int 0x10 ; prints A
; data that has been pop'd from the stack is garbage now
mov al, [0x8000]
int 0x10
jmp $
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 05-bootsector-functions-strings/README.md
================================================
*Concepts you may want to Google beforehand: control structures,
function calling, strings*
**Goal: Learn how to code basic stuff (loops, functions) with the assembler**
We are close to our definitive boot sector.
In lesson 7 we will start reading from the disk, which is the last step before
loading a kernel. But first, we will write some code with control structures,
function calling, and full strings usage. We really need to be comfortable with
those concepts before jumping to the disk and the kernel.
Strings
-------
Define strings like bytes, but terminate them with a null-byte (yes, like C)
to be able to determine their end.
```nasm
mystring:
db 'Hello, World', 0
```
Notice that text surrounded with quotes is converted to ASCII by the assembler,
while that lone zero will be passed as byte `0x00` (null byte)
Control structures
------------------
We have already used one: `jmp $` for the infinite loop.
Assembler jumps are defined by the *previous* instruction result. For example:
```nasm
cmp ax, 4 ; if ax = 4
je ax_is_four ; do something (by jumping to that label)
jmp else ; else, do another thing
jmp endif ; finally, resume the normal flow
ax_is_four:
.....
jmp endif
else:
.....
jmp endif ; not actually necessary but printed here for completeness
endif:
```
Think in your head in high level, then convert it to assembler in this fashion.
There are many `jmp` conditions: if equal, if less than, etc. They are pretty
intuitive but you can always Google them
Calling functions
-----------------
As you may suppose, calling a function is just a jump to a label.
The tricky part are the parameters. There are two steps to working with parameters:
1. The programmer knows they share a specific register or memory address
2. Write a bit more code and make function calls generic and without side effects
Step 1 is easy. Let's just agree that we will use `al` (actually, `ax`) for the parameters.
```nasm
mov al, 'X'
jmp print
endprint:
...
print:
mov ah, 0x0e ; tty code
int 0x10 ; I assume that 'al' already has the character
jmp endprint ; this label is also pre-agreed
```
You can see that this approach will quickly grow into spaghetti code. The current
`print` function will only return to `endprint`. What if some other function
wants to call it? We are killing code reusage.
The correct solution offers two improvements:
- We will store the return address so that it may vary
- We will save the current registers to allow subfunctions to modify them
without any side effects
To store the return address, the CPU will help us. Instead of using a couple of
`jmp` to call subroutines, use `call` and `ret`.
To save the register data, there is also a special command which uses the stack: `pusha`
and its brother `popa`, which pushes all registers to the stack automatically and
recovers them afterwards.
Including external files
------------------------
I assume you are a programmer and don't need to convince you why this is
a good idea.
The syntax is
```nasm
%include "file.asm"
```
Printing hex values
-------------------
In the next lesson we will start reading from disk, so we need some way
to make sure that we are reading the correct data. File `boot_sect_print_hex.asm`
extends `boot_sect_print.asm` to print hex bytes, not just ASCII chars.
Code!
-----
Let's jump to the code. File `boot_sect_print.asm` is the subroutine which will
get `%include`d in the main file. It uses a loop to print bytes on screen.
It also includes a function to print a newline. The familiar `'\n'` is
actually two bytes, the newline char `0x0A` and a carriage return `0x0D`. Please
experiment by removing the carriage return char and see its effect.
As stated above, `boot_sect_print_hex.asm` allows for printing of bytes.
The main file `boot_sect_main.asm` loads a couple strings and bytes,
calls `print` and `print_hex` and hangs. If you understood
the previous sections, it's quite straightforward.
================================================
FILE: 05-bootsector-functions-strings/boot_sect_main.asm
================================================
[org 0x7c00] ; tell the assembler that our offset is bootsector code
; The main routine makes sure the parameters are ready and then calls the function
mov bx, HELLO
call print
call print_nl
mov bx, GOODBYE
call print
call print_nl
mov dx, 0x12fe
call print_hex
; that's it! we can hang now
jmp $
; remember to include subroutines below the hang
%include "boot_sect_print.asm"
%include "boot_sect_print_hex.asm"
; data
HELLO:
db 'Hello, World', 0
GOODBYE:
db 'Goodbye', 0
; padding and magic number
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 05-bootsector-functions-strings/boot_sect_print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 05-bootsector-functions-strings/boot_sect_print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 06-bootsector-segmentation/README.md
================================================
*Concepts you may want to Google beforehand: segmentation*
**Goal: learn how to address memory with 16-bit real mode segmentation**
If you are comfortable with segmentation, skip this lesson.
We did segmentation
with `[org]` on lesson 3. Segmentation means that you can specify
an offset to all the data you refer to.
This is done by using special registers: `cs`, `ds`, `ss` and `es`, for
Code, Data, Stack and Extra (i.e. user-defined)
Beware: they are *implicitly* used by the CPU, so once you set some
value for, say, `ds`, then all your memory access will be offset by `ds`.
[Read more here](http://wiki.osdev.org/Segmentation)
Furthermore, to compute the real address we don't just join the two
addresses, but we *overlap* them: `segment << 4 + address`. For example,
if `ds` is `0x4d`, then `[0x20]` actually refers to `0x4d0 + 0x20 = 0x4f0`
Enough theory. Have a look at the code and play with it a bit.
Hint: We cannot `mov` literals to those registers, we have to
use a general purpose register before.
================================================
FILE: 06-bootsector-segmentation/boot_sect_segmentation.asm
================================================
mov ah, 0x0e ; tty
mov al, [the_secret]
int 0x10 ; we already saw this doesn't work, right?
mov bx, 0x7c0 ; remember, the segment is automatically <<4 for you
mov ds, bx
; WARNING: from now on all memory references will be offset by 'ds' implicitly
mov al, [the_secret]
int 0x10
mov al, [es:the_secret]
int 0x10 ; doesn't look right... isn't 'es' currently 0x000?
mov bx, 0x7c0
mov es, bx
mov al, [es:the_secret]
int 0x10
jmp $
the_secret:
db "X"
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 07-bootsector-disk/README.md
================================================
*Concepts you may want to Google beforehand: hard disk, cylinder, head, sector,
carry bit*
**Goal: Let the bootsector load data from disk in order to boot the kernel**
Our OS won't fit inside the bootsector 512 bytes, so we need to read data from
a disk in order to run the kernel.
Thankfully, we don't have to deal with turning spinning platters on and off,
we can just call some BIOS routines, like we did to print characters on the screen.
To do so, we set `al` to `0x02` (and other registers with the required cylinder, head
and sector) and raise `int 0x13`
You can access [a detailed int 13h guide here](http://stanislavs.org/helppc/int_13-2.html)
On this lesson we will use for the first time the *carry bit*, which is an extra bit
present on each register which stores when an operation has overflowed its current
capacity:
```nasm
mov ax, 0xFFFF
add ax, 1 ; ax = 0x0000 and carry = 1
```
The carry isn't accessed directly but used as a control structure by other operators,
like `jc` (jump if the carry bit is set)
The BIOS also sets `al` to the number of sectors read, so always compare it
to the expected number.
Code
----
Open and examine `boot_sect_disk.asm` for the complete routine that
reads from disk.
`boot_sect_main.asm` prepares the parameters for disk read and calls `disk_load`.
Notice how we write some extra data which does not actually belong to the boot
sector, since it is outside the 512 bits mark.
The boot sector is actually sector 1 (the first one, sectors start at 1)
of cylinder 0 of head 0 of hdd 0.
Thus, any bytes after byte 512 correspond to sector 2 of cylinder 0 of head 0 of hdd 0
The main routine will fill it with sample data and then let the bootsector
read it.
**Note: if you keep getting errors and your code seems fine, make sure that qemu
is booting from the right drive and set the drive on `dl` accordingly**
The BIOS sets `dl` to the drive number before calling the bootloader. However,
I found some problems with qemu when booting from the hdd.
There are two quick options:
1. Try the flag `-fda` for example, `qemu -fda boot_sect_main.bin` which will set `dl`
as `0x00`, it seems to work fine then.
2. Explicitly use the flag `-boot`, e.g. `qemu boot_sect_main.bin -boot c` which
automatically sets `dl` as `0x80` and lets the bootloader read data
================================================
FILE: 07-bootsector-disk/boot_sect_disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 07-bootsector-disk/boot_sect_main.asm
================================================
[org 0x7c00]
mov bp, 0x8000 ; set the stack safely away from us
mov sp, bp
mov bx, 0x9000 ; es:bx = 0x0000:0x9000 = 0x09000
mov dh, 2 ; read 2 sectors
; the bios sets 'dl' for our boot disk number
; if you have trouble, use the '-fda' flag: 'qemu -fda file.bin'
call disk_load
mov dx, [0x9000] ; retrieve the first loaded word, 0xdada
call print_hex
call print_nl
mov dx, [0x9000 + 512] ; first word from second loaded sector, 0xface
call print_hex
jmp $
%include "../05-bootsector-functions-strings/boot_sect_print.asm"
%include "../05-bootsector-functions-strings/boot_sect_print_hex.asm"
%include "boot_sect_disk.asm"
; Magic number
times 510 - ($-$$) db 0
dw 0xaa55
; boot sector = sector 1 of cyl 0 of head 0 of hdd 0
; from now on = sector 2 ...
times 256 dw 0xdada ; sector 2 = 512 bytes
times 256 dw 0xface ; sector 3 = 512 bytes
================================================
FILE: 08-32bit-print/32bit-print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_ON_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 08-32bit-print/README.md
================================================
*Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video
memory*
**Goal: Print on the screen when on 32-bit protected mode**
32-bit mode allows us to use 32 bit registers and memory addressing,
protected memory, virtual memory and other advantages, but we will lose
BIOS interrupts and we'll need to code the GDT (more on this later)
In this lesson we will write a new print string routine which works in
32-bit mode, where we don't have BIOS interrupts, by directly manipulating
the VGA video memory instead of calling `int 0x10`. The VGA memory starts
at address `0xb8000` and it has a text mode which is useful to avoid
manipulating direct pixels.
The formula for accessing a specific character on the 80x25 grid is:
`0xb8000 + 2 * (row * 80 + col)`
That is, every character uses 2 bytes (one for the ASCII, another for
color and such), and we see that the structure of the memory concatenates
rows.
Open `32bit-print.asm` to see the code. It will always print the string
on the top left of the screen, but soon we'll write higher level routines
to replace it.
Unfortunately we cannot yet call this routine from the bootloader, because
we still don't know how to write the GDT and enter protected mode. Once
you have understood the code, jump to the next lesson.
================================================
FILE: 09-32bit-gdt/32bit-gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 09-32bit-gdt/README.md
================================================
*Concepts you may want to Google beforehand: GDT*
**Goal: program the GDT**
Remember segmentation from lesson 6? The offset was left shifted
to address an extra level of indirection.
In 32-bit mode, segmentation works differently. Now, the offset becomes an
index to a segment descriptor (SD) in the GDT. This descriptor defines
the base address (32 bits), the size (20 bits) and some flags, like
readonly, permissions, etc. To add confusion, the data structures are split,
so open the os-dev.pdf file and check out the figure on page 34 or the
Wikipedia page for the GDT.
The easiest way to program the GDT is to define two segments, one for code
and another for data. These can overlap which means there is no memory protection,
but it's good enough to boot, we'll fix this later with a higher language.
As a curiosity, the first GDT entry must be `0x00` to make sure that the
programmer didn't make any mistakes managing addresses.
Furthermore, the CPU can't directly load the GDT address, but it requires
a meta structure called the "GDT descriptor" with the size (16b) and address
(32b) of our actual GDT. It is loaded with the `lgdt` operation.
Let's directly jump to the GDT code in assembly. Again, to understand
all the segment flags, refer to the os-dev.pdf document. The theory for
this lesson is quite complex.
In the next lesson we will make the switch to 32-bit protected mode
and test our code from these lessons.
================================================
FILE: 10-32bit-enter/32bit-main.asm
================================================
[org 0x7c00] ; bootloader offset
mov bp, 0x9000 ; set the stack
mov sp, bp
mov bx, MSG_REAL_MODE
call print ; This will be written after the BIOS messages
call switch_to_pm
jmp $ ; this will actually never be executed
%include "../05-bootsector-functions-strings/boot_sect_print.asm"
%include "../09-32bit-gdt/32bit-gdt.asm"
%include "../08-32bit-print/32bit-print.asm"
%include "32bit-switch.asm"
[bits 32]
BEGIN_PM: ; after the switch we will get here
mov ebx, MSG_PROT_MODE
call print_string_pm ; Note that this will be written at the top left corner
jmp $
MSG_REAL_MODE db "Started in 16-bit real mode", 0
MSG_PROT_MODE db "Loaded 32-bit protected mode", 0
; bootsector
times 510-($-$$) db 0
dw 0xaa55
================================================
FILE: 10-32bit-enter/32bit-switch.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 10-32bit-enter/README.md
================================================
*Concepts you may want to Google beforehand: interrupts, pipelining*
**Goal: Enter 32-bit protected mode and test our code from previous lessons**
To jump into 32-bit mode:
1. Disable interrupts
2. Load our GDT
3. Set a bit on the CPU control register `cr0`
4. Flush the CPU pipeline by issuing a carefully crafted far jump
5. Update all the segment registers
6. Update the stack
7. Call to a well-known label which contains the first useful code in 32 bits
We will encapsulate this process on the file `32bit-switch.asm`. Open it
and take a look at the code.
After entering 32-bit mode, we will call `BEGIN_PM` which is the entry point
for our actual useful code (e.g. kernel code, etc). You can read the code
at `32bit-main.asm`. Compile and run this last file and you will see the two
messages on the screen.
Congratulations! Our next step will be to write a simple kernel
================================================
FILE: 11-kernel-crosscompiler/README.md
================================================
*Concepts you may want to Google beforehand: cross-compiler*
**Goal: Create a development environment to build your kernel**
If you're using a Mac, you will need to do this process right away. Otherwise, it could have waited
for a few more lessons. Anyway, you will need a cross-compiler once we jump to developing in a higher
language, that is, C. [Read why](http://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler%3F)
I'll be adapting the instructions [at the OSDev wiki](http://wiki.osdev.org/GCC_Cross-Compiler).
Required packages
-----------------
First, install the required packages. On linux, use your package distribution. On a Mac, [install brew](http://brew.sh/) if
you didn't do it on lesson 00, and get those packages with `brew install`
- gmp
- mpfr
- libmpc
- gcc
Yes, we will need `gcc` to build our cross-compiled `gcc`, especially on a Mac where gcc has been deprecated for `clang`
Once installed, find where your packaged gcc is (remember, not clang) and export it. For example:
```
export CC=/usr/local/bin/gcc-4.9
export LD=/usr/local/bin/gcc-4.9
```
We will need to build binutils and a cross-compiled gcc, and we will put them into `/usr/local/i386elfgcc`, so
let's export some paths now. Feel free to change them to your liking.
```
export PREFIX="/usr/local/i386elfgcc"
export TARGET=i386-elf
export PATH="$PREFIX/bin:$PATH"
```
binutils
--------
Remember: always be careful before pasting walls of text from the internet. I recommend copying line by line.
```sh
mkdir /tmp/src
cd /tmp/src
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz # If the link 404's, look for a more recent version
tar xf binutils-2.24.tar.gz
mkdir binutils-build
cd binutils-build
../binutils-2.24/configure --target=$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix=$PREFIX 2>&1 | tee configure.log
make all install 2>&1 | tee make.log
```
gcc
---
```sh
cd /tmp/src
curl -O https://ftp.gnu.org/gnu/gcc/gcc-4.9.1/gcc-4.9.1.tar.bz2
tar xf gcc-4.9.1.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-4.9.1/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc
```
That's it! You should have all the GNU binutils and the compiler at `/usr/local/i386elfgcc/bin`, prefixed by `i386-elf-` to avoid
collisions with your system's compiler and binutils.
You may want to add the `$PATH` to your `.bashrc`. From now on, on this tutorial, we will explicitly use the prefixes when using
the cross-compiled gcc.
================================================
FILE: 12-kernel-c/README.md
================================================
*Concepts you may want to Google beforehand: C, object code, linker, disassemble*
**Goal: Learn to write the same low-level code as we did with assembler, but in C**
Compile
-------
Let's see how the C compiler compiles our code and compare it to the machine code
generated with the assembler.
We will start writing a simple program which contains a function, `function.c`.
Open the file and examine it.
To compile system-independent code, we need the flag `-ffreestanding`, so compile
`function.c` in this fashion:
`i386-elf-gcc -ffreestanding -c function.c -o function.o`
Let's examine the machine code generated by the compiler:
`i386-elf-objdump -d function.o`
Now that is something we recognize, isn't it?
Link
----
Finally, to produce a binary file, we will use the linker. An important part of this
step is to learn how high level languages call function labels. Which is the offset
where our function will be placed in memory? We don't actually know. For this
example, we'll place the offset at `0x0` and use the `binary` format which
generates machine code without any labels and/or metadata
`i386-elf-ld -o function.bin -Ttext 0x0 --oformat binary function.o`
*Note: a warning may appear when linking, disregard it*
Now examine both "binary" files, `function.o` and `function.bin` using `xxd`. You
will see that the `.bin` file is machine code, while the `.o` file has a lot
of debugging information, labels, etc.
Decompile
---------
As a curiosity, we will examine the machine code.
`ndisasm -b 32 function.bin`
More
----
I encourage you to write more small programs, which feature:
- Local variables `localvars.c`
- Function calls `functioncalls.c`
- Pointers `pointers.c`
Then compile and disassemble them, and examine the resulting machine code. Follow
the os-guide.pdf for explanations. Try to answer this question: why does the
disassemblement of `pointers.c` not resemble what you would expect? Where is
the ASCII `0x48656c6c6f` for "Hello"?
================================================
FILE: 12-kernel-c/function.c
================================================
int my_function() {
return 0xbaba;
}
================================================
FILE: 12-kernel-c/functioncalls.c
================================================
void caller() {
my_func(0xdede);
}
int my_func(int arg) {
return arg;
}
================================================
FILE: 12-kernel-c/localvars.c
================================================
int my_function() {
int my_var = 0xbaba;
return my_var;
}
================================================
FILE: 12-kernel-c/pointers.c
================================================
void func() {
char* string = "Hello";
}
================================================
FILE: 13-kernel-barebones/Makefile
================================================
# $@ = target file
# $< = first dependency
# $^ = all dependencies
# First rule is the one executed when no parameters are fed to the Makefile
all: run
# Notice how dependencies are built as needed
kernel.bin: kernel_entry.o kernel.o
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
kernel_entry.o: kernel_entry.asm
nasm $< -f elf -o $@
kernel.o: kernel.c
i386-elf-gcc -ffreestanding -c $< -o $@
# Rule to disassemble the kernel - may be useful to debug
kernel.dis: kernel.bin
ndisasm -b 32 $< > $@
bootsect.bin: bootsect.asm
nasm $< -f bin -o $@
os-image.bin: bootsect.bin kernel.bin
cat $^ > $@
run: os-image.bin
qemu-system-i386 -fda $<
clean:
rm *.bin *.o *.dis
================================================
FILE: 13-kernel-barebones/README.md
================================================
*Concepts you may want to Google beforehand: kernel, ELF format, makefile*
**Goal: Create a simple kernel and a bootsector capable of booting it**
The kernel
----------
Our C kernel will just print an 'X' on the top left corner of the screen. Go ahead
and open `kernel.c`.
You will notice a dummy function that does nothing. That function will force us
to create a kernel entry routine which does not point to byte 0x0 in our kernel, but
to an actual label which we know that launches it. In our case, function `main()`.
`i386-elf-gcc -ffreestanding -c kernel.c -o kernel.o`
That routine is coded on `kernel_entry.asm`. Read it and you will learn how to
use `[extern]` declarations in assembly. To compile this file, instead of generating
a binary, we will generate an `elf` format file which will be linked with `kernel.o`
`nasm kernel_entry.asm -f elf -o kernel_entry.o`
The linker
----------
A linker is a very powerful tool and we only started to benefit from it.
To link both object files into a single binary kernel and resolve label references,
run:
`i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary`
Notice how our kernel will be placed not at `0x0` in memory, but at `0x1000`. The
bootsector will need to know this address too.
The bootsector
--------------
It is very similar to the one in lesson 10. Open `bootsect.asm` and examine the code.
Actually, if you remove all the lines used to print messages on the screen, it accounts
to a couple dozen lines.
Compile it with `nasm bootsect.asm -f bin -o bootsect.bin`
Putting it all together
-----------------------
Now what? We have two separate files for the bootsector and the kernel?
Can't we just "link" them together into a single file? Yes, we can, and it's easy,
just concatenate them:
`cat bootsect.bin kernel.bin > os-image.bin`
Run!
----
You can now run `os-image.bin` with qemu.
Remember that if you find disk load errors you may need to play with the disk numbers
or qemu parameters (floppy = `0x0`, hdd = `0x80`). I usually use `qemu-system-i386 -fda os-image.bin`
You will see four messages:
- "Started in 16-bit Real Mode"
- "Loading kernel into memory"
- (Top left) "Landed in 32-bit Protected Mode"
- (Top left, overwriting previous message) "X"
Congratulations!
Makefile
--------
As a last step, we will tidy up the compilation process with a Makefile. Open the `Makefile`
script and examine its contents. If you don't know what a Makefile is, now is a good time
to Google and learn it, as this will save us a lot of time in the future.
================================================
FILE: 13-kernel-barebones/bootsect.asm
================================================
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "../05-bootsector-functions-strings/boot_sect_print.asm"
%include "../05-bootsector-functions-strings/boot_sect_print_hex.asm"
%include "../07-bootsector-disk/boot_sect_disk.asm"
%include "../09-32bit-gdt/32bit-gdt.asm"
%include "../08-32bit-print/32bit-print.asm"
%include "../10-32bit-enter/32bit-switch.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 2
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 13-kernel-barebones/kernel.c
================================================
/* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */
void dummy_test_entrypoint() {
}
void main() {
char* video_memory = (char*) 0xb8000;
*video_memory = 'X';
}
================================================
FILE: 13-kernel-barebones/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 14-checkpoint/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
================================================
FILE: 14-checkpoint/README.md
================================================
*Concepts you may want to Google beforehand: monolithic kernel, microkernel, debugger, gdb*
**Goal: Pause and organize our code a little bit. Then learn how to debug the kernel with gdb**
Maybe you didn't realize it, but you already have your own kernel
running!
However, it does very little, just print an 'X'. Now is the time to stop for
a moment and organize the code into folders, create a scalable Makefile for future code,
and think on a strategy.
Take a look at the new folder structure. Most of the files have been symlinked
from previous lessons, so if we have to change them at some point, it will be
a better idea to remove the symlink and create a new file.
Furthermore, since from now on we will use mostly C to code, we'll take advantage of qemu's
ability to open a connection to gdb. First, let's install a cross-compiled `gdb` since
OSX uses `lldb` which is not compatible with the ELF file format (neither is the `gdb` available
on Homebrew's repos)
```sh
cd /tmp/src
curl -O http://ftp.rediris.es/mirror/GNU/gdb/gdb-7.8.tar.gz
tar xf gdb-7.8.tar.gz
mkdir gdb-build
cd gdb-build
export PREFIX="/usr/local/i386elfgcc"
export TARGET=i386-elf
../gdb-7.8/configure --target="$TARGET" --prefix="$PREFIX" --program-prefix=i386-elf-
make
make install
```
Check out the Makefile target `make debug`. This target uses builds `kernel.elf`, which
is an object file (not binary) with all the symbols we generated on the kernel, thanks to
the `-g` flag on gcc. Please examine it with `xxd` and you'll see some strings. Actually,
the correct way to examine the strings in an object file is by `strings kernel.elf`
We can take advantage of this cool qemu feature. Type `make debug` and, on the gdb shell:
- Set up a breakpoint in `kernel.c:main()`: `b main`
- Run the OS: `continue`
- Run two steps into the code: `next` then `next`. You will see that we are just about to set
the 'X' on the screen, but it isn't there yet (check out the qemu screen)
- Let's see what's in the video memory: `print *video_memory`. There is the 'L' from "Landed in
32-bit Protected Mode"
- Hmmm, let's make sure that `video_memory` points to the correct address: `print video_memory`
- `next` to put there our 'X'
- Let's make sure: `print *video_memory` and look at the qemu screen. It's definitely there.
Now is a good time to read some tutorial on `gdb` and learn super useful things like `info registers`
which will save us a lot of time in the future!
You may notice that, since this is a tutorial, we haven't yet discussed which kind
of kernel we will write. It will probably be a monolithic one since they are easier
to design and implement, and after all this is our first OS. Maybe in the future
we'll add a lesson "15-b" with a microkernel design. Who knows.
================================================
FILE: 14-checkpoint/boot/32bit_print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_OB_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 14-checkpoint/boot/bootsect.asm
================================================
; Identical to lesson 13's boot sector, but the %included files have new paths
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "boot/print.asm"
%include "boot/print_hex.asm"
%include "boot/disk.asm"
%include "boot/gdt.asm"
%include "boot/32bit_print.asm"
%include "boot/switch_pm.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 16 ; Our future kernel will be larger, make this big
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 14-checkpoint/boot/disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 14-checkpoint/boot/gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 14-checkpoint/boot/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 14-checkpoint/boot/print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 14-checkpoint/boot/print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 14-checkpoint/boot/switch_pm.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 14-checkpoint/kernel/kernel.c
================================================
/* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */
void dummy_test_entrypoint() {
}
void main() {
char* video_memory = (char*) 0xb8000;
*video_memory = 'X';
}
================================================
FILE: 15-video-ports/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
================================================
FILE: 15-video-ports/README.md
================================================
*Concepts you may want to Google beforehand: I/O ports*
**Goal: Learn how to use the VGA card data ports**
We will use C to communicate with devices via I/O registers and ports.
Open `drivers/ports.c` and examine the inline C assembler syntax. It has
some differences, like the order of the source and destination operands,
and the funny syntax to assign variables to operands.
When you understand the concepts, open `kernel/kernel.c` for an example
of use.
In this example we will examine the I/O ports which map the screen cursor
position. Specifically, we will query port `0x3d4` with value `14` to request
the cursor position high byte, and the same port with `15` for the low byte.
When this port is queried, it saves the result in port `0x3d5`
Don't miss the opportunity to use `gdb` to inspect the value of C variables,
since we still can't print them on the screen. To do so, set a breakpoint
for a specific line, `breakpoint kernel.c:21` and use the `print` command
to examine variables. Aren't you glad now that we invested some time in
compiling the cross-compiled gdb? ;)
Finally, we will use the queried cursor position to write a character
at that location.
================================================
FILE: 15-video-ports/boot/32bit_print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_OB_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 15-video-ports/boot/bootsect.asm
================================================
; Identical to lesson 13's boot sector, but the %included files have new paths
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "boot/print.asm"
%include "boot/print_hex.asm"
%include "boot/disk.asm"
%include "boot/gdt.asm"
%include "boot/32bit_print.asm"
%include "boot/switch_pm.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 16 ; Our future kernel will be larger, make this big
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 15-video-ports/boot/disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 15-video-ports/boot/gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 15-video-ports/boot/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 15-video-ports/boot/print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 15-video-ports/boot/print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 15-video-ports/boot/switch_pm.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 15-video-ports/drivers/ports.c
================================================
/**
* Read a byte from the specified port
*/
unsigned char port_byte_in (unsigned short port) {
unsigned char result;
/* Inline assembler syntax
* !! Notice how the source and destination registers are switched from NASM !!
*
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
* '"d" (port)': map the C variable '(port)' into e'd'x register
*
* Inputs and outputs are separated by colons
*/
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out (unsigned short port, unsigned char data) {
/* Notice how here both registers are mapped to C variables and
* nothing is returned, thus, no equals '=' in the asm syntax
* However we see a comma since there are two variables in the input area
* and none in the 'return' area
*/
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
}
unsigned short port_word_in (unsigned short port) {
unsigned short result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void port_word_out (unsigned short port, unsigned short data) {
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}
================================================
FILE: 15-video-ports/drivers/ports.h
================================================
unsigned char port_byte_in (unsigned short port);
void port_byte_out (unsigned short port, unsigned char data);
unsigned short port_word_in (unsigned short port);
void port_word_out (unsigned short port, unsigned short data);
================================================
FILE: 15-video-ports/kernel/kernel.c
================================================
#include "../drivers/ports.h"
void main() {
/* Screen cursor position: ask VGA control register (0x3d4) for bytes
* 14 = high byte of cursor and 15 = low byte of cursor. */
port_byte_out(0x3d4, 14); /* Requesting byte 14: high byte of cursor pos */
/* Data is returned in VGA data register (0x3d5) */
int position = port_byte_in(0x3d5);
position = position << 8; /* high byte */
port_byte_out(0x3d4, 15); /* requesting low byte */
position += port_byte_in(0x3d5);
/* VGA 'cells' consist of the character and its control data
* e.g. 'white on black background', 'red text on white bg', etc */
int offset_from_vga = position * 2;
/* Now you can examine both variables using gdb, since we still
* don't know how to print strings on screen. Run 'make debug' and
* on the gdb console:
* breakpoint kernel.c:21
* continue
* print position
* print offset_from_vga
*/
/* Let's write on the current cursor position, we already know how
* to do that */
char *vga = 0xb8000;
vga[offset_from_vga] = 'X';
vga[offset_from_vga+1] = 0x0f; /* White text on black background */
}
================================================
FILE: 16-video-driver/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
================================================
FILE: 16-video-driver/README.md
================================================
*Concepts you may want to Google beforehand: VGA character cells, screen offset*
**Goal: Write strings on the screen**
Finally, we are going to be able to output text on the screen. This lesson contains
a bit more code than usual, so let's go step by step.
Open `drivers/screen.h` and you'll see that we have defined some constants for the VGA
card driver and three public functions, one to clear the screen and another couple
to write strings, the famously named `kprint` for "kernel print"
Now open `drivers/screen.c`. It starts with the declaration of private helper functions
that we will use to aid our `kprint` kernel API.
There are the two I/O port access routines that we learned in the previous lesson,
`get` and `set_cursor_offset()`.
Then there is the routine that directly manipulates the video memory, `print_char()`
Finally, there are three small helper functions to transform rows and columns into offsets
and vice versa.
kprint_at
---------
`kprint_at` may be called with a `-1` value for `col` and `row`, which indicates that
we will print the string at the current cursor position.
It first sets three variables for the col/row and the offset. Then it iterates through
the `char*` and calls `print_char()` with the current coordinates.
Note that `print_char` itself returns the offset of the next cursor position, and we reuse
it for the next loop.
`kprint` is basically a wrapper for `kprint_at`
print_char
----------
Like `kprint_at`, `print_char` allows cols/rows to be `-1`. In that case it retrieves
the cursor position from the hardware, using the `ports.c` routines.
`print_char` also handles newlines. In that case, we will position the cursor offset
to column 0 of the next row.
Remember that the VGA cells take two bytes, one for the character itself and another one
for the attribute.
kernel.c
--------
Our new kernel is finally able to print strings.
It tests correct character positioning, spanning through multiple lines, line breaks,
and finally it tries to write outside of the screen bounds. What happens then?
In the next lesson we will learn how to scroll the screen.
================================================
FILE: 16-video-driver/boot/32bit_print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_OB_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 16-video-driver/boot/bootsect.asm
================================================
; Identical to lesson 13's boot sector, but the %included files have new paths
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "boot/print.asm"
%include "boot/print_hex.asm"
%include "boot/disk.asm"
%include "boot/gdt.asm"
%include "boot/32bit_print.asm"
%include "boot/switch_pm.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 16 ; Our future kernel will be larger, make this big
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 16-video-driver/boot/disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 16-video-driver/boot/gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 16-video-driver/boot/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 16-video-driver/boot/print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 16-video-driver/boot/print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 16-video-driver/boot/switch_pm.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 16-video-driver/drivers/ports.c
================================================
/**
* Read a byte from the specified port
*/
unsigned char port_byte_in (unsigned short port) {
unsigned char result;
/* Inline assembler syntax
* !! Notice how the source and destination registers are switched from NASM !!
*
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
* '"d" (port)': map the C variable '(port)' into e'd'x register
*
* Inputs and outputs are separated by colons
*/
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out (unsigned short port, unsigned char data) {
/* Notice how here both registers are mapped to C variables and
* nothing is returned, thus, no equals '=' in the asm syntax
* However we see a comma since there are two variables in the input area
* and none in the 'return' area
*/
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
}
unsigned short port_word_in (unsigned short port) {
unsigned short result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void port_word_out (unsigned short port, unsigned short data) {
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}
================================================
FILE: 16-video-driver/drivers/ports.h
================================================
unsigned char port_byte_in (unsigned short port);
void port_byte_out (unsigned short port, unsigned char data);
unsigned short port_word_in (unsigned short port);
void port_word_out (unsigned short port, unsigned short data);
================================================
FILE: 16-video-driver/drivers/screen.c
================================================
#include "screen.h"
#include "ports.h"
/* Declaration of private functions */
int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char c, int col, int row, char attr);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);
/**********************************************************
* Public Kernel API functions *
**********************************************************/
/**
* Print a message on the specified location
* If col, row, are negative, we will use the current offset
*/
void kprint_at(char *message, int col, int row) {
/* Set cursor if col/row are negative */
int offset;
if (col >= 0 && row >= 0)
offset = get_offset(col, row);
else {
offset = get_cursor_offset();
row = get_offset_row(offset);
col = get_offset_col(offset);
}
/* Loop through message and print it */
int i = 0;
while (message[i] != 0) {
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
/* Compute row/col for next iteration */
row = get_offset_row(offset);
col = get_offset_col(offset);
}
}
void kprint(char *message) {
kprint_at(message, -1, -1);
}
/**********************************************************
* Private kernel functions *
**********************************************************/
/**
* Innermost print function for our kernel, directly accesses the video memory
*
* If 'col' and 'row' are negative, we will print at current cursor location
* If 'attr' is zero it will use 'white on black' as default
* Returns the offset of the next character
* Sets the video cursor to the returned offset
*/
int print_char(char c, int col, int row, char attr) {
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
if (!attr) attr = WHITE_ON_BLACK;
/* Error control: print a red 'E' if the coords aren't right */
if (col >= MAX_COLS || row >= MAX_ROWS) {
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
return get_offset(col, row);
}
int offset;
if (col >= 0 && row >= 0) offset = get_offset(col, row);
else offset = get_cursor_offset();
if (c == '\n') {
row = get_offset_row(offset);
offset = get_offset(0, row+1);
} else {
vidmem[offset] = c;
vidmem[offset+1] = attr;
offset += 2;
}
set_cursor_offset(offset);
return offset;
}
int get_cursor_offset() {
/* Use the VGA ports to get the current cursor position
* 1. Ask for high byte of the cursor offset (data 14)
* 2. Ask for low byte (data 15)
*/
port_byte_out(REG_SCREEN_CTRL, 14);
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
port_byte_out(REG_SCREEN_CTRL, 15);
offset += port_byte_in(REG_SCREEN_DATA);
return offset * 2; /* Position * size of character cell */
}
void set_cursor_offset(int offset) {
/* Similar to get_cursor_offset, but instead of reading we write data */
offset /= 2;
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}
void clear_screen() {
int screen_size = MAX_COLS * MAX_ROWS;
int i;
char *screen = VIDEO_ADDRESS;
for (i = 0; i < screen_size; i++) {
screen[i*2] = ' ';
screen[i*2+1] = WHITE_ON_BLACK;
}
set_cursor_offset(get_offset(0, 0));
}
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
================================================
FILE: 16-video-driver/drivers/screen.h
================================================
#define VIDEO_ADDRESS 0xb8000
#define MAX_ROWS 25
#define MAX_COLS 80
#define WHITE_ON_BLACK 0x0f
#define RED_ON_WHITE 0xf4
/* Screen i/o ports */
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
/* Public kernel API */
void clear_screen();
void kprint_at(char *message, int col, int row);
void kprint(char *message);
================================================
FILE: 16-video-driver/kernel/kernel.c
================================================
#include "../drivers/screen.h"
void main() {
clear_screen();
kprint_at("X", 1, 6);
kprint_at("This text spans multiple lines", 75, 10);
kprint_at("There is a line\nbreak", 0, 20);
kprint("There is a line\nbreak");
kprint_at("What happens when we run out of space?", 45, 24);
}
================================================
FILE: 17-video-scroll/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
================================================
FILE: 17-video-scroll/README.md
================================================
*Concepts you may want to Google beforehand: scroll*
**Goal: Scroll the screen when the text reaches the bottom**
For this short lesson, open `drivers/screen.c` and note that at the
bottom of `print_char` there is a new section (line 84) which checks
if the current offset is over the screen size and scrolls the text.
The actual scrolling is handled by a new function, `memory_copy`. It is
a simpler version of the standard `memcpy` but we named it differently
to avoid namespace collisions, at least for now. Open `kernel/util.c` to
see its implementation.
To help visualize scrolling, we will also implement a function to
convert integers to text, `int_to_ascii`. Again, it is a quick implementation
of the standard `itoa`. Notice that for integers which have double digits
or more, they are printed in reverse. This is intended. On future lessons
we will extend our helper functions, but that is not the point for now.
Finally, open `kernel/kernel.c`. Initially, each line displays its line
number. You can set a breakpoint on line 14 to confirm this. Then,
the following `kprint`s force the kernel to scroll down.
This lesson ends the coverage for the os-dev.pdf document. From now on, we'll
follow [the OSDev wiki](http://wiki.osdev.org/Meaty_Skeleton) and
other sources and examples. Thanks Prof. Blundell for that great document!
================================================
FILE: 17-video-scroll/boot/32bit_print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_OB_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 17-video-scroll/boot/bootsect.asm
================================================
; Identical to lesson 13's boot sector, but the %included files have new paths
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "boot/print.asm"
%include "boot/print_hex.asm"
%include "boot/disk.asm"
%include "boot/gdt.asm"
%include "boot/32bit_print.asm"
%include "boot/switch_pm.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 16 ; Our future kernel will be larger, make this big
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 17-video-scroll/boot/disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 17-video-scroll/boot/gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 17-video-scroll/boot/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 17-video-scroll/boot/print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 17-video-scroll/boot/print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 17-video-scroll/boot/switch_pm.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 17-video-scroll/drivers/ports.c
================================================
/**
* Read a byte from the specified port
*/
unsigned char port_byte_in (unsigned short port) {
unsigned char result;
/* Inline assembler syntax
* !! Notice how the source and destination registers are switched from NASM !!
*
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
* '"d" (port)': map the C variable '(port)' into e'd'x register
*
* Inputs and outputs are separated by colons
*/
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out (unsigned short port, unsigned char data) {
/* Notice how here both registers are mapped to C variables and
* nothing is returned, thus, no equals '=' in the asm syntax
* However we see a comma since there are two variables in the input area
* and none in the 'return' area
*/
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
}
unsigned short port_word_in (unsigned short port) {
unsigned short result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void port_word_out (unsigned short port, unsigned short data) {
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}
================================================
FILE: 17-video-scroll/drivers/ports.h
================================================
unsigned char port_byte_in (unsigned short port);
void port_byte_out (unsigned short port, unsigned char data);
unsigned short port_word_in (unsigned short port);
void port_word_out (unsigned short port, unsigned short data);
================================================
FILE: 17-video-scroll/drivers/screen.c
================================================
#include "screen.h"
#include "ports.h"
#include "../kernel/util.h"
/* Declaration of private functions */
int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char c, int col, int row, char attr);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);
/**********************************************************
* Public Kernel API functions *
**********************************************************/
/**
* Print a message on the specified location
* If col, row, are negative, we will use the current offset
*/
void kprint_at(char *message, int col, int row) {
/* Set cursor if col/row are negative */
int offset;
if (col >= 0 && row >= 0)
offset = get_offset(col, row);
else {
offset = get_cursor_offset();
row = get_offset_row(offset);
col = get_offset_col(offset);
}
/* Loop through message and print it */
int i = 0;
while (message[i] != 0) {
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
/* Compute row/col for next iteration */
row = get_offset_row(offset);
col = get_offset_col(offset);
}
}
void kprint(char *message) {
kprint_at(message, -1, -1);
}
/**********************************************************
* Private kernel functions *
**********************************************************/
/**
* Innermost print function for our kernel, directly accesses the video memory
*
* If 'col' and 'row' are negative, we will print at current cursor location
* If 'attr' is zero it will use 'white on black' as default
* Returns the offset of the next character
* Sets the video cursor to the returned offset
*/
int print_char(char c, int col, int row, char attr) {
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
if (!attr) attr = WHITE_ON_BLACK;
/* Error control: print a red 'E' if the coords aren't right */
if (col >= MAX_COLS || row >= MAX_ROWS) {
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
return get_offset(col, row);
}
int offset;
if (col >= 0 && row >= 0) offset = get_offset(col, row);
else offset = get_cursor_offset();
if (c == '\n') {
row = get_offset_row(offset);
offset = get_offset(0, row+1);
} else {
vidmem[offset] = c;
vidmem[offset+1] = attr;
offset += 2;
}
/* Check if the offset is over screen size and scroll */
if (offset >= MAX_ROWS * MAX_COLS * 2) {
int i;
for (i = 1; i < MAX_ROWS; i++)
memory_copy(get_offset(0, i) + VIDEO_ADDRESS,
get_offset(0, i-1) + VIDEO_ADDRESS,
MAX_COLS * 2);
/* Blank last line */
char *last_line = get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
offset -= 2 * MAX_COLS;
}
set_cursor_offset(offset);
return offset;
}
int get_cursor_offset() {
/* Use the VGA ports to get the current cursor position
* 1. Ask for high byte of the cursor offset (data 14)
* 2. Ask for low byte (data 15)
*/
port_byte_out(REG_SCREEN_CTRL, 14);
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
port_byte_out(REG_SCREEN_CTRL, 15);
offset += port_byte_in(REG_SCREEN_DATA);
return offset * 2; /* Position * size of character cell */
}
void set_cursor_offset(int offset) {
/* Similar to get_cursor_offset, but instead of reading we write data */
offset /= 2;
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}
void clear_screen() {
int screen_size = MAX_COLS * MAX_ROWS;
int i;
char *screen = VIDEO_ADDRESS;
for (i = 0; i < screen_size; i++) {
screen[i*2] = ' ';
screen[i*2+1] = WHITE_ON_BLACK;
}
set_cursor_offset(get_offset(0, 0));
}
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
================================================
FILE: 17-video-scroll/drivers/screen.h
================================================
#define VIDEO_ADDRESS 0xb8000
#define MAX_ROWS 25
#define MAX_COLS 80
#define WHITE_ON_BLACK 0x0f
#define RED_ON_WHITE 0xf4
/* Screen i/o ports */
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
/* Public kernel API */
void clear_screen();
void kprint_at(char *message, int col, int row);
void kprint(char *message);
================================================
FILE: 17-video-scroll/kernel/kernel.c
================================================
#include "../drivers/screen.h"
#include "util.h"
void main() {
clear_screen();
/* Fill up the screen */
int i = 0;
for (i = 0; i < 24; i++) {
char str[255];
int_to_ascii(i, str);
kprint_at(str, 0, i);
}
kprint_at("This text forces the kernel to scroll. Row 0 will disappear. ", 60, 24);
kprint("And with this text, the kernel will scroll again, and row 1 will disappear too!");
}
================================================
FILE: 17-video-scroll/kernel/util.c
================================================
void memory_copy(char *source, char *dest, int nbytes) {
int i;
for (i = 0; i < nbytes; i++) {
*(dest + i) = *(source + i);
}
}
/**
* K&R implementation
*/
void int_to_ascii(int n, char str[]) {
int i, sign;
if ((sign = n) < 0) n = -n;
i = 0;
do {
str[i++] = n % 10 + '0';
} while ((n /= 10) > 0);
if (sign < 0) str[i++] = '-';
str[i] = '\0';
/* TODO: implement "reverse" */
}
================================================
FILE: 17-video-scroll/kernel/util.h
================================================
void memory_copy(char *source, char *dest, int nbytes);
void int_to_ascii(int n, char str[]);
================================================
FILE: 18-interrupts/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
================================================
FILE: 18-interrupts/README.md
================================================
*Concepts you may want to Google beforehand: C types and structs, include guards, type attributes: packed, extern, volatile, exceptions*
**Goal: Set up the Interrupt Descriptor Table to handle CPU interrupts**
This lesson and the following ones have been heavily inspired
by [JamesM's tutorial](https://web.archive.org/web/20160412174753/http://www.jamesmolloy.co.uk/tutorial_html/index.html)
Data types
----------
First, we will define some special data types in `cpu/types.h`,
which will help us uncouple data structures for raw bytes from chars and ints.
It has been carefully placed on the `cpu/` folder, where we will
put machine-dependent code from now on. Yes, the boot code
is specifically x86 and is still on `boot/`, but let's leave
that alone for now.
Some of the already existing files have been changed to use
the new `u8`, `u16` and `u32` data types.
From now on, our C header files will also have include guards.
Interrupts
----------
Interrupts are one of the main things that a kernel needs to
handle. We will implement it now, as soon as possible, to be able
to receive keyboard input in future lessons.
Another examples of interrupts are: divisions by zero, out of bounds,
invalid opcodes, page faults, etc.
Interrupts are handled on a vector, with entries which are
similar to those of the GDT (lesson 9). However, instead of
programming the IDT in assembly, we'll do it in C.
`cpu/idt.h` defines how an idt entry is stored `idt_gate` (there need to be
256 of them, even if null, or the CPU may panic) and the actual
idt structure that the BIOS will load, `idt_register` which is
just a memory address and a size, similar to the GDT register.
Finally, we define a couple variables to access those data structures
from assembler code.
`cpu/idt.c` just fills in every struct with a handler.
As you can see, it is a matter
of setting the struct values and calling the `lidt` assembler command.
ISRs
----
The Interrupt Service Routines run every time the CPU detects an
interrupt, which is usually fatal.
We will write just enough code to handle them, print an error message,
and halt the CPU.
On `cpu/isr.h` we define 32 of them, manually. They are declared as
`extern` because they will be implemented in assembler, in `cpu/interrupt.asm`
Before jumping to the assembler code, check out `cpu/isr.c`. As you can see,
we define a function to install all isrs at once and load the IDT, a list of error
messages, and the high level handler, which kprints some information. You
can customize `isr_handler` to print/do whatever you want.
Now to the low level which glues every `idt_gate` with its low-level and
high-level handler. Open `cpu/interrupt.asm`. Here we define a common
low level ISR code, which basically saves/restores the state and calls
the C code, and then the actual ISR assembler functions which are referenced
on `cpu/isr.h`
Note how the `registers_t` struct is a representation of all the registers
we pushed in `interrupt.asm`
That's basically it. Now we need to reference `cpu/interrupt.asm` from our
Makefile, and make the kernel install the ISRs and launch one of them.
Notice how the CPU doesn't halt even though it would be good practice
to do it after some interrupts.
================================================
FILE: 18-interrupts/boot/32bit_print.asm
================================================
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_string_pm_loop:
mov al, [ebx] ; [ebx] is the address of our character
mov ah, WHITE_OB_BLACK
cmp al, 0 ; check if end of string
je print_string_pm_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_pm_loop
print_string_pm_done:
popa
ret
================================================
FILE: 18-interrupts/boot/bootsect.asm
================================================
; Identical to lesson 13's boot sector, but the %included files have new paths
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "boot/print.asm"
%include "boot/print_hex.asm"
%include "boot/disk.asm"
%include "boot/gdt.asm"
%include "boot/32bit_print.asm"
%include "boot/switch_pm.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 31 ; Our future kernel will be larger, make this big
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
MSG_RETURNED_KERNEL db "Returned from kernel. Error?", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55
================================================
FILE: 18-interrupts/boot/disk.asm
================================================
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
mov bx, DISK_ERROR
call print
call print_nl
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
jmp disk_loop
sectors_error:
mov bx, SECTORS_ERROR
call print
disk_loop:
jmp $
DISK_ERROR: db "Disk read error", 0
SECTORS_ERROR: db "Incorrect number of sectors read", 0
================================================
FILE: 18-interrupts/boot/gdt.asm
================================================
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
================================================
FILE: 18-interrupts/boot/kernel_entry.asm
================================================
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
================================================
FILE: 18-interrupts/boot/print.asm
================================================
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
; the part where we print with the BIOS help
mov ah, 0x0e
int 0x10 ; 'al' already contains the char
; increment pointer and do next loop
add bx, 1
jmp start
done:
popa
ret
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
================================================
FILE: 18-interrupts/boot/print_hex.asm
================================================
; receiving the data in 'dx'
; For the examples we'll assume that we're called with dx=0x1234
print_hex:
pusha
mov cx, 0 ; our index variable
; Strategy: get the last char of 'dx', then convert to ASCII
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
; Then, move the ASCII byte to the correct position on the resulting string
hex_loop:
cmp cx, 4 ; loop 4 times
je end
; 1. convert last char of 'dx' to ascii
mov ax, dx ; we will use 'ax' as our working register
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
jle step2
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
step2:
; 2. get the correct position of the string to place our ASCII char
; bx <- base address + string length - index of char
mov bx, HEX_OUT + 5 ; base + length
sub bx, cx ; our index variable
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
; increment index and loop
add cx, 1
jmp hex_loop
end:
; prepare the parameter and call the function
; remember that print receives parameters in 'bx'
mov bx, HEX_OUT
call print
popa
ret
HEX_OUT:
db '0x0000',0 ; reserve memory for our new string
================================================
FILE: 18-interrupts/boot/switch_pm.asm
================================================
[bits 16]
switch_to_pm:
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code
================================================
FILE: 18-interrupts/cpu/idt.c
================================================
#include "idt.h"
#include "../kernel/util.h"
void set_idt_gate(int n, u32 handler) {
idt[n].low_offset = low_16(handler);
idt[n].sel = KERNEL_CS;
idt[n].always0 = 0;
idt[n].flags = 0x8E;
idt[n].high_offset = high_16(handler);
}
void set_idt() {
idt_reg.base = (u32) &idt;
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
/* Don't make the mistake of loading &idt -- always load &idt_reg */
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
}
================================================
FILE: 18-interrupts/cpu/idt.h
================================================
#ifndef IDT_H
#define IDT_H
#include "types.h"
/* Segment selectors */
#define KERNEL_CS 0x08
/* How every interrupt gate (handler) is defined */
typedef struct {
u16 low_offset; /* Lower 16 bits of handler function address */
u16 sel; /* Kernel segment selector */
u8 always0;
/* First byte
* Bit 7: "Interrupt is present"
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
* Bit 4: Set to 0 for interrupt gates
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
u8 flags;
u16 high_offset; /* Higher 16 bits of handler function address */
} __attribute__((packed)) idt_gate_t ;
/* A pointer to the array of interrupt handlers.
* Assembly instruction 'lidt' will read it */
typedef struct {
u16 limit;
u32 base;
} __attribute__((packed)) idt_register_t;
#define IDT_ENTRIES 256
idt_gate_t idt[IDT_ENTRIES];
idt_register_t idt_reg;
/* Functions implemented in idt.c */
void set_idt_gate(int n, u32 handler);
void set_idt();
#endif
================================================
FILE: 18-interrupts/cpu/interrupt.asm
================================================
; Defined in isr.c
[extern isr_handler]
; Common ISR code
isr_common_stub:
; 1. Save CPU state
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; 2. Call C handler
call isr_handler
; 3. Restore state
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
; We don't get information about which interrupt was caller
; when the handler is run, so we will need to have a different handler
; for every interrupt.
; Furthermore, some interrupts push an error code onto the stack but others
; don't, so we will push a dummy error code for those which don't, so that
; we have a consistent stack for all of them.
; First make the ISRs global
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31
; 0: Divide By Zero Exception
isr0:
cli
push byte 0
push byte 0
jmp isr_common_stub
; 1: Debug Exception
isr1:
cli
push byte 0
push byte 1
jmp isr_common_stub
; 2: Non Maskable Interrupt Exception
isr2:
cli
push byte 0
push byte 2
jmp isr_common_stub
; 3: Int 3 Exception
isr3:
cli
push byte 0
push byte 3
jmp isr_common_stub
; 4: INTO Exception
isr4:
cli
push byte 0
push byte 4
jmp isr_common_stub
; 5: Out of Bounds Exception
isr5:
cli
push byte 0
push byte 5
jmp isr_common_stub
; 6: Invalid Opcode Exception
isr6:
cli
push byte 0
push byte 6
jmp isr_common_stub
; 7: Coprocessor Not Available Exception
isr7:
cli
push byte 0
push byte 7
jmp isr_common_stub
; 8: Double Fault Exception (With Error Code!)
isr8:
cli
push byte 8
jmp isr_common_stub
; 9: Coprocessor Segment Overrun Exception
isr9:
cli
push byte 0
push byte 9
jmp isr_common_stub
; 10: Bad TSS Exception (With Error Code!)
isr10:
cli
push byte 10
jmp isr_common_stub
; 11: Segment Not Present Exception (With Error Code!)
isr11:
cli
push byte 11
jmp isr_common_stub
; 12: Stack Fault Exception (With Error Code!)
isr12:
cli
push byte 12
jmp isr_common_stub
; 13: General Protection Fault Exception (With Error Code!)
isr13:
cli
push byte 13
jmp isr_common_stub
; 14: Page Fault Exception (With Error Code!)
isr14:
cli
push byte 14
jmp isr_common_stub
; 15: Reserved Exception
isr15:
cli
push byte 0
push byte 15
jmp isr_common_stub
; 16: Floating Point Exception
isr16:
cli
push byte 0
push byte 16
jmp isr_common_stub
; 17: Alignment Check Exception
isr17:
cli
push byte 0
push byte 17
jmp isr_common_stub
; 18: Machine Check Exception
isr18:
cli
push byte 0
push byte 18
jmp isr_common_stub
; 19: Reserved
isr19:
cli
push byte 0
push byte 19
jmp isr_common_stub
; 20: Reserved
isr20:
cli
push byte 0
push byte 20
jmp isr_common_stub
; 21: Reserved
isr21:
cli
push byte 0
push byte 21
jmp isr_common_stub
; 22: Reserved
isr22:
cli
push byte 0
push byte 22
jmp isr_common_stub
; 23: Reserved
isr23:
cli
push byte 0
push byte 23
jmp isr_common_stub
; 24: Reserved
isr24:
cli
push byte 0
push byte 24
jmp isr_common_stub
; 25: Reserved
isr25:
cli
push byte 0
push byte 25
jmp isr_common_stub
; 26: Reserved
isr26:
cli
push byte 0
push byte 26
jmp isr_common_stub
; 27: Reserved
isr27:
cli
push byte 0
push byte 27
jmp isr_common_stub
; 28: Reserved
isr28:
cli
push byte 0
push byte 28
jmp isr_common_stub
; 29: Reserved
isr29:
cli
push byte 0
push byte 29
jmp isr_common_stub
; 30: Reserved
isr30:
cli
push byte 0
push byte 30
jmp isr_common_stub
; 31: Reserved
isr31:
cli
push byte 0
push byte 31
jmp isr_common_stub
================================================
FILE: 18-interrupts/cpu/isr.c
================================================
#include "isr.h"
#include "idt.h"
#include "../drivers/screen.h"
#include "../kernel/util.h"
/* Can't do this with a loop because we need the address
* of the function names */
void isr_install() {
set_idt_gate(0, (u32)isr0);
set_idt_gate(1, (u32)isr1);
set_idt_gate(2, (u32)isr2);
set_idt_gate(3, (u32)isr3);
set_idt_gate(4, (u32)isr4);
set_idt_gate(5, (u32)isr5);
set_idt_gate(6, (u32)isr6);
set_idt_gate(7, (u32)isr7);
set_idt_gate(8, (u32)isr8);
set_idt_gate(9, (u32)isr9);
set_idt_gate(10, (u32)isr10);
set_idt_gate(11, (u32)isr11);
set_idt_gate(12, (u32)isr12);
set_idt_gate(13, (u32)isr13);
set_idt_gate(14, (u32)isr14);
set_idt_gate(15, (u32)isr15);
set_idt_gate(16, (u32)isr16);
set_idt_gate(17, (u32)isr17);
set_idt_gate(18, (u32)isr18);
set_idt_gate(19, (u32)isr19);
set_idt_gate(20, (u32)isr20);
set_idt_gate(21, (u32)isr21);
set_idt_gate(22, (u32)isr22);
set_idt_gate(23, (u32)isr23);
set_idt_gate(24, (u32)isr24);
set_idt_gate(25, (u32)isr25);
set_idt_gate(26, (u32)isr26);
set_idt_gate(27, (u32)isr27);
set_idt_gate(28, (u32)isr28);
set_idt_gate(29, (u32)isr29);
set_idt_gate(30, (u32)isr30);
set_idt_gate(31, (u32)isr31);
set_idt(); // Load with ASM
}
/* To print the message which defines every exception */
char *exception_messages[] = {
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
void isr_handler(registers_t r) {
kprint("received interrupt: ");
char s[3];
int_to_ascii(r.int_no, s);
kprint(s);
kprint("\n");
kprint(exception_messages[r.int_no]);
kprint("\n");
}
================================================
FILE: 18-interrupts/cpu/isr.h
================================================
#ifndef ISR_H
#define ISR_H
#include "types.h"
/* ISRs reserved for CPU exceptions */
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
/* Struct which aggregates many registers */
typedef struct {
u32 ds; /* Data segment selector */
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
} registers_t;
void isr_install();
void isr_handler(registers_t r);
#endif
================================================
FILE: 18-interrupts/cpu/types.h
================================================
#ifndef TYPES_H
#define TYPES_H
/* Instead of using 'chars' to allocate non-character bytes,
* we will use these new type with no semantic meaning */
typedef unsigned int u32;
typedef int s32;
typedef unsigned short u16;
typedef short s16;
typedef unsigned char u8;
typedef char s8;
#define low_16(address) (u16)((address) & 0xFFFF)
#define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
#endif
================================================
FILE: 18-interrupts/drivers/ports.c
================================================
#include "ports.h"
/**
* Read a byte from the specified port
*/
u8 port_byte_in (u16 port) {
u8 result;
/* Inline assembler syntax
* !! Notice how the source and destination registers are switched from NASM !!
*
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
* '"d" (port)': map the C variable '(port)' into e'd'x register
*
* Inputs and outputs are separated by colons
*/
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out (u16 port, u8 data) {
/* Notice how here both registers are mapped to C variables and
* nothing is returned, thus, no equals '=' in the asm syntax
* However we see a comma since there are two variables in the input area
* and none in the 'return' area
*/
__asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
}
u16 port_word_in (u16 port) {
u16 result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void port_word_out (u16 port, u16 data) {
__asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
}
================================================
FILE: 18-interrupts/drivers/ports.h
================================================
#ifndef PORTS_H
#define PORTS_H
#include "../cpu/types.h"
unsigned char port_byte_in (u16 port);
void port_byte_out (u16 port, u8 data);
unsigned short port_word_in (u16 port);
void port_word_out (u16 port, u16 data);
#endif
================================================
FILE: 18-interrupts/drivers/screen.c
================================================
#include "screen.h"
#include "ports.h"
#include "../kernel/util.h"
/* Declaration of private functions */
int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char c, int col, int row, char attr);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);
/**********************************************************
* Public Kernel API functions *
**********************************************************/
/**
* Print a message on the specified location
* If col, row, are negative, we will use the current offset
*/
void kprint_at(char *message, int col, int row) {
/* Set cursor if col/row are negative */
int offset;
if (col >= 0 && row >= 0)
offset = get_offset(col, row);
else {
offset = get_cursor_offset();
row = get_offset_row(offset);
col = get_offset_col(offset);
}
/* Loop through message and print it */
int i = 0;
while (message[i] != 0) {
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
/* Compute row/col for next iteration */
row = get_offset_row(offset);
col = get_offset_col(offset);
}
}
void kprint(char *message) {
kprint_at(message, -1, -1);
}
/**********************************************************
* Private kernel functions *
**********************************************************/
/**
* Innermost print function for our kernel, directly accesses the video memory
*
* If 'col' and 'row' are negative, we will print at current cursor location
* If 'attr' is zero it will use 'white on black' as default
* Returns the offset of the next character
* Sets the video cursor to the returned offset
*/
int print_char(char c, int col, int row, char attr) {
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
if (!attr) attr = WHITE_ON_BLACK;
/* Error control: print a red 'E' if the coords aren't right */
if (col >= MAX_COLS || row >= MAX_ROWS) {
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
return get_offset(col, row);
}
int offset;
if (col >= 0 && row >= 0) offset = get_offset(col, row);
else offset = get_cursor_offset();
if (c == '\n') {
row = get_offset_row(offset);
offset = get_offset(0, row+1);
} else {
vidmem[offset] = c;
vidmem[offset+1] = attr;
offset += 2;
}
/* Check if the offset is over screen size and scroll */
if (offset >= MAX_ROWS * MAX_COLS * 2) {
int i;
for (i = 1; i < MAX_ROWS; i++)
memory_copy(get_offset(0, i) + VIDEO_ADDRESS,
get_offset(0, i-1) + VIDEO_ADDRESS,
MAX_COLS * 2);
/* Blank last line */
char *last_line = get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
offset -= 2 * MAX_COLS;
}
set_cursor_offset(offset);
return offset;
}
int get_cursor_offset() {
/* Use the VGA ports to get the current cursor position
* 1. Ask for high byte of the cursor offset (data 14)
* 2. Ask for low byte (data 15)
*/
port_byte_out(REG_SCREEN_CTRL, 14);
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
port_byte_out(REG_SCREEN_CTRL, 15);
offset += port_byte_in(REG_SCREEN_DATA);
return offset * 2; /* Position * size of character cell */
}
void set_cursor_offset(int offset) {
/* Similar to get_cursor_offset, but instead of reading we write data */
offset /= 2;
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}
void clear_screen() {
int screen_size = MAX_COLS * MAX_ROWS;
int i;
char *screen = VIDEO_ADDRESS;
for (i = 0; i < screen_size; i++) {
screen[i*2] = ' ';
screen[i*2+1] = WHITE_ON_BLACK;
}
set_cursor_offset(get_offset(0, 0));
}
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
================================================
FILE: 18-interrupts/drivers/screen.h
================================================
#ifndef SCREEN_H
#define SCREEN_H
#define VIDEO_ADDRESS 0xb8000
#define MAX_ROWS 25
#define MAX_COLS 80
#define WHITE_ON_BLACK 0x0f
#define RED_ON_WHITE 0xf4
/* Screen i/o ports */
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
/* Public kernel API */
void clear_screen();
void kprint_at(char *message, int col, int row);
void kprint(char *message);
#endif
================================================
FILE: 18-interrupts/kernel/kernel.c
================================================
#include "../drivers/screen.h"
#include "util.h"
#include "../cpu/isr.h"
#include "../cpu/idt.h"
void main() {
isr_install();
/* Test the interrupts */
__asm__ __volatile__("int $2");
__asm__ __volatile__("int $3");
}
================================================
FILE: 18-interrupts/kernel/util.c
================================================
#include "util.h"
void memory_copy(char *source, char *dest, int nbytes) {
int i;
for (i = 0; i < nbytes; i++) {
*(dest + i) = *(source + i);
}
}
void memory_set(u8 *dest, u8 val, u32 len) {
u8 *temp = (u8 *)dest;
for ( ; len != 0; len--) *temp++ = val;
}
/**
* K&R implementation
*/
void int_to_ascii(int n, char str[]) {
int i, sign;
if ((sign = n) < 0) n = -n;
i = 0;
do {
str[i++] = n % 10 + '0';
} while ((n /= 10) > 0);
if (sign < 0) str[i++] = '-';
str[i] = '\0';
/* TODO: implement "reverse" */
}
================================================
FILE: 18-interrupts/kernel/util.h
================================================
#ifndef UTIL_H
#define UTIL_H
#include "../cpu/types.h"
void memory_copy(char *source, char *dest, int nbytes);
void memory_set(u8 *dest, u8 val, u32 len);
void int_to_ascii(int n, char str[]);
#endif
================================================
FILE: 19-interrupts-irqs/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
================================================
FILE: 19-interrupts-irqs/README.md
================================================
*Concepts you may want to Google beforehand: IRQs, PIC, polling*
**Goal: Finish the interrupts implementation and CPU timer**
When the CPU boots, the PIC maps IRQs 0-7 to INT 0x8-0xF
and IRQs 8-15 to INT 0x70-0x77. This conflicts with the ISRs
we programmed last lesson. Since we programmed ISRs 0-31,
it is standard to remap the IRQs to ISRs 32-47.
The PICs are communicated with via I/O ports (see lesson 15).
The Master PIC has command 0x20 and data 0x21, while the slave has
command 0xA0 and data 0xA1.
The code for remapping the PICs is weird and includes
some masks, so check
[this article](http://www.osdev.org/wiki/PIC) if you're curious.
Otherwise, just look at `cpu/isr.c`, new code after we set the IDT
gates for the ISRs. After that, we add the IDT gates for IRQs.
Now we jump to assembler, at `interrupt.asm`. The first task is to
add global definitions for the IRQ symbols we just used in the C code.
Look at the end of the `global` statements.
Then, add the IRQ handlers. Same `interrupt.asm`, at the bottom. Notice
how they jump to a new common stub: `irq_common_stub` (next step)
We then create this `irq_common_stub` which is very similar to the ISR one.
It is located at the top of `interrupt.asm`, and it also defines
a new `[extern irq_handler]`
Now back to C code, to write the `irq_handler()` in `isr.c`. It sends some
EOIs to the PICs and calls the appropriate handler, which is stored in an array
named `interrupt_handlers` and defined at the top of the file. The new structs
are defined in `isr.h`. We will also use a simple function to register
the interrupt handlers.
That was a lot of work, but now we can define our first IRQ handler!
There are no changes in `kernel.c`, so there is nothing new to run and see.
Please move on to the next lesson to check those shiny new IRQs.
================================================
FILE: 19-interrupts-irqs/cpu/idt.c
================================================
#include "idt.h"
#include "../kernel/util.h"
void set_idt_gate(int n, u32 handler) {
idt[n].low_offset = low_16(handler);
idt[n].sel = KERNEL_CS;
idt[n].always0 = 0;
idt[n].flags = 0x8E;
idt[n].high_offset = high_16(handler);
}
void set_idt() {
idt_reg.base = (u32) &idt;
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
/* Don't make the mistake of loading &idt -- always load &idt_reg */
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
}
================================================
FILE: 19-interrupts-irqs/cpu/idt.h
================================================
#ifndef IDT_H
#define IDT_H
#include "types.h"
/* Segment selectors */
#define KERNEL_CS 0x08
/* How every interrupt gate (handler) is defined */
typedef struct {
u16 low_offset; /* Lower 16 bits of handler function address */
u16 sel; /* Kernel segment selector */
u8 always0;
/* First byte
* Bit 7: "Interrupt is present"
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
* Bit 4: Set to 0 for interrupt gates
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
u8 flags;
u16 high_offset; /* Higher 16 bits of handler function address */
} __attribute__((packed)) idt_gate_t ;
/* A pointer to the array of interrupt handlers.
* Assembly instruction 'lidt' will read it */
typedef struct {
u16 limit;
u32 base;
} __attribute__((packed)) idt_register_t;
#define IDT_ENTRIES 256
idt_gate_t idt[IDT_ENTRIES];
idt_register_t idt_reg;
/* Functions implemented in idt.c */
void set_idt_gate(int n, u32 handler);
void set_idt();
#endif
================================================
FILE: 19-interrupts-irqs/cpu/interrupt.asm
================================================
; Defined in isr.c
[extern isr_handler]
[extern irq_handler]
; Common ISR code
isr_common_stub:
; 1. Save CPU state
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; 2. Call C handler
call isr_handler
; 3. Restore state
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
; Common IRQ code. Identical to ISR code except for the 'call'
; and the 'pop ebx'
irq_common_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call irq_handler ; Different than the ISR code
pop ebx ; Different than the ISR code
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 8
sti
iret
; We don't get information about which interrupt was caller
; when the handler is run, so we will need to have a different handler
; for every interrupt.
; Furthermore, some interrupts push an error code onto the stack but others
; don't, so we will push a dummy error code for those which don't, so that
; we have a consistent stack for all of them.
; First make the ISRs global
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31
; IRQs
global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15
; 0: Divide By Zero Exception
isr0:
cli
push byte 0
push byte 0
jmp isr_common_stub
; 1: Debug Exception
isr1:
cli
push byte 0
push byte 1
jmp isr_common_stub
; 2: Non Maskable Interrupt Exception
isr2:
cli
push byte 0
push byte 2
jmp isr_common_stub
; 3: Int 3 Exception
isr3:
cli
push byte 0
push byte 3
jmp isr_common_stub
; 4: INTO Exception
isr4:
cli
push byte 0
push byte 4
jmp isr_common_stub
; 5: Out of Bounds Exception
isr5:
cli
push byte 0
push byte 5
jmp isr_common_stub
; 6: Invalid Opcode Exception
isr6:
cli
push byte 0
push byte 6
jmp isr_common_stub
; 7: Coprocessor Not Available Exception
isr7:
cli
push byte 0
push byte 7
jmp isr_common_stub
; 8: Double Fault Exception (With Error Code!)
isr8:
cli
push byte 8
jmp isr_common_stub
; 9: Coprocessor Segment Overrun Exception
isr9:
cli
push byte 0
push byte 9
jmp isr_common_stub
; 10: Bad TSS Exception (With Error Code!)
isr10:
cli
push byte 10
jmp isr_common_stub
; 11: Segment Not Present Exception (With Error Code!)
isr11:
cli
push byte 11
jmp isr_common_stub
; 12: Stack Fault Exception (With Error Code!)
isr12:
cli
push byte 12
jmp isr_common_stub
; 13: General Protection Fault Exception (With Error Code!)
isr13:
cli
push byte 13
jmp isr_common_stub
; 14: Page Fault Exception (With Error Code!)
isr14:
cli
push byte 14
jmp isr_common_stub
; 15: Reserved Exception
isr15:
cli
push byte 0
push byte 15
jmp isr_common_stub
; 16: Floating Point Exception
isr16:
cli
push byte 0
push byte 16
jmp isr_common_stub
; 17: Alignment Check Exception
isr17:
cli
push byte 0
push byte 17
jmp isr_common_stub
; 18: Machine Check Exception
isr18:
cli
push byte 0
push byte 18
jmp isr_common_stub
; 19: Reserved
isr19:
cli
push byte 0
push byte 19
jmp isr_common_stub
; 20: Reserved
isr20:
cli
push byte 0
push byte 20
jmp isr_common_stub
; 21: Reserved
isr21:
cli
push byte 0
push byte 21
jmp isr_common_stub
; 22: Reserved
isr22:
cli
push byte 0
push byte 22
jmp isr_common_stub
; 23: Reserved
isr23:
cli
push byte 0
push byte 23
jmp isr_common_stub
; 24: Reserved
isr24:
cli
push byte 0
push byte 24
jmp isr_common_stub
; 25: Reserved
isr25:
cli
push byte 0
push byte 25
jmp isr_common_stub
; 26: Reserved
isr26:
cli
push byte 0
push byte 26
jmp isr_common_stub
; 27: Reserved
isr27:
cli
push byte 0
push byte 27
jmp isr_common_stub
; 28: Reserved
isr28:
cli
push byte 0
push byte 28
jmp isr_common_stub
; 29: Reserved
isr29:
cli
push byte 0
push byte 29
jmp isr_common_stub
; 30: Reserved
isr30:
cli
push byte 0
push byte 30
jmp isr_common_stub
; 31: Reserved
isr31:
cli
push byte 0
push byte 31
jmp isr_common_stub
; IRQ handlers
irq0:
cli
push byte 0
push byte 32
jmp irq_common_stub
irq1:
cli
push byte 1
push byte 33
jmp irq_common_stub
irq2:
cli
push byte 2
push byte 34
jmp irq_common_stub
irq3:
cli
push byte 3
push byte 35
jmp irq_common_stub
irq4:
cli
push byte 4
push byte 36
jmp irq_common_stub
irq5:
cli
push byte 5
push byte 37
jmp irq_common_stub
irq6:
cli
push byte 6
push byte 38
jmp irq_common_stub
irq7:
cli
push byte 7
push byte 39
jmp irq_common_stub
irq8:
cli
push byte 8
push byte 40
jmp irq_common_stub
irq9:
cli
push byte 9
push byte 41
jmp irq_common_stub
irq10:
cli
push byte 10
push byte 42
jmp irq_common_stub
irq11:
cli
push byte 11
push byte 43
jmp irq_common_stub
irq12:
cli
push byte 12
push byte 44
jmp irq_common_stub
irq13:
cli
push byte 13
push byte 45
jmp irq_common_stub
irq14:
cli
push byte 14
push byte 46
jmp irq_common_stub
irq15:
cli
push byte 15
push byte 47
jmp irq_common_stub
================================================
FILE: 19-interrupts-irqs/cpu/isr.c
================================================
#include "isr.h"
#include "idt.h"
#include "../drivers/screen.h"
#include "../kernel/util.h"
#include "../drivers/ports.h"
isr_t interrupt_handlers[256];
/* Can't do this with a loop because we need the address
* of the function names */
void isr_install() {
set_idt_gate(0, (u32)isr0);
set_idt_gate(1, (u32)isr1);
set_idt_gate(2, (u32)isr2);
set_idt_gate(3, (u32)isr3);
set_idt_gate(4, (u32)isr4);
set_idt_gate(5, (u32)isr5);
set_idt_gate(6, (u32)isr6);
set_idt_gate(7, (u32)isr7);
set_idt_gate(8, (u32)isr8);
set_idt_gate(9, (u32)isr9);
set_idt_gate(10, (u32)isr10);
set_idt_gate(11, (u32)isr11);
set_idt_gate(12, (u32)isr12);
set_idt_gate(13, (u32)isr13);
set_idt_gate(14, (u32)isr14);
set_idt_gate(15, (u32)isr15);
set_idt_gate(16, (u32)isr16);
set_idt_gate(17, (u32)isr17);
set_idt_gate(18, (u32)isr18);
set_idt_gate(19, (u32)isr19);
set_idt_gate(20, (u32)isr20);
set_idt_gate(21, (u32)isr21);
set_idt_gate(22, (u32)isr22);
set_idt_gate(23, (u32)isr23);
set_idt_gate(24, (u32)isr24);
set_idt_gate(25, (u32)isr25);
set_idt_gate(26, (u32)isr26);
set_idt_gate(27, (u32)isr27);
set_idt_gate(28, (u32)isr28);
set_idt_gate(29, (u32)isr29);
set_idt_gate(30, (u32)isr30);
set_idt_gate(31, (u32)isr31);
// Remap the PIC
port_byte_out(0x20, 0x11);
port_byte_out(0xA0, 0x11);
port_byte_out(0x21, 0x20);
port_byte_out(0xA1, 0x28);
port_byte_out(0x21, 0x04);
port_byte_out(0xA1, 0x02);
port_byte_out(0x21, 0x01);
port_byte_out(0xA1, 0x01);
port_byte_out(0x21, 0x0);
port_byte_out(0xA1, 0x0);
// Install the IRQs
set_idt_gate(32, (u32)irq0);
set_idt_gate(33, (u32)irq1);
set_idt_gate(34, (u32)irq2);
set_idt_gate(35, (u32)irq3);
set_idt_gate(36, (u32)irq4);
set_idt_gate(37, (u32)irq5);
set_idt_gate(38, (u32)irq6);
set_idt_gate(39, (u32)irq7);
set_idt_gate(40, (u32)irq8);
set_idt_gate(41, (u32)irq9);
set_idt_gate(42, (u32)irq10);
set_idt_gate(43, (u32)irq11);
set_idt_gate(44, (u32)irq12);
set_idt_gate(45, (u32)irq13);
set_idt_gate(46, (u32)irq14);
set_idt_gate(47, (u32)irq15);
set_idt(); // Load with ASM
}
/* To print the message which defines every exception */
char *exception_messages[] = {
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
void isr_handler(registers_t r) {
kprint("received interrupt: ");
char s[3];
int_to_ascii(r.int_no, s);
kprint(s);
kprint("\n");
kprint(exception_messages[r.int_no]);
kprint("\n");
}
void register_interrupt_handler(u8 n, isr_t handler) {
interrupt_handlers[n] = handler;
}
void irq_handler(registers_t r) {
/* After every interrupt we need to send an EOI to the PICs
* or they will not send another interrupt again */
if (r.int_no >= 40) port_byte_out(0xA0, 0x20); /* slave */
port_byte_out(0x20, 0x20); /* master */
/* Handle the interrupt in a more modular way */
if (interrupt_handlers[r.int_no] != 0) {
isr_t handler = interrupt_handlers[r.int_no];
handler(r);
}
}
================================================
FILE: 19-interrupts-irqs/cpu/isr.h
================================================
#ifndef ISR_H
#define ISR_H
#include "types.h"
/* ISRs reserved for CPU exceptions */
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
/* IRQ definitions */
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();
#define IRQ0 32
#define IRQ1 33
#define IRQ2 34
#define IRQ3 35
#define IRQ4 36
#define IRQ5 37
#define IRQ6 38
#define IRQ7 39
#define IRQ8 40
#define IRQ9 41
#define IRQ10 42
#define IRQ11 43
#define IRQ12 44
#define IRQ13 45
#define IRQ14 46
#define IRQ15 47
/* Struct which aggregates many registers */
typedef struct {
u32 ds; /* Data segment selector */
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
} registers_t;
void isr_install();
void isr_handler(registers_t r);
typedef void (*isr_t)(registers_t);
void register_interrupt_handler(u8 n, isr_t handler);
#endif
================================================
FILE: 19-interrupts-irqs/cpu/types.h
================================================
#ifndef TYPES_H
#define TYPES_H
/* Instead of using 'chars' to allocate non-character bytes,
* we will use these new type with no semantic meaning */
typedef unsigned int u32;
typedef int s32;
typedef unsigned short u16;
typedef short s16;
typedef unsigned char u8;
typedef char s8;
#define low_16(address) (u16)((address) & 0xFFFF)
#define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
#endif
================================================
FILE: 19-interrupts-irqs/kernel/kernel.c
================================================
#include "../drivers/screen.h"
#include "util.h"
#include "../cpu/isr.h"
#include "../cpu/idt.h"
void main() {
isr_install();
/* Test the interrupts */
__asm__ __volatile__("int $2");
__asm__ __volatile__("int $3");
}
================================================
FILE: 19-interrupts-irqs/kernel/util.c
================================================
#include "util.h"
void memory_copy(char *source, char *dest, int nbytes) {
int i;
for (i = 0; i < nbytes; i++) {
*(dest + i) = *(source + i);
}
}
void memory_set(u8 *dest, u8 val, u32 len) {
u8 *temp = (u8 *)dest;
for ( ; len != 0; len--) *temp++ = val;
}
/**
* K&R implementation
*/
void int_to_ascii(int n, char str[]) {
int i, sign;
if ((sign = n) < 0) n = -n;
i = 0;
do {
str[i++] = n % 10 + '0';
} while ((n /= 10) > 0);
if (sign < 0) str[i++] = '-';
str[i] = '\0';
/* TODO: implement "reverse" */
}
================================================
FILE: 19-interrupts-irqs/kernel/util.h
================================================
#ifndef UTIL_H
#define UTIL_H
#include "../cpu/types.h"
void memory_copy(char *source, char *dest, int nbytes);
void memory_set(u8 *dest, u8 val, u32 len);
void int_to_ascii(int n, char str[]);
#endif
================================================
FILE: 20-interrupts-timer/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o
================================================
FILE: 20-interrupts-timer/README.md
================================================
*Concepts you may want to Google beforehand: CPU timer, keyboard interrupts, scancode*
**Goal: Implement our first IRQ handlers: the CPU timer and the keyboard**
Everything is now ready to test our hardware interrupts.
Timer
-----
The timer is easy to configure. First we'll declare an `init_timer()` on `cpu/timer.h` and
implement it on `cpu/timer.c`. It is just a matter of computing the clock frequency and
sending the bytes to the appropriate ports.
We will now fix `kernel/utils.c int_to_ascii()` to print the numbers in the correct order.
For that, we need to implement `reverse()` and `strlen()`.
Finally, go back to the `kernel/kernel.c` and do two things. Enable interrupts again
(very important!) and then initialize the timer interrupt.
Go `make run` and you'll see the clock ticking!
Keyboard
--------
The keyboard is even easier, with a drawback. The PIC does not send us the ASCII code
for the pressed key, but the scancode for the key-down and the key-up events, so we
will need to translate those.
Check out `drivers/keyboard.c` where there are two functions: the callback and
the initialization which configures the interrupt callback. A new `keyboard.h` was
created with the definitions.
`keyboard.c` also has a long table to translate scancodes to ASCII keys. For the time
being, we will only implement a simple subset of the US keyboard. You can read
more [about scancodes here](http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html)
I don't know about you, but I'm thrilled! We are very close to building a simple shell.
In the next chapter, we will expand a little bit on keyboard input
================================================
FILE: 20-interrupts-timer/cpu/timer.c
================================================
#include "timer.h"
#include "../drivers/screen.h"
#include "../kernel/util.h"
#include "isr.h"
u32 tick = 0;
static void timer_callback(registers_t regs) {
tick++;
kprint("Tick: ");
char tick_ascii[256];
int_to_ascii(tick, tick_ascii);
kprint(tick_ascii);
kprint("\n");
}
void init_timer(u32 freq) {
/* Install the function we just wrote */
register_interrupt_handler(IRQ0, timer_callback);
/* Get the PIT value: hardware clock at 1193180 Hz */
u32 divisor = 1193180 / freq;
u8 low = (u8)(divisor & 0xFF);
u8 high = (u8)( (divisor >> 8) & 0xFF);
/* Send the command */
port_byte_out(0x43, 0x36); /* Command port */
port_byte_out(0x40, low);
port_byte_out(0x40, high);
}
================================================
FILE: 20-interrupts-timer/cpu/timer.h
================================================
#ifndef TIMER_H
#define TIMER_H
#include "../kernel/util.h"
void init_timer(u32 freq);
#endif
================================================
FILE: 20-interrupts-timer/drivers/keyboard.c
================================================
#include "keyboard.h"
#include "ports.h"
#include "../cpu/isr.h"
#include "screen.h"
static void keyboard_callback(registers_t regs) {
/* The PIC leaves us the scancode in port 0x60 */
u8 scancode = port_byte_in(0x60);
char *sc_ascii;
int_to_ascii(scancode, sc_ascii);
kprint("Keyboard scancode: ");
kprint(sc_ascii);
kprint(", ");
print_letter(scancode);
kprint("\n");
}
void init_keyboard() {
register_interrupt_handler(IRQ1, keyboard_callback);
}
void print_letter(u8 scancode) {
switch (scancode) {
case 0x0:
kprint("ERROR");
break;
case 0x1:
kprint("ESC");
break;
case 0x2:
kprint("1");
break;
case 0x3:
kprint("2");
break;
case 0x4:
kprint("3");
break;
case 0x5:
kprint("4");
break;
case 0x6:
kprint("5");
break;
case 0x7:
kprint("6");
break;
case 0x8:
kprint("7");
break;
case 0x9:
kprint("8");
break;
case 0x0A:
kprint("9");
break;
case 0x0B:
kprint("0");
break;
case 0x0C:
kprint("-");
break;
case 0x0D:
kprint("+");
break;
case 0x0E:
kprint("Backspace");
break;
case 0x0F:
kprint("Tab");
break;
case 0x10:
kprint("Q");
break;
case 0x11:
kprint("W");
break;
case 0x12:
kprint("E");
break;
case 0x13:
kprint("R");
break;
case 0x14:
kprint("T");
break;
case 0x15:
kprint("Y");
break;
case 0x16:
kprint("U");
break;
case 0x17:
kprint("I");
break;
case 0x18:
kprint("O");
break;
case 0x19:
kprint("P");
break;
case 0x1A:
kprint("[");
break;
case 0x1B:
kprint("]");
break;
case 0x1C:
kprint("ENTER");
break;
case 0x1D:
kprint("LCtrl");
break;
case 0x1E:
kprint("A");
break;
case 0x1F:
kprint("S");
break;
case 0x20:
kprint("D");
break;
case 0x21:
kprint("F");
break;
case 0x22:
kprint("G");
break;
case 0x23:
kprint("H");
break;
case 0x24:
kprint("J");
break;
case 0x25:
kprint("K");
break;
case 0x26:
kprint("L");
break;
case 0x27:
kprint(";");
break;
case 0x28:
kprint("'");
break;
case 0x29:
kprint("`");
break;
case 0x2A:
kprint("LShift");
break;
case 0x2B:
kprint("\\");
break;
case 0x2C:
kprint("Z");
break;
case 0x2D:
kprint("X");
break;
case 0x2E:
kprint("C");
break;
case 0x2F:
kprint("V");
break;
case 0x30:
kprint("B");
break;
case 0x31:
kprint("N");
break;
case 0x32:
kprint("M");
break;
case 0x33:
kprint(",");
break;
case 0x34:
kprint(".");
break;
case 0x35:
kprint("/");
break;
case 0x36:
kprint("Rshift");
break;
case 0x37:
kprint("Keypad *");
break;
case 0x38:
kprint("LAlt");
break;
case 0x39:
kprint("Spc");
break;
default:
/* 'keuyp' event corresponds to the 'keydown' + 0x80
* it may still be a scancode we haven't implemented yet, or
* maybe a control/escape sequence */
if (scancode <= 0x7f) {
kprint("Unknown key down");
} else if (scancode <= 0x39 + 0x80) {
kprint("key up ");
print_letter(scancode - 0x80);
} else kprint("Unknown key up");
break;
}
}
================================================
FILE: 20-interrupts-timer/drivers/keyboard.h
================================================
#include "../cpu/types.h"
void init_keyboard();
================================================
FILE: 20-interrupts-timer/drivers/ports.c
================================================
#include "ports.h"
/**
* Read a byte from the specified port
*/
u8 port_byte_in (u16 port) {
u8 result;
/* Inline assembler syntax
* !! Notice how the source and destination registers are switched from NASM !!
*
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
* '"d" (port)': map the C variable '(port)' into e'd'x register
*
* Inputs and outputs are separated by colons
*/
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void port_byte_out (u16 port, u8 data) {
/* Notice how here both registers are mapped to C variables and
* nothing is returned, thus, no equals '=' in the asm syntax
* However we see a comma since there are two variables in the input area
* and none in the 'return' area
*/
__asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
}
u16 port_word_in (u16 port) {
u16 result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void port_word_out (u16 port, u16 data) {
__asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
}
================================================
FILE: 20-interrupts-timer/drivers/ports.h
================================================
#ifndef PORTS_H
#define PORTS_H
#include "../cpu/types.h"
unsigned char port_byte_in (u16 port);
void port_byte_out (u16 port, u8 data);
unsigned short port_word_in (u16 port);
void port_word_out (u16 port, u16 data);
#endif
================================================
FILE: 20-interrupts-timer/drivers/screen.c
================================================
#include "screen.h"
#include "../drivers/ports.h"
/* Declaration of private functions */
int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char c, int col, int row, char attr);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);
/**********************************************************
* Public Kernel API functions *
**********************************************************/
/**
* Print a message on the specified location
* If col, row, are negative, we will use the current offset
*/
void kprint_at(char *message, int col, int row) {
/* Set cursor if col/row are negative */
int offset;
if (col >= 0 && row >= 0)
offset = get_offset(col, row);
else {
offset = get_cursor_offset();
row = get_offset_row(offset);
col = get_offset_col(offset);
}
/* Loop through message and print it */
int i = 0;
while (message[i] != 0) {
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
/* Compute row/col for next iteration */
row = get_offset_row(offset);
col = get_offset_col(offset);
}
}
void kprint(char *message) {
kprint_at(message, -1, -1);
}
/**********************************************************
* Private kernel functions *
**********************************************************/
/**
* Innermost print function for our kernel, directly accesses the video memory
*
* If 'col' and 'row' are negative, we will print at current cursor location
* If 'attr' is zero it will use 'white on black' as default
* Returns the offset of the next character
* Sets the video cursor to the returned offset
*/
int print_char(char c, int col, int row, char attr) {
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
if (!attr) attr = WHITE_ON_BLACK;
/* Error control: print a red 'E' if the coords aren't right */
if (col >= MAX_COLS || row >= MAX_ROWS) {
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
return get_offset(col, row);
}
int offset;
if (col >= 0 && row >= 0) offset = get_offset(col, row);
else offset = get_cursor_offset();
if (c == '\n') {
row = get_offset_row(offset);
offset = get_offset(0, row+1);
} else {
vidmem[offset] = c;
vidmem[offset+1] = attr;
offset += 2;
}
/* Check if the offset is over screen size and scroll */
if (offset >= MAX_ROWS * MAX_COLS * 2) {
int i;
for (i = 1; i < MAX_ROWS; i++)
memory_copy(get_offset(0, i) + VIDEO_ADDRESS,
get_offset(0, i-1) + VIDEO_ADDRESS,
MAX_COLS * 2);
/* Blank last line */
char *last_line = get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
offset -= 2 * MAX_COLS;
}
set_cursor_offset(offset);
return offset;
}
int get_cursor_offset() {
/* Use the VGA ports to get the current cursor position
* 1. Ask for high byte of the cursor offset (data 14)
* 2. Ask for low byte (data 15)
*/
port_byte_out(REG_SCREEN_CTRL, 14);
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
port_byte_out(REG_SCREEN_CTRL, 15);
offset += port_byte_in(REG_SCREEN_DATA);
return offset * 2; /* Position * size of character cell */
}
void set_cursor_offset(int offset) {
/* Similar to get_cursor_offset, but instead of reading we write data */
offset /= 2;
port_byte_out(REG_SCREEN_CTRL, 14);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
port_byte_out(REG_SCREEN_CTRL, 15);
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}
void clear_screen() {
int screen_size = MAX_COLS * MAX_ROWS;
int i;
char *screen = VIDEO_ADDRESS;
for (i = 0; i < screen_size; i++) {
screen[i*2] = ' ';
screen[i*2+1] = WHITE_ON_BLACK;
}
set_cursor_offset(get_offset(0, 0));
}
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }
================================================
FILE: 20-interrupts-timer/drivers/screen.h
================================================
#ifndef SCREEN_H
#define SCREEN_H
#define VIDEO_ADDRESS 0xb8000
#define MAX_ROWS 25
#define MAX_COLS 80
#define WHITE_ON_BLACK 0x0f
#define RED_ON_WHITE 0xf4
/* Screen i/o ports */
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
/* Public kernel API */
void clear_screen();
void kprint_at(char *message, int col, int row);
void kprint(char *message);
#endif
================================================
FILE: 20-interrupts-timer/kernel/kernel.c
================================================
#include "../cpu/isr.h"
#include "../cpu/timer.h"
#include "../drivers/keyboard.h"
void main() {
isr_install();
asm volatile("sti");
init_timer(50);
/* Comment out the timer IRQ handler to read
* the keyboard IRQs easier */
init_keyboard();
}
================================================
FILE: 20-interrupts-timer/kernel/util.c
================================================
#include "util.h"
void memory_copy(char *source, char *dest, int nbytes) {
int i;
for (i = 0; i < nbytes; i++) {
*(dest + i) = *(source + i);
}
}
void memory_set(u8 *dest, u8 val, u32 len) {
u8 *temp = (u8 *)dest;
for ( ; len != 0; len--) *temp++ = val;
}
/**
* K&R implementation
*/
void int_to_ascii(int n, char str[]) {
int i, sign;
if ((sign = n) < 0) n = -n;
i = 0;
do {
str[i++] = n % 10 + '0';
} while ((n /= 10) > 0);
if (sign < 0) str[i++] = '-';
str[i] = '\0';
reverse(str);
}
/* K&R */
void reverse(char s[]) {
int c, i, j;
for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
/* K&R */
int strlen(char s[]) {
int i = 0;
while (s[i] != '\0') ++i;
return i;
}
================================================
FILE: 20-interrupts-timer/kernel/util.h
================================================
#ifndef UTIL_H
#define UTIL_H
#include "../cpu/types.h"
void memory_copy(char *source, char *dest, int nbytes);
void memory_set(u8 *dest, u8 val, u32 len);
void int_to_ascii(int n, char str[]);
void reverse(char s[]);
int strlen(char s[]);
#endif
================================================
FILE: 21-shell/Makefile
================================================
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
# Nice syntax for file extension replacement
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
# Change this if your cross-compiler is somewhere else
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
# -g: Use debugging symbols in gcc
CFLAGS = -g -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs \
-Wall -Wextra -Werror
# First rule is run by default
os-image.bin: boot/bootsect.bin kernel.bin
cat $^ > os-image.bin
# '--oformat binary' deletes all symbols as a collateral, so we don't need
# to 'strip' them manually on this case
kernel.bin: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Used for debugging purposes
kernel.elf: boot/kernel_entry.o ${OBJ}
i386-elf-ld -o $@ -Ttext 0x1000 $^
run: os-image.bin
qemu-system-i386 -fda os-image.bin
# Open the connection to qemu and load our kernel-object file with symbols
debug: os-image.bin kernel.elf
qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
# Generic rules for wildcards
# To make an object, always compile from its .c
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
%.o: %.asm
nasm $< -f elf -o $@
%.bin: %.asm
nasm $< -f bin -o $@
clean:
rm -rf *.bin *.dis *.o os-image.bin *.elf
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o
================================================
FILE: 21-shell/README.md
================================================
**Goal: Clean the code a bit and parse user input**
In this lesson we will do two things. First, we will clean up the code a bit, so it is ready
for further lessons. During the previous ones I tried to put things in the most predictable places,
but it is also a good exercise to know when the code base is growing and adapt it to current
and further needs.
Code cleaning
-------------
First of all, we will quickly start to need more utility functions
for handling strings and so on. In a regular OS, this is called the C library,
or libc for short.
Right now we have a `utils.c` which we will split into `mem.c` and `string.c`, with their respective headers.
Second, we will create a new function `irq_install()` so that the kernel
only needs to perform one call to initialize all the IRQs. That function
is akin to `isr_install()` and placed on the same `irq.c`.
While we're here, we will disable the `kprint()` on `timer_callback()`
to avoid filling the screen with junk, now that we know that it works
properly.
There is not a clear distinction between `cpu/` and `drivers/`.
Keep in mind that I'm
creating this tutorial while following many others, and each of them
has a distinct folder structure. The only change we will do for now is to
move `drivers/ports.*` into `cpu/` since it is clearly cpu-dependent code.
`boot/` is also CPU-dependent code, but we will not mess with it until
we implement the boot sequence for a different machine.
There are more switches for the `CFLAGS` on the `Makefile`, since we will now
start creating higher-level functions for our C library and we don't want
the compiler to include any external code if we make a mistake with a declaration.
We also added some flags to turn warnings into errors, since an apparently minor mistake
converting pointers can blow up later on. This also forced us to modify some misc pointer
declarations in our code.
Finally, we'll add a macro to avoid warning-errors on unused parameters on `libc/function.h`
Keyboard characters
-------------------
How to access the typed characters, then?
- When a key is pressed, the callback gets the ASCII code via a new
arrays which are defined at the beginning of `keyboard.c`
- The callback then appends that character to a buffer, `key_buffer`
- It is also printed on the screen
- When the OS wants to read user input, it calls `libc/io.c:readline()`
`keyboard.c` also parses backspace, by removing the last element
of the key buffer, and deleting it from the screen, by calling
`screen.c:kprint_backspace()`. For this we needed to modify a bit
`print_char()` to not advance the offset when printing a backspace
Responding to user input
------------------------
The keyboard callback checks for a newline, and then calls the kernel,
telling it that the user has input something. Out final libc function
is `strcmp()`, which compares two strings and returns 0 if they
are equal. If the user inputs "END", we halt the CPU.
This is the most basic shell ever, but you should be proud, because
we implemented it from scratch. Do you realize how cool this is?
If you want to, expand `kernel.c` to parse more stuff. In the future,
when we have a filesystem, we will allow the user to run some basic commands.
================================================
FILE: 21-shell/cpu/idt.c
================================================
#include "idt.h"
void set_idt_gate(int n, u32 handler) {
idt[n].low_offset = low_16(handler);
idt[n].sel = KERNEL_CS;
idt[n].always0 = 0;
idt[n].flags = 0x8E;
idt[n].high_offset = high_16(handler);
}
void set_idt() {
idt_reg.base = (u32) &idt;
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
/* Don't make the mistake of loading &idt -- always load &idt_reg */
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
}
================================================
FILE: 21-shell/cpu/idt.h
================================================
#ifndef IDT_H
#define IDT_H
#include "types.h"
/* Segment selectors */
#define KERNEL_CS 0x08
/* How every interrupt gate (handler) is defined */
typedef struct {
u16 low_offset; /* Lower 16 bits of handler function address */
u16 sel; /* Kernel segment selector */
u8 always0;
/* First byte
* Bit 7: "Interrupt is present"
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
* Bit 4: Set to 0 for interrupt gates
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
u8 flags;
u16 high_offset; /* Higher 16 bits of handler function address */
} __attribute__((packed)) idt_gate_t ;
/* A pointer to the array of interrupt handlers.
* Assembly instruction 'lidt' will read it */
typedef struct {
u16 limit;
u32 base;
} __attribute__((packed)) idt_register_t;
#define IDT_ENTRIES 256
idt_gate_t idt[IDT_ENTRIES];
idt_register_t idt_reg;
/* Functions implemented in idt.c */
void set_idt_gate(int n, u32 handler);
void set_idt();
#endif
================================================
FILE: 21-shell/cpu/interrupt.asm
================================================
; Defined in isr.c
[extern isr_handler]
[extern irq_handler]
; Common ISR code
isr_common_stub:
; 1. Save CPU state
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; 2. Call C handler
call isr_handler
; 3. Restore state
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
; Common IRQ code. Identical to ISR code except for the 'call'
; and the 'pop ebx'
irq_common_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call irq_handler ; Different than the ISR code
pop ebx ; Different than the ISR code
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 8
sti
iret
; We don't get information about which interrupt was caller
; when the handler is run, so we will need to have a different handler
; for every interrupt.
; Furthermore, some interrupts push an error code onto the stack but others
; don't, so we will push a dummy error code for those which don't, so that
; we have a consistent stack for all of them.
; First make the ISRs global
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31
; IRQs
global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15
; 0: Divide By Zero Exception
isr0:
cli
push byte 0
push byte 0
jmp isr_common_stub
; 1: Debug Exception
isr1:
cli
push byte 0
push byte 1
jmp isr_common_stub
; 2: Non Maskable Interrupt Exception
isr2:
cli
push byte 0
push byte 2
jmp isr_common_stub
; 3: Int 3 Exception
isr3:
cli
push byte 0
push byte 3
jmp isr_common_stub
; 4: INTO Exception
isr4:
cli
push byte 0
push byte 4
jmp isr_common_stub
; 5: Out of Bounds Exception
isr5:
cli
push byte 0
push byte 5
jmp isr_common_stub
; 6: Invalid Opcode Exception
isr6:
cli
push byte 0
push byte 6
jmp isr_common_stub
; 7: Coprocessor Not Available Exception
isr7:
cli
push byte 0
push byte 7
jmp isr_common_stub
; 8: Double Fault Exception (With Error Code!)
isr8:
cli
push byte 8
jmp isr_common_stub
; 9: Coprocessor Segment Overrun Exception
isr9:
cli
push byte 0
push byte 9
jmp isr_common_stub
; 10: Bad TSS Exception (With Error Code!)
isr10:
cli
push byte 10
jmp isr_common_stub
; 11: Segment Not Present Exception (With Error Code!)
isr11:
cli
push byte 11
jmp isr_common_stub
; 12: Stack Fault Exception (With Error Code!)
isr12:
cli
push byte 12
jmp isr_common_stub
; 13: General Protection Fault Exception (With Error Code!)
isr13:
cli
push byte 13
jmp isr_common_stub
; 14: Page Fault Exception (With Error Code!)
isr14:
cli
push byte 14
jmp isr_common_stub
; 15: Reserved Exception
isr15:
cli
push byte 0
push byte 15
jmp isr_common_stub
; 16: Floating Point Exception
isr16:
cli
push byte 0
push byte 16
jmp isr_common_stub
; 17: Alignment Check Exception
isr17:
cli
push byte 0
push byte 17
jmp isr_common_stub
; 18: Machine Check Exception
isr18:
cli
push byte 0
push byte 18
jmp isr_common_stub
; 19: Reserved
isr19:
cli
push byte 0
push byte 19
jmp isr_common_stub
; 20: Reserved
isr20:
cli
push byte 0
push byte 20
jmp isr_common_stub
; 21: Reserved
isr21:
cli
push byte 0
push byte 21
jmp isr_common_stub
; 22: Reserved
isr22:
cli
push byte 0
push byte 22
jmp isr_common_stub
; 23: Reserved
isr23:
cli
push byte 0
push byte 23
jmp isr_common_stub
; 24: Reserved
isr24:
cli
push byte 0
push byte 24
jmp isr_common_stub
; 25: Reserved
isr25:
cli
push byte 0
push byte 25
jmp isr_common_stub
; 26: Reserved
isr26:
cli
push byte 0
push byte 26
jmp isr_common_stub
; 27: Reserved
isr27:
cli
push byte 0
push byte 27
jmp isr_common_stub
; 28: Reserved
isr28:
cli
push byte 0
push byte 28
jmp isr_common_stub
; 29: Reserved
isr29:
cli
push byte 0
push byte 29
jmp isr_common_stub
; 30: Reserved
isr30:
cli
push byte 0
push byte 30
jmp isr_common_stub
; 31: Reserved
isr31:
cli
push byte 0
push byte 31
jmp isr_common_stub
; IRQ handlers
irq0:
cli
push byte 0
push byte 32
jmp irq_common_stub
irq1:
cli
push byte 1
push byte 33
jmp irq_common_stub
irq2:
cli
push byte 2
push byte 34
jmp irq_common_stub
irq3:
cli
push byte 3
push byte 35
jmp irq_common_stub
irq4:
cli
push byte 4
push byte 36
jmp irq_common_stub
irq5:
cli
push byte 5
push byte 37
jmp irq_common_stub
irq6:
cli
push byte 6
push byte 38
jmp irq_common_stub
irq7:
cli
push byte 7
push byte 39
jmp irq_common_stub
irq8:
cli
push byte 8
push byte 40
jmp irq_common_stub
irq9:
cli
push byte 9
push byte 41
jmp irq_common_stub
irq10:
cli
push byte 10
push byte 42
jmp irq_common_stub
irq11:
cli
push byte 11
push byte 43
jmp irq_common_stub
irq12:
cli
push byte 12
push byte 44
jmp irq_common_stub
irq13:
cli
push byte 13
push byte 45
jmp irq_common_stub
irq14:
cli
push byte 14
push byte 46
jmp irq_common_stub
irq15:
cli
push byte 15
push byte 47
jmp irq_common_stub
================================================
FILE: 21-shell/cpu/isr.c
================================================
#include "isr.h"
#include "idt.h"
#include "../drivers/screen.h"
#include "../drivers/keyboard.h"
#include "../libc/string.h"
#include "timer.h"
#include "ports.h"
isr_t interrupt_handlers[256];
/* Can't do this with a loop because we need the address
* of the function names */
void isr_install() {
set_idt_gate(0, (u32)isr0);
set_idt_gate(1, (u32)isr1);
set_idt_gate(2, (u32)isr2);
set_idt_gate(3, (u32)isr3);
set_idt_gate(4, (u32)isr4);
set_idt_gate(5, (u32)isr5);
set_idt_gate(6, (u32)isr6);
set_idt_gate(7, (u32)isr7);
set_idt_gate(8, (u32)isr8);
set_idt_gate(9, (u32)isr9);
set_idt_gate(10, (u32)isr10);
set_idt_gate(11, (u32)isr11);
set_idt_gate(12, (u32)isr12);
set_idt_gate(13, (u32)isr13);
set_idt_gate(14, (u32)isr14);
set_idt_gate(15, (u32)isr15);
set_idt_gate(16, (u32)isr16);
set_idt_gate(17, (u32)isr17);
set_idt_gate(18, (u32)isr18);
set_idt_gate(19, (u32)isr19);
set_idt_gate(20, (u32)isr20);
set_idt_gate(21, (u32)isr21);
set_idt_gate(22, (u32)isr22);
set_idt_gate(23, (u32)isr23);
set_idt_gate(24, (u32)isr24);
set_idt_gate(25, (u32)isr25);
set_idt_gate(26, (u32)isr26);
set_idt_gate(27, (u32)isr27);
set_idt_gate(28, (u32)isr28);
set_idt_gate(29, (u32)isr29);
set_idt_gate(30, (u32)isr30);
set_idt_gate(31, (u32)isr31);
// Remap the PIC
port_byte_out(0x20, 0x11);
port_byte_out(0xA0, 0x11);
port_byte_out(0x21, 0x20);
port_byte_out(0xA1, 0x28);
port_byte_out(0x21, 0x04);
port_byte_out(0xA1, 0x02);
port_byte_out(0x21, 0x01);
port_byte_out(0xA1, 0x01);
port_byte_out(0x21, 0x0);
port_byte_out(0xA1, 0x0);
// Install the IRQs
set_idt_gate(32, (u32)irq0);
set_idt_gate(33, (u32)irq1);
set_idt_gate(34, (u32)irq2);
set_idt_gate(35, (u32)irq3);
set_idt_gate(36, (u32)irq4);
set_idt_gate(37, (u32)irq5);
set_idt_gate(38, (u32)irq6);
set_idt_gate(39, (u32)irq7);
set_idt_gate(40, (u32)irq8);
set_idt_gate(41, (u32)irq9);
set_idt_gate(42, (u32)irq10);
set_idt_gate(43, (u32)irq11);
set_idt_gate(44, (u32)irq12);
set_idt_gate(45, (u32)irq13);
set_idt_gate(46, (u32)irq14);
set_idt_gate(47, (u32)irq15);
set_idt(); // Load with ASM
}
/* To print the message which defines every exception */
char *exception_messages[] = {
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
void isr_handler(registers_t r) {
kprint("received interrupt: ");
char s[3];
int_to_ascii(r.int_no, s);
kprint(s);
kprint("\n");
kprint(exception_messages[r.int_no]);
kprint("\n");
}
void register_interrupt_handler(u8 n, isr_t handler) {
interrupt_handlers[n] = handler;
}
void irq_handler(registers_t r) {
/* After every interrupt we need to send an EOI to the PICs
* or they will not send another interrupt again */
if (r.int_no >= 40) port_byte_out(0xA0, 0x20); /* slave */
port_byte_out(0x20, 0x20); /* master */
/* Handle the interrupt in a more modular way */
if (interrupt_handlers[r.int_no] != 0) {
isr_t handler = interrupt_handlers[r.int_no];
handler(r);
}
}
void irq_install() {
/* Enable interruptions */
asm volatile("sti");
/* IRQ0: timer */
init_timer(50);
/* IRQ1: keyboard */
init_keyboard();
}
================================================
FILE: 21-shell/cpu/isr.h
================================================
#ifndef ISR_H
#define ISR_H
#include "types.h"
/* ISRs reserved for CPU exceptions */
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
/* IRQ definitions */
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();
#define IRQ0 32
#define IRQ1 33
#define IRQ2 34
#define IRQ3 35
#define IRQ4 36
#define IRQ5 37
#define IRQ6 38
#define IRQ7 39
#define IRQ8 40
#define IRQ9 41
#define IRQ10 42
#define IRQ11 43
#define IRQ12 44
#define IRQ13 45
#define IRQ14 46
#define IRQ15 47
/* Struct which aggregates many registers */
typedef struct {
u32 ds; /* Data segment selector */
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /
gitextract_elg482xn/ ├── .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 │ ├── cpu/ │ │ ├── idt.c │ │ ├── idt.h │ │ ├── interrupt.asm │ │ ├── isr.c │ │ ├── isr.h │ │ └── types.h │ └── kernel/ │ ├── kernel.c │ ├── util.c │ └── util.h ├── 20-interrupts-timer/ │ ├── Makefile │ ├── README.md │ ├── cpu/ │ │ ├── timer.c │ │ └── timer.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 │ ├── cpu/ │ │ ├── idt.c │ │ ├── idt.h │ │ ├── interrupt.asm │ │ ├── isr.c │ │ ├── isr.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 │ ├── 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/ │ └── README.md ├── LICENSE └── README.md
SYMBOL INDEX (243 symbols across 71 files)
FILE: 12-kernel-c/function.c
function my_function (line 1) | int my_function() {
FILE: 12-kernel-c/functioncalls.c
function caller (line 1) | void caller() {
function my_func (line 5) | int my_func(int arg) {
FILE: 12-kernel-c/localvars.c
function my_function (line 1) | int my_function() {
FILE: 12-kernel-c/pointers.c
function func (line 1) | void func() {
FILE: 13-kernel-barebones/kernel.c
function dummy_test_entrypoint (line 2) | void dummy_test_entrypoint() {
function main (line 5) | void main() {
FILE: 14-checkpoint/kernel/kernel.c
function dummy_test_entrypoint (line 2) | void dummy_test_entrypoint() {
function main (line 5) | void main() {
FILE: 15-video-ports/drivers/ports.c
function port_byte_in (line 4) | unsigned char port_byte_in (unsigned short port) {
function port_byte_out (line 18) | void port_byte_out (unsigned short port, unsigned char data) {
function port_word_in (line 27) | unsigned short port_word_in (unsigned short port) {
function port_word_out (line 33) | void port_word_out (unsigned short port, unsigned short data) {
FILE: 15-video-ports/kernel/kernel.c
function main (line 3) | void main() {
FILE: 16-video-driver/drivers/ports.c
function port_byte_in (line 4) | unsigned char port_byte_in (unsigned short port) {
function port_byte_out (line 18) | void port_byte_out (unsigned short port, unsigned char data) {
function port_word_in (line 27) | unsigned short port_word_in (unsigned short port) {
function port_word_out (line 33) | void port_word_out (unsigned short port, unsigned short data) {
FILE: 16-video-driver/drivers/screen.c
function kprint_at (line 20) | void kprint_at(char *message, int col, int row) {
function kprint (line 41) | void kprint(char *message) {
function print_char (line 59) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 86) | int get_cursor_offset() {
function set_cursor_offset (line 98) | void set_cursor_offset(int offset) {
function clear_screen (line 107) | void clear_screen() {
function get_offset (line 120) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 121) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 122) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 16-video-driver/kernel/kernel.c
function main (line 3) | void main() {
FILE: 17-video-scroll/drivers/ports.c
function port_byte_in (line 4) | unsigned char port_byte_in (unsigned short port) {
function port_byte_out (line 18) | void port_byte_out (unsigned short port, unsigned char data) {
function port_word_in (line 27) | unsigned short port_word_in (unsigned short port) {
function port_word_out (line 33) | void port_word_out (unsigned short port, unsigned short data) {
FILE: 17-video-scroll/drivers/screen.c
function kprint_at (line 21) | void kprint_at(char *message, int col, int row) {
function kprint (line 42) | void kprint(char *message) {
function print_char (line 60) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 103) | int get_cursor_offset() {
function set_cursor_offset (line 115) | void set_cursor_offset(int offset) {
function clear_screen (line 124) | void clear_screen() {
function get_offset (line 137) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 138) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 139) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 17-video-scroll/kernel/kernel.c
function main (line 4) | void main() {
FILE: 17-video-scroll/kernel/util.c
function memory_copy (line 1) | void memory_copy(char *source, char *dest, int nbytes) {
function int_to_ascii (line 11) | void int_to_ascii(int n, char str[]) {
FILE: 18-interrupts/cpu/idt.c
function set_idt_gate (line 4) | void set_idt_gate(int n, u32 handler) {
function set_idt (line 12) | void set_idt() {
FILE: 18-interrupts/cpu/idt.h
type idt_gate_t (line 10) | typedef struct {
type idt_register_t (line 25) | typedef struct {
FILE: 18-interrupts/cpu/isr.c
function isr_install (line 8) | void isr_install() {
function isr_handler (line 84) | void isr_handler(registers_t r) {
FILE: 18-interrupts/cpu/isr.h
type registers_t (line 41) | typedef struct {
FILE: 18-interrupts/cpu/types.h
type u32 (line 6) | typedef unsigned int u32;
type s32 (line 7) | typedef int s32;
type u16 (line 8) | typedef unsigned short u16;
type s16 (line 9) | typedef short s16;
type u8 (line 10) | typedef unsigned char u8;
type s8 (line 11) | typedef char s8;
FILE: 18-interrupts/drivers/ports.c
function u8 (line 6) | u8 port_byte_in (u16 port) {
function port_byte_out (line 20) | void port_byte_out (u16 port, u8 data) {
function u16 (line 29) | u16 port_word_in (u16 port) {
function port_word_out (line 35) | void port_word_out (u16 port, u16 data) {
FILE: 18-interrupts/drivers/screen.c
function kprint_at (line 21) | void kprint_at(char *message, int col, int row) {
function kprint (line 42) | void kprint(char *message) {
function print_char (line 60) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 103) | int get_cursor_offset() {
function set_cursor_offset (line 115) | void set_cursor_offset(int offset) {
function clear_screen (line 124) | void clear_screen() {
function get_offset (line 137) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 138) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 139) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 18-interrupts/kernel/kernel.c
function main (line 6) | void main() {
FILE: 18-interrupts/kernel/util.c
function memory_copy (line 3) | void memory_copy(char *source, char *dest, int nbytes) {
function memory_set (line 10) | void memory_set(u8 *dest, u8 val, u32 len) {
function int_to_ascii (line 18) | void int_to_ascii(int n, char str[]) {
FILE: 19-interrupts-irqs/cpu/idt.c
function set_idt_gate (line 4) | void set_idt_gate(int n, u32 handler) {
function set_idt (line 12) | void set_idt() {
FILE: 19-interrupts-irqs/cpu/idt.h
type idt_gate_t (line 10) | typedef struct {
type idt_register_t (line 25) | typedef struct {
FILE: 19-interrupts-irqs/cpu/isr.c
function isr_install (line 11) | void isr_install() {
function isr_handler (line 117) | void isr_handler(registers_t r) {
function register_interrupt_handler (line 127) | void register_interrupt_handler(u8 n, isr_t handler) {
function irq_handler (line 131) | void irq_handler(registers_t r) {
FILE: 19-interrupts-irqs/cpu/isr.h
type registers_t (line 75) | typedef struct {
FILE: 19-interrupts-irqs/cpu/types.h
type u32 (line 6) | typedef unsigned int u32;
type s32 (line 7) | typedef int s32;
type u16 (line 8) | typedef unsigned short u16;
type s16 (line 9) | typedef short s16;
type u8 (line 10) | typedef unsigned char u8;
type s8 (line 11) | typedef char s8;
FILE: 19-interrupts-irqs/kernel/kernel.c
function main (line 6) | void main() {
FILE: 19-interrupts-irqs/kernel/util.c
function memory_copy (line 3) | void memory_copy(char *source, char *dest, int nbytes) {
function memory_set (line 10) | void memory_set(u8 *dest, u8 val, u32 len) {
function int_to_ascii (line 18) | void int_to_ascii(int n, char str[]) {
FILE: 20-interrupts-timer/cpu/timer.c
function timer_callback (line 8) | static void timer_callback(registers_t regs) {
function init_timer (line 18) | void init_timer(u32 freq) {
FILE: 20-interrupts-timer/drivers/keyboard.c
function keyboard_callback (line 6) | static void keyboard_callback(registers_t regs) {
function init_keyboard (line 18) | void init_keyboard() {
function print_letter (line 22) | void print_letter(u8 scancode) {
FILE: 20-interrupts-timer/drivers/ports.c
function u8 (line 6) | u8 port_byte_in (u16 port) {
function port_byte_out (line 20) | void port_byte_out (u16 port, u8 data) {
function u16 (line 29) | u16 port_word_in (u16 port) {
function port_word_out (line 35) | void port_word_out (u16 port, u16 data) {
FILE: 20-interrupts-timer/drivers/screen.c
function kprint_at (line 20) | void kprint_at(char *message, int col, int row) {
function kprint (line 41) | void kprint(char *message) {
function print_char (line 59) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 102) | int get_cursor_offset() {
function set_cursor_offset (line 114) | void set_cursor_offset(int offset) {
function clear_screen (line 123) | void clear_screen() {
function get_offset (line 136) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 137) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 138) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 20-interrupts-timer/kernel/kernel.c
function main (line 5) | void main() {
FILE: 20-interrupts-timer/kernel/util.c
function memory_copy (line 3) | void memory_copy(char *source, char *dest, int nbytes) {
function memory_set (line 10) | void memory_set(u8 *dest, u8 val, u32 len) {
function int_to_ascii (line 18) | void int_to_ascii(int n, char str[]) {
function reverse (line 33) | void reverse(char s[]) {
function strlen (line 43) | int strlen(char s[]) {
FILE: 21-shell/cpu/idt.c
function set_idt_gate (line 3) | void set_idt_gate(int n, u32 handler) {
function set_idt (line 11) | void set_idt() {
FILE: 21-shell/cpu/idt.h
type idt_gate_t (line 10) | typedef struct {
type idt_register_t (line 25) | typedef struct {
FILE: 21-shell/cpu/isr.c
function isr_install (line 13) | void isr_install() {
function isr_handler (line 119) | void isr_handler(registers_t r) {
function register_interrupt_handler (line 129) | void register_interrupt_handler(u8 n, isr_t handler) {
function irq_handler (line 133) | void irq_handler(registers_t r) {
function irq_install (line 146) | void irq_install() {
FILE: 21-shell/cpu/isr.h
type registers_t (line 75) | typedef struct {
FILE: 21-shell/cpu/timer.c
function timer_callback (line 8) | static void timer_callback(registers_t regs) {
function init_timer (line 13) | void init_timer(u32 freq) {
FILE: 21-shell/cpu/types.h
type u32 (line 6) | typedef unsigned int u32;
type s32 (line 7) | typedef int s32;
type u16 (line 8) | typedef unsigned short u16;
type s16 (line 9) | typedef short s16;
type u8 (line 10) | typedef unsigned char u8;
type s8 (line 11) | typedef char s8;
FILE: 21-shell/drivers/keyboard.c
function keyboard_callback (line 27) | static void keyboard_callback(registers_t regs) {
function init_keyboard (line 49) | void init_keyboard() {
FILE: 21-shell/drivers/screen.c
function kprint_at (line 21) | void kprint_at(char *message, int col, int row) {
function kprint (line 42) | void kprint(char *message) {
function kprint_backspace (line 46) | void kprint_backspace() {
function print_char (line 67) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 113) | int get_cursor_offset() {
function set_cursor_offset (line 125) | void set_cursor_offset(int offset) {
function clear_screen (line 134) | void clear_screen() {
function get_offset (line 147) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 148) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 149) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 21-shell/kernel/kernel.c
function main (line 6) | void main() {
function user_input (line 14) | void user_input(char *input) {
FILE: 21-shell/libc/mem.c
function memory_copy (line 3) | void memory_copy(u8 *source, u8 *dest, int nbytes) {
function memory_set (line 10) | void memory_set(u8 *dest, u8 val, u32 len) {
FILE: 21-shell/libc/string.c
function int_to_ascii (line 6) | void int_to_ascii(int n, char str[]) {
function reverse (line 21) | void reverse(char s[]) {
function strlen (line 31) | int strlen(char s[]) {
function append (line 37) | void append(char s[], char n) {
function backspace (line 43) | void backspace(char s[]) {
function strcmp (line 50) | int strcmp(char s1[], char s2[]) {
FILE: 22-malloc/cpu/idt.c
function set_idt_gate (line 3) | void set_idt_gate(int n, u32 handler) {
function set_idt (line 11) | void set_idt() {
FILE: 22-malloc/cpu/idt.h
type idt_gate_t (line 10) | typedef struct {
type idt_register_t (line 25) | typedef struct {
FILE: 22-malloc/cpu/isr.c
function isr_install (line 13) | void isr_install() {
function isr_handler (line 119) | void isr_handler(registers_t r) {
function register_interrupt_handler (line 129) | void register_interrupt_handler(u8 n, isr_t handler) {
function irq_handler (line 133) | void irq_handler(registers_t r) {
function irq_install (line 146) | void irq_install() {
FILE: 22-malloc/cpu/isr.h
type registers_t (line 75) | typedef struct {
FILE: 22-malloc/cpu/ports.c
function u8 (line 6) | u8 port_byte_in (u16 port) {
function port_byte_out (line 20) | void port_byte_out (u16 port, u8 data) {
function u16 (line 29) | u16 port_word_in (u16 port) {
function port_word_out (line 35) | void port_word_out (u16 port, u16 data) {
FILE: 22-malloc/cpu/timer.c
function timer_callback (line 8) | static void timer_callback(registers_t regs) {
function init_timer (line 13) | void init_timer(u32 freq) {
FILE: 22-malloc/cpu/type.h
type u32 (line 6) | typedef unsigned int u32;
type s32 (line 7) | typedef int s32;
type u16 (line 8) | typedef unsigned short u16;
type s16 (line 9) | typedef short s16;
type u8 (line 10) | typedef unsigned char u8;
type s8 (line 11) | typedef char s8;
FILE: 22-malloc/drivers/keyboard.c
function keyboard_callback (line 27) | static void keyboard_callback(registers_t regs) {
function init_keyboard (line 49) | void init_keyboard() {
FILE: 22-malloc/drivers/screen.c
function kprint_at (line 21) | void kprint_at(char *message, int col, int row) {
function kprint (line 42) | void kprint(char *message) {
function kprint_backspace (line 46) | void kprint_backspace() {
function print_char (line 67) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 113) | int get_cursor_offset() {
function set_cursor_offset (line 125) | void set_cursor_offset(int offset) {
function clear_screen (line 134) | void clear_screen() {
function get_offset (line 147) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 148) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 149) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 22-malloc/kernel/kernel.c
function main (line 7) | void main() {
function user_input (line 15) | void user_input(char *input) {
FILE: 22-malloc/libc/mem.c
function memory_copy (line 3) | void memory_copy(u8 *source, u8 *dest, int nbytes) {
function memory_set (line 10) | void memory_set(u8 *dest, u8 val, u32 len) {
function u32 (line 21) | u32 kmalloc(u32 size, int align, u32 *phys_addr) {
FILE: 22-malloc/libc/string.c
function int_to_ascii (line 7) | void int_to_ascii(int n, char str[]) {
function hex_to_ascii (line 21) | void hex_to_ascii(int n, char str[]) {
function reverse (line 42) | void reverse(char s[]) {
function strlen (line 52) | int strlen(char s[]) {
function append (line 58) | void append(char s[], char n) {
function backspace (line 64) | void backspace(char s[]) {
function strcmp (line 71) | int strcmp(char s1[], char s2[]) {
FILE: 23-fixes/cpu/idt.c
function set_idt_gate (line 4) | void set_idt_gate(int n, uint32_t handler) {
function set_idt (line 12) | void set_idt() {
FILE: 23-fixes/cpu/idt.h
type idt_gate_t (line 10) | typedef struct {
type idt_register_t (line 25) | typedef struct {
FILE: 23-fixes/cpu/isr.c
function isr_install (line 13) | void isr_install() {
function isr_handler (line 119) | void isr_handler(registers_t *r) {
function register_interrupt_handler (line 129) | void register_interrupt_handler(uint8_t n, isr_t handler) {
function irq_handler (line 133) | void irq_handler(registers_t *r) {
function irq_install (line 146) | void irq_install() {
FILE: 23-fixes/cpu/isr.h
type registers_t (line 81) | typedef struct {
FILE: 23-fixes/cpu/ports.c
function port_byte_in (line 6) | uint8_t port_byte_in (uint16_t port) {
function port_byte_out (line 20) | void port_byte_out (uint16_t port, uint8_t data) {
function port_word_in (line 29) | uint16_t port_word_in (uint16_t port) {
function port_word_out (line 35) | void port_word_out (uint16_t port, uint16_t data) {
FILE: 23-fixes/cpu/timer.c
function timer_callback (line 8) | static void timer_callback(registers_t *regs) {
function init_timer (line 13) | void init_timer(uint32_t freq) {
FILE: 23-fixes/drivers/keyboard.c
function keyboard_callback (line 28) | static void keyboard_callback(registers_t *regs) {
function init_keyboard (line 50) | void init_keyboard() {
FILE: 23-fixes/drivers/screen.c
function kprint_at (line 22) | void kprint_at(char *message, int col, int row) {
function kprint (line 43) | void kprint(char *message) {
function kprint_backspace (line 47) | void kprint_backspace() {
function print_char (line 68) | int print_char(char c, int col, int row, char attr) {
function get_cursor_offset (line 114) | int get_cursor_offset() {
function set_cursor_offset (line 126) | void set_cursor_offset(int offset) {
function clear_screen (line 135) | void clear_screen() {
function get_offset (line 148) | int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
function get_offset_row (line 149) | int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
function get_offset_col (line 150) | int get_offset_col(int offset) { return (offset - (get_offset_row(offset...
FILE: 23-fixes/kernel/kernel.c
function kernel_main (line 8) | void kernel_main() {
function user_input (line 19) | void user_input(char *input) {
FILE: 23-fixes/libc/mem.c
function memory_copy (line 3) | void memory_copy(uint8_t *source, uint8_t *dest, int nbytes) {
function memory_set (line 10) | void memory_set(uint8_t *dest, uint8_t val, uint32_t len) {
function kmalloc (line 21) | uint32_t kmalloc(size_t size, int align, uint32_t *phys_addr) {
FILE: 23-fixes/libc/string.c
function int_to_ascii (line 7) | void int_to_ascii(int n, char str[]) {
function hex_to_ascii (line 21) | void hex_to_ascii(int n, char str[]) {
function reverse (line 42) | void reverse(char s[]) {
function strlen (line 52) | int strlen(char s[]) {
function append (line 58) | void append(char s[], char n) {
function backspace (line 64) | void backspace(char s[]) {
function strcmp (line 71) | int strcmp(char s1[], char s2[]) {
Condensed preview — 219 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (294K chars).
[
{
"path": ".gitignore",
"chars": 44,
"preview": "*.bin\n*.o\n*.swp\n*.dis\n*.elf\n*.sym\n.DS_STORE\n"
},
{
"path": "00-environment/README.md",
"chars": 604,
"preview": "*Concepts you may want to Google beforehand: linux, mac, terminal, compiler, emulator, nasm, qemu*\n\n**Goal: Install the "
},
{
"path": "01-bootsector-barebones/README.md",
"chars": 1899,
"preview": "*Concepts you may want to Google beforehand: assembler, BIOS*\n\n**Goal: Create a file which the BIOS interprets as a boot"
},
{
"path": "01-bootsector-barebones/boot_sect_simple.asm",
"chars": 102,
"preview": "; A simple boot sector program that loops forever\nloop:\n jmp loop\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n"
},
{
"path": "02-bootsector-print/README.md",
"chars": 1518,
"preview": "*Concepts you may want to Google beforehand: interrupts, CPU\nregisters*\n\n**Goal: Make our previously silent boot sector "
},
{
"path": "02-bootsector-print/boot_sect_hello.asm",
"chars": 261,
"preview": "mov ah, 0x0e ; tty mode\nmov al, 'H'\nint 0x10\nmov al, 'e'\nint 0x10\nmov al, 'l'\nint 0x10\nint 0x10 ; 'l' is still on al, re"
},
{
"path": "03-bootsector-memory/README.md",
"chars": 2118,
"preview": "*Concepts you may want to Google beforehand: memory offsets, pointers*\n\n**Goal: Learn how the computer memory is organiz"
},
{
"path": "03-bootsector-memory/boot_sect_memory.asm",
"chars": 1337,
"preview": "mov ah, 0x0e\n\n; attempt 1\n; Fails because it tries to print the memory address (i.e. pointer)\n; not its actual contents\n"
},
{
"path": "03-bootsector-memory/boot_sect_memory_org.asm",
"chars": 1039,
"preview": "[org 0x7c00]\nmov ah, 0x0e\n\n; attempt 1\n; Will fail again regardless of 'org' because we are still addressing the pointer"
},
{
"path": "04-bootsector-stack/README.md",
"chars": 563,
"preview": "*Concepts you may want to Google beforehand: stack*\n\n**Goal: Learn how to use the stack**\n\nThe usage of the stack is imp"
},
{
"path": "04-bootsector-stack/boot_sect_stack.asm",
"chars": 849,
"preview": "mov ah, 0x0e ; tty mode\n\nmov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten\nmov s"
},
{
"path": "05-bootsector-functions-strings/README.md",
"chars": 4017,
"preview": "*Concepts you may want to Google beforehand: control structures,\nfunction calling, strings*\n\n**Goal: Learn how to code b"
},
{
"path": "05-bootsector-functions-strings/boot_sect_main.asm",
"chars": 550,
"preview": "[org 0x7c00] ; tell the assembler that our offset is bootsector code\n\n; The main routine makes sure the parameters are r"
},
{
"path": "05-bootsector-functions-strings/boot_sect_print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "05-bootsector-functions-strings/boot_sect_print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "06-bootsector-segmentation/README.md",
"chars": 1021,
"preview": "*Concepts you may want to Google beforehand: segmentation*\n\n**Goal: learn how to address memory with 16-bit real mode se"
},
{
"path": "06-bootsector-segmentation/boot_sect_segmentation.asm",
"chars": 493,
"preview": "mov ah, 0x0e ; tty\n\nmov al, [the_secret]\nint 0x10 ; we already saw this doesn't work, right?\n\nmov bx, 0x7c0 ; remember, "
},
{
"path": "07-bootsector-disk/README.md",
"chars": 2322,
"preview": "*Concepts you may want to Google beforehand: hard disk, cylinder, head, sector, \ncarry bit*\n\n**Goal: Let the bootsector "
},
{
"path": "07-bootsector-disk/boot_sect_disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "07-bootsector-disk/boot_sect_main.asm",
"chars": 898,
"preview": "[org 0x7c00]\n mov bp, 0x8000 ; set the stack safely away from us\n mov sp, bp\n\n mov bx, 0x9000 ; es:bx = 0x0000:"
},
{
"path": "08-32bit-print/32bit-print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_ON_BLACK equ"
},
{
"path": "08-32bit-print/README.md",
"chars": 1300,
"preview": "*Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video \nmemory*\n\n**Goal: Print on the screen whe"
},
{
"path": "09-32bit-gdt/32bit-gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "09-32bit-gdt/README.md",
"chars": 1438,
"preview": "*Concepts you may want to Google beforehand: GDT*\n\n**Goal: program the GDT**\n\nRemember segmentation from lesson 6? The o"
},
{
"path": "10-32bit-enter/32bit-main.asm",
"chars": 748,
"preview": "[org 0x7c00] ; bootloader offset\n mov bp, 0x9000 ; set the stack\n mov sp, bp\n\n mov bx, MSG_REAL_MODE\n call p"
},
{
"path": "10-32bit-enter/32bit-switch.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "10-32bit-enter/README.md",
"chars": 883,
"preview": "*Concepts you may want to Google beforehand: interrupts, pipelining*\n\n**Goal: Enter 32-bit protected mode and test our c"
},
{
"path": "11-kernel-crosscompiler/README.md",
"chars": 2603,
"preview": "*Concepts you may want to Google beforehand: cross-compiler*\n\n**Goal: Create a development environment to build your ker"
},
{
"path": "12-kernel-c/README.md",
"chars": 1984,
"preview": "*Concepts you may want to Google beforehand: C, object code, linker, disassemble*\n\n**Goal: Learn to write the same low-l"
},
{
"path": "12-kernel-c/function.c",
"chars": 41,
"preview": "int my_function() {\n return 0xbaba;\n}\n"
},
{
"path": "12-kernel-c/functioncalls.c",
"chars": 81,
"preview": "void caller() {\n my_func(0xdede);\n}\n\nint my_func(int arg) {\n return arg;\n}\n"
},
{
"path": "12-kernel-c/localvars.c",
"chars": 66,
"preview": "int my_function() {\n int my_var = 0xbaba;\n return my_var;\n}\n"
},
{
"path": "12-kernel-c/pointers.c",
"chars": 44,
"preview": "void func() {\n char* string = \"Hello\";\n}\n"
},
{
"path": "13-kernel-barebones/Makefile",
"chars": 686,
"preview": "# $@ = target file\n# $< = first dependency\n# $^ = all dependencies\n\n# First rule is the one executed when no parameters "
},
{
"path": "13-kernel-barebones/README.md",
"chars": 2577,
"preview": "*Concepts you may want to Google beforehand: kernel, ELF format, makefile*\n\n**Goal: Create a simple kernel and a bootsec"
},
{
"path": "13-kernel-barebones/bootsect.asm",
"chars": 1459,
"preview": "[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel\n\n mov [BOOT_DRIVE], dl ; Remembe"
},
{
"path": "13-kernel-barebones/kernel.c",
"chars": 212,
"preview": "/* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */\nvoid dummy_test_entrypoin"
},
{
"path": "13-kernel-barebones/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "14-checkpoint/Makefile",
"chars": 1357,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h)\n# Nice syntax for file exten"
},
{
"path": "14-checkpoint/README.md",
"chars": 2769,
"preview": "*Concepts you may want to Google beforehand: monolithic kernel, microkernel, debugger, gdb*\n\n**Goal: Pause and organize "
},
{
"path": "14-checkpoint/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "14-checkpoint/boot/bootsect.asm",
"chars": 1436,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "14-checkpoint/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "14-checkpoint/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "14-checkpoint/boot/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "14-checkpoint/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "14-checkpoint/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "14-checkpoint/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "14-checkpoint/kernel/kernel.c",
"chars": 212,
"preview": "/* This will force us to create a kernel entry function instead of jumping to kernel.c:0x00 */\nvoid dummy_test_entrypoin"
},
{
"path": "15-video-ports/Makefile",
"chars": 1357,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h)\n# Nice syntax for file exten"
},
{
"path": "15-video-ports/README.md",
"chars": 1180,
"preview": "*Concepts you may want to Google beforehand: I/O ports*\n\n**Goal: Learn how to use the VGA card data ports**\n\nWe will use"
},
{
"path": "15-video-ports/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "15-video-ports/boot/bootsect.asm",
"chars": 1436,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "15-video-ports/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "15-video-ports/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "15-video-ports/boot/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "15-video-ports/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "15-video-ports/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "15-video-ports/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "15-video-ports/drivers/ports.c",
"chars": 1216,
"preview": "/**\n * Read a byte from the specified port\n */\nunsigned char port_byte_in (unsigned short port) {\n unsigned char resu"
},
{
"path": "15-video-ports/drivers/ports.h",
"chars": 226,
"preview": "unsigned char port_byte_in (unsigned short port);\nvoid port_byte_out (unsigned short port, unsigned char data);\nunsigned"
},
{
"path": "15-video-ports/kernel/kernel.c",
"chars": 1174,
"preview": "#include \"../drivers/ports.h\"\n\nvoid main() {\n /* Screen cursor position: ask VGA control register (0x3d4) for bytes\n "
},
{
"path": "16-video-driver/Makefile",
"chars": 1357,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h)\n# Nice syntax for file exten"
},
{
"path": "16-video-driver/README.md",
"chars": 2130,
"preview": "*Concepts you may want to Google beforehand: VGA character cells, screen offset*\n\n**Goal: Write strings on the screen**\n"
},
{
"path": "16-video-driver/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "16-video-driver/boot/bootsect.asm",
"chars": 1436,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "16-video-driver/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "16-video-driver/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "16-video-driver/boot/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "16-video-driver/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "16-video-driver/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "16-video-driver/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "16-video-driver/drivers/ports.c",
"chars": 1216,
"preview": "/**\n * Read a byte from the specified port\n */\nunsigned char port_byte_in (unsigned short port) {\n unsigned char resu"
},
{
"path": "16-video-driver/drivers/ports.h",
"chars": 226,
"preview": "unsigned char port_byte_in (unsigned short port);\nvoid port_byte_out (unsigned short port, unsigned char data);\nunsigned"
},
{
"path": "16-video-driver/drivers/screen.c",
"chars": 3852,
"preview": "#include \"screen.h\"\n#include \"ports.h\"\n\n/* Declaration of private functions */\nint get_cursor_offset();\nvoid set_cursor_"
},
{
"path": "16-video-driver/drivers/screen.h",
"chars": 331,
"preview": "#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define MAX_COLS 80\n#define WHITE_ON_BLACK 0x0f\n#define RED_ON_WHITE 0"
},
{
"path": "16-video-driver/kernel/kernel.c",
"chars": 302,
"preview": "#include \"../drivers/screen.h\"\n\nvoid main() {\n clear_screen();\n kprint_at(\"X\", 1, 6);\n kprint_at(\"This text spa"
},
{
"path": "17-video-scroll/Makefile",
"chars": 1357,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h)\n# Nice syntax for file exten"
},
{
"path": "17-video-scroll/README.md",
"chars": 1344,
"preview": "*Concepts you may want to Google beforehand: scroll*\n\n**Goal: Scroll the screen when the text reaches the bottom**\n\nFor "
},
{
"path": "17-video-scroll/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "17-video-scroll/boot/bootsect.asm",
"chars": 1436,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "17-video-scroll/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "17-video-scroll/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "17-video-scroll/boot/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "17-video-scroll/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "17-video-scroll/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "17-video-scroll/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "17-video-scroll/drivers/ports.c",
"chars": 1216,
"preview": "/**\n * Read a byte from the specified port\n */\nunsigned char port_byte_in (unsigned short port) {\n unsigned char resu"
},
{
"path": "17-video-scroll/drivers/ports.h",
"chars": 226,
"preview": "unsigned char port_byte_in (unsigned short port);\nvoid port_byte_out (unsigned short port, unsigned char data);\nunsigned"
},
{
"path": "17-video-scroll/drivers/screen.c",
"chars": 4400,
"preview": "#include \"screen.h\"\n#include \"ports.h\"\n#include \"../kernel/util.h\"\n\n/* Declaration of private functions */\nint get_curso"
},
{
"path": "17-video-scroll/drivers/screen.h",
"chars": 331,
"preview": "#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define MAX_COLS 80\n#define WHITE_ON_BLACK 0x0f\n#define RED_ON_WHITE 0"
},
{
"path": "17-video-scroll/kernel/kernel.c",
"chars": 435,
"preview": "#include \"../drivers/screen.h\"\n#include \"util.h\"\n\nvoid main() {\n clear_screen();\n\n /* Fill up the screen */\n in"
},
{
"path": "17-video-scroll/kernel/util.c",
"chars": 442,
"preview": "void memory_copy(char *source, char *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) {\n *(dest + "
},
{
"path": "17-video-scroll/kernel/util.h",
"chars": 94,
"preview": "void memory_copy(char *source, char *dest, int nbytes);\nvoid int_to_ascii(int n, char str[]);\n"
},
{
"path": "18-interrupts/Makefile",
"chars": 1418,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)\n# Nice synta"
},
{
"path": "18-interrupts/README.md",
"chars": 3236,
"preview": "*Concepts you may want to Google beforehand: C types and structs, include guards, type attributes: packed, extern, volat"
},
{
"path": "18-interrupts/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "18-interrupts/boot/bootsect.asm",
"chars": 1493,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "18-interrupts/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "18-interrupts/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "18-interrupts/boot/kernel_entry.asm",
"chars": 186,
"preview": "[bits 32]\n[extern main] ; Define calling point. Must have same name as kernel.c 'main' function\ncall main ; Calls the C "
},
{
"path": "18-interrupts/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "18-interrupts/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "18-interrupts/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "18-interrupts/cpu/idt.c",
"chars": 491,
"preview": "#include \"idt.h\"\n#include \"../kernel/util.h\"\n\nvoid set_idt_gate(int n, u32 handler) {\n idt[n].low_offset = low_16(han"
},
{
"path": "18-interrupts/cpu/idt.h",
"chars": 1011,
"preview": "#ifndef IDT_H\n#define IDT_H\n\n#include \"types.h\"\n\n/* Segment selectors */\n#define KERNEL_CS 0x08\n\n/* How every interrupt "
},
{
"path": "18-interrupts/cpu/interrupt.asm",
"chars": 4491,
"preview": "; Defined in isr.c\n[extern isr_handler]\n\n; Common ISR code\nisr_common_stub:\n ; 1. Save CPU state\n\tpusha ; Pushes edi,"
},
{
"path": "18-interrupts/cpu/isr.c",
"chars": 2257,
"preview": "#include \"isr.h\"\n#include \"idt.h\"\n#include \"../drivers/screen.h\"\n#include \"../kernel/util.h\"\n\n/* Can't do this with a lo"
},
{
"path": "18-interrupts/cpu/isr.h",
"chars": 1159,
"preview": "#ifndef ISR_H\n#define ISR_H\n\n#include \"types.h\"\n\n/* ISRs reserved for CPU exceptions */\nextern void isr0();\nextern void "
},
{
"path": "18-interrupts/cpu/types.h",
"chars": 436,
"preview": "#ifndef TYPES_H\n#define TYPES_H\n\n/* Instead of using 'chars' to allocate non-character bytes,\n * we will use these new t"
},
{
"path": "18-interrupts/drivers/ports.c",
"chars": 1152,
"preview": "#include \"ports.h\"\n\n/**\n * Read a byte from the specified port\n */\nu8 port_byte_in (u16 port) {\n u8 result;\n /* In"
},
{
"path": "18-interrupts/drivers/ports.h",
"chars": 228,
"preview": "#ifndef PORTS_H\n#define PORTS_H\n\n#include \"../cpu/types.h\"\n\nunsigned char port_byte_in (u16 port);\nvoid port_byte_out (u"
},
{
"path": "18-interrupts/drivers/screen.c",
"chars": 4400,
"preview": "#include \"screen.h\"\n#include \"ports.h\"\n#include \"../kernel/util.h\"\n\n/* Declaration of private functions */\nint get_curso"
},
{
"path": "18-interrupts/drivers/screen.h",
"chars": 374,
"preview": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define MAX_COLS 80\n#define WHITE_O"
},
{
"path": "18-interrupts/kernel/kernel.c",
"chars": 235,
"preview": "#include \"../drivers/screen.h\"\n#include \"util.h\"\n#include \"../cpu/isr.h\"\n#include \"../cpu/idt.h\"\n\nvoid main() {\n isr_"
},
{
"path": "18-interrupts/kernel/util.c",
"chars": 580,
"preview": "#include \"util.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) "
},
{
"path": "18-interrupts/kernel/util.h",
"chars": 204,
"preview": "#ifndef UTIL_H\n#define UTIL_H\n\n#include \"../cpu/types.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes);\nvoid m"
},
{
"path": "19-interrupts-irqs/Makefile",
"chars": 1418,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)\n# Nice synta"
},
{
"path": "19-interrupts-irqs/README.md",
"chars": 1820,
"preview": "*Concepts you may want to Google beforehand: IRQs, PIC, polling*\n\n**Goal: Finish the interrupts implementation and CPU t"
},
{
"path": "19-interrupts-irqs/cpu/idt.c",
"chars": 491,
"preview": "#include \"idt.h\"\n#include \"../kernel/util.h\"\n\nvoid set_idt_gate(int n, u32 handler) {\n idt[n].low_offset = low_16(han"
},
{
"path": "19-interrupts-irqs/cpu/idt.h",
"chars": 1011,
"preview": "#ifndef IDT_H\n#define IDT_H\n\n#include \"types.h\"\n\n/* Segment selectors */\n#define KERNEL_CS 0x08\n\n/* How every interrupt "
},
{
"path": "19-interrupts-irqs/cpu/interrupt.asm",
"chars": 6117,
"preview": "; Defined in isr.c\n[extern isr_handler]\n[extern irq_handler]\n\n; Common ISR code\nisr_common_stub:\n ; 1. Save CPU state"
},
{
"path": "19-interrupts-irqs/cpu/isr.c",
"chars": 3747,
"preview": "#include \"isr.h\"\n#include \"idt.h\"\n#include \"../drivers/screen.h\"\n#include \"../kernel/util.h\"\n#include \"../drivers/ports."
},
{
"path": "19-interrupts-irqs/cpu/isr.h",
"chars": 1861,
"preview": "#ifndef ISR_H\n#define ISR_H\n\n#include \"types.h\"\n\n/* ISRs reserved for CPU exceptions */\nextern void isr0();\nextern void "
},
{
"path": "19-interrupts-irqs/cpu/types.h",
"chars": 436,
"preview": "#ifndef TYPES_H\n#define TYPES_H\n\n/* Instead of using 'chars' to allocate non-character bytes,\n * we will use these new t"
},
{
"path": "19-interrupts-irqs/kernel/kernel.c",
"chars": 235,
"preview": "#include \"../drivers/screen.h\"\n#include \"util.h\"\n#include \"../cpu/isr.h\"\n#include \"../cpu/idt.h\"\n\nvoid main() {\n isr_"
},
{
"path": "19-interrupts-irqs/kernel/util.c",
"chars": 580,
"preview": "#include \"util.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) "
},
{
"path": "19-interrupts-irqs/kernel/util.h",
"chars": 204,
"preview": "#ifndef UTIL_H\n#define UTIL_H\n\n#include \"../cpu/types.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes);\nvoid m"
},
{
"path": "20-interrupts-timer/Makefile",
"chars": 1437,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc"
},
{
"path": "20-interrupts-timer/README.md",
"chars": 1622,
"preview": "*Concepts you may want to Google beforehand: CPU timer, keyboard interrupts, scancode*\n\n**Goal: Implement our first IRQ "
},
{
"path": "20-interrupts-timer/cpu/timer.c",
"chars": 745,
"preview": "#include \"timer.h\"\n#include \"../drivers/screen.h\"\n#include \"../kernel/util.h\"\n#include \"isr.h\"\n\nu32 tick = 0;\n\nstatic vo"
},
{
"path": "20-interrupts-timer/cpu/timer.h",
"chars": 97,
"preview": "#ifndef TIMER_H\n#define TIMER_H\n\n#include \"../kernel/util.h\"\n\nvoid init_timer(u32 freq);\n\n#endif\n"
},
{
"path": "20-interrupts-timer/drivers/keyboard.c",
"chars": 4444,
"preview": "#include \"keyboard.h\"\n#include \"ports.h\"\n#include \"../cpu/isr.h\"\n#include \"screen.h\"\n\nstatic void keyboard_callback(regi"
},
{
"path": "20-interrupts-timer/drivers/keyboard.h",
"chars": 49,
"preview": "#include \"../cpu/types.h\"\n\nvoid init_keyboard();\n"
},
{
"path": "20-interrupts-timer/drivers/ports.c",
"chars": 1152,
"preview": "#include \"ports.h\"\n\n/**\n * Read a byte from the specified port\n */\nu8 port_byte_in (u16 port) {\n u8 result;\n /* In"
},
{
"path": "20-interrupts-timer/drivers/ports.h",
"chars": 228,
"preview": "#ifndef PORTS_H\n#define PORTS_H\n\n#include \"../cpu/types.h\"\n\nunsigned char port_byte_in (u16 port);\nvoid port_byte_out (u"
},
{
"path": "20-interrupts-timer/drivers/screen.c",
"chars": 4383,
"preview": "#include \"screen.h\"\n#include \"../drivers/ports.h\"\n\n/* Declaration of private functions */\nint get_cursor_offset();\nvoid "
},
{
"path": "20-interrupts-timer/drivers/screen.h",
"chars": 374,
"preview": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define MAX_COLS 80\n#define WHITE_O"
},
{
"path": "20-interrupts-timer/kernel/kernel.c",
"chars": 270,
"preview": "#include \"../cpu/isr.h\"\n#include \"../cpu/timer.h\"\n#include \"../drivers/keyboard.h\"\n\nvoid main() {\n isr_install();\n\n "
},
{
"path": "20-interrupts-timer/kernel/util.c",
"chars": 827,
"preview": "#include \"util.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) "
},
{
"path": "20-interrupts-timer/kernel/util.h",
"chars": 250,
"preview": "#ifndef UTIL_H\n#define UTIL_H\n\n#include \"../cpu/types.h\"\n\nvoid memory_copy(char *source, char *dest, int nbytes);\nvoid m"
},
{
"path": "21-shell/Makefile",
"chars": 1560,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc"
},
{
"path": "21-shell/README.md",
"chars": 3236,
"preview": "\n**Goal: Clean the code a bit and parse user input**\n\nIn this lesson we will do two things. First, we will clean up the "
},
{
"path": "21-shell/cpu/idt.c",
"chars": 463,
"preview": "#include \"idt.h\"\n\nvoid set_idt_gate(int n, u32 handler) {\n idt[n].low_offset = low_16(handler);\n idt[n].sel = KERN"
},
{
"path": "21-shell/cpu/idt.h",
"chars": 1011,
"preview": "#ifndef IDT_H\n#define IDT_H\n\n#include \"types.h\"\n\n/* Segment selectors */\n#define KERNEL_CS 0x08\n\n/* How every interrupt "
},
{
"path": "21-shell/cpu/interrupt.asm",
"chars": 6117,
"preview": "; Defined in isr.c\n[extern isr_handler]\n[extern irq_handler]\n\n; Common ISR code\nisr_common_stub:\n ; 1. Save CPU state"
},
{
"path": "21-shell/cpu/isr.c",
"chars": 3956,
"preview": "#include \"isr.h\"\n#include \"idt.h\"\n#include \"../drivers/screen.h\"\n#include \"../drivers/keyboard.h\"\n#include \"../libc/stri"
},
{
"path": "21-shell/cpu/isr.h",
"chars": 1881,
"preview": "#ifndef ISR_H\n#define ISR_H\n\n#include \"types.h\"\n\n/* ISRs reserved for CPU exceptions */\nextern void isr0();\nextern void "
},
{
"path": "21-shell/cpu/timer.c",
"chars": 622,
"preview": "#include \"timer.h\"\n#include \"isr.h\"\n#include \"ports.h\"\n#include \"../libc/function.h\"\n\nu32 tick = 0;\n\nstatic void timer_c"
},
{
"path": "21-shell/cpu/timer.h",
"chars": 88,
"preview": "#ifndef TIMER_H\n#define TIMER_H\n\n#include \"types.h\"\n\nvoid init_timer(u32 freq);\n\n#endif\n"
},
{
"path": "21-shell/cpu/types.h",
"chars": 436,
"preview": "#ifndef TYPES_H\n#define TYPES_H\n\n/* Instead of using 'chars' to allocate non-character bytes,\n * we will use these new t"
},
{
"path": "21-shell/drivers/keyboard.c",
"chars": 1784,
"preview": "#include \"keyboard.h\"\n#include \"../cpu/ports.h\"\n#include \"../cpu/isr.h\"\n#include \"screen.h\"\n#include \"../libc/string.h\"\n"
},
{
"path": "21-shell/drivers/keyboard.h",
"chars": 49,
"preview": "#include \"../cpu/types.h\"\n\nvoid init_keyboard();\n"
},
{
"path": "21-shell/drivers/screen.c",
"chars": 4694,
"preview": "#include \"screen.h\"\n#include \"../cpu/ports.h\"\n#include \"../libc/mem.h\"\n\n/* Declaration of private functions */\nint get_c"
},
{
"path": "21-shell/drivers/screen.h",
"chars": 426,
"preview": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#include \"../cpu/types.h\"\n\n#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define "
},
{
"path": "21-shell/kernel/kernel.c",
"chars": 476,
"preview": "#include \"../cpu/isr.h\"\n#include \"../drivers/screen.h\"\n#include \"kernel.h\"\n#include \"../libc/string.h\"\n\nvoid main() {\n "
},
{
"path": "21-shell/kernel/kernel.h",
"chars": 73,
"preview": "#ifndef KERNEL_H\n#define KERNEL_H\n\nvoid user_input(char *input);\n\n#endif\n"
},
{
"path": "21-shell/libc/function.h",
"chars": 221,
"preview": "#ifndef FUNCTION_H\n#define FUNCTION_H\n\n/* Sometimes we want to keep parameters to a function for later use\n * and this i"
},
{
"path": "21-shell/libc/mem.c",
"chars": 281,
"preview": "#include \"mem.h\"\n\nvoid memory_copy(u8 *source, u8 *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) {\n "
},
{
"path": "21-shell/libc/mem.h",
"chars": 160,
"preview": "#ifndef MEM_H\n#define MEM_H\n\n#include \"../cpu/types.h\"\n\nvoid memory_copy(u8 *source, u8 *dest, int nbytes);\nvoid memory_"
},
{
"path": "21-shell/libc/string.c",
"chars": 951,
"preview": "#include \"string.h\"\n\n/**\n * K&R implementation\n */\nvoid int_to_ascii(int n, char str[]) {\n int i, sign;\n if ((sign"
},
{
"path": "21-shell/libc/string.h",
"chars": 220,
"preview": "#ifndef STRINGS_H\n#define STRINGS_H\n\nvoid int_to_ascii(int n, char str[]);\nvoid reverse(char s[]);\nint strlen(char s[]);"
},
{
"path": "22-malloc/Makefile",
"chars": 1560,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc"
},
{
"path": "22-malloc/README.md",
"chars": 889,
"preview": "*Concepts you may want to Google beforehand: malloc*\n\n**Goal: Implement a memory allocator**\n\nWe will add a kernel memor"
},
{
"path": "22-malloc/cpu/idt.c",
"chars": 463,
"preview": "#include \"idt.h\"\n\nvoid set_idt_gate(int n, u32 handler) {\n idt[n].low_offset = low_16(handler);\n idt[n].sel = KERN"
},
{
"path": "22-malloc/cpu/idt.h",
"chars": 1010,
"preview": "#ifndef IDT_H\n#define IDT_H\n\n#include \"type.h\"\n\n/* Segment selectors */\n#define KERNEL_CS 0x08\n\n/* How every interrupt g"
},
{
"path": "22-malloc/cpu/interrupt.asm",
"chars": 6117,
"preview": "; Defined in isr.c\n[extern isr_handler]\n[extern irq_handler]\n\n; Common ISR code\nisr_common_stub:\n ; 1. Save CPU state"
},
{
"path": "22-malloc/cpu/isr.c",
"chars": 3956,
"preview": "#include \"isr.h\"\n#include \"idt.h\"\n#include \"../drivers/screen.h\"\n#include \"../drivers/keyboard.h\"\n#include \"../libc/stri"
},
{
"path": "22-malloc/cpu/isr.h",
"chars": 1880,
"preview": "#ifndef ISR_H\n#define ISR_H\n\n#include \"type.h\"\n\n/* ISRs reserved for CPU exceptions */\nextern void isr0();\nextern void i"
},
{
"path": "22-malloc/cpu/ports.c",
"chars": 1152,
"preview": "#include \"ports.h\"\n\n/**\n * Read a byte from the specified port\n */\nu8 port_byte_in (u16 port) {\n u8 result;\n /* In"
},
{
"path": "22-malloc/cpu/ports.h",
"chars": 227,
"preview": "#ifndef PORTS_H\n#define PORTS_H\n\n#include \"../cpu/type.h\"\n\nunsigned char port_byte_in (u16 port);\nvoid port_byte_out (u1"
},
{
"path": "22-malloc/cpu/timer.c",
"chars": 622,
"preview": "#include \"timer.h\"\n#include \"isr.h\"\n#include \"ports.h\"\n#include \"../libc/function.h\"\n\nu32 tick = 0;\n\nstatic void timer_c"
},
{
"path": "22-malloc/cpu/timer.h",
"chars": 87,
"preview": "#ifndef TIMER_H\n#define TIMER_H\n\n#include \"type.h\"\n\nvoid init_timer(u32 freq);\n\n#endif\n"
},
{
"path": "22-malloc/cpu/type.h",
"chars": 434,
"preview": "#ifndef TYPE_H\n#define TYPE_H\n\n/* Instead of using 'chars' to allocate non-character bytes,\n * we will use these new typ"
},
{
"path": "22-malloc/drivers/keyboard.c",
"chars": 1784,
"preview": "#include \"keyboard.h\"\n#include \"../cpu/ports.h\"\n#include \"../cpu/isr.h\"\n#include \"screen.h\"\n#include \"../libc/string.h\"\n"
},
{
"path": "22-malloc/drivers/keyboard.h",
"chars": 48,
"preview": "#include \"../cpu/type.h\"\n\nvoid init_keyboard();\n"
},
{
"path": "22-malloc/drivers/screen.c",
"chars": 4694,
"preview": "#include \"screen.h\"\n#include \"../cpu/ports.h\"\n#include \"../libc/mem.h\"\n\n/* Declaration of private functions */\nint get_c"
},
{
"path": "22-malloc/drivers/screen.h",
"chars": 425,
"preview": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#include \"../cpu/type.h\"\n\n#define VIDEO_ADDRESS 0xb8000\n#define MAX_ROWS 25\n#define M"
},
{
"path": "22-malloc/kernel/kernel.c",
"chars": 1003,
"preview": "#include \"../cpu/isr.h\"\n#include \"../drivers/screen.h\"\n#include \"kernel.h\"\n#include \"../libc/string.h\"\n#include \"../libc"
},
{
"path": "22-malloc/kernel/kernel.h",
"chars": 73,
"preview": "#ifndef KERNEL_H\n#define KERNEL_H\n\nvoid user_input(char *input);\n\n#endif\n"
},
{
"path": "22-malloc/libc/function.h",
"chars": 221,
"preview": "#ifndef FUNCTION_H\n#define FUNCTION_H\n\n/* Sometimes we want to keep parameters to a function for later use\n * and this i"
},
{
"path": "22-malloc/libc/mem.c",
"chars": 980,
"preview": "#include \"mem.h\"\n\nvoid memory_copy(u8 *source, u8 *dest, int nbytes) {\n int i;\n for (i = 0; i < nbytes; i++) {\n "
},
{
"path": "22-malloc/libc/mem.h",
"chars": 262,
"preview": "#ifndef MEM_H\n#define MEM_H\n\n#include \"../cpu/type.h\"\n\nvoid memory_copy(u8 *source, u8 *dest, int nbytes);\nvoid memory_s"
},
{
"path": "22-malloc/libc/string.c",
"chars": 1435,
"preview": "#include \"string.h\"\n#include \"../cpu/type.h\"\n\n/**\n * K&R implementation\n */\nvoid int_to_ascii(int n, char str[]) {\n i"
},
{
"path": "22-malloc/libc/string.h",
"chars": 258,
"preview": "#ifndef STRINGS_H\n#define STRINGS_H\n\nvoid int_to_ascii(int n, char str[]);\nvoid hex_to_ascii(int n, char str[]);\nvoid re"
},
{
"path": "23-fixes/Makefile",
"chars": 1480,
"preview": "C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)\nHEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc"
},
{
"path": "23-fixes/README.md",
"chars": 3948,
"preview": "*Concepts you may want to Google beforehand: freestanding, uint32_t, size_t*\n\n**Goal: Fix miscellaneous issues with our "
},
{
"path": "23-fixes/boot/32bit_print.asm",
"chars": 598,
"preview": "[bits 32] ; using 32-bit protected mode\n\n; this is how constants are defined\nVIDEO_MEMORY equ 0xb8000\nWHITE_OB_BLACK equ"
},
{
"path": "23-fixes/boot/bootsect.asm",
"chars": 1493,
"preview": "; Identical to lesson 13's boot sector, but the %included files have new paths\n[org 0x7c00]\nKERNEL_OFFSET equ 0x1000 ; T"
},
{
"path": "23-fixes/boot/disk.asm",
"chars": 1563,
"preview": "; load 'dh' sectors from drive 'dl' into ES:BX\ndisk_load:\n pusha\n ; reading from disk requires setting specific va"
},
{
"path": "23-fixes/boot/gdt.asm",
"chars": 1031,
"preview": "gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps\n ; the GDT starts with a null 8-byte\n"
},
{
"path": "23-fixes/boot/kernel_entry.asm",
"chars": 236,
"preview": "global _start;\n[bits 32]\n\n_start:\n [extern kernel_main] ; Define calling point. Must have same name as kernel.c 'main"
},
{
"path": "23-fixes/boot/print.asm",
"chars": 594,
"preview": "print:\n pusha\n\n; keep this in mind:\n; while (string[i] != 0) { print string[i]; i++ }\n\n; the comparison for string en"
},
{
"path": "23-fixes/boot/print_hex.asm",
"chars": 1520,
"preview": "; receiving the data in 'dx'\n; For the examples we'll assume that we're called with dx=0x1234\nprint_hex:\n pusha\n\n "
},
{
"path": "23-fixes/boot/switch_pm.asm",
"chars": 611,
"preview": "[bits 16]\nswitch_to_pm:\n cli ; 1. disable interrupts\n lgdt [gdt_descriptor] ; 2. load the GDT descriptor\n mov e"
},
{
"path": "23-fixes/cpu/idt.c",
"chars": 483,
"preview": "#include \"idt.h\"\n#include \"type.h\"\n\nvoid set_idt_gate(int n, uint32_t handler) {\n idt[n].low_offset = low_16(handler)"
},
{
"path": "23-fixes/cpu/idt.h",
"chars": 1052,
"preview": "#ifndef IDT_H\n#define IDT_H\n\n#include <stdint.h>\n\n/* Segment selectors */\n#define KERNEL_CS 0x08\n\n/* How every interrupt"
},
{
"path": "23-fixes/cpu/interrupt.asm",
"chars": 5920,
"preview": "; Defined in isr.c\n[extern isr_handler]\n[extern irq_handler]\n\n; Common ISR code\nisr_common_stub:\n ; 1. Save CPU state"
},
{
"path": "23-fixes/cpu/isr.c",
"chars": 4208,
"preview": "#include \"isr.h\"\n#include \"idt.h\"\n#include \"../drivers/screen.h\"\n#include \"../drivers/keyboard.h\"\n#include \"../libc/stri"
},
{
"path": "23-fixes/cpu/isr.h",
"chars": 2173,
"preview": "#ifndef ISR_H\n#define ISR_H\n\n#include <stdint.h>\n\n/* ISRs reserved for CPU exceptions */\nextern void isr0();\nextern void"
}
]
// ... and 19 more files (download for full content)
About this extraction
This page contains the full source code of the cfenollosa/os-tutorial GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 219 files (262.2 KB), approximately 86.3k tokens, and a symbol index with 243 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.