How to make interrupts
How to make interrupts
I looked on the wiki for this and could not find anything that I understood. How do you create an IDT and write interrupt handlers.
(If you post examples, please make them in asm because C and osdev... I just don't get it )
(If you post examples, please make them in asm because C and osdev... I just don't get it )
- AndrewAPrice
- Member
- Posts: 2306
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: How to make interrupts
The IDT (interrupt descriptor table) is basically an array of 8-byte entries.
An entry consists of:
bits 0..15 - Lower address of the interrupt handler
bits 16..31 - the code segment selector (from your GDT), - the segment which the CPU will enter when this ring fires (e.g. this will be 0x8 and points to your second GDT entry, which is the segment your interrupt handler is in if you follow most GDT tutorials)
bits 32..39 - Unused, set to 0
bit 40 - Set to 1 to handle this interrupt
bits 41..42 - The ring level of the interrupt handler (usually 0 if the interrupt handler is within your kernel)
bit 43 - system segment (not sure what this bit is)
bits 44..47 - Gate type
bits 48..63 - Upper address of the interrupt handler
You can have as little as you want for how many interrupts you want to handle (there's no point going over 256 entries).
Once you have this table, you create a IDT descriptor, which is basically a 6-byte structure:
bits 0..7 - how many entries are in your IDT table
bits 8..47 - the address of you IDT table
Then call LIDT with the address of your IDT descriptor, and it will load the descriptor into a CPU register. Then call STI to enable interrupts.
In it's simplest form, each interrupt handler is just like a function except your return with IRET instead of RET.
There is far more you need to learn though, like remapping your IRQs so they don't overlap with your exceptions.
I recommend you learn the basics of C, so you can read tutorial code. C is very simple, you don't have any higher level constructs like classes, garbage collection, or templates built into the language. All you have to know is the basics of structs, functions, and variables and you're done!
EDIT: Oops, the IDT is a 6-byte structure, not 3 (don't know what I was thinking?!). Thanks Troy.
An entry consists of:
bits 0..15 - Lower address of the interrupt handler
bits 16..31 - the code segment selector (from your GDT), - the segment which the CPU will enter when this ring fires (e.g. this will be 0x8 and points to your second GDT entry, which is the segment your interrupt handler is in if you follow most GDT tutorials)
bits 32..39 - Unused, set to 0
bit 40 - Set to 1 to handle this interrupt
bits 41..42 - The ring level of the interrupt handler (usually 0 if the interrupt handler is within your kernel)
bit 43 - system segment (not sure what this bit is)
bits 44..47 - Gate type
bits 48..63 - Upper address of the interrupt handler
You can have as little as you want for how many interrupts you want to handle (there's no point going over 256 entries).
Once you have this table, you create a IDT descriptor, which is basically a 6-byte structure:
bits 0..7 - how many entries are in your IDT table
bits 8..47 - the address of you IDT table
Then call LIDT with the address of your IDT descriptor, and it will load the descriptor into a CPU register. Then call STI to enable interrupts.
In it's simplest form, each interrupt handler is just like a function except your return with IRET instead of RET.
There is far more you need to learn though, like remapping your IRQs so they don't overlap with your exceptions.
I recommend you learn the basics of C, so you can read tutorial code. C is very simple, you don't have any higher level constructs like classes, garbage collection, or templates built into the language. All you have to know is the basics of structs, functions, and variables and you're done!
EDIT: Oops, the IDT is a 6-byte structure, not 3 (don't know what I was thinking?!). Thanks Troy.
Last edited by AndrewAPrice on Wed Mar 04, 2009 9:58 pm, edited 1 time in total.
My OS is Perception.
Re: How to make interrupts
I do understand C, and (i think, anyway) well. I just can't compile it for use in osdev. Anyway, for the IDT, I don't mean to sound like a jerk, but I've read the Intel manual, and I've used Google, but I need a little more details. How do I create an interrupt that can be executed by typing int 32?
- Troy Martin
- Member
- Posts: 1686
- Joined: Fri Apr 18, 2008 4:40 pm
- Location: Langley, Vancouver, BC, Canada
- Contact:
Re: How to make interrupts
Now that you've broken it down into simple chunks, Messiah, I'm going to try to apply this to my 32-bit assembly OS
Re: How to make interrupts
MessiahAndrw broke it down pretty well. Using an instruction like int 32 tells the processor to call the interrupt handler installed in IDT entry 32. Each entry stores the address of the function. So, all you need to do is create the interrupt handler, and install a function pointer to it in the IDT entry.I do understand C, and (i think, anyway) well. I just can't compile it for use in osdev. Anyway, for the IDT, I don't mean to sound like a jerk, but I've read the Intel manual, and I've used Google, but I need a little more details. How do I create an interrupt that can be executed by typing int 32?
It should not be too hard. If there is anything you dont understand, please be more specific.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
Re: How to make interrupts
Simple example in asm
Code: Select all
; pm4.asm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; pm4.asm - protected-mode demo code
; Christopher Giese <geezer[AT]execpc.com>
;
; Release date 9/28/98. Distribute freely. ABSOLUTELY NO WARRANTY.
; Assemble with NASM: nasm -o pm4.com pm4.asm
;
; Demonstrates:
; - CPU detection (386 or better)
; - Validation in software interrupt handler
;Fixes/changes:
; - IDT now contains true interrupt gates (type 0x8E) instead
; of trap gates (type 0x8F)
; - spin: jmp spin changed to jmp $
; - Byte 6 of descriptors (flags/limit 19:16) changed from
; 0xFC to 0xCF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[SECTION .text]
[ORG 0x100]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 16-bit real mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 16]
; check for protected or V86 mode. Code from Freedows '98 ldr_asm.asm
; Copyright (C) 1997 Joachim Breitsprecher <[email protected]>
start: smsw ax
test al,1 ; look at PE bit of MSW (CR0)
je cpu_chk
mov ax,0x4C01 ; exit to DOS with error code 1
int 0x21
; determine CPU type. Code from Freedows '98 ldr_asm.asm
; Copyright (C) 1997 Joachim Breitsprecher <[email protected]>
; I (Chris Giese) have not tested this code with 8088/'286 systems.
cpu_chk:cli
pushf
pushf
pop ax
mov bx,ax ; ax=bx=flags
and ax,0x0FFF ; ax=flags & 0x0FFF
or bx,0x7000 ; bx=flags | 0x7000
push ax ; try clearing b15:b12 of flags
popf
pushf
pop ax ; ax=result
push bx ; try setting b14:b12 of flags
popf
pushf
pop bx ; bx=result
popf
and ax,0xF000
cmp ax,0xF000
je not_386 ; 80(1)86/88 sets b15:b12
test bx,0x7000 ; 80286 clears b14:b12
jne start2
not_386:mov ax,0x4C02 ; exit to DOS with error code 2
int 0x21
; set base of code/data descriptors to CS<<4/DS<<4 (CS=DS)
start2: xor ebx,ebx
mov bx,cs ; BX=segment
shl ebx,4 ; EBX=linear address of segment base
mov eax,ebx
mov [gdt1 + 2],ax
mov [gdt2 + 2],ax
mov [gdt3 + 2],ax
shr eax,16
mov [gdt1 + 4],al
mov [gdt2 + 4],al
mov [gdt3 + 4],al
mov [gdt1 + 7],ah
mov [gdt2 + 7],ah
mov [gdt3 + 7],ah
; point gdtr to the gdt, point idtr to the idt
add ebx,gdt ; EBX=linear address of gdt
mov [gdtr + 2],ebx
add ebx,idt - gdt ; EBX=linear address of idt
mov [idtr + 2],ebx
; disable interrupts
cli
; load GDT and IDT for full protected mode
lgdt [gdtr]
lidt [idtr]
; save real-mode CS in BP
mov bp,cs
; set PE [protected mode enable] bit and go
mov eax,cr0
or al,1
mov cr0,eax
jmp SYS_CODE_SEL:do_pm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 32-bit protected mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 32]
do_pm: mov ax,SYS_DATA_SEL
mov ds,ax ; not segments anymore: SELECTORS
mov ss,ax
nop
mov es,ax
mov fs,ax
mov gs,ax
; say hello!
lea esi,[hi_msg]
call wrstr
; try an interrupt
int 0x20
; switch to 16-bit protected mode on your way to real mode
jmp REAL_CODE_SEL:do_16
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; character-output video routine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wrch: push gs
push ecx
push ebx
push eax
mov ax,LINEAR_SEL
mov gs,ax
; (Y * 80 + X) * 2 --> EAX
movzx eax,byte [CsrY]
mov cl,80
mul cl
add al,[CsrX]
adc ah,0
shl eax,1
; EAX + 0xB8000 --> EBX; store char
lea ebx,[eax + 0xB8000]
pop eax
push eax
mov [gs:ebx],al
; advance cursor
mov cx,[CsrX]
inc cl
cmp cl,80 ; cursor off right side of screen?
jb wrch2
xor cl,cl ; yes, wrap to left side...
inc ch ; ...and down one line
cmp ch,25 ; cursor off bottom of screen?
jb wrch2
xor ch,ch ; yes, wrap to top left corner (no scroll)
wrch2: mov [CsrX],cx
pop eax
pop ebx
pop ecx
pop gs
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; string-output video routine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wrstr: push esi
push eax
cld
jmp wrstr2
wrstr1: call wrch
wrstr2: lodsb
or al,al
jne wrstr1
pop eax
pop esi
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; default handler for interrupts/exceptions
; prints " Unhandled interrupt!"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
unhand: cli
lea esi,[unhand_msg]
call wrstr
jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; handler for INT 0x20
; validates interrupt and prints " Hey, INT 0x20 occured!"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
isr20: pusha
mov ebx,[32+esp] ; get stacked EIP
; did we get here because of INT 0x20 instruction?
cmp word [ebx - 2],0x20CD
jne unhand ; no, goto unhand
lea esi,[isr20_msg] ; yes, print a message and return
call wrstr
popa
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 16-bit protected mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 16]
; switch to 16-bit stack and data
do_16: mov ax,REAL_DATA_SEL
mov ds,ax
mov ss,ax
nop
; push real-mode CS:IP
lea bx,[do_rm]
push bp
push bx
; clear PE [protected mode enable] bit and return to real mode
mov eax,cr0
and al,0xFE
mov cr0,eax
retf ; jumps to do_rm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 16-bit real mode again
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; restore real-mode segment register values
do_rm: mov ax,cs
mov ds,ax
mov ss,ax
nop
mov es,ax
mov fs,ax
mov gs,ax
; point to real-mode IDTR
lidt [ridtr]
; re-enable interrupts
sti
; exit to DOS with errorlevel 0
mov ax,0x4C00
int 0x21
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CsrX: db 0
CsrY: db 0
hi_msg: db "Hello, how's it going?", 0
unhand_msg:
db " Unhandled interrupt!", 0
isr20_msg:
db " Hey, INT 0x20 occured!", 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 16-bit limit/32-bit linear base address of GDT and IDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
gdtr: dw gdt_end - gdt - 1 ; GDT limit
dd gdt ; linear, physical address of GDT
idtr: dw idt_end - idt - 1 ; IDT limit
dd idt ; linear, physical address of IDT
; an IDTR 'appropriate' for real mode
ridtr: dw 0xFFFF ; limit=0xFFFF
dd 0 ; base=0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; global descriptor table (GDT)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; null descriptor
gdt: dw 0 ; limit 15:0
dw 0 ; base 15:0
db 0 ; base 23:16
db 0 ; type
db 0 ; limit 19:16, flags
db 0 ; base 31:24
; linear data segment descriptor
LINEAR_SEL equ $-gdt
dw 0xFFFF ; limit 0xFFFFF
dw 0 ; base for this one is always 0
db 0
db 0x92 ; present, ring 0, data, expand-up, writable
db 0xCF ; page-granular, 32-bit
db 0
; code segment descriptor
SYS_CODE_SEL equ $-gdt
gdt1: dw 0xFFFF
dw 0 ; (base gets set above)
db 0
db 0x9A ; present, ring 0, code, non-conforming, readable
db 0xCF
db 0
; data segment descriptor
SYS_DATA_SEL equ $-gdt
gdt2: dw 0xFFFF
dw 0 ; (base gets set above)
db 0
db 0x92 ; present, ring 0, data, expand-up, writable
db 0xCF
db 0
; code segment descriptor that is 'appropriate' for real mode
; (16-bit, limit=0xFFFF)
REAL_CODE_SEL equ $-gdt
gdt3: dw 0xFFFF
dw 0 ; (base gets set above)
db 0
db 0x9A ; present, ring 0, code, non-conforming, readable
db 0 ; byte-granular, 16-bit
db 0
; data segment descriptor that is 'appropriate' for real mode
; (16-bit, limit=0xFFFF)
REAL_DATA_SEL equ $-gdt
gdt4: dw 0xFFFF
dw 0 ; (base gets set above)
db 0
db 0x92 ; present, ring 0, data, expand-up, writable
db 0 ; byte-granular, 16-bit
db 0
gdt_end:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; interrupt descriptor table (IDT)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 32 reserved interrupts:
idt: dw unhand ; entry point 15:0
dw SYS_CODE_SEL ; selector
db 0 ; word count
db 0x8E ; type (32-bit Ring 0 interrupt gate)
dw 0 ; entry point 31:16 (XXX - unhand >> 16)
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
dw unhand
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
; user interrupt handler
dw isr20
dw SYS_CODE_SEL
db 0
db 0x8E
dw 0
idt_end:
Re: How to make interrupts
Okay, one thing. How do you create a pointer to your handler in your IDT entry?
Re: How to make interrupts
Just get the address of your interrupt handler, and store it in the IDT entry. The lower 16 bits of the address go in the first 16 bits of the IDT entry, the upper 15 bits go into bits 48-63 of the IDT entry. This makes up the full 32 bit linear address of your interrupt handler. (This can be seen in the IDT entry bit format posted earlier.)Sly wrote:Okay, one thing. How do you create a pointer to your handler in your IDT entry?
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
- Troy Martin
- Member
- Posts: 1686
- Joined: Fri Apr 18, 2008 4:40 pm
- Location: Langley, Vancouver, BC, Canada
- Contact:
Re: How to make interrupts
Couple questions:
1. Does this say, in NASM assembly, that the interrupt is a trap gate in ring 0?
2. Messiah said that the IDT descriptor is 3 bytes, but the wiki says 6. Which one is it?
1. Does this say, in NASM assembly, that the interrupt is a trap gate in ring 0?
Code: Select all
db 11110001b
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: How to make interrupts
it should be 6 bytes
- Troy Martin
- Member
- Posts: 1686
- Joined: Fri Apr 18, 2008 4:40 pm
- Location: Langley, Vancouver, BC, Canada
- Contact:
Re: How to make interrupts
So a dw for the number of entries and a dd for the location of the start of the IDT.Combuster wrote:it should be 6 bytes
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: How to make interrupts
a dw for the limit of the table (= 8 * entries - 1)