Skip to content

Commit

Permalink
Interrupt Service Routine foundation
Browse files Browse the repository at this point in the history
  • Loading branch information
taikiy committed Dec 28, 2023
1 parent 5c7a1da commit 42c0b63
Show file tree
Hide file tree
Showing 21 changed files with 217 additions and 95 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.DS_Store
*.code-workspace
bin/*
build/*
**/bin/*
**/build/*
2 changes: 1 addition & 1 deletion doc/12_user_space.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ To test the User Space functionality, we write a simple user program that goes i

---

[previous](./11_file_system_virtual_file_system.md) | [next](./13_.md)
[previous](./11_file_system_virtual_file_system.md) | [next](./13_calling_kernel_space_routines_from_user_space.md)
28 changes: 28 additions & 0 deletions doc/13_calling_kernel_space_routines_from_user_space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Talking with the kernel from User Space

We can use the `int` instruction to call a kernel function from User Space. The `int` instruction takes a single byte as an argument. This byte is the interrupt number. We can use this interrupt number to call a kernel function. The kernel can then use the interrupt number to determine which function to call. Once the kernel function is done, it returns to the user program.

Below is an example of a user program calling a kernel function.

1. Let's assume that the kernel function we want to call is `print`. We can assign a code number to this function. Let's say the code number is 0x1.
2. The user program sets `0x1` to EAX.
3. The user program pushes the address of a message to be printed to the stack.
4. The user program executes the `int` instruction. We can assign any number to the interrupt number. Let's say the interrupt number is 0x80.
5. The kernel function `print` is called.
6. The kernel returns from the call normally. The CPU will execute instructions equivalent to `switch_to_task` and `return_to_task`. We don't have to do this manually.

```asm
; void print(const char* message)
print:
push ebp
mov ebp, esp
mov eax, 1 ; 1 is the code number for print
mov ebx, [ebp + 8] ; address of the string to print
push dword ebx ; push the address to the stack
int 0x80 ; invoke the the kernel call
add esp, 4 ; restore the stack pointer (pop the argument)
pop ebp
ret
```

- Interrupt 0x80 handler [commit]()
2 changes: 1 addition & 1 deletion doc/6_programmable_interrupt_controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Assuming we've remapped IRQ 0 to 0x20, that means we'll receive timer interrupts

Previously, we implemented IDT (Interrupt Descriptor Table) where we define the mapping between interrupts and their handlers. This is the time we start using IDT. We can define IDT for INT 0x21, for example, to handle the keyboard interrupts.

However, before handling the interrupt, we'll need to disable interrupts while in the handler block, store the register contents before handling the interrupt so that we can go back to the previous state, and enable interrupts back again. For these reasons, just calling `idt_set` with a C function to its argument isn't enough. We'll create a wrapper in assembly for that.
However, before handling the interrupt, we'll need to disable interrupts while in the handler block, store the register contents before handling the interrupt so that we can go back to the previous state, and enable interrupts again. For these reasons, just calling `idt_set` with a C function to its argument isn't enough. We'll create a wrapper in assembly for that.

Note that in the previous section where we implemented `idt_zero`. Strictly speaking, this handler also needs to be defined in assembly as we implement `int21h` in this section. The reason we didn't was just for a quick demonstration purpose.

Expand Down
Binary file removed programs/blank/build/blank.asm.o
Binary file not shown.
1 change: 0 additions & 1 deletion programs/blank/build/blank.bin

This file was deleted.

Binary file removed programs/blank/build/blank.o
Binary file not shown.
10 changes: 5 additions & 5 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@

#define TOTAL_INTERRUPTS 512

#define KERNEL_CODE_SELECTOR 0x08
#define KERNEL_DATA_SELECTOR 0x10

// https://wiki.osdev.org/Memory_Map_(x86)
#define KERNEL_STACK_ADDRESS 0x600000
#define HEAP_SIZE_BYTES 104857600 // 100MB heap size
#define HEAP_BLOCK_SIZE_BYTES 4096
#define HEAP_TABLE_ADDRESS 0x00007E00 // We need 25,600 bytes for our table to store 100MB heap
#define HEAP_ADDRESS 0x01000000

#define KERNEL_CODE_SELECTOR 0x08
#define KERNEL_DATA_SELECTOR 0x10
// Requested Protection Level (RPL) allows software to override the CPL to select a new protection
// level. The Current Protection Level (CPL) is the protection level of the currently executing
// program. The CPL is stored in bits 0 and 1 of SS and CS.
// http://www.brokenthorn.com/Resources/OSDev23.html
#define USER_PROGRAM_CODE_SELECTOR 0x18 | 0x03 // user mode code selector = 0x18 | RPL 0x03
#define USER_PROGRAM_DATA_SELECTOR 0x20 | 0x03 // user mode data selector = 0x20 | RPL 0x03

// https://wiki.osdev.org/Memory_Map_(x86)
// Stack size (or any other memory allocation) must align to 4096 bytes (page size)
#define KERNEL_STACK_ADDRESS 0x600000
#define USER_PROGRAM_STACK_SIZE HEAP_BLOCK_SIZE_BYTES * 4
#define USER_PROGRAM_VIRTUAL_ADDRESS_START 0x400000
#define USER_PROGRAM_STACK_VIRTUAL_ADDRESS_START 0x3FF000
Expand Down
29 changes: 24 additions & 5 deletions src/idt/idt.asm
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
section .asm

global load_idt
global int21h
global int_noop
global int21h
global isr80h
global enable_interrupts
global disable_interrupts

extern int21h_handler
extern int_noop_handler
extern isr80h_handler

enable_interrupts:
sti
Expand All @@ -28,21 +30,38 @@ load_idt:
ret

int21h:
cli
pushad ; pushes the contents of general-purpose registers (EAX, EBX, ECX, EDX, ESP, EBP, ESI and EDI) onto the stack.

call int21h_handler

popad
sti
iret

int_noop:
cli
pushad ; pushes the contents of general-purpose registers (EAX, EBX, ECX, EDX, ESP, EBP, ESI and EDI) onto the stack.

call int_noop_handler

popad
sti
iret

; Interrupt Service Routines for handling system calls
isr80h:
; ip, cs, flags, sp, ss are pushed onto the stack by the CPU.
pushad ; pushes the contents of general-purpose registers onto the stack.

push esp ; push the stack pointer onto the stack so that we can access the arguments passed to the system call in C.
push eax ; push the system call number

call isr80h_handler

mov dword[res], eax ; save the return value of the system call in the res variable in case we use eax for something else.
add esp, 8 ; remove the arguments from the stack

popad ; restore the contents of general-purpose registers from the stack.
mov eax, dword[res] ; move the return value of the system call into the eax register.
iret

section .data
; used to store the return value of the system call in `isr80h`
res: dd 0
24 changes: 24 additions & 0 deletions src/idt/idt.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "idt.h"
#include "config.h"
#include "io/io.h"
#include "kernel.h"
#include "memory/memory.h"
#include "task/task.h"
#include "terminal/terminal.h"

struct idt_desc idt_descriptors[TOTAL_INTERRUPTS];
Expand All @@ -10,6 +12,7 @@ struct idtr_desc idtr_descriptor;
extern void load_idt(struct idtr_desc* ptr);
extern void int21h();
extern void int_noop();
extern void isr80h();

void
idt_zero()
Expand All @@ -30,6 +33,27 @@ int_noop_handler()
outb(0x20, 0x20);
}

void*
syscall(int command, struct interrupt_frame* frame)
{
return 0;
}

void*
isr80h_handler(int command, struct interrupt_frame* frame)
{
void* res = 0;

switch_to_kernel_page();
save_current_task_state(frame);

res = syscall(command, frame);

switch_to_user_page();

return res;
}

void
idt_set(int interrupt_number, void* address)
{
Expand Down
19 changes: 19 additions & 0 deletions src/idt/idt.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ struct idtr_desc
uint32_t base; // The address of the Interrupt Descriptor Table
} __attribute__((packed));

struct interrupt_frame
{
// https://c9x.me/x86/html/file_module_x86_id_270.html
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t reserved; // esp, but we don't use the one set by `pushad`.
// We use the one below set by CPU when an interrupt occurs.
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t ip;
uint32_t cs;
uint32_t flags;
uint32_t esp;
uint32_t ss;
} __attribute__((packed));

void initialize_idt();
void enable_interrupts();
void disable_interrupts();
Expand Down
9 changes: 9 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 set_kernel_segment_registers

extern kernel_main

Expand Down Expand Up @@ -38,6 +39,14 @@ _start:
; cli ; Disables interrupts
; hlt ; This hangs the computer

set_kernel_segment_registers:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
ret

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.
18 changes: 13 additions & 5 deletions src/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "task/tss.h"
#include "terminal/terminal.h"

extern void set_kernel_segment_registers();
void test_path_parser();
void test_file_system();
void test_user_space();
Expand All @@ -31,16 +32,23 @@ panic(const char* message)
};
}

static struct paging_4gb_chunk* paging_chunk = 0;
static struct paging_map* kernel_page = 0;

void
switch_to_kernel_page_directory()
initialize_kernel_space_paging()
{
paging_chunk = paging_new_4gb(PAGING_IS_WRITABLE | PAGING_IS_PRESENT | PAGING_ACCESS_FROM_ALL);
paging_switch(paging_chunk);
kernel_page = new_paging_map(PAGING_IS_WRITABLE | PAGING_IS_PRESENT | PAGING_ACCESS_FROM_ALL);
switch_page(kernel_page);
enable_paging();
}

void
switch_to_kernel_page()
{
set_kernel_segment_registers();
switch_page(kernel_page);
}

void
kernel_main()
{
Expand All @@ -53,7 +61,7 @@ kernel_main()

print("...kernel heap and paging\n");
initialize_kernel_heap();
switch_to_kernel_page_directory();
initialize_kernel_space_paging();

print("...IDT\n");
initialize_idt();
Expand Down
1 change: 1 addition & 0 deletions src/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define KERNEL_H

void kernel_main();
void switch_to_kernel_page();
void panic(const char* message);

#endif
4 changes: 2 additions & 2 deletions src/memory/paging/paging.asm
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

section .asm

global paging_load_directory
global load_directory
global enable_paging

paging_load_directory:
load_directory:
push ebp
mov ebp, esp

Expand Down
Loading

0 comments on commit 42c0b63

Please sign in to comment.