Add support for the PHAT table

On Alterlake and newer hardware the Platform Health Assessment Record
data can be used by the IHV to debug why a specific capsule update
failed. Any custom firmware loaded by the OEM can be identified and
used to further debug the root cause.
This commit is contained in:
Richard Hughes 2021-05-08 15:51:19 +01:00
parent 54d9de9290
commit 769cbbf8af
21 changed files with 1102 additions and 3 deletions

View File

@ -295,6 +295,7 @@ def _build(bld: Builder) -> None:
# plugins
for fzr in [
Fuzzer("acpi-phat", pattern="acpi-phat"),
Fuzzer("bcm57xx"),
Fuzzer("ccgx-dmc", srcdir="ccgx", globstr="ccgx-dmc*.bin"),
Fuzzer("ccgx", globstr="ccgx*.cyacd"),

View File

@ -401,6 +401,7 @@ done
%dir %{_libdir}/fwupd-plugins-3
%{_libdir}/fwupd-plugins-3/libfu_plugin_acpi_dmar.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_acpi_facp.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_acpi_phat.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_altos.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_amt.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_analogix.so

View File

@ -13,6 +13,7 @@ run_test()
run_test acpi-dmar-self-test
run_test acpi-facp-self-test
run_test acpi-phat-self-test
run_test ata-self-test
run_test nitrokey-self-test
run_test linux-swap-self-test

View File

@ -745,7 +745,7 @@ fwupd_guid_from_string (const gchar *guidstr,
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"is not valid format");
"GUID is not valid format");
return FALSE;
}
split = g_strsplit (guidstr, "-", 5);
@ -753,7 +753,7 @@ fwupd_guid_from_string (const gchar *guidstr,
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"is not valid format, no dashes");
"GUID is not valid format, no dashes");
return FALSE;
}
if (strlen (split[0]) != 8 && strlen (split[1]) != 4 &&
@ -762,7 +762,7 @@ fwupd_guid_from_string (const gchar *guidstr,
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"is not valid format, not GUID");
"GUID is not valid format, not GUID");
return FALSE;
}

View File

