// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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) void usage(void) { printf("usage: hid [-v] []\n\n"); printf(" commands:\n"); printf(" read [ [num reads]]\n"); printf(" get \n"); printf(" set [0xXX *]\n"); printf(" parse \n"); } typedef struct input_args { fbl::unique_fd fd; char name[128]; unsigned long int num_reads; } input_args_t; 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(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, fuchsia_hardware_input_ReportType* out_type) { if ((arg == NULL) || (out_type == NULL)) { return ZX_ERR_INVALID_ARGS; } static const struct { const char* name; fuchsia_hardware_input_ReportType type; } LUT[] = { {.name = "in", .type = fuchsia_hardware_input_ReportType_INPUT}, {.name = "out", .type = fuchsia_hardware_input_ReportType_OUTPUT}, {.name = "feature", .type = 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 parse_set_get_report_args(int argc, const char** argv, uint8_t* out_id, fuchsia_hardware_input_ReportType* out_type) { if ((argc < 3) || (argv == NULL) || (out_id == NULL) || (out_type == NULL)) { return ZX_ERR_INVALID_ARGS; } zx_status_t res; uint32_t tmp; res = parse_uint_arg(argv[2], 0, 255, &tmp); if (res != ZX_OK) { return res; } *out_id = static_cast(tmp); return parse_input_report_type(argv[1], out_type); } static zx_status_t get_hid_protocol(const fzl::FdioCaller& caller, const char* name) { uint32_t proto; zx_status_t status = fuchsia_hardware_input_DeviceGetBootProtocol(caller.borrow_channel(), &proto); if (status != ZX_OK) { lprintf("hid: could not get protocol from %s (status=%d)\n", name, status); } else { lprintf("hid: %s proto=%d\n", name, proto); } return status; } static zx_status_t get_report_desc_len(const fzl::FdioCaller& caller, const char* name, size_t* report_desc_len) { uint16_t len; zx_status_t status = fuchsia_hardware_input_DeviceGetReportDescSize(caller.borrow_channel(), &len); if (status != ZX_OK) { lprintf("hid: could not get report descriptor length from %s (status=%d)\n", name, status); } else { *report_desc_len = len; lprintf("hid: %s report descriptor len=%zu\n", name, *report_desc_len); } return status; } static zx_status_t get_report_desc(const fzl::FdioCaller& caller, const char* name, size_t report_desc_len) { fbl::unique_ptr buf(new uint8_t[report_desc_len]); size_t actual; zx_status_t status = fuchsia_hardware_input_DeviceGetReportDesc( caller.borrow_channel(), buf.get(), report_desc_len, &actual); if (status != ZX_OK) { lprintf("hid: could not get report descriptor from %s (status=%d)\n", name, status); return status; } if (actual != report_desc_len) { lprintf("hid: got unexpected length on report descriptor: %zu versus %zu\n", actual, report_desc_len); return ZX_ERR_BAD_STATE; } mtx_lock(&print_lock); printf("hid: %s report descriptor:\n", name); if (verbose) { print_hex(buf.get(), report_desc_len); } print_report_descriptor(buf.get(), report_desc_len); mtx_unlock(&print_lock); return status; } static zx_status_t get_num_reports(const fzl::FdioCaller& caller, const char* name, size_t* num_reports) { uint16_t count; zx_status_t status = fuchsia_hardware_input_DeviceGetNumReports(caller.borrow_channel(), &count); if (status != ZX_OK) { lprintf("hid: could not get number of reports from %s (status=%d)\n", name, status); } else { *num_reports = count; lprintf("hid: %s num reports: %zu\n", name, *num_reports); } return status; } static zx_status_t get_report_ids(const fzl::FdioCaller& caller, const char* name, size_t num_reports) { fbl::unique_ptr ids(new uint8_t[num_reports]); size_t actual; zx_status_t status = fuchsia_hardware_input_DeviceGetReportIds(caller.borrow_channel(), ids.get(), num_reports, &actual); if (status != ZX_OK) { lprintf("hid: could not get report ids from %s (status=%d)\n", name, status); return status; } if (actual != num_reports) { lprintf("hid: got unexpected number of reports: %zu versus %zu\n", actual, num_reports); return ZX_ERR_BAD_STATE; } mtx_lock(&print_lock); printf("hid: %s report ids...\n", name); for (size_t i = 0; i < num_reports; i++) { static const struct { fuchsia_hardware_input_ReportType type; const char* tag; } TYPES[] = { {.type = fuchsia_hardware_input_ReportType_INPUT, .tag = "Input"}, {.type = fuchsia_hardware_input_ReportType_OUTPUT, .tag = "Output"}, {.type = fuchsia_hardware_input_ReportType_FEATURE, .tag = "Feature"}, }; bool found = false; for (size_t j = 0; j < fbl::count_of(TYPES); ++j) { uint16_t size; zx_status_t call_status; status = fuchsia_hardware_input_DeviceGetReportSize( caller.borrow_channel(), TYPES[j].type, ids[i], &call_status, &size); if (status == ZX_OK && call_status == ZX_OK) { printf(" ID 0x%02x : TYPE %7s : SIZE %u bytes\n", ids[i], TYPES[j].tag, size); found = true; } } if (!found) { printf(" hid: failed to find any report sizes for report id 0x%02x's (dev %s)\n", ids[i], name); } } mtx_unlock(&print_lock); return status; } static zx_status_t get_max_report_len(const fzl::FdioCaller& caller, const char* name, uint16_t* max_report_len) { uint16_t tmp; if (max_report_len == NULL) { max_report_len = &tmp; } zx_status_t status = fuchsia_hardware_input_DeviceGetMaxInputReportSize(caller.borrow_channel(), max_report_len); if (status != ZX_OK) { lprintf("hid: could not get max report size from %s (status=%d)\n", name, status); } else { lprintf("hid: %s maxreport=%u\n", name, *max_report_len); } return status; } #define TRY(fn) \ do { \ zx_status_t status = fn; \ if (status != ZX_OK) \ return status; \ } while (0) static zx_status_t hid_status(const fzl::FdioCaller& caller, const char* name, uint16_t* max_report_len) { size_t num_reports; TRY(get_hid_protocol(caller, name)); TRY(get_num_reports(caller, name, &num_reports)); TRY(get_report_ids(caller, name, num_reports)); TRY(get_max_report_len(caller, name, max_report_len)); return ZX_OK; } static zx_status_t parse_rpt_descriptor(const fzl::FdioCaller& caller, const char* name) { size_t report_desc_len; TRY(get_report_desc_len(caller, "", &report_desc_len)); TRY(get_report_desc(caller, "", report_desc_len)); return ZX_OK; } #undef TRY static int hid_input_thread(void* arg) { input_args_t* args = (input_args_t*)arg; lprintf("hid: input thread started for %s\n", args->name); fzl::FdioCaller caller(std::move(args->fd)); uint16_t max_report_len = 0; ssize_t rc = hid_status(caller, args->name, &max_report_len); if (rc < 0) { return static_cast(rc); } // Add 1 to the max report length to make room for a Report ID. max_report_len++; fbl::unique_ptr report(new uint8_t[max_report_len]); args->fd = caller.release(); for (uint32_t i = 0; i < args->num_reads; i++) { ssize_t r = read(args->fd.get(), report.get(), max_report_len); mtx_lock(&print_lock); printf("read returned %ld\n", r); if (r < 0) { printf("read errno=%d (%s)\n", errno, strerror(errno)); mtx_unlock(&print_lock); break; } printf("hid: input from %s\n", args->name); print_hex(report.get(), r); mtx_unlock(&print_lock); } lprintf("hid: closing %s\n", args->name); delete args; return ZX_OK; } 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 {}; args->fd = fbl::unique_fd(fd); // TODO: support setting num_reads across all devices. requires a way to // signal shutdown to all input threads. args->num_reads = ULONG_MAX; thrd_t t; snprintf(args->name, sizeof(args->name), "hid-input-%s", fn); int ret = thrd_create_with_name(&t, hid_input_thread, (void*)args, args->name); if (ret != thrd_success) { printf("hid: input thread %s did not start (error=%d)\n", args->name, 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 read_reports(int argc, const char** argv) { argc--; argv++; if (argc < 1) { usage(); return 0; } uint32_t tmp = 0xffffffff; if (argc > 1) { zx_status_t res = parse_uint_arg(argv[1], 0, 0xffffffff, &tmp); if (res != ZX_OK) { printf("Failed to parse (res %d)\n", res); usage(); return 0; } } int fd = open(argv[0], O_RDWR); if (fd < 0) { printf("could not open %s: %d\n", argv[0], errno); return -1; } input_args_t* args = new input_args_t {}; args->fd = fbl::unique_fd(fd); args->num_reads = tmp; strlcpy(args->name, argv[0], sizeof(args->name)); thrd_t t; int ret = thrd_create_with_name(&t, hid_input_thread, (void*)args, args->name); if (ret != thrd_success) { printf("hid: input thread %s did not start (error=%d)\n", args->name, ret); delete args; return -1; } thrd_join(t, NULL); return 0; } int readall_reports(int argc, const char** argv) { 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; } int get_report(int argc, const char** argv) { argc--; argv++; if (argc < 3) { usage(); return 0; } uint8_t id; fuchsia_hardware_input_ReportType type; zx_status_t res = parse_set_get_report_args(argc, argv, &id, &type); if (res != ZX_OK) { printf("Failed to parse type/id for get report operation (res %d)\n", res); usage(); return 0; } int fd = open(argv[0], O_RDWR); if (fd < 0) { printf("could not open %s: %d\n", argv[0], errno); return -1; } fzl::FdioCaller caller{fbl::unique_fd(fd)}; xprintf("hid: getting report size for id=0x%02x type=%u\n", id, type); uint16_t size; zx_status_t call_status; res = fuchsia_hardware_input_DeviceGetReportSize(caller.borrow_channel(), type, id, &call_status, &size); if (res != ZX_OK || call_status != ZX_OK) { printf("hid: could not get report (id 0x%02x type %u) size from %s (status=%d, %d)\n", id, type, argv[0], res, call_status); return static_cast(-1); } xprintf("hid: report size=%u\n", size); // TODO(johngro) : Come up with a better policy than this... While devices // are *supposed* to only deliver a report descriptor's computed size, in // practice they frequently seem to deliver number of bytes either greater // or fewer than the number of bytes originally requested. For example... // // ++ Sometimes a device is expected to deliver a Report ID byte along with // the payload contents, but does not do so. // ++ Sometimes it is unclear whether or not a device needs to deliver a // Report ID byte at all since there is only one report listed (and, // sometimes the device delivers that ID, and sometimes it chooses not // to). // ++ Sometimes no bytes at all are returned for a report (this seems to // be relatively common for input reports) // ++ Sometimes the number of bytes returned has basically nothing to do // with the expected size of the report (this seems to be relatively // common for vendor feature reports). // // Because of this uncertainty, we currently just provide a worst-case 4KB // buffer to read into, and report the number of bytes which came back along // with the expected size of the raw report. size_t bufsz = 4u << 10; size_t actual; fbl::unique_ptr buf(new uint8_t[bufsz]); res = fuchsia_hardware_input_DeviceGetReport(caller.borrow_channel(), type, id, &call_status, buf.get(), bufsz, &actual); if (res != ZX_OK || call_status != ZX_OK) { printf("hid: could not get report: %d, %d\n", res, call_status); return -1; } printf("hid: got %zu bytes (raw report size %u)\n", actual, size); print_hex(buf.get(), actual); return 0; } int set_report(int argc, const char** argv) { argc--; argv++; if (argc < 4) { usage(); return 0; } uint8_t id; fuchsia_hardware_input_ReportType type; zx_status_t res = parse_set_get_report_args(argc, argv, &id, &type); if (res != ZX_OK) { printf("Failed to parse type/id for get report operation (res %d)\n", res); usage(); return 0; } xprintf("hid: setting report size for id=0x%02x type=%u\n", id, type); int fd = open(argv[0], O_RDWR); if (fd < 0) { printf("could not open %s: %d\n", argv[0], errno); return -1; } fzl::FdioCaller caller{fbl::unique_fd(fd)}; // If the set/get report args parsed, then we must have at least 3 arguments. ZX_DEBUG_ASSERT(argc >= 3); uint16_t payload_size = static_cast(argc - 3); uint16_t size; zx_status_t call_status; res = fuchsia_hardware_input_DeviceGetReportSize(caller.borrow_channel(), type, id, &call_status, &size); if (res != ZX_OK || call_status != ZX_OK) { printf("hid: could not get report (id 0x%02x type %u) size from %s (status=%d, %d)\n", id, type, argv[0], res, call_status); return -1; } xprintf("hid: report size=%u, tx payload size=%u\n", size, payload_size); fbl::unique_ptr report(new uint8_t[payload_size]); for (int i = 0; i < payload_size; i++) { uint32_t tmp; zx_status_t res = parse_uint_arg(argv[i+3], 0, 255, &tmp); if (res != ZX_OK) { printf("Failed to parse payload byte \"%s\" (res = %d)\n", argv[i+3], res); return res; } report[i] = static_cast(tmp); } res = fuchsia_hardware_input_DeviceSetReport(caller.borrow_channel(), type, id, report.get(), payload_size, &call_status); if (res != ZX_OK || call_status != ZX_OK) { printf("hid: could not set report: %d, %d\n", res, call_status); return -1; } printf("hid: success\n"); return 0; } int parse(int argc, const char** argv) { argc--; argv++; if (argc < 1) { usage(); return 0; } int fd = open(argv[0], O_RDWR); if (fd < 0) { printf("could not open %s: %d\n", argv[0], errno); return -1; } fzl::FdioCaller caller{fbl::unique_fd(fd)}; zx_status_t status = parse_rpt_descriptor(caller, argv[0]); return static_cast(status); } int main(int argc, const char** argv) { if (argc < 2) { usage(); return 0; } argc--; argv++; if (!strcmp("-v", argv[0])) { verbose = true; argc--; argv++; } if (!strcmp("read", argv[0])) { if (argc > 1) { return read_reports(argc, argv); } else { return readall_reports(argc, argv); } } if (!strcmp("get", argv[0])) { return get_report(argc, argv); } if (!strcmp("set", argv[0])) { return set_report(argc, argv); } if (!strcmp("parse", argv[0])) { return parse(argc, argv); } usage(); return 0; }