-
Notifications
You must be signed in to change notification settings - Fork 0
Interrupt Handling
JNode routes hardware interrupts from x86 CPU exceptions and external devices through assembly handlers into Java-level IRQ threads managed by the scheduler.
JNode handles two distinct categories of interrupts: CPU exceptions (traps and faults from the processor) and hardware IRQs (external devices like timers, keyboards, and disk controllers). Both travel through the x86 Interrupt Descriptor Table (IDT), but they take different paths to reach Java code.
The IDT is set up in ints.asm using the intport macro. Hardware IRQs are routed through the 8259A PIC (or Local APIC on newer systems) and dispatched by IRQManager to registered Java IRQHandler implementations. CPU exceptions (division by zero, page faults, etc.) are caught in ints.asm and converted to Java exceptions via VmThread exception constants.
| Class / File | Role |
|---|---|
core/src/native/x86/ints.asm |
IDT construction, CPU exception handlers, PIC initialization |
core/src/native/x86/vm-ints.asm |
IRQ handlers, yieldpoint/trap handlers, FPU not-available handler |
core/src/core/org/jnode/vm/x86/X86IRQManager.java |
x86-specific IRQ dispatch with PIC/APIC EOI support |
core/src/core/org/jnode/vm/scheduler/IRQManager.java |
IRQ claim/release, interrupt dispatch to IRQ threads |
core/src/core/org/jnode/vm/scheduler/IRQThread.java |
Per-IRQ high-priority thread that runs IRQHandler.handleInterrupt()
|
core/src/core/org/jnode/system/resource/IRQHandler.java |
Interface implemented by device drivers to handle IRQs |
core/src/core/org/jnode/vm/x86/PIC8259A.java |
8259A PIC wrapper for legacy IRQ routing and EOI |
core/src/core/org/jnode/vm/x86/LocalAPIC.java |
Local APIC for SMP and modern IRQ routing |
The IDT is constructed in ints.asm via the Lsetup_idt function. Each entry is created using the intport macro, which defines the gate type (trap vs interrupt) and privilege level. The key entries are:
; CPU exceptions
int_noerror int_div, 0 ; Division by zero
int_error int_pf, 14 ; Page fault
; Hardware IRQs (remapped to 0x20-0x2F)
int_irq 0, timer_handler ; IRQ0 = timer
int_irq 1, def_irq_handler ; keyboard
; ...
; Software interrupts
intport 0x30, yieldPointHandler, 3 ; Yieldpoint (INT 0x30)
intport 0x31, int_stack_overflow, 3
intport 0x33, timesliceHandler, 3 ; Timeslice (INT 0x33)The PIC is reprogrammed to remap IRQs 0-15 to vectors 0x20-0x2F, so they don't conflict with CPU exceptions (vectors 0-31).
When a CPU exception occurs in user mode, the handler checks the CS register to distinguish kernel vs user mode. In user mode, it uses the SYSTEM_EXCEPTION macro to save the exception state and redirect execution to doSystemException, which calls SoftByteCodes.systemException to throw a Java exception:
int_div:
cmp GET_OLD_CS, USER_CS
jne int_die
SYSTEM_EXCEPTION VmThread_EX_DIV0, GET_OLD_EIP
retKernel-mode exceptions and unimplemented exceptions jump to int_die, which prints a message and halts.
The IRQ handling chain is:
Hardware interrupt (PIC/APIC)
→ IDT gate → irq0-15 handler in ints.asm
→ def_irq_handler / timer_handler in vm-ints.asm
→ Set TSI_SWITCH_NEEDED, increment IRQ counter
→ Return to Java context
Later (on yieldpoint/trap):
→ VmProcessor.reschedule() is called
→ VmScheduler calls IRQManager.dispatchInterrupts()
→ For each IRQ with a registered handler:
→ IRQThread.signalIRQ() is called
→ IRQThread wakes up at MAX_PRIORITY
→ IRQThread.run() calls IRQHandler.handleInterrupt(irq)
→ EOI is sent to PIC/APIC
IRQManager.claimIRQ() registers a handler and starts an IRQThread:
IRQThread newThread = new IRQThread(this, irq, owner, handler, shared, defaultIrqProcessor);
handlers[irq] = newThread;
newThread.start();Each IRQThread runs at Thread.MAX_PRIORITY and waits until signalIRQ() is called. The dispatch cycle:
-
IRQManager.dispatchInterrupts()is called by the scheduler (at yieldpoint) - It iterates all IRQs, calling
IRQThread.signalIRQ(irqCount[irq], current) -
signalIRQ()incrementsirqCountand callsvmThread.unsecureResume()if needed - The
IRQThreadwakes up and callshandleInterrupt(irq)for all registered handlers - After handling,
IRQManager.eoi(irq)sends End-Of-Interrupt to the PIC/APIC
The 8259A PIC requires an EOI command after each IRQ:
// Master IRQ
io8259_A.outPortByte(0x20, 0x60 + irq);
// Slave IRQ (8-15)
io8259_B.outPortByte(0xA0, 0x60 + irq - 8);
io8259_A.outPortByte(0x20, 0x60 + 2); // Cascade EOILocal APIC uses a memory-mapped write:
mem.setInt(REG_EOI, 0);The INT 0x30 and INT 0x33 software interrupts are used for scheduling:
-
INT 0x30 (yieldpoint): Injected by JIT at method prologues and branches. Handler saves thread state and calls
VmProcessor.reschedule(). -
INT 0x33 (timeslice): Broadcast to all CPUs via Local APIC IPI. Sets
TSI_SWITCH_NEEDEDto trigger preemption.
Both are defined in ints.asm:
intport 0x30, yieldPointHandler, 3
intport 0x33, timesliceHandler, 3The int_dev_na handler in vm-ints.asm handles the "Device Not Available" exception (CR0.TS set). It restores FPU/XMM state for the current thread and clears CR0.TS, enabling lazy FPU context switching.
-
IRQ handlers run with interrupts disabled:
IRQHandler.handleInterrupt()is called from the kernel with interrupts disabled. Keep the handler short — no locking, no blocking. All heavy work is deferred to theIRQThreadwhich runs normally. - Lazy FPU saving: CR0.TS is set on thread switch. The FPU state is only restored when the thread actually uses an FPU/SSE instruction, minimizing save/restore overhead.
- PIC remapping: The PIC is reprogrammed at boot to map IRQs 0-15 to vectors 0x20-0x2F. This is non-negotiable — CPU exceptions occupy 0x00-0x1F and must not overlap.
-
IRQ threads vs handler: The
IRQHandlerinterface is just the dispatch hook. The actual work runs inIRQThread.run()at MAX_PRIORITY after the scheduler signals the IRQ. -
No shared IRQs by default:
claimIRQ()throwsResourceNotFreeExceptionif the IRQ is already claimed unlessshared=trueis passed. Shared IRQs chain multiple handlers viaIRQAction. - EOI order for cascaded IRQs: When handling IRQ 8-15 (slave PIC), EOI must be sent to both the slave and the master cascade line (IRQ 2).
-
Timeslice broadcast:
broadcastTimeSliceInterrupt()sends an IPI to all CPUs except self usingICR_DESTINATION_SHORTHAND_ALL_EX_SELF. This ensures SMP preemption fairness. - No nested interrupts: JNode runs with interrupts disabled during critical sections. Nested hardware interrupts are not supported — the IDT gates have appropriate privilege levels to prevent re-entry.
- Core-Thread-Scheduling — How timer interrupts and yieldpoints drive scheduling
- TSI — Thread Switch Indicator atomic flags that are set/modified by interrupt handlers
-
Assembly-Files — The role of
ints.asmandvm-ints.asmin the assembly layer - Architecture — System layers and where interrupt handling fits
- Driver-Framework — How drivers register IRQ handlers via the device manager
- Boot-Sequence — Where IDT setup happens during boot