I Can't Get Interrupts Working: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
mNo edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(32 intermediate revisions by 19 users not shown)
Line 5: Line 5:
== ISR problems ==
== ISR problems ==


=== My handler doesn't get called!? (ASM) ===
=== My handler doesn't get called?! (Assembly) ===


For this test, you need to call the interrupt yourself, by software. Don't try to get [[IRQ]] handled right from the start before you're sure your IDT setup is correct. You need to have:
For this test, you need to call the interrupt yourself, by software. Don't try to get [[IRQ]] handled right from the start before you're sure your IDT setup is correct. You need to have:


* your IDT loaded and filled properly.
* Your IDT loaded and filled properly.
* your IDT's ''linear'' address loaded in a structure together with the table's size (in bytes, iirc). Be especially cautious if you have a [[Higher Half Kernel]] design or did not set up [[Identity Paging|identity paging]].
* Your IDT's ''linear'' address loaded in a structure together with the table's size (in bytes, IIRC). Be especially cautious if you have a [[Higher Half Kernel]] design or did not set up [[Identity Paging|identity paging]].
* a valid Code selector and offset in the [[Descriptors|descriptor]], proper type, etc.
* A valid Code selector and offset in the [[Descriptors|descriptor]], proper type, etc.
* a handling code at the defined offset.
* A handling code at the defined offset.


