fwupd/plugins/superio/fu-superio-device.c
Richard Hughes 1263446b23 trivial: Add a 'setup' vfunc that is used after open()
The setup() is the counterpart to probe(), the difference being the former needs
the device open and the latter does not.

This allows objects that derive from FuDevice, and use FuDeviceLocker to use
open() and close() without worrying about the performance implications of
probing the hardware, i.e. open() now simply opens a file or device.
2018-09-03 19:56:26 -05:00

330 lines
8.7 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-device.h"
#define FU_PLUGIN_SUPERIO_TIMEOUT 5 /* s */
#define KB_IBF (1 << 1) /* i/p buffer full */
#define KB_OBF (1 << 0) /* o/p buffer full */
struct _FuSuperioDevice {
FuDevice parent_instance;
gchar *devport;
gint fd;
gchar *chipset;
guint16 data_port;
guint16 cmd_port;
guint16 id;
guint32 size;
};
G_DEFINE_TYPE (FuSuperioDevice, fu_superio_device, FU_TYPE_DEVICE)
static void
fu_superio_device_to_string (FuDevice *device, GString *str)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
g_string_append (str, " FuSuperioDevice:\n");
g_string_append_printf (str, " fd:\t\t\t%i\n", self->fd);
g_string_append_printf (str, " devport:\t\t%s\n", self->devport);
g_string_append_printf (str, " chipset:\t\t%s\n", self->chipset);
g_string_append_printf (str, " data-port:\t\t0x%04x\n", (guint) self->data_port);
g_string_append_printf (str, " cmd-port:\t\t0x%04x\n", (guint) self->cmd_port);
g_string_append_printf (str, " id:\t\t\t0x%04x\n", (guint) self->id);
g_string_append_printf (str, " size:\t\t0x%04x\n", (guint) self->size);
}
static gboolean
fu_superio_device_outb (FuSuperioDevice *self, guint16 port, guint8 data, GError **error)
{
if (pwrite (self->fd, &data, 1, (goffset) port) != 1) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to write to port %04x: %s",
(guint) port,
strerror (errno));
return FALSE;
}
return TRUE;
}
static gboolean
fu_superio_device_inb (FuSuperioDevice *self, guint16 port, guint8 *data, GError **error)
{
if (pread (self->fd, data, 1, (goffset) port) != 1) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to read from port %04x: %s",
(guint) port,
strerror (errno));
return FALSE;
}
return TRUE;
}
static guint16
fu_superio_device_check_id (FuSuperioDevice *self, GError **error)
{
guint8 msb;
guint8 lsb;
guint16 id_tmp;
if (!fu_superio_device_outb (self, 0x2e, 0x20, error))
return FALSE;
if (!fu_superio_device_inb (self, 0x2f, &msb, error))
return FALSE;
if (!fu_superio_device_outb (self, 0x2e, 0x21, error))
return FALSE;
if (!fu_superio_device_inb (self, 0x2f, &lsb, error))
return FALSE;
/* check matches */
id_tmp = ((guint16) msb << 8) | (guint16) lsb;
if (self->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) self->id);
return FALSE;
}
return TRUE;
}
static gboolean
fu_superio_device_wait_for (FuSuperioDevice *self, guint8 mask, gboolean set, GError **error)
{
g_autoptr(GTimer) timer = g_timer_new ();
do {
guint8 status = 0x00;
if (!fu_superio_device_inb (self, self->cmd_port, &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;
}
static gboolean
fu_superio_device_ec_cmd (FuSuperioDevice *self, guint8 data, GError **error)
{
if (!fu_superio_device_wait_for (self, KB_IBF, FALSE, error))
return FALSE;
return fu_superio_device_outb (self, self->cmd_port, data, error);
}
static gboolean
fu_superio_device_ec_read (FuSuperioDevice *self, guint8 *data, GError **error)
{
if (!fu_superio_device_wait_for (self, KB_OBF, TRUE, error))
return FALSE;
return fu_superio_device_inb (self, self->data_port, data, error);
}
static gboolean
fu_superio_device_ec_write (FuSuperioDevice *self, guint8 data, GError **error)
{
if (!fu_superio_device_wait_for (self, KB_IBF, FALSE, error))
return FALSE;
return fu_superio_device_outb (self, self->data_port, data, error);
}
static gboolean
fu_superio_device_ec_flush (FuSuperioDevice *self, GError **error)
{
guint8 status = 0x00;
g_autoptr(GTimer) timer = g_timer_new ();
do {
guint8 unused = 0;
if (!fu_superio_device_inb (self, self->cmd_port, &status, error))
return FALSE;
if ((status & KB_OBF) == 0)
break;
if (!fu_superio_device_inb (self, self->data_port, &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;
}
static gboolean
fu_superio_device_ec_get_param (FuSuperioDevice *self, guint8 param, guint8 *data, GError **error)
{
if (!fu_superio_device_ec_cmd (self, 0x80, error))
return FALSE;
if (!fu_superio_device_ec_write (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_cmd (self, 0x81, error))
return FALSE;
if (!fu_superio_device_ec_write (self, param, error))
return FALSE;
return fu_superio_device_ec_write (self, data, error);
}
#endif
static gchar *
fu_superio_device_ec_get_str (FuSuperioDevice *self, guint8 idx, GError **error)
{
GString *str = g_string_new (NULL);
if (!fu_superio_device_ec_cmd (self, idx, error))
return FALSE;
for (guint i = 0; i < 0xff; i++) {
guint8 c = 0;
if (!fu_superio_device_ec_read (self, &c, error))
return FALSE;
if (c == '$')
break;
g_string_append_c (str, c);
}
return g_string_free (str, FALSE);
}
static gboolean
fu_superio_device_open (FuDevice *device, GError **error)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
/* open device */
self->fd = g_open (self->devport, O_RDWR);
if (self->fd < 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to open %s: %s",
self->devport,
strerror (errno));
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_superio_device_setup (FuDevice *device, GError **error)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
guint8 size_tmp = 0;
g_autofree gchar *guid = NULL;
g_autofree gchar *name = NULL;
g_autofree gchar *version = NULL;
/* check ID is correct */
if (!fu_superio_device_check_id (self, error)) {
g_prefix_error (error, "failed to probe id: ");
return FALSE;
}
/* use the chipset name as the ID and for the GUID */
fu_device_set_id (device, self->chipset);
guid = g_strdup_printf ("SuperIO-%s", self->chipset);
fu_device_add_guid (device, guid);
/* get EC size */
if (!fu_superio_device_ec_flush (self, error))
return FALSE;
if (!fu_superio_device_ec_get_param (self, 0xe5, &size_tmp, error))
return FALSE;
self->size = ((guint32) size_tmp) << 10;
/* get EC strings */
name = fu_superio_device_ec_get_str (self, 0x92, error);
if (name == NULL)
return FALSE;
fu_device_set_name (device, name);
version = fu_superio_device_ec_get_str (self, 0x93, error);
if (version == NULL)
return FALSE;
fu_device_set_version (device, version);
/* success */
return TRUE;
}
static gboolean
fu_superio_device_close (FuDevice *device, GError **error)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (device);
if (!g_close (self->fd, error))
return FALSE;
self->fd = 0;
return TRUE;
}
static void
fu_superio_device_init (FuSuperioDevice *self)
{
self->devport = g_strdup ("/dev/port");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_set_summary (FU_DEVICE (self), "SuperIO device");
fu_device_add_icon (FU_DEVICE (self), "computer");
}
static void
fu_superio_device_finalize (GObject *object)
{
FuSuperioDevice *self = FU_SUPERIO_DEVICE (object);
g_free (self->devport);
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);
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
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->setup = fu_superio_device_setup;
klass_device->close = fu_superio_device_close;
}
FuSuperioDevice *
fu_superio_device_new (const gchar *chipset, guint16 id, guint8 data_port, guint8 cmd_port)
{
FuSuperioDevice *self;
self = g_object_new (FU_TYPE_SUPERIO_DEVICE, NULL);
self->chipset = g_strdup (chipset);
self->id = id;
self->data_port = data_port > 0 ? data_port : 0x62;
self->cmd_port = cmd_port > 0 ? cmd_port : 0x66;
return self;
}