Interrupts Tutorial: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(added semi-compatibility with the barebones tutorials)
m (Reverted edits by Acccidiccc (talk) to last revision by Klakap)
Line 467: Line 467:


I have tested this code and it works in both Virtualbox and QEMU. Good luck!
I have tested this code and it works in both Virtualbox and QEMU. Good luck!



Klakap
Klakap

==barebones II (x86)==

===barebones GDT===

If you are following the barebones II/meaty-skeleton route and want to add interrupts to your OS, add the following code instead of the one provided above, as it will not work. This is because the segment registers haven't been reloaded among other things. To make it work, add the following code instead:

<source lang="asm">

;global descriptor table
gdt:

gdt_null:
dq 0

gdt_code:
dw 0xFFFF
dw 0

db 0
db 10011010b
db 11001111b
db 0

gdt_data:
dw 0xFFFF
dw 0

db 0
db 10010010b
db 11001111b
db 0

gdt_end:

gdt_desc:

gdt_size:
dw gdt_end - gdt - 1
dq gdt_null
codeseg equ gdt_code - gdt_null
dataseg equ gdt_data - gdt_null
load_gdt:
cli
lgdt [gdt_desc]
jmp codeseg:reload_cs
reload_cs:
mov ax, dataseg
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
sti
ret
</source>

