summaryrefslogtreecommitdiff
path: root/kernel/hal/intsupport.asm
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/hal/intsupport.asm')
-rw-r--r--kernel/hal/intsupport.asm326
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