mirror of https://github.com/ipxe/ipxe.git
594 lines
15 KiB
C
594 lines
15 KiB
C
/*
|
|
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <ipxe/console.h>
|
|
#include <ipxe/keys.h>
|
|
#include <ipxe/keymap.h>
|
|
#include <ipxe/usb.h>
|
|
#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
|
|
* @v leds LED state
|
|
* @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 leds ) {
|
|
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' );
|
|
} 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_CAPS_LOCK ];
|
|
} else if ( keycode <= USBKBD_KEY_PAD_ENTER ) {
|
|
/* Keypad (unaffected by Num Lock) */
|
|
key = "\0/*-+\n" [ keycode - USBKBD_KEY_NUM_LOCK ];
|
|
} else if ( keycode <= USBKBD_KEY_PAD_DOT ) {
|
|
/* Keypad (affected by Num Lock) */
|
|
if ( leds & USBKBD_LED_NUM_LOCK ) {
|
|
key = "1234567890." [ keycode - USBKBD_KEY_PAD_1 ];
|
|
} else {
|
|
static const uint16_t keypad[] = {
|
|
KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, 0,
|
|
KEY_RIGHT, KEY_HOME, KEY_UP, KEY_PPAGE,
|
|
KEY_IC, KEY_DC
|
|
};
|
|
key = keypad[ keycode - USBKBD_KEY_PAD_1 ];
|
|
};
|
|
} else {
|
|
key = 0;
|
|
}
|
|
|
|
/* Remap key if applicable */
|
|
if ( keycode < USBKBD_KEY_CAPS_LOCK )
|
|
key = key_remap ( key );
|
|
|
|
/* Handle upper/lower case and Ctrl-<key> */
|
|
if ( islower ( key ) ) {
|
|
if ( modifiers & USBKBD_CTRL ) {
|
|
key -= ( 'a' - CTRL_A );
|
|
} else if ( ( modifiers & USBKBD_SHIFT ) ||
|
|
( leds & USBKBD_LED_CAPS_LOCK ) ) {
|
|
key -= ( 'a' - 'A' );
|
|
}
|
|
}
|
|
|
|
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 leds = 0;
|
|
unsigned int key;
|
|
|
|
/* Check for LED-modifying keys */
|
|
if ( keycode == USBKBD_KEY_CAPS_LOCK ) {
|
|
leds = USBKBD_LED_CAPS_LOCK;
|
|
} else if ( keycode == USBKBD_KEY_NUM_LOCK ) {
|
|
leds = USBKBD_LED_NUM_LOCK;
|
|
}
|
|
|
|
/* Handle LED-modifying keys */
|
|
if ( leds ) {
|
|
kbd->leds ^= leds;
|
|
kbd->leds_changed = 1;
|
|
return;
|
|
}
|
|
|
|
/* Map to iPXE key */
|
|
key = usbkbd_map ( keycode, modifiers, kbd->leds );
|
|
|
|
/* 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,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Keyboard LEDs
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Set keyboard LEDs
|
|
*
|
|
* @v kbd USB keyboard
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usbkbd_set_leds ( struct usb_keyboard *kbd ) {
|
|
struct usb_function *func = kbd->hid.func;
|
|
int rc;
|
|
|
|
DBGC2 ( kbd, "KBD %s setting LEDs to %#02x\n", kbd->name, kbd->leds );
|
|
|
|
/* Set keyboard LEDs */
|
|
if ( ( rc = usbhid_set_report ( func->usb, func->interface[0],
|
|
USBHID_REPORT_OUTPUT, 0, &kbd->leds,
|
|
sizeof ( kbd->leds ) ) ) != 0 ) {
|
|
DBGC ( kbd, "KBD %s could not set LEDs to %#02x: %s\n",
|
|
kbd->name, kbd->leds, strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* 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, 0, 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 );
|
|
|
|
/* Set initial LED state */
|
|
usbkbd_set_leds ( kbd );
|
|
|
|
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,
|
|
},
|
|
};
|
|
|
|
/** USB keyboard driver */
|
|
struct usb_driver usbkbd_driver __usb_driver = {
|
|
.ids = usbkbd_ids,
|
|
.id_count = ( sizeof ( usbkbd_ids ) / sizeof ( usbkbd_ids[0] ) ),
|
|
.class = USB_CLASS_ID ( USB_CLASS_HID, USB_SUBCLASS_HID_BOOT,
|
|
USBKBD_PROTOCOL ),
|
|
.score = USB_SCORE_NORMAL,
|
|
.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 USB keyboards, refill endpoints, and set LEDs if applicable */
|
|
list_for_each_entry ( kbd, &usb_keyboards, list ) {
|
|
|
|
/* Poll keyboard */
|
|
usb_poll ( kbd->bus );
|
|
|
|
/* Refill endpoints */
|
|
usb_refill ( &kbd->hid.in );
|
|
|
|
/* Update keyboard LEDs, if applicable */
|
|
if ( kbd->leds_changed ) {
|
|
usbkbd_set_leds ( kbd );
|
|
kbd->leds_changed = 0;
|
|
}
|
|
}
|
|
|
|
/* 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,
|
|
};
|