Files
yttrium-virtio-gpu/vioinput/sys/HidKeyboard.c
T
Yuri Benditovich 80a41e2665 vioinput: use VirtIO WDF DMA API
Allocate 2 blocks of 4K. One will be sliced for input events
(8 bytes each, i.e. 512 events, when the queue size is usually 64).
Another one for sporadic output of keyboard reports, 16 bytes
allocated for each.

Signed-off-by: Yuri Benditovich <[email protected]>
2019-12-11 13:55:04 +01:00

547 lines
18 KiB
C

/*
* Keyboard specific HID functionality
*
* Copyright (c) 2016-2017 Red Hat, Inc.
*
* Author(s):
* Ladi Prosek <[email protected]>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution.
* 3. Neither the names of the copyright holders nor the names of their contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "precomp.h"
#include "vioinput.h"
#include "Hid.h"
#if defined(EVENT_TRACING)
#include "HidKeyboard.tmh"
#endif
typedef struct _tagInputClassKeyboard
{
INPUT_CLASS_COMMON Common;
#define HID_KEYBOARD_KEY_SLOTS 6
// the keyboard HID report is laid out as follows:
// offset 0
// * report ID
// offset 1
// * 8 modifiers; one bit per modifier
// offset 2
// * padding
// offset 3
// * key array of length HID_KEYBOARD_KEY_SLOTS; one byte per key
// bitmap of currently pressed keys
PUCHAR pKeysPressed;
// length of the pKeysPressed bitmap in bytes
SIZE_T cbKeysPressedLen;
// number of keys currently pressed
SIZE_T nKeysPressed;
// size of the output (host -> device) report
SIZE_T cbOutputReport;
// last seen output (host -> device) report
PUCHAR pLastOutputReport;
} INPUT_CLASS_KEYBOARD, *PINPUT_CLASS_KEYBOARD;
static UCHAR
HIDLEDEventCodeToUsageCode(
USHORT uEventCode)
{
switch (uEventCode)
{
case LED_NUML: return HID_USAGE_LED_NUM_LOCK;
case LED_CAPSL: return HID_USAGE_LED_CAPS_LOCK;
case LED_SCROLLL: return HID_USAGE_LED_SCROLL_LOCK;
case LED_COMPOSE: return HID_USAGE_LED_COMPOSE;
case LED_KANA: return HID_USAGE_LED_KANA;
case LED_SLEEP: return HID_USAGE_LED_STAND_BY;
case LED_SUSPEND: return HID_USAGE_LED_SYSTEM_SUSPEND;
case LED_MUTE: return HID_USAGE_LED_MUTE;
case LED_MISC: return HID_USAGE_LED_GENERIC_INDICATOR;
case LED_MAIL: return HID_USAGE_LED_MESSAGE_WAITING;
case LED_CHARGING: return HID_USAGE_LED_EXTERNAL_POWER_CONNECTED;
}
return 0;
}
static USHORT
HIDLEDUsageCodeToEventCode(
ULONG uCode)
{
switch (uCode)
{
case HID_USAGE_LED_NUM_LOCK: return LED_NUML;
case HID_USAGE_LED_CAPS_LOCK: return LED_CAPSL;
case HID_USAGE_LED_SCROLL_LOCK: return LED_SCROLLL;
case HID_USAGE_LED_COMPOSE: return LED_COMPOSE;
case HID_USAGE_LED_KANA: return LED_KANA;
case HID_USAGE_LED_STAND_BY: return LED_SLEEP;
case HID_USAGE_LED_SYSTEM_SUSPEND: return LED_SUSPEND;
case HID_USAGE_LED_MUTE: return LED_MUTE;
case HID_USAGE_LED_GENERIC_INDICATOR: return LED_MISC;
case HID_USAGE_LED_MESSAGE_WAITING: return LED_MAIL;
case HID_USAGE_LED_EXTERNAL_POWER_CONNECTED: return LED_CHARGING;
}
return 0xFF;
}
static VOID
HIDKeyboardEventKeyToReportKey(
PINPUT_CLASS_KEYBOARD pKeyboardDesc,
UCHAR uCode,
ULONG uValue)
{
PUCHAR pKeyArray = pKeyboardDesc->Common.pHidReport + HID_REPORT_DATA_OFFSET + 2;
BOOLEAN bPressed = FALSE, bReleased = FALSE;
UCHAR uMask, uByte, uBit;
SIZE_T i, iIndex;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "--> %s\n", __FUNCTION__);
// figure out the bitmap index and mask
iIndex = uCode / 8;
uMask = 1 << (uCode % 8);
// set or clear the corresponding bitmap bit
if (uValue)
{
if (!(pKeyboardDesc->pKeysPressed[iIndex] & uMask))
{
pKeyboardDesc->nKeysPressed++;
pKeyboardDesc->pKeysPressed[iIndex] |= uMask;
bPressed = TRUE;
}
}
else
{
if (pKeyboardDesc->pKeysPressed[iIndex] & uMask)
{
pKeyboardDesc->nKeysPressed--;
pKeyboardDesc->pKeysPressed[iIndex] &= ~uMask;
bReleased = TRUE;
}
}
// update the HID report key array
if (bPressed)
{
if (pKeyboardDesc->nKeysPressed <= HID_KEYBOARD_KEY_SLOTS)
{
PUCHAR pSlot = memchr(pKeyArray, 0, HID_KEYBOARD_KEY_SLOTS);
ASSERT(pSlot);
if (pSlot)
{
*pSlot = uCode;
}
}
else if (pKeyboardDesc->nKeysPressed == HID_KEYBOARD_KEY_SLOTS + 1)
{
// we just got into the "rolled over" state
#pragma warning (push)
#pragma warning (disable:28625) // C28625 heuristic triggered by "key" in the variable name
RtlFillMemory(pKeyArray, HID_KEYBOARD_KEY_SLOTS, HID_USAGE_KEYBOARD_ROLLOVER);
#pragma warning (pop)
}
}
else if (bReleased)
{
if (pKeyboardDesc->nKeysPressed < HID_KEYBOARD_KEY_SLOTS)
{
PUCHAR pSlot = memchr(pKeyArray, uCode, HID_KEYBOARD_KEY_SLOTS);
ASSERT(pSlot);
if (pSlot)
{
*pSlot = 0;
}
}
else if (pKeyboardDesc->nKeysPressed == HID_KEYBOARD_KEY_SLOTS)
{
// we just got out of the "rolled over" state
i = 0;
for (iIndex = 0; iIndex < pKeyboardDesc->cbKeysPressedLen; iIndex++)
{
uByte = pKeyboardDesc->pKeysPressed[iIndex];
while (DecodeNextBit(&uByte, &uBit))
{
ASSERT(i < HID_KEYBOARD_KEY_SLOTS);
if (i < HID_KEYBOARD_KEY_SLOTS)
{
pKeyArray[i++] = (UCHAR)(8 * iIndex + uBit);
}
}
}
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- %s\n", __FUNCTION__);
}
static NTSTATUS
HIDKeyboardEventToReport(
PINPUT_CLASS_COMMON pClass,
PVIRTIO_INPUT_EVENT pEvent)
{
PUCHAR pReport = pClass->pHidReport;
PINPUT_CLASS_KEYBOARD pKeyboardDesc = (PINPUT_CLASS_KEYBOARD)pClass;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "--> %s\n", __FUNCTION__);
pReport[HID_REPORT_ID_OFFSET] = pClass->uReportID;
if (pEvent->type == EV_KEY)
{
ULONG uCode = HIDKeyboardEventCodeToUsageCode(pEvent->code);
if (uCode >= 0xE0 && uCode <= 0xE7)
{
// one bit per modifier, all in one byte
unsigned char uMask = 1 << (uCode - 0xE0);
if (pEvent->value)
{
pReport[HID_REPORT_DATA_OFFSET] |= uMask;
}
else
{
pReport[HID_REPORT_DATA_OFFSET] &= ~uMask;
}
pClass->bDirty = TRUE;
}
else if (uCode != 0)
{
HIDKeyboardEventKeyToReportKey(pKeyboardDesc, (UCHAR)uCode, pEvent->value);
pClass->bDirty = TRUE;
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- %s\n", __FUNCTION__);
return STATUS_SUCCESS;
}
static NTSTATUS
HIDKeyboardSendStatus(
PINPUT_DEVICE pContext,
USHORT uType,
USHORT uCode,
ULONG uValue,
WDFREQUEST Request)
{
PVIRTIO_INPUT_EVENT_WITH_REQUEST pEvent;
NTSTATUS status;
PHYSICAL_ADDRESS pa;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "--> %s\n", __FUNCTION__);
pEvent = pContext->StatusQMemBlock->get_slice(pContext->StatusQMemBlock, &pa);
if (pEvent == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
pEvent->Event.type = uType;
pEvent->Event.code = uCode;
pEvent->Event.value = uValue;
pEvent->Request = Request;
WdfSpinLockAcquire(pContext->StatusQLock);
status = VIOInputAddOutBuf(pContext->StatusQ, &pEvent->Event, pa);
WdfSpinLockRelease(pContext->StatusQLock);
if (!NT_SUCCESS(status))
{
pContext->StatusQMemBlock->return_slice(pContext->StatusQMemBlock, pEvent);
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "<-- %s\n", __FUNCTION__);
return status;
}
static NTSTATUS
HIDKeyboardReportToEvent(
PINPUT_CLASS_COMMON pClass,
PINPUT_DEVICE pContext,
WDFREQUEST Request,
PUCHAR pReport,
ULONG cbReport)
{
PINPUT_CLASS_KEYBOARD pKeyboardDesc = (PINPUT_CLASS_KEYBOARD)pClass;
USHORT uPendingLedCode = 0xFF;
USHORT uShortPendingLedValue = 0;
NTSTATUS status = STATUS_SUCCESS;
SIZE_T i;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "--> %s\n", __FUNCTION__);
if (cbReport < pKeyboardDesc->cbOutputReport)
{
// unexpected output report size
return STATUS_BUFFER_TOO_SMALL;
}
if (pReport[HID_REPORT_ID_OFFSET] != pClass->uReportID)
{
// unexpected output report ID
return STATUS_INVALID_PARAMETER;
}
// diff this report with the last one we've seen
for (i = HID_REPORT_DATA_OFFSET; i < pKeyboardDesc->cbOutputReport; i++)
{
UCHAR uDiff = pKeyboardDesc->pLastOutputReport[i] ^ pReport[i];
UCHAR uValue;
while (DecodeNextBit(&uDiff, &uValue))
{
ULONG uCode = uValue + 8 * (ULONG)(i - HID_REPORT_DATA_OFFSET);
// LED codes are 1-based
USHORT uLedCode = HIDLEDUsageCodeToEventCode(uCode + 1);
if (uLedCode != 0xFF)
{
if (uPendingLedCode != 0xFF)
{
// send the pending LED change to the host
status = HIDKeyboardSendStatus(pContext, EV_LED, uPendingLedCode,
uShortPendingLedValue, NULL);
if (!NT_SUCCESS(status))
{
return status;
}
}
uPendingLedCode = uLedCode;
uShortPendingLedValue = !!(pReport[i] & (1 << uValue));
}
}
}
// send the last pending LED change; this one will complete the request
if (uPendingLedCode != 0xFF)
{
status = HIDKeyboardSendStatus(pContext, EV_LED, uPendingLedCode,
uShortPendingLedValue, Request);
}
if (NT_SUCCESS(status))
{
// save this report
RtlCopyMemory(pKeyboardDesc->pLastOutputReport, pReport,
pKeyboardDesc->cbOutputReport);
}
if (uPendingLedCode == 0xFF)
{
// nothing was sent up, complete the request now
WdfRequestComplete(Request, status);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "<-- %s\n", __FUNCTION__);
return status;
}
static VOID
HIDKeyboardCleanup(
PINPUT_CLASS_COMMON pClass)
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "--> %s\n", __FUNCTION__);
PINPUT_CLASS_KEYBOARD pKeyboardDesc = (PINPUT_CLASS_KEYBOARD)pClass;
VIOInputFree(&pKeyboardDesc->pLastOutputReport);
VIOInputFree(&pKeyboardDesc->pKeysPressed);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "<-- %s\n", __FUNCTION__);
}
NTSTATUS
HIDKeyboardProbe(
PINPUT_DEVICE pContext,
PDYNAMIC_ARRAY pHidDesc,
PVIRTIO_INPUT_CFG_DATA pKeys,
PVIRTIO_INPUT_CFG_DATA pLeds)
{
PINPUT_CLASS_KEYBOARD pKeyboardDesc = NULL;
NTSTATUS status = STATUS_SUCCESS;
UCHAR i, uValue, uMaxKey, uMaxLed;
BOOLEAN bGotKey, bGotLed;
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "--> %s\n", __FUNCTION__);
uMaxKey = 0;
bGotKey = FALSE;
for (i = 0; i < pKeys->size; i++)
{
UCHAR uNonKeys = 0;
while (DecodeNextBit(&pKeys->u.bitmap[i], &uValue))
{
USHORT uKeyCode = uValue + 8 * i;
ULONG uCode = HIDKeyboardEventCodeToUsageCode(uKeyCode);
if (uCode != 0 && (uCode & KEY_TYPE_MASK) == KEY_TYPE_KEYBOARD)
{
bGotKey = TRUE;
if (uCode < 0xE0 || uCode > 0xE7)
{
uMaxKey = max(uMaxKey, (UCHAR)uCode);
}
}
else
{
// we have not recognized this EV_KEY code as a keyboard key
uNonKeys |= (1 << uValue);
}
}
pKeys->u.bitmap[i] = uNonKeys;
}
if (!bGotKey)
{
// no keys in the array means that we're done
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "No keyboard key found\n");
goto Exit;
}
// allocate and initialize pKeyboardDesc
pKeyboardDesc = VIOInputAlloc(sizeof(INPUT_CLASS_KEYBOARD));
if (pKeyboardDesc == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
pKeyboardDesc->Common.EventToReportFunc = HIDKeyboardEventToReport;
pKeyboardDesc->Common.ReportToEventFunc = HIDKeyboardReportToEvent;
pKeyboardDesc->Common.CleanupFunc = HIDKeyboardCleanup;
pKeyboardDesc->Common.uReportID = (UCHAR)(pContext->uNumOfClasses + 1);
HIDAppend2(pHidDesc, HID_TAG_USAGE_PAGE, HID_USAGE_PAGE_GENERIC);
HIDAppend2(pHidDesc, HID_TAG_USAGE, HID_USAGE_GENERIC_KEYBOARD);
HIDAppend2(pHidDesc, HID_TAG_COLLECTION, HID_COLLECTION_APPLICATION);
HIDAppend2(pHidDesc, HID_TAG_REPORT_ID, pKeyboardDesc->Common.uReportID);
// one bit per modifier
HIDAppend2(pHidDesc, HID_TAG_USAGE_PAGE, HID_USAGE_PAGE_KEYBOARD);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MINIMUM, 0xE0);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MAXIMUM, 0xE7);
HIDAppend2(pHidDesc, HID_TAG_LOGICAL_MINIMUM, 0x00);
HIDAppend2(pHidDesc, HID_TAG_LOGICAL_MAXIMUM, 0x01);
HIDAppend2(pHidDesc, HID_TAG_REPORT_SIZE, 0x01);
HIDAppend2(pHidDesc, HID_TAG_REPORT_COUNT, 0x08);
HIDAppend2(pHidDesc, HID_TAG_INPUT, HID_DATA_FLAG_VARIABLE);
// reserved byte
HIDAppend2(pHidDesc, HID_TAG_REPORT_SIZE, 0x01);
HIDAppend2(pHidDesc, HID_TAG_REPORT_COUNT, 0x08);
HIDAppend2(pHidDesc, HID_TAG_INPUT, HID_DATA_FLAG_CONSTANT);
// LEDs
uMaxLed = 0;
bGotLed = FALSE;
for (i = 0; i < pLeds->size; i++)
{
while (DecodeNextBit(&pLeds->u.bitmap[i], &uValue))
{
USHORT uLedCode = uValue + 8 * i;
UCHAR uCode = HIDLEDEventCodeToUsageCode(uLedCode);
if (uCode != 0)
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got LED %d\n", uLedCode);
uMaxLed = max(uMaxLed, uCode);
bGotLed = TRUE;
}
}
}
if (bGotLed)
{
ULONG uNumOfLEDs = uMaxLed + 1;
pKeyboardDesc->cbOutputReport = HID_REPORT_DATA_OFFSET + (uNumOfLEDs + 7) / 8;
// one bit per LED as usual in a keyboard device
HIDAppend2(pHidDesc, HID_TAG_REPORT_SIZE, 0x01);
HIDAppend2(pHidDesc, HID_TAG_REPORT_COUNT, uMaxLed + 1);
HIDAppend2(pHidDesc, HID_TAG_USAGE_PAGE, HID_USAGE_PAGE_LED);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MINIMUM, 1);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MAXIMUM, uMaxLed + 1);
HIDAppend2(pHidDesc, HID_TAG_OUTPUT, HID_DATA_FLAG_VARIABLE);
// pad to the nearest whole byte
if (uNumOfLEDs % 8)
{
HIDAppend2(pHidDesc, HID_TAG_REPORT_COUNT, 0x01);
HIDAppend2(pHidDesc, HID_TAG_REPORT_SIZE,
(ULONG)(pKeyboardDesc->cbOutputReport * 8) - uNumOfLEDs);
HIDAppend2(pHidDesc, HID_TAG_OUTPUT, HID_DATA_FLAG_CONSTANT);
}
// allocate and initialize a buffer holding the last seen output report
pKeyboardDesc->pLastOutputReport = ExAllocatePoolWithTag(
NonPagedPool,
pKeyboardDesc->cbOutputReport,
VIOINPUT_DRIVER_MEMORY_TAG);
if (pKeyboardDesc->pLastOutputReport == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(pKeyboardDesc->pLastOutputReport, pKeyboardDesc->cbOutputReport);
}
// keys
HIDAppend2(pHidDesc, HID_TAG_REPORT_SIZE, 0x08);
HIDAppend2(pHidDesc, HID_TAG_REPORT_COUNT, HID_KEYBOARD_KEY_SLOTS);
HIDAppend2(pHidDesc, HID_TAG_LOGICAL_MINIMUM, 0x00);
HIDAppend2(pHidDesc, HID_TAG_LOGICAL_MAXIMUM, uMaxKey);
HIDAppend2(pHidDesc, HID_TAG_USAGE_PAGE, HID_USAGE_PAGE_KEYBOARD);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MINIMUM, 0x00);
HIDAppend2(pHidDesc, HID_TAG_USAGE_MAXIMUM, uMaxKey);
HIDAppend2(pHidDesc, HID_TAG_INPUT, 0);
HIDAppend1(pHidDesc, HID_TAG_END_COLLECTION);
// allocate and initialize the bitmap of currently pressed keys
pKeyboardDesc->cbKeysPressedLen = ((uMaxKey + 1) + 7) / 8;
pKeyboardDesc->pKeysPressed = VIOInputAlloc(pKeyboardDesc->cbKeysPressedLen);
if (pKeyboardDesc->pKeysPressed == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT,
"Created HID keyboard report descriptor with %d keys and %d LEDs\n",
uMaxKey + 1,
bGotLed ? (uMaxLed + 1) : 0);
// calculate the keyboard HID report size
pKeyboardDesc->Common.cbHidReportSize =
HID_REPORT_DATA_OFFSET +
2 + // modifiers and padding
HID_KEYBOARD_KEY_SLOTS;
// register the keyboard class
status = RegisterClass(pContext, &pKeyboardDesc->Common);
Exit:
if (!NT_SUCCESS(status) && pKeyboardDesc != NULL)
{
pKeyboardDesc->Common.CleanupFunc(&pKeyboardDesc->Common);
VIOInputFree(&pKeyboardDesc);
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "<-- %s (%08x)\n", __FUNCTION__, status);
return status;
}