Page 1 of 1

IRQs - Why and What

Posted: Tue Feb 20, 2007 1:12 am
by XCHG
I had some questions about IRQs and I would really appreciate it if somebody could answer them for me.

1. Do we have to handle IRQs using Interrupt Service Routines just as we handle traps?
2. We have IRQ #0 and Division by Zero Trap which uses the #0 index in the IDT. Do we have to tell the CPU which one to handle?
3. Are IRQs enabled and disabled with STI/CLI respectively?
4. Are IRQs sent to the CPU without the initialization of Control Words of the 8259 Chip?

Thanks in advance.

Re: IRQs - Why and What

Posted: Tue Feb 20, 2007 2:15 am
by JoeKayzA
XCHG wrote:1. Do we have to handle IRQs using Interrupt Service Routines just as we handle traps?
When an IRQ (from a peripheral device) occurs, it *triggers* an interrupt in the cpu, and thus an interrupt service routine is called. It basically is the same, the main difference is that you'll have to send at least an EOI to the interrupt controller (in addition to IRET).
XCHG wrote:2. We have IRQ #0 and Division by Zero Trap which uses the #0 index in the IDT. Do we have to tell the CPU which one to handle?
This is one thing that the programmable interrupt controller is for. It can map IRQs to certain interrupt ranges. Usually, you'll map IRQs into a higher range (like, starting at 0x20). This would mean that IRQ #0 now equals interrupt 0x20 and so on, thus you can distinguish them better.
XCHG wrote:3. Are IRQs enabled and disabled with STI/CLI respectively?
When you disable the interrupt flag (with CLI), you won't receive any interrupts anymore, so you won't receive any IRQs either, but they'll still arrive at the interrupt controller. So you got 2 seperate stages here: The interrupt controller and the cpu itself. STI/CLI only controls the cpu receiving interrupts.
XCHG wrote:4. Are IRQs sent to the CPU without the initialization of Control Words of the 8259 Chip?
I'm not sure, but I guess the BIOS already initializes the 8259 with a basic set of functionality. It is recommended however that you at least remap the IRQ ranges (like mentioned above) and disable/enable IRQs as you need them (there is no need to enable a floppy controller's IRQ line, f.ex., when you can't handle the device yet).

I hope I could help you!

cheers
Joe

Posted: Sun Feb 25, 2007 1:58 am
by XCHG
Okay, that really helped. Thank you so much. Although I have one more question about IRQs if anybody is willing to help.

I have set up an IDT with 50 slots. I then initialize the 8259A and map IRQs to 0x20 and 0x28 in my IDT. I then build the first 32 Trap Handlers in my IDT for CPU generated exceptions/traps and load the IDT and set the interrupt flag in the Flags Register. What happens after that is strange because the kernel does *not* crash but whenever a key is pressed on the keyboard, my Int6Handler (Invalid Opcode Exception) is called. Can anybody help me with this or tell me what I might be doing wrong?

Posted: Sun Feb 25, 2007 7:59 am
by frank
XCHG wrote:Okay, that really helped. Thank you so much. Although I have one more question about IRQs if anybody is willing to help.

I have set up an IDT with 50 slots. I then initialize the 8259A and map IRQs to 0x20 and 0x28 in my IDT. I then build the first 32 Trap Handlers in my IDT for CPU generated exceptions/traps and load the IDT and set the interrupt flag in the Flags Register. What happens after that is strange because the kernel does *not* crash but whenever a key is pressed on the keyboard, my Int6Handler (Invalid Opcode Exception) is called. Can anybody help me with this or tell me what I might be doing wrong?
When a key on the keyboard is pressed it would send an IRQ1 when the CPU calls the interrupt handler for IRQ1 it could point to an invalid opcode. Try setting up the rest of the interrupt handlers, the ones for the IRQs.

Posted: Thu Mar 01, 2007 1:14 am
by XCHG
Thank you for your reply. I have set the procedures for all CPU generated traps/interrupts/faults up to 0x20 and then I created two procedures to handle IRQs that are sent from the master PIC and another for the slave 8259A. The code for my kernel is given below:

Code: Select all

