Hi,
Mercury1964 wrote:On what you said about common interrupt handlers: should I write each for just the original 32 CPU interrupts or every interrupt? The thought of writing individual handlers hadn't occurred to me, but it makes a lot more sense than a common handler
.
I'd start by writing a general purpose "kernelPanic(optionalData, reason, flags)" function. This would be used when the kernel detects that something is "unrecoverably wrong". For example, if the kernel tries to free a physical page and detects the page is already free, or tries to release a lock that was never acquired, or tries to use an invalid pointer, or whatever else. You could even implement some sort of "kernelAssert(condition, reason, flags)" that would be used just like "assert()" (but less crappy). Initially "kernelPanic()" might just display the details on the screen (e.g. "blue screen of death") but later on it could do other things (e.g. store details somewhere and restart the OS, send the details to a remote machine, etc).
Once that's done; I'd have 32 separate IDT entries for each exception; with different assembly stubs that all just call the "kernelPanic(optionalData, reason, flags)" function for now. This will change later. Also, if the kernel is going to use a software interrupt for its API (e.g. "int 0x80") install that too - it can be a generic thing that returns "E_UNSUPPORTED_FUNCTION" for now (but eventually the assembly stub would do something like "call [kernelAPIfunctionPointerTable + eax*4]").
Next; determine if there are multiple CPUs or not. If there are multiple CPUs install the IDT entries that you'll use for IPIs. The IPI handlers are typically very tiny and don't need any C/C++ at all (e.g. one might just to an INVLPG instruction and nothing else).
Next; write code to determine (e.g. during boot):
- if the kernel will be using the local APIC (e.g. if a local APIC exists)
- if the kernel will be using PIC chips or IO APIC
- what the kernel will be using for timing (RTC, PIT, HPET, ACPI timer, local APIC, TSC)
- how the kernel will handle FPU errors. Note: for 80486 and later the kernel should enable and use "native FPU error" (where FPU errors are delivered as a normal exception and do not go through the PIC chip's IRQ13).
If the kernel has decided to use local APIC/s, then setup an IDT entry for the local APIC's "spurious IRQ"; plus any other IRQs that come from the local APIC that the kernel will be using (local APIC timer, performance monitoring, thermal monitoring, etc).
If the kernel has decided to use the PIC chips (e.g. there are no IO APICs) then install the PIC chip IRQ handlers; such that:
- IRQ 0 (PIT) = special IRQ handler for kernel if PIT is used by kernel, else IDT entry left as "not present" and PIT chip IRQ left masked in PIC
- IRQ 8 (RTC) = special IRQ handler for kernel if RTC is used by kernel, else IDT entry left as "not present" and RTC's IRQ left masked in PIC
- IRQ 13 (FPU error) = special IRQ handler for kernel if "legacy FPU errors" used by kernel, else IDT entry left as "not present" and IRQ13 left masked in PIC
- IRQ 2 (cascade) = IDT entry left as "not present"
- All other PIC chip IRQs get handled by a generic "common PIC IRQ handler". Note that the assembly stubs for IRQ 7 and 15 should detect if the IRQ is a spurious IRQ or not, and if it's not spurious then call the "common PIC IRQ handler", or if it is spurious increment some sort of "number of spurious IRQs" counter instead of calling the "common PIC IRQ handler".
In this case most of your IDT entries (e.g. about 200 of them) won't be used and will be left as "not present".
If the kernel has decided to use the IO APIC/s (instead of PIC chips) then:
- Install the PIC chip's spurious IRQ handlers (sadly, even with everything masked in the PIC these can still occur). In this case the assembly handling stubs don't need code to determine if the IRQ is spurious or not and they can just increment some sort of "number of spurious IRQs" counter
- All remaining IRQs get handled by a generic "common IO APIC IRQ handler" (which is not the same as the "common PIC IRQ handler"). This can be done as a loop that checks if each IDT entry is still "not present" in the IDT and replaces them if they are (so you don't need to care much what IDT entries previous kernel initialisation code decided to use)
In this case most of your IDT entries (e.g. about 200 of them) will go to the "common IO APIC IRQ handler".
Note that for a tutorial it's likely that you will cut corners (e.g. only support the PIC chips). In this case I'd still recommend using the general approach described above, but leaving comments in the code for things you skipped. For example; instead of detecting if the kernel will use IO APIC or not you'd have a comment ("// Insert IO APIC detection code here!"), and instead of having code to configure the IDT for IO APIC you'd have a comment ("// Insert code to install IO APIC interrupts here!").
Do not make it impossible for the reader to determine how things should be done in a real OS by simplifying the tutorial too much (e.g. if you have a single "init IDT" function that makes a whole pile of assumptions, then you've created an "anti-tutorial" that teaches the reader how to do things wrong).
Cheers,
Brendan