| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "garnet/bin/ui/input_reader/fdio_hid_decoder.h" |
| |
| #include <unistd.h> |
| |
| #include <fuchsia/hardware/input/c/fidl.h> |
| #include <hid-parser/parser.h> |
| #include <hid-parser/usages.h> |
| #include <hid/acer12.h> |
| #include <hid/egalax.h> |
| #include <hid/eyoyo.h> |
| #include <hid/ft3x27.h> |
| #include <hid/paradise.h> |
| #include <hid/samsung.h> |
| #include <lib/fxl/arraysize.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fzl/fdio.h> |
| #include <zircon/device/device.h> |
| |
| namespace { |
| |
| // Variable to quickly re-enable the hardcoded touchpad reports. |
| // TODO(ZX-3219): Remove this once touchpads are stable |
| bool USE_TOUCHPAD_HARDCODED_REPORTS = false; |
| |
| bool log_err(ssize_t rc, const std::string& what, const std::string& name) { |
| FXL_LOG(ERROR) << "hid: could not get " << what << " from " << name |
| << " (status=" << rc << ")"; |
| return false; |
| } |
| |
| // TODO(SCN-843): We need to generalize these extraction functions |
| |
| // Casting from unsigned to signed can change the bit pattern so |
| // we need to resort to this method. |
| int8_t signed_bit_cast(uint8_t src) { |
| int8_t dest; |
| memcpy(&dest, &src, sizeof(uint8_t)); |
| return dest; |
| } |
| |
| // Extracts a up to 8 bits unsigned number from a byte vector |v|. |
| // Both |begin| and |count| are in bits units. This function does not |
| // check for the vector being long enough. |
| static uint8_t extract_uint8(const std::vector<uint8_t>& v, uint32_t begin, |
| uint32_t count) { |
| uint8_t val = v[begin / 8u] >> (begin % 8u); |
| return (count < 8) ? (val & ~(1u << count)) : val; |
| } |
| |
| // Extracts a 16 bits unsigned number from a byte vector |v|. |
| // |begin| is in bits units. This function does not check for the vector |
| // being long enough. |
| static uint16_t extract_uint16(const std::vector<uint8_t>& v, uint32_t begin) { |
| return static_cast<uint16_t>(extract_uint8(v, begin, 8)) | |
| static_cast<uint16_t>(extract_uint8(v, begin + 8, 8)) << 8; |
| } |
| |
| // Extracts up to 8 bits sign extended to int32_t from a byte vector |v|. |
| // Both |begin| and |count| are in bits units. This function does not |
| // check for the vector being long enough. |
| static int32_t extract_int8_ext(const std::vector<uint8_t>& v, uint32_t begin, |
| uint32_t count) { |
| uint8_t val = extract_uint8(v, begin, count); |
| return signed_bit_cast(val); |
| } |
| |
| } // namespace |
| |
| namespace mozart { |
| |
| FdioHidDecoder::FdioHidDecoder(const std::string& name, fbl::unique_fd fd) |
| : fd_(std::move(fd)), name_(name), protocol_(Protocol::Other) {} |
| |
| FdioHidDecoder::~FdioHidDecoder() = default; |
| |
| bool FdioHidDecoder::Init() { |
| // |fzl::FdioCaller| expects full temporary ownership of the the file |
| // descriptor, but it doesn't actually require it. We still need the file |
| // descriptor to set up some devices in |ParseProtocol| using C setup |
| // functions. They do the same thing as |fzl::FdioCaller|, in particular |
| // |fzl::FdioCaller::borrow_channel()|. They do both take references to the |
| // corresponding |fdio_t|, but that is refcounted. |
| // |
| // Since this is really unsafe wrt. |fd_|, we need to be sure to relinquish |
| // ownership by |caller| when we're done. |
| fzl::FdioCaller caller(fbl::unique_fd(fd_.get())); |
| |
| bool success = ParseProtocol(caller, &protocol_); |
| if (!success) { |
| caller.release().release(); |
| return false; |
| } |
| |
| uint16_t max_len = 0; |
| zx_status_t status = fuchsia_hardware_input_DeviceGetMaxInputReportSize( |
| caller.borrow_channel(), &max_len); |
| caller.release().release(); |
| if (status != ZX_OK) { |
| return false; |
| } |
| |
| report_.resize(max_len); |
| return true; |
| } |
| |
| zx::event FdioHidDecoder::GetEvent() { |
| zx::event event; |
| |
| ssize_t rc = |
| ioctl_device_get_event_handle(fd_.get(), event.reset_and_get_address()); |
| if (rc < 0) { |
| log_err(rc, "event handle", name_); |
| return {}; |
| } else { |
| return event; |
| } |
| } |
| |
| FdioHidDecoder::Protocol ExtractProtocol(hid::Usage input) { |
| using ::hid::usage::Consumer; |
| using ::hid::usage::Digitizer; |
| using ::hid::usage::Page; |
| using ::hid::usage::Sensor; |
| struct { |
| hid::Usage usage; |
| FdioHidDecoder::Protocol protocol; |
| } usage_to_protocol[] = { |
| {{static_cast<uint16_t>(Page::kSensor), |
| static_cast<uint32_t>(Sensor::kAmbientLight)}, |
| HidDecoder::Protocol::LightSensor}, |
| {{static_cast<uint16_t>(Page::kConsumer), |
| static_cast<uint32_t>(Consumer::kConsumerControl)}, |
| HidDecoder::Protocol::Buttons}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kTouchScreen)}, |
| HidDecoder::Protocol::Touch}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kTouchPad)}, |
| HidDecoder::Protocol::Touchpad}, |
| // Add more sensors here |
| }; |
| for (auto& j : usage_to_protocol) { |
| if (input.page == j.usage.page && input.usage == j.usage.usage) { |
| return j.protocol; |
| } |
| } |
| return FdioHidDecoder::Protocol::Other; |
| } |
| |
| bool FdioHidDecoder::ParseProtocol(const fzl::FdioCaller& caller, |
| Protocol* protocol) { |
| zx_handle_t svc = caller.borrow_channel(); |
| |
| fuchsia_hardware_input_BootProtocol boot_protocol; |
| zx_status_t status = |
| fuchsia_hardware_input_DeviceGetBootProtocol(svc, &boot_protocol); |
| if (status != ZX_OK) |
| return log_err(status, "ioctl protocol", name_); |
| |
| // For most keyboards and mouses Zircon requests the boot protocol |
| // which has a fixed layout. This covers the following two cases: |
| |
| if (boot_protocol == fuchsia_hardware_input_BootProtocol_KBD) { |
| *protocol = Protocol::Keyboard; |
| return true; |
| } |
| if (boot_protocol == fuchsia_hardware_input_BootProtocol_MOUSE) { |
| *protocol = Protocol::Mouse; |
| return true; |
| } |
| |
| // For the rest of devices (fuchsia_hardware_input_BootProtocol_NONE) we need |
| // to parse the report descriptor. The legacy method involves memcmp() of |
| // known descriptors which cover the next 8 devices: |
| |
| uint16_t report_desc_len; |
| status = |
| fuchsia_hardware_input_DeviceGetReportDescSize(svc, &report_desc_len); |
| if (status != ZX_OK) |
| return log_err(status, "report descriptor length", name_); |
| |
| std::vector<uint8_t> desc(report_desc_len); |
| size_t actual; |
| status = fuchsia_hardware_input_DeviceGetReportDesc(svc, desc.data(), |
| desc.size(), &actual); |
| if (status != ZX_OK) |
| return log_err(status, "report descriptor", name_); |
| desc.resize(actual); |
| |
| if (is_acer12_touch_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::Acer12Touch; |
| return true; |
| } |
| if (is_samsung_touch_report_desc(desc.data(), desc.size())) { |
| setup_samsung_touch(fd_.get()); |
| *protocol = Protocol::SamsungTouch; |
| return true; |
| } |
| if (is_paradise_touch_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseV1Touch; |
| return true; |
| } |
| if (is_paradise_touch_v2_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseV2Touch; |
| return true; |
| } |
| if (is_paradise_touch_v3_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseV3Touch; |
| return true; |
| } |
| if (USE_TOUCHPAD_HARDCODED_REPORTS) { |
| if (is_paradise_touchpad_v1_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseV1TouchPad; |
| return true; |
| } |
| if (is_paradise_touchpad_v2_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseV2TouchPad; |
| return true; |
| } |
| } |
| if (is_egalax_touchscreen_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::EgalaxTouch; |
| return true; |
| } |
| if (is_paradise_sensor_report_desc(desc.data(), desc.size())) { |
| *protocol = Protocol::ParadiseSensor; |
| return true; |
| } |
| if (is_eyoyo_touch_report_desc(desc.data(), desc.size())) { |
| setup_eyoyo_touch(fd_.get()); |
| *protocol = Protocol::EyoyoTouch; |
| return true; |
| } |
| // TODO(SCN-867) Use HID parsing for all touch devices |
| // will remove the need for this |
| if (is_ft3x27_touch_report_desc(desc.data(), desc.size())) { |
| setup_ft3x27_touch(fd_.get()); |
| *protocol = Protocol::Ft3x27Touch; |
| return true; |
| } |
| |
| // For the rest of devices we use the new way; with the hid-parser |
| // library. |
| |
| hid::DeviceDescriptor* dev_desc = nullptr; |
| auto parse_res = |
| hid::ParseReportDescriptor(desc.data(), desc.size(), &dev_desc); |
| if (parse_res != hid::ParseResult::kParseOk) { |
| FXL_LOG(ERROR) << "hid-parser: error " << int(parse_res) |
| << " parsing report descriptor for " << name_; |
| return false; |
| } |
| |
| auto count = dev_desc->rep_count; |
| if (count == 0) { |
| FXL_LOG(ERROR) << "no report descriptors for " << name_; |
| return false; |
| } |
| |
| // Find the first input report. |
| const hid::ReportDescriptor* input_desc = nullptr; |
| for (size_t rep = 0; rep < count; rep++) { |
| const hid::ReportDescriptor* desc = &dev_desc->report[rep]; |
| if (desc->first_field[0].type == hid::kInput) { |
| input_desc = desc; |
| break; |
| } |
| } |
| |
| if (input_desc == nullptr) { |
| FXL_LOG(ERROR) << "no input report fields for " << name_; |
| return false; |
| } |
| |
| // Traverse up the nested collections to the Application collection. |
| auto collection = input_desc->first_field[0].col; |
| while (collection != nullptr) { |
| if (collection->type == hid::CollectionType::kApplication) { |
| break; |
| } |
| collection = collection->parent; |
| } |
| |
| if (collection == nullptr) { |
| FXL_LOG(ERROR) << "invalid hid collection for " << name_; |
| return false; |
| } |
| |
| FXL_LOG(INFO) << "hid-parser succesful for " << name_ << " with usage page " |
| << collection->usage.page << " and usage " |
| << collection->usage.usage; |
| |
| // Most modern gamepads report themselves as Joysticks. Madness. |
| if (collection->usage.page == hid::usage::Page::kGenericDesktop && |
| collection->usage.usage == hid::usage::GenericDesktop::kJoystick && |
| ParseGamepadDescriptor(input_desc->first_field, input_desc->count)) { |
| protocol_ = Protocol::Gamepad; |
| } else { |
| protocol_ = ExtractProtocol(collection->usage); |
| switch (protocol_) { |
| case Protocol::LightSensor: |
| ParseAmbientLightDescriptor(input_desc->first_field, input_desc->count); |
| break; |
| case Protocol::Buttons: |
| ParseButtonsDescriptor(input_desc->first_field, input_desc->count); |
| break; |
| case Protocol::Touchpad: |
| // Fallthrough |
| case Protocol::Touch: { |
| bool success = ts_.ParseTouchscreenDescriptor(input_desc); |
| if (!success) { |
| FXL_LOG(ERROR) << "invalid touchscreen descriptor for " << name_; |
| return false; |
| } |
| break; |
| } |
| // Add more protocols here |
| default: |
| break; |
| } |
| } |
| |
| *protocol = protocol_; |
| return true; |
| } |
| |
| bool FdioHidDecoder::ParseGamepadDescriptor(const hid::ReportField* fields, |
| size_t count) { |
| // Need to recover the five fields as seen in HidGamepadSimple and put |
| // them into the decoder_ in the same order. |
| if (count < 5u) |
| return false; |
| |
| decoder_.resize(6u); |
| uint8_t offset = 0; |
| |
| if (fields[0].report_id != 0) { |
| // If exists, the first entry (8-bits) is always the report id and |
| // all items start after the first byte. |
| decoder_[0] = DataLocator{0u, 8u, fields[0].report_id}; |
| offset = 8u; |
| } |
| |
| // Needs to be kept in sync with HidGamepadSimple {}. |
| const uint16_t table[] = { |
| static_cast<uint16_t>(hid::usage::GenericDesktop::kX), // left X. |
| static_cast<uint16_t>(hid::usage::GenericDesktop::kY), // left Y. |
| static_cast<uint16_t>(hid::usage::GenericDesktop::kZ), // right X. |
| static_cast<uint16_t>(hid::usage::GenericDesktop::kRz), // right Y. |
| static_cast<uint16_t>(hid::usage::GenericDesktop::kHatSwitch) // buttons |
| }; |
| |
| uint32_t bit_count = 0; |
| |
| // Traverse each input report field and see if there is a match in the table. |
| // If so place the location in |decoder_| array. |
| for (size_t ix = 0; ix != count; ix++) { |
| if (fields[ix].type != hid::kInput) |
| continue; |
| |
| for (size_t iy = 0; iy != arraysize(table); iy++) { |
| if (fields[ix].attr.usage.usage == table[iy]) { |
| // Found a required usage. |
| decoder_[iy + 1] = |
| DataLocator{bit_count + offset, fields[ix].attr.bit_sz, 0}; |
| break; |
| } |
| } |
| |
| bit_count += fields[ix].attr.bit_sz; |
| } |
| |
| // Here |decoder_| should look like this: |
| // [rept_id][left X][left Y]....[hat_sw] |
| // With each box, the location in a report for each item, for example: |
| // [0, 0, 0][24, 0, 0][8, 0, 0][0, 0, 0]...[64, 4, 0] |
| return true; |
| } |
| |
| bool FdioHidDecoder::ParseAmbientLightDescriptor(const hid::ReportField* fields, |
| size_t count) { |
| if (count == 0u) |
| return false; |
| |
| decoder_.resize(2u); |
| uint8_t offset = 0; |
| |
| if (fields[0].report_id != 0) { |
| // If exists, the first entry (8-bits) is always the report id and |
| // all items start after the first byte. |
| decoder_[0] = DataLocator{0u, 8u, fields[0].report_id}; |
| offset = 8u; |
| } |
| |
| uint32_t bit_count = 0; |
| |
| // Traverse each input report field and see if there is a match in the table. |
| // If so place the location in |decoder_| array. |
| for (size_t ix = 0; ix != count; ix++) { |
| if (fields[ix].type != hid::kInput) |
| continue; |
| |
| if (fields[ix].attr.usage.usage == hid::usage::Sensor::kLightIlluminance) { |
| decoder_[1] = DataLocator{bit_count + offset, fields[ix].attr.bit_sz, 0}; |
| // Found a required usage. |
| // Here |decoder_| should look like this: |
| // [rept_id][abs_light] |
| return true; |
| } |
| |
| bit_count += fields[ix].attr.bit_sz; |
| } |
| return false; |
| } |
| |
| bool FdioHidDecoder::ParseButtonsDescriptor(const hid::ReportField* fields, |
| size_t count) { |
| if (count == 0u) |
| return false; |
| |
| decoder_.resize(3u); |
| uint8_t offset = 0; |
| |
| if (fields[0].report_id != 0) { |
| // If exists, the first entry (8-bits) is always the report id and |
| // all items start after the first byte. |
| decoder_[0] = DataLocator{0u, 8u, fields[0].report_id}; |
| offset = 8u; |
| } |
| |
| // Needs to be kept in sync with HidButtons {}. |
| const uint16_t table[] = { |
| static_cast<uint16_t>(hid::usage::Consumer::kVolume), |
| static_cast<uint16_t>(hid::usage::Telephony::kPhoneMute), |
| }; |
| |
| uint32_t bit_count = 0; |
| |
| // Traverse each input report field and see if there is a match in the table. |
| // If so place the location in |decoder_| array. |
| for (size_t ix = 0; ix != count; ix++) { |
| if (fields[ix].type != hid::kInput) |
| continue; |
| |
| for (size_t iy = 0; iy != arraysize(table); iy++) { |
| if (fields[ix].attr.usage.usage == table[iy]) { |
| // Found a required usage. |
| decoder_[iy + 1] = |
| DataLocator{bit_count + offset, fields[ix].attr.bit_sz, 0}; |
| break; |
| } |
| } |
| |
| bit_count += fields[ix].attr.bit_sz; |
| } |
| |
| // Here |decoder_| should look like this: |
| // [rept_id][volume][mic_mute] |
| return true; |
| } |
| |
| const std::vector<uint8_t>& FdioHidDecoder::Read(int* bytes_read) { |
| *bytes_read = read(fd_.get(), report_.data(), report_.size()); |
| return report_; |
| } |
| |
| bool FdioHidDecoder::Read(HidGamepadSimple* gamepad) { |
| if (protocol_ != Protocol::Gamepad) |
| return false; |
| |
| int rc; |
| auto report = Read(&rc); |
| if (rc < 1) { |
| FXL_LOG(ERROR) << "Failed to read from input: " << rc; |
| return false; |
| } |
| |
| auto cur = &decoder_[0]; |
| if ((cur->match != 0) && (cur->count == 8u)) { |
| // The first byte is the report id. |
| if (report[0] != cur->match) { |
| // This is a normal condition. The device can generate reports |
| // for controls we don't yet handle. |
| *gamepad = {}; |
| return true; |
| } |
| ++cur; |
| } |
| |
| gamepad->left_x = extract_int8_ext(report, cur->begin, cur->count) / 2; |
| ++cur; |
| gamepad->left_y = extract_int8_ext(report, cur->begin, cur->count) / 2; |
| ++cur; |
| gamepad->right_x = extract_int8_ext(report, cur->begin, cur->count) / 2; |
| ++cur; |
| gamepad->right_y = extract_int8_ext(report, cur->begin, cur->count) / 2; |
| ++cur; |
| gamepad->hat_switch = extract_int8_ext(report, cur->begin, cur->count); |
| return true; |
| } |
| |
| bool FdioHidDecoder::Read(HidAmbientLightSimple* data) { |
| if (protocol_ != Protocol::LightSensor) |
| return false; |
| |
| int rc; |
| auto report = Read(&rc); |
| if (rc < 1) { |
| FXL_LOG(ERROR) << "Failed to read from input: " << rc; |
| return false; |
| } |
| |
| auto cur = &decoder_[0]; |
| if ((cur->match != 0) && (cur->count == 8u)) { |
| // The first byte is the report id. |
| if (report[0] != cur->match) { |
| // This is a normal condition. The device can generate reports |
| // for controls we don't yet handle. |
| *data = {}; |
| return true; |
| } |
| ++cur; |
| } |
| if (cur->count != 16u) { |
| FXL_LOG(ERROR) << "Unexpected count in report from ambient light:" |
| << cur->count; |
| return false; |
| } |
| data->illuminance = extract_uint16(report, cur->begin); |
| return true; |
| } |
| |
| bool FdioHidDecoder::Read(HidButtons* data) { |
| if (protocol_ != Protocol::Buttons) |
| return false; |
| |
| int rc; |
| auto report = Read(&rc); |
| if (rc < 1) { |
| FXL_LOG(ERROR) << "Failed to read from input: " << rc; |
| return false; |
| } |
| |
| auto cur = &decoder_[0]; |
| if ((cur->match != 0) && (cur->count == 8u)) { |
| // The first byte is the report id. |
| if (report[0] != cur->match) { |
| // This is a normal condition. The device can generate reports |
| // for controls we don't yet handle. |
| *data = {}; |
| return true; |
| } |
| ++cur; |
| } |
| |
| // 2 bits, see zircon/system/ulib/hid's buttons.c and include/hid/buttons.h |
| if (cur->count != 2u) { |
| FXL_LOG(ERROR) << "Unexpected count in report from buttons:" << cur->count; |
| return false; |
| } |
| // TODO(SCN-843): We need to generalize these extraction functions, e.g. add |
| // extract_int8 |
| data->volume = extract_uint8(report, cur->begin, 2u); |
| if (data->volume == 3) { // 2 bits unsigned 3 is signed -1 |
| data->volume = -1; |
| } |
| ++cur; |
| |
| // 1 bit, see zircon/system/ulib/hid's buttons.c and include/hid/buttons.h |
| if (cur->count != 1u) { |
| FXL_LOG(ERROR) << "Unexpected count in report from buttons:" << cur->count; |
| return false; |
| } |
| data->mic_mute = extract_uint8(report, cur->begin, 1u); |
| return true; |
| } |
| |
| bool FdioHidDecoder::Read(Touchscreen::Report* report) { |
| int rc; |
| auto r = Read(&rc); |
| |
| if (rc < 1) { |
| FXL_LOG(ERROR) << "Failed to read from input: " << rc; |
| return false; |
| } |
| |
| if (r[0] != ts_.report_id()) { |
| FXL_VLOG(0) << name() << " Touchscreen report " |
| << static_cast<uint32_t>(r[0]) << " does not match report id " |
| << static_cast<uint32_t>(ts_.report_id()); |
| return false; |
| } |
| |
| return ts_.ParseReport(r.data(), rc, report); |
| } |
| |
| bool FdioHidDecoder::SetDescriptor(Touchscreen::Descriptor* touch_desc) { |
| return ts_.SetDescriptor(touch_desc); |
| } |
| |
| } // namespace mozart |