123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- /*
- * This file is part of the flashrom project.
- *
- * Copyright (C) 2012 The Chromium OS Authors. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * Neither the name of Google or the names of contributors or
- * licensors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * This software is provided "AS IS," without a warranty of any kind.
- * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
- * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
- * GOOGLE INC AND ITS LICENSORS SHALL NOT BE LIABLE
- * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
- * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
- * GOOGLE OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
- * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
- * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
- * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
- * EVEN IF GOOGLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/param.h>
- #include <unistd.h>
- #include "flashchips.h"
- #include "fmap.h"
- #if USE_CROS_EC_LOCK == 1
- #include "cros_ec_lock.h"
- #endif
- #include "cros_ec_commands.h"
- #include "programmer.h"
- #include "cros_ec.h"
- #include "spi.h"
- #include "writeprotect.h"
- #define INITIAL_UDELAY 10 /* 10 us */
- #define MAXIMUM_UDELAY 10000 /* 10 ms */
- static int ec_timeout_usec = 1000000;
- #define CROS_EC_LOCK_TIMEOUT_SECS 30 /* 30 secs */
- /*
- * Wait for the EC to be unbusy. Returns 0 if unbusy, non-zero if
- * timeout.
- */
- static int wait_for_ec(int status_addr, int timeout_usec)
- {
- int i;
- int delay = INITIAL_UDELAY;
- for (i = 0; i < timeout_usec; i += delay) {
- /*
- * Delay first, in case we just sent out a command but the EC
- * hasn't raise the busy flag. However, I think this doesn't
- * happen since the LPC commands are executed in order and the
- * busy flag is set by hardware.
- *
- * TODO: move this delay after inb(status).
- */
- usleep(MIN(delay, timeout_usec - i));
- if (!(inb(status_addr) & EC_LPC_STATUS_BUSY_MASK))
- return 0;
- /* Increase the delay interval after a few rapid checks */
- if (i > 20)
- delay = MIN(delay * 2, MAXIMUM_UDELAY);
- }
- return -1; /* Timeout */
- }
- /*
- **************************** EC API v0 ****************************
- */
- /* preserve legacy naming to be consistent with legacy implementation. */
- #define EC_LPC_ADDR_OLD_PARAM EC_HOST_CMD_REGION1
- #define EC_OLD_PARAM_SIZE EC_HOST_CMD_REGION_SIZE
- static enum ec_status cros_ec_get_result() {
- return inb(EC_LPC_ADDR_HOST_DATA);
- }
- /* Sends a command to the EC. Returns the command status code, or
- * -1 if other error. */
- static int cros_ec_command_lpc_old(int command, int version,
- const void *outdata, int outsize,
- void *indata, int insize) {
- uint8_t *d;
- int i;
- if ((outsize > EC_OLD_PARAM_SIZE) ||
- (insize > EC_OLD_PARAM_SIZE)) {
- msg_pdbg2("Data size too big for buffer.\n");
- return -EC_RES_INVALID_PARAM;
- }
- if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
- msg_pdbg2("Timeout waiting for EC ready\n");
- return -EC_RES_ERROR;
- }
- /* Write data, if any */
- /* TODO: optimized copy using outl() */
- for (i = 0, d = (uint8_t *)outdata; i < outsize; i++, d++) {
- msg_pdbg2("CROS_EC: Port[0x%x] <-- 0x%x\n",
- EC_LPC_ADDR_OLD_PARAM + i, *d);
- outb(*d, EC_LPC_ADDR_OLD_PARAM + i);
- }
- msg_pdbg2("CROS_EC: Run EC Command: 0x%x ----\n", command);
- outb(command, EC_LPC_ADDR_HOST_CMD);
- if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
- msg_pdbg2("Timeout waiting for EC response\n");
- return -EC_RES_ERROR;
- }
- /* Check status */
- if ((i = cros_ec_get_result()) != EC_RES_SUCCESS) {
- msg_pdbg2("EC returned error status %d\n", i);
- return -i;
- }
- /* Read data, if any */
- for (i = 0, d = (uint8_t *)indata; i < insize; i++, d++) {
- *d = inb(EC_LPC_ADDR_OLD_PARAM + i);
- msg_pdbg2("CROS_EC: Port[0x%x] ---> 0x%x\n",
- EC_LPC_ADDR_OLD_PARAM + i, *d);
- }
- return 0;
- }
- /*
- **************************** EC API v2 ****************************
- */
- static int cros_ec_command_lpc(int command, int version,
- const void *outdata, int outsize,
- void *indata, int insize,
- int (*read_memmap)(uint16_t, uint8_t *),
- int (*write_memmap)(uint8_t, uint16_t)) {
- struct ec_lpc_host_args args;
- const uint8_t *dout;
- uint8_t *din;
- int csum;
- int i;
- /* Fill in args */
- args.flags = EC_HOST_ARGS_FLAG_FROM_HOST;
- args.command_version = version;
- args.data_size = outsize;
- /* Calculate checksum */
- csum = command + args.flags + args.command_version + args.data_size;
- for (i = 0, dout = (const uint8_t *)outdata; i < outsize; i++, dout++)
- csum += *dout;
- args.checksum = (uint8_t)csum;
- /* Wait EC is ready to accept command before writing anything to
- * parameter.
- */
- if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
- msg_pdbg("Timeout waiting for EC ready\n");
- return -EC_RES_ERROR;
- }
- /* Write args */
- for (i = 0, dout = (const uint8_t *)&args;
- i < sizeof(args);
- i++, dout++)
- if (write_memmap(*dout, EC_LPC_ADDR_HOST_ARGS + i))
- return -EC_RES_ERROR;
- /* Write data, if any */
- /* TODO: optimized copy using outl() */
- for (i = 0, dout = (uint8_t *)outdata; i < outsize; i++, dout++)
- if (write_memmap(*dout, EC_LPC_ADDR_HOST_PARAM + i))
- return -EC_RES_ERROR;
- outb(command, EC_LPC_ADDR_HOST_CMD);
- if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
- msg_perr("Timeout waiting for EC response\n");
- return -EC_RES_ERROR;
- }
- /* Check result */
- i = inb(EC_LPC_ADDR_HOST_DATA);
- if (i) {
- msg_pdbg2("EC returned error result code %d\n", i);
- return -i;
- }
- /* Read back args */
- for (i = 0, din = (uint8_t *)&args; i < sizeof(args); i++, din++)
- if (read_memmap(EC_LPC_ADDR_HOST_ARGS + i, din))
- return -EC_RES_ERROR;
- /*
- * If EC didn't modify args flags, then somehow we sent a new-style
- * command to an old EC, which means it would have read its params
- * from the wrong place.
- */
- if (!(args.flags & EC_HOST_ARGS_FLAG_TO_HOST)) {
- msg_perr("EC protocol mismatch\n");
- return -EC_RES_INVALID_RESPONSE;
- }
- if (args.data_size > insize) {
- msg_perr("EC returned too much data\n");
- return -EC_RES_INVALID_RESPONSE;
- }
- /* Read data, if any */
- /* TODO: optimized copy using outl() */
- for (i = 0, din = (uint8_t *)indata; i < args.data_size; i++, din++)
- if (read_memmap(EC_LPC_ADDR_HOST_PARAM + i, din))
- return -EC_RES_ERROR;
- /* Verify checksum */
- csum = command + args.flags + args.command_version + args.data_size;
- for (i = 0, din = (uint8_t *)indata; i < args.data_size; i++, din++)
- csum += *din;
- if (args.checksum != (uint8_t)csum) {
- msg_perr("EC response has invalid checksum\n");
- return -EC_RES_INVALID_CHECKSUM;
- }
- /* Return actual amount of data received */
- return args.data_size;
- }
- /*
- **************************** EC API v3 ****************************
- */
- static int cros_ec_command_lpc_v3(int command, int version,
- const void *outdata, int outsize,
- void *indata, int insize,
- int (*read_memmap)(uint16_t, uint8_t *),
- int (*write_memmap)(uint8_t, uint16_t)) {
- struct ec_host_request rq;
- struct ec_host_response rs;
- const uint8_t *d;
- uint8_t *dout;
- int csum = 0;
- int i;
- /* Fail if output size is too big */
- if (outsize + sizeof(rq) > EC_LPC_HOST_PACKET_SIZE)
- return -EC_RES_REQUEST_TRUNCATED;
- /* Fill in request packet */
- /* TODO(crosbug.com/p/23825): This should be common to all protocols */
- rq.struct_version = EC_HOST_REQUEST_VERSION;
- rq.checksum = 0;
- rq.command = command;
- rq.command_version = version;
- rq.reserved = 0;
- rq.data_len = outsize;
- /* Copy data and start checksum */
- for (i = 0, d = (const uint8_t *)outdata; i < outsize; i++, d++) {
- if (write_memmap(*d, EC_LPC_ADDR_HOST_PACKET + sizeof(rq) + i))
- return -EC_RES_ERROR;
- csum += *d;
- }
- /* Finish checksum */
- for (i = 0, d = (const uint8_t *)&rq; i < sizeof(rq); i++, d++)
- csum += *d;
- /* Write checksum field so the entire packet sums to 0 */
- rq.checksum = (uint8_t)(-csum);
- /* Copy header */
- for (i = 0, d = (const uint8_t *)&rq; i < sizeof(rq); i++, d++)
- if (write_memmap(*d, EC_LPC_ADDR_HOST_PACKET + i))
- return -EC_RES_ERROR;
- /* Start the command */
- outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD);
- if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
- msg_perr("Timeout waiting for EC response\n");
- return -EC_RES_ERROR;
- }
- /* Check result */
- i = inb(EC_LPC_ADDR_HOST_DATA);
- if (i) {
- msg_perr("EC returned error result code %d\n", i);
- return -i;
- }
- /* Read back response header and start checksum */
- csum = 0;
- for (i = 0, dout = (uint8_t *)&rs; i < sizeof(rs); i++, dout++) {
- if (read_memmap(EC_LPC_ADDR_HOST_PACKET + i, dout))
- return -EC_RES_ERROR;
- csum += *dout;
- }
- if (rs.struct_version != EC_HOST_RESPONSE_VERSION) {
- msg_perr("EC response version mismatch\n");
- return -EC_RES_INVALID_RESPONSE;
- }
- if (rs.reserved) {
- msg_perr("EC response reserved != 0\n");
- return -EC_RES_INVALID_RESPONSE;
- }
- if (rs.data_len > insize) {
- msg_perr("EC returned too much data\n");
- return -EC_RES_RESPONSE_TOO_BIG;
- }
- /* Read back data and update checksum */
- for (i = 0, dout = (uint8_t *)indata; i < rs.data_len; i++, dout++) {
- if (read_memmap(EC_LPC_ADDR_HOST_PACKET + sizeof(rs) + i, dout))
- return -EC_RES_ERROR;
- csum += *dout;
- }
- /* Verify checksum */
- if ((uint8_t)csum) {
- msg_perr("EC response has invalid checksum\n");
- return -EC_RES_INVALID_CHECKSUM;
- }
- /* Return actual amount of data received */
- return rs.data_len;
- }
- static int read_memmap_lm4(uint16_t port, uint8_t * value)
- {
- *value = inb(port);
- return 0;
- }
- static int write_memmap_lm4(uint8_t value, uint16_t port)
- {
- outb(value, port);
- return 0;
- }
- /*
- **************************** EC API v2 ****************************
- */
- static int cros_ec_command_lpc_lm4(int command, int version,
- const void *outdata, int outsize,
- void *indata, int insize) {
- return cros_ec_command_lpc(command, version, outdata, outsize,
- indata, insize, read_memmap_lm4, write_memmap_lm4);
- }
- /*
- **************************** EC API v3 ****************************
- */
- static int cros_ec_command_lpc_v3_lm4(int command, int version,
- const void *outdata, int outsize,
- void *indata, int insize) {
- return cros_ec_command_lpc_v3(command, version, outdata, outsize,
- indata, insize, read_memmap_lm4, write_memmap_lm4);
- }
- static struct cros_ec_priv cros_ec_lpc_priv = {
- .detected = 0,
- .ec_command = cros_ec_command_lpc_lm4,
- };
- static struct opaque_programmer cros_ec = {
- .max_data_read = EC_HOST_CMD_REGION_SIZE,
- .max_data_write = 64,
- .probe = cros_ec_probe_size,
- .read = cros_ec_read,
- .write = cros_ec_write,
- .erase = cros_ec_block_erase,
- };
- /*
- * The algorithm is following:
- *
- * 1. If you detect EC command args support, success.
- * 2. If all ports read 0xff, fail.
- * 3. Try hello command (for API v0).
- *
- * TODO: This is an intrusive command for non-Google ECs. Needs a more proper
- * and more friendly way to detect.
- */
- static int detect_ec(struct cros_ec_priv *priv) {
- int i;
- int byte = 0xff;
- int old_timeout = ec_timeout_usec;
- #if USE_CROS_EC_LOCK == 1
- msg_gdbg("Acquiring CROS_EC lock (timeout=%d sec)...\n",
- CROS_EC_LOCK_TIMEOUT_SECS);
- if (acquire_cros_ec_lock(CROS_EC_LOCK_TIMEOUT_SECS) < 0) {
- msg_gerr("Could not acquire CROS_EC lock.\n");
- return 1;
- }
- #endif
- /*
- * Test if the I/O port has been configured for Chromium EC LPC
- * interface. If all the bytes are 0xff, very likely that Chromium EC
- * is not present.
- *
- * TODO: (crosbug.com/p/10963) Should only need to look at the command
- * byte, since we don't support ACPI burst mode and thus bit 4 should
- * be 0.
- */
- byte &= inb(EC_LPC_ADDR_HOST_CMD);
- byte &= inb(EC_LPC_ADDR_HOST_DATA);
- for (i = 0; i < EC_OLD_PARAM_SIZE && byte == 0xff; ++i)
- byte &= inb(EC_LPC_ADDR_OLD_PARAM + i);
- if (byte == 0xff) {
- msg_pdbg("Port 0x%x,0x%x,0x%x-0x%x are all 0xFF.\n",
- EC_LPC_ADDR_HOST_CMD, EC_LPC_ADDR_HOST_DATA,
- EC_LPC_ADDR_OLD_PARAM,
- EC_LPC_ADDR_OLD_PARAM + EC_OLD_PARAM_SIZE - 1);
- msg_pdbg(
- "Very likely this board doesn't have a Chromium EC.\n");
- return 1;
- }
- /*
- * Test if LPC command args are supported.
- *
- * The cheapest way to do this is by looking for the memory-mapped
- * flag. This is faster than sending a new-style 'hello' command and
- * seeing whether the EC sets the EC_HOST_ARGS_FLAG_FROM_HOST flag
- * in args when it responds.
- */
- if (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E' ||
- inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C') {
- msg_perr("Missing Chromium EC memory map.\n");
- return -1;
- }
- i = inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_HOST_CMD_FLAGS);
- if (i & EC_HOST_CMD_FLAG_VERSION_3) {
- /* Protocol version 3 */
- priv->ec_command = cros_ec_command_lpc_v3_lm4;
- #if 0
- /* FIXME(dhendrix): Overflow errors occurred when using
- EC_LPC_HOST_PACKET_SIZE */
- cros_ec.max_data_write =
- EC_LPC_HOST_PACKET_SIZE - sizeof(struct ec_host_request);
- cros_ec.max_data_read =
- EC_LPC_HOST_PACKET_SIZE - sizeof(struct ec_host_response);
- #endif
- cros_ec.max_data_write = 128 - sizeof(struct ec_host_request);
- cros_ec.max_data_read = 128 - sizeof(struct ec_host_response);
- } else if (i & EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED) {
- /* Protocol version 2 */
- priv->ec_command = cros_ec_command_lpc_lm4;
- #if 0
- /*
- * FIXME(dhendrix): We should be able to use
- * EC_PROTO2_MAX_PARAM_SIZE, but that has not been thoroughly
- * tested so for now leave the sizes at default.
- */
- cros_ec.max_data_read = EC_PROTO2_MAX_PARAM_SIZE;
- cros_ec.max_data_write = EC_PROTO2_MAX_PARAM_SIZE;
- #endif
- } else {
- priv->ec_command = cros_ec_command_lpc_old;
- }
- /* Try hello command -- for EC only supports API v0
- * TODO: (crosbug.com/p/33102) Remove after MP.
- */
- /* reduce timeout period temporarily in case EC is not present */
- ec_timeout_usec = 25000;
- if (cros_ec_test(&cros_ec_lpc_priv))
- return 1;
- ec_timeout_usec = old_timeout;
- cros_ec_set_max_size(&cros_ec_lpc_priv, &cros_ec);
- return 0;
- }
- static int cros_ec_lpc_shutdown(void *data)
- {
- #if USE_CROS_EC_LOCK == 1
- release_cros_ec_lock();
- #endif
- return 0;
- }
- int cros_ec_probe_lpc(const char *name) {
- msg_pdbg("%s():%d ...\n", __func__, __LINE__);
- if (alias && alias->type != ALIAS_EC)
- return 1;
- if (cros_ec_parse_param(&cros_ec_lpc_priv))
- return 1;
- if (cros_ec_lpc_priv.dev && strcmp(cros_ec_lpc_priv.dev, "ec")) {
- msg_pdbg("cros_ec_lpc only supports \"ec\" type devices.\n");
- return 1;
- }
- if (detect_ec(&cros_ec_lpc_priv))
- return 1;
- msg_pdbg("CROS_EC detected on LPC bus\n");
- cros_ec_lpc_priv.detected = 1;
- cros_ec_priv = &cros_ec_lpc_priv;
- if (buses_supported & BUS_SPI) {
- msg_pdbg("%s():%d remove BUS_SPI from buses_supported.\n",
- __func__, __LINE__);
- buses_supported &= ~BUS_SPI;
- }
- register_opaque_programmer(&cros_ec);
- buses_supported |= BUS_LPC;
- if (register_shutdown(cros_ec_lpc_shutdown, NULL)) {
- msg_perr("Cannot register LPC shutdown function.\n");
- return 1;
- }
- return 0;
- }
|