Experiments with IRQs and the iret instruction
Posted: Tue Mar 10, 2020 9:53 am
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:
Assembly routines
A minimal kernel would be:
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 if 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.
Code: Select all
#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 (;;);
}
Code: Select all
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
Code: Select all
void kmain()
{
idt_init();
return;
}
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 if 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.