summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2021-03-11 13:17:35 -0800
committerCommit Bot <commit-bot@chromium.org>2021-04-05 20:50:07 +0000
commit362e02845295c56a3c1fbb4c5fd39b318a45253c (patch)
treee87d8533d0d27ce8513d407320ea9de58f353f86
parenta801f6505a207ff5f0787cfb89e1810eacf4a1db (diff)
downloadchrome-ec-362e02845295c56a3c1fbb4c5fd39b318a45253c.tar.gz
PCHG: Support firmware update
This patch adds EC_CMD_PCHG_UPDATE, which allows the host to update firmware of ctn730 via I2C. An updater (e.g. ectool) is expected to issue EC_PCHG_UPDATE_CMD_OPEN, multiple EC_PCHG_UPDATE_CMD_WRITEs, then EC_PCHG_UPDATE_CLOSE. Each sub-command completion is notified to the host via EC_MKBP_EVENT_PCHG. An updater is supposed to wait for the previous sub-command to complete before proceeding to the next. Example: localhost ~ # ectool pchg 0 State: DOWNLOAD (6) FW Version: 0x104 localhost ~ # ectool pchg 0 update 0x207000 0x105 /path/to/image.bin Update file /path/to/image.bin (85632 bytes) is opened. Writing firmware (port=0 ver=0x105 addr=0x207000 bsize=128): ********************************************************************** FW update session closed (CRC32=0x7bd5c66f). localhost ~ # ectool pchg 0 reset Reset port 0 complete localhost ~ # ectool pchg 0 State: ENABLED (2) FW Version: 0x105 BUG=b:182600604, b:173235954 BRANCH=trogdor TEST=ectool pchg 0 update 0x201200 0x105 /tmp/user_ee_X0.1_V1.5.bin TEST=ectool pchg 0 reset Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Change-Id: I9c62f1714dd69428ab5870c443cb4eb77881a6c6 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2757099
-rw-r--r--board/coachz/board.c1
-rw-r--r--common/peripheral_charger.c194
-rw-r--r--driver/nfc/ctn730.c134
-rw-r--r--include/ec_commands.h68
-rw-r--r--include/peripheral_charger.h68
-rw-r--r--util/build.mk1
-rw-r--r--util/ectool.c262
7 files changed, 671 insertions, 57 deletions
diff --git a/board/coachz/board.c b/board/coachz/board.c
index d927afd8b1..1442b5b147 100644
--- a/board/coachz/board.c
+++ b/board/coachz/board.c
@@ -60,6 +60,7 @@ struct pchg pchgs[] = {
.i2c_port = I2C_PORT_WLC,
.irq_pin = GPIO_WLC_IRQ_CONN,
.full_percent = 96,
+ .block_size = 128,
},
.events = QUEUE_NULL(PCHG_EVENT_QUEUE_SIZE, enum pchg_event),
},
diff --git a/common/peripheral_charger.c b/common/peripheral_charger.c
index 3eeaf62883..73bd5e32b8 100644
--- a/common/peripheral_charger.c
+++ b/common/peripheral_charger.c
@@ -9,6 +9,7 @@
#include "device_event.h"
#include "hooks.h"
#include "host_command.h"
+#include "mkbp_event.h"
#include "peripheral_charger.h"
#include "queue.h"
#include "stdbool.h"
@@ -20,6 +21,9 @@
#define CPRINTS(fmt, args...) cprints(CC_PCHG, "PCHG: " fmt, ##args)
+/* Currently only used for FW update. */
+static uint32_t pchg_host_events;
+
static void pchg_queue_event(struct pchg *ctx, enum pchg_event event)
{
mutex_lock(&ctx->mtx);
@@ -30,6 +34,14 @@ static void pchg_queue_event(struct pchg *ctx, enum pchg_event event)
mutex_unlock(&ctx->mtx);
}
+static void _send_host_event(const struct pchg *ctx, uint32_t event)
+{
+ int port = PCHG_CTX_TO_PORT(ctx);
+
+ atomic_or(&pchg_host_events, event | port << EC_MKBP_PCHG_PORT_SHIFT);
+ mkbp_send_event(EC_MKBP_EVENT_PCHG);
+}
+
static const char *_text_state(enum pchg_state state)
{
/* TODO: Use "S%d" for normal build. */
@@ -58,11 +70,18 @@ static const char *_text_event(enum pchg_event event)
[PCHG_EVENT_CHARGE_UPDATE] = "CHARGE_UPDATE",
[PCHG_EVENT_CHARGE_ENDED] = "CHARGE_ENDED",
[PCHG_EVENT_CHARGE_STOPPED] = "CHARGE_STOPPED",
+ [PCHG_EVENT_UPDATE_OPENED] = "UPDATE_OPENED",
+ [PCHG_EVENT_UPDATE_CLOSED] = "UPDATE_CLOSED",
+ [PCHG_EVENT_UPDATE_WRITTEN] = "UPDATE_WRITTEN",
[PCHG_EVENT_IN_NORMAL] = "IN_NORMAL",
[PCHG_EVENT_CHARGE_ERROR] = "CHARGE_ERROR",
- [PCHG_EVENT_INITIALIZE] = "INITIALIZE",
+ [PCHG_EVENT_UPDATE_ERROR] = "UPDATE_ERROR",
+ [PCHG_EVENT_OTHER_ERROR] = "OTHER_ERROR",
[PCHG_EVENT_ENABLE] = "ENABLE",
[PCHG_EVENT_DISABLE] = "DISABLE",
+ [PCHG_EVENT_UPDATE_OPEN] = "UPDATE_OPEN",
+ [PCHG_EVENT_UPDATE_WRITE] = "UPDATE_WRITE",
+ [PCHG_EVENT_UPDATE_CLOSE] = "UPDATE_CLOSE",
};
BUILD_ASSERT(ARRAY_SIZE(event_names) == PCHG_EVENT_COUNT);
@@ -80,6 +99,7 @@ static void _clear_port(struct pchg *ctx)
atomic_clear(&ctx->irq);
ctx->battery_percent = 0;
ctx->error = 0;
+ ctx->update.data_ready = 0;
}
static enum pchg_state pchg_reset(struct pchg *ctx)
@@ -101,6 +121,9 @@ static enum pchg_state pchg_reset(struct pchg *ctx)
} else if (rv != EC_SUCCESS_IN_PROGRESS) {
CPRINTS("ERR: Failed to reset to normal mode");
}
+ } else {
+ state = PCHG_STATE_DOWNLOAD;
+ pchg_queue_event(ctx, PCHG_EVENT_UPDATE_OPEN);
}
return state;
@@ -257,6 +280,78 @@ static void pchg_state_charging(struct pchg *ctx)
}
}
+static void pchg_state_download(struct pchg *ctx)
+{
+ int rv;
+
+ switch (ctx->event) {
+ case PCHG_EVENT_RESET:
+ ctx->state = pchg_reset(ctx);
+ break;
+ case PCHG_EVENT_UPDATE_OPEN:
+ rv = ctx->cfg->drv->update_open(ctx);
+ if (rv == EC_SUCCESS) {
+ ctx->state = PCHG_STATE_DOWNLOADING;
+ } else if (rv != EC_SUCCESS_IN_PROGRESS) {
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
+ CPRINTS("ERR: Failed to open");
+ }
+ break;
+ case PCHG_EVENT_UPDATE_OPENED:
+ ctx->state = PCHG_STATE_DOWNLOADING;
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_OPENED);
+ break;
+ case PCHG_EVENT_UPDATE_ERROR:
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
+ break;
+ default:
+ break;
+ }
+}
+
+static void pchg_state_downloading(struct pchg *ctx)
+{
+ int rv;
+
+ switch (ctx->event) {
+ case PCHG_EVENT_RESET:
+ ctx->state = pchg_reset(ctx);
+ break;
+ case PCHG_EVENT_UPDATE_WRITE:
+ if (ctx->update.data_ready == 0)
+ break;
+ rv = ctx->cfg->drv->update_write(ctx);
+ if (rv != EC_SUCCESS && rv != EC_SUCCESS_IN_PROGRESS) {
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
+ CPRINTS("ERR: Failed to write");
+ }
+ break;
+ case PCHG_EVENT_UPDATE_WRITTEN:
+ ctx->update.data_ready = 0;
+ _send_host_event(ctx, EC_MKBP_PCHG_WRITE_COMPLETE);
+ break;
+ case PCHG_EVENT_UPDATE_CLOSE:
+ rv = ctx->cfg->drv->update_close(ctx);
+ if (rv == EC_SUCCESS) {
+ ctx->state = PCHG_STATE_DOWNLOAD;
+ } else if (rv != EC_SUCCESS_IN_PROGRESS) {
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
+ CPRINTS("ERR: Failed to close");
+ }
+ break;
+ case PCHG_EVENT_UPDATE_CLOSED:
+ ctx->state = PCHG_STATE_DOWNLOAD;
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_CLOSED);
+ break;
+ case PCHG_EVENT_UPDATE_ERROR:
+ CPRINTS("ERR: Failed to update");
+ _send_host_event(ctx, EC_MKBP_PCHG_UPDATE_ERROR);
+ break;
+ default:
+ break;
+ }
+}
+
static int pchg_run(struct pchg *ctx)
{
enum pchg_state previous_state = ctx->state;
@@ -303,6 +398,12 @@ static int pchg_run(struct pchg *ctx)
case PCHG_STATE_CHARGING:
pchg_state_charging(ctx);
break;
+ case PCHG_STATE_DOWNLOAD:
+ pchg_state_download(ctx);
+ break;
+ case PCHG_STATE_DOWNLOADING:
+ pchg_state_downloading(ctx);
+ break;
default:
CPRINTS("ERR: Unknown state (%d)", ctx->state);
return 0;
@@ -422,6 +523,8 @@ static enum ec_status hc_pchg_count(struct host_cmd_handler_args *args)
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG_COUNT, hc_pchg_count, EC_VER_MASK(0));
+#define HCPRINTS(fmt, args...) cprints(CC_PCHG, "HC:PCHG: " fmt, ##args)
+
static enum ec_status hc_pchg(struct host_cmd_handler_args *args)
{
const struct ec_params_pchg *p = args->params;
@@ -451,13 +554,96 @@ static enum ec_status hc_pchg(struct host_cmd_handler_args *args)
}
DECLARE_HOST_COMMAND(EC_CMD_PCHG, hc_pchg, EC_VER_MASK(1));
+int pchg_get_next_event(uint8_t *out)
+{
+ uint32_t events = atomic_clear(&pchg_host_events);
+
+ memcpy(out, &events, sizeof(events));
+
+ return sizeof(events);
+}
+DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_PCHG, pchg_get_next_event);
+
+static enum ec_status hc_pchg_update(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_pchg_update *p = args->params;
+ struct ec_response_pchg_update *r = args->response;
+ int port = p->port;
+ struct pchg *ctx;
+
+ if (port >= pchg_count)
+ return EC_RES_INVALID_PARAM;
+
+ ctx = &pchgs[port];
+
+ switch (p->cmd) {
+ case EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL:
+ HCPRINTS("Resetting to normal mode");
+
+ gpio_disable_interrupt(ctx->cfg->irq_pin);
+ _clear_port(ctx);
+ ctx->mode = PCHG_MODE_NORMAL;
+ ctx->cfg->drv->reset(ctx);
+ gpio_enable_interrupt(ctx->cfg->irq_pin);
+ break;
+
+ case EC_PCHG_UPDATE_CMD_OPEN:
+ HCPRINTS("Resetting to download mode");
+
+ gpio_disable_interrupt(ctx->cfg->irq_pin);
+ _clear_port(ctx);
+ ctx->mode = PCHG_MODE_DOWNLOAD;
+ ctx->cfg->drv->reset(ctx);
+ gpio_enable_interrupt(ctx->cfg->irq_pin);
+
+ ctx->update.version = p->version;
+ r->block_size = ctx->cfg->block_size;
+ args->response_size = sizeof(*r);
+ break;
+
+ case EC_PCHG_UPDATE_CMD_WRITE:
+ if (ctx->state != PCHG_STATE_DOWNLOADING)
+ return EC_RES_ERROR;
+ if (p->size > sizeof(ctx->update.data))
+ return EC_RES_OVERFLOW;
+ if (ctx->update.data_ready)
+ return EC_RES_BUSY;
+
+ HCPRINTS("Writing %u bytes to 0x%x", p->size, p->addr);
+ ctx->update.addr = p->addr;
+ ctx->update.size = p->size;
+ memcpy(ctx->update.data, p->data, p->size);
+ pchg_queue_event(ctx, PCHG_EVENT_UPDATE_WRITE);
+ ctx->update.data_ready = 1;
+ break;
+
+ case EC_PCHG_UPDATE_CMD_CLOSE:
+ if (ctx->state != PCHG_STATE_DOWNLOADING)
+ return EC_RES_ERROR;
+ if (ctx->update.data_ready)
+ return EC_RES_BUSY;
+
+ HCPRINTS("Closing update session (crc=0x%x)", p->crc32);
+ ctx->update.crc32 = p->crc32;
+ pchg_queue_event(ctx, PCHG_EVENT_UPDATE_CLOSE);
+ break;
+ default:
+ return EC_RES_INVALID_PARAM;
+ }
+
+ task_wake(TASK_ID_PCHG);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_PCHG_UPDATE, hc_pchg_update, EC_VER_MASK(0));
+
static int cc_pchg(int argc, char **argv)
{
int port;
char *end;
struct pchg *ctx;
- if (argc < 2 || 3 < argc)
+ if (argc < 2 || 4 < argc)
return EC_ERROR_PARAM_COUNT;
port = strtoi(argv[1], &end, 0);
@@ -477,6 +663,8 @@ static int cc_pchg(int argc, char **argv)
if (!strcasecmp(argv[2], "reset")) {
if (argc == 3)
ctx->mode = PCHG_MODE_NORMAL;
+ else if (!strcasecmp(argv[3], "download"))
+ ctx->mode = PCHG_MODE_DOWNLOAD;
else
return EC_ERROR_PARAM3;
gpio_disable_interrupt(ctx->cfg->irq_pin);
@@ -497,7 +685,7 @@ static int cc_pchg(int argc, char **argv)
}
DECLARE_CONSOLE_COMMAND(pchg, cc_pchg,
"\n\t<port>"
- "\n\t<port> reset"
+ "\n\t<port> reset [download]"
"\n\t<port> enable"
"\n\t<port> disable",
"Control peripheral chargers");
diff --git a/driver/nfc/ctn730.c b/driver/nfc/ctn730.c
index d3b0c578ca..b3da1bbb79 100644
--- a/driver/nfc/ctn730.c
+++ b/driver/nfc/ctn730.c
@@ -36,6 +36,9 @@ static const int _detection_interval_ms = 500;
*/
#define CTN730_I2C_ADDR 0x28
+/* Size of flash address space in bytes */
+#define CTN730_FLASH_ADDR_SIZE 3
+
/* All commands are guaranteed to finish within 1 second. */
#define CTN730_COMMAND_TIME_OUT (1 * SECOND)
@@ -46,6 +49,9 @@ static const int _detection_interval_ms = 500;
/* Instruction Codes */
#define WLC_HOST_CTRL_RESET 0b000000
+#define WLC_HOST_CTRL_DL_OPEN_SESSION 0b000011
+#define WLC_HOST_CTRL_DL_COMMIT_SESSION 0b000100
+#define WLC_HOST_CTRL_DL_WRITE_FLASH 0b000101
#define WLC_HOST_CTRL_DUMP_STATUS 0b001100
#define WLC_HOST_CTRL_GENERIC_ERROR 0b001111
#define WLC_HOST_CTRL_BIST 0b000110
@@ -68,6 +74,16 @@ static const int _detection_interval_ms = 500;
#define WLC_HOST_CTRL_RESET_CMD_MODE_NORMAL 0x00
#define WLC_HOST_CTRL_RESET_CMD_MODE_DOWNLOAD 0x01
+/* WLC_HOST_CTRL_DL_* constants */
+#define WLC_HOST_CTRL_DL_OPEN_SESSION_CMD_SIZE 2
+#define WLC_HOST_CTRL_DL_OPEN_SESSION_RSP_SIZE 1
+#define WLC_HOST_CTRL_DL_WRITE_FLASH_BLOCK_SIZE 128
+#define WLC_HOST_CTRL_DL_WRITE_FLASH_CMD_SIZE \
+ (CTN730_FLASH_ADDR_SIZE + WLC_HOST_CTRL_DL_WRITE_FLASH_BLOCK_SIZE)
+#define WLC_HOST_CTRL_DL_WRITE_FLASH_RSP_SIZE 1
+#define WLC_HOST_CTRL_DL_COMMIT_SESSION_CMD_SIZE 4
+#define WLC_HOST_CTRL_DL_COMMIT_SESSION_RSP_SIZE 1
+
/* WLC_CHG_CTRL_ENABLE constants */
#define WLC_CHG_CTRL_ENABLE_CMD_SIZE 2
#define WLC_CHG_CTRL_ENABLE_RSP_SIZE 1
@@ -137,6 +153,12 @@ static const char *_text_instruction(uint8_t instruction)
switch (instruction) {
case WLC_HOST_CTRL_RESET:
return "RESET";
+ case WLC_HOST_CTRL_DL_OPEN_SESSION:
+ return "DL_OPEN";
+ case WLC_HOST_CTRL_DL_COMMIT_SESSION:
+ return "DL_COMMIT";
+ case WLC_HOST_CTRL_DL_WRITE_FLASH:
+ return "DL_WRITE";
case WLC_HOST_CTRL_DUMP_STATUS:
return "DUMP_STATUS";
case WLC_HOST_CTRL_GENERIC_ERROR:
@@ -358,6 +380,42 @@ static int _process_payload_response(struct pchg *ctx, struct ctn730_msg *res)
if (buf[0] != WLC_HOST_STATUS_OK)
ctx->event = PCHG_EVENT_OTHER_ERROR;
break;
+ case WLC_HOST_CTRL_DL_OPEN_SESSION:
+ if (len != WLC_HOST_CTRL_DL_OPEN_SESSION_RSP_SIZE)
+ return EC_ERROR_UNKNOWN;
+ if (buf[0] != WLC_HOST_STATUS_OK) {
+ CPRINTS("FW open session failed for %s",
+ _text_status_code(buf[0]));
+ ctx->event = PCHG_EVENT_UPDATE_ERROR;
+ ctx->error |= PCHG_ERROR_MASK(PCHG_ERROR_FW_VERSION);
+ } else {
+ ctx->event = PCHG_EVENT_UPDATE_OPENED;
+ }
+ break;
+ case WLC_HOST_CTRL_DL_COMMIT_SESSION:
+ if (len != WLC_HOST_CTRL_DL_COMMIT_SESSION_RSP_SIZE)
+ return EC_ERROR_UNKNOWN;
+ if (buf[0] != WLC_HOST_STATUS_OK) {
+ CPRINTS("FW commit failed for %s",
+ _text_status_code(buf[0]));
+ ctx->event = PCHG_EVENT_UPDATE_ERROR;
+ ctx->error |= PCHG_ERROR_MASK(PCHG_ERROR_INVALID_FW);
+ } else {
+ ctx->event = PCHG_EVENT_UPDATE_CLOSED;
+ }
+ break;
+ case WLC_HOST_CTRL_DL_WRITE_FLASH:
+ if (len != WLC_HOST_CTRL_DL_WRITE_FLASH_RSP_SIZE)
+ return EC_ERROR_UNKNOWN;
+ if (buf[0] != WLC_HOST_STATUS_OK) {
+ CPRINTS("FW write failed for %s",
+ _text_status_code(buf[0]));
+ ctx->event = PCHG_EVENT_UPDATE_ERROR;
+ ctx->error |= PCHG_ERROR_MASK(PCHG_ERROR_WRITE_FLASH);
+ } else {
+ ctx->event = PCHG_EVENT_UPDATE_WRITTEN;
+ }
+ break;
case WLC_CHG_CTRL_ENABLE:
if (len != WLC_CHG_CTRL_ENABLE_RSP_SIZE)
return EC_ERROR_UNKNOWN;
@@ -530,6 +588,79 @@ static int ctn730_get_soc(struct pchg *ctx)
return EC_SUCCESS_IN_PROGRESS;
}
+static int ctn730_update_open(struct pchg *ctx)
+{
+ uint8_t buf[sizeof(struct ctn730_msg)
+ + WLC_HOST_CTRL_DL_OPEN_SESSION_CMD_SIZE];
+ struct ctn730_msg *cmd = (void *)buf;
+ uint32_t version = ctx->update.version;
+ int rv;
+
+ cmd->message_type = CTN730_MESSAGE_TYPE_COMMAND;
+ cmd->instruction = WLC_HOST_CTRL_DL_OPEN_SESSION;
+ cmd->length = WLC_HOST_CTRL_DL_OPEN_SESSION_CMD_SIZE;
+ cmd->payload[0] = (version >> 8) & 0xff;
+ cmd->payload[1] = version & 0xff;
+
+ rv = _send_command(ctx, cmd);
+ if (rv)
+ return rv;
+
+ return EC_SUCCESS_IN_PROGRESS;
+}
+
+static int ctn730_update_write(struct pchg *ctx)
+{
+ uint8_t buf[sizeof(struct ctn730_msg)
+ + WLC_HOST_CTRL_DL_WRITE_FLASH_CMD_SIZE];
+ struct ctn730_msg *cmd = (void *)buf;
+ uint32_t *a = (void *)cmd->payload;
+ uint8_t *d = (void *)&cmd->payload[CTN730_FLASH_ADDR_SIZE];
+ int rv;
+
+ /* Address is 3 bytes. FW size must be a multiple of 128 bytes. */
+ if (ctx->update.addr & GENMASK(31, 24)
+ || ctx->update.size != WLC_HOST_CTRL_DL_WRITE_FLASH_BLOCK_SIZE)
+ return EC_ERROR_INVAL;
+
+ cmd->message_type = CTN730_MESSAGE_TYPE_COMMAND;
+ cmd->instruction = WLC_HOST_CTRL_DL_WRITE_FLASH;
+ cmd->length = WLC_HOST_CTRL_DL_WRITE_FLASH_CMD_SIZE;
+
+ /* 4th byte will be overwritten by memcpy below. */
+ *a = ctx->update.addr;
+
+ /* Store data in payload with 0-padding for short blocks. */
+ memset(d, 0, WLC_HOST_CTRL_DL_WRITE_FLASH_BLOCK_SIZE);
+ memcpy(d, ctx->update.data, ctx->update.size);
+
+ rv = _send_command(ctx, cmd);
+ if (rv)
+ return rv;
+
+ return EC_SUCCESS_IN_PROGRESS;
+}
+
+static int ctn730_update_close(struct pchg *ctx)
+{
+ uint8_t buf[sizeof(struct ctn730_msg)
+ + WLC_HOST_CTRL_DL_COMMIT_SESSION_CMD_SIZE];
+ struct ctn730_msg *cmd = (void *)buf;
+ uint32_t *crc32 = (void *)cmd->payload;
+ int rv;
+
+ cmd->message_type = CTN730_MESSAGE_TYPE_COMMAND;
+ cmd->instruction = WLC_HOST_CTRL_DL_COMMIT_SESSION;
+ cmd->length = WLC_HOST_CTRL_DL_COMMIT_SESSION_CMD_SIZE;
+ *crc32 = ctx->update.crc32;
+
+ rv = _send_command(ctx, cmd);
+ if (rv)
+ return rv;
+
+ return EC_SUCCESS_IN_PROGRESS;
+}
+
/**
* Send command in blocking loop
*
@@ -598,6 +729,9 @@ const struct pchg_drv ctn730_drv = {
.enable = ctn730_enable,
.get_event = ctn730_get_event,
.get_soc = ctn730_get_soc,
+ .update_open = ctn730_update_open,
+ .update_write = ctn730_update_write,
+ .update_close = ctn730_update_close,
};
static int cc_ctn730(int argc, char **argv)
diff --git a/include/ec_commands.h b/include/ec_commands.h
index bd62d3a3ce..2df691b3f8 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -3789,6 +3789,9 @@ enum ec_mkbp_event {
/* New online calibration values are available. */
EC_MKBP_EVENT_ONLINE_CALIBRATION = 11,
+ /* Peripheral device charger event */
+ EC_MKBP_EVENT_PCHG = 12,
+
/* Number of MKBP events */
EC_MKBP_EVENT_COUNT,
};
@@ -6821,6 +6824,10 @@ enum pchg_state {
PCHG_STATE_CHARGING,
/* Device is fully charged. It implies DETECTED (& not charging). */
PCHG_STATE_FULL,
+ /* In download (or firmware update) mode. Update session is closed. */
+ PCHG_STATE_DOWNLOAD,
+ /* In download mode. Session is opened. Ready for receiving data. */
+ PCHG_STATE_DOWNLOADING,
/* Put no more entry below */
PCHG_STATE_COUNT,
};
@@ -6832,8 +6839,69 @@ enum pchg_state {
[PCHG_STATE_DETECTED] = "DETECTED", \
[PCHG_STATE_CHARGING] = "CHARGING", \
[PCHG_STATE_FULL] = "FULL", \
+ [PCHG_STATE_DOWNLOAD] = "DOWNLOAD", \
+ [PCHG_STATE_DOWNLOADING] = "DOWNLOADING", \
}
+/**
+ * Update firmware of peripheral chip
+ */
+#define EC_CMD_PCHG_UPDATE 0x0136
+
+/* Port number is encoded in bit[28:31]. */
+#define EC_MKBP_PCHG_PORT_SHIFT 28
+/* Utility macro for converting MKBP event to port number. */
+#define EC_MKBP_PCHG_EVENT_TO_PORT(e) (((e) >> EC_MKBP_PCHG_PORT_SHIFT) & 0xf)
+/* Utility macro for extracting event bits. */
+#define EC_MKBP_PCHG_EVENT_MASK(e) ((e) \
+ & GENMASK(EC_MKBP_PCHG_PORT_SHIFT-1, 0))
+
+#define EC_MKBP_PCHG_UPDATE_OPENED BIT(0)
+#define EC_MKBP_PCHG_WRITE_COMPLETE BIT(1)
+#define EC_MKBP_PCHG_UPDATE_CLOSED BIT(2)
+#define EC_MKBP_PCHG_UPDATE_ERROR BIT(3)
+
+enum ec_pchg_update_cmd {
+ /* Reset chip to normal mode. */
+ EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL = 0,
+ /* Reset and put a chip in update (a.k.a. download) mode. */
+ EC_PCHG_UPDATE_CMD_OPEN,
+ /* Write a block of data containing FW image. */
+ EC_PCHG_UPDATE_CMD_WRITE,
+ /* Close update session. */
+ EC_PCHG_UPDATE_CMD_CLOSE,
+ /* End of commands */
+ EC_PCHG_UPDATE_CMD_COUNT,
+};
+
+struct ec_params_pchg_update {
+ /* PCHG port number */
+ uint8_t port;
+ /* enum ec_pchg_update_cmd */
+ uint8_t cmd;
+ /* Padding */
+ uint8_t reserved0;
+ uint8_t reserved1;
+ /* Version of new firmware */
+ uint32_t version;
+ /* CRC32 of new firmware */
+ uint32_t crc32;
+ /* Address in chip memory where <data> is written to */
+ uint32_t addr;
+ /* Size of <data> */
+ uint32_t size;
+ /* Partial data of new firmware */
+ uint8_t data[];
+} __ec_align4;
+
+BUILD_ASSERT(EC_PCHG_UPDATE_CMD_COUNT
+ < BIT(sizeof(((struct ec_params_pchg_update *)0)->cmd)*8));
+
+struct ec_response_pchg_update {
+ /* Block size */
+ uint32_t block_size;
+} __ec_align4;
+
/*****************************************************************************/
/* The command range 0x200-0x2FF is reserved for Rotor. */
diff --git a/include/peripheral_charger.h b/include/peripheral_charger.h
index 7944efd109..93d6427814 100644
--- a/include/peripheral_charger.h
+++ b/include/peripheral_charger.h
@@ -60,6 +60,22 @@
* +--------------+ CHARGING +---------------+
* DEVICE_LOST +---------------+ ERROR
*
+ *
+ * In download (update firmware) mode, the state machine transitions as follows:
+ *
+ * +---------------+
+ * | DOWNLOAD |
+ * +------+--------+
+ * | ^
+ * UPDATE_OPEN | |
+ * | | UPDATE_CLOSE
+ * v |
+ * +--------+------+
+ * +-->| DOWNLOADING |
+ * | +------+--------+
+ * | |
+ * +----------+
+ * UPDATE_WRITE
*/
/* Size of event queue. Use it to initialize struct pchg.events. */
@@ -83,30 +99,43 @@ enum pchg_event {
PCHG_EVENT_CHARGE_UPDATE,
PCHG_EVENT_CHARGE_ENDED,
PCHG_EVENT_CHARGE_STOPPED,
+ PCHG_EVENT_UPDATE_OPENED,
+ PCHG_EVENT_UPDATE_CLOSED,
+ PCHG_EVENT_UPDATE_WRITTEN,
PCHG_EVENT_IN_NORMAL,
/* Errors */
PCHG_EVENT_CHARGE_ERROR,
+ PCHG_EVENT_UPDATE_ERROR,
PCHG_EVENT_OTHER_ERROR,
/* Internal (a.k.a. Host) Events */
- PCHG_EVENT_INITIALIZE,
PCHG_EVENT_ENABLE,
PCHG_EVENT_DISABLE,
+ PCHG_EVENT_UPDATE_OPEN,
+ PCHG_EVENT_UPDATE_WRITE,
+ PCHG_EVENT_UPDATE_CLOSE,
/* Counter. Add new entry above. */
PCHG_EVENT_COUNT,
};
enum pchg_error {
- PCHG_ERROR_NONE = 0,
- /* Error initiated by host. */
- PCHG_ERROR_HOST = BIT(0),
- PCHG_ERROR_OVER_TEMPERATURE = BIT(1),
- PCHG_ERROR_OVER_CURRENT = BIT(2),
- PCHG_ERROR_FOREIGN_OBJECT = BIT(3),
+ /* Errors reported by host. */
+ PCHG_ERROR_HOST,
+ PCHG_ERROR_OVER_TEMPERATURE,
+ PCHG_ERROR_OVER_CURRENT,
+ PCHG_ERROR_FOREIGN_OBJECT,
+ /* Errors reported by chip. */
+ PCHG_ERROR_FW_VERSION,
+ PCHG_ERROR_INVALID_FW,
+ PCHG_ERROR_WRITE_FLASH,
+ /* All other errors */
+ PCHG_ERROR_OTHER,
};
+#define PCHG_ERROR_MASK(e) BIT(e)
+
enum pchg_mode {
PCHG_MODE_NORMAL = 0,
PCHG_MODE_DOWNLOAD,
@@ -124,6 +153,23 @@ struct pchg_config {
const enum gpio_signal irq_pin;
/* Full battery percentage */
const uint8_t full_percent;
+ /* Update block size */
+ const uint32_t block_size;
+};
+
+struct pchg_update {
+ /* Version of new firmware. Usually used by EC_PCHG_UPDATE_CMD_OPEN. */
+ uint32_t version;
+ /* CRC32 of new firmware. Usually used by EC_PCHG_UPDATE_CMD_CLOSE. */
+ uint32_t crc32;
+ /* Address which <data> will be written to. */
+ uint32_t addr;
+ /* Size of <data> */
+ uint32_t size;
+ /* 0: No data. 1: Data is ready for write. */
+ uint8_t data_ready;
+ /* Partial data of new firmware */
+ uint8_t data[128];
};
/**
@@ -153,6 +199,8 @@ struct pchg {
uint8_t mode;
/* FW version */
uint32_t fw_version;
+ /* Context related to FW update */
+ struct pchg_update update;
};
/**
@@ -169,6 +217,12 @@ struct pchg_drv {
int (*get_event)(struct pchg *ctx);
/* Get battery level. */
int (*get_soc)(struct pchg *ctx);
+ /* open update session */
+ int (*update_open)(struct pchg *ctx);
+ /* write update image */
+ int (*update_write)(struct pchg *ctx);
+ /* close update session */
+ int (*update_close)(struct pchg *ctx);
};
/**
diff --git a/util/build.mk b/util/build.mk
index e868e32eff..9f9430a3b4 100644
--- a/util/build.mk
+++ b/util/build.mk
@@ -30,6 +30,7 @@ comm-objs+=comm-lpc.o comm-i2c.o misc_util.o
iteflash-objs = iteflash.o usb_if.o
ectool-objs=ectool.o ectool_keyscan.o ec_flash.o ec_panicinfo.o $(comm-objs)
+ectool-objs+=../common/crc.o
ectool_servo-objs=$(ectool-objs) comm-servo-spi.o
ec_sb_firmware_update-objs=ec_sb_firmware_update.o $(comm-objs) misc_util.o
ec_sb_firmware_update-objs+=powerd_lock.o
diff --git a/util/ectool.c b/util/ectool.c
index 091362c234..cd348aba28 100644
--- a/util/ectool.c
+++ b/util/ectool.c
@@ -20,6 +20,7 @@
#include "comm-host.h"
#include "chipset.h"
#include "compile_time_macros.h"
+#include "crc.h"
#include "cros_ec_dev.h"
#include "ec_panicinfo.h"
#include "ec_flash.h"
@@ -445,6 +446,24 @@ static int read_mapped_string(uint8_t offset, char *buffer, int max_size)
return ret;
}
+static int wait_event(long event_type,
+ struct ec_response_get_next_event_v1 *buffer,
+ size_t buffer_size, long timeout)
+{
+ int rv;
+
+ rv = ec_pollevent(1 << event_type, buffer, buffer_size, timeout);
+ if (rv == 0) {
+ fprintf(stderr, "Timeout waiting for MKBP event\n");
+ return -ETIMEDOUT;
+ } else if (rv < 0) {
+ perror("Error polling for MKBP event\n");
+ return -EIO;
+ }
+
+ return rv;
+}
+
int cmd_adc_read(int argc, char *argv[])
{
char *e;
@@ -9348,65 +9367,232 @@ static void cmd_pchg_help(char *cmd)
{
fprintf(stderr,
" Usage1: %s\n"
+ " Print the number of ports.\n"
+ "\n"
" Usage2: %s <port>\n"
+ " Print the status of <port>.\n"
"\n"
- " Usage1 prints the number of ports.\n"
- " Usage2 prints the status of a port.\n",
- cmd, cmd);
+ " Usage3: %s <port> reset\n"
+ " Reset <port>.\n"
+ "\n"
+ " Usage4: %s <port> update <address> <version> <file>\n"
+ " Update firmware of <port>.\n",
+ cmd, cmd, cmd, cmd);
+}
+
+static int cmd_pchg_info(const struct ec_response_pchg *res)
+{
+ static const char * const pchg_state_text[] = EC_PCHG_STATE_TEXT;
+
+ printf("State: %s (%d)\n", res->state < sizeof(pchg_state_text)
+ ? pchg_state_text[res->state] : "UNDEF", res->state);
+ printf("Battery: %u%%\n", res->battery_percentage);
+ printf("Errors: 0x%x\n", res->error);
+ printf("FW Version: 0x%x\n", res->fw_version);
+ printf("Dropped events: %u\n", res->dropped_event_count);
+ return 0;
}
-int cmd_pchg(int argc, char *argv[])
+static int cmd_pchg_wait_event(int port, uint32_t expected)
+{
+ struct ec_response_get_next_event_v1 event;
+ const long timeout = 5000;
+ uint32_t *e = &event.data.host_event;
+ int rv;
+
+ rv = wait_event(EC_MKBP_EVENT_PCHG, &event, sizeof(event), timeout);
+ if (rv < 0)
+ return rv;
+
+ if (EC_MKBP_PCHG_EVENT_TO_PORT(*e) == port) {
+ if (*e & EC_MKBP_PCHG_UPDATE_ERROR) {
+ fprintf(stderr, "\nReceived update error\n");
+ return -1;
+ }
+ if (*e & expected)
+ return 0;
+ }
+
+ fprintf(stderr, "\nExpected event=0x%x but received 0x%x\n",
+ expected, *e);
+ return -1;
+}
+
+static int cmd_pchg_update(int port, uint32_t address, uint32_t version,
+ const char *filename)
+{
+ struct ec_params_pchg_update *p = ec_outbuf;
+ struct ec_response_pchg_update *r = ec_inbuf;
+ FILE *fp;
+ size_t len, total;
+ int progress;
+ int rv;
+
+ fp = fopen(filename, "rb");
+ if (!fp) {
+ fprintf(stderr, "\nCan't open %s: %s\n",
+ filename, strerror(errno));
+ return -1;
+ }
+
+ fseek(fp, 0L, SEEK_END);
+ total = ftell(fp);
+ rewind(fp);
+ printf("Update file %s (%zu bytes) is opened.\n", filename, total);
+
+ /* Open session. */
+ p->port = port;
+ p->cmd = EC_PCHG_UPDATE_CMD_OPEN;
+ p->version = version;
+ rv = ec_command(EC_CMD_PCHG_UPDATE, 0, p, sizeof(*p), r, sizeof(*r));
+ if (rv < 0) {
+ fprintf(stderr, "\nFailed to open update session: %d\n", rv);
+ fclose(fp);
+ return rv;
+ }
+
+ if (r->block_size + sizeof(*p) > ec_max_outsize) {
+ fprintf(stderr, "\nBlock size (%d) is too large.\n",
+ r->block_size);
+ fclose(fp);
+ return -1;
+ }
+
+ rv = cmd_pchg_wait_event(port, EC_MKBP_PCHG_UPDATE_OPENED);
+ if (rv)
+ return rv;
+
+ printf("Writing firmware (port=%d ver=0x%x addr=0x%x bsize=%d):\n",
+ port, version, address, r->block_size);
+
+ p->cmd = EC_PCHG_UPDATE_CMD_WRITE;
+ p->addr = address;
+ crc32_init();
+
+ /* Write firmware in blocks. */
+ len = fread(p->data, 1, r->block_size, fp);
+ while (len > 0) {
+ int previous_progress = progress;
+ int i;
+
+ crc32_hash(p->data, len);
+ p->size = len;
+ rv = ec_command(EC_CMD_PCHG_UPDATE, 0, p,
+ sizeof(*p) + len, NULL, 0);
+ if (rv < 0) {
+ fprintf(stderr, "\nFailed to write FW: %d\n", rv);
+ fclose(fp);
+ return rv;
+ }
+
+ rv = cmd_pchg_wait_event(port, EC_MKBP_PCHG_WRITE_COMPLETE);
+ if (rv)
+ return rv;
+
+ p->addr += len;
+ progress = (p->addr - address) * 100 / total;
+ for (i = 0; i < progress - previous_progress; i++) {
+ printf("*");
+ fflush(stdout);
+ }
+
+ len = fread(p->data, 1, r->block_size, fp);
+ }
+
+ printf("\n");
+ fclose(fp);
+
+ /* Close session. */
+ p->cmd = EC_PCHG_UPDATE_CMD_CLOSE;
+ p->crc32 = crc32_result();
+ rv = ec_command(EC_CMD_PCHG_UPDATE, 0, p, sizeof(*p), NULL, 0);
+
+ if (rv < 0) {
+ fprintf(stderr, "\nFailed to close update session: %d\n", rv);
+ return rv;
+ }
+
+ rv = cmd_pchg_wait_event(port, EC_MKBP_PCHG_UPDATE_CLOSED);
+ if (rv)
+ return rv;
+
+ printf("FW update session closed (CRC32=0x%x).\n", p->crc32);
+
+ return 0;
+}
+
+static int cmd_pchg(int argc, char *argv[])
{
int port, port_count;
+ struct ec_response_pchg_count rcnt;
+ struct ec_params_pchg p;
+ struct ec_response_pchg r;
+ uint32_t address, version;
char *e;
int rv;
- struct ec_response_pchg_count *rsp_count = ec_inbuf;
- static const char * const pchg_state_text[] = EC_PCHG_STATE_TEXT;
- rv = ec_command(EC_CMD_PCHG_COUNT, 0, NULL, 0, ec_inbuf, ec_max_insize);
+ rv = ec_command(EC_CMD_PCHG_COUNT, 0, NULL, 0, &rcnt, sizeof(rcnt));
if (rv < 0) {
- fprintf(stderr, "Failed to get port count: %d\n", rv);
+ fprintf(stderr, "\nFailed to get port count: %d\n", rv);
return rv;
}
- port_count = rsp_count->port_count;
+ port_count = rcnt.port_count;
if (argc == 1) {
- /* Usage1 */
+ /* Usage.1 */
printf("%d\n", port_count);
return 0;
}
port = strtol(argv[1], &e, 0);
if ((e && *e) || port >= port_count) {
- fprintf(stderr, "Bad port index\n");
+ fprintf(stderr, "\nBad port index: %s\n", argv[1]);
+ cmd_pchg_help(argv[0]);
return -1;
}
- if (argc < 3) {
- /* Usage2 */
- struct ec_params_pchg *p = ec_outbuf;
- struct ec_response_pchg *r = ec_inbuf;
+ p.port = port;
+ rv = ec_command(EC_CMD_PCHG, 1, &p, sizeof(p), &r, sizeof(r));
+ if (rv < 0) {
+ fprintf(stderr, "\nError code: %d\n", rv);
+ return rv;
+ }
- p->port = port;
- rv = ec_command(EC_CMD_PCHG, 1, ec_outbuf, sizeof(*p),
- ec_inbuf, ec_max_insize);
+ if (argc == 2) {
+ /* Usage.2 */
+ return cmd_pchg_info(&r);
+ } else if (argc == 3 && !strcmp(argv[2], "reset")) {
+ /* Usage.3 */
+ struct ec_params_pchg_update *u = ec_outbuf;
+
+ u->cmd = EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL;
+ rv = ec_command(EC_CMD_PCHG_UPDATE, 0, u, sizeof(*u), NULL, 0);
if (rv < 0) {
- fprintf(stderr, "Error code: %d\n", rv);
+ fprintf(stderr, "\nFailed to reset port %d: %d\n",
+ port, rv);
+ cmd_pchg_help(argv[0]);
return rv;
}
-
- printf("State: %s (%d)\n",
- r->state < sizeof(pchg_state_text) ?
- pchg_state_text[r->state] : "UNDEF",
- r->state);
- printf("Battery: %u%%\n", r->battery_percentage);
- printf("Errors: 0x%x\n", r->error);
- printf("FW Version: 0x%x\n", r->fw_version);
- printf("Dropped events: %u\n", r->dropped_event_count);
+ printf("Reset port %d complete.\n", port);
return 0;
+ } else if (argc == 6 && !strcmp(argv[2], "update")) {
+ /* Usage.4 */
+ address = strtol(argv[3], &e, 0);
+ if (e && *e) {
+ fprintf(stderr, "\nBad address: %s.\n", argv[3]);
+ cmd_pchg_help(argv[0]);
+ return -1;
+ }
+ version = strtol(argv[4], &e, 0);
+ if (e && *e) {
+ fprintf(stderr, "\nBad version: %s.\n", argv[4]);
+ cmd_pchg_help(argv[0]);
+ return -1;
+ }
+ return cmd_pchg_update(port, address, version, argv[5]);
}
- fprintf(stderr, "Invalid parameter count\n\n");
+ fprintf(stderr, "Invalid parameter\n\n");
cmd_pchg_help(argv[0]);
return -1;
@@ -10045,24 +10231,6 @@ err:
return rv < 0;
}
-static int wait_event(long event_type,
- struct ec_response_get_next_event_v1 *buffer,
- size_t buffer_size, long timeout)
-{
- int rv;
-
- rv = ec_pollevent(1 << event_type, buffer, buffer_size, timeout);
- if (rv == 0) {
- fprintf(stderr, "Timeout waiting for MKBP event\n");
- return -ETIMEDOUT;
- } else if (rv < 0) {
- perror("Error polling for MKBP event\n");
- return -EIO;
- }
-
- return rv;
-}
-
int cmd_wait_event(int argc, char *argv[])
{
int rv, i;