Put this code into a file named gdt.asm (you can name it something else, but why?) then include it in the boot.asm file (I do not know if position of the include matters, but I have put it at the top and it works)
and put in the following line of code after the stack has been set up
(again don't know if it matters, but if it ain't broke, don't fix it)
<source lang="asm"> call load_gdt </source>

I have tested this on my own os in QEMU.

This can hopefully save you the headache that I had to endure.
Thanks to Klapap, poncho for the implementation of the codeseg and dataseg, and to this wiki for the explanations.

===compatability with the original barebones tutorial===


A huge problem in addition to this, is the mixing of syntaces in these tutorials. this one uses nasm, but barebones and meaty-skeleton use gas.
Try to either write these in gas yourself, or mix them. here is how to mix this tutorial with the original barebones. if you are using the nasm barebones, just include the gdt.asm file. if you use the original, just declare the load_gdt label as a global. <source lang="asm"> global load_gdt </source> in my tests, this worked perfectly with the exception of nasm complaining about the 64 bit relocation. this may be subject to a FIXME.
i am discouraging this though as it just messes up the code base. if you have the assembly in another directory, IIRC nasm places the object file there, whereas gcc puts them in the current working directory IIRC. so instead of just moving every object file like this:
<source lang="make">
mvobj:
mv *.o objdir
</source>
you have to do this
<source lang="make">
mvobj:
mov $$(find source/asm/ -name '*.asm') objdir # in my project i used asm for nasm and .S for gas
mv *.o objdir
</source>
this can get incredibly annoying. especially when moving the library path etc.
acccidiccc

==Exceptions==
To add exceptions, treat them like interrupts. the interrupts are between 32 and 47, the exceptions are between 0 and 31. add them and the handlers to your IDT. To prove you understood this tutorial, implement them yourself.




==64 bit IDT==

On amd64, the IDT structure changes. an entry is now 64 bits.
the structure looks like this

<source lang="c">

struct IDT64_entry {
uint16_t offset_low; //the low part of the address. ( address & 0x000000000000ffff)
uint16_t select; // a selector. the same as in the 32 bit IDT
uint8_t ist; // offset of the interrupt stack. check the IDT page for more information
uint8_t attributes; //the same as above
uint16_t offset_mid; //the "middle" part of the address (( address & 0x00000000ffff0000) >> 16)
uint32_t offset_high; // the upper half of the address ((address & 0xffffffff00000000) >> 32)
uint32_t zero; // just put this to zero
}
</source>
keep this in mind:
* you additionally have to reprogram the PIC differently.
* to return from an interrupt routine use iretq instead of iret
* address assignment: <source lang="c">irq1_address = (unsigned long long) &irq1; // uint64_t is also ok </source>
* if you don't plan on using the ist, put a zero in it
* all address are 64 bit. including the irq addresses. change them from <source lang="c"> unsigned long irq1; </source> to <source lang="c"> unsigned long long irq1; </source>
* the IDT itself now has a 64 bit address. the limit stays at 16 bits. keep in mind that
# you should use 64 bit registers, whose uses are sometimes mandatory. the <source lang="asm"> mov edx, [ 4 + esp ] </source> has to change to <source lang="asm"> mov rdx, [ 4 + rsp ] </source>
# this will still not work due to the way the x86_64 sysV ABI works. the integer parameters are not pushed to stack if possible, but reside in the registers. more information will be on [https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions Wikipedia]
* use a function with more parameters than a pointer. better use a function with following parameters: <source lang="c"> void loadIDT (void *base, size_t limit);</source> this is from nullplans loadIDT function, which he posted in this [https://forum.osdev.org/viewtopic.php?f=1&t=43919 thread].

==Tips==

little tip: write scripts or programs that automatically output the handlers for you. you don't want to type 3000 characters, right?
<source lang=c>
for (int i = 0; i<=15;i++) {
printf("irq%dAddr\n", i);
}</source>
this makes development faster and you wont mistype something!


==See also==
==See also==

Revision as of 19:04, 30 May 2021

Difficulty level

Beginner

Every operating system that needs to work with the hardware must have a set of interrupts. First, you need to learn about interrupts and how they work. http://www.osdever.net/tutorials/view/irqs is a good resource to check. This tutorial will tell you how to set up and use interrupts.

IDT table

When an interrupt is fired, the CPU looks at the IDT table, and finds what method needs to be called. Here, we build the IDT table as an array, and load it using the function "lidt"

struct IDT_entry{
	unsigned short int offset_lowerbits;
	unsigned short int selector;
	unsigned char zero;
	unsigned char type_attr;
	unsigned short int offset_higherbits;
};

struct IDT_entry IDT[256];

Now, we need to remap the PIC (Programmable Interrupt Controller) and fill the IDT with the correct values.

void idt_init(void) {
        extern int load_idt();
        extern int irq0();
        extern int irq1();
        extern int irq2();
        extern int irq3();
        extern int irq4();
        extern int irq5();
        extern int irq6();
        extern int irq7();
        extern int irq8();
        extern int irq9();
        extern int irq10();
        extern int irq11();
        extern int irq12();
        extern int irq13();
        extern int irq14();
        extern int irq15();

	unsigned long irq0_address;
        unsigned long irq1_address;
        unsigned long irq2_address;
        unsigned long irq3_address;          
        unsigned long irq4_address; 
        unsigned long irq5_address;
        unsigned long irq6_address;
        unsigned long irq7_address;
        unsigned long irq8_address;
        unsigned long irq9_address;          
        unsigned long irq10_address;
        unsigned long irq11_address;
        unsigned long irq12_address;
        unsigned long irq13_address;
        unsigned long irq14_address;          
        unsigned long irq15_address;         
	unsigned long idt_address;
	unsigned long idt_ptr[2];

        /* remapping the PIC */
	outb(0x20, 0x11);
        outb(0xA0, 0x11);
        outb(0x21, 0x20);
        outb(0xA1, 40);
        outb(0x21, 0x04);
        outb(0xA1, 0x02);
        outb(0x21, 0x01);
        outb(0xA1, 0x01);
        outb(0x21, 0x0);
        outb(0xA1, 0x0);

	irq0_address = (unsigned long)irq0; 
	IDT[32].offset_lowerbits = irq0_address & 0xffff;
	IDT[32].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[32].zero = 0;
	IDT[32].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[32].offset_higherbits = (irq0_address & 0xffff0000) >> 16;

	irq1_address = (unsigned long)irq1; 
	IDT[33].offset_lowerbits = irq1_address & 0xffff;
	IDT[33].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[33].zero = 0;
	IDT[33].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[33].offset_higherbits = (irq1_address & 0xffff0000) >> 16;

	irq2_address = (unsigned long)irq2; 
	IDT[34].offset_lowerbits = irq2_address & 0xffff;
	IDT[34].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[34].zero = 0;
	IDT[34].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[34].offset_higherbits = (irq2_address & 0xffff0000) >> 16;

	irq3_address = (unsigned long)irq3; 
	IDT[35].offset_lowerbits = irq3_address & 0xffff;
	IDT[35].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[35].zero = 0;
	IDT[35].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[35].offset_higherbits = (irq3_address & 0xffff0000) >> 16;

	irq4_address = (unsigned long)irq4; 
	IDT[36].offset_lowerbits = irq4_address & 0xffff;
	IDT[36].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[36].zero = 0;
	IDT[36].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[36].offset_higherbits = (irq4_address & 0xffff0000) >> 16;

	irq5_address = (unsigned long)irq5; 
	IDT[37].offset_lowerbits = irq5_address & 0xffff;
	IDT[37].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[37].zero = 0;
	IDT[37].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[37].offset_higherbits = (irq5_address & 0xffff0000) >> 16;

	irq6_address = (unsigned long)irq6; 
	IDT[38].offset_lowerbits = irq6_address & 0xffff;
	IDT[38].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[38].zero = 0;
	IDT[38].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[38].offset_higherbits = (irq6_address & 0xffff0000) >> 16;

	irq7_address = (unsigned long)irq7; 
	IDT[39].offset_lowerbits = irq7_address & 0xffff;
	IDT[39].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[39].zero = 0;
	IDT[39].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[39].offset_higherbits = (irq7_address & 0xffff0000) >> 16;

	irq8_address = (unsigned long)irq8; 
	IDT[40].offset_lowerbits = irq8_address & 0xffff;
	IDT[40].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[40].zero = 0;
	IDT[40].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[40].offset_higherbits = (irq8_address & 0xffff0000) >> 16;

	irq9_address = (unsigned long)irq9; 
	IDT[41].offset_lowerbits = irq9_address & 0xffff;
	IDT[41].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[41].zero = 0;
	IDT[41].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[41].offset_higherbits = (irq9_address & 0xffff0000) >> 16;

	irq10_address = (unsigned long)irq10; 
	IDT[42].offset_lowerbits = irq10_address & 0xffff;
	IDT[42].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[42].zero = 0;
	IDT[42].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[42].offset_higherbits = (irq10_address & 0xffff0000) >> 16;

	irq11_address = (unsigned long)irq11; 
	IDT[43].offset_lowerbits = irq11_address & 0xffff;
	IDT[43].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[43].zero = 0;
	IDT[43].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[43].offset_higherbits = (irq11_address & 0xffff0000) >> 16;

	irq12_address = (unsigned long)irq12; 
	IDT[44].offset_lowerbits = irq12_address & 0xffff;
	IDT[44].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[44].zero = 0;
	IDT[44].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[44].offset_higherbits = (irq12_address & 0xffff0000) >> 16;

	irq13_address = (unsigned long)irq13; 
	IDT[45].offset_lowerbits = irq13_address & 0xffff;
	IDT[45].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[45].zero = 0;
	IDT[45].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[45].offset_higherbits = (irq13_address & 0xffff0000) >> 16;

	irq14_address = (unsigned long)irq14; 
	IDT[46].offset_lowerbits = irq14_address & 0xffff;
	IDT[46].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[46].zero = 0;
	IDT[46].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[46].offset_higherbits = (irq14_address & 0xffff0000) >> 16;

        irq15_address = (unsigned long)irq15; 
	IDT[47].offset_lowerbits = irq15_address & 0xffff;
	IDT[47].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
	IDT[47].zero = 0;
	IDT[47].type_attr = 0x8e; /* INTERRUPT_GATE */
	IDT[47].offset_higherbits = (irq15_address & 0xffff0000) >> 16;

	/* fill the IDT descriptor */
	idt_address = (unsigned long)IDT ;
	idt_ptr[0] = (sizeof (struct IDT_entry) * 256) + ((idt_address & 0xffff) << 16);
	idt_ptr[1] = idt_address >> 16 ;



	load_idt(idt_ptr);

}

Here are the IRQ handlers in assembly

global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15

global load_idt

global irq0_handler
global irq1_handler
global irq2_handler
global irq3_handler
global irq4_handler
global irq5_handler
global irq6_handler
global irq7_handler
global irq8_handler
global irq9_handler
global irq10_handler
global irq11_handler
global irq12_handler
global irq13_handler
global irq14_handler
global irq15_handler

extern irq0_handler
extern irq1_handler
extern irq2_handler
extern irq3_handler
extern irq4_handler
extern irq5_handler
extern irq6_handler
extern irq7_handler
extern irq8_handler
extern irq9_handler
extern irq10_handler
extern irq11_handler
extern irq12_handler
extern irq13_handler
extern irq14_handler
extern irq15_handler

irq0:
  pusha
  call irq0_handler
  popa
  iret

irq1:
  pusha
  call irq1_handler
  popa
  iret

irq2:
  pusha
  call irq2_handler
  popa
  iret

irq3:
  pusha
  call irq3_handler
  popa
  iret

irq4:
  pusha
  call irq4_handler
  popa
  iret

irq5:
  pusha
  call irq5_handler
  popa
  iret

irq6:
  pusha
  call irq6_handler
  popa
  iret

irq7:
  pusha
  call irq7_handler
  popa
  iret

irq8:
  pusha
  call irq8_handler
  popa
  iret

irq9:
  pusha
  call irq9_handler
  popa
  iret

irq10:
  pusha
  call irq10_handler
  popa
  iret

irq11:
  pusha
  call irq11_handler
  popa
  iret

irq12:
  pusha
  call irq12_handler
  popa
  iret

irq13:
  pusha
  call irq13_handler
  popa
  iret

irq14:
  pusha
  call irq14_handler
  popa
  iret

irq15:
  pusha
  call irq15_handler
  popa
  iret

load_idt:
	mov edx, [esp + 4]
	lidt [edx]
	sti
	ret

And here are the IRQ handlers in C, which are called from the assembly code. We must send the EOI (End of Interrupt) signal to the PIC, to tell it that we're done handling the interrupt. If we dont, the PIC will think that we're still busy, and won't send any more IRQs.

void irq0_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq1_handler(void) {
	  outb(0x20, 0x20); //EOI
}

void irq2_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq3_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq4_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq5_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq6_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq7_handler(void) {
          outb(0x20, 0x20); //EOI
}

void irq8_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI          
}

void irq9_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq10_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq11_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq12_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq13_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq14_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

void irq15_handler(void) {
          outb(0xA0, 0x20);
          outb(0x20, 0x20); //EOI
}

Now you can write your handler methods!

Interrupts in GRUB

If you use GRUB as your bootloader, after setting up the IDT emulators, you will get a fatal error. This is most likely due to the fact that you haven't set up the GDT. Here is some the assembly code to example how you can set it. You should set the GDT before IDT, best in bootloader.

  jmp load_gdt

  ;global descriptor table
  gdt:

  gdt_null:
  dq 0

  gdt_code:
  dw 0FFFFh
  dw 0

  db 0
  db 10011010b
  db 11001111b
  db 0

  gdt_data:
  dw 0FFFFh
  dw 0

  db 0
  db 10010010b
  db 11001111b
  db 0

  gdt_end:

  gdt_desc:
   dw gdt_end - gdt - 1
   dd gdt

  ;load gdt
  load_gdt:
    cli  ;disable interrupts
    lgdt [gdt_desc]  ;load GDT
    sti  ;enable interrupts

I have tested this code and it works in both Virtualbox and QEMU. Good luck!

Klakap

See also

Threads

References