Fix file system racing and ext2 directory insertion (#58)
[lunaix-os.git] / docs / tutorial / 7-ps2_keyboard.md
1 ## 准备工作
2
3 ```
4 git checkout af336b49c908dc0d2b62846a19001d4dac7cad61
5 ```
6
7 观看对应视频。
8
9 ## 代码分析
10
11 ### mutex
12
13 ```c
14 #include <stdatomic.h>
15
16 struct sem_t {
17     _Atomic unsigned int counter;
18     // FUTURE: might need a waiting list
19 };
20 ```
21
22 `stdatomic.h`里面的函数是可以使用的。我们会用到里面的原子操作。
23
24 counter的类型是`unsigned int`的原子版本(_Atomic)
25
26 如果sem->counter为0的话,就会一直等待。
27
28 ```c
29 void sem_wait(struct sem_t *sem) {
30     while (!atomic_load(&sem->counter)) {
31         // TODO: yield the cpu
32     }
33     atomic_fetch_sub(&sem->counter, 1);
34 }
35 ```
36
37 增加变量的值
38
39 ```c
40 void sem_post(struct sem_t *sem) {
41     atomic_fetch_add(&sem->counter, 1);
42     // TODO: wake up a thread
43 }
44 ```
45
46 mutex_lock需要获得锁,如果没有(sem->counter为0)就要等待。mutex_unlock会释放锁,sem->counter自增1。
47
48 ```c
49 typedef struct sem_t mutex_t;
50
51 static inline void mutex_init(mutex_t *mutex) {
52     sem_init(mutex, 1);
53 }
54
55 static inline unsigned int mutex_on_hold(mutex_t *mutex) {
56     return !atomic_load(&mutex->counter);
57 }
58
59 static inline void mutex_lock(mutex_t *mutex) {
60     sem_wait(mutex);
61 }
62
63 static inline void mutex_unlock(mutex_t *mutex) {
64     sem_post(mutex);
65 }
66 ```
67
68 ### kernel/peripheral/ps2kbd.c
69
70 `ps2_post_cmd`用于向端口写入命令
71
72 `io_inb(PS2_PORT_CTRL_STATUS)`读取端口,读取后才能清空值。
73
74 `PS2_PORT_CTRL_STATUS`代表Status Register,它在0x64。
75
76 > Data should be written to the
77 > controller's input buffer only if the input buffer's full bit in the
78 > status register is equal to 0.[1]
79
80 full bit就是第二位bit,如果输入缓冲区满,该bit为1
81
82 > When the controller reads the input buffer, this bit will return to 0.[2]
83
84 这里就是等待输入buffer被读取。如果没有被读取,就会阻塞在while。参数可以稍后再看。
85
86 ```c
87 static void ps2_post_cmd(uint8_t port, char cmd, uint16_t arg) {
88     char result;
89     // 等待PS/2输入缓冲区清空,这样我们才可以写入命令
90     while((result = io_inb(PS2_PORT_CTRL_STATUS)) & PS2_STATUS_IFULL);
91
92     io_outb(port, cmd);
93     io_delay(PS2_DELAY);
94     
95     if (!(arg & PS2_NO_ARG)) {
96         // 所有参数一律通过0x60传入。
97         io_outb(PS2_PORT_ENC_CMDREG, (uint8_t)(arg & 0x00ff));
98         io_delay(PS2_DELAY);
99     }
100 }
101 ```
102
103 因为写入端口需要时间,所以要使用`io_delay`。`io_delay`是用一个循环来实现的。
104
105 ```c
106 static inline void
107 io_delay(int counter)
108 {
109     asm volatile (
110         "   test %0, %0\n"
111         "   jz 1f\n"
112         "2: dec %0\n"
113         "   jnz 2b\n"
114         "1: dec %0"::"a"(counter));
115 }
116 ```
117
118 `ps2_issue_dev_cmd`调用`ps2_post_cmd`,最后要等待状态。之前等待的是`PS2_STATUS_IFULL`,这次等待的是`PS2_STATUS_OFULL`。这里循环里面进行了取反,表示output未满就等待。
119
120 > The output buffer should be read only when the output buffer's full bit in the status register is 1.[3]
121
122 ```c
123 static uint8_t ps2_issue_dev_cmd(char cmd, uint16_t arg) {
124     ps2_post_cmd(PS2_PORT_ENC_CMDREG, cmd, arg);
125
126     char result;
127     
128     // 等待PS/2控制器返回。通过轮询(polling)状态寄存器的 bit 0
129     // 如置位,则表明返回代码此时就在 0x60 IO口上等待读取。
130     while(!((result = io_inb(PS2_PORT_CTRL_STATUS)) & PS2_STATUS_OFULL));
131
132     return io_inb(PS2_PORT_ENC_CMDREG);
133 }
134 ```
135
136 `ps2_kbd_init`函数可以看注释。该函数中的下面两个函数需要接着分析。
137
138 ```c
139     intr_subscribe(PC_KBD_IV, intr_ps2_kbd_handler);
140     timer_run_ms(5, ps2_process_cmd, NULL, TIMER_MODE_PERIODIC);
141 ```
142
143 `intr_ps2_kbd_handler`在键盘中断(键盘按下或抬起时触发)时被调用。该函数会对键盘发出的扫描码进行预处理得到`Lunaix Keycode`,保存到`kbd_keycode_t`中。
144
145 每5毫秒会执行一次`ps2_process_cmd`,来进一步处理。
146
147 ### ps2_kbd_init
148
149 用于初始化,略。
150
151 ### kbd_keycode_t
152
153 根据资料,数字1的扫描码是0x16,所以`scancode_set2[0x16]`是`KEY_NUM(1)`。即数组下标是扫描码,值为lunaix-os自定义的*Lunaix Keycode*。如果不想查资料,可以在中断处理函数中打印接收的扫描码。
154
155 ```c
156 // 大部分的扫描码(键码)
157 static kbd_keycode_t scancode_set2[] = {
158     0, KEY_F9, 0, KEY_F5, KEY_F3, KEY_F1, KEY_F2, KEY_F12, 0, KEY_F10, KEY_F8, KEY_F6,
159     KEY_F4, KEY_HTAB, '`', 0, 0, KEY_LALT, KEY_LSHIFT, 0, KEY_LCTRL, 'q', KEY_NUM(1), 
160     0, 0, 0, 'z', 's', 'a', 'w', KEY_NUM(2), 0, 0, 'c', 'x', 'd', 'e', KEY_NUM(4), KEY_NUM(3), 
161     0, 0, KEY_SPACE, 'v', 'f', 't', 'r', KEY_NUM(5),
162     0, 0, 'n', 'b', 'h', 'g', 'y', KEY_NUM(6), 0, 0, 0, 'm', 'j', 'u', KEY_NUM(7), KEY_NUM(8),
163     0, 0, ',', 'k', 'i', 'o', KEY_NUM(0), KEY_NUM(9), 0, 0, '.', '/', 'l', ';', 'p', '-', 0, 0,
164     0, '\'', 0, '[', '=', 0, 0, KEY_CAPSLK, KEY_RSHIFT, KEY_LF, ']', 0, '\\', 0, 0, 0, 0, 0, 0, 0,
165     0, KEY_BS, 0, 0, KEY_NPAD(1), 0, KEY_NPAD(4), KEY_NPAD(7), 0, 0, 0, KEY_NPAD(0), ON_KEYPAD('.'),
166     KEY_NPAD(2), KEY_NPAD(5), KEY_NPAD(6), KEY_NPAD(8), KEY_ESC, KEY_NUMSLK, KEY_F11, ON_KEYPAD('+'),
167     KEY_NPAD(3), ON_KEYPAD('-'), ON_KEYPAD('*'), KEY_NPAD(9), KEY_SCRLLK, 0, 0, 0, 0, KEY_F7
168 };
169 ```
170
171 对于字符1,它的category是0x0,sequence是0x31(1的ASCII值)。KEY_NUM(1)的值就是这么来的。
172
173 ```c
174 //      Lunaix Keycode
175 //       15        7         0
176 // key = |xxxx xxxx|xxxx xxxx|
177 // key[0:7] = sequence
178 // key[8:15] = category
179 //   0x0: ASCII codes
180 //   0x1: keypad keys
181 //   0x2: Function keys
182 //   0x3: Cursor keys (arrow keys)
183 //   0x4: Modifier keys
184 //   0xff: Other keys (Un-categorized)
185 ```
186
187 再举一个例子,`KEY_F8`属于`Function keys`,`sequence`是0x07(这个值也是自定义的)。它的值是`(0x07 | 0x0200)`。
188
189 可以理解成,Lunaix Keycode从两个维度来分类,一个是类型,一个是自定义序号。
190
191 ### intr_ps2_kbd_handler
192
193 里面有一些对`kbd_state.state`的操作,用于切换视频中提到的状态机的状态。
194
195 读取扫描码。
196
197 ```c
198         // Do not move this line. It is in the right place and right order.
199     // This is to ensure we've cleared the output buffer everytime, so it won't pile up across irqs.
200     uint8_t scancode = io_inb(PS2_PORT_ENC_DATA);
201     kbd_keycode_t key;
202 ```
203
204 我们读取的不一定是scancode,还有可能是指令的返回码0xfa。对于这种数据不能用intr_ps2_kbd_handler处理。这个稍后来看。
205
206 先来看看的一个case。如果`scancode == 0xf0`,说明释放了一个按键。状态改为`KBD_STATE_KRELEASED`。如果`scancode == 0xe0`,则使用特殊表`scancode_set2_ex`。如果都不是,就可以正常地获得Lunaix Keycode。
207
208 ```c
209 switch (kbd_state.state)
210     {
211     case KBD_STATE_KWAIT:
212         if (scancode == 0xf0) { // release code
213             kbd_state.state = KBD_STATE_KRELEASED;       
214         } else if (scancode == 0xe0) {
215             kbd_state.state = KBD_STATE_KSPECIAL;
216             kbd_state.translation_table = scancode_set2_ex;
217         } else {
218             key = kbd_state.translation_table[scancode];
219             kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
220         }
221         break;
222 ```
223
224 第二个case。在特殊处理的状态下,如果没有释放,就正常地获得Lunaix Keycode。最后换成普通的表。
225
226 ```c
227     case KBD_STATE_KSPECIAL:
228         if (scancode == 0xf0) { //release code
229             kbd_state.state = KBD_STATE_KRELEASED;
230         } else {
231             key = kbd_state.translation_table[scancode];
232             kbd_buffer_key_event(key, scancode, KBD_KEY_FPRESSED);
233
234             kbd_state.state = KBD_STATE_KWAIT;
235             kbd_state.translation_table = scancode_set2;
236         }
237         break;
238 ```
239
240 最后一个case略。
241
242 ### kbd_buffer_key_event
243
244 state完整位图布局如下
245
246 ```tex
247 15-10 保留
248 9 右ALT
249 8 左ALT
250 7 右CTRL
251 6 左CTRL
252 5 右SHIFT
253 4 左SHIFT
254 3 CAPSLOCK
255 2 NUMLOCK
256 1 SCREENLOCK
257 0 此位为1(KBD_KEY_FPRESSED)表示按下,为0表示抬起
258 ```
259
260 这里是用来记录是否CAPSLOCK等键处于按下状态。简单来说下面代码是用于设置state的一个位。因为我们的状态是用一个bit表示的,所以处理起来有些麻烦。如果用一个byte表示一个状态,可以直接用更简单的异或来切换。
261
262 ```c
263     if (key == KEY_CAPSLK) {
264         kbd_state.key_state ^= KBD_KEY_FCAPSLKED & -state;
265     } else if (key == KEY_NUMSLK) {
266         kbd_state.key_state ^= KBD_KEY_FNUMBLKED & -state;
267     } else if (key == KEY_SCRLLK) {
268         kbd_state.key_state ^= KBD_KEY_FSCRLLKED & -state;
269     } 
270 ```
271
272 state的值是0或者1。-state就是0x0或者0xffff。
273
274 假如key == KEY_CAPSLK,而且state是1,kbd_state.key_state就会异或上0x8。
275
276 假如key == KEY_CAPSLK,而且state是0,kbd_state.key_state就会异或上0。kbd_state.key_state不会改变
277
278 所以按下CAPSLOCK再后抬起,kbd_state.key_state还是会CAPSLOCK锁定的状态。只有再次按下CAPSLOCK才会接触锁定。
279
280 lunaix定义modifier的sequence刚好是每个modifier state bit相对于lshift的位移。这里的位移就是于根据key设置对应的modifier state。当按下lctrl,`key & 0xff == 2`,lshift 左移两位刚好等于lctrl state bit的位置,后面的state取补就是决定这个bit该不该被设置(如果release,就不用设置了)。
281
282 ```c
283 } else {
284         if ((key & MODIFR)) {
285             kbd_kstate_t tmp = (KBD_KEY_FLSHIFT_HELD << (key & 0x00ff));
286             kbd_state.key_state = (kbd_state.key_state & ~tmp) | (tmp & -state);
287         }
288 ```
289
290 如果按下了shift,就用`scancode_set2_shift`表。
291
292 ```c
293         else if (!(key & 0xff00) && (kbd_state.key_state & (KBD_KEY_FLSHIFT_HELD | KBD_KEY_FRSHIFT_HELD))) {
294             key = scancode_set2_shift[scancode];
295         }
296         state = state | kbd_state.key_state;
297         key = key & (0xffdf | -('a' > key || key > 'z' || !(state & KBD_KEY_FCAPSLKED)));
298 ```
299
300 最后得到预处理好的key,存储到keyevent_pkt()。
301
302 最后是让键盘亮灯的操作,但是这个操作会产生返回码干扰我们的状态机。
303
304 ```c
305     if (state & KBD_KEY_FPRESSED) {
306         // Ooops, this guy generates irq!
307         ps2_device_post_cmd(PS2_KBD_CMD_SETLED, (kbd_state.key_state >> 1) & 0x00ff);
308     }
309 ```
310
311 所以`intr_ps2_kbd_handler`中代码使用了叠加掩码的方式来保护状态机
312
313 ```c
314 #ifdef KBD_ENABLE_SPIRQ_FIX
315     if ((kbd_state.state & 0xc0)) {
316         kbd_state.state -= KBD_STATE_CMDPROCS;
317
318         return;
319     }
320 #endif
321 ```
322
323 `ps2_device_post_cmd`略
324
325 ### 接收key
326
327 kernel/lxinit.c调用了`kbd_recv_key`。直接打印了keyevent.keycode的最低字节。
328
329 ```c
330     struct kdb_keyinfo_pkt keyevent;
331     while (1) {
332         if (!kbd_recv_key(&keyevent)) {
333             // yield();
334             continue;
335         }
336         if ((keyevent.state & KBD_KEY_FPRESSED) &&
337             (keyevent.keycode & 0xff00) <= KEYPAD) {
338             tty_put_char((char)(keyevent.keycode & 0x00ff));
339             tty_sync_cursor();
340         }
341     }
342 ```
343
344 `kbd_buffer_key_event`中存储的`kdb_keyinfo_pkt`会在`kbd_recv_key`中读取。视频讲过它的原理,而且比较简单,所以略过。
345
346 ```c
347 int kbd_recv_key(struct kdb_keyinfo_pkt* key_event) {
348     if (!key_buf.buffered_len) {
349         return 0;
350     }
351     mutex_lock(&key_buf.mutex);
352
353     struct kdb_keyinfo_pkt* pkt_current = &key_buf.buffer[key_buf.read_ptr];
354
355     *key_event = *pkt_current;
356     key_buf.buffered_len--;
357     key_buf.read_ptr = (key_buf.read_ptr + 1) % PS2_KBD_RECV_BUFFER_SIZE;
358
359     mutex_unlock(&key_buf.mutex);
360     return 1;
361 }
362 ```
363
364 ## 参考
365
366 [1]IBM_PC_AT_Technical_Reference_Mar84, 1-40 System Board, Input Buffer
367
368 [2]IBM_PC_AT_Technical_Reference_Mar84, 1-38 System Board, Status-Register Bit Definition, Bit 1
369
370 [3]IBM_PC_AT_Technical_Reference_Mar84, 1-40 System Board, Output Buffer