mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-01-27 06:35:20 +00:00
454 lines
15 KiB
C
454 lines
15 KiB
C
/*
|
|
* Copyright (C) 2017 BlueKitchen GmbH
|
|
*
|
|
* 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 name of the copyright holders nor the names of
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
* 4. Any redistribution, use, or modification is done solely for
|
|
* personal benefit and not for any commercial purpose or for
|
|
* monetary gain.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH 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 MATTHIAS
|
|
* RINGWALD 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.
|
|
*
|
|
* Please inquire about commercial licensing options at
|
|
* contact@bluekitchen-gmbh.com
|
|
*
|
|
*/
|
|
|
|
#define __BTSTACK_FILE__ "btstack_hid_parser.c"
|
|
|
|
#include <string.h>
|
|
|
|
#include "btstack_hid_parser.h"
|
|
#include "btstack_util.h"
|
|
#include "btstack_debug.h"
|
|
|
|
// Not implemented:
|
|
// - Support for Push/Pop
|
|
// - Optional Pretty Print of HID Descripor
|
|
// - Support to query descriptort for contained usages, e.g. to detect keyboard or mouse
|
|
|
|
// #define HID_PARSER_PRETTY_PRINT
|
|
|
|
/*
|
|
* btstack_hid_parser.c
|
|
*/
|
|
|
|
|
|
typedef enum {
|
|
Main=0,
|
|
Global,
|
|
Local,
|
|
Reserved
|
|
} TagType;
|
|
|
|
typedef enum {
|
|
Input=8,
|
|
Output,
|
|
Coll,
|
|
Feature,
|
|
EndColl
|
|
} MainItemTag;
|
|
|
|
typedef enum {
|
|
UsagePage,
|
|
LogicalMinimum,
|
|
LogicalMaximum,
|
|
PhysicalMinimum,
|
|
PhysicalMaximum,
|
|
UnitExponent,
|
|
Unit,
|
|
ReportSize,
|
|
ReportID,
|
|
ReportCount,
|
|
Push,
|
|
Pop
|
|
} GlobalItemTag;
|
|
|
|
typedef enum {
|
|
Usage,
|
|
UsageMinimum,
|
|
UsageMaximum,
|
|
DesignatorIndex,
|
|
DesignatorMinimum,
|
|
DesignatorMaximum,
|
|
StringIndex,
|
|
StringMinimum,
|
|
StringMaximum,
|
|
Delimiter
|
|
} LocalItemTag;
|
|
|
|
const int hid_item_sizes[] = { 0, 1, 2, 4 };
|
|
|
|
#ifdef HID_PARSER_PRETTY_PRINT
|
|
|
|
static const char * type_names[] = {
|
|
"Main",
|
|
"Global",
|
|
"Local",
|
|
"Reserved"
|
|
};
|
|
static const char * main_tags[] = {
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"Input ",
|
|
"Output",
|
|
"Collection",
|
|
"Feature",
|
|
"End Collection",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved"
|
|
};
|
|
static const char * global_tags[] = {
|
|
"Usage Page",
|
|
"Logical Minimum",
|
|
"Logical Maximum",
|
|
"Physical Minimum",
|
|
"Physical Maximum",
|
|
"Unit Exponent",
|
|
"Unit",
|
|
"Report Size",
|
|
"Report ID",
|
|
"Report Count",
|
|
"Push",
|
|
"Pop",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved"
|
|
};
|
|
static const char * local_tags[] = {
|
|
"Usage",
|
|
"Usage Minimum",
|
|
"Usage Maximum",
|
|
"Designator Index",
|
|
"Designator Minimum",
|
|
"Designator Maximum",
|
|
"String Index",
|
|
"String Minimum",
|
|
"String Maximum",
|
|
"Delimiter",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved"
|
|
};
|
|
#endif
|
|
|
|
static void hid_pretty_print_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
|
|
#ifdef HID_PARSER_PRETTY_PRINT
|
|
const char ** item_tag_table;
|
|
switch (item->item_type){
|
|
case Main:
|
|
item_tag_table = main_tags;
|
|
break;
|
|
case Global:
|
|
item_tag_table = global_tags;
|
|
break;
|
|
case Local:
|
|
item_tag_table = local_tags;
|
|
break;
|
|
default:
|
|
item_tag_table = NULL;
|
|
break;
|
|
}
|
|
const char * item_tag_name = "Invalid";
|
|
if (item_tag_table){
|
|
item_tag_name = item_tag_table[item->item_tag];
|
|
}
|
|
log_info("%-15s (%-6s) // %02x 0x%0008x", item_tag_table[item->item_tag], type_names[item->item_type], parser->descriptor[parser->descriptor_pos], item->item_value);
|
|
#else
|
|
UNUSED(parser);
|
|
UNUSED(item);
|
|
#endif
|
|
}
|
|
|
|
// parse descriptor item and read up to 32-bit bit value
|
|
static void btstack_hid_parse_descriptor_item(hid_descriptor_item_t * item, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len){
|
|
// parse item header
|
|
if (hid_descriptor_len < 1) return;
|
|
uint16_t pos = 0;
|
|
uint8_t item_header = hid_descriptor[pos++];
|
|
item->data_size = hid_item_sizes[item_header & 0x03];
|
|
item->item_type = (item_header & 0x0c) >> 2;
|
|
item->item_tag = (item_header & 0xf0) >> 4;
|
|
// long item
|
|
if (item->data_size == 2 && item->item_tag == 0x0f && item->item_type == 3){
|
|
if (hid_descriptor_len < 3) return;
|
|
item->data_size = hid_descriptor[pos++];
|
|
item->item_tag = hid_descriptor[pos++];
|
|
}
|
|
item->item_size = pos + item->data_size;
|
|
item->item_value = 0;
|
|
|
|
// read item value
|
|
if (hid_descriptor_len < item->item_size) return;
|
|
if (item->data_size > 4) return;
|
|
int i;
|
|
int sgnd = item->item_type == Global && item->item_tag > 0 && item->item_tag < 5;
|
|
int32_t value = 0;
|
|
uint8_t latest_byte = 0;
|
|
for (i=0;i<item->data_size;i++){
|
|
latest_byte = hid_descriptor[pos++];
|
|
value = (latest_byte << (8*i)) | value;
|
|
}
|
|
if (sgnd && item->data_size > 0){
|
|
if (latest_byte & 0x80) {
|
|
value -= 1 << (item->data_size*8);
|
|
}
|
|
}
|
|
item->item_value = value;
|
|
}
|
|
|
|
static void btstack_hid_handle_global_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
|
|
switch(item->item_tag){
|
|
case UsagePage:
|
|
parser->global_usage_page = item->item_value;
|
|
break;
|
|
case LogicalMinimum:
|
|
parser->global_logical_minimum = item->item_value;
|
|
break;
|
|
case LogicalMaximum:
|
|
parser->global_logical_maximum = item->item_value;
|
|
break;
|
|
case ReportSize:
|
|
parser->global_report_size = item->item_value;
|
|
break;
|
|
case ReportID:
|
|
if (parser->active_record && parser->global_report_id != item->item_value){
|
|
// log_debug("New report, don't match anymore");
|
|
parser->active_record = 0;
|
|
}
|
|
parser->global_report_id = item->item_value;
|
|
// log_info("- Report ID: %02x", parser->global_report_id);
|
|
break;
|
|
case ReportCount:
|
|
parser->global_report_count = item->item_value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hid_find_next_usage(btstack_hid_parser_t * parser){
|
|
while (parser->available_usages == 0 && parser->usage_pos < parser->descriptor_pos){
|
|
hid_descriptor_item_t usage_item;
|
|
// parser->usage_pos < parser->descriptor_pos < parser->descriptor_len
|
|
btstack_hid_parse_descriptor_item(&usage_item, &parser->descriptor[parser->usage_pos], parser->descriptor_len - parser->usage_pos);
|
|
if (usage_item.item_type == Global && usage_item.item_tag == UsagePage){
|
|
parser->usage_page = usage_item.item_value;
|
|
}
|
|
if (usage_item.item_type == Local){
|
|
uint32_t usage_value = (usage_item.data_size > 2) ? usage_item.item_value : (parser->usage_page << 16) | usage_item.item_value;
|
|
switch (usage_item.item_tag){
|
|
case Usage:
|
|
parser->available_usages = 1;
|
|
parser->usage_minimum = usage_value;
|
|
break;
|
|
case UsageMinimum:
|
|
parser->usage_minimum = usage_value;
|
|
parser->have_usage_min = 1;
|
|
break;
|
|
case UsageMaximum:
|
|
parser->usage_maximum = usage_value;
|
|
parser->have_usage_max = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (parser->have_usage_min && parser->have_usage_max){
|
|
parser->available_usages = parser->usage_maximum - parser->usage_minimum + 1;
|
|
parser->have_usage_min = 0;
|
|
parser->have_usage_max = 0;
|
|
}
|
|
}
|
|
parser->usage_pos += usage_item.item_size;
|
|
}
|
|
}
|
|
|
|
static void hid_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
|
|
hid_pretty_print_item(parser, item);
|
|
int valid_field = 0;
|
|
switch (item->item_type){
|
|
case Main:
|
|
switch (item->item_tag){
|
|
case Input:
|
|
valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_INPUT;
|
|
break;
|
|
case Output:
|
|
valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_OUTPUT;
|
|
break;
|
|
case Feature:
|
|
valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_FEATURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case Global:
|
|
btstack_hid_handle_global_item(parser, item);
|
|
break;
|
|
case Local:
|
|
break;
|
|
}
|
|
if (!valid_field) return;
|
|
|
|
// verify record id
|
|
if (parser->global_report_id && !parser->active_record){
|
|
if (parser->report[0] != parser->global_report_id){
|
|
return;
|
|
}
|
|
parser->report_pos_in_bit += 8;
|
|
}
|
|
parser->active_record = 1;
|
|
// handle constant fields used for padding
|
|
if (item->item_value & 1){
|
|
int item_bits = parser->global_report_size * parser->global_report_count;
|
|
#ifdef HID_PARSER_PRETTY_PRINT
|
|
log_info("- Skip %u constant bits", item_bits);
|
|
#endif
|
|
parser->report_pos_in_bit += item_bits;
|
|
return;
|
|
}
|
|
// Empty Item
|
|
if (parser->global_report_count == 0) return;
|
|
// let's start
|
|
parser->required_usages = parser->global_report_count;
|
|
}
|
|
|
|
static void hid_post_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
|
|
if (item->item_type == Main){
|
|
// reset usage
|
|
parser->usage_pos = parser->descriptor_pos;
|
|
parser->usage_page = parser->global_usage_page;
|
|
}
|
|
parser->descriptor_pos += item->item_size;
|
|
}
|
|
|
|
static void btstack_hid_parser_find_next_usage(btstack_hid_parser_t * parser){
|
|
while (parser->state == BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM){
|
|
if (parser->descriptor_pos >= parser->descriptor_len){
|
|
// end of descriptor
|
|
parser->state = BTSTACK_HID_PARSER_COMPLETE;
|
|
break;
|
|
}
|
|
btstack_hid_parse_descriptor_item(&parser->descriptor_item, &parser->descriptor[parser->descriptor_pos], parser->descriptor_len - parser->descriptor_pos);
|
|
hid_process_item(parser, &parser->descriptor_item);
|
|
if (parser->required_usages){
|
|
hid_find_next_usage(parser);
|
|
if (parser->available_usages) {
|
|
parser->state = BTSTACK_HID_PARSER_USAGES_AVAILABLE;
|
|
} else {
|
|
log_error("no usages found");
|
|
parser->state = BTSTACK_HID_PARSER_COMPLETE;
|
|
}
|
|
} else {
|
|
hid_post_process_item(parser, &parser->descriptor_item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PUBLIC API
|
|
|
|
void btstack_hid_parser_init(btstack_hid_parser_t * parser, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len, btstack_hid_report_type_t hid_report_type, const uint8_t * hid_report, uint16_t hid_report_len){
|
|
|
|
memset(parser, 0, sizeof(btstack_hid_parser_t));
|
|
|
|
parser->descriptor = hid_descriptor;
|
|
parser->descriptor_len = hid_descriptor_len;
|
|
parser->report_type = hid_report_type;
|
|
parser->report = hid_report;
|
|
parser->report_len = hid_report_len;
|
|
parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
|
|
|
|
btstack_hid_parser_find_next_usage(parser);
|
|
}
|
|
|
|
int btstack_hid_parser_has_more(btstack_hid_parser_t * parser){
|
|
return parser->state == BTSTACK_HID_PARSER_USAGES_AVAILABLE;
|
|
}
|
|
|
|
void btstack_hid_parser_get_field(btstack_hid_parser_t * parser, uint16_t * usage_page, uint16_t * usage, int32_t * value){
|
|
|
|
*usage_page = parser->usage_minimum >> 16;
|
|
|
|
// read field (up to 32 bit unsigned, up to 31 bit signed - 32 bit signed behaviour is undefined) - check report len
|
|
int is_variable = parser->descriptor_item.item_value & 2;
|
|
int is_signed = parser->global_logical_minimum < 0;
|
|
int pos_start = btstack_min( parser->report_pos_in_bit >> 3, parser->report_len);
|
|
int pos_end = btstack_min( (parser->report_pos_in_bit + parser->global_report_size - 1) >> 3, parser->report_len);
|
|
int bytes_to_read = pos_end - pos_start + 1;
|
|
int i;
|
|
uint32_t multi_byte_value = 0;
|
|
for (i=0;i < bytes_to_read;i++){
|
|
multi_byte_value |= parser->report[pos_start+i] << (i*8);
|
|
}
|
|
uint32_t unsigned_value = (multi_byte_value >> (parser->report_pos_in_bit & 0x07)) & ((1<<parser->global_report_size)-1);
|
|
// log_debug("bit pos %2u, report size %u, start %u, end %u, len %u;; unsigned value %08x", parser->report_pos_in_bit, parser->global_report_size, pos_start, pos_end, parser->report_len, unsigned_value);
|
|
if (is_variable){
|
|
*usage = parser->usage_minimum & 0xffff;
|
|
if (is_signed && (unsigned_value & (1<<(parser->global_report_size-1)))){
|
|
*value = unsigned_value - (1<<parser->global_report_size);
|
|
} else {
|
|
*value = unsigned_value;
|
|
}
|
|
} else {
|
|
*usage = unsigned_value;
|
|
*value = 1;
|
|
}
|
|
parser->required_usages--;
|
|
parser->report_pos_in_bit += parser->global_report_size;
|
|
|
|
// next usage
|
|
if (is_variable){
|
|
parser->usage_minimum++;
|
|
parser->available_usages--;
|
|
} else {
|
|
if (parser->required_usages == 0){
|
|
parser->available_usages = 0;
|
|
}
|
|
}
|
|
if (parser->available_usages) {
|
|
return;
|
|
}
|
|
if (parser->required_usages == 0){
|
|
hid_post_process_item(parser, &parser->descriptor_item);
|
|
parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
|
|
btstack_hid_parser_find_next_usage(parser);
|
|
} else {
|
|
hid_find_next_usage(parser);
|
|
if (parser->available_usages == 0) {
|
|
parser->state = BTSTACK_HID_PARSER_COMPLETE;
|
|
}
|
|
}
|
|
}
|