rework external irq system, introduce hierarchical irq
[lunaix-os.git] / lunaix-os / arch / x86 / hal / ps2kbd.c
1 #include <lunaix/clock.h>
2 #include <lunaix/ds/mutex.h>
3 #include <lunaix/input.h>
4 #include <lunaix/keyboard.h>
5 #include <lunaix/syslog.h>
6 #include <lunaix/timer.h>
7 #include <lunaix/hart_state.h>
8
9 #include <hal/irq.h>
10
11 #include "asm/x86.h"
12
13 #include <klibc/string.h>
14
15 #include "asm/x86_cpu.h"
16 #include <asm/x86_isrm.h>
17 #include <asm/x86_pmio.h>
18
19 #define PS2_PORT_ENC_DATA 0x60
20 #define PS2_PORT_ENC_CMDREG 0x60
21 #define PS2_PORT_CTRL_STATUS 0x64
22 #define PS2_PORT_CTRL_CMDREG 0x64
23
24 #define PS2_STATUS_OFULL 0x1
25 #define PS2_STATUS_IFULL 0x2
26
27 #define PS2_RESULT_ACK 0xfa
28 #define PS2_RESULT_NAK 0xfe // resend
29 #define PS2_RESULT_ECHO 0xee
30 #define PS2_RESULT_TEST_OK 0x55
31
32 // PS/2 keyboard device related commands
33 #define PS2_KBD_CMD_SETLED 0xed
34 #define PS2_KBD_CMD_ECHO 0xee
35 #define PS2_KBD_CMD_SCANCODE_SET 0xf0
36 #define PS2_KBD_CMD_IDENTIFY 0xf2
37 #define PS2_KBD_CMD_SCAN_ENABLE 0xf4
38 #define PS2_KBD_CMD_SCAN_DISABLE 0xf5
39
40 // PS/2 *controller* related commands
41 #define PS2_CMD_PORT1_DISABLE 0xad
42 #define PS2_CMD_PORT1_ENABLE 0xae
43 #define PS2_CMD_PORT2_DISABLE 0xa7
44 #define PS2_CMD_PORT2_ENABLE 0xa8
45 #define PS2_CMD_SELFTEST 0xaa
46 #define PS2_CMD_SELFTEST_PORT1 0xab
47
48 #define PS2_CMD_READ_CFG 0x20
49 #define PS2_CMD_WRITE_CFG 0x60
50
51 #define PS2_CFG_P1INT 0x1
52 #define PS2_CFG_P2INT 0x2
53 #define PS2_CFG_TRANSLATION 0x40
54
55 #define PS2_DELAY 1000
56
57 #define PS2_CMD_QUEUE_SIZE 8
58
59 #define PS2_NO_ARG 0xff00
60
61 #define PC_AT_IRQ_KBD                   1
62
63 struct ps2_cmd
64 {
65     char cmd;
66     char arg;
67 };
68
69 struct ps2_kbd_state
70 {
71     volatile char state;
72     volatile char masked;
73     volatile kbd_kstate_t key_state;
74     kbd_keycode_t* translation_table;
75 };
76
77 struct ps2_cmd_queue
78 {
79     struct ps2_cmd cmd_queue[PS2_CMD_QUEUE_SIZE];
80     int queue_ptr;
81     int queue_len;
82     mutex_t mutex;
83 };
84
85 /**
86  * @brief 向PS/2控制器的控制端口(0x64)发送指令并等待返回代码。
87  * 注意,对于没有返回代码的命令请使用`ps2_post_cmd`,否则会造成死锁。
88  * 通过调用该方法向控制器发送指令,请区别 ps2_issue_dev_cmd
89  *
90  * @param cmd
91  * @param args
92  */
93 static u8_t
94 ps2_issue_cmd(char cmd, u16_t arg);
95
96 /**
97  * @brief 向PS/2控制器的编码器端口(0x60)发送指令并等待返回代码。
98  * 注意,对于没有返回代码的命令请使用`ps2_post_cmd`,否则会造成死锁。
99  * 通过调用该方法向PS/2设备发送指令,请区别 ps2_issue_cmd
100  *
101  * @param cmd
102  * @param args
103  */
104 static u8_t
105 ps2_issue_dev_cmd(char cmd, u16_t arg);
106
107 /**
108  * @brief 向PS/2控制器发送指令,不等待返回代码。
109  *
110  * @param port 端口号
111  * @param cmd
112  * @param args
113  * @return char
114  */
115 static void
116 ps2_post_cmd(u8_t port, char cmd, u16_t arg);
117
118 static void
119 ps2_device_post_cmd(char cmd, char arg);
120
121 static void
122 ps2_process_cmd(void* arg);
123
124 #define PS2_DEV_CMD_MAX_ATTEMPTS 5
125
126 LOG_MODULE("i8042");
127
128 static struct ps2_cmd_queue cmd_q;
129 static struct ps2_kbd_state kbd_state;
130
131 #define KEY_NUM(x) (x + 0x30)
132 #define KEY_NPAD(x) ON_KEYPAD(KEY_NUM(x))
133
134 // 我们使用 Scancode Set 2
135
136 // clang-format off
137
138 // 大部分的扫描码(键码)
139 static kbd_keycode_t scancode_set2[] = {
140     0, KEY_F9, 0, KEY_F5, KEY_F3, KEY_F1, KEY_F2, KEY_F12, 0, KEY_F10, KEY_F8, KEY_F6,
141     KEY_F4, KEY_HTAB, '`', 0, 0, KEY_LALT, KEY_LSHIFT, 0, KEY_LCTRL, 'q', KEY_NUM(1), 
142     0, 0, 0, 'z', 's', 'a', 'w', KEY_NUM(2), 0, 0, 'c', 'x', 'd', 'e', KEY_NUM(4), KEY_NUM(3), 
143     0, 0, KEY_SPACE, 'v', 'f', 't', 'r', KEY_NUM(5),
144     0, 0, 'n', 'b', 'h', 'g', 'y', KEY_NUM(6), 0, 0, 0, 'm', 'j', 'u', KEY_NUM(7), KEY_NUM(8),
145     0, 0, ',', 'k', 'i', 'o', KEY_NUM(0), KEY_NUM(9), 0, 0, '.', '/', 'l', ';', 'p', '-', 0, 0,
146     0, '\'', 0, '[', '=', 0, 0, KEY_CAPSLK, KEY_RSHIFT, KEY_LF, ']', 0, '\\', 0, 0, 0, 0, 0, 0, 0,
147     0, KEY_BS, 0, 0, KEY_NPAD(1), 0, KEY_NPAD(4), KEY_NPAD(7), 0, 0, 0, KEY_NPAD(0), ON_KEYPAD('.'),
148     KEY_NPAD(2), KEY_NPAD(5), KEY_NPAD(6), KEY_NPAD(8), KEY_ESC, KEY_NUMSLK, KEY_F11, ON_KEYPAD('+'),
149     KEY_NPAD(3), ON_KEYPAD('-'), ON_KEYPAD('*'), KEY_NPAD(9), KEY_SCRLLK, 0, 0, 0, 0, KEY_F7
150 };
151
152 // 一些特殊的键码(以 0xe0 位前缀的)
153 static kbd_keycode_t scancode_set2_ex[] = {
154     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RALT, 0, 0,
155     KEY_RCTRL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
156     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
157     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ON_KEYPAD('/'), 0, 0, 0, 0, 0, 0, 0, 0, 
158     0, 0, 0, 0, 0, 0, 0, ON_KEYPAD(KEY_LF), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159     KEY_END, 0, KEY_LEFT, KEY_HOME,
160     0, 0, 0, KEY_INSERT, KEY_DELETE, KEY_DOWN, 0, KEY_RIGHT, KEY_UP, 0, 0,
161     0, 0, KEY_PG_DOWN, 0, 0, KEY_PG_UP
162 };
163
164 // 用于处理 Shift+<key> 的情况
165 static kbd_keycode_t scancode_set2_shift[] = {
166     0, KEY_F9, 0, KEY_F5, KEY_F3, KEY_F1, KEY_F2, KEY_F12, 0, KEY_F10, KEY_F8, KEY_F6,
167     KEY_F4, KEY_HTAB, '~', 0, 0, KEY_LALT, KEY_LSHIFT, 0, KEY_LCTRL, 'Q', '!', 
168     0, 0, 0, 'Z', 'S', 'A', 'W', '@', 0, 0, 'C', 'X', 'D', 'E', '$', '#', 
169     0, 0, KEY_SPACE, 'V', 'F', 'T', 'R', '%',
170     0, 0, 'N', 'B', 'H', 'G', 'Y', '^', 0, 0, 0, 'M', 'J', 'U', '&', '*',
171     0, 0, '<', 'K', 'I', 'O', ')', '(', 0, 0, '>', '?', 'L', ':', 'P', '_', 0, 0,
172     0, '"', 0, '{', '+', 0, 0, KEY_CAPSLK, KEY_RSHIFT, KEY_LF, '}', 0, '|', 0, 0, 0, 0, 0, 0, 0,
173     0, KEY_BS, 0, 0, KEY_NPAD(1), 0, KEY_NPAD(4), KEY_NPAD(7), 0, 0, 0, KEY_NPAD(0), ON_KEYPAD('.'),
174     KEY_NPAD(2), KEY_NPAD(5), KEY_NPAD(6), KEY_NPAD(8), KEY_ESC, KEY_NUMSLK, KEY_F11, ON_KEYPAD('+'),
175     KEY_NPAD(3), ON_KEYPAD('-'), ON_KEYPAD('*'), KEY_SCRLLK, 0, 0, 0, 0, KEY_F7
176 };
177
178 // clang-format on
179
180 static struct input_device* kbd_idev;
181
182 #define KBD_STATE_KWAIT 0x00
183 #define KBD_STATE_KSPECIAL 0x01
184 #define KBD_STATE_KRELEASED 0x02
185 #define KBD_STATE_E012 0x03
186 #define KBD_STATE_KRELEASED_E0 0x04
187 #define KBD_STATE_CMDPROCS 0x40
188
189 // #define KBD_ENABLE_SPIRQ_FIX
190 #define KBD_ENABLE_SPIRQ_FIX2
191 // #define KBD_DBGLOG
192
193 static void
194 intr_ps2_kbd_handler(irq_t irq, const struct hart_state* hstate);
195
196 static u8_t
197 ps2_issue_cmd_wretry(char cmd, u16_t arg);
198
199 static void
200 ps2_device_post_cmd(char cmd, char arg)
201 {
202     mutex_lock(&cmd_q.mutex);
203     int index = (cmd_q.queue_ptr + cmd_q.queue_len) % PS2_CMD_QUEUE_SIZE;
204     if (index == cmd_q.queue_ptr && cmd_q.queue_len) {
205         // 队列已满!
206         mutex_unlock(&cmd_q.mutex);
207         return;
208     }
209
210     struct ps2_cmd* container = &cmd_q.cmd_queue[index];
211     container->cmd = cmd;
212     container->arg = arg;
213     cmd_q.queue_len++;
214
215     // 释放锁,同理。
216     mutex_unlock(&cmd_q.mutex);
217 }
218
219 static int
220 ps2_kbd_create(struct device_def* devdef, morph_t* obj)
221 {
222
223     memset(&cmd_q, 0, sizeof(cmd_q));
224     memset(&kbd_state, 0, sizeof(kbd_state));
225
226     mutex_init(&cmd_q.mutex);
227
228     kbd_state.translation_table = scancode_set2;
229     kbd_state.state = KBD_STATE_KWAIT;
230
231     kbd_idev = input_add_device(&devdef->class, devdef->name);
232
233     /* FIXME This require systematical rework! */
234     // acpi_context* acpi_ctx = acpi_get_context();
235     // if (acpi_ctx->fadt.header.rev > 1) {
236     //     /*
237     //      *
238     //      只有当前ACPI版本大于1时,我们才使用FADT的IAPC_BOOT_ARCH去判断8042是否存在。
239     //      *  这是一个坑,在ACPI v1中,这个字段是reserved!而这及至ACPI
240     //      v2才出现。
241     //      *  需要注意:Bochs 和 QEMU 使用的是ACPI v1,而非 v2
242     //      * (virtualbox好像是v4)
243     //      *
244     //      *  (2022/6/29)
245     //      *
246     //      QEMU在7.0.0版本中,修复了FADT::IAPC_BOOT无法正确提供关于i8042的信息的bug
247     //      *      https://wiki.qemu.org/ChangeLog/7.0#ACPI_.2F_SMBIOS
248     //      *
249     //      *
250     //      请看Bochs的bios源码(QEMU的BIOS其实是照抄bochs的,所以也是一个德行。。):
251     //      *
252     //      https://bochs.sourceforge.io/cgi-bin/lxr/source/bios/rombios32.c#L1314
253     //      */
254     //     if (!(acpi_ctx->fadt.boot_arch & IAPC_ARCH_8042)) {
255     //         ERROR("not found\n");
256     //         // FUTURE: Some alternative fallback on this? Check PCI bus for
257     //         USB
258     //         // controller instead?
259     //         return;
260     //     }
261     // } else {
262     //     WARN("outdated FADT used, assuming exists.\n");
263     // }
264
265     char result;
266
267     // 1、禁用任何的PS/2设备
268     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_PORT1_DISABLE, PS2_NO_ARG);
269     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_PORT2_DISABLE, PS2_NO_ARG);
270
271     // 2、清空控制器缓冲区
272     port_rdbyte(PS2_PORT_ENC_DATA);
273
274     // 3、屏蔽所有PS/2设备(端口1&2)IRQ,并且禁用键盘键码转换功能
275     result = ps2_issue_cmd(PS2_CMD_READ_CFG, PS2_NO_ARG);
276     result = result & ~(PS2_CFG_P1INT | PS2_CFG_P2INT);
277     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_WRITE_CFG, result);
278
279     // 4、控制器自检
280     result = ps2_issue_cmd_wretry(PS2_CMD_SELFTEST, PS2_NO_ARG);
281     if (result != PS2_RESULT_TEST_OK) {
282         WARN("controller self-test failed. (%x)", result);
283         goto done;
284     }
285
286     // 5、设备自检(端口1自检,通常是我们的键盘)
287     result = ps2_issue_cmd_wretry(PS2_CMD_SELFTEST_PORT1, PS2_NO_ARG);
288     if (result != 0) {
289         ERROR("interface test on port 1 failed. (%x)", result);
290         goto done;
291     }
292
293     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_PORT2_DISABLE, PS2_NO_ARG);
294
295     // 6、开启位于端口1的 IRQ,并启用端口1。不用理会端口2,那儿一般是鼠标。
296     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_PORT1_ENABLE, PS2_NO_ARG);
297     result = ps2_issue_cmd(PS2_CMD_READ_CFG, PS2_NO_ARG);
298     // 重新设置配置字节,因为控制器自检有可能重置我们先前做的改动。
299     result = (result | PS2_CFG_P1INT) & ~(PS2_CFG_TRANSLATION | PS2_CFG_P2INT);
300     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, PS2_CMD_WRITE_CFG, result);
301
302     // 至此,PS/2控制器和设备已完成初始化,可以正常使用。
303
304     /*
305      *   一切准备就绪后,我们才教ioapic去启用IRQ#1。
306      *   至于为什么要在这里,原因是:初始化所使用的一些指令可能会导致IRQ#1的触发(因为返回码),或者是一些什么
307      *  情况导致IRQ#1的误触发(可能是未初始化导致IRQ#1线上不稳定)。于是这些IRQ#1会堆积在APIC的队列里(因为此时我们正在
308      *  初始化8042,屏蔽了所有中断,IF=0)。
309      *  当sti后,这些堆积的中断会紧跟着递送进CPU里,导致我们的键盘handler误认为由按键按下,从而将这个毫无意义的数值加入
310      *  我们的队列中,以供上层读取。
311      *
312      *  所以,保险的方法是:在初始化后才去设置ioapic,这样一来我们就能有一个稳定的IRQ#1以放心使用。
313      */
314     
315     irq_t irq = irq_declare_line(intr_ps2_kbd_handler, PC_AT_IRQ_KBD, NULL);    
316     irq_assign(irq_owning_domain(kbd_idev->dev_if), irq);
317
318     return 0;
319
320 done:
321     return 1;
322 }
323
324 static void
325 ps2_process_cmd(void* arg)
326 {
327     /*
328      * 检查锁是否已被启用,如果启用,则表明该timer中断发生时,某个指令正在入队。
329      * 如果是这种情况则跳过,留到下一轮再尝试处理。
330      * 注意,这里其实是ISR的一部分(timer中断),对于单核CPU来说,ISR等同于单个的原子操作。
331      * (因为EFLAGS.IF=0,所有可屏蔽中断被屏蔽。对于NMI的情况,那么就直接算是triple
332      * fault了,所以也没有讨论的意义)
333      * 所以,假若我们遵从互斥锁的严格定义(即这里需要阻塞),那么中断将会被阻塞,进而造成死锁。
334      * 因此,我们这里仅仅进行判断。
335      * 会不会产生指令堆积?不会,因为指令发送的频率远远低于指令队列清空的频率。在目前,我们发送的唯一指令
336      * 就只是用来开关键盘上的LED灯(如CAPSLOCK)。
337      */
338     if (mutex_on_hold(&cmd_q.mutex) || !cmd_q.queue_len) {
339         return;
340     }
341
342     // 处理队列排头的指令
343     struct ps2_cmd* pending_cmd = &cmd_q.cmd_queue[cmd_q.queue_ptr];
344     u8_t result;
345     int attempts = 0;
346
347     // 尝试将命令发送至PS/2键盘(通过PS/2控制器)
348     // 如果不成功(0x60 IO口返回 0xfe,即 NAK i.e. Resend)
349     // 则尝试最多五次
350     do {
351         result = ps2_issue_dev_cmd(pending_cmd->cmd, pending_cmd->arg);
352 #ifdef KBD_ENABLE_SPIRQ_FIX
353         kbd_state.state += KBD_STATE_CMDPROCS;
354 #endif
355         attempts++;
356     } while (result == PS2_RESULT_NAK && attempts < PS2_DEV_CMD_MAX_ATTEMPTS);
357     // XXX: 是否需要处理不成功的指令?
358
359     cmd_q.queue_ptr = (cmd_q.queue_ptr + 1) % PS2_CMD_QUEUE_SIZE;
360     cmd_q.queue_len--;
361 }
362
363 static void
364 kbd_buffer_key_event(kbd_keycode_t key, u8_t scancode, kbd_kstate_t state)
365 {
366     /*
367         forgive me on these ugly bit-level tricks,
368         I really hate doing branching on these "fliping switch" things
369     */
370     if (key == KEY_CAPSLK) {
371         kbd_state.key_state ^= KBD_KEY_FCAPSLKED & -state;
372     } else if (key == KEY_NUMSLK) {
373         kbd_state.key_state ^= KBD_KEY_FNUMBLKED & -state;
374     } else if (key == KEY_SCRLLK) {
375         kbd_state.key_state ^= KBD_KEY_FSCRLLKED & -state;
376     } else {
377         if ((key & MODIFR)) {
378             kbd_kstate_t tmp = (KBD_KEY_FLSHIFT_HELD << (key & 0x00ff));
379             kbd_state.key_state = (kbd_state.key_state & ~tmp) | (tmp & -state);
380         } else if (!(key & 0xff00) &&
381                    (kbd_state.key_state &
382                     (KBD_KEY_FLSHIFT_HELD | KBD_KEY_FRSHIFT_HELD))) {
383             key = scancode_set2_shift[scancode];
384         }
385         state = state | kbd_state.key_state;
386         key = key & (0xffdf |
387                      -('a' > key || key > 'z' || !(state & KBD_KEY_FCAPSLKED)));
388
389         struct input_evt_pkt ipkt = { .pkt_type = (state & KBD_KEY_FPRESSED)
390                                                     ? PKT_PRESS
391                                                     : PKT_RELEASE,
392                                       .scan_code = scancode,
393                                       .sys_code = (state << 16) | key };
394
395         input_fire_event(kbd_idev, &ipkt);
396
397         return;
398     }
399
400     if (state & KBD_KEY_FPRESSED) {
401         // Ooops, this guy generates irq!
402         ps2_device_post_cmd(PS2_KBD_CMD_SETLED,
403                             (kbd_state.key_state >> 1) & 0x00ff);
404     }
405 }
406
407 static void
408 intr_ps2_kbd_handler(irq_t irq, const struct hart_state* hstate)
409 {
410
411     // This is important! Don't believe me? try comment it out and run on Bochs!
412     // while (!(port_rdbyte(PS2_PORT_CTRL_STATUS) & PS2_STATUS_OFULL))
413     //    ;
414
415     // I know you are tempting to move this chunk after the keyboard state
416     // check. But DO NOT. This chunk is in right place and right order. Moving
417     // it at your own risk This is to ensure we've cleared the output buffer
418     // everytime, so it won't pile up across irqs.
419     u8_t scancode = port_rdbyte(PS2_PORT_ENC_DATA);
420     kbd_keycode_t key;
421
422     /*
423      *    判断键盘是否处在指令发送状态,防止误触发。(伪输入中断)
424      * 这是因为我们需要向ps/2设备发送指令(比如控制led灯),而指令会有返回码。
425      * 这就会有可能导致ps/2控制器在受到我们的命令后(在ps2_process_cmd中),
426      * 产生IRQ#1中断(虽然说这种情况取决于底层BIOS实现,但还是会发生,比如QEMU和bochs)。
427      * 所以这就是说,当IRQ#1中断产生时,我们的CPU正处在另一个ISR中。这样就会导致所有的外部中断被缓存在APIC内部的
428      * FIFO队列里,进行排队等待(APIC长度为二的队列 {IRR, TMR};参考 Intel
429      * Manual Vol.3A 10.8.4)
430      * 那么当ps2_process_cmd执行完后(内嵌在#APIC_TIMER_IV),CPU返回EOI给APIC,APIC紧接着将排在队里的IRQ#1发送给CPU
431      * 造成误触发。也就是说,我们此时读入的scancode实则上是上一个指令的返回代码。
432      *
433      * Problem 1 (Fixed):
434      *      但是这种方法有个问题,那就是,假若我们的某一个命令失败了一次,ps/2给出0xfe,我们重传,ps/2收到指令并给出0xfa。
435      *  那么这样一来,将会由两个连续的IRQ#1产生。而APIC是最多可以缓存两个IRQ,于是我们就会漏掉一个IRQ,依然会误触发。
436      * Solution:
437      *      累加掩码 ;)
438      *
439      * Problem 2:
440      *    +
441      * 这种累加掩码的操作是基于只有一号IRQ产生的中断的假设,万一中间夹杂了别的中断?Race
442      * Condition!
443      *    +
444      * 不很稳定x1,假如连续4次发送失败,那么就会导致累加的掩码上溢出,从而导致下述判断失败。
445      */
446 #ifdef KBD_ENABLE_SPIRQ_FIX
447     if ((kbd_state.state & 0xc0)) {
448         kbd_state.state -= KBD_STATE_CMDPROCS;
449
450         return;
451     }
452 #endif
453
454 #ifdef KBD_ENABLE_SPIRQ_FIX2
455     if (scancode == PS2_RESULT_ACK || scancode == PS2_RESULT_NAK) {
456         ps2_process_cmd(NULL);
457         return;
458     }
459 #endif
460
461 #ifdef KBD_DBGLOG
462     DEBUG("%x\n", scancode & 0xff);
463 #endif
464
465     switch (kbd_state.state) {
466         case KBD_STATE_KWAIT:
467             if (scancode == 0xf0) { // release code
468                 kbd_state.state = KBD_STATE_KRELEASED;
469             } else if (scancode == 0xe0) {
470                 kbd_state.state = KBD_STATE_KSPECIAL;
471                 kbd_state.translation_table = scancode_set2_ex;
472             } else {
473                 key = kbd_state.translation_table[scancode];
474                 kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
475             }
476             break;
477         case KBD_STATE_KSPECIAL:
478             if (scancode == 0x12) {
479                 kbd_state.state = KBD_STATE_E012;
480             } else if (scancode == 0xf0) { // release code
481                 kbd_state.state = KBD_STATE_KRELEASED_E0;
482             } else {
483                 key = kbd_state.translation_table[scancode];
484                 kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
485
486                 kbd_state.state = KBD_STATE_KWAIT;
487                 kbd_state.translation_table = scancode_set2;
488             }
489             break;
490         // handle the '0xE0, 0x12, 0xE0, xx' sequence
491         case KBD_STATE_E012:
492             if (scancode == 0xe0) {
493                 kbd_state.state = KBD_STATE_KSPECIAL;
494                 kbd_state.translation_table = scancode_set2_ex;
495             }
496             break;
497         case KBD_STATE_KRELEASED_E0:
498             if (scancode == 0x12) {
499                 goto escape_release;
500             }
501             // fall through
502         case KBD_STATE_KRELEASED:
503             key = kbd_state.translation_table[scancode];
504             kbd_buffer_key_event(key, scancode, KBD_KEY_FRELEASED);
505
506         escape_release:
507             // reset the translation table to scancode_set2
508             kbd_state.state = KBD_STATE_KWAIT;
509             kbd_state.translation_table = scancode_set2;
510             break;
511
512         default:
513             break;
514     }
515 }
516
517 static u8_t
518 ps2_issue_cmd(char cmd, u16_t arg)
519 {
520     ps2_post_cmd(PS2_PORT_CTRL_CMDREG, cmd, arg);
521
522     // 等待PS/2控制器返回。通过轮询(polling)状态寄存器的 bit 0
523     // 如置位,则表明返回代码此时就在 0x60 IO口上等待读取。
524     while (!(port_rdbyte(PS2_PORT_CTRL_STATUS) & PS2_STATUS_OFULL))
525         ;
526
527     return port_rdbyte(PS2_PORT_ENC_CMDREG);
528 }
529
530 static u8_t
531 ps2_issue_cmd_wretry(char cmd, u16_t arg)
532 {
533     u8_t r, c = 0;
534     while ((r = ps2_issue_cmd(cmd, arg)) == PS2_RESULT_NAK && c < 5) {
535         c++;
536     }
537     if (c >= 5) {
538         WARN("max attempt reached.");
539     }
540     return r;
541 }
542
543 static void
544 ps2_post_cmd(u8_t port, char cmd, u16_t arg)
545 {
546     // 等待PS/2输入缓冲区清空,这样我们才可以写入命令
547     while (port_rdbyte(PS2_PORT_CTRL_STATUS) & PS2_STATUS_IFULL)
548         ;
549
550     port_wrbyte(port, cmd);
551     port_delay(PS2_DELAY);
552
553     if (!(arg & PS2_NO_ARG)) {
554         // 所有参数一律通过0x60传入。
555         while (port_rdbyte(PS2_PORT_CTRL_STATUS) & PS2_STATUS_IFULL)
556             ;
557         port_wrbyte(PS2_PORT_ENC_CMDREG, (u8_t)(arg & 0x00ff));
558         port_delay(PS2_DELAY);
559     }
560 }
561
562 static u8_t
563 ps2_issue_dev_cmd(char cmd, u16_t arg)
564 {
565     ps2_post_cmd(PS2_PORT_ENC_CMDREG, cmd, arg);
566
567     // 等待PS/2控制器返回。通过轮询(polling)状态寄存器的 bit 0
568     // 如置位,则表明返回代码此时就在 0x60 IO口上等待读取。
569     while (!(port_rdbyte(PS2_PORT_CTRL_STATUS) & PS2_STATUS_OFULL))
570         ;
571
572     return port_rdbyte(PS2_PORT_ENC_CMDREG);
573 }
574
575 static struct device_def devrtc_i8042kbd = {
576     def_device_class(INTEL, INPUT, KBD),
577     def_device_name("i8042 Keyboard"),
578     def_on_create(ps2_kbd_create)
579 };
580 EXPORT_DEVICE(i8042_kbd, &devrtc_i8042kbd, load_onboot);