mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-11 06:35:28 +00:00

This means we can use the standard ->set_quirk_kv() vfunc rather than using FuQuirks directly with a custom group.
469 lines
13 KiB
C
469 lines
13 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-superio-common.h"
|
|
#include "fu-superio-device.h"
|
|
|
|
#define FU_PLUGIN_SUPERIO_TIMEOUT 0.25 /* s */
|
|
|
|
typedef struct
|
|
{
|
|
gchar *chipset;
|
|
guint16 port;
|
|
guint16 pm1_iobad0;
|
|
guint16 pm1_iobad1;
|
|
guint16 id;
|
|
} FuSuperioDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuSuperioDevice, fu_superio_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
#define GET_PRIVATE(o) (fu_superio_device_get_instance_private (o))
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_CHIPSET,
|
|
PROP_LAST
|
|
};
|
|
|
|
gboolean
|
|
fu_superio_device_regval (FuSuperioDevice *self, guint8 addr,
|
|
guint8 *data, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (!fu_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->port, addr, error))
|
|
return FALSE;
|
|
if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), priv->port + 1, data, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_regval16 (FuSuperioDevice *self, guint8 addr,
|
|
guint16 *data, GError **error)
|
|
{
|
|
guint8 msb;
|
|
guint8 lsb;
|
|
if (!fu_superio_device_regval (self, addr, &msb, error))
|
|
return FALSE;
|
|
if (!fu_superio_device_regval (self, addr + 1, &lsb, error))
|
|
return FALSE;
|
|
*data = ((guint16) msb << 8) | (guint16) lsb;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_regwrite (FuSuperioDevice *self, guint8 addr,
|
|
guint8 data, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (!fu_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->port, addr, error))
|
|
return FALSE;
|
|
if (!fu_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->port + 1, data, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_set_ldn (FuSuperioDevice *self, guint8 ldn, GError **error)
|
|
{
|
|
return fu_superio_device_regwrite (self, SIO_LDNxx_IDX_LDNSEL, ldn, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_regdump (FuSuperioDevice *self, guint8 ldn, GError **error)
|
|
{
|
|
const gchar *ldnstr = fu_superio_ldn_to_text (ldn);
|
|
guint8 buf[0xff] = { 0x00 };
|
|
guint16 iobad0 = 0x0;
|
|
guint16 iobad1 = 0x0;
|
|
g_autoptr(GString) str = g_string_new (NULL);
|
|
|
|
/* set LDN */
|
|
if (!fu_superio_device_set_ldn (self, ldn, error))
|
|
return FALSE;
|
|
for (guint i = 0x00; i < 0xff; i++) {
|
|
if (!fu_superio_device_regval (self, i, &buf[i], error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* get the i/o base addresses */
|
|
if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD0, &iobad0, error))
|
|
return FALSE;
|
|
if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD1, &iobad1, error))
|
|
return FALSE;
|
|
|
|
g_string_append_printf (str, "LDN:0x%02x ", ldn);
|
|
if (iobad0 != 0x0)
|
|
g_string_append_printf (str, "IOBAD0:0x%04x ", iobad0);
|
|
if (iobad1 != 0x0)
|
|
g_string_append_printf (str, "IOBAD1:0x%04x ", iobad1);
|
|
if (ldnstr != NULL)
|
|
g_string_append_printf (str, "(%s)", ldnstr);
|
|
fu_common_dump_raw (G_LOG_DOMAIN, str->str, buf, sizeof(buf));
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
|
|
/* FuUdevDevice->to_string */
|
|
FU_DEVICE_CLASS (fu_superio_device_parent_class)->to_string (device, idt, str);
|
|
|
|
fu_common_string_append_kv (str, idt, "Chipset", priv->chipset);
|
|
fu_common_string_append_kx (str, idt, "Id", priv->id);
|
|
fu_common_string_append_kx (str, idt, "Port", priv->port);
|
|
fu_common_string_append_kx (str, idt, "PM1_IOBAD0", priv->pm1_iobad0);
|
|
fu_common_string_append_kx (str, idt, "PM1_IOBAD1", priv->pm1_iobad1);
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_check_id (FuSuperioDevice *self, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
guint16 id_tmp;
|
|
|
|
/* no quirk entry */
|
|
if (priv->id == 0x0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"invalid SuperioId");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check ID, which can be done from any LDN */
|
|
if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_CHIPID1, &id_tmp, error))
|
|
return FALSE;
|
|
if (priv->id != id_tmp) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"SuperIO chip not supported, got %04x, expected %04x",
|
|
(guint) id_tmp, (guint) priv->id);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_wait_for (FuSuperioDevice *self, guint8 mask, gboolean set, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
g_autoptr(GTimer) timer = g_timer_new ();
|
|
do {
|
|
guint8 status = 0x00;
|
|
if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), priv->pm1_iobad1, &status, error))
|
|
return FALSE;
|
|
if (g_timer_elapsed (timer, NULL) > FU_PLUGIN_SUPERIO_TIMEOUT)
|
|
break;
|
|
if (set && (status & mask) != 0)
|
|
return TRUE;
|
|
if (!set && (status & mask) == 0)
|
|
return TRUE;
|
|
} while (TRUE);
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT,
|
|
"timed out whilst waiting for 0x%02x:%i", mask, set);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_ec_read (FuSuperioDevice *self, guint8 *data, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_OBF, TRUE, error))
|
|
return FALSE;
|
|
return fu_udev_device_pread (FU_UDEV_DEVICE (self), priv->pm1_iobad0, data, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_ec_write0 (FuSuperioDevice *self, guint8 data, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_IBF, FALSE, error))
|
|
return FALSE;
|
|
return fu_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->pm1_iobad0, data, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_ec_write1 (FuSuperioDevice *self, guint8 data, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_IBF, FALSE, error))
|
|
return FALSE;
|
|
return fu_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->pm1_iobad1, data, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_ec_flush (FuSuperioDevice *self, GError **error)
|
|
{
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
guint8 status = 0x00;
|
|
g_autoptr(GTimer) timer = g_timer_new ();
|
|
do {
|
|
guint8 unused = 0;
|
|
if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), priv->pm1_iobad1, &status, error))
|
|
return FALSE;
|
|
if ((status & SIO_STATUS_EC_OBF) == 0)
|
|
break;
|
|
if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), priv->pm1_iobad0, &unused, error))
|
|
return FALSE;
|
|
if (g_timer_elapsed (timer, NULL) > FU_PLUGIN_SUPERIO_TIMEOUT) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT,
|
|
"timed out whilst waiting for flush");
|
|
return FALSE;
|
|
}
|
|
} while (TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_superio_device_ec_get_param (FuSuperioDevice *self, guint8 param, guint8 *data, GError **error)
|
|
{
|
|
if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_READ, error))
|
|
return FALSE;
|
|
if (!fu_superio_device_ec_write0 (self, param, error))
|
|
return FALSE;
|
|
return fu_superio_device_ec_read (self, data, error);
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
fu_superio_device_ec_set_param (FuSuperioDevice *self, guint8 param, guint8 data, GError **error)
|
|
{
|
|
if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_WRITE, error))
|
|
return FALSE;
|
|
if (!fu_superio_device_ec_write0 (self, param, error))
|
|
return FALSE;
|
|
return fu_superio_device_ec_write0 (self, data, error);
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_superio_device_probe (FuDevice *device, GError **error)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
g_autofree gchar *devid = NULL;
|
|
g_autofree gchar *name = NULL;
|
|
|
|
/* use the chipset name as the logical ID and for the GUID */
|
|
fu_device_set_logical_id (device, priv->chipset);
|
|
devid = g_strdup_printf ("SuperIO-%s", priv->chipset);
|
|
fu_device_add_instance_id (device, devid);
|
|
name = g_strdup_printf ("SuperIO %s", priv->chipset);
|
|
fu_device_set_name (FU_DEVICE (self), name);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
|
|
/* check ID is correct */
|
|
if (!fu_superio_device_check_id (self, error)) {
|
|
g_prefix_error (error, "failed to probe id: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* dump LDNs */
|
|
if (g_getenv ("FWUPD_SUPERIO_VERBOSE") != NULL) {
|
|
for (guint j = 0; j < SIO_LDN_LAST; j++) {
|
|
if (!fu_superio_device_regdump (self, j, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* set Power Management I/F Channel 1 LDN */
|
|
if (!fu_superio_device_set_ldn (self, SIO_LDN_PM1, error))
|
|
return FALSE;
|
|
|
|
/* get the PM1 IOBAD0 address */
|
|
if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD0,
|
|
&priv->pm1_iobad0, error))
|
|
return FALSE;
|
|
|
|
/* get the PM1 IOBAD1 address */
|
|
if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD1,
|
|
&priv->pm1_iobad1, error))
|
|
return FALSE;
|
|
|
|
/* drain */
|
|
if (!fu_superio_device_ec_flush (self, error)) {
|
|
g_prefix_error (error, "failed to flush: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* dump PMC register map */
|
|
if (g_getenv ("FWUPD_SUPERIO_VERBOSE") != NULL) {
|
|
guint8 buf[0xff] = { 0x00 };
|
|
for (guint i = 0x00; i < 0xff; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_superio_device_ec_get_param (self, i, &buf[i], &error_local)) {
|
|
g_debug ("param: 0x%02x = %s", i, error_local->message);
|
|
continue;
|
|
}
|
|
}
|
|
fu_common_dump_raw (G_LOG_DOMAIN, "EC Registers", buf, 0x100);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_superio_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
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 fu_firmware_new_from_bytes (fw);
|
|
}
|
|
}
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"did not detect signature in firmware image");
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (object);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
switch (prop_id) {
|
|
case PROP_CHIPSET:
|
|
g_value_set_string (value, priv->chipset);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (object);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
switch (prop_id) {
|
|
case PROP_CHIPSET:
|
|
g_free (priv->chipset);
|
|
priv->chipset = g_value_dup_string (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_superio_device_set_quirk_kv (FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
|
|
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
|
|
|
|
if (g_strcmp0 (key, "SuperioId") == 0) {
|
|
guint64 tmp = fu_common_strtoull (value);
|
|
if (tmp < G_MAXUINT16) {
|
|
priv->id = tmp;
|
|
return TRUE;
|
|
}
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid value");
|
|
return FALSE;
|
|
}
|
|
if (g_strcmp0 (key, "SuperioPort") == 0) {
|
|
guint64 tmp = fu_common_strtoull (value);
|
|
if (tmp < G_MAXUINT16) {
|
|
priv->port = tmp;
|
|
return TRUE;
|
|
}
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid value");
|
|
return FALSE;
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"quirk key not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_init (FuSuperioDevice *self)
|
|
{
|
|
fu_device_set_physical_id (FU_DEVICE (self), "/dev/port");
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
|
|
fu_device_add_protocol (FU_DEVICE (self), "tw.com.ite.superio");
|
|
fu_device_set_summary (FU_DEVICE (self), "Embedded Controller");
|
|
fu_device_add_icon (FU_DEVICE (self), "computer");
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (fu_superio_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_superio_device_class_init (FuSuperioDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
|
|
/* properties */
|
|
object_class->get_property = fu_superio_device_get_property;
|
|
object_class->set_property = fu_superio_device_set_property;
|
|
pspec = g_param_spec_string ("chipset", NULL, NULL, NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME);
|
|
g_object_class_install_property (object_class, PROP_CHIPSET, pspec);
|
|
|
|
object_class->finalize = fu_superio_device_finalize;
|
|
klass_device->to_string = fu_superio_device_to_string;
|
|
klass_device->set_quirk_kv = fu_superio_device_set_quirk_kv;
|
|
klass_device->probe = fu_superio_device_probe;
|
|
klass_device->setup = fu_superio_device_setup;
|
|
klass_device->prepare_firmware = fu_superio_device_prepare_firmware;
|
|
}
|