fwupd/plugins/intel-spi/fu-intel-spi-device.c
Richard Hughes f56878ff88 Allow adding GUIDs to each HSI security attr
This indicates the GUID in some way contributed to the result decided.

It also allows us to match the submitted HSI results back to a firmware
stream on the LVFS, which allows us to allow vendors to see a subset of
results for uploaded devices.
2021-09-03 22:03:28 +01:00

524 lines
15 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: GPL-2+
*/
#include "config.h"
#include <fwupdplugin.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <gio/gunixinputstream.h>
#include "fu-ifd-device.h"
#include "fu-intel-spi-common.h"
#include "fu-intel-spi-device.h"
#include "fu-pci-device.h"
struct _FuIntelSpiDevice {
FuDevice parent_instance;
FuIntelSpiKind kind;
gchar *spibar_proxy;
guint32 phys_spibar;
gpointer spibar;
guint16 hsfs;
guint16 frap;
guint32 freg[4];
guint32 flvalsig;
guint32 descriptor_map0;
guint32 descriptor_map1;
guint32 descriptor_map2;
guint32 components_rcd;
guint32 illegal_jedec;
guint32 flpb;
guint32 flash_master[4];
guint32 protected_range[4];
};
#define FU_INTEL_SPI_PHYS_SPIBAR_SIZE 0x10000 /* bytes */
#define FU_INTEL_SPI_READ_TIMEOUT 10 /* ms */
#define PCI_BASE_ADDRESS_0 0x0010
/**
* FU_INTEL_SPI_DEVICE_FLAG_ICH:
*
* Device is an I/O Controller Hub.
*/
#define FU_INTEL_SPI_DEVICE_FLAG_ICH (1 << 0)
/**
* FU_INTEL_SPI_DEVICE_FLAG_PCH:
*
* Device is a Platform Controller Hub.
*/
#define FU_INTEL_SPI_DEVICE_FLAG_PCH (1 << 1)
G_DEFINE_TYPE(FuIntelSpiDevice, fu_intel_spi_device, FU_TYPE_DEVICE)
static void
fu_intel_spi_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
fu_common_string_append_kv(str, idt, "Kind", fu_intel_spi_kind_to_string(self->kind));
fu_common_string_append_kx(str, idt, "SPIBAR", self->phys_spibar);
fu_common_string_append_kx(str, idt, "HSFS", self->hsfs);
fu_common_string_append_kx(str, idt, "FRAP", self->frap);
for (guint i = 0; i < 4; i++) {
g_autofree gchar *title = g_strdup_printf("FREG%u", i);
fu_common_string_append_kx(str, idt, title, self->freg[i]);
}
for (guint i = 0; i < 4; i++) {
g_autofree gchar *title = g_strdup_printf("FLMSTR%u", i);
fu_common_string_append_kx(str, idt, title, self->flash_master[i]);
}
fu_common_string_append_kx(str, idt, "FLVALSIG", self->flvalsig);
fu_common_string_append_kx(str, idt, "FLMAP0", self->descriptor_map0);
fu_common_string_append_kx(str, idt, "FLMAP1", self->descriptor_map1);
fu_common_string_append_kx(str, idt, "FLMAP2", self->descriptor_map2);
fu_common_string_append_kx(str, idt, "FLCOMP", self->components_rcd);
fu_common_string_append_kx(str, idt, "FLILL", self->illegal_jedec);
fu_common_string_append_kx(str, idt, "FLPB", self->flpb);
/* PRx */
for (guint i = 0; i < 4; i++) {
guint32 limit = 0;
guint32 base = 0;
FuIfdAccess access = FU_IFD_ACCESS_NONE;
g_autofree gchar *title = NULL;
g_autofree gchar *tmp = NULL;
if (self->protected_range[i] == 0x0)
continue;
if ((self->protected_range[i] >> 31) & 0b1)
access |= FU_IFD_ACCESS_WRITE;
if ((self->protected_range[i] >> 15) & 0b1)
access |= FU_IFD_ACCESS_READ;
if (access != FU_IFD_ACCESS_NONE) {
base = ((self->protected_range[i] >> 0) & 0x1FFF) << 12;
limit = (((self->protected_range[i] >> 16) & 0x1FFF) << 12) | 0xFFFF;
}
title = g_strdup_printf("PR%u", i);
tmp = g_strdup_printf("blocked %s from 0x%x to 0x%x [0x%x]",
fu_ifd_access_to_string(access),
base,
limit,
self->protected_range[i]);
fu_common_string_append_kv(str, idt, title, tmp);
}
}
static gboolean
fu_intel_spi_device_open(FuDevice *device, GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
int fd;
g_autoptr(GInputStream) istr = NULL;
/* this will fail if the kernel is locked down */
fd = open("/dev/mem", O_SYNC | O_RDWR);
if (fd == -1) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
#ifdef HAVE_ERRNO_H
"failed to open /dev/mem: %s",
strerror(errno));
#else
"failed to open /dev/mem");
#endif
return FALSE;
}
istr = g_unix_input_stream_new(fd, TRUE);
if (istr == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to create input stream");
return FALSE;
}
self->spibar = mmap(NULL,
FU_INTEL_SPI_PHYS_SPIBAR_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
self->phys_spibar);
if (self->spibar == MAP_FAILED) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
#ifdef HAVE_ERRNO_H
"failed to mmap SPIBAR: %s",
strerror(errno));
#else
"failed to mmap SPIBAR");
#endif
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_intel_spi_device_close(FuDevice *device, GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
/* close */
if (self->spibar != NULL) {
if (munmap(self->spibar, FU_INTEL_SPI_PHYS_SPIBAR_SIZE) == -1) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
#ifdef HAVE_ERRNO_H
"failed to unmap SPIBAR: %s",
strerror(errno));
#else
"failed to unmap SPIBAR");
#endif
return FALSE;
}
self->spibar = NULL;
}
/* success */
return TRUE;
}
static guint32
fu_intel_spi_device_read_reg(FuIntelSpiDevice *self, guint8 section, guint16 offset)
{
guint32 control = 0;
control |= (((guint32)section) << 12) & FDOC_FDSS;
control |= (((guint32)offset) << 2) & FDOC_FDSI;
fu_mmio_write32_le(self->spibar, PCH100_REG_FDOC, control);
return fu_mmio_read32_le(self->spibar, PCH100_REG_FDOD);
}
static void
fu_intel_spi_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
FuIfdAccess access_global = FU_IFD_ACCESS_NONE;
g_autoptr(FwupdSecurityAttr) attr = NULL;
/* create attr */
attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR);
fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self)));
fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL);
fwupd_security_attr_add_guids(attr, fu_device_get_guids(device));
fu_security_attrs_append(attrs, attr);
/* check for read access from other regions */
for (guint j = FU_IFD_REGION_BIOS; j < 4; j++) {
FuIfdAccess access;
access =
fu_ifd_region_to_access(FU_IFD_REGION_DESC, self->flash_master[j - 1], TRUE);
fwupd_security_attr_add_metadata(attr,
fu_ifd_region_to_string(j),
fu_ifd_access_to_string(access));
access_global |= access;
}
/* any region can write to the flash descriptor */
if (access_global & FU_IFD_ACCESS_WRITE) {
fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID);
return;
}
/* FLOCKDN is unset */
if ((self->hsfs >> 15 & 0b1) == 0) {
fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED);
return;
}
/* success */
fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS);
fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED);
}
static gboolean
fu_intel_spi_device_probe(FuDevice *device, GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
/* verify this was set in the quirk file */
if (self->kind == FU_INTEL_SPI_KIND_UNKNOWN) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"IntelSpiKind not set");
return FALSE;
}
/* use a hidden PCI device to get the RCBA */
if (self->spibar_proxy != NULL) {
g_autoptr(FuDevice) pcidev = NULL;
g_autoptr(FuDeviceLocker) locker = NULL;
/* get SPIBAR from a hidden (VID set to 0xFFFF) PCI device */
pcidev = fu_pci_device_new(self->spibar_proxy, error);
if (pcidev == NULL)
return FALSE;
locker = fu_device_locker_new(pcidev, error);
if (locker == NULL)
return FALSE;
self->phys_spibar =
fu_pci_device_read_config(FU_PCI_DEVICE(pcidev), PCI_BASE_ADDRESS_0);
if (self->phys_spibar == 0 || self->phys_spibar == G_MAXUINT32) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"SPIBAR not valid: 0x%x",
self->phys_spibar);
return FALSE;
}
}
/* specified explicitly as a physical address */
if (self->phys_spibar == 0) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"IntelSpiBar not set");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_intel_spi_device_setup(FuDevice *device, GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
guint64 total_size = 0;
guint8 comp1_density;
guint8 comp2_density;
guint16 reg_pr0 = fu_device_has_private_flag(device, FU_INTEL_SPI_DEVICE_FLAG_ICH)
? ICH9_REG_PR0
: PCH100_REG_FPR0;
/* dump everything */
if (g_getenv("FWUPD_INTEL_SPI_VERBOSE") != NULL) {
for (guint i = 0; i < 0xff; i += 4) {
guint32 tmp = fu_mmio_read32(self->spibar, i);
g_print("SPIBAR[0x%02x] = 0x%x\n", i, tmp);
}
}
/* read from descriptor */
self->hsfs = fu_mmio_read16(self->spibar, ICH9_REG_HSFS);
self->frap = fu_mmio_read16(self->spibar, ICH9_REG_FRAP);
for (guint i = FU_IFD_REGION_DESC; i < 4; i++)
self->freg[i] = fu_mmio_read32(self->spibar, ICH9_REG_FREG0 + i * 4);
self->flvalsig = fu_intel_spi_device_read_reg(self, 0, 0);
self->descriptor_map0 = fu_intel_spi_device_read_reg(self, 0, 1);
self->descriptor_map1 = fu_intel_spi_device_read_reg(self, 0, 2);
self->descriptor_map2 = fu_intel_spi_device_read_reg(self, 0, 3);
self->components_rcd = fu_intel_spi_device_read_reg(self, 1, 0);
self->illegal_jedec = fu_intel_spi_device_read_reg(self, 1, 1);
self->flpb = fu_intel_spi_device_read_reg(self, 1, 2);
for (guint i = 0; i < 4; i++)
self->flash_master[i] = fu_intel_spi_device_read_reg(self, 3, i);
for (guint i = 0; i < 4; i++) {
self->protected_range[i] =
fu_mmio_read32(self->spibar, reg_pr0 + i * sizeof(guint32));
}
/* set size */
comp1_density = (self->components_rcd & 0x0f) >> 0;
if (comp1_density != 0xf)
total_size += 1 << (19 + comp1_density);
comp2_density = (self->components_rcd & 0xf0) >> 4;
if (comp2_density != 0xf)
total_size += 1 << (19 + comp2_density);
fu_device_set_firmware_size(device, total_size);
/* add children */
for (guint i = FU_IFD_REGION_BIOS; i < 4; i++) {
g_autoptr(FuDevice) child = NULL;
if (self->freg[i] == 0x0)
continue;
child = fu_ifd_device_new(i, self->freg[i]);
for (guint j = 1; j < 4; j++) {
FuIfdAccess access;
access = fu_ifd_region_to_access(i, self->flash_master[j - 1], TRUE);
fu_ifd_device_set_access(FU_IFD_DEVICE(child), j, access);
}
fu_device_add_child(device, child);
}
return TRUE;
}
static gboolean
fu_intel_spi_device_wait(FuIntelSpiDevice *self, guint timeout_ms, GError **error)
{
g_usleep(1);
for (guint i = 0; i < timeout_ms * 100; i++) {
guint16 hsfs = fu_mmio_read16(self->spibar, ICH9_REG_HSFS);
if (hsfs & HSFS_FDONE)
return TRUE;
if (hsfs & HSFS_FCERR) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "HSFS transaction error");
return FALSE;
}
g_usleep(10);
}
g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "HSFS timed out");
return FALSE;
}
static void
fu_intel_spi_device_set_addr(FuIntelSpiDevice *self, guint32 addr)
{
guint32 addr_old = fu_mmio_read32(self->spibar, ICH9_REG_FADDR) & ~PCH100_FADDR_FLA;
fu_mmio_write32(self->spibar, ICH9_REG_FADDR, (addr & PCH100_FADDR_FLA) | addr_old);
}
GBytes *
fu_intel_spi_device_dump(FuIntelSpiDevice *self,
FuDevice *device,
guint32 offset,
guint32 length,
GError **error)
{
guint8 block_len = 0x40;
g_autoptr(GByteArray) buf = g_byte_array_sized_new(length);
/* set FDONE, FCERR, AEL */
fu_device_set_status(device, FWUPD_STATUS_DEVICE_READ);
fu_mmio_write16(self->spibar, ICH9_REG_HSFS, fu_mmio_read16(self->spibar, ICH9_REG_HSFS));
for (guint32 addr = offset; addr < offset + length; addr += block_len) {
guint16 hsfc;
guint32 buftmp32 = 0;
/* set up read */
fu_intel_spi_device_set_addr(self, addr);
hsfc = fu_mmio_read16(self->spibar, ICH9_REG_HSFC);
hsfc &= ~PCH100_HSFC_FCYCLE;
hsfc &= ~HSFC_FDBC;
/* set byte count */
hsfc |= ((block_len - 1) << 8) & HSFC_FDBC;
hsfc |= HSFC_FGO;
fu_mmio_write16(self->spibar, ICH9_REG_HSFC, hsfc);
if (!fu_intel_spi_device_wait(self, FU_INTEL_SPI_READ_TIMEOUT, error)) {
g_prefix_error(error, "failed @0x%x: ", addr);
return NULL;
}
/* copy out data */
for (guint i = 0; i < block_len; i++) {
if (i % 4 == 0)
buftmp32 = fu_mmio_read32(self->spibar, ICH9_REG_FDATA0 + i);
fu_byte_array_append_uint8(buf, buftmp32 >> ((i % 4) * 8));
}
/* progress */
fu_device_set_progress_full(device, addr - offset + block_len, length);
}
/* success */
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
}
static GBytes *
fu_intel_spi_device_dump_firmware(FuDevice *device, GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
guint64 total_size = fu_device_get_firmware_size_max(device);
return fu_intel_spi_device_dump(self, device, 0x0, total_size, error);
}
static FuFirmware *
fu_intel_spi_device_read_firmware(FuDevice *device, GError **error)
{
g_autoptr(FuFirmware) firmware = fu_ifd_firmware_new();
g_autoptr(GBytes) blob = NULL;
blob = fu_intel_spi_device_dump_firmware(device, error);
if (blob == NULL)
return NULL;
if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, error))
return NULL;
return g_steal_pointer(&firmware);
}
static gboolean
fu_intel_spi_device_set_quirk_kv(FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device);
if (g_strcmp0(key, "IntelSpiBar") == 0) {
guint64 tmp = fu_common_strtoull(value);
self->phys_spibar = tmp;
return TRUE;
}
if (g_strcmp0(key, "IntelSpiKind") == 0) {
g_autofree gchar *instance_id = NULL;
g_autofree gchar *kind_up = NULL;
/* validate */
self->kind = fu_intel_spi_kind_from_string(value);
if (self->kind == FU_INTEL_SPI_KIND_UNKNOWN) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"%s not supported",
value);
return FALSE;
}
/* get things like SPIBAR */
kind_up = g_ascii_strup(value, -1);
instance_id = g_strdup_printf("INTEL_SPI_CHIPSET\\%s", kind_up);
fu_device_add_instance_id(device, instance_id);
return TRUE;
}
if (g_strcmp0(key, "IntelSpiBarProxy") == 0) {
self->spibar_proxy = g_strdup(value);
return TRUE;
}
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported");
return FALSE;
}
static void
fu_intel_spi_device_init(FuIntelSpiDevice *self)
{
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_icon(FU_DEVICE(self), "computer");
fu_device_set_physical_id(FU_DEVICE(self), "intel_spi");
fu_device_register_private_flag(FU_DEVICE(self), FU_INTEL_SPI_DEVICE_FLAG_ICH, "ICH");
fu_device_register_private_flag(FU_DEVICE(self), FU_INTEL_SPI_DEVICE_FLAG_PCH, "PCH");
}
static void
fu_intel_spi_device_class_init(FuIntelSpiDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->to_string = fu_intel_spi_device_to_string;
klass_device->probe = fu_intel_spi_device_probe;
klass_device->setup = fu_intel_spi_device_setup;
klass_device->dump_firmware = fu_intel_spi_device_dump_firmware;
klass_device->read_firmware = fu_intel_spi_device_read_firmware;
klass_device->open = fu_intel_spi_device_open;
klass_device->close = fu_intel_spi_device_close;
klass_device->set_quirk_kv = fu_intel_spi_device_set_quirk_kv;
klass_device->add_security_attrs = fu_intel_spi_device_add_security_attrs;
}