@ -0,0 +1,23 @@
Platform Health Assessment Table
================================
Introduction
------------
The PHAT is an ACPI table where a platform can expose health related telemetry
that may be useful for software running within the constraints of an OS.
These elements are typically going to encompass things that are likely otherwise
not enumerable during the OS runtime phase of operations, such as version of
pre-OS components.
The daemon includes some of the PHAT data in the report data sent to the LVFS
so that we can debug failures with the help of the IHV. This allows us to find
the root cause of the problem, and so we know what other OEMs may be affected.
See https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#platform-health-assessment-table
for more information.
External interface access
-------------------------
This plugin requires read access to `/sys/firmware/acpi/tables`.

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-common.h"
#include "fu-firmware-common.h"
#include "fu-acpi-phat.h"
#include "fu-acpi-phat-health-record.h"
struct _FuAcpiPhatHealthRecord {
FuFirmware parent_instance;
guint8 am_healthy;
gchar *guid;
gchar *device_path;
};
G_DEFINE_TYPE (FuAcpiPhatHealthRecord, fu_acpi_phat_health_record, FU_TYPE_FIRMWARE)
static void
fu_acpi_phat_health_record_export (FuFirmware *firmware,
FuFirmwareExportFlags flags,
XbBuilderNode *bn)
{
FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD (firmware);
if (self->guid != NULL)
fu_xmlb_builder_insert_kv (bn, "guid", self->guid);
if (self->device_path != NULL)
fu_xmlb_builder_insert_kv (bn, "device_path", self->device_path);
if (self->am_healthy != 0)
fu_xmlb_builder_insert_kx (bn, "am_healthy", self->am_healthy);
}
static gboolean
fu_acpi_phat_health_record_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD (firmware);
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
fwupd_guid_t guid = { 0x0 };
/* am healthy */
if (!fu_common_read_uint8_safe (buf, bufsz, 7, &self->am_healthy, error))
return FALSE;
/* device signature */
if (!fu_memcpy_safe ((guint8 *) &guid, sizeof(guid), 0x0, /* dst */
buf, bufsz, 8, /* src */
sizeof(guid), error))
return FALSE;
self->guid = fwupd_guid_to_string (&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN);
/* device path */
if (bufsz > 28) {
self->device_path = g_utf16_to_utf8 ((const gunichar2 *) (buf + 28),
(bufsz - 28) / 2,
NULL, NULL,
error);
if (self->device_path == NULL)
return FALSE;
}
/* success */
return TRUE;
}
static GBytes *
fu_acpi_phat_health_record_write (FuFirmware *firmware, GError **error)
{
FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD (firmware);
fwupd_guid_t guid = { 0x0 };
glong device_path_utf16sz = 0;
g_autofree gunichar2 *device_path_utf16 = NULL;
g_autoptr(GByteArray) buf = g_byte_array_new ();
/* convert device path ahead of time to get total record length */
if (self->device_path != NULL) {
device_path_utf16 = g_utf8_to_utf16 (self->device_path, -1,
NULL, &device_path_utf16sz,
error);
if (device_path_utf16 == NULL)
return NULL;
device_path_utf16sz *= 2;
}
/* data record */
fu_byte_array_append_uint16 (buf,
FU_ACPI_PHAT_RECORD_TYPE_HEALTH,
G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, 28 + device_path_utf16sz, G_LITTLE_ENDIAN);
fu_byte_array_append_uint8 (buf, fu_firmware_get_version_raw (firmware));
fu_byte_array_append_uint8 (buf, 0x00);
fu_byte_array_append_uint8 (buf, 0x00);
fu_byte_array_append_uint8 (buf, self->am_healthy);
/* device signature */
if (self->guid != NULL) {
if (!fwupd_guid_from_string (self->guid, &guid,
FWUPD_GUID_FLAG_MIXED_ENDIAN,
error))
return NULL;
}
g_byte_array_append (buf, guid, sizeof(guid));
/* device-specific data unsupported */
fu_byte_array_append_uint32 (buf, 0x0, G_LITTLE_ENDIAN);
/* device path */
if (self->device_path != NULL) {
g_byte_array_append (buf,
(const guint8 *) device_path_utf16,
device_path_utf16sz);
}
/* success */
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static void
fu_acpi_phat_health_record_set_guid (FuAcpiPhatHealthRecord *self,
const gchar *guid)
{
g_free (self->guid);
self->guid = g_strdup (guid);
}
static void
fu_acpi_phat_health_record_set_device_path (FuAcpiPhatHealthRecord *self,
const gchar *device_path)
{
g_free (self->device_path);
self->device_path = g_strdup (device_path);
}
static gboolean
fu_acpi_phat_health_record_build (FuFirmware *firmware, XbNode *n, GError **error)
{
FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD (firmware);
const gchar *tmp;
guint64 tmp64;
/* optional properties */
tmp = xb_node_query_text (n, "device_path", NULL);
if (tmp != NULL)
fu_acpi_phat_health_record_set_device_path (self, tmp);
tmp = xb_node_query_text (n, "guid", NULL);
if (tmp != NULL)
fu_acpi_phat_health_record_set_guid (self, tmp);
tmp64 = xb_node_query_text_as_uint (n, "am_healthy", NULL);
if (tmp64 > G_MAXUINT8) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"am_healthy value invalid, got 0x%x",
(guint) tmp64);
return FALSE;
}
self->am_healthy = (guint8) tmp64;
/* success */
return TRUE;
}
static void
fu_acpi_phat_health_record_init (FuAcpiPhatHealthRecord *self)
{
}
static void
fu_acpi_phat_health_record_finalize (GObject *object)
{
FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD (object);
g_free (self->guid);
g_free (self->device_path);
G_OBJECT_CLASS (fu_acpi_phat_health_record_parent_class)->finalize (object);
}
static void
fu_acpi_phat_health_record_class_init (FuAcpiPhatHealthRecordClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
object_class->finalize = fu_acpi_phat_health_record_finalize;
klass_firmware->parse = fu_acpi_phat_health_record_parse;
klass_firmware->write = fu_acpi_phat_health_record_write;
klass_firmware->export = fu_acpi_phat_health_record_export;
klass_firmware->build = fu_acpi_phat_health_record_build;
}
FuFirmware *
fu_acpi_phat_health_record_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_ACPI_PHAT_HEALTH_RECORD, NULL));
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-firmware.h"
#define FU_TYPE_ACPI_PHAT_HEALTH_RECORD (fu_acpi_phat_health_record_get_type ())
G_DECLARE_FINAL_TYPE (FuAcpiPhatHealthRecord, fu_acpi_phat_health_record, FU, ACPI_PHAT_HEALTH_RECORD, FuFirmware)
FuFirmware *fu_acpi_phat_health_record_new (void);

View File

