summaryrefslogtreecommitdiff
path: root/common/usb_host_command.c
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2022-03-28 19:51:44 -0700
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-03-29 05:27:12 +0000
commit43868d4c15cf716036bf14c9454bac439433f03a (patch)
treedab547b99e1eb925f314e995f1b9644ed968e875 /common/usb_host_command.c
parent600e4bac22acf5ad40cb984e49bbae6e0df04c13 (diff)
downloadchrome-ec-43868d4c15cf716036bf14c9454bac439433f03a.tar.gz
USBHC: Add USB host command interface
This patch adds a host command interface for USB. BUG=b:211496726 BRANCH=None TEST=Prism on Vell. Change-Id: Icead7a1bdc593b3c4740ede0ddd5fc2cf5700bfa Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3354039 Reviewed-by: caveh jalali <caveh@chromium.org> Commit-Queue: caveh jalali <caveh@chromium.org>
Diffstat (limited to 'common/usb_host_command.c')
-rw-r--r--common/usb_host_command.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/common/usb_host_command.c b/common/usb_host_command.c
new file mode 100644
index 0000000000..ccae57dd43
--- /dev/null
+++ b/common/usb_host_command.c
@@ -0,0 +1,272 @@
+/* Copyright 2022 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 "common.h"
+#include "console.h"
+#include "consumer.h"
+#include "ec_commands.h"
+#include "queue_policies.h"
+#include "host_command.h"
+#include "system.h"
+#include "usb_api.h"
+#include "usb_hw.h"
+#include "usb-stream.h"
+#include "util.h"
+
+#define CPUTS(outstr) cputs(CC_USB, outstr)
+#define CPRINTS(format, args...) cprints(CC_HOSTCMD, "USBHC: " format, ## args)
+
+enum usbhc_state {
+ /* Not enabled (initial state, and when chipset is off) */
+ USBHC_STATE_DISABLED = 0,
+ /* Ready to receive next request */
+ USBHC_STATE_READY_TO_RX,
+ /* Receiving request */
+ USBHC_STATE_RECEIVING,
+ /* Processing request */
+ USBHC_STATE_PROCESSING,
+ /* Sending response */
+ USBHC_STATE_SENDING,
+ /* Received bad data */
+ USBHC_STATE_RX_BAD,
+} state;
+
+struct consumer const hostcmd_consumer;
+struct producer const hostcmd_producer;
+struct usb_stream_config const usbhc_stream;
+
+/* RX (Host->EC) queue */
+static struct queue const usb_to_hostcmd = QUEUE_DIRECT(64,
+ uint8_t,
+ usbhc_stream.producer,
+ hostcmd_consumer);
+/* TX (EC->Host) queue */
+static struct queue const hostcmd_to_usb = QUEUE_DIRECT(64,
+ uint8_t,
+ hostcmd_producer,
+ usbhc_stream.consumer);
+
+USB_STREAM_CONFIG_FULL(usbhc_stream,
+ USB_IFACE_HOSTCMD,
+ USB_CLASS_VENDOR_SPEC,
+ USB_SUBCLASS_GOOGLE_HOSTCMD,
+ USB_PROTOCOL_GOOGLE_HOSTCMD,
+ USB_STR_HOSTCMD_NAME,
+ USB_EP_HOSTCMD,
+ USB_MAX_PACKET_SIZE,
+ USB_MAX_PACKET_SIZE,
+ usb_to_hostcmd,
+ hostcmd_to_usb)
+
+static uint8_t in_msg[USBHC_MAX_REQUEST_SIZE];
+static uint8_t out_msg[USBHC_MAX_RESPONSE_SIZE];
+static uint32_t out_size;
+static struct host_packet usbhc_packet;
+static struct ec_host_request *header = (struct ec_host_request *)in_msg;
+
+static void usbhc_read(struct producer const *producer, size_t count)
+{
+ static uint32_t out_index;
+ size_t len;
+
+ len = MIN(producer->queue->buffer_units, out_size - out_index);
+ len = MIN(count, len);
+
+ /* If we're not sending, what's going on? */
+ if (state != USBHC_STATE_SENDING)
+ return;
+
+ /* Put a piece of a response in the Tx queue. */
+ QUEUE_ADD_UNITS(producer->queue, out_msg + out_index, len);
+ out_index += len;
+
+ if (out_index < out_size)
+ /* More data to send. */
+ return;
+
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("Tx complete (%u bytes)", out_index);
+ out_index = 0;
+ state = USBHC_STATE_READY_TO_RX;
+}
+
+struct producer const hostcmd_producer = {
+ .queue = &hostcmd_to_usb,
+ .ops = &((struct producer_ops const) {
+ .read = usbhc_read,
+ }),
+};
+
+/*
+ * Called when a command handler finished execution and prepared a response.
+ * It's supposed returns a response back to the host.
+ */
+static void usbhc_send_response_packet(struct host_packet *pkt)
+{
+ /*
+ * If we're not processing, then the AP has already terminated the
+ * transaction and won't be listening to a response.
+ */
+ if (state != USBHC_STATE_PROCESSING)
+ return;
+
+ if (sizeof(out_msg) < pkt->response_size) {
+ CPRINTS("Reponse size (%u) exceeds Tx buffer",
+ pkt->response_size);
+ return;
+ }
+
+ memcpy(out_msg, pkt->response, pkt->response_size);
+ out_size = pkt->response_size;
+ state = USBHC_STATE_SENDING;
+
+ usbhc_read(&hostcmd_producer, hostcmd_to_usb.buffer_units);
+}
+
+/*
+ * Pass a complete packet to the host command protocol subsystem.
+ */
+static void usbhc_process_packet(uint32_t pkt_size)
+{
+ usbhc_packet.send_response = usbhc_send_response_packet;
+ usbhc_packet.request = in_msg;
+ usbhc_packet.request_temp = NULL;
+ usbhc_packet.request_max = sizeof(in_msg);
+ usbhc_packet.request_size = pkt_size;
+
+ usbhc_packet.response = out_msg;
+ usbhc_packet.response_max = sizeof(out_msg);
+ usbhc_packet.response_size = 0;
+ usbhc_packet.driver_result = EC_RES_SUCCESS;
+
+ host_packet_receive(&usbhc_packet);
+}
+
+/*
+ * Called when usb-stream copies incoming data to the USBHC's Rx queue.
+ */
+static void usbhc_written(struct consumer const *consumer, size_t count)
+{
+ static uint32_t block_index;
+ static uint32_t expected_size;
+ static uint64_t prev_activity_timestamp;
+ uint64_t delta_time;
+
+ /* How much time since the previous USB callback? */
+ delta_time = get_time().val - prev_activity_timestamp;
+ prev_activity_timestamp += delta_time;
+
+ /* If a session lasts more than 5 seconds, let's start over. */
+ if ((delta_time > 5 * SECOND) && state != USBHC_STATE_READY_TO_RX) {
+ state = USBHC_STATE_READY_TO_RX;
+ CPRINTS("Recovering after timeout");
+ }
+
+ switch (state) {
+ case USBHC_STATE_READY_TO_RX:
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("Rx start. (count=%d)", count);
+ block_index = 0;
+ /* Only version 3 is supported. Using in_msg as a courtesy. */
+ QUEUE_REMOVE_UNITS(consumer->queue, in_msg, count);
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("%ph", HEX_BUF(in_msg, count));
+ if (in_msg[0] != EC_HOST_REQUEST_VERSION) {
+ CPRINTS("Unsupported version: %u", in_msg[0]);
+ return;
+ }
+ block_index += count;
+ expected_size = host_request_expected_size(header);
+ if (block_index < expected_size) {
+ state = USBHC_STATE_RECEIVING;
+ } else if (sizeof(in_msg) < expected_size) {
+ CPRINTS("Expected data size (%d) is too large",
+ expected_size);
+ state = USBHC_STATE_RX_BAD;
+ } else {
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("Rx complete (%d bytes)", block_index);
+ state = USBHC_STATE_PROCESSING;
+ usbhc_process_packet(block_index);
+ }
+ return;
+ case USBHC_STATE_RECEIVING:
+ /* Continue to receive the remaining data. */
+ break;
+ case USBHC_STATE_RX_BAD:
+ /*
+ * Once we're in RX_BAD, we'll discard the incoming data for 5
+ * seconds. We don't want to be READY too soon because most
+ * likely more anomalous data will come, hopefully the host will
+ * fix the situation.
+ */
+ queue_advance_head(consumer->queue, count);
+ return;
+ case USBHC_STATE_PROCESSING:
+ case USBHC_STATE_SENDING:
+ case USBHC_STATE_DISABLED:
+ /*
+ * Take no action and return though we may have resource to
+ * receive a new request. Host will get a buffer full error or
+ * timeout.
+ */
+ return;
+ }
+
+ /* Receive the remaining packet. */
+
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("Received %d bytes", count);
+
+ if (sizeof(in_msg) < block_index + count) {
+ CPRINTS("Rx buffer overflow");
+ state = USBHC_STATE_RX_BAD;
+ return;
+ }
+ QUEUE_REMOVE_UNITS(consumer->queue, in_msg + block_index, count);
+ block_index += count;
+
+ if (block_index < expected_size)
+ return; /* More to come. */
+
+ if (IS_ENABLED(DEBUG))
+ CPRINTS("Rx complete (%d bytes)", block_index);
+
+ if (expected_size < block_index) {
+ CPRINTS("Packet is larger than expected (%d)", expected_size);
+ state = USBHC_STATE_RX_BAD;
+ return;
+ }
+
+ /* Ok, the entire packet has been received and assembled. */
+ state = USBHC_STATE_PROCESSING;
+ usbhc_process_packet(block_index);
+}
+
+struct consumer const hostcmd_consumer = {
+ .queue = &usb_to_hostcmd,
+ .ops = &((struct consumer_ops const) {
+ .written = usbhc_written,
+ }),
+};
+
+static enum ec_status host_command_protocol_info(
+ struct host_cmd_handler_args *args)
+{
+ struct ec_response_get_protocol_info *r = args->response;
+
+ memset(r, 0, sizeof(*r));
+ r->protocol_versions |= BIT(3);
+ r->max_request_packet_size = USBHC_MAX_REQUEST_SIZE;
+ r->max_response_packet_size = USBHC_MAX_RESPONSE_SIZE;
+ r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED;
+
+ args->response_size = sizeof(*r);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO,
+ host_command_protocol_info,
+ EC_VER_MASK(0));