[BITS 32]
[ORG  0x00100000]
VIDEOSEGMENT            EQU             0x000B8000
VIDEOBYTECOUNT          EQU             (80*25) << 1
KERNELENTRY             EQU             0x00100000
KERNEL_SIZE_IN_KB       EQU             0x04
IDT_ENTRY_COUNT         EQU             0x30
; --------------------------------------------------
%IDEFINE OFFSET
%IDEFINE PTR
; --------------------------------------------------
%MACRO  SETIDTENTRY 2 ; (int IDTNumber , void IDTProcedure)
  MOV     EBX , OFFSET IDT + (%1 * 0x08)
  MOV     EAX , %2
  MOV     WORD PTR [EBX] , AX
  MOV     WORD PTR [EBX + 0x02] , 0x0008
  MOV     WORD PTR [EBX + 0x04] , 0x8E00
  SHR     EAX , 0x10
  MOV     WORD PTR [EBX + 0x06] , AX
%ENDMACRO
; --------------------------------------------------
PIC1            EQU              0x20
PIC2            EQU              0xA0
PIC1_COMMAND    EQU              PIC1
PIC1_DATA       EQU              (PIC1+1)
PIC2_COMMAND    EQU              PIC2
PIC2_DATA       EQU              (PIC2+1)
PIC_EOI         EQU              0x20
ICW1_ICW4       EQU              0x01
ICW1_INIT       EQU              0x10
ICW4_8086       EQU              0x01
; --------------------------------------------------
[SECTION .text]
START:
  CLI

  CALL    __ClearScreen

  ; Fill all the IDT slots with zero
  MOV     ECX , IDT_ENTRY_COUNT
  MOV     EBX , OFFSET IDT
  XOR     EAX , EAX
  @@__FillIDTWithZero:
    MOV     DWORD PTR [EBX] , EAX
    MOV     DWORD PTR [EBX + 0x04] , EAX
    ADD     EBX , 0x08
    DEC     ECX
    JNZ     @@__FillIDTWithZero

  ; Initialize the 8259A PIC
  IN      AL , PIC1_DATA
  MOV     DL , AL
  IN      AL , PIC2_DATA
  MOV     DH , AL

  MOV     AL , ICW1_INIT | ICW1_ICW4
  OUT     PIC1_COMMAND , AL

  OUT     PIC2_COMMAND , AL

  MOV     AL , 0x20
  OUT     PIC1_DATA , AL

  MOV     AL , 0x28
  OUT     PIC2_DATA , AL

  MOV     AL , 0x04
  OUT     PIC1_DATA , AL

  MOV     AL , 0x02
  OUT     PIC2_DATA , AL

  MOV     AL , ICW4_8086
  OUT     PIC1_DATA , AL

  OUT     PIC2_DATA , AL

  MOV     AL , DL
  OUT     PIC1_DATA , AL

  MOV     AL , DH
  OUT     PIC2_DATA , AL


  ; Set IDT entries for the first 32 slots
  SETIDTENTRY   0x00 , __Trap0Handler
  SETIDTENTRY   0x01 , __Trap1Handler
  SETIDTENTRY   0x02 , __Trap2Handler
  SETIDTENTRY   0x03 , __Trap3Handler
  SETIDTENTRY   0x04 , __Trap4Handler
  SETIDTENTRY   0x05 , __Trap5Handler
  SETIDTENTRY   0x06 , __Trap6Handler
  SETIDTENTRY   0x07 , __Trap7Handler
  SETIDTENTRY   0x08 , __Trap8Handler
  SETIDTENTRY   0x09 , __Trap9Handler
  SETIDTENTRY   0x0A , __Trap10Handler
  SETIDTENTRY   0x0B , __Trap11Handler
  SETIDTENTRY   0x0C , __Trap12Handler
  SETIDTENTRY   0x0D , __Trap13Handler
  SETIDTENTRY   0x0E , __Trap14Handler
  SETIDTENTRY   0x0F , __Trap15Handler
  SETIDTENTRY   0x10 , __Trap16Handler
  SETIDTENTRY   0x11 , __Trap17Handler
  SETIDTENTRY   0x12 , __Trap18Handler
  SETIDTENTRY   0x13 , __Trap19To31Handler
  SETIDTENTRY   0x14 , __Trap19To31Handler
  SETIDTENTRY   0x15 , __Trap19To31Handler
  SETIDTENTRY   0x16 , __Trap19To31Handler
  SETIDTENTRY   0x17 , __Trap19To31Handler
  SETIDTENTRY   0x18 , __Trap19To31Handler
  SETIDTENTRY   0x19 , __Trap19To31Handler
  SETIDTENTRY   0x1A , __Trap19To31Handler
  SETIDTENTRY   0x1B , __Trap19To31Handler
  SETIDTENTRY   0x1C , __Trap19To31Handler
  SETIDTENTRY   0x1D , __Trap19To31Handler
  SETIDTENTRY   0x1E , __Trap19To31Handler
  SETIDTENTRY   0x1F , __Trap19To31Handler


  ; Fill IRQ gates in the IDT for the first 8 IRQs
  MOV     EBX , OFFSET IDT + (0x20 * 0x08)
  MOV     ECX , 0x08
  MOV     EAX , OFFSET __MasterPICHandler
  @@__FillIRQSlots1to8:
    MOV     WORD PTR [EBX] , AX
    MOV     WORD PTR [EBX + 0x02] , 0x0008
    MOV     WORD PTR [EBX + 0x04] , 0x8F00
    ROR     EAX , 0x10
    MOV     WORD PTR [EBX + 0x06] , AX
    ADD     EBX , 0x08
    DEC     ECX
    JNZ     @@__FillIRQSlots1to8

  ; Fill IRQ gates in the IDT for the last 8 IRQs
  MOV     ECX , 0x08
  MOV     EAX , OFFSET __SlavePICHandler
  @@__FillIRQSlots9to16:
    MOV     WORD PTR [EBX] , AX
    MOV     WORD PTR [EBX + 0x02] , 0x0008
    MOV     WORD PTR [EBX + 0x04] , 0x8F00
    ROR     EAX , 0x10
    MOV     WORD PTR [EBX + 0x06] , AX
    ADD     EBX , 0x08
    DEC     ECX
    JNZ     @@__FillIRQSlots9to16

  LIDT    [IDTR]
  STI

  HLT
  
  

  %INCLUDE "Procs.asm"
  %INCLUDE "VideoT.asm"
