1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Slim Bootloader(SBL) firmware update signaling driver 4 * 5 * Slim Bootloader is a small, open-source, non UEFI compliant, boot firmware 6 * optimized for running on certain Intel platforms. 7 * 8 * SBL exposes an ACPI-WMI device via /sys/bus/wmi/devices/<INTEL_WMI_SBL_GUID>. 9 * This driver further adds "firmware_update_request" device attribute. 10 * This attribute normally has a value of 0 and userspace can signal SBL 11 * to update firmware, on next reboot, by writing a value of 1. 12 * 13 * More details of SBL firmware update process is available at: 14 * https://slimbootloader.github.io/security/firmware-update.html 15 */ 16 17#include <linux/acpi.h> 18#include <linux/device.h> 19#include <linux/module.h> 20#include <linux/slab.h> 21#include <linux/sysfs.h> 22#include <linux/wmi.h> 23 24#define INTEL_WMI_SBL_GUID "44FADEB1-B204-40F2-8581-394BBDC1B651" 25 26static int get_fwu_request(struct device *dev, u32 *out) 27{ 28 union acpi_object *obj; 29 30 obj = wmidev_block_query(to_wmi_device(dev), 0); 31 if (!obj) 32 return -ENODEV; 33 34 if (obj->type != ACPI_TYPE_INTEGER) { 35 dev_warn(dev, "wmidev_block_query returned invalid value\n"); 36 kfree(obj); 37 return -EINVAL; 38 } 39 40 *out = obj->integer.value; 41 kfree(obj); 42 43 return 0; 44} 45 46static int set_fwu_request(struct device *dev, u32 in) 47{ 48 struct acpi_buffer input; 49 acpi_status status; 50 u32 value; 51 52 value = in; 53 input.length = sizeof(u32); 54 input.pointer = &value; 55 56 status = wmidev_block_set(to_wmi_device(dev), 0, &input); 57 if (ACPI_FAILURE(status)) { 58 dev_err(dev, "wmidev_block_set failed\n"); 59 return -ENODEV; 60 } 61 62 return 0; 63} 64 65static ssize_t firmware_update_request_show(struct device *dev, 66 struct device_attribute *attr, 67 char *buf) 68{ 69 u32 val; 70 int ret; 71 72 ret = get_fwu_request(dev, &val); 73 if (ret) 74 return ret; 75 76 return sprintf(buf, "%d\n", val); 77} 78 79static ssize_t firmware_update_request_store(struct device *dev, 80 struct device_attribute *attr, 81 const char *buf, size_t count) 82{ 83 unsigned int val; 84 int ret; 85 86 ret = kstrtouint(buf, 0, &val); 87 if (ret) 88 return ret; 89 90 /* May later be extended to support values other than 0 and 1 */ 91 if (val > 1) 92 return -ERANGE; 93 94 ret = set_fwu_request(dev, val); 95 if (ret) 96 return ret; 97 98 return count; 99} 100static DEVICE_ATTR_RW(firmware_update_request); 101 102static struct attribute *firmware_update_attrs[] = { 103 &dev_attr_firmware_update_request.attr, 104 NULL 105}; 106ATTRIBUTE_GROUPS(firmware_update); 107 108static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, 109 const void *context) 110{ 111 dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); 112 return 0; 113} 114 115static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) 116{ 117 dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); 118} 119 120static const struct wmi_device_id intel_wmi_sbl_id_table[] = { 121 { .guid_string = INTEL_WMI_SBL_GUID }, 122 {} 123}; 124MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table); 125 126static struct wmi_driver intel_wmi_sbl_fw_update_driver = { 127 .driver = { 128 .name = "intel-wmi-sbl-fw-update", 129 .dev_groups = firmware_update_groups, 130 }, 131 .probe = intel_wmi_sbl_fw_update_probe, 132 .remove = intel_wmi_sbl_fw_update_remove, 133 .id_table = intel_wmi_sbl_id_table, 134 .no_singleton = true, 135}; 136module_wmi_driver(intel_wmi_sbl_fw_update_driver); 137 138MODULE_AUTHOR("Jithu Joseph <jithu.joseph@intel.com>"); 139MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); 140MODULE_LICENSE("GPL v2"); 141