summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyungwoo Yang <hyungwoo.yang@intel.com>2018-10-12 19:35:31 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-12-28 16:14:01 -0800
commitfe24755501c10405f02da94f3179ceb0bcde4fc3 (patch)
tree5aa35ee8dfe94d53ded36056a93295f050f3b6df
parenta2c87e75cc5b42b900832f0e80244c077b497f2c (diff)
downloadchrome-ec-fe24755501c10405f02da94f3179ceb0bcde4fc3.tar.gz
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 <chromiumos-cl-exonerator@appspot.gserviceaccount.com> Tested-by: Hyungwoo Yang <hyungwoo.yang@intel.com> Reviewed-by: Hyungwoo Yang <hyungwoo.yang@intel.com> Reviewed-by: Jett Rink <jettrink@chromium.org>
-rw-r--r--chip/ish/build.mk1
-rw-r--r--chip/ish/hid_device.h83
-rw-r--r--chip/ish/hid_subsys.c447
3 files changed, 531 insertions, 0 deletions
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 <stdint.h>
+#include <stddef.h>
+
+#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);