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 26 static int get_fwu_request(struct device *dev, u32 *out) 27 { 28 struct acpi_buffer result = {ACPI_ALLOCATE_BUFFER, NULL}; 29 union acpi_object *obj; 30 acpi_status status; 31 32 status = wmi_query_block(INTEL_WMI_SBL_GUID, 0, &result); 33 if (ACPI_FAILURE(status)) { 34 dev_err(dev, "wmi_query_block failed\n"); 35 return -ENODEV; 36 } 37 38 obj = (union acpi_object *)result.pointer; 39 if (!obj || obj->type != ACPI_TYPE_INTEGER) { 40 dev_warn(dev, "wmi_query_block returned invalid value\n"); 41 kfree(obj); 42 return -EINVAL; 43 } 44 45 *out = obj->integer.value; 46 kfree(obj); 47 48 return 0; 49 } 50 51 static int set_fwu_request(struct device *dev, u32 in) 52 { 53 struct acpi_buffer input; 54 acpi_status status; 55 u32 value; 56 57 value = in; 58 input.length = sizeof(u32); 59 input.pointer = &value; 60 61 status = wmi_set_block(INTEL_WMI_SBL_GUID, 0, &input); 62 if (ACPI_FAILURE(status)) { 63 dev_err(dev, "wmi_set_block failed\n"); 64 return -ENODEV; 65 } 66 67 return 0; 68 } 69 70 static ssize_t firmware_update_request_show(struct device *dev, 71 struct device_attribute *attr, 72 char *buf) 73 { 74 u32 val; 75 int ret; 76 77 ret = get_fwu_request(dev, &val); 78 if (ret) 79 return ret; 80 81 return sprintf(buf, "%d\n", val); 82 } 83 84 static ssize_t firmware_update_request_store(struct device *dev, 85 struct device_attribute *attr, 86 const char *buf, size_t count) 87 { 88 unsigned int val; 89 int ret; 90 91 ret = kstrtouint(buf, 0, &val); 92 if (ret) 93 return ret; 94 95 /* May later be extended to support values other than 0 and 1 */ 96 if (val > 1) 97 return -ERANGE; 98 99 ret = set_fwu_request(dev, val); 100 if (ret) 101 return ret; 102 103 return count; 104 } 105 static DEVICE_ATTR_RW(firmware_update_request); 106 107 static struct attribute *firmware_update_attrs[] = { 108 &dev_attr_firmware_update_request.attr, 109 NULL 110 }; 111 ATTRIBUTE_GROUPS(firmware_update); 112 113 static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, 114 const void *context) 115 { 116 dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); 117 return 0; 118 } 119 120 static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) 121 { 122 dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); 123 } 124 125 static const struct wmi_device_id intel_wmi_sbl_id_table[] = { 126 { .guid_string = INTEL_WMI_SBL_GUID }, 127 {} 128 }; 129 MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table); 130 131 static struct wmi_driver intel_wmi_sbl_fw_update_driver = { 132 .driver = { 133 .name = "intel-wmi-sbl-fw-update", 134 .dev_groups = firmware_update_groups, 135 }, 136 .probe = intel_wmi_sbl_fw_update_probe, 137 .remove = intel_wmi_sbl_fw_update_remove, 138 .id_table = intel_wmi_sbl_id_table, 139 }; 140 module_wmi_driver(intel_wmi_sbl_fw_update_driver); 141 142 MODULE_AUTHOR("Jithu Joseph <jithu.joseph@intel.com>"); 143 MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); 144 MODULE_LICENSE("GPL v2"); 145