; --------------------------------------------------
  __MasterPICHandler:
    MOV     AL , PIC_EOI
    OUT     PIC1_COMMAND , AL
    IRET

  __SlavePICHandler:
    MOV     AL , PIC_EOI
    OUT     PIC2_COMMAND , AL
    OUT     PIC1_COMMAND , AL
    IRET
; --------------------------------------------------
  Trap0Msg      DB      'Division by zero', 0x00
  Trap1Msg      DB      'Debug Exception', 0x00
  Trap2Msg      DB      'Non-maskable Interrupt Exception', 0x00
  Trap3Msg      DB      'Breakpoint Exception', 0x00
  Trap4Msg      DB      'Into Detected Overflow Exception', 0x00
  Trap5Msg      DB      'Out of Bounds Exception', 0x00
  Trap6Msg      DB      'Invalid Opcode Exception', 0x00
  Trap7Msg      DB      'No Coprocessor Exception', 0x00
  Trap8Msg      DB      'Double Fault Exception', 0x00
  Trap9Msg      DB      'Coprocessor Segment Overrun Exception', 0x00
  Trap10Msg     DB      'Bad TSS Exception', 0x00
  Trap11Msg     DB      'Segment Not Present Exception', 0x00
  Trap12Msg     DB      'Stack Fault Exception', 0x00
  Trap13Msg     DB      'General Protection Fault', 0x00
  Trap14Msg     DB      'Page Fault Exception', 0x00
  Trap15Msg     DB      'Unknown Interrupt Exception', 0x00
  Trap16Msg     DB      'Coprocessor Fault Exception', 0x00
  Trap17Msg     DB      'Alignment Check Exception', 0x00
  Trap18Msg     DB      'Machine Check Exception', 0x00
  Trap19Msg     DB      'Reserved Exception', 0x00