@ -0,0 +1,180 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-common.h"
#include "fu-firmware-common.h"
#include "fu-acpi-phat-version-element.h"
struct _FuAcpiPhatVersionElement {
FuFirmware parent_instance;
gchar *guid;
gchar *producer_id;
};
G_DEFINE_TYPE (FuAcpiPhatVersionElement, fu_acpi_phat_version_element, FU_TYPE_FIRMWARE)
static void
fu_acpi_phat_version_element_export (FuFirmware *firmware,
FuFirmwareExportFlags flags,
XbBuilderNode *bn)
{
FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT (firmware);
if (self->guid != NULL)
fu_xmlb_builder_insert_kv (bn, "guid", self->guid);
if (self->producer_id != NULL)
fu_xmlb_builder_insert_kv (bn, "producer_id", self->producer_id);
}
static gboolean
fu_acpi_phat_version_element_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT (firmware);
fwupd_guid_t component_id = { 0x0 };
gchar producer_id[4] = { '\0' };
gsize bufsz = 0;
guint64 version_value = 0;
g_autofree gchar *guid_str = NULL;
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
/* hardcoded */
fu_firmware_set_size (firmware, 28);
if (!fu_memcpy_safe ((guint8 *) &component_id, sizeof(component_id), 0x0, /* dst */
buf, bufsz, 0, /* src */
sizeof(component_id), error))
return FALSE;
self->guid = fwupd_guid_to_string (&component_id, FWUPD_GUID_FLAG_MIXED_ENDIAN);
if (!fu_common_read_uint64_safe (buf, bufsz, 16, &version_value,
G_LITTLE_ENDIAN, error))
return FALSE;
fu_firmware_set_version_raw (firmware, version_value);
if (!fu_memcpy_safe ((guint8 *) producer_id, sizeof(producer_id), 0x0, /* dst */
buf, bufsz, 24, /* src */
sizeof(producer_id), error))
return FALSE;
if (memcmp (producer_id, "\0\0\0\0", 4) == 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"PHAT version element invalid");
return FALSE;
}
self->producer_id = fu_common_strsafe ((const gchar *) producer_id,
sizeof(producer_id));
return TRUE;
}
static GBytes *
fu_acpi_phat_version_element_write (FuFirmware *firmware, GError **error)
{
FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT (firmware);
fwupd_guid_t guid = { 0x0 };
guint8 producer_id[4] = { '\0' };
g_autoptr(GByteArray) buf = g_byte_array_new ();
/* component ID */
if (self->guid != NULL) {
if (!fwupd_guid_from_string (self->guid, &guid,
FWUPD_GUID_FLAG_MIXED_ENDIAN,
error))
return NULL;
}
g_byte_array_append (buf, guid, sizeof(guid));
/* version value */
fu_byte_array_append_uint64 (buf,
fu_firmware_get_version_raw (firmware),
G_LITTLE_ENDIAN);
/* producer ID */
if (self->producer_id != NULL) {
gsize producer_idsz = strlen (self->producer_id);
if (!fu_memcpy_safe (producer_id, sizeof(producer_id), 0x0, /* dst */
(const guint8 *) self->producer_id, producer_idsz, 0x0, /* src */
producer_idsz, error))
return NULL;
}
g_byte_array_append (buf, producer_id, sizeof(producer_id));
/* success */
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static void
fu_acpi_phat_version_element_set_guid (FuAcpiPhatVersionElement *self,
const gchar *guid)
{
g_free (self->guid);
self->guid = g_strdup (guid);
}
static void
fu_acpi_phat_version_element_set_producer_id (FuAcpiPhatVersionElement *self,
const gchar *producer_id)
{
g_free (self->producer_id);
self->producer_id = g_strdup (producer_id);
}
static gboolean
fu_acpi_phat_version_element_build (FuFirmware *firmware, XbNode *n, GError **error)
{
FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT (firmware);
const gchar *tmp;
/* optional properties */
tmp = xb_node_query_text (n, "producer_id", NULL);
if (tmp != NULL)
fu_acpi_phat_version_element_set_producer_id (self, tmp);
tmp = xb_node_query_text (n, "guid", NULL);
if (tmp != NULL)
fu_acpi_phat_version_element_set_guid (self, tmp);
/* success */
return TRUE;
}
static void
fu_acpi_phat_version_element_init (FuAcpiPhatVersionElement *self)
{
}
static void
fu_acpi_phat_version_element_finalize (GObject *object)
{
FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT (object);
g_free (self->guid);
g_free (self->producer_id);
G_OBJECT_CLASS (fu_acpi_phat_version_element_parent_class)->finalize (object);
}
static void
fu_acpi_phat_version_element_class_init (FuAcpiPhatVersionElementClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
object_class->finalize = fu_acpi_phat_version_element_finalize;
klass_firmware->parse = fu_acpi_phat_version_element_parse;
klass_firmware->write = fu_acpi_phat_version_element_write;
klass_firmware->export = fu_acpi_phat_version_element_export;
klass_firmware->build = fu_acpi_phat_version_element_build;
}
FuFirmware *
fu_acpi_phat_version_element_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_ACPI_PHAT_VERSION_ELEMENT, NULL));
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-firmware.h"
#define FU_TYPE_ACPI_PHAT_VERSION_ELEMENT (fu_acpi_phat_version_element_get_type ())
G_DECLARE_FINAL_TYPE (FuAcpiPhatVersionElement, fu_acpi_phat_version_element, FU, ACPI_PHAT_VERSION_ELEMENT, FuFirmware)
FuFirmware *fu_acpi_phat_version_element_new (void);

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-common.h"
#include "fu-firmware-common.h"
#include "fu-acpi-phat.h"
#include "fu-acpi-phat-version-element.h"
#include "fu-acpi-phat-version-record.h"
struct _FuAcpiPhatVersionRecord {
FuFirmware parent_instance;
};
G_DEFINE_TYPE (FuAcpiPhatVersionRecord, fu_acpi_phat_version_record, FU_TYPE_FIRMWARE)
static gboolean
fu_acpi_phat_version_record_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
gsize bufsz = 0;
gsize offset = 0;
guint32 record_count = 0;
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
if (!fu_common_read_uint32_safe (buf, bufsz, offset + 8, &record_count,
G_LITTLE_ENDIAN, error))
return FALSE;
for (guint32 i = 0; i < record_count; i++) {
g_autoptr(FuFirmware) firmware_tmp = fu_acpi_phat_version_element_new ();
g_autoptr(GBytes) fw_tmp = NULL;
fw_tmp = fu_common_bytes_new_offset (fw, offset + 12, 28, error);
if (fw_tmp == NULL)
return FALSE;
fu_firmware_set_offset (firmware_tmp, offset + 12);
if (!fu_firmware_parse (firmware_tmp, fw_tmp, flags, error))
return FALSE;
fu_firmware_add_image (firmware, firmware_tmp);
offset += fu_firmware_get_size (firmware_tmp);
}
return TRUE;
}
static GBytes *
fu_acpi_phat_version_record_write (FuFirmware *firmware, GError **error)
{
g_autoptr(GByteArray) buf = g_byte_array_new ();
g_autoptr(GByteArray) buf2 = g_byte_array_new ();
g_autoptr(GPtrArray) images = fu_firmware_get_images (firmware);
/* write each element so we get the image size */
for (guint i = 0; i < images->len; i++) {
FuFirmware *img = g_ptr_array_index (images, i);
g_autoptr(GBytes) blob = fu_firmware_write (img, error);
if (blob == NULL)
return NULL;
fu_byte_array_append_bytes (buf2, blob);
}
/* data record */
fu_byte_array_append_uint16 (buf,
FU_ACPI_PHAT_RECORD_TYPE_VERSION,
G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, 12 + buf2->len, G_LITTLE_ENDIAN);
fu_byte_array_append_uint8 (buf, fu_firmware_get_version_raw (firmware));
fu_byte_array_append_uint8 (buf, 0x00);
fu_byte_array_append_uint8 (buf, 0x00);
fu_byte_array_append_uint8 (buf, 0x00);
fu_byte_array_append_uint32 (buf, images->len, G_LITTLE_ENDIAN);
/* element data */
g_byte_array_append (buf, buf2->data, buf2->len);
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static void
fu_acpi_phat_version_record_init (FuAcpiPhatVersionRecord *self)
{
}
static void
fu_acpi_phat_version_record_class_init (FuAcpiPhatVersionRecordClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
klass_firmware->parse = fu_acpi_phat_version_record_parse;
klass_firmware->write = fu_acpi_phat_version_record_write;
}
FuFirmware *
fu_acpi_phat_version_record_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_ACPI_PHAT_VERSION_RECORD, NULL));
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-firmware.h"
#define FU_TYPE_ACPI_PHAT_VERSION_RECORD (fu_acpi_phat_version_record_get_type ())
G_DECLARE_FINAL_TYPE (FuAcpiPhatVersionRecord, fu_acpi_phat_version_record, FU, ACPI_PHAT_VERSION_RECORD, FuFirmware)
FuFirmware *fu_acpi_phat_version_record_new (void);

