text: /* Copyright (c) 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 "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 CPRINTS(format, args...) cprints(CC_HOSTCMD, format, ## args)
  
  #define TASK_EVENT_CMD_PENDING TASK_EVENT_CUSTOM(1)
  
  /* 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_LPC
  /*
   * 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;
  
  uint8_t *host_get_memmap(int offset)
  {
  #ifdef CONFIG_LPC
  	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 woud 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 woud 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);
  	CPRINTS("hostcmd init 0x%x", host_get_events());
  #endif
  }
  
  void host_command_task(void)
  {
  	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 int 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 int 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 int 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))
  		return EC_RES_ERROR;
  
  	for (i = 0; i < size; i++)
  		r->data[i] = offset + i;
  
  	args->response_size = sizeof(*r);
  
  	return EC_RES_SUCCESS;
  }
  DECLARE_HOST_COMMAND(EC_CMD_READ_TEST,
  		     host_command_read_test,
  		     EC_VER_MASK(0));
  
  #ifndef CONFIG_LPC
  /*
   * 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 int 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)
  		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 int 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));
  
  /**
   * 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 (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 int 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_SUCCESS;
  }
  
  DECLARE_HOST_COMMAND(EC_CMD_GET_COMMS_STATUS,
  		     host_command_get_comms_status,
  		     EC_VER_MASK(0));
  
  /* Resend the last saved response */
  static int 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_SUCCESS;
  }
  
  DECLARE_HOST_COMMAND(EC_CMD_RESEND_RESPONSE,
  		     host_command_resend_response,
  		     EC_VER_MASK(0));
  #endif /* CONFIG_HOST_COMMAND_STATUS */
  
  
  static int 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_SUCCESS;
  }
  DECLARE_HOST_COMMAND(EC_CMD_ENTERING_MODE,
  		host_command_entering_mode, EC_VER_MASK(0));
  
  /* Returns what we tell it to. */
  static int 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 = p->ret_len;
  
  	return p->ec_result;
  }
  DECLARE_HOST_COMMAND(EC_CMD_TEST_PROTOCOL,
  		     host_command_test_protocol,
  		     EC_VER_MASK(0));
  
  /* Returns supported features. */
  static int 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] = 0
  #ifdef CONFIG_FW_LIMITED_IMAGE
  		| EC_FEATURE_MASK_0(EC_FEATURE_LIMITED)
  #endif
  #ifdef CONFIG_FLASH
  		| EC_FEATURE_MASK_0(EC_FEATURE_FLASH)
  #endif
  #ifdef CONFIG_FANS
  		| EC_FEATURE_MASK_0(EC_FEATURE_PWM_FAN)
  #endif
  #ifdef CONFIG_PWM_KBLIGHT
  		| EC_FEATURE_MASK_0(EC_FEATURE_PWM_KEYB)
  #endif
  #ifdef HAS_TASK_LIGHTBAR
  		| EC_FEATURE_MASK_0(EC_FEATURE_LIGHTBAR)
  #endif
  #ifdef CONFIG_LED_COMMON
  		| EC_FEATURE_MASK_0(EC_FEATURE_LED)
  #endif
  #ifdef HAS_TASK_MOTIONSENSE
  		| EC_FEATURE_MASK_0(EC_FEATURE_MOTION_SENSE)
  #endif
  #ifdef HAS_TASK_KEYSCAN
  		| EC_FEATURE_MASK_0(EC_FEATURE_KEYB)
  #endif
  #ifdef CONFIG_PSTORE
  		| EC_FEATURE_MASK_0(EC_FEATURE_PSTORE)
  #endif
  #ifdef CONFIG_LPC
  		| EC_FEATURE_MASK_0(EC_FEATURE_PORT80)
  #endif
  #ifdef CONFIG_TEMP_SENSOR
  		| EC_FEATURE_MASK_0(EC_FEATURE_THERMAL)
  #endif
  /* Hack to uniquely identify Samus ec */
  #if (defined CONFIG_BACKLIGHT_LID) || (defined CONFIG_BATTERY_SAMUS)
  		| EC_FEATURE_MASK_0(EC_FEATURE_BKLIGHT_SWITCH)
  #endif
  #ifdef CONFIG_WIRELESS
  		| EC_FEATURE_MASK_0(EC_FEATURE_WIFI_SWITCH)
  #endif
  #ifdef CONFIG_HOSTCMD_EVENTS
  		| EC_FEATURE_MASK_0(EC_FEATURE_HOST_EVENTS)
  #endif
  #ifdef CONFIG_COMMON_GPIO
  		| EC_FEATURE_MASK_0(EC_FEATURE_GPIO)
  #endif
  #ifdef CONFIG_I2C_MASTER
  		| EC_FEATURE_MASK_0(EC_FEATURE_I2C)
  #endif
  #ifdef CONFIG_CHARGER
  		| EC_FEATURE_MASK_0(EC_FEATURE_CHARGER)
  #endif
  #if (defined CONFIG_BATTERY) || (defined CONFIG_BATTERY_SMART)
  		| EC_FEATURE_MASK_0(EC_FEATURE_BATTERY)
  #endif
  #ifdef CONFIG_BATTERY_SMART
  		| EC_FEATURE_MASK_0(EC_FEATURE_SMART_BATTERY)
  #endif
  #ifdef CONFIG_AP_HANG_DETECT
  		| EC_FEATURE_MASK_0(EC_FEATURE_HANG_DETECT)
  #endif
  #ifdef CONFIG_PMU_POWERINFO
  		| EC_FEATURE_MASK_0(EC_FEATURE_PMU)
  #endif
  #ifdef CONFIG_HOSTCMD_PD
  		| EC_FEATURE_MASK_0(EC_FEATURE_SUB_MCU)
  #endif
  #ifdef CONFIG_CHARGE_MANAGER
  		| EC_FEATURE_MASK_0(EC_FEATURE_USB_PD)
  #endif
  #ifdef CONFIG_ACCEL_FIFO
  		| EC_FEATURE_MASK_0(EC_FEATURE_MOTION_SENSE_FIFO)
  #endif
  #ifdef CONFIG_VSTORE
  		| EC_FEATURE_MASK_0(EC_FEATURE_VSTORE)
  #endif
  #ifdef CONFIG_USB_MUX_VIRTUAL
  		| EC_FEATURE_MASK_0(EC_FEATURE_USBC_SS_MUX_VIRTUAL)
  #endif
  #ifdef CONFIG_HOSTCMD_RTC
  		| EC_FEATURE_MASK_0(EC_FEATURE_RTC)
  #endif
  #ifdef CONFIG_SPI_FP_PORT
  		| EC_FEATURE_MASK_0(EC_FEATURE_FINGERPRINT)
  #endif
  #ifdef HAS_TASK_CENTROIDING
  		| EC_FEATURE_MASK_0(EC_FEATURE_TOUCHPAD)
  #endif
  #ifdef HAS_TASK_RWSIG
  		| EC_FEATURE_MASK_0(EC_FEATURE_RWSIG)
  #endif
  		;
  	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(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]);
  
  	return EC_SUCCESS;
  }
  DECLARE_CONSOLE_COMMAND(hcdebug, command_hcdebug,
  			"hcdebug [off | normal | every | params]",
  			"Set host command debug output mode");
  #endif /* CONFIG_CMD_HCDEBUG */
  