blob: 3e25dbc883d1615c119bccbfd83ed5cccaf94e95 [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 "hidctl.h"
#include <fidl/fuchsia.hardware.hidctl/cpp/wire.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/platform-defs.h>
#include <stdio.h>
#include <string.h>
#include <zircon/compiler.h>
#include <memory>
#include <utility>
#include <cinttypes>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <pretty/hexdump.h>
namespace hidctl {
namespace fhidbus = fuchsia_hardware_hidbus;
zx_status_t HidCtl::Create(void* ctx, zx_device_t* parent) {
auto dev = std::unique_ptr<HidCtl>(new HidCtl(parent));
zx_status_t status = dev->DdkAdd(ddk::DeviceAddArgs("hidctl").set_flags(DEVICE_ADD_NON_BINDABLE));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not add device: %d", __func__, status);
} else {
// devmgr owns the memory now
[[maybe_unused]] auto* ptr = dev.release();
}
return status;
}
void HidCtl::MakeHidDevice(MakeHidDeviceRequestView request,
MakeHidDeviceCompleter::Sync& completer) {
// Create the sockets for Sending/Recieving fake HID reports.
zx::socket local, remote;
zx_status_t status = zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote);
if (status != ZX_OK) {
completer.Close(status);
return;
}
// Create the fake HID device.
uint8_t* report_desc_data = new uint8_t[request->rpt_desc.size()];
memcpy(report_desc_data, request->rpt_desc.data(), request->rpt_desc.size());
fbl::Array<const uint8_t> report_desc(report_desc_data, request->rpt_desc.size());
auto hiddev = std::make_unique<hidctl::HidDevice>(zxdev(), request->config,
std::move(report_desc), std::move(local));
auto client_end = hiddev->ServeOutgoing();
std::array offers = {
fhidbus::Service::Name,
};
status = hiddev->DdkAdd(ddk::DeviceAddArgs("hidctl-dev")
.set_fidl_service_offers(offers)
.set_outgoing_dir(client_end->TakeChannel()));
if (status != ZX_OK) {
zxlogf(ERROR, "hidctl: could not add hid device: %d", status);
completer.Close(status);
return;
}
// The device thread will be created in DdkInit.
zxlogf(INFO, "hidctl: created hid device");
// devmgr owns the memory until release is called
[[maybe_unused]] auto ptr = hiddev.release();
completer.Reply(std::move(remote));
}
HidCtl::HidCtl(zx_device_t* device) : DeviceType(device) {}
void HidCtl::DdkRelease() { delete this; }
HidDevice::HidDevice(zx_device_t* device, const fuchsia_hardware_hidctl::wire::HidCtlConfig& config,
fbl::Array<const uint8_t> report_desc, zx::socket data)
: ddk::Device<HidDevice, ddk::Initializable, ddk::Unbindable>(device),
outgoing_(fdf::Dispatcher::GetCurrent()->async_dispatcher()),
boot_protocol_(config.boot_device
? static_cast<fhidbus::wire::HidBootProtocol>(config.dev_class)
: fhidbus::wire::HidBootProtocol::kNone),
report_desc_(std::move(report_desc)),
data_(std::move(data)) {
ZX_DEBUG_ASSERT(data_.is_valid());
}
void HidDevice::DdkInit(ddk::InitTxn txn) {
wait_handler_.set_object(data_.get());
wait_handler_.set_trigger(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
txn.Reply(wait_handler_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()));
}
void HidDevice::DdkRelease() {
zxlogf(DEBUG, "hidctl: DdkRelease");
delete this;
}
void HidDevice::DdkUnbind(ddk::UnbindTxn txn) {
zxlogf(DEBUG, "hidctl: DdkUnbind");
fbl::AutoLock lock(&lock_);
if (data_.is_valid()) {
// Prevent further writes to the socket
zx_status_t status = data_.set_disposition(0, ZX_SOCKET_DISPOSITION_WRITE_DISABLED);
ZX_DEBUG_ASSERT(status == ZX_OK);
// Signal the thread to shutdown
wait_handler_.Cancel();
}
txn.Reply();
}
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> HidDevice::ServeOutgoing() {
zx::result<> result = outgoing_.AddService<fhidbus::Service>(fhidbus::Service::InstanceHandler({
.device =
[this](fidl::ServerEnd<fhidbus::Hidbus> server_end) {
if (binding_) {
server_end.Close(ZX_ERR_ALREADY_BOUND);
return;
}
binding_.emplace(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(server_end), this, [this](fidl::UnbindInfo info) {
Stop();
binding_.reset();
});
},
}));
if (result.is_error()) {
zxlogf(ERROR, "Failed to add Hidbus protocol: %s", result.status_string());
return result.take_error();
}
auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
result = outgoing_.Serve(std::move(server));
if (result.is_error()) {
zxlogf(ERROR, "Failed to service the outgoing directory");
return result.take_error();
}
return zx::ok(std::move(client));
}
void HidDevice::Query(QueryCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: query");
fidl::Arena<> arena;
completer.ReplySuccess(fhidbus::wire::HidInfo::Builder(arena)
.dev_num(0)
.boot_protocol(boot_protocol_)
.vendor_id(0)
.product_id(0)
.version(0)
.polling_rate(0)
.Build());
}
void HidDevice::Start(StartCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: start");
if (started_) {
zxlogf(ERROR, "Already started");
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
started_ = true;
completer.ReplySuccess();
}
void HidDevice::Stop(StopCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: stop");
Stop();
}
void HidDevice::Stop() { started_ = false; }
void HidDevice::GetDescriptor(fhidbus::wire::HidbusGetDescriptorRequest* request,
GetDescriptorCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: get descriptor %" PRIu8, static_cast<uint8_t>(request->desc_type));
if (request->desc_type != fhidbus::wire::HidDescriptorType::kReport) {
completer.ReplyError(ZX_ERR_NOT_FOUND);
return;
}
completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(
const_cast<uint8_t*>(report_desc_.data()), report_desc_.size()));
}
void HidDevice::SetDescriptor(fhidbus::wire::HidbusSetDescriptorRequest* request,
SetDescriptorCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void HidDevice::GetReport(fhidbus::wire::HidbusGetReportRequest* request,
GetReportCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: get report type=%" PRIu8 " id=%" PRIu8,
static_cast<uint8_t>(request->rpt_type), request->rpt_id);
// TODO: send get report message over socket
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void HidDevice::SetReport(fhidbus::wire::HidbusSetReportRequest* request,
SetReportCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: set report type=%" PRIu8 " id=%" PRIu8,
static_cast<uint8_t>(request->rpt_type), request->rpt_id);
// TODO: send set report message over socket
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void HidDevice::GetIdle(fhidbus::wire::HidbusGetIdleRequest* request,
GetIdleCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: get idle");
// TODO: send get idle message over socket
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void HidDevice::SetIdle(fhidbus::wire::HidbusSetIdleRequest* request,
SetIdleCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: set idle");
// TODO: send set idle message over socket
completer.ReplySuccess();
}
void HidDevice::GetProtocol(GetProtocolCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: get protocol");
// TODO: send get protocol message over socket
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void HidDevice::SetProtocol(fhidbus::wire::HidbusSetProtocolRequest* request,
SetProtocolCompleter::Sync& completer) {
zxlogf(DEBUG, "hidctl: set protocol");
// TODO: send set protocol message over socket
completer.ReplySuccess();
}
void HidDevice::HandleData(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
zxlogf(ERROR, "hidctl: error waiting on data: %d", status);
DdkAsyncRemove();
return;
}
if (signal->observed & ZX_SOCKET_READABLE) {
status = Recv(buf_.data(), mtu_);
if (status != ZX_OK) {
DdkAsyncRemove();
return;
}
}
if (signal->observed & ZX_SOCKET_PEER_CLOSED) {
zxlogf(DEBUG, "hidctl: socket closed (peer)");
DdkAsyncRemove();
return;
}
wait_handler_.Begin(dispatcher);
}
zx_status_t HidDevice::Recv(uint8_t* buffer, uint32_t capacity) {
size_t actual = 0;
zx_status_t status = ZX_OK;
// Read all the datagrams out of the socket.
while (status == ZX_OK) {
status = data_.read(0u, buffer, capacity, &actual);
if (status == ZX_ERR_SHOULD_WAIT || status == ZX_ERR_PEER_CLOSED) {
break;
}
if (status != ZX_OK) {
zxlogf(ERROR, "hidctl: error reading data: %d", status);
return status;
}
fbl::AutoLock lock(&lock_);
if (unlikely(zxlog_level_enabled(DEBUG))) {
zxlogf(DEBUG, "hidctl: received %zu bytes", actual);
hexdump8_ex(buffer, actual, 0);
}
if (started_ && binding_) {
fidl::Arena arena;
auto result = fidl::WireSendEvent(*binding_)->OnReportReceived(
fhidbus::wire::Report::Builder(arena)
.buf(fidl::VectorView<uint8_t>::FromExternal(buffer, actual))
.timestamp(zx_clock_get_monotonic())
.Build());
if (!result.ok()) {
zxlogf(ERROR, "OnReportReceived failed %s", result.error().FormatDescription().c_str());
}
}
}
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = HidCtl::Create;
return ops;
}();
} // namespace hidctl
ZIRCON_DRIVER(hidctl, hidctl::driver_ops, "zircon", "0.1");