View File

@ -0,0 +1,327 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-common.h"
#include "fu-firmware-common.h"
#include "fu-acpi-phat.h"
#include "fu-acpi-phat-health-record.h"
#include "fu-acpi-phat-version-record.h"
struct _FuAcpiPhat {
FuFirmware parent_instance;
gchar *oem_id;
};
G_DEFINE_TYPE (FuAcpiPhat, fu_acpi_phat, FU_TYPE_FIRMWARE)
static void
fu_acpi_phat_export (FuFirmware *firmware,
FuFirmwareExportFlags flags,
XbBuilderNode *bn)
{
FuAcpiPhat *self = FU_ACPI_PHAT (firmware);
if (self->oem_id != NULL)
fu_xmlb_builder_insert_kv (bn, "oem_id", self->oem_id);
}
static gboolean
fu_acpi_phat_record_parse (FuFirmware *firmware,
GBytes *fw,
gsize *offset,
FwupdInstallFlags flags,
GError **error)
{
gsize bufsz = 0;
guint16 record_length = 0;
guint16 record_type = 0;
guint8 revision;
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
g_autoptr(FuFirmware) firmware_rcd = NULL;
/* common header */
if (!fu_common_read_uint16_safe (buf, bufsz, *offset, &record_type,
G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint16_safe (buf, bufsz, *offset + 2, &record_length,
G_LITTLE_ENDIAN, error))
return FALSE;
if (record_length < 5) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"PHAT record length invalid, got 0x%x",
record_length);
return FALSE;
}
if (!fu_common_read_uint8_safe (buf, bufsz, *offset + 4, &revision, error))
return FALSE;
/* firmware version data record */
if (record_type == FU_ACPI_PHAT_RECORD_TYPE_VERSION) {
firmware_rcd = fu_acpi_phat_version_record_new ();
} else if (record_type == FU_ACPI_PHAT_RECORD_TYPE_HEALTH) {
firmware_rcd = fu_acpi_phat_health_record_new ();
}
/* supported record type */
if (firmware_rcd != NULL) {
g_autoptr(GBytes) fw_tmp = NULL;
fw_tmp = fu_common_bytes_new_offset (fw, *offset, record_length, error);
if (fw_tmp == NULL)
return FALSE;
fu_firmware_set_size (firmware_rcd, record_length);
fu_firmware_set_offset (firmware_rcd, *offset);
fu_firmware_set_version_raw (firmware_rcd, revision);
if (!fu_firmware_parse (firmware_rcd, fw_tmp, flags, error))
return FALSE;
fu_firmware_add_image (firmware, firmware_rcd);
}
*offset += record_length;
return TRUE;
}
static void
fu_acpi_phat_set_oem_id (FuAcpiPhat *self, const gchar *oem_id)
{
g_free (self->oem_id);
self->oem_id = g_strdup (oem_id);
}
static gboolean
fu_acpi_phat_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuAcpiPhat *self = FU_ACPI_PHAT (firmware);
gchar oem_id[6] = { '\0' };
gchar oem_table_id[8] = { '\0' };
gchar signature[4] = { '\0' };
gsize bufsz = 0;
guint32 length = 0;
guint32 oem_revision = 0;
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
g_autofree gchar *oem_id_safe = NULL;
g_autofree gchar *oem_table_id_safe = NULL;
/* parse table */
if (!fu_memcpy_safe ((guint8 *) signature, sizeof(signature), 0x0, /* dst */
buf, bufsz, 0x00, /* src */
sizeof(signature), error))
return FALSE;
if (memcmp (signature, "PHAT", 4) != 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"Not a PHAT table, got %s",
signature);
return FALSE;
}
if (!fu_common_read_uint32_safe (buf, bufsz, 4, &length, G_LITTLE_ENDIAN, error))
return FALSE;
if (bufsz < length) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"PHAT table invalid size, got 0x%x, expected 0x%x",
(guint) bufsz, length);
return FALSE;
}
/* spec revision */
if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
guint8 revision = 0;
if (!fu_common_read_uint8_safe (buf, bufsz, 8, &revision, error))
return FALSE;
if (revision != FU_ACPI_PHAT_REVISION) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"PHAT table revision invalid, got 0x%x, expected 0x%x",
revision, (guint) FU_ACPI_PHAT_REVISION);
return FALSE;
}
}
/* verify checksum */
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) {
guint8 checksum = 0;
for (gsize i = 0; i < length; i++)
checksum += buf[i];
if (checksum != 0x00) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"PHAT table checksum invalid, got 0x%x",
checksum);
return FALSE;
}
}
/* OEMID */
if (!fu_memcpy_safe ((guint8 *) oem_id, sizeof(oem_id), 0x0, /* dst */
buf, bufsz, 10, /* src */
sizeof(oem_id), error))
return FALSE;
oem_id_safe = fu_common_strsafe ((const gchar *) oem_id, sizeof(oem_id));
fu_acpi_phat_set_oem_id (self, oem_id_safe);
/* OEM Table ID */
if (!fu_memcpy_safe ((guint8 *) oem_table_id, sizeof(oem_table_id), 0x0, /* dst */
buf, bufsz, 16, /* src */
sizeof(oem_table_id), error))
return FALSE;
oem_table_id_safe = fu_common_strsafe ((const gchar *) oem_table_id, sizeof(oem_table_id));
fu_firmware_set_id (firmware, oem_table_id_safe);
if (!fu_common_read_uint32_safe (buf, bufsz, 24, &oem_revision, G_LITTLE_ENDIAN, error))
return FALSE;
fu_firmware_set_version_raw (firmware, oem_revision);
/* platform telemetry records */
for (gsize offset = 36; offset < length;) {
if (!fu_acpi_phat_record_parse (firmware, fw, &offset, flags, error))
return FALSE;
}
/* success */
return TRUE;
}
static GBytes *
fu_acpi_phat_write (FuFirmware *firmware, GError **error)
{
FuAcpiPhat *self = FU_ACPI_PHAT (firmware);
const gchar *oem_table_id_str = fu_firmware_get_id (firmware);
guint8 checksum = 0;
guint8 creator_id[] = { 'F', 'W', 'U', 'P' };
guint8 creator_rev[] = { '0', '0', '0', '0' };
guint8 oem_id[6] = { '\0' };
guint8 oem_table_id[8] = { '\0' };
guint8 signature[] = { 'P', 'H', 'A', 'T' };
g_autoptr(GByteArray) buf = g_byte_array_new ();
g_autoptr(GByteArray) buf2 = g_byte_array_new ();
g_autoptr(GPtrArray) images = fu_firmware_get_images (firmware);
/* write each image so we get the total size */
for (guint i = 0; i < images->len; i++) {
FuFirmware *img = g_ptr_array_index (images, i);
g_autoptr(GBytes) blob = fu_firmware_write (img, error);
if (blob == NULL)
return NULL;
fu_byte_array_append_bytes (buf2, blob);
}
/* header */
g_byte_array_append (buf, signature, sizeof(signature));
fu_byte_array_append_uint32 (buf, buf2->len + 36, G_LITTLE_ENDIAN);
fu_byte_array_append_uint8 (buf, fu_firmware_get_version_raw (firmware));
fu_byte_array_append_uint8 (buf, 0xFF); /* will fixup */
if (self->oem_id != NULL) {
gsize oem_id_strlen = strlen (self->oem_id);
if (!fu_memcpy_safe (oem_id, sizeof(oem_id), 0x0, /* dst */
(const guint8 *) self->oem_id, oem_id_strlen, 0x0, /* src */
oem_id_strlen, error))
return NULL;
}
g_byte_array_append (buf, oem_id, sizeof(oem_id));
if (oem_table_id_str != NULL) {
gsize oem_table_id_strlen = strlen (oem_table_id_str);
if (!fu_memcpy_safe (oem_table_id, sizeof(oem_table_id), 0x0, /* dst */
(const guint8 *) oem_table_id_str, oem_table_id_strlen, 0x0, /* src */
oem_table_id_strlen, error))
return NULL;
}
g_byte_array_append (buf, oem_table_id, sizeof(oem_table_id));
fu_byte_array_append_uint32 (buf, fu_firmware_get_version_raw (firmware), G_LITTLE_ENDIAN);
g_byte_array_append (buf, creator_id, sizeof(creator_id));
g_byte_array_append (buf, creator_rev, sizeof(creator_rev));
g_byte_array_append (buf, buf2->data, buf2->len);
/* fixup checksum */
for (gsize i = 0; i < buf->len; i++)
checksum += buf->data[i];
buf->data[9] = 0xFF - checksum;
/* success */
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static gboolean
fu_acpi_phat_build (FuFirmware *firmware, XbNode *n, GError **error)
{
FuAcpiPhat *self = FU_ACPI_PHAT (firmware);
const gchar *tmp;
/* optional properties */
tmp = xb_node_query_text (n, "oem_id", NULL);
if (tmp != NULL)
fu_acpi_phat_set_oem_id (self, tmp);
/* success */
return TRUE;
}
static gboolean
fu_acpi_phat_to_report_string_cb (XbBuilderNode *bn, gpointer user_data)
{
if (g_strcmp0 (xb_builder_node_get_element (bn), "offset") == 0 ||
g_strcmp0 (xb_builder_node_get_element (bn), "flags") == 0 ||
g_strcmp0 (xb_builder_node_get_element (bn), "size") == 0)
xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE);
return FALSE;
}
gchar *
fu_acpi_phat_to_report_string (FuAcpiPhat *self)
{
g_autoptr(XbBuilderNode) bn = xb_builder_node_new ("firmware");
fu_firmware_export (FU_FIRMWARE (self), FU_FIRMWARE_EXPORT_FLAG_NONE, bn);
xb_builder_node_traverse (bn, G_PRE_ORDER, G_TRAVERSE_ALL, 3,
fu_acpi_phat_to_report_string_cb, NULL);
return xb_builder_node_export (bn,
XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE |
XB_NODE_EXPORT_FLAG_FORMAT_INDENT,
NULL);
}
static void
fu_acpi_phat_init (FuAcpiPhat *self)
{
fu_firmware_add_flag (FU_FIRMWARE (self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
}
static void
fu_acpi_phat_finalize (GObject *object)
{
FuAcpiPhat *self = FU_ACPI_PHAT (object);
g_free (self->oem_id);
G_OBJECT_CLASS (fu_acpi_phat_parent_class)->finalize (object);
}
static void
fu_acpi_phat_class_init (FuAcpiPhatClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
object_class->finalize = fu_acpi_phat_finalize;
klass_firmware->parse = fu_acpi_phat_parse;
klass_firmware->write = fu_acpi_phat_write;
klass_firmware->export = fu_acpi_phat_export;
klass_firmware->build = fu_acpi_phat_build;
}
FuFirmware *
fu_acpi_phat_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_ACPI_PHAT, NULL));
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-firmware.h"
#define FU_TYPE_ACPI_PHAT (fu_acpi_phat_get_type ())
G_DECLARE_FINAL_TYPE (FuAcpiPhat, fu_acpi_phat, FU, ACPI_PHAT, FuFirmware)
#define FU_ACPI_PHAT_RECORD_TYPE_VERSION 0x0000
#define FU_ACPI_PHAT_RECORD_TYPE_HEALTH 0x0001
#define FU_ACPI_PHAT_REVISION 0x01
FuFirmware *fu_acpi_phat_new (void);
gchar *fu_acpi_phat_to_report_string (FuAcpiPhat *self);

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-acpi-phat.h"
#include "fu-acpi-phat-health-record.h"
#include "fu-acpi-phat-version-element.h"
#include "fu-acpi-phat-version-record.h"
#include "fu-plugin-vfuncs.h"
void
fu_plugin_init (FuPlugin *plugin)
{
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_ACPI_PHAT);
fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_ACPI_PHAT_HEALTH_RECORD);
fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_ACPI_PHAT_VERSION_ELEMENT);
fu_plugin_add_firmware_gtype (plugin, NULL, FU_TYPE_ACPI_PHAT_VERSION_RECORD);
}
gboolean
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
{
g_autofree gchar *path = NULL;
g_autofree gchar *fn = NULL;
g_autofree gchar *str = NULL;
g_autoptr(FuFirmware) phat = fu_acpi_phat_new ();
g_autoptr(GBytes) blob = NULL;
path = fu_common_get_path (FU_PATH_KIND_ACPI_TABLES);
fn = g_build_filename (path, "PHAT", NULL);
blob = fu_common_get_contents_bytes (fn, error);
if (blob == NULL)
return FALSE;
if (!fu_firmware_parse (phat, blob, FWUPD_INSTALL_FLAG_NONE, error))
return FALSE;
str = fu_acpi_phat_to_report_string (FU_ACPI_PHAT (phat));
fu_plugin_add_report_metadata (plugin, "PHAT", str);
return TRUE;
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupd.h>
#include "fu-common.h"
#include "fu-acpi-phat.h"
static void
fu_acpi_phat_parse_func (void)
{
gboolean ret;
g_autoptr(FuFirmware) phat = fu_acpi_phat_new ();
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) blob = NULL;
g_autofree gchar *str = NULL;
g_autofree gchar *fn = NULL;
fn = g_test_build_filename (G_TEST_DIST, "tests", "PHAT", NULL);
if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
g_test_skip ("missing PHAT");
return;
}
blob = fu_common_get_contents_bytes (fn, &error);
g_assert_no_error (error);
g_assert_nonnull (blob);
ret = fu_firmware_parse (phat, blob, FWUPD_INSTALL_FLAG_FORCE, &error);
g_assert_no_error (error);
g_assert_true (ret);
str = fu_acpi_phat_to_report_string (FU_ACPI_PHAT (phat));
g_print ("%s\n", str);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
/* only critical and error are fatal */
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
/* tests go here */
g_test_add_func ("/acpi-phat/parse", fu_acpi_phat_parse_func);
return g_test_run ();
}

View File

@ -0,0 +1,61 @@
if host_machine.system() == 'linux'
cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiPhat"']
shared_module('fu_plugin_acpi_phat',
fu_hash,
sources : [
'fu-plugin-acpi-phat.c',
'fu-acpi-phat.c', # fuzzing
'fu-acpi-phat-health-record.c', # fuzzing
'fu-acpi-phat-version-element.c', # fuzzing
'fu-acpi-phat-version-record.c', # fuzzing
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
install : true,
install_dir: plugin_dir,
link_with : [
fwupd,
fwupdplugin,
],
c_args : cargs,
dependencies : [
plugin_deps,
],
)
if get_option('tests')
testdatadirs = environment()
testdatadirs.set('G_TEST_SRCDIR', meson.current_source_dir())
testdatadirs.set('G_TEST_BUILDDIR', meson.current_build_dir())
e = executable(
'acpi-phat-self-test',
fu_hash,
sources : [
'fu-self-test.c',
'fu-acpi-phat.c',
'fu-acpi-phat-health-record.c',
'fu-acpi-phat-version-element.c',
'fu-acpi-phat-version-record.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
dependencies : [
plugin_deps,
],
link_with : [
fwupd,
fwupdplugin,
],
install : true,
install_dir : installed_test_bindir,
)
test('acpi-phat-self-test', e, env : testdatadirs) # added to installed-tests
endif
endif

View File

@ -1,5 +1,6 @@
subdir('acpi-dmar')
subdir('acpi-facp')
subdir('acpi-phat')
subdir('altos')
subdir('amt')
subdir('analogix')

View File

@ -43,6 +43,7 @@ fu_plugin_init (FuPlugin *plugin)
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "tpm_eventlog");
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "dell");
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown");
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "acpi_phat");
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_CONFLICTS, "uefi"); /* old name */
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
}

