From 7395877e6895231255baab6838d8b92e44f0342b Mon Sep 17 00:00:00 2001 Message-Id: <7395877e6895231255baab6838d8b92e44f0342b.1424244086.git.hock.leong.kweh@intel.com> In-Reply-To: <90701184a919bb58c8c71cd5d9d2e28cc722e5d1.1424244086.git.hock.leong.kweh@intel.com> References: <90701184a919bb58c8c71cd5d9d2e28cc722e5d1.1424244086.git.hock.leong.kweh@intel.com> From: "Kweh, Hock Leong" Date: Tue, 2 Sep 2014 22:10:42 +0800 Subject: [PATCH 2/2] efi: Capsule update with user helper interface Introducing a kernel module to expose user helper interface for user to upload capsule binaries. This module leverage the request_firmware_nowait() to expose an interface to user. Example steps to load the capsule binary: 1.) num=$(cat /sys/devices/platform/efi_capsule_user_helper/capsule_ticket) 2.) continue to acquire the number if the number is '0' 3.) echo 1 > /sys/class/firmware/efi-capsule-file/loading 4.) cat capsule.bin > /sys/class/firmware/efi-capsule-file/data 5.) echo 0 > /sys/class/firmware/efi-capsule-file/loading 6.) cat /sys/devices/platform/efi_capsule_user_helper/capsule_report | grep $num Whereas, this module does not allow the capsule binaries to be obtained from the request_firmware library search path. If any capsule binary loaded from firmware seach path, the module will abort the capsule storing function. Besides the request_firmware user helper interface, this module also exposes two read only file notes to interact with user. - 'capsule_ticket' allows user to acquire a number and enabling firmware_class user helper interface for uploading the capsule binary. The ticket number is starting from 001 to 999. If obtained a number of '0' means someone is performing the loading. Please wait until the loading is done and obtained the number in the correct range then perform to the next steps. A 2 minutes time out is implemented for each loading activity. - 'capsule_report' allows user to verify the status of the capsule loading. User can verify by using the number acquired at the 1st step with 'capsule_ticket'. This design cater the preventing of multi capsules concurrent loading problem as well as the status reporting synchronization. Cc: Matt Fleming Signed-off-by: Kweh, Hock Leong --- drivers/firmware/efi/Kconfig | 13 + drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/efi-capsule-user-helper.c | 316 ++++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 drivers/firmware/efi/efi-capsule-user-helper.c diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index f712d47..7dc814e 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -60,6 +60,19 @@ config EFI_RUNTIME_WRAPPERS config EFI_ARMSTUB bool +config EFI_CAPSULE_USER_HELPER + tristate "EFI capsule user mode helper" + depends on EFI + select FW_LOADER + select FW_LOADER_USER_HELPER + help + This option exposes the user mode helper interface for user to load + EFI capsule binary and update the EFI firmware after system reboot. + This feature does not support auto locating capsule binaries at the + firmware lib search path. + + If unsure, say N. + endmenu config UEFI_CPER diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 698846e..63f6910 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_UEFI_CPER) += cper.o obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o obj-$(CONFIG_EFI_STUB) += libstub/ +obj-$(CONFIG_EFI_CAPSULE_USER_HELPER) += efi-capsule-user-helper.o diff --git a/drivers/firmware/efi/efi-capsule-user-helper.c b/drivers/firmware/efi/efi-capsule-user-helper.c new file mode 100644 index 0000000..05838f5 --- /dev/null +++ b/drivers/firmware/efi/efi-capsule-user-helper.c @@ -0,0 +1,316 @@ +/* + * EFI capsule user mode helper interface driver. + * + * Copyright 2015 Intel Corporation + * + * This file is part of the Linux kernel, and is made available under + * the terms of the GNU General Public License version 2. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAPSULE_NAME "efi-capsule-file" +#define DEV_NAME "efi_capsule_user_helper" +#define STRING_INTEGER_MAX_LENGTH 13 +#define MAX_SYSFS_BUF_LENGTH 4096 +#define CAPSULE_USER_HELPER_TIMEOUT 120 +#define CAPSULE_REPORT_MAX_STR_SIZE 32 +#define CAPSULE_RESULT_PASS "successful" +#define CAPSULE_RESULT_FAIL "failed " +#define CAPSULE_RESULT_EXIT "aborted " + +static DEFINE_MUTEX(user_helper_lock); +static int capsule_count; +static struct platform_device *efi_capsule_pdev; +static struct delayed_work timeout_work; +static unsigned char *report_buf; +static int report_buf_head_off, report_buf_write_off; + +/* + * This function will store the capsule binary and pass it to + * efi_capsule_update() API in capsule.c + */ +static int efi_capsule_store(const struct firmware *fw) +{ + int i; + int ret; + int count = fw->size; + int copy_size = (fw->size > PAGE_SIZE) ? PAGE_SIZE : fw->size; + int pages_needed = ALIGN(fw->size, PAGE_SIZE) >> PAGE_SHIFT; + struct page **pages; + void *page_data; + efi_capsule_header_t *capsule = NULL; + + pages = kmalloc_array(pages_needed, sizeof(void *), GFP_KERNEL); + if (!pages) { + pr_err("%s: kmalloc_array() failed\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < pages_needed; i++) { + pages[i] = alloc_page(GFP_KERNEL); + if (!pages[i]) { + pr_err("%s: alloc_page() failed\n", __func__); + --i; + ret = -ENOMEM; + goto failed; + } + } + + for (i = 0; i < pages_needed; i++) { + page_data = kmap(pages[i]); + memcpy(page_data, fw->data + (i * PAGE_SIZE), copy_size); + + if (i == 0) + capsule = (efi_capsule_header_t *)page_data; + else + kunmap(pages[i]); + + count -= copy_size; + if (count < PAGE_SIZE) + copy_size = count; + } + + ret = efi_capsule_update(capsule, pages); + if (ret) { + pr_err("%s: efi_capsule_update() failed\n", __func__); + --i; + goto failed; + } + kunmap(pages[0]); + + /* + * we cannot free the pages here due to reboot need that data + * maintained. + */ + return 0; + +failed: + while (i >= 0) + __free_page(pages[i--]); + kfree(pages); + return ret; +} + +/* + * This callback function will be called by request_firmware_nowait() when + * user has loaded the capsule binary or aborted user helper interface + */ +static void callbackfn_efi_capsule(const struct firmware *fw, void *context) +{ + int ret; + + if (fw) { + /* + * Binary which is not getting from user helper interface, + * the fw->pages is equal to NULL + */ + if (!fw->pages) { + pr_err("%s: ERROR: Capsule binary '%s' at %s\n", + __func__, CAPSULE_NAME, + "firmware lib search path are not supported"); + pr_err("user helper interface disabled\n"); + snprintf(report_buf + report_buf_write_off, + CAPSULE_REPORT_MAX_STR_SIZE, + "Capsule %03d uploads %s\n", + capsule_count, + CAPSULE_RESULT_FAIL); + } else { + ret = efi_capsule_store(fw); + if (!ret) { + snprintf(report_buf + report_buf_write_off, + CAPSULE_REPORT_MAX_STR_SIZE, + "Capsule %03d uploads %s\n", + capsule_count, + CAPSULE_RESULT_PASS); + } else { + pr_err("%s: %s %03d, return error code = %d\n", + __func__, + "Failed to store capsule binary", + capsule_count, ret); + snprintf(report_buf + report_buf_write_off, + CAPSULE_REPORT_MAX_STR_SIZE, + "Capsule %03d uploads %s\n", + capsule_count, + CAPSULE_RESULT_FAIL); + } + } + release_firmware(fw); + } else { + snprintf(report_buf + report_buf_write_off, + CAPSULE_REPORT_MAX_STR_SIZE, + "Capsule %03d uploads %s\n", + capsule_count, + CAPSULE_RESULT_EXIT); + } + + report_buf_write_off += CAPSULE_REPORT_MAX_STR_SIZE; + report_buf_write_off %= PAGE_SIZE; + + cancel_delayed_work_sync(&timeout_work); + mutex_unlock(&user_helper_lock); +} + +static void capsule_user_helper_timeout_work(struct work_struct *work) +{ + request_firmware_abort(CAPSULE_NAME); +} + +static ssize_t capsule_ticket_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + if (mutex_trylock(&user_helper_lock)) { + /* + * Use request_firmware_nowait() to expose an user helper + * interface for obtaining the capsule binary from user space + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + CAPSULE_NAME, + &efi_capsule_pdev->dev, + GFP_KERNEL, NULL, + callbackfn_efi_capsule); + if (ret) { + pr_err("%s: request_firmware_nowait() failed\n", + __func__); + mutex_unlock(&user_helper_lock); + return snprintf(buf, STRING_INTEGER_MAX_LENGTH, "0\n"); + } + + queue_delayed_work(system_power_efficient_wq, + &timeout_work, + CAPSULE_USER_HELPER_TIMEOUT * HZ); + + ++capsule_count; + capsule_count %= 1000 ? : ++capsule_count; + + return snprintf(buf, STRING_INTEGER_MAX_LENGTH, "%03d\n", + capsule_count); + } else { + return snprintf(buf, STRING_INTEGER_MAX_LENGTH, "0\n"); + } +} + +static DEVICE_ATTR_RO(capsule_ticket); + +static ssize_t capsule_report_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int length = capsule_count * CAPSULE_REPORT_MAX_STR_SIZE; + + length = length > PAGE_SIZE ? PAGE_SIZE : length; + memcpy(buf, report_buf + report_buf_head_off, length); + return length; +} + +static DEVICE_ATTR_RO(capsule_report); + +/* reboot notifier for avoid deadlock with usermode_lock */ +static int capsule_shutdown_notify(struct notifier_block *nb, + unsigned long sys_state, + void *reboot_cmd) +{ + request_firmware_abort(CAPSULE_NAME); + return NOTIFY_DONE; +} + +static struct notifier_block capsule_shutdown_nb = { + .notifier_call = capsule_shutdown_notify, + /* + * In order to reboot properly, it is required to ensure the priority + * here is higher than firmware_class fw_shutdown_nb priority + */ + .priority = 1, +}; + +static int __init efi_capsule_user_helper_init(void) +{ + int ret; + + efi_capsule_pdev = platform_device_register_simple(DEV_NAME, + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(efi_capsule_pdev)) { + pr_err("%s: platform_device_register_simple() failed\n", + __func__); + return PTR_ERR(efi_capsule_pdev); + } + + report_buf = devm_kzalloc(&efi_capsule_pdev->dev, PAGE_SIZE, + GFP_KERNEL); + if (!report_buf) { + ret = -ENOMEM; + pr_err("%s: devm_kzalloc() failed\n", __func__); + goto out; + } + + /* + * create this file node for user to acquire a ticket number and get + * ready to upload capsule binaries + */ + ret = device_create_file(&efi_capsule_pdev->dev, + &dev_attr_capsule_ticket); + if (ret) { + pr_err("%s: device_create_file() failed\n", __func__); + goto out; + } + + /* + * create this file node for user to check the capsule binaries upload + * status + */ + ret = device_create_file(&efi_capsule_pdev->dev, + &dev_attr_capsule_report); + if (ret) { + pr_err("%s: device_create_file() failed\n", __func__); + goto out; + } + + INIT_DELAYED_WORK(&timeout_work, capsule_user_helper_timeout_work); + register_reboot_notifier(&capsule_shutdown_nb); + return 0; + +out: + platform_device_unregister(efi_capsule_pdev); + return ret; +} +module_init(efi_capsule_user_helper_init); + +/* + * To remove this kernel module, just perform: + * rmmod efi_capsule_user_helper.ko + * + * rmmod -f efi_capsule_user_helper.ko is NOT recommended and NOT supported + * by this module design. Any force unload may cause the system crash. + */ +static void __exit efi_capsule_user_helper_exit(void) +{ + unregister_reboot_notifier(&capsule_shutdown_nb); + + // request_firmware_abort(CAPSULE_NAME); + // /* + // * synchronization is needed to make sure request_firmware is fully + // * aborted + // */ + // while (efi_capsule_pdev->dev.kobj.kref.refcount.counter > 3) + // msleep(20); /* avoid busy waiting for cooperative kernel */ + + platform_device_unregister(efi_capsule_pdev); +} +module_exit(efi_capsule_user_helper_exit); + +MODULE_DESCRIPTION("EFI Capsule user helper binary load utility"); +MODULE_LICENSE("GPL v2"); -- 1.7.9.5