blob: eebbec625b76dd529d2ef6a2e17468abc54cc171 [file] [log] [blame]
// Copyright 2019 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-instance.h"
#include <lib/hid/boot.h>
#include <lib/trace/event.h>
#include "hid.h"
namespace hid_driver {
namespace fhidbus = fuchsia_hardware_hidbus;
namespace {
constexpr uint32_t kHidFlagsDead = (1 << 0);
constexpr uint32_t kHidFlagsWriteFailed = (1 << 1);
constexpr uint64_t hid_report_trace_id(uint32_t instance_id, uint64_t report_id) {
return (report_id << 32) | instance_id;
}
} // namespace
HidInstance::HidInstance(HidDevice* base, zx::event fifo_event,
fidl::ServerEnd<fuchsia_hardware_input::Device> session)
: base_(base),
fifo_event_(std::move(fifo_event)),
binding_(fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(session), this,
[](HidInstance* instance, fidl::UnbindInfo info) {
if (!instance) {
return;
}
instance->CloseInstance();
instance->base_->RemoveInstance(*instance);
}) {
zx_hid_fifo_init(&fifo_);
}
void HidInstance::SetReadable() { fifo_event_.signal(0, ZX_USER_SIGNAL_0); }
void HidInstance::ClearReadable() { fifo_event_.signal(ZX_USER_SIGNAL_0, 0); }
zx_status_t HidInstance::ReadReportFromFifo(uint8_t* buf, size_t buf_size, zx_time_t* time,
size_t* report_size) {
uint8_t rpt_id;
if (zx_hid_fifo_peek(&fifo_, &rpt_id) <= 0) {
return ZX_ERR_SHOULD_WAIT;
}
size_t xfer = base_->GetReportSizeById(rpt_id, fhidbus::ReportType::kInput);
if (xfer == 0) {
FDF_LOG(ERROR, "error reading hid device: unknown report id (%u)!", rpt_id);
return ZX_ERR_BAD_STATE;
}
// Check if we have enough room left in the buffer.
if (xfer > buf_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
ssize_t rpt_size = zx_hid_fifo_read(&fifo_, buf, xfer);
if (rpt_size <= 0) {
// Something went wrong. The fifo should always contain full reports in it.
return ZX_ERR_INTERNAL;
}
size_t left = zx_hid_fifo_size(&fifo_);
if (left == 0) {
ClearReadable();
}
*report_size = rpt_size;
*time = timestamps_.front();
timestamps_.pop();
reports_sent_ += 1;
TRACE_FLOW_STEP("input", "hid_report", hid_report_trace_id(trace_id_, reports_sent_));
return ZX_OK;
}
void HidInstance::ReadReport(ReadReportCompleter::Sync& completer) {
TRACE_DURATION("input", "HID ReadReport Instance", "bytes_in_fifo", zx_hid_fifo_size(&fifo_));
if (flags_ & kHidFlagsDead) {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
std::array<uint8_t, fhidbus::wire::kMaxReportData> buf;
zx_time_t time = 0;
size_t report_size = 0;
zx_status_t status = ReadReportFromFifo(buf.data(), buf.size(), &time, &report_size);
if (status != ZX_OK) {
return completer.ReplyError(status);
}
auto buf_view = fidl::VectorView<uint8_t>::FromExternal(buf.data(), report_size);
fidl::Arena arena;
auto report = fhidbus::wire::Report::Builder(arena).buf(buf_view).timestamp(time);
completer.ReplySuccess(report.Build());
}
void HidInstance::ReadReports(ReadReportsCompleter::Sync& completer) {
TRACE_DURATION("input", "HID GetReports Instance", "bytes_in_fifo", zx_hid_fifo_size(&fifo_));
if (flags_ & kHidFlagsDead) {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
std::array<uint8_t, fhidbus::wire::kMaxReportData> buf;
size_t buf_index = 0;
zx_status_t status = ZX_OK;
zx_time_t time;
while (status == ZX_OK) {
size_t report_size;
status =
ReadReportFromFifo(buf.data() + buf_index, buf.size() - buf_index, &time, &report_size);
if (status == ZX_OK) {
buf_index += report_size;
}
}
if ((buf_index > 0) && ((status == ZX_ERR_BUFFER_TOO_SMALL) || (status == ZX_ERR_SHOULD_WAIT))) {
status = ZX_OK;
}
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
auto buf_view = fidl::VectorView<uint8_t>::FromExternal(buf.data(), buf_index);
completer.ReplySuccess(buf_view);
}
void HidInstance::GetReportsEvent(GetReportsEventCompleter::Sync& completer) {
zx::event new_event;
zx_status_t status = fifo_event_.duplicate(ZX_RIGHTS_BASIC, &new_event);
if (status != ZX_OK) {
return completer.ReplyError(status);
}
completer.ReplySuccess(std::move(new_event));
}
void HidInstance::Query(QueryCompleter::Sync& completer) {
fidl::Arena arena;
completer.ReplySuccess(fidl::ToWire(arena, base_->GetHidInfo()));
}
void HidInstance::GetReportDesc(GetReportDescCompleter::Sync& completer) {
size_t desc_size = base_->GetReportDescLen();
const uint8_t* desc = base_->GetReportDesc();
// (BUG 35762) Const cast is necessary until simple data types are generated
// as const in LLCPP. We know the data is not modified.
completer.Reply(fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(desc), desc_size));
}
void HidInstance::GetReport(GetReportRequestView request, GetReportCompleter::Sync& completer) {
size_t needed = base_->GetReportSizeById(request->id, request->type);
if (needed == 0) {
completer.ReplyError(ZX_ERR_NOT_FOUND);
return;
}
if (needed > fhidbus::kMaxReportLen) {
FDF_LOG(ERROR, "hid: GetReport: Report size 0x%lx larger than max size 0x%x", needed,
fhidbus::kMaxReportLen);
completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
auto& client = base_->GetHidbusProtocol();
auto result = client.sync()->GetReport(request->type, request->id, needed);
if (!result.ok()) {
FDF_LOG(ERROR, "FIDL transport failed on GetReport(): %s",
result.error().FormatDescription().c_str());
completer.ReplyError(result.status());
return;
}
if (result->is_error()) {
FDF_LOG(ERROR, "HID device failed to get report: %d", result->error_value());
completer.ReplyError(result->error_value());
return;
}
completer.ReplySuccess(result.value()->data);
}
void HidInstance::SetReport(SetReportRequestView request, SetReportCompleter::Sync& completer) {
size_t needed = base_->GetReportSizeById(request->id, request->type);
if (needed != request->report.size()) {
FDF_LOG(ERROR, "Tried to set Report %d (size 0x%lx) with 0x%lx bytes\n", request->id, needed,
request->report.size());
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
auto& client = base_->GetHidbusProtocol();
auto result = client.sync()->SetReport(request->type, request->id, request->report);
if (!result.ok()) {
FDF_LOG(ERROR, "FIDL transport failed on SetReport(): %s",
result.error().FormatDescription().c_str());
completer.ReplyError(result.status());
return;
}
if (result->is_error()) {
FDF_LOG(ERROR, "HID device failed to set report: %d", result->error_value());
completer.ReplyError(result->error_value());
return;
}
completer.ReplySuccess();
}
void HidInstance::GetDeviceReportsReader(GetDeviceReportsReaderRequestView request,
GetDeviceReportsReaderCompleter::Sync& completer) {
readers_binding_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(request->reader), &readers_.emplace_back(base_),
fidl::kIgnoreBindingClosure);
completer.ReplySuccess();
}
void HidInstance::SetTraceId(SetTraceIdRequestView request, SetTraceIdCompleter::Sync& completer) {
trace_id_ = request->id;
}
void HidInstance::CloseInstance() {
flags_ |= kHidFlagsDead;
SetReadable();
}
void HidInstance::WriteToFifo(const uint8_t* report, size_t report_len, zx_time_t time) {
auto iter = readers_.begin();
while (iter != readers_.end()) {
if ((*iter).WriteToFifo(report, report_len, time) != ZX_OK) {
iter = readers_.erase(iter);
} else {
iter++;
}
}
if (timestamps_.full()) {
flags_ |= kHidFlagsWriteFailed;
return;
}
bool was_empty = zx_hid_fifo_size(&fifo_) == 0;
ssize_t wrote = zx_hid_fifo_write(&fifo_, report, report_len);
if (wrote <= 0) {
if (!(flags_ & kHidFlagsWriteFailed)) {
FDF_LOG(ERROR, "Could not write to hid fifo (ret=%zd)", wrote);
flags_ |= kHidFlagsWriteFailed;
}
return;
}
timestamps_.push(time);
TRACE_FLOW_BEGIN("input", "hid_report", hid_report_trace_id(trace_id_, reports_written_));
++reports_written_;
flags_ &= ~kHidFlagsWriteFailed;
if (was_empty) {
SetReadable();
}
}
void HidInstance::SetWakeLease(const zx::eventpair& wake_lease) {
for (auto& reader : readers_) {
reader.SetWakeLease(wake_lease);
}
}
} // namespace hid_driver