mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-04 00:38:43 +00:00
235 lines
6.1 KiB
C
235 lines
6.1 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-uefi-cod-device.h"
|
|
#include "fu-uefi-common.h"
|
|
|
|
struct _FuUefiCodDevice {
|
|
FuUefiDevice parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuUefiCodDevice, fu_uefi_cod_device, FU_TYPE_UEFI_DEVICE)
|
|
|
|
static gboolean
|
|
fu_uefi_cod_device_get_results_for_idx(FuDevice *device, guint idx, GError **error)
|
|
{
|
|
FuUefiDevice *device_uefi = FU_UEFI_DEVICE(device);
|
|
fwupd_guid_t guid = {0x0};
|
|
gsize bufsz = 0;
|
|
guint32 status = 0;
|
|
guint32 total_size = 0;
|
|
g_autofree gchar *guidstr = NULL;
|
|
g_autofree gchar *name = NULL;
|
|
g_autofree guint8 *buf = NULL;
|
|
|
|
/* read out result */
|
|
name = g_strdup_printf("Capsule%04u", idx);
|
|
if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_CAPSULE_REPORT,
|
|
name,
|
|
&buf,
|
|
&bufsz,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "failed to read %s: ", name);
|
|
return FALSE;
|
|
}
|
|
|
|
/* sanity check */
|
|
if (!fu_common_read_uint32_safe(buf, bufsz, 0x00, &total_size, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
if (total_size < 0x3A) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"EFI_CAPSULE_RESULT_VARIABLE_HEADER too small");
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify guid */
|
|
if (!fu_memcpy_safe(guid,
|
|
sizeof(guid),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
0x08, /* src */
|
|
sizeof(guid),
|
|
error))
|
|
return FALSE;
|
|
guidstr = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN);
|
|
if (g_strcmp0(guidstr, fu_uefi_device_get_guid(device_uefi)) != 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"wrong GUID, expected %s, got %s",
|
|
fu_uefi_device_get_guid(device_uefi),
|
|
guidstr);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get status */
|
|
if (!fu_common_read_uint32_safe(buf, bufsz, 0x28, &status, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
fu_uefi_device_set_status(device_uefi, status);
|
|
return TRUE;
|
|
}
|
|
|
|
#define VARIABLE_IDX_SIZE 11 /* of CHAR16 */
|
|
|
|
static gboolean
|
|
fu_uefi_cod_device_get_variable_idx(const gchar *name, guint *value, GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
gunichar2 buf16[VARIABLE_IDX_SIZE] = {0x0};
|
|
|
|
if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_CAPSULE_REPORT, name, &buf, &bufsz, NULL, error))
|
|
return FALSE;
|
|
if (!fu_memcpy_safe((guint8 *)buf16,
|
|
sizeof(buf16),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
0x0, /* src */
|
|
sizeof(buf16),
|
|
error))
|
|
return FALSE;
|
|
|
|
/* parse the value */
|
|
str = g_utf16_to_utf8(buf16, VARIABLE_IDX_SIZE, NULL, NULL, error);
|
|
if (str == NULL)
|
|
return FALSE;
|
|
if (!g_str_has_prefix(str, "Capsule")) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"wrong contents, got %s",
|
|
str);
|
|
return FALSE;
|
|
}
|
|
if (value != NULL)
|
|
*value = fu_common_strtoull(str + strlen("Capsule"));
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_cod_device_get_results(FuDevice *device, GError **error)
|
|
{
|
|
guint capsule_last = 1024;
|
|
|
|
/* tell us where to stop */
|
|
if (!fu_uefi_cod_device_get_variable_idx("CapsuleLast", &capsule_last, error))
|
|
return FALSE;
|
|
for (guint i = 0; i <= capsule_last; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (fu_uefi_cod_device_get_results_for_idx(device, i, &error_local))
|
|
return TRUE;
|
|
if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND) &&
|
|
!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* nothing found */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_cod_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuUefiDevice *self = FU_UEFI_DEVICE(device);
|
|
g_autofree gchar *basename = NULL;
|
|
g_autofree gchar *cod_path = NULL;
|
|
g_autofree gchar *esp_path = fu_uefi_device_get_esp_path(self);
|
|
g_autoptr(GBytes) fw = NULL;
|
|
|
|
/* ensure we have the existing state */
|
|
if (fu_uefi_device_get_guid(self) == NULL) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"cannot update device info with no GUID");
|
|
return FALSE;
|
|
}
|
|
|
|
/* copy the capsule */
|
|
fw = fu_firmware_get_bytes(firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
basename = g_strdup_printf("fwupd-%s.cap", fu_uefi_device_get_guid(self));
|
|
cod_path = g_build_filename(esp_path, "EFI", "UpdateCapsule", basename, NULL);
|
|
if (!fu_common_mkdir_parent(cod_path, error))
|
|
return FALSE;
|
|
if (!fu_common_set_contents_bytes(cod_path, fw, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* NOTE: The EFI spec requires setting OsIndications!
|
|
* RT->SetVariable is not supported for all hardware, and so when using
|
|
* U-Boot, it applies the capsule even if OsIndications isn't set.
|
|
* The capsule is then deleted by U-Boot after it has been deployed.
|
|
*/
|
|
if (!fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE)) {
|
|
gsize bufsz = 0;
|
|
guint64 os_indications = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL,
|
|
"OsIndications",
|
|
&buf,
|
|
&bufsz,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "failed to read EFI variable: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_common_read_uint64_safe(buf,
|
|
bufsz,
|
|
0x0,
|
|
&os_indications,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
os_indications |= EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED;
|
|
if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL,
|
|
"OsIndications",
|
|
(guint8 *)&os_indications,
|
|
sizeof(os_indications),
|
|
FU_EFIVAR_ATTR_NON_VOLATILE |
|
|
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
|
|
FU_EFIVAR_ATTR_RUNTIME_ACCESS,
|
|
error)) {
|
|
g_prefix_error(error, "Could not set OsIndications: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_uefi_cod_device_init(FuUefiCodDevice *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_uefi_cod_device_class_init(FuUefiCodDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->write_firmware = fu_uefi_cod_device_write_firmware;
|
|
klass_device->get_results = fu_uefi_cod_device_get_results;
|
|
}
|