├── .gitignore
├── Makefile
├── README.md
├── bin
└── .gitkeep
├── config
└── linker.ld
├── doc
├── CODE.md
├── CONFIG.md
├── DOCUMENTATION.md
├── code
│ ├── Utils
│ │ ├── UTILS.md
│ │ ├── VGA.md
│ │ └── string.md
│ ├── boot
│ │ ├── boot.md
│ │ ├── boot_sector.md
│ │ ├── disk.md
│ │ ├── gdt.md
│ │ ├── kernel_entry.md
│ │ └── switch_pm.md
│ ├── drivers
│ │ ├── drivers.md
│ │ └── keyboard.md
│ ├── interrupts
│ │ ├── idt.md
│ │ ├── interrupts.md
│ │ ├── interrupts_asm.md
│ │ ├── isr.md
│ │ ├── pic.md
│ │ ├── port.md
│ │ └── timer.md
│ └── memory
│ │ └── segmentation.md
└── img
│ └── hard_disk_structure.png
├── docker
└── Dockerfile
├── iso
└── .gitkeep
└── src
├── boot_sector
├── boot_sector.asm
├── disk.asm
├── gdt.asm
└── switch_pm.asm
├── entry
├── entry_point.asm
└── kernel_entry.c
├── interrupts
├── pic.c
├── pic.h
├── port.c
├── port.h
├── timer.c
└── timer.h
└── utils
├── VGA.h
├── VGA
├── VGA.h
├── clear.c
├── clear.h
├── print.c
└── print.h
├── string.h
├── string
├── itoa.c
├── itoa.h
├── revstr.c
├── revstr.h
├── strlen.c
└── strlen.h
└── types.h
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/*
2 | !bin/.gitkeep
3 | iso/*
4 | !iso/.gitkeep
5 | *.o
6 | *.bin
7 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SRC = ./src
2 | CONFIG = ./config
3 | BIN = ./bin
4 | ISO = ./iso
5 |
6 | # Main folders path
7 | ENTRY = $(SRC)/entry
8 | UTILS = $(SRC)/utils
9 | BOOT = $(SRC)/boot_sector
10 | INTERRUPTS = $(SRC)/interrupts
11 |
12 | # Kernel needed file(s)
13 | KERNEL_BIN = $(BIN)/kernel.bin
14 | KERNEL_BUILD = $(BIN)/kernelfull.o
15 | OS_BIN = $(ISO)/epi-os.iso
16 |
17 | # Compilation tools (compiler, linker, etc..)
18 | NASM = nasm
19 | CC = i686-elf-gcc
20 | LD = i686-elf-ld
21 |
22 | # Boot sector
23 | BOOT_SRC = $(BOOT)/boot_sector.asm
24 | BOOT_BIN = $(BIN)/boot.bin
25 | BOOT_FLAGS = -f bin
26 |
27 | # Includes
28 | INCLUDES = -I $(SRC) -I $(UTILS)
29 |
30 | # Flags
31 | ASM_FLAGS = -f elf
32 | CFLAGS = -g -ffreestanding $(INCLUDES)
33 | LDFLAGS = -Ttext 0x1000 --oformat binary
34 |
35 | # Sources
36 | ASM_SRC = $(ENTRY)/entry_point.asm
37 | C_SRC = $(ENTRY)/kernel_entry.c \
38 | $(UTILS)/VGA/clear.c \
39 | $(UTILS)/VGA/print.c \
40 | $(UTILS)/string/revstr.c \
41 | $(UTILS)/string/itoa.c \
42 | $(UTILS)/string/strlen.c
43 |
44 | # Objects
45 | C_OBJ = $(C_SRC:.c=.o)
46 | ASM_OBJ = $(ASM_SRC:.asm=.o)
47 | KERNEL_OBJS = $(ASM_OBJ) $(C_OBJ)
48 |
49 |
50 | # Targets
51 | all: build
52 |
53 | build: boot_bin kernel_bin
54 | dd if=$(BOOT_BIN) >> $(OS_BIN)
55 | dd if=$(KERNEL_BIN) >> $(OS_BIN)
56 | dd if=/dev/zero bs=1048576 count=16 >> $(OS_BIN)
57 |
58 | # Compile and launch QEMU
59 | run:
60 | qemu-system-x86_64 -d int -no-reboot $(OS_BIN)
61 |
62 | build_and_run: build run
63 |
64 | boot_bin:
65 | $(NASM) $(BOOT_FLAGS) $(BOOT_SRC) -o $(BOOT_BIN)
66 |
67 | kernel_bin: $(KERNEL_OBJS)
68 | $(LD) $(LDFLAGS) $(KERNEL_OBJS) -o $(KERNEL_BIN)
69 |
70 | clean:
71 | $(RM) $(C_OBJ)
72 | $(RM) $(ASM_OBJ)
73 | $(RM) $(KERNEL_BIN)
74 | $(RM) $(BOOT_BIN)
75 | $(RM) $(KERNEL_BUILD)
76 |
77 | fclean: clean
78 | $(RM) $(OS_BIN)
79 |
80 | re: fclean all
81 |
82 | # Compilations rules
83 | %.o: %.c
84 | $(CC) $(CFLAGS) -c $< -o $@
85 |
86 | %.o: %.asm
87 | $(NASM) $(ASM_FLAGS) $< -o $@
88 |
89 | .PHONY: build run build_and_run boot_bin kernel_bin clean fclean re
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OS workshops
2 |
3 | ## Cross Compiler
4 |
5 | To compile the OS for the CPU, we need a cross compiler, a cross linker, etc.. It allows us to compile for another target than your current OS.
6 |
7 | We have made a Docker image, to build and run it, run the next instructions:
8 |
9 | #### Docker
10 | ```bash
11 | docker build -t osdev .
12 | ```
13 |
14 | If you want to run qemu in graphical mode from the container (Forwarding X socket),
15 |
16 | ```
17 | docker run -it -e DISPLAY=$DISPLAY \
18 | -v /tmp/.X11-unix:/tmp/.X11-unix \
19 | -v $PWD:/osdev osdev
20 | ```
21 |
22 | if you just want to compile,
23 |
24 | ```bash
25 | docker run -it -v $PWD:/osdev osdev
26 | ```
27 |
28 | #### From sources
29 | You can build it by hand from this source https://wiki.osdev.org/GCC_Cross-Compiler.
30 |
31 | ## Documentation
32 |
33 | To read the documentaion [**Doc**](doc/DOCUMENTATION.md).
34 |
--------------------------------------------------------------------------------
/bin/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/bin/.gitkeep
--------------------------------------------------------------------------------
/config/linker.ld:
--------------------------------------------------------------------------------
1 | ENTRY(_start)
2 | OUTPUT_FORMAT(binary)
3 | SECTIONS
4 | {
5 | . = 1M;
6 | .text : ALIGN(4096)
7 | {
8 | *(.text)
9 | }
10 |
11 | .asm : ALIGN(4096)
12 | {
13 | *(.asm)
14 | }
15 |
16 | .rodata : ALIGN(4096)
17 | {
18 | *(.rodata)
19 | }
20 |
21 | .data : ALIGN(4096)
22 | {
23 | *(.data)
24 | }
25 |
26 | .bss : ALIGN(4096)
27 | {
28 | *(COMMON)
29 | *(.bss)
30 | }
31 | }
--------------------------------------------------------------------------------
/doc/CODE.md:
--------------------------------------------------------------------------------
1 | # Code documentation
2 |
3 | ## Introduction
4 |
5 | this is the code documentation of the project. you can find all the explainations about the differents parts of the code, and the differents theorical parts of the project. all the documentation is store in the `doc` folder. and splitted in differents parts:
6 |
7 | - [Boot](code/boot/boot.md) \
8 | this part explains all the differents parts of the boot process of the project (boot_sector, kernel entry, etc...).
--------------------------------------------------------------------------------
/doc/CONFIG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/doc/CONFIG.md
--------------------------------------------------------------------------------
/doc/DOCUMENTATION.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | Here you can find the project documentation. It explains the following differents parts:
4 |
5 | - [Config](CONFIG.md)
6 |
7 | - [Code](CODE.md)
--------------------------------------------------------------------------------
/doc/code/Utils/UTILS.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This is the introduction of all utils, it's all the useful functions that you can use to access some datas and some drivers.
4 | For now it's only including some folders with functions but in the future it will be more complex and more useful. So it will be a good idea to compile it with other makefiles.
5 | An important things about the utils is the fact that only one file is needed to be included in your other projects to use all the functions. So it's **FORBIDDEN** to include any other files.
6 |
7 | # TABLE OF CONTENTS
8 |
9 | - [VGA](#vga)
10 | - [string](#string)
11 |
12 | # VGA
13 |
14 | The vga is the first displaying mode of the PM, it's a `80` x `25` matrix starting from 0xB80000, each characters is 2 bytes long, the first one is the character (`ASCII`) and the second one is the `color` of the character. The color is a 4 bits value, the first 2 bits are the background color and the last 2 bits are the foreground color. The color is defined in the [`vga.h`](../../../src/utils/VGA/VGA.h) file.
15 | All the functions are described [HERE](VGA.md)
16 |
17 | # string
18 |
19 | The string utils is a string library, it contains all the functions to manipulate strings. All the functions are described [HERE](string.md)
20 |
--------------------------------------------------------------------------------
/doc/code/Utils/VGA.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | Here is the VGA (Video Graphics Array) driver, it's a driver that allows you to display text on the screen. In the project, it's stored it the `src/utils/VGA` folder and can be used with the [`VGA.h`](../../../src/utils/VGA.h) include.
4 |
5 | # TABLE OF CONTENTS
6 |
7 | - [How it works ?](#how-it-works)
8 | - [How to use it ?](#how-to-use-it)
9 | - [Functions list](#functions-list)
10 | - [Want to contribute ?](#want-to-contribute)
11 |
12 | # How it works ?
13 |
14 | The VGA is provided by the BIOS, so we have to follow some rules, the first things to know is that the VGA is a buffer of `80 x 25` characters starting from the adress `0xB8000`, each characters is 2 bytes long, the first one is the character (`ASCII`) and the second one is the `color` of the character. A color is a 4 bits values (0-15) and the first 4 bits are the background color and the last 4 bits are the foreground color. The color is defined in the [`vga.h`](../../../src/utils/VGA/VGA.h) file.
15 |
16 | # How to use it ?
17 |
18 | The VGA folder provides an API that can be included to use all the functions. Only the `VGA.h` file is needed to use the VGA driver. It's made this way to have a cleaner code
19 |
20 |
21 | # Functions list
22 |
23 | - [vga_clear_screen](#func-vga-clear-screen)
24 | - [vga_putchar_at](#func-vga-putchar-at)
25 | - [vga_putstr_at](#func-vga-putstr-at)
26 | - [vga_print_int_at](#func-vga-print-int-at)
27 | - [vga_printf_at](#func-vga-printf-at)
28 |
29 | ## vga_clear_screen
30 |
31 | This function clear fill all the screen with the character ' ' and the color 0x00.
32 | Here is the prototype of the function:
33 |
34 | ```c
35 | void vga_clear_screen();
36 | ```
37 |
38 | ## vga_putchar_at
39 |
40 | This function display a character at the given position with the given color.
41 | It's the basic function to display a character on the screen.
42 | It will return 1 if the character is displayed and will return a negative value in case of error.
43 | Errors:
44 | - -1: The position is out of the screen (x > 80 or y > 25)
45 | Here is the prototype of the function:
46 |
47 | ```c
48 | int vga_putchar_at(char c, uint8_t color, uint8_t x, uint8_t y);
49 | ```
50 |
51 | ## vga_putstr_at
52 |
53 | This function display a string at the given position with the given color.
54 | It's a basic function that display a string on the screen.
55 | It will return the string length if the string is displayed and will return a negative value in case of error.
56 | Errors:
57 | - -1: The position is out of the screen (x > 80 or y > 25)
58 | Here is the prototype of the function:
59 |
60 | ```c
61 | int vga_putstr_at(const char *str, uint8_t color, uint8_t x, uint8_t y);
62 | ```
63 |
64 | ## vga_print_int_at
65 |
66 | This function display an integer at the given position with the given color.
67 | It's a basic function that display an integer on the screen.
68 | It will return the string length if the integer is displayed and will return a negative value in case of error.
69 | Errors:
70 | - -1: The position is out of the screen (x > 80 or y > 25)
71 | Here is the prototype of the function:
72 |
73 | ```c
74 | int vga_print_int_at(int num, uint8_t color, uint8_t x, uint8_t y);
75 | ```
76 |
77 | ## vga_printf_at
78 |
79 | This function display a formatted string at the given position with the given color.
80 | It's a basic function that display a formatted string on the screen.
81 | It will return the string length if the string is displayed and will return a negative value in case of error.
82 | Errors:
83 | - -1: The position is out of the screen (x > 80 or y > 25)
84 | Here is the prototype of the function:
85 |
86 | ```c
87 | int vga_printf_at(const char *format, uint8_t color, uint8_t x, uint8_t y, ...);
88 | ```
89 |
90 | # Want to contribute ?
91 |
92 | If you want to add a function in the VGA driver, you can do it by creating a new file in the `src/utils/VGA` folder and add the function in the [`VGA.h`](../../../src/utils/VGA.h) file. Pay attention to add the documentation of the function in this folder. And please make clean commented code with a proof of working code.
93 |
94 |
--------------------------------------------------------------------------------
/doc/code/Utils/string.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | String utils is a library that contains all the functions to manipulate strings. It's just basic functions as you may know, but it's a good start to manipulate strings and it will be useful in the future.
4 |
5 | # TABLE OF CONTENTS
6 |
7 | - [How it works ?](#how-it-works)
8 | - [How to use it ?](#how-to-use-it)
9 | - [Functions list](#functions-list)
10 | - [Want to contribute ?](#want-to-contribute)
11 |
12 | # How it works ?
13 |
14 | This is containing all the functions to manipulate strings. It's just basic functions as you may know, but it's a good start to manipulate strings and it will be useful in the future. All the functions are stored in the `src/utils/string/` folder.
15 |
16 | # How to use it ?
17 |
18 | The string utils is a library that contains all the functions to manipulate strings. It's just basic functions as you may know, but it's a good start to manipulate strings and it will be useful in the future. It's providing an API named [string.h](../../../src/utils/string.h) that contains all the functions. Only this file must be included to use the string utils.
19 |
20 | # Functions list
21 |
22 | - [itoa](#func-itoa)
23 | - [revstr](#func-revstr)
24 |
25 | ## itoa
26 |
27 | This function convert an integer to a string into the given base. It will return nothing but the final string will be stored in the buffer given in parameter. The buffer must be big enough to store the string.
28 |
29 | Here is the prototype of the function:
30 |
31 | ```c
32 | void itoa(int num, char *str, uint8_t base);
33 | ```
34 |
35 | ## revstr
36 |
37 | This function reverse a string. It will return nothing but the final string will be stored in the buffer given in parameter.
38 |
39 | Here is the prototype of the function:
40 |
41 | ```c
42 | void revstr(char *str);
43 | ```
44 |
45 | # Want to contribute ?
46 |
47 | If you want to contribute to this utils, you can add your own function, please make sure that the code is clean and that it's working. If you want to add a function, please add it in the [string.h](../../../src/utils/string.h) file and add the documentation in the [string.md](string.md) file.
48 |
--------------------------------------------------------------------------------
/doc/code/boot/boot.md:
--------------------------------------------------------------------------------
1 | # Boot documentation
2 |
3 | ## Introduction
4 |
5 | this is the documentation of all the booting things, it's splitted into differents parts that explain all the differents parts of the booting process. You have several parts:
6 |
7 | ## Table of content
8 |
9 | - [Boot sector](boot_sector.md)
10 | - [Kernel entry](kernel_entry.md)
11 |
12 | ## Boot sector
13 |
14 | The MD doc:
15 | - [Boot sector](boot_sector.md)
16 |
17 | The boot sector is the first part of the booting process. It's serve to load the kernel and give kernel precious informations about the hardware and how the processor should be configured.
18 |
19 | ## Kernel entry
20 |
21 | The MD doc:
22 | - [Kernel entry](kernel_entry.md)
23 |
24 | The kernel entry is the entry point of the kernel. It's the first function that is called when the kernel is loaded. It's serve to initialize the kernel and to call the kernel main function. It's the equivalent of the C main function.
25 |
--------------------------------------------------------------------------------
/doc/code/boot/boot_sector.md:
--------------------------------------------------------------------------------
1 | # Boot sector documentation
2 |
3 | ## Introduction
4 |
5 | This document explains the boot sector stuff. It is splitted into different parts that explain the booting process. Every file that we will refers to are in `src/boot_sector/`.
6 |
7 | ## Table of content
8 |
9 | - [Processor modes](#processor-modes)
10 | - [Bios routines](#bios-routines)
11 |
12 | ## The existing modes
13 |
14 | This is the theorical documentation for the processor modes. In the Intel architecture, there are many different modes that the CPU can use. The CPU can boot in differents modes, **16 bits**, **32 bits** or **64 bits**, respectively named **Real mode**, **Protected mode** and **Long mode**. Every mode has differents features and limitations. More details below:
15 |
16 | - [Real mode](#real-mode)
17 | - [Protected mode](#protected-mode)
18 | - [Long mode](#long-mode)
19 |
20 | ### Real mode (16 bits)
21 |
22 | The real mode is the firt mode that we have access when booting the processor. It's a 16 bits mode that have a lot of limitations. The main limitation is that the processor can only access 64KB of memory (because the max amount of addresses is 65,536 bytes (216) that represents 64KB. But with some tricky things you can upgrade to less that 1MB of memory
23 |
24 | Here is a list of the quality and limitations of the real mode:
25 |
26 | - Cons:
27 | - Less than 1 MB of RAM is available for use.
28 | - There is no hardware-based memory protection (GDT), nor virtual memory.
29 | - There is no built in security mechanisms to protect against buggy or malicious applications.
30 | - The default CPU operand length is only 16 bits.
31 | - The memory addressing modes provided are more restrictive than other CPU modes.
32 | - Accessing more than 64k requires the use of segment register that are difficult to work with.
33 | - Pros
34 | - The BIOS installs device drivers to control devices and handle interrupt.
35 | - BIOS functions provide operating systems with a advanced collection of low level API functions.
36 | - Memory access is faster due to the lack of descriptor tables to check and smaller registers.
37 |
38 | ### Protected mode (32 bits)
39 |
40 | the protected mode is the second mode that we have access when booting the processor. It's a 32 bits mode that have a lot of limitations. The main limitation is that the processor can only access 4GB of memory (because the max amount of addresses is 4,294,967,296 bytes (232) that represents 4GB. But with some tricky things you can upgrade to less that 4GB of memory. \
41 | It's a bit old now but it was for a long time the main mode of the processor. It's still used in some cases like windows 32 bits. The main advantage of the protected mode is that it's a lot more secure than the real mode. It's also a lot more powerful than the real mode. It's also a lot more easy to use than the real mode. But the main cons of the protected mode is that you cannot access to the BIOS interrupts and it's more complex to use but it's not a big deal.
42 |
43 | ### Long mode (64 bits)
44 |
45 | the long mode is the third mode that we have access when booting the processor. It's a 64 bits mode that have a lot of limitations. The main limitation is that the processor can only access 256TB of memory (because the max amount of addresses is 256, terabytes (248) that represents 256TB. But with some tricky things you can upgrade to less that 256TB of memory. \
46 | Don't be affraid of this because you will not have to use this for now :).
47 |
48 | ## BIOS routine
49 |
50 | When you are starting your computer, the bios is doing some routine to check if in the current disk there is a bootable partition. To check this, it's checking if the address `0x7C00 + 510` has the two bytes `0x55` and `0xAA`. If it's the case, it's loading the first 512 bytes of the disk in the memory at the address `0x7C00` and running the code.
51 |
52 | ## [boot_sector.asm](../../../src/boot_sector/boot_sector.asm) explaination
53 |
54 | Explaination of all the code in the file `src/boot_sector/boot_sector.asm`
55 |
56 | ```nasm
57 | [org 0x7C00]
58 | [bits 16]
59 | ```
60 |
61 | Here you have the two first lines that you must understand. The first line is telling the assembler that the code will be at the address `0x7C00`. The second line is telling the assembler that the code will be in 16 bits mode. If you read the [BIOS Routine](#bios-routine) section, you will understand that the bios is loading the first 512 bytes of the disk in the memory at the address `0x7C00`. So we have to put our code at this address.
62 |
63 | After this you have the first label
64 |
65 | ```nasm
66 | start:
67 | mov ax, 0x00
68 | mov ds, ax
69 | mov es, ax
70 | mov ss, ax
71 | mov bp, 0x7c00
72 | mov sp, bp
73 | ```
74 |
75 | This label is initializing every segment register to 0 (if you don't know what a segment register is, please see the [**Doc**](../memory/segmentation.md)) \
76 | The `mov ax, 0x00` is moving the value `0x00` in the register `ax`. It's useful for initializing the registers ds, es and ss to a default value
77 |
78 | `bp` and `sp` are the stack register, (sp = stack pointer, bp = base pointer). The stack is the local memory of your program, it's where all the local variables are stored. By definition the stack is growing downward (from high address to low address). The base pointer is the beginning of the stack and the stack pointer is the current position of the stack. We are setting both to `0x7c00` because we are beginning all the kernel code at this address so when the stack is growing it will not overlapping the kernel code.
79 |
80 | After the start label that will init all the useful register we need to load the disk and switch to the protected mode. To do this we have a label in 16bits mode that manage all of this.
81 |
82 | ```nasm
83 | [bits 16]
84 | run_16:
85 | mov [BOOT_DRIVE], dl
86 | call load_kernel
87 | call switch_to_pm
88 | ```
89 | So as you can see it's a 16 bits label that load our kernel and switch to the protected mode. The hardest line in this label is the `mov [BOOT_DRIVE], dl`. When you are launching the kernel, BIOS store the id of the boot drive in the register `dl`. So we are moving the value of `dl` in the address `BOOT_DRIVE` to save the data.
90 | The two other lines are just calling the two functions that we will see later.
91 |
92 | The first function that will be called in our `run_16` label is the `load_kernel` function. This function is loading the entire kernel in the memory it's a 16 bits label that read the disk and store the content into the memory. Here is the actual code of the function:
93 |
94 | ```nasm
95 | [bits 16]
96 | load_kernel:
97 | mov bx, KERNEL_OFFSET
98 | mov dh, 31
99 | mov dl, [BOOT_DRIVE]
100 | call disk_load
101 | ret
102 | ```
103 | If you want to see what's doing the `disk_load` function, you can see it in the [disk.md](disk.md) file. this function ask to set some registers before using it:
104 | - `bx`: it's the offset in the memory where the kernel will be loaded
105 | - `dh`: it's the number of sectors that will be loaded
106 | - `dl`: it's the id of the disk that will be loaded
107 | once the function is called, it will load `dh` sector from the disk `dl` and store the result into the memory at the offset `bx`.
108 |
109 | Once the kernel is loaded in memory, we have to switch to the protected mode. To do this we have to call the `switch_to_pm` function. This function is doing all the routine that we need to switch from RM to PM, if you want to see what is doing exactly this function you can see it in the [switch_pm.md](switch_pm.md) file.
110 |
111 | When all is done we are in protected mode (finally), so we want to jump to the C kernel code. to do this we have a label called `entry_point`.
112 |
113 | ```nasm
114 | entry_point:
115 | call KERNEL_OFFSET
116 | jmp $
117 | ```
118 | Previously, we have loaded the kernel in the memory at the offset `KERNEL_OFFSET`. So we are calling the function at this offset. The `jmp $` is just a infinite loop.
119 |
120 | The end of the file is for loading all the other files and writting the magic bytes at the end of the file.
121 |
122 | ```nasm
123 | %include "src/boot_sector/disk.asm"
124 | %include "src/boot_sector/gdt.asm"
125 |
126 | times 510-($ - $$) db 0
127 | dw 0xAA55
128 | ```
129 | %include instruction is obviously for including the other files.
130 | - [gdt.asm](gdt.md)
131 | - [disk.asm](disk.md)
132 | - [switch_pm.asm](switch_to_pm.md)
133 |
134 | the `times 510-($ - $$) db 0` is writing 510 - (current address - start address) bytes of 0. It's just padding the file with 0 to have a size of 510 bytes. The last two bytes are the magic bytes `0xAA55` that are telling the bios that the file is bootable (necessary for the [BIOS routine](#bios-routine)).
--------------------------------------------------------------------------------
/doc/code/boot/disk.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This document explains all the code in the file `src/boot_sector/disk.asm`.
4 | This file manage all the hard disk related stuff.
5 |
6 | ## Table of content
7 |
8 | - [Introduction](#introduction)
9 | - [Table of content](#table-of-content)
10 | - [Disk structure](#disk-structure)
11 | - [Head](#head)
12 | - [Cylinder](#cylinder)
13 | - [Sector](#sector)
14 | - [Disk routines](#disk-routines)
15 | - [Code explaination](#code-explaination)
16 |
17 | ## Disk structure
18 |
19 | The structure of a hard disk is quite simple, it inherits the real structure of hard disks of that time.
20 | Here is a simple schema of the structure of a hard disk:
21 |
22 | You can see that the hard disk is divided in 4 parts, the biggest part is the head, an head contains many cylinders and a cylinder contains many sectors.
23 | Here is a description of what is a head, a cylinder and a sector:
24 |
25 | #### Head
26 |
27 | The head is the needle that is used to read and write on a disk, so a head represents on which disk you want to write. In asm, the head is represented by the `DH` register.
28 |
29 | #### Cylinder
30 |
31 | The cylinder is a division of the disk in a vertical way. It's used to represent the position of the head on the disk. In asm, the cylinder is represented by the `CH` register.
32 |
33 | #### Sector
34 |
35 | The sector is a division of the disk in a horizontal way. It's used to represent the position of the head on the disk. In asm, the sector is represented by the `CL` register.
36 |
37 | ## Disk routines
38 |
39 | To access the hard disk, we need to use some routines, but first we need to know on which mode we want to access the hard disk. It's important because the hard disk can only be directly accessed by the CPU in RM and not in PM or above. In the file [boot_sector/disk.asm](../../../src/boot_sector/disk.asm) there is only the routine for RM, but if you want to access the hard disk in PM or above you will need to use the BIOS to communicate with the hard disk.
40 |
41 | To read the disk we need to call the interrupts 0x13 `ah` set to 0x02. All thoses register needs to be set before calling the interrupt:
42 | - `al`: number of sectors to read
43 | - `ch`: cylinder
44 | - `cl`: sector
45 | - `dh`: head
46 | - `dl`: drive
47 |
48 | Once all of this is done, the interrupts will read the sectors and put them in the memory at the address `es:bx`.
49 |
50 | ## Code explaination
51 |
52 | So, if you read all the previous part, you should know how to read the hard disk. In this file, we created a function named `disk_load` that will load the sectors from the hard disk to the memory. This function takes only two parameters:
53 | - `bx` for the address in memory where the sectors will be loaded
54 | - `dl` for the drive number
55 | - `dh` for the number of sectors to read (yes if you already read [Disk routines](#disk-routines) you must know that it's the `al` register but if we are using `dh` instead of `al` it's because we are using only one register (`dx`) so it's cleaner)
56 |
57 | Once all of this is set we can call this function:
58 | ```nasm
59 | disk_load:
60 | push dx
61 | mov ah, 0x02
62 | mov al, dh
63 | mov ch, 0x00
64 | mov dh, 0x00
65 | mov cl, 0x02
66 | int 0x13
67 | jc disk_error
68 | pop dx
69 | cmp dh, al
70 | jne disk_error
71 | ret
72 | ```
73 |
74 | It's not a pretty hard function, it's just a wrapper around the interrupt 0x13. The only thing that is a bit tricky is the `jc disk_error`.
75 | The first line is `push dx`, it's storing the register dx to the stack so we can use it later. The register dx is containing both of the register dh and dl. dl is the low bytes of dx (the first byte) and dh is the high bytes of dx (the second byte). So we are storing the register dx to the stack so we can use it later.
76 | After we have a couple of line:
77 | ```nasm
78 | mov ah, 0x02
79 | mov al, dh
80 | mov ch, 0x00
81 | mov dh, 0x00
82 | mov cl, 0x02
83 | ```
84 | Those line is just setting all the register needed to call the interrupts 0x13 and read the sectors. Remember that we have `dh` for the number of sectors to read and `dl` for the drive number (dl is already set).
85 | After that we just need to call the interrupt 0x13 with the command `int 0x13`. A particularity of the interrupts is that if it fails it will set the carry flag (CF) to 1. So we need to check if the carry flag is set to 1, to do this the keyword `jc` is used. If the carry flag is set to 1, it will jump to the label `disk_error` and if it's not set to 1 it will continue the execution of the function.
86 | Finally, we need to check if the number of sectors read is the same as the number of sectors we wanted to read. If it's not the same, it means that there was an error and we need to jump to the label `disk_error`. If it's the same, we just need to return from the function.
87 | ```nasm
88 | pop dx ; Restore DX from the stack
89 | cmp dh, al ; if AL ( sectors read ) != DH ( sectors expected )
90 | jne disk_error ; display error message
91 | ret
92 |
93 | disk_error:
94 | jmp $
95 | ```
96 | Theses lines are just restoring the register `dx` from the stack (previously pushed) and checking if the number of sectors read is the same as the number of sectors we wanted to read. If it's not the same, it means that there was an error and we need to jump to the label `disk_error`. If it's the same, we just need to return from the function.
--------------------------------------------------------------------------------
/doc/code/boot/gdt.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This document explains all the code in the file `src/boot_sector/gdt.asm`.
4 |
5 | ## What does it mean?
6 |
7 | GDT stands for `Global Descriptor Table`, basically it is a binary structure. It contains entries telling the CPU about memory segments. A similar Interrupt Descriptor Table exists containing task and interrupt descriptors.
8 |
9 |
10 | ## What does it do ?
11 |
12 | As we remember, in real mode (16 bits), the segmentation is managed by the segment registers (`cs`, `ds`, `es`, `fs` and `gs`). Now it is managed by the segment stored in the GDT.
13 |
14 | The GDT should be seen as a large table with segment descriptors as entries.
15 |
16 | Each entry has a size of 8 bytes, remember that the first one is always filled with zeros, it looks like this
17 |
18 | ```nasm
19 | first_segment:
20 | dd 0x0
21 | dd 0x0
22 | ```
23 |
24 | Every other entries should look like this:
25 |
26 | ```nasm
27 | entry_example:
28 | dw 0xffff ; Segment limit first 0-15 bits
29 | dw 0 ; Base first 0-15 bits
30 | db 0 ; Base 16-23 bits
31 | db 0x9a ; Access byte
32 | db 11001111b ; High 4 bit flags and the low 4 bit flags
33 | db 0 ; Base 24-31 bits
34 | ```
35 |
36 | ## Loading the GDT
37 |
38 | To load the GDT, there is an instruction named `lgdt`, it means `Load Global Descriptor Table`. `lgdt` requires a 6 bytes structure, in this code we named it `gdt_descriptor`, as we can see below:
39 | ```nasm
40 | lgdt [gdt_descriptor]
41 | ```
42 |
43 | ## Usage example
44 |
45 | As example you can see in `src/boot_sector/switch_pm.asm`:
46 | ```nasm
47 | jmp CODE_SEG:init_pm
48 | ```
49 |
50 | Here, `CODE_SEG` is not a reference to the segment itself. Actually, it refers to the segment descriptor entry in the GDT. Moreover, in this part of code we were not in the segment `CODE`, it will make us using a different segment. It is called a `far jump`.
--------------------------------------------------------------------------------
/doc/code/boot/kernel_entry.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/doc/code/boot/kernel_entry.md
--------------------------------------------------------------------------------
/doc/code/boot/switch_pm.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This document explains all the code in the file `src/boot_sector/switch_pm.md`. It's containing the code to switch from RM to PM. It's called in the [boot_sector.asm](../../../src/boot_sector/boot_sector.asm) file. It's important to switch to PM because it's allowing us to use all the 32 bits features of the CPU, like more
4 | memory, more registers, etc. To do the switch you must make a routine that will modify the behavior and switch the CPU into 32 bits mode.
5 |
6 | ## Table of content
7 |
8 | - [BIOS Routine](#bios-routine)
9 | - [Routine explaination](#routine-explaination)
10 | - [Code explaination](#code-explaination)
11 |
12 | ## BIOS Routine
13 |
14 | We need to do a routine to do the switch, this routine is provided by the Intel doc
15 | Here is the routine to switch from RM to PM:
16 |
17 | - Clear the interrupts
18 | - Load the GDT
19 | - Set the first bit of the CR0 register to 1
20 | - Make a far jump to the first 32 bits label in the code segment
21 | - Clear all the old segment registers
22 | - Update the stack pointer
23 |
24 | All theses instructions must be done to switch from RM to PM. If one is missing, the switch can fail.
25 |
26 | ## Routine explaination
27 |
28 | The first task of the routine is to clear the interrupts, you have to know that in PM it's not possible to use the interrupts because it's made radically different. So we need to disable all the interrupts to avoid conflicts with the new mode and the switching routine.
29 |
30 | Next, we need to load the GDT, the memory management is different between RM (basic flat memory) and PM (memory segmentation). The memory segmentation ask first to create a complex data structure that named [GDT](gdt.md)
31 |
32 | Next, we are setting the first bit of the CR0 register to 1, it's done by this code:
33 | ```nasm
34 | mov eax, cr0
35 | or eax, 1
36 | mov cr0, eax
37 | ```
38 | No explication for this (for now), it's juste asked by the Intel doc.
39 |
40 | Next, we are making a far jump to the first 32 bits label in the code segment, it's done by this code:
41 | ```nasm
42 | jmp CODE_SEG:init_pm
43 | ```
44 | You probably now how this type of jump works but there is an important feature to know. The far jump clear the CPU pipeline, the CPU pipeline in modern CPU is used to run tasks in a parallel way. It's very useful but for us when we are changing the mode some tasks can crash because of the differents mode (like 16 bits segmentation, interrupts, etc..). To avoid theses crashes we need to clear the CPU pipeline, it's done by the far jump.
45 |
46 | Now we are entering the first PM label. like said previously (#routine-explaination) we need to clear all the old segment because it's useless so we are setting all the segment registers (ds, ss, es, fs, gs) to the data segment. So when using theses, it will refer to the data segment.
47 |
48 | Finally, we are updating the stack pointer, before, the stack pointer was in 16 bits mode, now we are in 32 bits mode so we have more space, more memory adress, and new registers to the stack pointer. So we need to update the stack pointers to the new 32 bits mode.
49 |
50 | ## Code explaination
51 |
52 | we have two different labels in this file, one for RM and the other one for PM. Here is the first one
53 |
54 | ```nasm
55 | [bits 16]
56 | switch_to_pm:
57 | cli ; 1. disable interrupts
58 | lgdt [gdt_descriptor] ; 2. load the GDT descriptor
59 | mov eax, cr0
60 | or eax, 0x1 ; 3. set 32-bit mode bit in cr0
61 | mov cr0, eax
62 | jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
63 | ```
64 | this is our 16 bits mode, we are still in RM and as you can see, it's the first steps of the [bios routine](#bios-routine). Once we have done this, we are jumping to the PM label.
65 |
66 | ```nasm
67 | [bits 32] ; protected mode
68 | init_pm: ; we are now using 32-bit instructions
69 | mov ax, DATA_SEG ; 5. update the segment registers
70 | mov ds, ax
71 | mov ss, ax
72 | mov es, ax
73 | mov fs, ax
74 | mov gs, ax
75 |
76 | mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
77 | mov esp, ebp
78 |
79 | call entry_point ; 7. Call a well-known label with useful code
80 |
81 | ```
82 | No more to say here, it's just the second part of the [bios routine](#bios-routine). Once we have done this, we are jumping to the entry_point label. It's were we can finally start to use the 32 bits features of the CPU.
83 |
--------------------------------------------------------------------------------
/doc/code/drivers/drivers.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This document describes the drivers and how to use them.
4 | All the drivers provides an interface to the hardware, and are used by the kernel to access the hardware.
5 |
6 | # TABLE OF CONTENTS
7 |
8 | - [DRIVERS LIST](#drivers-list)
9 |
10 | # DRIVERS LIST
11 |
12 | - [keyboard](keyboard.md): keyboard management
13 |
--------------------------------------------------------------------------------
/doc/code/drivers/keyboard.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This file explains you the code in the [keyboard.c](../../../src/drivers/keyboard.c) file.
4 | All this code takes the data from the `PIC` and manages it to be able to use it later in the kernel.
5 | After initializing all the routines, `IDT` and `PIC`, we can now use the keyboard. We can get the key pressed with the `port_byte_in` function. This function is in the [port.c](../../../src/interrupts/port.c) file. See the [doc](port.md) for more details. When a key is pressed it will check the port `0x60` and return the value of the key pressed. We can then use this value to do what we want. If no key is pressed, it will return `0`.
6 | It will not return the ASCII value but it will return the `scancode` of the key pressed. The `scancode` is the value of the key pressed (similar to an index). To transform this code into an ASCII value we are using a layout it will make the transfert between the `scancode` and the ASCII value. You already hear about layouts: `AZERTY`, `QWERTY` is existing layouts.
7 |
8 | # TABLE OF CONTENTS
9 |
10 | - [INTRODUCTION](#introduction)
11 | - [CODE EXPLANATION](#code-explanation)
12 |
13 | # CODE EXPLANATION
14 |
15 | We can see in this file the global that represents the current layout:
16 | ```c
17 | static KEYBOARD_LAYOUT current_layout = FR;
18 | ```
19 | It's a static variable to makes it private to this file. It's the current layout used by the keyboard. It's initialized to `FR` (French layout). the type `KEYBOARD_LAYOUT` is an enum that contains all the layouts available. It's defined in the [keyboard.h](../../../src/drivers/keyboard/keyboard.h) file. For now it only contains this:
20 | ```c
21 | typedef enum {
22 | FR,
23 | US
24 | } KEYBOARD_LAYOUT;
25 | ```
26 | This enum is showing us all the existing layouts. But the definition of the layouts are in another global that looks like this:
27 | ```c
28 | static char layouts[LAYOUTS_NUM][KEYS_NUM] = {
29 | #include "layouts/fr.txt"
30 | #include "layouts/us.txt"
31 | };
32 | ```
33 | It's another static that contains an array of string (string is an array of char). To be more explicit, We are creating an array of `LAYOUTS_NUM` lines and each line contains `KEYS_NUM` char. `LAYOUTS_NUM` and `KEYS_NUM` are both macro defined in the [keyboard.h](../../../src/drivers/keyboard/keyboard.h). `LAYOUTS_NUM` is equal to `2` and `KEYS_NUM` is equal to `58`. They are arbitrary values.
34 |
35 | In this file we also have getter and setter to get and set the current layout
36 | ```c
37 | void set_layout(KEYBOARD_LAYOUT layout)
38 | {
39 | current_layout = layout;
40 | }
41 |
42 | KEYBOARD_LAYOUT get_layout()
43 | {
44 | return current_layout;
45 | }
46 | ```
47 | Nothing special to say here.
48 | The interesting things are here:
49 | ```c
50 | static uint8_t resolve_scancode(uint8_t scancode)
51 | {
52 | if (scancode > KEYS_NUM) {
53 | return 0;
54 | }
55 |
56 | return layouts[current_layout][scancode];
57 | }
58 |
59 | static void callback(registers_t *regs)
60 | {
61 | uint8_t scancode = port_byte_in(0x60);
62 | uint8_t pressed_char = resolve_scancode(scancode);
63 |
64 | // TODO
65 | vga_putchar_at(pressed_char, 0x0f, 20, 20);
66 | }
67 | void init_keyboard()
68 | {
69 | isr_register_handler(33, callback);
70 | }
71 | ```
72 | The `init_keyboard` function is called once in our program, in the [kernel.c](../../../src/kernel.c) file. It registers the `callback` function to the `33` interrupt. The `33` interrupt is the interrupt that is called when a key is pressed. The `callback` function is called when a key is pressed. It will get the `scancode` of the key pressed (The scancode is provided in the `PIC` port `0x60`) and then it will resolve it to get the ASCII value of the key pressed. It will then print the ASCII value on the screen. It's a very simple example of what we can do with the keyboard. We can do a lot of things with it. We can create a shell, a text editor, a game, etc. It's up to you to do what you want with it. The function `resolve_scancode` is here to check if the scancode exists in our arrays
73 |
--------------------------------------------------------------------------------
/doc/code/interrupts/idt.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This file is explaining how is working the [idt.c](../../../src/interrupts/idt.c). This file is holding our `IDT` and it's providing functions to manipulate it.
4 |
5 | # TABLE OF CONTENTS
6 |
7 | - [INTRODUCTION](#introduction)
8 | - [HEADERS](#headers)
9 | - [DATA STRUCTURES](#data-structures)
10 | - [FUNCTIONS](#functions)
11 | - [CODE EXPLANATION](#code-explanation)
12 | - [idt_init](#idt_init)
13 | - [idt_set_gate](#idt_set_gate)
14 | - [idt_load](#idt_load)
15 |
16 | # HEADERS
17 |
18 | ## DATA STRUCTURES
19 |
20 | In the header we are defining some useful datas. The first one that we can see is the:
21 | ```c
22 | #define MAX_IDT_ENTRIES 256
23 | ```
24 | it defines the max size of our `IDT`. The second interesting thing is the `idt_entry_t` structure. It's defining how an entry of our `IDT` will look like. Here is the structure:
25 | ```c
26 | typedef struct idt_entry_s {
27 | uint16_t base_low;
28 | uint16_t segment;
29 | uint8_t always_null;
30 | uint8_t flags;
31 | uint16_t base_high;
32 | } __attribute__((packed)) idt_entry_t;
33 | ```
34 | It describes an entry of our `IDT`. All the explaination about the fields of this structure can be found in the [interrupts.md](../../../doc/code/interrupts/interrupts.md) file.
35 | The other interesting structure is the `idt_descriptor_t` structure. It's defining how our `IDT` descriptor will look like. Here is the structure:
36 | ```c
37 | typedef struct idt_descriptor_s {
38 | uint16_t size;
39 | idt_entry_t *idt_start;
40 | } __attribute__((packed)) idt_descriptor_t;
41 | ```
42 | It's the `IDT` descriptor, you already know what a descriptor is with the [GDT](../boot/gdt.md) descriptor, it's the same things. The `size` field is the size of our `IDT` and the `idt_start` field is the address of the first entry of our `IDT`.
43 | Those two structures have already been explained in the [interrupts.md](interrupts.md) file. But it's a good thing to have a quick look at them.
44 |
45 | There is another data that useful. It's an enum named `IDT_FLAGS`. This enum is containing all the flags that we can use in our `IDT` entries. Here is the enum:
46 | ```c
47 | enum IDT_FLAGS {
48 | // Gate
49 | IDT_FLAG_GATE_TASK = 0X5,
50 | IDT_FLAG_GATE_16BIT_INT = 0x6,
51 | IDT_FLAG_GATE_16BIT_TRAP = 0x7,
52 | IDT_FLAG_GATE_32BIT_INT = 0xe,
53 | IDT_FLAG_GATE_32BIT_TRAP = 0xf,
54 | // Ring
55 | IDT_FLAG_RING0 = (0 << 5),
56 | IDT_FLAG_RING1 = (1 << 5),
57 | IDT_FLAG_RING2 = (2 << 5),
58 | IDT_FLAG_RING3 = (3 << 5),
59 | IDT_FLAG_PRESENT = 0x80
60 | };
61 | ```
62 |
63 | ## FUNCTIONS
64 |
65 | The rest of the header is just function prototypes. Here is the prototypes list:
66 |
67 | - [idt_init](#func-idt-init)
68 | - [set_idt_gate](#func-set-idt-gate)
69 | - [set_gate_flag](#func-set-gate-flag)
70 | - [idt_enable_gates](#func-idt-enable-gates)
71 | - [unset_gate_flag](#func-unset-gate-flag)
72 | - [lidt](#func-lidt)
73 |
74 | ### idt_init
75 |
76 | This function is initialzing our `IDT`, this function is loading all the `IDT` with the `lidt` instruction. Here is the function prototype:
77 | ```c
78 | void idt_init();
79 | ```
80 |
81 | ### set_idt_gate
82 |
83 | This function is setting an entry of our `IDT`. Here is the function prototype:
84 | It takes 4 parameters:
85 | - `uint8_t index`: the index of the entry that we want to set (0 to 255)
86 | - `void *base`: the address of the function that we want to call when the interrupt will be triggered
87 | - `uint16_t segment`: the segment selector of the code segment that we want to call (0x08 for kernel code segment by default)
88 | - `uint8_t flags`: the flags that we want to set for this entry (you can use the `IDT_FLAGS` enum to set the flags)
89 | Here is the function prototype:
90 | ```c
91 | void set_idt_gate(uint8_t index, void *base, uint16_t segment, uint8_t flags);
92 | ```
93 |
94 | ### set_gate_flag
95 |
96 | The set_gate_flag function is setting a flag for an entry of our `IDT`. It's taking 2 parameters:
97 | - `uint8_t index`: the index of the entry that we want to set the flag (0 to 255)
98 | - `uint8_t flag`: the flag that we want to set (you can use the `IDT_FLAGS` enum to set the flags)
99 | Here is the function prototype:
100 | ```c
101 | void set_gate_flag(uint8_t index, uint8_t flag);
102 | ```
103 |
104 | ### idt_enable_gates
105 |
106 | This function enables all the interrupts of our `IDT`. It's taking no parameters but it changes all the flags of our `IDT` entries. Here is the function prototype:
107 | ```c
108 | void idt_enable_gates();
109 | ```
110 |
111 | ### unset_gate_flag
112 |
113 | This function unsets a flag for an entry of our `IDT`. It's taking 2 parameters:
114 | - `uint8_t index`: the index of the entry that we want to unset the flag (0 to 255)
115 | - `uint8_t flag`: the flag that we want to unset (you can use the `IDT_FLAGS` enum to set the flags)
116 | Here is the function prototype:
117 | ```c
118 | void unset_gate_flag(uint8_t index, uint8_t flag);
119 | ```
120 |
121 | ### lidt
122 |
123 | This function is a wrapper for the `lidt` instruction. The `lidt` instruction doesn't have an equivalent function in C. So we are using this function to load our `IDT` descriptor.
124 | Here is the function prototype:
125 | ```c
126 | void lidt();
127 | ```
--------------------------------------------------------------------------------
/doc/code/interrupts/interrupts.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | The interrupts are the main parts of the communication between the CPU and the kernel. But what are they? How do they work? How can we use them? This document will try to answer these questions.
4 |
5 | What are interrupts ?
6 | When you are doing something wrong like dividing by zero, the CPU will send an interrupt to the kernel. The kernel will be processed in priority order. This is the main usage of the interrupts, but you can also program your own interrupts. `ISR` (Interrupts Sub Routine) is the interrupts used by the CPU. `IRQ` (Interrupt Request) is the interrupts used by the hardware (programmable interrupts). `ISR` and `IRQ` are managed by the `IDT` (Interrupt Descriptor Table). It's two names for the same thing.
7 |
8 | How do they work ?
9 | In PM there are `256` possibles interrupts. The first `32` are reserved for the CPU (to handle basic things as the division by zero). The other `224` are free to use. We cannot program the behaving of the interrupts but we can define `callbacks`, a callback is a function that will be called when the interrupt is triggered. With this, when the interrupt x is triggered, the callback x will be called.
10 |
11 | How can we use them ?
12 | To use the interrupts, you need to define a datastructure named `IDT`, it's an array of entries describing each 256 possible entries. Each entry is composed of a pointer to the callback and some flags. When your `IDT` is ready, you need to load it with the `lidt` instruction. After that, you can enable the interrupts with the `sti` instruction. Now, when an interrupt is triggered, the callback will be called.
13 |
14 | # TABLE OF CONTENTS
15 |
16 | - [INTRODUCTION](#introduction)
17 | - [IMPLEMENTATION](#implementation)
18 | - [FILES](#files)
19 |
20 | # IMPLEMENTATION
21 |
22 | To be easier to understand we are implementing the `IDT` in C. Like I said previously, the IDT is just a table of entries. An entry in the `IDT` is shaped like this:
23 | - `base_low`: The lower 16 bits of the address to jump to when this interrupt fires. (2 bytes)
24 | - `selector`: Kernel segment selector. (2 bytes)
25 | - `zero`: This must always be zero. (1 byte)
26 | - `flags`: More information about the entry. (1 byte)
27 | - `base_high`: The upper 16 bits of the address to jump to. (2 bytes)
28 |
29 | In total an entry is 8 bytes. The C implementation can be this (Actually defined [here](../../../src/interrupts/idt.h)):
30 | ```c
31 | typedef struct idt_entry_s {
32 | uint16_t base_low;
33 | uint16_t selector;
34 | uint8_t zero;
35 | uint8_t flags;
36 | uint16_t base_high;
37 | } __attribute__((packed)) idt_entry_t;
38 | ```
39 | If you don't know what `__attribute__((packed))` does, it's just to tell the compiler to not add padding between the fields of the structure. It's really useful here because we want to have a structure of 8 bytes and not more. More information about this can be found [here](https://gcc.gnu.org/onlinedocs/gcc-4.8.1/gcc/Type-Attributes.html).
40 |
41 | the `idt_entry_t` is just a structure of one entry, but we need a table of entries. It also must be used by many functions (for example to set an entry, a flag, remove an entry, etc...). So we need to make it global. In the file [idt.c](../../../src/interrupts/idt.c) we can see this:
42 | ```c
43 | static idt_entry_t idt_entries[MAX_IDT_ENTRIES];
44 | ```
45 | We are creating a global named `idt_entries` that is an array of `idt_entry_t` of size `MAX_IDT_ENTRIES`. `MAX_IDT_ENTRIES` is defined in the file [idt.h](../../../src/interrupts/idt.h) and is equal to `256`.
46 | ```c
47 | #define MAX_IDT_ENTRIES 256
48 | ```
49 | We are using the `static` keyword to make it private to the file. It's not really necessary but it's a good practice to do it.
50 |
51 | Also like the `GDT`, we need a structure to describe the `IDT`, it must contains:
52 | - `limit`: The size of the `IDT` minus one. (2 bytes)
53 | - `base`: The address of the first element of the `IDT`. (4 bytes)
54 |
55 | This descriptor must be passed to the `lidt` instruction. In C, the implementation can be this:
56 | ```c
57 | typedef struct idt_descriptor_s {
58 | uint16_t limit;
59 | idt_entry_t *idt_start;
60 | } __attribute__((packed)) idt_descriptor_t;
61 | ```
62 | The `idt_entry_t *idt_start` is a pointer to the `IDT`, a pointer is an address of 4 bytes. We can change the type of the pointer to `uint32_t` if we want to be more precise, but it's not really necessary and it's more understandable like this.
63 |
64 | Once we have our `IDT` we can create functions to set an entry, remove an entry, set a flag, etc... In the file [idt.c](../../../src/interrupts/idt.c). All the documentation can be found in the file [idt.md](idt.md)
65 |
66 | Once we had set the shape of our `IDT`, we can fill it with our callbacks, the definitions of the callbacks can be found in the file [interrupts.asm](../../../src/interrupts/interrupts.asm). The documentation can be found in the file [interrupts.md](interrupts.md). Those callbacks are given to the `IDT` in the file [isr.c](../../../src/interrupts/isr.c). The documentation can be found in the file [isr.md](isr.md).
67 |
68 | # FILES
69 |
70 | - [idt.c](../../../src/interrupts/idt.c): It's describing the `IDT` and it's providing functions to manipulate it.
71 | - [isr.c](../../../src/interrupts/isr.c): This is the file that contains all the initialization of our `IDT`. So it will call the functions to set the entries, set the flags, etc... (It's called `ISR` but in fact it contains all the interrupts, not only the `ISR`).
72 | - [interrupts.asm](../../../src/interrupts/interrupts.asm): This file contains all the callbacks that will be called when an interrupt is triggered. It must be in assembly because we have data that are not accessible directly in C, like the code errors or the interrupt number.
73 |
--------------------------------------------------------------------------------
/doc/code/interrupts/interrupts_asm.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This document describes all the theory and code in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file.
4 |
5 | This file is handling all `ISR` callback declarations. Like explained [here](interrupts.md) each `ISR` needs a callback. When the CPU is triggering an interrupt, it will call push information in some asm register and then call a callback. To make things easier, we are first declaring the callback in assembler to make sure we have all the data provided by the CPU. Then we are pushing the data in the stack before calling C functions.
6 |
7 | To declare all the callbacks we are using a label and we are using the keyword `global` to make sure the C code can access it. (In the C code, it can be accessed with the `extern` keyword).
8 | We must define 256 callbacks, and many of them are identical. So to make things easier, we are using a macro to define all the callbacks.
9 |
10 | Each callback is basically doing the same things:
11 | - clearing the interrupts (with the `cli` instruction)
12 | - pushing the data in the stack
13 | - calling the label `isr_common_stub` (for `ISR`) or `irq_common_stub` (for `IRQ`)
14 |
15 | The `isr_common_stub` and `irq_common_stub` are defined in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file. These functions are called by all the callbacks that we have defined. They are doing they are doing the transition between the assembler code and the C code. It's pushing the datas in the stack and then calling the C function.
16 |
17 | # TABLE OF CONTENTS
18 |
19 | - [INTRODUCTION](#introduction)
20 | - [CODE EXPLAINATION](#code-explaination)
21 | - [ISR CALLBACKS](#isr-callbacks)
22 | - [IRQ CALLBACKS](#irq-callbacks)
23 | - [COMMON STUBS](#common-stubs)
24 |
25 |
26 | # CODE EXPLAINATION
27 |
28 | We need to set 256 `ISR` and 16 `IRQ` callbacks. Barely all of them are identical, the only difference is the number of the interrupt. So we are using a macro to define all the callbacks.
29 |
30 | ## ISR CALLBACKS
31 |
32 | Here is the macro that we are using to define all the `ISR` callbacks:
33 | ```nasm
34 | %macro ISR 1
35 | global isr%1
36 |
37 | isr%1:
38 | cli
39 | push 0
40 | push %1
41 | jmp isr_common_stub
42 | %endmacro
43 | ```
44 | with the keyword `%macro` we are defining a macro, and with the keyword `%endmacro` we are closing the macro. This macro is named `ISR` and takes `1` parameter. This parameter is the number of the interrupt. We can use this parameter with the keyword `%1`.
45 | So, we are declaring a global label named `isr%1` (with the `%1` we are using the parameter of the macro). Then we are clearing the interrupts, pushing `0` and the number of the interrupt in the stack. Finally we are calling the `isr_common_stub` function. It's pushing 0 because the CPU push the error code in the stack only for some interrupts.
46 |
47 | Also, specific `ISR` push some data in the stack (error code). So we are using a specific macro to define these callbacks:
48 | ```nasm
49 | %macro ISR_ERROR 1
50 | global isr%1
51 |
52 | isr%1:
53 | cli
54 | push %1
55 | jmp isr_common_stub
56 | %endmacro
57 | ```
58 | Globally, it's the same as the previous macro, but we are not pushing `0` in the stack. Because the CPU is pushing the error code in the stack.
59 |
60 | ## IRQ CALLBACKS
61 |
62 | The difference between the `ISR` and the `IRQ` is that the `IRQ` are not triggered by the CPU. They are triggered by the PIC. So we are using a macro to define all the `IRQ` callbacks:
63 | ```nasm
64 | %macro IRQ 2
65 | global irq%1
66 |
67 | irq%1:
68 | cli
69 | push %1; dummy error
70 | push %2 ; interrupt number
71 | jmp irq_common_stub
72 | %endmacro
73 | ```
74 | It's a macro taking 2 parameters. The first one is the number of the interrupt, and the second one is the number of the IRQ. We are using the second one to know which PIC is triggering the interrupt. We are using this information to send an `EOI` to the PIC (End Of Interrupt).
75 |
76 | ## COMMON STUBS
77 |
78 | A common stub is a function that is called by all the callbacks. It's doing the transition between the assembler code and the C code. It's pushing the data in the stack and then calling the C function.
79 |
80 | The `isr_common_stub` and the `irq_common_stub` are the same function. The only difference is the name of the function that is called. We are not using macro for these functions because it's easier when you want to add some code in the function.
81 |
82 | ```nasm
83 | isr_common_stub: ; or irq_common_stub
84 | pusha
85 |
86 | xor eax, eax
87 | mov ax, ds
88 | push eax
89 |
90 | mov ax, 0x10
91 | mov ds, ax
92 | mov es, ax
93 | mov fs, ax
94 | mov gs, ax
95 |
96 | push esp
97 | call isr_handler ; or irq_handler
98 | add esp, 4
99 |
100 | pop eax
101 | mov ds, ax
102 | mov es, ax
103 | mov fs, ax
104 | mov gs, ax
105 |
106 | popa
107 | add esp, 8
108 | sti
109 | iret
110 | ```
111 | Here is all the things done by the common stub:
112 | - push all the registers in the stack (with the `pusha` instruction). Pushing into the stack permits to save the registers values and to use them in the C code.
113 | - saving the data segment register (`ds`) in the stack. We will pop it later, it's avoiding C code to modify our data segment register.
114 | - Initializing all the data segment registers (`ds`, `es`, `fs`, `gs`) with the data segment register in our `GDT` (0x10). This is to make sure that the C code can access the data segment register.
115 | - push the stack pointer in the stack, it's very useful because it's a pointer to all the registers that we pushed before.
116 | - call the C function that will manage all the interrupts (`ISR` or `IRQ` are handle in different functions).
117 | - add 4 to the stack pointer, it's because we pushed the stack pointer in the stack before and we want to remove it.
118 | - pop the data segment register that we saved before.
119 | - reset all the data segment registers (`ds`, `es`, `fs`, `gs`) with the data segment register that we saved before.
120 | - pop all the registers in the stack (with the `popa` instruction). Popping from the stack permits to restore the registers values.
121 | - add 8 to the stack pointer, it's because we pushed 2 values in the stack before (the dummy error and the interrupt number).
122 | - enable the interrupts (with the `sti` instruction).
123 | - return from the interrupt (with the `iret` instruction).
--------------------------------------------------------------------------------
/doc/code/interrupts/isr.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This document explains the [isr.c](../../../src/interrupts/isr.c) file.
4 | This file is currently initializing and filling the `IDT` with the callbacks defined in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file (doc [here](interrupts_asm.md)). The meaning of this file is to setup all the information and routines to handle interrupts and keyboard inputs.
5 |
6 | The code in this file is handling a function `isr_init` that do the following things:
7 | - fill the `IDT` with the callbacks defined in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file
8 | - remap the PIC (Programmable Interrupt Controller), see [pic.md](pic.md) for more information
9 | - enable all the interrupts in the `IDT`
10 | - unset the syscall interrupt
11 |
12 | There is also an array of function pointers named `isr_handler` that will contains all the functions that we want to call when an interrupt is triggered. For example, if we want to call a function when the keyboard is pressed, we will set the function pointer at the index `33` (the index of the keyboard interrupt).
13 |
14 | # TABLE OF CONTENTS
15 |
16 | - [INTRODUCTION](#introduction)
17 | - [HEADER FILES](#header-files)
18 | - [CODE EXPLAINATION](#code-explaination)
19 |
20 | # HEADER FILES
21 |
22 | The header file [isr.h](../../../src/interrupts/isr.h) contains interesting datas.
23 | The first thing that you can see is the macro
24 | ```c
25 | #define KERNEL_CODE_SEG 0x08
26 | ```
27 | This macro defines the code segment offset of the `GDT`. (see [gdt.md](../boot/gdt.md) for more information).
28 |
29 | There is many cursed lines in the header file, they are all shaped like this:
30 | ```c
31 | extern void isr0();
32 | extern void isr1();
33 | // ...
34 | extern void isr255();
35 | extern void irq0();
36 | extern void irq1();
37 | // ...
38 | extern void irq15();
39 | ```
40 | all those lines are basic function prototypes, but they have a special keyword `extern`. This keyword is used to tell the compiler that the function is defined somewhere else. In this case, the functions are defined in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file (doc [here](interrupts_asm.md)).
41 | All the functions will fill the `IDT`
42 |
43 | After all the function prototypes, there is a structure named registers_t, they will contains all the registers of the CPU that we pushed in the file [interrupts.asm](../../../src/interrupts/interrupts.asm) with the instruction `lidt` (doc [here](interrupts_asm.md)).
44 | The structure that will handle all the registers is the following:
45 | ```c
46 | typedef struct registers_s {
47 | uint32_t ds;
48 | uint32_t edi, esi, ebp, useless, ebx, edx, ecx, eax;
49 | uint32_t interrupt, error;
50 | uint32_t eip, cs, eflags, esp, ss;
51 | } __attribute__((packed)) registers_t;
52 | ```
53 |
54 | The last thing that we can see in the header file is the following typedef:
55 | ```c
56 | typedef void (*isr_callback)(registers_t *regs);
57 | ```
58 | It's creating a type named isr_callback that is a pointer to a function that takes a pointer to a registers_t structure as parameter and returns nothing.
59 |
60 | # CODE EXPLAINATION
61 |
62 | We saw the header file with all the declarations, now we will see the code of the file [isr.c](../../../src/interrupts/isr.c).
63 |
64 | The first line that we can see is the following:
65 | ```c
66 | static isr_callback isr_handlers[MAX_IDT_ENTRIES];
67 | ```
68 | This is a global variable that has the keyword `static` (see [here](https://www.geeksforgeeks.org/static-keyword-c/)).
69 | This variable is handling all the functions that we want to call when an interrupt is triggered. When an interrupts is called, all the functions named `isr` or `irq` (defined in [interrupts.asm](../../../src/interrupts/interrupts.asm) (doc [here](interrupts_asm.md))) will call the function `isr_handler` (or `irq_handler`) with the index of the interrupt as parameter. And the handler will call the function at the index of the interrupt in the `isr_handlers` array.
70 | The `MAX_IDT_ENTRIES` is defined in the header file [isr.h](../../../src/interrupts/isr.h) and is equal to `256` that is the maximum number of interrupts that we can handle.
71 |
72 | The first function that we see is:
73 | ```c
74 | static void isr_init_idt_gates();
75 | ```
76 | This function is filling all the 256 entries of the `IDT` with the callbacks defined in the [interrupts.asm](../../../src/interrupts/interrupts.asm) file that we externalized in the [isr.h](../../../src/interrupts/isr.h) (doc [here](interrupts_asm.md)).
77 | This is typically what we can see in the function:
78 | ```c
79 | // ...
80 | set_idt_gate(34, irq2, KERNEL_CODE_SEG, IDT_FLAG_RING0 | IDT_FLAG_GATE_32BIT_INT);
81 | set_idt_gate(35, irq3, KERNEL_CODE_SEG, IDT_FLAG_RING0 | IDT_FLAG_GATE_32BIT_INT);
82 | // ...
83 | set_idt_gate(56, isr56, KERNEL_CODE_SEG, IDT_FLAG_RING0 | IDT_FLAG_GATE_32BIT_INT);
84 | set_idt_gate(57, isr57, KERNEL_CODE_SEG, IDT_FLAG_RING0 | IDT_FLAG_GATE_32BIT_INT);
85 | // ...
86 | ```
87 |
88 | Next we have the function `isr_init`
89 | ```c
90 | void isr_init()
91 | {
92 | isr_init_idt_gates();
93 | pic_remap();
94 | idt_enable_gates();
95 |
96 | unset_gate_flag(0x80, IDT_FLAG_PRESENT);
97 | }
98 | ```
99 | it's doing the following things:
100 | - call the function `isr_init_idt_gates` to fill the `IDT`
101 | - remap the PIC (Programmable Interrupt Controller), see [pic.md](pic.md) for more information
102 | - enable all the interrupts in the `IDT`
103 | - unset the syscall interrupt, We are unsetting the syscall interrupt because we do not have syscall implemented yet, so we don't want to trigger an interrupt when the syscall is called.
104 |
105 | The next function is the `isr_handler` it's a function that is called by the `isr_common_stub` function. Here is the implementation:
106 | ```c
107 | void isr_handler(registers_t *regs)
108 | {
109 | uint32_t interrupt = regs->interrupt;
110 |
111 | if (isr_handlers[interrupt] != 0) {
112 | isr_handlers[interrupt](regs);
113 | }
114 | }
115 | ```
116 | it's just checking is an handler is set in the global `isr_handlers`, and if it is, it's calling it.
117 |
118 | The next function is just a setter for the `isr_handlers` array:
119 | ```c
120 | void isr_register_handler(int interrupt, isr_callback callback)
121 | {
122 | isr_handlers[interrupt] = callback;
123 | set_gate_flag(interrupt, IDT_FLAG_PRESENT);
124 | }
125 | ```
126 |
127 | And the last one is a bit more complicated, it's the `irq_handler` function:
128 | ```c
129 | void irq_handler(registers_t *regs)
130 | {
131 | if (regs->interrupt >= 40) {
132 | port_byte_out(0xa0, 0x20);
133 | }
134 | port_byte_out(0x20, 0x20);
135 | if (isr_handlers[regs->interrupt] != 0) {
136 | isr_handlers[regs->interrupt](regs);
137 | }
138 | }
139 | ```
140 | This function is called by the `irq_common_stub` function. It's doing the following things:
141 | - if the interrupt is greater than 40, it's sending an EOI (End Of Interrupt) to the slave PIC (Programmable Interrupt Controller), see [pic.md](pic.md) for more information
142 | - it's sending an EOI to the master PIC (Programmable Interrupt Controller), see [pic.md](pic.md) for more information
143 | - it's calling the handler if it's set
144 |
--------------------------------------------------------------------------------
/doc/code/interrupts/pic.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This document describes the code in the [pic.c](../../../src/interrupts/pic.c) file.
4 | In this file you will have the pic remaping. They are existing questions about the remaping of the pic. So I will try to explain it here.
5 |
6 | What is the PIC ?
7 | The PIC (or 8259_PIC) is one of the most important chips making up the x86 architecture. It holds communication between hardware and software with a thing that we call bus, a bus is just a collection of memory adresses we can read or write in. Due to growing complexity during the time, we need more than one PIC to hold all our hardware devices. We call the addresses of the PICs "ports"
8 |
9 | Why do we need to remap it ?
10 | We need to remap the PIC because in the past, the PIC was designed to be used with a 16-bit architecture. Things evolved and we now have a 32-bit architecture. So we need to remap the PIC to avoid conflicts with the memory adresses.
11 |
12 | Why was it not fixed with the time ?
13 | Because of the backward compatibility, it's important to keep the old architecture working to avoid breaking old software. So we need to keep this mistake.
14 |
15 | # TABLE OF CONTENTS
16 |
17 | - [INTRODUCTION](#introduction)
18 | - [CODE EXPLANATION](#code-explanation)
19 |
20 | # CODE EXPLANATION
21 |
22 | In this file you will find only one function named `pic_remap`, it will do all the remap routine before loading the `IDT`.
23 | ```c
24 | void pic_remap()
25 | {
26 | port_byte_out(0x20, 0x11);
27 | port_byte_out(0xA0, 0x11);
28 | port_byte_out(0x21, 0x20);
29 | port_byte_out(0xA1, 0x28);
30 | port_byte_out(0x21, 0x04);
31 | port_byte_out(0xA1, 0x02);
32 | port_byte_out(0x21, 0x01);
33 | port_byte_out(0xA1, 0x01);
34 | port_byte_out(0x21, 0x0);
35 | port_byte_out(0xA1, 0x0);
36 | }
37 | ```
38 | You can see that we are using the `port_byte_out` function to write in the ports. The first argument is the port number and the second is the value we want to write in the port. You can see more details in the [port.c](../../../src/interrupts/pic.c) file see the [doc](port.md) for more details.
39 |
40 |
--------------------------------------------------------------------------------
/doc/code/interrupts/port.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This file describes the code in the [port.c](../../../src/interrupts/port.c) file.
4 | You can see in this file two functions that communicate with the PIC.
5 | See the `PIC` [doc](pic.md) for more details.
6 | We have two functions, one to write in a port and one to read from a port. Both of them are using the `out` and `in` asm instructions. So we need to call in C the `asm` function to use them (If you want more about the `asm` function, see the this link: [asm](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html)).
7 |
8 | Here is the reading function:
9 | ```c
10 | unsigned char port_byte_in(unsigned short port)
11 | {
12 | unsigned char result = 0;
13 |
14 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
15 | return (result);
16 | }
17 | ```
18 | It launches the `in` instruction with the `port` as the first argument and the `result` as the second argument. The `result` is the value we want to read from the port.
19 |
20 | The writing function is similar:
21 | ```c
22 | void port_byte_out(unsigned short port, unsigned char data)
23 | {
24 | __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
25 | }
26 | ```
27 | It launches the `out` instruction with the `data` as the first argument and the `port` as the second argument. The `data` is the value we want to write in the port.
28 |
29 | If you do not understand all this asm bullshit (it's normal), please see the `asm` keyword documentation: [asm](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html)
--------------------------------------------------------------------------------
/doc/code/interrupts/timer.md:
--------------------------------------------------------------------------------
1 | # INTRODUCTION
2 |
3 | This file explains the code in the [timer.c](../../../src/interrupts/timer.c) file.
4 | The timer is a hardware device that sends an interrupt to the CPU at a regular interval. It's used to schedule tasks and to create a time reference. It can be usefull for us to create a time reference for things like a sleep function. or to schedule tasks in general.
5 |
6 | # TABLE OF CONTENTS
7 |
8 | - [INTRODUCTION](#introduction)
9 | - [CODE EXPLANATION](#code-explanation)
10 |
11 | # CODE EXPLANATION
12 |
13 | This file for now only contains this:
14 | ```c
15 | static unsigned int tick = 0;
16 |
17 | static void timer_callback(__attribute__((unused))registers_t *regs)
18 | {
19 | tick++;
20 | }
21 |
22 | void init_timer(unsigned int freq)
23 | {
24 | isr_register_handler(32, timer_callback);
25 |
26 | uint32_t divisor = 1193180 / freq;
27 | uint8_t low = (uint8_t)(divisor & 0xff);
28 | uint8_t high = (uint8_t)((divisor >> 8) & 0xff);
29 |
30 | port_byte_out(0x43, 0x36);
31 | port_byte_out(0x40, low);
32 | port_byte_out(0x40, high);
33 | }
34 | ```
35 | We have two functions:
36 | - One for the initialization of the timer
37 | - One for the callback of the timer
38 |
39 | The timer is called each time by the interrupts `32`. We can set a frequency for the timer. The frequency is the number of times the timer is called per second. The higher the frequency, the more precise the time reference will be. But the higher the frequency, the more CPU time it will take to call the timer. So we have to find a balance between the two.
40 | To set the frequency we have to set the divisor of the timer. The divisor is the number of ticks per second. The higher the divisor, the lower the frequency. The lower the divisor, the higher the frequency. The formula to calculate the divisor is `1193180 / freq`. `1193180` is the base frequency of the timer. `freq` is the frequency we want to set. The result of the formula is the divisor. We have to split the divisor in two parts. The lower part and the higher part. The lower part is the first 8 bits of the divisor. The higher part is the last 8 bits of the divisor. We have to send the lower part to the port `0x40` and the higher part to the port `0x40` too. But before sending the lower part we have to send the command `0x36` to the port `0x43`. This command is used to set the divisor of the timer. The lower part is sent first and then the higher part. The order is important.
41 |
42 | The `timer_callback` function is just a function that increments the static `tick` variable. This variable is used to create a time reference. We can use this variable to create a sleep function for example.
43 |
--------------------------------------------------------------------------------
/doc/code/memory/segmentation.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/doc/code/memory/segmentation.md
--------------------------------------------------------------------------------
/doc/img/hard_disk_structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/doc/img/hard_disk_structure.png
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM i686/ubuntu:latest
2 |
3 | # Update from apt sources files and upgrade packages
4 | # + download needed dependencies
5 | RUN apt-get update
6 | RUN apt-get install -y make \
7 | wget \
8 | unzip \
9 | nasm \
10 | lbzip2 \
11 | grub-common \
12 | qemu
13 |
14 | # Re-used environment variables
15 | ENV DIR="/osdev"
16 | ENV I686="i686-elf-tools-linux"
17 | ENV I686OUT="/opt/${I686}"
18 |
19 | # Working directory of the container
20 | WORKDIR ${DIR}
21 |
22 | # Install the i686 toolchain
23 |
24 | # Download pre-built i686 binaries (GCC + linker)
25 | RUN wget -P /tmp "https://github.com/lordmilko/i686-elf-tools/releases/download/7.1.0/${I686}.zip"
26 | # Extract the files to the i686 tool directory
27 | RUN unzip -d "${I686OUT}" "/tmp/${I686}"
28 |
29 | # Create an user (avoid root)
30 | RUN useradd -ms /bin/bash theo
31 | USER theo
32 |
33 | # Update PATH
34 | ENV PATH="${PATH}:${I686OUT}/bin"
35 |
36 | ENTRYPOINT ["/bin/bash"]
37 |
38 | # Usage example:
39 | #
40 | # docker build -t osdev .
41 | #
42 | # docker run -it -e DISPLAY=$DISPLAY \
43 | # -v /tmp/.X11-unix:/tmp/.X11-unix \
44 | # -v $PWD:/osdev osdev
45 | #
46 | # In this case $PWD point to the project root folder
47 | #
48 | # xhost manages X server control, it filters the authorized clients
49 | # perms: family:user
50 | # xhost +si:localuser:root
51 | # ^ allow a single loacl user root
52 |
--------------------------------------------------------------------------------
/iso/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epi-osdev/workshop-student/50ebc664c652d10992c75ef0d35312b767a8cce7/iso/.gitkeep
--------------------------------------------------------------------------------
/src/boot_sector/boot_sector.asm:
--------------------------------------------------------------------------------
1 | [org 0x7c00]
2 |
3 | [bits 16]
4 | start:
5 | mov ax, 0x00
6 | mov ds, ax
7 | mov es, ax
8 | mov ss, ax
9 | mov bp, 0x7c00
10 | mov sp, bp
11 |
12 | [bits 16]
13 | run_16:
14 | mov [BOOT_DRIVE], dl
15 | call load_kernel
16 | call switch_to_pm
17 |
18 | %include "src/boot_sector/disk.asm"
19 | %include "src/boot_sector/switch_pm.asm"
20 | %include "src/boot_sector/gdt.asm"
21 |
22 | [bits 16]
23 | load_kernel:
24 | mov bx, KERNEL_OFFSET
25 | mov dh, 31
26 | mov dl, [BOOT_DRIVE]
27 | call disk_load
28 | ret
29 |
30 | [bits 32]
31 | entry_point:
32 | call KERNEL_OFFSET
33 | jmp $
34 |
35 | BOOT_DRIVE db 0
36 | KERNEL_OFFSET equ 0x1000
37 |
38 | times 510 - ($-$$) db 0
39 | dw 0xaa55
40 |
--------------------------------------------------------------------------------
/src/boot_sector/disk.asm:
--------------------------------------------------------------------------------
1 | ; load DH sectors to ES:BX from drive DL
2 | ; parameters:
3 | ; DL = drive number
4 | ; ES:BX = buffer to load sectors to
5 | ; DH = number of sectors
6 |
7 | disk_load:
8 | push dx
9 | mov ah, 0x02
10 | mov al, dh
11 | mov ch, 0x00
12 | mov dh, 0x00
13 | mov cl, 0x02
14 | int 0x13
15 | jc disk_error
16 | pop dx
17 | cmp dh, al
18 | jne disk_error
19 | ret
20 |
21 | disk_error:
22 | jmp $
23 |
--------------------------------------------------------------------------------
/src/boot_sector/gdt.asm:
--------------------------------------------------------------------------------
1 | ; Offset for the segment descriptor
2 | ; It doesnt point on the segment itself
3 | CODE_SEG equ gdt_code - gdt_start
4 | DATA_SEG equ gdt_data - gdt_start
5 |
6 | gdt_start:
7 |
8 | gdt_null:
9 | dd 0x0
10 | dd 0x0
11 | ; offset 0x8
12 | gdt_code: ; CS SHOULD POINT TO THIS
13 | dw 0xffff ; Segment limit first 0-15 bits
14 | dw 0 ; Base first 0-15 bits
15 | db 0 ; Base 16-23 bits
16 | db 0x9a ; Access byte
17 | db 11001111b ; High 4 bit flags and the low 4 bit flags
18 | db 0 ; Base 24-31 bits
19 |
20 | ; offset 0x10
21 | gdt_data: ; DS, SS, ES, FS, GS
22 | dw 0xffff ; Segment limit first 0-15 bits
23 | dw 0 ; Base first 0-15 bits
24 | db 0 ; Base 16-23 bits
25 | db 0x92 ; Access byte
26 | db 11001111b ; High 4 bit flags and the low 4 bit flags
27 | db 0 ; Base 24-31 bits
28 |
29 | gdt_end:
30 |
31 | gdt_descriptor:
32 | dw gdt_end - gdt_start-1
33 | dd gdt_start
34 |
--------------------------------------------------------------------------------
/src/boot_sector/switch_pm.asm:
--------------------------------------------------------------------------------
1 | ; Switching from real mode to protected mode
2 |
3 | [bits 16]
4 | switch_to_pm:
5 | cli
6 | lgdt [gdt_descriptor]
7 | mov eax, cr0
8 | or eax, 0x1
9 | mov cr0, eax
10 | jmp CODE_SEG:init_pm
11 |
12 | [bits 32]
13 | init_pm:
14 | mov ax, DATA_SEG
15 | mov ds, ax
16 | mov ss, ax
17 | mov es, ax
18 | mov fs, ax
19 | mov gs, ax
20 |
21 | mov ebp, 0x90000
22 | mov esp, ebp
23 |
24 | call entry_point
25 |
--------------------------------------------------------------------------------
/src/entry/entry_point.asm:
--------------------------------------------------------------------------------
1 | [bits 32]
2 | [extern kernel_main] ; Define calling point. Must have same name as kernel.c 'main' function
3 | call kernel_main ; Calls the C function. The linker will know where it is placed in memory
4 | jmp $
5 |
--------------------------------------------------------------------------------
/src/entry/kernel_entry.c:
--------------------------------------------------------------------------------
1 | #include "VGA.h"
2 |
3 | void init()
4 | {
5 | vga_clear_screen();
6 | }
7 |
8 | void kernel_main()
9 | {
10 | init();
11 | char str[] = "Hello, World!";
12 | vga_printf_at(str, 0x0F, 0, 0);
13 | }
14 |
--------------------------------------------------------------------------------
/src/interrupts/pic.c:
--------------------------------------------------------------------------------
1 | #include "interrupts/port.h"
2 |
3 | void pic_remap()
4 | {
5 | port_byte_out(0x20, 0x11);
6 | port_byte_out(0xA0, 0x11);
7 | port_byte_out(0x21, 0x20);
8 | port_byte_out(0xA1, 0x28);
9 | port_byte_out(0x21, 0x04);
10 | port_byte_out(0xA1, 0x02);
11 | port_byte_out(0x21, 0x01);
12 | port_byte_out(0xA1, 0x01);
13 | port_byte_out(0x21, 0x0);
14 | port_byte_out(0xA1, 0x0);
15 | }
16 |
--------------------------------------------------------------------------------
/src/interrupts/pic.h:
--------------------------------------------------------------------------------
1 | #ifndef __PIC_H__
2 | #define __PIC_H__
3 |
4 | void pic_remap();
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/src/interrupts/port.c:
--------------------------------------------------------------------------------
1 | #include "port.h"
2 |
3 | /**
4 | * @brief
5 | *
6 | * Cathcing the reponse of the request (port_byte_out)
7 | *
8 | * @param port : the port to read from
9 | * @return unsigned char : the byte read from the port
10 | */
11 | unsigned char port_byte_in(unsigned short port)
12 | {
13 | unsigned char result = 0;
14 |
15 | __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
16 | return (result);
17 | }
18 |
19 | /**
20 | * @brief
21 | *
22 | * Requesting the port with the data
23 | *
24 | * @param port : port number
25 | * @param data : data to write
26 | */
27 | void port_byte_out(unsigned short port, unsigned char data)
28 | {
29 | __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
30 | }
31 |
--------------------------------------------------------------------------------
/src/interrupts/port.h:
--------------------------------------------------------------------------------
1 | #ifndef __PORT_H__
2 | #define __PORT_H__
3 |
4 | unsigned char port_byte_in(unsigned short port);
5 | void port_byte_out(unsigned short port, unsigned char data);
6 |
7 | #endif
8 |
--------------------------------------------------------------------------------
/src/interrupts/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 |
3 | static unsigned int tick = 0;
4 |
5 | static void timer_callback(__attribute__((unused))registers_t *regs)
6 | {
7 | tick++;
8 | }
9 |
10 | void init_timer(unsigned int freq)
11 | {
12 | isr_register_handler(32, timer_callback);
13 |
14 | uint32_t divisor = 1193180 / freq;
15 | uint8_t low = (uint8_t)(divisor & 0xff);
16 | uint8_t high = (uint8_t)((divisor >> 8) & 0xff);
17 |
18 | port_byte_out(0x43, 0x36);
19 | port_byte_out(0x40, low);
20 | port_byte_out(0x40, high);
21 | }
22 |
--------------------------------------------------------------------------------
/src/interrupts/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef __TIMER_H__
2 | #define __TIMER_H__
3 |
4 | #include "interrupts/isr.h"
5 | #include "interrupts/port.h"
6 | #include "utils/types.h"
7 |
8 | void init_timer(uint32_t freq);
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/utils/VGA.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_VGA_H__
2 | #define __EOS_UTILS_VGA_H__
3 |
4 | /**
5 | * @brief This is the global API for the VGA driver.
6 | * all the functions that could be called by other files must be included here.
7 | * And this file must be the only one to be included when using the VGA driver.
8 | */
9 |
10 | #include "VGA/clear.h"
11 | #include "VGA/print.h"
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/src/utils/VGA/VGA.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_VGA_VGA_H__
2 | #define __EOS_UTILS_VGA_VGA_H__
3 |
4 | #include "types.h"
5 |
6 | #define VGA_WIDTH 80
7 | #define VGA_HEIGHT 25
8 | #define VGA_MEMORY ((char *)0xb8000)
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/utils/VGA/clear.c:
--------------------------------------------------------------------------------
1 | #include "clear.h"
2 | #include "VGA.h"
3 |
4 | void vga_clear_screen()
5 | {
6 | for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT * 2; i += 2) {
7 | VGA_MEMORY[i] = '0';
8 | VGA_MEMORY[i + 1] = 0x00;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/VGA/clear.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_VGA_VGA_CLEAR_H__
2 | #define __EOS_UTILS_VGA_VGA_CLEAR_H__
3 |
4 | /**
5 | * @brief clear the entire VGA screen with spaces
6 | *
7 | */
8 | void vga_clear_screen();
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/utils/VGA/print.c:
--------------------------------------------------------------------------------
1 | #include "VGA.h"
2 | #include "print.h"
3 | #include "string.h"
4 | #include
5 |
6 | static size_t get_index(uint8_t x, uint8_t y)
7 | {
8 | return y * 2 * VGA_WIDTH + x * 2;
9 | }
10 |
11 | int vga_putchar_at(char c, uint8_t color, uint8_t x, uint8_t y)
12 | {
13 | if (x >= VGA_WIDTH || y >= VGA_HEIGHT)
14 | return -1;
15 | const size_t index = get_index(x, y);
16 | VGA_MEMORY[index] = c;
17 | VGA_MEMORY[index + 1] = color;
18 | return 1;
19 | }
20 |
21 | int vga_putstr_at(const char *str, uint8_t color, uint8_t x, uint8_t y)
22 | {
23 | size_t size_printed = 0;
24 |
25 | if (x >= VGA_WIDTH || y >= VGA_HEIGHT)
26 | return -1;
27 | for (size_t i = 0; str[i]; ++i, ++size_printed)
28 | vga_putchar_at(str[i], color, x + i, y);
29 | return size_printed;
30 | }
31 |
32 | int vga_print_int_at(int num, uint8_t color, uint8_t x, uint8_t y)
33 | {
34 | char str[16] = {0};
35 |
36 | if (x >= VGA_WIDTH || y >= VGA_HEIGHT)
37 | return -1;
38 | itoa(num, str, 10);
39 | return vga_putstr_at(str, color, x, y);
40 | }
41 |
42 | int vga_printf_at(const char *format, uint8_t color, uint8_t x, uint8_t y, ...)
43 | {
44 | va_list args;
45 | int size_printed = 0;
46 |
47 | if (x >= VGA_WIDTH || y >= VGA_HEIGHT)
48 | return -1;
49 | va_start(args, y);
50 | for (size_t i = 0; format[i]; ++i) {
51 | if (format[i] == '%') {
52 | switch (format[++i]) {
53 | case 'd':
54 | size_printed += vga_print_int_at(va_arg(args, int), color, x + size_printed, y);
55 | break;
56 | case 's':
57 | size_printed += vga_putstr_at(va_arg(args, char *), color, x + size_printed, y);
58 | break;
59 | case 'c':
60 | size_printed += vga_putchar_at(va_arg(args, int), color, x + size_printed, y);
61 | break;
62 | default:
63 | size_printed += vga_putchar_at(format[i], color, x + size_printed, y);
64 | break;
65 | }
66 | } else {
67 | size_printed += vga_putchar_at(format[i], color, x + size_printed, y);
68 | }
69 | }
70 | va_end(args);
71 | return size_printed;
72 | }
73 |
--------------------------------------------------------------------------------
/src/utils/VGA/print.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_VGA_PRINT_H__
2 | #define __EOS_VGA_PRINT_H__
3 |
4 | #include "types.h"
5 |
6 | /**
7 | * @brief Prints a string to the VGA buffer with the given color and position
8 | * @param c: the char to print
9 | * @param color: the color to print the char with
10 | * @param x: the x position to print the char at
11 | * @param y: the y position to print the char at
12 | * @return 1 if success
13 | * @return -1 if position error
14 | */
15 | int vga_putchar_at(char c, uint8_t color, uint8_t x, uint8_t y);
16 |
17 | /**
18 | * @brief Prints a string to the VGA buffer with the given colors at the given position
19 | * @param str: the string to print
20 | * @param color: the color to print the string with
21 | * @param x: the x position to print the string at
22 | * @param y: the y position to print the string at
23 | * @return length of the string printed if success
24 | * @return -1 if position error
25 | */
26 | int vga_putstr_at(const char *str, uint8_t color, uint8_t x, uint8_t y);
27 |
28 | /**
29 | * @brief Prints a number to the VGA buffer with the given colors at the given position
30 | * @param num: the number to print
31 | * @param color: the color to print the number with
32 | * @param x: the x position to print the number at
33 | * @param y: the y position to print the number at
34 | * @return length of the number printed if success
35 | * @return -1 if position error
36 | */
37 | int vga_print_int_at(int num, uint8_t color, uint8_t x, uint8_t y);
38 |
39 | /**
40 | * @brief Prints a formatted string to the VGA buffer with the given colors at the given position
41 | * @param fmt: the formatted string to print
42 | * @param color: the color to print the formatted string with
43 | * @param x: the x position to print the formatted string at
44 | * @param y: the y position to print the formatted string at
45 | * @param ...: the arguments to the formatted string
46 | * @return length of the formatted string printed if success
47 | * @return -1 if position error
48 | */
49 | int vga_printf_at(const char *format, uint8_t color, uint8_t x, uint8_t y, ...);
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/src/utils/string.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_STRING_H__
2 | #define __EOS_UTILS_STRING_H__
3 |
4 | /**
5 | * @brief This is the global API for the string utils.
6 | * all the functions that could be called by other files must be included here.
7 | * And this file must be the only one to be included when using the string utils.
8 | */
9 |
10 | #include "string/itoa.h"
11 | #include "string/revstr.h"
12 | #include "string/strlen.h"
13 |
14 | #endif
--------------------------------------------------------------------------------
/src/utils/string/itoa.c:
--------------------------------------------------------------------------------
1 | #include "itoa.h"
2 | #include "revstr.h"
3 |
4 | void itoa(int num, char *str, uint8_t base)
5 | {
6 | int i = 0;
7 | int is_negative = 0;
8 |
9 | if (num == 0) {
10 | str[i++] = '0';
11 | str[i] = '\0';
12 | return;
13 | }
14 | if (num < 0 && base == 10) {
15 | is_negative = 1;
16 | num = -num;
17 | }
18 | while (num != 0) {
19 | int rem = num % base;
20 | str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0';
21 | num /= base;
22 | }
23 | if (is_negative) {
24 | str[i++] = '-';
25 | }
26 | str[i] = '\0';
27 | revstr(str);
28 | }
--------------------------------------------------------------------------------
/src/utils/string/itoa.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_STRING_ITOA_H__
2 | #define __EOS_UTILS_STRING_ITOA_H__
3 |
4 | #include "types.h"
5 |
6 | /**
7 | * @brief This function converts an integer to a string and stores it in a buffer.
8 | * @param num The number to be converted.
9 | * @param str The buffer to store the string.
10 | * @param base The base of the number.
11 | */
12 | void itoa(int num, char *str, uint8_t base);
13 |
14 | #endif
--------------------------------------------------------------------------------
/src/utils/string/revstr.c:
--------------------------------------------------------------------------------
1 | #include "revstr.h"
2 |
3 | void revstr(char *str)
4 | {
5 | char *end = str;
6 | char tmp;
7 |
8 | if (str) {
9 | while (*end) {
10 | ++end;
11 | }
12 | --end;
13 | while (str < end) {
14 | tmp = *str;
15 | *str++ = *end;
16 | *end-- = tmp;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/string/revstr.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_STRING_REVSTR_H__
2 | #define __EOS_UTILS_STRING_REVSTR_H__
3 |
4 | /**
5 | * @brief This function reverses a string itself.
6 | * @param str The string to be reversed.
7 | */
8 | void revstr(char *str);
9 |
10 | #endif
--------------------------------------------------------------------------------
/src/utils/string/strlen.c:
--------------------------------------------------------------------------------
1 | #include "strlen.h"
2 |
3 | size_t strlen(const char *str)
4 | {
5 | if (str == NULL) {
6 | return 0;
7 | }
8 | size_t size;
9 | for (size = 0; str[size] != '\0'; size++);
10 | return size;
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/string/strlen.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_STRING_STRLEN_H__
2 | #define __EOS_UTILS_STRING_STRLEN_H__
3 |
4 | #include "types.h"
5 |
6 | /*
7 | * @brief: a function to get the lenght of a string
8 | * @param str: the string that will recover the size
9 | * @return: the value of the lenght of the string
10 | */
11 | size_t strlen(const char *str);
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/src/utils/types.h:
--------------------------------------------------------------------------------
1 | #ifndef __EOS_UTILS_TYPES_H__
2 | #define __EOS_UTILS_TYPES_H__
3 |
4 | typedef unsigned char uint8_t;
5 | typedef unsigned short uint16_t;
6 | typedef unsigned int uint32_t;
7 | typedef uint32_t size_t;
8 | #define NULL 0
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------