Apparently some keyboards can have led control on another interface from the one where input is received. Now led mask is global to the usb devices instead of per keyboard instance.
922 lines
29 KiB
C++
922 lines
29 KiB
C++
#include <BAN/ByteSpan.h>
|
|
#include <BAN/ScopeGuard.h>
|
|
|
|
#include <kernel/FS/DevFS/FileSystem.h>
|
|
#include <kernel/USB/HID/HIDDriver.h>
|
|
#include <kernel/USB/HID/Joystick.h>
|
|
#include <kernel/USB/HID/Keyboard.h>
|
|
#include <kernel/USB/HID/Mouse.h>
|
|
|
|
#define DUMP_HID_REPORT 0
|
|
|
|
namespace Kernel
|
|
{
|
|
|
|
enum HIDRequest : uint8_t
|
|
{
|
|
SET_PROTOCOL = 0x0B,
|
|
};
|
|
|
|
enum class HIDDescriptorType : uint8_t
|
|
{
|
|
HID = 0x21,
|
|
Report = 0x22,
|
|
Physical = 0x23,
|
|
};
|
|
|
|
struct HIDDescriptor
|
|
{
|
|
uint8_t bLength;
|
|
uint8_t bDescriptorType;
|
|
uint16_t bcdHID;
|
|
uint8_t bCountryCode;
|
|
uint8_t bNumDescriptors;
|
|
struct
|
|
{
|
|
uint8_t bDescriptorType;
|
|
uint16_t wItemLength;
|
|
} __attribute__((packed)) descriptors[];
|
|
} __attribute__((packed));
|
|
static_assert(sizeof(HIDDescriptor) == 6);
|
|
|
|
|
|
struct GlobalState
|
|
{
|
|
BAN::Optional<uint16_t> usage_page;
|
|
BAN::Optional<int32_t> logical_minimum;
|
|
BAN::Optional<int32_t> logical_maximum_signed;
|
|
BAN::Optional<int32_t> logical_maximum_unsigned;
|
|
BAN::Optional<int32_t> physical_minimum;
|
|
BAN::Optional<int32_t> physical_maximum;
|
|
// FIXME: support units
|
|
BAN::Optional<uint8_t> report_id;
|
|
BAN::Optional<uint32_t> report_size;
|
|
BAN::Optional<uint32_t> report_count;
|
|
};
|
|
|
|
struct LocalState
|
|
{
|
|
BAN::Vector<uint32_t> usage_stack;
|
|
BAN::Optional<uint32_t> usage_minimum;
|
|
BAN::Optional<uint32_t> usage_maximum;
|
|
// FIXME: support all local items
|
|
};
|
|
|
|
using namespace USBHID;
|
|
|
|
#if DUMP_HID_REPORT
|
|
static void dump_hid_collection(const Collection& collection, size_t indent, bool use_report_id);
|
|
#endif
|
|
|
|
static BAN::ErrorOr<BAN::Vector<Collection>> parse_report_descriptor(BAN::ConstByteSpan report_data, bool& out_use_report_id);
|
|
|
|
USBHIDDriver::USBHIDDriver(USBDevice& device, const USBDevice::InterfaceDescriptor& interface)
|
|
: m_device(device)
|
|
, m_interface(interface)
|
|
{}
|
|
|
|
USBHIDDriver::~USBHIDDriver()
|
|
{
|
|
for (auto& device_report : m_device_reports)
|
|
if (device_report.device)
|
|
DevFileSystem::get().remove_device(device_report.device);
|
|
}
|
|
|
|
BAN::ErrorOr<void> USBHIDDriver::initialize()
|
|
{
|
|
auto dma_buffer = TRY(DMARegion::create(1024));
|
|
|
|
ASSERT(static_cast<USB::InterfaceBaseClass>(m_interface.descriptor.bInterfaceClass) == USB::InterfaceBaseClass::HID);
|
|
|
|
bool hid_descriptor_invalid = false;
|
|
size_t hid_descriptor_index = static_cast<size_t>(-1);
|
|
for (size_t i = 0; i < m_interface.misc_descriptors.size(); i++)
|
|
{
|
|
if (static_cast<HIDDescriptorType>(m_interface.misc_descriptors[i][1]) != HIDDescriptorType::HID)
|
|
continue;
|
|
if (m_interface.misc_descriptors[i].size() < sizeof(HIDDescriptor))
|
|
hid_descriptor_invalid = true;
|
|
const auto& hid_descriptor = *reinterpret_cast<const HIDDescriptor*>(m_interface.misc_descriptors[i].data());
|
|
if (hid_descriptor.bLength != m_interface.misc_descriptors[i].size())
|
|
hid_descriptor_invalid = true;
|
|
if (hid_descriptor.bLength != sizeof(HIDDescriptor) + hid_descriptor.bNumDescriptors * 3)
|
|
hid_descriptor_invalid = true;
|
|
hid_descriptor_index = i;
|
|
break;
|
|
}
|
|
|
|
if (hid_descriptor_index >= m_interface.misc_descriptors.size())
|
|
{
|
|
dwarnln("HID device does not contain HID descriptor");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (hid_descriptor_invalid)
|
|
{
|
|
dwarnln("HID device contains an invalid HID descriptor");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
// If this device supports boot protocol, make sure it is not used
|
|
if (m_interface.descriptor.bInterfaceSubClass == 0x01)
|
|
{
|
|
USBDeviceRequest request;
|
|
request.bmRequestType = USB::RequestType::HostToDevice | USB::RequestType::Class | USB::RequestType::Interface;
|
|
request.bRequest = HIDRequest::SET_PROTOCOL;
|
|
request.wValue = 1; // report protocol
|
|
request.wIndex = m_interface.descriptor.bInterfaceNumber;
|
|
request.wLength = 0;
|
|
TRY(m_device.send_request(request, 0));
|
|
}
|
|
|
|
const auto& hid_descriptor = *reinterpret_cast<const HIDDescriptor*>(m_interface.misc_descriptors[hid_descriptor_index].data());
|
|
dprintln_if(DEBUG_USB_HID, "HID descriptor ({} bytes)", m_interface.misc_descriptors[hid_descriptor_index].size());
|
|
dprintln_if(DEBUG_USB_HID, " bLength: {}", hid_descriptor.bLength);
|
|
dprintln_if(DEBUG_USB_HID, " bDescriptorType: {}", hid_descriptor.bDescriptorType);
|
|
dprintln_if(DEBUG_USB_HID, " bcdHID: {H}.{2H}", hid_descriptor.bcdHID >> 8, hid_descriptor.bcdHID & 0xFF);
|
|
dprintln_if(DEBUG_USB_HID, " bCountryCode: {}", hid_descriptor.bCountryCode);
|
|
dprintln_if(DEBUG_USB_HID, " bNumDescriptors: {}", hid_descriptor.bNumDescriptors);
|
|
|
|
uint32_t report_descriptor_index = 0;
|
|
BAN::Vector<Collection> collections;
|
|
for (size_t i = 0; i < hid_descriptor.bNumDescriptors; i++)
|
|
{
|
|
auto descriptor = hid_descriptor.descriptors[i];
|
|
|
|
if (static_cast<HIDDescriptorType>(descriptor.bDescriptorType) != HIDDescriptorType::Report)
|
|
{
|
|
dprintln_if(DEBUG_USB_HID, "Skipping HID descriptor type 0x{2H}", descriptor.bDescriptorType);
|
|
continue;
|
|
}
|
|
|
|
if (descriptor.wItemLength > dma_buffer->size())
|
|
{
|
|
dwarnln("Too big report descriptor size {} bytes ({} supported)", +descriptor.wItemLength, dma_buffer->size());
|
|
return BAN::Error::from_errno(ENOBUFS);
|
|
}
|
|
|
|
{
|
|
USBDeviceRequest request;
|
|
request.bmRequestType = USB::RequestType::DeviceToHost | USB::RequestType::Standard | USB::RequestType::Interface;
|
|
request.bRequest = USB::Request::GET_DESCRIPTOR;
|
|
request.wValue = (static_cast<uint16_t>(HIDDescriptorType::Report) << 8) | report_descriptor_index++;
|
|
request.wIndex = m_interface.descriptor.bInterfaceNumber;
|
|
request.wLength = descriptor.wItemLength;
|
|
auto transferred = TRY(m_device.send_request(request, dma_buffer->paddr()));
|
|
|
|
if (transferred < descriptor.wItemLength)
|
|
{
|
|
dwarnln("HID device did not respond with full report descriptor");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
}
|
|
|
|
dprintln_if(DEBUG_USB_HID, "Parsing {} byte report descriptor", +descriptor.wItemLength);
|
|
|
|
auto report_data = BAN::ConstByteSpan(reinterpret_cast<uint8_t*>(dma_buffer->vaddr()), descriptor.wItemLength);
|
|
auto new_collections = TRY(parse_report_descriptor(report_data, m_uses_report_id));
|
|
for (auto& collection : new_collections)
|
|
TRY(collections.push_back(BAN::move(collection)));
|
|
}
|
|
|
|
if (collections.empty())
|
|
{
|
|
dwarnln("No collections specified for HID device");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
TRY(initializes_device_reports(collections));
|
|
|
|
if (has_led_control())
|
|
m_led_region = TRY(DMARegion::create(PAGE_SIZE, PageTable::MemoryType::Normal));
|
|
|
|
for (const auto& endpoint : m_interface.endpoints)
|
|
{
|
|
const auto& desc = endpoint.descriptor;
|
|
|
|
if (!(desc.bEndpointAddress & 0x80))
|
|
continue;
|
|
if ((desc.bmAttributes & 0x03) != 0x03)
|
|
continue;
|
|
|
|
TRY(m_device.configure_endpoint(desc));
|
|
m_data_buffer = TRY(DMARegion::create(desc.wMaxPacketSize & 0x07FF, PageTable::MemoryType::Normal));
|
|
|
|
m_data_endpoint_id = (desc.bEndpointAddress & 0x0F) * 2 + !!(desc.bEndpointAddress & 0x80);
|
|
|
|
break;
|
|
}
|
|
|
|
if (m_data_endpoint_id == 0)
|
|
{
|
|
dwarnln("HID device does not an interrupt IN endpoints");
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
for (auto& report : m_device_reports)
|
|
if (report.device && report.device->initialize().is_error())
|
|
report.device.clear();
|
|
|
|
m_device.send_data_buffer(m_data_endpoint_id, m_data_buffer->paddr(), m_data_buffer->size());
|
|
|
|
return {};
|
|
}
|
|
|
|
static BAN::ErrorOr<void> gather_collection_reports(const USBHID::Collection& collection, BAN::Vector<USBHID::Report>& output, USBHID::Report::Type type)
|
|
{
|
|
for (const auto& entry : collection.entries)
|
|
{
|
|
if (entry.has<USBHID::Collection>())
|
|
{
|
|
TRY(gather_collection_reports(entry.get<USBHID::Collection>(), output, type));
|
|
continue;
|
|
}
|
|
|
|
const auto& report = entry.get<USBHID::Report>();
|
|
if (report.type != type)
|
|
continue;
|
|
|
|
TRY(output.push_back(report));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> USBHIDDriver::initializes_device_reports(const BAN::Vector<USBHID::Collection>& collection_list)
|
|
{
|
|
TRY(m_device_reports.reserve(collection_list.size()));
|
|
|
|
for (size_t i = 0; i < collection_list.size(); i++)
|
|
{
|
|
const auto& collection = collection_list[i];
|
|
|
|
USBHIDDriver::DeviceReport report;
|
|
TRY(gather_collection_reports(collection, report.inputs, USBHID::Report::Type::Input));
|
|
TRY(gather_collection_reports(collection, report.outputs, USBHID::Report::Type::Output));
|
|
|
|
switch (collection.usage_page)
|
|
{
|
|
case 0x01:
|
|
switch (collection.usage_id)
|
|
{
|
|
case 0x02:
|
|
report.device = TRY(BAN::RefPtr<USBMouse>::create());
|
|
dprintln("Initialized an USB Mouse");
|
|
break;
|
|
case 0x04:
|
|
report.device = TRY(BAN::RefPtr<USBJoystick>::create(*this));
|
|
dprintln("Initialized an USB Joystick");
|
|
break;
|
|
case 0x06:
|
|
report.device = TRY(BAN::RefPtr<USBKeyboard>::create(*this));
|
|
dprintln("Initialized an USB Keyboard");
|
|
break;
|
|
default:
|
|
dwarnln("Unsupported generic descript page usage 0x{2H}", collection.usage_id);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x0C:
|
|
switch (collection.usage_id)
|
|
{
|
|
case 0x01:
|
|
report.device = TRY(BAN::RefPtr<USBKeyboard>::create(*this));
|
|
dprintln("Initialized an USB Consumer Control");
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRY(m_device_reports.push_back(BAN::move(report)));
|
|
}
|
|
|
|
for (auto& report : m_device_reports)
|
|
{
|
|
if (!report.device)
|
|
continue;
|
|
DevFileSystem::get().add_device(report.device);
|
|
|
|
uint8_t led_report_ids[0x100 / 8] {};
|
|
for (const auto& output : report.outputs)
|
|
{
|
|
if (output.usage_page != 0x08)
|
|
continue;
|
|
|
|
const auto byte = output.report_id / 8;
|
|
const auto bit = output.report_id % 8;
|
|
if (led_report_ids[byte] & (1u << bit))
|
|
continue;
|
|
led_report_ids[byte] |= (1u << bit);
|
|
|
|
uint32_t report_bits = 0;
|
|
for (const auto& temp : report.outputs)
|
|
if (temp.report_id == output.report_id)
|
|
report_bits += temp.report_size * temp.report_count;
|
|
|
|
TRY(m_led_controls.push_back({
|
|
.report = &m_device_reports.back(),
|
|
.report_id = output.report_id,
|
|
.report_bits = report_bits,
|
|
}));
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void USBHIDDriver::handle_stall(uint8_t endpoint_id)
|
|
{
|
|
(void)endpoint_id;
|
|
// FIXME: do something :)
|
|
}
|
|
|
|
void USBHIDDriver::handle_input_data(size_t byte_count, uint8_t endpoint_id)
|
|
{
|
|
if (m_data_endpoint_id != endpoint_id)
|
|
return;
|
|
|
|
auto data = BAN::ConstByteSpan(reinterpret_cast<uint8_t*>(m_data_buffer->vaddr()), byte_count);
|
|
BAN::ScopeGuard _([&] {
|
|
m_device.send_data_buffer(m_data_endpoint_id, m_data_buffer->paddr(), m_data_buffer->size());
|
|
});
|
|
|
|
if constexpr(DEBUG_USB_HID)
|
|
{
|
|
const auto nibble_to_hex = [](uint8_t x) -> char { return x + (x < 10 ? '0' : 'A' - 10); };
|
|
|
|
char buffer[512];
|
|
char* ptr = buffer;
|
|
for (size_t i = 0; i < BAN::Math::min<size_t>((sizeof(buffer) - 1) / 3, data.size()); i++)
|
|
{
|
|
*ptr++ = nibble_to_hex(data[i] >> 4);
|
|
*ptr++ = nibble_to_hex(data[i] & 0xF);
|
|
*ptr++ = ' ';
|
|
}
|
|
*ptr = '\0';
|
|
|
|
dprintln_if(DEBUG_USB_HID, "Received {} bytes from endpoint {}: {}", data.size(), endpoint_id, buffer);
|
|
}
|
|
|
|
const auto extract_bits =
|
|
[&data](size_t bit_offset, size_t bit_count, bool as_unsigned) -> int64_t
|
|
{
|
|
if (bit_offset >= data.size() * 8)
|
|
return 0;
|
|
if (bit_count + bit_offset > data.size() * 8)
|
|
bit_count = data.size() * 8 - bit_offset;
|
|
|
|
uint32_t result = 0;
|
|
uint32_t result_offset = 0;
|
|
|
|
while (result_offset < bit_count)
|
|
{
|
|
const uint32_t byte = bit_offset / 8;
|
|
const uint32_t bit = bit_offset % 8;
|
|
const uint32_t count = BAN::Math::min<uint32_t>(bit_count - result_offset, 8 - bit);
|
|
const uint32_t mask = (1 << count) - 1;
|
|
|
|
result |= static_cast<uint32_t>((data[byte] >> bit) & mask) << result_offset;
|
|
|
|
bit_offset += count;
|
|
result_offset += count;
|
|
}
|
|
|
|
if (!as_unsigned && (result & (1u << (bit_count - 1))))
|
|
{
|
|
const uint32_t mask = (1u << bit_count) - 1;
|
|
return -(static_cast<int64_t>(~result & mask) + 1);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
BAN::Optional<uint8_t> report_id;
|
|
if (m_uses_report_id)
|
|
{
|
|
report_id = data[0];
|
|
data = data.slice(1);
|
|
}
|
|
|
|
size_t bit_offset = 0;
|
|
for (auto& device_report : m_device_reports)
|
|
{
|
|
if (device_report.device)
|
|
device_report.device->start_report();
|
|
|
|
for (const auto& input : device_report.inputs)
|
|
{
|
|
if (report_id.value_or(input.report_id) != input.report_id)
|
|
continue;
|
|
|
|
ASSERT(input.report_size <= 32);
|
|
|
|
if (!device_report.device || (input.usage_id == 0 && input.usage_minimum == 0 && input.usage_maximum == 0))
|
|
{
|
|
bit_offset += input.report_size * input.report_count;
|
|
continue;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < input.report_count; i++)
|
|
{
|
|
const int64_t logical = extract_bits(bit_offset, input.report_size, input.logical_minimum >= 0);
|
|
if (logical < input.logical_minimum || logical > input.logical_maximum)
|
|
{
|
|
bit_offset += input.report_size;
|
|
continue;
|
|
}
|
|
|
|
const bool relative = !!(input.flags & 0x04);
|
|
const bool variable = !!(input.flags & 0x02);
|
|
|
|
const auto usage = input.usage_id ? input.usage_id : (input.usage_minimum + (variable ? i : logical));
|
|
|
|
if (!variable)
|
|
device_report.device->handle_array(input.usage_page, usage);
|
|
else
|
|
{
|
|
const int64_t physical =
|
|
(input.physical_maximum - input.physical_minimum) *
|
|
(logical - input.logical_minimum) /
|
|
(input.logical_maximum - input.logical_minimum) +
|
|
input.physical_minimum;
|
|
|
|
if (relative)
|
|
device_report.device->handle_variable(input.usage_page, usage, physical);
|
|
else
|
|
device_report.device->handle_variable_absolute(input.usage_page, usage, physical, input.physical_minimum, input.physical_maximum);
|
|
}
|
|
|
|
bit_offset += input.report_size;
|
|
}
|
|
}
|
|
|
|
if (device_report.device)
|
|
device_report.device->stop_report();
|
|
}
|
|
}
|
|
|
|
void USBHIDDriver::set_leds(uint32_t led_mask)
|
|
{
|
|
for (const auto& led_control : m_led_controls)
|
|
{
|
|
const size_t report_bytes = BAN::Math::div_round_up<uint32_t>(led_control.report_bits, 8);
|
|
|
|
auto led_data = BAN::ByteSpan(reinterpret_cast<uint8_t*>(m_led_region->vaddr()), report_bytes);
|
|
memset(led_data.data(), 0, report_bytes);
|
|
|
|
size_t bit_offset = 0;
|
|
for (const auto& output : led_control.report->outputs)
|
|
{
|
|
if (output.report_id != led_control.report_id)
|
|
continue;
|
|
|
|
const size_t usage_base = output.usage_id ? output.usage_id : output.usage_minimum;
|
|
for (size_t i = 0; output.report_size == 1 && i < output.report_count; i++, bit_offset++)
|
|
if (led_mask & (1u << (usage_base + bit_offset)))
|
|
led_data[bit_offset / 8] |= 1u << (bit_offset % 8);
|
|
|
|
bit_offset += output.report_size * output.report_count;
|
|
}
|
|
|
|
USBDeviceRequest request;
|
|
request.bmRequestType = USB::RequestType::HostToDevice | USB::RequestType::Class | USB::RequestType::Interface;
|
|
request.bRequest = 0x09;
|
|
request.wValue = 0x0200 | led_control.report_id;
|
|
request.wIndex = m_interface.descriptor.bInterfaceNumber;
|
|
request.wLength = report_bytes;
|
|
if (auto ret = m_device.send_request(request, m_led_region->paddr()); ret.is_error())
|
|
dprintln_if(DEBUG_USB_HID, "Failed to update LEDs: {}", ret.error());
|
|
}
|
|
}
|
|
|
|
BAN::ErrorOr<BAN::Vector<Collection>> parse_report_descriptor(BAN::ConstByteSpan report_data, bool& out_use_report_id)
|
|
{
|
|
BAN::Vector<GlobalState> global_stack;
|
|
GlobalState global_state;
|
|
|
|
LocalState local_state;
|
|
|
|
BAN::Vector<Collection> result_stack;
|
|
BAN::Vector<Collection> collection_stack;
|
|
|
|
bool one_has_report_id = false;
|
|
bool all_has_report_id = true;
|
|
|
|
const auto extract_report_item =
|
|
[&](bool as_unsigned) -> int64_t
|
|
{
|
|
uint32_t value = 0;
|
|
auto value_data = report_data.slice(1);
|
|
switch (report_data[0] & 0x03)
|
|
{
|
|
case 1: value = as_unsigned ? value_data.as<const uint8_t>() : value_data.as<const int8_t>(); break;
|
|
case 2: value = as_unsigned ? value_data.as<const uint16_t>() : value_data.as<const int16_t>(); break;
|
|
case 3: value = as_unsigned ? value_data.as<const uint32_t>() : value_data.as<const int32_t>(); break;
|
|
}
|
|
return value;
|
|
};
|
|
|
|
constexpr auto get_correct_sign =
|
|
[](int64_t min, int64_t max_signed, int64_t max_unsigned) -> int64_t
|
|
{
|
|
if (min < 0 || max_signed >= 0)
|
|
return max_signed;
|
|
return max_unsigned;
|
|
};
|
|
|
|
const auto add_data_item =
|
|
[&](Report::Type type, uint32_t item_data, BAN::Vector<BAN::Variant<Collection, Report>>& container) -> BAN::ErrorOr<void>
|
|
{
|
|
if (!global_state.report_count.has_value() || !global_state.report_size.has_value())
|
|
{
|
|
dwarnln("Report count and/or report size is not defined");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (!global_state.usage_page.has_value())
|
|
{
|
|
dwarnln("Usage page is not defined");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (!global_state.logical_minimum.has_value() || !global_state.logical_maximum_signed.has_value())
|
|
{
|
|
dwarnln("Logical minimum and/or logical maximum is not defined");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (global_state.physical_minimum.has_value() != global_state.physical_minimum.has_value())
|
|
{
|
|
dwarnln("Only one of physical minimum and physical maximum is defined");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (local_state.usage_minimum.has_value() != local_state.usage_maximum.has_value())
|
|
{
|
|
dwarnln("Only one of logical minimum and logical maximum is defined");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
if (global_state.report_id.has_value())
|
|
one_has_report_id = true;
|
|
else
|
|
all_has_report_id = false;
|
|
|
|
const int64_t logical_minimum = global_state.logical_minimum.value();
|
|
const int64_t logical_maximum = get_correct_sign(
|
|
global_state.logical_minimum.value(),
|
|
global_state.logical_maximum_signed.value(),
|
|
global_state.logical_maximum_unsigned.value()
|
|
);
|
|
|
|
int64_t physical_minimum = logical_minimum;
|
|
int64_t physical_maximum = logical_maximum;
|
|
if (global_state.physical_minimum.has_value() && (global_state.physical_minimum.value() || global_state.physical_maximum.value()))
|
|
{
|
|
physical_minimum = global_state.physical_minimum.value();
|
|
physical_maximum = global_state.physical_maximum.value();
|
|
}
|
|
|
|
if (local_state.usage_stack.empty())
|
|
{
|
|
if (local_state.usage_minimum.has_value() && local_state.usage_maximum.has_value())
|
|
{
|
|
Report item;
|
|
item.usage_page = global_state.usage_page.value();
|
|
item.usage_id = 0;
|
|
item.usage_minimum = local_state.usage_minimum.value();
|
|
item.usage_maximum = local_state.usage_maximum.value();
|
|
item.type = type;
|
|
item.report_id = global_state.report_id.value_or(0);
|
|
item.report_count = global_state.report_count.value();
|
|
item.report_size = global_state.report_size.value();
|
|
item.logical_minimum = logical_minimum;
|
|
item.logical_maximum = logical_maximum;
|
|
item.physical_minimum = physical_minimum;
|
|
item.physical_maximum = physical_maximum;
|
|
item.flags = item_data;
|
|
TRY(container.push_back(item));
|
|
|
|
return {};
|
|
}
|
|
|
|
Report item;
|
|
item.usage_page = global_state.usage_page.value();
|
|
item.usage_id = 0;
|
|
item.usage_minimum = 0;
|
|
item.usage_maximum = 0;
|
|
item.type = type;
|
|
item.report_id = global_state.report_id.value_or(0);
|
|
item.report_count = global_state.report_count.value();
|
|
item.report_size = global_state.report_size.value();
|
|
item.logical_minimum = 0;
|
|
item.logical_maximum = 0;
|
|
item.physical_minimum = 0;
|
|
item.physical_maximum = 0;
|
|
item.flags = item_data;
|
|
TRY(container.push_back(item));
|
|
|
|
return {};
|
|
}
|
|
|
|
for (size_t i = 0; i < local_state.usage_stack.size(); i++)
|
|
{
|
|
const uint32_t usage = local_state.usage_stack[i];
|
|
const uint32_t count = (i + 1 < local_state.usage_stack.size()) ? 1 : global_state.report_count.value() - i;
|
|
|
|
Report item;
|
|
item.usage_page = (usage >> 16) ? (usage >> 16) : global_state.usage_page.value();
|
|
item.usage_id = usage & 0xFFFF;
|
|
item.usage_minimum = 0;
|
|
item.usage_maximum = 0;
|
|
item.type = type;
|
|
item.report_id = global_state.report_id.value_or(0);
|
|
item.report_count = count;
|
|
item.report_size = global_state.report_size.value();
|
|
item.logical_minimum = logical_minimum;
|
|
item.logical_maximum = logical_maximum;
|
|
item.physical_minimum = physical_minimum;
|
|
item.physical_maximum = physical_maximum;
|
|
item.flags = item_data;
|
|
TRY(container.push_back(item));
|
|
}
|
|
|
|
return {};
|
|
};
|
|
|
|
while (report_data.size() > 0)
|
|
{
|
|
const uint8_t item_size = report_data[0] & 0x03;
|
|
const uint8_t item_type = (report_data[0] >> 2) & 0x03;
|
|
const uint8_t item_tag = (report_data[0] >> 4) & 0x0F;
|
|
|
|
if (item_type == 0)
|
|
{
|
|
switch (item_tag)
|
|
{
|
|
case 0b0000:
|
|
break;
|
|
case 0b1000: // input
|
|
if (collection_stack.empty())
|
|
{
|
|
dwarnln("Invalid input item outside of collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
TRY(add_data_item(Report::Type::Input, extract_report_item(true), collection_stack.back().entries));
|
|
break;
|
|
case 0b1001: // output
|
|
if (collection_stack.empty())
|
|
{
|
|
dwarnln("Invalid input item outside of collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
TRY(add_data_item(Report::Type::Output, extract_report_item(true), collection_stack.back().entries));
|
|
break;
|
|
case 0b1011: // feature
|
|
if (collection_stack.empty())
|
|
{
|
|
dwarnln("Invalid input item outside of collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
TRY(add_data_item(Report::Type::Feature, extract_report_item(true), collection_stack.back().entries));
|
|
break;
|
|
case 0b1010: // collection
|
|
{
|
|
if (local_state.usage_stack.size() > 1)
|
|
{
|
|
dwarnln("Multiple usages specified for a collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
uint16_t usage_page = 0;
|
|
if (global_state.usage_page.has_value())
|
|
usage_page = global_state.usage_page.value();
|
|
if (!local_state.usage_stack.empty() && local_state.usage_stack.front() >> 16)
|
|
usage_page = local_state.usage_stack.front() >> 16;
|
|
if (usage_page == 0)
|
|
{
|
|
dwarnln("Usage page not specified for a collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
TRY(collection_stack.emplace_back());
|
|
collection_stack.back().type = report_data[1];
|
|
collection_stack.back().usage_page = usage_page;
|
|
if (!local_state.usage_stack.empty())
|
|
collection_stack.back().usage_id = local_state.usage_stack.front();
|
|
|
|
break;
|
|
}
|
|
case 0b1100: // end collection
|
|
if (collection_stack.empty())
|
|
{
|
|
dwarnln("End collection outside of collection");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (collection_stack.size() == 1)
|
|
{
|
|
TRY(result_stack.push_back(BAN::move(collection_stack.back())));
|
|
collection_stack.pop_back();
|
|
}
|
|
else
|
|
{
|
|
TRY(collection_stack[collection_stack.size() - 2].entries.push_back(BAN::move(collection_stack.back())));
|
|
collection_stack.pop_back();
|
|
}
|
|
break;
|
|
default:
|
|
dwarnln("Report has reserved main item tag 0b{4b}", item_tag);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
local_state = LocalState();
|
|
}
|
|
else if (item_type == 1)
|
|
{
|
|
switch (item_tag)
|
|
{
|
|
case 0b0000: // usage page
|
|
global_state.usage_page = extract_report_item(true);
|
|
break;
|
|
case 0b0001: // logical minimum
|
|
global_state.logical_minimum = extract_report_item(false);
|
|
break;
|
|
case 0b0010: // logical maximum
|
|
global_state.logical_maximum_signed = extract_report_item(false);
|
|
global_state.logical_maximum_unsigned = extract_report_item(true);
|
|
break;
|
|
case 0b0011: // physical minimum
|
|
global_state.physical_minimum = extract_report_item(false);
|
|
break;
|
|
case 0b0100: // physical maximum
|
|
global_state.physical_maximum = extract_report_item(false);
|
|
break;
|
|
case 0b0101: // unit exponent
|
|
dwarnln("Report units are not supported");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
case 0b0110: // unit
|
|
dwarnln("Report units are not supported");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
case 0b0111: // report size
|
|
global_state.report_size = extract_report_item(true);
|
|
break;
|
|
case 0b1000: // report id
|
|
{
|
|
auto report_id = extract_report_item(true);
|
|
if (report_id > 0xFF)
|
|
{
|
|
dwarnln("Multi-byte report id");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
global_state.report_id = report_id;
|
|
break;
|
|
}
|
|
case 0b1001: // report count
|
|
global_state.report_count = extract_report_item(true);
|
|
break;
|
|
case 0b1010: // push
|
|
TRY(global_stack.push_back(global_state));
|
|
break;
|
|
case 0b1011: // pop
|
|
if (global_stack.empty())
|
|
{
|
|
dwarnln("Report pop from empty stack");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
global_state = global_stack.back();
|
|
global_stack.pop_back();
|
|
break;
|
|
default:
|
|
dwarnln("Report has reserved global item tag 0b{4b}", item_tag);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
}
|
|
else if (item_type == 2)
|
|
{
|
|
switch (item_tag)
|
|
{
|
|
case 0b0000: // usage
|
|
TRY(local_state.usage_stack.emplace_back(extract_report_item(true)));
|
|
break;
|
|
case 0b0001: // usage minimum
|
|
local_state.usage_minimum = extract_report_item(true);
|
|
break;
|
|
case 0b0010: // usage maximum
|
|
local_state.usage_maximum = extract_report_item(true);
|
|
break;
|
|
case 0b0011: // designator index
|
|
case 0b0100: // designator minimum
|
|
case 0b0101: // designator maximum
|
|
case 0b0111: // string index
|
|
case 0b1000: // string minimum
|
|
case 0b1001: // string maximum
|
|
case 0b1010: // delimeter
|
|
dwarnln("Unsupported local item tag 0b{4b}", item_tag);
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
default:
|
|
dwarnln("Report has reserved local item tag 0b{4b}", item_tag);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwarnln("Report has reserved item type 0b{2b}", item_type);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
report_data = report_data.slice(1 + item_size);
|
|
}
|
|
|
|
if (result_stack.empty())
|
|
{
|
|
dwarnln("No collection defined in report descriptor");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
if (one_has_report_id != all_has_report_id)
|
|
{
|
|
dwarnln("Some but not all reports have report id");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
#if DUMP_HID_REPORT
|
|
{
|
|
SpinLockGuard _(Debug::s_debug_lock);
|
|
for (const auto& collection : result_stack)
|
|
dump_hid_collection(collection, 0, one_has_report_id);
|
|
}
|
|
#endif
|
|
|
|
out_use_report_id = one_has_report_id;
|
|
return BAN::move(result_stack);
|
|
}
|
|
|
|
#if DUMP_HID_REPORT
|
|
static void print_indent(size_t indent)
|
|
{
|
|
for (size_t i = 0; i < indent; i++)
|
|
Debug::putchar(' ');
|
|
}
|
|
|
|
static void dump_hid_report(const Report& report, size_t indent, bool use_report_id)
|
|
{
|
|
const char* report_type = "";
|
|
switch (report.type)
|
|
{
|
|
case Report::Type::Input: report_type = "input"; break;
|
|
case Report::Type::Output: report_type = "output"; break;
|
|
case Report::Type::Feature: report_type = "feature"; break;
|
|
}
|
|
print_indent(indent);
|
|
BAN::Formatter::println(Debug::putchar, "report {}", report_type);
|
|
|
|
if (use_report_id)
|
|
{
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "report id: {2H}", report.report_id);
|
|
}
|
|
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "usage page: {2H}", report.usage_page);
|
|
|
|
if (report.usage_id || report.usage_minimum || report.usage_maximum)
|
|
{
|
|
print_indent(indent + 4);
|
|
if (report.usage_id)
|
|
BAN::Formatter::println(Debug::putchar, "usage: {2H}", report.usage_id);
|
|
else
|
|
BAN::Formatter::println(Debug::putchar, "usage: {2H}->{2H}", report.usage_minimum, report.usage_maximum);
|
|
}
|
|
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "flags: 0b{8b}", report.flags);
|
|
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "size: {}", report.report_size);
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "count: {}", report.report_count);
|
|
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "lminimum: {}", report.logical_minimum);
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "lmaximum: {}", report.logical_maximum);
|
|
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "pminimum: {}", report.physical_minimum);
|
|
print_indent(indent + 4);
|
|
BAN::Formatter::println(Debug::putchar, "pmaximum: {}", report.physical_maximum);
|
|
}
|
|
|
|
static void dump_hid_collection(const Collection& collection, size_t indent, bool use_report_id)
|
|
{
|
|
print_indent(indent);
|
|
BAN::Formatter::println(Debug::putchar, "collection {}", collection.type);
|
|
print_indent(indent);
|
|
BAN::Formatter::println(Debug::putchar, "usage {H}:{H}", collection.usage_page, collection.usage_id);
|
|
|
|
for (const auto& entry : collection.entries)
|
|
{
|
|
if (entry.has<Collection>())
|
|
dump_hid_collection(entry.get<Collection>(), indent + 4, use_report_id);
|
|
if (entry.has<Report>())
|
|
dump_hid_report(entry.get<Report>(), indent + 4, use_report_id);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|