diff options
Diffstat (limited to 'kernel/hal/intsupport.asm')
| -rw-r--r-- | kernel/hal/intsupport.asm | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/kernel/hal/intsupport.asm b/kernel/hal/intsupport.asm new file mode 100644 index 0000000..3d1fcc8 --- /dev/null +++ b/kernel/hal/intsupport.asm @@ -0,0 +1,326 @@ +[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 |
