superio: Add support for writing new e-flash contents

This commit is contained in:
Richard Hughes 2019-03-22 12:20:06 +00:00
parent 6c2cc7868f
commit 38b357b131
4 changed files with 407 additions and 6 deletions

View File

@ -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 See https://en.wikipedia.org/wiki/Super_I/O for more details about SuperIO
and what the EC actually does. 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: Other useful links:
* https://raw.githubusercontent.com/system76/ecflash/master/ec.py * https://raw.githubusercontent.com/system76/ecflash/master/ec.py

View File

@ -87,6 +87,7 @@ void
fu_plugin_init (FuPlugin *plugin) fu_plugin_init (FuPlugin *plugin)
{ {
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "tw.com.ite.superio");
} }
gboolean gboolean
@ -153,3 +154,38 @@ fu_plugin_verify (FuPlugin *plugin, FuDevice *device,
} }
return TRUE; 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);
}

View File

@ -362,6 +362,29 @@ fu_superio_device_setup (FuDevice *device, GError **error)
return TRUE; 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 static gboolean
fu_superio_device_close (FuDevice *device, GError **error) 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->probe = fu_superio_device_probe;
klass_device->setup = fu_superio_device_setup; klass_device->setup = fu_superio_device_setup;
klass_device->close = fu_superio_device_close; klass_device->close = fu_superio_device_close;
klass_device->prepare_firmware = fu_superio_device_prepare_firmware;
} }

View File

@ -161,7 +161,73 @@ fu_superio_device_ec_read_status (FuSuperioDevice *self, GError **error)
} while ((tmp & SIO_STATUS_EC_OBF) != 0); } while ((tmp & SIO_STATUS_EC_OBF) != 0);
/* watch SCI events */ /* 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 * static GBytes *
@ -174,6 +240,8 @@ fu_superio_it89_device_read_addr (FuSuperioDevice *self,
g_autofree guint8 *buf = NULL; g_autofree guint8 *buf = NULL;
/* check... */ /* check... */
if (!fu_superio_device_ec_write_disable (self, error))
return NULL;
if (!fu_superio_device_ec_read_status (self, error)) if (!fu_superio_device_ec_read_status (self, error))
return NULL; 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); 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 /* 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 * 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... */ * .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; 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 static void
fu_superio_it89_device_init (FuSuperioIt89Device *self) 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 static void
@ -324,5 +669,6 @@ fu_superio_it89_device_class_init (FuSuperioIt89DeviceClass *klass)
klass_device->attach = fu_superio_it89_device_attach; klass_device->attach = fu_superio_it89_device_attach;
klass_device->detach = fu_superio_it89_device_detach; klass_device->detach = fu_superio_it89_device_detach;
klass_device->read_firmware = fu_superio_it89_device_read_firmware; 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; klass_superio_device->setup = fu_superio_it89_device_setup;
} }