Dsafds Dsafds - 4 months ago 16
C++ Question

IRQ Handler Triple Fault

Currently my IRQ is triple faulting and giving the division by 0 error. Here is the viedo i recorded that will so show you this in action.

irq.c++:

#include "irq.h"

#define PIC_MASTER_CONTROL 0x20
#define PIC_MASTER_MASK 0x21
#define PIC_SLAVE_CONTROL 0xa0
#define PIC_SLAVE_MASK 0xa1


typedef void(*regs_func)(struct regs *r);


/*Get all irq's*/
extern "C" void irq0(void);
extern "C" void irq1(void);
extern "C" void irq2(void);
extern "C" void irq3(void);
extern "C" void irq4(void);
extern "C" void irq5(void);
extern "C" void irq6(void);
extern "C" void irq7(void);
extern "C" void irq8(void);
extern "C" void irq9(void);
extern "C" void irq10(void);
extern "C" void irq11(void);
extern "C" void irq12(void);
extern "C" void irq13(void);
extern "C" void irq14(void);
extern "C" void irq15(void);


extern void panic(const char* exception);

regs_func irq_routines[16] = {
0,0,0,0,0,0,0,0
,0,0,0,0,0,0,0,0
};

static PORT::Port8Bits p8b_irq;
static SerialPort sp_irq;

//Basically a declaration of IDT_ENTRY in
//idt.c++
struct idt_entry {
uint16_t base_lo;
uint16_t sel; // Kernel segment goes here.
uint8_t always0;
uint8_t flags; // Set using the table.
uint16_t base_hi;
}__attribute__((packed));

//Get the Exact IDT array from idt.c++
extern struct idt_entry idt[256];

static inline void idt_set_gate(uint8_t num, void(*handler)(void), uint16_t sel,
uint8_t flags)
{
idt[num].base_lo = (uintptr_t)handler >> 0 & 0xFFFF;
idt[num].base_hi = (uintptr_t)handler >> 16 & 0xffff;
idt[num].always0 = 0;
idt[num].sel = sel;
idt[num].flags = flags;
}

IRQ::IRQ(){};
IRQ::~IRQ(){};

/* Normally, IRQs 0 to 7 are mapped to entries 8 to 15. This
* is a problem in protected mode, because IDT entry 8 is a
* Double Fault! Without remapping, every time IRQ0 fires,
* you get a Double Fault Exception, which is NOT actually
* what's happening. We send commands to the Programmable
* Interrupt Controller (PICs - also called the 8259's) in
* order to make IRQ0 to 15 be remapped to IDT entries 32 to
* 47 */
void IRQ::irq_remap()
{

// ICW1 - begin initialization
p8b_irq.out(0x11,PIC_MASTER_CONTROL);
p8b_irq.out(0x11,PIC_SLAVE_CONTROL);

// Remap interrupts beyond 0x20 because the first 32 are cpu exceptions
p8b_irq.out(0x21,PIC_MASTER_MASK);
p8b_irq.out(0x28,PIC_SLAVE_MASK);

// ICW3 - setup cascading
p8b_irq.out(0x00,PIC_MASTER_MASK);
p8b_irq.out(0x00,PIC_SLAVE_MASK);

// ICW4 - environment info
p8b_irq.out(0x01,PIC_MASTER_MASK);
p8b_irq.out(0x01,PIC_SLAVE_MASK);

// mask interrupts
p8b_irq.out(0xff,PIC_MASTER_MASK);
p8b_irq.out(0xff,PIC_SLAVE_MASK);
}

void install_handler_irq(int irq, regs_func handler)
{
printf(" \n Installer IRQ %d \n ", irq);
irq_routines[irq] = handler;
irq0();
}

void uninstall_handler_irq(int irq)
{
irq_routines[irq] = 0;
}




/* First remap the interrupt controllers, and then we install
* the appropriate ISRs to the correct entries in the IDT. This
* is just like installing the exception handlers */

