/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include "comm-host.h" #include "compile_time_macros.h" #include "ec_sb_firmware_update.h" #include "ec_commands.h" #include "lock/gec_lock.h" #include "misc_util.h" #include "powerd_lock.h" /* Subcommands: [check|update] */ enum { OP_UNKNOWN = 0, OP_CHECK = 1, OP_UPDATE = 2, }; struct delay_value { uint32_t steps; uint32_t value; }; /* Default retry counter on errors */ #define SB_FW_UPDATE_DEFAULT_RETRY_CNT 3 /* Default delay value */ #define SB_FW_UPDATE_DEFAULT_DELAY 1000 #define DELAY_US_BEGIN 500000 #define DELAY_US_END 1000000 #define DELAY_US_BUSY 1000000 #define DELAY_US_WRITE_END 50000 static struct delay_value sb_delays[] = { {1, 100000}, {2, 9000000}, {4, 100000}, {771, 30000}, {2200, 10000}, {0xFFFFFF, 50000}, }; enum fw_update_state { S0_READ_STATUS = 0, S1_READ_INFO = 1, S2_WRITE_PREPARE = 2, S3_READ_STATUS = 3, S4_WRITE_UPDATE = 4, S5_READ_STATUS = 5, S6_WRITE_BLOCK = 6, S7_READ_STATUS = 7, S8_WRITE_END = 8, S9_READ_STATUS = 9, S10_TERMINAL = 10 }; #define MAX_FW_IMAGE_NAME_SIZE 80 /* Firmware Update Control Flags */ enum { F_AC_PRESENT = 0x1, /* AC Present */ F_VERSION_CHECK = 0x2, /* do firmware version check */ F_UPDATE = 0x4, /* do firmware update */ F_NEED_UPDATE = 0x8, /* need firmware update */ F_POWERD_DISABLED = 0x10, /* powerd is disabled */ F_LFCC_ZERO = 0x20, /* last full charge is zero */ F_BATT_DISCHARGE = 0x40 /* battery discharging */ }; struct fw_update_ctrl { uint32_t flags; /* fw update control flags */ int size; /* size of battery firmware image */ char *ptr; /* current read pointer of the firmware image */ int offset; /* current block write offset */ struct sb_fw_header *fw_img_hdr; /*pointer to firmware image header*/ struct sb_fw_update_status status; struct sb_fw_update_info info; int err_retry_cnt; int fec_err_retry_cnt; int busy_retry_cnt; int step_size; int rv; char image_name[MAX_FW_IMAGE_NAME_SIZE]; char msg[256]; }; /* * Global Firmware Update Control Data Structure */ static struct fw_update_ctrl fw_update; static uint32_t get_delay_value(uint32_t offset, uint32_t step_size) { int sz = ARRAY_SIZE(sb_delays); int i; for (i = 0; i < sz; i++) { if (offset <= sb_delays[i].steps * step_size) return sb_delays[i].value; } return sb_delays[sz-1].value; } static void print_battery_firmware_image_hdr( struct sb_fw_header *hdr) { printf("Latest Battery Firmware:\n"); printf("\t%c%c%c%c hdr_ver:%04x major_minor:%04x\n", hdr->signature[0], hdr->signature[1], hdr->signature[2], hdr->signature[3], hdr->hdr_version, hdr->pkg_version_major_minor); printf("\tmaker:0x%04x hwid:0x%04x fw_ver:0x%04x tbl_ver:0x%04x\n", hdr->vendor_id, hdr->battery_type, hdr->fw_version, hdr->data_table_version); printf("\tbinary offset:0x%08x size:0x%08x chk_sum:0x%02x\n", hdr->fw_binary_offset, hdr->fw_binary_size, hdr->checksum); } static void print_info(struct sb_fw_update_info *info) { printf("\nCurrent Battery Firmware:\n"); printf("\tmaker:0x%04x hwid:0x%04x fw_ver:0x%04x tbl_ver:0x%04x\n", info->maker_id, info->hardware_id, info->fw_version, info->data_version); return; } static void print_status(struct sb_fw_update_status *sts) { printf("f_maker_id:%d f_hw_id:%d f_fw_ver:%d f_permnent:%d\n", sts->v_fail_maker_id, sts->v_fail_hw_id, sts->v_fail_fw_version, sts->v_fail_permanent); printf("permanent failure:%d abnormal:%d fw_update:%d\n", sts->permanent_failure, sts->abnormal_condition, sts->fw_update_supported); printf("fw_update_mode:%d fw_corrupted:%d cmd_reject:%d\n", sts->fw_update_mode, sts->fw_corrupted, sts->cmd_reject); printf("invliad data:%d fw_fatal_err:%d fec_err:%d busy:%d\n", sts->invalid_data, sts->fw_fatal_error, sts->fec_error, sts->busy); printf("\n"); return; } /* @return 1 (True) if img signature is valid */ static int check_battery_firmware_image_signature( struct sb_fw_header *hdr) { return (hdr->signature[0] == 'B') && (hdr->signature[1] == 'T') && (hdr->signature[2] == 'F') && (hdr->signature[3] == 'W'); } /* @return 1 (True) if img checksum is valid. */ static int check_battery_firmware_image_checksum( struct sb_fw_header *hdr) { int i; uint8_t sum = 0; uint8_t *img = (uint8_t *)hdr; img += hdr->fw_binary_offset; for (i = 0; i < hdr->fw_binary_size; i++) sum += img[i]; sum += hdr->checksum; return sum == 0; } /* @return 1 (True) if img versions are ok to update. */ static int check_battery_firmware_image_version( struct sb_fw_header *hdr, struct sb_fw_update_info *p) { /* * If the battery firmware has a newer fw version * or a newer data table version, then it is ok to update. */ return (hdr->fw_version > p->fw_version) || (hdr->data_table_version > p->data_version); } static int check_battery_firmware_ids( struct sb_fw_header *hdr, struct sb_fw_update_info *p) { return ((hdr->vendor_id == p->maker_id) && (hdr->battery_type == p->hardware_id)); } /* check_if_need_update_fw * @return 1 (true) if need; 0 (false) if not. */ static int check_if_valid_fw( struct sb_fw_header *hdr, struct sb_fw_update_info *info) { return check_battery_firmware_image_signature(hdr) && check_battery_firmware_ids(hdr, info) && check_battery_firmware_image_checksum(hdr); } /* check_if_need_update_fw * @return 1 (true) if need; 0 (false) if not. */ static int check_if_need_update_fw( struct sb_fw_header *hdr, struct sb_fw_update_info *info) { return check_battery_firmware_image_version(hdr, info); } static void log_msg(struct fw_update_ctrl *fw_update, enum fw_update_state state, const char *msg) { sprintf(fw_update->msg, "Battery Firmware Updater State:%d %s", state, msg); } static char *read_fw_image(struct fw_update_ctrl *fw_update) { int size; char *buf; fw_update->size = 0; fw_update->ptr = NULL; fw_update->fw_img_hdr = (struct sb_fw_header *)NULL; /* Read the input file */ buf = read_file(fw_update->image_name, &size); if (!buf) return NULL; fw_update->size = size; fw_update->ptr = buf; fw_update->fw_img_hdr = (struct sb_fw_header *)buf; print_battery_firmware_image_hdr(fw_update->fw_img_hdr); if (fw_update->fw_img_hdr->fw_binary_offset >= fw_update->size || fw_update->size < 256) { printf("Load Firmware Image[%s] Error offset:%d size:%d\n", fw_update->image_name, fw_update->fw_img_hdr->fw_binary_offset, fw_update->size); free(buf); return NULL; } return buf; } static int get_status(struct sb_fw_update_status *status) { int rv = EC_RES_SUCCESS; int cnt = 0; struct ec_params_sb_fw_update *param = (struct ec_params_sb_fw_update *)ec_outbuf; struct ec_response_sb_fw_update *resp = (struct ec_response_sb_fw_update *)ec_inbuf; param->hdr.subcmd = EC_SB_FW_UPDATE_STATUS; do { usleep(SB_FW_UPDATE_DEFAULT_DELAY); rv = ec_command(EC_CMD_SB_FW_UPDATE, 0, param, sizeof(struct ec_sb_fw_update_header), resp, SB_FW_UPDATE_CMD_STATUS_SIZE); } while ((rv < 0) && (cnt++ < SB_FW_UPDATE_DEFAULT_RETRY_CNT)); if (rv < 0) { memset(status, 0, SB_FW_UPDATE_CMD_STATUS_SIZE); return -EC_RES_ERROR; } memcpy(status, resp->status.data, SB_FW_UPDATE_CMD_STATUS_SIZE); return EC_RES_SUCCESS; } static int get_info(struct sb_fw_update_info *info) { int rv = EC_RES_SUCCESS; int cnt = 0; struct ec_params_sb_fw_update *param = (struct ec_params_sb_fw_update *)ec_outbuf; struct ec_response_sb_fw_update *resp = (struct ec_response_sb_fw_update *)ec_inbuf; param->hdr.subcmd = EC_SB_FW_UPDATE_INFO; do { usleep(SB_FW_UPDATE_DEFAULT_DELAY); rv = ec_command(EC_CMD_SB_FW_UPDATE, 0, param, sizeof(struct ec_sb_fw_update_header), resp, SB_FW_UPDATE_CMD_INFO_SIZE); } while ((rv < 0) && (cnt++ < SB_FW_UPDATE_DEFAULT_RETRY_CNT)); if (rv < 0) { memset(info, 0, SB_FW_UPDATE_CMD_INFO_SIZE); return -EC_RES_ERROR; } memcpy(info, resp->info.data, SB_FW_UPDATE_CMD_INFO_SIZE); return EC_RES_SUCCESS; } static int send_subcmd(int subcmd) { int rv = EC_RES_SUCCESS; struct ec_params_sb_fw_update *param = (struct ec_params_sb_fw_update *)ec_outbuf; param->hdr.subcmd = subcmd; rv = ec_command(EC_CMD_SB_FW_UPDATE, 0, param, sizeof(struct ec_sb_fw_update_header), NULL, 0); if (rv < 0) { printf("Firmware Update subcmd:%d Error\n", subcmd); return -EC_RES_ERROR; } return EC_RES_SUCCESS; } static int write_block(struct fw_update_ctrl *fw_update, int offset, int bsize) { int rv; struct ec_params_sb_fw_update *param = (struct ec_params_sb_fw_update *)ec_outbuf; memcpy(param->write.data, fw_update->ptr+offset, bsize); param->hdr.subcmd = EC_SB_FW_UPDATE_WRITE; rv = ec_command(EC_CMD_SB_FW_UPDATE, 0, param, sizeof(struct ec_params_sb_fw_update), NULL, 0); if (rv < 0) { printf("Firmware Update Write Error ptr:%p offset@%x\n", fw_update->ptr, offset); return -EC_RES_ERROR; } return EC_RES_SUCCESS; } static void dump_data(uint8_t *data, int offset, int size) { int i = 0; printf("Offset:0x%X\n", offset); for (i = 0; i < size; i++) { if ((i%16) == 0) printf("\n"); printf("%02X ", data[i]); } printf("\n"); } static enum fw_update_state s0_read_status(struct fw_update_ctrl *fw_update) { if (fw_update->busy_retry_cnt == 0) { fw_update->rv = -1; log_msg(fw_update, S0_READ_STATUS, "Busy"); return S10_TERMINAL; } fw_update->busy_retry_cnt--; fw_update->rv = get_status(&fw_update->status); if (fw_update->rv) { fw_update->rv = -1; log_msg(fw_update, S0_READ_STATUS, "Interface Error"); return S10_TERMINAL; } if (!((fw_update->status.abnormal_condition == 0) && (fw_update->status.fw_update_supported == 1))) { return S0_READ_STATUS; } if (fw_update->status.busy) { usleep(DELAY_US_BUSY); return S0_READ_STATUS; } else return S1_READ_INFO; } static enum fw_update_state s1_read_battery_info( struct fw_update_ctrl *fw_update) { int rv; if (fw_update->err_retry_cnt == 0) { fw_update->rv = -1; log_msg(fw_update, S1_READ_INFO, "Retry Error"); return S10_TERMINAL; } fw_update->err_retry_cnt--; rv = get_info(&fw_update->info); if (rv) { fw_update->rv = -1; log_msg(fw_update, S1_READ_INFO, "Interface Error"); return S10_TERMINAL; } print_info(&fw_update->info); sprintf(fw_update->image_name, "/lib/firmware/battery/maker.%04x.hwid.%04x.bin", fw_update->info.maker_id, fw_update->info.hardware_id); if (NULL == read_fw_image(fw_update)) { fw_update->rv = 0; log_msg(fw_update, S1_READ_INFO, "Open Image File"); return S10_TERMINAL; } rv = get_status(&fw_update->status); if (rv) { fw_update->rv = -1; log_msg(fw_update, S1_READ_INFO, "Interface Error"); return S10_TERMINAL; } rv = check_if_valid_fw(fw_update->fw_img_hdr, &fw_update->info); if (rv == 0) { fw_update->rv = -EC_RES_INVALID_PARAM; log_msg(fw_update, S1_READ_INFO, "Invalid Firmware"); return S10_TERMINAL; } rv = check_if_need_update_fw(fw_update->fw_img_hdr, &fw_update->info); if (rv == 0 && (fw_update->flags & F_VERSION_CHECK)) { fw_update->rv = 0; log_msg(fw_update, S1_READ_INFO, "Latest Firmware"); return S10_TERMINAL; } fw_update->flags |= F_NEED_UPDATE; if (!(fw_update->flags & F_UPDATE)) { fw_update->rv = 0; return S10_TERMINAL; } if (!(fw_update->flags & F_AC_PRESENT)) { fw_update->rv = 0; log_msg(fw_update, S1_READ_INFO, "Require AC Adapter Counnected."); return S10_TERMINAL; } if ((fw_update->flags & F_BATT_DISCHARGE) && (fw_update->flags & F_AC_PRESENT)) { /* * If battery discharge due to battery learning mode, * we can't update battery FW, because device will shutdown * during FW update. */ fw_update->rv = 0; log_msg(fw_update, S1_READ_INFO, "battery can't update in learning mode"); return S10_TERMINAL; } return S2_WRITE_PREPARE; } static enum fw_update_state s2_write_prepare(struct fw_update_ctrl *fw_update) { int rv; rv = disable_power_management(); if (0 == rv) fw_update->flags |= F_POWERD_DISABLED; rv = send_subcmd(EC_SB_FW_UPDATE_PREPARE); if (rv) { fw_update->rv = -1; log_msg(fw_update, S2_WRITE_PREPARE, "Interface Error"); return S10_TERMINAL; } return S3_READ_STATUS; } static enum fw_update_state s3_read_status(struct fw_update_ctrl *fw_update) { int rv; rv = get_status(&fw_update->status); if (rv) { fw_update->rv = -1; log_msg(fw_update, S3_READ_STATUS, "Interface Error"); return S10_TERMINAL; } return S4_WRITE_UPDATE; } static enum fw_update_state s4_write_update(struct fw_update_ctrl *fw_update) { int rv; rv = send_subcmd(EC_SB_FW_UPDATE_BEGIN); if (rv) { fw_update->rv = -1; log_msg(fw_update, S4_WRITE_UPDATE, "Interface Error"); return S10_TERMINAL; } usleep(DELAY_US_BEGIN); return S5_READ_STATUS; } static enum fw_update_state s5_read_status(struct fw_update_ctrl *fw_update) { int rv = get_status(&fw_update->status); if (rv) { fw_update->rv = -1; log_msg(fw_update, S5_READ_STATUS, "Interface Error"); return S10_TERMINAL; } if (fw_update->status.fw_update_mode == 0) return S2_WRITE_PREPARE; /* Init Write Block Loop Controls */ fw_update->ptr += fw_update->fw_img_hdr->fw_binary_offset; fw_update->size -= fw_update->fw_img_hdr->fw_binary_offset; fw_update->offset = 0; return S6_WRITE_BLOCK; } static enum fw_update_state s6_write_block(struct fw_update_ctrl *fw_update) { int rv; int bsize; int offset = fw_update->offset; if (offset >= fw_update->size) return S8_WRITE_END; bsize = fw_update->step_size; if ((offset & 0xFFFF) == 0x0) printf("\n%X\n", offset); if (fw_update->fec_err_retry_cnt == 0) { fw_update->rv = -1; log_msg(fw_update, S6_WRITE_BLOCK, "FEC Retry Error"); return S10_TERMINAL; } fw_update->fec_err_retry_cnt--; rv = write_block(fw_update, offset, bsize); if (rv) { fw_update->rv = -1; log_msg(fw_update, S6_WRITE_BLOCK, "Interface Error"); return S10_TERMINAL; } /* * Add more detays after the last a few block (3) writes. * 3 is chosen based on current test results. */ if ((offset + 3*fw_update->step_size) >= fw_update->size) usleep(DELAY_US_WRITE_END); usleep(get_delay_value(offset, fw_update->step_size)); return S7_READ_STATUS; } static enum fw_update_state s7_read_status(struct fw_update_ctrl *fw_update) { int rv; int offset = fw_update->offset; int bsize; int cnt = 0; bsize = fw_update->step_size; do { usleep(SB_FW_UPDATE_DEFAULT_DELAY); rv = get_status(&fw_update->status); if (rv) { dump_data(fw_update->ptr+offset, offset, bsize); print_status(&fw_update->status); fw_update->rv = -1; log_msg(fw_update, S7_READ_STATUS, "Interface Error"); return S10_TERMINAL; } } while (fw_update->status.busy && (cnt++ < SB_FW_UPDATE_DEFAULT_RETRY_CNT)); if (fw_update->status.fec_error) { dump_data(fw_update->ptr+offset, offset, bsize); print_status(&fw_update->status); fw_update->rv = 0; return S6_WRITE_BLOCK; } if (fw_update->status.permanent_failure || fw_update->status.v_fail_permanent) { dump_data(fw_update->ptr+offset, offset, bsize); print_status(&fw_update->status); fw_update->rv = -1; log_msg(fw_update, S7_READ_STATUS, "Battery Permanent Error"); return S8_WRITE_END; } if (fw_update->status.v_fail_maker_id || fw_update->status.v_fail_hw_id || fw_update->status.v_fail_fw_version || fw_update->status.fw_corrupted || fw_update->status.cmd_reject || fw_update->status.invalid_data || fw_update->status.fw_fatal_error) { dump_data(fw_update->ptr+offset, offset, bsize); print_status(&fw_update->status); fw_update->rv = 0; return S1_READ_INFO; } fw_update->fec_err_retry_cnt = SB_FW_UPDATE_FEC_ERROR_RETRY_CNT; fw_update->offset += fw_update->step_size; return S6_WRITE_BLOCK; } static enum fw_update_state s8_write_end(struct fw_update_ctrl *fw_update) { int rv; rv = send_subcmd(EC_SB_FW_UPDATE_END); if (rv && (0 == fw_update->rv)) { fw_update->rv = -1; log_msg(fw_update, S8_WRITE_END, "Interface Error"); } if (fw_update->rv) return S10_TERMINAL; usleep(DELAY_US_END); fw_update->busy_retry_cnt = SB_FW_UPDATE_BUSY_ERROR_RETRY_CNT; return S9_READ_STATUS; } static enum fw_update_state s9_read_status(struct fw_update_ctrl *fw_update) { int rv; if (fw_update->busy_retry_cnt == 0) { fw_update->rv = -1; log_msg(fw_update, S9_READ_STATUS, "Busy"); return S10_TERMINAL; } rv = get_status(&fw_update->status); if (rv) { fw_update->rv = -1; log_msg(fw_update, S9_READ_STATUS, "Interface Error"); return S10_TERMINAL; } if ((fw_update->status.fw_update_mode == 1) || (fw_update->status.busy == 1)) { usleep(SB_FW_UPDATE_DEFAULT_DELAY); fw_update->busy_retry_cnt--; return S9_READ_STATUS; } log_msg(fw_update, S9_READ_STATUS, "Complete"); fw_update->flags &= ~F_NEED_UPDATE; return S10_TERMINAL; } typedef enum fw_update_state (*fw_state_func)(struct fw_update_ctrl *fw_update); fw_state_func state_table[] = { s0_read_status, s1_read_battery_info, s2_write_prepare, s3_read_status, s4_write_update, s5_read_status, s6_write_block, s7_read_status, s8_write_end, s9_read_status }; /** * Update Smart Battery Firmware * * @param fw_update struct fw_update_ctrl * * @return 0 if success, negative if error. */ static int ec_sb_firmware_update(struct fw_update_ctrl *fw_update) { enum fw_update_state state; fw_update->err_retry_cnt = SB_FW_UPDATE_ERROR_RETRY_CNT; fw_update->fec_err_retry_cnt = SB_FW_UPDATE_FEC_ERROR_RETRY_CNT; fw_update->busy_retry_cnt = SB_FW_UPDATE_BUSY_ERROR_RETRY_CNT; fw_update->step_size = SB_FW_UPDATE_CMD_WRITE_BLOCK_SIZE; state = S0_READ_STATUS; while (state != S10_TERMINAL) state = state_table[state](fw_update); if (fw_update->fw_img_hdr) free(fw_update->fw_img_hdr); return fw_update->rv; } #define GEC_LOCK_TIMEOUT_SECS 30 /* 30 secs */ void usage(char *argv[]) { printf("Usage: %s [check|update]\n" " check: check if AC Adaptor is connected.\n" " update: trigger battery firmware update.\n", argv[0]); } int main(int argc, char *argv[]) { int rv = 0, interfaces = COMM_ALL; int op = OP_UNKNOWN; uint8_t val = 0; if (argc != 2) { usage(argv); return -1; } if (!strcmp(argv[1], "check")) op = OP_CHECK; else if (!strcmp(argv[1], "update")) op = OP_UPDATE; else { op = OP_UNKNOWN; usage(argv); return -1; } if (acquire_gec_lock(GEC_LOCK_TIMEOUT_SECS) < 0) { printf("Could not acquire GEC lock.\n"); return -1; } if (comm_init(interfaces, NULL)) { printf("Couldn't find EC\n"); goto out; } fw_update.flags = 0; rv = ec_readmem(EC_MEMMAP_BATT_FLAG, sizeof(val), &val); if (rv <= 0) { printf("EC Memmap read error:%d\n", rv); goto out; } rv = get_status(&fw_update.status); if (rv) { fw_update.rv = -1; log_msg(&fw_update, S1_READ_INFO, "Interface Error"); return S10_TERMINAL; } if (fw_update.status.fw_update_mode) { /* * Previous battery firmware update was interrupted, * and we may not detect the presence of AC correctly. * Therefore, lie about our AC status. */ fw_update.flags |= F_AC_PRESENT; printf("Assuming AC_PRESENT due to interrupted FW update\n"); } else { if (val & EC_BATT_FLAG_AC_PRESENT) { fw_update.flags |= F_AC_PRESENT; printf("AC_PRESENT\n"); } } if (val & EC_BATT_FLAG_DISCHARGING) { fw_update.flags |= F_BATT_DISCHARGE; printf("Battery is in discharge state\n"); } rv = ec_readmem(EC_MEMMAP_BATT_LFCC, sizeof(val), &val); if (rv <= 0) { printf("EC Memmap read error:%d\n", rv); goto out; } if (val == 0) fw_update.flags |= F_LFCC_ZERO; if (op == OP_UPDATE) fw_update.flags |= F_UPDATE; fw_update.flags |= F_VERSION_CHECK; rv = ec_sb_firmware_update(&fw_update); printf("Battery Firmware Update:0x%02x %s\n%s\n", fw_update.flags, ((rv) ? "FAIL " : " "), fw_update.msg); /* Update battery firmware update interface to be protected */ if (!(fw_update.flags & F_NEED_UPDATE)) rv |= send_subcmd(EC_SB_FW_UPDATE_PROTECT); if (fw_update.flags & F_POWERD_DISABLED) rv |= restore_power_management(); out: release_gec_lock(); if (rv) return -1; else return fw_update.flags & (F_LFCC_ZERO | F_NEED_UPDATE); }