mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-27 20:58:22 +00:00
1061 lines
31 KiB
C
1061 lines
31 KiB
C
#include <kernel/usb.h>
|
|
#include <kernel/usb_hid.h>
|
|
#include <logging.h>
|
|
#include <kernel/alloc.h>
|
|
|
|
#define HID_ITEM_LONG_PREFIX 0xfe
|
|
|
|
#define HID_ITEM_TYPE_MAIN 0x00
|
|
#define HID_ITEM_TYPE_GLOBAL 0x01
|
|
#define HID_ITEM_TYPE_LOCAL 0x02
|
|
#define HID_ITEM_TYPE_RESERVED 0x03
|
|
|
|
#define HID_MAIN_ITEM_INPUT 0x08
|
|
#define HID_MAIN_ITEM_OUTPUT 0x09
|
|
#define HID_MAIN_ITEM_COLLECTION 0x0a
|
|
#define HID_MAIN_ITEM_FEATURE 0x0b
|
|
#define HID_MAIN_ITEM_END_COLLECTION 0x0c
|
|
|
|
#define HID_GLOBAL_ITEM_USAGE_PAGE 0x00
|
|
#define HID_GLOBAL_ITEM_LOGICAL_MIN 0x01
|
|
#define HID_GLOBAL_ITEM_LOGICAL_MAX 0x02
|
|
#define HID_GLOBAL_ITEM_PHYSICAL_MIN 0x03
|
|
#define HID_GLOBAL_ITEM_PHYSICAL_MAX 0x04
|
|
#define HID_GLOBAL_ITEM_UNIT_EXPONENT 0x05
|
|
#define HID_GLOBAL_ITEM_UNIT 0x06
|
|
#define HID_GLOBAL_ITEM_REPORT_SIZE 0x07
|
|
#define HID_GLOBAL_ITEM_REPORT_ID 0x08
|
|
#define HID_GLOBAL_ITEM_REPORT_COUNT 0x09
|
|
#define HID_GLOBAL_ITEM_PUSH 0x0a
|
|
#define HID_GLOBAL_ITEM_POP 0x0b
|
|
|
|
#define HID_LOCAL_ITEM_USAGE 0x00
|
|
#define HID_LOCAL_ITEM_USAGE_MIN 0x01
|
|
#define HID_LOCAL_ITEM_USAGE_MAX 0x02
|
|
#define HID_LOCAL_ITEM_DESIGNATOR_INDEX 0x03
|
|
#define HID_LOCAL_ITEM_DESIGNATOR_MIN 0x04
|
|
#define HID_LOCAL_ITEM_DESIGNATOR_MAX 0x05
|
|
#define HID_LOCAL_ITEM_STRING_INDEX 0x07
|
|
#define HID_LOCAL_ITEM_STRING_MIN 0x08
|
|
#define HID_LOCAL_ITEM_STRING_MAX 0x09
|
|
#define HID_LOCAL_ITEM_DELIMITER 0x0a
|
|
|
|
#define HID_COLLECTION_PHYSICAL 0x00
|
|
#define HID_COLLECTION_APPLICATION 0x01
|
|
#define HID_COLLECTION_LOGICAL 0x02
|
|
#define HID_COLLECTION_REPORT 0x03
|
|
#define HID_COLLECTION_NAMED_ARRAY 0x04
|
|
#define HID_COLLECTION_USAGE_SWITCH 0x05
|
|
#define HID_COLLECTION_USAGE_MODIFIER 0x06
|
|
|
|
#define HID_GD_X 0x30
|
|
#define HID_GD_Y 0x31
|
|
#define HID_GD_Z 0x32
|
|
#define HID_GD_RX 0x33
|
|
#define HID_GD_RY 0x34
|
|
#define HID_GD_RZ 0x35
|
|
#define HID_GD_SLIDER 0x36
|
|
#define HID_GD_DIAL 0x37
|
|
#define HID_GD_WHEEL 0x38
|
|
#define HID_GD_HAT_SWITCH 0x39
|
|
|
|
#define HID_BUTTON_PRIMARY 0x01
|
|
#define HID_BUTTON_SECONDARY 0x02
|
|
#define HID_BUTTON_TERTIARY 0x03
|
|
#define HID_BUTTON_SIDE 0x04
|
|
#define HID_BUTTON_EXTRA 0x05
|
|
#define HID_BUTTON_FORWARD 0x06
|
|
#define HID_BUTTON_MAX HID_BUTTON_FORWARD
|
|
|
|
#define HID_KEYBOARD_KEYPAD_MAX 0xe7
|
|
|
|
static inline void decode_item(uint8_t item, uint8_t *tag, uint8_t *type, uint8_t *size) {
|
|
*size = item & 0x3;
|
|
*type = (item >> 2) & 0x3;
|
|
*tag = (item >> 4) & 0xf;
|
|
}
|
|
|
|
static inline uint32_t get_data_u(uint8_t *data, size_t size) {
|
|
switch (size) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return data[0];
|
|
case 2:
|
|
return (uint32_t)data[0] |
|
|
((uint32_t)data[1] << 8);
|
|
case 4:
|
|
return (uint32_t)data[0] |
|
|
((uint32_t)data[1] << 8) |
|
|
((uint32_t)data[2] << 16) |
|
|
((uint32_t)data[3] << 24);
|
|
default:
|
|
__assert(!"Corruption in HID driver");
|
|
}
|
|
}
|
|
|
|
static inline int32_t get_data_i(uint8_t *data, size_t size) {
|
|
switch (size) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return (int32_t)(int8_t)data[0];
|
|
case 2:
|
|
return (int32_t)(int16_t)((uint16_t)data[0] |
|
|
((uint16_t)data[1] << 8));
|
|
case 4:
|
|
return (int32_t)((uint32_t)data[0] |
|
|
((uint32_t)data[1] << 8) |
|
|
((uint32_t)data[2] << 16) |
|
|
((uint32_t)data[3] << 24));
|
|
default:
|
|
__assert(!"Corruption in HID driver");
|
|
}
|
|
}
|
|
|
|
static inline size_t translate_size(uint8_t size) {
|
|
switch (size) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
return size;
|
|
case 3:
|
|
return 4;
|
|
default:
|
|
__assert(!"Corruption in HID driver");
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
uint32_t usage_page;
|
|
int32_t logical_minimum;
|
|
int32_t logical_maximum;
|
|
int32_t physical_minimum;
|
|
int32_t physical_maximum;
|
|
int32_t unit_exponent;
|
|
uint32_t unit;
|
|
uint32_t report_size;
|
|
uint32_t report_id;
|
|
uint32_t report_count;
|
|
} hid_global_state_t;
|
|
|
|
static int handle_global(hid_global_state_t *global_state, uint8_t tag, uint8_t *item, size_t actual_size) {
|
|
uint32_t udata = get_data_u(item + 1, actual_size);
|
|
int32_t idata = get_data_i(item + 1, actual_size);
|
|
|
|
switch (tag) {
|
|
case HID_GLOBAL_ITEM_USAGE_PAGE:
|
|
global_state->usage_page = udata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_LOGICAL_MIN:
|
|
global_state->logical_minimum = idata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_LOGICAL_MAX:
|
|
global_state->logical_maximum = idata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_PHYSICAL_MIN:
|
|
global_state->physical_minimum = idata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_PHYSICAL_MAX:
|
|
global_state->physical_maximum = idata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_UNIT_EXPONENT:
|
|
global_state->unit_exponent = idata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_UNIT:
|
|
global_state->unit = udata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_REPORT_SIZE:
|
|
global_state->report_size = udata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_REPORT_ID:
|
|
if (udata == 0 || udata > 0xff)
|
|
return EINVAL;
|
|
|
|
global_state->report_id = udata;
|
|
break;
|
|
case HID_GLOBAL_ITEM_REPORT_COUNT:
|
|
global_state->report_count = udata;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#define HID_LOCAL_USAGE_CAPACITY 128
|
|
|
|
typedef struct {
|
|
hid_usage_t usages[HID_LOCAL_USAGE_CAPACITY];
|
|
size_t usage_count;
|
|
|
|
bool has_usage_minimum;
|
|
uint32_t usage_minimum;
|
|
|
|
bool has_designator_index;
|
|
bool has_designator_minimum;
|
|
bool has_designator_maximum;
|
|
uint32_t designator_index;
|
|
uint32_t designator_minimum;
|
|
uint32_t designator_maximum;
|
|
|
|
bool has_string_index;
|
|
bool has_string_minimum;
|
|
bool has_string_maximum;
|
|
uint32_t string_index;
|
|
uint32_t string_minimum;
|
|
uint32_t string_maximum;
|
|
} hid_local_state_t;
|
|
|
|
static inline hid_usage_t hid_make_usage(uint32_t current_page, uint32_t raw_usage, size_t actual_size) {
|
|
hid_usage_t usage = {
|
|
.usage = actual_size <= 2 ? ((current_page << 16) | raw_usage) : raw_usage,
|
|
|
|
};
|
|
|
|
return usage;
|
|
}
|
|
|
|
static inline hid_usage_t hid_make_usage_range(hid_global_state_t *global_state, uint32_t min, uint32_t max) {
|
|
hid_usage_t usage = {
|
|
.minimum = min,
|
|
.maximum = max,
|
|
.usage = global_state->usage_page << 16
|
|
};
|
|
|
|
return usage;
|
|
}
|
|
|
|
static int hid_local_add_usage(hid_local_state_t *local_state, hid_usage_t usage) {
|
|
if (local_state->usage_count >= HID_LOCAL_USAGE_CAPACITY)
|
|
return E2BIG;
|
|
|
|
local_state->usages[local_state->usage_count++] = usage;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_local(hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) {
|
|
uint32_t udata = get_data_u(item + 1, actual_size);
|
|
|
|
switch (tag) {
|
|
case HID_LOCAL_ITEM_USAGE: {
|
|
hid_usage_t usage = hid_make_usage(global_state->usage_page, udata, actual_size);
|
|
return hid_local_add_usage(local_state, usage);
|
|
}
|
|
case HID_LOCAL_ITEM_USAGE_MIN:
|
|
local_state->has_usage_minimum = true;
|
|
local_state->usage_minimum = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_USAGE_MAX:
|
|
if (local_state->has_usage_minimum == false)
|
|
return EINVAL;
|
|
|
|
hid_usage_t usage = hid_make_usage_range(global_state, local_state->usage_minimum, udata);
|
|
local_state->has_usage_minimum = false;
|
|
return hid_local_add_usage(local_state, usage);
|
|
case HID_LOCAL_ITEM_DESIGNATOR_INDEX:
|
|
local_state->has_designator_index = true;
|
|
local_state->designator_index = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_DESIGNATOR_MIN:
|
|
local_state->has_designator_minimum = true;
|
|
local_state->designator_minimum = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_DESIGNATOR_MAX:
|
|
local_state->has_designator_maximum = true;
|
|
local_state->designator_maximum = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_STRING_INDEX:
|
|
local_state->has_string_index = true;
|
|
local_state->string_index = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_STRING_MIN:
|
|
local_state->has_string_minimum = true;
|
|
local_state->string_minimum = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_STRING_MAX:
|
|
local_state->has_string_maximum = true;
|
|
local_state->string_maximum = udata;
|
|
break;
|
|
case HID_LOCAL_ITEM_DELIMITER:
|
|
return ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
unsigned int current_depth;
|
|
} hid_collection_state_t;
|
|
|
|
static int handle_collection(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t type) {
|
|
// only accept root application collections
|
|
if (collection_state->current_depth == 0 && type != HID_COLLECTION_APPLICATION)
|
|
return EINVAL;
|
|
|
|
// currently ignore all non-root collections
|
|
if (collection_state->current_depth++ != 0)
|
|
return 0;
|
|
|
|
// check if we have valid usage state before proceeding
|
|
if (local_state->usage_count != 1)
|
|
return EINVAL;
|
|
|
|
// add a new application to the parser
|
|
void *p;
|
|
if (parser->applications == NULL)
|
|
p = alloc(sizeof(hid_application_t));
|
|
else
|
|
p = realloc(parser->applications, (parser->application_count + 1) * sizeof(hid_application_t));
|
|
|
|
if (p == NULL)
|
|
return ENOMEM;
|
|
|
|
parser->applications = p;
|
|
parser->applications[parser->application_count].usage = local_state->usages[0].usage;
|
|
++parser->application_count;
|
|
return 0;
|
|
}
|
|
|
|
static int handle_collection_end(hid_collection_state_t *collection_state) {
|
|
if (collection_state->current_depth == 0)
|
|
return EINVAL;
|
|
|
|
--collection_state->current_depth;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int insert_report(hid_parser_t *parser, unsigned int id, hid_report_t **report) {
|
|
void *p = parser->reports ? realloc(parser->reports, sizeof(hid_report_t) * (parser->report_count + 1)) : alloc(sizeof(hid_report_t));
|
|
if (p == NULL)
|
|
return ENOMEM;
|
|
|
|
parser->reports = p;
|
|
*report = &parser->reports[parser->report_count++];
|
|
|
|
(*report)->report_id = id;
|
|
return 0;
|
|
}
|
|
|
|
static int insert_input(hid_parser_t *parser, hid_input_t **inputp, size_t *input_count, size_t *report_bit_size, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) {
|
|
size_t field_bit_size = global_state->report_size * global_state->report_count;
|
|
if (global_state->report_count && field_bit_size / global_state->report_count != global_state->report_size)
|
|
return EOVERFLOW;
|
|
if (*report_bit_size + field_bit_size < *report_bit_size)
|
|
return EOVERFLOW;
|
|
|
|
void *p = *inputp ? realloc(*inputp, sizeof(hid_input_t) * (*input_count + 1)) : alloc(sizeof(hid_input_t));
|
|
if (p == NULL)
|
|
return ENOMEM;
|
|
|
|
*inputp = p;
|
|
hid_input_t *input = &(*inputp)[(*input_count)++];
|
|
input->application_id = parser->application_count - 1;
|
|
input->bit_offset = *report_bit_size;
|
|
if (local_state->usage_count) {
|
|
input->usages = alloc(local_state->usage_count * sizeof(hid_usage_t));
|
|
if (input->usages == NULL) {
|
|
// TODO proper cleanup here
|
|
return ENOMEM;
|
|
}
|
|
memcpy(input->usages, local_state->usages, local_state->usage_count * sizeof(hid_usage_t));
|
|
input->usage_count = local_state->usage_count;
|
|
}
|
|
input->logical_maximum = global_state->logical_maximum;
|
|
input->logical_minimum = global_state->logical_minimum;
|
|
input->bit_size = global_state->report_size;
|
|
input->count = global_state->report_count;
|
|
input->flags = flags;
|
|
*report_bit_size += field_bit_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_input(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) {
|
|
if (collection_state->current_depth == 0)
|
|
return EINVAL;
|
|
|
|
if (global_state->report_id == 0) {
|
|
// add it to the input list without being gated behind a report id
|
|
if (parser->report_count)
|
|
return EINVAL;
|
|
|
|
return insert_input(parser, &parser->inputs, &parser->input_count, &parser->input_bit_size, collection_state, global_state, local_state, flags);
|
|
}
|
|
|
|
if (parser->input_count)
|
|
return EINVAL;
|
|
|
|
hid_report_t *report = hid_get_report_from_id(parser, global_state->report_id);
|
|
if (report == NULL) {
|
|
// we are guaranteed to have a valid report_id here
|
|
int error = insert_report(parser, global_state->report_id, &report);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
return insert_input(parser, &report->inputs, &report->input_count, &report->bit_size, collection_state, global_state, local_state, flags);
|
|
}
|
|
|
|
static int handle_main(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) {
|
|
uint32_t udata = get_data_u(item + 1, actual_size);
|
|
|
|
int error = 0;
|
|
switch (tag) {
|
|
case HID_MAIN_ITEM_INPUT:
|
|
error = handle_input(parser, collection_state, global_state, local_state, udata);
|
|
break;
|
|
case HID_MAIN_ITEM_COLLECTION:
|
|
error = handle_collection(parser, collection_state, global_state, local_state, udata);
|
|
break;
|
|
case HID_MAIN_ITEM_END_COLLECTION:
|
|
error = handle_collection_end(collection_state);
|
|
break;
|
|
}
|
|
|
|
memset(local_state, 0, sizeof(hid_local_state_t));
|
|
return error;
|
|
}
|
|
|
|
int hid_parser_init(hid_parser_t *parser, void *data, size_t data_size) {
|
|
uint8_t *item = data;
|
|
size_t current_offset = 0;
|
|
uint8_t tag, type, size;
|
|
|
|
hid_global_state_t globals = {0};
|
|
hid_local_state_t locals = {0};
|
|
hid_collection_state_t collection_state = {0};
|
|
|
|
// TODO support PUSH and POP operations
|
|
while (current_offset < data_size) {
|
|
// TODO: if item 0xfe, it is a long item and will be ignored for now.
|
|
decode_item(item[current_offset], &tag, &type, &size);
|
|
size_t actual_size = translate_size(size);
|
|
if (current_offset + 1 + actual_size > data_size) {
|
|
printf("hid: short item exceeds report descriptor length\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
int error = 0;
|
|
switch (type) {
|
|
case HID_ITEM_TYPE_GLOBAL:
|
|
error = handle_global(&globals, tag, &item[current_offset], actual_size);
|
|
break;
|
|
case HID_ITEM_TYPE_LOCAL:
|
|
error = handle_local(&globals, &locals, tag, &item[current_offset], actual_size);
|
|
break;
|
|
case HID_ITEM_TYPE_MAIN:
|
|
error = handle_main(parser, &collection_state, &globals, &locals, tag, &item[current_offset], actual_size);
|
|
break;
|
|
}
|
|
|
|
if (error) {
|
|
// TODO eventual cleanup
|
|
printf("hid: parsing returned an error: %s\n", strerror(error));
|
|
return error;
|
|
}
|
|
|
|
current_offset += 1 + actual_size;
|
|
}
|
|
|
|
if (collection_state.current_depth != 0) {
|
|
printf("hid: unclosed collection in report descriptor\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define EVENT_EMISSION_LIMIT 32
|
|
|
|
static const uint16_t hid_keyboard_keypad_to_input_key_table[HID_KEYBOARD_KEYPAD_MAX + 1] = {
|
|
[0x04] = INPUT_KEY_A,
|
|
[0x05] = INPUT_KEY_B,
|
|
[0x06] = INPUT_KEY_C,
|
|
[0x07] = INPUT_KEY_D,
|
|
[0x08] = INPUT_KEY_E,
|
|
[0x09] = INPUT_KEY_F,
|
|
[0x0a] = INPUT_KEY_G,
|
|
[0x0b] = INPUT_KEY_H,
|
|
[0x0c] = INPUT_KEY_I,
|
|
[0x0d] = INPUT_KEY_J,
|
|
[0x0e] = INPUT_KEY_K,
|
|
[0x0f] = INPUT_KEY_L,
|
|
[0x10] = INPUT_KEY_M,
|
|
[0x11] = INPUT_KEY_N,
|
|
[0x12] = INPUT_KEY_O,
|
|
[0x13] = INPUT_KEY_P,
|
|
[0x14] = INPUT_KEY_Q,
|
|
[0x15] = INPUT_KEY_R,
|
|
[0x16] = INPUT_KEY_S,
|
|
[0x17] = INPUT_KEY_T,
|
|
[0x18] = INPUT_KEY_U,
|
|
[0x19] = INPUT_KEY_V,
|
|
[0x1a] = INPUT_KEY_W,
|
|
[0x1b] = INPUT_KEY_X,
|
|
[0x1c] = INPUT_KEY_Y,
|
|
[0x1d] = INPUT_KEY_Z,
|
|
[0x1e] = INPUT_KEY_1,
|
|
[0x1f] = INPUT_KEY_2,
|
|
[0x20] = INPUT_KEY_3,
|
|
[0x21] = INPUT_KEY_4,
|
|
[0x22] = INPUT_KEY_5,
|
|
[0x23] = INPUT_KEY_6,
|
|
[0x24] = INPUT_KEY_7,
|
|
[0x25] = INPUT_KEY_8,
|
|
[0x26] = INPUT_KEY_9,
|
|
[0x27] = INPUT_KEY_0,
|
|
[0x28] = INPUT_KEY_ENTER,
|
|
[0x29] = INPUT_KEY_ESC,
|
|
[0x2a] = INPUT_KEY_BACKSPACE,
|
|
[0x2b] = INPUT_KEY_TAB,
|
|
[0x2c] = INPUT_KEY_SPACE,
|
|
[0x2d] = INPUT_KEY_MINUS,
|
|
[0x2e] = INPUT_KEY_EQUAL,
|
|
[0x2f] = INPUT_KEY_LEFTBRACE,
|
|
[0x30] = INPUT_KEY_RIGHTBRACE,
|
|
[0x31] = INPUT_KEY_BACKSLASH,
|
|
[0x33] = INPUT_KEY_SEMICOLON,
|
|
[0x34] = INPUT_KEY_APOSTROPHE,
|
|
[0x35] = INPUT_KEY_GRAVE,
|
|
[0x36] = INPUT_KEY_COMMA,
|
|
[0x37] = INPUT_KEY_DOT,
|
|
[0x38] = INPUT_KEY_SLASH,
|
|
[0x39] = INPUT_KEY_CAPSLOCK,
|
|
[0x3a] = INPUT_KEY_F1,
|
|
[0x3b] = INPUT_KEY_F2,
|
|
[0x3c] = INPUT_KEY_F3,
|
|
[0x3d] = INPUT_KEY_F4,
|
|
[0x3e] = INPUT_KEY_F5,
|
|
[0x3f] = INPUT_KEY_F6,
|
|
[0x40] = INPUT_KEY_F7,
|
|
[0x41] = INPUT_KEY_F8,
|
|
[0x42] = INPUT_KEY_F9,
|
|
[0x43] = INPUT_KEY_F10,
|
|
[0x44] = INPUT_KEY_F11,
|
|
[0x45] = INPUT_KEY_F12,
|
|
[0x47] = INPUT_KEY_SCROLLLOCK,
|
|
[0x49] = INPUT_KEY_INSERT,
|
|
[0x4a] = INPUT_KEY_HOME,
|
|
[0x4b] = INPUT_KEY_PAGEUP,
|
|
[0x4c] = INPUT_KEY_DELETE,
|
|
[0x4d] = INPUT_KEY_END,
|
|
[0x4e] = INPUT_KEY_PAGEDOWN,
|
|
[0x4f] = INPUT_KEY_RIGHT,
|
|
[0x50] = INPUT_KEY_LEFT,
|
|
[0x51] = INPUT_KEY_DOWN,
|
|
[0x52] = INPUT_KEY_UP,
|
|
[0x53] = INPUT_KEY_NUMLOCK,
|
|
[0x54] = INPUT_KEY_KPSLASH,
|
|
[0x55] = INPUT_KEY_KPASTERISK,
|
|
[0x56] = INPUT_KEY_KPMINUS,
|
|
[0x57] = INPUT_KEY_KPPLUS,
|
|
[0x58] = INPUT_KEY_KPENTER,
|
|
[0x59] = INPUT_KEY_KP1,
|
|
[0x5a] = INPUT_KEY_KP2,
|
|
[0x5b] = INPUT_KEY_KP3,
|
|
[0x5c] = INPUT_KEY_KP4,
|
|
[0x5d] = INPUT_KEY_KP5,
|
|
[0x5e] = INPUT_KEY_KP6,
|
|
[0x5f] = INPUT_KEY_KP7,
|
|
[0x60] = INPUT_KEY_KP8,
|
|
[0x61] = INPUT_KEY_KP9,
|
|
[0x62] = INPUT_KEY_KP0,
|
|
[0x63] = INPUT_KEY_KPDOT,
|
|
[0x68] = INPUT_KEY_F13,
|
|
[0x69] = INPUT_KEY_F14,
|
|
[0x6a] = INPUT_KEY_F15,
|
|
[0x6b] = INPUT_KEY_F16,
|
|
[0x6c] = INPUT_KEY_F17,
|
|
[0x6d] = INPUT_KEY_F18,
|
|
[0x6e] = INPUT_KEY_F19,
|
|
[0x6f] = INPUT_KEY_F20,
|
|
[0x70] = INPUT_KEY_F21,
|
|
[0x71] = INPUT_KEY_F22,
|
|
[0x72] = INPUT_KEY_F23,
|
|
[0x73] = INPUT_KEY_F24,
|
|
[0xe0] = INPUT_KEY_LEFTCTRL,
|
|
[0xe1] = INPUT_KEY_LEFTSHIFT,
|
|
[0xe2] = INPUT_KEY_LEFTALT,
|
|
[0xe3] = INPUT_KEY_LEFTMETA,
|
|
[0xe4] = INPUT_KEY_RIGHTCTRL,
|
|
[0xe5] = INPUT_KEY_RIGHTSHIFT,
|
|
[0xe6] = INPUT_KEY_RIGHTALT,
|
|
[0xe7] = INPUT_KEY_RIGHTMETA
|
|
};
|
|
|
|
static uint16_t hid_keyboard_keypad_to_input_key(uint32_t hid_key) {
|
|
if (hid_key > HID_KEYBOARD_KEYPAD_MAX)
|
|
return INPUT_KEY_RESERVED;
|
|
|
|
return hid_keyboard_keypad_to_input_key_table[hid_key];
|
|
}
|
|
|
|
static void push_event(input_event_t event, input_event_t *events, size_t *events_emitted) {
|
|
if (*events_emitted >= EVENT_EMISSION_LIMIT)
|
|
return;
|
|
|
|
events[*events_emitted] = event;
|
|
*events_emitted += 1;
|
|
}
|
|
|
|
static inline void hid_key_set(uint64_t *keys, uint32_t usage) {
|
|
if (usage > HID_KEYBOARD_KEYPAD_MAX)
|
|
return;
|
|
|
|
keys[usage / 64] |= 1ull << (usage % 64);
|
|
}
|
|
|
|
static inline bool hid_key_is_modifier(uint32_t usage) {
|
|
return usage >= 0xe0 && usage <= 0xe7;
|
|
}
|
|
|
|
static inline int64_t hid_input_value_signed(hid_input_t *input, uint64_t value) {
|
|
if (input->logical_minimum >= 0 || input->bit_size == 0 || input->bit_size >= 64)
|
|
return (int64_t)value;
|
|
|
|
uint64_t sign_bit = 1ull << (input->bit_size - 1);
|
|
if (value & sign_bit)
|
|
value |= ~((1ull << input->bit_size) - 1);
|
|
|
|
return (int64_t)value;
|
|
}
|
|
|
|
static void hid_emit_event_keyboard_keypad(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
if (input->flags & HID_INPUT_VARIABLE) {
|
|
if (value == 0)
|
|
return;
|
|
|
|
if (usage->minimum || usage->maximum) {
|
|
value = usage->minimum + idx;
|
|
if (value > usage->maximum)
|
|
return;
|
|
} else {
|
|
value = USAGE(usage->usage);
|
|
}
|
|
}
|
|
|
|
hid_key_set(application->pressed_keys, value);
|
|
}
|
|
|
|
static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage);
|
|
static uint16_t hid_generic_desktop_to_input_abs(uint32_t usage);
|
|
|
|
static void hid_emit_event_generic_desktop(hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
uint32_t hid_usage = USAGE(usage->usage);
|
|
if (usage->minimum || usage->maximum) {
|
|
hid_usage = usage->minimum + idx;
|
|
if (hid_usage > usage->maximum)
|
|
return;
|
|
}
|
|
|
|
int64_t signed_value = hid_input_value_signed(input, value);
|
|
|
|
input_event_t event = {0};
|
|
event.value = signed_value;
|
|
|
|
if (input->flags & HID_INPUT_RELATIVE) {
|
|
if (signed_value == 0)
|
|
return;
|
|
|
|
uint16_t code = hid_generic_desktop_to_input_rel(hid_usage);
|
|
if (code > INPUT_REL_MAX)
|
|
return;
|
|
|
|
event.type = INPUT_EV_REL;
|
|
event.code = code;
|
|
} else {
|
|
uint16_t code = hid_generic_desktop_to_input_abs(hid_usage);
|
|
if (code > INPUT_ABS_MAX)
|
|
return;
|
|
|
|
event.type = INPUT_EV_ABS;
|
|
event.code = code;
|
|
}
|
|
|
|
push_event(event, events, events_emitted);
|
|
}
|
|
|
|
static inline void hid_button_set(hid_application_t *application, uint32_t button) {
|
|
if (button < HID_BUTTON_PRIMARY || button > HID_BUTTON_MAX)
|
|
return;
|
|
|
|
application->pressed_buttons |= 1ull << (button - HID_BUTTON_PRIMARY);
|
|
}
|
|
|
|
static void hid_emit_event_button(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
if (input->flags & HID_INPUT_VARIABLE) {
|
|
if (value == 0)
|
|
return;
|
|
|
|
if (usage->minimum || usage->maximum) {
|
|
uint32_t button = usage->minimum + idx;
|
|
if (button > usage->maximum)
|
|
return;
|
|
|
|
hid_button_set(application, button);
|
|
} else {
|
|
hid_button_set(application, USAGE(usage->usage));
|
|
}
|
|
} else if (value) {
|
|
hid_button_set(application, value);
|
|
}
|
|
}
|
|
|
|
static void hid_emit_event(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) {
|
|
switch (USAGE_PAGE(usage->usage)) {
|
|
case HID_USAGE_PAGE_GENERIC_DESKTOP:
|
|
hid_emit_event_generic_desktop(input, usage, idx, value, events, events_emitted);
|
|
break;
|
|
case HID_USAGE_PAGE_KEYBOARD_KEYPAD:
|
|
hid_emit_event_keyboard_keypad(application, input, usage, idx, value, events, events_emitted);
|
|
break;
|
|
case HID_USAGE_PAGE_BUTTON:
|
|
hid_emit_event_button(application, input, usage, idx, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline uint64_t get_bitfield_data_at_offset(uint8_t *data, size_t bit_count, size_t bit_offset) {
|
|
uint64_t value = 0;
|
|
|
|
if (bit_count == 0)
|
|
return 0;
|
|
|
|
__assert(bit_count <= 64);
|
|
|
|
for (size_t i = 0; i < bit_count; i++) {
|
|
size_t src_bit = bit_offset + i;
|
|
uint8_t byte = data[src_bit / 8];
|
|
uint8_t bit = (byte >> (src_bit % 8)) & 1;
|
|
|
|
value |= (uint64_t)bit << i;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static void do_input(hid_parser_t *parser, hid_input_t *input, uint8_t *data) {
|
|
hid_application_t *application = &parser->applications[input->application_id];
|
|
|
|
if (input->bit_size > 64) {
|
|
printf("hid: bit sizes above 64-bit are currently a TODO.\n");
|
|
return;
|
|
}
|
|
|
|
input_event_t events[EVENT_EMISSION_LIMIT];
|
|
size_t events_emitted = 0;
|
|
hid_usage_t *usage = NULL;
|
|
for (size_t i = 0; i < input->count; ++i) {
|
|
if (i < input->usage_count)
|
|
usage = &input->usages[i];
|
|
|
|
// no point in handling this if we are not gonna have an use for it
|
|
if (usage == NULL)
|
|
break;
|
|
|
|
uint64_t value = get_bitfield_data_at_offset(data, input->bit_size, input->bit_offset + input->bit_size * i);
|
|
hid_emit_event(application, input, usage, i, value, events, &events_emitted);
|
|
}
|
|
|
|
if (events_emitted)
|
|
input_queue_packet(application->input_device, events, events_emitted);
|
|
}
|
|
|
|
typedef struct {
|
|
hid_input_t *inputs;
|
|
size_t input_count;
|
|
unsigned int next_input;
|
|
} parse_state_t;
|
|
|
|
static bool consume_next_input(parse_state_t *parse_state, hid_input_t **current_input) {
|
|
if (parse_state->next_input >= parse_state->input_count)
|
|
return false;
|
|
|
|
*current_input = &parse_state->inputs[parse_state->next_input];
|
|
|
|
++parse_state->next_input;
|
|
return true;
|
|
}
|
|
|
|
static void hid_handle_key_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_keys, size_t offset, bool modifiers) {
|
|
uint64_t changes = old_keys ^ application->pressed_keys[offset];
|
|
while (changes) {
|
|
int bit = __builtin_ctzll(changes);
|
|
uint32_t value = offset * 64 + bit;
|
|
|
|
if (hid_key_is_modifier(value) != modifiers) {
|
|
changes &= ~(1llu << bit);
|
|
continue;
|
|
}
|
|
|
|
input_event_t event = {0};
|
|
event.type = INPUT_EV_KEY;
|
|
event.code = hid_keyboard_keypad_to_input_key(value);
|
|
if (event.code != INPUT_KEY_RESERVED) {
|
|
event.value = (application->pressed_keys[offset] & (1ull << bit)) ? 1 : 0;
|
|
push_event(event, events, events_emitted);
|
|
}
|
|
|
|
changes &= ~(1llu << bit);
|
|
}
|
|
}
|
|
|
|
static uint16_t hid_button_to_input_key(uint32_t usage);
|
|
|
|
static void hid_handle_button_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_buttons) {
|
|
uint64_t changes = old_buttons ^ application->pressed_buttons;
|
|
while (changes) {
|
|
int bit = __builtin_ctzll(changes);
|
|
uint32_t button = HID_BUTTON_PRIMARY + bit;
|
|
|
|
input_event_t event = {0};
|
|
event.type = INPUT_EV_KEY;
|
|
event.code = hid_button_to_input_key(button);
|
|
if (event.code != INPUT_KEY_RESERVED) {
|
|
event.value = (application->pressed_buttons & (1ull << bit)) ? 1 : 0;
|
|
push_event(event, events, events_emitted);
|
|
}
|
|
|
|
changes &= ~(1llu << bit);
|
|
}
|
|
}
|
|
|
|
static void do_report_parse(hid_parser_t *parser, hid_input_t *inputs, size_t input_count, uint8_t *data) {
|
|
parse_state_t parse_state = {
|
|
.inputs = inputs,
|
|
.input_count = input_count
|
|
};
|
|
|
|
hid_input_t *current_input;
|
|
|
|
__assert(parser->application_count <= 64);
|
|
uint64_t applications_handled = 0;
|
|
uint64_t old_keys[4 * parser->application_count];
|
|
uint64_t old_buttons[parser->application_count];
|
|
while (consume_next_input(&parse_state, ¤t_input)) {
|
|
if ((applications_handled & (1llu << current_input->application_id)) == 0) {
|
|
memcpy(&old_keys[current_input->application_id * 4], parser->applications[current_input->application_id].pressed_keys, sizeof(parser->applications[current_input->application_id].pressed_keys));
|
|
memset(parser->applications[current_input->application_id].pressed_keys, 0, sizeof(parser->applications[current_input->application_id].pressed_keys));
|
|
old_buttons[current_input->application_id] = parser->applications[current_input->application_id].pressed_buttons;
|
|
parser->applications[current_input->application_id].pressed_buttons = 0;
|
|
applications_handled |= (1llu << current_input->application_id);
|
|
}
|
|
|
|
do_input(parser, current_input, data);
|
|
}
|
|
|
|
for (size_t i = 0; i < parser->application_count; ++i) {
|
|
if ((applications_handled & (1llu << i)) == 0)
|
|
continue;
|
|
|
|
input_event_t events[EVENT_EMISSION_LIMIT];
|
|
size_t events_emitted = 0;
|
|
|
|
for (int j = 0; j < 4; ++j)
|
|
if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j])
|
|
hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, true);
|
|
|
|
for (int j = 0; j < 4; ++j)
|
|
if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j])
|
|
hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, false);
|
|
|
|
if (old_buttons[i] ^ parser->applications[i].pressed_buttons)
|
|
hid_handle_button_changes(&parser->applications[i], events, &events_emitted, old_buttons[i]);
|
|
|
|
if (events_emitted)
|
|
input_queue_packet(parser->applications[i].input_device, events, events_emitted);
|
|
}
|
|
}
|
|
|
|
void hid_parse_report(hid_parser_t *parser, hid_report_t *report, uint8_t *data, size_t size) {
|
|
if (report) {
|
|
// separated by reports
|
|
if (ROUND_UP(report->bit_size, 8) / 8 > size)
|
|
return;
|
|
|
|
do_report_parse(parser, report->inputs, report->input_count, data);
|
|
} else {
|
|
// only anonymous inputs
|
|
if (ROUND_UP(parser->input_bit_size, 8) / 8 > size)
|
|
return;
|
|
|
|
do_report_parse(parser, parser->inputs, parser->input_count, data);
|
|
}
|
|
}
|
|
|
|
static void hid_advertise_keyboard_keypad(input_device_t *device, hid_input_t *input, hid_usage_t *usage) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
if (usage->minimum == 0 && usage->maximum == 0) {
|
|
uint16_t code = hid_keyboard_keypad_to_input_key(USAGE(usage->usage));
|
|
if (code != INPUT_KEY_RESERVED)
|
|
bitmap_set(&device->key_bits, code, 1);
|
|
}
|
|
|
|
for (uint32_t hid_key = usage->minimum; hid_key <= usage->maximum && hid_key <= HID_KEYBOARD_KEYPAD_MAX; ++hid_key) {
|
|
uint16_t code = hid_keyboard_keypad_to_input_key(hid_key);
|
|
if (code != INPUT_KEY_RESERVED)
|
|
bitmap_set(&device->key_bits, code, 1);
|
|
}
|
|
}
|
|
|
|
static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage) {
|
|
switch (usage) {
|
|
case HID_GD_X:
|
|
return INPUT_REL_X;
|
|
case HID_GD_Y:
|
|
return INPUT_REL_Y;
|
|
case HID_GD_WHEEL:
|
|
return INPUT_REL_WHEEL;
|
|
default:
|
|
return INPUT_REL_MAX + 1;
|
|
}
|
|
}
|
|
|
|
static uint16_t hid_generic_desktop_to_input_abs(uint32_t usage) {
|
|
switch (usage) {
|
|
case HID_GD_X:
|
|
return INPUT_ABS_X;
|
|
case HID_GD_Y:
|
|
return INPUT_ABS_Y;
|
|
default:
|
|
return INPUT_ABS_MAX + 1;
|
|
}
|
|
}
|
|
|
|
static int32_t hid_abs_initial_value(hid_input_t *input) {
|
|
if (input->logical_minimum > 0)
|
|
return input->logical_minimum;
|
|
if (input->logical_maximum < 0)
|
|
return input->logical_maximum;
|
|
return 0;
|
|
}
|
|
|
|
static void hid_advertise_abs(input_device_t *device, hid_input_t *input, uint16_t code) {
|
|
if (code > INPUT_ABS_MAX)
|
|
return;
|
|
|
|
bitmap_set(&device->ev_bits, INPUT_EV_ABS, 1);
|
|
bitmap_set(&device->abs_bits, code, 1);
|
|
device->abs_info[code].value = hid_abs_initial_value(input);
|
|
device->abs_info[code].min = input->logical_minimum;
|
|
device->abs_info[code].max = input->logical_maximum;
|
|
}
|
|
|
|
static void hid_advertise_generic_desktop(input_device_t *device, hid_input_t *input, hid_usage_t *usage) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
if (usage->minimum == 0 && usage->maximum == 0) {
|
|
uint32_t hid_usage = USAGE(usage->usage);
|
|
if (input->flags & HID_INPUT_RELATIVE) {
|
|
uint16_t code = hid_generic_desktop_to_input_rel(hid_usage);
|
|
if (code <= INPUT_REL_MAX) {
|
|
bitmap_set(&device->ev_bits, INPUT_EV_REL, 1);
|
|
bitmap_set(&device->rel_bits, code, 1);
|
|
}
|
|
} else {
|
|
hid_advertise_abs(device, input, hid_generic_desktop_to_input_abs(hid_usage));
|
|
}
|
|
}
|
|
|
|
for (uint32_t hid_usage = usage->minimum; hid_usage <= usage->maximum && hid_usage <= HID_GD_WHEEL; ++hid_usage) {
|
|
if (input->flags & HID_INPUT_RELATIVE) {
|
|
uint16_t code = hid_generic_desktop_to_input_rel(hid_usage);
|
|
if (code <= INPUT_REL_MAX) {
|
|
bitmap_set(&device->ev_bits, INPUT_EV_REL, 1);
|
|
bitmap_set(&device->rel_bits, code, 1);
|
|
}
|
|
} else {
|
|
hid_advertise_abs(device, input, hid_generic_desktop_to_input_abs(hid_usage));
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint16_t hid_button_to_input_key(uint32_t usage) {
|
|
switch (usage) {
|
|
case HID_BUTTON_PRIMARY:
|
|
return INPUT_KEY_BTN_LEFT;
|
|
case HID_BUTTON_SECONDARY:
|
|
return INPUT_KEY_BTN_RIGHT;
|
|
case HID_BUTTON_TERTIARY:
|
|
return INPUT_KEY_BTN_MIDDLE;
|
|
case HID_BUTTON_SIDE:
|
|
return INPUT_KEY_BTN_SIDE;
|
|
case HID_BUTTON_EXTRA:
|
|
return INPUT_KEY_BTN_EXTRA;
|
|
case HID_BUTTON_FORWARD:
|
|
return INPUT_KEY_BTN_FORWARD;
|
|
default:
|
|
return INPUT_KEY_RESERVED;
|
|
}
|
|
}
|
|
|
|
static void hid_advertise_button(input_device_t *device, hid_input_t *input, hid_usage_t *usage) {
|
|
if (input->flags & HID_INPUT_CONSTANT)
|
|
return;
|
|
|
|
if (usage->minimum == 0 && usage->maximum == 0) {
|
|
uint16_t code = hid_button_to_input_key(USAGE(usage->usage));
|
|
if (code != INPUT_KEY_RESERVED) {
|
|
bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1);
|
|
bitmap_set(&device->key_bits, code, 1);
|
|
}
|
|
}
|
|
|
|
for (uint32_t button = usage->minimum; button <= usage->maximum && button <= HID_BUTTON_MAX; ++button) {
|
|
uint16_t code = hid_button_to_input_key(button);
|
|
if (code != INPUT_KEY_RESERVED) {
|
|
bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1);
|
|
bitmap_set(&device->key_bits, code, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void hid_advertise_input(hid_parser_t *parser, hid_input_t *input) {
|
|
hid_application_t *application = &parser->applications[input->application_id];
|
|
input_device_t *device = application->input_device;
|
|
|
|
for (size_t i = 0; i < input->usage_count; ++i) {
|
|
hid_usage_t *usage = &input->usages[i];
|
|
switch (USAGE_PAGE(usage->usage)) {
|
|
case HID_USAGE_PAGE_GENERIC_DESKTOP:
|
|
hid_advertise_generic_desktop(device, input, usage);
|
|
break;
|
|
case HID_USAGE_PAGE_KEYBOARD_KEYPAD:
|
|
bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1);
|
|
hid_advertise_keyboard_keypad(device, input, usage);
|
|
break;
|
|
case HID_USAGE_PAGE_BUTTON:
|
|
hid_advertise_button(device, input, usage);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hid_advertise_events(hid_parser_t *parser) {
|
|
if (parser->report_count) {
|
|
for (size_t i = 0; i < parser->report_count; ++i) {
|
|
hid_report_t *report = &parser->reports[i];
|
|
for (size_t j = 0; j < report->input_count; ++j)
|
|
hid_advertise_input(parser, &report->inputs[j]);
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < parser->input_count; ++i)
|
|
hid_advertise_input(parser, &parser->inputs[i]);
|
|
}
|
|
}
|