'' see [[#Assembly_Example|test code]] below ''
'' see [[#Assembly_Examples|test code]] below ''


=== My Handler doesn't get called (C)?! ===


If you are programming the IDT setup in C, make sure the IDTR structure has been correctly understood by your compiler. As Intel's 6 bytes structures infringe most compiler's packing rules, you'll need to use either ''bitfields'' or ''packing pragmas''. Use <tt>sizeof()</tt> and <tt>OFFSETOF()</tt> macros to make sure the expected definition is used (a runtime test would be fine)
=== My Handler doesn't get called (C) !? ===

If you're programming the IDT setup in C, make sure the IDTR structure has been correctly understood by your compiler. As Intel's 6 bytes structures infringe most compiler's packing rules, you'll need to use either ''bitfields'' or ''packing pragmas''. Use <tt>sizeof()</tt> and <tt>OFFSETOF()</tt> macros to make sure the expected definition is used (a runtime test would be fine)



=== My handler is called but it doesn't return !? ===
=== My handler is called but it doesn't return !? ===


Try to run it in the BOCHS and see if you get any exception report. Program all your exception to have the same kind of behavior as [[#Assembly_Example|the example]], but displaying a character indicating the fault. Exceptions occurring at the end of an interrupt handler are usually due to a wrong stack operation within the handler.
Try to run it in the Bochs and see if you get any exception report. Program all your exception to have the same kind of behavior as [[#Assembly_Examples|the example]], but displaying a character indicating the fault. Exceptions occurring at the end of an interrupt handler are usually due to a wrong stack operation within the handler.


* don't try to return from an exception (unless you solved its cause). Returning from a division by zero, for instance, makes no sense at all
* don't try to return from an exception (unless you solved its cause). Returning from a division by zero, for instance, makes no sense at all
Line 31: Line 29:
* make sure your handler doesn't trash unexpected registers. For exceptions and hardware IRQ handlers, no registers *at all* should be modified.
* make sure your handler doesn't trash unexpected registers. For exceptions and hardware IRQ handlers, no registers *at all* should be modified.


Another common source of error at this point comes from misimplementation of ISR in C. Check the InterruptServiceRoutines page for enlightenment ...
Another common source of error at this point comes from mis implementation of ISR in C. Check the InterruptServiceRoutines page for enlightenment ...


== IRQ problems ==
== IRQ problems ==
Line 38: Line 36:
Use the mask feature of the PIC to enable/disable some handlers.
Use the mask feature of the PIC to enable/disable some handlers.


<source lang="c">
<syntaxhighlight lang="c">
outb(0x21,0xfd);
outb(0x21,0xfd);
outb(0xa1,0xff);
outb(0xa1,0xff);
enable(); // asm("sti");
enable(); // asm("sti");
</syntaxhighlight>
</source>


=== I'm receiving EXC9 instead of IRQ1 when striking a key ?! ===
=== I'm receiving a General Protection Fault interrupt immediately after returning from my first interrupt ===

After initializing the gdt with the lgdt command, make sure that you are performing a long jump. For example:

<syntaxhighlight lang="asm">
init_gdt:
lgdt [gdt_table]
jmp 0x08:longjmp_after_gdt
longjmp_after_gdt:
; Do something like repoint segment registers next
</syntaxhighlight>

=== I'm receiving EXC9 instead of IRQ1 when striking a key?! ===


You missed the PIC vector reprogramming step. Check [[PIC|Can I remap the PIC?]] page. Note that if you remap the PIC vectors out of the IDT you'll get a GPF exception instead of any interrupt.
You missed the PIC vector reprogramming step. Check [[PIC|Can I remap the PIC?]] page. Note that if you remap the PIC vectors out of the IDT you'll get a GPF exception instead of any interrupt.


=== I'm receiving a double fault after enabling interrupts ===

May be a different symptom for the same error as above, this time caused by a timer interrupt calling vector 8. May also be caused if you've enabled interrupts in protected mode but haven't got an interrupt handler defined for whatever vector you've remapped the timer to, as the timer interrupt will come soon after enabling interrupts and cause a fault unless you've got a handler for it or you've masked it.


=== I'm not receiving any IRQ ===
=== I'm not receiving any IRQ ===
Line 55: Line 68:
=== I can only receive one IRQ ===
=== I can only receive one IRQ ===


Each IRQ needs to be acknowledged to the PIC manually. You need to have <source lang="c"> outb(0x20,0x20) </source> within any master handler and any <source lang="c"> outb(0x20,0x20); outb(0xa0,0x20); </source> within any slave handler.
Each IRQ needs to be acknowledged to the PIC manually by sending an EOI. You need to have <syntaxhighlight lang="c"> outb(0x20,0x20) </syntaxhighlight> within any master handler and any <syntaxhighlight lang="c"> outb(0x20,0x20); outb(0xa0,0x20); </syntaxhighlight> within any slave handler.<br><br>

When handling the keyboard IRQ, make sure that you read the byte the keyboard sends you. The interrupt might not trigger again until it has been read: <syntaxhighlight lang="c"> unsigned char scan_code = inb(0x60); </syntaxhighlight>

Also, if you are following the barebones tutorial, be sure that your main function doesn't exit too soon (because when it does, it disables interrupts). A common solution to make sure it doesn't exit prematurely is to add <syntaxhighlight lang="c"> for(;;) {
asm("hlt");
} </syntaxhighlight>
to the end of your main kernel function. The ''for'' loop is necessary because execution continues after the CPU receives an interrupt.


=== When I try to enable the PIT, the keyboard doesn't work anymore ===
=== When I try to enable the PIT, the keyboard doesn't work anymore ===
Line 63: Line 83:
=== I keep getting an IRQ7 for no apparent reason ===
=== I keep getting an IRQ7 for no apparent reason ===


This is a known problem that cannot be prevented from happening, although there is a workaround. When any IRQ7 is received, simply read the In-Service Register <source lang="c"> outb(0x20, 0x0B); unsigned char irr = inb(0x20);</source> and check if bit 7 <source lang="c">irr & 0x80</source> is set. If it isn't, then return from the interrupt without sending an EOI.
This is a known problem that cannot be prevented from happening, although there is a workaround. When any IRQ7 is received, simply read the In-Service Register <syntaxhighlight lang="c"> outb(0x20, 0x0B); unsigned char irr = inb(0x20);</syntaxhighlight> and check if bit 7 <syntaxhighlight lang="c">irr & 0x80</syntaxhighlight> is set. If it isn't, then return from the interrupt without sending an EOI.


For more information, including a more detailed explanation, see Brendan's post in [[Topic:11379|this thread]].
For more information, including a more detailed explanation, see Brendan's post in [[Topic:11379|this thread]].




=== what does "shift operator may only be applied to scalar values" mean? ===
== IDT problems in Assembly ==

=== what does "shift operator may only be applied to scalar values" mean ? ===


You're trying to load a 16-bits field (a part of the IDT descriptor) with a reference to a 32-bit label that is subject to relocation. Try to replace
You're trying to load a 16-bits field (a part of the IDT descriptor) with a reference to a 32-bit label that is subject to relocation. Try to replace
<source lang="asm">
<syntaxhighlight lang="asm">
isr_label:
isr_label:
iret
iret
Line 80: Line 98:
dw 0xbeef
dw 0xbeef
dw isr_label >> 16
dw isr_label >> 16
</syntaxhighlight>
</source>


by something that extracts a 'pure value' from the address (e.g. the difference of two addresses are a pure value and <tt>$$</tt> means to NASM the start of the section)
by something that extracts a 'pure value' from the address (e.g. the difference of two addresses are a pure value and <tt>$$</tt> means to NASM the start of the section)
<source lang="asm">
<syntaxhighlight lang="asm">
%define BASE_OF_SECTION SOME_CONSTANT_YOU_SHOULD_KNOW
%define BASE_OF_SECTION SOME_CONSTANT_YOU_SHOULD_KNOW
isr_label:
isr_label:
iret
iret
good_stuff dw (BASE_OF_SECTION isr_label - $$) & 0xFFFF
good_stuff dw (BASE_OF_SECTION + isr_label - $$) & 0xFFFF
dw 0xcafe
dw 0xcafe
dw 0xbabe
dw 0xbabe
dw (BASE_OF_SECTION isr_label - $$) >> 16
dw (BASE_OF_SECTION + isr_label - $$) >> 16
</syntaxhighlight>
</source>


The role of <pre>BASE_OF_SECTION</pre> is to adjust the pure offset to the real situation (usually as defined in your linker script), e.g. if your kernel get loaded at 1MB, you'll set it to 0x100000 to keep the CPU happy.
The role of <pre>BASE_OF_SECTION</pre> is to adjust the pure offset to the real situation (usually as defined in your linker script), e.g. if your kernel get loaded at 1MB, you'll set it to 0x100000 to keep the CPU happy.


==Assembly Example==
==Assembly Examples==
===NASM===
<source lang="asm">

This example is made for x86 CPUs running in IA32 mode (32-bit).
<syntaxhighlight lang="asm">
int_handler:
int_handler:
mov ax, LINEAR_DATA_SELECTOR
mov ax, LINEAR_DATA_SELECTOR
Line 119: Line 140:
mov [idt+49*8+6],ax
mov [idt+49*8+6],ax
int 49
int 49
</syntaxhighlight>
</source>


should display a smiley on the top-left corner ... then the CPU is halted indefinitely.
should display a smiley on the top-left corner ... then the CPU is halted indefinitely.

===GNU Assembler===

This example sets up an interrupt handler in long mode.
<syntaxhighlight lang="asm">
.text
int_handler:
movq $0x123abc, 0x0 // this places magic value "0x123abc" at the beginning of memory
hlt

.p2align 4
idt:
.skip 50*16

idtr:
.short (50*16)-1
.quad idt

.globl do_test
do_test:
lidt idtr
movq $int_handler, %rax
mov %ax, idt+49*16
movw $0x20, idt+49*16+2 // replace 0x20 with your code section selector
movw $0x8e00, idt+49*16+4
shr $16, %rax
mov %ax, idt+49*16+6
shr $16, %rax
mov %rax, idt+49*16+8
int $49
</syntaxhighlight>

This example differs from the previous one: it will not touch the screen, but will write the value "0x123abc" to 0x0 memory address and halt. It may be useful when there's no screen or BIOS available.

== Problems with IDTs ==
Many of us while OS dev'ing will encounter a problem with IDT's. Here are some solved problems with IDT's

This is for solved problems. The unsolved ones can be found here on the
[http://forum.osdev.org/viewtopic.php?f=1&t=24805 Forum]

==Problems==
Please post '''''Completed''''' problems here.

First of all, check your GDT.

Keyboard handlers need to actually read the scancode from port 0x60—it's not enough to just have the handler print something to indicate success and then send EOI. The symptoms are identical to forgetting to send EOI.

== IDT problems in Assembly ==
Make sure the structure is correct and you are using linear addresses.


=== FASM notice ===
=== FASM notice ===


Since fasm doesn't accept the normal way as described above, I will describe it.
Since FASM doesn't accept the normal way as described above, I will describe it.
Fasm does, however, support shl and shr, so to describe the higher part of an ISR address, we just use ''label shl 0x10'' where label is the name of the ISR.
FASM does, however, support shl and shr, so to describe the higher part of an ISR address, we just use ''label shl 0x10'' where label is the name of the ISR.
To define the higher part, we need to write a little more, since fasm use 64 bit, before compiling.
To define the higher part, we need to write a little more, since fasm use 64 bit, before compiling.
This means that IF we just shl and shr, it will be that same as before.
This means that IF we just shl and shr, it will be that same as before.
This is how we are supposed to do: (label shl 0x30) shr 0x30
This is how we are supposed to do: (label shl 0x30) shr 0x30
Here is a little example, so you can see how it works:
Here is a little example, so you can see how it works:
<source lang="asm">
<syntaxhighlight lang="asm">
idt:
idt:
dw ((isr1 shl 0x30) shr 0x30) ; the low part of the address
dw ((isr1 shl 0x30) shr 0x30) ; the low part of the address
Line 141: Line 211:
isr1:
isr1:
mov ax,0xdead
mov ax,0xdead
</syntaxhighlight>
</source>


== See Also ==
=== Articles ===
*[[IDT]]
*[[IDT_problems|IDT Problems]]


[[Category:Troubleshooting]]
[[Category:Troubleshooting]]
[[Category:FAQ]]
[[Category:FAQ]]
[[Category:Interrupts]]

Latest revision as of 04:32, 9 June 2024

This page is a sort of TroubleShooting manual to help you getting through common interrupts framework problems encountered by guests and members of the forum

Make sure you collected enough information about your own situation (for instance running your code in Bochs).

ISR problems

My handler doesn't get called?! (Assembly)

For this test, you need to call the interrupt yourself, by software. Don't try to get IRQ handled right from the start before you're sure your IDT setup is correct. You need to have:

  • Your IDT loaded and filled properly.
  • Your IDT's linear address loaded in a structure together with the table's size (in bytes, IIRC). Be especially cautious if you have a Higher Half Kernel design or did not set up identity paging.
  • A valid Code selector and offset in the descriptor, proper type, etc.
  • A handling code at the defined offset.

see test code below

My Handler doesn't get called (C)?!

If you are programming the IDT setup in C, make sure the IDTR structure has been correctly understood by your compiler. As Intel's 6 bytes structures infringe most compiler's packing rules, you'll need to use either bitfields or packing pragmas. Use sizeof() and OFFSETOF() macros to make sure the expected definition is used (a runtime test would be fine)

My handler is called but it doesn't return !?

Try to run it in the Bochs and see if you get any exception report. Program all your exception to have the same kind of behavior as the example, but displaying a character indicating the fault. Exceptions occurring at the end of an interrupt handler are usually due to a wrong stack operation within the handler.

  • don't try to return from an exception (unless you solved its cause). Returning from a division by zero, for instance, makes no sense at all
  • pops everything you push, but no more
  • make sure you didn't forget the CPU-pushed error code (for exceptions 8,10 and 14 at least)
  • make sure your handler doesn't trash unexpected registers. For exceptions and hardware IRQ handlers, no registers *at all* should be modified.

Another common source of error at this point comes from mis implementation of ISR in C. Check the InterruptServiceRoutines page for enlightenment ...

IRQ problems

Now that you're sure an interrupt can be called and can return, you're ready to enable hardware interrupts. As a first step, you're suggested to enable the _keyboard handler only_, as you'll have almost complete control of what it does. Use the mask feature of the PIC to enable/disable some handlers.

   outb(0x21,0xfd);
   outb(0xa1,0xff);
   enable(); // asm("sti");

I'm receiving a General Protection Fault interrupt immediately after returning from my first interrupt

After initializing the gdt with the lgdt command, make sure that you are performing a long jump. For example:

init_gdt:
    lgdt [gdt_table]
    jmp 0x08:longjmp_after_gdt
longjmp_after_gdt:
    ; Do something like repoint segment registers next

I'm receiving EXC9 instead of IRQ1 when striking a key?!

You missed the PIC vector reprogramming step. Check Can I remap the PIC? page. Note that if you remap the PIC vectors out of the IDT you'll get a GPF exception instead of any interrupt.

I'm receiving a double fault after enabling interrupts

May be a different symptom for the same error as above, this time caused by a timer interrupt calling vector 8. May also be caused if you've enabled interrupts in protected mode but haven't got an interrupt handler defined for whatever vector you've remapped the timer to, as the timer interrupt will come soon after enabling interrupts and cause a fault unless you've got a handler for it or you've masked it.

I'm not receiving any IRQ

Make sure you receive software interrupts first. Also make sure you enabled the IRQ of your interest on the PIC mask and that you enabled the cascading line (bit #2 of the master) if you're waiting for a slave IRQ.

I can only receive one IRQ

Each IRQ needs to be acknowledged to the PIC manually by sending an EOI. You need to have

 outb(0x20,0x20)

within any master handler and any

 outb(0x20,0x20); outb(0xa0,0x20);

within any slave handler.

When handling the keyboard IRQ, make sure that you read the byte the keyboard sends you. The interrupt might not trigger again until it has been read:

 unsigned char scan_code = inb(0x60);

Also, if you are following the barebones tutorial, be sure that your main function doesn't exit too soon (because when it does, it disables interrupts). A common solution to make sure it doesn't exit prematurely is to add

 for(;;) {
    asm("hlt");
 }

to the end of your main kernel function. The for loop is necessary because execution continues after the CPU receives an interrupt.

When I try to enable the PIT, the keyboard doesn't work anymore

A common mistake is that people reload the mask with 0xFE when they want to add timer, but doing this actually enables only the timer and disables the keyboard (bit #1 of 0xFE is set!) The correct value for enabling both keyboard and timer is 0xFC.

I keep getting an IRQ7 for no apparent reason

This is a known problem that cannot be prevented from happening, although there is a workaround. When any IRQ7 is received, simply read the In-Service Register

 outb(0x20, 0x0B); unsigned char irr = inb(0x20);

and check if bit 7

irr & 0x80

is set. If it isn't, then return from the interrupt without sending an EOI.

For more information, including a more detailed explanation, see Brendan's post in this thread.


what does "shift operator may only be applied to scalar values" mean?

You're trying to load a 16-bits field (a part of the IDT descriptor) with a reference to a 32-bit label that is subject to relocation. Try to replace

 isr_label:
    iret
 bad_stuff dw isr_label & 0xFFFF
           dw 0xdead
           dw 0xbeef
           dw isr_label >> 16

by something that extracts a 'pure value' from the address (e.g. the difference of two addresses are a pure value and $$ means to NASM the start of the section)

 %define BASE_OF_SECTION SOME_CONSTANT_YOU_SHOULD_KNOW
isr_label:
   iret
 good_stuff dw (BASE_OF_SECTION + isr_label - $$) & 0xFFFF
            dw 0xcafe
            dw 0xbabe
            dw (BASE_OF_SECTION + isr_label - $$) >> 16

The role of

BASE_OF_SECTION

is to adjust the pure offset to the real situation (usually as defined in your linker script), e.g. if your kernel get loaded at 1MB, you'll set it to 0x100000 to keep the CPU happy.

Assembly Examples

NASM

This example is made for x86 CPUs running in IA32 mode (32-bit).

 int_handler:
    mov ax, LINEAR_DATA_SELECTOR
    mov gs, ax
    mov dword [gs:0xB8000],') : '
    hlt
 
 idt:
    resd 50*2
 
 idtr:
    dw (50*8)-1
    dd LINEAR_ADDRESS(idt)
 
 test1:
    lidt [idtr]
    mov eax,int_handler
    mov [idt+49*8],ax
    mov word [idt+49*8+2],CODE_SELECTOR
    mov word [idt+49*8+4],0x8E00
    shr eax,16
    mov [idt+49*8+6],ax
    int 49

should display a smiley on the top-left corner ... then the CPU is halted indefinitely.

GNU Assembler

This example sets up an interrupt handler in long mode.

.text
int_handler:
    movq $0x123abc, 0x0 // this places magic value "0x123abc" at the beginning of memory
    hlt

.p2align 4
idt:
    .skip 50*16

  idtr:
    .short (50*16)-1
    .quad idt

.globl do_test
do_test:
    lidt idtr
    movq $int_handler, %rax
    mov %ax, idt+49*16
    movw $0x20, idt+49*16+2 // replace 0x20 with your code section selector
    movw $0x8e00, idt+49*16+4
    shr $16, %rax
    mov %ax, idt+49*16+6
    shr $16, %rax
    mov %rax, idt+49*16+8
    int $49

This example differs from the previous one: it will not touch the screen, but will write the value "0x123abc" to 0x0 memory address and halt. It may be useful when there's no screen or BIOS available.

Problems with IDTs

Many of us while OS dev'ing will encounter a problem with IDT's. Here are some solved problems with IDT's

This is for solved problems. The unsolved ones can be found here on the Forum

Problems

Please post Completed problems here.

First of all, check your GDT.

Keyboard handlers need to actually read the scancode from port 0x60—it's not enough to just have the handler print something to indicate success and then send EOI. The symptoms are identical to forgetting to send EOI.

IDT problems in Assembly

Make sure the structure is correct and you are using linear addresses.

FASM notice

Since FASM doesn't accept the normal way as described above, I will describe it. FASM does, however, support shl and shr, so to describe the higher part of an ISR address, we just use label shl 0x10 where label is the name of the ISR. To define the higher part, we need to write a little more, since fasm use 64 bit, before compiling. This means that IF we just shl and shr, it will be that same as before. This is how we are supposed to do: (label shl 0x30) shr 0x30 Here is a little example, so you can see how it works:

 idt:
  dw  ((isr1 shl 0x30) shr 0x30)        ; the low part of the address
  dw   0x8    ; selector
  db   0
  db   010001110b  ; type
  dw (isr1 shr 0x10) the high part of the address
 
 isr1:
  mov ax,0xdead

See Also

Articles