Suraaj K S
Suraaj K S

Reputation: 661

Working of the IRQs and the iret instruction semantics on a 32 bit kernel (protected mode)

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

Answers (1)

mevets
mevets

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

Related Questions