From 38b357b1310f2e55dced443e0357e210721dc448 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Fri, 22 Mar 2019 12:20:06 +0000 Subject: [PATCH] superio: Add support for writing new e-flash contents --- plugins/superio/README.md | 5 - plugins/superio/fu-plugin-superio.c | 36 +++ plugins/superio/fu-superio-device.c | 24 ++ plugins/superio/fu-superio-it89-device.c | 348 ++++++++++++++++++++++- 4 files changed, 407 insertions(+), 6 deletions(-) diff --git a/plugins/superio/README.md b/plugins/superio/README.md index 3be1ae935..7a3c6f3db 100644 --- a/plugins/superio/README.md +++ b/plugins/superio/README.md @@ -8,11 +8,6 @@ to add a HwId quirk entry to `superio.quirk`. See https://en.wikipedia.org/wiki/Super_I/O for more details about SuperIO and what the EC actually does. -Eventually we could support flashing the EC using this plugin, but not until we -have a way to recover a failed flash. The pragmatic decision is probably to use -the vendor-suplied UEFI capsule binary, as the ITE85* datasheets are seemingly -not available without signing an NDA with ITE. - Other useful links: * https://raw.githubusercontent.com/system76/ecflash/master/ec.py diff --git a/plugins/superio/fu-plugin-superio.c b/plugins/superio/fu-plugin-superio.c index 66e6bb6fd..f69561fb8 100644 --- a/plugins/superio/fu-plugin-superio.c +++ b/plugins/superio/fu-plugin-superio.c @@ -87,6 +87,7 @@ void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); + fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "tw.com.ite.superio"); } gboolean @@ -153,3 +154,38 @@ fu_plugin_verify (FuPlugin *plugin, FuDevice *device, } return TRUE; } + +gboolean +fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) +{ + g_autoptr(FuDeviceLocker) locker = NULL; + if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) + return TRUE; + locker = fu_device_locker_new (device, error); + if (locker == NULL) + return FALSE; + return fu_device_detach (device, error); +} + +gboolean +fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) +{ + g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); + if (locker == NULL) + return FALSE; + return fu_device_attach (device, error); +} + +gboolean +fu_plugin_update (FuPlugin *plugin, + FuDevice *device, + GBytes *blob_fw, + FwupdInstallFlags flags, + GError **error) +{ + g_autoptr(FuDeviceLocker) locker = NULL; + locker = fu_device_locker_new (device, error); + if (locker == NULL) + return FALSE; + return fu_device_write_firmware (device, blob_fw, error); +} diff --git a/plugins/superio/fu-superio-device.c b/plugins/superio/fu-superio-device.c index 57c94938f..a4f3b481e 100644 --- a/plugins/superio/fu-superio-device.c +++ b/plugins/superio/fu-superio-device.c @@ -362,6 +362,29 @@ fu_superio_device_setup (FuDevice *device, GError **error) return TRUE; } +static GBytes * +fu_superio_device_prepare_firmware (FuDevice *device, GBytes *fw, GError **error) +{ + gsize sz = 0; + const guint8 *buf = g_bytes_get_data (fw, &sz); + const guint8 sig1[] = { 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5 }; + const guint8 sig2[] = { 0x85, 0x12, 0x5a, 0x5a, 0xaa }; + + /* find signature -- maybe ignore byte 0x14 too? */ + for (gsize off = 0; off < sz; off += 16) { + if (memcmp (&buf[off], sig1, sizeof(sig1)) == 0 && + memcmp (&buf[off + 8], sig2, sizeof(sig2)) == 0) { + g_debug ("found signature at 0x%04x", (guint) off); + return g_bytes_ref (fw); + } + } + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "did not detect signature in firmware image"); + return NULL; +} + static gboolean fu_superio_device_close (FuDevice *device, GError **error) { @@ -467,4 +490,5 @@ fu_superio_device_class_init (FuSuperioDeviceClass *klass) klass_device->probe = fu_superio_device_probe; klass_device->setup = fu_superio_device_setup; klass_device->close = fu_superio_device_close; + klass_device->prepare_firmware = fu_superio_device_prepare_firmware; } diff --git a/plugins/superio/fu-superio-it89-device.c b/plugins/superio/fu-superio-it89-device.c index 4ff7893c8..0fbd6afeb 100644 --- a/plugins/superio/fu-superio-it89-device.c +++ b/plugins/superio/fu-superio-it89-device.c @@ -161,7 +161,73 @@ fu_superio_device_ec_read_status (FuSuperioDevice *self, GError **error) } while ((tmp & SIO_STATUS_EC_OBF) != 0); /* watch SCI events */ - return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); + return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); +} + +static gboolean +fu_superio_device_ec_write_disable (FuSuperioDevice *self, GError **error) +{ + guint8 tmp = 0x00; + + /* read existing status */ + if (!fu_superio_device_ec_read_status (self, error)) + return FALSE; + + /* write disable */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WRDI, error)) + return FALSE; + + /* read status register */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_RDSR, error)) + return FALSE; + + /* wait for read */ + do { + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) + return FALSE; + if (!fu_superio_device_ec_read (self, &tmp, error)) + return FALSE; + } while ((tmp & SIO_STATUS_EC_IBF) != 0); + + /* watch SCI events */ + return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); +} + +static gboolean +fu_superio_device_ec_write_enable (FuSuperioDevice *self, GError **error) +{ + guint8 tmp = 0x0; + + /* read existing status */ + if (!fu_superio_device_ec_read_status (self, error)) + return FALSE; + + /* write enable */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WREN, error)) + return FALSE; + + /* read status register */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_RDSR, error)) + return FALSE; + + /* wait for !BUSY */ + do { + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) + return FALSE; + if (!fu_superio_device_ec_read (self, &tmp, error)) + return FALSE; + } while ((tmp & 3) != SIO_STATUS_EC_IBF); + + /* watch SCI events */ + return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); } static GBytes * @@ -174,6 +240,8 @@ fu_superio_it89_device_read_addr (FuSuperioDevice *self, g_autofree guint8 *buf = NULL; /* check... */ + if (!fu_superio_device_ec_write_disable (self, error)) + return NULL; if (!fu_superio_device_ec_read_status (self, error)) return NULL; @@ -223,6 +291,97 @@ fu_superio_it89_device_progress_cb (goffset current, goffset total, gpointer use fu_device_set_progress_full (device, (gsize) current, (gsize) total); } +static gboolean +fu_superio_it89_device_write_addr (FuSuperioDevice *self, guint addr, GBytes *fw, GError **error) +{ + gsize size = 0; + const guint8 *buf = g_bytes_get_data (fw, &size); + + /* sanity check */ + if ((addr & 0xff) != 0x00) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "write addr unaligned, got 0x%04x", + (guint) addr); + } + if (size % 2 != 0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "write length not supported, got 0x%04x", + (guint) size); + } + + /* enable writes */ + if (!fu_superio_device_ec_write_enable (self, error)) + return FALSE; + + /* write DWORDs */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WRITE_WORD, error)) + return FALSE; + + /* set address, MSB, MID, LSB */ + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 16, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 8, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr & 0xff, error)) + return FALSE; + + /* write data two bytes at a time */ + for (guint i = 0; i < size; i += 2) { + if (i > 0) { + if (!fu_superio_device_ec_read_status (self, error)) + return FALSE; + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, + SIO_SPI_CMD_WRITE_WORD, + error)) + return FALSE; + } + if (!fu_superio_it89_device_ec_pm1do_smi (self, buf[i+0], error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_smi (self, buf[i+1], error)) + return FALSE; + } + + /* reset back? */ + if (!fu_superio_device_ec_write_disable (self, error)) + return FALSE; + return fu_superio_device_ec_read_status (self, error); +} + +static gboolean +fu_superio_it89_device_erase_addr (FuSuperioDevice *self, guint addr, GError **error) +{ + /* enable writes */ + if (!fu_superio_device_ec_write_enable (self, error)) + return FALSE; + + /* sector erase */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_4K_SECTOR_ERASE, error)) + return FALSE; + + /* set address, MSB, MID, LSB */ + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 16, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 8, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_smi (self, addr & 0xff, error)) + return FALSE; + + /* watch SCI events */ + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error)) + return FALSE; + return fu_superio_device_ec_read_status (self, error); +} + /* The 14th byte of the 16 byte signature is always read from the hardware as * 0x00 rather than the specified 0xAA. Fix up the firmware to match the * .ROM file which uses 0x7F as the number of bytes to mirror to e-flash... */ @@ -311,9 +470,195 @@ fu_superio_it89_device_detach (FuDevice *device, GError **error) return TRUE; } +static gboolean +fu_superio_it89_device_check_eflash (FuSuperioDevice *self, GError **error) +{ + g_autoptr(GBytes) fw = NULL; + const guint64 fwsize = fu_device_get_firmware_size_min (FU_DEVICE (self)); + const guint sigsz = 16; + + /* last 16 bytes of eeprom */ + fw = fu_superio_it89_device_read_addr (self, fwsize - sigsz, + sigsz, NULL, error); + if (fw == NULL) { + g_prefix_error (error, "failed to read signature bytes"); + return FALSE; + } + + /* cannot flash here without keyboard programmer */ + if (!fu_common_bytes_is_empty (fw)) { + gsize sz = 0; + const guint8 *buf = g_bytes_get_data (fw, &sz); + g_autoptr(GString) str = g_string_new (NULL); + for (guint i = 0; i < sz; i++) + g_string_append_printf (str, "0x%02x ", buf[i]); + if (str->len > 0) + g_string_truncate (str, str->len - 1); + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "e-flash has been protected: %s", + str->str); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_superio_it89_device_write_chunk (FuSuperioDevice *self, FuChunk *chk, GError **error) +{ + g_autoptr(GBytes) fw1 = NULL; + g_autoptr(GBytes) fw2 = NULL; + g_autoptr(GBytes) fw3 = NULL; + + /* erase page */ + if (!fu_superio_it89_device_erase_addr (self, chk->address, error)) { + g_prefix_error (error, "failed to erase @0x%04x", (guint) chk->address); + return FALSE; + } + + /* check erased */ + fw1 = fu_superio_it89_device_read_addr (self, chk->address, + chk->data_sz, NULL, + error); + if (fw1 == NULL) { + g_prefix_error (error, "failed to read erased " + "bytes @0x%04x", (guint) chk->address); + return FALSE; + } + if (!fu_common_bytes_is_empty (fw1)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_READ, + "sector was not erased"); + return FALSE; + } + + /* skip empty page */ + fw2 = g_bytes_new_static (chk->data, chk->data_sz); + if (fu_common_bytes_is_empty (fw2)) + return TRUE; + + /* write page */ + if (!fu_superio_it89_device_write_addr (self, chk->address, fw2, error)) { + g_prefix_error (error, "failed to write @0x%04x", (guint) chk->address); + return FALSE; + } + + /* verify page */ + fw3 = fu_superio_it89_device_read_addr (self, chk->address, + chk->data_sz, NULL, + error); + if (fw3 == NULL) { + g_prefix_error (error, "failed to read written " + "bytes @0x%04x", (guint) chk->address); + return FALSE; + } + if (!fu_common_bytes_compare (fw2, fw3, error)) { + g_prefix_error (error, "failed to verify @0x%04x", + (guint) chk->address); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_superio_it89_device_get_jedec_id (FuSuperioDevice *self, guint8 *id, GError **error) +{ + /* read status register */ + if (!fu_superio_device_ec_read_status (self, error)) + return FALSE; + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) + return FALSE; + if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_JEDEC_ID, error)) + return FALSE; + + /* wait for reads */ + for (guint i = 0; i < 4; i++) { + if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) + return FALSE; + if (!fu_superio_device_ec_read (self, &id[i], error)) + return FALSE; + } + + /* watch SCI events */ + return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); +} + +static gboolean +fu_superio_it89_device_write_firmware (FuDevice *device, GBytes *fw, GError **error) +{ + FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); + guint8 id[4] = { 0x0 }; + g_autoptr(GBytes) fw_fixed = NULL; + g_autoptr(GPtrArray) chunks = NULL; + + /* check JEDEC ID */ + if (!fu_superio_it89_device_get_jedec_id (self, id, error)) { + g_prefix_error (error, "failed to get JEDEC ID: "); + return FALSE; + } + if (id[0] != 0xff || id[1] != 0xff || id[2] != 0xfe || id[3] != 0xff) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "JEDEC ID not valid, 0x%02x%02x%02x%02x", + id[0], id[1], id[2], id[3]); + return FALSE; + } + + /* check eflash is writable */ + if (!fu_superio_it89_device_check_eflash (self, error)) + return FALSE; + + /* disable the mirroring of e-flash */ + if (g_getenv ("FWUPD_SUPERIO_DISABLE_MIRROR") != NULL) { + fw_fixed = fu_plugin_superio_fix_signature (self, fw, error); + if (fw_fixed == NULL) + return FALSE; + } else { + fw_fixed = g_bytes_ref (fw); + } + + /* chunks of 1kB, skipping the final chunk */ + chunks = fu_chunk_array_new_from_bytes (fw_fixed, 0x00, 0x00, 0x400); + fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); + for (guint i = 0; i < chunks->len - 1; i++) { + FuChunk *chk = g_ptr_array_index (chunks, i); + + /* try this many times; the failure-to-flash case leaves you + * without a keyboard and future boot may completely fail */ + for (guint j = 0;; j++) { + g_autoptr(GError) error_chk = NULL; + if (fu_superio_it89_device_write_chunk (self, chk, &error_chk)) + break; + if (j > 5) { + g_propagate_error (error, g_steal_pointer (&error_chk)); + return FALSE; + } + g_warning ("failure %u: %s", j, error_chk->message); + } + + /* set progress */ + fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); + } + + /* success */ + fu_device_set_progress (device, 100); + return TRUE; +} + static void fu_superio_it89_device_init (FuSuperioIt89Device *self) { + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ONLY_OFFLINE); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } static void @@ -324,5 +669,6 @@ fu_superio_it89_device_class_init (FuSuperioIt89DeviceClass *klass) klass_device->attach = fu_superio_it89_device_attach; klass_device->detach = fu_superio_it89_device_detach; klass_device->read_firmware = fu_superio_it89_device_read_firmware; + klass_device->write_firmware = fu_superio_it89_device_write_firmware; klass_superio_device->setup = fu_superio_it89_device_setup; }