Refine command execution and enhance documentation. Improved the CommandTaskContext structure for better task management and updated README.md with clearer instructions for 'memtest' and 'tasktest' commands.
This commit is contained in:
269
docs/interrupts-and-exceptions.md
Normal file
269
docs/interrupts-and-exceptions.md
Normal file
@@ -0,0 +1,269 @@
|
||||
## Interrupt and exception handling overview
|
||||
|
||||
The kernel's interrupt/exception handling is implemented in `idt.c` and a companion assembly file `isr.S` (not shown here). The design goals are:
|
||||
|
||||
- Preserve the firmware's existing hardware interrupt handlers where possible.
|
||||
- Override CPU **exception vectors 0–31** with kernel stubs that route into a C dispatcher.
|
||||
- Print detailed diagnostics for exceptions and halt on unrecoverable faults.
|
||||
|
||||
---
|
||||
|
||||
## IDT representation
|
||||
|
||||
The x86-64 Interrupt Descriptor Table (IDT) is represented as an array of packed 16-byte entries:
|
||||
|
||||
```22:37:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
typedef struct {
|
||||
UINT16 offset_low; /* bits 0-15 of handler address */
|
||||
UINT16 selector; /* code segment selector */
|
||||
UINT8 ist; /* interrupt stack table index */
|
||||
UINT8 type_attr; /* type and attributes */
|
||||
UINT16 offset_mid; /* bits 16-31 of handler address */
|
||||
UINT32 offset_high; /* bits 32-63 of handler address */
|
||||
UINT32 zero; /* reserved, must be zero */
|
||||
} __attribute__((packed)) IdtEntry;
|
||||
|
||||
typedef struct {
|
||||
UINT16 limit;
|
||||
UINT64 base;
|
||||
} __attribute__((packed)) IdtPtr;
|
||||
```
|
||||
|
||||
Module state:
|
||||
|
||||
```42:47:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static IdtEntry idt[IDT_SIZE];
|
||||
static BootInfo *gBoot = NULL;
|
||||
|
||||
/* Defined in isr.S – one stub function per vector (0-255). */
|
||||
extern void (*isr_stub_table[])(void);
|
||||
```
|
||||
|
||||
Each element of `isr_stub_table` is a low-level assembly stub that:
|
||||
|
||||
- Saves CPU state into an `ISRFrame`.
|
||||
- Calls the common C dispatcher `isr_handler`.
|
||||
- Restores state and performs `iretq` (for IRQs) or loops into a halt (for fatal faults).
|
||||
|
||||
---
|
||||
|
||||
## Installing IDT entries
|
||||
|
||||
`idt_set_gate` encodes a handler function pointer into an IDT entry:
|
||||
|
||||
```53:67:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static void idt_set_gate(UINTN index, void (*handler)(void))
|
||||
{
|
||||
UINT64 addr = (UINT64)(UINTN)handler;
|
||||
UINT16 selector = 0;
|
||||
|
||||
__asm__ __volatile__("mov %%cs, %0" : "=r"(selector));
|
||||
|
||||
idt[index].offset_low = (UINT16)(addr & 0xFFFF);
|
||||
idt[index].selector = selector;
|
||||
idt[index].ist = 0;
|
||||
idt[index].type_attr = IDT_TYPE_INTERRUPT;
|
||||
idt[index].offset_mid = (UINT16)((addr >> 16) & 0xFFFF);
|
||||
idt[index].offset_high = (UINT32)((addr >> 32) & 0xFFFFFFFF);
|
||||
idt[index].zero = 0;
|
||||
}
|
||||
```
|
||||
|
||||
`lidt` loads a new IDTR:
|
||||
|
||||
```69:73:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static void lidt(const IdtPtr *idtr)
|
||||
{
|
||||
__asm__ __volatile__("lidt (%0)" :: "r"(idtr));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exception names
|
||||
|
||||
For better diagnostics, the kernel maps exception vectors to human-readable names:
|
||||
|
||||
```80:116:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static const CHAR16 *exception_name(UINTN vector)
|
||||
{
|
||||
switch (vector) {
|
||||
case 0: return L"Divide Error";
|
||||
case 1: return L"Debug";
|
||||
...
|
||||
case 14: return L"Page Fault";
|
||||
...
|
||||
case 30: return L"Security";
|
||||
case 31: return L"Reserved";
|
||||
default: return L"Unknown";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These strings are used by `isr_handler` when printing exception banners.
|
||||
|
||||
---
|
||||
|
||||
## PIC helpers and halting
|
||||
|
||||
The kernel provides minimal support for acknowledging legacy PIC interrupts and halting the CPU in fatal cases:
|
||||
|
||||
```124:136:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static inline void outb(UINT16 port, UINT8 value)
|
||||
{
|
||||
__asm__ __volatile__("outb %0, %1" :: "a"(value), "Nd"(port));
|
||||
}
|
||||
|
||||
static void pic_eoi(UINTN vector)
|
||||
{
|
||||
if (vector >= 40) {
|
||||
outb(0xA0, 0x20); /* EOI to slave PIC */
|
||||
}
|
||||
outb(0x20, 0x20); /* EOI to master PIC */
|
||||
}
|
||||
```
|
||||
|
||||
`halt_forever` disables interrupts and executes `hlt` in an infinite loop:
|
||||
|
||||
```138:144:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
static void halt_forever(void)
|
||||
{
|
||||
for (;;) {
|
||||
__asm__ __volatile__("cli; hlt");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ISR dispatcher (`isr_handler`)
|
||||
|
||||
`isr_handler` is the central C function that receives control from all exception and interrupt stubs:
|
||||
|
||||
```150:177:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
void isr_handler(ISRFrame *frame)
|
||||
{
|
||||
UINT64 cr2 = 0;
|
||||
|
||||
/* Hardware IRQs (vectors 32-47): send EOI and return */
|
||||
if (frame->vector >= 32 && frame->vector <= 47) {
|
||||
pic_eoi(frame->vector);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CPU exceptions (vectors 0-31): print diagnostics and halt */
|
||||
if (gBoot != NULL && gBoot->print != NULL) {
|
||||
gBoot->print(L"\n\rEXCEPTION: %d (%s)\n\r", frame->vector,
|
||||
exception_name(frame->vector));
|
||||
gBoot->print(L" Error Code: 0x%lx\n\r", frame->error_code);
|
||||
gBoot->print(L" RIP: 0x%lx CS: 0x%lx RFLAGS: 0x%lx\n\r",
|
||||
frame->rip, frame->cs, frame->rflags);
|
||||
}
|
||||
|
||||
if (frame->vector == 14) {
|
||||
__asm__ __volatile__("mov %%cr2, %0" : "=r"(cr2));
|
||||
if (gBoot != NULL && gBoot->print != NULL) {
|
||||
gBoot->print(L" CR2: 0x%lx\n\r", cr2);
|
||||
}
|
||||
}
|
||||
|
||||
halt_forever();
|
||||
}
|
||||
```
|
||||
|
||||
The `ISRFrame` structure (defined in `idt.h`) contains the state saved by the assembly stubs, including:
|
||||
|
||||
- Exception/interrupt vector number.
|
||||
- Error code (if applicable).
|
||||
- `RIP`, `CS`, and `RFLAGS` at the time of the fault.
|
||||
|
||||
The handler's behaviour is:
|
||||
|
||||
- For **hardware IRQs** (vectors 32–47):
|
||||
- Send an **End Of Interrupt** (EOI) to the PIC.
|
||||
- Return to the interrupted context.
|
||||
- For **CPU exceptions** (vectors 0–31):
|
||||
- Print a diagnostic header with the vector number and name.
|
||||
- Show error code and execution context (`RIP`, `CS`, `RFLAGS`).
|
||||
- For page faults (vector 14), read and print CR2 (faulting virtual address).
|
||||
- Halt the system via `halt_forever`.
|
||||
|
||||
This makes debugging faults significantly easier when running under QEMU or on real hardware.
|
||||
|
||||
---
|
||||
|
||||
## IDT initialisation (`idt_init`)
|
||||
|
||||
`idt_init` is called from `kmain` early during boot:
|
||||
|
||||
```183:214:/home/lochlan/Documents/Coding/c/os/idt.c
|
||||
void idt_init(BootInfo *Boot)
|
||||
{
|
||||
IdtPtr old_idtr;
|
||||
IdtPtr idtr;
|
||||
IdtEntry *old_idt = NULL;
|
||||
UINTN i = 0;
|
||||
|
||||
gBoot = Boot;
|
||||
|
||||
/* Read the firmware's existing IDT so we can preserve its entries */
|
||||
__asm__ __volatile__("sidt %0" : "=m"(old_idtr));
|
||||
old_idt = (IdtEntry *)(UINTN)old_idtr.base;
|
||||
|
||||
/* Copy the entire existing IDT first (preserves firmware IRQ handlers) */
|
||||
for (i = 0; i < IDT_SIZE; i++) {
|
||||
if (old_idt != NULL && (i * sizeof(IdtEntry)) < (UINTN)(old_idtr.limit + 1)) {
|
||||
idt[i] = old_idt[i];
|
||||
} else {
|
||||
idt_set_gate(i, isr_stub_table[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Override only CPU exception vectors (0-31) with our handlers */
|
||||
for (i = 0; i < 32; i++) {
|
||||
idt_set_gate(i, isr_stub_table[i]);
|
||||
}
|
||||
|
||||
idtr.limit = (UINT16)(sizeof(idt) - 1);
|
||||
idtr.base = (UINT64)(UINTN)idt;
|
||||
|
||||
lidt(&idtr);
|
||||
}
|
||||
```
|
||||
|
||||
The process is:
|
||||
|
||||
1. **Capture firmware IDT**:
|
||||
- `sidt` reads the current IDTR into `old_idtr`.
|
||||
- `old_idt` is set to the base of the firmware's IDT.
|
||||
2. **Copy firmware entries**:
|
||||
- For all indices `i` where the address lies within the firmware's IDT limit, copy the existing entry into the kernel's `idt` array.
|
||||
- For indices beyond the firmware's limit, install the kernel's own stub from `isr_stub_table`.
|
||||
3. **Override CPU exceptions**:
|
||||
- For vectors 0–31, call `idt_set_gate` with the kernel's stubs, ensuring that exceptions are always handled by `isr_handler`.
|
||||
4. **Activate new IDT**:
|
||||
- Populate `idtr` with the address and size of the kernel's `idt`.
|
||||
- Call `lidt` to load the new IDT.
|
||||
|
||||
This approach preserves any firmware-installed handlers for higher interrupt vectors (e.g., hardware IRQs or system-specific events), while guaranteeing full control over CPU exception handling.
|
||||
|
||||
---
|
||||
|
||||
## Interaction with the rest of the kernel
|
||||
|
||||
The IDT/ISR subsystem interacts with other parts of the kernel in the following ways:
|
||||
|
||||
- **BootInfo access**:
|
||||
- `id_tinit` stores `Boot` in `gBoot` so that `isr_handler` can safely use `Boot->print` for diagnostics.
|
||||
- **Memory subsystem**:
|
||||
- `isr_handler` reads CR2 for page faults; combined with `paging_get_phys` from `memory.c`, this can be used to inspect paging state.
|
||||
- **Tasks and scheduler**:
|
||||
- The current implementation is **non-preemptive**: context switches happen only through explicit calls to `task_yield`, not timer interrupts. Exceptions still interrupt tasks asynchronously, but there is no timer tick driving the scheduler.
|
||||
- **User-level diagnostics**:
|
||||
- When an exception occurs, the on-screen diagnostics provide enough context to identify the type of fault and its location in the kernel, especially when used alongside a symbol-enabled build and external debugger.
|
||||
|
||||
Future extensions might include:
|
||||
|
||||
- Installing a periodic timer IRQ handler that calls `task_yield` to add preemptive scheduling.
|
||||
- Extending `ISRFrame` and `isr_handler` with richer diagnostics or a kernel debugger stub.
|
||||
|
||||
Reference in New Issue
Block a user