* Copyright 2008-2011 Michael Lotz <mmlr@mlotz.ch>
* Distributed under the terms of the MIT license.
*/
#include <new>
#include <stdlib.h>
#include <string.h>
#include <interface/InterfaceDefs.h>
#include <usb/USB_hid.h>
#include <util/AutoLock.h>
#include <debug.h>
#include <kernel.h>
#include "Driver.h"
#include "KeyboardProtocolHandler.h"
#include "HIDCollection.h"
#include "HIDDevice.h"
#include "HIDReport.h"
#include "HIDReportItem.h"
#include <keyboard_mouse_driver.h>
#define LEFT_ALT_KEY 0x04
#define RIGHT_ALT_KEY 0x40
#define ALT_KEYS (LEFT_ALT_KEY | RIGHT_ALT_KEY)
#define KEYBOARD_HANDLER_COOKIE_FLAG_READER 0x01
#define KEYBOARD_HANDLER_COOKIE_FLAG_DEBUGGER 0x02
#ifdef KEYBOARD_SUPPORTS_KDL
static bool sDebugKeyboardFound = false;
static size_t sDebugKeyboardReportSize = 0;
static int32 sDebuggerCommandAdded = 0;
#ifdef USB_KDL
static usb_id sDebugKeyboardPipe = 0;
static int
debug_get_keyboard_config(int argc, char **argv)
{
set_debug_variable("_usbPipeID", (uint64)sDebugKeyboardPipe);
set_debug_variable("_usbReportSize", (uint64)sDebugKeyboardReportSize);
return 0;
}
#endif
#endif
KeyboardProtocolHandler::KeyboardProtocolHandler(HIDReport &inputReport,
HIDReport *outputReport)
:
ProtocolHandler(inputReport.Device(), "input/keyboard/" DEVICE_PATH_SUFFIX
"/", 512),
fInputReport(inputReport),
fOutputReport(outputReport),
fRepeatDelay(300000),
fRepeatRate(35000),
fCurrentRepeatDelay(B_INFINITE_TIMEOUT),
fCurrentRepeatKey(0),
fKeyCount(0),
fModifierCount(0),
fLastModifiers(0),
fCurrentKeys(NULL),
fLastKeys(NULL),
fHasReader(0),
fHasDebugReader(false)
{
mutex_init(&fLock, DEVICE_PATH_SUFFIX " keyboard");
bool debugUsable = false;
for (uint32 i = 0; i < inputReport.CountItems(); i++) {
HIDReportItem *item = inputReport.ItemAt(i);
if (!item->HasData())
continue;
if (item->UsagePage() == B_HID_USAGE_PAGE_KEYBOARD
|| item->UsagePage() == B_HID_USAGE_PAGE_CONSUMER
|| item->UsagePage() == B_HID_USAGE_PAGE_BUTTON) {
TRACE("keyboard item with usage %" B_PRIx32 "\n",
item->Usage());
debugUsable = true;
if (item->UsageID() >= B_HID_UID_KB_LEFT_CONTROL
&& item->UsageID() <= B_HID_UID_KB_RIGHT_GUI) {
if (fModifierCount < MAX_MODIFIERS)
fModifiers[fModifierCount++] = item;
} else if (fKeyCount < MAX_KEYS)
fKeys[fKeyCount++] = item;
}
}
#ifdef KEYBOARD_SUPPORTS_KDL
if (!sDebugKeyboardFound && debugUsable) {
#ifdef USB_KDL
sDebugKeyboardPipe = fInputReport.Device()->InterruptPipe();
#endif
sDebugKeyboardReportSize =
fInputReport.Parser()->MaxReportSize(HID_REPORT_TYPE_INPUT);
if (outputReport != NULL)
sDebugKeyboardFound = true;
}
#endif
TRACE("keyboard device with %" B_PRIu32 " keys and %" B_PRIu32
" modifiers\n", fKeyCount, fModifierCount);
TRACE("input report: %u; output report: %u\n", inputReport.ID(),
outputReport != NULL ? outputReport->ID() : 255);
fLastKeys = (uint16 *)malloc(fKeyCount * 2 * sizeof(uint16));
fCurrentKeys = &fLastKeys[fKeyCount];
if (fLastKeys == NULL) {
fStatus = B_NO_MEMORY;
return;
}
memset(fLastKeys, 0, fKeyCount * 2 * sizeof(uint16));
for (uint32 i = 0; i < MAX_LEDS; i++)
fLEDs[i] = NULL;
if (outputReport != NULL) {
for (uint32 i = 0; i < outputReport->CountItems(); i++) {
HIDReportItem *item = outputReport->ItemAt(i);
if (!item->HasData())
continue;
if (item->UsagePage() == B_HID_USAGE_PAGE_LED) {
switch (item->UsageID()) {
case B_HID_UID_LED_NUM_LOCK:
fLEDs[0] = item;
break;
case B_HID_UID_LED_CAPS_LOCK:
fLEDs[1] = item;
break;
case B_HID_UID_LED_SCROLL_LOCK:
fLEDs[2] = item;
break;
}
}
}
}
#ifdef KEYBOARD_SUPPORTS_KDL
if (atomic_add(&sDebuggerCommandAdded, 1) == 0) {
#ifdef USB_KDL
add_debugger_command("get_usb_keyboard_config",
&debug_get_keyboard_config,
"Gets the required config of the USB keyboard");
#endif
}
#endif
}
KeyboardProtocolHandler::~KeyboardProtocolHandler()
{
free(fLastKeys);
#ifdef KEYBOARD_SUPPORTS_KDL
if (atomic_add(&sDebuggerCommandAdded, -1) == 1) {
#ifdef USB_KDL
remove_debugger_command("get_usb_keyboard_config",
&debug_get_keyboard_config);
#endif
}
#endif
mutex_destroy(&fLock);
}
void
KeyboardProtocolHandler::AddHandlers(HIDDevice &device,
HIDCollection &collection, ProtocolHandler *&handlerList)
{
bool handled = false;
switch (collection.UsagePage()) {
case B_HID_USAGE_PAGE_GENERIC_DESKTOP:
{
switch (collection.UsageID()) {
case B_HID_UID_GD_KEYBOARD:
case B_HID_UID_GD_KEYPAD:
#if 0
case B_HID_UID_GD_SYSTEM_CONTROL:
#endif
handled = true;
}
break;
}
case B_HID_USAGE_PAGE_CONSUMER:
{
switch (collection.UsageID()) {
case B_HID_UID_CON_CONSUMER_CONTROL:
handled = true;
}
break;
}
}
if (!handled) {
TRACE("collection not a supported keyboard subset\n");
return;
}
HIDParser &parser = device.Parser();
uint32 maxReportCount = parser.CountReports(HID_REPORT_TYPE_INPUT);
if (maxReportCount == 0)
return;
uint32 inputReportCount = 0;
HIDReport *inputReports[maxReportCount];
collection.BuildReportList(HID_REPORT_TYPE_INPUT, inputReports,
inputReportCount);
TRACE("input report count: %" B_PRIu32 "\n", inputReportCount);
for (uint32 i = 0; i < inputReportCount; i++) {
HIDReport *inputReport = inputReports[i];
bool foundKeyboardUsage = false;
for (uint32 j = 0; j < inputReport->CountItems(); j++) {
HIDReportItem *item = inputReport->ItemAt(j);
if (!item->HasData())
continue;
if (item->UsagePage() == B_HID_USAGE_PAGE_KEYBOARD
|| (item->UsagePage() == B_HID_USAGE_PAGE_CONSUMER
&& item->Array())
|| (item->UsagePage() == B_HID_USAGE_PAGE_BUTTON
&& item->Array())) {
foundKeyboardUsage = true;
break;
}
}
if (!foundKeyboardUsage)
continue;
bool foundOutputReport = false;
HIDReport *outputReport = NULL;
do {
maxReportCount = parser.CountReports(HID_REPORT_TYPE_OUTPUT);
if (maxReportCount == 0)
break;
uint32 outputReportCount = 0;
HIDReport *outputReports[maxReportCount];
collection.BuildReportList(HID_REPORT_TYPE_OUTPUT,
outputReports, outputReportCount);
for (uint32 j = 0; j < outputReportCount; j++) {
outputReport = outputReports[j];
for (uint32 k = 0; k < outputReport->CountItems(); k++) {
HIDReportItem *item = outputReport->ItemAt(k);
if (item->UsagePage() == B_HID_USAGE_PAGE_LED) {
foundOutputReport = true;
break;
}
}
if (foundOutputReport)
break;
}
} while (false);
ProtocolHandler *newHandler = new(std::nothrow) KeyboardProtocolHandler(
*inputReport, foundOutputReport ? outputReport : NULL);
if (newHandler == NULL) {
TRACE("failed to allocated keyboard protocol handler\n");
continue;
}
newHandler->SetNextHandler(handlerList);
handlerList = newHandler;
}
}
status_t
KeyboardProtocolHandler::Open(uint32 flags, uint32 *cookie)
{
status_t status = ProtocolHandler::Open(flags, cookie);
if (status != B_OK) {
TRACE_ALWAYS("keyboard device failed to open: %s\n",
strerror(status));
return status;
}
if (Device()->OpenCount() == 1) {
fCurrentRepeatDelay = B_INFINITE_TIMEOUT;
fCurrentRepeatKey = 0;
}
return B_OK;
}
status_t
KeyboardProtocolHandler::Close(uint32 *cookie)
{
if ((*cookie & KEYBOARD_HANDLER_COOKIE_FLAG_DEBUGGER) != 0)
fHasDebugReader = false;
if ((*cookie & KEYBOARD_HANDLER_COOKIE_FLAG_READER) != 0)
atomic_and(&fHasReader, 0);
return ProtocolHandler::Close(cookie);
}
status_t
KeyboardProtocolHandler::Control(uint32 *cookie, uint32 op, void *buffer,
size_t length)
{
switch (op) {
case B_GET_DEVICE_NAME:
{
const char name[] = DEVICE_NAME" Keyboard";
return IOGetDeviceName(name,buffer,length);
}
case KB_READ:
{
if (*cookie == 0) {
if (atomic_or(&fHasReader, 1) != 0)
return B_BUSY;
*cookie = KEYBOARD_HANDLER_COOKIE_FLAG_READER;
}
while (true) {
MutexLocker locker(fLock);
bigtime_t enterTime = system_time();
while (RingBufferReadable() == 0) {
status_t result = _ReadReport(fCurrentRepeatDelay, cookie);
if (result != B_OK && result != B_TIMED_OUT)
return result;
if (!Device()->IsOpen())
return B_ERROR;
if (RingBufferReadable() == 0 && fCurrentRepeatKey != 0
&& system_time() - enterTime > fCurrentRepeatDelay) {
_WriteKey(fCurrentRepeatKey, true);
fCurrentRepeatDelay = fRepeatRate;
break;
}
}
if (fHasDebugReader
&& (*cookie & KEYBOARD_HANDLER_COOKIE_FLAG_DEBUGGER)
== 0) {
locker.Unlock();
snooze(25000);
continue;
}
if (!IS_USER_ADDRESS(buffer))
return B_BAD_ADDRESS;
return RingBufferRead(buffer, sizeof(raw_key_info));
}
}
case KB_SET_LEDS:
{
uint8 ledData[4];
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(ledData, buffer, sizeof(ledData)) != B_OK) {
return B_BAD_ADDRESS;
}
return _SetLEDs(ledData);
}
case KB_SET_KEY_REPEAT_RATE:
{
int32 repeatRate;
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(&repeatRate, buffer, sizeof(repeatRate))
!= B_OK) {
return B_BAD_ADDRESS;
}
if (repeatRate == 0 || repeatRate > 1000000)
return B_BAD_VALUE;
fRepeatRate = 10000000 / repeatRate;
return B_OK;
}
case KB_GET_KEY_REPEAT_RATE:
{
int32 repeatRate = 10000000 / fRepeatRate;
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(buffer, &repeatRate, sizeof(repeatRate))
!= B_OK) {
return B_BAD_ADDRESS;
}
return B_OK;
}
case KB_SET_KEY_REPEAT_DELAY:
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(&fRepeatDelay, buffer, sizeof(fRepeatDelay))
!= B_OK) {
return B_BAD_ADDRESS;
}
return B_OK;
case KB_GET_KEY_REPEAT_DELAY:
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(buffer, &fRepeatDelay, sizeof(fRepeatDelay))
!= B_OK) {
return B_BAD_ADDRESS;
}
return B_OK;
case KB_SET_DEBUG_READER:
#ifdef KEYBOARD_SUPPORTS_KDL
if (fHasDebugReader)
return B_BUSY;
*cookie |= KEYBOARD_HANDLER_COOKIE_FLAG_DEBUGGER;
fHasDebugReader = true;
return B_OK;
#else
return B_NOT_SUPPORTED;
#endif
}
TRACE_ALWAYS("keyboard device unhandled control 0x%08" B_PRIx32 "\n", op);
return B_ERROR;
}
void
KeyboardProtocolHandler::_WriteKey(uint32 key, bool down)
{
raw_key_info info;
info.keycode = key;
info.is_keydown = down;
info.timestamp = system_time();
RingBufferWrite(&info, sizeof(raw_key_info));
}
status_t
KeyboardProtocolHandler::_SetLEDs(uint8 *data)
{
if (fOutputReport == NULL || fOutputReport->Device()->IsRemoved())
return B_ERROR;
for (uint32 i = 0; i < MAX_LEDS; i++) {
if (fLEDs[i] == NULL)
continue;
fLEDs[i]->SetData(data[i]);
}
return fOutputReport->SendReport();
}
status_t
KeyboardProtocolHandler::_ReadReport(bigtime_t timeout, uint32 *cookie)
{
status_t result = fInputReport.WaitForReport(timeout);
if (result != B_OK) {
if (fInputReport.Device()->IsRemoved()) {
TRACE("device has been removed\n");
return B_ERROR;
}
if (result == B_INTERRUPTED)
return result;
if ((*cookie & PROTOCOL_HANDLER_COOKIE_FLAG_CLOSED) != 0)
return B_FILE_ERROR;
if (result != B_TIMED_OUT && result != B_BUSY) {
TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
}
return B_OK;
}
TRACE("got keyboard input report\n");
uint8 modifiers = 0;
for (uint32 i = 0; i < fModifierCount; i++) {
HIDReportItem *modifier = fModifiers[i];
if (modifier == NULL)
break;
if (modifier->Extract() == B_OK && modifier->Valid()) {
modifiers |= (modifier->Data() & 1)
<< (modifier->UsageID() - B_HID_UID_KB_LEFT_CONTROL);
}
}
for (uint32 i = 0; i < fKeyCount; i++) {
HIDReportItem *key = fKeys[i];
if (key == NULL)
break;
if (key->Extract() == B_OK && key->Valid()) {
if (key->Array()) {
fCurrentKeys[i] = key->Data() - key->Minimum();
} else {
if (key->Data() == 1)
fCurrentKeys[i] = key->UsageID();
else
fCurrentKeys[i] = 0;
}
}
else
fCurrentKeys[i] = 0;
}
fInputReport.DoneProcessing();
static const uint32 kModifierTable[] = {
KEY_ControlL,
KEY_ShiftL,
KEY_AltL,
KEY_WinL,
KEY_ControlR,
KEY_ShiftR,
KEY_AltR,
KEY_WinR
};
uint8 modifierChange = fLastModifiers ^ modifiers;
for (uint8 i = 0; modifierChange; i++, modifierChange >>= 1) {
if (modifierChange & 1)
_WriteKey(kModifierTable[i], (modifiers >> i) & 1);
}
fLastModifiers = modifiers;
static const uint32 kKeyTable[] = {
0x00,
0x00,
0x00,
0x00,
0x3c,
0x50,
0x4e,
0x3e,
0x29,
0x3f,
0x40,
0x41,
0x2e,
0x42,
0x43,
0x44,
0x52,
0x51,
0x2f,
0x30,
0x27,
0x2a,
0x3d,
0x2b,
0x2d,
0x4f,
0x28,
0x4d,
0x2c,
0x4c,
0x12,
0x13,
0x14,
0x15,
0x16,
0x17,
0x18,
0x19,
0x1a,
0x1b,
0x47,
0x01,
0x1e,
0x26,
0x5e,
0x1c,
0x1d,
0x31,
0x32,
0x33,
0x33,
0x45,
0x46,
0x11,
0x53,
0x54,
0x55,
B_CAPS_LOCK_KEY,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0a,
0x0b,
0x0c,
0x0d,
0x0e,
B_SCROLL_KEY,
B_PAUSE_KEY,
0x1f,
0x20,
0x21,
0x34,
0x35,
0x36,
0x63,
0x61,
0x62,
0x57,
0x22,
0x23,
0x24,
0x25,
0x3a,
0x5b,
0x58,
0x59,
0x5a,
0x48,
0x49,
0x4a,
0x37,
0x38,
0x39,
0x64,
0x65,
0x69,
KEY_Menu,
0x00,
B_NUMPAD_EQUAL_KEY,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x70,
0x00,
0x6b,
0x6e,
0x6a,
0x6d,
0x6c,
0x00,
0x00,
0x00,
0x00,
0xf0,
0xf1,
};
static const size_t kKeyTableSize = B_COUNT_OF(kKeyTable);
bool phantomState = true;
for (size_t i = 0; i < fKeyCount; i++) {
if (fCurrentKeys[i] != 1
|| fKeys[i]->UsagePage() != B_HID_USAGE_PAGE_KEYBOARD) {
phantomState = false;
break;
}
}
if (phantomState) {
return B_OK;
}
static bool sysReqPressed = false;
bool keyDown = false;
uint16 *current = fLastKeys;
uint16 *compare = fCurrentKeys;
for (int32 twice = 0; twice < 2; twice++) {
for (size_t i = 0; i < fKeyCount; i++) {
if (current[i] == 0 || (current[i] == 1
&& fKeys[i]->UsagePage() == B_HID_USAGE_PAGE_KEYBOARD))
continue;
bool found = false;
for (size_t j = 0; j < fKeyCount; j++) {
if (compare[j] == current[i]) {
found = true;
break;
}
}
if (found)
continue;
uint32 key = 0;
if (fKeys[i]->UsagePage() == B_HID_USAGE_PAGE_KEYBOARD) {
if (current[i] < kKeyTableSize)
key = kKeyTable[current[i]];
if (key == B_PAUSE_KEY && (modifiers & ALT_KEYS) != 0)
key = KEY_Break;
else if (key == 0xe && (modifiers & ALT_KEYS) != 0) {
key = KEY_SysRq;
sysReqPressed = keyDown;
} else if (sysReqPressed && keyDown
&& current[i] >= 4 && current[i] <= 29
&& (fLastModifiers & ALT_KEYS) != 0) {
#ifdef KEYBOARD_SUPPORTS_KDL
#ifdef USB_KDL
sDebugKeyboardPipe
= fInputReport.Device()->InterruptPipe();
#endif
sDebugKeyboardReportSize
= fInputReport.Parser()->MaxReportSize(HID_REPORT_TYPE_INPUT);
#endif
char letter = current[i] - 4 + 'a';
if (debug_emergency_key_pressed(letter)) {
sysReqPressed = false;
continue;
}
}
}
if (key == 0) {
key = fInputReport.Usages()[0] + current[i];
}
_WriteKey(key, keyDown);
if (keyDown) {
fCurrentRepeatKey = key;
fCurrentRepeatDelay = fRepeatDelay;
} else {
if (fCurrentRepeatKey == key) {
fCurrentRepeatDelay = B_INFINITE_TIMEOUT;
fCurrentRepeatKey = 0;
}
}
}
current = fCurrentKeys;
compare = fLastKeys;
keyDown = true;
}
memcpy(fLastKeys, fCurrentKeys, fKeyCount * sizeof(uint16));
return B_OK;
}