Michał Żygowski has uploaded this change for review.

View Change

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, &reg_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, &reg_value)) {
+ msg_perr("Failed to query first byte of state register.\n");
+ return false;
+ }
+ if (!ec_read_reg(0x10, &reg_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

To view, visit change 55715. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I0e42260155ffea38a6f60790871cd8da7b657031
Gerrit-Change-Number: 55715
Gerrit-PatchSet: 1
Gerrit-Owner: Michał Żygowski <michal.zygowski@3mdeb.com>
Gerrit-MessageType: newchange