/* * Copyright (C) 2015 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include #include "usbkbd.h" /** @file * * USB keyboard driver * */ /** List of USB keyboards */ static LIST_HEAD ( usb_keyboards ); /****************************************************************************** * * Keyboard map * ****************************************************************************** */ /** * Map USB keycode to iPXE key * * @v keycode Keycode * @v modifiers Modifiers * @ret key iPXE key * * Key codes are defined in the USB HID Usage Tables Keyboard/Keypad * page. */ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers ) { unsigned int key; if ( keycode < USBKBD_KEY_A ) { /* Not keys */ key = 0; } else if ( keycode <= USBKBD_KEY_Z ) { /* Alphabetic keys */ key = ( keycode - USBKBD_KEY_A + 'a' ); if ( modifiers & USBKBD_CTRL ) { key -= ( 'a' - CTRL_A ); } else if ( modifiers & USBKBD_SHIFT ) { key -= ( 'a' - 'A' ); } } else if ( keycode <= USBKBD_KEY_0 ) { /* Numeric key row */ if ( modifiers & USBKBD_SHIFT ) { key = "!@#$%^&*()" [ keycode - USBKBD_KEY_1 ]; } else { key = ( ( ( keycode - USBKBD_KEY_1 + 1 ) % 10 ) + '0' ); } } else if ( keycode <= USBKBD_KEY_SPACE ) { /* Unmodifiable keys */ static const uint8_t unmodifable[] = { LF, ESC, BACKSPACE, TAB, ' ' }; key = unmodifable[ keycode - USBKBD_KEY_ENTER ]; } else if ( keycode <= USBKBD_KEY_SLASH ) { /* Punctuation keys */ if ( modifiers & USBKBD_SHIFT ) { key = "_+{}|~:\"~<>?" [ keycode - USBKBD_KEY_MINUS ]; } else { key = "-=[]\\#;'`,./" [ keycode - USBKBD_KEY_MINUS ]; } } else if ( keycode <= USBKBD_KEY_UP ) { /* Special keys */ static const uint16_t special[] = { 0, 0, 0, 0, 0, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, 0, 0, 0, KEY_IC, KEY_HOME, KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP }; key = special[ keycode - USBKBD_KEY_CAPSLOCK ]; } else { key = 0; } return key; } /****************************************************************************** * * Keyboard buffer * ****************************************************************************** */ /** * Insert keypress into keyboard buffer * * @v kbd USB keyboard * @v keycode Keycode * @v modifiers Modifiers */ static void usbkbd_produce ( struct usb_keyboard *kbd, unsigned int keycode, unsigned int modifiers ) { unsigned int key; /* Map to iPXE key */ key = usbkbd_map ( keycode, modifiers ); /* Do nothing if this keycode has no corresponding iPXE key */ if ( ! key ) { DBGC ( kbd, "KBD %s has no key for keycode %#02x:%#02x\n", kbd->name, modifiers, keycode ); return; } /* Check for buffer overrun */ if ( usbkbd_fill ( kbd ) >= USBKBD_BUFSIZE ) { DBGC ( kbd, "KBD %s buffer overrun (key %#02x)\n", kbd->name, key ); return; } /* Insert into buffer */ kbd->key[ ( kbd->prod++ ) % USBKBD_BUFSIZE ] = key; DBGC2 ( kbd, "KBD %s key %#02x produced\n", kbd->name, key ); } /** * Consume character from keyboard buffer * * @v kbd USB keyboard * @ret character Character */ static unsigned int usbkbd_consume ( struct usb_keyboard *kbd ) { static char buf[] = "\x1b[xx~"; char *tmp = &buf[2]; unsigned int key; unsigned int character; unsigned int ansi_n; unsigned int len; /* Sanity check */ assert ( usbkbd_fill ( kbd ) > 0 ); /* Get current keypress */ key = kbd->key[ kbd->cons % USBKBD_BUFSIZE ]; /* If this is a straightforward key, just consume and return it */ if ( key < KEY_MIN ) { kbd->cons++; DBGC2 ( kbd, "KBD %s key %#02x consumed\n", kbd->name, key ); return key; } /* Construct ANSI sequence */ ansi_n = KEY_ANSI_N ( key ); if ( ansi_n ) tmp += sprintf ( tmp, "%d", ansi_n ); *(tmp++) = KEY_ANSI_TERMINATOR ( key ); *tmp = '\0'; len = ( tmp - buf ); assert ( len < sizeof ( buf ) ); if ( kbd->subcons == 0 ) { DBGC2 ( kbd, "KBD %s key %#02x consumed as ^[%s\n", kbd->name, key, &buf[1] ); } /* Extract character from ANSI sequence */ assert ( kbd->subcons < len ); character = buf[ kbd->subcons++ ]; /* Consume key if applicable */ if ( kbd->subcons == len ) { kbd->cons++; kbd->subcons = 0; } return character; } /****************************************************************************** * * Keyboard report * ****************************************************************************** */ /** * Check for presence of keycode in report * * @v report Keyboard report * @v keycode Keycode (must be non-zero) * @ret has_keycode Keycode is present in report */ static int usbkbd_has_keycode ( struct usb_keyboard_report *report, unsigned int keycode ) { unsigned int i; /* Check for keycode */ for ( i = 0 ; i < ( sizeof ( report->keycode ) / sizeof ( report->keycode[0] ) ) ; i++ ) { if ( report->keycode[i] == keycode ) return keycode; } return 0; } /** * Handle keyboard report * * @v kbd USB keyboard * @v new New keyboard report */ static void usbkbd_report ( struct usb_keyboard *kbd, struct usb_keyboard_report *new ) { struct usb_keyboard_report *old = &kbd->report; unsigned int keycode; unsigned int i; /* Check if current key has been released */ if ( kbd->keycode && ! usbkbd_has_keycode ( new, kbd->keycode ) ) { DBGC2 ( kbd, "KBD %s keycode %#02x released\n", kbd->name, kbd->keycode ); kbd->keycode = 0; } /* Decrement auto-repeat hold-off timer, if applicable */ if ( kbd->holdoff ) kbd->holdoff--; /* Check if a new key has been pressed */ for ( i = 0 ; i < ( sizeof ( new->keycode ) / sizeof ( new->keycode[0] ) ) ; i++ ) { /* Ignore keys present in the previous report */ keycode = new->keycode[i]; if ( ( keycode == 0 ) || usbkbd_has_keycode ( old, keycode ) ) continue; DBGC2 ( kbd, "KBD %s keycode %#02x pressed\n", kbd->name, keycode ); /* Insert keypress into keyboard buffer */ usbkbd_produce ( kbd, keycode, new->modifiers ); /* Record as most recent keycode */ kbd->keycode = keycode; /* Start auto-repeat hold-off timer */ kbd->holdoff = USBKBD_HOLDOFF; } /* Insert auto-repeated keypress into keyboard buffer, if applicable */ if ( kbd->keycode && ! kbd->holdoff ) usbkbd_produce ( kbd, kbd->keycode, new->modifiers ); /* Record report */ memcpy ( old, new, sizeof ( *old ) ); } /****************************************************************************** * * Interrupt endpoint * ****************************************************************************** */ /** * Complete interrupt transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v rc Completion status code */ static void usbkbd_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { struct usb_keyboard *kbd = container_of ( ep, struct usb_keyboard, hid.in ); struct usb_keyboard_report *report; /* Ignore packets cancelled when the endpoint closes */ if ( ! ep->open ) goto drop; /* Ignore packets with errors */ if ( rc != 0 ) { DBGC ( kbd, "KBD %s interrupt IN failed: %s\n", kbd->name, strerror ( rc ) ); goto drop; } /* Ignore underlength packets */ if ( iob_len ( iobuf ) < sizeof ( *report ) ) { DBGC ( kbd, "KBD %s underlength report:\n", kbd->name ); DBGC_HDA ( kbd, 0, iobuf->data, iob_len ( iobuf ) ); goto drop; } report = iobuf->data; /* Handle keyboard report */ usbkbd_report ( kbd, report ); drop: /* Recycle I/O buffer */ usb_recycle ( &kbd->hid.in, iobuf ); } /** Interrupt endpoint operations */ static struct usb_endpoint_driver_operations usbkbd_operations = { .complete = usbkbd_complete, }; /****************************************************************************** * * USB interface * ****************************************************************************** */ /** * Probe device * * @v func USB function * @v config Configuration descriptor * @ret rc Return status code */ static int usbkbd_probe ( struct usb_function *func, struct usb_configuration_descriptor *config ) { struct usb_device *usb = func->usb; struct usb_keyboard *kbd; int rc; /* Allocate and initialise structure */ kbd = zalloc ( sizeof ( *kbd ) ); if ( ! kbd ) { rc = -ENOMEM; goto err_alloc; } kbd->name = func->name; kbd->bus = usb->port->hub->bus; usbhid_init ( &kbd->hid, func, &usbkbd_operations, NULL ); usb_refill_init ( &kbd->hid.in, sizeof ( kbd->report ), USBKBD_INTR_MAX_FILL ); /* Describe USB human interface device */ if ( ( rc = usbhid_describe ( &kbd->hid, config ) ) != 0 ) { DBGC ( kbd, "KBD %s could not describe: %s\n", kbd->name, strerror ( rc ) ); goto err_describe; } DBGC ( kbd, "KBD %s using %s (len %zd)\n", kbd->name, usb_endpoint_name ( &kbd->hid.in ), kbd->hid.in.mtu ); /* Set boot protocol */ if ( ( rc = usbhid_set_protocol ( usb, func->interface[0], USBHID_PROTOCOL_BOOT ) ) != 0 ) { DBGC ( kbd, "KBD %s could not set boot protocol: %s\n", kbd->name, strerror ( rc ) ); goto err_set_protocol; } /* Set idle time */ if ( ( rc = usbhid_set_idle ( usb, func->interface[0], 0, USBKBD_IDLE_DURATION ) ) != 0 ) { DBGC ( kbd, "KBD %s could not set idle time: %s\n", kbd->name, strerror ( rc ) ); goto err_set_idle; } /* Open USB human interface device */ if ( ( rc = usbhid_open ( &kbd->hid ) ) != 0 ) { DBGC ( kbd, "KBD %s could not open: %s\n", kbd->name, strerror ( rc ) ); goto err_open; } /* Add to list of USB keyboards */ list_add_tail ( &kbd->list, &usb_keyboards ); usb_func_set_drvdata ( func, kbd ); return 0; usbhid_close ( &kbd->hid ); err_open: err_set_idle: err_set_protocol: err_describe: free ( kbd ); err_alloc: return rc; } /** * Remove device * * @v func USB function */ static void usbkbd_remove ( struct usb_function *func ) { struct usb_keyboard *kbd = usb_func_get_drvdata ( func ); /* Remove from list of USB keyboards */ list_del ( &kbd->list ); /* Close USB human interface device */ usbhid_close ( &kbd->hid ); /* Free device */ free ( kbd ); } /** USB keyboard device IDs */ static struct usb_device_id usbkbd_ids[] = { { .name = "kbd", .vendor = USB_ANY_ID, .product = USB_ANY_ID, .class = { .class = USB_CLASS_HID, .subclass = USB_SUBCLASS_HID_BOOT, .protocol = USBKBD_PROTOCOL, }, }, }; /** USB keyboard driver */ struct usb_driver usbkbd_driver __usb_driver = { .ids = usbkbd_ids, .id_count = ( sizeof ( usbkbd_ids ) / sizeof ( usbkbd_ids[0] ) ), .probe = usbkbd_probe, .remove = usbkbd_remove, }; /****************************************************************************** * * Console interface * ****************************************************************************** */ /** * Read a character from the console * * @ret character Character read */ static int usbkbd_getchar ( void ) { struct usb_keyboard *kbd; /* Consume first available key */ list_for_each_entry ( kbd, &usb_keyboards, list ) { if ( usbkbd_fill ( kbd ) ) return usbkbd_consume ( kbd ); } return 0; } /** * Check for available input * * @ret is_available Input is available */ static int usbkbd_iskey ( void ) { struct usb_keyboard *kbd; unsigned int fill; /* Poll all USB keyboards and refill endpoints */ list_for_each_entry ( kbd, &usb_keyboards, list ) { usb_poll ( kbd->bus ); usb_refill ( &kbd->hid.in ); } /* Check for a non-empty keyboard buffer */ list_for_each_entry ( kbd, &usb_keyboards, list ) { fill = usbkbd_fill ( kbd ); if ( fill ) return fill; } return 0; } /** USB keyboard console */ struct console_driver usbkbd_console __console_driver = { .getchar = usbkbd_getchar, .iskey = usbkbd_iskey, };