Reputation: 661
I've been writing a hobby OS, and I'm trying to do interrupt/exception handling in the kernel. I'm in ring 0, so there's no inter privilege stack switch,etc. These are my routines:
#include <stdint.h>
#include "dadio.h"
#define MAX_INTERRUPTS 256
#define IDT_DESC_BIT16 0x06 //00000110
#define IDT_DESC_BIT32 0x0E //00001110
#define IDT_DESC_RING1 0x40 //01000000
#define IDT_DESC_RING2 0x20 //00100000
#define IDT_DESC_RING3 0x60 //01100000
#define IDT_DESC_PRESENT 0x80//10000000
//Structs used in this routine
typedef struct __attribute__ ((__packed__)) idtr {
uint16_t limit;
uint32_t base;
}idtr_t;
typedef struct __attribute__ ((__packed__)) gdtr {
uint16_t limit;
uint32_t base;
}gdtr_t;
typedef struct __attribute__ ((__packed__)) idt_descriptor {
uint16_t baseLo;
uint16_t sel;
uint8_t reserved;
uint8_t flags;
uint16_t baseHi;
}idt_descriptor_t;
typedef struct __attribute__((__packed__)) gdt_descriptor {
uint16_t limit;
uint16_t baseLo;
uint8_t baseMid;
uint16_t flags;
uint8_t baseHi;
} gdt_descriptor_t;
//External assembly functions
void init_pic();
void install_idt(idtr_t* address);
void enable_interrupts();
//Global variables in this routine
static idt_descriptor_t _idt[MAX_INTERRUPTS];
static idtr_t _idtr; //This will be the 6 byte base + limit
//Helper functions
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address);
static void default_handler();
void idt_init()
{
_idtr.base = (uint32_t)_idt;
_idtr.limit = (sizeof (idt_descriptor_t) * MAX_INTERRUPTS) -1 ;
for (int i=0;i<MAX_INTERRUPTS;i++)
{
_idt[i].baseLo = 0;
_idt[i].sel = 0;
_idt[i].reserved = 0;
_idt[i].flags = 0;
_idt[i].baseHi = 0;
}
for (int i=0;i<MAX_INTERRUPTS;i++)
install_ir(i,IDT_DESC_BIT32 | IDT_DESC_PRESENT, 0x08, (uint32_t*) default_handler);
init_pic();
install_idt(& _idtr);
enable_interrupts();
}
static void install_ir(uint32_t index,uint16_t flags, uint16_t sel, uint32_t* handler_address)
{
if (index >=MAX_INTERRUPTS) return;
_idt[index].baseLo = (uint32_t)handler_address & 0xffff;
_idt[index].baseHi = ((uint32_t)handler_address >> 16) & 0xffff;
_idt[index].reserved = 0;
_idt[index].flags = flags;
_idt[index].sel = sel;
}
static void default_handler()
{
monitor-puts("This is the default exception handler"); //This is a routine that prints messages on the screen... The gist is that it writes to 0xb8000 and so on...
for (;;);
}
Assembly routines
init_pic:
mov al, 0x11 ;ICW 1 ;Expect IC4|single?|0|level?|init?|000
out 0x20,al
out 0xA0,al
mov al,0x20 ;Remapping the IRQs
out 0x21,al
mov al,0x28
out 0xA1,al
; Send ICW 3 to primary PIC
mov al, 0x4 ; 0x4 = 0100 Second bit (IR Line 2)
out 0x21, al ; write to data register of primary PIC
; Send ICW 3 to secondary PIC
mov al, 0x2 ; 010=> IR line 2
out 0xA1, al ; write to data register of secondary PIC
; Send ICW 4 - Set x86 mode --------------------------------
mov al, 1 ; bit 0 enables 80x86 mode
out 0x21, al
out 0xA1, al
; Zeroing out the data registers
mov al, 0
out 0x21, al
out 0xA1, al
ret
enable_interrupts:
sti
ret
A minimal kernel would be:
void kmain()
{
idt_init();
return;
}
If I comment out the init_pic() in the idt_init function, I get the message: This is the default exception handler, followed by the for(;;). I think that this is expected because as soon as I enable interrupts, something like the timer will send an IRQ, and since it's mapped to the (divide by zero?) exception by default, I get the handler message that I've defined.
But if I uncomment the init_pic(), I don't get the message. I understand that the IRQs (0-15) have been remapped to interrupt vectors (32 - 47). But a timer interrupt would still fire, and I should get the message. (All of the 256 possible interrupts/exceptions are mapped to the same routine in my case, I think). Where have I gone wrong?
Also, a small followup question. I know that some exceptions will push an error code while some do not. But the iret instruction cannot know that right? So is it the programmers responsibility to manually add to the esp (popping of the error code of the exception does push an error) and then doing an iret?
I've read the 80386 developers manual, from where I understand this. Am I wrong somewhere?
PS: I've tried to provide the bare minimum of code, although my project has a lot more code.
Upvotes: 3
Views: 227
Reputation: 10445
One critical bit might be what happens when kmain returns?
The followup question is important: you have to clean up the stack before iret. For this reason alone, it is really cumbersome to point your gates at C functions. Most people make two assembly stubs:
#define SAVE() pusha; push %ds; push %es; push %fs; push %gs
#define RESTORE() pop %gs; pop %fs; pop %es; pop %ds; popa
#define TRAP0(n) .global vec#n; vec#n: \
pushl $(n<<8); \
pushl $0; \
SAVE(); \
call generic(); \
RESTORE(); \
add $8, %esp; \
iret
#define TRAP1(n) .global vec#n; vec#n: \
movw $n, 2(%esp); \
pushl $0; \
SAVE();\
call generic(); \
RESTORE(); \
add $8, %esp; \
iret
#define PTRAP(n) .global vec#n; vec#n: \
movw $n, 2(%esp);\
pushl $0; SAVE();\
mov %cr3, %eax; \
mov %eax, (12*4)(%esp); \
call generic(); \
RESTORE(); \
add $8, %esp; \
iret
TRAP0(0); TRAP0(1); ... TRAP0(7);
TRAP1(8); TRAP0(9);
TRAP1(10); ...TRAP1(13);
PTRAP(14); TRAP1(15); ...; TRAP1(31);
/* interrupts: */
TRAP0(32); TRAP0(33); .... TRAP0(47)
So you install vec0..vec47 into idt[0]..[47]; then your generic handler looks like it has been passed a structure:
struct kstk {
uint32_t seg[4];
uint32_t reg[8];
uint32_t cr3;
uint16_t errc, trap;
uint32_t ip, cs, fl, sp, ss;
};
void handler(struct kstk x); The ss,sp are optional. (std disclaimer, I just typed this into the message, so it likely needs some adjustment).
Upvotes: 1