summaryrefslogtreecommitdiff
path: root/kernel/hal/intsupport.asm
blob: 3d1fcc82b77109a7b8c9ed12a0fbb1983492f8d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
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