blob: 5224e082a45168b1b6c1b48215c380b7ffaebe74 [file] [log] [blame]
// Copyright 2017 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 "hid.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <memory>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/hidbus.h>
#include <ddk/trace/event.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <hid/boot.h>
namespace hid_driver {
size_t HidDevice::GetReportSizeById(input_report_id_t id, ReportType type) {
for (size_t i = 0; i < parsed_hid_desc_->rep_count; i++) {
// If we have more than one report, get the report with the right id. If we only have
// one report, then always match that report.
if ((parsed_hid_desc_->report[i].report_id == id) || (parsed_hid_desc_->rep_count == 1)) {
switch (type) {
case ReportType::INPUT:
return parsed_hid_desc_->report[i].input_byte_sz;
case ReportType::OUTPUT:
return parsed_hid_desc_->report[i].output_byte_sz;
case ReportType::FEATURE:
return parsed_hid_desc_->report[i].feature_byte_sz;
}
}
}
return 0;
}
BootProtocol HidDevice::GetBootProtocol() {
if (info_.device_class == HID_DEVICE_CLASS_KBD ||
info_.device_class == HID_DEVICE_CLASS_KBD_POINTER) {
return BootProtocol::KBD;
} else if (info_.device_class == HID_DEVICE_CLASS_POINTER) {
return BootProtocol::MOUSE;
}
return BootProtocol::NONE;
}
void HidDevice::RemoveHidInstanceFromList(HidInstance* instance) {
fbl::AutoLock lock(&instance_lock_);
// TODO(dgilhooley): refcount the base device and call stop if no instances are open
for (auto& iter : instance_list_) {
if (iter.zxdev() == instance->zxdev()) {
instance_list_.erase(iter);
break;
}
}
}
zx_status_t HidDevice::GetReportIds(size_t report_ids_size, uint8_t* report_ids, size_t* out_size) {
if (report_ids_size < parsed_hid_desc_->rep_count) {
*out_size = 0;
return ZX_ERR_BUFFER_TOO_SMALL;
}
for (size_t i = 0; i < parsed_hid_desc_->rep_count; i++) {
report_ids[i] = parsed_hid_desc_->report[i].report_id;
}
*out_size = parsed_hid_desc_->rep_count;
return ZX_OK;
}
size_t HidDevice::GetMaxInputReportSize() {
size_t size = 0;
for (size_t i = 0; i < parsed_hid_desc_->rep_count; i++) {
if (parsed_hid_desc_->report[i].input_byte_sz > size)
size = parsed_hid_desc_->report[i].input_byte_sz;
}
return size;
}
zx_status_t HidDevice::ProcessReportDescriptor() {
hid::ParseResult res = hid::ParseReportDescriptor(hid_report_desc_.data(),
hid_report_desc_.size(), &parsed_hid_desc_);
if (res != hid::ParseResult::kParseOk) {
return ZX_ERR_INTERNAL;
}
size_t num_reports = 0;
for (size_t i = 0; i < parsed_hid_desc_->rep_count; i++) {
if (parsed_hid_desc_->report[i].input_count != 0) {
num_reports++;
}
if (parsed_hid_desc_->report[i].output_count != 0) {
num_reports++;
}
if (parsed_hid_desc_->report[i].feature_count != 0) {
num_reports++;
}
}
num_reports_ = num_reports;
return ZX_OK;
}
void HidDevice::ReleaseReassemblyBuffer() {
if (rbuf_ != NULL) {
free(rbuf_);
}
rbuf_ = NULL;
rbuf_size_ = 0;
rbuf_filled_ = 0;
rbuf_needed_ = 0;
}
zx_status_t HidDevice::InitReassemblyBuffer() {
ZX_DEBUG_ASSERT(rbuf_ == NULL);
ZX_DEBUG_ASSERT(rbuf_size_ == 0);
ZX_DEBUG_ASSERT(rbuf_filled_ == 0);
ZX_DEBUG_ASSERT(rbuf_needed_ == 0);
// TODO(johngro) : Take into account the underlying transport's ability to
// deliver payloads. For example, if this is a USB HID device operating at
// full speed, we can expect it to deliver up to 64 bytes at a time. If the
// maximum HID input report size is only 60 bytes, we should not need a
// reassembly buffer.
size_t max_report_size = GetMaxInputReportSize();
rbuf_ = static_cast<uint8_t*>(malloc(max_report_size));
if (rbuf_ == NULL) {
return ZX_ERR_NO_MEMORY;
}
rbuf_size_ = max_report_size;
return ZX_OK;
}
void HidDevice::DdkRelease() {
ReleaseReassemblyBuffer();
if (parsed_hid_desc_) {
FreeDeviceDescriptor(parsed_hid_desc_);
}
delete this;
}
zx_status_t HidDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
auto inst = std::make_unique<HidInstance>(zxdev());
zx_status_t status = inst->Bind(this);
if (status != ZX_OK) {
return status;
}
{
fbl::AutoLock lock(&instance_lock_);
instance_list_.push_front(inst.get());
}
*dev_out = inst->zxdev();
// devmgr is now in charge of the memory for inst.
__UNUSED auto ptr = inst.release();
return ZX_OK;
}
void HidDevice::DdkUnbindNew(ddk::UnbindTxn txn) {
{
fbl::AutoLock lock(&instance_lock_);
for (auto& instance : instance_list_) {
instance.CloseInstance();
}
}
txn.Reply();
}
void HidDevice::IoQueue(void* cookie, const void* _buf, size_t len, zx_time_t time) {
const uint8_t* buf = static_cast<const uint8_t*>(_buf);
HidDevice* hid = static_cast<HidDevice*>(cookie);
TRACE_DURATION("input", "HID IO Queue");
fbl::AutoLock lock(&hid->instance_lock_);
while (len) {
// Start by figuring out if this payload either completes a partially
// assembled input report or represents an entire input buffer report on
// its own.
const uint8_t* rbuf;
size_t rlen;
size_t consumed;
if (hid->rbuf_needed_) {
// Reassembly is in progress, just continue the process.
consumed = std::min(len, hid->rbuf_needed_);
ZX_DEBUG_ASSERT(hid->rbuf_size_ >= hid->rbuf_filled_);
ZX_DEBUG_ASSERT((hid->rbuf_size_ - hid->rbuf_filled_) >= consumed);
memcpy(hid->rbuf_ + hid->rbuf_filled_, buf, consumed);
if (consumed == hid->rbuf_needed_) {
// reassembly finished. Reset the bookkeeping and deliver the
// payload.
rbuf = hid->rbuf_;
rlen = hid->rbuf_filled_ + consumed;
hid->rbuf_filled_ = 0;
hid->rbuf_needed_ = 0;
} else {
// We have not finished the process yet. Update the bookkeeping
// and get out.
hid->rbuf_filled_ += consumed;
hid->rbuf_needed_ -= consumed;
break;
}
} else {
// No reassembly is in progress. Start by identifying this report's
// size.
size_t rpt_sz = hid->GetReportSizeById(buf[0], ReportType::INPUT);
// If we don't recognize this report ID, we are in trouble. Drop
// the rest of this payload and hope that the next one gets us back
// on track.
if (!rpt_sz) {
zxlogf(ERROR, "%s: failed to find input report size (report id %u)", hid->name_.data(),
buf[0]);
break;
}
// Is the entire report present in this payload? If so, just go
// ahead an deliver it directly from the input buffer.
if (len >= rpt_sz) {
rbuf = buf;
consumed = rlen = rpt_sz;
} else {
// Looks likes our report is fragmented over multiple buffers.
// Start the process of reassembly and get out.
ZX_DEBUG_ASSERT(hid->rbuf_ != NULL);
ZX_DEBUG_ASSERT(hid->rbuf_size_ >= rpt_sz);
memcpy(hid->rbuf_, buf, len);
hid->rbuf_filled_ = len;
hid->rbuf_needed_ = rpt_sz - len;
break;
}
}
ZX_DEBUG_ASSERT(rbuf != NULL);
ZX_DEBUG_ASSERT(consumed <= len);
buf += consumed;
len -= consumed;
for (auto& instance : hid->instance_list_) {
instance.WriteToFifo(rbuf, rlen, time);
}
{
fbl::AutoLock lock(&hid->listener_lock_);
if (hid->report_listener_.is_valid()) {
hid->report_listener_.ReceiveReport(rbuf, rlen, time);
}
}
}
}
zx_status_t HidDevice::HidDeviceRegisterListener(const hid_report_listener_protocol_t* listener) {
fbl::AutoLock lock(&listener_lock_);
if (report_listener_.is_valid()) {
return ZX_ERR_ALREADY_BOUND;
}
report_listener_ = ddk::HidReportListenerProtocolClient(listener);
return ZX_OK;
}
void HidDevice::HidDeviceUnregisterListener() {
fbl::AutoLock lock(&listener_lock_);
report_listener_.clear();
}
zx_status_t HidDevice::HidDeviceGetDescriptor(uint8_t* out_descriptor_data, size_t descriptor_count,
size_t* out_descriptor_actual) {
if (descriptor_count < hid_report_desc_.size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_descriptor_data, hid_report_desc_.data(), hid_report_desc_.size());
*out_descriptor_actual = hid_report_desc_.size();
return ZX_OK;
}
zx_status_t HidDevice::HidDeviceGetReport(hid_report_type_t rpt_type, uint8_t rpt_id,
uint8_t* out_report_data, size_t report_count,
size_t* out_report_actual) {
size_t needed = GetReportSizeById(rpt_id, static_cast<ReportType>(rpt_type));
if (needed == 0) {
return ZX_ERR_NOT_FOUND;
}
if (needed > report_count) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
if (needed > HID_MAX_REPORT_LEN) {
zxlogf(ERROR, "hid: GetReport: Report size 0x%lx larger than max size 0x%x", needed,
HID_MAX_REPORT_LEN);
return ZX_ERR_INTERNAL;
}
uint8_t report[HID_MAX_REPORT_LEN];
size_t actual = 0;
zx_status_t status = hidbus_.GetReport(rpt_type, rpt_id, report, needed, &actual);
if (status != ZX_OK) {
return status;
}
memcpy(out_report_data, report, actual);
*out_report_actual = actual;
return ZX_OK;
}
zx_status_t HidDevice::HidDeviceSetReport(hid_report_type_t rpt_type, uint8_t rpt_id,
const uint8_t* report_data, size_t report_count) {
size_t needed = GetReportSizeById(rpt_id, static_cast<ReportType>(rpt_type));
if (needed < report_count) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
zx_status_t status = hidbus_.SetReport(rpt_type, rpt_id, report_data, report_count);
return status;
}
hidbus_ifc_protocol_ops_t hid_ifc_ops = {
.io_queue = HidDevice::IoQueue,
};
zx_status_t HidDevice::SetReportDescriptor() {
hid_report_desc_.resize(HID_MAX_DESC_LEN);
size_t actual;
zx_status_t status = hidbus_.GetDescriptor(HID_DESCRIPTION_TYPE_REPORT, hid_report_desc_.data(),
hid_report_desc_.size(), &actual);
if (status != ZX_OK) {
return status;
}
hid_report_desc_.resize(actual);
if (!info_.boot_device) {
return ZX_OK;
}
hid_protocol_t protocol;
status = hidbus_.GetProtocol(&protocol);
if (status != ZX_OK) {
if (status == ZX_ERR_NOT_SUPPORTED) {
status = ZX_OK;
}
return status;
}
// Only continue if the device was put into the boot protocol.
if (protocol != HID_PROTOCOL_BOOT) {
return ZX_OK;
}
// If we are a boot protocol kbd, we need to use the right HID descriptor.
if (info_.device_class == HID_DEVICE_CLASS_KBD) {
const uint8_t* boot_kbd_desc = get_boot_kbd_report_desc(&actual);
hid_report_desc_.resize(actual);
memcpy(hid_report_desc_.data(), boot_kbd_desc, actual);
// Disable numlock
uint8_t zero = 0;
hidbus_.SetReport(HID_REPORT_TYPE_OUTPUT, 0, &zero, sizeof(zero));
// ignore failure for now
}
// If we are a boot protocol pointer, we need to use the right HID descriptor.
if (info_.device_class == HID_DEVICE_CLASS_POINTER) {
const uint8_t* boot_mouse_desc = get_boot_mouse_report_desc(&actual);
hid_report_desc_.resize(actual);
memcpy(hid_report_desc_.data(), boot_mouse_desc, actual);
}
return ZX_OK;
}
const char* HidDevice::GetName() { return name_.data(); }
zx_status_t HidDevice::Bind(ddk::HidbusProtocolClient hidbus_proto) {
hidbus_ = std::move(hidbus_proto);
zx_status_t status = ZX_OK;
if ((status = hidbus_.Query(0, &info_)) < 0) {
zxlogf(ERROR, "hid: bind: hidbus query failed: %d", status);
return status;
}
snprintf(name_.data(), name_.size(), "hid-device-%03d", info_.dev_num);
name_[ZX_DEVICE_NAME_MAX] = 0;
status = SetReportDescriptor();
if (status != ZX_OK) {
zxlogf(ERROR, "hid: could not retrieve HID report descriptor: %d", status);
return status;
}
status = ProcessReportDescriptor();
if (status != ZX_OK) {
zxlogf(ERROR, "hid: could not parse hid report descriptor: %d", status);
return status;
}
status = InitReassemblyBuffer();
if (status != ZX_OK) {
zxlogf(ERROR, "hid: failed to initialize reassembly buffer: %d", status);
return status;
}
// TODO: delay calling start until we've been opened by someone
status = hidbus_.Start(this, &hid_ifc_ops);
if (status != ZX_OK) {
zxlogf(ERROR, "hid: could not start hid device: %d", status);
ReleaseReassemblyBuffer();
return status;
}
status = hidbus_.SetIdle(0, 0);
if (status != ZX_OK) {
zxlogf(TRACE, "hid: [W] set_idle failed for %s: %d", name_.data(), status);
// continue anyway
}
status = DdkAdd(name_.data());
if (status != ZX_OK) {
zxlogf(ERROR, "hid: device_add failed for HID device: %d", status);
ReleaseReassemblyBuffer();
return status;
}
return ZX_OK;
}
static zx_status_t hid_bind(void* ctx, zx_device_t* parent) {
zx_status_t status;
auto dev = std::make_unique<HidDevice>(parent);
hidbus_protocol_t hidbus;
if (device_get_protocol(parent, ZX_PROTOCOL_HIDBUS, &hidbus)) {
zxlogf(ERROR, "hid: bind: no hidbus protocol");
return ZX_ERR_INTERNAL;
}
ddk::HidbusProtocolClient client = ddk::HidbusProtocolClient(&hidbus);
status = dev->Bind(std::move(client));
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev.
__UNUSED auto ptr = dev.release();
}
return status;
}
static zx_driver_ops_t hid_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = hid_bind;
return ops;
}();
} // namespace hid_driver
// clang-format off
ZIRCON_DRIVER_BEGIN(hid, hid_driver::hid_driver_ops, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_HIDBUS),
ZIRCON_DRIVER_END(hid)
// clang-format on