933 lines
23 KiB
C
933 lines
23 KiB
C
/* Copyright 2012 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.
|
|
*/
|
|
|
|
/* Host command module for Chrome EC */
|
|
|
|
#include "ap_hang_detect.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "ec_commands.h"
|
|
#include "host_command.h"
|
|
#include "link_defs.h"
|
|
#include "lpc.h"
|
|
#include "shared_mem.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_HOSTCMD, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_HOSTCMD, format, ## args)
|
|
#define CPRINTS(format, args...) cprints(CC_HOSTCMD, format, ## args)
|
|
|
|
#define TASK_EVENT_CMD_PENDING TASK_EVENT_CUSTOM_BIT(0)
|
|
|
|
/* Maximum delay to skip printing repeated host command debug output */
|
|
#define HCDEBUG_MAX_REPEAT_DELAY (50 * MSEC)
|
|
|
|
/* Stop printing repeated host commands "+" after this count */
|
|
#define HCDEBUG_MAX_REPEAT_COUNT 5
|
|
|
|
static struct host_cmd_handler_args *pending_args;
|
|
|
|
/* Verify Boot Mode */
|
|
static int g_vboot_mode;
|
|
|
|
#ifndef CONFIG_HOSTCMD_X86
|
|
/*
|
|
* Simulated memory map. Must be word-aligned, because some of the elements
|
|
* in the memory map are words.
|
|
*/
|
|
static uint8_t host_memmap[EC_MEMMAP_SIZE] __aligned(4);
|
|
#endif
|
|
|
|
static enum {
|
|
HCDEBUG_OFF, /* No host command debug output */
|
|
HCDEBUG_NORMAL, /* Normal output mode; skips repeated commands */
|
|
HCDEBUG_EVERY, /* Print every command */
|
|
HCDEBUG_PARAMS, /* ... and print params for request/response */
|
|
|
|
/* Number of host command debug modes */
|
|
HCDEBUG_MODES
|
|
} hcdebug = CONFIG_HOSTCMD_DEBUG_MODE;
|
|
|
|
#ifdef CONFIG_CMD_HCDEBUG
|
|
static const char * const hcdebug_mode_names[HCDEBUG_MODES] = {
|
|
"off", "normal", "every", "params"};
|
|
#endif
|
|
|
|
#ifdef CONFIG_HOST_COMMAND_STATUS
|
|
/*
|
|
* Indicates that a 'slow' command has sent EC_RES_IN_PROGRESS but hasn't
|
|
* sent a final status (i.e. it is in progress)
|
|
*/
|
|
static uint8_t command_pending;
|
|
|
|
/* The result of the last 'slow' operation */
|
|
static uint8_t saved_result = EC_RES_UNAVAILABLE;
|
|
#endif
|
|
|
|
/*
|
|
* Host command args passed to command handler. Static to keep it off the
|
|
* stack. Note this means we can handle only one host command at a time.
|
|
*/
|
|
static struct host_cmd_handler_args args0;
|
|
|
|
/* Current host command packet from host, for protocol version 3+ */
|
|
static struct host_packet *pkt0;
|
|
|
|
/*
|
|
* Host command suppress
|
|
*/
|
|
#ifdef CONFIG_SUPPRESSED_HOST_COMMANDS
|
|
#define SUPPRESSED_CMD_INTERVAL (60UL * 60 * SECOND)
|
|
static timestamp_t suppressed_cmd_deadline;
|
|
static const uint16_t hc_suppressed_cmd[] = { CONFIG_SUPPRESSED_HOST_COMMANDS };
|
|
static uint32_t hc_suppressed_cnt[ARRAY_SIZE(hc_suppressed_cmd)];
|
|
#endif
|
|
|
|
uint8_t *host_get_memmap(int offset)
|
|
{
|
|
#ifdef CONFIG_HOSTCMD_X86
|
|
return lpc_get_memmap_range() + offset;
|
|
#else
|
|
return host_memmap + offset;
|
|
#endif
|
|
}
|
|
|
|
int host_get_vboot_mode(void)
|
|
{
|
|
return g_vboot_mode;
|
|
}
|
|
|
|
test_mockable void host_send_response(struct host_cmd_handler_args *args)
|
|
{
|
|
#ifdef CONFIG_HOST_COMMAND_STATUS
|
|
/*
|
|
*
|
|
* If we are in interrupt context, then we are handling a get_status
|
|
* response or an immediate error which prevented us from processing
|
|
* the command. Note we can't check for the GET_COMMS_STATUS command in
|
|
* args->command because the original command value has now been
|
|
* overwritten.
|
|
*
|
|
* When a EC_CMD_RESEND_RESPONSE arrives we will supply this response
|
|
* to that command.
|
|
*/
|
|
if (!in_interrupt_context()) {
|
|
if (command_pending) {
|
|
/*
|
|
* We previously got EC_RES_IN_PROGRESS. This must be
|
|
* the completion of that command, so stash the result
|
|
* code.
|
|
*/
|
|
CPRINTS("HC pending done, size=%d, result=%d",
|
|
args->response_size, args->result);
|
|
|
|
/*
|
|
* We don't support stashing response data, so mark the
|
|
* response as unavailable in that case.
|
|
*/
|
|
if (args->response_size != 0)
|
|
saved_result = EC_RES_UNAVAILABLE;
|
|
else
|
|
saved_result = args->result;
|
|
|
|
/*
|
|
* We can't send the response back to the host now
|
|
* since we already sent the in-progress response and
|
|
* the host is on to other things now.
|
|
*/
|
|
command_pending = 0;
|
|
return;
|
|
|
|
} else if (args->result == EC_RES_IN_PROGRESS) {
|
|
command_pending = 1;
|
|
CPRINTS("HC pending");
|
|
}
|
|
}
|
|
#endif
|
|
args->send_response(args);
|
|
}
|
|
|
|
void host_command_received(struct host_cmd_handler_args *args)
|
|
{
|
|
/*
|
|
* TODO(crosbug.com/p/23806): should warn if we already think we're in
|
|
* a command.
|
|
*/
|
|
|
|
/*
|
|
* If this is the reboot command, reboot immediately. This gives the
|
|
* host processor a way to unwedge the EC even if it's busy with some
|
|
* other command.
|
|
*/
|
|
if (args->command == EC_CMD_REBOOT) {
|
|
system_reset(SYSTEM_RESET_HARD);
|
|
/* Reset should never return; if it does, post an error */
|
|
args->result = EC_RES_ERROR;
|
|
}
|
|
|
|
#ifdef CONFIG_AP_HANG_DETECT
|
|
/* If hang detection is enabled, check stop on host command */
|
|
hang_detect_stop_on_host_command();
|
|
#endif
|
|
|
|
if (args->result) {
|
|
; /* driver has signalled an error, respond now */
|
|
#ifdef CONFIG_HOST_COMMAND_STATUS
|
|
} else if (args->command == EC_CMD_GET_COMMS_STATUS) {
|
|
args->result = host_command_process(args);
|
|
#endif
|
|
} else {
|
|
/* Save the command */
|
|
pending_args = args;
|
|
|
|
/* Wake up the task to handle the command */
|
|
task_set_event(TASK_ID_HOSTCMD, TASK_EVENT_CMD_PENDING, 0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* TODO (crosbug.com/p/29315): This is typically running in interrupt
|
|
* context, so it would be better not to send the response here, and to
|
|
* let the host command task send the response.
|
|
*/
|
|
/* Send the response now */
|
|
host_send_response(args);
|
|
}
|
|
|
|
void host_packet_respond(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_host_response *r = (struct ec_host_response *)pkt0->response;
|
|
uint8_t *out = (uint8_t *)pkt0->response;
|
|
int csum = 0;
|
|
int i;
|
|
|
|
/* Clip result size to what we can accept */
|
|
if (args->result) {
|
|
/* Error results don't have data */
|
|
args->response_size = 0;
|
|
} else if (args->response_size > pkt0->response_max - sizeof(*r)) {
|
|
/* Too much data */
|
|
args->result = EC_RES_RESPONSE_TOO_BIG;
|
|
args->response_size = 0;
|
|
}
|
|
|
|
/* Fill in response struct */
|
|
r->struct_version = EC_HOST_RESPONSE_VERSION;
|
|
r->checksum = 0;
|
|
r->result = args->result;
|
|
r->data_len = args->response_size;
|
|
r->reserved = 0;
|
|
|
|
/* Start checksum; this also advances *out to end of response */
|
|
for (i = sizeof(*r); i > 0; i--)
|
|
csum += *out++;
|
|
|
|
/* Checksum response data, if any */
|
|
for (i = args->response_size; i > 0; i--)
|
|
csum += *out++;
|
|
|
|
/* Write checksum field so the entire packet sums to 0 */
|
|
r->checksum = (uint8_t)(-csum);
|
|
|
|
pkt0->response_size = sizeof(*r) + r->data_len;
|
|
pkt0->driver_result = args->result;
|
|
pkt0->send_response(pkt0);
|
|
}
|
|
|
|
int host_request_expected_size(const struct ec_host_request *r)
|
|
{
|
|
/* Check host request version */
|
|
if (r->struct_version != EC_HOST_REQUEST_VERSION)
|
|
return 0;
|
|
|
|
/* Reserved byte should be 0 */
|
|
if (r->reserved)
|
|
return 0;
|
|
|
|
return sizeof(*r) + r->data_len;
|
|
}
|
|
|
|
void host_packet_receive(struct host_packet *pkt)
|
|
{
|
|
const struct ec_host_request *r =
|
|
(const struct ec_host_request *)pkt->request;
|
|
const uint8_t *in = (const uint8_t *)pkt->request;
|
|
uint8_t *itmp = (uint8_t *)pkt->request_temp;
|
|
int csum = 0;
|
|
int i;
|
|
|
|
/* Track the packet we're handling */
|
|
pkt0 = pkt;
|
|
|
|
/* If driver indicates error, don't even look at the data */
|
|
if (pkt->driver_result) {
|
|
args0.result = pkt->driver_result;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
if (pkt->request_size < sizeof(*r)) {
|
|
/* Packet too small for even a header */
|
|
args0.result = EC_RES_REQUEST_TRUNCATED;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
if (pkt->request_size > pkt->request_max) {
|
|
/* Got a bigger request than the interface can handle */
|
|
args0.result = EC_RES_REQUEST_TRUNCATED;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
/*
|
|
* Response buffer needs to be big enough for a header. If it's not
|
|
* we can't even return an error packet.
|
|
*/
|
|
ASSERT(pkt->response_max >= sizeof(struct ec_host_response));
|
|
|
|
/* Start checksum and copy request header if necessary */
|
|
if (pkt->request_temp) {
|
|
/* Copy to temp buffer and checksum */
|
|
for (i = sizeof(*r); i > 0; i--) {
|
|
*itmp = *in++;
|
|
csum += *itmp++;
|
|
}
|
|
r = (const struct ec_host_request *)pkt->request_temp;
|
|
} else {
|
|
/* Just checksum */
|
|
for (i = sizeof(*r); i > 0; i--)
|
|
csum += *in++;
|
|
}
|
|
|
|
if (r->struct_version != EC_HOST_REQUEST_VERSION) {
|
|
/* Request header we don't know how to handle */
|
|
args0.result = EC_RES_INVALID_HEADER;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
if (pkt->request_size < sizeof(*r) + r->data_len) {
|
|
/*
|
|
* Packet too small for expected params. Note that it's ok if
|
|
* the received packet data is too big; some interfaces may pad
|
|
* the data at the end (SPI) or may not know how big the
|
|
* received data is (LPC).
|
|
*/
|
|
args0.result = EC_RES_REQUEST_TRUNCATED;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
/* Copy request data and validate checksum */
|
|
if (pkt->request_temp) {
|
|
/* Params go in temporary buffer */
|
|
args0.params = itmp;
|
|
|
|
/* Copy request data and checksum */
|
|
for (i = r->data_len; i > 0; i--) {
|
|
*itmp = *in++;
|
|
csum += *itmp++;
|
|
}
|
|
} else {
|
|
/* Params read directly from request */
|
|
args0.params = in;
|
|
|
|
/* Just checksum */
|
|
for (i = r->data_len; i > 0; i--)
|
|
csum += *in++;
|
|
}
|
|
|
|
/* Validate checksum */
|
|
if ((uint8_t)csum) {
|
|
args0.result = EC_RES_INVALID_CHECKSUM;
|
|
goto host_packet_bad;
|
|
}
|
|
|
|
/* Set up host command handler args */
|
|
args0.send_response = host_packet_respond;
|
|
args0.command = r->command;
|
|
args0.version = r->command_version;
|
|
args0.params_size = r->data_len;
|
|
args0.response = (struct ec_host_response *)(pkt->response) + 1;
|
|
args0.response_max = pkt->response_max -
|
|
sizeof(struct ec_host_response);
|
|
args0.response_size = 0;
|
|
args0.result = EC_RES_SUCCESS;
|
|
|
|
/* Chain to host command received */
|
|
host_command_received(&args0);
|
|
return;
|
|
|
|
host_packet_bad:
|
|
/*
|
|
* TODO (crosbug.com/p/29315): This is typically running in interrupt
|
|
* context, so it would be better not to send the response here, and to
|
|
* let the host command task send the response.
|
|
*/
|
|
/* Improperly formed packet from host, so send an error response */
|
|
host_packet_respond(&args0);
|
|
}
|
|
|
|
/**
|
|
* Find a command by command number.
|
|
*
|
|
* @param command Command number to find
|
|
* @return The command structure, or NULL if no match found.
|
|
*/
|
|
static const struct host_command *find_host_command(int command)
|
|
{
|
|
#ifdef CONFIG_HOSTCMD_SECTION_SORTED
|
|
const struct host_command *l, *r, *m;
|
|
uint32_t num;
|
|
|
|
/* Use binary search to locate host command handler */
|
|
l = __hcmds;
|
|
r = __hcmds_end - 1;
|
|
|
|
while (1) {
|
|
if (l > r)
|
|
return NULL;
|
|
|
|
num = r - l;
|
|
m = l + (num / 2);
|
|
|
|
if (m->command < command)
|
|
l = m + 1;
|
|
else if (m->command > command)
|
|
r = m - 1;
|
|
else
|
|
return m;
|
|
}
|
|
#else
|
|
const struct host_command *cmd;
|
|
|
|
for (cmd = __hcmds; cmd < __hcmds_end; cmd++) {
|
|
if (command == cmd->command)
|
|
return cmd;
|
|
}
|
|
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
static void host_command_init(void)
|
|
{
|
|
/* Initialize memory map ID area */
|
|
host_get_memmap(EC_MEMMAP_ID)[0] = 'E';
|
|
host_get_memmap(EC_MEMMAP_ID)[1] = 'C';
|
|
*host_get_memmap(EC_MEMMAP_ID_VERSION) = 1;
|
|
*host_get_memmap(EC_MEMMAP_EVENTS_VERSION) = 1;
|
|
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
host_set_single_event(EC_HOST_EVENT_INTERFACE_READY);
|
|
HOST_EVENT_CPRINTS("hostcmd init", host_get_events());
|
|
#endif
|
|
|
|
#ifdef CONFIG_SUPPRESSED_HOST_COMMANDS
|
|
suppressed_cmd_deadline.val = get_time().val + SUPPRESSED_CMD_INTERVAL;
|
|
#endif
|
|
}
|
|
|
|
void host_command_task(void *u)
|
|
{
|
|
timestamp_t t0, t1, t_recess;
|
|
t_recess.val = 0;
|
|
t1.val = 0;
|
|
|
|
host_command_init();
|
|
|
|
while (1) {
|
|
/* Wait for the next command event */
|
|
int evt = task_wait_event(-1);
|
|
t0 = get_time();
|
|
|
|
/* Process it */
|
|
if ((evt & TASK_EVENT_CMD_PENDING) && pending_args) {
|
|
pending_args->result =
|
|
host_command_process(pending_args);
|
|
host_send_response(pending_args);
|
|
}
|
|
|
|
/* reset rate limiting if we have slept enough */
|
|
if (t0.val - t1.val > CONFIG_HOSTCMD_RATE_LIMITING_MIN_REST)
|
|
t_recess = t0;
|
|
|
|
t1 = get_time();
|
|
/*
|
|
* rate limiting : check how long we have gone without a
|
|
* significant interruption to avoid DoS from host
|
|
*/
|
|
if (t1.val - t_recess.val > CONFIG_HOSTCMD_RATE_LIMITING_PERIOD)
|
|
/* Short recess */
|
|
usleep(CONFIG_HOSTCMD_RATE_LIMITING_RECESS);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Host commands */
|
|
|
|
/* TODO(crosbug.com/p/11223): Remove this once the kernel no longer cares */
|
|
static enum ec_status
|
|
host_command_proto_version(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_proto_version *r = args->response;
|
|
|
|
r->version = EC_PROTO_VERSION;
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_PROTO_VERSION,
|
|
host_command_proto_version,
|
|
EC_VER_MASK(0));
|
|
|
|
static enum ec_status host_command_hello(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_hello *p = args->params;
|
|
struct ec_response_hello *r = args->response;
|
|
uint32_t d = p->in_data;
|
|
|
|
r->out_data = d + 0x01020304;
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_HELLO,
|
|
host_command_hello,
|
|
EC_VER_MASK(0));
|
|
|
|
static enum ec_status host_command_read_test(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_read_test *p = args->params;
|
|
struct ec_response_read_test *r = args->response;
|
|
|
|
int offset = p->offset;
|
|
int size = p->size / sizeof(uint32_t);
|
|
int i;
|
|
|
|
if (size > ARRAY_SIZE(r->data) || p->size > args->response_size)
|
|
return EC_RES_ERROR;
|
|
|
|
for (i = 0; i < size; i++)
|
|
r->data[i] = offset + i;
|
|
|
|
args->response_size = size * sizeof(uint32_t);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_READ_TEST,
|
|
host_command_read_test,
|
|
EC_VER_MASK(0));
|
|
|
|
#ifndef CONFIG_HOSTCMD_X86
|
|
/*
|
|
* Host command to read memory map is not needed on LPC, because LPC can
|
|
* directly map the data to the host's memory space.
|
|
*/
|
|
static enum ec_status
|
|
host_command_read_memmap(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_read_memmap *p = args->params;
|
|
|
|
/* Copy params out of data before we overwrite it with output */
|
|
uint8_t offset = p->offset;
|
|
uint8_t size = p->size;
|
|
|
|
if (size > EC_MEMMAP_SIZE || offset > EC_MEMMAP_SIZE ||
|
|
offset + size > EC_MEMMAP_SIZE || size > args->response_max)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
/* Make sure switch data is initialized */
|
|
if (offset == EC_MEMMAP_SWITCHES &&
|
|
*host_get_memmap(EC_MEMMAP_SWITCHES_VERSION) == 0)
|
|
return EC_RES_UNAVAILABLE;
|
|
|
|
memcpy(args->response, host_get_memmap(offset), size);
|
|
args->response_size = size;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_READ_MEMMAP,
|
|
host_command_read_memmap,
|
|
EC_VER_MASK(0));
|
|
#endif
|
|
|
|
static enum ec_status
|
|
host_command_get_cmd_versions(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_get_cmd_versions *p = args->params;
|
|
const struct ec_params_get_cmd_versions_v1 *p_v1 = args->params;
|
|
struct ec_response_get_cmd_versions *r = args->response;
|
|
|
|
const struct host_command *cmd =
|
|
(args->version == 1) ? find_host_command(p_v1->cmd) :
|
|
find_host_command(p->cmd);
|
|
|
|
if (!cmd)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
r->version_mask = cmd->version_mask;
|
|
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_CMD_VERSIONS,
|
|
host_command_get_cmd_versions,
|
|
EC_VER_MASK(0) | EC_VER_MASK(1));
|
|
|
|
static int host_command_is_suppressed(uint16_t cmd)
|
|
{
|
|
#ifdef CONFIG_SUPPRESSED_HOST_COMMANDS
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(hc_suppressed_cmd); i++) {
|
|
if (hc_suppressed_cmd[i] == cmd) {
|
|
hc_suppressed_cnt[i]++;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Print & reset suppressed command counters. It should be called periodically
|
|
* and on important events (e.g. shutdown, sysjump, etc.).
|
|
*/
|
|
static void dump_host_command_suppressed(int force)
|
|
{
|
|
#ifdef CONFIG_SUPPRESSED_HOST_COMMANDS
|
|
int i;
|
|
|
|
if (!force && !timestamp_expired(suppressed_cmd_deadline, NULL))
|
|
return;
|
|
|
|
CPRINTF("[%T HC Suppressed:");
|
|
for (i = 0; i < ARRAY_SIZE(hc_suppressed_cmd); i++) {
|
|
CPRINTF(" 0x%x=%d", hc_suppressed_cmd[i], hc_suppressed_cnt[i]);
|
|
hc_suppressed_cnt[i] = 0;
|
|
}
|
|
CPRINTF("]\n");
|
|
cflush();
|
|
|
|
/* Reset the timer */
|
|
suppressed_cmd_deadline.val = get_time().val + SUPPRESSED_CMD_INTERVAL;
|
|
}
|
|
|
|
static void dump_host_command_suppressed_(void)
|
|
{
|
|
dump_host_command_suppressed(1);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN,
|
|
dump_host_command_suppressed_, HOOK_PRIO_DEFAULT);
|
|
DECLARE_HOOK(HOOK_SYSJUMP,
|
|
dump_host_command_suppressed_, HOOK_PRIO_DEFAULT);
|
|
#else
|
|
}
|
|
#endif /* CONFIG_SUPPRESSED_HOST_COMMANDS */
|
|
|
|
/**
|
|
* Print debug output for the host command request, before it's processed.
|
|
*
|
|
* @param args Host command args
|
|
*/
|
|
static void host_command_debug_request(struct host_cmd_handler_args *args)
|
|
{
|
|
static int hc_prev_cmd;
|
|
static int hc_prev_count;
|
|
static uint64_t hc_prev_time;
|
|
|
|
/*
|
|
* In normal output mode, skip printing repeats of the same command
|
|
* that occur in rapid succession - such as flash commands during
|
|
* software sync.
|
|
*/
|
|
if (hcdebug == HCDEBUG_NORMAL) {
|
|
uint64_t t = get_time().val;
|
|
if (host_command_is_suppressed(args->command)) {
|
|
dump_host_command_suppressed(0);
|
|
return;
|
|
}
|
|
if (args->command == hc_prev_cmd &&
|
|
t - hc_prev_time < HCDEBUG_MAX_REPEAT_DELAY) {
|
|
hc_prev_count++;
|
|
hc_prev_time = t;
|
|
if (hc_prev_count < HCDEBUG_MAX_REPEAT_COUNT)
|
|
CPUTS("+");
|
|
else if (hc_prev_count == HCDEBUG_MAX_REPEAT_COUNT)
|
|
CPUTS("(++)");
|
|
return;
|
|
}
|
|
hc_prev_count = 1;
|
|
hc_prev_time = t;
|
|
hc_prev_cmd = args->command;
|
|
}
|
|
|
|
if (hcdebug >= HCDEBUG_PARAMS && args->params_size)
|
|
CPRINTS("HC 0x%02x.%d:%.*h", args->command,
|
|
args->version, args->params_size, args->params);
|
|
else
|
|
CPRINTS("HC 0x%02x", args->command);
|
|
}
|
|
|
|
uint16_t host_command_process(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct host_command *cmd;
|
|
int rv;
|
|
|
|
if (hcdebug)
|
|
host_command_debug_request(args);
|
|
|
|
#ifdef CONFIG_HOSTCMD_PD
|
|
if (args->command >= EC_CMD_PASSTHRU_OFFSET(1) &&
|
|
args->command <= EC_CMD_PASSTHRU_MAX(1)) {
|
|
rv = pd_host_command(args->command - EC_CMD_PASSTHRU_OFFSET(1),
|
|
args->version,
|
|
args->params, args->params_size,
|
|
args->response, args->response_max);
|
|
if (rv >= 0) {
|
|
/* Success; store actual response size */
|
|
args->response_size = rv;
|
|
rv = EC_SUCCESS;
|
|
} else {
|
|
/* Failure, returned as negative error code */
|
|
rv = -rv;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
cmd = find_host_command(args->command);
|
|
if (!cmd)
|
|
rv = EC_RES_INVALID_COMMAND;
|
|
else if (!(EC_VER_MASK(args->version) & cmd->version_mask))
|
|
rv = EC_RES_INVALID_VERSION;
|
|
else
|
|
rv = cmd->handler(args);
|
|
}
|
|
|
|
if (rv != EC_RES_SUCCESS)
|
|
CPRINTS("HC 0x%02x err %d", args->command, rv);
|
|
|
|
if (hcdebug >= HCDEBUG_PARAMS && args->response_size)
|
|
CPRINTS("HC resp:%.*h", args->response_size,
|
|
args->response);
|
|
|
|
return rv;
|
|
}
|
|
|
|
#ifdef CONFIG_HOST_COMMAND_STATUS
|
|
/* Returns current command status (busy or not) */
|
|
static enum ec_status
|
|
host_command_get_comms_status(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_get_comms_status *r = args->response;
|
|
|
|
r->flags = command_pending ? EC_COMMS_STATUS_PROCESSING : 0;
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_COMMS_STATUS,
|
|
host_command_get_comms_status,
|
|
EC_VER_MASK(0));
|
|
|
|
/* Resend the last saved response */
|
|
static enum ec_status
|
|
host_command_resend_response(struct host_cmd_handler_args *args)
|
|
{
|
|
/* Handle resending response */
|
|
args->result = saved_result;
|
|
args->response_size = 0;
|
|
|
|
saved_result = EC_RES_UNAVAILABLE;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
|
|
DECLARE_HOST_COMMAND(EC_CMD_RESEND_RESPONSE,
|
|
host_command_resend_response,
|
|
EC_VER_MASK(0));
|
|
#endif /* CONFIG_HOST_COMMAND_STATUS */
|
|
|
|
static enum ec_status
|
|
host_command_entering_mode(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_params_entering_mode *param =
|
|
(struct ec_params_entering_mode *)args->params;
|
|
args->response_size = 0;
|
|
g_vboot_mode = param->vboot_mode;
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_ENTERING_MODE,
|
|
host_command_entering_mode, EC_VER_MASK(0));
|
|
|
|
/* Returns what we tell it to. */
|
|
static enum ec_status
|
|
host_command_test_protocol(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_test_protocol *p = args->params;
|
|
struct ec_response_test_protocol *r = args->response;
|
|
int copy_len = MIN(p->ret_len, sizeof(r->buf)); /* p,r bufs same size */
|
|
|
|
memset(r->buf, 0, sizeof(r->buf));
|
|
memcpy(r->buf, p->buf, copy_len);
|
|
args->response_size = copy_len;
|
|
|
|
return p->ec_result;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_TEST_PROTOCOL,
|
|
host_command_test_protocol,
|
|
EC_VER_MASK(0));
|
|
|
|
/* Returns supported features. */
|
|
static enum ec_status
|
|
host_command_get_features(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_get_features *r = args->response;
|
|
args->response_size = sizeof(*r);
|
|
|
|
memset(r, 0, sizeof(*r));
|
|
r->flags[0] = get_feature_flags0();
|
|
r->flags[1] = get_feature_flags1();
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_FEATURES,
|
|
host_command_get_features,
|
|
EC_VER_MASK(0));
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
|
|
#ifdef CONFIG_CMD_HOSTCMD
|
|
static int parse_byte(char *b, uint8_t *out)
|
|
{
|
|
int i;
|
|
*out = 0;
|
|
for (i = 0; i < 2; ++i) {
|
|
*out *= 16;
|
|
if (*b >= '0' && *b <= '9')
|
|
*out += *b - '0';
|
|
else if (*b >= 'a' && *b <= 'f')
|
|
*out += *b - 'a' + 10;
|
|
else if (*b >= 'A' && *b <= 'F')
|
|
*out += *b - 'A' + 10;
|
|
else
|
|
return EC_ERROR_INVAL;
|
|
++b;
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int parse_params(char *s, uint8_t *params)
|
|
{
|
|
int len = 0;
|
|
|
|
while (*s) {
|
|
if (parse_byte(s, params))
|
|
return -1;
|
|
s += 2;
|
|
params++;
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int command_host_command(int argc, char **argv)
|
|
{
|
|
struct host_cmd_handler_args args;
|
|
char *cmd_params;
|
|
uint16_t res;
|
|
char *e;
|
|
int rv;
|
|
|
|
/* Use shared memory for command params space */
|
|
if (SHARED_MEM_ACQUIRE_CHECK(EC_PROTO2_MAX_PARAM_SIZE, &cmd_params)) {
|
|
ccputs("Can't acquire shared memory buffer.\n");
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Assume no version or params unless proven otherwise */
|
|
args.version = 0;
|
|
args.params_size = 0;
|
|
args.params = cmd_params;
|
|
|
|
if (argc < 2) {
|
|
shared_mem_release(cmd_params);
|
|
return EC_ERROR_PARAM_COUNT;
|
|
}
|
|
|
|
args.command = strtoi(argv[1], &e, 0);
|
|
if (*e) {
|
|
shared_mem_release(cmd_params);
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
|
|
if (argc > 2) {
|
|
args.version = strtoi(argv[2], &e, 0);
|
|
if (*e) {
|
|
shared_mem_release(cmd_params);
|
|
return EC_ERROR_PARAM2;
|
|
}
|
|
}
|
|
|
|
if (argc > 3) {
|
|
rv = parse_params(argv[3], cmd_params);
|
|
if (rv < 0) {
|
|
shared_mem_release(cmd_params);
|
|
return EC_ERROR_PARAM3;
|
|
}
|
|
args.params_size = rv;
|
|
}
|
|
|
|
args.response = cmd_params;
|
|
args.response_max = EC_PROTO2_MAX_PARAM_SIZE;
|
|
args.response_size = 0;
|
|
|
|
res = host_command_process(&args);
|
|
|
|
if (res != EC_RES_SUCCESS)
|
|
ccprintf("Command returned %d\n", res);
|
|
else if (args.response_size)
|
|
ccprintf("Response: %.*h\n", args.response_size, cmd_params);
|
|
else
|
|
ccprintf("Command succeeded; no response.\n");
|
|
|
|
shared_mem_release(cmd_params);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(hostcmd, command_host_command,
|
|
"cmd ver param",
|
|
"Fake host command");
|
|
#endif /* CONFIG_CMD_HOSTCMD */
|
|
|
|
#ifdef CONFIG_CMD_HCDEBUG
|
|
static int command_hcdebug(int argc, char **argv)
|
|
{
|
|
if (argc > 1) {
|
|
int i;
|
|
|
|
for (i = 0; i < HCDEBUG_MODES; i++) {
|
|
if (!strcasecmp(argv[1], hcdebug_mode_names[i])) {
|
|
hcdebug = i;
|
|
break;
|
|
}
|
|
}
|
|
if (i == HCDEBUG_MODES)
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
|
|
ccprintf("Host command debug mode is %s\n",
|
|
hcdebug_mode_names[hcdebug]);
|
|
dump_host_command_suppressed(1);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(hcdebug, command_hcdebug,
|
|
"hcdebug [off | normal | every | params]",
|
|
"Set host command debug output mode");
|
|
#endif /* CONFIG_CMD_HCDEBUG */
|