interrupt.c 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #include "interrupt.h"
  2. #include "stdint.h"
  3. #include "global.h"
  4. #include "../lib/kernel/print.h"
  5. #include "../lib/kernel/io.h"
  6. #define IDT_DESC_CNT 0x81 // 目前支持的中断数
  7. #define PIC_M_CTRL 0x20 // 主片的控制端口是 0x20
  8. #define PIC_M_DATA 0x21 // 主片的数据端口是 0x21
  9. #define PIC_S_CTRL 0xA0 // 从片的控制端口是 0xA0
  10. #define PIC_S_DATA 0xA1 // 从片的数据端口是 0xA1
  11. #define EFLAGS_IF 0x200 // eflags寄存器中的if位为1
  12. #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g"(EFLAG_VAR))
  13. extern uint32_t syscall_handle(void);
  14. /* 中断门描述符结构体 */
  15. // Example type_attributes values that people are likely to use (assuming DPL is 0):
  16. // 32-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
  17. // 32-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
  18. // Task Gate: 0x85 (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101=0x85)
  19. typedef struct
  20. {
  21. uint16_t offset_low; // 16位 偏移量的低 16 位 (0..15)
  22. uint16_t selector; // 选择器 acode segment selector in GDT or LDT
  23. uint8_t dcount; // 置 0
  24. uint8_t attribute; // 述符类型 (bit 7:0) gate type, dpl, and p fields
  25. uint16_t offset_high; // 16 位 偏移量的高 16 位 (16..31)
  26. } __attribute__((packed)) gate_desc;
  27. char *intr_name[IDT_DESC_CNT]; // 用于保存异常的名字
  28. /****************************** 定义中断数组 **********************
  29. * 在 kernel.S 中定义的 intrXXentry 只是中断处理程序的入口,
  30. * 最终调用的 idt_table 中的处理程序*/
  31. intr_handler idt_table[IDT_DESC_CNT]; // 中断处理函数数组
  32. /***************************************************************/
  33. static gate_desc idt[IDT_DESC_CNT]; // 中断描述符表,本质上就是个中断门描述符数组
  34. extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在 kernel.S 中的中断处理函数入口数组
  35. /*创建中断门描述符*/
  36. static void make_idt_desc(gate_desc *p_gdesc, uint8_t attr, intr_handler function)
  37. {
  38. p_gdesc->offset_low = (uint32_t)((uintptr_t)function & 0x0000FFFF);
  39. p_gdesc->selector = SELECTOR_K_CODE;
  40. p_gdesc->dcount = 0;
  41. p_gdesc->attribute = attr;
  42. p_gdesc->offset_high = (uint32_t)(((uintptr_t)function & 0xFFFF0000) >> 16);
  43. }
  44. /* 初始化中断描述符表*/
  45. static void itd_desc_init(void)
  46. {
  47. int i, last_index = IDT_DESC_CNT - 1;
  48. for (i = 0; i < IDT_DESC_CNT; i++)
  49. {
  50. make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
  51. }
  52. // syscall_handle int 0x80 -> dpl 为 3
  53. make_idt_desc(&idt[last_index], IDT_DESC_ATTR_DPL3, syscall_handle);
  54. put_str(" idt_desc_init done\n");
  55. }
  56. /* 初始化可编程中断控制器 8259A */
  57. static void pic_init(void)
  58. {
  59. /* 初始化主片*/
  60. outb(PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要 ICW4
  61. outb(PIC_M_DATA, 0x20); // ICW2: 起始中断向量号 0x20, 也就是 IRQ[0~7] 为 0x20~0x27
  62. outb(PIC_M_DATA, 0x04); // ICW3: IR2 接从片
  63. outb(PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
  64. /* 初始化从片 */
  65. outb(PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要 ICW4
  66. outb(PIC_S_DATA, 0x28); // ICW2: 起始中断向量号 0x28, 也就是 IRQ[8~15] 以 0x28~0x2F
  67. outb(PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的 IR2 引脚
  68. outb(PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
  69. /* 打开主片上 IR0, 也就是目前只接受时钟产生的中断*/
  70. // outb(PIC_M_DATA, 0xfe);
  71. // outb(PIC_M_DATA, 0xfd); // 测试键盘,只打开键盘中断,其他全部关闭
  72. outb(PIC_M_DATA, 0xfc); // 测试键盘,时钟,开键盘,时钟中断,其他全部关闭 1111 1100
  73. outb(PIC_S_DATA, 0xff);
  74. put_str(" pic_init done\n");
  75. }
  76. // 通用中断处理函数, 一般用于处理异常
  77. static void general_intr_handler(uint8_t vec_nr)
  78. {
  79. if (vec_nr == 0x27 || vec_nr == 0x2f)
  80. {
  81. // IRQ7 和 IRQ15 会产生伪中断,无需处理
  82. // 0x2f 是从片8259A上的最后一个 IRQ 引脚,保留项
  83. return;
  84. }
  85. set_cursor(0); // 将光标置为屏幕左上角,从此处打印异常信息,方便阅读
  86. int cursor_pos = 0;
  87. while (cursor_pos < 320)
  88. { // 清空 4 行位置的字符
  89. put_char(' ');
  90. cursor_pos++;
  91. }
  92. set_cursor(0); // 重置光标位置
  93. put_str("!!!!!!! exception message begin !!!!!!!!!!!!!\n");
  94. set_cursor(88); // 从第 2 行第 8 个字符开始打印, 80 字符宽
  95. put_str("intr name: ");
  96. put_str(intr_name[vec_nr]);
  97. if (vec_nr == 14) // 若为 Pagefault, 将缺失的地址打印出来并悬停
  98. {
  99. int page_fault_vaddr = 0;
  100. asm("movl %%cr2, %0" : "=r"(page_fault_vaddr)); // cr2是存放page_fault的线性地址
  101. put_str("\nPage fault addr is: 0X"); // 输出缺失的地址
  102. put_int(page_fault_vaddr);
  103. }
  104. put_str("\n!!!!!!! exception message end !!!!!!!!!!!!!\n");
  105. // 能进入中断处理程序就表示已经处在关中断情况下, 不会出现调度进程的情况。
  106. // 所以此处的 while 不会再被中断打断,此处的死循环是为了保证当前任务继续运行
  107. while (1)
  108. ;
  109. }
  110. static void exception_init(void)
  111. {
  112. int i;
  113. for (i = 0; i < IDT_DESC_CNT; i++)
  114. {
  115. /*idt_table 数组中的函数是进入中断后根据中断向量号调用的,见 kernel/kernel.S 的 call [itd_table + %1*4]*/
  116. idt_table[i] = general_intr_handler; // 默认为 general_intr_handler
  117. intr_name[i] = "unknown";
  118. }
  119. intr_name[0] = "#DE Divide Error";
  120. intr_name[1] = "#DB Debug Exception";
  121. intr_name[2] = "#NMI Interrupt";
  122. intr_name[3] = "#BP Breakpoint Exception";
  123. intr_name[4] = "#OF Overflow Exception";
  124. intr_name[5] = "#BR BOUND Range Exceeded Exception";
  125. intr_name[6] = "#UD Invalid Opcode Exception";
  126. intr_name[7] = "#NM Device Not Available Exception";
  127. intr_name[8] = "#DF Double Fault Exception";
  128. intr_name[9] = "#Coprocessor Segment Overrun";
  129. intr_name[10] = "#TS Invalid TSS Exception";
  130. intr_name[11] = "#NP Segment Not Present";
  131. intr_name[12] = "#SS Stack Fault Exception";
  132. intr_name[13] = "#GP General Protection Exception";
  133. intr_name[14] = "#PF Page-Fault Exception";
  134. // intr_name[15] 第 15 项是 intel 保留项,未使用
  135. intr_name[16] = "#MF x87 FPU Floating-Point Error";
  136. intr_name[17] = "#AC Alignment Check Exception";
  137. intr_name[18] = "#MC Machine-Check Exception";
  138. intr_name[19] = "#XF SIMD Floating-Point Exception";
  139. put_str(" exception_init done\n");
  140. }
  141. /* 中断初始化主函数 */
  142. void idt_init(void)
  143. {
  144. put_str("init_idt start\n");
  145. itd_desc_init(); // 初始化中断描述符表
  146. exception_init(); // 完成一般中断处理函数注册及异常名称注册
  147. pic_init(); // 初始化可编程中断控制器8259A
  148. /* 加载 idt */
  149. uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uintptr_t)idt << 16)));
  150. asm volatile("lidt %0" ::"m"(idt_operand));
  151. put_str("idt_init done\n");
  152. }
  153. enum intr_status intr_get_status(void)
  154. {
  155. uint32_t eflags = 0;
  156. GET_EFLAGS(eflags);
  157. return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
  158. }
  159. enum intr_status intr_set_status(enum intr_status status)
  160. {
  161. return status & INTR_ON ? intr_enable() : intr_disable();
  162. }
  163. enum intr_status intr_enable(void)
  164. {
  165. enum intr_status old_status;
  166. if (INTR_ON == intr_get_status())
  167. {
  168. old_status = INTR_ON;
  169. return old_status;
  170. }
  171. else
  172. {
  173. old_status = INTR_OFF;
  174. asm volatile("sti"); // 开中断, sti 指令将IF位置1
  175. return old_status;
  176. }
  177. }
  178. enum intr_status intr_disable(void)
  179. {
  180. enum intr_status old_status;
  181. if (INTR_ON == intr_get_status())
  182. {
  183. old_status = INTR_ON;
  184. asm volatile("cli" : : : "memory"); // 关中断, cli 指令将IF位置0
  185. return old_status;
  186. }
  187. else
  188. {
  189. old_status = INTR_OFF;
  190. return old_status;
  191. }
  192. }
  193. void register_handler(uint8_t vector_no, intr_handler function)
  194. { // idt_table 数组中的函数是进入中断后根据中断向量号调用的,
  195. // 见 kernel/kernel.S 的 call [itd_table + %1*4]
  196. idt_table[vector_no] = function;
  197. }