Michał Żygowski has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/55715 )
Change subject: tuxedo_ec: Implement support for flashing ECs on TUXEDO laptops ......................................................................
tuxedo_ec: Implement support for flashing ECs on TUXEDO laptops
Add a new programmer supporting TUXEDO Embedded Controllers. Tested on TUXEDO InfinityBook S 14 Gen6 and 15 Gen6 with EC firmware updates from IBV.
Signed-off-by: Michał Żygowski michal.zygowski@3mdeb.com Signed-off-by: Sergii Dmytruk sergii.dmytruk@3mdeb.com Change-Id: I0e42260155ffea38a6f60790871cd8da7b657031 --- M Makefile M flashrom.8.tmpl M flashrom.c M programmer.h M programmer_table.c A tuxedo_ec.c A tuxedo_ec.h 7 files changed, 1,058 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/15/55715/1
diff --git a/Makefile b/Makefile index be6e551..1427452 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,11 @@ else override CONFIG_MEC1308 = no endif +ifeq ($(CONFIG_TUXEDO_EC), yes) +UNSUPPORTED_FEATURES += CONFIG_TUXEDO_EC=yes +else +override CONFIG_TUXEDO_EC = no +endif ifeq ($(CONFIG_USBBLASTER_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_USBBLASTER_SPI=yes else @@ -291,6 +296,11 @@ else override CONFIG_MEC1308 = no endif +ifeq ($(CONFIG_TUXEDO_EC), yes) +UNSUPPORTED_FEATURES += CONFIG_TUXEDO_EC=yes +else +override CONFIG_TUXEDO_EC = no +endif ifeq ($(CONFIG_NICREALTEK), yes) UNSUPPORTED_FEATURES += CONFIG_NICREALTEK=yes else @@ -396,6 +406,11 @@ else override CONFIG_MEC1308 = no endif +ifeq ($(CONFIG_TUXEDO_EC), yes) +UNSUPPORTED_FEATURES += CONFIG_TUXEDO_EC=yes +else +override CONFIG_TUXEDO_EC = no +endif ifeq ($(CONFIG_USBBLASTER_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_USBBLASTER_SPI=yes else @@ -532,6 +547,11 @@ else override CONFIG_MEC1308 = no endif +ifeq ($(CONFIG_TUXEDO_EC), yes) +UNSUPPORTED_FEATURES += CONFIG_TUXEDO_EC=yes +else +override CONFIG_TUXEDO_EC = no +endif endif
# Disable all drivers needing raw access (memory, PCI, port I/O) on @@ -702,6 +722,9 @@ # Microchip MEC1308 Embedded Controller CONFIG_MEC1308 ?= yes
+# Embedded Controller of TUXEDO laptops. +CONFIG_TUXEDO_EC ?= yes + # Always enable Altera USB-Blaster dongles for now. CONFIG_USBBLASTER_SPI ?= yes
@@ -813,6 +836,7 @@ override CONFIG_IT8212 = no override CONFIG_DRKAISER = no override CONFIG_MEC1308 = no +override CONFIG_TUXEDO_EC = no override CONFIG_NICREALTEK = no override CONFIG_NICNATSEMI = no override CONFIG_NICINTEL = no @@ -874,6 +898,16 @@
FEATURE_CFLAGS += -D'CONFIG_DEFAULT_PROGRAMMER_ARGS="$(CONFIG_DEFAULT_PROGRAMMER_ARGS)"'
+ +ifeq ($(CONFIG_TUXEDO_EC), yes) +# Disable internal DMI decoder to be able to locate DMI tables on EFI systems and match TUXEDO laptops +override CONFIG_INTERNAL_DMI = no +FEATURE_CFLAGS += -D'CONFIG_TUXEDO_EC=1' -D'CONFIG_INTERNAL_DMI=0' +PROGRAMMER_OBJS += acpi_ec.o tuxedo_ec.o +NEED_RAW_ACCESS += CONFIG_TUXEDO_EC +NEED_LIBPCI += CONFIG_TUXEDO_EC +endif + ifeq ($(CONFIG_INTERNAL), yes) FEATURE_CFLAGS += -D'CONFIG_INTERNAL=1' PROGRAMMER_OBJS += processor_enable.o chipset_enable.o board_enable.o cbtable.o internal.o diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index 4ae7e71..e086360 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -385,6 +385,8 @@ .sp .BR "* stlinkv3_spi" " (for SPI flash ROMs attached to STMicroelectronics STLINK V3 devices)" .sp +.BR "* tuxedo_ec" " (for Embedded Controller of TUXEDO laptops)" +.sp Some programmers have optional or mandatory parameters which are described in detail in the .B PROGRAMMER-SPECIFIC INFORMATION @@ -645,7 +647,6 @@ .B " flashrom -p internal:laptop=this_is_not_a_laptop" .sp to tell flashrom (at your own risk) that it is not running on a laptop. -.SS .BR "dummy " programmer .IP The dummy programmer operates on a buffer in memory only. It provides a safe and fast way to test various @@ -1356,6 +1357,63 @@ If the passed frequency is not supported by the adapter the nearest lower supported frequency will be used. .SS +.BR "tuxedo_ec " programmer +.IP +This module supports EC flash programming on TUXEDO laptops. +.IP +An optional +.B portpair +parameter specifies ports to use to read/write EC ROM data. +Syntax is +.sp +.B " flashrom -p tuxedo_ec:portpair=pairnum" +.sp +where +.B pairnum +can be (data port/control port): +.BR 0 " (0x60/0x64), " 1 " (0x62/0x66), " 2 " (0x68/0x6C) or " 3 " (0x6A/0x6E)." +. The default is 1. +.sp +An optional +.B noaccheck +parameter allows operations with unplugged AC adapter, which is an error +otherwise. +Syntax is +.sp +.B " flashrom -p tuxedo_ec:noaccheck=yes" +.sp +An optional +.B romsize +parameter specifies size of EC ROM. Syntax is +.sp +.B " flashrom -p tuxedo_ec:romsize=size" +.sp +where +.B size +can be: +.BR 64K ", " 128K ", " 192K " or " 256K "." +The default value is queried from EC. +.sp +An optional +.B autload +parameter specifies how to treat autoloading. +Syntax is +.sp +.B " flashrom -p tuxedo_ec:autoload=action" +.sp +where +.B action +can be: +.BR none ", " disable ", " on " or " enable "." +. The default is none. +.sp +An optional +.B ite5570 +parameter enables support of ITE5570. Syntax is +.sp +.B " flashrom -p tuxedo_ec:ite5570=yes" +.sp +.SS
.SH EXAMPLES To back up and update your BIOS, run diff --git a/flashrom.c b/flashrom.c index a1f4710..af97a7c 100644 --- a/flashrom.c +++ b/flashrom.c @@ -39,6 +39,9 @@ #include "programmer.h" #include "hwaccess.h" #include "chipdrivers.h" +#if CONFIG_TUXEDO_EC == 1 +#include "tuxedo_ec.h" +#endif
const char flashrom_version[] = FLASHROM_VERSION; const char *chip_to_probe = NULL; @@ -2103,6 +2106,27 @@ msg_cinfo("done.\n"); }
+#if CONFIG_TUXEDO_EC == 1 + if (programmer == &programmer_tuxedo_ec) { + int status; + status = tuxedo_ec_verify_file_project(newcontents, curcontents, + flash_size); + if (status == 1) { + ret = 1; + goto _free_ret; + } + if (status == 2) { + if (flashctx->flags.force) { + msg_pinfo("\nProceeding anyway because user forced us to.\n"); + } else { + msg_pinfo(" Skipping...\n"); + ret = 0; + goto _free_ret; + } + } + } +#endif + if (write_by_layout(flashctx, curcontents, newcontents)) { msg_cerr("Uh oh. Erase/write failed. "); ret = 2; diff --git a/programmer.h b/programmer.h index 9d1a9ad..e6e52d0 100644 --- a/programmer.h +++ b/programmer.h @@ -97,6 +97,7 @@ extern const struct programmer_entry programmer_serprog; extern const struct programmer_entry programmer_lspcon_i2c_spi; extern const struct programmer_entry programmer_realtek_mst_i2c_spi; +extern const struct programmer_entry programmer_tuxedo_ec;
int programmer_init(const struct programmer_entry *prog, const char *param); int programmer_shutdown(void); diff --git a/programmer_table.c b/programmer_table.c index e7f2c98..d2167eb 100644 --- a/programmer_table.c +++ b/programmer_table.c @@ -171,6 +171,9 @@ #if CONFIG_STLINKV3_SPI == 1 &programmer_stlinkv3_spi, #endif +#if CONFIG_TUXEDO_EC == 1 + &programmer_tuxedo_ec, +#endif };
const size_t programmer_table_size = ARRAY_SIZE(programmer_table); \ No newline at end of file diff --git a/tuxedo_ec.c b/tuxedo_ec.c new file mode 100644 index 0000000..7f4cefc --- /dev/null +++ b/tuxedo_ec.c @@ -0,0 +1,912 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2021, TUXEDO Computers GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Contains programmer implementation for EC used by TUXEDO laptops. + */ + +#if defined(__i386__) || defined(__x86_64__) + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include "chipdrivers.h" +#include "acpi_ec.h" +#include "flashchips.h" +#include "hwaccess.h" +#include "programmer.h" +#include "tuxedo_ec.h" + +#define EC_CMD_ERASE_ALL 0x01 +#define EC_CMD_WRITE_BLOCK 0x02 +#define EC_CMD_READ_BLOCK 0x03 +#define EC_CMD_GET_FLASH_ID 0x04 +#define EC_CMD_ERASE_KBYTE 0x05 +#define EC_CMD_WRITE_KBYTE 0x06 +#define EC_CMD_READ_PRJ 0x92 +#define EC_CMD_READ_VER 0x93 + +#define BYTES_PER_BLOCK (64 * 1024) +#define BYTES_PER_CHUNK 256 +#define KBYTES_PER_BLOCK 64 +#define CHUNKS_PER_KBYTE 4 +#define CHUNKS_PER_BLOCK 256 + +#define INFO_BUFFER_SIZE 16 + +enum autoloadaction { + AUTOLOAD_NO_ACTION, + AUTOLOAD_DISABLE, + AUTOLOAD_SETON, + AUTOLOAD_SETOFF +}; + +/* Need to match a pattern that spans 6 bytes to find patching place */ +enum autoloadseekstate { + AUTOLOAD_NONE, + AUTOLOAD_ONE_BYTE, + AUTOLOAD_TWO_BYTES, + AUTOLOAD_THREE_BYTES, + AUTOLOAD_FOUR_BYTES, + AUTOLOAD_FIVE_BYTES, + AUTOLOAD_SIX_BYTES +}; + +typedef struct +{ + unsigned int rom_size_in_blocks; + unsigned int rom_size_in_kbytes; + + unsigned int autoload_offset; /* meaningful for AUTOLOAD_SIX_BYTES */ + enum autoloadseekstate autoload_state; + enum autoloadaction autload_action; + + uint8_t first_kbyte[CHUNKS_PER_KBYTE * BYTES_PER_CHUNK]; + bool support_ite5570; + uint8_t write_mode; + + uint8_t control_port; + uint8_t data_port; + bool ac_adapter_plugged; +} tuxedo_ec_data_t; + +static bool tuxedo_ec_write_cmd(tuxedo_ec_data_t *ctx_data, uint8_t cmd) +{ + return ec_write_cmd(ctx_data->control_port, cmd); +} + +static bool tuxedo_ec_read_byte(tuxedo_ec_data_t *ctx_data, uint8_t *data) +{ + return ec_read_byte(ctx_data->control_port, ctx_data->data_port, data); +} + +static bool tuxedo_ec_write_byte(tuxedo_ec_data_t *ctx_data, uint8_t data) +{ + return ec_write_byte(ctx_data->control_port, ctx_data->data_port, data); +} + +static int tuxedo_ec_shutdown(void *data) +{ + tuxedo_ec_data_t *ctx_data = (tuxedo_ec_data_t *)data; + int ret = 0; + + if (!tuxedo_ec_write_cmd(ctx_data, 0xfe)) { + msg_perr("Failed to shutdown tuxedo_ec\n"); + ret = 1; + } + + free(data); + + return ret; +} + +static void tuxedo_ec_read_project(tuxedo_ec_data_t *ctx_data) +{ + uint8_t ec_project[INFO_BUFFER_SIZE]; + uint8_t i; + + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_READ_PRJ)) { + msg_perr("Failed to write cmd...\n"); + return; + } + + for (i = 0; i < INFO_BUFFER_SIZE - 1; i++) { + if (!tuxedo_ec_read_byte(ctx_data, &ec_project[i])) { + msg_perr("Failed to read EC project\n"); + return; + } + if (ec_project[i] == '$') { + ec_project[i] = '\0'; + break; + } + } + + msg_pinfo("Mainboard EC Project: %s\n", (char *)ec_project); +} + +static void tuxedo_ec_read_version(tuxedo_ec_data_t *ctx_data) +{ + uint8_t ec_version[INFO_BUFFER_SIZE]; + uint8_t i; + + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_READ_VER)) { + msg_perr("Failed to write cmd...\n"); + return; + } + + for (i = 0; i < INFO_BUFFER_SIZE - 1; i++) { + if (!tuxedo_ec_read_byte(ctx_data, &ec_version[i])) { + msg_perr("Failed to read EC version\n"); + return; + } + if (ec_version[i] == '$') { + ec_version[i] = '\0'; + break; + } + } + + msg_pinfo("Mainboard EC Version: 1.%s\n", (char *)ec_version); +} + +static bool tuxedo_ec_init_ctx(tuxedo_ec_data_t *ctx_data) +{ + uint8_t reg_value; + + ctx_data->control_port = EC_CONTROL; + ctx_data->data_port = EC_DATA; + + if (!ec_read_reg(0xf9, ®_value)) { + msg_perr("Failed to query flash ROM size.\n"); + return false; + } + + msg_pdbg("%s: ROM size register value %02x\n", __func__, reg_value); + + if (reg_value == 0xff) { + msg_pwarn("Querying EC ROM size returned unexpected result.\n" + "Probably the EC has just been flashed and the EC " + "RAM has been reset.\nYou may need to pass the flash" + " size via the programmer parameters or try again in" + " a while.\nIf this warning persists please email a " + "report to flashrom@flashrom.org\n"); + return false; + } + + switch (reg_value & 0xf0) { + case 0x40: + ctx_data->rom_size_in_blocks = 3; + break; + case 0xf0: + ctx_data->rom_size_in_blocks = 4; + break; + default: + ctx_data->rom_size_in_blocks = 2; + break; + } + + /* flush the EC registers */ + INB(EC_CONTROL); + INB(EC_DATA); + + msg_pdbg("%s: Querying AC adapter state...\n", __func__); + if (!ec_read_reg(0x10, ®_value)) { + msg_perr("Failed to query first byte of state register.\n"); + return false; + } + if (!ec_read_reg(0x10, ®_value)) { + msg_perr("Failed to query AC adapter state.\n"); + return false; + } + + msg_pdbg("%s: AC adapter state %02x\n", __func__, reg_value); + + ctx_data->ac_adapter_plugged = reg_value & 0x01; + + ctx_data->rom_size_in_kbytes = + ctx_data->rom_size_in_blocks * KBYTES_PER_BLOCK; + + tuxedo_ec_read_project(ctx_data); + tuxedo_ec_read_version(ctx_data); + + return true; +} + +static int tuxedo_ec_read(struct flashctx *flash, uint8_t *buf, + unsigned int start, unsigned int len) +{ + unsigned int offset, block, block_start, block_end; + uint8_t rom_byte; + tuxedo_ec_data_t *ctx_data = (tuxedo_ec_data_t *)flash->mst->opaque.data; + + /* This EC can read only a whole block. */ + if (len % BYTES_PER_BLOCK != 0 && len < BYTES_PER_BLOCK) { + msg_perr("Incorrect read length %x\n", len); + return 1; + } + + if (start % BYTES_PER_BLOCK != 0) { + msg_perr("Incorrect read region start: %x\n", len); + return 1; + } + + block_start = start / BYTES_PER_BLOCK; + block_end = (start + len) / BYTES_PER_BLOCK; + + if (block_end > ctx_data->rom_size_in_blocks) { + msg_perr("Requested to read block outside of chip boundaries\n"); + return 1; + } + + for (block = block_start; block < block_end; block++) { + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_READ_BLOCK) || + !tuxedo_ec_write_cmd(ctx_data, block)) { + msg_perr("Failed to select block to read %d\n", block); + return 1; + } + for (offset = 0; offset < BYTES_PER_BLOCK; ++offset) { + + if (!tuxedo_ec_read_byte(ctx_data, &rom_byte)) { + msg_perr("Flash read failed @ 0x%x\n", offset); + return 1; + } + + *buf = rom_byte; + ++buf; + } + } + + return 0; +} + +static bool tuxedo_ec_write_patched(tuxedo_ec_data_t *ctx_data, unsigned int offset, + uint8_t data) +{ + const bool blocks_1_2 = ctx_data->rom_size_in_blocks == 1 || + ctx_data->rom_size_in_blocks == 2; + + + if (ctx_data->autoload_state != AUTOLOAD_SIX_BYTES) + return tuxedo_ec_write_byte(ctx_data, data); + + switch (ctx_data->autload_action) { + case AUTOLOAD_NO_ACTION: + return tuxedo_ec_write_byte(ctx_data, data); + case AUTOLOAD_DISABLE: + if (offset == ctx_data->autoload_offset + 2) { + data = (blocks_1_2 ? 0x94 : 0x85); + } else if (offset == ctx_data->autoload_offset + 8) { + data = 0x00; + } + break; + case AUTOLOAD_SETON: + if (offset == ctx_data->autoload_offset + 2) { + data = (blocks_1_2 ? 0x94 : 0x85); + } else if (offset == ctx_data->autoload_offset + 8) { + data = (blocks_1_2 ? 0x7f : 0xbe); + } + break; + case AUTOLOAD_SETOFF: + if (offset == ctx_data->autoload_offset + 2) { + data = (blocks_1_2 ? 0xa5 : 0xb5); + } else if (offset == ctx_data->autoload_offset + 8) { + data = 0xaa; + } + break; + } + + return tuxedo_ec_write_byte(ctx_data, data); +} + +static bool tuxedo_ec_write_block(tuxedo_ec_data_t *ctx_data, const uint8_t *buf, + unsigned int block) +{ + const unsigned int param = ctx_data->support_ite5570 ? 0x00 : 0x02; + unsigned int offset = block * BYTES_PER_BLOCK; + unsigned int third_param = param; + unsigned int i; + bool skip_first_kbyte = false; + + /* Required: stash first kilobyte and write it after the last block. */ + if (ctx_data->write_mode != 0 && block == 0) { + memcpy(ctx_data->first_kbyte, buf, CHUNKS_PER_KBYTE * BYTES_PER_CHUNK); + third_param = 0x04; + skip_first_kbyte = true; + } + + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_WRITE_BLOCK) || + !tuxedo_ec_write_cmd(ctx_data, param) || + !tuxedo_ec_write_cmd(ctx_data, block) || + !tuxedo_ec_write_cmd(ctx_data, third_param) || + !tuxedo_ec_write_cmd(ctx_data, param)) { + msg_perr("Unable to send block write command.\n"); + return 1; + } + + for (i = 0; i < BYTES_PER_BLOCK; i++, ++offset) { + if (skip_first_kbyte && (i < CHUNKS_PER_KBYTE * BYTES_PER_CHUNK)) + continue; + if (!tuxedo_ec_write_patched(ctx_data, offset, buf[i])){ + msg_perr("Unable to write byte @ 0x%x\n", block * BYTES_PER_BLOCK + i); + return false; + } + } + + /* If we're done, write the first kilobyte separately. */ + if (ctx_data->write_mode != 0 && block == ctx_data->rom_size_in_blocks - 1) { + if (!tuxedo_ec_write_cmd(ctx_data, 0x06)) { + msg_perr("Unable to send kbyte write command.\n"); + return 1; + } + for (i = 0; i < CHUNKS_PER_KBYTE * BYTES_PER_CHUNK; i++) { + if (!tuxedo_ec_write_patched(ctx_data, i, + ctx_data->first_kbyte[i])) { + msg_perr("Unable to write byte @ 0x%04x\n", i); + return false; + } + } + + memset(ctx_data->first_kbyte, 0, CHUNKS_PER_KBYTE * BYTES_PER_CHUNK); + } + + return true; +} + +static void tuxedo_ec_update_autoload_state(tuxedo_ec_data_t *ctx_data, + const uint8_t *buf, + unsigned int start, unsigned int len) +{ + unsigned int i; + unsigned int offset = start; + + if (ctx_data->autload_action == AUTOLOAD_NO_ACTION || + ctx_data->autoload_state == AUTOLOAD_SIX_BYTES) + return; + + for (i = 0; i < len; ++i, ++offset) { + switch (ctx_data->autoload_state) { + case AUTOLOAD_NONE: + if (buf[i] != 0xa5) + break; + ctx_data->autoload_state = AUTOLOAD_ONE_BYTE; + ctx_data->autoload_offset = offset; + continue; + case AUTOLOAD_ONE_BYTE: + if (offset - ctx_data->autoload_offset != 1) + break; + if (buf[i] != 0xa5 && buf[i] != 0xa4) + break; + ctx_data->autoload_state = AUTOLOAD_TWO_BYTES; + continue; + case AUTOLOAD_TWO_BYTES: + if (offset - ctx_data->autoload_offset != 2) + break; + ctx_data->autoload_state = AUTOLOAD_THREE_BYTES; + continue; + case AUTOLOAD_THREE_BYTES: + if (offset - ctx_data->autoload_offset != 3) + break; + ctx_data->autoload_state = AUTOLOAD_FOUR_BYTES; + continue; + case AUTOLOAD_FOUR_BYTES: + if (offset - ctx_data->autoload_offset != 4) + break; + ctx_data->autoload_state = AUTOLOAD_FIVE_BYTES; + continue; + case AUTOLOAD_FIVE_BYTES: + if (offset - ctx_data->autoload_offset != 5) + break; + if (buf[i] != 0x5a) + break; + ctx_data->autoload_state = AUTOLOAD_SIX_BYTES; + continue; + case AUTOLOAD_SIX_BYTES: + /* Done matching. */ + return; + } + + ctx_data->autoload_state = AUTOLOAD_NONE; + } +} + +static int tuxedo_ec_write(struct flashctx *flash, const uint8_t *buf, + unsigned int start, unsigned int len) +{ + tuxedo_ec_data_t *ctx_data = (tuxedo_ec_data_t *)flash->mst->opaque.data; + unsigned int block_start; + unsigned int block_end; + unsigned int block; + + /* This EC can write only a whole block. */ + if (len % BYTES_PER_BLOCK != 0 && len < BYTES_PER_BLOCK) { + msg_perr("Incorrect write length %x\n", len); + return 1; + } + + if (start % BYTES_PER_BLOCK != 0) { + msg_perr("Incorrect write region start: %x\n", len); + return 1; + } + + block_start = start / BYTES_PER_BLOCK; + block_end = (start + len) / BYTES_PER_BLOCK; + + if (block_end > ctx_data->rom_size_in_blocks) { + msg_perr("Requested to write block outside of chip boundaries\n"); + return 1; + } + + tuxedo_ec_update_autoload_state(ctx_data, buf, start, len); + + for (block = block_start; block < block_end; block++) { + if (!tuxedo_ec_write_block(ctx_data, + buf + (BYTES_PER_BLOCK * block), + block)) { + msg_perr("Unable to write full block.\n"); + return 1; + } + } + + return 0; +} + +static int tuxedo_ec_full_erase(tuxedo_ec_data_t *ctx_data) +{ + unsigned int i; + + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_ERASE_ALL) || + !tuxedo_ec_write_cmd(ctx_data, 0x00) || + !tuxedo_ec_write_cmd(ctx_data, 0x00) || + !tuxedo_ec_write_cmd(ctx_data, 0x00) || + !tuxedo_ec_write_cmd(ctx_data, 0x00)) + return 1; + + if (ctx_data->rom_size_in_blocks < 3) { + internal_sleep(15000 * 64); + return 0; + } + + for (i = 0; i < 4; i++) { + if (!ec_wait_for_obuf(ctx_data->control_port, + EC_MAX_STATUS_CHECKS * 3)) + return 1; + + if (INB(ctx_data->data_port) == 0xf8) + return 0; + } + + internal_sleep(100000); + + return 1; +} + +static int tuxedo_ec_chunkwise_erase(tuxedo_ec_data_t *ctx_data, + unsigned int start, unsigned int len) +{ + const unsigned int from_chunk = start / BYTES_PER_CHUNK; + const unsigned int end = start + len; + unsigned int to_chunk = end / BYTES_PER_CHUNK; + unsigned int i; + + if (end % BYTES_PER_CHUNK != 0) + ++to_chunk; + + if (to_chunk / CHUNKS_PER_BLOCK > ctx_data->rom_size_in_blocks) { + msg_perr("Requested to erase block outside of chip boundaries\n"); + return 1; + } + + for (i = from_chunk; i < to_chunk; i += CHUNKS_PER_KBYTE) { + if (!tuxedo_ec_write_cmd(ctx_data, EC_CMD_ERASE_KBYTE) || + !tuxedo_ec_write_cmd(ctx_data, i / CHUNKS_PER_BLOCK) || + !tuxedo_ec_write_cmd(ctx_data, i % CHUNKS_PER_BLOCK) || + !tuxedo_ec_write_cmd(ctx_data, 0x00)) { + msg_perr("Failed to erase chunk %d\n", i); + return 1; + } + + internal_sleep(1000); + } + + internal_sleep(100000); + return 0; +} + +static int tuxedo_ec_erase(struct flashctx *flash, unsigned int blockaddr, + unsigned int blocklen) +{ + tuxedo_ec_data_t *ctx_data = (tuxedo_ec_data_t *)flash->mst->opaque.data; + + /* This EC can erase only a whole block */ + if (blocklen % BYTES_PER_BLOCK != 0 && blocklen < BYTES_PER_BLOCK) { + msg_perr("Incorrect erase legnth %x\n", blocklen); + return 1; + } + + if (ctx_data->support_ite5570) + return tuxedo_ec_chunkwise_erase(ctx_data, blockaddr, blocklen); + + return tuxedo_ec_full_erase(ctx_data); +} + + +static int tuxedo_ec_probe(struct flashctx *flash) +{ + tuxedo_ec_data_t *ctx_data = (tuxedo_ec_data_t *)flash->mst->opaque.data; + + flash->chip->tested = TEST_OK_PREW; + flash->chip->page_size = BYTES_PER_BLOCK; + flash->chip->total_size = ctx_data->rom_size_in_kbytes; + /* This EC supports only write granularity of 64K */ + flash->chip->gran = write_gran_64kbytes; + /* + * Erase operation must be done in one sway. + * So report an eraser for the whole chip size. + */ + flash->chip->block_erasers[0].eraseblocks[0].size = + ctx_data->rom_size_in_blocks * BYTES_PER_BLOCK; + flash->chip->block_erasers[0].eraseblocks[0].count = 1; + + return 1; +} + +static struct opaque_master programmer_opaque_tuxedo_ec = { + .max_data_read = BYTES_PER_BLOCK, + .max_data_write = BYTES_PER_BLOCK, + .probe = tuxedo_ec_probe, + .read = tuxedo_ec_read, + .write = tuxedo_ec_write, + .erase = tuxedo_ec_erase, +}; + +static bool tuxedo_ec_check_params(tuxedo_ec_data_t *ctx_data) +{ + char *p; + bool ret = true; + + msg_pdbg("%s()\n", __func__); + + p = extract_programmer_param("type"); + if (p && strcmp(p, "ec")) { + msg_pdbg("%s(): tuxedo_ec only supports "ec" type devices\n", + __func__); + ret = false; + } + free(p); + + p = extract_programmer_param("noaccheck"); + if (p && strcmp(p, "yes") == 0) { + /* Just mark it as present. */ + ctx_data->ac_adapter_plugged = true; + } + free(p); + + p = extract_programmer_param("ite5570"); + if (p && strcmp(p, "yes") == 0) { + ctx_data->support_ite5570 = true; + } + free(p); + + p = extract_programmer_param("portpair"); + if (p) { + if (!strcmp(p, "0")) { + ctx_data->control_port = 0x64; + ctx_data->data_port = 0x60; + } else if (!strcmp(p, "1")) { + ctx_data->control_port = 0x66; + ctx_data->data_port = 0x62; + } else if (!strcmp(p, "2")) { + ctx_data->control_port = 0x6c; + ctx_data->data_port = 0x68; + } else if (!strcmp(p, "3")) { + ctx_data->control_port = 0x6e; + ctx_data->data_port = 0x6a; + } else { + msg_pdbg("%s(): incorrect portpair param value: %s\n", + __func__, p); + ret = false; + } + } + free(p); + + p = extract_programmer_param("autoload"); + if (p) { + if (!strcmp(p, "none")) { + ctx_data->autload_action = AUTOLOAD_NO_ACTION; + } else if (!strcmp(p, "disable")) { + ctx_data->autload_action = AUTOLOAD_DISABLE; + } else if (!strcmp(p, "on")) { + ctx_data->autload_action = AUTOLOAD_SETON; + } else if (!strcmp(p, "off")) { + ctx_data->autload_action = AUTOLOAD_SETOFF; + } else { + msg_pdbg("%s(): incorrect autoload param value: %s\n", + __func__, p); + ret = false; + } + } + free(p); + + p = extract_programmer_param("romsize"); + if (p) { + if (!strcmp(p, "64K")) { + ctx_data->rom_size_in_blocks = 1; + } else if (!strcmp(p, "128K")) { + ctx_data->rom_size_in_blocks = 2; + } else if (!strcmp(p, "192K")) { + ctx_data->rom_size_in_blocks = 3; + } else if (!strcmp(p, "256K")) { + ctx_data->rom_size_in_blocks = 4; + } else { + msg_pdbg("%s(): incorrect romsize param value: %s\n", + __func__, p); + ret = false; + } + + ctx_data->rom_size_in_kbytes = + ctx_data->rom_size_in_blocks * KBYTES_PER_BLOCK; + } + free(p); + + return ret; +} + +static void get_flash_part_from_id(tuxedo_ec_data_t *ctx_data, uint32_t manuf_id, + uint32_t model_id) +{ + const struct flashchip *chip; + const char *vendor = NULL; + const char *device = NULL; + for (chip = flashchips; chip && chip->name; chip++) { + if (chip->manufacture_id == manuf_id) { + vendor = chip->vendor; + if (chip->model_id == model_id) { + device = chip->name; + break; + } + } + } + + if (vendor && device) + msg_pinfo("Found %s flash chip "%s" (%d kB).\n", + chip->vendor, chip->name, chip->total_size); + else if (vendor) + msg_pinfo("Found unknown %s flash chip\n", vendor); +} + +static void tuxedo_ec_read_flash_id(tuxedo_ec_data_t *ctx_data) +{ + + uint8_t rom_data[4]; + unsigned int id_length, i; + uint32_t model_id; + + tuxedo_ec_write_cmd(ctx_data, EC_CMD_GET_FLASH_ID); + + id_length = 3; + if (ctx_data->rom_size_in_blocks == 3 || + ctx_data->rom_size_in_blocks == 4) + id_length = 4; + + for (i = 0; i < id_length; i++) + tuxedo_ec_read_byte (ctx_data, &rom_data[i]); + + msg_pinfo("Flash Part ID: "); + for (i = 0; i < id_length; i++) + msg_pinfo("%02x ", rom_data[i]); + + msg_pinfo("\n"); + + model_id = rom_data[1] | ((uint32_t)rom_data[2] << 8); + if (id_length == 4) + model_id |= ((uint32_t)rom_data[3] << 16); + + get_flash_part_from_id(ctx_data, rom_data[0], model_id); +} + +static int tuxedo_ec_init(void) +{ + bool read_success; + tuxedo_ec_data_t *ctx_data = NULL; + + if (rget_io_perms()) + return 1; + + dmi_init(); + + if (!dmi_match("^TUXEDO$")) { + msg_perr("Not a TUXEDO device\n"); + return 1; + } + + if (!dmi_match("^InfinityBook S 14 Gen6$") && + !dmi_match("^InfinityBook S 15 Gen6$")) { + msg_perr("TUXEDO EC programmer not yet supported on this laptop\n"); + return 1; + } + + if (!ec_write_reg(0xf9, 0x20) || + !ec_write_reg(0xfa, 0x02) || + !ec_write_reg(0xfb, 0x00) || + !ec_write_reg(0xf8, 0xb1)) { + msg_perr("Unable to initialize controller.\n"); + return 1; + } + + ctx_data = calloc(1, sizeof(tuxedo_ec_data_t)); + if (!ctx_data) { + msg_perr("Unable to allocate space for extra context data.\n"); + return 1; + } + + if (!tuxedo_ec_init_ctx(ctx_data)) + goto tuxedo_ec_init_exit; + + if (!tuxedo_ec_check_params(ctx_data)) + goto tuxedo_ec_init_exit; + + if (!tuxedo_ec_write_cmd(ctx_data, 0xde) || + !tuxedo_ec_write_cmd(ctx_data, 0xdc)) { + msg_perr("%s(): failed to prepare controller\n", __func__); + goto tuxedo_ec_init_exit; + } + + if (!tuxedo_ec_write_cmd(ctx_data, 0xf0)) { + msg_perr("Failed to write identification commands.\n"); + goto tuxedo_ec_init_exit_shutdown; + } + + read_success = tuxedo_ec_read_byte(ctx_data, &ctx_data->write_mode); + msg_pdbg("%s(): write mode %02x\n", __func__, ctx_data->write_mode); + if (read_success && ctx_data->write_mode != 0x00 && + ctx_data->write_mode != 0xff) { + msg_pdbg("%s(): selecting ITE5570 support\n", __func__); + ctx_data->support_ite5570 = true; + } else { + ctx_data->write_mode = 0; + } + + tuxedo_ec_read_flash_id(ctx_data); + + if (!ctx_data->ac_adapter_plugged) { + msg_perr("AC adapter is not plugged.\n"); + goto tuxedo_ec_init_exit_shutdown; + } + + programmer_opaque_tuxedo_ec.data = ctx_data; + + if (register_shutdown(tuxedo_ec_shutdown, ctx_data)) + goto tuxedo_ec_init_exit_shutdown; + if (register_opaque_master(&programmer_opaque_tuxedo_ec, ctx_data)) + return 1; + + msg_pdbg("%s(): successfully initialized tuxedo_ec\n", __func__); + return 0; + +tuxedo_ec_init_exit: + tuxedo_ec_shutdown(ctx_data); + return 1; + +tuxedo_ec_init_exit_shutdown: + tuxedo_ec_shutdown(ctx_data); + return 1; +} + +static void copy_version_string(uint8_t *const contents, char *buf) +{ + size_t i; + + for (i = 0; i < INFO_BUFFER_SIZE - 1; i++) { + buf[i] = contents[i]; + if (buf[i] == '$') { + buf[i] = '\0'; + break; + } + } +} + +/** + * @brief Checks the current and file's EC project and version. + * + * Retrieves the EC project and version from the file and current flash + * contents. Compares both values and returns the action to take by flashrom. + * If any of the EC project or EC version is nto found, return one (failure). + * Ifthe EC project from the flas hdump and the firmware file does not match + * return one (failure). If the current and the file's EC version is identical, + * return two (skip flashing). Otherwise return zero to tell flashrom to + * proceed with write. + * + * @param newcontents The contents of the file to be flashed. + * @param curcontents Contects dumped from the EC flash. + * @return 0 on success, 1 on failure, 2 when flashing shall be skipped + */ +int tuxedo_ec_verify_file_project(uint8_t *const newcontents, + uint8_t *const curcontents, + const size_t flash_size) +{ + char current_ec_project[INFO_BUFFER_SIZE]; + char new_ec_project[INFO_BUFFER_SIZE]; + char current_ec_version[INFO_BUFFER_SIZE]; + char new_ec_version[INFO_BUFFER_SIZE]; + size_t new_prj_offset = 0; + size_t cur_prj_offset = 0; + size_t new_ver_offset = 0; + size_t cur_ver_offset = 0; + size_t i; + + for (i = 0; i < flash_size - 4; i++) { + if (!strncmp((char *)&newcontents[i], "PRJ:", 4)) + new_prj_offset = i + 4; + if (!strncmp((char *)&curcontents[i], "PRJ:", 4)) + cur_prj_offset = i + 4; + if (!strncmp((char *)&newcontents[i], "VER:", 4)) + new_ver_offset = i + 4; + if (!strncmp((char *)&curcontents[i], "VER:", 4)) + cur_ver_offset = i + 4; + } + + if (new_prj_offset == 0) { + msg_perr("EC project not found in the file"); + return 1; + } + + if (cur_prj_offset == 0) { + msg_perr("EC project not found in the flash content"); + return 1; + } + + if (new_ver_offset == 0) { + msg_perr("EC version not found in the file"); + return 1; + } + + if (cur_ver_offset == 0) { + msg_perr("EC version not found in the flash content"); + return 1; + } + + copy_version_string(&newcontents[new_prj_offset], new_ec_project); + copy_version_string(&newcontents[new_ver_offset], new_ec_version); + copy_version_string(&curcontents[cur_prj_offset], current_ec_project); + copy_version_string(&curcontents[cur_ver_offset], current_ec_version); + + msg_pdbg("Current EC project: %s, EC version: %s\n", current_ec_project, + current_ec_version); + msg_pdbg("New EC project: %s, EC version: %s\n", new_ec_project, + new_ec_version); + + if (strcmp(current_ec_project, new_ec_project)) { + msg_perr("Wrong EC project. This file can't be used on this machine\n"); + return 1; + } + + if (!strcmp(current_ec_version, new_ec_version)) { + msg_pinfo("The EC firmware version is identical to the requested image."); + return 2; + } + + return 0; +} + +const struct programmer_entry programmer_tuxedo_ec = { + .name = "tuxedo_ec", + .type = OTHER, + .devs.note = "Embedded Controller of TUXEDO laptops.\n", + .init = tuxedo_ec_init, + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, +}; +#endif diff --git a/tuxedo_ec.h b/tuxedo_ec.h new file mode 100644 index 0000000..b64d726 --- /dev/null +++ b/tuxedo_ec.h @@ -0,0 +1,25 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2021, TUXEDO Computers GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#if defined(__i386__) || defined(__x86_64__) + +#include <stdint.h> +#include <stddef.h> + +int tuxedo_ec_verify_file_project(uint8_t *const newcontents, + uint8_t *const curcontents, + const size_t flash_size); + +#endif