[BITS 32] FIRST_EXTERNAL_INT equ 0x30 LAST_EXTERNAL_INT equ 0x30+15 GLOBAL HalInterruptLoadIDTR GLOBAL HalInterruptGenericHandler GLOBAL HalInterruptHandlerPreamble GLOBAL HalInterruptHandlerPreambleBefore GLOBAL HalInterruptHandlerPreambleAfter GLOBAL HalInterruptInitializePICs GLOBAL HalIoDelay EXTERN g_InterruptHandlerTable EXTERN HalVideoPrint EXTERN HalInterruptSendIrqEoi ;; Save registers prior to calling a handler function. ;; This must be kept up to date with: ;; - Interrupt_State struct in int.h ;; - Setup_Initial_Thread_Context() in kthread.c %macro SAVE_REGISTERS 0 push eax push ebx push ecx push edx push esi push edi push ebp push ds push es push fs push gs %endmacro ;; Restore registers and clean up the stack after calling a handler function ;; (i.e., just before we return from the interrupt via an iret instruction). %macro RESTORE_REGISTERS 0 pop gs pop fs pop es pop ds pop ebp pop edi pop esi pop edx pop ecx pop ebx pop eax add esp, 8 %endmacro %macro INT_NO_ERROR 1 align 8 push 0xffffffff ; fake the error code push %1 ; push the int number jmp HalInterruptGenericHandler; %endmacro %macro INT_WITH_ERROR 1 align 8 push %1 ; error code already pushed, just push int num jmp HalInterruptGenericHandler %endmacro [SECTION .text] ;;; ;;; void ;;; InterruptLoadIDTR(IDT *p) ;;; ;;; Function to load the interrupt descriptor table register (IDTR) ;;; callable from C. ;;; HalInterruptLoadIDTR: mov eax, [esp+4] ; p lidt [eax] ret ;;; ;;; The following is code to initialize the master and slave 8259 ;;; programmable interrupt controllers (PICs). ;;; ;;; For ISA machines there are two 8259's, a master and a slave. Each has ;;; eight interrupt request (IR) pins and one interrupt (INT) pin. The ;;; master's INT pin is tied to the INT line of the microprocessor. The ;;; slave's INT pin is tied to the master's IR2 line. Thus the master's ;;; IR0, IR1, and IR3..7 are real devices. But the master's IR2 means ;;; that some IR pin on the slave is asserted. So the system IRQs are: ;;; ;;; (highest default priority) ;;; IRQ0 = master IR0 [IRQ0 = system timer] ;;; IRQ1 = master IR1 [IRQ1 = keyboard] ;;; IRQ2 = master IR2 --> IRQ8..IRQ15 = slave IR0..7 ;;; [IRQ8 = rt. timer][IRQ13 = coproc.] ;;; IRQ3 = master IR3 ;;; IRQ4 = master IR4 ;;; IRQ5 = master IR5 ;;; IRQ6 = master IR6 ;;; IRQ7 = master IR7 ;;; (lowest default priority) ;;; ;;; By default these IRQs raise the following interrupt numbers: ;;; ;;; IRQ0 = int 0x8 [collides with double exception] ;;; IRQ1 = int 0x9 [collides with coprocessor exception] ;;; IRQ2/9 = int 0x71 ;;; IRQ3 = int 0xb [collides with segment not present exception] ;;; IRQ4 = int 0xc [collides with stack fault exception] ;;; IRQ5 = int 0xd [collides with general protection exception] ;;; IRQ6 = int 0xe [collides with page fault] ;;; IRQ7 = int 0xf ;;; IRQ8 = int 0x70 ;;; IRQ10 = int 0x72 ;;; IRQ11 = int 0x73 ;;; IRQ12 = int 0x74 ;;; IRQ13 = int 0x75 ;;; IRQ14 = int 0x76 ;;; IRQ15 = int 0x77 ;;; ;;; So the problem is that IRQ0, IRQ1, IRQ3..6 all, by default, map to ;;; interrupt numbers that collide with interrupts raised by the cpu ;;; as a result of a software exception. The solution to this problem ;;; is to remap what interrupts are raised by these IRQs by reprogramming ;;; the 8259 PICs to raise new interrupt numbers. ;;; ;;; This is accomplished by sending initialization command words (ICWs) ;;; to I/O ports 0x20-0x21 (master PIC) and 0xA0-0xA1 (slave PIC). By doing ;;; so we can remap the interrupt numbers raised by IRQs so that: ;;; ;;; IRQ0 = 0x30 IRQ8 = 0x38 ;;; IRQ1 = 0x31 IRQ10 = 0x3A ;;; IRQ2/9 = 0x39 IRQ11 = 0x3B ;;; IRQ3 = 0x33 IRQ12 = 0x3C ;;; IRQ4 = 0x34 IRQ13 = 0x3D ;;; IRQ5 = 0x35 IRQ14 = 0x3E ;;; IRQ6 = 0x36 IRQ15 = 0x3F ;;; IRQ7 = 0x37 ;;; ;;; ICW1 is the same for both the master and slave PIC. Here's the meaning ;;; of the individual bits in the word: ;;; ;;; F E D C B A 9 8 | 7 6 5 4 3 2 1 0 ;;; (not used) | | | | |__ 1=expect ICW4 ;;; | | | |_____ 0=cascade 1=single ;;; | | |________ 0=interval-4, 1=8 ;;; | |___________ 0=edge triggered ;;; | 1=level triggered ;;; |______________ must be 1 ;;; ;;; BOTH_ICW1 equ 0x11 ; expect ICW4, cascade mode, call address ; interval=8, edge triggered mode ;;; ;;; ICW2 tells the PICs their base interrupt number. For example, the ;;; default base interrupt for the master PIC is 0x8. Thus IRQ0=0x8, ;;; IRQ1=0x9 ... IRQ7=0xF. As stated, we want to relocate these. ;;; MASTER_ICW2 equ 0x30 ; use ints 0x30..0x37 SLAVE_ICW2 equ 0x38 ; use ints 0x38..0x3F ;;; ;;; ICW3 to the master tells it which of its IR pins is connected to the ;;; slave PIC. ICW3 to the slave tells its id number. ;;; MASTER_ICW3 equ 0x04 ; slave on IR pin 2 SLAVE_ICW3 equ 0x02 ; slave id=2 ;;; ;;; ICW4 to both PICs is another bitvector to set some features: ;;; ;;; ;;; F E D C B A 9 8 | 7 6 5 4 3 2 1 0 ;;; (not used) | ---- | |__ 0=MCS80/85 1=8086/88 ;;; | | |_____ 0=normal 1=auto EOI ;;; | |_________ 00=non-buf mode ;;; | 10=buf mode, slave ;;; | 11=buf mode, master ;;; |______________ 0=!nested 1=nested ;;; BOTH_ICW4 equ 0x01 HalInterruptInitializePICs: mov al, BOTH_ICW1 out 0x20, al ; ICW1 to master PIC call HalIoDelay out 0xA0, al ; ICW1 to slave PIC call HalIoDelay mov al, MASTER_ICW2 out 0x21, al ; ICW2 to master PIC call HalIoDelay mov al, SLAVE_ICW2 out 0xA1, al ; ICW2 to slave PIC call HalIoDelay mov al, MASTER_ICW3 out 0x21, al ; ICW3 to master PIC call HalIoDelay mov al, SLAVE_ICW3 out 0xA1, al ; ICW3 to slave PIC call HalIoDelay mov al, BOTH_ICW4 out 0x21, al ; ICW4 to master PIC call HalIoDelay out 0xA1, al ; ICW4 to slave PIC call HalIoDelay ;;; ;;; After the four ICW sequence has been completed any subsequent writes ;;; to port 0x21 or 0xA1 set the IRQ mask for the master/slave PIC. If ;;; this mask is 0xFF all IRQs are disabled. If this mask is 0x00 ;;; all IRQs are enabled. ;;; mov al, 0xff ; slave PIC cannot interrupt out 0xA1, al call HalIoDelay mov al, 0xfb ; mask all IRQs but 2 (the slave PIC) in master out 0x21, al call HalIoDelay ret ;;; ;;; When doing I/O operations this routine can be used to "delay" long ;;; enough to let the external controller keep up with the cpu. ;;; HalIoDelay: jmp .done .done: ret ;;; ;;; This is the start of the main interrupt handler code. The first section ;;; of this code is called the InterruptHandlerPreamble. It consists of ;;; 256 entry points in a table (one per possible interrupt number). These ;;; preamble entry points push the interrupt number and (when needed for ;;; interrupt numbers that the processor doesn't automatically push an error ;;; code for) a fake error code. Then they jump to the main generic interrupt ;;; handling routing: InterruptGenericHandler. Thus the stack is always layed ;;; out in the same way when control passes to the generic handler: ;;; ;;; saved ss (pushed by cpu for system calls) ;;; saved esp (pushed by cpu for system calls) ;;; ------------ ;;; saved eflags (pushed by cpu) ;;; saved CS (pushed by cpu) ;;; saved IP (pushed by cpu) ;;; error code (pushed by preamble or cpu) ;;; interrupt number (pushed by preamble) ;;; esp --> return address (pushed by jmp InteruptGenericHandler) ;;; ;;; The first thing InterruptGenericHandler does is push the rest of the ;;; registers onto the stack. ;;; align 8 HalInterruptHandlerPreamble: HalInterruptHandlerPreambleBefore: INT_NO_ERROR 0 align 8 HalInterruptHandlerPreambleAfter: INT_NO_ERROR 1 INT_NO_ERROR 2 INT_NO_ERROR 3 INT_NO_ERROR 4 INT_NO_ERROR 5 INT_NO_ERROR 6 INT_NO_ERROR 7 INT_WITH_ERROR 8 INT_NO_ERROR 9 INT_WITH_ERROR 10 INT_WITH_ERROR 11 INT_WITH_ERROR 12 INT_WITH_ERROR 13 INT_WITH_ERROR 14 INT_NO_ERROR 15 INT_NO_ERROR 16 INT_WITH_ERROR 17 ;;; ;;; The rest of the interrupt numbers are INT_NO_ERRORs. Use nasm's ;;; %rep command to do them all at once. ;;; %assign intNum 18 %rep (256 - 18) INT_NO_ERROR intNum %assign intNum intNum + 1 %endrep ;;; ;;; This is the generic interrupt handler which is called by the preambles ;;; above. It's job is to save the registers on the stack and then transfer ;;; control to a specific per-interrupt handler. It finds the address of ;;; the per-interrupt handler to call by indexing into g_InterruptHandlerTable ;;; align 8 HalInterruptGenericHandler: SAVE_REGISTERS ;; Ensure that we're using the kernel data segment mov ax, (2<<3) mov ds, ax mov es, ax mov fs, ax mov gs, ax ;; Get interrupt number the preamble pushed for us mov esi, [esp+44] ;; Get the address of the handler function from the ;; table of handler functions. mov eax, g_InterruptHandlerTable mov ebx, [eax+esi*4] ;; Call the handler. The argument passed is a pointer to ;; INTERRUPT_STATE. Interrupts are enabled at this point ;; so if the handler is non-reentrant it must disable them. push esp call ebx add esp, 4 push esp call HalInterruptSendIrqEoi add esp, 4 RESTORE_REGISTERS iret