2
votes

Here is the source code my OS.

I made sure that when the CPU gets an interrupt from the 8259 PIC (Programmable Interrupt Controller), it always "offsets" correctly into the array of ISRs (Interrupt Service Routines). (You can find the whole code here.):

isrs:
  dd _isr0
  dd _isr1
  dd _irq_unhandled
  dd _irq_unhandled
  dd _irq_unhandled
  ....
  dd _isr32
  dd _isr33

Interrupt handlers not yet implemented are denoted by _irq_unhandled. So far, I got CPU exceptions and software interrupts (traps) working correctly. For example, when my program tries to divide by zero, it jumps to _isr0. Or when I try int 1, int 2, int 7, or whatever similar, the correct ISR in the isrs array (IDT; Interrupt Descriptor Table) is indexed and called. But now, I cannot get the PIC to execute my interrupt handler _isr32:

_isr32:
  mov bl, 5
  mov bh, 15
  ; mov eax, MovCur
  ; and eax, 0xFFFF
  call MovCur

  mov eax, PicIntrMsg
  ; and eax, 0xFFFF  ; retrieve offset only when base address is something different than 0
  ; call 0x38:0x1008a
  call Puts32

  mov al, 0x20
  out 0x20, al
  ret

_isr32 only prints a message to signify that it was called and sends an EOI (End Of Interrupt) message back to the PIC. Here is the routine to enable the PIC, with all interrupts disabled except the timer and the keyboard:

%define IRQ_0   0x20                ; IRQs 0-7 mapped to use interrupts 0x20-0x27
%define IRQ_8   0x28                ; IRQs 8-15 mapped to use interrupts 0x28-0x36

; Initialization Control Word 1
%define ICW1_SEND_IC4 0x1
%define ICW1_SINGLE 0x2
%define ICW1_ADDRESS_INTERVAL_4 0x4 ; if set, use addresss inter, else 8
%define ICW1_LEVEL_TRIGGERED 0x8
%define ICW1_PIC_INITIALIZED 0x10
%define ICW1_IVT_ADDR1 0x20
%define ICW1_IVT_ADDR2 0x40
%define ICW1_IVT_ADDR3 0x80

; Initialization
; 1. write ICW1 to port 20h
; 2. write ICW2 to port 21h
; 3. if ICW1 bit D1=1  do nothing
; if ICW1 bit D1=0  write ICW3 to port 20h
; 4. write ICW4 to port 21h
; 5. OCW's can follow in any order
; http://stanislavs.org/helppc/8259.html
MapPIC:
  cli
  ; Setup ICW1
  mov al, (ICW1_SEND_IC4 | ICW1_PIC_INITIALIZED)
  out 0x20, al
  out 0xa0, al

  ; Setup ICW2
    ; send ICW 2 to primary PIC
  ; the first 31 interrupts (0x0-0x1F) are reserved
    mov al, IRQ_0       ; Primary PIC handled IRQ 0..7. IRQ 0 is now mapped to interrupt number 0x20
    out 0x21, al

    ; send ICW 2 to secondary controller
    mov al, IRQ_8       ; Secondary PIC handles IRQ's 8..15. IRQ 8 is now mapped to use interrupt 0x28
    out 0xa1, al

  ; Setup ICW3
  mov al, 0x4                   ; 0x4 = 0100 Second bit (IR Line 2)
  out 0x21, al  ; send to data register

  ; Send ICW 3 to secondary PIC
    mov al, 0x2     ; 0010=> IR line 2
    out 0xa1, al    ; write to data register of secondary PIC

  ; Setup ICW4
    mov al, 0x1     ; bit 0 enables 80x86 mode

    ; send ICW 4 to both primary and secondary PICs
    out 0x21, al
    out 0xA1, al

  ; All done. Null out the data registers
    mov al, 0
    out 0x21, al
    out 0xa1, al

  ; Disable all IRQs, except the timer and the keyboard
  mov al, 0xfc
  out 0x21, al
  out 0xA1, al

  ret

(the full source code is in pic.inc).

Checking Bochs's log, IRQ0 did come from the PIC, as well as the keyboard interrupt:

...
00030543642d[PIC   ] IRQ line 0 now low
00030543646d[PIC   ] IRQ line 0 now high
00030763342d[PIC   ] IRQ line 0 now low
00030763346d[PIC   ] IRQ line 0 now high
00030916000i[KBD   ] internal keyboard buffer full, ignoring scancode.(27)
00030983046d[PIC   ] IRQ line 0 now low
00030983050d[PIC   ] IRQ line 0 now high
00031048000i[KBD   ] internal keyboard buffer full, ignoring scancode.(26)
00031202746d[PIC   ] IRQ line 0 now low
00031202750d[PIC   ] IRQ line 0 now high
...

You can check the full log here. The PIC is initialized correctly, according to the log:

