From 2e60e95804e1998a0a67abda9be1cf8737bc36d3 Mon Sep 17 00:00:00 2001 From: Hyungwoo Yang Date: Fri, 12 Oct 2018 19:35:31 -0700 Subject: ISH: HID: implement HID subsystem Introduce HID subsystem. HID subsystem provides interface for a HID device to communicate with host. Using this API, a HID device can use hid-core in host. BUG=b:79676054 BRANCH=none TEST=Tested on Atlas board. CQ-DEPEND=CL:1279432 Change-Id: I0547a07e1c1cb5d34ba11b245ca539cf53b7d30d Reviewed-on: https://chromium-review.googlesource.com/1279433 Commit-Ready: ChromeOS CL Exonerator Bot Tested-by: Hyungwoo Yang Reviewed-by: Hyungwoo Yang Reviewed-by: Jett Rink (cherry picked from commit fe24755501c10405f02da94f3179ceb0bcde4fc3) Reviewed-on: https://chromium-review.googlesource.com/c/1392412 Reviewed-by: Caveh Jalali Commit-Queue: Caveh Jalali Tested-by: Caveh Jalali --- chip/ish/build.mk | 1 + chip/ish/hid_device.h | 83 ++++++++++ chip/ish/hid_subsys.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 chip/ish/hid_device.h create mode 100644 chip/ish/hid_subsys.c diff --git a/chip/ish/build.mk b/chip/ish/build.mk index 13b6ae2474..c938df70fd 100644 --- a/chip/ish/build.mk +++ b/chip/ish/build.mk @@ -22,6 +22,7 @@ chip-$(CONFIG_I2C)+=i2c.o chip-$(CONFIG_HOSTCMD_LPC)+=ipc.o chip-$(CONFIG_ISH_IPC)+=ipc_heci.o chip-$(CONFIG_HECI)+=heci.o system_state_subsys.o +chip-$(CONFIG_HID_SUBSYS)+=hid_subsys.o chip-$(CONFIG_WATCHDOG)+=watchdog.o # location of the scripts and keys used to pack the SPI flash image diff --git a/chip/ish/hid_device.h b/chip/ish/hid_device.h new file mode 100644 index 0000000000..0a32e305af --- /dev/null +++ b/chip/ish/hid_device.h @@ -0,0 +1,83 @@ +/* Copyright 2018 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __HID_DEVICE_H +#define __HID_DEVICE_H + +#include +#include + +#include "hooks.h" + +#define HID_SUBSYS_MAX_PAYLOAD_SIZE 4954 + +enum HID_SUBSYS_ERR { + HID_SUBSYS_ERR_NOT_READY = EC_ERROR_INTERNAL_FIRST + 0, + HID_SUBSYS_ERR_TOO_MANY_HID_DEVICES = EC_ERROR_INTERNAL_FIRST + 1, +}; + +typedef void * hid_handle_t; +#define HID_INVALID_HANDLE NULL + +struct hid_callbacks { + /* + * function called during registration. + * if returns non-zero, the registration will fail. + */ + int (*initialize)(const hid_handle_t handle); + + /* return size of data copied to buf. if returns <= 0, error */ + int (*get_hid_descriptor)(const hid_handle_t handle, uint8_t *buf, + const size_t buf_size); + /* return size of data copied to buf. if return <= 0, error */ + int (*get_report_descriptor)(const hid_handle_t handle, uint8_t *buf, + const size_t buf_size); + /* return size of data copied to buf. if return <= 0, error */ + int (*get_feature_report)(const hid_handle_t handle, + const uint8_t report_id, uint8_t *buf, + const size_t buf_size); + /* return tranferred data size. if returns <= 0, error */ + int (*set_feature_report)(const hid_handle_t handle, + const uint8_t report_id, const uint8_t *data, + const size_t data_size); + /* return size of data copied to buf. if returns <= 0, error */ + int (*get_input_report)(const hid_handle_t handle, + const uint8_t report_id, uint8_t *buf, + const size_t buf_size); + + /* suspend/resume, if returns non-zero, error */ + int (*resume)(const hid_handle_t handle); + int (*suspend)(const hid_handle_t handle); +}; + +struct hid_device { + uint8_t dev_class; + uint16_t pid; + uint16_t vid; + + const struct hid_callbacks *cbs; +}; + +/* + * Do not call this function directly. + * The function should be called only by HID_DEVICE_ENTRY() + */ +hid_handle_t hid_subsys_register_device(const struct hid_device *dev_info); +/* send HID input report */ +int hid_subsys_send_input_report(const hid_handle_t handle, uint8_t *buf, + const size_t buf_size); +/* store HID device specific data */ +int hid_subsys_set_device_data(const hid_handle_t handle, void *data); +/* retrieve HID device specific data */ +void *hid_subsys_get_device_data(const hid_handle_t handle); + +#define HID_DEVICE_ENTRY(hid_dev) \ + void _hid_dev_entry_##hid_dev(void) \ + { \ + hid_subsys_register_device(&(hid_dev)); \ + } \ + DECLARE_HOOK(HOOK_INIT, _hid_dev_entry_##hid_dev, HOOK_PRIO_LAST - 2) + +#endif /* __HID_DEVICE_H */ diff --git a/chip/ish/hid_subsys.c b/chip/ish/hid_subsys.c new file mode 100644 index 0000000000..bd3f331fdc --- /dev/null +++ b/chip/ish/hid_subsys.c @@ -0,0 +1,447 @@ +/* Copyright 2018 The Chromium OS 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 "compile_time_macros.h" +#include "console.h" +#include "heci_client.h" +#include "hid_device.h" +#include "util.h" + +#ifdef HID_SUBSYS_DEBUG +#define CPUTS(outstr) cputs(CC_LPC, outstr) +#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_LPC, format, ## args) +#else +#define CPUTS(outstr) +#define CPRINTS(format, args...) +#define CPRINTF(format, args...) +#endif + +#define __packed __attribute__((packed)) + +#define HECI_CLIENT_HID_GUID { 0x33AECD58, 0xB679, 0x4E54,\ + { 0x9B, 0xD9, 0xA0, 0x4D, 0x34, 0xF0, 0xC2, 0x26 } } + +#define HID_SUBSYS_MAX_HID_DEVICES 3 + +/* + * the following enum values and data structures with __packed are used for + * communicating with host driver and they are copied from host driver. + */ +enum { + HID_GET_HID_DESCRIPTOR = 0, + HID_GET_REPORT_DESCRIPTOR, + HID_GET_FEATURE_REPORT, + HID_SET_FEATURE_REPORT, + HID_GET_INPUT_REPORT, + HID_PUBLISH_INPUT_REPORT, + HID_PUBLISH_INPUT_REPORT_LIST, /* TODO: need to support batch report */ + + HID_HID_CLIENT_READY_CMD = 30, + HID_HID_COMMAND_MAX = 31, + + HID_DM_COMMAND_BASE, + HID_DM_ENUM_DEVICES, + HID_DM_ADD_DEVICE, + HID_COMMAND_LAST +}; + +struct hid_device_info { + uint32_t dev_id; + uint8_t dev_class; + uint16_t pid; + uint16_t vid; +} __packed; + +struct hid_enum_payload { + uint8_t num_of_hid_devices; + struct hid_device_info dev_info[0]; +} __packed; + +#define COMMAND_MASK 0x7F +#define RESPONSE_FLAG 0x80 +struct hid_msg_hdr { + uint8_t command; /* bit 7 is used to indicate "response" */ + uint8_t device_id; + uint8_t status; + uint8_t flags; + uint16_t size; +} __packed; + +struct hid_msg { + struct hid_msg_hdr hdr; + uint8_t payload[HID_SUBSYS_MAX_PAYLOAD_SIZE]; +} __packed; + +struct hid_subsys_hid_device { + struct hid_device_info info; + const struct hid_callbacks *cbs; + int can_send_hid_input; + + void *data; +}; + +struct hid_subsystem { + heci_handle_t heci_handle; + + uint32_t num_of_hid_devices; + struct hid_subsys_hid_device hid_devices[HID_SUBSYS_MAX_HID_DEVICES]; +}; + +static struct hid_subsystem hid_subsys_ctx = { + .heci_handle = HECI_INVALID_HANDLE, +}; + +#define handle_to_dev_id(_handle) ((uintptr_t)(_handle)) +#define dev_id_to_handle(_dev_id) ((hid_handle_t)(uintptr_t)(_dev_id)) + +static inline hid_handle_t device_index_to_handle(int device_index) +{ + return (hid_handle_t)(uintptr_t)(device_index + 1); +} + +static inline int is_valid_handle(hid_handle_t handle) +{ + return (uintptr_t)handle > 0 && + (uintptr_t)handle <= hid_subsys_ctx.num_of_hid_devices; +} + +static inline +struct hid_subsys_hid_device *handle_to_hid_device(hid_handle_t handle) +{ + if (!is_valid_handle(handle)) + return NULL; + + return &hid_subsys_ctx.hid_devices[(uintptr_t)handle - 1]; +} + + +hid_handle_t hid_subsys_register_device(const struct hid_device *dev_info) +{ + struct hid_subsys_hid_device *hid_device; + hid_handle_t handle; + int ret, hid_device_index; + + if (hid_subsys_ctx.num_of_hid_devices >= HID_SUBSYS_MAX_HID_DEVICES) + return HID_INVALID_HANDLE; + + hid_device_index = hid_subsys_ctx.num_of_hid_devices++; + + handle = device_index_to_handle(hid_device_index); + + hid_device = &hid_subsys_ctx.hid_devices[hid_device_index]; + + hid_device->info.dev_class = dev_info->dev_class; + hid_device->info.pid = dev_info->pid; + hid_device->info.vid = dev_info->vid; + hid_device->info.dev_id = handle_to_dev_id(handle); + + hid_device->cbs = dev_info->cbs; + + if (dev_info->cbs->initialize) { + ret = dev_info->cbs->initialize(handle); + if (ret) { + CPRINTF("initialize error %d\n", ret); + hid_subsys_ctx.num_of_hid_devices--; + return HID_INVALID_HANDLE; + } + } + + return handle; +} + +int hid_subsys_send_input_report(const hid_handle_t handle, uint8_t *buf, + const size_t buf_size) +{ + struct hid_subsys_hid_device *hid_device; + struct hid_msg_hdr hid_msg_hdr = {0}; + struct heci_msg_item msg_item[2]; + struct heci_msg_list msg_list; + + hid_device = handle_to_hid_device(handle); + if (!hid_device) + return -EC_ERROR_INVAL; + + if (buf_size > HID_SUBSYS_MAX_PAYLOAD_SIZE) + return -EC_ERROR_OVERFLOW; + + if (hid_subsys_ctx.heci_handle == HECI_INVALID_HANDLE) + return -HID_SUBSYS_ERR_NOT_READY; + + if (!hid_device->can_send_hid_input) + return -HID_SUBSYS_ERR_NOT_READY; + + hid_msg_hdr.command = HID_PUBLISH_INPUT_REPORT; + hid_msg_hdr.device_id = hid_device->info.dev_id; + hid_msg_hdr.size = buf_size; + + msg_item[0].size = sizeof(hid_msg_hdr); + msg_item[0].buf = (uint8_t *)&hid_msg_hdr; + + msg_item[1].size = buf_size; + msg_item[1].buf = buf; + + msg_list.num_of_items = 2; + msg_list.items[0] = &msg_item[0]; + msg_list.items[1] = &msg_item[1]; + + heci_send_msgs(hid_subsys_ctx.heci_handle, &msg_list); + + return 0; +} + +int hid_subsys_set_device_data(const hid_handle_t handle, void *data) +{ + struct hid_subsys_hid_device *hid_device; + + hid_device = handle_to_hid_device(handle); + if (!hid_device) + return -EC_ERROR_INVAL; + + hid_device->data = data; + + return 0; +} + +void *hid_subsys_get_device_data(const hid_handle_t handle) +{ + struct hid_subsys_hid_device *hid_device; + + hid_device = handle_to_hid_device(handle); + if (!hid_device) + return NULL; + + return hid_device->data; +} + +static int handle_hid_device_msg(struct hid_msg *hid_msg) +{ + int ret = 0, payload_size, buf_size; + uint8_t *payload; + struct hid_subsys_hid_device *hid_dev; + const struct hid_callbacks *cbs; + hid_handle_t handle; + + handle = dev_id_to_handle(hid_msg->hdr.device_id); + hid_dev = handle_to_hid_device(handle); + + if (!hid_dev) { + /* + * use HID_HID_COMMAND_MAX as error message. + * host driver will reset ISH. + */ + hid_msg->hdr.size = 0; + hid_msg->hdr.status = 0; + hid_msg->hdr.command |= RESPONSE_FLAG | HID_HID_COMMAND_MAX; + hid_msg->hdr.flags = 0; + + heci_send_msg(hid_subsys_ctx.heci_handle, (uint8_t *)hid_msg, + sizeof(hid_msg->hdr)); + + return 0; + } + + cbs = hid_dev->cbs; + + payload = hid_msg->payload; + payload_size = hid_msg->hdr.size; /* input data */ + buf_size = sizeof(hid_msg->payload); /* buffer to be written by cb */ + + /* + * re-use hid_msg from host for reply. + */ + switch (hid_msg->hdr.command & COMMAND_MASK) { + case HID_GET_HID_DESCRIPTOR: + if (cbs->get_hid_descriptor) + ret = cbs->get_hid_descriptor(handle, payload, + buf_size); + + break; + case HID_GET_REPORT_DESCRIPTOR: + if (cbs->get_report_descriptor) + ret = cbs->get_report_descriptor(handle, payload, + buf_size); + + hid_dev->can_send_hid_input = 1; + + break; + + case HID_GET_FEATURE_REPORT: + if (cbs->get_feature_report) + ret = cbs->get_feature_report(handle, payload[0], + payload, buf_size); + + break; + + case HID_SET_FEATURE_REPORT: + if (cbs->set_feature_report) { + ret = cbs->set_feature_report(handle, + payload[0], + payload, + payload_size); + /* + * if no error, reply only with the report id. + * re-use the first byte of payload + * from host that has report id + */ + if (ret >= 0) + ret = sizeof(uint8_t); + } + + break; + case HID_GET_INPUT_REPORT: + if (cbs->get_input_report) + ret = cbs->get_input_report(handle, payload[0], + payload, buf_size); + + break; + + default: + CPRINTF("invalid hid command %d, ignoring request\n", + hid_msg->hdr.command & COMMAND_MASK); + ret = -1; /* send error */ + } + + if (ret > 0) { + hid_msg->hdr.size = ret; + hid_msg->hdr.status = 0; + } else { /* error in callback */ + /* + * Note : errors of HID device should be transferred + * through their HID formatted data. + */ + hid_msg->hdr.size = 0; + hid_msg->hdr.status = -ret; + } + + hid_msg->hdr.command |= RESPONSE_FLAG; + hid_msg->hdr.flags = 0; + + heci_send_msg(hid_subsys_ctx.heci_handle, (uint8_t *)hid_msg, + sizeof(hid_msg->hdr) + hid_msg->hdr.size); + + return 0; +} + +static int handle_hid_subsys_msg(struct hid_msg *hid_msg) +{ + int size = 0, i; + struct hid_enum_payload *enum_payload; + + switch (hid_msg->hdr.command & COMMAND_MASK) { + case HID_DM_ENUM_DEVICES: + enum_payload = (struct hid_enum_payload *)hid_msg->payload; + + for (i = 0; i < hid_subsys_ctx.num_of_hid_devices; i++) { + enum_payload->dev_info[i] = + hid_subsys_ctx.hid_devices[i].info; + } + + enum_payload->num_of_hid_devices = + hid_subsys_ctx.num_of_hid_devices; + + /* reply payload size */ + size = sizeof(enum_payload->num_of_hid_devices); + size += enum_payload->num_of_hid_devices * + sizeof(enum_payload->dev_info[0]); + + break; + + default: + CPRINTF("invalid hid command %d, ignoring request\n", + hid_msg->hdr.command & COMMAND_MASK); + size = -1; /* send error */ + } + + if (size > 0) { + hid_msg->hdr.size = size; + hid_msg->hdr.status = 0; + } else { /* error in callback */ + hid_msg->hdr.size = 0; + hid_msg->hdr.status = -size; + } + + hid_msg->hdr.command |= RESPONSE_FLAG; + hid_msg->hdr.flags = 0; + + heci_send_msg(hid_subsys_ctx.heci_handle, (uint8_t *)hid_msg, + sizeof(hid_msg->hdr) + hid_msg->hdr.size); + + return 0; +} + +static void hid_subsys_new_msg_received(const heci_handle_t handle, + uint8_t *msg, const size_t msg_size) +{ + struct hid_msg *hid_msg = (struct hid_msg *)msg; + + /* workaround, since Host driver doesn't set size properly */ + if (hid_msg->hdr.size == 0 && msg_size > sizeof(hid_msg->hdr)) + hid_msg->hdr.size = msg_size - sizeof(hid_msg->hdr); + + if (hid_msg->hdr.size > HID_SUBSYS_MAX_PAYLOAD_SIZE) { + CPRINTF("too big payload size : %d. discard heci msg\n", + hid_msg->hdr); + return; /* invalid hdr. discard */ + } + + if (hid_msg->hdr.device_id) + handle_hid_device_msg(hid_msg); + else + handle_hid_subsys_msg(hid_msg); +} + +static int hid_subsys_initialize(const heci_handle_t heci_handle) +{ + hid_subsys_ctx.heci_handle = heci_handle; + + return 0; +} + +/* return zero if resume request handled successfully */ +static int hid_subsys_resume(const heci_handle_t heci_handle) +{ + int i, ret = 0; + + for (i = 0; i < hid_subsys_ctx.num_of_hid_devices; i++) { + if (hid_subsys_ctx.hid_devices[i].cbs->resume) + ret |= hid_subsys_ctx.hid_devices[i].cbs->resume( + device_index_to_handle(i)); + } + + return ret; +} + +/* return zero if suspend request handled successfully */ +static int hid_subsys_suspend(const heci_handle_t heci_handle) +{ + int i, ret = 0; + + for (i = hid_subsys_ctx.num_of_hid_devices - 1; i >= 0; i--) { + if (hid_subsys_ctx.hid_devices[i].cbs->suspend) + ret |= hid_subsys_ctx.hid_devices[i].cbs->suspend( + device_index_to_handle(i)); + } + + return ret; +} + +static const struct heci_client_callbacks hid_subsys_heci_cbs = { + .initialize = hid_subsys_initialize, + .new_msg_received = hid_subsys_new_msg_received, + .suspend = hid_subsys_suspend, + .resume = hid_subsys_resume, +}; + +static const struct heci_client hid_subsys_heci_client = { + .protocol_id = HECI_CLIENT_HID_GUID, + .max_msg_size = HECI_MAX_MSG_SIZE, + .protocol_ver = 1, + .max_n_of_connections = 1, + + .cbs = &hid_subsys_heci_cbs, +}; + +HECI_CLIENT_ENTRY(hid_subsys_heci_client); -- cgit v1.2.1