void IRQ::install_irqs()
{
this->irq_remap();
idt_set_gate(32, irq0, 0x08, 0x8E);
idt_set_gate(33, irq1, 0x08, 0x8E);
idt_set_gate(34, irq2, 0x08, 0x8E);
idt_set_gate(35, irq3, 0x08, 0x8E);
idt_set_gate(36, irq4, 0x08, 0x8E);
idt_set_gate(37, irq5, 0x08, 0x8E);
idt_set_gate(38, irq6, 0x08, 0x8E);
idt_set_gate(39, irq7, 0x08, 0x8E);
idt_set_gate(40, irq8, 0x08, 0x8E);
idt_set_gate(41, irq9, 0x08, 0x8E);
idt_set_gate(42, irq10, 0x08, 0x8E);
idt_set_gate(43, irq11, 0x08, 0x8E);
idt_set_gate(44, irq12, 0x08, 0x8E);
idt_set_gate(45, irq13, 0x08, 0x8E);
idt_set_gate(46, irq14, 0x08, 0x8E);
idt_set_gate(47, irq15, 0x08, 0x8E);

}

/* Each of the IRQ ISRs point to this function, rather than
* the 'fault_handler' in 'isrs.c'. The IRQ Controllers need
* to be told when you are done servicing them, so you need
* to send them an "End of Interrupt" command (0x20). There
* are two 8259 chips: The first exists at 0x20, the second
* exists at 0xA0. If the second controller (an IRQ from 8 to
* 15) gets an interrupt, you need to acknowledge the
* interrupt at BOTH controllers, otherwise, you only send
* an EOI command to the first controller. If you don't send
* an EOI, you won't raise any more IRQs */
extern "C" void irq_handler(struct regs *r)
{
printf("IRQ Being Handled");
}


irq.S:

.section .text
.extern irq_handler
.extern test_func

.macro irq number
.global irq\number
irq\number:
cli
pushl $0
pushl $\number
jmp common_handler_irq
.endm

common_handler_irq:
# save registers


pusha

# call C++ Handler
call irq_handler

# restore registers
popa
iret

#TODO FOR LOOP
irq 0
irq 1
irq 2
irq 3
irq 4
irq 5
irq 6
irq 7
irq 8
irq 9
irq 10
irq 11
irq 12
irq 13
irq 14
irq 15


Like shown in the viedo in irq.c++ if i remove that irq0() calling in install_irq function, the triple faulting will be off... But if i remove that i dont know how to correctly handle my timer driver...

timer.c++:

#include "timer.h"

/* This will keep track of how many ticks that the system
* has been running for */

typedef void(*regs_func)(struct regs *r);


static int32_t timer_ticks = 0;

extern void install_handler_irq(int irq, regs_func handler);


/* Handles the timer. In this case, it's very simple: We
* increment the 'Timer::timer_ticks' variable every time the
* timer fires. By default, the timer fires 18.222 times
* per second. Why 18.222Hz? Some engineer at IBM must've
* been smoking something funky */
void timer_handler_driver(struct regs *r)
{
/* Increment our 'tick count' */
timer_ticks++;

/* Every 18 clocks (approximately 1 second), we will
* display a message on the screen */
if (timer_ticks % 18 == 0)
{
printf("One second has passed\n");
}
}

Timer::Timer()
{
}

/* This will continuously loop until the given time has
* been reached */
void Timer::timer_wait(int ticks)
{
unsigned long eticks;

eticks = timer_ticks + ticks;
while((unsigned)timer_ticks < eticks);
}

void Timer::install_timer()
{
install_handler_irq(0, timer_handler_driver);
}

/* Sets up the system clock by installing the timer handler
* into IRQ0 */
Timer::~Timer()
{

}


If you want to see my whole code. I update my code here in github: https://github.com/amanuel2/OS_Mirror . Help would be greatly appreciated, i have been stuck on this problem for some time now. Thanks for reading.

Answer

irq0() is an interrupt service routine, which ends with iret. You can't do a C calls into that routine. If you really want to trigger an interrupt, use the int instruction.

However, you don't really need to manually trigger the timer IRQ, it should fire once you configured the timer/APIC and receive interrupts once you sti.

As a side note, it's considered bad practice (wasting clock cycle and pollute cache) for a "common IRQ handler" approach which then just to do a manual switch-case branch, also different IRQ may require different handling (eg. EOI for slaves). Just install handlers directly into the IDT.


Investigation summary:

After investigation it was the remap code caused the fault, the mask was set with 0xff such that PIT get ignored. Fixing this (set 0 to mask) cause triple fault which indicate the timer is then triggered but there will be problem elsewhere in the IDT/IRQ chain.