; --------------------------------------------------
  __Trap0Handler:
    ___WriteStr       Trap0Msg
    JMP     $
    ; --------------------
  __Trap1Handler:
    ___WriteStr       Trap1Msg
    JMP     $
    ; --------------------
  __Trap2Handler:
    ___WriteStr       Trap2Msg
    JMP     $
    ; --------------------
  __Trap3Handler:
    ___WriteStr       Trap3Msg
    JMP     $
    ; --------------------
  __Trap4Handler:
    ___WriteStr       Trap4Msg
    JMP     $
    ; --------------------
  __Trap5Handler:
    ___WriteStr       Trap5Msg
    JMP     $
    ; --------------------
  __Trap6Handler:
    ___WriteStr       Trap6Msg
    JMP     $
    ; --------------------
  __Trap7Handler:
    ___WriteStr       Trap7Msg
    JMP     $
    ; --------------------
  __Trap8Handler:
    ___WriteStr       Trap8Msg
    JMP     $
    ; --------------------
  __Trap9Handler:
    ___WriteStr       Trap9Msg
    JMP     $
    ; --------------------
  __Trap10Handler:
    ___WriteStr       Trap10Msg
    JMP     $
    ; --------------------
  __Trap11Handler:
    ___WriteStr       Trap11Msg
    JMP     $
    ; --------------------
  __Trap12Handler:
    ___WriteStr       Trap12Msg
    JMP     $
    ; --------------------
  __Trap13Handler:
    ___WriteStr       Trap13Msg
    JMP     $
    ; --------------------
  __Trap14Handler:
    ___WriteStr       Trap14Msg
    JMP     $
    ; --------------------
  __Trap15Handler:
    ___WriteStr       Trap15Msg
    JMP     $
    ; --------------------
  __Trap16Handler:
    ___WriteStr       Trap16Msg
    JMP     $
    ; --------------------
  __Trap17Handler:
    ___WriteStr       Trap17Msg
    JMP     $
    ; --------------------
  __Trap18Handler:
    ___WriteStr       Trap18Msg
    JMP     $
    ; --------------------
  __Trap19To31Handler:
    ___WriteStr       Trap19Msg
    JMP     $
; --------------------------------------------------
  ALIGN 8, NOP
  [SECTION .bss]
  IDT:
    RESD (IDT_ENTRY_COUNT << 1)
  IDT_END:
  ;------------------------------
  [SECTION .data]
  IDTR:
    DW (IDT_END - IDT) - 1
    DD IDT

  String1             DB            'Kernel', 0x00
  String2             DB            'A key is pressed', 0
  VideoCursor         DD            0x00000000

  TIMES               (KERNEL_SIZE_IN_KB << 10)-($-$$) DB 0x00
I then tried aligning all the interrupt handler procedures' codes on a DWORD boundary and it didn't work either. There must be something wrong with the definition of the procedures I reckon because why would the CPU report "Invalid Opcode Exception" when a key is pressed on the keyword? Or maybe there is something wrong with the way I am initializing the PICs.

I'd really appreciate it if somebody could help me.

Posted: Thu Mar 01, 2007 1:52 am
by XCHG
Okay I made some changes to the code and now the IRQs are disabled by default:

Code: Select all

%MACRO OUTP 2
  ; %1 = Port Number
  ; %2 = Value
  MOV     AL , %2
  OUT     %1 , AL
%ENDMACRO
; -------------------------------------------------- 
  OUTP    PIC1 ,0x11
  OUTP    PIC2, 0x11
  OUTP    PIC1_DATA, 0x20
  OUTP    PIC2_DATA, 0x28
  OUTP    PIC1_DATA, 0x04
  OUTP    PIC2_DATA, 0x02
  OUTP    PIC1_DATA, 0x01
  OUTP    PIC2_DATA, 0x01
  OUTP    PIC1_DATA, 0xFF
  OUTP    PIC2_DATA, 0xFF
I then wrote this procedure that enables a specific IRQ, given its vector number:

Code: Select all

