at_keyboard: Fix falco chromebook case.
[grub.git] / grub-core / term / at_keyboard.c
1 /*
2  *  GRUB  --  GRand Unified Bootloader
3  *  Copyright (C) 2007,2008,2009  Free Software Foundation, Inc.
4  *
5  *  GRUB is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  GRUB is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <grub/dl.h>
20 #include <grub/at_keyboard.h>
21 #include <grub/cpu/at_keyboard.h>
22 #include <grub/cpu/io.h>
23 #include <grub/misc.h>
24 #include <grub/term.h>
25 #include <grub/time.h>
26 #include <grub/loader.h>
27 #include <grub/ps2.h>
28
29 GRUB_MOD_LICENSE ("GPLv3+");
30
31 static grub_uint8_t grub_keyboard_controller_orig;
32 static grub_uint8_t grub_keyboard_orig_set;
33 struct grub_ps2_state ps2_state;
34
35 static int ping_sent;
36
37 static void
38 grub_keyboard_controller_init (void);
39
40 static void
41 keyboard_controller_wait_until_ready (void)
42 {
43   /* 50 us would be enough but our current time resolution is 1ms.  */
44   grub_millisleep (1);
45   while (! KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS)));
46 }
47
48 static grub_uint8_t
49 wait_ack (void)
50 {
51   grub_uint64_t endtime;
52   grub_uint8_t ack;
53
54   endtime = grub_get_time_ms () + 20;
55   do {
56     keyboard_controller_wait_until_ready ();
57     ack = grub_inb (KEYBOARD_REG_DATA);
58   } while (ack != GRUB_AT_ACK && ack != GRUB_AT_NACK
59            && grub_get_time_ms () < endtime);
60   return ack;
61 }
62
63 static int
64 at_command (grub_uint8_t data)
65 {
66   unsigned i;
67   for (i = 0; i < GRUB_AT_TRIES; i++)
68     {
69       grub_uint8_t ack;
70       keyboard_controller_wait_until_ready ();
71       grub_outb (data, KEYBOARD_REG_STATUS);
72       ack = wait_ack ();
73       if (ack == GRUB_AT_NACK)
74         continue;
75       if (ack == GRUB_AT_ACK)
76         break;
77       return 0;
78     }
79   return (i != GRUB_AT_TRIES);
80 }
81
82 static void
83 grub_keyboard_controller_write (grub_uint8_t c)
84 {
85   at_command (KEYBOARD_COMMAND_WRITE);
86   keyboard_controller_wait_until_ready ();
87   grub_outb (c, KEYBOARD_REG_DATA);
88 }
89
90 #if defined (GRUB_MACHINE_MIPS_LOONGSON) || defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS)
91 #define USE_SCANCODE_SET 1
92 #else
93 #define USE_SCANCODE_SET 0
94 #endif
95
96 #if !USE_SCANCODE_SET
97
98 static grub_uint8_t
99 grub_keyboard_controller_read (void)
100 {
101   at_command (KEYBOARD_COMMAND_READ);
102   keyboard_controller_wait_until_ready ();
103   return grub_inb (KEYBOARD_REG_DATA);
104 }
105
106 #endif
107
108 static int
109 write_mode (int mode)
110 {
111   unsigned i;
112   for (i = 0; i < GRUB_AT_TRIES; i++)
113     {
114       grub_uint8_t ack;
115       keyboard_controller_wait_until_ready ();
116       grub_outb (0xf0, KEYBOARD_REG_DATA);
117       keyboard_controller_wait_until_ready ();
118       grub_outb (mode, KEYBOARD_REG_DATA);
119       keyboard_controller_wait_until_ready ();
120       ack = wait_ack ();
121       if (ack == GRUB_AT_NACK)
122         continue;
123       if (ack == GRUB_AT_ACK)
124         break;
125       return 0;
126     }
127
128   return (i != GRUB_AT_TRIES);
129 }
130
131 static int
132 query_mode (void)
133 {
134   grub_uint8_t ret;
135   int e;
136
137   e = write_mode (0);
138   if (!e)
139     return 0;
140
141   do {
142     keyboard_controller_wait_until_ready ();
143     ret = grub_inb (KEYBOARD_REG_DATA);
144   } while (ret == GRUB_AT_ACK);
145   /* QEMU translates the set even in no-translate mode.  */
146   if (ret == 0x43 || ret == 1)
147     return 1;
148   if (ret == 0x41 || ret == 2)
149     return 2;
150   if (ret == 0x3f || ret == 3)
151     return 3;
152   return 0;
153 }
154
155 static void
156 set_scancodes (void)
157 {
158   /* You must have visited computer museum. Keyboard without scancode set
159      knowledge. Assume XT. */
160   if (!grub_keyboard_orig_set)
161     {
162       grub_dprintf ("atkeyb", "No sets support assumed\n");
163       ps2_state.current_set = 1;
164       return;
165     }
166
167 #if !USE_SCANCODE_SET
168   ps2_state.current_set = 1;
169   return;
170 #else
171
172   grub_keyboard_controller_write (grub_keyboard_controller_orig
173                                   & ~KEYBOARD_AT_TRANSLATE
174                                   & ~KEYBOARD_AT_DISABLE);
175
176   keyboard_controller_wait_until_ready ();
177   grub_outb (KEYBOARD_COMMAND_ENABLE, KEYBOARD_REG_DATA);
178
179   write_mode (2);
180   ps2_state.current_set = query_mode ();
181   grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set);
182   if (ps2_state.current_set == 2)
183     return;
184
185   write_mode (1);
186   ps2_state.current_set = query_mode ();
187   grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set);
188   if (ps2_state.current_set == 1)
189     return;
190   grub_dprintf ("atkeyb", "no supported scancode set found\n");
191 #endif
192 }
193
194 static void
195 keyboard_controller_led (grub_uint8_t leds)
196 {
197   keyboard_controller_wait_until_ready ();
198   grub_outb (0xed, KEYBOARD_REG_DATA);
199   keyboard_controller_wait_until_ready ();
200   grub_outb (leds & 0x7, KEYBOARD_REG_DATA);
201 }
202
203 int
204 grub_at_keyboard_is_alive (void)
205 {
206   if (ps2_state.current_set != 0)
207     return 1;
208   if (ping_sent
209       && KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS))
210       && grub_inb (KEYBOARD_REG_DATA) == 0x55)
211     {
212       grub_keyboard_controller_init ();
213       return 1;
214     }
215
216   if (KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
217     {
218       grub_outb (0xaa, KEYBOARD_REG_STATUS);
219       ping_sent = 1;
220     }
221   return 0;
222 }
223
224 /* If there is a character pending, return it;
225    otherwise return GRUB_TERM_NO_KEY.  */
226 static int
227 grub_at_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused)))
228 {
229   grub_uint8_t at_key;
230   int ret;
231   grub_uint8_t old_led;
232
233   if (!grub_at_keyboard_is_alive ())
234     return GRUB_TERM_NO_KEY;
235
236   if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
237     return -1;
238   at_key = grub_inb (KEYBOARD_REG_DATA);
239   old_led = ps2_state.led_status;
240
241   ret = grub_ps2_process_incoming_byte (&ps2_state, at_key);
242   if (old_led != ps2_state.led_status)
243     keyboard_controller_led (ps2_state.led_status);
244   return ret;
245 }
246
247 static void
248 grub_keyboard_controller_init (void)
249 {
250   ps2_state.at_keyboard_status = 0;
251   /* Drain input buffer. */
252   while (1)
253     {
254       keyboard_controller_wait_until_ready ();
255       if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
256         break;
257       keyboard_controller_wait_until_ready ();
258       grub_inb (KEYBOARD_REG_DATA);
259     }
260 #if defined (GRUB_MACHINE_MIPS_LOONGSON) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS)
261   grub_keyboard_controller_orig = 0;
262   grub_keyboard_orig_set = 2;
263 #elif defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT)
264   /* *BSD relies on those settings.  */
265   grub_keyboard_controller_orig = KEYBOARD_AT_TRANSLATE;
266   grub_keyboard_orig_set = 2;
267 #else
268   grub_keyboard_controller_orig = grub_keyboard_controller_read ();
269   grub_keyboard_orig_set = query_mode ();
270 #endif
271   set_scancodes ();
272   keyboard_controller_led (ps2_state.led_status);
273 }
274
275 static grub_err_t
276 grub_keyboard_controller_fini (struct grub_term_input *term __attribute__ ((unused)))
277 {
278   if (ps2_state.current_set == 0)
279     return GRUB_ERR_NONE;
280   if (grub_keyboard_orig_set)
281     write_mode (grub_keyboard_orig_set);
282   grub_keyboard_controller_write (grub_keyboard_controller_orig);
283   return GRUB_ERR_NONE;
284 }
285
286 static grub_err_t
287 grub_at_fini_hw (int noreturn __attribute__ ((unused)))
288 {
289   return grub_keyboard_controller_fini (NULL);
290 }
291
292 static grub_err_t
293 grub_at_restore_hw (void)
294 {
295   if (ps2_state.current_set == 0)
296     return GRUB_ERR_NONE;
297
298   /* Drain input buffer. */
299   while (1)
300     {
301       keyboard_controller_wait_until_ready ();
302       if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
303         break;
304       keyboard_controller_wait_until_ready ();
305       grub_inb (KEYBOARD_REG_DATA);
306     }
307   set_scancodes ();
308   keyboard_controller_led (ps2_state.led_status);
309
310   return GRUB_ERR_NONE;
311 }
312
313
314 static struct grub_term_input grub_at_keyboard_term =
315   {
316     .name = "at_keyboard",
317     .fini = grub_keyboard_controller_fini,
318     .getkey = grub_at_keyboard_getkey
319   };
320
321 GRUB_MOD_INIT(at_keyboard)
322 {
323   grub_term_register_input ("at_keyboard", &grub_at_keyboard_term);
324   grub_loader_register_preboot_hook (grub_at_fini_hw, grub_at_restore_hw,
325                                      GRUB_LOADER_PREBOOT_HOOK_PRIO_CONSOLE);
326 }
327
328 GRUB_MOD_FINI(at_keyboard)
329 {
330   grub_keyboard_controller_fini (NULL);
331   grub_term_unregister_input (&grub_at_keyboard_term);
332 }