From f3c64adb6c6d4ad6b2045682c48cdd3bbf3a85a7 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 17 Mar 2021 19:35:21 +0000 Subject: [PATCH] intel-spi: Allow downloading the firmware image from the eSPI controller Add the IFD regions as child devices and set the region access on the child devices. Also add read-only SPI descriptor as an HSI attribute and require FLOCKDN on Intel hardware. Use the hidden PCI 00:1f.5 device to set the SPIBAR automatically and generate the quirk file automatically to support more hardware. --- docs/hsi.xml | 16 + libfwupd/fwupd-security-attr-private.h | 1 + plugins/intel-spi/fu-ifd-common.c | 70 ++++ plugins/intel-spi/fu-ifd-common.h | 8 + plugins/intel-spi/fu-ifd-device.c | 155 ++++++++ plugins/intel-spi/fu-ifd-device.h | 24 ++ plugins/intel-spi/fu-ifd-firmware.c | 35 +- plugins/intel-spi/fu-intel-spi-common.c | 101 +++++ plugins/intel-spi/fu-intel-spi-common.h | 85 ++++ plugins/intel-spi/fu-intel-spi-device.c | 506 ++++++++++++++++++++++++ plugins/intel-spi/fu-intel-spi-device.h | 18 + plugins/intel-spi/fu-pci-device.c | 175 ++++++++ plugins/intel-spi/fu-pci-device.h | 22 ++ plugins/intel-spi/fu-plugin-intel-spi.c | 22 ++ plugins/intel-spi/generate-quirk.py | 76 ++++ plugins/intel-spi/intel-spi.quirk | 401 +++++++++++++++++++ plugins/intel-spi/meson.build | 8 + src/fu-security-attr.c | 4 + 18 files changed, 1696 insertions(+), 31 deletions(-) create mode 100644 plugins/intel-spi/fu-ifd-device.c create mode 100644 plugins/intel-spi/fu-ifd-device.h create mode 100644 plugins/intel-spi/fu-intel-spi-common.c create mode 100644 plugins/intel-spi/fu-intel-spi-common.h create mode 100644 plugins/intel-spi/fu-intel-spi-device.c create mode 100644 plugins/intel-spi/fu-intel-spi-device.h create mode 100644 plugins/intel-spi/fu-pci-device.c create mode 100644 plugins/intel-spi/fu-pci-device.h create mode 100755 plugins/intel-spi/generate-quirk.py create mode 100644 plugins/intel-spi/intel-spi.quirk diff --git a/docs/hsi.xml b/docs/hsi.xml index 6450c061d..dc7a3ef07 100644 --- a/docs/hsi.xml +++ b/docs/hsi.xml @@ -393,6 +393,22 @@ + + Read-only SPI Descriptor + + The SPI descriptor must always be read only from all other regions. + Additionally on Intel architectures the FLOCKDN register must be set to + prevent configuration registers in the SPI BAR from being changed. + + + + + For HSI-1 this should be read only from all regions v1.6.0 + + + + + TPM 2.0 Present diff --git a/libfwupd/fwupd-security-attr-private.h b/libfwupd/fwupd-security-attr-private.h index f72faddec..b0849ba5c 100644 --- a/libfwupd/fwupd-security-attr-private.h +++ b/libfwupd/fwupd-security-attr-private.h @@ -36,6 +36,7 @@ G_BEGIN_DECLS #define FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE "org.fwupd.hsi.Spi.Bioswe" /* Since: 1.5.0 */ #define FWUPD_SECURITY_ATTR_ID_SPI_BLE "org.fwupd.hsi.Spi.Ble" /* Since: 1.5.0 */ #define FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP "org.fwupd.hsi.Spi.SmmBwp" /* Since: 1.5.0 */ +#define FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR "org.fwupd.hsi.Spi.Descriptor" /* Since: 1.6.0 */ #define FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE "org.fwupd.hsi.SuspendToIdle" /* Since: 1.5.0 */ #define FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM "org.fwupd.hsi.SuspendToRam" /* Since: 1.5.0 */ #define FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0 "org.fwupd.hsi.Tpm.ReconstructionPcr0" /* Since: 1.5.0 */ diff --git a/plugins/intel-spi/fu-ifd-common.c b/plugins/intel-spi/fu-ifd-common.c index 36390c47e..2a4f28cd0 100644 --- a/plugins/intel-spi/fu-ifd-common.c +++ b/plugins/intel-spi/fu-ifd-common.c @@ -44,6 +44,42 @@ fu_ifd_region_to_string (FuIfdRegion region) return NULL; } +/** + * fu_ifd_region_to_name: + * @region: A #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS + * + * Converts a #FuIfdRegion to a name the user might recognize. + * + * Return value: identifier string + * + * Since: 1.6.0 + **/ +const gchar * +fu_ifd_region_to_name (FuIfdRegion region) +{ + if (region == FU_IFD_REGION_DESC) + return "IFD descriptor region"; + if (region == FU_IFD_REGION_BIOS) + return "BIOS"; + if (region == FU_IFD_REGION_ME) + return "Intel Management Engine"; + if (region == FU_IFD_REGION_GBE) + return "Gigabit Ethernet"; + if (region == FU_IFD_REGION_PLATFORM) + return "Platform firmware"; + if (region == FU_IFD_REGION_DEVEXP) + return "Device Firmware"; + if (region == FU_IFD_REGION_BIOS2) + return "BIOS Backup"; + if (region == FU_IFD_REGION_EC) + return "Embedded Controller"; + if (region == FU_IFD_REGION_IE) + return "Innovation Engine"; + if (region == FU_IFD_REGION_10GBE) + return "10 Gigabit Ethernet"; + return NULL; +} + /** * fu_ifd_access_to_string: * @access: A #FuIfdAccess, e.g. %FU_IFD_ACCESS_READ @@ -57,6 +93,8 @@ fu_ifd_region_to_string (FuIfdRegion region) const gchar * fu_ifd_access_to_string (FuIfdAccess access) { + if (access == FU_IFD_ACCESS_NONE) + return "--"; if (access == FU_IFD_ACCESS_READ) return "ro"; if (access == FU_IFD_ACCESS_WRITE) @@ -65,3 +103,35 @@ fu_ifd_access_to_string (FuIfdAccess access) return "rw"; return NULL; } + +FuIfdAccess +fu_ifd_region_to_access (FuIfdRegion region, guint32 flash_master, gboolean is_skylake) +{ + guint8 bit_r = 0; + guint8 bit_w = 0; + + /* new layout */ + if (is_skylake) { + bit_r = (flash_master >> (region + 8)) & 0b1; + bit_w = (flash_master >> (region + 20)) & 0b1; + return (bit_r ? FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | + (bit_w ? FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE); + } + + /* old layout */ + if (region == FU_IFD_REGION_DESC) { + bit_r = 16; + bit_w = 24; + } else if (region == FU_IFD_REGION_BIOS) { + bit_r = 17; + bit_w = 25; + } else if (region == FU_IFD_REGION_ME) { + bit_r = 18; + bit_w = 26; + } else if (region == FU_IFD_REGION_GBE) { + bit_r = 19; + bit_w = 27; + } + return ((flash_master >> bit_r) & 0b1 ? FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | + ((flash_master >> bit_w) & 0b1 ? FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE); +} diff --git a/plugins/intel-spi/fu-ifd-common.h b/plugins/intel-spi/fu-ifd-common.h index 93ee315dd..0cdfef69c 100644 --- a/plugins/intel-spi/fu-ifd-common.h +++ b/plugins/intel-spi/fu-ifd-common.h @@ -28,5 +28,13 @@ typedef enum { FU_IFD_ACCESS_WRITE = 1 << 1, } FuIfdAccess; +#define FU_IFD_FREG_BASE(freg) (((freg) << 12) & 0x07FFF000) +#define FU_IFD_FREG_LIMIT(freg) ((((freg) >> 4) & 0x07FFF000) | 0x00000FFF) + const gchar *fu_ifd_region_to_string (FuIfdRegion region); +const gchar *fu_ifd_region_to_name (FuIfdRegion region); const gchar *fu_ifd_access_to_string (FuIfdAccess access); + +FuIfdAccess fu_ifd_region_to_access (FuIfdRegion region, + guint32 flash_master, + gboolean is_skylake); diff --git a/plugins/intel-spi/fu-ifd-device.c b/plugins/intel-spi/fu-ifd-device.c new file mode 100644 index 000000000..0cba98dea --- /dev/null +++ b/plugins/intel-spi/fu-ifd-device.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-common.h" + +#include "fu-ifd-device.h" +#include "fu-ifd-bios.h" + +#include "fu-intel-spi-device.h" + +typedef struct { + FuDevice parent_instance; + FuIfdRegion region; + guint32 offset; + FuIfdAccess access[FU_IFD_REGION_MAX]; +} FuIfdDevicePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (FuIfdDevice, fu_ifd_device, FU_TYPE_DEVICE) + +#define GET_PRIVATE(o) (fu_ifd_device_get_instance_private (o)) + +static void +fu_ifd_device_set_region (FuIfdDevice *self, FuIfdRegion region) +{ + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + const gchar *region_str = fu_ifd_region_to_string (region); + g_autofree gchar *instance_id = NULL; + g_autofree gchar *region_str_up = NULL; + + priv->region = region; + fu_device_set_name (FU_DEVICE (self), fu_ifd_region_to_name (region)); + fu_device_set_logical_id (FU_DEVICE (self), region_str); + region_str_up = g_ascii_strup (region_str, -1); + instance_id = g_strdup_printf ("IFD\\%s", region_str_up); + fu_device_add_instance_id (FU_DEVICE (self), instance_id); +} + +static void +fu_ifd_device_set_freg (FuIfdDevice *self, guint32 freg) +{ + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + guint32 freg_base = FU_IFD_FREG_BASE (freg); + guint32 freg_limt = FU_IFD_FREG_LIMIT (freg); + guint32 freg_size = (freg_limt - freg_base) + 1; + + priv->offset = freg_base; + fu_device_set_firmware_size (FU_DEVICE (self), freg_size); +} + +void +fu_ifd_device_set_access (FuIfdDevice *self, FuIfdRegion region, FuIfdAccess access) +{ + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + priv->access[region] = access; +} + +static gboolean +fu_ifd_device_open (FuDevice *device, GError **error) +{ + return fu_device_open (fu_device_get_parent (device), error); +} + +static gboolean +fu_ifd_device_close (FuDevice *device, GError **error) +{ + return fu_device_close (fu_device_get_parent (device), error); +} + +static void +fu_ifd_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuIfdDevice *self = FU_IFD_DEVICE (device); + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + + fu_common_string_append_kv (str, idt, "Region", + fu_ifd_region_to_string (priv->region)); + fu_common_string_append_kx (str, idt, "Offset", priv->offset); + + for (guint i = 0; i < FU_IFD_REGION_MAX; i++) { + g_autofree gchar *title = NULL; + if (priv->access[i] == FU_IFD_ACCESS_NONE) + continue; + title = g_strdup_printf ("Access[%s]", fu_ifd_region_to_string (i)); + fu_common_string_append_kv (str, idt, title, + fu_ifd_access_to_string (priv->access[i])); + } + +} + +static GBytes * +fu_ifd_device_dump_firmware (FuDevice *device, GError **error) +{ + FuIfdDevice *self = FU_IFD_DEVICE (device); + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + FuDevice *parent = fu_device_get_parent (device); + guint64 total_size = fu_device_get_firmware_size_max (device); + return fu_intel_spi_device_dump (FU_INTEL_SPI_DEVICE (parent), + device, + priv->offset, + total_size, + error); +} + +static FuFirmware * +fu_ifd_device_read_firmware (FuDevice *device, GError **error) +{ + FuIfdDevice *self = FU_IFD_DEVICE (device); + FuIfdDevicePrivate *priv = GET_PRIVATE (self); + g_autoptr(FuFirmware) firmware = fu_ifd_image_new (); + g_autoptr(GBytes) blob = NULL; + + blob = fu_ifd_device_dump_firmware (device, error); + if (blob == NULL) + return NULL; + if (priv->region == FU_IFD_REGION_BIOS) + firmware = fu_ifd_bios_new (); + else + firmware = fu_ifd_image_new (); + if (!fu_firmware_parse (firmware, blob, FWUPD_INSTALL_FLAG_NONE, error)) + return NULL; + return g_steal_pointer (&firmware); +} + +static void +fu_ifd_device_init (FuIfdDevice *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"); +} + +static void +fu_ifd_device_class_init (FuIfdDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->to_string = fu_ifd_device_to_string; + klass_device->dump_firmware = fu_ifd_device_dump_firmware; + klass_device->read_firmware = fu_ifd_device_read_firmware; + klass_device->open = fu_ifd_device_open; + klass_device->close = fu_ifd_device_close; +} + +FuDevice * +fu_ifd_device_new (FuIfdRegion region, guint32 freg) +{ + FuIfdDevice *self = FU_IFD_DEVICE (g_object_new (FU_TYPE_IFD_DEVICE, NULL)); + fu_ifd_device_set_region (self, region); + fu_ifd_device_set_freg (self, freg); + return FU_DEVICE (self); +} diff --git a/plugins/intel-spi/fu-ifd-device.h b/plugins/intel-spi/fu-ifd-device.h new file mode 100644 index 000000000..c399a704e --- /dev/null +++ b/plugins/intel-spi/fu-ifd-device.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-device.h" +#include "fu-ifd-common.h" + +#define FU_TYPE_IFD_DEVICE (fu_ifd_device_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuIfdDevice, fu_ifd_device, FU, IFD_DEVICE, FuDevice) + +struct _FuIfdDeviceClass +{ + FuDeviceClass parent_class; +}; + +FuDevice *fu_ifd_device_new (FuIfdRegion region, + guint32 freg); +void fu_ifd_device_set_access (FuIfdDevice *self, + FuIfdRegion region, + FuIfdAccess access); diff --git a/plugins/intel-spi/fu-ifd-firmware.c b/plugins/intel-spi/fu-ifd-firmware.c index 9fab3a006..6649a98c5 100644 --- a/plugins/intel-spi/fu-ifd-firmware.c +++ b/plugins/intel-spi/fu-ifd-firmware.c @@ -204,8 +204,6 @@ fu_ifd_firmware_parse (FuFirmware *firmware, guint32 freg_size = (freg_limt - freg_base) + 1; g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) contents = NULL; - guint8 bit_r = 0; - guint8 bit_w = 0; /* invalid */ if (freg_base > freg_limt) @@ -230,35 +228,10 @@ fu_ifd_firmware_parse (FuFirmware *firmware, fu_firmware_add_image (firmware, img); /* is writable by anything other than the region itself */ - if (priv->is_skylake) { - for (FuIfdRegion r = 1; r <= 3; r++) { - bit_r = (priv->flash_master[r] >> (i + 8)) & 0b1; - bit_w = (priv->flash_master[r] >> (i + 20)) & 0b1; - fu_ifd_image_set_access (FU_IFD_IMAGE (img), r, - (bit_r ? FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | - (bit_w ? FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE)); - } - } else { - if (i == FU_IFD_REGION_DESC) { - bit_r = 16; - bit_w = 24; - } else if (i == FU_IFD_REGION_BIOS) { - bit_r = 17; - bit_w = 25; - } else if (i == FU_IFD_REGION_ME) { - bit_r = 18; - bit_w = 26; - } else if (i == FU_IFD_REGION_GBE) { - bit_r = 19; - bit_w = 27; - } - for (FuIfdRegion r = 1; r <= 3 && bit_r != 0; r++) { - fu_ifd_image_set_access (FU_IFD_IMAGE (img), r, - ((priv->flash_master[r] >> bit_r) & 0b1 ? - FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | - ((priv->flash_master[r] >> bit_w) & 0b1 ? - FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE)); - } + for (FuIfdRegion r = 1; r <= 3; r++) { + FuIfdAccess acc; + acc = fu_ifd_region_to_access (i, priv->flash_master[r], priv->is_skylake); + fu_ifd_image_set_access (FU_IFD_IMAGE (img), r, acc); } } diff --git a/plugins/intel-spi/fu-intel-spi-common.c b/plugins/intel-spi/fu-intel-spi-common.c new file mode 100644 index 000000000..1c26910dc --- /dev/null +++ b/plugins/intel-spi/fu-intel-spi-common.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: GPL-2+ + */ + +#include "config.h" + +#include "fu-intel-spi-common.h" + +FuIntelSpiKind +fu_intel_spi_kind_from_string (const gchar *kind) +{ + if (g_strcmp0 (kind, "ich9") == 0) + return FU_INTEL_SPI_KIND_ICH9; + if (g_strcmp0 (kind, "pch100") == 0) + return FU_INTEL_SPI_KIND_PCH100; + if (g_strcmp0 (kind, "apl") == 0) + return FU_INTEL_SPI_KIND_APL; + if (g_strcmp0 (kind, "c620") == 0) + return FU_INTEL_SPI_KIND_C620; + if (g_strcmp0 (kind, "ich0") == 0) + return FU_INTEL_SPI_KIND_ICH0; + if (g_strcmp0 (kind, "ich2345") == 0) + return FU_INTEL_SPI_KIND_ICH2345; + if (g_strcmp0 (kind, "ich6") == 0) + return FU_INTEL_SPI_KIND_ICH6; + if (g_strcmp0 (kind, "pch300") == 0) + return FU_INTEL_SPI_KIND_PCH300; + if (g_strcmp0 (kind, "pch400") == 0) + return FU_INTEL_SPI_KIND_PCH400; + if (g_strcmp0 (kind, "poulsbo") == 0) + return FU_INTEL_SPI_KIND_POULSBO; + return FU_INTEL_SPI_KIND_UNKNOWN; +} + +const gchar * +fu_intel_spi_kind_to_string (FuIntelSpiKind kind) +{ + if (kind == FU_INTEL_SPI_KIND_ICH9) + return "ich9"; + if (kind == FU_INTEL_SPI_KIND_PCH100) + return "pch100"; + if (kind == FU_INTEL_SPI_KIND_APL) + return "apl"; + if (kind == FU_INTEL_SPI_KIND_C620) + return "c620"; + if (kind == FU_INTEL_SPI_KIND_ICH0) + return "ich0"; + if (kind == FU_INTEL_SPI_KIND_ICH2345) + return "ich2345"; + if (kind == FU_INTEL_SPI_KIND_ICH6) + return "ich6"; + if (kind == FU_INTEL_SPI_KIND_PCH300) + return "pch300"; + if (kind == FU_INTEL_SPI_KIND_PCH400) + return "pch400"; + if (kind == FU_INTEL_SPI_KIND_POULSBO) + return "poulsbo"; + return NULL; +} + +guint16 +fu_mmio_read16 (gconstpointer addr, goffset offset) +{ + addr = (guint8 *) addr + offset; + return *(volatile const guint16 *) addr; +} + +guint32 +fu_mmio_read32 (gconstpointer addr, goffset offset) +{ + addr = (guint8 *) addr + offset; + return *(volatile const guint32 *) addr; +} + +void +fu_mmio_write16 (gpointer addr, goffset offset, guint16 val) +{ + addr = (guint8 *) addr + offset; + *(volatile guint16 *) addr = val; +} + +void +fu_mmio_write32 (gpointer addr, goffset offset, guint32 val) +{ + addr = (guint8 *) addr + offset; + *(volatile guint32 *) addr = val; +} + +guint32 +fu_mmio_read32_le (gconstpointer addr, goffset offset) +{ + return GUINT32_FROM_LE (fu_mmio_read32 (addr, offset)); +} + +void +fu_mmio_write32_le (gpointer addr, goffset offset, guint32 val) +{ + fu_mmio_write32 (addr, offset, GUINT32_TO_LE (val)); +} diff --git a/plugins/intel-spi/fu-intel-spi-common.h b/plugins/intel-spi/fu-intel-spi-common.h new file mode 100644 index 000000000..2eee1b1ac --- /dev/null +++ b/plugins/intel-spi/fu-intel-spi-common.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define ICH9_REG_BFPR 0x00 +#define ICH9_REG_HSFS 0x04 +#define ICH9_REG_HSFC 0x06 +#define ICH9_REG_FADDR 0x08 +#define ICH9_REG_RESRVD 0x0C +#define ICH9_REG_FDATA0 0x10 +#define ICH9_REG_FDATAN 0x14 + +#define ICH9_REG_FRAP 0x50 +#define ICH9_REG_FREG0 0x54 +#define ICH9_REG_PR0 0x74 +#define ICH9_REG_FDOC 0xB0 +#define ICH9_REG_FDOD 0xB4 + +#define PCH100_REG_FDOC 0xB4 +#define PCH100_REG_FDOD 0xB8 +#define PCH100_REG_FPR0 0x84 +#define PCH100_REG_GPR0 0x98 + +#define PCH100_FADDR_FLA 0x07ffffff + +#define PCH100_HSFC_FCYCLE (0xf << 1) + +#define FDOC_FDSI (0x3F << 2) +#define FDOC_FDSS (0x03 << 12) + +#define HSFS_FDONE (0x01 << 0) +#define HSFS_FCERR (0x01 << 1) +#define HSFS_AEL (0x01 << 2) +#define HSFS_BERASE (0x03 << 3) +#define HSFS_SCIP (0x01 << 5) +#define HSFS_FDOPSS (0x01 << 13) +#define HSFS_FDV (0x01 << 14) +#define HSFS_FLOCKDN (0x01 << 15) + +#define HSFC_FGO (0x01 << 0) +#define HSFC_FCYCLE (0x03 << 1) +#define HSFC_FDBC (0x3f << 8) +#define HSFC_SME (0x01 << 15) + +typedef enum { + FU_INTEL_SPI_KIND_UNKNOWN, + FU_INTEL_SPI_KIND_APL, + FU_INTEL_SPI_KIND_C620, + FU_INTEL_SPI_KIND_ICH0, + FU_INTEL_SPI_KIND_ICH2345, + FU_INTEL_SPI_KIND_ICH6, + FU_INTEL_SPI_KIND_ICH9, + FU_INTEL_SPI_KIND_PCH100, + FU_INTEL_SPI_KIND_PCH300, + FU_INTEL_SPI_KIND_PCH400, + FU_INTEL_SPI_KIND_POULSBO, + FU_INTEL_SPI_KIND_LAST +} FuIntelSpiKind; + +FuIntelSpiKind fu_intel_spi_kind_from_string (const gchar *kind); +const gchar *fu_intel_spi_kind_to_string (FuIntelSpiKind kind); + +guint16 fu_mmio_read16 (gconstpointer addr, + goffset offset); +void fu_mmio_write16 (gpointer addr, + goffset offset, + guint16 val); + +guint32 fu_mmio_read32 (gconstpointer addr, + goffset offset); +void fu_mmio_write32 (gpointer addr, + goffset offset, + guint32 val); + +guint32 fu_mmio_read32_le (gconstpointer addr, + goffset offset); +void fu_mmio_write32_le (gpointer addr, + goffset offset, + guint32 val); diff --git a/plugins/intel-spi/fu-intel-spi-device.c b/plugins/intel-spi/fu-intel-spi-device.c new file mode 100644 index 000000000..d277b32e1 --- /dev/null +++ b/plugins/intel-spi/fu-intel-spi-device.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: GPL-2+ + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#include "fu-device-locker.h" +#include "fu-plugin-vfuncs.h" + +#include "fu-pci-device.h" + +#include "fu-intel-spi-common.h" +#include "fu-intel-spi-device.h" + +#include "fu-ifd-common.h" +#include "fu-ifd-device.h" +#include "fu-ifd-firmware.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 + +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, + "failed to open /dev/mem: %s", + strerror (errno)); + 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, + "failed to open mmap SPIBAR: %s", + strerror (errno)); + 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, + "failed to unmap spibar: %s", + strerror (errno)); + 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); + 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_custom_flag (device, "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"); +} + +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; +} diff --git a/plugins/intel-spi/fu-intel-spi-device.h b/plugins/intel-spi/fu-intel-spi-device.h new file mode 100644 index 000000000..736b7d27a --- /dev/null +++ b/plugins/intel-spi/fu-intel-spi-device.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-plugin.h" + +#define FU_TYPE_INTEL_SPI_DEVICE (fu_intel_spi_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuIntelSpiDevice, fu_intel_spi_device, FU, INTEL_SPI_DEVICE, FuDevice) + +GBytes *fu_intel_spi_device_dump (FuIntelSpiDevice *self, + FuDevice *device, + guint32 offset, + guint32 length, + GError **error); diff --git a/plugins/intel-spi/fu-pci-device.c b/plugins/intel-spi/fu-pci-device.c new file mode 100644 index 000000000..6d38c0f37 --- /dev/null +++ b/plugins/intel-spi/fu-pci-device.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include + +#include "fu-common.h" + +#include "fu-pci-device.h" + +typedef struct { + FuDevice parent_instance; + guint32 bus; + guint32 dev; + guint32 fun; +} FuPciDevicePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (FuPciDevice, fu_pci_device, FU_TYPE_DEVICE) + +#define PCI_CONFIG_ADDRESS 0x0CF8 +#define PCI_CONFIG_DATA 0x0CFC + +#define GET_PRIVATE(o) (fu_pci_device_get_instance_private (o)) + +guint32 +fu_pci_device_read_config (FuPciDevice *self, guint32 addr) +{ + FuPciDevicePrivate *priv = GET_PRIVATE (self); + guint32 val = 0x80000000; + + /* we have to do this horrible port access as the PCI device is not + * visible to even the kernel as the vendor ID is set as 0xFFFF */ + val |= priv->bus << 16; + val |= priv->dev << 11; + val |= priv->fun << 8; + val |= addr; + + /* we do this multiple times until we get the same result for every + * request as the port is shared between the kernel and all processes */ + for (guint cnt = 0; cnt < 0xff; cnt++) { + guint32 results[0x20] = { 0x0 }; + gboolean consistent = TRUE; + + /* fill up array */ + for (guint i = 0; i < G_N_ELEMENTS(results); i++) { + outl (val, PCI_CONFIG_ADDRESS); + results[i] = inl (PCI_CONFIG_DATA); + } + + /* check they are all the same */ + for (guint i = 0; i < G_N_ELEMENTS(results); i++) { + if (results[0] != results[i]) { + consistent = FALSE; + break; + } + } + + /* success */ + if (consistent) + return results[0]; + } + + /* failed */ + return G_MAXUINT32; +} + +static gboolean +fu_pci_device_open (FuDevice *device, GError **error) +{ + /* this will fail if userspace is locked down */ + if (ioperm (PCI_CONFIG_ADDRESS, 64, 1) < 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to open port: %s", + strerror (errno)); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_pci_device_close (FuDevice *device, GError **error) +{ + /* this might fail if userspace is locked down */ + if (ioperm (PCI_CONFIG_ADDRESS, 64, 0) < 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to open port: %s", + strerror (errno)); + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_pci_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuPciDevice *self = FU_PCI_DEVICE (device); + FuPciDevicePrivate *priv = GET_PRIVATE (self); + fu_common_string_append_kx (str, idt, "Bus", priv->bus); + fu_common_string_append_kx (str, idt, "Dev", priv->dev); + fu_common_string_append_kx (str, idt, "Fun", priv->fun); +} + +static gboolean +fu_pci_device_parse_bdf (FuPciDevice *self, const gchar *bdf, GError **error) +{ + FuPciDevicePrivate *priv = GET_PRIVATE (self); + guint64 bus_tmp; + guint64 dev_tmp; + guint64 fun_tmp; + g_auto(GStrv) split = g_strsplit_set (bdf, ":.", 0); + + /* parse the BDF */ + if (g_strv_length (split) != 3) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s invalid, expected '00:1f.5'", + bdf); + return FALSE; + } + bus_tmp = g_ascii_strtoull (split[0], NULL, 16); + dev_tmp = g_ascii_strtoull (split[1], NULL, 16); + fun_tmp = g_ascii_strtoull (split[2], NULL, 16); + if (bus_tmp > 0xff || dev_tmp > 0x1f || fun_tmp > 0x7) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s invalid, expected '00:1f.5'", + bdf); + return FALSE; + } + + /* success */ + priv->bus = bus_tmp; + priv->dev = dev_tmp; + priv->fun = fun_tmp; + return TRUE; +} + +static void +fu_pci_device_init (FuPciDevice *self) +{ + fu_device_set_physical_id (FU_DEVICE (self), "PCI"); +} + +static void +fu_pci_device_class_init (FuPciDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->to_string = fu_pci_device_to_string; + klass_device->open = fu_pci_device_open; + klass_device->close = fu_pci_device_close; +} + +FuDevice * +fu_pci_device_new (const gchar *bdf, GError **error) +{ + g_autoptr(FuPciDevice) self = FU_PCI_DEVICE (g_object_new (FU_TYPE_PCI_DEVICE, NULL)); + if (!fu_pci_device_parse_bdf (self, bdf, error)) + return NULL; + return FU_DEVICE (g_steal_pointer (&self)); +} diff --git a/plugins/intel-spi/fu-pci-device.h b/plugins/intel-spi/fu-pci-device.h new file mode 100644 index 000000000..5fb2d6cb3 --- /dev/null +++ b/plugins/intel-spi/fu-pci-device.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-device.h" + +#define FU_TYPE_PCI_DEVICE (fu_pci_device_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuPciDevice, fu_pci_device, FU, PCI_DEVICE, FuDevice) + +struct _FuPciDeviceClass +{ + FuDeviceClass parent_class; +}; + +FuDevice *fu_pci_device_new (const gchar *bdf, + GError **error); +guint32 fu_pci_device_read_config (FuPciDevice *self, + guint32 addr); diff --git a/plugins/intel-spi/fu-plugin-intel-spi.c b/plugins/intel-spi/fu-plugin-intel-spi.c index 576633bc1..c1d611fef 100644 --- a/plugins/intel-spi/fu-plugin-intel-spi.c +++ b/plugins/intel-spi/fu-plugin-intel-spi.c @@ -8,6 +8,8 @@ #include "fu-plugin-vfuncs.h" +#include "fu-intel-spi-device.h" + #include "fu-ifd-firmware.h" #include "fu-ifd-bios.h" @@ -19,6 +21,7 @@ void fu_plugin_init (FuPlugin *plugin) { + FuContext *ctx = fu_plugin_get_context (plugin); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_EFI_FIRMWARE_FILE); fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_EFI_FIRMWARE_FILESYSTEM); @@ -26,4 +29,23 @@ fu_plugin_init (FuPlugin *plugin) fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_EFI_FIRMWARE_VOLUME); fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_IFD_BIOS); fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_IFD_FIRMWARE); + fu_context_add_udev_subsystem (ctx, "pci"); + fu_context_add_quirk_key (ctx, "IntelSpiKind"); + fu_context_add_quirk_key (ctx, "IntelSpiBar"); + fu_context_add_quirk_key (ctx, "IntelSpiBarProxy"); + fu_context_add_quirk_key (ctx, "IntelSpiBiosCntl"); + fu_plugin_set_device_gtype (plugin, FU_TYPE_INTEL_SPI_DEVICE); +} + +gboolean +fu_plugin_startup (FuPlugin *plugin, GError **error) +{ + if (fu_common_kernel_locked_down ()) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "not supported when kernel locked down"); + return FALSE; + } + return TRUE; } diff --git a/plugins/intel-spi/generate-quirk.py b/plugins/intel-spi/generate-quirk.py new file mode 100755 index 000000000..78935beaa --- /dev/null +++ b/plugins/intel-spi/generate-quirk.py @@ -0,0 +1,76 @@ +#!/usr/bin/python3 +# pylint: disable=invalid-name,missing-docstring +# +# Copyright (C) 2021 Richard Hughes +# +# SPDX-License-Identifier: LGPL-2.1+ + +import sys + + +class Chipset: + def __init__(self, spibar=None, bios_cntl=0x0, spibar_proxy=None, flags=None): + + self.bios_cntl = bios_cntl + self.spibar_proxy = spibar_proxy + self.spibar = spibar + self.flags = flags + + +if __name__ == "__main__": + + if len(sys.argv) != 2: + print("required: /path/to/chipset_enable.c") + sys.exit(1) + + chipsets = { + "apl": Chipset(flags="PCH", bios_cntl=0xDC, spibar_proxy="00:0d.2"), + "c620": Chipset(flags="PCH", bios_cntl=0xDC, spibar_proxy="00:1f.5"), + "ich0": Chipset(flags="ICH", bios_cntl=0x4E), + "ich2345": Chipset(flags="ICH", bios_cntl=0x4E), + "ich6": Chipset(flags="ICH", bios_cntl=0xDC), + "pch100": Chipset(flags="PCH", bios_cntl=0xDC, spibar_proxy="00:1f.5"), + "pch300": Chipset(flags="PCH", bios_cntl=0xDC, spibar_proxy="00:1f.5"), + "pch400": Chipset(flags="PCH", bios_cntl=0xDC, spibar_proxy="00:1f.5"), + "poulsbo": Chipset(flags="ICH", bios_cntl=0xD8), + } + + devices = {"PCI\VEN_8086&DEV_A0A4": "pch100"} + + with open("intel-spi.quirk", "w") as out_f: + with open(sys.argv[1], "r") as in_f: + lines = in_f.read().split("\n") + for line in lines: + if line.find("0x8086") == -1: + continue + if line.find("Sample") != -1: + continue + for char in ["}", "{", '"', " ", "\t"]: + line = line.replace(char, "") + ven, dev, _, _, _, _, kind, _ = line.split(",") + + if kind.startswith("enable_flash_"): + kind = kind[13:] + if kind not in chipsets: + print("ignoring {}...".format(kind)) + continue + + devices["PCI\VEN_{}&DEV_{}".format(ven[2:], dev[2:].upper())] = kind + + for device in devices: + kind = devices[device] + out_f.write("[{}]\n".format(device)) + out_f.write("Plugin = intel_spi\n") + out_f.write("IntelSpiKind = {}\n\n".format(kind)) + + for kind in sorted(chipsets): + cs = chipsets[kind] + out_f.write("\n[INTEL_SPI_CHIPSET\\{}]\n".format(kind.upper())) + if cs.spibar: + out_f.write("IntelSpiBar = 0x{:x}\n".format(cs.spibar)) + if cs.spibar_proxy: + out_f.write("IntelSpiBarProxy = {}\n".format(cs.spibar_proxy)) + if cs.bios_cntl: + out_f.write("IntelSpiBiosCntl = 0x{:X}\n".format(cs.bios_cntl)) + if cs.flags: + out_f.write("Flags = {}\n".format(cs.flags)) diff --git a/plugins/intel-spi/intel-spi.quirk b/plugins/intel-spi/intel-spi.quirk new file mode 100644 index 000000000..2abb73551 --- /dev/null +++ b/plugins/intel-spi/intel-spi.quirk @@ -0,0 +1,401 @@ +[PCI\VEN_8086&DEV_A0A4] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_2410] +Plugin = intel_spi +IntelSpiKind = ich0 + +[PCI\VEN_8086&DEV_2420] +Plugin = intel_spi +IntelSpiKind = ich0 + +[PCI\VEN_8086&DEV_2440] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_244C] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_2450] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_2480] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_248C] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_24C0] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_24CC] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_24D0] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_25A1] +Plugin = intel_spi +IntelSpiKind = ich2345 + +[PCI\VEN_8086&DEV_2640] +Plugin = intel_spi +IntelSpiKind = ich6 + +[PCI\VEN_8086&DEV_2641] +Plugin = intel_spi +IntelSpiKind = ich6 + +[PCI\VEN_8086&DEV_2642] +Plugin = intel_spi +IntelSpiKind = ich6 + +[PCI\VEN_8086&DEV_2670] +Plugin = intel_spi +IntelSpiKind = ich6 + +[PCI\VEN_8086&DEV_8119] +Plugin = intel_spi +IntelSpiKind = poulsbo + +[PCI\VEN_8086&DEV_9D43] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D46] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D48] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D4B] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D4E] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D50] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D53] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D56] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D58] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_9D84] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_0284] +Plugin = intel_spi +IntelSpiKind = pch400 + +[PCI\VEN_8086&DEV_0285] +Plugin = intel_spi +IntelSpiKind = pch400 + +[PCI\VEN_8086&DEV_A143] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A144] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A145] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A146] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A147] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A148] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A149] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A14A] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A14D] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A14E] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A150] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A151] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A152] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A153] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A154] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A155] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A1A4] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C0] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C1] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C2] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C3] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C4] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C5] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C6] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C7] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C8] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1C9] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1CA] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1CB] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1CC] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A1CD] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A240] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A241] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A242] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A243] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A244] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A245] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A246] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A247] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A248] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A249] +Plugin = intel_spi +IntelSpiKind = c620 + +[PCI\VEN_8086&DEV_A2C4] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2C5] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2C6] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2C7] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2C8] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2C9] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_A2D2] +Plugin = intel_spi +IntelSpiKind = pch100 + +[PCI\VEN_8086&DEV_5AE8] +Plugin = intel_spi +IntelSpiKind = apl + +[PCI\VEN_8086&DEV_5AF0] +Plugin = intel_spi +IntelSpiKind = apl + +[PCI\VEN_8086&DEV_A303] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A304] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A305] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A306] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A308] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A309] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A30A] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A30C] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A30D] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_A30E] +Plugin = intel_spi +IntelSpiKind = pch300 + +[PCI\VEN_8086&DEV_3482] +Plugin = intel_spi +IntelSpiKind = pch300 + + +[INTEL_SPI_CHIPSET\APL] +IntelSpiBarProxy = 00:0d.2 +IntelSpiBiosCntl = 0xDC +Flags = PCH + +[INTEL_SPI_CHIPSET\C620] +IntelSpiBarProxy = 00:1f.5 +IntelSpiBiosCntl = 0xDC +Flags = PCH + +[INTEL_SPI_CHIPSET\ICH0] +IntelSpiBiosCntl = 0x4E +Flags = ICH + +[INTEL_SPI_CHIPSET\ICH2345] +IntelSpiBiosCntl = 0x4E +Flags = ICH + +[INTEL_SPI_CHIPSET\ICH6] +IntelSpiBiosCntl = 0xDC +Flags = ICH + +[INTEL_SPI_CHIPSET\PCH100] +IntelSpiBarProxy = 00:1f.5 +IntelSpiBiosCntl = 0xDC +Flags = PCH + +[INTEL_SPI_CHIPSET\PCH300] +IntelSpiBarProxy = 00:1f.5 +IntelSpiBiosCntl = 0xDC +Flags = PCH + +[INTEL_SPI_CHIPSET\PCH400] +IntelSpiBarProxy = 00:1f.5 +IntelSpiBiosCntl = 0xDC +Flags = PCH + +[INTEL_SPI_CHIPSET\POULSBO] +IntelSpiBiosCntl = 0xD8 +Flags = ICH diff --git a/plugins/intel-spi/meson.build b/plugins/intel-spi/meson.build index 118374a04..f36160659 100644 --- a/plugins/intel-spi/meson.build +++ b/plugins/intel-spi/meson.build @@ -1,6 +1,10 @@ if get_option('plugin_intel_spi') cargs = ['-DG_LOG_DOMAIN="FuPluginIntelSpi"'] +install_data(['intel-spi.quirk'], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + shared_module('fu_plugin_intel_spi', fu_hash, sources : [ @@ -12,8 +16,12 @@ shared_module('fu_plugin_intel_spi', 'fu-efi-firmware-volume.c', # fuzzing 'fu-ifd-bios.c', # fuzzing 'fu-ifd-common.c', # fuzzing + 'fu-ifd-device.c', 'fu-ifd-firmware.c', # fuzzing 'fu-ifd-image.c', # fuzzing + 'fu-intel-spi-common.c', + 'fu-intel-spi-device.c', + 'fu-pci-device.c', 'fu-plugin-intel-spi.c', ], include_directories : [ diff --git a/src/fu-security-attr.c b/src/fu-security-attr.c index 6be49519b..bb293e353 100644 --- a/src/fu-security-attr.c +++ b/src/fu-security-attr.c @@ -27,6 +27,10 @@ fu_security_attr_get_name (FwupdSecurityAttr *attr) /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return g_strdup (_("SPI BIOS region")); } + if (g_strcmp0 (appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR) == 0) { + /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ + return g_strdup (_("SPI BIOS Descriptor")); + } if (g_strcmp0 (appstream_id, FWUPD_SECURITY_ATTR_ID_ACPI_DMAR) == 0) { /* TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack */ return g_strdup (_("Pre-boot DMA protection"));