| /* |
| * 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; |
| } |