e04a1775a41c83095d841b6073368a32d117cb7b
[bootcensus] / 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 "intr.h"
20 #include "desc.h"
21 #include "segm.h"
22 #include "asmops.h"
23 #include "panic.h"
24
25 #define SYSCALL_INT             0x80
26
27 /* IDT gate descriptor bits */
28 #define GATE_TASK               (5 << 8)
29 #define GATE_INTR               (6 << 8)
30 #define GATE_TRAP               (7 << 8)
31 #define GATE_DEFAULT    (1 << 11)
32 #define GATE_PRESENT    (1 << 15)
33
34 /* PIC command and data ports */
35 #define PIC1_CMD        0x20
36 #define PIC1_DATA       0x21
37 #define PIC2_CMD        0xa0
38 #define PIC2_DATA       0xa1
39
40 /* PIC initialization command word 1 bits */
41 #define ICW1_ICW4_NEEDED        (1 << 0)
42 #define ICW1_SINGLE                     (1 << 1)
43 #define ICW1_INTERVAL4          (1 << 2)
44 #define ICW1_LEVEL                      (1 << 3)
45 #define ICW1_INIT                       (1 << 4)
46 /* PIC initialization command word 4 bits */
47 #define ICW4_8086                       (1 << 0)
48 #define ICW4_AUTO_EOI           (1 << 1)
49 #define ICW4_BUF_SLAVE          (1 << 3) /* 1000 */
50 #define ICW4_BUF_MASTER         (3 << 2) /* 1100 */
51 #define ICW4_SPECIAL            (1 << 4)
52
53 /* PIC operation command word 2 bits */
54 #define OCW2_EOI        (1 << 5)
55
56
57 static void init_pic(int offset);
58 static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type);
59
60 /* defined in intr_asm.S */
61 void set_idt(uint32_t addr, uint16_t limit);
62 void intr_entry_default(void);
63 void irq7_entry_check_spurious(void);
64 void irq15_entry_check_spurious(void);
65
66 /* the IDT (interrupt descriptor table) */
67 static desc_t idt[256] __attribute__((aligned(8)));
68
69 /* table of handler functions for all interrupts */
70 static intr_func_t intr_func[256];
71
72 static struct intr_frame *cur_intr_frame;
73 static int eoi_pending;
74
75
76 void init_intr(void)
77 {
78         int i;
79
80         set_idt((uint32_t)idt, sizeof idt - 1);
81
82         /* initialize all entry points and interrupt handlers */
83         for(i=0; i<256; i++) {
84                 set_intr_entry(i, intr_entry_default);
85                 interrupt(i, 0);
86         }
87
88         /* by including intrtab.h here (without ASM being defined)
89          * the series of INTR_ENTRY_* macros will be expanded to a series
90          * of function prototypes for all interrupt entry points and the
91          * corresponding calls to set_intr_entry to set up the IDT slots
92          */
93 #include "intrtab.h"
94
95         /* change irq7 and irq15 to special entry points which first
96          * make sure we didn't get a spurious interrupt before proceeding
97          */
98         set_intr_entry(IRQ_TO_INTR(7), irq7_entry_check_spurious);
99         set_intr_entry(IRQ_TO_INTR(15), irq15_entry_check_spurious);
100
101         /* initialize the programmable interrupt controller
102          * setting up the maping of IRQs [0, 15] to interrupts [32, 47]
103          */
104         init_pic(IRQ_OFFSET);
105         eoi_pending = 0;
106 }
107
108 /* retrieve the current interrupt frame.
109  * returns 0 when called during init.
110  */
111 struct intr_frame *get_intr_frame(void)
112 {
113         return cur_intr_frame;
114 }
115
116 /* set an interrupt handler function for a particular interrupt */
117 void interrupt(int intr_num, intr_func_t func)
118 {
119         int iflag = get_intr_flag();
120         disable_intr();
121         intr_func[intr_num] = func;
122         set_intr_flag(iflag);
123 }
124
125 /* this function is called from all interrupt entry points
126  * it calls the appropriate interrupt handlers if available and handles
127  * sending an end-of-interrupt command to the PICs when finished.
128  */
129 void dispatch_intr(struct intr_frame frm)
130 {
131         cur_intr_frame = &frm;
132
133         if(IS_IRQ(frm.inum)) {
134                 eoi_pending = frm.inum;
135         }
136
137         if(intr_func[frm.inum]) {
138                 intr_func[frm.inum](frm.inum);
139         } else {
140                 if(frm.inum < 32) {
141                         panic("unhandled exception %d, error code: %d\n", frm.inum, frm.err);
142                 }
143                 printf("unhandled interrupt %d\n", frm.inum);
144         }
145
146         disable_intr();
147         if(eoi_pending) {
148                 end_of_irq(INTR_TO_IRQ(eoi_pending));
149         }
150 }
151
152 static void init_pic(int offset)
153 {
154         /* send ICW1 saying we'll follow with ICW4 later on */
155         outb(ICW1_INIT | ICW1_ICW4_NEEDED, PIC1_CMD);
156         outb(ICW1_INIT | ICW1_ICW4_NEEDED, PIC2_CMD);
157         /* send ICW2 with IRQ remapping */
158         outb(offset, PIC1_DATA);
159         outb(offset + 8, PIC2_DATA);
160         /* send ICW3 to setup the master/slave relationship */
161         /* ... set bit3 = 3rd interrupt input has a slave */
162         outb(4, PIC1_DATA);
163         /* ... set slave ID to 2 */
164         outb(2, PIC2_DATA);
165         /* send ICW4 to set 8086 mode (no calls generated) */
166         outb(ICW4_8086, PIC1_DATA);
167         outb(ICW4_8086, PIC2_DATA);
168         /* done, just reset the data port to 0 */
169         outb(0, PIC1_DATA);
170         outb(0, PIC2_DATA);
171 }
172
173 static void gate_desc(desc_t *desc, uint16_t sel, uint32_t addr, int dpl, int type)
174 {
175         /* first 16bit part is the low 16bits of the entry address */
176         desc->d[0] = addr & 0xffff;
177         /* second 16bit part is the segment selector for the entry code */
178         desc->d[1] = sel;
179         /* third 16bit part has the privilege level, type, and present bit */
180         desc->d[2] = ((dpl & 3) << 13) | type | GATE_DEFAULT | GATE_PRESENT;
181         /* last 16bit part is the high 16bits of the entry address */
182         desc->d[3] = (addr & 0xffff0000) >> 16;
183 }
184
185 #define IS_TRAP(n)      ((n) >= 32 && !IS_IRQ(n))
186 void set_intr_entry(int num, void (*handler)(void))
187 {
188         int type = IS_TRAP(num) ? GATE_TRAP : GATE_INTR;
189
190         /* the syscall interrupt has to have a dpl of 3 otherwise calling it from
191          * user space will raise a general protection exception. All the rest should
192          * have a dpl of 0 to disallow user programs to execute critical interrupt
193          * handlers and possibly crashing the system.
194          */
195         int dpl = (num == SYSCALL_INT) ? 3 : 0;
196
197         gate_desc(idt + num, selector(SEGM_KCODE, 0), (uint32_t)handler, dpl, type);
198 }
199
200 void end_of_irq(int irq)
201 {
202         int intr_state = get_intr_flag();
203         disable_intr();
204
205         if(!eoi_pending) {
206                 return;
207         }
208         eoi_pending = 0;
209
210         if(irq > 7) {
211                 outb(OCW2_EOI, PIC2_CMD);
212         }
213         outb(OCW2_EOI, PIC1_CMD);
214
215         set_intr_flag(intr_state);
216 }