vaseboot/VasEBoot-core/term/usb_keyboard.c

472 lines
13 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Support for the HID Boot Protocol. */
/*
* VasEBoot -- GRand Unified Bootloader
* Copyright (C) 2008, 2009 Free Software Foundation, Inc.
*
* VasEBoot 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 3 of the License, or
* (at your option) any later version.
*
* VasEBoot 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 VasEBoot. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/term.h>
#include <VasEBoot/time.h>
#include <VasEBoot/misc.h>
#include <VasEBoot/term.h>
#include <VasEBoot/usb.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/time.h>
#include <VasEBoot/keyboard_layouts.h>
VasEBoot_MOD_LICENSE ("GPLv3+");
enum
{
KEY_NO_KEY = 0x00,
KEY_ERR_BUFFER = 0x01,
KEY_ERR_POST = 0x02,
KEY_ERR_UNDEF = 0x03,
KEY_CAPS_LOCK = 0x39,
KEY_NUM_LOCK = 0x53,
};
enum
{
LED_NUM_LOCK = 0x01,
LED_CAPS_LOCK = 0x02
};
/* Valid values for bRequest. See HID definition version 1.11 section 7.2. */
#define USB_HID_GET_REPORT 0x01
#define USB_HID_GET_IDLE 0x02
#define USB_HID_GET_PROTOCOL 0x03
#define USB_HID_SET_REPORT 0x09
#define USB_HID_SET_IDLE 0x0A
#define USB_HID_SET_PROTOCOL 0x0B
#define USB_HID_BOOT_SUBCLASS 0x01
#define USB_HID_KBD_PROTOCOL 0x01
#define VasEBoot_USB_KEYBOARD_LEFT_CTRL 0x01
#define VasEBoot_USB_KEYBOARD_LEFT_SHIFT 0x02
#define VasEBoot_USB_KEYBOARD_LEFT_ALT 0x04
#define VasEBoot_USB_KEYBOARD_RIGHT_CTRL 0x10
#define VasEBoot_USB_KEYBOARD_RIGHT_SHIFT 0x20
#define VasEBoot_USB_KEYBOARD_RIGHT_ALT 0x40
struct VasEBoot_usb_keyboard_data
{
VasEBoot_usb_device_t usbdev;
VasEBoot_uint8_t status;
VasEBoot_uint16_t mods;
int interfno;
struct VasEBoot_usb_desc_endp *endp;
VasEBoot_usb_transfer_t transfer;
VasEBoot_uint8_t report[8];
int dead;
int last_key;
VasEBoot_uint64_t repeat_time;
VasEBoot_uint8_t current_report[8];
VasEBoot_uint8_t last_report[8];
int index;
int max_index;
};
static int VasEBoot_usb_keyboard_getkey (struct VasEBoot_term_input *term);
static int VasEBoot_usb_keyboard_getkeystatus (struct VasEBoot_term_input *term);
static struct VasEBoot_term_input VasEBoot_usb_keyboard_term =
{
.getkey = VasEBoot_usb_keyboard_getkey,
.getkeystatus = VasEBoot_usb_keyboard_getkeystatus,
.next = 0
};
static struct VasEBoot_term_input VasEBoot_usb_keyboards[16];
static int
interpret_status (VasEBoot_uint8_t data0)
{
int mods = 0;
/* Check Shift, Control, and Alt status. */
if (data0 & VasEBoot_USB_KEYBOARD_LEFT_SHIFT)
mods |= VasEBoot_TERM_STATUS_LSHIFT;
if (data0 & VasEBoot_USB_KEYBOARD_RIGHT_SHIFT)
mods |= VasEBoot_TERM_STATUS_RSHIFT;
if (data0 & VasEBoot_USB_KEYBOARD_LEFT_CTRL)
mods |= VasEBoot_TERM_STATUS_LCTRL;
if (data0 & VasEBoot_USB_KEYBOARD_RIGHT_CTRL)
mods |= VasEBoot_TERM_STATUS_RCTRL;
if (data0 & VasEBoot_USB_KEYBOARD_LEFT_ALT)
mods |= VasEBoot_TERM_STATUS_LALT;
if (data0 & VasEBoot_USB_KEYBOARD_RIGHT_ALT)
mods |= VasEBoot_TERM_STATUS_RALT;
return mods;
}
static void
VasEBoot_usb_keyboard_detach (VasEBoot_usb_device_t usbdev,
int config __attribute__ ((unused)),
int interface __attribute__ ((unused)))
{
unsigned i;
for (i = 0; i < ARRAY_SIZE (VasEBoot_usb_keyboards); i++)
{
struct VasEBoot_usb_keyboard_data *data = VasEBoot_usb_keyboards[i].data;
if (!data)
continue;
if (data->usbdev != usbdev)
continue;
if (data->transfer)
VasEBoot_usb_cancel_transfer (data->transfer);
VasEBoot_term_unregister_input (&VasEBoot_usb_keyboards[i]);
VasEBoot_free ((char *) VasEBoot_usb_keyboards[i].name);
VasEBoot_usb_keyboards[i].name = NULL;
VasEBoot_free (VasEBoot_usb_keyboards[i].data);
VasEBoot_usb_keyboards[i].data = 0;
}
}
static int
VasEBoot_usb_keyboard_attach (VasEBoot_usb_device_t usbdev, int configno, int interfno)
{
unsigned curnum;
struct VasEBoot_usb_keyboard_data *data;
struct VasEBoot_usb_desc_endp *endp = NULL;
int j;
VasEBoot_dprintf ("usb_keyboard", "%x %x %x %d %d\n",
usbdev->descdev.class, usbdev->descdev.subclass,
usbdev->descdev.protocol, configno, interfno);
for (curnum = 0; curnum < ARRAY_SIZE (VasEBoot_usb_keyboards); curnum++)
if (!VasEBoot_usb_keyboards[curnum].data)
break;
if (curnum == ARRAY_SIZE (VasEBoot_usb_keyboards))
return 0;
if (usbdev->descdev.class != 0
|| usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0)
return 0;
if (usbdev->config[configno].interf[interfno].descif->subclass
!= USB_HID_BOOT_SUBCLASS
|| usbdev->config[configno].interf[interfno].descif->protocol
!= USB_HID_KBD_PROTOCOL)
return 0;
for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt;
j++)
{
endp = &usbdev->config[configno].interf[interfno].descendp[j];
if ((endp->endp_addr & 128) && VasEBoot_usb_get_ep_type(endp)
== VasEBoot_USB_EP_INTERRUPT)
break;
}
if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt)
return 0;
VasEBoot_dprintf ("usb_keyboard", "HID found!\n");
data = VasEBoot_malloc (sizeof (*data));
if (!data)
{
VasEBoot_print_error ();
return 0;
}
data->usbdev = usbdev;
data->interfno = interfno;
data->endp = endp;
/* Configure device */
VasEBoot_usb_set_configuration (usbdev, configno + 1);
/* Place the device in boot mode. */
VasEBoot_usb_control_msg (usbdev, VasEBoot_USB_REQTYPE_CLASS_INTERFACE_OUT,
USB_HID_SET_PROTOCOL, 0, interfno, 0, 0);
/* Reports every time an event occurs and not more often than that. */
VasEBoot_usb_control_msg (usbdev, VasEBoot_USB_REQTYPE_CLASS_INTERFACE_OUT,
USB_HID_SET_IDLE, 0<<8, interfno, 0, 0);
VasEBoot_memcpy (&VasEBoot_usb_keyboards[curnum], &VasEBoot_usb_keyboard_term,
sizeof (VasEBoot_usb_keyboards[curnum]));
VasEBoot_usb_keyboards[curnum].data = data;
usbdev->config[configno].interf[interfno].detach_hook
= VasEBoot_usb_keyboard_detach;
VasEBoot_usb_keyboards[curnum].name = VasEBoot_xasprintf ("usb_keyboard%d", curnum);
if (!VasEBoot_usb_keyboards[curnum].name)
{
VasEBoot_print_error ();
return 0;
}
/* Test showed that getting report may make the keyboard go nuts.
Moreover since we're reattaching keyboard it usually sends
an initial message on interrupt pipe and so we retrieve
the same keystatus.
*/
#if 0
{
VasEBoot_uint8_t report[8];
VasEBoot_usb_err_t err;
VasEBoot_memset (report, 0, sizeof (report));
err = VasEBoot_usb_control_msg (usbdev, VasEBoot_USB_REQTYPE_CLASS_INTERFACE_IN,
USB_HID_GET_REPORT, 0x0100, interfno,
sizeof (report), (char *) report);
if (err)
data->status = 0;
else
data->status = report[0];
}
#else
data->status = 0;
#endif
data->transfer = VasEBoot_usb_bulk_read_background (usbdev,
data->endp,
sizeof (data->report),
(char *) data->report);
if (!data->transfer)
{
VasEBoot_print_error ();
return 0;
}
data->last_key = -1;
data->mods = 0;
data->dead = 0;
VasEBoot_term_register_input_active ("usb_keyboard", &VasEBoot_usb_keyboards[curnum]);
return 1;
}
static void
send_leds (struct VasEBoot_usb_keyboard_data *termdata)
{
char report[1];
report[0] = 0;
if (termdata->mods & VasEBoot_TERM_STATUS_CAPS)
report[0] |= LED_CAPS_LOCK;
if (termdata->mods & VasEBoot_TERM_STATUS_NUM)
report[0] |= LED_NUM_LOCK;
VasEBoot_usb_control_msg (termdata->usbdev, VasEBoot_USB_REQTYPE_CLASS_INTERFACE_OUT,
USB_HID_SET_REPORT, 0x0200, termdata->interfno,
sizeof (report), (char *) report);
VasEBoot_errno = VasEBoot_ERR_NONE;
}
static int
parse_keycode (struct VasEBoot_usb_keyboard_data *termdata)
{
int index = termdata->index;
int i, keycode;
/* Sanity check */
if (index < 2)
index = 2;
for ( ; index < termdata->max_index; index++)
{
keycode = termdata->current_report[index];
if (keycode == KEY_NO_KEY
|| keycode == KEY_ERR_BUFFER
|| keycode == KEY_ERR_POST
|| keycode == KEY_ERR_UNDEF)
{
/* Don't parse (rest of) this report */
termdata->index = 0;
if (keycode != KEY_NO_KEY)
/* Don't replace last report with current faulty report
* in future ! */
VasEBoot_memcpy (termdata->current_report,
termdata->last_report,
sizeof (termdata->report));
return VasEBoot_TERM_NO_KEY;
}
/* Try to find current keycode in last report. */
for (i = 2; i < 8; i++)
if (keycode == termdata->last_report[i])
break;
if (i < 8)
/* Keycode is in last report, it means it was not released,
* ignore it. */
continue;
if (keycode == KEY_CAPS_LOCK)
{
termdata->mods ^= VasEBoot_TERM_STATUS_CAPS;
send_leds (termdata);
continue;
}
if (keycode == KEY_NUM_LOCK)
{
termdata->mods ^= VasEBoot_TERM_STATUS_NUM;
send_leds (termdata);
continue;
}
termdata->last_key = VasEBoot_term_map_key (keycode,
interpret_status (termdata->current_report[0])
| termdata->mods);
termdata->repeat_time = VasEBoot_get_time_ms () + VasEBoot_TERM_REPEAT_PRE_INTERVAL;
VasEBoot_errno = VasEBoot_ERR_NONE;
index++;
if (index >= termdata->max_index)
termdata->index = 0;
else
termdata->index = index;
return termdata->last_key;
}
/* All keycodes parsed */
termdata->index = 0;
return VasEBoot_TERM_NO_KEY;
}
static int
VasEBoot_usb_keyboard_getkey (struct VasEBoot_term_input *term)
{
VasEBoot_usb_err_t err;
struct VasEBoot_usb_keyboard_data *termdata = term->data;
VasEBoot_size_t actual;
int keycode = VasEBoot_TERM_NO_KEY;
if (termdata->dead)
return VasEBoot_TERM_NO_KEY;
if (termdata->index)
keycode = parse_keycode (termdata);
if (keycode != VasEBoot_TERM_NO_KEY)
return keycode;
/* Poll interrupt pipe. */
err = VasEBoot_usb_check_transfer (termdata->transfer, &actual);
if (err == VasEBoot_USB_ERR_WAIT)
{
if (termdata->last_key != -1
&& VasEBoot_get_time_ms () > termdata->repeat_time)
{
termdata->repeat_time = VasEBoot_get_time_ms ()
+ VasEBoot_TERM_REPEAT_INTERVAL;
return termdata->last_key;
}
return VasEBoot_TERM_NO_KEY;
}
if (!err && (actual >= 3))
VasEBoot_memcpy (termdata->last_report,
termdata->current_report,
sizeof (termdata->report));
VasEBoot_memcpy (termdata->current_report,
termdata->report,
sizeof (termdata->report));
termdata->transfer = VasEBoot_usb_bulk_read_background (termdata->usbdev,
termdata->endp,
sizeof (termdata->report),
(char *) termdata->report);
if (!termdata->transfer)
{
VasEBoot_printf ("%s failed. Stopped\n", term->name);
termdata->dead = 1;
}
termdata->last_key = -1;
VasEBoot_dprintf ("usb_keyboard",
"err = %d, actual = %" PRIuVasEBoot_SIZE
" report: 0x%02x 0x%02x 0x%02x 0x%02x"
" 0x%02x 0x%02x 0x%02x 0x%02x\n",
err, actual,
termdata->current_report[0], termdata->current_report[1],
termdata->current_report[2], termdata->current_report[3],
termdata->current_report[4], termdata->current_report[5],
termdata->current_report[6], termdata->current_report[7]);
if (err || actual < 1)
return VasEBoot_TERM_NO_KEY;
termdata->status = termdata->current_report[0];
if (actual < 3)
return VasEBoot_TERM_NO_KEY;
termdata->index = 2; /* New data received. */
termdata->max_index = actual;
return parse_keycode (termdata);
}
static int
VasEBoot_usb_keyboard_getkeystatus (struct VasEBoot_term_input *term)
{
struct VasEBoot_usb_keyboard_data *termdata = term->data;
return interpret_status (termdata->status) | termdata->mods;
}
static struct VasEBoot_usb_attach_desc attach_hook =
{
.class = VasEBoot_USB_CLASS_HID,
.hook = VasEBoot_usb_keyboard_attach
};
VasEBoot_MOD_INIT(usb_keyboard)
{
VasEBoot_usb_register_attach_hook_class (&attach_hook);
}
VasEBoot_MOD_FINI(usb_keyboard)
{
unsigned i;
for (i = 0; i < ARRAY_SIZE (VasEBoot_usb_keyboards); i++)
if (VasEBoot_usb_keyboards[i].data)
{
struct VasEBoot_usb_keyboard_data *data = VasEBoot_usb_keyboards[i].data;
if (!data)
continue;
if (data->transfer)
VasEBoot_usb_cancel_transfer (data->transfer);
VasEBoot_term_unregister_input (&VasEBoot_usb_keyboards[i]);
VasEBoot_free ((char *) VasEBoot_usb_keyboards[i].name);
VasEBoot_usb_keyboards[i].name = NULL;
VasEBoot_free (VasEBoot_usb_keyboards[i].data);
VasEBoot_usb_keyboards[i].data = 0;
}
VasEBoot_usb_unregister_attach_hook_class (&attach_hook);
}