; --------------------------------------------------
  __EnableIRQ: ;AL = IRQ Number
    MOV     CL , AL
    MOV     AX , 0xFFFF
    MOV     DX , 0x0001
    SHL     DX , CL
    NOT     DX
    AND     AX , DX
    OUTP    PIC1_DATA , AL
    MOV     AL , AH
    OUTP    PIC2_DATA , AL
    RET
I then attempted to enable IRQ 0x01 and then the same problem happened again. As soon as I pressed a key on my keyboard, Invalid Opcode Exception was fired!!! [umm, makes a sad face]

Posted: Thu Mar 01, 2007 1:57 am
by Combuster
well, for one your idt lists only 32 entries (0-31) and you try to receive interrupts in the range 32-47, which does not work...
Not sure why it would #UD instead of #GP though...
[edit]the holes in your idt probrably cause your code to get sent off into the weeds, causing an UD there[/edit]

Posted: Thu Mar 01, 2007 4:16 am
by XCHG
I have defined the IDT in this way:

Code: Select all

; --------------------------------------------------
  ALIGN 8, NOP
  [SECTION .bss]
  IDT:
    RESD (IDT_ENTRY_COUNT << 1)
  IDT_END:
  ;------------------------------
  [SECTION .data]
  IDTR:
    DW (IDT_END - IDT) - 1
    DD IDT
while the IDT_ENTRY_COUNT constant is set to 0x30 at the 7th step of the kernel's code. The IDT is creating (IDT_ENTRY_COUNT * 2) DWORDs which would make it (48 * 2) * 4 bytes = 384 bytes or enough room for 48 interrupt routines. Correct me if I am wrong because I have been trying to make this work for over a week now and no dice yet. Thank you for your reply.

Posted: Thu Mar 01, 2007 4:46 am
by XCHG
I had also done something very embarrassing in the code which was rotating the accumulator to the right 16 bits in my iteration like this:

Code: Select all

 ; Fill IRQ gates in the IDT for the first 8 IRQs 
  MOV     EBX , OFFSET IDT + (0x20 * 0x08) 
  MOV     ECX , 0x08 
  MOV     EAX , OFFSET __MasterPICHandler 
  @@__FillIRQSlots1to8: 
    MOV     WORD PTR [EBX] , AX 
    MOV     WORD PTR [EBX + 0x02] , 0x0008 
    MOV     WORD PTR [EBX + 0x04] , 0x8F00 
    ROR     EAX , 0x10 
    MOV     WORD PTR [EBX + 0x06] , AX 
    ADD     EBX , 0x08 
    DEC     ECX 
    JNZ     @@__FillIRQSlots1to8
After the first iteration, the AX will still have the High Order Word of the __MasterPICHandler procedure and so the consecutive iterations will place wrong values in the IDT. I fixed that problem in this way:

Code: Select all

  ; Fill IRQ gates in the IDT for the first 8 IRQs
  MOV     EBX , OFFSET IDT + (0x20 * 0x08)   ; EBX points to IDT at byte (0x20 * 0x08)
  MOV     ECX , 0x08                         ; Need to fill 8 IDT Descriptors
  MOV     EAX , OFFSET __MasterPICHandler    ; EAX Points to the Master PIC Procedure
  MOV     EDX , EAX                          ; EDX is the same pointer now
  SHR     EDX , 0x10                         ; DX is the High Order Word of this pointer
  @@__FillIRQSlots1to8:                      ; Iteration begins here
    MOV     WORD PTR [EBX] , AX              ; Put the Low Order Word of the pointer
    MOV     WORD PTR [EBX + 0x02] , 0x0008   ; The Code Segment Descriptor
    MOV     WORD PTR [EBX + 0x04] , 0x8E00   ; Flags of the Interrupt Gate
    MOV     WORD PTR [EBX + 0x06] , DX       ; Put the High Order Word of the pointer
    ADD     EBX , 0x08                       ; Move to the next Descriptor
    DEC     ECX                              ; Decrement the counter
    JNZ     @@__FillIRQSlots1to8             ; Keep iterating until ECX != 0


  ; Fill IRQ gates in the IDT for the last 8 IRQs
  MOV     ECX , 0x08                         ; 8 More Descriptors left for the Slave 8259
  MOV     EAX , OFFSET __SlavePICHandler     ; EAX points to the Slave PIC Procedure
  MOV     EDX , EAX                          ; EDX is the same pointer now
  SHR     EDX , 0x10                         ; DX is the High Order Word of the pointer
  @@__FillIRQSlots9to16:                     ; The iteration begins here
    MOV     WORD PTR [EBX] , AX              ; Put the Low Order Word of the pointer
    MOV     WORD PTR [EBX + 0x02] , 0x0008   ; The Code Segment Descriptor
    MOV     WORD PTR [EBX + 0x04] , 0x8E00   ; Flags of the Interrupt Gate
    MOV     WORD PTR [EBX + 0x06] , DX       ; The High Order Word of the pointer
    ADD     EBX , 0x08                       ; Move to the next Descriptor
    DEC     ECX                              ; Decrement the counter
    JNZ     @@__FillIRQSlots9to16            ; Keep iterating until ECX != 0

  MOV     AL , 0x01
  CALL    __EnableInterruptRequest
  
  LIDT    [IDTR]
  STI
