9 Commits
serial ... time

Author SHA1 Message Date
54f26c506e 1000Hz PIC timer working + IDT dispatch/handler fixes 2025-12-27 13:52:05 +01:00
24d75463b8 Merge pull request #4 from xamidev/idt
Idt
2025-12-22 21:05:42 +01:00
42fc169e10 Interrupt Dispatch and Handling (for first common vectors) 2025-12-22 21:04:45 +01:00
d0b4da0596 IDT: set entry, load into IDTR, interrupt stub + dispatcher for common faults 2025-12-22 19:38:50 +01:00
0031c2fe03 Woops.. it wasnt nonsense after all 2025-12-22 11:27:39 +01:00
282a423387 Delete GCH nonsense 2025-12-22 11:26:59 +01:00
c43be0bddd Merge pull request #3 from xamidev/gdt
GDT init (load + flush)
2025-12-22 11:24:15 +01:00
6fc7266716 GDT init (load + flush) 2025-12-22 11:20:24 +01:00
29deb20cd7 Merge pull request #2 from xamidev/serial
Serial communication
2025-12-21 20:35:02 +01:00
15 changed files with 794 additions and 73 deletions

3
.gitignore vendored
View File

@@ -4,3 +4,6 @@ pepperk
iso_root iso_root
*.o *.o
*.iso *.iso
*.gch
*/*.gch
*/*/*.gch

View File

@@ -1,7 +1,8 @@
build: build:
rm -f *.o rm -f *.o
x86_64-elf-gcc -g -c -I src src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel x86_64-elf-gcc -g -c -I src src/time/timer.c src/idt/idt.c src/mem/utils.c src/mem/gdt.c src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o
nasm -f elf64 src/idt/idt.S -o idt_stub.o
x86_64-elf-ld -o pepperk -T linker.ld *.o x86_64-elf-ld -o pepperk -T linker.ld *.o
limine/limine: limine/limine:
@@ -27,7 +28,7 @@ build-iso: limine/limine build
./limine/limine bios-install pepper.iso ./limine/limine bios-install pepper.iso
debug: debug:
qemu-system-x86_64 -drive file=pepper.iso -s -S & qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot &
gdb pepperk --command=debug.gdb gdb pepperk --command=debug.gdb
run: build-iso run: build-iso

View File

@@ -1 +1,3 @@
target remote localhost:1234 target remote localhost:1234
set disassembly-flavor intel
display/8i $rip

303
src/idt/idt.S Normal file
View File

@@ -0,0 +1,303 @@
; Assembly stub for the IDT
bits 64
extern interrupt_dispatch
global interrupt_stub
global vector_0_handler
global vector_1_handler
global vector_2_handler
global vector_3_handler
global vector_4_handler
global vector_5_handler
global vector_6_handler
global vector_7_handler
global vector_8_handler
global vector_9_handler
global vector_10_handler
global vector_11_handler
global vector_12_handler
global vector_13_handler
global vector_14_handler
global vector_15_handler
global vector_16_handler
global vector_17_handler
global vector_18_handler
global vector_19_handler
global vector_20_handler
global vector_21_handler
interrupt_stub:
; We'll push all general-purpose registers to the stack,
; so they're intact and don't bother the code that was
; executed when the interrupt happened.
; (except rsp because it will already be saved in the iret frame)
push qword rax
push qword rbx
push qword rcx
push qword rdx
push qword rsi
push qword rdi
;push qword rsp
push qword rbp
push qword r8
push qword r9
push qword r10
push qword r11
push qword r12
push qword r13
push qword r14
push qword r15
; Put stack pointer as first argument of our function
mov rdi, rsp
call interrupt_dispatch
; What the function returns (new stack pointer) is saved in rbp
mov rsp, rax
pop qword r15
pop qword r14
pop qword r13
pop qword r12
pop qword r11
pop qword r10
pop qword r9
pop qword r8
pop qword rbp
;pop qword rsp
pop qword rdi
pop qword rsi
pop qword rdx
pop qword rcx
pop qword rbx
pop qword rax
; Removing the error code and vector number so stack doesn't
; get corrupted
add rsp, 16
; Restore ss, rsp, rflags, cs, rip of code that was executing
; before the interrupt
iretq
; Vector handlers will be 16-byte aligned so that we can loop over them
; like <vector_no> * 16 to get each one's address
; Divide Error
align 16
vector_0_handler:
; error code (nothing, so we push a dummy 0 quadword, 64bits/8bytes long)
push qword 0
; vector number (so our interrupt stub knows which one it is)
push qword 0
jmp interrupt_stub
; Debug Exception
align 16
vector_1_handler:
push qword 0
push qword 1
jmp interrupt_stub
; NMI
align 16
vector_2_handler:
push qword 0
push qword 2
jmp interrupt_stub
; Breakpoint
align 16
vector_3_handler:
push qword 0
push qword 3
jmp interrupt_stub
; Overflow
align 16
vector_4_handler:
push qword 0
push qword 4
jmp interrupt_stub
; BOUND Range exceeded
align 16
vector_5_handler:
push qword 0
push qword 5
jmp interrupt_stub
; Invalid Opcode
align 16
vector_6_handler:
push qword 0
push qword 6
jmp interrupt_stub
; Device Not Available
align 16
vector_7_handler:
push qword 0
push qword 7
jmp interrupt_stub
; Double Fault
align 16
vector_8_handler:
; No error code, we only push vector number
push qword 1
jmp interrupt_stub
; Coprocessor Segment Overrun
align 16
vector_9_handler:
push qword 9
jmp interrupt_stub
; Invalid TSS
align 16
vector_10_handler:
push qword 10
jmp interrupt_stub
; Segment Not Present
align 16
vector_11_handler:
push qword 11
jmp interrupt_stub
; Stack-Segment Fault
align 16
vector_12_handler:
push qword 12
jmp interrupt_stub
; General Protection
align 16
vector_13_handler:
push qword 13
jmp interrupt_stub
; Page Fault
align 16
vector_14_handler:
push qword 14
jmp interrupt_stub
; Intel reserved
align 16
vector_15_handler:
push qword 0
push qword 15
jmp interrupt_stub
; x87 FPU Floating-Point Error
align 16
vector_16_handler:
push qword 0
push qword 16
jmp interrupt_stub
; Alignment Check
align 16
vector_17_handler:
push qword 17
jmp interrupt_stub
; Machine Check
align 16
vector_18_handler:
push qword 0
push qword 18
jmp interrupt_stub
; SIMD Floating-Point Exception
align 16
vector_19_handler:
push qword 0
push qword 19
jmp interrupt_stub
; Virtualization Exception
align 16
vector_20_handler:
push qword 0
push qword 20
jmp interrupt_stub
; Control Protection Exception
align 16
vector_21_handler:
push qword 21
jmp interrupt_stub
; The others are reserved (22->31) or external (32->255) interrupts
align 16
vector_22_handler:
push qword 0
push qword 22
jmp interrupt_stub
align 16
vector_23_handler:
push qword 0
push qword 23
jmp interrupt_stub
align 16
vector_24_handler:
push qword 0
push qword 24
jmp interrupt_stub
align 16
vector_25_handler:
push qword 0
push qword 25
jmp interrupt_stub
align 16
vector_26_handler:
push qword 0
push qword 26
jmp interrupt_stub
align 16
vector_27_handler:
push qword 0
push qword 27
jmp interrupt_stub
align 16
vector_28_handler:
push qword 0
push qword 28
jmp interrupt_stub
align 16
vector_29_handler:
push qword 0
push qword 29
jmp interrupt_stub
align 16
vector_30_handler:
push qword 0
push qword 30
jmp interrupt_stub
align 16
vector_31_handler:
push qword 0
push qword 31
jmp interrupt_stub
; PIT timer
align 16
vector_32_handler:
push qword 0
push qword 32
jmp interrupt_stub

138
src/idt/idt.c Normal file
View File

@@ -0,0 +1,138 @@
#include "idt.h"
#include <stdint.h>
#include <stddef.h>
#include "../io/serial.h"
struct interrupt_descriptor idt[256];
struct idtr idt_reg;
// Address to our first interrupt handler
extern char vector_0_handler[];
// Timer ticks
extern uint64_t ticks;
void idt_set_entry(uint8_t vector, void* handler, uint8_t dpl)
{
uint64_t handler_addr = (uint64_t)handler;
struct interrupt_descriptor* entry = &idt[vector];
// Address is split in three parts so we right-shift progressively to get it all
entry->address_low = handler_addr & 0xFFFF;
entry->address_mid = (handler_addr >> 16) & 0xFFFF;
entry->address_high = handler_addr >> 32;
// Kernel code selector (as set in GDT)
entry->selector = 0x8;
// Interrupt gate, present, DPL (having: max DPL = 3)
entry->flags = 0b1110 | ((dpl & 0b11) << 5) | (1 << 7);
// We won't use IST for now
entry->ist = 0;
}
void idt_load(void* idt_addr)
{
// "limit" = "size" = Size of the IDT - 1 byte = (16*256)-1 = 0xFFF
idt_reg.limit = 0xFFF;
idt_reg.base = (uint64_t)idt_addr;
asm volatile("lidt %0" :: "m"(idt_reg));
}
void idt_init()
{
// We set 256 entries, but we have only the first few stubs.
// Undefined behavior?
for (size_t i=0; i<256; i++)
{
// Each vector handler is 16-byte aligned, so <vector_no>*16 = address of that handler
idt_set_entry(i, vector_0_handler + (i*16), 0);
}
idt_load(&idt);
serial_kputs("kernel: idt: Initialized IDT!\n");
}
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
{
switch(context->vector_number)
{
case 0:
serial_kputs("kernel: idt: Divide Error!\n");
break;
case 1:
serial_kputs("kernel: idt: Debug Exception!\n");
break;
case 2:
serial_kputs("kernel: idt: NMI Interrupt!\n");
break;
case 3:
serial_kputs("kernel: idt: Breakpoint Interrupt!\n");
break;
case 4:
serial_kputs("kernel: idt: Overflow Trap!\n");
break;
case 5:
serial_kputs("kernel: idt: BOUND Range Exceeded!\n");
break;
case 6:
serial_kputs("kernel: idt: Invalid Opcode!\n");
break;
case 7:
serial_kputs("kernel: idt: Device Not Available!\n");
break;
case 8:
serial_kputs("kernel: idt: Double Fault!\n");
break;
case 9:
serial_kputs("kernel: idt: Coprocessor Segment Overrun!\n");
break;
case 10:
serial_kputs("kernel: idt: Invalid TSS!\n");
break;
case 11:
serial_kputs("kernel: idt: Segment Not Present!\n");
break;
case 12:
serial_kputs("kernel: idt: Stack-Segment Fault!\n");
break;
case 13:
serial_kputs("kernel: idt: General Protection Fault!\n");
break;
case 14:
serial_kputs("kernel: idt: Page Fault!\n");
break;
case 15:
serial_kputs("kernel: idt: Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)\n");
break;
case 16:
serial_kputs("kernel: idt: x87 Floating-Point Error!\n");
break;
case 17:
serial_kputs("kernel: idt: Alignment Check Fault!\n");
break;
case 18:
serial_kputs("kernel: idt: Machine Check!\n");
break;
case 19:
serial_kputs("kernel: idt: SIMD Floating-Point Exception!\n");
break;
case 20:
serial_kputs("kernel: idt: Virtualization Exception!\n");
break;
case 21:
serial_kputs("kernel: idt: Control Protection Exception!\n");
break;
case 32:
serial_kputs("Tick!");
ticks++;
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
break;
default:
serial_kputs("kernel: idt: Unexpected interrupt\n");
break;
}
return context;
}

57
src/idt/idt.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef IDT_H
#define IDT_H
#include <stdint.h>
void idt_init();
struct interrupt_descriptor
{
uint16_t address_low;
uint16_t selector;
uint8_t ist;
uint8_t flags;
uint16_t address_mid;
uint32_t address_high;
uint32_t reserved;
} __attribute__((packed));
struct idtr
{
uint16_t limit;
uint64_t base;
} __attribute__((packed));
// All general-purpose registers (except rsp) as stored on the stack,
// plus the values we pushed (vector number, error code) and the iret frame
// In reverse order because the stack grows downwards.
struct cpu_status_t
{
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rbp;
//uint64_t rsp;
uint64_t rdi;
uint64_t rsi;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rax;
uint64_t vector_number;
uint64_t error_code;
uint64_t iret_rip;
uint64_t iret_cs;
uint64_t iret_flags;
uint64_t iret_rsp;
uint64_t iret_ss;
};
#endif

View File

@@ -1,4 +1,5 @@
#include "../kernel.h" #include "../kernel.h"
#include "serial.h"
void outb(int port, unsigned char data) void outb(int port, unsigned char data)
{ {
@@ -34,6 +35,8 @@ int serial_init()
// Set normal operation mode // Set normal operation mode
outb(PORT + 4, 0x0F); outb(PORT + 4, 0x0F);
serial_kputs("\n\nkernel: serial: Serial initialization OK!\n");
return 0; return 0;
} }

View File

@@ -7,4 +7,7 @@ enum ErrorCodes
EIO EIO
}; };
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
#define SET_INTERRUPTS __asm__ volatile("sti")
#endif #endif

View File

@@ -4,6 +4,11 @@
#include "io/term.h" #include "io/term.h"
#include "io/printf.h" #include "io/printf.h"
#include "io/serial.h" #include "io/serial.h"
#include "mem/gdt.h"
#include "mem/utils.h"
#include "idt/idt.h"
#include "kernel.h"
#include "time/timer.h"
// Limine version used // Limine version used
__attribute__((used, section(".limine_requests"))) __attribute__((used, section(".limine_requests")))
@@ -24,74 +29,6 @@ static volatile LIMINE_REQUESTS_END_MARKER;
struct limine_framebuffer* framebuffer; struct limine_framebuffer* framebuffer;
// We won't be linked to standard library, but still need the basic mem* functions
// so everything goes allright with the compiler
// We use the "restrict" keyword on pointers so that the compiler knows it can
// do more optimization on them (and as it's a much used function, it's good to
// be able to do that)
void* memcpy(void* restrict dest, const void* restrict src, size_t n)
{
uint8_t* restrict pdest = (uint8_t* restrict)dest;
const uint8_t* restrict psrc = (const uint8_t* restrict)src;
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void* memmove(void *dest, const void* src, size_t n)
{
uint8_t* pdest = (uint8_t*)dest;
const uint8_t* psrc = (uint8_t*)src;
if (src > dest)
{
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
int memcmp(const void* s1, const void* s2, size_t n)
{
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i=0; i<n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}
// Panic // Panic
static void hcf() static void hcf()
{ {
@@ -113,11 +50,16 @@ void kmain()
if (term_init()) hcf(); if (term_init()) hcf();
if (serial_init()) kputs("kernel: serial: error: Cannot init serial communication!"); if (serial_init()) kputs("kernel: serial: error: Cannot init serial communication!");
serial_kputs("\n\nkernel: serial: Hello, world from serial!"); CLEAR_INTERRUPTS;
gdt_init();
idt_init();
timer_init();
SET_INTERRUPTS;
// Draw something // Draw something
printf("%s, %s!", "Hello", "world"); printf("%s, %s!", "Hello", "world");
//printf("%d", 4/0);
hcf(); hcf();
} }

82
src/mem/gdt.c Normal file
View File

@@ -0,0 +1,82 @@
#include "gdt.h"
#include <stdint.h>
#include "../io/serial.h"
// Descriptors are 8-byte wide (64bits)
// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc..
uint64_t gdt_entries[NUM_GDT_ENTRIES];
struct GDTR gdtr;
static void gdt_load()
{
asm("lgdt %0" : : "m"(gdtr));
}
static void gdt_flush()
{
// Here, 0x8 is the kernel code selector
// and 0x10 is the kernel data selector
asm volatile (
"mov $0x10, %%ax \n" // Reload segments with kernel data selector
"mov %%ax, %%ds \n"
"mov %%ax, %%es \n"
"mov %%ax, %%fs \n"
"mov %%ax, %%gs \n"
"mov %%ax, %%ss \n"
"pushq $0x8 \n" // CS reload
"lea 1f(%%rip), %%rax \n"
"push %%rax \n"
"lretq \n"
"1: \n" // Execution continues here after CS reload
:
:
: "rax", "memory"
);
}
void gdt_init()
{
// Null descriptor (required)
gdt_entries[0] = 0;
// Kernel code segment
uint64_t kernel_code = 0;
kernel_code |= 0b1101 << 8; // Selector type: accessed, read-enable, no conforming
kernel_code |= 1 << 12; // not a system descriptor
kernel_code |= 0 << 13; // DPL field = 0
kernel_code |= 1 << 15; // Present
kernel_code |= 1 << 21; // Long mode
// Left shift 32 bits so we place our stuff in the upper 32 bits of the descriptor.
// The lower 32 bits contain limit and part of base and therefore are ignored in Long Mode
// (because we'll use paging; segmentation is used only for legacy)
gdt_entries[1] = kernel_code << 32;
uint64_t kernel_data = 0;
kernel_data |= 0b0011 << 8;
kernel_data |= 1 << 12;
kernel_data |= 0 << 13;
kernel_data |= 1 << 15;
kernel_data |= 1 << 21;
gdt_entries[2] = kernel_data << 32;
// We re-use the kernel descriptors here, and just update their DPL fields
// (Descriptor privilege level) from ring 0 -> to ring 3 (userspace)
uint64_t user_code = kernel_code | (3 << 13);
gdt_entries[3] = user_code;
uint64_t user_data = kernel_data | (3 << 13);
gdt_entries[4] = user_data;
// The -1 subtraction is some wizardry explained in the OSDev wiki -> GDT
gdtr.limit = NUM_GDT_ENTRIES * sizeof(uint64_t) - 1;
gdtr.address = (uint64_t)gdt_entries;
// Load the GDT we created, flush the old one
gdt_load();
gdt_flush();
serial_kputs("kernel: gdt: Initialized GDT!\n");
}

26
src/mem/gdt.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef GDT_H
#define GDT_H
#include <stdint.h>
// We're using the GDT for segmentation, but as we want to target Long Mode,
// we'll only use this as a requirement for paging, not more.
// This means base 0 and no limit (whole address space)
#define NUM_GDT_ENTRIES 5
#define NULL_SELECTOR 0x00
#define KERNEL_CODE_SEGMENT 0x08
#define KERNEL_DATA_SEGMENT 0x10
#define USER_CODE_SEGMENT 0x18
#define USER_DATA_SEGMENT 0x20
struct GDTR
{
uint16_t limit;
uint64_t address;
} __attribute__((packed));
void gdt_init();
#endif

70
src/mem/utils.c Normal file
View File

@@ -0,0 +1,70 @@
#include <stddef.h>
#include <stdint.h>
// We won't be linked to standard library, but still need the basic mem* functions
// so everything goes allright with the compiler
// We use the "restrict" keyword on pointers so that the compiler knows it can
// do more optimization on them (and as it's a much used function, it's good to
// be able to do that)
void* memcpy(void* restrict dest, const void* restrict src, size_t n)
{
uint8_t* restrict pdest = (uint8_t* restrict)dest;
const uint8_t* restrict psrc = (const uint8_t* restrict)src;
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void* memmove(void *dest, const void* src, size_t n)
{
uint8_t* pdest = (uint8_t*)dest;
const uint8_t* psrc = (uint8_t*)src;
if (src > dest)
{
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
int memcmp(const void* s1, const void* s2, size_t n)
{
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i=0; i<n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}

11
src/mem/utils.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef MEM_UTILS_H
#define MEM_UTILS_H
#include <stddef.h>
void* memcpy(void* restrict dest, const void* restrict src, size_t n);
void* memset(void* s, int c, size_t n);
void* memmove(void *dest, const void* src, size_t n);
int memcmp(const void* s1, const void* s2, size_t n);
#endif

74
src/time/timer.c Normal file
View File

@@ -0,0 +1,74 @@
#include <stdint.h>
#include "../io/serial.h"
/*
For now, the timer module will be using the PIC.
Even though it's quite old, it's still supported by newer CPUs
and it will be precise enough for what we'll do. Also it's easier
to implement than ACPI etc. (we may upgrade to ACPI when we're
interested in multi-core functionnality like SMP)
*/
volatile uint64_t ticks = 0;
void pic_remap()
{
uint8_t master_mask = inb(0x21);
uint8_t slave_mask = inb(0xA1);
// ICW1: start initialization
outb(0x20, 0x11);
outb(0xA0, 0x11);
// ICW2: vector offsets
outb(0x21, 0x20); // Master PIC -> 0x20
outb(0xA1, 0x28); // Slave PIC -> 0x28
// ICW3: tell Master about Slave at IRQ2 (0000 0100)
outb(0x21, 0x04);
// ICW3: tell Slave its cascade identity (0000 0010)
outb(0xA1, 0x02);
// ICW4: 8086 mode
outb(0x21, 0x01);
outb(0xA1, 0x01);
// Restore saved masks
outb(0x21, master_mask);
outb(0xA1, slave_mask);
}
void pic_enable()
{
// Enabling IRQ0 (unmasking it) but not the others
uint8_t mask = inb(0x21);
mask &= ~(1 << 0); // Clear bit 0 (IRQ0)
outb(0x21, mask);
}
/*
Base frequency = 1.193182 MHz
1 tick per ms (divide by 1000) = roughly 1193 Hz
*/
void pit_init()
{
uint32_t frequency = 1000; // 1 kHz
uint32_t divisor = 1193182 / frequency;
// Set PIT to mode 3, channel 0
outb(0x43, 0x36); // 0x36
// Send divisor (low byte, then high byte)
outb(0x40, divisor & 0xFF);
outb(0x40, (divisor >> 8) & 0xFF);
}
void timer_init()
{
// Remapping the PIC, because at startup it conflicts with
// the reserved IRQs we have for faults/exceptions etc.
// so we move its IRQ0 to something not reserved (32)
pic_remap();
pic_enable();
pit_init();
}

6
src/time/timer.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef TIMER_H
#define TIMER_H
void timer_init();
#endif