| /* |
| * This file is part of the flashrom project. |
| * |
| * Copyright (C) 2012 Google Inc. |
| * |
| * 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 <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "file.h" |
| #include "flash.h" |
| #include "gec_lpc_commands.h" |
| #include "programmer.h" |
| |
| #define SYSFS_I2C_DEV_ROOT "/sys/bus/i2c/devices" |
| #define GEC_I2C_ADAPTER_NAME "cros_ec_i2c" |
| #define GEC_I2C_ADDRESS 0x1e |
| |
| /* protocol bytes (command/response code + checksum byte) */ |
| #define GEC_PROTO_BYTES 2 |
| |
| static int ec_timeout_usec = 1000000; |
| |
| static unsigned int bus; |
| |
| static int gec_i2c_shutdown(void *data) |
| { |
| return linux_i2c_shutdown(data); |
| } |
| |
| /* |
| * gec_command_i2c - Issue command to GEC over I2C |
| * |
| * @command: command code |
| * @outdata: data to send to EC |
| * @outsize: number of bytes in outbound payload |
| * @indata: (unallocated) buffer to store data received from EC |
| * @insize: number of bytes in inbound payload |
| * |
| * Protocol-related details will be handled by this function. The outdata |
| * and indata buffers will contain payload data (if any); command and response |
| * codes as well as checksum data are handled transparently by this function. |
| * |
| * Returns the command status code, or -1 if other error. |
| */ |
| static int gec_command_i2c(int command, const void *outdata, int outsize, |
| void *indata, int insize) { |
| int ret = -1; |
| uint8_t *req_buf = NULL, *resp_buf = NULL; |
| int req_len = 0, resp_len = 0; |
| int i, csum; |
| |
| if (outsize) { |
| req_len = outsize + GEC_PROTO_BYTES; |
| req_buf = calloc(1, req_len); |
| if (!req_buf) |
| goto done; |
| |
| /* copy message payload and compute checksum */ |
| memcpy(&req_buf[1], outdata, outsize); |
| for (i = 0, csum = 0; i < outsize; i++) { |
| req_buf[i + 1] = *((uint8_t *)outdata + i); |
| csum += *((uint8_t *)outdata + i); |
| } |
| req_buf[req_len - 1] = csum & 0xff; |
| } else { |
| /* request buffer will hold command code only */ |
| req_len = 1; |
| req_buf = calloc(1, req_len); |
| if (!req_buf) |
| goto done; |
| } |
| req_buf[0] = command; |
| |
| msg_pspew("%s: req_buf: ", __func__); |
| for (i = 0; i < req_len; i++) |
| msg_pspew("%02x ", req_buf[i]); |
| msg_pspew("\n"); |
| |
| if (insize) { |
| resp_len = insize + GEC_PROTO_BYTES; |
| resp_buf = calloc(1, resp_len); |
| if (!resp_buf) |
| goto done; |
| } else { |
| /* response buffer will hold error code only */ |
| resp_len = 1; |
| resp_buf = calloc(1, resp_len); |
| if (!resp_buf) |
| goto done; |
| } |
| |
| ret = linux_i2c_xfer(bus, GEC_I2C_ADDRESS, |
| resp_buf, resp_len, req_buf, req_len); |
| if (ret) |
| goto done; |
| |
| msg_pspew("%s: resp_buf: ", __func__); |
| for (i = 0; i < resp_len; i++) |
| msg_pspew("%02x ", resp_buf[i]); |
| msg_pspew("\n"); |
| |
| /* check response error code */ |
| ret = resp_buf[0]; |
| if (ret) { |
| msg_pdbg("command 0x%02x returned an error %d\n", |
| command, resp_buf[0]); |
| } |
| |
| if (insize) { |
| /* copy response packet payload and compute checksum */ |
| for (i = 0, csum = 0; i < insize; i++) |
| csum += resp_buf[i + 1]; |
| csum &= 0xff; |
| |
| if (csum != resp_buf[resp_len - 1]) { |
| msg_pdbg("bad checksum (got 0x%02x from EC, calculated " |
| "0x%02x\n", resp_buf[resp_len - 1], csum); |
| ret = -1; |
| goto done; |
| } |
| |
| memcpy(indata, &resp_buf[1], insize); |
| } |
| done: |
| if (resp_buf) |
| free(resp_buf); |
| if (req_buf) |
| free(req_buf); |
| return ret; |
| } |
| |
| static int detect_ec(void) |
| { |
| struct lpc_params_hello request; |
| struct lpc_response_hello response; |
| int rc = 0; |
| int old_timeout = ec_timeout_usec; |
| |
| /* FIXME: for now we allow BUS_LPC because scripts might rely on |
| passing "-p internal:bus=lpc" to target EC */ |
| if ((target_bus != BUS_PROG) && (target_bus != BUS_LPC)) { |
| msg_pdbg("%s():%d cannot use target_bus\n", __func__, __LINE__); |
| return 1; |
| } |
| |
| /* reduce timeout period temporarily in case EC is not present */ |
| ec_timeout_usec = 25000; |
| |
| /* Say hello to EC. */ |
| request.in_data = 0xf0e0d0c0; /* Expect EC will add on 0x01020304. */ |
| msg_pdbg("%s: sending HELLO request with 0x%08x\n", |
| __func__, request.in_data); |
| rc = gec_command_i2c(EC_LPC_COMMAND_HELLO, &request, |
| sizeof(request), &response, sizeof(response)); |
| msg_pdbg("%s: response: 0x%08x\n", __func__, response.out_data); |
| |
| ec_timeout_usec = old_timeout; |
| |
| if (rc || response.out_data != 0xf1e2d3c4) { |
| msg_pdbg("response.out_data is not 0xf1e2d3c4.\n" |
| "rc=%d, request=0x%x response=0x%x\n", |
| rc, request.in_data, response.out_data); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static struct gec_priv gec_i2c_priv = { |
| .detected = 0, |
| .ec_command = gec_command_i2c, |
| }; |
| |
| static const struct opaque_programmer opaque_programmer_gec_i2c = { |
| .max_data_read = EC_LPC_PARAM_SIZE, |
| .max_data_write = 64, |
| .probe = gec_probe_size, |
| .read = gec_read, |
| .write = gec_write, |
| .erase = gec_block_erase, |
| .data = &gec_i2c_priv, |
| }; |
| |
| int gec_probe_i2c(const char *name) |
| { |
| const char *path, *s, *p; |
| int ret = 1; |
| |
| msg_pdbg("%s: probing for GEC on I2C...\n", __func__); |
| |
| /* |
| * It is possible for the MFD to show up on a different bus than |
| * its I2C adapter. For ChromeOS EC, the I2C passthru adapter piggy- |
| * backs on top of the kernel EC driver. This allows the kernel to |
| * use the MFD while allowing userspace access to the I2C module. |
| * |
| * So for the purposes of finding the correct bus, use the name of |
| * the I2C adapter and not the MFD itself. |
| */ |
| path = scanft(SYSFS_I2C_DEV_ROOT, "name", GEC_I2C_ADAPTER_NAME, 1); |
| if (!path) { |
| msg_pdbg("GEC I2C adapter not found\n"); |
| goto gec_probe_i2c_done; |
| } |
| |
| /* |
| * i2c-* may show up more than once in the path (especially in the |
| * case of the MFD with passthru I2C adapter), so use whichever |
| * instance shows up last. |
| */ |
| for (s = path + strlen(path) - 4; s > path; s--) { |
| if (!strncmp(s, "i2c-", 4)) |
| break; |
| } |
| |
| if ((s == path) || (sscanf(s, "i2c-%u", &bus) != 1)) { |
| msg_perr("Unable to parse I2C bus number\n"); |
| goto gec_probe_i2c_done; |
| } |
| |
| msg_pdbg("Opening GEC (bus %u, addr 0x%02x)\n", bus, GEC_I2C_ADDRESS); |
| if (linux_i2c_open(bus, GEC_I2C_ADDRESS)) |
| goto gec_probe_i2c_done; |
| |
| if (detect_ec()) { |
| linux_i2c_close(); |
| goto gec_probe_i2c_done; |
| } |
| |
| if (register_shutdown(gec_i2c_shutdown, NULL)) |
| goto gec_probe_i2c_done; |
| |
| msg_pdbg("GEC detected on I2C bus\n"); |
| register_opaque_programmer(&opaque_programmer_gec_i2c); |
| gec_i2c_priv.detected = 1; |
| ret = 0; |
| |
| gec_probe_i2c_done: |
| free(path); |
| return ret; |
| } |