blob: 6d023e7cc25d51741b12b07542276476eae8af36 [file] [log] [blame]
// 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