mirror of
https://git.proxmox.com/git/fwupd
synced 2026-01-05 22:04:57 +00:00
uefi: Upload the firmware capsule without using libfwup
This commit is contained in:
parent
11d62030a3
commit
a80f79cb4d
@ -221,6 +221,7 @@ if get_option('plugin_uefi')
|
||||
|
||||
efivar = dependency('efivar')
|
||||
conf.set_quoted('EFIVAR_LIBRARY_VERSION', efivar.version())
|
||||
efiboot = dependency('efiboot')
|
||||
objcopy = find_program ('objcopy')
|
||||
readelf = find_program ('readelf')
|
||||
|
||||
|
||||
@ -62,60 +62,6 @@ fu_plugin_destroy (FuPlugin *plugin)
|
||||
g_object_unref (data->bgrt);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
fu_plugin_uefi_guid_to_string (efi_guid_t *guid_raw)
|
||||
{
|
||||
g_autofree gchar *guid = g_strdup ("00000000-0000-0000-0000-000000000000");
|
||||
if (efi_guid_to_str (guid_raw, &guid) < 0)
|
||||
return NULL;
|
||||
return g_steal_pointer (&guid);
|
||||
}
|
||||
|
||||
static fwup_resource *
|
||||
fu_plugin_uefi_find_resource (fwup_resource_iter *iter, FuDevice *device, GError **error)
|
||||
{
|
||||
efi_guid_t *guid_raw;
|
||||
fwup_resource *re = NULL;
|
||||
g_autofree gchar *guids_str = NULL;
|
||||
|
||||
/* get the hardware we're referencing */
|
||||
while (fwup_resource_iter_next (iter, &re) > 0) {
|
||||
g_autofree gchar *guid_tmp = NULL;
|
||||
|
||||
/* convert to strings */
|
||||
fwup_get_guid (re, &guid_raw);
|
||||
guid_tmp = fu_plugin_uefi_guid_to_string (guid_raw);
|
||||
if (guid_tmp == NULL) {
|
||||
g_warning ("failed to convert guid to string");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* FIXME: also match hardware_instance too */
|
||||
if (fu_device_has_guid (device, guid_tmp))
|
||||
return re;
|
||||
}
|
||||
|
||||
/* paradoxically, no hardware matched */
|
||||
guids_str = fu_device_get_guids_as_str (device);
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"No UEFI firmware matched '%s'",
|
||||
guids_str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_fwup_resource_iter_free (fwup_resource_iter *iter)
|
||||
{
|
||||
fwup_resource_iter_destroy (&iter);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(fwup_resource_iter, _fwup_resource_iter_free);
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
gboolean
|
||||
fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
@ -153,46 +99,6 @@ fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_plugin_uefi_update_resource (fwup_resource *re,
|
||||
guint64 hardware_instance,
|
||||
GBytes *blob,
|
||||
GError **error)
|
||||
{
|
||||
int rc;
|
||||
rc = fwup_set_up_update_with_buf (re, hardware_instance,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
g_bytes_get_size (blob));
|
||||
if (rc < 0) {
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
rc = 1;
|
||||
for (int i = 0; rc > 0; i++) {
|
||||
char *filename = NULL;
|
||||
char *function = NULL;
|
||||
char *message = NULL;
|
||||
int line = 0;
|
||||
int err = 0;
|
||||
|
||||
rc = efi_error_get (i, &filename, &function, &line,
|
||||
&message, &err);
|
||||
if (rc <= 0)
|
||||
break;
|
||||
g_string_append_printf (str, "{error #%d} %s:%d %s(): %s: %s\t",
|
||||
i, filename, line, function,
|
||||
message, strerror (err));
|
||||
}
|
||||
if (str->len > 1)
|
||||
g_string_truncate (str, str->len - 1);
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"UEFI firmware update failed: %s",
|
||||
str->str);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
fu_plugin_uefi_get_splash_data (guint width, guint height, GError **error)
|
||||
{
|
||||
@ -441,21 +347,12 @@ fu_plugin_update (FuPlugin *plugin,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
fwup_resource *re = NULL;
|
||||
guint64 hardware_instance = 0; /* FIXME */
|
||||
g_autoptr(fwup_resource_iter) iter = NULL;
|
||||
const gchar *str;
|
||||
guint32 flashes_left;
|
||||
g_autofree gchar *efibootmgr_path = NULL;
|
||||
g_autofree gchar *boot_variables = NULL;
|
||||
g_autoptr(GError) error_splash = NULL;
|
||||
|
||||
/* get the hardware we're referencing */
|
||||
fwup_resource_iter_create (&iter);
|
||||
re = fu_plugin_uefi_find_resource (iter, device, error);
|
||||
if (re == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* test the flash counter */
|
||||
flashes_left = fu_device_get_flashes_left (device);
|
||||
if (flashes_left > 0) {
|
||||
@ -481,6 +378,7 @@ fu_plugin_update (FuPlugin *plugin,
|
||||
if (!fu_plugin_uefi_esp_mounted (plugin, error))
|
||||
return FALSE;
|
||||
|
||||
|
||||
/* perform the update */
|
||||
g_debug ("Performing UEFI capsule update");
|
||||
fu_device_set_status (device, FWUPD_STATUS_SCHEDULING);
|
||||
@ -488,7 +386,7 @@ fu_plugin_update (FuPlugin *plugin,
|
||||
g_debug ("failed to upload UEFI UX capsule text: %s",
|
||||
error_splash->message);
|
||||
}
|
||||
if (!fu_plugin_uefi_update_resource (re, hardware_instance, blob_fw, error))
|
||||
if (!fu_device_write_firmware (device, blob_fw, error))
|
||||
return FALSE;
|
||||
|
||||
/* record boot information to system log for future debugging */
|
||||
|
||||
416
plugins/uefi/fu-uefi-bootmgr.c
Normal file
416
plugins/uefi/fu-uefi-bootmgr.c
Normal file
@ -0,0 +1,416 @@
|
||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
*
|
||||
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <efivar/efiboot.h>
|
||||
#include <efivar/efivar.h>
|
||||
#include <gio/gio.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "fu-ucs2.h"
|
||||
#include "fu-uefi-bootmgr.h"
|
||||
#include "fu-uefi-common.h"
|
||||
|
||||
/* XXX PJFIX: this should be in efiboot-loadopt.h in efivar */
|
||||
#define LOAD_OPTION_ACTIVE 0x00000001
|
||||
|
||||
static gboolean
|
||||
fu_uefi_bootmgr_add_to_boot_order (guint16 boot_entry, GError **error)
|
||||
{
|
||||
gsize boot_order_size = 0;
|
||||
gint rc;
|
||||
guint i = 0;
|
||||
guint32 attr = EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS;
|
||||
g_autofree guint16 *boot_order = NULL;
|
||||
g_autofree guint16 *new_boot_order = NULL;
|
||||
|
||||
/* get size of the BootOrder */
|
||||
rc = efi_get_variable_size (efi_guid_global, "BootOrder", &boot_order_size);
|
||||
if (rc == ENOENT) {
|
||||
boot_order_size = 0;
|
||||
efi_error_clear ();
|
||||
} else if (rc < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_get_variable_size() failed");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* get the current boot order */
|
||||
if (boot_order_size != 0) {
|
||||
rc = efi_get_variable (efi_guid_global, "BootOrder",
|
||||
(guint8 **)&boot_order, &boot_order_size,
|
||||
&attr);
|
||||
if (rc < 0) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_get_variable(BootOrder) failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* already set next */
|
||||
for (i = 0; i < boot_order_size / sizeof (guint16); i++) {
|
||||
guint16 val = boot_order[i];
|
||||
if (val == boot_entry)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* add the new boot index to the end of the list */
|
||||
new_boot_order = g_malloc0 (boot_order_size + sizeof (guint16));
|
||||
if (boot_order_size != 0)
|
||||
memcpy (new_boot_order, boot_order, boot_order_size);
|
||||
|
||||
i = boot_order_size / sizeof (guint16);
|
||||
new_boot_order[i] = boot_entry;
|
||||
boot_order_size += sizeof (guint16);
|
||||
rc = efi_set_variable(efi_guid_global, "BootOrder",
|
||||
(guint8 *)new_boot_order, boot_order_size,
|
||||
attr, 0644);
|
||||
if (rc < 0) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_set_variable(BootOrder) failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_uefi_setup_bootnext_with_dp (const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error)
|
||||
{
|
||||
efi_guid_t *guid = NULL;
|
||||
efi_load_option *loadopt = NULL;
|
||||
gchar *name = NULL;
|
||||
gint rc;
|
||||
gint set_entries[0x10000 / sizeof(gint)] = {0,};
|
||||
gsize var_data_size = 0;
|
||||
guint16 real_boot16;
|
||||
guint32 attr;
|
||||
guint32 boot_next = 0x10000;
|
||||
g_autofree guint8 *var_data = NULL;
|
||||
|
||||
while ((rc = efi_get_next_variable_name (&guid, &name)) > 0) {
|
||||
gint div, mod;
|
||||
gint scanned = 0;
|
||||
gssize sz;
|
||||
guint16 entry = 0;
|
||||
efidp found_dp;
|
||||
g_autofree guint8 *var_data_tmp = NULL;
|
||||
|
||||
if (efi_guid_cmp (guid, &efi_guid_global))
|
||||
continue;
|
||||
rc = sscanf (name, "Boot%hX%n", &entry, &scanned);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"failed to parse Boot entry %s", name);
|
||||
return FALSE;
|
||||
}
|
||||
if (rc != 1)
|
||||
continue;
|
||||
if (scanned != 8)
|
||||
continue;
|
||||
|
||||
div = entry / (sizeof(set_entries[0]) * 8);
|
||||
mod = entry % (sizeof(set_entries[0]) * 8);
|
||||
|
||||
set_entries[div] |= 1 << mod;
|
||||
|
||||
rc = efi_get_variable (*guid, name, &var_data_tmp, &var_data_size, &attr);
|
||||
if (rc < 0) {
|
||||
g_debug ("efi_get_variable(%s) failed", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
loadopt = (efi_load_option *)var_data_tmp;
|
||||
if (!efi_loadopt_is_valid(loadopt, var_data_size)) {
|
||||
g_debug ("load option was invalid");
|
||||
continue;
|
||||
}
|
||||
|
||||
sz = efi_loadopt_pathlen(loadopt, var_data_size);
|
||||
if (sz != efidp_size((efidp)dp_buf)) {
|
||||
g_debug ("pathlen device path doesn't match");
|
||||
continue;
|
||||
}
|
||||
|
||||
found_dp = efi_loadopt_path (loadopt, var_data_size);
|
||||
if (memcmp (found_dp, dp_buf, sz)) {
|
||||
g_debug ("found_dp/dp_buf device path doesn't match");
|
||||
}
|
||||
|
||||
if ((gssize)var_data_size != opt_size) {
|
||||
g_debug ("variable data doesn't match");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memcmp (loadopt, opt, opt_size)) {
|
||||
g_debug ("load option doesn't match");
|
||||
continue;
|
||||
}
|
||||
|
||||
var_data = g_steal_pointer (&var_data_tmp);
|
||||
boot_next = entry;
|
||||
efi_error_clear ();
|
||||
break;
|
||||
}
|
||||
if (rc < 0) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"failed to find boot variable");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* already exists */
|
||||
if (var_data != NULL) {
|
||||
efi_loadopt_attr_set (loadopt, LOAD_OPTION_ACTIVE);
|
||||
rc = efi_set_variable (*guid, name, var_data,
|
||||
var_data_size, attr, 0644);
|
||||
if (rc < 0) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"could not set boot variable active");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* create a new one */
|
||||
} else {
|
||||
g_autofree gchar *boot_next_name = NULL;
|
||||
for (guint32 value = 0; value < 0x10000; value++) {
|
||||
gint div = value / (sizeof(set_entries[0]) * 8);
|
||||
gint mod = value % (sizeof(set_entries[0]) * 8);
|
||||
if (set_entries[div] & (1 << mod))
|
||||
continue;
|
||||
boot_next = value;
|
||||
break;
|
||||
}
|
||||
if (boot_next >= 0x10000) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"no free boot variables (tried %x)",
|
||||
boot_next);
|
||||
return FALSE;
|
||||
}
|
||||
boot_next_name = g_strdup_printf ("Boot%04X",
|
||||
(guint) (boot_next & 0xffff));
|
||||
rc = efi_set_variable (efi_guid_global, boot_next_name, opt, opt_size,
|
||||
EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS,
|
||||
0644);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"could not set boot variable %s: %d",
|
||||
boot_next_name, rc);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: conditionalize this on the UEFI version? */
|
||||
if(!fu_uefi_bootmgr_add_to_boot_order (boot_next, error))
|
||||
return FALSE;
|
||||
|
||||
/* set the boot next */
|
||||
real_boot16 = boot_next;
|
||||
rc = efi_set_variable (efi_guid_global, "BootNext", (guint8 *)&real_boot16, 2,
|
||||
EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS,
|
||||
0644);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"could not set BootNext(%" G_GUINT16_FORMAT ")",
|
||||
real_boot16);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_uefi_cmp_asset (const gchar *source, const gchar *target)
|
||||
{
|
||||
gsize len = 0;
|
||||
g_autofree gchar *source_checksum = NULL;
|
||||
g_autofree gchar *source_data = NULL;
|
||||
g_autofree gchar *target_checksum = NULL;
|
||||
g_autofree gchar *target_data = NULL;
|
||||
|
||||
/* nothing in target yet */
|
||||
if (!g_file_test (target, G_FILE_TEST_EXISTS))
|
||||
return FALSE;
|
||||
|
||||
/* test if the file needs to be updated */
|
||||
if (!g_file_get_contents (source, &source_data, &len, NULL))
|
||||
return FALSE;
|
||||
source_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
|
||||
(guchar *) source_data, len);
|
||||
if (!g_file_get_contents (target, &target_data, &len, NULL))
|
||||
return FALSE;
|
||||
target_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
|
||||
(guchar *) target_data, len);
|
||||
return g_strcmp0 (target_checksum, source_checksum) == 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_uefi_copy_asset (const gchar *source, const gchar *target, GError **error)
|
||||
{
|
||||
g_autoptr(GFile) source_file = g_file_new_for_path (source);
|
||||
g_autoptr(GFile) target_file = g_file_new_for_path (target);
|
||||
|
||||
if (!g_file_copy (source_file,
|
||||
target_file,
|
||||
G_FILE_COPY_OVERWRITE,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
error)) {
|
||||
g_prefix_error (error, "Failed to copy %s to %s: ",
|
||||
source, target);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_uefi_bootmgr_bootnext (const gchar *esp_path, GError **error)
|
||||
{
|
||||
gboolean use_fwup_path = FALSE;
|
||||
gsize loader_sz = 0;
|
||||
gssize opt_size = 0;
|
||||
gssize sz, dp_size = 0;
|
||||
guint32 attributes = LOAD_OPTION_ACTIVE;
|
||||
g_autofree guint16 *loader_str = NULL;
|
||||
g_autofree gchar *fwup_esp_path = NULL;
|
||||
g_autofree gchar *fwup_fs_basename = NULL;
|
||||
g_autofree gchar *label = NULL;
|
||||
g_autofree gchar *shim_app = NULL;
|
||||
g_autofree guint8 *dp_buf = NULL;
|
||||
g_autofree guint8 *opt = NULL;
|
||||
g_autofree gchar *source_app = NULL;
|
||||
g_autofree gchar *target_app = NULL;
|
||||
|
||||
/* skip for self tests */
|
||||
if (g_getenv ("FWUPD_UEFI_ESP_PATH") != NULL)
|
||||
return TRUE;
|
||||
|
||||
/* if secure boot was turned on this might need to be installed separately */
|
||||
source_app = fu_uefi_get_built_app_path (error);
|
||||
if (source_app == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* test to make sure shim is there if we need it */
|
||||
shim_app = fu_uefi_get_esp_app_path (esp_path, "shim", error);
|
||||
if (shim_app == NULL)
|
||||
return FALSE;
|
||||
if (!g_file_test (shim_app, G_FILE_TEST_EXISTS)) {
|
||||
if (fu_uefi_secure_boot_enabled ()) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Secure boot is enabled, but shim isn't installed to the EFI system partition");
|
||||
return FALSE;
|
||||
}
|
||||
use_fwup_path = TRUE;
|
||||
}
|
||||
|
||||
/* test if correct asset in place */
|
||||
target_app = fu_uefi_get_esp_app_path (esp_path, "fwup", error);
|
||||
if (target_app == NULL)
|
||||
return FALSE;
|
||||
if (!fu_uefi_cmp_asset (source_app, target_app)) {
|
||||
if (!fu_uefi_copy_asset (source_app, target_app, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sz = efi_generate_file_device_path (dp_buf, dp_size, use_fwup_path
|
||||
? target_app
|
||||
: shim_app,
|
||||
EFIBOOT_OPTIONS_IGNORE_FS_ERROR|
|
||||
EFIBOOT_ABBREV_HD);
|
||||
if (sz < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_generate_file_device_path(%s) failed",
|
||||
use_fwup_path ? target_app : shim_app);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dp_size = sz;
|
||||
dp_buf = g_malloc0 (dp_size);
|
||||
fwup_fs_basename = g_path_get_basename (target_app);
|
||||
fwup_esp_path = g_strdup_printf ("\\%s", fwup_fs_basename);
|
||||
if (!use_fwup_path) {
|
||||
loader_str = fu_uft8_to_ucs2 (fwup_esp_path, -1);
|
||||
loader_sz = fu_ucs2_strlen (loader_str, -1) * 2;
|
||||
if (loader_sz)
|
||||
loader_sz += 2;
|
||||
}
|
||||
|
||||
sz = efi_generate_file_device_path (dp_buf, dp_size, use_fwup_path
|
||||
? target_app
|
||||
: shim_app,
|
||||
EFIBOOT_OPTIONS_IGNORE_FS_ERROR|
|
||||
EFIBOOT_ABBREV_HD);
|
||||
if (sz != dp_size) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_generate_file_device_path(%s) failed",
|
||||
use_fwup_path ? target_app : shim_app);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
label = g_strdup_printf ("Linux-Firmware-Updater %s", fwup_esp_path);
|
||||
sz = efi_loadopt_create (opt, opt_size, attributes,
|
||||
(efidp)dp_buf, dp_size,
|
||||
(guint8 *)label,
|
||||
(guint8 *)loader_str, loader_sz);
|
||||
if (sz < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"efi_loadopt_create(%s) failed",
|
||||
label);
|
||||
return FALSE;
|
||||
}
|
||||
opt = g_malloc0 (sz);
|
||||
opt_size = sz;
|
||||
sz = efi_loadopt_create (opt, opt_size, attributes,
|
||||
(efidp)dp_buf, dp_size,
|
||||
(guint8 *)label,
|
||||
(guint8 *)loader_str, loader_sz);
|
||||
if (sz != opt_size) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"loadopt size was unreasonable.");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_uefi_setup_bootnext_with_dp (dp_buf, opt, opt_size, error))
|
||||
return FALSE;
|
||||
efi_error_clear();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
22
plugins/uefi/fu-uefi-bootmgr.h
Normal file
22
plugins/uefi/fu-uefi-bootmgr.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
*
|
||||
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
||||
* Copyright (C) 2015-2017 Peter Jones <pjones@redhat.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef __FU_UEFI_BOOTMGR_H
|
||||
#define __FU_UEFI_BOOTMGR_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <efivar.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gboolean fu_uefi_bootmgr_bootnext (const gchar *esp_path,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __FU_UEFI_BOOTMGR_H */
|
||||
@ -18,6 +18,89 @@
|
||||
#include "fwupd-common.h"
|
||||
#include "fwupd-error.h"
|
||||
|
||||
static const gchar *
|
||||
fu_uefi_bootmgr_get_suffix (GError **error)
|
||||
{
|
||||
guint64 firmware_bits;
|
||||
struct {
|
||||
guint64 bits;
|
||||
const gchar *arch;
|
||||
} suffixes[] = {
|
||||
#if defined(__x86_64__)
|
||||
{ 64, "x64" },
|
||||
#elif defined(__aarch64__)
|
||||
{ 64, "aa64" },
|
||||
#endif
|
||||
#if defined(__x86_64__) || defined(__i386__) || defined(__i686__)
|
||||
{ 32, "ia32" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
|
||||
g_autofree gchar *sysfsefidir = g_build_filename (sysfsfwdir, "efi", NULL);
|
||||
firmware_bits = fu_uefi_read_file_as_uint64 (sysfsefidir, "fw_platform_size");
|
||||
if (firmware_bits == 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"%s/fw_platform_size cannot be found",
|
||||
sysfsefidir);
|
||||
return NULL;
|
||||
}
|
||||
for (guint i = 0; suffixes[i].arch != NULL; i++) {
|
||||
if (firmware_bits != suffixes[i].bits)
|
||||
continue;
|
||||
return suffixes[i].arch;
|
||||
}
|
||||
|
||||
/* this should exist */
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"%s/fw_platform_size has unknown value %" G_GUINT64_FORMAT,
|
||||
sysfsefidir, firmware_bits);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
fu_uefi_get_esp_app_path (const gchar *esp_path, const gchar *cmd, GError **error)
|
||||
{
|
||||
const gchar *suffix = fu_uefi_bootmgr_get_suffix (error);
|
||||
g_autofree gchar *base = NULL;
|
||||
if (suffix == NULL)
|
||||
return NULL;
|
||||
base = fu_uefi_get_esp_path_for_os (esp_path);
|
||||
return g_strdup_printf ("%s/%s%s.efi", base, cmd, suffix);
|
||||
}
|
||||
|
||||
gchar *
|
||||
fu_uefi_get_built_app_path (GError **error)
|
||||
{
|
||||
const gchar *extension = "";
|
||||
const gchar *suffix;
|
||||
g_autofree gchar *source_path = NULL;
|
||||
if (fu_uefi_secure_boot_enabled ())
|
||||
extension = ".signed";
|
||||
if (g_file_test (EFI_APP_LOCATION_BUILD, G_FILE_TEST_EXISTS))
|
||||
return g_strdup_printf ("%s%s", EFI_APP_LOCATION_BUILD, extension);
|
||||
suffix = fu_uefi_bootmgr_get_suffix (error);
|
||||
if (suffix == NULL)
|
||||
return NULL;
|
||||
source_path = g_strdup_printf ("%s/fwup%s.efi%s",
|
||||
EFI_APP_LOCATION,
|
||||
suffix,
|
||||
extension);
|
||||
if (!g_file_test (source_path, G_FILE_TEST_EXISTS)) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"%s cannot be found",
|
||||
source_path);
|
||||
return NULL;
|
||||
}
|
||||
return g_steal_pointer (&source_path);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_uefi_get_framebuffer_size (guint32 *width, guint32 *height, GError **error)
|
||||
{
|
||||
@ -179,3 +262,28 @@ fu_uefi_read_file_as_uint64 (const gchar *path, const gchar *attr_name)
|
||||
return g_ascii_strtoull (data, NULL, 10);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_uefi_prefix_efi_errors (GError **error)
|
||||
{
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
gint rc = 1;
|
||||
for (gint i = 0; rc > 0; i++) {
|
||||
gchar *filename = NULL;
|
||||
gchar *function = NULL;
|
||||
gchar *message = NULL;
|
||||
gint line = 0;
|
||||
gint err = 0;
|
||||
|
||||
rc = efi_error_get (i, &filename, &function, &line,
|
||||
&message, &err);
|
||||
if (rc <= 0)
|
||||
break;
|
||||
g_string_append_printf (str, "{error #%d} %s:%d %s(): %s: %s\t",
|
||||
i, filename, line, function,
|
||||
message, strerror (err));
|
||||
}
|
||||
if (str->len > 1)
|
||||
g_string_truncate (str, str->len - 1);
|
||||
g_prefix_error (error, "%s: ", str->str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -18,6 +18,9 @@ G_BEGIN_DECLS
|
||||
#define EFI_CAPSULE_HEADER_FLAGS_POPULATE_SYSTEM_TABLE 0x00020000
|
||||
#define EFI_CAPSULE_HEADER_FLAGS_INITIATE_RESET 0x00040000
|
||||
|
||||
#define EFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE 0x00000001
|
||||
#define EFI_UPDATE_INFO_STATUS_ATTEMPTED 0x00000002
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
guint16 year;
|
||||
guint8 month;
|
||||
@ -58,6 +61,10 @@ typedef struct __attribute__((__packed__)) {
|
||||
guint32 status;
|
||||
} efi_update_info_t;
|
||||
|
||||
gchar *fu_uefi_get_esp_app_path (const gchar *esp_path,
|
||||
const gchar *cmd,
|
||||
GError **error);
|
||||
gchar *fu_uefi_get_built_app_path (GError **error);
|
||||
gboolean fu_uefi_get_bitmap_size (const guint8 *buf,
|
||||
gsize bufsz,
|
||||
guint32 *width,
|
||||
@ -72,6 +79,7 @@ GPtrArray *fu_uefi_get_esrt_entry_paths (const gchar *esrt_path,
|
||||
GError **error);
|
||||
guint64 fu_uefi_read_file_as_uint64 (const gchar *path,
|
||||
const gchar *attr_name);
|
||||
gboolean fu_uefi_prefix_efi_errors (GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@ -9,9 +9,12 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <efivar.h>
|
||||
#include <efivar/efiboot.h>
|
||||
|
||||
#include "fu-uefi-common.h"
|
||||
#include "fu-uefi-device.h"
|
||||
#include "fu-uefi-bootmgr.h"
|
||||
#include "fu-uefi-vars.h"
|
||||
|
||||
struct _FuUefiDevice {
|
||||
@ -225,6 +228,131 @@ fu_uefi_device_clear_status (FuUefiDevice *self, GError **error)
|
||||
return fu_uefi_device_set_efivar (self, data, datasz, 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;
|
||||
|
||||
/* 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 FALSE;
|
||||
}
|
||||
|
||||
/* 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 FALSE;
|
||||
}
|
||||
|
||||
/* 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 FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
if (bufsz != NULL)
|
||||
*bufsz = sz;
|
||||
return g_steal_pointer (&dp_buf);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_uefi_device_write_firmware (FuDevice *device, GBytes *fw, GError **error)
|
||||
{
|
||||
FuUefiDevice *self = FU_UEFI_DEVICE (device);
|
||||
const gchar *esp_path = fu_device_get_metadata (device, "EspPath");
|
||||
efi_guid_t guid;
|
||||
efi_update_info_t info;
|
||||
gsize datasz = 0;
|
||||
gsize dp_bufsz = 0;
|
||||
g_autofree gchar *basename = NULL;
|
||||
g_autofree gchar *directory = NULL;
|
||||
g_autofree gchar *fn = NULL;
|
||||
g_autofree guint8 *data = NULL;
|
||||
g_autofree guint8 *dp_buf = NULL;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* save the blob to the ESP */
|
||||
directory = fu_uefi_get_esp_path_for_os (esp_path);
|
||||
basename = g_strdup_printf ("fwupdate-%s.cap", self->fw_class);
|
||||
fn = g_build_filename (directory, "fw", basename, NULL);
|
||||
if (!fu_common_mkdir_parent (fn, error))
|
||||
return FALSE;
|
||||
if (!fu_common_set_contents_bytes (fn, fw, error))
|
||||
return FALSE;
|
||||
|
||||
/* set the blob header shared with fwup.efi */
|
||||
memset (&info, 0x0, sizeof(info));
|
||||
info.status = EFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE;
|
||||
info.capsule_flags = self->capsule_flags;
|
||||
info.update_info_version = 0x7;
|
||||
info.hw_inst = self->fmp_hardware_instance;
|
||||
memcpy (&guid, &info.guid, sizeof(efi_guid_t));
|
||||
if (efi_str_to_guid (self->fw_class, &guid) < 0) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to get convert GUID");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set the body as the device path */
|
||||
if (g_getenv ("FWUPD_UEFI_ESP_PATH") == NULL) {
|
||||
dp_buf = fu_uefi_device_build_dp_buf (fn, &dp_bufsz, error);
|
||||
if (dp_buf == NULL) {
|
||||
fu_uefi_prefix_efi_errors (error);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* save this header and body to the hardware */
|
||||
datasz = sizeof(info) + dp_bufsz;
|
||||
data = g_malloc0 (datasz);
|
||||
memcpy (data, &info, sizeof(info));
|
||||
memcpy (data + sizeof(info), dp_buf, dp_bufsz);
|
||||
if (!fu_uefi_device_set_efivar (self, data, datasz, error)) {
|
||||
fu_uefi_prefix_efi_errors (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* update the firmware before the bootloader runs */
|
||||
if (!fu_uefi_bootmgr_bootnext (esp_path, error))
|
||||
return FALSE;
|
||||
|
||||
/* success! */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_uefi_device_init (FuUefiDevice *self)
|
||||
{
|
||||
@ -247,6 +375,7 @@ fu_uefi_device_class_init (FuUefiDeviceClass *klass)
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
object_class->finalize = fu_uefi_device_finalize;
|
||||
klass_device->to_string = fu_uefi_device_to_string;
|
||||
klass_device->write_firmware = fu_uefi_device_write_firmware;
|
||||
}
|
||||
|
||||
FuUefiDevice *
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
subdir('efi')
|
||||
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginUefi"']
|
||||
cargs += '-DEFI_APP_LOCATION_BUILD="' + app.full_path() + '"'
|
||||
|
||||
shared_module('fu_plugin_uefi',
|
||||
sources : [
|
||||
'fu-plugin-uefi.c',
|
||||
'fu-uefi-bgrt.c',
|
||||
'fu-ucs2.c',
|
||||
'fu-uefi-bootmgr.c',
|
||||
'fu-uefi-common.c',
|
||||
'fu-uefi-device.c',
|
||||
'fu-uefi-vars.c',
|
||||
@ -18,6 +23,8 @@ shared_module('fu_plugin_uefi',
|
||||
c_args : cargs,
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
efivar,
|
||||
efiboot,
|
||||
fwup,
|
||||
],
|
||||
)
|
||||
@ -34,6 +41,7 @@ if get_option('tests')
|
||||
sources : [
|
||||
'fu-self-test.c',
|
||||
'fu-uefi-bgrt.c',
|
||||
'fu-uefi-bootmgr.c',
|
||||
'fu-uefi-common.c',
|
||||
'fu-uefi-device.c',
|
||||
'fu-uefi-vars.c',
|
||||
@ -46,6 +54,8 @@ if get_option('tests')
|
||||
],
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
efivar,
|
||||
efiboot,
|
||||
fwup,
|
||||
],
|
||||
link_with : [
|
||||
@ -56,5 +66,3 @@ if get_option('tests')
|
||||
)
|
||||
test('uefi-self-test', e)
|
||||
endif
|
||||
|
||||
subdir('efi')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user