diff options
author | Daisuke Nojiri <dnojiri@chromium.org> | 2021-03-11 13:17:35 -0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-04-05 20:50:07 +0000 |
commit | 362e02845295c56a3c1fbb4c5fd39b318a45253c (patch) | |
tree | e87d8533d0d27ce8513d407320ea9de58f353f86 | |
parent | a801f6505a207ff5f0787cfb89e1810eacf4a1db (diff) | |
download | chrome-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.c | 1 | ||||
-rw-r--r-- | common/peripheral_charger.c | 194 | ||||
-rw-r--r-- | driver/nfc/ctn730.c | 134 | ||||
-rw-r--r-- | include/ec_commands.h | 68 | ||||
-rw-r--r-- | include/peripheral_charger.h | 68 | ||||
-rw-r--r-- | util/build.mk | 1 | ||||
-rw-r--r-- | util/ectool.c | 262 |
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; |