Reputation: 339
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?
Upvotes: 2
Views: 1499
Reputation: 339
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.
Upvotes: 3