fwupd/plugins/superio/fu-superio-device.c
Richard Hughes dfa9e11bb2 Allow handling FORCE for devices that subclass FuDevice
Pass FwupdInstallFlags down to the vfunc to allow us to check the flags when
parsing the firmware and updating the device.
2019-05-05 15:29:00 -05:00

498 lines
14 KiB
C

/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fcntl.h>
#include <string.h>
#include <sys/errno.h>
#include <glib/gstdio.h>
#include "fu-superio-common.h"
#include "fu-superio-device.h"
#define FU_PLUGIN_SUPERIO_TIMEOUT 0.25 /* s */
typedef struct
{
gint fd;
gchar *chipset;
guint16 port;
guint16 pm1_iobad0;
guint16 pm1_iobad1;
guint16 id;
} FuSuperioDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (FuSuperioDevice, fu_superio_device, FU_TYPE_DEVICE)
#define GET_PRIVATE(o) (fu_superio_device_get_instance_private (o))
enum {
PROP_0,
PROP_CHIPSET,
PROP_PORT,
PROP_ID,
PROP_LAST
};
gboolean
fu_superio_device_regval (FuSuperioDevice *self, guint8 addr,
guint8 *data, GError **error)
{
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
if (!fu_superio_outb (priv->fd, priv->port, addr, error))
return FALSE;
if (!fu_superio_inb (priv->fd, 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_superio_outb (priv->fd, priv->port, addr, error))
return FALSE;
if (!fu_superio_outb (priv->fd, 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, GString *str)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
g_string_append (str, " FuSuperioDevice:\n");
g_string_append_printf (str, " fd:\t\t\t%i\n", priv->fd);
g_string_append_printf (str, " chipset:\t\t%s\n", priv->chipset);
g_string_append_printf (str, " id:\t\t\t0x%04x\n", (guint) priv->id);
g_string_append_printf (str, " port:\t\t0x%04x\n", (guint) priv->port);
g_string_append_printf (str, " pm1-iobad0:\t\t0x%04x\n", (guint) priv->pm1_iobad0);
g_string_append_printf (str, " pm1-iobad1:\t\t0x%04x\n", (guint) priv->pm1_iobad1);
}
static guint16
fu_superio_device_check_id (FuSuperioDevice *self, GError **error)
{
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
guint16 id_tmp;
/* 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_superio_inb (priv->fd, 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_superio_inb (priv->fd, 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_superio_outb (priv->fd, 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_superio_outb (priv->fd, 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_superio_inb (priv->fd, priv->pm1_iobad1, &status, error))
return FALSE;
if ((status & SIO_STATUS_EC_OBF) == 0)
break;
if (!fu_superio_inb (priv->fd, 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_open (FuDevice *device, GError **error)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
/* open device */
priv->fd = g_open (fu_device_get_physical_id (device), O_RDWR);
if (priv->fd < 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to open %s: %s",
fu_device_get_physical_id (device),
strerror (errno));
return FALSE;
}
/* success */
return TRUE;
}
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)
{
FuSuperioDeviceClass *klass = FU_SUPERIO_DEVICE_GET_CLASS (device);
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
guint8 tmp = 0x0;
/* check port is valid */
if (!fu_superio_inb (priv->fd, priv->pm1_iobad0, &tmp, error))
return FALSE;
if (tmp != 0xff) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"check port!");
return FALSE;
}
/* 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);
}
/* subclassed setup */
if (klass->setup != NULL)
return klass->setup (self, error);
/* success */
return TRUE;
}
static GBytes *
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 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)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
FuSuperioDevicePrivate *priv = GET_PRIVATE (self);
if (!g_close (priv->fd, error))
return FALSE;
priv->fd = 0;
return TRUE;
}
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;
case PROP_PORT:
g_value_set_uint (value, priv->port);
break;
case PROP_ID:
g_value_set_uint (value, priv->id);
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;
case PROP_PORT:
priv->port = g_value_get_uint (value);
break;
case PROP_ID:
priv->id = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
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_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);
pspec = g_param_spec_uint ("port", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_PORT, pspec);
pspec = g_param_spec_uint ("id", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_ID, pspec);
object_class->finalize = fu_superio_device_finalize;
klass_device->to_string = fu_superio_device_to_string;
klass_device->open = fu_superio_device_open;
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;
}