]> git.proxmox.com Git - grub2.git/blob - grub-core/term/usb_keyboard.c
merge with mainline
[grub2.git] / grub-core / term / usb_keyboard.c
1 /* Support for the HID Boot Protocol. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008, 2009 Free Software Foundation, Inc.
5 *
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <grub/term.h>
21 #include <grub/time.h>
22 #include <grub/cpu/io.h>
23 #include <grub/misc.h>
24 #include <grub/term.h>
25 #include <grub/usb.h>
26 #include <grub/dl.h>
27 #include <grub/time.h>
28
29 \f
30 static char keyboard_map[128] =
31 {
32 '\0', '\0', '\0', '\0', 'a', 'b', 'c', 'd',
33 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
34 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
35 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
36 '3', '4', '5', '6', '7', '8', '9', '0',
37 '\n', GRUB_TERM_ESC, GRUB_TERM_BACKSPACE, GRUB_TERM_TAB, ' ', '-', '=', '[',
38 ']', '\\', '#', ';', '\'', '`', ',', '.',
39 '/', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
40 '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
41 '\0', '\0', GRUB_TERM_HOME, GRUB_TERM_PPAGE, GRUB_TERM_DC, GRUB_TERM_END, GRUB_TERM_NPAGE, GRUB_TERM_RIGHT,
42 GRUB_TERM_LEFT, GRUB_TERM_DOWN, GRUB_TERM_UP
43 };
44
45 static char keyboard_map_shift[128] =
46 {
47 '\0', '\0', '\0', '\0', 'A', 'B', 'C', 'D',
48 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
49 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
50 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
51 '#', '$', '%', '^', '&', '*', '(', ')',
52 '\n', '\0', '\0', '\0', ' ', '_', '+', '{',
53 '}', '|', '#', ':', '"', '`', '<', '>',
54 '?'
55 };
56
57
58 /* Valid values for bRequest. See HID definition version 1.11 section 7.2. */
59 #define USB_HID_GET_REPORT 0x01
60 #define USB_HID_GET_IDLE 0x02
61 #define USB_HID_GET_PROTOCOL 0x03
62 #define USB_HID_SET_REPORT 0x09
63 #define USB_HID_SET_IDLE 0x0A
64 #define USB_HID_SET_PROTOCOL 0x0B
65
66 #define USB_HID_BOOT_SUBCLASS 0x01
67 #define USB_HID_KBD_PROTOCOL 0x01
68
69 static int grub_usb_keyboard_checkkey (struct grub_term_input *term);
70 static int grub_usb_keyboard_getkey (struct grub_term_input *term);
71 static int grub_usb_keyboard_getkeystatus (struct grub_term_input *term);
72
73 static struct grub_term_input grub_usb_keyboard_term =
74 {
75 .checkkey = grub_usb_keyboard_checkkey,
76 .getkey = grub_usb_keyboard_getkey,
77 .getkeystatus = grub_usb_keyboard_getkeystatus,
78 .next = 0
79 };
80
81 struct grub_usb_keyboard_data
82 {
83 grub_usb_device_t usbdev;
84 grub_uint8_t status;
85 int key;
86 struct grub_usb_desc_endp *endp;
87 };
88
89 static struct grub_term_input grub_usb_keyboards[16];
90
91 static void
92 grub_usb_keyboard_detach (grub_usb_device_t usbdev,
93 int config __attribute__ ((unused)),
94 int interface __attribute__ ((unused)))
95 {
96 unsigned i;
97 for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++)
98 {
99 struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data;
100
101 if (!data)
102 continue;
103
104 if (data->usbdev != usbdev)
105 continue;
106
107 grub_term_unregister_input (&grub_usb_keyboards[i]);
108 grub_free ((char *) grub_usb_keyboards[i].name);
109 grub_usb_keyboards[i].name = NULL;
110 grub_free (grub_usb_keyboards[i].data);
111 grub_usb_keyboards[i].data = 0;
112 }
113 }
114
115 static int
116 grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno)
117 {
118 unsigned curnum;
119 struct grub_usb_keyboard_data *data;
120 struct grub_usb_desc_endp *endp = NULL;
121 int j;
122
123 grub_dprintf ("usb_keyboard", "%x %x %x %d %d\n",
124 usbdev->descdev.class, usbdev->descdev.subclass,
125 usbdev->descdev.protocol, configno, interfno);
126
127 for (curnum = 0; curnum < ARRAY_SIZE (grub_usb_keyboards); curnum++)
128 if (!grub_usb_keyboards[curnum].data)
129 break;
130
131 if (curnum == ARRAY_SIZE (grub_usb_keyboards))
132 return 0;
133
134 if (usbdev->descdev.class != 0
135 || usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0)
136 return 0;
137
138 if (usbdev->config[configno].interf[interfno].descif->subclass
139 != USB_HID_BOOT_SUBCLASS
140 || usbdev->config[configno].interf[interfno].descif->protocol
141 != USB_HID_KBD_PROTOCOL)
142 return 0;
143
144 for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt;
145 j++)
146 {
147 endp = &usbdev->config[configno].interf[interfno].descendp[j];
148
149 if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp)
150 == GRUB_USB_EP_INTERRUPT)
151 break;
152 }
153 if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt)
154 return 0;
155
156 grub_dprintf ("usb_keyboard", "HID found!\n");
157
158 data = grub_malloc (sizeof (*data));
159 if (!data)
160 {
161 grub_print_error ();
162 return 0;
163 }
164
165 data->usbdev = usbdev;
166 data->endp = endp;
167
168 /* Place the device in boot mode. */
169 grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT,
170 USB_HID_SET_PROTOCOL, 0, 0, 0, 0);
171
172 /* Reports every time an event occurs and not more often than that. */
173 grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT,
174 USB_HID_SET_IDLE, 0<<8, 0, 0, 0);
175
176 grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term,
177 sizeof (grub_usb_keyboards[curnum]));
178 grub_usb_keyboards[curnum].data = data;
179 usbdev->config[configno].interf[interfno].detach_hook
180 = grub_usb_keyboard_detach;
181 grub_usb_keyboards[curnum].name = grub_xasprintf ("usb_keyboard%d", curnum);
182 if (!grub_usb_keyboards[curnum].name)
183 {
184 grub_print_error ();
185 return 0;
186 }
187
188 {
189 grub_uint8_t report[8];
190 grub_usb_err_t err;
191 grub_memset (report, 0, sizeof (report));
192 err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN,
193 USB_HID_GET_REPORT, 0x0000, interfno,
194 sizeof (report), (char *) report);
195 if (err)
196 {
197 data->status = 0;
198 data->key = -1;
199 }
200 else
201 {
202 data->status = report[0];
203 data->key = report[2] ? : -1;
204 }
205 }
206
207 grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]);
208
209 return 1;
210 }
211
212 \f
213
214 static int
215 grub_usb_keyboard_checkkey (struct grub_term_input *term)
216 {
217 grub_uint8_t data[8];
218 grub_usb_err_t err;
219 struct grub_usb_keyboard_data *termdata = term->data;
220 grub_size_t actual;
221
222 if (termdata->key != -1)
223 return termdata->key;
224
225 data[2] = 0;
226 /* Poll interrupt pipe. */
227 err = grub_usb_bulk_read_extended (termdata->usbdev,
228 termdata->endp->endp_addr, sizeof (data),
229 (char *) data, 10, &actual);
230 if (err || actual < 1)
231 return -1;
232
233 termdata->status = data[0];
234
235 if (actual < 3 || !data[2])
236 return -1;
237
238 grub_dprintf ("usb_keyboard",
239 "report: 0x%02x 0x%02x 0x%02x 0x%02x"
240 " 0x%02x 0x%02x 0x%02x 0x%02x\n",
241 data[0], data[1], data[2], data[3],
242 data[4], data[5], data[6], data[7]);
243
244 /* Check if the Control or Shift key was pressed. */
245 if (data[0] & 0x01 || data[0] & 0x10)
246 termdata->key = keyboard_map[data[2]] - 'a' + 1;
247 else if (data[0] & 0x02 || data[0] & 0x20)
248 termdata->key = keyboard_map_shift[data[2]];
249 else
250 termdata->key = keyboard_map[data[2]];
251
252 if (termdata->key == 0)
253 grub_printf ("Unknown key 0x%x detected\n", data[2]);
254
255 grub_errno = GRUB_ERR_NONE;
256
257 return termdata->key;
258 }
259
260 static int
261 grub_usb_keyboard_getkey (struct grub_term_input *term)
262 {
263 int ret;
264 struct grub_usb_keyboard_data *termdata = term->data;
265
266 while (termdata->key == -1)
267 grub_usb_keyboard_checkkey (term);
268
269 ret = termdata->key;
270
271 termdata->key = -1;
272
273 return ret;
274 }
275
276 static int
277 grub_usb_keyboard_getkeystatus (struct grub_term_input *term)
278 {
279 struct grub_usb_keyboard_data *termdata = term->data;
280 int mods = 0;
281
282 /* Check Shift, Control, and Alt status. */
283 if (termdata->status & 0x02 || termdata->status & 0x20)
284 mods |= GRUB_TERM_STATUS_SHIFT;
285 if (termdata->status & 0x01 || termdata->status & 0x10)
286 mods |= GRUB_TERM_STATUS_CTRL;
287 if (termdata->status & 0x04 || termdata->status & 0x40)
288 mods |= GRUB_TERM_STATUS_ALT;
289
290 return mods;
291 }
292
293 struct grub_usb_attach_desc attach_hook =
294 {
295 .class = GRUB_USB_CLASS_HID,
296 .hook = grub_usb_keyboard_attach
297 };
298
299 GRUB_MOD_INIT(usb_keyboard)
300 {
301 grub_usb_register_attach_hook_class (&attach_hook);
302 }
303
304 GRUB_MOD_FINI(usb_keyboard)
305 {
306 unsigned i;
307 for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++)
308 if (grub_usb_keyboards[i].data)
309 {
310 grub_term_unregister_input (&grub_usb_keyboards[i]);
311 grub_free ((char *) grub_usb_keyboards[i].name);
312 grub_usb_keyboards[i].name = NULL;
313 grub_usb_keyboards[i].data = 0;
314 }
315 grub_usb_unregister_attach_hook_class (&attach_hook);
316 }