u-boot-brain/lib/efi_loader/efi_capsule.c

664 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* EFI Capsule
*
* Copyright (c) 2018 Linaro Limited
* Author: AKASHI Takahiro
*/
#include <common.h>
#include <efi_loader.h>
#include <efi_variable.h>
#include <fs.h>
#include <malloc.h>
#include <mapmem.h>
#include <sort.h>
const efi_guid_t efi_guid_capsule_report = EFI_CAPSULE_REPORT_GUID;
#ifdef CONFIG_EFI_CAPSULE_ON_DISK
/* for file system access */
static struct efi_file_handle *bootdev_root;
#endif
/**
* get_last_capsule - get the last capsule index
*
* Retrieve the index of the capsule invoked last time from "CapsuleLast"
* variable.
*
* Return:
* * > 0 - the last capsule index invoked
* * 0xffff - on error, or no capsule invoked yet
*/
static __maybe_unused unsigned int get_last_capsule(void)
{
u16 value16[11]; /* "CapsuleXXXX": non-null-terminated */
char value[11], *p;
efi_uintn_t size;
unsigned long index = 0xffff;
efi_status_t ret;
size = sizeof(value16);
ret = efi_get_variable_int(L"CapsuleLast", &efi_guid_capsule_report,
NULL, &size, value16, NULL);
if (ret != EFI_SUCCESS || u16_strncmp(value16, L"Capsule", 7))
goto err;
p = value;
utf16_utf8_strcpy(&p, value16);
strict_strtoul(&value[7], 16, &index);
err:
return index;
}
/**
* set_capsule_result - set a result variable
* @capsule: Capsule
* @return_status: Return status
*
* Create and set a result variable, "CapsuleXXXX", for the capsule,
* @capsule.
*/
static __maybe_unused
void set_capsule_result(int index, struct efi_capsule_header *capsule,
efi_status_t return_status)
{
u16 variable_name16[12];
struct efi_capsule_result_variable_header result;
struct efi_time time;
efi_status_t ret;
efi_create_indexed_name(variable_name16, "Capsule", index);
result.variable_total_size = sizeof(result);
result.capsule_guid = capsule->capsule_guid;
ret = EFI_CALL((*efi_runtime_services.get_time)(&time, NULL));
if (ret == EFI_SUCCESS)
memcpy(&result.capsule_processed, &time, sizeof(time));
else
memset(&result.capsule_processed, 0, sizeof(time));
result.capsule_status = return_status;
ret = efi_set_variable(variable_name16, &efi_guid_capsule_report,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(result), &result);
if (ret)
printf("EFI: creating %ls failed\n", variable_name16);
}
/**
* efi_update_capsule() - process information from operating system
* @capsule_header_array: Array of virtual address pointers
* @capsule_count: Number of pointers in capsule_header_array
* @scatter_gather_list: Array of physical address pointers
*
* This function implements the UpdateCapsule() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* Return: status code
*/
efi_status_t EFIAPI efi_update_capsule(
struct efi_capsule_header **capsule_header_array,
efi_uintn_t capsule_count,
u64 scatter_gather_list)
{
struct efi_capsule_header *capsule;
unsigned int i;
efi_status_t ret;
EFI_ENTRY("%p, %lu, %llu\n", capsule_header_array, capsule_count,
scatter_gather_list);
if (!capsule_count) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
ret = EFI_UNSUPPORTED;
for (i = 0, capsule = *capsule_header_array; i < capsule_count;
i++, capsule = *(++capsule_header_array)) {
}
out:
return EFI_EXIT(ret);
}
/**
* efi_query_capsule_caps() - check if capsule is supported
* @capsule_header_array: Array of virtual pointers
* @capsule_count: Number of pointers in capsule_header_array
* @maximum_capsule_size: Maximum capsule size
* @reset_type: Type of reset needed for capsule update
*
* This function implements the QueryCapsuleCapabilities() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* Return: status code
*/
efi_status_t EFIAPI efi_query_capsule_caps(
struct efi_capsule_header **capsule_header_array,
efi_uintn_t capsule_count,
u64 *maximum_capsule_size,
u32 *reset_type)
{
struct efi_capsule_header *capsule __attribute__((unused));
unsigned int i;
efi_status_t ret;
EFI_ENTRY("%p, %lu, %p, %p\n", capsule_header_array, capsule_count,
maximum_capsule_size, reset_type);
if (!maximum_capsule_size) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
*maximum_capsule_size = U64_MAX;
*reset_type = EFI_RESET_COLD;
ret = EFI_SUCCESS;
for (i = 0, capsule = *capsule_header_array; i < capsule_count;
i++, capsule = *(++capsule_header_array)) {
/* TODO */
}
out:
return EFI_EXIT(ret);
}
#ifdef CONFIG_EFI_CAPSULE_ON_DISK
/**
* get_dp_device - retrieve a device path from boot variable
* @boot_var: Boot variable name
* @device_dp Device path
*
* Retrieve a device patch from boot variable, @boot_var.
*
* Return: status code
*/
static efi_status_t get_dp_device(u16 *boot_var,
struct efi_device_path **device_dp)
{
void *buf = NULL;
efi_uintn_t size;
struct efi_load_option lo;
struct efi_device_path *file_dp;
efi_status_t ret;
size = 0;
ret = efi_get_variable_int(boot_var, &efi_global_variable_guid,
NULL, &size, NULL, NULL);
if (ret == EFI_BUFFER_TOO_SMALL) {
buf = malloc(size);
if (!buf)
return EFI_OUT_OF_RESOURCES;
ret = efi_get_variable_int(boot_var, &efi_global_variable_guid,
NULL, &size, buf, NULL);
}
if (ret != EFI_SUCCESS)
return ret;
efi_deserialize_load_option(&lo, buf, &size);
if (lo.attributes & LOAD_OPTION_ACTIVE) {
efi_dp_split_file_path(lo.file_path, device_dp, &file_dp);
efi_free_pool(file_dp);
ret = EFI_SUCCESS;
} else {
ret = EFI_NOT_FOUND;
}
free(buf);
return ret;
}
/**
* device_is_present_and_system_part - check if a device exists
* @dp Device path
*
* Check if a device pointed to by the device path, @dp, exists and is
* located in UEFI system partition.
*
* Return: true - yes, false - no
*/
static bool device_is_present_and_system_part(struct efi_device_path *dp)
{
efi_handle_t handle;
handle = efi_dp_find_obj(dp, NULL);
if (!handle)
return false;
return efi_disk_is_system_part(handle);
}
/**
* find_boot_device - identify the boot device
*
* Identify the boot device from boot-related variables as UEFI
* specification describes and put its handle into bootdev_root.
*
* Return: status code
*/
static efi_status_t find_boot_device(void)
{
char boot_var[9];
u16 boot_var16[9], *p, bootnext, *boot_order = NULL;
efi_uintn_t size;
int i, num;
struct efi_simple_file_system_protocol *volume;
struct efi_device_path *boot_dev = NULL;
efi_status_t ret;
/* find active boot device in BootNext */
bootnext = 0;
size = sizeof(bootnext);
ret = efi_get_variable_int(L"BootNext",
(efi_guid_t *)&efi_global_variable_guid,
NULL, &size, &bootnext, NULL);
if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) {
/* BootNext does exist here */
if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) {
printf("BootNext must be 16-bit integer\n");
goto skip;
}
sprintf((char *)boot_var, "Boot%04X", bootnext);
p = boot_var16;
utf8_utf16_strcpy(&p, boot_var);
ret = get_dp_device(boot_var16, &boot_dev);
if (ret == EFI_SUCCESS) {
if (device_is_present_and_system_part(boot_dev)) {
goto out;
} else {
efi_free_pool(boot_dev);
boot_dev = NULL;
}
}
}
skip:
/* find active boot device in BootOrder */
size = 0;
ret = efi_get_variable_int(L"BootOrder", &efi_global_variable_guid,
NULL, &size, NULL, NULL);
if (ret == EFI_BUFFER_TOO_SMALL) {
boot_order = malloc(size);
if (!boot_order) {
ret = EFI_OUT_OF_RESOURCES;
goto out;
}
ret = efi_get_variable_int(L"BootOrder",
&efi_global_variable_guid,
NULL, &size, boot_order, NULL);
}
if (ret != EFI_SUCCESS)
goto out;
/* check in higher order */
num = size / sizeof(u16);
for (i = 0; i < num; i++) {
sprintf((char *)boot_var, "Boot%04X", boot_order[i]);
p = boot_var16;
utf8_utf16_strcpy(&p, boot_var);
ret = get_dp_device(boot_var16, &boot_dev);
if (ret != EFI_SUCCESS)
continue;
if (device_is_present_and_system_part(boot_dev))
break;
efi_free_pool(boot_dev);
boot_dev = NULL;
}
out:
if (boot_dev) {
u16 *path_str;
path_str = efi_dp_str(boot_dev);
EFI_PRINT("EFI Capsule: bootdev is %ls\n", path_str);
efi_free_pool(path_str);
volume = efi_fs_from_path(boot_dev);
if (!volume)
ret = EFI_DEVICE_ERROR;
else
ret = EFI_CALL(volume->open_volume(volume,
&bootdev_root));
efi_free_pool(boot_dev);
} else {
ret = EFI_NOT_FOUND;
}
free(boot_order);
return ret;
}
/**
* efi_capsule_scan_dir - traverse a capsule directory in boot device
* @files: Array of file names
* @num: Number of elements in @files
*
* Traverse a capsule directory in boot device.
* Called by initialization code, and returns an array of capsule file
* names in @files.
*
* Return: status code
*/
static efi_status_t efi_capsule_scan_dir(u16 ***files, unsigned int *num)
{
struct efi_file_handle *dirh;
struct efi_file_info *dirent;
efi_uintn_t dirent_size, tmp_size;
unsigned int count;
u16 **tmp_files;
efi_status_t ret;
ret = find_boot_device();
if (ret == EFI_NOT_FOUND) {
EFI_PRINT("EFI Capsule: bootdev is not set\n");
*num = 0;
return EFI_SUCCESS;
} else if (ret != EFI_SUCCESS) {
return EFI_DEVICE_ERROR;
}
/* count capsule files */
ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
EFI_CAPSULE_DIR,
EFI_FILE_MODE_READ, 0));
if (ret != EFI_SUCCESS) {
*num = 0;
return EFI_SUCCESS;
}
dirent_size = 256;
dirent = malloc(dirent_size);
if (!dirent)
return EFI_OUT_OF_RESOURCES;
count = 0;
while (1) {
tmp_size = dirent_size;
ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
if (ret == EFI_BUFFER_TOO_SMALL) {
dirent = realloc(dirent, tmp_size);
if (!dirent) {
ret = EFI_OUT_OF_RESOURCES;
goto err;
}
dirent_size = tmp_size;
ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
}
if (ret != EFI_SUCCESS)
goto err;
if (!tmp_size)
break;
if (!(dirent->attribute & EFI_FILE_DIRECTORY) &&
u16_strcmp(dirent->file_name, L".") &&
u16_strcmp(dirent->file_name, L".."))
count++;
}
ret = EFI_CALL((*dirh->setpos)(dirh, 0));
if (ret != EFI_SUCCESS)
goto err;
/* make a list */
tmp_files = malloc(count * sizeof(*files));
if (!tmp_files) {
ret = EFI_OUT_OF_RESOURCES;
goto err;
}
count = 0;
while (1) {
tmp_size = dirent_size;
ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
if (ret != EFI_SUCCESS)
goto err;
if (!tmp_size)
break;
if (!(dirent->attribute & EFI_FILE_DIRECTORY) &&
u16_strcmp(dirent->file_name, L".") &&
u16_strcmp(dirent->file_name, L".."))
tmp_files[count++] = u16_strdup(dirent->file_name);
}
/* ignore an error */
EFI_CALL((*dirh->close)(dirh));
/* in ascii order */
/* FIXME: u16 version of strcasecmp */
qsort(tmp_files, count, sizeof(*tmp_files),
(int (*)(const void *, const void *))strcasecmp);
*files = tmp_files;
*num = count;
ret = EFI_SUCCESS;
err:
free(dirent);
return ret;
}
/**
* efi_capsule_read_file - read in a capsule file
* @filename: File name
* @capsule: Pointer to buffer for capsule
*
* Read a capsule file and put its content in @capsule.
*
* Return: status code
*/
static efi_status_t efi_capsule_read_file(const u16 *filename,
struct efi_capsule_header **capsule)
{
struct efi_file_handle *dirh, *fh;
struct efi_file_info *file_info = NULL;
struct efi_capsule_header *buf = NULL;
efi_uintn_t size;
efi_status_t ret;
ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
EFI_CAPSULE_DIR,
EFI_FILE_MODE_READ, 0));
if (ret != EFI_SUCCESS)
return ret;
ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename,
EFI_FILE_MODE_READ, 0));
/* ignore an error */
EFI_CALL((*dirh->close)(dirh));
if (ret != EFI_SUCCESS)
return ret;
/* file size */
size = 0;
ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid,
&size, file_info));
if (ret == EFI_BUFFER_TOO_SMALL) {
file_info = malloc(size);
if (!file_info) {
ret = EFI_OUT_OF_RESOURCES;
goto err;
}
ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid,
&size, file_info));
}
if (ret != EFI_SUCCESS)
goto err;
size = file_info->file_size;
free(file_info);
buf = malloc(size);
if (!buf) {
ret = EFI_OUT_OF_RESOURCES;
goto err;
}
/* fetch data */
ret = EFI_CALL((*fh->read)(fh, &size, buf));
if (ret == EFI_SUCCESS) {
if (size >= buf->capsule_image_size) {
*capsule = buf;
} else {
free(buf);
ret = EFI_INVALID_PARAMETER;
}
} else {
free(buf);
}
err:
EFI_CALL((*fh->close)(fh));
return ret;
}
/**
* efi_capsule_delete_file - delete a capsule file
* @filename: File name
*
* Delete a capsule file from capsule directory.
*
* Return: status code
*/
static efi_status_t efi_capsule_delete_file(const u16 *filename)
{
struct efi_file_handle *dirh, *fh;
efi_status_t ret;
ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
EFI_CAPSULE_DIR,
EFI_FILE_MODE_READ, 0));
if (ret != EFI_SUCCESS)
return ret;
ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename,
EFI_FILE_MODE_READ, 0));
/* ignore an error */
EFI_CALL((*dirh->close)(dirh));
ret = EFI_CALL((*fh->delete)(fh));
return ret;
}
/**
* efi_capsule_scan_done - reset a scan help function
*
* Reset a scan help function
*/
static void efi_capsule_scan_done(void)
{
EFI_CALL((*bootdev_root->close)(bootdev_root));
bootdev_root = NULL;
}
/**
* arch_efi_load_capsule_drivers - initialize capsule drivers
*
* Architecture or board specific initialization routine
*
* Return: status code
*/
efi_status_t __weak arch_efi_load_capsule_drivers(void)
{
return EFI_SUCCESS;
}
/**
* efi_launch_capsule - launch capsules
*
* Launch all the capsules in system at boot time.
* Called by efi init code
*
* Return: status codde
*/
efi_status_t efi_launch_capsules(void)
{
u64 os_indications;
efi_uintn_t size;
struct efi_capsule_header *capsule = NULL;
u16 **files;
unsigned int nfiles, index, i;
u16 variable_name16[12];
efi_status_t ret;
size = sizeof(os_indications);
ret = efi_get_variable_int(L"OsIndications", &efi_global_variable_guid,
NULL, &size, &os_indications, NULL);
if (ret != EFI_SUCCESS ||
!(os_indications
& EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED))
return EFI_SUCCESS;
index = get_last_capsule();
/* Load capsule drivers */
ret = arch_efi_load_capsule_drivers();
if (ret != EFI_SUCCESS)
return ret;
/*
* Find capsules on disk.
* All the capsules are collected at the beginning because
* capsule files will be removed instantly.
*/
nfiles = 0;
files = NULL;
ret = efi_capsule_scan_dir(&files, &nfiles);
if (ret != EFI_SUCCESS)
return ret;
if (!nfiles)
return EFI_SUCCESS;
/* Launch capsules */
for (i = 0, ++index; i < nfiles; i++, index++) {
EFI_PRINT("capsule from %ls ...\n", files[i]);
if (index > 0xffff)
index = 0;
ret = efi_capsule_read_file(files[i], &capsule);
if (ret == EFI_SUCCESS) {
ret = EFI_CALL(efi_update_capsule(&capsule, 1, 0));
if (ret != EFI_SUCCESS)
printf("EFI Capsule update failed at %ls\n",
files[i]);
free(capsule);
} else {
printf("EFI: reading capsule failed: %ls\n",
files[i]);
}
/* create CapsuleXXXX */
set_capsule_result(index, capsule, ret);
/* delete a capsule either in case of success or failure */
ret = efi_capsule_delete_file(files[i]);
if (ret != EFI_SUCCESS)
printf("EFI: deleting a capsule file failed: %ls\n",
files[i]);
}
efi_capsule_scan_done();
for (i = 0; i < nfiles; i++)
free(files[i]);
free(files);
/* CapsuleLast */
efi_create_indexed_name(variable_name16, "Capsule", index - 1);
efi_set_variable_int(L"CapsuleLast", &efi_guid_capsule_report,
EFI_VARIABLE_READ_ONLY |
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
22, variable_name16, false);
return ret;
}
#endif /* CONFIG_EFI_CAPSULE_ON_DISK */