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 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 46 static 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 65 static 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 79 static 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 } 100 static DEVICE_ATTR_RW(firmware_update_request); 101 102 static struct attribute *firmware_update_attrs[] = { 103 &dev_attr_firmware_update_request.attr, 104 NULL 105 }; 106 ATTRIBUTE_GROUPS(firmware_update); 107 108 static 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 115 static 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 120 static const struct wmi_device_id intel_wmi_sbl_id_table[] = { 121 { .guid_string = INTEL_WMI_SBL_GUID }, 122 {} 123 }; 124 MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table); 125 126 static 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 }; 136 module_wmi_driver(intel_wmi_sbl_fw_update_driver); 137 138 MODULE_AUTHOR("Jithu Joseph <jithu.joseph@intel.com>"); 139 MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); 140 MODULE_LICENSE("GPL v2"); 141