mirror of https://gitlab.com/nakst/essence
784 lines
22 KiB
C++
784 lines
22 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#include <module.h>
|
|
|
|
// #define TRACE_REPORTS
|
|
|
|
// TODO Key repeat not working on Qemu.
|
|
|
|
struct ReportItem {
|
|
uint32_t usage, application, arrayCount;
|
|
int32_t logicalMinimum, logicalMaximum;
|
|
|
|
uint8_t reportPrefix;
|
|
uint8_t bits;
|
|
uint8_t group;
|
|
|
|
#define REPORT_ITEM_CONSTANT (1 << 0)
|
|
#define REPORT_ITEM_RELATIVE (1 << 1)
|
|
#define REPORT_ITEM_WRAP (1 << 2)
|
|
#define REPORT_ITEM_NON_LINEAR (1 << 3)
|
|
#define REPORT_ITEM_SIGNED (1 << 4)
|
|
#define REPORT_ITEM_ARRAY (1 << 5)
|
|
uint8_t flags;
|
|
|
|
#define REPORT_ITEM_INPUT (1)
|
|
#define REPORT_ITEM_OUTPUT (2)
|
|
#define REPORT_ITEM_FEATURE (3)
|
|
uint8_t type;
|
|
};
|
|
|
|
struct BitBuffer {
|
|
const uint8_t *buffer;
|
|
size_t bytes;
|
|
uintptr_t index;
|
|
|
|
void Discard(size_t count);
|
|
uint32_t ReadUnsigned(size_t count);
|
|
int32_t ReadSigned(size_t count);
|
|
};
|
|
|
|
struct GameController {
|
|
uint64_t id;
|
|
uint8_t reportPrefix;
|
|
};
|
|
|
|
struct HIDDevice : KHIDevice {
|
|
KUSBDevice *device;
|
|
|
|
Array<ReportItem, K_FIXED> reportItems;
|
|
bool usesReportPrefixes;
|
|
|
|
Array<GameController, K_FIXED> gameControllers;
|
|
|
|
KUSBEndpointDescriptor *reportEndpoint;
|
|
uint8_t *lastReport;
|
|
size_t lastReportBytes;
|
|
|
|
void Initialise();
|
|
bool ParseReportDescriptor(const uint8_t *report, size_t reportBytes);
|
|
void ReportReceived(BitBuffer *buffer);
|
|
};
|
|
|
|
struct HIDDescriptorLink {
|
|
uint8_t type;
|
|
uint8_t length[2];
|
|
};
|
|
|
|
struct HIDDescriptor : KUSBDescriptorHeader {
|
|
uint8_t specification[2];
|
|
uint8_t countryCode;
|
|
uint8_t linkCount;
|
|
HIDDescriptorLink links[1];
|
|
};
|
|
|
|
struct ReportGlobalState {
|
|
int32_t logicalMinimum, logicalMaximum;
|
|
uint16_t usagePage;
|
|
uint8_t reportSize, reportCount;
|
|
uint8_t reportID;
|
|
};
|
|
|
|
struct ReportLocalState {
|
|
#define USAGE_ARRAY_SIZE (32)
|
|
uint32_t usages[USAGE_ARRAY_SIZE];
|
|
uint32_t usageMinimum, usageMaximum;
|
|
uint8_t usageCount;
|
|
|
|
#define DELIMITER_NONE (0)
|
|
#define DELIMITER_FIRST (1)
|
|
#define DELIMITER_IGNORE (2)
|
|
uint8_t delimiterState;
|
|
};
|
|
|
|
struct UsageString {
|
|
uint32_t usage;
|
|
const char *string;
|
|
};
|
|
|
|
#define HID_APPLICATION_MOUSE (0x010002)
|
|
#define HID_APPLICATION_JOYSTICK (0x010004)
|
|
#define HID_APPLICATION_KEYBOARD (0x010006)
|
|
#define HID_USAGE_X_AXIS (0x010030)
|
|
#define HID_USAGE_Y_AXIS (0x010031)
|
|
#define HID_USAGE_Z_AXIS (0x010032)
|
|
#define HID_USAGE_X_ROTATION (0x010033)
|
|
#define HID_USAGE_Y_ROTATION (0x010034)
|
|
#define HID_USAGE_Z_ROTATION (0x010035)
|
|
#define HID_USAGE_WHEEL (0x010038)
|
|
#define HID_USAGE_HAT_SWITCH (0x010039)
|
|
#define HID_USAGE_KEYCODES (0x070000)
|
|
#define HID_USAGE_BUTTON_1 (0x090001)
|
|
#define HID_USAGE_BUTTON_2 (0x090002)
|
|
#define HID_USAGE_BUTTON_3 (0x090003)
|
|
#define HID_USAGE_BUTTON_16 (0x090010)
|
|
|
|
UsageString usageStrings[] = {
|
|
{ 0x000000, "padding" },
|
|
|
|
// Generic desktop page.
|
|
{ 0x010001, "pointer" },
|
|
{ 0x010002, "mouse" },
|
|
{ 0x010004, "joystick" },
|
|
{ 0x010005, "gamepad" },
|
|
{ 0x010006, "keyboard" },
|
|
{ 0x010007, "keypad" },
|
|
{ 0x010008, "multi-axis controller" },
|
|
{ 0x010009, "tablet PC system controls" },
|
|
{ 0x010030, "X axis" },
|
|
{ 0x010031, "Y axis" },
|
|
{ 0x010032, "Z axis" },
|
|
{ 0x010033, "X rotation" },
|
|
{ 0x010034, "Y rotation" },
|
|
{ 0x010035, "Z rotation" },
|
|
{ 0x010036, "slider" },
|
|
{ 0x010037, "dial" },
|
|
{ 0x010038, "wheel" },
|
|
{ 0x010039, "hat switch" },
|
|
|
|
// Keyboard/keypad page.
|
|
{ 0x070000, "keycodes" },
|
|
{ 0x0700E0, "left ctrl" },
|
|
{ 0x0700E1, "left shift" },
|
|
{ 0x0700E2, "left alt" },
|
|
{ 0x0700E3, "left gui" },
|
|
{ 0x0700E4, "right ctrl" },
|
|
{ 0x0700E5, "right shift" },
|
|
{ 0x0700E6, "right alt" },
|
|
{ 0x0700E7, "right gui" },
|
|
|
|
// LED page.
|
|
{ 0x080001, "num lock" },
|
|
{ 0x080002, "caps lock" },
|
|
{ 0x080003, "scroll lock" },
|
|
{ 0x080004, "compose" },
|
|
{ 0x080005, "kana" },
|
|
|
|
// Button page.
|
|
{ 0x090001, "button 1" },
|
|
{ 0x090002, "button 2" },
|
|
{ 0x090003, "button 3" },
|
|
{ 0x090004, "button 4" },
|
|
{ 0x090005, "button 5" },
|
|
{ 0x090006, "button 6" },
|
|
{ 0x090007, "button 7" },
|
|
{ 0x090008, "button 8" },
|
|
{ 0x090009, "button 9" },
|
|
{ 0x09000A, "button 10" },
|
|
{ 0x09000B, "button 11" },
|
|
{ 0x09000C, "button 12" },
|
|
{ 0x09000D, "button 13" },
|
|
{ 0x09000E, "button 14" },
|
|
{ 0x09000F, "button 15" },
|
|
{ 0x090010, "button 16" },
|
|
};
|
|
|
|
const char *LookupUsageString(uint32_t usage) {
|
|
if (usage > 0xFF000000) {
|
|
return "vendor-specific";
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < sizeof(usageStrings) / sizeof(usageStrings[0]); i++) {
|
|
if (usageStrings[i].usage == usage) {
|
|
return usageStrings[i].string;
|
|
}
|
|
}
|
|
|
|
EsPrint("unknown usage %x\n", usage);
|
|
return "unknown";
|
|
}
|
|
|
|
void BitBuffer::Discard(size_t count) {
|
|
index += count;
|
|
}
|
|
|
|
uint32_t BitBuffer::ReadUnsigned(size_t count) {
|
|
uint32_t result = 0;
|
|
uint32_t bit = 0;
|
|
|
|
while (bit != count) {
|
|
uintptr_t byte = index >> 3;
|
|
|
|
if (byte >= bytes) {
|
|
break;
|
|
}
|
|
|
|
if (buffer[byte] & (1 << (index & 7))) {
|
|
result |= 1 << bit;
|
|
}
|
|
|
|
bit++, index++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int32_t BitBuffer::ReadSigned(size_t count) {
|
|
if (!count) return 0;
|
|
|
|
uint32_t result = ReadUnsigned(count);
|
|
|
|
if (result & (1 << (count - 1))) {
|
|
for (uintptr_t i = count; i < 32; i++) {
|
|
result |= 1 << i;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool HIDDevice::ParseReportDescriptor(const uint8_t *report, size_t reportBytes) {
|
|
#define REPORT_GLOBAL_STACK_SIZE (8)
|
|
ReportGlobalState global[REPORT_GLOBAL_STACK_SIZE] = {};
|
|
uintptr_t gIndex = 0;
|
|
ReportLocalState local = {};
|
|
uint32_t application = 0;
|
|
uint8_t group = 0;
|
|
|
|
uintptr_t position = 0;
|
|
|
|
while (position < reportBytes) {
|
|
uint8_t header = report[position];
|
|
|
|
if (header == 0xFE) {
|
|
// Long items, unused.
|
|
if (position + 3 > reportBytes) return false;
|
|
position += 3 + report[position + 1];
|
|
continue;
|
|
}
|
|
|
|
uint8_t size = header & 3;
|
|
uint8_t type = header & ~3;
|
|
position++;
|
|
|
|
if (size == 3) size++;
|
|
if (position + size > reportBytes) return false;
|
|
|
|
uint32_t uData = 0;
|
|
int32_t sData = 0;
|
|
|
|
for (uintptr_t i = 0; i < size; i++) {
|
|
uData |= report[position + i] << (i * 8);
|
|
}
|
|
|
|
sData = uData;
|
|
|
|
if (size && (report[position + size - 1] & 0x80)) {
|
|
for (uintptr_t i = size; i < 4; i++) {
|
|
sData |= 0xFF << (i * 8);
|
|
}
|
|
}
|
|
|
|
position += size;
|
|
|
|
switch (type) {
|
|
case 0b00000100: { global[gIndex].usagePage = uData; } break;
|
|
case 0b00010100: { global[gIndex].logicalMinimum = sData; } break;
|
|
case 0b00100100: { global[gIndex].logicalMaximum = sData; } break;
|
|
case 0b01110100: { global[gIndex].reportSize = uData; } break;
|
|
case 0b10010100: { global[gIndex].reportCount = uData; } break;
|
|
|
|
case 0b10000100: {
|
|
global[gIndex].reportID = uData;
|
|
if (uData) usesReportPrefixes = true;
|
|
} break;
|
|
|
|
case 0b10100100: {
|
|
if (gIndex + 1 == REPORT_GLOBAL_STACK_SIZE) return false;
|
|
gIndex++;
|
|
global[gIndex] = global[gIndex - 1];
|
|
} break;
|
|
|
|
case 0b10110100: {
|
|
if (gIndex == 0) return false;
|
|
gIndex--;
|
|
} break;
|
|
|
|
case 0b00001000: {
|
|
if (local.usageCount == USAGE_ARRAY_SIZE) return false;
|
|
|
|
if (local.delimiterState != DELIMITER_IGNORE) {
|
|
local.usages[local.usageCount++] = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0);
|
|
}
|
|
|
|
if (local.delimiterState == DELIMITER_FIRST) local.delimiterState = DELIMITER_IGNORE;
|
|
} break;
|
|
|
|
case 0b00011000: { local.usageMinimum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break;
|
|
case 0b00101000: { local.usageMaximum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break;
|
|
|
|
case 0b10101000: {
|
|
if (uData) local.delimiterState = DELIMITER_FIRST;
|
|
else local.delimiterState = DELIMITER_NONE;
|
|
} break;
|
|
|
|
case 0b10100000: {
|
|
if (uData == 1) {
|
|
if (local.usageCount == 0) return false;
|
|
application = local.usages[0];
|
|
group++;
|
|
}
|
|
} break;
|
|
|
|
case 0b10010000:
|
|
case 0b10110000:
|
|
case 0b10000000: {
|
|
for (uintptr_t i = 0; i < global[gIndex].reportCount; i++) {
|
|
ReportItem item = {
|
|
.application = application,
|
|
.logicalMinimum = global[gIndex].logicalMinimum,
|
|
.logicalMaximum = global[gIndex].logicalMaximum,
|
|
.reportPrefix = global[gIndex].reportID,
|
|
.bits = global[gIndex].reportSize,
|
|
};
|
|
|
|
if (type == 0b10000000) {
|
|
item.type = REPORT_ITEM_INPUT;
|
|
} else if (type == 0b10010000) {
|
|
item.type = REPORT_ITEM_OUTPUT;
|
|
} else if (type == 0b10110000) {
|
|
item.type = REPORT_ITEM_FEATURE;
|
|
}
|
|
|
|
if ( uData & (1 << 0)) item.flags |= REPORT_ITEM_CONSTANT;
|
|
if (~uData & (1 << 1)) item.flags |= REPORT_ITEM_ARRAY;
|
|
if ( uData & (1 << 2)) item.flags |= REPORT_ITEM_RELATIVE;
|
|
if ( uData & (1 << 3)) item.flags |= REPORT_ITEM_WRAP;
|
|
if ( uData & (1 << 4)) item.flags |= REPORT_ITEM_NON_LINEAR;
|
|
if (item.logicalMinimum < 0 || item.logicalMaximum < 0) item.flags |= REPORT_ITEM_SIGNED;
|
|
|
|
if (local.usageCount) {
|
|
item.usage = local.usages[i >= local.usageCount ? local.usageCount - 1 : i];
|
|
} else {
|
|
item.usage = (i > local.usageMaximum - local.usageMinimum) ? local.usageMaximum : (local.usageMinimum + i);
|
|
}
|
|
|
|
if (item.flags & REPORT_ITEM_ARRAY) {
|
|
item.arrayCount = global[gIndex].reportCount;
|
|
}
|
|
|
|
if (!reportItems.Add(item)) {
|
|
return false;
|
|
}
|
|
|
|
KernelLog(LOG_INFO, "USBHID", "parsed report item",
|
|
"Parsed report item - group: %d, application: '%z', usage: '%z', range: %i->%i, report: %d, bits: %d, "
|
|
"flags: %z%z%z%z%z%z, type: %z, array count: %d\n",
|
|
group, LookupUsageString(item.application), LookupUsageString(item.usage),
|
|
item.logicalMinimum, item.logicalMaximum,
|
|
item.reportPrefix, item.bits,
|
|
(item.flags & REPORT_ITEM_CONSTANT) ? "constant|" : "",
|
|
(item.flags & REPORT_ITEM_RELATIVE) ? "relative|" : "",
|
|
(item.flags & REPORT_ITEM_WRAP) ? "wrap|" : "",
|
|
(item.flags & REPORT_ITEM_NON_LINEAR) ? "non-linear|" : "",
|
|
(item.flags & REPORT_ITEM_ARRAY) ? "array|" : "",
|
|
(item.flags & REPORT_ITEM_SIGNED) ? "signed" : "unsigned",
|
|
item.type == REPORT_ITEM_INPUT ? "input" : item.type == REPORT_ITEM_OUTPUT ? "output" : "feature", item.arrayCount);
|
|
|
|
if (item.application == HID_APPLICATION_KEYBOARD) {
|
|
cDebugName = "USB HID keyboard";
|
|
} else if (item.application == HID_APPLICATION_MOUSE) {
|
|
cDebugName = "USB HID mouse";
|
|
} else if (item.application == HID_APPLICATION_JOYSTICK) {
|
|
cDebugName = "USB HID joystick";
|
|
}
|
|
|
|
if (item.flags & REPORT_ITEM_ARRAY) {
|
|
break;
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
|
|
if ((type & 0b00001100) == 0) {
|
|
EsMemoryZero(&local, sizeof(ReportLocalState));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ReportReceivedCallback(ptrdiff_t bytesNotTransferred, EsGeneric context) {
|
|
HIDDevice *device = (HIDDevice *) context.p;
|
|
size_t bytesTransferred = device->reportEndpoint->GetMaximumPacketSize() - bytesNotTransferred;
|
|
|
|
if (bytesNotTransferred == -1) {
|
|
KernelLog(LOG_ERROR, "USBHID", "report transfer failure", "Report transfer failed.\n");
|
|
bytesTransferred = 0;
|
|
}
|
|
|
|
BitBuffer buffer = { device->lastReport, bytesTransferred };
|
|
device->ReportReceived(&buffer);
|
|
}
|
|
|
|
void HIDDevice::ReportReceived(BitBuffer *buffer) {
|
|
uint8_t prefix = 0;
|
|
|
|
if (usesReportPrefixes) {
|
|
prefix = buffer->ReadUnsigned(8);
|
|
}
|
|
|
|
#ifdef TRACE_REPORTS
|
|
EsPrint("-- report (%d) --\n", prefix);
|
|
#endif
|
|
|
|
bool mouseEvent = false;
|
|
KMouseUpdateData mouse = {};
|
|
bool keyboardEvent = false;
|
|
uint16_t keysDown[32];
|
|
size_t keysDownCount = 0;
|
|
bool gameControllerEvent = false;
|
|
EsGameControllerState controllerState = {};
|
|
controllerState.directionalPad = 15;
|
|
controllerState.analogCount = 2;
|
|
|
|
for (uintptr_t i = 0; i < gameControllers.Length(); i++) {
|
|
if (gameControllers[i].reportPrefix == prefix) {
|
|
controllerState.id = gameControllers[i].id;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < reportItems.Length(); i++) {
|
|
ReportItem *item = &reportItems[i];
|
|
|
|
if (item->type != REPORT_ITEM_INPUT || item->reportPrefix != prefix) {
|
|
continue;
|
|
}
|
|
|
|
#ifdef TRACE_REPORTS
|
|
uintptr_t startIndex = buffer->index;
|
|
|
|
EsPrint("%d/%z: ", item->group, LookupUsageString(item->usage));
|
|
|
|
size_t count = (item->flags & REPORT_ITEM_ARRAY) ? item->arrayCount : 1;
|
|
|
|
for (uintptr_t i = 0; i < count; i++) {
|
|
if (item->flags & REPORT_ITEM_SIGNED) {
|
|
EsPrint("%i", buffer->ReadSigned(item->bits));
|
|
} else {
|
|
EsPrint("%d", buffer->ReadUnsigned(item->bits));
|
|
}
|
|
|
|
if (i != count - 1) {
|
|
EsPrint(", ");
|
|
}
|
|
}
|
|
|
|
EsPrint("\n");
|
|
|
|
buffer->index = startIndex;
|
|
#endif
|
|
|
|
if (item->flags & REPORT_ITEM_ARRAY) {
|
|
bool handled = false;
|
|
|
|
if (item->application == HID_APPLICATION_KEYBOARD) {
|
|
if (item->usage == HID_USAGE_KEYCODES) {
|
|
for (uintptr_t i = 0; i < item->arrayCount; i++) {
|
|
uint32_t scancode = buffer->ReadUnsigned(item->bits);
|
|
keyboardEvent = true;
|
|
|
|
if (scancode > 0 && scancode < 0x200 && keysDownCount != 32) {
|
|
keysDown[keysDownCount++] = scancode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
buffer->Discard(item->bits * item->arrayCount);
|
|
}
|
|
} else {
|
|
bool handled = false;
|
|
|
|
if (item->application == HID_APPLICATION_MOUSE) {
|
|
// TODO Handle absolute, and wrapping movements.
|
|
|
|
mouseEvent = true;
|
|
handled = true;
|
|
|
|
if (item->usage == HID_USAGE_X_AXIS) {
|
|
if (item->flags & REPORT_ITEM_SIGNED) {
|
|
mouse.xMovement = buffer->ReadSigned(item->bits) * K_CURSOR_MOVEMENT_SCALE;
|
|
} else {
|
|
mouse.xMovement = buffer->ReadUnsigned(item->bits) * K_CURSOR_MOVEMENT_SCALE;
|
|
}
|
|
|
|
mouse.xIsAbsolute = !(item->flags & REPORT_ITEM_RELATIVE);
|
|
mouse.xFrom = item->logicalMinimum;
|
|
mouse.xTo = item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_Y_AXIS) {
|
|
if (item->flags & REPORT_ITEM_SIGNED) {
|
|
mouse.yMovement = buffer->ReadSigned(item->bits) * K_CURSOR_MOVEMENT_SCALE;
|
|
} else {
|
|
mouse.yMovement = buffer->ReadUnsigned(item->bits) * K_CURSOR_MOVEMENT_SCALE;
|
|
}
|
|
|
|
mouse.yIsAbsolute = !(item->flags & REPORT_ITEM_RELATIVE);
|
|
mouse.yFrom = item->logicalMinimum;
|
|
mouse.yTo = item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_BUTTON_1) {
|
|
if (buffer->ReadUnsigned(item->bits)) mouse.buttons |= 1 << 0;
|
|
} else if (item->usage == HID_USAGE_BUTTON_2) {
|
|
if (buffer->ReadUnsigned(item->bits)) mouse.buttons |= 1 << 2;
|
|
} else if (item->usage == HID_USAGE_BUTTON_3) {
|
|
if (buffer->ReadUnsigned(item->bits)) mouse.buttons |= 1 << 1;
|
|
} else if (item->usage == HID_USAGE_WHEEL) {
|
|
mouse.yScroll = buffer->ReadSigned(item->bits) * K_CURSOR_MOVEMENT_SCALE;
|
|
} else {
|
|
handled = false;
|
|
}
|
|
} else if (item->application == HID_APPLICATION_KEYBOARD) {
|
|
handled = true;
|
|
|
|
if (item->usage > HID_USAGE_KEYCODES && item->usage < HID_USAGE_KEYCODES + 0x200) {
|
|
handled = true;
|
|
keyboardEvent = true;
|
|
|
|
if (buffer->ReadUnsigned(item->bits) && keysDownCount != 32) {
|
|
keysDown[keysDownCount++] = item->usage - HID_USAGE_KEYCODES;
|
|
}
|
|
}
|
|
} else if (item->application == HID_APPLICATION_JOYSTICK) {
|
|
handled = true;
|
|
|
|
if (item->usage >= HID_USAGE_BUTTON_1 && item->usage <= HID_USAGE_BUTTON_16) {
|
|
gameControllerEvent = true;
|
|
|
|
if (buffer->ReadUnsigned(item->bits)) {
|
|
controllerState.buttons |= 1 << controllerState.buttonCount;
|
|
}
|
|
|
|
controllerState.buttonCount++;
|
|
} else if (item->usage == HID_USAGE_X_AXIS) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[0].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_Y_AXIS) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[0].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_Z_AXIS) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[0].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_X_ROTATION) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[1].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_Y_ROTATION) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[1].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_Z_ROTATION) {
|
|
gameControllerEvent = true;
|
|
controllerState.analog[1].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
|
|
} else if (item->usage == HID_USAGE_HAT_SWITCH) {
|
|
gameControllerEvent = true;
|
|
controllerState.directionalPad = buffer->ReadUnsigned(item->bits);
|
|
} else {
|
|
handled = false;
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
buffer->Discard(item->bits);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mouseEvent) {
|
|
KMouseUpdate(&mouse);
|
|
}
|
|
|
|
if (keyboardEvent) {
|
|
KKeyboardUpdate(keysDown, keysDownCount);
|
|
}
|
|
|
|
if (gameControllerEvent) {
|
|
KGameControllerUpdate(&controllerState);
|
|
}
|
|
|
|
if (device->flags & K_DEVICE_REMOVED) {
|
|
KDeviceCloseHandle(this);
|
|
} else {
|
|
if (!device->queueTransfer(device, reportEndpoint, ReportReceivedCallback,
|
|
lastReport, reportEndpoint->GetMaximumPacketSize(), this)) {
|
|
KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive the next report packet.\n");
|
|
KDeviceCloseHandle(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HIDDevice::Initialise() {
|
|
// Find the HID descriptor.
|
|
|
|
HIDDescriptor *hidDescriptor = (HIDDescriptor *) device->GetCommonDescriptor(0x21, 0);
|
|
|
|
if (!hidDescriptor) {
|
|
KernelLog(LOG_ERROR, "USBHID", "missing descriptor", "Could not find the HID descriptor.\n");
|
|
return;
|
|
} else if (hidDescriptor->length < sizeof(HIDDescriptor) || hidDescriptor->linkCount == 0
|
|
|| (hidDescriptor->linkCount - 1) * sizeof(HIDDescriptorLink) + sizeof(HIDDescriptor) > hidDescriptor->length) {
|
|
KernelLog(LOG_ERROR, "USBHID", "bad descriptor length", "HID descriptor too short (%D) for %d links.\n",
|
|
hidDescriptor->length, hidDescriptor->linkCount);
|
|
return;
|
|
}
|
|
|
|
// Get the size of the report descriptor.
|
|
|
|
size_t reportBytes = 0;
|
|
|
|
for (uintptr_t i = 0; i < hidDescriptor->linkCount; i++) {
|
|
if (hidDescriptor->links[i].type == 0x22) {
|
|
reportBytes = (size_t) hidDescriptor->links[i].length[0] | ((size_t) hidDescriptor->links[i].length[1] << 8);
|
|
}
|
|
}
|
|
|
|
if (!reportBytes) {
|
|
KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not find report descriptor link in HID descriptor.\n");
|
|
return;
|
|
}
|
|
|
|
// Switch to the report protocol.
|
|
|
|
if (!device->controlTransfer(device, 0x21, 0x0B /* set protocol */, 1 /* report protocol */,
|
|
device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) {
|
|
KernelLog(LOG_ERROR, "USBHID", "set protocol failure", "Could not switch to the report protocol.\n");
|
|
}
|
|
|
|
// Get the report descriptor and parse it.
|
|
|
|
uint8_t *report = (uint8_t *) EsHeapAllocate(reportBytes, false, K_FIXED);
|
|
|
|
if (!report) {
|
|
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store the report descriptor.\n");
|
|
return;
|
|
}
|
|
|
|
EsDefer(EsHeapFree(report, reportBytes, K_FIXED));
|
|
|
|
uint16_t transferred = 0;
|
|
|
|
if (!device->controlTransfer(device, 0x81, 0x06, 0x22 << 8, 0, report, reportBytes, K_ACCESS_READ, &transferred)
|
|
|| transferred != reportBytes) {
|
|
KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not read the report descriptor from the device.\n");
|
|
return;
|
|
}
|
|
|
|
if (!ParseReportDescriptor(report, reportBytes)) {
|
|
KernelLog(LOG_ERROR, "USBHID", "invalid report descriptor", "Could not parse the report descriptor.\n");
|
|
return;
|
|
}
|
|
|
|
// Set idle.
|
|
|
|
if (!device->controlTransfer(device, 0x21, 0x0A /* set idle */, 0 /* infinite duration, apply to all report IDs */,
|
|
device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) {
|
|
KernelLog(LOG_ERROR, "USBHID", "enable idle failure", "Could not enable idle mode on the device.\n");
|
|
return;
|
|
}
|
|
|
|
// Get the interrupt-in endpoint descriptor.
|
|
|
|
KUSBEndpointDescriptor *endpoint = nullptr;
|
|
|
|
{
|
|
uintptr_t index = 0;
|
|
|
|
while (true) {
|
|
KUSBEndpointDescriptor *e = (KUSBEndpointDescriptor *) device->GetCommonDescriptor(0x05 /* endpoint */, index++);
|
|
|
|
if (!e) {
|
|
break;
|
|
} else if (e->IsInterrupt() && e->IsInput()) {
|
|
endpoint = e;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
lastReport = (uint8_t *) EsHeapAllocate(endpoint->GetMaximumPacketSize(), true, K_FIXED);
|
|
|
|
if (!lastReport) {
|
|
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store received reports.\n");
|
|
return;
|
|
}
|
|
|
|
// Start receiving interrupt packets.
|
|
|
|
reportEndpoint = endpoint;
|
|
KDeviceOpenHandle(this);
|
|
|
|
if (!device->queueTransfer(device, endpoint, ReportReceivedCallback, lastReport, endpoint->GetMaximumPacketSize(), this)) {
|
|
KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive report packets.\n");
|
|
KDeviceCloseHandle(this);
|
|
return;
|
|
}
|
|
|
|
// Work out if this is a keyboard or mouse.
|
|
|
|
for (uintptr_t i = 0; i < reportItems.Length(); i++) {
|
|
ReportItem *item = &reportItems[i];
|
|
|
|
if (item->application == HID_APPLICATION_KEYBOARD && item->usage == HID_USAGE_KEYCODES) {
|
|
KDeviceSendConnectedMessage(this, ES_DEVICE_KEYBOARD);
|
|
break;
|
|
} else if (item->application == HID_APPLICATION_MOUSE) {
|
|
KDeviceSendConnectedMessage(this, ES_DEVICE_MOUSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If this is a game controller, tell the window manager it's been connected.
|
|
|
|
{
|
|
uint64_t seen = 0;
|
|
|
|
for (uintptr_t i = 0; i < reportItems.Length(); i++) {
|
|
ReportItem *item = &reportItems[i];
|
|
|
|
if (item->application == HID_APPLICATION_JOYSTICK
|
|
&& item->reportPrefix < 64
|
|
&& (~seen & (1 << item->reportPrefix))) {
|
|
seen |= (1 << item->reportPrefix);
|
|
|
|
GameController controller = {};
|
|
controller.id = KGameControllerConnect();
|
|
|
|
if (controller.id) {
|
|
controller.reportPrefix = item->reportPrefix;
|
|
gameControllers.Add(controller);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DeviceDestroy(KDevice *_device) {
|
|
HIDDevice *device = (HIDDevice *) _device;
|
|
|
|
for (uintptr_t i = 0; i < device->gameControllers.Length(); i++) {
|
|
KGameControllerDisconnect(device->gameControllers[i].id);
|
|
}
|
|
|
|
device->reportItems.Free();
|
|
device->gameControllers.Free();
|
|
EsHeapFree(device->lastReport, 0, K_FIXED);
|
|
}
|
|
|
|
static void DeviceAttach(KDevice *parent) {
|
|
HIDDevice *device = (HIDDevice *) KDeviceCreate("USB HID", parent, sizeof(HIDDevice));
|
|
|
|
if (!device) {
|
|
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate HIDDevice structure.\n");
|
|
return;
|
|
}
|
|
|
|
device->destroy = DeviceDestroy;
|
|
device->device = (KUSBDevice *) parent;
|
|
device->Initialise();
|
|
KRegisterHIDevice(device);
|
|
}
|
|
|
|
KDriver driverUSBHID = {
|
|
.attach = DeviceAttach,
|
|
};
|