blob: 3d4cb34ae96773c34f3e26f802d9e2a761319bb4 [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/input/llcpp/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/watcher.h>
#include <lib/zx/event.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/listnode.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <optional>
#include <utility>
#include <vector>
#include <fbl/algorithm.h>
// defined in report.cpp
void print_report_descriptor(const uint8_t* rpt_desc, size_t desc_len);
#define DEV_INPUT "/dev/class/input"
static bool verbose = false;
#define xprintf(fmt...) \
do { \
if (verbose) \
printf(fmt); \
} while (0)
enum class Command { read, readall, get, set, parse };
void usage(void) {
printf("usage: hid [-v] <command> [<args>]\n\n");
printf(" commands:\n");
printf(" read [<devpath> [num reads]]\n");
printf(" get <devpath> <in|out|feature> <id>\n");
printf(" set <devpath> <in|out|feature> <id> [0xXX *]\n");
printf(" parse <devpath>\n");
}
constexpr size_t kDevPathSize = 128;
struct input_args_t {
Command command;
std::optional<llcpp::fuchsia::hardware::input::Device::SyncClient> sync_client;
char devpath[kDevPathSize];
size_t num_reads;
llcpp::fuchsia::hardware::input::ReportType report_type;
uint8_t report_id;
const char** data;
size_t data_size;
};
static thrd_t input_poll_thread;
static mtx_t print_lock = MTX_INIT;
#define lprintf(fmt...) \
do { \
mtx_lock(&print_lock); \
printf(fmt); \
mtx_unlock(&print_lock); \
} while (0)
static void print_hex(const uint8_t* buf, size_t len) {
for (size_t i = 0; i < len; i++) {
printf("%02x ", buf[i]);
if (i % 16 == 15)
printf("\n");
}
printf("\n");
}
static zx_status_t parse_uint_arg(const char* arg, uint32_t min, uint32_t max, uint32_t* out_val) {
if ((arg == NULL) || (out_val == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
bool is_hex = (arg[0] == '0') && (arg[1] == 'x');
if (sscanf(arg, is_hex ? "%x" : "%u", out_val) != 1) {
return ZX_ERR_INVALID_ARGS;
}
if ((*out_val < min) || (*out_val > max)) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
static zx_status_t parse_input_report_type(const char* arg,
llcpp::fuchsia::hardware::input::ReportType* out_type) {
if ((arg == NULL) || (out_type == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
static const struct {
const char* name;
llcpp::fuchsia::hardware::input::ReportType type;
} LUT[] = {
{.name = "in", .type = llcpp::fuchsia::hardware::input::ReportType::INPUT},
{.name = "out", .type = llcpp::fuchsia::hardware::input::ReportType::OUTPUT},
{.name = "feature", .type = llcpp::fuchsia::hardware::input::ReportType::FEATURE},
};
for (size_t i = 0; i < fbl::count_of(LUT); ++i) {
if (!strcasecmp(arg, LUT[i].name)) {
*out_type = LUT[i].type;
return ZX_OK;
}
}
return ZX_ERR_INVALID_ARGS;
}
static zx_status_t print_hid_protocol(input_args_t* args) {
auto result = args->sync_client->GetBootProtocol();
if (result.status() != ZX_OK) {
lprintf("hid: could not get protocol from %s (status=%d)\n", args->devpath, result.status());
} else {
lprintf("hid: %s proto=%d\n", args->devpath, static_cast<uint32_t>(result->protocol));
}
return ZX_OK;
}
static zx_status_t get_report_desc_len(input_args_t* args, size_t* report_desc_len) {
auto result = args->sync_client->GetReportDescSize();
if (result.status() != ZX_OK) {
lprintf("hid: could not get report descriptor length from %s (status=%d)\n", args->devpath,
result.status());
} else {
*report_desc_len = result->size;
lprintf("hid: %s report descriptor len=%zu\n", args->devpath, *report_desc_len);
}
return ZX_OK;
}
static zx_status_t print_report_desc(input_args_t* args, size_t report_desc_len) {
auto result = args->sync_client->GetReportDesc();
if (result.status() != ZX_OK) {
lprintf("hid: could not get report descriptor from %s (status=%d)\n", args->devpath,
result.status());
return result.status();
}
if (result->desc.count() != report_desc_len) {
lprintf("hid: got unexpected length on report descriptor: %zu versus %zu\n",
result->desc.count(), report_desc_len);
return ZX_ERR_BAD_STATE;
}
mtx_lock(&print_lock);
printf("hid: %s report descriptor:\n", args->devpath);
if (verbose) {
print_hex(result->desc.data(), result->desc.count());
}
print_report_descriptor(result->desc.data(), result->desc.count());
mtx_unlock(&print_lock);
return ZX_OK;
}
static zx_status_t get_num_reports(input_args_t* args, size_t* num_reports) {
auto result = args->sync_client->GetNumReports();
if (result.status() != ZX_OK) {
lprintf("hid: could not get number of reports from %s (status=%d)\n", args->devpath,
result.status());
} else {
*num_reports = result->count;
lprintf("hid: %s num reports: %zu\n", args->devpath, *num_reports);
}
return ZX_OK;
}
static zx_status_t print_report_ids(input_args_t* args, size_t num_reports) {
auto result = args->sync_client->GetReportIds();
if (result.status() != ZX_OK) {
lprintf("hid: could not get report ids from %s (status=%d)\n", args->devpath, result.status());
return ZX_OK;
}
if (result->ids.count() != num_reports) {
lprintf("hid: got unexpected number of reports: %zu versus %zu\n", result->ids.count(),
num_reports);
return ZX_ERR_BAD_STATE;
}
mtx_lock(&print_lock);
printf("hid: %s report ids...\n", args->devpath);
for (size_t i = 0; i < num_reports; i++) {
static const struct {
llcpp::fuchsia::hardware::input::ReportType type;
const char* tag;
} TYPES[] = {
{.type = llcpp::fuchsia::hardware::input::ReportType::INPUT, .tag = "Input"},
{.type = llcpp::fuchsia::hardware::input::ReportType::OUTPUT, .tag = "Output"},
{.type = llcpp::fuchsia::hardware::input::ReportType::FEATURE, .tag = "Feature"},
};
bool found = false;
for (size_t j = 0; j < fbl::count_of(TYPES); ++j) {
auto res = args->sync_client->GetReportSize(TYPES[j].type, result->ids[i]);
if (res.status() == ZX_OK && res->status == ZX_OK) {
printf(" ID 0x%02x : TYPE %7s : SIZE %u bytes\n", result->ids[i], TYPES[j].tag, res->size);
found = true;
}
}
if (!found) {
printf(" hid: failed to find any report sizes for report id 0x%02x's (dev %s)\n",
result->ids[i], args->devpath);
}
}
mtx_unlock(&print_lock);
return ZX_OK;
}
static zx_status_t get_max_report_len(input_args_t* args, uint16_t* max_report_len) {
auto result = args->sync_client->GetMaxInputReportSize();
if (result.status() != ZX_OK) {
lprintf("hid: could not get max report size from %s (status=%d)\n", args->devpath,
result.status());
} else {
lprintf("hid: %s maxreport=%u\n", args->devpath, result->size);
}
if (max_report_len) {
*max_report_len = result->size;
}
return ZX_OK;
}
#define TRY(fn) \
do { \
zx_status_t status = fn; \
if (status != ZX_OK) \
return status; \
} while (0)
static zx_status_t hid_status(input_args_t* args, uint16_t* max_report_len) {
size_t num_reports;
TRY(print_hid_protocol(args));
TRY(get_num_reports(args, &num_reports));
TRY(print_report_ids(args, num_reports));
TRY(get_max_report_len(args, max_report_len));
return ZX_OK;
}
static zx_status_t parse_rpt_descriptor(input_args_t* args) {
size_t report_desc_len;
TRY(get_report_desc_len(args, &report_desc_len));
TRY(print_report_desc(args, report_desc_len));
return ZX_OK;
}
#undef TRY
static zx_status_t hid_input_read_report(input_args_t* args, const zx::event& report_event,
size_t report_size, uint8_t* report_data,
size_t* returned_size) {
while (true) {
auto result = args->sync_client->ReadReport();
if (result.status() != ZX_OK) {
return result.status();
}
if (result->status == ZX_ERR_SHOULD_WAIT) {
report_event.wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), nullptr);
continue;
}
if (result->data.count() > report_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
*returned_size = result->data.count();
memcpy(report_data, result->data.data(), result->data.count());
return ZX_OK;
}
}
static int hid_read_reports(input_args_t* args) {
uint16_t max_report_len = 0;
ssize_t rc = hid_status(args, &max_report_len);
if (rc < 0) {
return static_cast<int>(rc);
}
zx_status_t status;
zx::event report_event;
auto result = args->sync_client->GetReportsEvent();
if ((result.status() != ZX_OK) || (result->status != ZX_OK)) {
mtx_lock(&print_lock);
printf("read returned error: (call_status=%d) (status=%d)\n", result.status(), result->status);
mtx_unlock(&print_lock);
return ZX_ERR_INTERNAL;
}
report_event = std::move(result->event);
// Add 1 to the max report length to make room for a Report ID.
max_report_len++;
std::vector<uint8_t> report(max_report_len);
for (uint32_t i = 0; i < args->num_reads; i++) {
size_t returned_size;
status =
hid_input_read_report(args, report_event, report.size(), report.data(), &returned_size);
if (status != ZX_OK) {
mtx_lock(&print_lock);
printf("hid_input_read_report returned %d\n", status);
mtx_unlock(&print_lock);
break;
}
mtx_lock(&print_lock);
printf("read returned %ld bytes\n", returned_size);
printf("hid: input from %s\n", args->devpath);
print_hex(report.data(), returned_size);
mtx_unlock(&print_lock);
}
lprintf("hid: closing %s\n", args->devpath);
return ZX_OK;
}
static int hid_input_thread(void* arg) {
input_args_t* args = (input_args_t*)arg;
lprintf("hid: input thread started for %s\n", args->devpath);
zx_status_t status = hid_read_reports(args);
delete args;
return status;
}
static zx_status_t hid_input_device_added(int dirfd, int event, const char* fn, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
int fd = openat(dirfd, fn, O_RDONLY);
if (fd < 0) {
return ZX_OK;
}
input_args_t* args = new input_args_t;
zx::channel chan;
zx_status_t status = fdio_get_service_handle(fd, chan.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
args->sync_client = llcpp::fuchsia::hardware::input::Device::SyncClient(std::move(chan));
// TODO: support setting num_reads across all devices. requires a way to
// signal shutdown to all input threads.
args->num_reads = ULONG_MAX;
snprintf(args->devpath, kDevPathSize, "%s", fn);
thrd_t t;
int ret = thrd_create_with_name(&t, hid_input_thread, (void*)args, args->devpath);
if (ret != thrd_success) {
printf("hid: input thread %s did not start (error=%d)\n", args->devpath, ret);
close(fd);
return thrd_status_to_zx_status(ret);
}
thrd_detach(t);
return ZX_OK;
}
static int hid_input_devices_poll_thread(void* arg) {
int dirfd = open(DEV_INPUT, O_DIRECTORY | O_RDONLY);
if (dirfd < 0) {
printf("hid: error opening %s\n", DEV_INPUT);
return ZX_ERR_INTERNAL;
}
fdio_watch_directory(dirfd, hid_input_device_added, ZX_TIME_INFINITE, NULL);
close(dirfd);
return -1;
}
int readall_reports() {
int ret = thrd_create_with_name(&input_poll_thread, hid_input_devices_poll_thread, NULL,
"hid-inputdev-poll");
if (ret != thrd_success) {
return -1;
}
thrd_join(input_poll_thread, NULL);
return 0;
}
// Get a single report from the device with a given report id and then print it.
int get_report(input_args_t* args) {
auto result = args->sync_client->GetReport(args->report_type, args->report_id);
if (result.status() != ZX_OK || result->status != ZX_OK) {
printf("hid: could not get report: %d, %d\n", result.status(), result->status);
return -1;
}
printf("hid: got %zu bytes\n", result->report.count());
print_hex(result->report.data(), result->report.count());
return 0;
}
int set_report(input_args_t* args) {
xprintf("hid: setting report size for id=0x%02x\n", args->report_id);
auto result = args->sync_client->GetReportSize(args->report_type, args->report_id);
if (result.status() != ZX_OK || result->status != ZX_OK) {
printf("hid: could not get report (id 0x%02x type %u) size from %s (status=%d, %d)\n",
args->report_id, static_cast<uint8_t>(args->report_type), args->devpath, result.status(),
result->status);
return -1;
}
xprintf("hid: report size=%u, tx payload size=%lu\n", result->size, args->data_size);
std::unique_ptr<uint8_t[]> report(new uint8_t[args->data_size]);
for (size_t i = 0; i < args->data_size; i++) {
uint32_t tmp;
zx_status_t res = parse_uint_arg(args->data[i], 0, 255, &tmp);
if (res != ZX_OK) {
printf("Failed to parse payload byte \"%s\" (res = %d)\n", args->data[i], res);
return res;
}
report[i] = static_cast<uint8_t>(tmp);
}
fidl::VectorView<uint8_t> report_view =
fidl::VectorView<uint8_t>(fidl::unowned_ptr(report.get()), args->data_size);
auto res =
args->sync_client->SetReport(args->report_type, args->report_id, std::move(report_view));
if (res.status() != ZX_OK || res->status != ZX_OK) {
printf("hid: could not set report: %d, %d\n", res.status(), res->status);
return -1;
}
printf("hid: success\n");
return 0;
}
zx_status_t parse_input_args(int argc, const char** argv, input_args_t* args) {
zx_status_t status;
// Move past the first arg which is just the binary.
argc--;
argv++;
if (argc == 0) {
return ZX_ERR_INVALID_ARGS;
}
if (!strcmp("-v", argv[0])) {
verbose = true;
argc--;
argv++;
}
if (argc == 0) {
return ZX_ERR_INVALID_ARGS;
}
// Parse the command name.
if (!strcmp("read", argv[0])) {
if (argc == 1) {
args->command = Command::readall;
return ZX_OK;
}
args->command = Command::read;
} else if (!strcmp("get", argv[0])) {
args->command = Command::get;
} else if (!strcmp("set", argv[0])) {
args->command = Command::set;
} else if (!strcmp("parse", argv[0])) {
args->command = Command::parse;
} else {
return ZX_ERR_INVALID_ARGS;
}
if (argc < 2) {
return ZX_ERR_INVALID_ARGS;
}
// Parse <devpath>
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("could not open %s: %d\n", argv[0], errno);
return ZX_ERR_INTERNAL;
}
zx::channel chan;
status = fdio_get_service_handle(fd, chan.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
args->sync_client = llcpp::fuchsia::hardware::input::Device::SyncClient(std::move(chan));
snprintf(args->devpath, kDevPathSize, "%s", argv[1]);
if (args->command == Command::parse) {
if (argc > 2) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
// Parse Read arguments.
if (args->command == Command::read) {
if (argc == 3) {
uint32_t tmp = std::numeric_limits<uint32_t>::max();
status = parse_uint_arg(argv[2], 0, std::numeric_limits<uint32_t>::max(), &tmp);
if (status != ZX_OK) {
return status;
}
args->num_reads = tmp;
} else if (argc == 2) {
args->num_reads = std::numeric_limits<uint32_t>::max();
} else {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
if (argc < 4) {
return ZX_ERR_INTERNAL;
}
// Parse ReportType argument.
llcpp::fuchsia::hardware::input::ReportType report_type;
status = parse_input_report_type(argv[2], &report_type);
if (status != ZX_OK) {
return status;
}
args->report_type = report_type;
// Parse Report id.
uint32_t report_id = 255;
status = parse_uint_arg(argv[3], 0, 255, &report_id);
if (status != ZX_OK) {
return status;
}
args->report_id = static_cast<uint8_t>(report_id);
if ((args->command == Command::get) && (argc > 4)) {
return ZX_ERR_INVALID_ARGS;
}
if ((args->command == Command::set) && (argc > 4)) {
args->data_size = argc - 4;
args->data = &argv[4];
}
return ZX_OK;
}
int main(int argc, const char** argv) {
input_args_t args = {};
zx_status_t status = parse_input_args(argc, argv, &args);
if (status != ZX_OK) {
usage();
return 1;
}
if (args.command == Command::parse) {
return parse_rpt_descriptor(&args);
} else if (args.command == Command::get) {
return get_report(&args);
} else if (args.command == Command::set) {
return set_report(&args);
} else if (args.command == Command::read) {
return hid_read_reports(&args);
} else if (args.command == Command::readall) {
return readall_reports();
}
return 1;
}