blob: b98961e1ade9737a84c5a3b367d64d9e7431d3cd [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 <ddk/debug.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <pretty/hexdump.h>
#include <zircon/compiler.h>
#include <fuchsia/hardware/hidctl/c/fidl.h>
#include <stdio.h>
#include <string.h>
#include <utility>
namespace hidctl {
zx_status_t HidCtl::FidlMakeHidDevice(void* ctx, const fuchsia_hardware_hidctl_HidCtlConfig* config,
const uint8_t* rpt_desc_data, size_t rpt_desc_count,
fidl_txn_t* txn) {
HidCtl* hidctl = static_cast<HidCtl*>(ctx);
// 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) {
return status;
}
// Create the fake HID device.
uint8_t* report_desc_data = new uint8_t[rpt_desc_count];
memcpy(report_desc_data, rpt_desc_data, rpt_desc_count);
fbl::Array<const uint8_t> report_desc(report_desc_data, rpt_desc_count);
auto hiddev = fbl::unique_ptr<hidctl::HidDevice>(
new hidctl::HidDevice(hidctl->zxdev(), config, std::move(report_desc), std::move(local)));
status = hiddev->DdkAdd("hidctl-dev");
if (status != ZX_OK) {
zxlogf(ERROR, "hidctl: could not add hid device: %d\n", status);
hiddev->Shutdown();
return status;
}
zxlogf(INFO, "hidctl: created hid device\n");
// devmgr owns the memory until release is called
__UNUSED auto ptr = hiddev.release();
zx_handle_t report_socket = remote.release();
return fuchsia_hardware_hidctl_DeviceMakeHidDevice_reply(txn, report_socket);
}
zx_status_t HidCtl::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
static const fuchsia_hardware_hidctl_Device_ops_t kOps = {
.MakeHidDevice = HidCtl::FidlMakeHidDevice,
};
return fuchsia_hardware_hidctl_Device_dispatch(this, txn, msg, &kOps);
}
HidCtl::HidCtl(zx_device_t* device) : ddk::Device<HidCtl, ddk::Messageable>(device) {}
void HidCtl::DdkRelease() {
delete this;
}
int hid_device_thread(void* arg) {
HidDevice* device = reinterpret_cast<HidDevice*>(arg);
return device->Thread();
}
#define HID_SHUTDOWN ZX_USER_SIGNAL_7
HidDevice::HidDevice(zx_device_t* device, const fuchsia_hardware_hidctl_HidCtlConfig* config,
fbl::Array<const uint8_t> report_desc, zx::socket data)
: ddk::Device<HidDevice, ddk::Unbindable>(device),
boot_device_(config->boot_device),
dev_class_(config->dev_class),
report_desc_(std::move(report_desc)),
data_(std::move(data)) {
ZX_DEBUG_ASSERT(data_.is_valid());
int ret = thrd_create_with_name(&thread_, hid_device_thread, reinterpret_cast<void*>(this),
"hidctl-thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
}
void HidDevice::DdkRelease() {
zxlogf(TRACE, "hidctl: DdkRelease\n");
// Only the thread will call DdkRemove() when the loop exits. This detachs the thread before it
// exits, so no need to join.
delete this;
}
void HidDevice::DdkUnbind() {
zxlogf(TRACE, "hidctl: DdkUnbind\n");
Shutdown();
// The thread will call DdkRemove when it exits the loop.
}
zx_status_t HidDevice::HidbusQuery(uint32_t options, hid_info_t* info) {
zxlogf(TRACE, "hidctl: query\n");
info->dev_num = 0;
info->device_class = dev_class_;
info->boot_device = boot_device_;
return ZX_OK;
}
zx_status_t HidDevice::HidbusStart(const hidbus_ifc_protocol_t* ifc) {
zxlogf(TRACE, "hidctl: start\n");
fbl::AutoLock lock(&lock_);
if (client_.is_valid()) {
return ZX_ERR_ALREADY_BOUND;
}
client_ = ddk::HidbusIfcProtocolClient(ifc);
return ZX_OK;
}
void HidDevice::HidbusStop() {
zxlogf(TRACE, "hidctl: stop\n");
fbl::AutoLock lock(&lock_);
client_.clear();
}
zx_status_t HidDevice::HidbusGetDescriptor(uint8_t desc_type, void** data, size_t* len) {
zxlogf(TRACE, "hidctl: get descriptor %u\n", desc_type);
if (data == nullptr || len == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (desc_type != HID_DESCRIPTION_TYPE_REPORT) {
return ZX_ERR_NOT_FOUND;
}
*data = malloc(report_desc_.size());
if (*data == nullptr) {
return ZX_ERR_NO_MEMORY;
}
*len = report_desc_.size();
memcpy(*data, report_desc_.get(), report_desc_.size());
return ZX_OK;
}
zx_status_t HidDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id,
void* data, size_t len, size_t* out_len) {
zxlogf(TRACE, "hidctl: get report type=%u id=%u\n", rpt_type, rpt_id);
if (out_len == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
// TODO: send get report message over socket
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t HidDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id,
const void* data, size_t len) {
zxlogf(TRACE, "hidctl: set report type=%u id=%u\n", rpt_type, rpt_id);
// TODO: send set report message over socket
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t HidDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
zxlogf(TRACE, "hidctl: get idle\n");
// TODO: send get idle message over socket
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t HidDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) {
zxlogf(TRACE, "hidctl: set idle\n");
// TODO: send set idle message over socket
return ZX_OK;
}
zx_status_t HidDevice::HidbusGetProtocol(uint8_t* protocol) {
zxlogf(TRACE, "hidctl: get protocol\n");
// TODO: send get protocol message over socket
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t HidDevice::HidbusSetProtocol(uint8_t protocol) {
zxlogf(TRACE, "hidctl: set protocol\n");
// TODO: send set protocol message over socket
return ZX_OK;
}
int HidDevice::Thread() {
zxlogf(TRACE, "hidctl: starting main thread\n");
zx_signals_t pending;
fbl::unique_ptr<uint8_t[]> buf(new uint8_t[mtu_]);
zx_status_t status = ZX_OK;
const zx_signals_t wait = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED | HID_SHUTDOWN;
while (true) {
status = data_.wait_one(wait, zx::time::infinite(), &pending);
if (status != ZX_OK) {
zxlogf(ERROR, "hidctl: error waiting on data: %d\n", status);
break;
}
if (pending & ZX_SOCKET_READABLE) {
status = Recv(buf.get(), mtu_);
if (status != ZX_OK) {
break;
}
}
if (pending & ZX_SOCKET_PEER_CLOSED) {
zxlogf(TRACE, "hidctl: socket closed (peer)\n");
break;
}
if (pending & HID_SHUTDOWN) {
zxlogf(TRACE, "hidctl: socket closed (self)\n");
break;
}
}
zxlogf(INFO, "hidctl: device destroyed\n");
{
fbl::AutoLock lock(&lock_);
data_.reset();
thrd_detach(thread_);
}
DdkRemove();
return static_cast<int>(status);
}
void HidDevice::Shutdown() {
fbl::AutoLock lock(&lock_);
if (data_.is_valid()) {
// Prevent further writes to the socket
zx_status_t status = data_.shutdown(ZX_SOCKET_SHUTDOWN_READ);
ZX_DEBUG_ASSERT(status == ZX_OK);
// Signal the thread to shutdown
status = data_.signal(0, HID_SHUTDOWN);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
}
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\n", status);
return status;
}
fbl::AutoLock lock(&lock_);
if (unlikely(driver_get_log_flags() & DDK_LOG_TRACE)) {
zxlogf(TRACE, "hidctl: received %zu bytes\n", actual);
hexdump8_ex(buffer, actual, 0);
}
if (client_.is_valid()) {
client_.IoQueue(buffer, actual);
}
}
return ZX_OK;
}
} // namespace hidctl
extern "C" zx_status_t hidctl_bind(void* ctx, zx_device_t* device, void** cookie) {
auto dev = fbl::unique_ptr<hidctl::HidCtl>(new hidctl::HidCtl(device));
zx_status_t status = dev->DdkAdd("hidctl");
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not add device: %d\n", __func__, status);
} else {
// devmgr owns the memory now
__UNUSED auto ptr = dev.release();
}
return status;
}