-void intr_ps2_kbd_handler(const isr_param* param) {
- uint8_t scancode = io_inb(PS2_PORT_DATA) & 0xff;
- kbd_keycode key;
-
- kprintf(KINFO "%x\n", scancode & 0xff);
-
- // FIXME: 实现 Shift+<key>
- switch (kbd_state.state)
- {
- case KBD_STATE_WAIT_KEY:
- if (scancode == 0xf0) { // release code
- kbd_state.state = KBD_STATE_RELEASED;
- } else if (scancode == 0xe0) {
- kbd_state.state = KBD_STATE_SPECIAL;
- kbd_state.translation_table = scancode_set2_ex;
- } else {
- key = kbd_state.translation_table[scancode];
- kbd_buffer_key_event(key, scancode, KBD_KEY_PRESSED);
- }
- break;
- case KBD_STATE_SPECIAL:
- if (scancode == 0xf0) { //release code
- kbd_state.state = KBD_STATE_RELEASED;
- } else {
+void
+intr_ps2_kbd_handler(const isr_param* param)
+{
+
+ // This is important! Don't believe me? try comment it out and run on Bochs!
+ while (!(io_inb(PS2_PORT_CTRL_STATUS) & PS2_STATUS_OFULL))
+ ;
+
+ // I know you are tempting to move this chunk after the keyboard state
+ // check. But DO NOT. This chunk is in right place and right order. Moving
+ // it at your own risk This is to ensure we've cleared the output buffer
+ // everytime, so it won't pile up across irqs.
+ uint8_t scancode = io_inb(PS2_PORT_ENC_DATA);
+ kbd_keycode_t key;
+
+ /*
+ * 判断键盘是否处在指令发送状态,防止误触发。(伪输入中断)
+ * 这是因为我们需要向ps/2设备发送指令(比如控制led灯),而指令会有返回码。
+ * 这就会有可能导致ps/2控制器在受到我们的命令后(在ps2_process_cmd中),
+ * 产生IRQ#1中断(虽然说这种情况取决于底层BIOS实现,但还是会发生,比如QEMU和bochs)。
+ * 所以这就是说,当IRQ#1中断产生时,我们的CPU正处在另一个ISR中。这样就会导致所有的外部中断被缓存在APIC内部的
+ * FIFO队列里,进行排队等待(APIC长度为二的队列 {IRR, TMR};参考 Intel
+ * Manual Vol.3A 10.8.4)
+ * 那么当ps2_process_cmd执行完后(内嵌在#APIC_TIMER_IV),CPU返回EOI给APIC,APIC紧接着将排在队里的IRQ#1发送给CPU
+ * 造成误触发。也就是说,我们此时读入的scancode实则上是上一个指令的返回代码。
+ *
+ * Problem 1 (Fixed):
+ * 但是这种方法有个问题,那就是,假若我们的某一个命令失败了一次,ps/2给出0xfe,我们重传,ps/2收到指令并给出0xfa。
+ * 那么这样一来,将会由两个连续的IRQ#1产生。而APIC是最多可以缓存两个IRQ,于是我们就会漏掉一个IRQ,依然会误触发。
+ * Solution:
+ * 累加掩码 ;)
+ *
+ * Problem 2:
+ * +
+ * 这种累加掩码的操作是基于只有一号IRQ产生的中断的假设,万一中间夹杂了别的中断?Race
+ * Condition!
+ * +
+ * 不很稳定x1,假如连续4次发送失败,那么就会导致累加的掩码上溢出,从而导致下述判断失败。
+ */
+#ifdef KBD_ENABLE_SPIRQ_FIX
+ if ((kbd_state.state & 0xc0)) {
+ kbd_state.state -= KBD_STATE_CMDPROCS;
+
+ return;
+ }
+#endif
+
+#ifdef KBD_DBGLOG
+ kprintf(KDEBUG "%x\n", scancode & 0xff);
+#endif
+
+ switch (kbd_state.state) {
+ case KBD_STATE_KWAIT:
+ if (scancode == 0xf0) { // release code
+ kbd_state.state = KBD_STATE_KRELEASED;
+ } else if (scancode == 0xe0) {
+ kbd_state.state = KBD_STATE_KSPECIAL;
+ kbd_state.translation_table = scancode_set2_ex;
+ } else {
+ key = kbd_state.translation_table[scancode];
+ kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
+ }
+ break;
+ case KBD_STATE_KSPECIAL:
+ if (scancode == 0xf0) { // release code
+ kbd_state.state = KBD_STATE_KRELEASED;
+ } else {
+ key = kbd_state.translation_table[scancode];
+ kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
+
+ kbd_state.state = KBD_STATE_KWAIT;
+ kbd_state.translation_table = scancode_set2;
+ }
+ break;
+ case KBD_STATE_KRELEASED: