mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-14 16:11:22 +00:00
superio: Add support for writing new e-flash contents
This commit is contained in:
parent
6c2cc7868f
commit
38b357b131
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user