00014765045d[PIC   ] master: init command 1 found
00014765045d[PIC   ]         requires 4 = 1
00014765045d[PIC   ]         cascade mode: [0=cascade,1=single] 0
00014765045d[PIC   ] master: ICW1: edge triggered mode selected
00014765046d[PIC   ] IO write to 00a0 = 11
00014765046d[PIC   ] slave: init command 1 found
00014765046d[PIC   ]        requires 4 = 1
00014765046d[PIC   ]        cascade mode: [0=cascade,1=single] 0
00014765046d[PIC   ] slave: ICW1: edge triggered mode selected
00014765048d[PIC   ] IO write to 0021 = 20
00014765048d[PIC   ] master: init command 2 = 20
00014765048d[PIC   ]         offset = INT 20
00014765050d[PIC   ] IO write to 00a1 = 28
00014765050d[PIC   ] slave: init command 2 = 28
00014765050d[PIC   ]        offset = INT 28
00014765052d[PIC   ] IO write to 0021 = 04
00014765052d[PIC   ] master: init command 3 = 04
00014765054d[PIC   ] IO write to 00a1 = 02
00014765054d[PIC   ] slave: init command 3 = 02
00014765056d[PIC   ] IO write to 0021 = 01
00014765056d[PIC   ] master: init command 4 = 01
00014765056d[PIC   ] normal EOI interrupt
00014765056d[PIC   ]        80x86 mode
00014765057d[PIC   ] IO write to 00a1 = 01
00014765057d[PIC   ] slave: init command 4 = 01
00014765057d[PIC   ] normal EOI interrupt
00014765057d[PIC   ]        80x86 mode
00014765059d[PIC   ] IO write to 0021 = fb
00014765059d[PIC   ] setting master pic IMR to fb
00014765060d[PIC   ] IO write to 00a1 = fb
00014765060d[PIC   ] setting slave pic IMR to fb
00014765064d[PIC   ] IO write to 0021 = 00
00014765064d[PIC   ] setting master pic IMR to 00

Despite all that, it still goes wrong and I don't know what I am missing. Can anybody help me?

1
The PIC initialization indeed looks correct... are you sure "nulling out" the data registers is required?cadaniluk
Have you initialized ss:esp?cadaniluk
@cad I tried not nulling out, but it's the same. But, I did not set ss when ISR is entered. I only set other segment registers, as you can see here. Is it important, and what value should I set ss to? Currently, my OS can enter an interrupt when an exception (i.e. divide by 0) occurs, selects correct ISR, executes and returns to previous executing instruction. Is something missing?Tu Do
Maybe I'm just not reading this right but your code does this to enable specific interrupts mov al, 0xfb out 0x21, al out 0xA1, al . 0xfb=11111011 . Since you write that value to the slave and master pic that seems to be enabling IRQ 2 (on master) and IRQ 10 (on slave). Wouldn't you want to enable INT 0 and INT 1 (both on master). Wouldn't that require writing 0xFC(11111100) to port 0x21 and 0xFF to port 0xA1?Michael Petch
@cad actually, I already setup and never change the ss when entering Stage3. You can see it here. Whenever an ISR is entered, ds, gs... are changed to kernel data segment, as you can see here.Tu Do

1 Answers

3
votes

Switching to userspace make my OS unable to receive interrupts from PIC anymore. If I don't enter userspace (i.e. infinite loop in the kernel space), the OS can receives PIC interrupts fine. It turned out that sysenter disable IF bit in EFLAGS register. When I put sti in the system call entry (that jumps to a routine depends on syscall number), interrupt works fine again. In my code, the routine is named Sysenter_Entry that compares syscall number in eax and jump accordingly (I will need to turn into an array of function pointers in the future).

Sysenter_Entry:
  sti   ; This solved the problem. VERY IMPORTANT.
  mov       bx, 0x10        ; set data segments to data selector (0x10)
  mov       ds, bx
    ; sysenter jumps here, is is executing this code at prividege level 0. Simular to Call Gates, normally we will
    ; provide a single entry point for all system calls.
  cmp eax, 0
  je clrscr
  cmp eax, 1
  je monitor_out
  cmp eax, 2
  je test_intr_kernel_space
  cmp eax, 3
  je test_intr_pic
  cmp eax, 4
  je STOP
  ; mov eax, GoodbyeMsg
  ; call Puts32
syscall_exit:
  ; restore back the stack for userspace afterward
  mov bx, 0x23
  mov ds, bx
  sysexit

Also, when switching to userspace the first time with iret, IF bit is also disabled, and I need to set the IF bit and put it back to EFLAGS register with pushf for interrupt to function in userspace.

After interrupt bit is set accordingly (either by sti or modifying EFLAGS), I can verify interrupt is working by seeing the ISR got called and checking Bochs log with sequences like this:

....
04433276227d[PIC   ] IRQ line 0 now high
04433276227d[PIC   ] signalling IRQ(0)
04433277486d[PIC   ] IO write to 0020 = 20
04433372617d[PIC   ] IRQ line 0 now low
....

That is, IRQ0 is high, then CPU responses with 0x20 to acknowledge the interrupt and IRQ0 is low again.