Files
ipxe/src/drivers/usb/usbkbd.c
Michael Brown 3ef4f7e2ef [console] Avoid overlap between special keys and Unicode characters
The special key range (from KEY_MIN upwards) currently overlaps with
the valid range for Unicode characters, and therefore prohibits the
use of Unicode key values outside the ASCII range.

Create space for Unicode key values by moving the special keys to the
range immediately above the maximum valid Unicode character.  This
allows the existing encoding of special keys as an efficiently packed
representation of the equivalent ANSI escape sequence to be maintained
almost as-is.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2023-07-04 14:33:43 +01:00

603 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' );
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 unsigned int 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 unsigned int 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 if ( keycode == USBKBD_KEY_NON_US ) {
/* Non-US \ and | */
key = ( ( modifiers & USBKBD_SHIFT ) ?
( KEYMAP_PSEUDO | '|' ) : ( KEYMAP_PSEUDO | '\\' ) );
} else {
key = 0;
}
/* Remap key if applicable */
if ( ( keycode < USBKBD_KEY_CAPS_LOCK ) ||
( keycode == USBKBD_KEY_NON_US ) ) {
/* Apply modifiers */
if ( modifiers & USBKBD_CTRL )
key |= KEYMAP_CTRL;
if ( modifiers & USBKBD_ALT_RIGHT )
key |= KEYMAP_ALTGR;
if ( leds & USBKBD_LED_CAPS_LOCK )
key |= KEYMAP_CAPSLOCK;
/* Remap key */
key = key_remap ( key );
}
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,
};