| // 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <limits.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/unique_ptr.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/fzl/fdio.h> |
| #include <zircon/assert.h> |
| #include <zircon/input/c/fidl.h> |
| #include <zircon/listnode.h> |
| #include <zircon/threads.h> |
| #include <zircon/types.h> |
| |
| #include <utility> |
| |
| // 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] <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"); |
| } |
| |
| 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, zircon_input_ReportType* out_type) { |
| if ((arg == NULL) || (out_type == NULL)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| static const struct { |
| const char* name; |
| zircon_input_ReportType type; |
| } LUT[] = { |
| { .name = "in", .type = zircon_input_ReportType_INPUT }, |
| { .name = "out", .type = zircon_input_ReportType_OUTPUT }, |
| { .name = "feature", .type = zircon_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, |
| zircon_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<uint8_t>(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 = zircon_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 = zircon_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<uint8_t[]> buf(new uint8_t[report_desc_len]); |
| |
| size_t actual; |
| zx_status_t status = zircon_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 = zircon_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<uint8_t[]> ids(new uint8_t[num_reports]); |
| |
| size_t actual; |
| zx_status_t status = zircon_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 { |
| zircon_input_ReportType type; |
| const char* tag; |
| } TYPES[] = { |
| { .type = zircon_input_ReportType_INPUT, .tag = "Input" }, |
| { .type = zircon_input_ReportType_OUTPUT, .tag = "Output" }, |
| { .type = zircon_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 = zircon_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 = zircon_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<int>(rc); |
| } |
| |
| // Add 1 to the max report length to make room for a Report ID. |
| max_report_len++; |
| fbl::unique_ptr<uint8_t[]> 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 <num reads> (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; |
| zircon_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 = zircon_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<int>(-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<uint8_t[]> buf(new uint8_t[bufsz]); |
| res = zircon_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; |
| zircon_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<uint16_t>(argc - 3); |
| |
| uint16_t size; |
| zx_status_t call_status; |
| res = zircon_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<uint8_t[]> 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<uint8_t>(tmp); |
| } |
| |
| res = zircon_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<int>(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; |
| } |