29ca70e62d8876305d888c3a67621e0705d7e96a
[com32] / src / intr.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include "config.h"
20 #include "intr.h"
21 #include "desc.h"
22 #include "segm.h"
23 #include "asmops.h"
24 #include "panic.h"
25
26 #define SYSCALL_INT             0x80
27
28 /* IDT gate descriptor bits */
29 #define GATE_TASK               (5 << 8)
30 #define GATE_INTR               (6 << 8)
31 #define GATE_TRAP               (7 << 8)
32 #define GATE_DEFAULT    (1 << 11)
33 #define GATE_PRESENT    (1 << 15)
34
35 /* PIC command and data ports */
36 #define PIC1_CMD        0x20
37 #define PIC1_DATA       0x21
38 #define PIC2_CMD        0xa0
39 #define PIC2_DATA       0xa1
40
41 /* PIC initialization command word 1 bits */
42 #define ICW1_ICW4_NEEDED        (1 << 0)
43 #define ICW1_SINGLE                     (1 << 1)
44 #define ICW1_INTERVAL4          (1 << 2)
45 #define ICW1_LEVEL                      (1 << 3)
46 #define ICW1_INIT                       (1 << 4)
47 /* PIC initialization command word 4 bits */
48 #define ICW4_8086                       (1 << 0)
49 #define ICW4_AUTO_EOI           (1 << 1)
50 #define ICW4_BUF_SLAVE          (1 << 3) /* 1000 */
51 #define ICW4_BUF_MASTER         (3 << 2) /* 1100 */
52 #define ICW4_SPECIAL            (1 << 4)
53
54 /* PIC operation command word 2 bits */
55 #define OCW2_EOI        (1 << 5)
56
57
58 void init_pic(void);
59 static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type);
60
61 /* defined in intr_asm.S */
62 void set_idt(uint32_t addr, uint16_t limit);
63 void intr_entry_default(void);
64 void irq7_entry_check_spurious(void);
65 void irq15_entry_check_spurious(void);
66
67 /* the IDT (interrupt descriptor table) */
68 static desc_t idt[256] __attribute__((aligned(8)));
69
70 /* table of handler functions for all interrupts */
71 static intr_func_t intr_func[256];
72
73 static struct intr_frame *cur_intr_frame;
74 static int eoi_pending;
75
76 #define INTR_ENTRY_EC(n, name)          \
77         void intr_entry_##name(void);   \
78         set_intr_entry(n, intr_entry_##name);
79 #define INTR_ENTRY_NOEC(n, name)        INTR_ENTRY_EC(n, name)
80
81 void init_intr(void)
82 {
83         int i;
84
85         set_idt((uint32_t)idt, sizeof idt - 1);
86
87         /* initialize all entry points and interrupt handlers */
88         for(i=0; i<256; i++) {
89                 set_intr_entry(i, intr_entry_default);
90                 interrupt(i, 0);
91         }
92
93         /* mask all IRQs by default */
94         for(i=0; i<16; i++) {
95                 mask_irq(i);
96         }
97
98         /* by including intrtab.h here the series of INTR_ENTRY_* macros will be
99          * expanded to a series of function prototypes for all interrupt entry
100          * points and the corresponding calls to set_intr_entry to set up the IDT
101          * slots
102          */
103 #include "intrtab.h"
104
105         /* change irq7 and irq15 to special entry points which first
106          * make sure we didn't get a spurious interrupt before proceeding
107          */
108         set_intr_entry(IRQ_TO_INTR(7), irq7_entry_check_spurious);
109         set_intr_entry(IRQ_TO_INTR(15), irq15_entry_check_spurious);
110
111         /* initialize the programmable interrupt controller
112          * setting up the maping of IRQs [0, 15] to interrupts [32, 47]
113          */
114         init_pic();
115         eoi_pending = 0;
116 }
117
118 /* retrieve the current interrupt frame.
119  * returns 0 when called during init.
120  */
121 struct intr_frame *get_intr_frame(void)
122 {
123         return cur_intr_frame;
124 }
125
126 /* set an interrupt handler function for a particular interrupt */
127 void interrupt(int intr_num, intr_func_t func)
128 {
129         int iflag = get_intr_flag();
130         disable_intr();
131         intr_func[intr_num] = func;
132         set_intr_flag(iflag);
133 }
134
135 /* this function is called from all interrupt entry points
136  * it calls the appropriate interrupt handlers if available and handles
137  * sending an end-of-interrupt command to the PICs when finished.
138  */
139 void dispatch_intr(struct intr_frame frm)
140 {
141         cur_intr_frame = &frm;
142
143         if(IS_IRQ(frm.inum)) {
144                 eoi_pending = frm.inum;
145         }
146
147         if(intr_func[frm.inum]) {
148                 intr_func[frm.inum](frm.inum);
149         } else {
150                 if(frm.inum < 32) {
151                         panic("unhandled exception %u, error code: %u, at cs:eip=%x:%x\n",
152                                         frm.inum, frm.err, frm.cs, frm.eip);
153                 }
154                 printf("unhandled interrupt %d\n", frm.inum);
155         }
156
157         disable_intr();
158         if(eoi_pending) {
159                 end_of_irq(INTR_TO_IRQ(eoi_pending));
160         }
161 }
162
163 void init_pic(void)
164 {
165         prog_pic(IRQ_OFFSET);
166 }
167
168 void prog_pic(int offs)
169 {
170         /* send ICW1 saying we'll follow with ICW4 later on */
171         outp(PIC1_CMD, ICW1_INIT | ICW1_ICW4_NEEDED);
172         outp(PIC2_CMD, ICW1_INIT | ICW1_ICW4_NEEDED);
173         /* send ICW2 with IRQ remapping */
174         outp(PIC1_DATA, offs);
175         outp(PIC2_DATA, offs + 8);
176         /* send ICW3 to setup the master/slave relationship */
177         /* ... set bit3 = 3rd interrupt input has a slave */
178         outp(PIC1_DATA, 4);
179         /* ... set slave ID to 2 */
180         outp(PIC2_DATA, 2);
181         /* send ICW4 to set 8086 mode (no calls generated) */
182         outp(PIC1_DATA, ICW4_8086);
183         outp(PIC2_DATA, ICW4_8086);
184         /* done, just reset the data port to 0 */
185         outp(PIC1_DATA, 0);
186         outp(PIC2_DATA, 0);
187 }
188
189 static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type)
190 {
191         /* first 16bit part is the low 16bits of the entry address */
192         desc->d[0] = addr & 0xffff;
193         /* second 16bit part is the segment selector for the entry code */
194         desc->d[1] = sel;
195         /* third 16bit part has the privilege level, type, and present bit */
196         desc->d[2] = ((dpl & 3) << 13) | type | GATE_DEFAULT | GATE_PRESENT;
197         /* last 16bit part is the high 16bits of the entry address */
198         desc->d[3] = (addr & 0xffff0000) >> 16;
199 }
200
201 #define IS_TRAP(n)      ((n) >= 32 && !IS_IRQ(n))
202 void set_intr_entry(int num, void (*handler)(void))
203 {
204         int type = IS_TRAP(num) ? GATE_TRAP : GATE_INTR;
205
206         /* the syscall interrupt has to have a dpl of 3 otherwise calling it from
207          * user space will raise a general protection exception. All the rest should
208          * have a dpl of 0 to disallow user programs to execute critical interrupt
209          * handlers and possibly crashing the system.
210          */
211         int dpl = (num == SYSCALL_INT) ? 3 : 0;
212
213         gate_desc(idt + num, selector(SEGM_KCODE, 0), (uint32_t)handler, dpl, type);
214 }
215
216 void set_pic_mask(int pic, unsigned char mask)
217 {
218         outp(pic > 0 ? PIC2_DATA : PIC1_DATA, mask);
219 }
220
221 unsigned char get_pic_mask(int pic)
222 {
223         return inp(pic > 0 ? PIC2_DATA : PIC1_DATA);
224 }
225
226 void mask_irq(int irq)
227 {
228         int port;
229         unsigned char mask;
230
231         if(irq < 8) {
232                 port = PIC1_DATA;
233         } else {
234                 port = PIC2_DATA;
235                 irq -= 8;
236         }
237
238         mask = inp(port) | (1 << irq);
239         outp(port, mask);
240 }
241
242 void unmask_irq(int irq)
243 {
244         int port;
245         unsigned char mask;
246
247         if(irq < 8) {
248                 port = PIC1_DATA;
249         } else {
250                 port = PIC2_DATA;
251                 irq -= 8;
252         }
253
254         mask = inp(port) & ~(1 << irq);
255         outp(port, mask);
256 }
257
258
259 void end_of_irq(int irq)
260 {
261         int intr_state = get_intr_flag();
262         disable_intr();
263
264         if(!eoi_pending) {
265                 set_intr_flag(intr_state);
266                 return;
267         }
268         eoi_pending = 0;
269
270         if(irq > 7) {
271                 outp(PIC2_CMD, OCW2_EOI);
272         }
273         outp(PIC1_CMD, OCW2_EOI);
274
275         set_intr_flag(intr_state);
276 }
277
278 #ifdef ENABLE_GDB_STUB
279 void exceptionHandler(int id, void (*func)())
280 {
281         set_intr_entry(id, func);
282 }
283 #endif