In this way, the __MasterPICHandler is called and the Invalid Opcode Exception is no longer generated by the CPU. One more thing that I did was to replace the HLT at the end of my kernel's code by JMP $. I noticed that when __MasterPICHandler issues the IRET instruction, the CPU will attempt to execute the instruction after HLT which is data and then it generates the Invalid Opcode Exception. Now everything works fine except one thing: the first time a key is pressed, the __MasterPICHandler does its job but the second time, no, nothing, nada, zip. I know that it's because of the JMP $ instruction but how can I work around this? Could somebody please help?

Posted: Thu Mar 01, 2007 8:53 am
by uglyoldbob
Don't forget to send the EOI in the keyboard IRQ handler.

Like this:

mov al, 0x20

out 0x20, al
out 0xA0, al ;this is only needed for IRQ's coming off of the slave PIC

Posted: Thu Mar 01, 2007 1:05 pm
by XCHG
(un)fortunately, I already am doing it if you look at the code for both the __MasterPICHandler and __SlavePICHandler [makes a sad face]

Posted: Thu Mar 01, 2007 5:53 pm
by Combuster
Time for the cliche advice: have you tried debugging using Bochs?

Posted: Thu Mar 01, 2007 6:56 pm
by Brendan
Hi,
XCHG wrote:Now everything works fine except one thing: the first time a key is pressed, the __MasterPICHandler does its job but the second time, no, nothing, nada, zip. I know that it's because of the JMP $ instruction but how can I work around this? Could somebody please help?
That is because you don't get the byte out of the keyboard controller.

Basically, the keyboard sends a byte to the keyboard controller, then the keyboard controller puts it in a one byte buffer and sends an IRQ to the CPU to say "byte received". If the CPU doesn't take this byte out of the keyboard controller's buffer, then the next time the keyboard tries to send a byte the buffer is full. In this case the keyboard controller tells the keyboard to retry and doesn't send a new "byte received" IRQ to the CPU because a new byte wasn't received.

Try something like this:

Code: Select all

__MasterPICHandler:
    PUSH AX                                  ; <- DON'T TRASH REGISTERS!
    IN AL , 0x60                              ; <- Get byte from keyboard controller
    MOV     AL , PIC_EOI
    OUT     PIC1_COMMAND , AL
    POP AX
    IRET 
The next problem will be determining which IRQ caused __MasterPICHandler to be called. The best way is to have a seperate routine for each IRQ....


Cheers,

Brendan

Posted: Fri Mar 02, 2007 3:24 am
by XCHG
Thanks Brendan. My keyboard IRQs are now working and so are the other IRQs. I thought after the keyboard's microcontroller sent the Scan Code to the PC and asserted the IRQ #1, I didn't have to read the Scan Code from port 0x60 anymore. I thought it was useful when polling. I appreciate your help.

Thank you Joe, Frank, Combuster, everyone. Appreciations.