From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Sean Rhodes <sean@starlabs.systems>
Cc: platform-driver-x86@vger.kernel.org,
Tzung-Bi Shih <tzungbi@kernel.org>,
Brian Norris <briannorris@chromium.org>,
Julius Werner <jwerner@chromium.org>,
Hans de Goede <hansg@kernel.org>,
LKML <linux-kernel@vger.kernel.org>,
chrome-platform@lists.linux.dev
Subject: Re: [PATCH] platform/x86: Add coreboot CFR firmware attributes
Date: Thu, 9 Jul 2026 15:05:35 +0300 (EEST) [thread overview]
Message-ID: <5d1ba6a1-7341-a9ac-2de6-a3f81346bd63@linux.intel.com> (raw)
In-Reply-To: <5c545ab86ca7381ec18d753df8f412a521e63829.1782811265.git.sean@starlabs.systems>
On Tue, 30 Jun 2026, Sean Rhodes wrote:
> coreboot can publish a CFR tree in its coreboot table to describe
> firmware setup options. Add a firmware-attributes driver for that table
> entry and move the coreboot table bus definitions into <linux/coreboot.h>
> so the driver can bind from outside drivers/firmware/google.
>
> The driver exposes runtime EFI variable-backed boolean, enum and integer
> settings through the firmware-attributes ABI. It only registers settings
> with readable runtime EFI variables, skips suppressed, volatile,
> dependency-gated and non-runtime options, and keeps read-only or inactive
> options read-only in sysfs.
>
> For settings with an APM_CNT runtime-apply record, write the EFI variable
> and ask firmware to apply the setting immediately. If firmware reports a
> failure, restore the old EFI variable value. Settings without a runtime
> apply hook keep the EFI variable update and set pending_reboot.
>
> Tested on a Star Labs Byte with firmware 26.07 and the driver built as a
> module against Linux 6.17: runtime APM_CNT apply was verified with
> power_led, and reboot-needed behavior was verified with debug_level.
>
> Link: https://doc.coreboot.org/drivers/cfr.html
> Link: https://doc.coreboot.org/drivers/cfr_internal.html
> Signed-off-by: Sean Rhodes <sean@starlabs.systems>
> ---
> MAINTAINERS | 6 +
> drivers/firmware/google/coreboot_table.h | 45 --
> drivers/platform/x86/Kconfig | 13 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/coreboot-cfr.c | 986 +++++++++++++++++++++++
> include/linux/coreboot.h | 146 ++++
> 6 files changed, 1152 insertions(+), 45 deletions(-)
> create mode 100644 drivers/platform/x86/coreboot-cfr.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 15011f5752a9..c5d8daab2196 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6665,6 +6665,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 <sean@starlabs.systems>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/coreboot-cfr.c
> +
> CORETEMP HARDWARE MONITORING DRIVER
> L: linux-hwmon@vger.kernel.org
> S: Orphan
> diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h
> index 616ca3903e5c..0c4956d3836a 100644
> --- a/drivers/firmware/google/coreboot_table.h
> +++ b/drivers/firmware/google/coreboot_table.h
> @@ -13,50 +13,5 @@
> #define __COREBOOT_TABLE_H
>
> #include <linux/coreboot.h>
> -#include <linux/device.h>
> -
> -/* A device, additionally with information from coreboot. */
> -struct coreboot_device {
> - struct device dev;
> - union {
> - struct coreboot_table_entry entry;
> - struct lb_cbmem_ref cbmem_ref;
> - struct lb_cbmem_entry cbmem_entry;
> - struct lb_framebuffer framebuffer;
> - DECLARE_FLEX_ARRAY(u8, raw);
> - };
> -};
> -
> -static inline struct coreboot_device *dev_to_coreboot_device(struct device *dev)
> -{
> - return container_of(dev, struct coreboot_device, dev);
> -}
> -
> -/* A driver for handling devices described in coreboot tables. */
> -struct coreboot_driver {
> - int (*probe)(struct coreboot_device *);
> - void (*remove)(struct coreboot_device *);
> - struct device_driver drv;
> - const struct coreboot_device_id *id_table;
> -};
> -
> -/* use a macro to avoid include chaining to get THIS_MODULE */
> -#define coreboot_driver_register(driver) \
> - __coreboot_driver_register(driver, THIS_MODULE)
> -/* Register a driver that uses the data from a coreboot table. */
> -int __coreboot_driver_register(struct coreboot_driver *driver,
> - struct module *owner);
> -
> -/* Unregister a driver that uses the data from a coreboot table. */
> -void coreboot_driver_unregister(struct coreboot_driver *driver);
> -
> -/* module_coreboot_driver() - Helper macro for drivers that don't do
> - * anything special in module init/exit. This eliminates a lot of
> - * boilerplate. Each module may only use this macro once, and
> - * calling it replaces module_init() and module_exit()
> - */
> -#define module_coreboot_driver(__coreboot_driver) \
> - module_driver(__coreboot_driver, coreboot_driver_register, \
> - coreboot_driver_unregister)
>
> #endif /* __COREBOOT_TABLE_H */
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index b54b5212b204..683c35a9d288 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -851,6 +851,19 @@ config SONYPI_COMPAT
> help
> Build the sonypi driver compatibility code into the sony-laptop driver.
>
> +config COREBOOT_CFR
> + tristate "coreboot CFR firmware attributes"
> + depends on EFI
> + depends on GOOGLE_COREBOOT_TABLE
> + 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 SYSTEM76_ACPI
> tristate "System76 ACPI Driver"
> depends on ACPI_EC
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 872ac3842391..71a0378e4d9b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -129,6 +129,7 @@ obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
> obj-$(CONFIG_LG_LAPTOP) += lg-laptop.o
> obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
> obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
> +obj-$(CONFIG_COREBOOT_CFR) += coreboot-cfr.o
> obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o
> obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
>
> diff --git a/drivers/platform/x86/coreboot-cfr.c b/drivers/platform/x86/coreboot-cfr.c
> new file mode 100644
> index 000000000000..fb7a0f8eb35f
> --- /dev/null
> +++ b/drivers/platform/x86/coreboot-cfr.c
> @@ -0,0 +1,986 @@
> +// 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/bitops.h>
> +#include <linux/coreboot.h>
> +#include <linux/efi.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/kdev_t.h>
> +#include <linux/kobject.h>
> +#include <linux/list.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +
> +#include "firmware_attributes_class.h"
> +
> +#define DRIVER_NAME "coreboot-cfr"
> +
> +#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
> +
> +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 pending_reboot;
> +};
> +
> +static struct coreboot_cfr_drvdata *coreboot_cfr_data;
> +
> +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, '/');
> +}
> +
> +static char *coreboot_cfr_string_dup(const struct lb_cfr_varbinary *str)
> +{
> + const char *data = (const char *)str + sizeof(*str);
> + 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_child_entry(const void *base, size_t len, u32 tag)
> +{
> + const struct coreboot_table_entry *entry;
> + size_t off = 0;
> +
> + while (off < len) {
> + if (len - off < sizeof(*entry))
> + return NULL;
> +
> + entry = base + off;
> + if (entry->size < sizeof(*entry) || entry->size > len - off)
> + return NULL;
> +
> + if (entry->tag == tag)
> + return entry;
> +
> + off += entry->size;
> + }
> +
> + return NULL;
> +}
> +
> +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 (!entry)
> + return NULL;
> +
> + if (entry->size < sizeof(*str))
> + return NULL;
> +
> + str = (const struct lb_cfr_varbinary *)entry;
> + if (str->data_length > entry->size - sizeof(*str))
> + return NULL;
> +
> + 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 (!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)
Please add include.
> + return ERR_PTR(-EINVAL);
> +
> + return runtime_apply;
> +}
> +
> +static efi_char16_t *coreboot_cfr_efi_name(const char *name)
> +{
> + efi_char16_t *efi_name;
> + size_t len, i;
Please add include.
> +
> + len = strlen(name);
> + if (len >= EFI_VAR_NAME_LEN)
> + return ERR_PTR(-ENAMETOOLONG);
> +
> + efi_name = kcalloc(len + 1, sizeof(*efi_name), GFP_KERNEL);
> + if (!efi_name)
> + return ERR_PTR(-ENOMEM);
> +
> + for (i = 0; i < len; i++) {
> + if (name[i] & 0x80) {
!isascii()?
> + kfree(efi_name);
Please use __free() and no_free_ptr() instead. Remember to move the
variable declaration to the kcalloc() line (as instructed in the long
comment in cleanup.h).
> + return ERR_PTR(-EINVAL);
> + }
> + efi_name[i] = name[i];
> + }
> +
> + return efi_name;
> +}
> +
> +static int coreboot_cfr_read_value(const struct coreboot_cfr_setting *setting,
> + u32 *value, u32 *attrs)
> +{
> + unsigned long size = sizeof(__le32);
> + efi_char16_t *efi_name;
> + efi_status_t status;
> + __le32 data;
> + u32 attr;
> +
> + efi_name = coreboot_cfr_efi_name(setting->name);
> + if (IS_ERR(efi_name))
> + return PTR_ERR(efi_name);
> +
> + status = efivar_get_variable(efi_name, &coreboot_cfr_guid, &attr,
> + &size, &data);
> + kfree(efi_name);
> + 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_write_efi_value(struct coreboot_cfr_setting *setting,
> + u32 value, u32 attrs)
> +{
> + efi_char16_t *efi_name;
> + efi_status_t status;
> + __le32 data;
> +
> + efi_name = coreboot_cfr_efi_name(setting->name);
> + if (IS_ERR(efi_name))
> + return PTR_ERR(efi_name);
> +
> + data = cpu_to_le32(value);
> + status = efivar_set_variable(efi_name, &coreboot_cfr_guid, attrs,
> + sizeof(data), &data);
> + kfree(efi_name);
> + 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 ||
> + !setting->runtime_apply_id)
> + 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)
> +{
> + u32 attrs;
> + u32 old;
> + int ret;
> + bool changed = false;
> +
> + if (setting->read_only)
> + return -EACCES;
> +
> + mutex_lock(&setting->drvdata->lock);
Use scoped_guard() and remove gotos.
> +
> + ret = coreboot_cfr_read_value(setting, &old, &attrs);
> + if (ret)
> + goto out_unlock;
> +
> + if ((attrs & COREBOOT_CFR_EFI_ATTRS) != COREBOOT_CFR_EFI_ATTRS)
> + goto out_not_supported;
> +
> + ret = coreboot_cfr_write_efi_value(setting, value, attrs);
> + if (ret)
> + goto out_unlock;
> + changed = true;
> +
> + ret = coreboot_cfr_apply_runtime(setting);
> + if (ret == -EOPNOTSUPP) {
> + setting->drvdata->pending_reboot = true;
> + ret = 0;
You're leaving changed to true, is that intentional?
I expect things will get simpler once you convert to scoped_guard() so you
can mostly do early returns. It may also be that changed variable is
longer required at all.
> + } else if (ret) {
> + int restore_ret;
> +
> + restore_ret = coreboot_cfr_write_efi_value(setting, old, attrs);
> + if (restore_ret)
> + ret = restore_ret;
> + else
> + changed = false;
> + }
> +
> +out_unlock:
> + mutex_unlock(&setting->drvdata->lock);
> + if (changed)
> + kobject_uevent(&setting->drvdata->class_dev->kobj, KOBJ_CHANGE);
> + return ret;
> +
> +out_not_supported:
> + ret = -EOPNOTSUPP;
> + goto out_unlock;
> +}
> +
> +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_value_from_label(struct coreboot_cfr_setting *setting,
> + const char *label, u32 *value)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < setting->n_values; i++) {
> + if (!sysfs_streq(label, setting->values[i].label))
> + continue;
> +
> + *value = setting->values[i].value;
> + return 0;
> + }
> +
> + return kstrtou32(label, 0, value);
So this 'value' is a default value? Should it be named as such?
> +}
> +
> +static bool coreboot_cfr_value_is_valid(struct coreboot_cfr_setting *setting,
> + u32 value)
> +{
> + u32 delta;
> +
> + if (setting->type != COREBOOT_CFR_SETTING_NUMBER)
> + 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");
Please add include.
> +}
> +
> +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;
> +
> + for (i = 0; i < setting->n_values; i++) {
> + len += sysfs_emit_at(buf, len, "%s%s", i ? ";" : "",
> + setting->values[i].label);
> + if (len >= PAGE_SIZE)
> + return len;
> + }
> +
> + len += sysfs_emit_at(buf, len, "\n");
> + return len;
> +}
> +
> +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> +
> + return sysfs_emit(buf, "%u\n", setting->min);
> +}
> +
> +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> +
> + return sysfs_emit(buf, "%u\n", setting->max);
> +}
> +
> +static ssize_t scalar_increment_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> +
> + return sysfs_emit(buf, "%u\n", setting->step);
> +}
> +
> +static ssize_t default_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> + const char *label;
> +
> + if (setting->type == COREBOOT_CFR_SETTING_NUMBER)
> + return sysfs_emit(buf, "%u\n", setting->default_value);
> +
> + label = coreboot_cfr_label_from_value(setting, setting->default_value);
> + if (!label)
> + return sysfs_emit(buf, "%u\n", setting->default_value);
> +
> + return sysfs_emit(buf, "%s\n", label);
> +}
> +
> +static ssize_t current_value_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> + const char *label;
> + u32 value;
> + int ret;
> +
> + ret = coreboot_cfr_read_value(setting, &value, NULL);
> + if (ret)
> + return ret;
> +
> + if (setting->type == COREBOOT_CFR_SETTING_NUMBER)
> + return sysfs_emit(buf, "%u\n", value);
> +
> + label = coreboot_cfr_label_from_value(setting, value);
> + if (!label)
> + return -EINVAL;
> +
> + return sysfs_emit(buf, "%s\n", label);
> +}
> +
> +static ssize_t current_value_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> + u32 value;
> + int ret;
> +
> + ret = coreboot_cfr_value_from_label(setting, buf, &value);
> + if (ret)
> + return ret;
> +
> + if (!coreboot_cfr_value_is_valid(setting, value))
> + return -EINVAL;
> +
> + ret = coreboot_cfr_write_value(setting, value);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +
> +static struct kobj_attribute type_attr = __ATTR_RO(type);
> +static struct kobj_attribute display_name_language_code_attr =
> + __ATTR_RO(display_name_language_code);
> +static struct kobj_attribute display_name_attr = __ATTR_RO(display_name);
> +static struct kobj_attribute possible_values_attr = __ATTR_RO(possible_values);
> +static struct kobj_attribute min_value_attr = __ATTR_RO(min_value);
> +static struct kobj_attribute max_value_attr = __ATTR_RO(max_value);
> +static struct kobj_attribute scalar_increment_attr = __ATTR_RO(scalar_increment);
> +static struct kobj_attribute default_value_attr = __ATTR_RO(default_value);
> +static struct kobj_attribute current_value_attr = __ATTR_RW(current_value);
> +
> +static struct attribute *coreboot_cfr_setting_attrs[] = {
> + &type_attr.attr,
> + &display_name_language_code_attr.attr,
> + &display_name_attr.attr,
> + &possible_values_attr.attr,
> + &min_value_attr.attr,
> + &max_value_attr.attr,
> + &scalar_increment_attr.attr,
> + &default_value_attr.attr,
> + ¤t_value_attr.attr,
> + NULL,
> +};
> +
> +static umode_t coreboot_cfr_attr_is_visible(struct kobject *kobj,
> + struct attribute *attr, int n)
> +{
> + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj);
> +
> + if (setting->type == COREBOOT_CFR_SETTING_NUMBER &&
> + attr == &possible_values_attr.attr)
> + return 0;
> +
> + if (setting->type != COREBOOT_CFR_SETTING_NUMBER &&
> + (attr == &min_value_attr.attr || attr == &max_value_attr.attr ||
> + attr == &scalar_increment_attr.attr))
> + return 0;
> +
> + if (setting->read_only && attr == ¤t_value_attr.attr)
> + return 0444;
> +
> + return attr->mode;
> +}
> +
> +static const struct attribute_group coreboot_cfr_setting_group = {
> + .attrs = coreboot_cfr_setting_attrs,
> + .is_visible = coreboot_cfr_attr_is_visible,
> +};
> +
> +static void coreboot_cfr_free_setting(struct coreboot_cfr_setting *setting)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < setting->n_values; i++)
> + kfree(setting->values[i].label);
> +
> + kfree(setting->values);
> + kfree(setting->display_name);
> + kfree(setting->name);
> + kfree(setting);
> +}
> +
> +static void coreboot_cfr_setting_release(struct kobject *kobj)
> +{
> + coreboot_cfr_free_setting(to_coreboot_cfr_setting(kobj));
> +}
> +
> +static const struct kobj_type coreboot_cfr_setting_ktype = {
> + .release = coreboot_cfr_setting_release,
> + .sysfs_ops = &kobj_sysfs_ops,
> +};
> +
> +static ssize_t pending_reboot_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "%d\n",
> + coreboot_cfr_data && coreboot_cfr_data->pending_reboot);
> +}
> +
> +static struct kobj_attribute pending_reboot_attr = __ATTR_RO(pending_reboot);
> +
> +static int coreboot_cfr_copy_bool_values(struct coreboot_cfr_setting *setting)
> +{
> + static const struct coreboot_cfr_enum bool_values[] = {
> + { .label = "Disabled", .value = 0 },
> + { .label = "Enabled", .value = 1 },
> + };
> + unsigned int i;
> +
> + setting->values = kcalloc(ARRAY_SIZE(bool_values), sizeof(*setting->values),
Include for ARRAY_SIZE().
> + GFP_KERNEL);
> + if (!setting->values)
> + return -ENOMEM;
> +
> + for (i = 0; i < ARRAY_SIZE(bool_values); i++) {
> + setting->values[i].label = kstrdup(bool_values[i].label,
> + GFP_KERNEL);
> + if (!setting->values[i].label)
> + return -ENOMEM;
> + setting->values[i].value = bool_values[i].value;
> + setting->n_values++;
> + }
> +
> + return 0;
> +}
> +
> +static int coreboot_cfr_count_enum_values(const void *base, size_t len)
> +{
> + const struct coreboot_table_entry *entry;
> + size_t off = 0;
> + int count = 0;
> +
> + while (off < len) {
> + if (len - off < sizeof(*entry))
> + return -EINVAL;
> +
> + entry = base + off;
> + if (entry->size < sizeof(*entry) || entry->size > len - off)
> + return -EINVAL;
> +
> + if (entry->tag == CFR_TAG_ENUM_VALUE)
> + count++;
> +
> + off += entry->size;
> + }
> +
> + return count;
> +}
> +
> +static int coreboot_cfr_copy_enum_values(struct coreboot_cfr_setting *setting,
> + const void *base, size_t len)
> +{
> + const struct lb_cfr_enum_value *enum_value;
> + const struct lb_cfr_varbinary *label;
> + const struct coreboot_table_entry *entry;
> + size_t off = 0;
> + int count;
> +
> + count = coreboot_cfr_count_enum_values(base, len);
> + if (count <= 0)
> + return count ?: -EINVAL;
> +
> + setting->values = kcalloc(count, sizeof(*setting->values), GFP_KERNEL);
> + if (!setting->values)
> + return -ENOMEM;
> +
> + while (off < len) {
> + entry = base + off;
> + if (entry->tag != CFR_TAG_ENUM_VALUE) {
> + off += entry->size;
> + continue;
> + }
> +
> + if (entry->size < sizeof(*enum_value))
> + return -EINVAL;
> +
> + enum_value = base + off;
> + label = coreboot_cfr_child_string(enum_value + 1,
> + enum_value->size - sizeof(*enum_value),
> + CFR_TAG_VARCHAR_UI_NAME);
> + if (!label)
> + return -EINVAL;
> +
> + setting->values[setting->n_values].label =
> + coreboot_cfr_string_dup(label);
> + if (!setting->values[setting->n_values].label)
> + return -ENOMEM;
> +
> + setting->values[setting->n_values].value = enum_value->value;
> + setting->n_values++;
> + off += entry->size;
> + }
> +
> + return 0;
> +}
> +
> +static int coreboot_cfr_setting_is_usable(struct coreboot_cfr_setting *setting)
> +{
> + u32 value;
> + int ret;
> +
> + ret = coreboot_cfr_read_value(setting, &value, NULL);
> + if (ret)
> + return ret;
> +
> + if (!coreboot_cfr_value_is_valid(setting, value))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int coreboot_cfr_register_setting(struct coreboot_cfr_drvdata *data,
> + struct coreboot_cfr_setting *setting)
> +{
> + int ret;
> +
> + ret = kobject_init_and_add(&setting->kobj, &coreboot_cfr_setting_ktype,
> + &data->attrs_kset->kobj, "%s", setting->name);
> + if (ret) {
> + kobject_put(&setting->kobj);
> + return ret;
> + }
> +
> + ret = sysfs_create_group(&setting->kobj, &coreboot_cfr_setting_group);
> + if (ret) {
> + kobject_put(&setting->kobj);
> + return ret;
> + }
> +
> + list_add_tail(&setting->node, &data->settings);
> + return 0;
> +}
> +
> +static int coreboot_cfr_add_numeric_option(struct coreboot_cfr_drvdata *data,
> + const struct lb_cfr_numeric_option *option)
> +{
> + const struct lb_cfr_varbinary *name;
> + const struct lb_cfr_varbinary *display_name;
> + const struct lb_cfr_runtime_apply *runtime_apply;
> + const void *child_base = option + 1;
> + struct coreboot_cfr_setting *setting;
> + size_t child_len = option->size - sizeof(*option);
> + int ret;
> +
> + if (!(option->flags & CFR_OPTFLAG_RUNTIME))
> + return 0;
> +
> + if (option->flags & COREBOOT_CFR_OPT_SKIP_FLAGS)
> + return 0;
> +
> + if (option->dependency_id)
> + return 0;
> +
> + setting = kzalloc_obj(*setting, GFP_KERNEL);
> + if (!setting)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&setting->node);
> + setting->drvdata = data;
> + setting->default_value = option->default_value;
> + setting->min = option->min;
> + setting->max = option->max;
> + setting->step = option->step ?: 1;
> + setting->read_only = option->flags & COREBOOT_CFR_OPT_READ_ONLY_FLAGS;
> +
> + runtime_apply = coreboot_cfr_child_runtime_apply(child_base, child_len);
> + if (IS_ERR(runtime_apply)) {
> + ret = PTR_ERR(runtime_apply);
> + goto err_put_setting;
> + }
> +
> + if (runtime_apply && runtime_apply->method == CFR_RUNTIME_APPLY_APM_CNT &&
> + runtime_apply->id) {
> + setting->runtime_apply_method = runtime_apply->method;
> + setting->runtime_apply_id = runtime_apply->id;
> + }
> +
> + name = coreboot_cfr_child_string(child_base, child_len,
> + CFR_TAG_VARCHAR_OPT_NAME);
> + if (!name) {
> + ret = -EINVAL;
> + goto err_put_setting;
> + }
> +
> + setting->name = coreboot_cfr_string_dup(name);
> + if (!setting->name) {
> + ret = -ENOMEM;
> + goto err_put_setting;
> + }
> +
> + if (!coreboot_cfr_string_is_valid_name(setting->name)) {
> + ret = -EINVAL;
> + goto err_put_setting;
> + }
> +
> + display_name = coreboot_cfr_child_string(child_base, child_len,
> + CFR_TAG_VARCHAR_UI_NAME);
> + if (display_name)
> + setting->display_name = coreboot_cfr_string_dup(display_name);
> + else
> + setting->display_name = kstrdup(setting->name, GFP_KERNEL);
> + if (!setting->display_name) {
> + ret = -ENOMEM;
> + goto err_put_setting;
> + }
> +
> + switch (option->tag) {
> + case CFR_TAG_OPTION_BOOL:
> + setting->type = COREBOOT_CFR_SETTING_BOOL;
> + setting->min = 0;
> + setting->max = 1;
> + setting->step = 1;
> + ret = coreboot_cfr_copy_bool_values(setting);
> + break;
> + case CFR_TAG_OPTION_ENUM:
> + setting->type = COREBOOT_CFR_SETTING_ENUM;
> + ret = coreboot_cfr_copy_enum_values(setting, child_base,
> + child_len);
> + break;
> + case CFR_TAG_OPTION_NUMBER:
> + setting->type = COREBOOT_CFR_SETTING_NUMBER;
> + if (!setting->min && !setting->max)
> + setting->max = U32_MAX;
> + else if (setting->max < setting->min)
> + setting->max = U32_MAX;
> + ret = 0;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (ret)
> + goto err_put_setting;
> +
> + ret = coreboot_cfr_setting_is_usable(setting);
> + if (ret) {
> + ret = 0;
> + goto err_put_setting;
> + }
> +
> + ret = coreboot_cfr_register_setting(data, setting);
> + if (ret)
> + return ret;
> +
> + return 0;
> +
> +err_put_setting:
> + coreboot_cfr_free_setting(setting);
> + return ret;
> +}
> +
> +static int coreboot_cfr_parse_records(struct coreboot_cfr_drvdata *data,
> + const void *base, size_t len)
> +{
> + const struct coreboot_table_entry *entry;
> + const void *child_base;
> + size_t child_len;
> + size_t off = 0;
> + int ret;
> +
> + while (off < len) {
> + if (len - off < sizeof(*entry))
> + return -EINVAL;
> +
> + entry = base + off;
> + if (entry->size < sizeof(*entry) || entry->size > len - off)
> + return -EINVAL;
> +
> + switch (entry->tag) {
> + case CFR_TAG_OPTION_FORM:
> + if (entry->size < sizeof(struct lb_cfr_option_form))
> + return -EINVAL;
> +
> + child_base = base + off + sizeof(struct lb_cfr_option_form);
> + child_len = entry->size - sizeof(struct lb_cfr_option_form);
> + ret = coreboot_cfr_parse_records(data, child_base,
> + child_len);
> + if (ret)
> + return ret;
> + break;
> + case CFR_TAG_OPTION_ENUM:
> + case CFR_TAG_OPTION_NUMBER:
> + case CFR_TAG_OPTION_BOOL:
> + if (entry->size < sizeof(struct lb_cfr_numeric_option))
> + return -EINVAL;
> + ret = coreboot_cfr_add_numeric_option(data, base + off);
> + if (ret)
> + return ret;
> + break;
> + default:
> + break;
> + }
> +
> + off += entry->size;
> + }
> +
> + return 0;
> +}
> +
> +static void coreboot_cfr_unregister_settings(struct coreboot_cfr_drvdata *data)
> +{
> + struct coreboot_cfr_setting *setting, *tmp;
> +
> + list_for_each_entry_safe(setting, tmp, &data->settings, node) {
> + sysfs_remove_group(&setting->kobj, &coreboot_cfr_setting_group);
> + list_del(&setting->node);
> + kobject_put(&setting->kobj);
> + }
> +}
> +
> +static int coreboot_cfr_probe(struct coreboot_device *dev)
> +{
> + const struct lb_cfr *root = (const struct lb_cfr *)dev->raw;
> + struct coreboot_cfr_drvdata *data;
> + size_t payload_len;
> + int ret;
> +
> + if (dev->entry.size < sizeof(*root))
> + return -EINVAL;
> +
> + if (root->tag != LB_TAG_CFR_ROOT || root->version != CFR_VERSION)
> + return -EINVAL;
> +
> + if (root->size < sizeof(*root) || root->size > dev->entry.size)
> + return -EINVAL;
> +
> + if (!efivar_is_available())
> + return -EPROBE_DEFER;
> +
> + data = kzalloc_obj(*data, GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&data->settings);
> + mutex_init(&data->lock);
You need pairing mutex_destroy() or use devm_mutex_init() (alloc should be
converted to devm as well then to ensure correct ordering).
> + dev_set_drvdata(&dev->dev, data);
> + coreboot_cfr_data = data;
> +
> + data->class_dev = device_create(&firmware_attributes_class, NULL,
> + MKDEV(0, 0), NULL, DRIVER_NAME);
> + if (IS_ERR(data->class_dev)) {
> + ret = PTR_ERR(data->class_dev);
> + goto err_free_data;
> + }
> +
> + data->attrs_kset = kset_create_and_add("attributes", NULL,
> + &data->class_dev->kobj);
> + if (!data->attrs_kset) {
> + ret = -ENOMEM;
> + goto err_unregister_dev;
> + }
> +
> + ret = sysfs_create_file(&data->attrs_kset->kobj,
> + &pending_reboot_attr.attr);
> + if (ret)
> + goto err_unregister_attrs;
> +
> + payload_len = root->size - sizeof(*root);
> + ret = coreboot_cfr_parse_records(data, root + 1, payload_len);
> + if (ret)
> + goto err_unregister_settings;
> +
> + if (list_empty(&data->settings)) {
> + ret = -ENODEV;
> + goto err_unregister_settings;
> + }
> +
> + return 0;
> +
> +err_unregister_settings:
> + coreboot_cfr_unregister_settings(data);
> + sysfs_remove_file(&data->attrs_kset->kobj, &pending_reboot_attr.attr);
> +err_unregister_attrs:
> + kset_unregister(data->attrs_kset);
> +err_unregister_dev:
> + device_unregister(data->class_dev);
> +err_free_data:
> + if (coreboot_cfr_data == data)
> + coreboot_cfr_data = NULL;
> + kfree(data);
> + return ret;
> +}
> +
> +static void coreboot_cfr_remove(struct coreboot_device *dev)
> +{
> + struct coreboot_cfr_drvdata *data = dev_get_drvdata(&dev->dev);
> +
> + coreboot_cfr_unregister_settings(data);
> + sysfs_remove_file(&data->attrs_kset->kobj, &pending_reboot_attr.attr);
> + kset_unregister(data->attrs_kset);
> + device_unregister(data->class_dev);
> + if (coreboot_cfr_data == data)
> + coreboot_cfr_data = NULL;
> + kfree(data);
> +}
> +
> +static const struct coreboot_device_id coreboot_cfr_ids[] = {
> + { .tag = LB_TAG_CFR_ROOT },
> + { }
> +};
> +MODULE_DEVICE_TABLE(coreboot, coreboot_cfr_ids);
> +
> +static struct coreboot_driver coreboot_cfr_driver = {
> + .probe = coreboot_cfr_probe,
> + .remove = coreboot_cfr_remove,
> + .drv = {
> + .name = DRIVER_NAME,
> + },
> + .id_table = coreboot_cfr_ids,
> +};
> +module_coreboot_driver(coreboot_cfr_driver);
> +
> +MODULE_AUTHOR("Sean Rhodes <sean@starlabs.systems>");
> +MODULE_DESCRIPTION("coreboot CFR firmware attributes driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("EFIVAR");
> diff --git a/include/linux/coreboot.h b/include/linux/coreboot.h
> index 5d40ca7a1d89..2f1e6e6a9ceb 100644
> --- a/include/linux/coreboot.h
> +++ b/include/linux/coreboot.h
> @@ -12,16 +12,21 @@
> #ifndef _LINUX_COREBOOT_H
> #define _LINUX_COREBOOT_H
>
> +#include <linux/bits.h>
> #include <linux/compiler_attributes.h>
> +#include <linux/device.h>
> #include <linux/stddef.h>
> #include <linux/types.h>
>
> +struct module;
> +
> typedef __aligned(4) u64 cb_u64;
>
> /* List of coreboot entry structures that is used */
>
> #define CB_TAG_FRAMEBUFFER 0x12
> #define LB_TAG_CBMEM_ENTRY 0x31
> +#define LB_TAG_CFR_ROOT 0x47
>
> /* Generic */
> struct coreboot_table_entry {
> @@ -87,4 +92,145 @@ struct lb_framebuffer {
> #define LB_FRAMEBUFFER_HAS_ORIENTATION(__fb) \
> ((__fb)->size >= offsetofend(struct lb_framebuffer, orientation))
>
> +#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_OPTION_VARCHAR = 6,
> + CFR_TAG_VARCHAR_OPT_NAME = 7,
> + CFR_TAG_VARCHAR_UI_NAME = 8,
> + CFR_TAG_VARCHAR_UI_HELPTEXT = 9,
> + CFR_TAG_VARCHAR_DEF_VALUE = 10,
> + CFR_TAG_OPTION_COMMENT = 11,
> + CFR_TAG_DEP_VALUES = 12,
> + 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),
> +};
> +
> +struct lb_cfr_varbinary {
> + u32 tag;
> + u32 size;
> + u32 data_length;
> +} __packed;
> +
> +struct lb_cfr_enum_value {
> + u32 tag;
> + u32 size;
> + u32 value;
> +} __packed;
> +
> +enum cfr_runtime_apply_method {
> + CFR_RUNTIME_APPLY_NONE = 0,
> + CFR_RUNTIME_APPLY_APM_CNT = 1,
> +};
> +
> +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_varchar_option {
> + u32 tag;
> + u32 size;
> + cb_u64 object_id;
> + cb_u64 dependency_id;
> + u32 flags;
> +} __packed;
> +
> +struct lb_cfr_option_comment {
> + u32 tag;
> + u32 size;
> + cb_u64 object_id;
> + cb_u64 dependency_id;
> + u32 flags;
> +} __packed;
> +
> +struct lb_cfr_option_form {
> + u32 tag;
> + u32 size;
> + cb_u64 object_id;
> + cb_u64 dependency_id;
> + u32 flags;
> +} __packed;
> +
> +struct lb_cfr {
> + u32 tag;
> + u32 size;
> + u32 version;
> + u32 checksum;
> +} __packed;
> +
> +struct coreboot_device_id;
> +struct coreboot_device;
> +
> +/* A device, additionally with information from coreboot. */
> +struct coreboot_device {
> + struct device dev;
> + union {
> + struct coreboot_table_entry entry;
> + struct lb_cbmem_ref cbmem_ref;
> + struct lb_cbmem_entry cbmem_entry;
> + struct lb_framebuffer framebuffer;
> + DECLARE_FLEX_ARRAY(u8, raw);
> + };
> +};
> +
> +static inline struct coreboot_device *dev_to_coreboot_device(struct device *dev)
> +{
> + return container_of(dev, struct coreboot_device, dev);
Please add include.
> +}
> +
> +/* A driver for handling devices described in coreboot tables. */
> +struct coreboot_driver {
> + int (*probe)(struct coreboot_device *dev);
> + void (*remove)(struct coreboot_device *dev);
> + struct device_driver drv;
> + const struct coreboot_device_id *id_table;
> +};
> +
> +/* use a macro to avoid include chaining to get THIS_MODULE */
> +#define coreboot_driver_register(driver) \
> + __coreboot_driver_register(driver, THIS_MODULE)
> +/* Register a driver that uses the data from a coreboot table. */
> +int __coreboot_driver_register(struct coreboot_driver *driver,
> + struct module *owner);
> +
> +/* Unregister a driver that uses the data from a coreboot table. */
> +void coreboot_driver_unregister(struct coreboot_driver *driver);
> +
> +/* module_coreboot_driver() - Helper macro for drivers that don't do
> + * anything special in module init/exit. This eliminates a lot of
> + * boilerplate. Each module may only use this macro once, and
> + * calling it replaces module_init() and module_exit()
> + */
> +#define module_coreboot_driver(__coreboot_driver) \
> + module_driver(__coreboot_driver, coreboot_driver_register, \
> + coreboot_driver_unregister)
> +
> #endif /* _LINUX_COREBOOT_H */
>
--
i.
prev parent reply other threads:[~2026-07-09 12:05 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-30 9:21 Sean Rhodes
2026-07-09 12:05 ` Ilpo Järvinen [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5d1ba6a1-7341-a9ac-2de6-a3f81346bd63@linux.intel.com \
--to=ilpo.jarvinen@linux.intel.com \
--cc=briannorris@chromium.org \
--cc=chrome-platform@lists.linux.dev \
--cc=hansg@kernel.org \
--cc=jwerner@chromium.org \
--cc=linux-kernel@vger.kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
--cc=sean@starlabs.systems \
--cc=tzungbi@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome