fwupd/plugins/uefi-capsule/fu-uefi-device.c
Richard Hughes aaa77c6f51 Allow adding and removing custom flags on devices
The CustomFlags feature is a bit of a hack where we just join the flags
and store in the device metadata section as a string. This makes it
inefficient to check if just one flag exists as we have to split the
string to a temporary array each time.

Rather than adding to the hack by splitting, appending (if not exists)
then joining again, store the flags in the plugin privdata directly.

This allows us to support negating custom properties (e.g. ~hint) and
also allows quirks to append custom values without duplicating them on
each GUID match, e.g.

[USB\VID_17EF&PID_307F]
Plugin = customflag1
[USB\VID_17EF&PID_307F&HUB_0002]
Flags = customflag2

...would result in customflag1,customflag2 which is the same as you'd
get from an enumerated device flag doing the same thing.
2021-06-23 07:59:15 +01:00

903 lines
27 KiB
C

/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2015 Peter Jones <pjones@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <efivar.h>
#include <efivar/efiboot.h>
#include <fwupdplugin.h>
#include "fu-uefi-common.h"
#include "fu-uefi-device.h"
#include "fu-uefi-devpath.h"
#include "fu-uefi-bootmgr.h"
#include "fu-uefi-pcrs.h"
struct _FuUefiDevice {
FuDevice parent_instance;
FuVolume *esp;
FuDeviceLocker *esp_locker;
gchar *fw_class;
FuUefiDeviceKind kind;
guint32 capsule_flags;
guint32 fw_version;
guint32 fw_version_lowest;
FuUefiDeviceStatus last_attempt_status;
guint32 last_attempt_version;
guint64 fmp_hardware_instance;
gboolean missing_header;
gboolean automounted_esp;
gboolean requires_header;
};
G_DEFINE_TYPE (FuUefiDevice, fu_uefi_device, FU_TYPE_DEVICE)
enum {
PROP_0,
PROP_FW_CLASS,
PROP_KIND,
PROP_CAPSULE_FLAGS,
PROP_FW_VERSION,
PROP_FW_VERSION_LOWEST,
PROP_LAST_ATTEMPT_STATUS,
PROP_LAST_ATTEMPT_VERSION,
PROP_FMP_HARDWARE_INSTANCE,
PROP_LAST
};
void
fu_uefi_device_set_esp (FuUefiDevice *self, FuVolume *esp)
{
g_return_if_fail (FU_IS_UEFI_DEVICE (self));
g_return_if_fail (FU_IS_VOLUME (esp));
g_set_object (&self->esp, esp);
}
const gchar *
fu_uefi_device_kind_to_string (FuUefiDeviceKind kind)
{
if (kind == FU_UEFI_DEVICE_KIND_UNKNOWN)
return "unknown";
if (kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE)
return "system-firmware";
if (kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE)
return "device-firmware";
if (kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER)
return "uefi-driver";
if (kind == FU_UEFI_DEVICE_KIND_FMP)
return "fmp";
if (kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE)
return "dell-tpm-firmware";
return NULL;
}
static FuUefiDeviceKind
fu_uefi_device_kind_from_string (const gchar *kind)
{
if (g_strcmp0 (kind, "system-firmware") == 0)
return FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE;
if (g_strcmp0 (kind, "device-firmware") == 0)
return FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE;
if (g_strcmp0 (kind, "uefi-driver") == 0)
return FU_UEFI_DEVICE_KIND_UEFI_DRIVER;
if (g_strcmp0 (kind, "fmp") == 0)
return FU_UEFI_DEVICE_KIND_FMP;
if (g_strcmp0 (kind, "dell-tpm-firmware") == 0)
return FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE;
return FU_UEFI_DEVICE_KIND_UNKNOWN;
}
const gchar *
fu_uefi_device_status_to_string (FuUefiDeviceStatus status)
{
if (status == FU_UEFI_DEVICE_STATUS_SUCCESS)
return "success";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL)
return "unsuccessful";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_INSUFFICIENT_RESOURCES)
return "insufficient resources";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_INCORRECT_VERSION)
return "incorrect version";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_INVALID_FORMAT)
return "invalid firmware format";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_AUTH_ERROR)
return "authentication signing error";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC)
return "AC power required";
if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT)
return "battery level is too low";
return NULL;
}
static void
fu_uefi_device_to_string (FuDevice *device, guint idt, GString *str)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
fu_common_string_append_kv (str, idt, "Kind", fu_uefi_device_kind_to_string (self->kind));
fu_common_string_append_kv (str, idt, "FwClass", self->fw_class);
fu_common_string_append_kx (str, idt, "CapsuleFlags", self->capsule_flags);
fu_common_string_append_kx (str, idt, "FwVersion", self->fw_version);
fu_common_string_append_kx (str, idt, "FwVersionLowest", self->fw_version_lowest);
fu_common_string_append_kv (str, idt, "LastAttemptStatus",
fu_uefi_device_status_to_string (self->last_attempt_status));
fu_common_string_append_kx (str, idt, "LastAttemptVersion", self->last_attempt_version);
if (self->esp != NULL) {
fu_common_string_append_kv (str, idt, "EspId",
fu_volume_get_id (self->esp));
}
fu_common_string_append_ku (str, idt, "RequireESPFreeSpace",
fu_device_get_metadata_integer (device, "RequireESPFreeSpace"));
fu_common_string_append_kb (str, idt, "RequireShimForSecureBoot",
fu_device_get_metadata_boolean (device, "RequireShimForSecureBoot"));
}
static void
fu_uefi_device_report_metadata_pre (FuDevice *device, GHashTable *metadata)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
/* record if we had an invalid header during update */
g_hash_table_insert (metadata,
g_strdup ("MissingCapsuleHeader"),
g_strdup (self->missing_header ? "True" : "False"));
/* where the ESP was mounted during installation */
g_hash_table_insert (metadata,
g_strdup ("EspPath"),
fu_volume_get_mount_point (self->esp));
}
static void
fu_uefi_device_report_metadata_post (FuDevice *device, GHashTable *metadata)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
/* the actual last_attempt values */
g_hash_table_insert (metadata,
g_strdup ("LastAttemptStatus"),
g_strdup_printf ("0x%x", self->last_attempt_status));
g_hash_table_insert (metadata,
g_strdup ("LastAttemptVersion"),
g_strdup_printf ("0x%x", self->last_attempt_version));
}
FuUefiDeviceKind
fu_uefi_device_get_kind (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0);
return self->kind;
}
guint32
fu_uefi_device_get_version (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0);
return self->fw_version;
}
guint32
fu_uefi_device_get_version_lowest (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0);
return self->fw_version_lowest;
}
guint32
fu_uefi_device_get_version_error (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0);
return self->last_attempt_version;
}
guint64
fu_uefi_device_get_hardware_instance (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0);
return self->fmp_hardware_instance;
}
FuUefiDeviceStatus
fu_uefi_device_get_status (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0);
return self->last_attempt_status;
}
guint32
fu_uefi_device_get_capsule_flags (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0);
return self->capsule_flags;
}
const gchar *
fu_uefi_device_get_guid (FuUefiDevice *self)
{
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), NULL);
return self->fw_class;
}
static gchar *
fu_uefi_device_build_varname (FuUefiDevice *self)
{
return g_strdup_printf ("fwupd-%s-%"G_GUINT64_FORMAT,
self->fw_class,
self->fmp_hardware_instance);
}
FuUefiUpdateInfo *
fu_uefi_device_load_update_info (FuUefiDevice *self, GError **error)
{
gsize datasz = 0;
g_autofree gchar *varname = fu_uefi_device_build_varname (self);
g_autofree guint8 *data = NULL;
g_autoptr(FuUefiUpdateInfo) info = fu_uefi_update_info_new ();
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* get the existing status */
if (!fu_efivar_get_data (FU_EFIVAR_GUID_FWUPDATE, varname,
&data, &datasz, NULL, error))
return NULL;
if (!fu_uefi_update_info_parse (info, data, datasz, error))
return NULL;
return g_steal_pointer (&info);
}
gboolean
fu_uefi_device_clear_status (FuUefiDevice *self, GError **error)
{
efi_update_info_t info;
gsize datasz = 0;
g_autofree gchar *varname = fu_uefi_device_build_varname (self);
g_autofree guint8 *data = NULL;
g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* get the existing status */
if (!fu_efivar_get_data (FU_EFIVAR_GUID_FWUPDATE, varname,
&data, &datasz, NULL, error))
return FALSE;
if (datasz < sizeof(efi_update_info_t)) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"EFI variable is corrupt");
return FALSE;
}
/* just copy the efi_update_info_t, ignore devpath then save it back */
memcpy (&info, data, sizeof(info));
info.status = FU_UEFI_DEVICE_STATUS_SUCCESS;
memcpy (data, &info, sizeof(info));
return fu_efivar_set_data (FU_EFIVAR_GUID_FWUPDATE, varname,
data, datasz,
FU_EFIVAR_ATTR_NON_VOLATILE |
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
FU_EFIVAR_ATTR_RUNTIME_ACCESS,
error);
}
static guint8 *
fu_uefi_device_build_dp_buf (const gchar *path, gsize *bufsz, GError **error)
{
gssize req;
gssize sz;
g_autofree guint8 *dp_buf = NULL;
g_autoptr(GPtrArray) dps = NULL;
/* get the size of the path first */
req = efi_generate_file_device_path (NULL, 0, path,
EFIBOOT_OPTIONS_IGNORE_FS_ERROR |
EFIBOOT_ABBREV_HD);
if (req < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to efi_generate_file_device_path(%s)",
path);
return NULL;
}
/* if we just have an end device path, it's not going to work */
if (req <= 4) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to get valid device_path for (%s)",
path);
return NULL;
}
/* actually get the path this time */
dp_buf = g_malloc0 (req);
sz = efi_generate_file_device_path (dp_buf, req, path,
EFIBOOT_OPTIONS_IGNORE_FS_ERROR |
EFIBOOT_ABBREV_HD);
if (sz < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to efi_generate_file_device_path(%s)",
path);
return NULL;
}
/* parse what we got back from efivar */
dps = fu_uefi_devpath_parse (dp_buf, (gsize) sz,
FU_UEFI_DEVPATH_PARSE_FLAG_NONE, error);
if (dps == NULL) {
fu_common_dump_raw (G_LOG_DOMAIN, "dp_buf", dp_buf, (gsize) sz);
return NULL;
}
/* success */
if (bufsz != NULL)
*bufsz = sz;
return g_steal_pointer (&dp_buf);
}
static GBytes *
fu_uefi_device_fixup_firmware (FuDevice *device, GBytes *fw, GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
gsize fw_length;
const guint8 *data = g_bytes_get_data (fw, &fw_length);
g_autofree gchar *guid_new = NULL;
self->missing_header = FALSE;
/* GUID is the first 16 bytes */
if (fw_length < sizeof(fwupd_guid_t)) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
"Invalid payload");
return NULL;
}
guid_new = fwupd_guid_to_string ((fwupd_guid_t *) data, FWUPD_GUID_FLAG_MIXED_ENDIAN);
/* ESRT header matches payload */
if (g_strcmp0 (fu_uefi_device_get_guid (self), guid_new) == 0) {
g_debug ("ESRT matches payload GUID");
return g_bytes_ref (fw);
/* Type that doesn't require a header */
} else if (!self->requires_header) {
return g_bytes_ref (fw);
/* Missing, add a header */
} else {
guint header_size = getpagesize();
guint8 *new_data = g_malloc (fw_length + header_size);
guint8 *capsule = new_data + header_size;
fwupd_guid_t esrt_guid = { 0x0 };
efi_capsule_header_t *header = (efi_capsule_header_t *) new_data;
g_warning ("missing or invalid embedded capsule header");
self->missing_header = TRUE;
header->flags = self->capsule_flags;
header->header_size = header_size;
header->capsule_image_size = fw_length + header_size;
if (!fwupd_guid_from_string (fu_uefi_device_get_guid (self), &esrt_guid,
FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) {
g_prefix_error (error, "Invalid ESRT GUID: ");
return NULL;
}
memcpy (&header->guid, &esrt_guid, sizeof (fwupd_guid_t));
memcpy (capsule, data, fw_length);
return g_bytes_new_take (new_data, fw_length + header_size);
}
}
gboolean
fu_uefi_device_write_update_info (FuUefiDevice *self,
const gchar *filename,
const gchar *varname,
const gchar *guid,
GError **error)
{
gsize datasz = 0;
gsize dp_bufsz = 0;
g_autofree guint8 *data = NULL;
g_autofree guint8 *dp_buf = NULL;
efi_update_info_t info = {
.update_info_version = 0x7,
.guid = { 0x0 },
.capsule_flags = self->capsule_flags,
.hw_inst = self->fmp_hardware_instance,
.time_attempted = { 0x0 },
.status = FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE,
};
/* set the body as the device path */
if (g_getenv ("FWUPD_UEFI_TEST") != NULL) {
g_debug ("not building device path, in tests....");
return TRUE;
}
/* convert to EFI device path */
dp_buf = fu_uefi_device_build_dp_buf (filename, &dp_bufsz, error);
if (dp_buf == NULL)
return FALSE;
/* save this header and body to the hardware */
if (!fwupd_guid_from_string (guid, &info.guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error))
return FALSE;
datasz = sizeof(info) + dp_bufsz;
data = g_malloc0 (datasz);
memcpy (data, &info, sizeof(info));
memcpy (data + sizeof(info), dp_buf, dp_bufsz);
return fu_efivar_set_data (FU_EFIVAR_GUID_FWUPDATE, varname,
data, datasz,
FU_EFIVAR_ATTR_NON_VOLATILE |
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
FU_EFIVAR_ATTR_RUNTIME_ACCESS,
error);
}
static gboolean
fu_uefi_device_check_esp_free (FuDevice *device, GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
guint64 sz_reqd = fu_device_get_metadata_integer (device, "RequireESPFreeSpace");
if (sz_reqd == G_MAXUINT) {
g_debug ("maximum size is not configured");
return TRUE;
}
return fu_volume_check_free_space (self->esp, sz_reqd, error);
}
static gboolean
fu_uefi_check_asset (FuDevice *device, GError **error)
{
g_autofree gchar *source_app = fu_uefi_get_built_app_path (error);
if (source_app == NULL) {
if (fu_efivar_secure_boot_enabled ())
g_prefix_error (error, "missing signed bootloader for secure boot: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_uefi_device_cleanup_esp (FuDevice *device, GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
g_autofree gchar *esp_path = fu_volume_get_mount_point (self->esp);
g_autofree gchar *pattern = NULL;
g_autoptr(GPtrArray) files = NULL;
/* in case we call capsule install twice before reboot */
if (fu_efivar_exists (FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext"))
return TRUE;
/* delete any files matching the glob in the ESP */
files = fu_common_get_files_recursive (esp_path, error);
if (files == NULL)
return FALSE;
pattern = g_build_filename (esp_path, "EFI/*/fw/fwupd*.cap", NULL);
for (guint i = 0; i < files->len; i++) {
const gchar *fn = g_ptr_array_index (files, i);
if (fu_common_fnmatch (pattern, fn)) {
g_autoptr(GFile) file = g_file_new_for_path (fn);
g_debug ("deleting %s", fn);
if (!g_file_delete (file, NULL, error))
return FALSE;
}
}
/* delete any old variables */
if (!fu_efivar_delete_with_glob (FU_EFIVAR_GUID_FWUPDATE, "fwupd*-*", error))
return FALSE;
return TRUE;
}
static gboolean
fu_uefi_device_prepare (FuDevice *device,
FwupdInstallFlags flags,
GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
/* mount if required */
self->esp_locker = fu_volume_locker (self->esp, error);
if (self->esp_locker == NULL)
return FALSE;
/* sanity checks */
if (!fu_uefi_device_cleanup_esp (device, error))
return FALSE;
if (!fu_uefi_device_check_esp_free (device, error))
return FALSE;
if (!fu_uefi_check_asset (device, error))
return FALSE;
return TRUE;
}
static gboolean
fu_uefi_device_cleanup (FuDevice *device,
FwupdInstallFlags flags,
GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
/* unmount ESP if we opened it */
if (!fu_device_locker_close (self->esp_locker, error))
return FALSE;
g_clear_object (&self->esp_locker);
return TRUE;
}
static gboolean
fu_uefi_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags install_flags,
GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
FuUefiBootmgrFlags flags = FU_UEFI_BOOTMGR_FLAG_NONE;
const gchar *bootmgr_desc = "Linux Firmware Updater";
g_autofree gchar *esp_path = fu_volume_get_mount_point (self->esp);
g_autoptr(GBytes) fixed_fw = NULL;
g_autoptr(GBytes) fw = NULL;
g_autofree gchar *basename = NULL;
g_autofree gchar *directory = NULL;
g_autofree gchar *fn = NULL;
g_autofree gchar *varname = fu_uefi_device_build_varname (self);
/* ensure we have the existing state */
if (self->fw_class == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"cannot update device info with no GUID");
return FALSE;
}
/* get default image */
fw = fu_firmware_get_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* save the blob to the ESP */
directory = fu_uefi_get_esp_path_for_os (device, esp_path);
basename = g_strdup_printf ("fwupd-%s.cap", self->fw_class);
fn = g_build_filename (directory, "fw", basename, NULL);
if (!fu_common_mkdir_parent (fn, error))
return FALSE;
fixed_fw = fu_uefi_device_fixup_firmware (device, fw, error);
if (fixed_fw == NULL)
return FALSE;
if (!fu_common_set_contents_bytes (fn, fixed_fw, error))
return FALSE;
/* delete the logs to save space; use fwupdate to debug the EFI binary */
if (fu_efivar_exists (FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE")) {
if (!fu_efivar_delete (FU_EFIVAR_GUID_FWUPDATE,
"FWUPDATE_VERBOSE", error))
return FALSE;
}
if (fu_efivar_exists (FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG")) {
if (!fu_efivar_delete (FU_EFIVAR_GUID_FWUPDATE,
"FWUPDATE_DEBUG_LOG", error))
return FALSE;
}
/* set the blob header shared with fwupd.efi */
if (!fu_uefi_device_write_update_info (self, fn, varname, self->fw_class, error))
return FALSE;
/* update the firmware before the bootloader runs */
if (fu_device_get_metadata_boolean (device, "RequireShimForSecureBoot"))
flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB;
if (fu_device_has_private_flag (device, FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE))
flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE;
/* some legacy devices use the old name to deduplicate boot entries */
if (fu_device_has_private_flag (device, FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC))
bootmgr_desc = "Linux-Firmware-Updater";
if (!fu_uefi_bootmgr_bootnext (device, esp_path, bootmgr_desc, flags, error))
return FALSE;
/* success! */
return TRUE;
}
static gboolean
fu_uefi_device_add_system_checksum (FuDevice *device, GError **error)
{
g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new ();
g_autoptr(GError) error_local = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
/* get all the PCRs */
if (!fu_uefi_pcrs_setup (pcrs, &error_local)) {
if (g_error_matches (error_local,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED) ||
g_error_matches (error_local,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND)) {
g_debug ("%s", error_local->message);
return TRUE;
}
g_propagate_error (error, g_steal_pointer (&error_local));
return FALSE;
}
/* get all the PCR0s */
pcr0s = fu_uefi_pcrs_get_checksums (pcrs, 0);
if (pcr0s->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no PCR0s detected");
return FALSE;
}
for (guint i = 0; i < pcr0s->len; i++) {
const gchar *checksum = g_ptr_array_index (pcr0s, i);
fu_device_add_checksum (device, checksum);
}
/* success */
return TRUE;
}
static gboolean
fu_uefi_device_probe (FuDevice *device, GError **error)
{
FuUefiDevice *self = FU_UEFI_DEVICE (device);
FwupdVersionFormat version_format;
g_autofree gchar *devid = NULL;
g_autofree gchar *guid_strup = NULL;
g_autofree gchar *version_lowest = NULL;
g_autofree gchar *version = NULL;
/* broken sysfs? */
if (self->fw_class == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to read fw_class");
return FALSE;
}
/* this is invalid */
if (!fwupd_guid_is_valid (self->fw_class)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"ESRT GUID '%s' was not valid", self->fw_class);
return FALSE;
}
/* add GUID first, as quirks may set the version format */
fu_device_add_guid (device, self->fw_class);
/* set versions */
version_format = fu_device_get_version_format (device);
version = fu_common_version_from_uint32 (self->fw_version, version_format);
fu_device_set_version_format (device, version_format);
fu_device_set_version_raw (device, self->fw_version);
fu_device_set_version (device, version);
if (self->fw_version_lowest != 0) {
version_lowest = fu_common_version_from_uint32 (self->fw_version_lowest,
version_format);
fu_device_set_version_lowest_raw (device, self->fw_version_lowest);
fu_device_set_version_lowest (device, version_lowest);
}
/* set flags */
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC);
fu_device_add_internal_flag (device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT);
fu_device_add_internal_flag (device, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON);
/* add icons */
if (self->kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) {
/* nothing better in the icon naming spec */
fu_device_add_icon (device, "audio-card");
} else {
/* this is probably system firmware */
fu_device_add_icon (device, "computer");
fu_device_add_instance_id (device, "main-system-firmware");
}
/* set the PCR0 as the device checksum */
if (self->kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) {
g_autoptr(GError) error_local = NULL;
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_CAN_VERIFY);
if (!fu_uefi_device_add_system_checksum (device, &error_local))
g_warning ("Failed to get PCR0s: %s", error_local->message);
}
/* whether to create a missing header */
if (self->kind == FU_UEFI_DEVICE_KIND_FMP ||
self->kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE)
self->requires_header = FALSE;
else
self->requires_header = TRUE;
/* Windows seems to be case insensitive, but for convenience we'll
* match the upper case values typically specified in the .inf file */
guid_strup = g_ascii_strup (self->fw_class, -1);
devid = g_strdup_printf ("UEFI\\RES_{%s}", guid_strup);
fu_device_add_instance_id (device, devid);
return TRUE;
}
static void
fu_uefi_device_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
FuUefiDevice *self = FU_UEFI_DEVICE (object);
switch (prop_id) {
case PROP_FW_CLASS:
self->fw_class = g_value_dup_string (value);
break;
case PROP_KIND:
self->kind = g_value_get_uint (value);
break;
case PROP_CAPSULE_FLAGS:
self->capsule_flags = g_value_get_uint (value);
break;
case PROP_FW_VERSION:
self->fw_version = g_value_get_uint (value);
break;
case PROP_FW_VERSION_LOWEST:
self->fw_version_lowest = g_value_get_uint (value);
break;
case PROP_LAST_ATTEMPT_STATUS:
self->last_attempt_status = g_value_get_uint (value);
break;
case PROP_LAST_ATTEMPT_VERSION:
self->last_attempt_version = g_value_get_uint (value);
break;
case PROP_FMP_HARDWARE_INSTANCE:
self->fmp_hardware_instance = g_value_get_uint64 (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_uefi_device_init (FuUefiDevice *self)
{
fu_device_add_protocol (FU_DEVICE (self), "org.uefi.capsule");
fu_device_register_private_flag (FU_DEVICE (self),
FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE,
"no-ux-capsule");
fu_device_register_private_flag (FU_DEVICE (self),
FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE,
"use-shim-unique");
fu_device_register_private_flag (FU_DEVICE (self),
FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC,
"use-legacy-bootmgr-desc");
}
static void
fu_uefi_device_finalize (GObject *object)
{
FuUefiDevice *self = FU_UEFI_DEVICE (object);
g_free (self->fw_class);
if (self->esp != NULL)
g_object_unref (self->esp);
if (self->esp_locker != NULL)
g_object_unref (self->esp_locker);
G_OBJECT_CLASS (fu_uefi_device_parent_class)->finalize (object);
}
static void
fu_uefi_device_class_init (FuUefiDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
object_class->set_property = fu_uefi_device_set_property;
object_class->finalize = fu_uefi_device_finalize;
klass_device->to_string = fu_uefi_device_to_string;
klass_device->probe = fu_uefi_device_probe;
klass_device->prepare = fu_uefi_device_prepare;
klass_device->write_firmware = fu_uefi_device_write_firmware;
klass_device->cleanup = fu_uefi_device_cleanup;
klass_device->report_metadata_pre = fu_uefi_device_report_metadata_pre;
klass_device->report_metadata_post = fu_uefi_device_report_metadata_post;
pspec = g_param_spec_string ("fw-class", NULL, NULL, NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_FW_CLASS, pspec);
pspec = g_param_spec_uint ("kind", NULL, NULL,
FU_UEFI_DEVICE_KIND_UNKNOWN,
FU_UEFI_DEVICE_KIND_LAST - 1,
FU_UEFI_DEVICE_KIND_UNKNOWN,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_KIND, pspec);
pspec = g_param_spec_uint ("capsule-flags", NULL, NULL,
0, G_MAXUINT32, 0,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_CAPSULE_FLAGS, pspec);
pspec = g_param_spec_uint ("fw-version", NULL, NULL,
0, G_MAXUINT32, 0,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_FW_VERSION, pspec);
pspec = g_param_spec_uint ("fw-version-lowest", NULL, NULL,
0, G_MAXUINT32, 0,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_FW_VERSION_LOWEST, pspec);
pspec = g_param_spec_uint ("last-attempt-status", NULL, NULL,
FU_UEFI_DEVICE_STATUS_SUCCESS,
FU_UEFI_DEVICE_STATUS_LAST - 1,
FU_UEFI_DEVICE_STATUS_SUCCESS,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_LAST_ATTEMPT_STATUS, pspec);
pspec = g_param_spec_uint ("last-attempt-version", NULL, NULL,
0, G_MAXUINT32, 0,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_LAST_ATTEMPT_VERSION, pspec);
pspec = g_param_spec_uint64 ("fmp-hardware-instance", NULL, NULL,
0, G_MAXUINT64, 0,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_FMP_HARDWARE_INSTANCE, pspec);
}
FuUefiDevice *
fu_uefi_device_new_from_dev (FuDevice *dev)
{
const gchar *tmp;
FuUefiDevice *self;
g_return_val_if_fail (fu_device_get_guid_default (dev) != NULL, NULL);
/* create virtual object not backed by an ESRT entry */
self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL);
fu_device_incorporate (FU_DEVICE (self), dev);
self->fw_class = g_strdup (fu_device_get_guid_default (dev));
tmp = fu_device_get_metadata (dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND);
self->kind = fu_uefi_device_kind_from_string (tmp);
self->capsule_flags = fu_device_get_metadata_integer (dev, FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS);
self->fw_version = fu_device_get_metadata_integer (dev, FU_DEVICE_METADATA_UEFI_FW_VERSION);
g_assert (self->fw_class != NULL);
return self;
}
FuUefiDevice *
fu_uefi_device_new_from_guid (const gchar *guid)
{
FuUefiDevice *self;
self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL);
self->fw_class = g_strdup (guid);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_NUMBER);
return self;
}