3ab4e205f40b8d31520546af399e789b9bcd746a
[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   while (! KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS)));
44 }
45
46 static grub_uint8_t
47 wait_ack (void)
48 {
49   grub_uint64_t endtime;
50   grub_uint8_t ack;
51
52   endtime = grub_get_time_ms () + 20;
53   do
54     ack = grub_inb (KEYBOARD_REG_DATA);
55   while (ack != GRUB_AT_ACK && ack != GRUB_AT_NACK
56          && grub_get_time_ms () < endtime);
57   return ack;
58 }
59
60 static int
61 at_command (grub_uint8_t data)
62 {
63   unsigned i;
64   for (i = 0; i < GRUB_AT_TRIES; i++)
65     {
66       grub_uint8_t ack;
67       keyboard_controller_wait_until_ready ();
68       grub_outb (data, KEYBOARD_REG_STATUS);
69       ack = wait_ack ();
70       if (ack == GRUB_AT_NACK)
71         continue;
72       if (ack == GRUB_AT_ACK)
73         break;
74       return 0;
75     }
76   return (i != GRUB_AT_TRIES);
77 }
78
79 static void
80 grub_keyboard_controller_write (grub_uint8_t c)
81 {
82   at_command (KEYBOARD_COMMAND_WRITE);
83   keyboard_controller_wait_until_ready ();
84   grub_outb (c, KEYBOARD_REG_DATA);
85 }
86
87 #if defined (GRUB_MACHINE_MIPS_LOONGSON) || defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS)
88 #define USE_SCANCODE_SET 1
89 #else
90 #define USE_SCANCODE_SET 0
91 #endif
92
93 #if !USE_SCANCODE_SET
94
95 static grub_uint8_t
96 grub_keyboard_controller_read (void)
97 {
98   at_command (KEYBOARD_COMMAND_READ);
99   keyboard_controller_wait_until_ready ();
100   return grub_inb (KEYBOARD_REG_DATA);
101 }
102
103 #endif
104
105 static int
106 write_mode (int mode)
107 {
108   unsigned i;
109   for (i = 0; i < GRUB_AT_TRIES; i++)
110     {
111       grub_uint8_t ack;
112       keyboard_controller_wait_until_ready ();
113       grub_outb (0xf0, KEYBOARD_REG_DATA);
114       keyboard_controller_wait_until_ready ();
115       grub_outb (mode, KEYBOARD_REG_DATA);
116       keyboard_controller_wait_until_ready ();
117       ack = wait_ack ();
118       if (ack == GRUB_AT_NACK)
119         continue;
120       if (ack == GRUB_AT_ACK)
121         break;
122       return 0;
123     }
124
125   return (i != GRUB_AT_TRIES);
126 }
127
128 static int
129 query_mode (void)
130 {
131   grub_uint8_t ret;
132   int e;
133
134   e = write_mode (0);
135   if (!e)
136     return 0;
137
138   keyboard_controller_wait_until_ready ();
139
140   do
141     ret = grub_inb (KEYBOARD_REG_DATA);
142   while (ret == GRUB_AT_ACK);
143
144   /* QEMU translates the set even in no-translate mode.  */
145   if (ret == 0x43 || ret == 1)
146     return 1;
147   if (ret == 0x41 || ret == 2)
148     return 2;
149   if (ret == 0x3f || ret == 3)
150     return 3;
151   return 0;
152 }
153
154 static void
155 set_scancodes (void)
156 {
157   /* You must have visited computer museum. Keyboard without scancode set
158      knowledge. Assume XT. */
159   if (!grub_keyboard_orig_set)
160     {
161       grub_dprintf ("atkeyb", "No sets support assumed\n");
162       ps2_state.current_set = 1;
163       return;
164     }
165
166 #if !USE_SCANCODE_SET
167   ps2_state.current_set = 1;
168   return;
169 #else
170
171   grub_keyboard_controller_write (grub_keyboard_controller_orig
172                                   & ~KEYBOARD_AT_TRANSLATE);
173
174   write_mode (2);
175   ps2_state.current_set = query_mode ();
176   grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set);
177   if (ps2_state.current_set == 2)
178     return;
179
180   write_mode (1);
181   ps2_state.current_set = query_mode ();
182   grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set);
183   if (ps2_state.current_set == 1)
184     return;
185   grub_dprintf ("atkeyb", "no supported scancode set found\n");
186 #endif
187 }
188
189 static void
190 keyboard_controller_led (grub_uint8_t leds)
191 {
192   keyboard_controller_wait_until_ready ();
193   grub_outb (0xed, KEYBOARD_REG_DATA);
194   keyboard_controller_wait_until_ready ();
195   grub_outb (leds & 0x7, KEYBOARD_REG_DATA);
196 }
197
198 int
199 grub_at_keyboard_is_alive (void)
200 {
201   if (ps2_state.current_set != 0)
202     return 1;
203   if (ping_sent
204       && KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS))
205       && grub_inb (KEYBOARD_REG_DATA) == 0x55)
206     {
207       grub_keyboard_controller_init ();
208       return 1;
209     }
210
211   if (KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
212     {
213       grub_outb (0xaa, KEYBOARD_REG_STATUS);
214       ping_sent = 1;
215     }
216   return 0;
217 }
218
219 /* If there is a character pending, return it;
220    otherwise return GRUB_TERM_NO_KEY.  */
221 static int
222 grub_at_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused)))
223 {
224   grub_uint8_t at_key;
225   int ret;
226   grub_uint8_t old_led;
227
228   if (!grub_at_keyboard_is_alive ())
229     return GRUB_TERM_NO_KEY;
230
231   if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
232     return -1;
233   at_key = grub_inb (KEYBOARD_REG_DATA);
234   old_led = ps2_state.led_status;
235
236   ret = grub_ps2_process_incoming_byte (&ps2_state, at_key);
237   if (old_led != ps2_state.led_status)
238     keyboard_controller_led (ps2_state.led_status);
239   return ret;
240 }
241
242 static void
243 grub_keyboard_controller_init (void)
244 {
245   ps2_state.at_keyboard_status = 0;
246   /* Drain input buffer. */
247   while (1)
248     {
249       keyboard_controller_wait_until_ready ();
250       if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
251         break;
252       keyboard_controller_wait_until_ready ();
253       grub_inb (KEYBOARD_REG_DATA);
254     }
255 #if defined (GRUB_MACHINE_MIPS_LOONGSON) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS)
256   grub_keyboard_controller_orig = 0;
257   grub_keyboard_orig_set = 2;
258 #elif defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT)
259   /* *BSD relies on those settings.  */
260   grub_keyboard_controller_orig = KEYBOARD_AT_TRANSLATE;
261   grub_keyboard_orig_set = 2;
262 #else
263   grub_keyboard_controller_orig = grub_keyboard_controller_read ();
264   grub_keyboard_orig_set = query_mode ();
265 #endif
266   set_scancodes ();
267   keyboard_controller_led (ps2_state.led_status);
268 }
269
270 static grub_err_t
271 grub_keyboard_controller_fini (struct grub_term_input *term __attribute__ ((unused)))
272 {
273   if (ps2_state.current_set == 0)
274     return GRUB_ERR_NONE;
275   if (grub_keyboard_orig_set)
276     write_mode (grub_keyboard_orig_set);
277   grub_keyboard_controller_write (grub_keyboard_controller_orig);
278   return GRUB_ERR_NONE;
279 }
280
281 static grub_err_t
282 grub_at_fini_hw (int noreturn __attribute__ ((unused)))
283 {
284   return grub_keyboard_controller_fini (NULL);
285 }
286
287 static grub_err_t
288 grub_at_restore_hw (void)
289 {
290   if (ps2_state.current_set == 0)
291     return GRUB_ERR_NONE;
292
293   /* Drain input buffer. */
294   while (1)
295     {
296       keyboard_controller_wait_until_ready ();
297       if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS)))
298         break;
299       keyboard_controller_wait_until_ready ();
300       grub_inb (KEYBOARD_REG_DATA);
301     }
302   set_scancodes ();
303   keyboard_controller_led (ps2_state.led_status);
304
305   return GRUB_ERR_NONE;
306 }
307
308
309 static struct grub_term_input grub_at_keyboard_term =
310   {
311     .name = "at_keyboard",
312     .fini = grub_keyboard_controller_fini,
313     .getkey = grub_at_keyboard_getkey
314   };
315
316 GRUB_MOD_INIT(at_keyboard)
317 {
318   grub_term_register_input ("at_keyboard", &grub_at_keyboard_term);
319   grub_loader_register_preboot_hook (grub_at_fini_hw, grub_at_restore_hw,
320                                      GRUB_LOADER_PREBOOT_HOOK_PRIO_CONSOLE);
321 }
322
323 GRUB_MOD_FINI(at_keyboard)
324 {
325   grub_keyboard_controller_fini (NULL);
326   grub_term_unregister_input (&grub_at_keyboard_term);
327 }