Files
yttrium-virtio-gpu/vioinput/sys/Hid.c
T
Ladi Prosek 3853cf804a vioinput: Switch to BSD license
The project is switching from GPL to BSD.

Signed-off-by: Ladi Prosek <[email protected]>
2017-08-08 11:28:16 +03:00

658 lines
19 KiB
C

/*
* HID related 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 "Hid.tmh"
#endif
//
// HID descriptor based on this structure is returned by the mini driver in response
// to IOCTL_HID_GET_DEVICE_DESCRIPTOR.
//
static const HID_DESCRIPTOR G_DefaultHidDescriptor =
{
0x09, // length of HID descriptor
0x21, // descriptor type == HID 0x21
0x0100, // hid spec release
0x00, // country code == Not Specified
0x01, // number of HID class descriptors
{ // DescriptorList[0]
0x22, // report descriptor type 0x22
0x0000 // total length of report descriptor
}
};
VOID
EvtIoDeviceControl(
WDFQUEUE Queue,
WDFREQUEST Request,
size_t OutputBufferLength,
size_t InputBufferLength,
ULONG IoControlCode)
{
NTSTATUS status;
BOOLEAN completeRequest = TRUE;
WDFDEVICE device = WdfIoQueueGetDevice(Queue);
PINPUT_DEVICE pContext = GetDeviceContext(device);
ULONG uReportSize;
HID_XFER_PACKET Packet;
WDF_REQUEST_PARAMETERS params;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS,
"--> %s, code = %d\n", __FUNCTION__, IoControlCode);
switch (IoControlCode)
{
case IOCTL_HID_GET_DEVICE_DESCRIPTOR:
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "IOCTL_HID_GET_DEVICE_DESCRIPTOR\n");
//
// Return the device's HID descriptor.
//
ASSERT(pContext->HidDescriptor.bLength != 0);
status = RequestCopyFromBuffer(Request,
&pContext->HidDescriptor,
pContext->HidDescriptor.bLength);
break;
case IOCTL_HID_GET_DEVICE_ATTRIBUTES:
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "IOCTL_HID_GET_DEVICE_ATTRIBUTES\n");
//
// Return the device's attributes in a HID_DEVICE_ATTRIBUTES structure.
//
status = RequestCopyFromBuffer(Request,
&pContext->HidDeviceAttributes,
sizeof(HID_DEVICE_ATTRIBUTES));
break;
case IOCTL_HID_GET_REPORT_DESCRIPTOR:
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "IOCTL_HID_GET_REPORT_DESCRIPTOR\n");
//
// Return the report descriptor for the HID device.
//
status = RequestCopyFromBuffer(Request,
pContext->HidReportDescriptor,
pContext->HidDescriptor.DescriptorList[0].wReportLength);
break;
case IOCTL_HID_READ_REPORT:
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "IOCTL_HID_READ_REPORT\n");
//
// Queue up a report request. We'll complete it when we actually
// receive data from the device.
//
status = WdfRequestForwardToIoQueue(
Request,
pContext->HidQueue);
if (NT_SUCCESS(status))
{
completeRequest = FALSE;
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS,
"WdfRequestForwardToIoQueue failed with 0x%x\n", status);
}
break;
case IOCTL_HID_WRITE_REPORT:
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "IOCTL_HID_WRITE_REPORT\n");
//
// Write a report to the device, commonly used for controlling keyboard
// LEDs. We'll complete the request after the host processes all virtio
// buffers we add to the status queue.
//
WDF_REQUEST_PARAMETERS_INIT(&params);
WdfRequestGetParameters(Request, &params);
if (params.Parameters.DeviceIoControl.InputBufferLength < sizeof(HID_XFER_PACKET))
{
status = STATUS_BUFFER_TOO_SMALL;
}
else
{
RtlCopyMemory(&Packet, WdfRequestWdmGetIrp(Request)->UserBuffer,
sizeof(HID_XFER_PACKET));
WdfRequestSetInformation(Request, Packet.reportBufferLen);
status = ProcessOutputReport(pContext, Request, &Packet);
if (NT_SUCCESS(status))
{
completeRequest = FALSE;
}
}
break;
default:
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
"Unrecognized IOCTL %d\n", IoControlCode);
status = STATUS_NOT_IMPLEMENTED;
break;
}
if (completeRequest)
{
WdfRequestComplete(Request, status);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "<-- %s\n", __FUNCTION__);
}
static VOID
CompleteHIDQueueRequest(
PINPUT_DEVICE pContext,
PINPUT_CLASS_COMMON pClass)
{
WDFREQUEST request;
NTSTATUS status;
if (!pClass->bDirty)
{
// nothing to do
return;
}
status = WdfIoQueueRetrieveNextRequest(pContext->HidQueue, &request);
if (NT_SUCCESS(status))
{
status = RequestCopyFromBuffer(
request,
pClass->pHidReport,
pClass->cbHidReportSize);
WdfRequestComplete(request, status);
if (NT_SUCCESS(status))
{
pClass->bDirty = FALSE;
}
}
}
VOID
ProcessInputEvent(
PINPUT_DEVICE pContext,
PVIRTIO_INPUT_EVENT pEvent)
{
ULONG i;
TraceEvents(
TRACE_LEVEL_VERBOSE,
DBG_READ,
"--> %s TYPE: %d, CODE: %d, VALUE: %d\n",
__FUNCTION__,
pEvent->type,
pEvent->code,
pEvent->value);
if (pEvent->type == EV_SYN)
{
// send report(s) up
for (i = 0; i < pContext->uNumOfClasses; i++)
{
CompleteHIDQueueRequest(pContext, pContext->InputClasses[i]);
}
}
// ask each class to translate the input event into a HID report
for (i = 0; i < pContext->uNumOfClasses; i++)
{
pContext->InputClasses[i]->EventToReportFunc(
pContext->InputClasses[i],
pEvent);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- %s\n", __FUNCTION__);
}
NTSTATUS
ProcessOutputReport(
PINPUT_DEVICE pContext,
WDFREQUEST Request,
PHID_XFER_PACKET pPacket)
{
NTSTATUS status = STATUS_NONE_MAPPED;
ULONG i;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "--> %s\n", __FUNCTION__);
for (i = 0; i < pContext->uNumOfClasses; i++)
{
PINPUT_CLASS_COMMON pClass = pContext->InputClasses[i];
if (pClass->uReportID == pPacket->reportId)
{
if (pClass->ReportToEventFunc)
{
status = pClass->ReportToEventFunc(
pClass,
pContext,
Request,
pPacket->reportBuffer,
pPacket->reportBufferLen);
}
break;
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE, "<-- %s\n", __FUNCTION__);
return status;
}
VOID HIDAppend1(PDYNAMIC_ARRAY pArray, UCHAR tag)
{
DynamicArrayAppend(pArray, &tag, sizeof(tag));
}
VOID HIDAppend2(PDYNAMIC_ARRAY pArray, UCHAR tag, LONG value)
{
if (value >= -MINCHAR && value <= MAXCHAR)
{
HIDAppend1(pArray, tag | 0x01);
DynamicArrayAppend(pArray, &value, 1);
}
else if (value >= -MINSHORT && value <= MAXSHORT)
{
HIDAppend1(pArray, tag | 0x02);
DynamicArrayAppend(pArray, &value, 2);
}
else
{
HIDAppend1(pArray, tag | 0x03);
DynamicArrayAppend(pArray, &value, 4);
}
}
BOOLEAN DecodeNextBit(PUCHAR pBitmap, PUCHAR pValue)
{
ULONG Index;
BOOLEAN bResult = BitScanForward(&Index, *pBitmap);
if (bResult)
{
*pValue = (unsigned char)Index;
*pBitmap &= ~(1 << Index);
}
return bResult;
}
static void DumpReportDescriptor(PHID_REPORT_DESCRIPTOR pDescriptor, SIZE_T cbSize)
{
SIZE_T i = 0;
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "HID report descriptor begin\n");
while (i < cbSize)
{
CHAR buffer[20];
switch (pDescriptor[i] & 0x03)
{
case 0x00:
{
sprintf_s(buffer, sizeof(buffer), "%02x", pDescriptor[i]);
i++;
break;
}
case 0x01:
{
sprintf_s(buffer, sizeof(buffer), "%02x %02x",
pDescriptor[i], pDescriptor[i + 1]);
i += 2;
break;
}
case 0x02:
{
sprintf_s(buffer, sizeof(buffer), "%02x %02x %02x",
pDescriptor[i], pDescriptor[i + 1], pDescriptor[i + 2]);
i += 3;
break;
}
case 0x03:
{
sprintf_s(buffer, sizeof(buffer), "%02x %02x %02x %02x %02x",
pDescriptor[i], pDescriptor[i + 1], pDescriptor[i + 2],
pDescriptor[i + 3], pDescriptor[i + 4]);
i += 5;
break;
}
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "%s\n", buffer);
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "HID report descriptor end\n");
}
static u8 SelectInputConfig(PINPUT_DEVICE pContext, u8 cfgSelect, u8 cfgSubSel)
{
u8 size = 0;
VirtIOWdfDeviceSet(
&pContext->VDevice, offsetof(struct virtio_input_config, select),
&cfgSelect, sizeof(cfgSelect));
VirtIOWdfDeviceSet(
&pContext->VDevice, offsetof(struct virtio_input_config, subsel),
&cfgSubSel, sizeof(cfgSubSel));
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, size),
&size, sizeof(size));
return size;
}
BOOLEAN InputCfgDataHasBit(PVIRTIO_INPUT_CFG_DATA pData, ULONG bit)
{
if (pData->size <= (bit / 8))
{
return FALSE;
}
return (pData->u.bitmap[bit / 8] & (1 << (bit % 8)));
}
static BOOLEAN InputCfgDataEmpty(PVIRTIO_INPUT_CFG_DATA pCfgData)
{
UCHAR i;
for (i = 0; i < pCfgData->size; i++)
{
if (pCfgData->u.bitmap[i] != 0)
{
return FALSE;
}
}
return TRUE;
}
static UINT64 ComputeHash(PUCHAR pData, SIZE_T Length)
{
UINT64 hash = 0;
SIZE_T i;
for (i = 0; i < Length / sizeof(UINT64); i++)
{
hash ^= *(PUINT64)pData;
pData += sizeof(UINT64);
}
for (i = 0; i < (Length & 7); i++)
{
hash ^= (UINT64)*pData << (i * sizeof(UCHAR));
pData++;
}
return hash;
}
NTSTATUS
VIOInputBuildReportDescriptor(PINPUT_DEVICE pContext)
{
DYNAMIC_ARRAY ReportDescriptor = { NULL };
NTSTATUS status = STATUS_SUCCESS;
VIRTIO_INPUT_CFG_DATA KeyData, RelData, AbsData, LedData;
SIZE_T cbReportDescriptor;
UCHAR i, uReportID = 0;
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "--> %s\n", __FUNCTION__);
// key/button config
KeyData.size = SelectInputConfig(pContext, VIRTIO_INPUT_CFG_EV_BITS, EV_KEY);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got EV_KEY bits size %d\n", KeyData.size);
for (i = 0; i < KeyData.size; i++)
{
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, u.bitmap[i]),
&KeyData.u.bitmap[i], 1);
}
// relative axis config
RelData.size = SelectInputConfig(pContext, VIRTIO_INPUT_CFG_EV_BITS, EV_REL);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got EV_REL bits size %d\n", RelData.size);
for (i = 0; i < RelData.size; i++)
{
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, u.bitmap[i]),
&RelData.u.bitmap[i], 1);
}
// absolute axis config
AbsData.size = SelectInputConfig(pContext, VIRTIO_INPUT_CFG_EV_BITS, EV_ABS);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got EV_ABS bits size %d\n", AbsData.size);
for (i = 0; i < AbsData.size; i++)
{
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, u.bitmap[i]),
&AbsData.u.bitmap[i], 1);
}
// if we have any relative axes, we'll expose a mouse device
// if we have any absolute axes, we may expose a mouse as well
if (!InputCfgDataEmpty(&RelData) || !InputCfgDataEmpty(&AbsData))
{
status = HIDMouseProbe(
pContext,
&ReportDescriptor,
&RelData,
&AbsData,
&KeyData);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
// if we have any absolute axes left, we may expose a joystick
if (!InputCfgDataEmpty(&AbsData))
{
status = HIDJoystickProbe(
pContext,
&ReportDescriptor,
&AbsData,
&KeyData);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
// if we have any absolute axes left, we'll expose a tablet device
if (!InputCfgDataEmpty(&AbsData))
{
status = HIDTabletProbe(
pContext,
&ReportDescriptor,
&AbsData,
&KeyData);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
// if we have any keys left, we'll expose a keyboard device
if (!InputCfgDataEmpty(&KeyData))
{
// LED config
LedData.size = SelectInputConfig(pContext, VIRTIO_INPUT_CFG_EV_BITS, EV_LED);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got EV_LED bits size %d\n", LedData.size);
for (i = 0; i < LedData.size; i++)
{
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, u.bitmap[i]),
&LedData.u.bitmap[i], 1);
}
status = HIDKeyboardProbe(
pContext,
&ReportDescriptor,
&KeyData,
&LedData);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
// if we still have any keys left, we'll check for a consumer device
if (!InputCfgDataEmpty(&KeyData))
{
status = HIDConsumerProbe(
pContext,
&ReportDescriptor,
&KeyData);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
if (DynamicArrayIsEmpty(&ReportDescriptor))
{
// we are not exposing any device
pContext->HidReportDescriptor = NULL;
}
else
{
// initialize the HID descriptor
pContext->HidReportDescriptor = (PHID_REPORT_DESCRIPTOR)DynamicArrayGet(
&ReportDescriptor,
&cbReportDescriptor);
if (!pContext->HidReportDescriptor)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
pContext->HidDescriptor = G_DefaultHidDescriptor;
pContext->HidDescriptor.DescriptorList[0].wReportLength = (USHORT)cbReportDescriptor;
pContext->HidReportDescriptorHash = ComputeHash(pContext->HidReportDescriptor, cbReportDescriptor);
DumpReportDescriptor(pContext->HidReportDescriptor, cbReportDescriptor);
}
}
Exit:
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "<-- %s (%08x)\n", __FUNCTION__, status);
return status;
}
VOID
GetAbsAxisInfo(
PINPUT_DEVICE pContext,
ULONG uAbsAxis,
struct virtio_input_absinfo *pAbsInfo)
{
UCHAR uSize, i;
RtlZeroMemory(pAbsInfo, sizeof(struct virtio_input_absinfo));
if (uAbsAxis <= MAXUCHAR)
{
uSize = SelectInputConfig(pContext, VIRTIO_INPUT_CFG_ABS_INFO, (UCHAR)uAbsAxis);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "Got abs axis %d info size %d\n", uAbsAxis, uSize);
for (i = 0; i < uSize && i < sizeof(struct virtio_input_absinfo); i++)
{
VirtIOWdfDeviceGet(
&pContext->VDevice, offsetof(struct virtio_input_config, u.bitmap[i]),
(PUCHAR)pAbsInfo + i, 1);
}
}
}
NTSTATUS
RegisterClass(
PINPUT_DEVICE pContext,
PINPUT_CLASS_COMMON pClass)
{
if (pContext->uNumOfClasses >= MAX_INPUT_CLASS_COUNT)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
// allocate HID report buffer
if (pClass->pHidReport == NULL)
{
pClass->pHidReport = VIOInputAlloc(pClass->cbHidReportSize);
if (pClass->pHidReport == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
}
// insert the class into our array
pContext->InputClasses[pContext->uNumOfClasses++] = pClass;
return STATUS_SUCCESS;
}
NTSTATUS
RequestCopyFromBuffer(
WDFREQUEST Request,
PVOID SourceBuffer,
size_t NumBytesToCopyFrom)
{
NTSTATUS status;
WDFMEMORY memory;
size_t outputBufferLength;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveOutputMemory(Request, &memory);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_READ, "<-- %s\n", __FUNCTION__);
return status;
}
WdfMemoryGetBuffer(memory, &outputBufferLength);
if (outputBufferLength < NumBytesToCopyFrom)
{
status = STATUS_INVALID_BUFFER_SIZE;
TraceEvents(TRACE_LEVEL_ERROR, DBG_READ,
"RequestCopyFromBuffer: buffer too small. Size %d, expect %d\n",
(int)outputBufferLength, (int)NumBytesToCopyFrom);
return status;
}
status = WdfMemoryCopyFromBuffer(memory,
0,
SourceBuffer,
NumBytesToCopyFrom);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_READ,
"WdfMemoryCopyFromBuffer failed 0x%x\n", status);
return status;
}
WdfRequestSetInformation(Request, NumBytesToCopyFrom);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- %s\n", __FUNCTION__);
return status;
}