View File

@ -0,0 +1,35 @@
<firmware gtype="FuAcpiPhat">
<id>SUDODAVE</id>
<version_raw>0x1</version_raw>
<oem_id>HUGHES</oem_id>
<firmware gtype="FuAcpiPhatVersionRecord">
<version_raw>0x1</version_raw>
<firmware gtype="FuAcpiPhatVersionElement">
<version_raw>0x123</version_raw>
<guid>40338ceb-b966-4eae-adae-9c32edfcc484</guid>
<producer_id>HUGH</producer_id>
</firmware>
<firmware gtype="FuAcpiPhatVersionElement">
<version_raw>0x124</version_raw>
<guid>2082b5e0-7a64-478a-b1b2-e3404fab6dad</guid>
<producer_id>LENO</producer_id>
</firmware>
</firmware>
<firmware gtype="FuAcpiPhatVersionRecord">
<version_raw>0x1</version_raw>
<firmware gtype="FuAcpiPhatVersionElement">
<version_raw>0x125</version_raw>
<guid>dfbaaded-754b-5214-a5f2-46aa3331e8ce</guid>
<producer_id>DELL</producer_id>
</firmware>
</firmware>
<firmware gtype="FuAcpiPhatVersionRecord">
<version_raw>0x1</version_raw>
</firmware>
<firmware gtype="FuAcpiPhatHealthRecord">
<version_raw>0x1</version_raw>
<am_healthy>0x1</am_healthy>
<guid>dfbaaded-754b-5214-a5f2-46aa3331e8ce</guid>
<device_path>/dev/foo</device_path>
</firmware>
</firmware>

Binary file not shown.

View File

@ -13,6 +13,7 @@ import subprocess
if __name__ == "__main__":
for fn_src, fn_dst in [
("acpi-phat.builder.xml", "acpi-phat.bin"),
("bcm57xx.builder.xml", "bcm57xx.bin"),
("ccgx.builder.xml", "ccgx.cyacd"),
("ccgx-dmc.builder.xml", "ccgx-dmc.bin"),
@ -45,6 +46,7 @@ if __name__ == "__main__":
print("INFO: converting {} into {}".format(fn_src, fn_dst))
try:
argv = [
"sudo",
"../../build/src/fwupdtool",
"firmware-build",
fn_src,