| // 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 |