Skip to content

Commit

Permalink
irq: Start refactoring IRQ allocation and interrupt handling
Browse files Browse the repository at this point in the history
  • Loading branch information
marv7000 committed Feb 24, 2025
1 parent abbab97 commit 4962e56
Show file tree
Hide file tree
Showing 27 changed files with 258 additions and 151 deletions.
35 changes: 18 additions & 17 deletions include/menix/system/arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,28 @@
#ifdef ARCH_HAS_DYNAMIC_PAGE_SIZE
extern usize arch_page_size;
#else
#define arch_page_size ((usize)(0x1000))
#define arch_page_size ((usize)(ARCH_DEFAULT_PAGE_SIZE))
#endif

// CPU-local information.
typedef struct [[gnu::aligned(arch_page_size)]] Cpu
typedef struct [[gnu::aligned(ARCH_DEFAULT_PAGE_SIZE)]] CpuInfo
{
usize id; // Unique ID of this CPU.
usize kernel_stack; // Stack pointer for the kernel.
usize user_stack; // Stack pointer for the user space.
struct Thread* thread; // Current thread running on this CPU.
usize ticks_active; // The amount of ticks the running thread has been active.
bool is_present; // If the CPU is present.
InterruptFn irq_handlers[256]; // IRQ handlers.
void* irq_data[256]; // IRQ context to pass along.
usize id; // Unique ID of this CPU.
usize kernel_stack; // Stack pointer for the kernel.
usize user_stack; // Stack pointer for the user space.
struct Thread* thread; // Current thread running on this CPU.
usize ticks_active; // The amount of ticks the running thread has been active.
bool is_present; // If the CPU is present.

#ifdef __x86_64__
Gdt gdt;
TaskStateSegment tss;
u32 lapic_id; // Local APIC ID.
usize fpu_size; // Size of the FPU in bytes.
void (*fpu_save)(void* dst); // Function to call when saving the FPU state.
void (*fpu_restore)(void* dst); // Function to call when restoring the FPU state.
Gdt gdt; // Global Descriptor Table.
TaskStateSegment tss; // Task state segment.
IdtCallbackFn idt_callbacks[IDT_SIZE]; // IDT handlers.
Irq idt_to_irq_map[IDT_SIZE]; // ISR to IRQ mapping.
u32 lapic_id; // Local APIC ID.
usize fpu_size; // Size of the FPU in bytes.
void (*fpu_save)(void* dst); // Function to call when saving the FPU state.
void (*fpu_restore)(void* dst); // Function to call when restoring the FPU state.
#elif defined(__riscv) && (__riscv_xlen == 64)
u32 hart_id; // Hart CPU ID.
#endif
Expand Down Expand Up @@ -65,7 +65,8 @@ bool arch_stop_cpu(usize id);
// Writes all registers to the current output stream.
void arch_dump_registers(Context* regs);

// Gets processor metadata.
// Gets processor local information.
// ! Extremely unsafe to call directly, 99% of the time you want to call sch_acquire_percpu()!
CpuInfo* arch_current_cpu();

typedef enum : usize
Expand Down
51 changes: 41 additions & 10 deletions include/menix/system/interrupts.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
// Function prototypes for interrupts
// Interrupt handling

#pragma once

#include <menix/common.h>
#include <menix/util/list.h>

typedef usize Irq;
typedef struct Context Context;
typedef Context* (*InterruptFn)(usize isr, Context* regs, void* priv);

// Handler called by the processor.
Context* int_handler(usize isr, Context* regs);
typedef enum
{
IrqIgnored = 0, // Interrupt was not handled.
IrqHandled = (1 << 0), // Handler completed the IRQ work.
IrqWake = (1 << 1), // Handler wants to wake up the handler thread.
} IrqStatus;

// Registers a new IRQ handler. Automatically selects optimal IRQ placement.
// You can also pass an additional parameter for context passed to the handler.
// Returns `true` upon success.
bool irq_allocate_handler(InterruptFn handler, void* data);
typedef enum
{
IrqFlags_None = 0,
} IrqFlags;

// Internal function to register an interrupt handler at a specific ISR index on the current CPU.
bool isr_register_handler(usize cpu, usize idx, InterruptFn handler, void* data);
// An IRQ handler callback.
typedef IrqStatus (*IrqHandlerFn)(Irq irq, void* context);

typedef struct IrqAction
{
struct IrqAction* next; // Next action in the list.
Irq irq; // The IRQ number.
IrqFlags flags; // Flags for this action.
IrqHandlerFn handler; // Called directly to handle the IRQ.
IrqHandlerFn worker; // Function to call in a worker thread, if woken up by the handler.
struct Thread* thread; // The thread to execute the worker function on.
const char* name; // Name of the IRQ.
void* context; // A generic context to pass to the handler.
} IrqAction;

extern IrqAction* irq_actions;

// Platform independent handler that runs the given IRQ. To be called by architecture specific interrupt handlers.
void irq_generic_handler(Irq irq);

// Registers a new IRQ handler. Automatically selects optimal CPU placement.
// Returns true upon success.
// `handler`: The main interrupt handler. Must not be NULL.
// `thread_handler`: The threaded handler. Optional.
// `flags`: Flags to control how this IRQ handler behaves.
// `name`: Name of this IRQ.
// `data`: Context to pass to the IRQ on invocation.
bool irq_allocate(IrqHandlerFn handler, IrqHandlerFn thread_handler, IrqFlags flags, const char* name, void* data);
3 changes: 3 additions & 0 deletions include/menix/system/sch/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ typedef struct Process

#define PROC_USER_INTERP_BASE 0x0000060000000000

// The kernel process.
extern Process* proc_kernel;

// Creates a new process. Returns a reference to the newly created process.
// `name`: Name of the process.
// `state`: Which state the process should be initialized with.
Expand Down
8 changes: 6 additions & 2 deletions include/menix/system/sch/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@

extern Process* proc_list;
extern Process* hanging_proc_list;

extern Thread* thread_list;
extern Thread* hanging_thread_list;

extern Thread* sleeping_thread_list;

// Halts preemption of the current thread by the scheduler.
void sch_stop_preemption();

// Continues preemption of the current thread.
void sch_start_preemption();

// Initializes the scheduler.
void sch_init(VirtAddr entry_point);

Expand Down
5 changes: 4 additions & 1 deletion include/menix/system/sch/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ void thread_set_errno(usize errno);

// Creates a new thread in a process.
// `parent`: The parent process of the new thread.
Thread* thread_create(Process* parent);
Thread* thread_new(Process* parent);

// Creates a new kernel thread.
Thread* thread_create_kernel(Process* parent, VirtAddr start);

// Sets up the context of a user thread.
// `target`: The thread to set up.
Expand Down
2 changes: 1 addition & 1 deletion kernel/arch/x86_64/include/apic.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ void lapic_write(u32 register, u32 value);
usize lapic_get_id();

// Handles an interrupt triggered by the timer.
Context* timer_handler(usize isr, Context* regs, void* data);
Context* timer_handler(usize isr, Context* regs);
1 change: 1 addition & 0 deletions kernel/arch/x86_64/include/bits/arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <menix/common.h>

#include <gdt.h>
#include <idt.h>
#include <tss.h>

// CPUID Leaf 1 ECX
Expand Down
4 changes: 2 additions & 2 deletions kernel/arch/x86_64/include/bits/arch_defs.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma once

#define ARCH_BITS 64
#define ARCH_MAX_PAGE_SIZE 0x1000
#define ARCH_BITS 64
#define ARCH_DEFAULT_PAGE_SIZE 0x1000
9 changes: 6 additions & 3 deletions kernel/arch/x86_64/include/idt.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#include <gdt.h>

#define IDT_MAX_SIZE 256
#define IDT_SIZE 256
#define IDT_GATE_INT 0xE
#define IDT_GATE_TRAP 0xF
#define IDT_TYPE(priv, gate) ((1 << 7) | (((priv) & 0x3) << 0x5) | ((gate) & 0xF))
Expand All @@ -16,15 +16,15 @@ typedef struct [[gnu::packed]]
{
u16 base_0_15;
u16 selector;
#if ARCH_BITS >= 64
#ifdef __x86_64__
Bits ist:2;
Bits reserved:6;
#else
Bits reserved:8;
#endif
u8 type;
u16 base_16_31;
#if ARCH_BITS >= 64
#ifdef __x86_64__
u32 base_32_63;
u32 reserved2;
#endif
Expand All @@ -41,6 +41,9 @@ typedef struct [[gnu::packed]]

static_assert(sizeof(IdtRegister) == 10);

typedef struct Context Context;
typedef Context* (*IdtCallbackFn)(usize isr, Context* regs);

// Install the Interrupt Descriptor Table.
void idt_init();

Expand Down
8 changes: 4 additions & 4 deletions kernel/arch/x86_64/include/interrupts.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <menix/system/arch.h>

Context* interrupt_debug_handler(usize isr, Context* regs, void* data);
Context* interrupt_ud_handler(usize isr, Context* regs, void* data);
Context* interrupt_pf_handler(usize isr, Context* regs, void* data);
Context* syscall_handler(usize isr, Context* regs, void* data);
Context* interrupt_debug_handler(usize isr, Context* regs);
Context* interrupt_ud_handler(usize isr, Context* regs);
Context* interrupt_pf_handler(usize isr, Context* regs);
Context* syscall_handler(usize isr, Context* regs);
6 changes: 4 additions & 2 deletions kernel/arch/x86_64/memory/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,12 @@ Context* interrupt_pf_handler(usize isr, Context* regs, void* data)
panic();
}

CpuInfo* info = arch_current_cpu();

// TODO: Handle user page fault.
if (arch_current_cpu())
if (info)
{
Process* proc = arch_current_cpu()->thread->parent;
Process* proc = info->thread->parent;

// If nothing can make the process recover, we have to put it out of its misery.
proc_kill(proc, true);
Expand Down
11 changes: 7 additions & 4 deletions kernel/arch/x86_64/sch/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <menix/memory/vm.h>
#include <menix/system/arch.h>
#include <menix/system/sch/process.h>
#include <menix/system/sch/scheduler.h>
#include <menix/system/sch/thread.h>

#include <gdt.h>
Expand Down Expand Up @@ -77,19 +78,21 @@ void thread_arch_setup(Thread* target, VirtAddr start, bool is_user, VirtAddr st
void thread_arch_destroy(Thread* thread)
{
kfree((void*)(thread->kernel_stack - VM_KERNEL_STACK_SIZE));
pm_free(thread->saved_fpu - pm_get_phys_base(), ROUND_UP(arch_current_cpu()->fpu_size, arch_page_size));
CpuInfo* info = arch_current_cpu();
pm_free(thread->saved_fpu - pm_get_phys_base(), ROUND_UP(info->fpu_size, arch_page_size));
}

void thread_arch_fork(Thread* forked, Thread* original)
{
forked->fs_base = original->fs_base;
forked->gs_base = original->gs_base;

CpuInfo* cpu = arch_current_cpu();

// Allocate FPU memory.
PhysAddr fpu_pages = pm_alloc(ROUND_UP(arch_current_cpu()->fpu_size, vm_get_page_size(VMLevel_Small)));
PhysAddr fpu_pages = pm_alloc(ROUND_UP(cpu->fpu_size, vm_get_page_size(VMLevel_Small)));
forked->saved_fpu = pm_get_phys_base() + fpu_pages;

memcpy(forked->saved_fpu, original->saved_fpu, arch_current_cpu()->fpu_size);
memcpy(forked->saved_fpu, original->saved_fpu, cpu->fpu_size);

// Return SYSCALL_OK(0) to the forked syscall process.
forked->registers.rax = 0;
Expand Down
5 changes: 4 additions & 1 deletion kernel/arch/x86_64/system/apic.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <io.h>
#include <pic.h>

#include "menix/util/log.h"

static PhysAddr lapic_addr = 0;
static bool has_x2apic = 0;
u32 apic_ticks_in_10ms = 0;
Expand Down Expand Up @@ -70,6 +72,7 @@ void lapic_init(usize cpu_id)
else
{
// TODO
print_error("x86_64: No X2APIC found, this is currently unsupported!\n");
return;
}

Expand Down Expand Up @@ -115,7 +118,7 @@ void apic_send_eoi()
lapic_write(0xB0, 0);
}

Context* timer_handler(usize isr, Context* regs, void* data)
Context* timer_handler(usize isr, Context* regs)
{
Context* new_context = sch_reschedule(regs);
apic_send_eoi();
Expand Down
4 changes: 2 additions & 2 deletions kernel/arch/x86_64/system/arch.s
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ arch_int_\+:
mov $\+, %rdi /* Load the ISR as first argument */
mov %rsp, %rsi /* Load the Context* as second argument. */
xor %rbp, %rbp
.extern int_handler
call int_handler
.extern idt_dispatcher
call idt_dispatcher
mov %rax, %rsp /* int_handler returns a pointer to the new context. */
pop_all_regs
swapgs_if_necessary
Expand Down
50 changes: 40 additions & 10 deletions kernel/arch/x86_64/system/idt.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <menix/memory/mmio.h>
#include <menix/system/arch.h>
#include <menix/system/interrupts.h>
#include <menix/system/sch/scheduler.h>
#include <menix/util/log.h>

#include <apic.h>
Expand All @@ -12,7 +13,7 @@
#include <io.h>
#include <pic.h>

[[gnu::aligned(0x1000)]] static IdtDesc idt_table[IDT_MAX_SIZE];
[[gnu::aligned(0x1000)]] static IdtDesc idt_table[IDT_SIZE];
[[gnu::aligned(0x10)]] static IdtRegister idtr;

void idt_set(u8 idx, void* handler, u8 type_attr)
Expand All @@ -31,27 +32,56 @@ void idt_set(u8 idx, void* handler, u8 type_attr)
#endif
}

// Directly called by the IDT handler.
// Referenced by arch.s
Context* idt_dispatcher(usize isr, Context* regs)
{
CpuInfo* cpu = arch_current_cpu();

Context* result = regs;
// If the IDT slot is occupied, call that function directly.
if (likely(cpu->idt_callbacks[isr]))
result = cpu->idt_callbacks[isr](isr, regs);
// If it is not, call the generic handler instead.
else
irq_generic_handler(cpu->idt_to_irq_map[isr]);

return result;
}

// Deliberately does nothing.
static Context* idt_noop(usize isr, Context* regs)
{
return regs;
}

void idt_reload()
{
const usize cpu = arch_current_cpu()->id;
sch_stop_preemption();

// Set IRQ handlers for known ISRs (Exceptions, timer, syscall) on this core.
isr_register_handler(cpu, 0x3, interrupt_debug_handler, NULL);
isr_register_handler(cpu, 0x6, interrupt_ud_handler, NULL);
isr_register_handler(cpu, 0xE, interrupt_pf_handler, NULL);
isr_register_handler(cpu, INT_TIMER, timer_handler, NULL);
isr_register_handler(cpu, INT_SYSCALL, syscall_handler, NULL);
CpuInfo* cpu = arch_current_cpu();

// Set known ISRs (Exceptions, timer, syscall) on this core.
for (usize i = 0; i < 32; i++)
cpu->idt_callbacks[i] = idt_noop; // Exceptions
cpu->idt_callbacks[0x3] = interrupt_debug_handler;
cpu->idt_callbacks[0x6] = interrupt_ud_handler;
cpu->idt_callbacks[0xE] = interrupt_pf_handler;
cpu->idt_callbacks[INT_TIMER] = timer_handler;
cpu->idt_callbacks[INT_SYSCALL] = syscall_handler;

idtr.limit = sizeof(idt_table) - 1; // Limit is the last entry, not total size.
idtr.base = idt_table;
asm volatile("lidt %0" ::"m"(idtr));

sch_start_preemption();
}

extern void* arch_int_table[IDT_MAX_SIZE];
extern void* arch_int_table[IDT_SIZE];

void idt_init()
{
for (usize i = 0; i < IDT_MAX_SIZE; i++)
for (usize i = 0; i < IDT_SIZE; i++)
{
idt_set(i, arch_int_table[i], IDT_TYPE(0, IDT_GATE_INT));
}
Expand Down
Loading

0 comments on commit 4962e56

Please sign in to comment.