Skip to content

Commit

Permalink
Interrupt Descriptor Table
Browse files Browse the repository at this point in the history
  • Loading branch information
taikiy committed Feb 23, 2023
1 parent 269ab13 commit e6abc5c
Show file tree
Hide file tree
Showing 17 changed files with 238 additions and 23 deletions.
25 changes: 25 additions & 0 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"configurations": [
{
"name": "Mac",
"includePath": ["${workspaceFolder}/**"],
"defines": [],
"compilerPath": "/opt/homebrew/bin/i686-elf-gcc",
"compilerArgs": [
"-ffreestanding",
"-nostdlib",
"-nostartfiles",
"-nodefaultlibs",
"-Iinc"
],
"cStandard": "gnu99",
"intelliSenseMode": "macos-gcc-x86",
"configurationProvider": "ms-vscode.makefile-tools",
"browse": {
"path": ["${workspaceFolder}"],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FILES = ./build/kernel.asm.o ./build/kernel.o
FILES = ./build/kernel.asm.o ./build/kernel.o ./build/idt/idt.asm.o ./build/idt/idt.o ./build/memory/memory.o
INCLUDES = -I./src
FLAGS = -g -ffreestanding -falign-jumps -falign-functions -falign-labels -falign-loops -fstrength-reduce -fomit-frame-pointer -finline-functions -Wno-unused-function -fno-builtin -Werror -Wno-unused-label -Wno-cpp -Wno-unused-parammeter -nostdlib -nostartfiles -nodefaultlibs -Wall -O0 -Iinc

Expand All @@ -21,6 +21,15 @@ all: ./bin/boot.bin ./bin/kernel.bin
./build/kernel.o: ./src/kernel.c
i686-elf-gcc $(INCLUDES) $(FLAGS) -std=gnu99 -c ./src/kernel.c -o ./build/kernel.o

./build/idt/idt.asm.o: ./src/idt/idt.asm
nasm -f elf -g ./src/idt/idt.asm -o ./build/idt/idt.asm.o

./build/idt/idt.o: ./src/idt/idt.c
i686-elf-gcc $(INCLUDES) -I./src/idt $(FLAGS) -std=gnu99 -c ./src/idt/idt.c -o ./build/idt/idt.o

./build/memory/memory.o: ./src/memory/memory.c
i686-elf-gcc $(INCLUDES) -I./src/memory $(FLAGS) -std=gnu99 -c ./src/memory/memory.c -o ./build/memory/memory.o

run:
qemu-system-x86_64 -hda ./bin/os.bin

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Developing a multi-threaded kernel from scratch

Documents in this repo assumes macOS on Apple M1 chip.

```
```shell
> brew install nasm
> brew install qemu
```
Expand Down
2 changes: 1 addition & 1 deletion doc/protected_mode_development.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ We create entries for Global Descriptor Table (GDT) and load its address into GD

This repo assumes you are using a Mac on M1 Chip. For that, we need to use LLDB instead of GDB. Here's how:

```
```shell
# Launch QEMU with gdb server enabled
# '-s' shorthand for -gdb tcp::1234
# '-S' freeze CPU at startup (use 'c' to start execution)
Expand Down
4 changes: 2 additions & 2 deletions doc/protected_mode_development_2.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Installing the i686-elf toolchain on M1 Mac is very easy.

```
```shell
> brew install i686-elf-binutils i686-elf-gcc i386-elf-gdb
```

Expand Down Expand Up @@ -49,7 +49,7 @@ Now, we can use `i386-elf-gdb` instead of `lldb`. Load the symbol file `kernelfu

Now you can debug the kernel just like you would do with any executables!

```
```shell
> make clean
> make
❯ qemu-system-x86_64 -hda ./bin/os.bin -s -S &
Expand Down
67 changes: 64 additions & 3 deletions doc/protected_mode_development_3.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Each character will take 2 bytes.

Example: Display "AB" in white

```
```x86asm
0xB8000 = 'A'
0xB8001 = 0x0F
0xB8002 = 'B'
Expand All @@ -37,15 +37,15 @@ Example: Display "AB" in white

### Writing to video memory in C

```
```c
char *video_mem = (char *)0xB8000;
video_mem[0] = 'A';
video_mem[1] = '2'; // Green
```

but, we can optimize this by using `uint16_t`

```
```c
#include <stdint.h>

...
Expand All @@ -57,3 +57,64 @@ video_mem[0] = 0x0241; // 'A' (65 decimal or 0x41 hex) + Green (0x02) in the lit
and with bit more helper functions, we can call `print` and write "Hello, World!".

[commit](https://github.com/taikiy/kernel/commit/fa0fbabaf9c9cd93bcaff966fb54164fa3da3df6)

### Interrupt Descriptor Table

IDT describes how interrupts are invoked in Protected Mode, and can be mapped anywhere in memory (as opposed to IVT must be loaded at 0x00). This is equivalent of IVT (Interrupt Vector Table) in Real Mode, but a bit harder to setup. ([osdev wiki](https://wiki.osdev.org/Interrupt_descriptor_table))

We'll use the C structure to represent IDT entries.

```c
struct idt_desc
{
uint16_t offset_1; // offset bits 0..15
uint16_t selector; // a code segment selector in GDT or LDT
uint8_t zero; // unused bits, set to 0
uint8_t type_attr; // type and attributes
uint16_t offset_2; // offset bits 16..31
} __attribute__((packed)); // ensures there's no unexpected padding
```

We can create an array to store interrupt descriptors. Index 0 defines interrupt 0 `int 0`, index 1 defines `int 1`, and so on.

```c
struct idt_desc idt_descriptors[MAX_INTERRUPTS];
```

The location of the IDT is kept in the IDTR (IDT Register). This is loaded using the `lidt` assembly instruction, whose argument is a pointer to an IDTR.

```c
struct idtr_desc
{
uint16_t limit; // The length of the Interrupt Descriptor Table minus one
uint32_t base; // The address of the Interrupt Descriptor Table
} __attribute__((packed));
```

![IDT](https://pdos.csail.mit.edu/6.828/2008/readings/i386/fig9-1.gif)

### Gate Types

- 32-bit Task Gate (0x05 / 0b0101)
- 16-bit Interrupt Gate (0x06 / 0b0110)
- 16-bit Trap Gate (0x07 / 0b0111)
- 32-bit Interrupt Gate (0x0E / 0b1110)
- 32-bit Trap Gate (0x0F / 0b1111)

#### Task Gate

Task gates reference TSS descriptors and can assist in multi-tasking when exceptions occur.

#### Interrupt Gate

Interrupt gates are to be used for interrupts that we want to invoke ourselves in our code.

#### Trap Gate

Trap gates are like interrupt gates, but used for exceptions. They disable interrupts on entry and re-enable on an `iret` instruction.

### Loading IDT

Use `lidt` instruction.

([commit]())
28 changes: 14 additions & 14 deletions doc/real_mode_development.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ For this section, we'll print "Hello, World!" on the screen, and for that we'll

---

1. Edit [`boot.asm`](../boot.asm) ([git](https://github.com/taikiy/kernel/commit/fa5ced2e4e5b3dab0105ed001ef021cc7759e329#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))
1. Edit [`boot.asm`](../boot.asm) ([commit](https://github.com/taikiy/kernel/commit/fa5ced2e4e5b3dab0105ed001ef021cc7759e329#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))

2. Assemble

```
```shell
> nasm -f bin ./boot.asm -o ./boot.bin
```

`-f bin` option assembles the file to binary. We don't use object file etc. because there's no concept of executables, file formats, etc. in the BIOS.

Output has no header information. Just raw code output.

```
```shell
❯ ll
total 32
-rw-r--r-- 1 taiki staff 501B Jan 29 20:38 boot.asm
Expand All @@ -41,7 +41,7 @@ total 32

You can disassemble the bin file to see the contents.

```
```shell
> ndisasm ./boot.bin`
00000000 B40E mov ah,0xe
00000002 B041 mov al,0x41
Expand All @@ -56,7 +56,7 @@ You can disassemble the bin file to see the contents.

4. Run

```
```shell
> qemu-system-x86_64 -hda ./boot.bin
```

Expand All @@ -66,11 +66,11 @@ You can disassemble the bin file to see the contents.

In the previous section, `ORG 0x7c00` worked fine. This is because QEMU BIOS starts with DS set to 0. On other BIOS, however, this might not work if it initializes the data segment to, for example, `0x7c0`. In that case, our DS will be `0x7c00 + 0x7c0 * 16` which does not point to `message`.

To prevent this, we set DS ourselves in the assembly. ([git](https://github.com/taikiy/kernel/commit/6b08bf6ba316d4bcc16c7f214151aca9cfdcfab7#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))
To prevent this, we set DS ourselves in the assembly. ([commit](https://github.com/taikiy/kernel/commit/6b08bf6ba316d4bcc16c7f214151aca9cfdcfab7#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))

## 3. BIOS Parameter Block (BPB)

Usually, the binary file so far will work fine on real machines, but some BIOS expect what's known as [BPB](https://wiki.osdev.org/FAT#BPB_.28BIOS_Parameter_Block.29). For a maximum compatibility, we should reserve the block if BIOS decides to write some data in this data block. ([git](https://github.com/taikiy/kernel/commit/ec33f9a20982be55a0caf5eb59890048b4cfd064#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))
Usually, the binary file so far will work fine on real machines, but some BIOS expect what's known as [BPB](https://wiki.osdev.org/FAT#BPB_.28BIOS_Parameter_Block.29). For a maximum compatibility, we should reserve the block if BIOS decides to write some data in this data block. ([commit](https://github.com/taikiy/kernel/commit/ec33f9a20982be55a0caf5eb59890048b4cfd064#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))
## 4. Writing the bootloader to a USB stick
Expand All @@ -82,22 +82,22 @@ On Mac, just like Linux, you can use `dd` to copy our binary bootloader file to
2. Check the interface path
```
diskutil list
```shell
> diskutil list
```
This is equivalent to `fdisk -l` on Linux.
3. Unmount the USB stick
```
diskutil unmountDisk /dev/disk6
```shell
> diskutil unmountDisk /dev/disk6
```
4. Create the disk image. ❗ _Backup the USB stick before executing this command_ ❗
```
sudo dd if=./boot.bin of=/dev/disk6
```shell
> sudo dd if=./boot.bin of=/dev/disk6
```
5. Unplug the stick, plug it into a PC, boot!
Expand All @@ -119,7 +119,7 @@ In Real Mode, Interrupt Vector Table (IVT) is loaded at 0x00. IVT is a table tha
4 2 0
```

To define a custom interrupt handler, we first define a routine with a label, and then write the segment/offset of the routine to IVT. ([git](https://github.com/taikiy/kernel/commit/8a5fb00bc8bdaf47af6cb9de8ac107e8a6655db6#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))
To define a custom interrupt handler, we first define a routine with a label, and then write the segment/offset of the routine to IVT. ([commit](https://github.com/taikiy/kernel/commit/8a5fb00bc8bdaf47af6cb9de8ac107e8a6655db6#diff-ef96aa02ede6928fc12bc906ab8b222af1250dde26bb066466d339e48ab4e658))

## 6. Reading from the disk

Expand Down
9 changes: 9 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef CONFIG_H
#define CONFIG_H

#define KERNEL_CODE_SELECTOR 0x08
#define KERNEL_DATA_SELECTOR 0x10

#define TOTAL_INTERRUPTS 512

#endif
13 changes: 13 additions & 0 deletions src/idt/idt.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
section .asm

global idt_load

idt_load:
push ebp
mov ebp, esp

mov ebx, [ebp+8] ; first argument passed to this function
lidt [edx]

pop ebp
ret
41 changes: 41 additions & 0 deletions src/idt/idt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "idt.h"
#include "config.h"
#include "kernel.h"
#include "memory/memory.h"

struct idt_desc idt_descriptors[TOTAL_INTERRUPTS];
struct idtr_desc idtr_descriptor;

extern void idt_load(struct idtr_desc *ptr);

void idt_zero()
{
print("Divide by zero error\n");
}

void idt_set(int interrupt_number, void *address)
{
struct idt_desc *desc = &idt_descriptors[interrupt_number];

desc->offset_1 = (uint32_t)address & 0xffff;
desc->selector = KERNEL_CODE_SELECTOR;
desc->zero = 0x00;
// Type: 0x0e/0b1110 = 32-bit interrupt gate
// Attribute: Storage Segment = 0 (interrupt)
// DPL (Descriptor Privilege Level) = 0b11 or 3 (Ring 3)
// Present = 1 (0 for unused interrupt)
desc->type_attr = 0x0e | 0xe0;
desc->offset_2 = (uint32_t)address >> 16;
}

void idt_init()
{
memset(idt_descriptors, 0, sizeof(idt_descriptors));
idtr_descriptor.limit = sizeof(idt_descriptors) - 1;
idtr_descriptor.base = (uint32_t)idt_descriptors;

idt_set(0, idt_zero);

// Load the IDT
idt_load(&idtr_descriptor);
}
23 changes: 23 additions & 0 deletions src/idt/idt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef IDT_H
#define IDT_H

#include <stdint.h>

struct idt_desc
{
uint16_t offset_1; // offset bits 0..15
uint16_t selector; // a code segment selector in GDT or LDT
uint8_t zero; // unused bits, set to 0
uint8_t type_attr; // type and attributes
uint16_t offset_2; // offset bits 16..31
} __attribute__((packed)); // ensures there's no unexpected padding

struct idtr_desc
{
uint16_t limit; // The length of the Interrupt Descriptor Table minus one
uint32_t base; // The address of the Interrupt Descriptor Table
} __attribute__((packed));

void idt_init();

#endif
4 changes: 4 additions & 0 deletions src/kernel.asm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[BITS 32]

global _start
global divide_by_zero

extern kernel_main

Expand Down Expand Up @@ -28,6 +29,9 @@ _start:
cli ; Disables interrupts
hlt ; This hangs the computer

divide_by_zero:
int 0

times 512 - ($ - $$) db 0 ; Pad the kernel code sector to 512 bytes
; This ensures that any object files written in C and linked with this assembly
; will be correctly aligned.
Loading

0 comments on commit e6abc5c

Please sign in to comment.