firmware: coreboot: Add CFR firmware attributes driver
Changes
4 files changed · +1226 −0
| File | Status | Add | Del | Content |
|---|---|---|---|---|
| MAINTAINERS | modified | +6 | −0 | View file |
| drivers/firmware/coreboot/Kconfig | modified | +15 | −0 | View file |
| drivers/firmware/coreboot/Makefile | modified | +1 | −0 | View file |
| drivers/firmware/coreboot/coreboot-cfr.c | added | +1204 | −0 | View file |
Only the first 600 diff lines are shown. Download the complete patch.
MAINTAINERS
View filediff --git a/MAINTAINERS b/MAINTAINERS
index c22cb3d..3dd5c13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6716,6 +6716,12 @@ F: tools/testing/selftests/cgroup/test_hugetlb_memcg.c
F: tools/testing/selftests/cgroup/test_kmem.c
F: tools/testing/selftests/cgroup/test_memcontrol.c
+COREBOOT CFR FIRMWARE ATTRIBUTES DRIVER
+M: Sean Rhodes <[email protected]>
+S: Maintained
+F: drivers/firmware/coreboot/coreboot-cfr.c
+
COREBOOT FIRMWARE DRIVERS
M: Tzung-Bi Shih <[email protected]>
R: Brian Norris <[email protected]>
drivers/firmware/coreboot/Kconfig
View filediff --git a/drivers/firmware/coreboot/Kconfig b/drivers/firmware/coreboot/Kconfig
index d273bd8..acfc7d1 100644
--- a/drivers/firmware/coreboot/Kconfig
+++ b/drivers/firmware/coreboot/Kconfig
@@ -49,6 +49,21 @@ config COREBOOT_TABLE
device tree node /firmware/coreboot.
If unsure say N.
+config COREBOOT_CFR
+ tristate "coreboot CFR firmware attributes"
+ depends on EFI
+ depends on COREBOOT_TABLE
+ depends on HAS_IOPORT
+ select CRC32
+ select FW_ATTR_CLASS
+ help
+ This driver parses the coreboot CFR table and exposes runtime EFI
+ variable-backed options through the firmware-attributes sysfs
+ interface.
+
+ If you have a system running coreboot with CFR runtime
+ configuration support, say Y or M here.
+
config COREBOOT_MEMCONSOLE_COMMON
tristate
depends on GOOGLE_MEMCONSOLE_X86_LEGACY || COREBOOT_MEMCONSOLE
drivers/firmware/coreboot/Makefile
View filediff --git a/drivers/firmware/coreboot/Makefile b/drivers/firmware/coreboot/Makefile
index e681f27..a08d167 100644
--- a/drivers/firmware/coreboot/Makefile
+++ b/drivers/firmware/coreboot/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
obj-$(CONFIG_COREBOOT_TABLE) += coreboot_table.o
+obj-$(CONFIG_COREBOOT_CFR) += coreboot-cfr.o
obj-$(CONFIG_COREBOOT_FRAMEBUFFER) += framebuffer-coreboot.o
obj-$(CONFIG_COREBOOT_MEMCONSOLE_COMMON) += memconsole.o
obj-$(CONFIG_COREBOOT_MEMCONSOLE) += memconsole-coreboot.o
drivers/firmware/coreboot/coreboot-cfr.c
View filediff --git a/drivers/firmware/coreboot/coreboot-cfr.c b/drivers/firmware/coreboot/coreboot-cfr.c
new file mode 100644
index 0000000..2d9ea5d
--- /dev/null
+++ b/drivers/firmware/coreboot/coreboot-cfr.c
@@ -0,0 +1,1204 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * coreboot CFR firmware attributes driver.
+ *
+ * Parses LB_TAG_CFR_ROOT records from the coreboot table and exposes
+ * runtime EFI variable-backed options through the firmware-attributes class.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/container_of.h>
+#include <linux/crc32.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/device-id/coreboot.h>
+#include <linux/efi.h>
+#include <linux/err.h>
+#include <linux/firmware_attributes.h>
+#include <linux/io.h>
+#include <linux/kdev_t.h>
+#include <linux/kobject.h>
+#include <linux/kstrtox.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "coreboot_table.h"
+
+#define DRIVER_NAME "coreboot-cfr"
+
+#define LB_TAG_CFR_ROOT 0x47
+#define CFR_VERSION 0
+
+enum cfr_tags {
+ CFR_TAG_OPTION_FORM = 1,
+ CFR_TAG_ENUM_VALUE = 2,
+ CFR_TAG_OPTION_ENUM = 3,
+ CFR_TAG_OPTION_NUMBER = 4,
+ CFR_TAG_OPTION_BOOL = 5,
+ CFR_TAG_VARCHAR_OPT_NAME = 7,
+ CFR_TAG_VARCHAR_UI_NAME = 8,
+ CFR_TAG_RUNTIME_APPLY = 13,
+};
+
+enum cfr_option_flags {
+ CFR_OPTFLAG_READONLY = BIT(0),
+ CFR_OPTFLAG_INACTIVE = BIT(1),
+ CFR_OPTFLAG_SUPPRESS = BIT(2),
+ CFR_OPTFLAG_VOLATILE = BIT(3),
+ CFR_OPTFLAG_RUNTIME = BIT(4),
+};
+
+enum cfr_runtime_apply_method {
+ CFR_RUNTIME_APPLY_NONE = 0,
+ CFR_RUNTIME_APPLY_APM_CNT = 1,
+};
+
+struct lb_cfr {
+ u32 tag;
+ u32 size;
+ u32 version;
+ u32 checksum;
+} __packed;
+
+struct lb_cfr_varbinary {
+ u32 tag;
+ u32 size;
+ u32 data_length;
+} __packed;
+
+struct lb_cfr_enum_value {
+ u32 tag;
+ u32 size;
+ u32 value;
+} __packed;
+
+struct lb_cfr_runtime_apply {
+ u32 tag;
+ u32 size;
+ u32 method;
+ u32 id;
+} __packed;
+
+struct lb_cfr_numeric_option {
+ u32 tag;
+ u32 size;
+ cb_u64 object_id;
+ cb_u64 dependency_id;
+ u32 flags;
+ u32 default_value;
+ u32 min;
+ u32 max;
+ u32 step;
+ u32 display_flags;
+} __packed;
+
+struct lb_cfr_option_form {
+ u32 tag;
+ u32 size;
+ cb_u64 object_id;
+ cb_u64 dependency_id;
+ u32 flags;
+} __packed;
+
+#define COREBOOT_CFR_OPT_SKIP_FLAGS \
+ (CFR_OPTFLAG_SUPPRESS | CFR_OPTFLAG_VOLATILE)
+
+#define COREBOOT_CFR_OPT_READ_ONLY_FLAGS \
+ (CFR_OPTFLAG_READONLY | CFR_OPTFLAG_INACTIVE)
+
+#define COREBOOT_CFR_EFI_ATTRS \
+ (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | \
+ EFI_VARIABLE_RUNTIME_ACCESS)
+
+#define COREBOOT_CFR_APM_CNT_PORT 0xb2
+#define COREBOOT_CFR_APM_STS_PORT 0xb3
+#define COREBOOT_CFR_APM_APPLY_CMD 0xe3
+#define COREBOOT_CFR_MAX_FORM_DEPTH 16
+
+static efi_guid_t coreboot_cfr_guid = EFI_GUID(0xceae4c1d, 0x335b, 0x4685,
+ 0xa4, 0xa0, 0xfc, 0x4a,
+ 0x94, 0xee, 0xa0, 0x85);
+
+enum coreboot_cfr_setting_type {
+ COREBOOT_CFR_SETTING_ENUM,
+ COREBOOT_CFR_SETTING_NUMBER,
+ COREBOOT_CFR_SETTING_BOOL,
+};
+
+struct coreboot_cfr_enum {
+ char *label;
+ u32 value;
+};
+
+struct coreboot_cfr_setting {
+ struct kobject kobj;
+ struct list_head node;
+ struct coreboot_cfr_drvdata *drvdata;
+ enum coreboot_cfr_setting_type type;
+ char *name;
+ char *display_name;
+ struct coreboot_cfr_enum *values;
+ unsigned int n_values;
+ u32 default_value;
+ u32 min;
+ u32 max;
+ u32 step;
+ u32 runtime_apply_method;
+ u32 runtime_apply_id;
+ bool read_only;
+};
+
+struct coreboot_cfr_drvdata {
+ struct device *class_dev;
+ struct kset *attrs_kset;
+ struct list_head settings;
+ /* Serializes EFI variable writes and the matching runtime apply hook. */
+ struct mutex lock;
+ bool efi_writes_supported;
+ bool pending_reboot;
+};
+
+struct coreboot_cfr_iterator {
+ const u8 *cursor;
+ size_t remaining;
+};
+
+static struct coreboot_cfr_setting *to_coreboot_cfr_setting(struct kobject *kobj)
+{
+ return container_of(kobj, struct coreboot_cfr_setting, kobj);
+}
+
+static bool coreboot_cfr_string_is_valid_name(const char *name)
+{
+ return name && name[0] && !strchr(name, '/') &&
+ strcmp(name, "pending_reboot") != 0;
+}
+
+static bool coreboot_cfr_string_is_valid_label(const char *label)
+{
+ return label && !strchr(label, ';') && !strchr(label, '\n');
+}
+
+static char *coreboot_cfr_string_dup(const struct lb_cfr_varbinary *str)
+{
+ const char *data = (const char *)(str + 1);
+ size_t len = str->data_length;
+
+ if (len && !data[len - 1])
+ len--;
+
+ return kmemdup_nul(data, len, GFP_KERNEL);
+}
+
+static const struct coreboot_table_entry *
+coreboot_cfr_next_entry(struct coreboot_cfr_iterator *iterator)
+{
+ const struct coreboot_table_entry *entry;
+
+ if (!iterator->remaining)
+ return NULL;
+
+ if (iterator->remaining < sizeof(*entry))
+ return ERR_PTR(-EINVAL);
+
+ entry = (const struct coreboot_table_entry *)iterator->cursor;
+ if (entry->size < sizeof(*entry) || entry->size > iterator->remaining)
+ return ERR_PTR(-EINVAL);
+
+ iterator->cursor += entry->size;
+ iterator->remaining -= entry->size;
+
+ return entry;
+}
+
+static const struct coreboot_table_entry *
+coreboot_cfr_child_entry(const void *base, size_t len, u32 tag)
+{
+ struct coreboot_cfr_iterator iterator = {
+ .cursor = base,
+ .remaining = len,
+ };
+ const struct coreboot_table_entry *entry;
+
+ for (;;) {
+ entry = coreboot_cfr_next_entry(&iterator);
+ if (IS_ERR_OR_NULL(entry))
+ return entry;
+
+ if (entry->tag == tag)
+ return entry;
+ }
+}
+
+static const struct lb_cfr_varbinary *
+coreboot_cfr_child_string(const void *base, size_t len, u32 tag)
+{
+ const struct lb_cfr_varbinary *str;
+ const struct coreboot_table_entry *entry;
+
+ entry = coreboot_cfr_child_entry(base, len, tag);
+ if (IS_ERR(entry))
+ return ERR_CAST(entry);
+ if (!entry)
+ return NULL;
+
+ if (entry->size < sizeof(*str))
+ return ERR_PTR(-EINVAL);
+
+ str = (const struct lb_cfr_varbinary *)entry;
+ if (str->data_length > entry->size - sizeof(*str))
+ return ERR_PTR(-EINVAL);
+
+ return str;
+}
+
+static const struct lb_cfr_runtime_apply *
+coreboot_cfr_child_runtime_apply(const void *base, size_t len)
+{
+ const struct lb_cfr_runtime_apply *runtime_apply;
+ const struct coreboot_table_entry *entry;
+
+ entry = coreboot_cfr_child_entry(base, len, CFR_TAG_RUNTIME_APPLY);
+ if (IS_ERR(entry))
+ return ERR_CAST(entry);
+ if (!entry)
+ return NULL;
+
+ if (entry->size < sizeof(*runtime_apply))
+ return ERR_PTR(-EINVAL);
+
+ runtime_apply = (const struct lb_cfr_runtime_apply *)entry;
+ if (runtime_apply->method == CFR_RUNTIME_APPLY_APM_CNT &&
+ runtime_apply->id > U8_MAX)
+ return ERR_PTR(-EINVAL);
+
+ return runtime_apply;
+}
+
+static efi_char16_t *coreboot_cfr_efi_name(const char *name)
+{
+ size_t len, i;
+
+ len = strlen(name);
+ if (len >= EFI_VAR_NAME_LEN)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ efi_char16_t *efi_name __free(kfree) =
+ kcalloc(len + 1, sizeof(*efi_name), GFP_KERNEL);
+ if (!efi_name)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < len; i++) {
+ if (!isascii(name[i]))
+ return ERR_PTR(-EINVAL);
+ efi_name[i] = name[i];
+ }
+
+ return no_free_ptr(efi_name);
+}
+
+/* Caller must hold the efivar lock. */
+static int coreboot_cfr_read_efi_value(efi_char16_t *efi_name, u32 *value,
+ u32 *attrs)
+{
+ unsigned long size = sizeof(__le32);
+ efi_status_t status;
+ __le32 data;
+ u32 attr;
+
+ status = efivar_get_variable(efi_name, &coreboot_cfr_guid, &attr,
+ &size, &data);
+ if (status != EFI_SUCCESS)
+ return efi_status_to_err(status);
+
+ if (size != sizeof(data))
+ return -EINVAL;
+
+ if (!(attr & EFI_VARIABLE_RUNTIME_ACCESS))
+ return -EOPNOTSUPP;
+
+ *value = le32_to_cpu(data);
+ if (attrs)
+ *attrs = attr;
+
+ return 0;
+}
+
+static int coreboot_cfr_read_value(const struct coreboot_cfr_setting *setting,
+ u32 *value, u32 *attrs)
+{
+ efi_char16_t *efi_name __free(kfree) =
+ coreboot_cfr_efi_name(setting->name);
+ int ret;
+
+ if (IS_ERR(efi_name))
+ return PTR_ERR(no_free_ptr(efi_name));
+
+ ret = efivar_lock();
+ if (ret)
+ return ret;
+
+ ret = coreboot_cfr_read_efi_value(efi_name, value, attrs);
+ efivar_unlock();
+
+ return ret;
+}
+
+/* Caller must hold the efivar lock. */
+static int coreboot_cfr_write_efi_value(efi_char16_t *efi_name, u32 value,
+ u32 attrs)
+{
+ efi_status_t status;
+ __le32 data;
+
+ if (!efivar_supports_writes())
+ return -EROFS;
+
+ data = cpu_to_le32(value);
+ status = efivar_set_variable_locked(efi_name, &coreboot_cfr_guid, attrs,
+ sizeof(data), &data, false);
+ if (status != EFI_SUCCESS)
+ return efi_status_to_err(status);
+
+ return 0;
+}
+
+static int coreboot_cfr_apply_runtime(struct coreboot_cfr_setting *setting)
+{
+ u8 status;
+
+ if (setting->runtime_apply_method != CFR_RUNTIME_APPLY_APM_CNT)
+ return -EOPNOTSUPP;
+
+ outb((u8)setting->runtime_apply_id, COREBOOT_CFR_APM_STS_PORT);
+ outb(COREBOOT_CFR_APM_APPLY_CMD, COREBOOT_CFR_APM_CNT_PORT);
+ status = inb(COREBOOT_CFR_APM_STS_PORT);
+ if (status)
+ return -EIO;
+
+ return 0;
+}
+
+static int coreboot_cfr_write_value(struct coreboot_cfr_setting *setting,
+ u32 value)
+{
+ efi_char16_t *efi_name;
+ u32 attrs;
+ u32 old;
+ int restore_ret;
+ int ret;
+
+ if (setting->read_only)
+ return -EACCES;
+
+ efi_name = coreboot_cfr_efi_name(setting->name);
+ if (IS_ERR(efi_name))
+ return PTR_ERR(efi_name);
+
+ mutex_lock(&setting->drvdata->lock);
+
+ ret = efivar_lock();
+ if (ret)
+ goto out_unlock_mutex;
+
+ ret = coreboot_cfr_read_efi_value(efi_name, &old, &attrs);
+ if (ret)
+ goto out_unlock_efi;
+
+ if ((attrs & COREBOOT_CFR_EFI_ATTRS) != COREBOOT_CFR_EFI_ATTRS) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock_efi;
+ }
+
+ if (old == value)
+ goto out_unlock_efi;
+
+ ret = coreboot_cfr_write_efi_value(efi_name, value, attrs);
+ if (ret)
+ goto out_unlock_efi;
+
+ ret = coreboot_cfr_apply_runtime(setting);
+ if (ret == -EOPNOTSUPP) {
+ /* EFI changed; firmware will consume it after reboot. */
+ setting->drvdata->pending_reboot = true;
+ ret = 0;
+ } else if (ret) {
+ restore_ret = coreboot_cfr_write_efi_value(efi_name, old, attrs);
+ if (restore_ret) {
+ setting->drvdata->pending_reboot = true;
+ ret = restore_ret;
+ } else {
+ goto out_unlock_efi;
+ }
+ }
+
+ efivar_unlock();
+ kobject_uevent(&setting->drvdata->class_dev->kobj, KOBJ_CHANGE);
+ mutex_unlock(&setting->drvdata->lock);
+ kfree(efi_name);
+ return ret;
+
+out_unlock_efi:
+ efivar_unlock();
+out_unlock_mutex:
+ mutex_unlock(&setting->drvdata->lock);
+ kfree(efi_name);
+ return ret;
+}
+
+static const char *
+coreboot_cfr_label_from_value(const struct coreboot_cfr_setting *setting,
+ u32 value)
+{
+ unsigned int i;
+
+ for (i = 0; i < setting->n_values; i++) {
+ if (setting->values[i].value == value)
+ return setting->values[i].label;
+ }
+
+ return NULL;
+}
+
+static int coreboot_cfr_parse_value(struct coreboot_cfr_setting *setting,
+ const char *label, u32 *value_out)
+{
+ unsigned int i;
+
+ for (i = 0; i < setting->n_values; i++) {
+ if (!sysfs_streq(label, setting->values[i].label))
+ continue;
+
+ *value_out = setting->values[i].value;
+ return 0;
+ }
+
+ return kstrtou32(label, 0, value_out);
+}
+
+static bool coreboot_cfr_value_is_valid(struct coreboot_cfr_setting *setting,
+ u32 value)
+{
+ u32 delta;
+
+ if (setting->type != COREBOOT_CFR_SETTING_NUMBER) {
+ /* Enum and bool values are valid only if they have a label. */
+ return coreboot_cfr_label_from_value(setting, value);
+ }
+
+ if (value < setting->min || value > setting->max)
+ return false;
+
+ if (!setting->step)
+ return true;
+
+ delta = value - setting->min;
+ return delta % setting->step == 0;
+}
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
+
+ if (setting->type == COREBOOT_CFR_SETTING_NUMBER)
+ return sysfs_emit(buf, "integer\n");
+
+ return sysfs_emit(buf, "enumeration\n");
+}
+
+static ssize_t display_name_language_code_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "en_US.UTF-8\n");
+}
+
+static ssize_t display_name_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
+
+ return sysfs_emit(buf, "%s\n", setting->display_name);
+}
+
+static ssize_t possible_values_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
+ ssize_t len = 0;
+ unsigned int i;
This diff is incomplete. Download the complete patch.