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