summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/google/vpd.c
diff options
context:
space:
mode:
authorMiguel Ojeda <ojeda@kernel.org>2026-08-16 15:32:33 +0200
committerMiguel Ojeda <ojeda@kernel.org>2026-08-23 21:27:17 +0200
commit5febf432df1cfa5b25d99b54c32103fafbdd0eb9 (patch)
tree93a1ed1d337bc53adf1e572c2586d347703b286e /drivers/firmware/google/vpd.c
downloadlinux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.tar.gz
linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.zip
kbuild: rust: keep Rust objects out of Clang LTO with inline helpersgrafted
Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit `objtool` errors such as: vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a: can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd The reason is that in such builds, the Clang invocation that compiles the combined Rust plus helpers bitcode emits LLVM bitcode (again) -- the final code generation happens in the linker's LTO step, which the `-mllvm` trap options passed to Clang do not reach. This, in turn, means that unreachable traps are missing, and the impossible paths do not merely fallthrough to the next symbol, but past the end of their own section, since LTO builds place each function in its own section. Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always emits machine code directly, with the traps in place. Assisted-by: LLM Cc: Gary Guo <gary@garyguo.net> Cc: Boqun Feng <boqun@kernel.org> Cc: Alice Ryhl <aliceryhl@google.com> Cc: Matthew Maurer <mmaurer@google.com> Cc: Josh Poimboeuf <jpoimboe@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo <gary@garyguo.net> Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Diffstat (limited to 'drivers/firmware/google/vpd.c')
-rw-r--r--drivers/firmware/google/vpd.c328
1 files changed, 328 insertions, 0 deletions
diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c
new file mode 100644
index 000000000..fbb5a8d7c
--- /dev/null
+++ b/drivers/firmware/google/vpd.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * vpd.c
+ *
+ * Driver for exporting VPD content to sysfs.
+ *
+ * Copyright 2017 Google Inc.
+ */
+
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/device-id/coreboot.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include "coreboot_table.h"
+#include "vpd_decode.h"
+
+#define CB_TAG_VPD 0x2c
+#define VPD_CBMEM_MAGIC 0x43524f53
+
+static struct kobject *vpd_kobj;
+
+struct vpd_cbmem {
+ u32 magic;
+ u32 version;
+ u32 ro_size;
+ u32 rw_size;
+ u8 blob[];
+};
+
+struct vpd_section {
+ bool enabled;
+ const char *name;
+ char *raw_name; /* the string name_raw */
+ struct kobject *kobj; /* vpd/name directory */
+ char *baseaddr;
+ struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
+ struct list_head attribs; /* key/value in vpd_attrib_info list */
+};
+
+struct vpd_attrib_info {
+ char *key;
+ const char *value;
+ struct bin_attribute bin_attr;
+ struct list_head list;
+};
+
+static struct vpd_section ro_vpd;
+static struct vpd_section rw_vpd;
+
+static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct vpd_attrib_info *info = bin_attr->private;
+
+ return memory_read_from_buffer(buf, count, &pos, info->value,
+ info->bin_attr.size);
+}
+
+/*
+ * vpd_section_check_key_name()
+ *
+ * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
+ * old firmware versions may have entries like "S/N" which are problematic when
+ * exporting them as sysfs attributes. These keys present in old firmwares are
+ * ignored.
+ *
+ * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
+ *
+ * @key: The key name to check
+ * @key_len: key name length
+ */
+static int vpd_section_check_key_name(const u8 *key, s32 key_len)
+{
+ int c;
+
+ while (key_len-- > 0) {
+ c = *key++;
+
+ if (!isalnum(c) && c != '_')
+ return VPD_FAIL;
+ }
+
+ return VPD_OK;
+}
+
+static int vpd_section_attrib_add(const u8 *key, u32 key_len,
+ const u8 *value, u32 value_len,
+ void *arg)
+{
+ int ret;
+ struct vpd_section *sec = arg;
+ struct vpd_attrib_info *info;
+
+ /*
+ * Return VPD_OK immediately to decode next entry if the current key
+ * name contains invalid characters.
+ */
+ if (vpd_section_check_key_name(key, key_len) != VPD_OK)
+ return VPD_OK;
+
+ info = kzalloc_obj(*info);
+ if (!info)
+ return -ENOMEM;
+
+ info->key = kstrndup(key, key_len, GFP_KERNEL);
+ if (!info->key) {
+ ret = -ENOMEM;
+ goto free_info;
+ }
+
+ sysfs_bin_attr_init(&info->bin_attr);
+ info->bin_attr.attr.name = info->key;
+ info->bin_attr.attr.mode = 0444;
+ info->bin_attr.size = value_len;
+ info->bin_attr.read = vpd_attrib_read;
+ info->bin_attr.private = info;
+
+ info->value = value;
+
+ INIT_LIST_HEAD(&info->list);
+
+ ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
+ if (ret)
+ goto free_info_key;
+
+ list_add_tail(&info->list, &sec->attribs);
+ return 0;
+
+free_info_key:
+ kfree(info->key);
+free_info:
+ kfree(info);
+
+ return ret;
+}
+
+static void vpd_section_attrib_destroy(struct vpd_section *sec)
+{
+ struct vpd_attrib_info *info;
+ struct vpd_attrib_info *temp;
+
+ list_for_each_entry_safe(info, temp, &sec->attribs, list) {
+ sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
+ kfree(info->key);
+ kfree(info);
+ }
+}
+
+static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct vpd_section *sec = bin_attr->private;
+
+ return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
+ sec->bin_attr.size);
+}
+
+static int vpd_section_create_attribs(struct vpd_section *sec)
+{
+ s32 consumed;
+ int ret;
+
+ consumed = 0;
+ do {
+ ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
+ &consumed, vpd_section_attrib_add, sec);
+ } while (ret == VPD_OK);
+
+ return 0;
+}
+
+static int vpd_section_init(const char *name, struct vpd_section *sec,
+ phys_addr_t physaddr, size_t size)
+{
+ int err;
+
+ sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
+ if (!sec->baseaddr)
+ return -ENOMEM;
+
+ sec->name = name;
+
+ /* We want to export the raw partition with name ${name}_raw */
+ sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
+ if (!sec->raw_name) {
+ err = -ENOMEM;
+ goto err_memunmap;
+ }
+
+ sysfs_bin_attr_init(&sec->bin_attr);
+ sec->bin_attr.attr.name = sec->raw_name;
+ sec->bin_attr.attr.mode = 0444;
+ sec->bin_attr.size = size;
+ sec->bin_attr.read = vpd_section_read;
+ sec->bin_attr.private = sec;
+
+ err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
+ if (err)
+ goto err_free_raw_name;
+
+ sec->kobj = kobject_create_and_add(name, vpd_kobj);
+ if (!sec->kobj) {
+ err = -EINVAL;
+ goto err_sysfs_remove;
+ }
+
+ INIT_LIST_HEAD(&sec->attribs);
+ vpd_section_create_attribs(sec);
+
+ sec->enabled = true;
+
+ return 0;
+
+err_sysfs_remove:
+ sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+err_free_raw_name:
+ kfree(sec->raw_name);
+err_memunmap:
+ memunmap(sec->baseaddr);
+ return err;
+}
+
+static int vpd_section_destroy(struct vpd_section *sec)
+{
+ if (sec->enabled) {
+ vpd_section_attrib_destroy(sec);
+ kobject_put(sec->kobj);
+ sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+ kfree(sec->raw_name);
+ memunmap(sec->baseaddr);
+ sec->enabled = false;
+ }
+
+ return 0;
+}
+
+static int vpd_sections_init(phys_addr_t physaddr)
+{
+ struct vpd_cbmem *temp;
+ struct vpd_cbmem header;
+ int ret = 0;
+
+ temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
+ if (!temp)
+ return -ENOMEM;
+
+ memcpy(&header, temp, sizeof(struct vpd_cbmem));
+ memunmap(temp);
+
+ if (header.magic != VPD_CBMEM_MAGIC)
+ return -ENODEV;
+
+ if (header.ro_size) {
+ ret = vpd_section_init("ro", &ro_vpd,
+ physaddr + sizeof(struct vpd_cbmem),
+ header.ro_size);
+ if (ret)
+ return ret;
+ }
+
+ if (header.rw_size) {
+ ret = vpd_section_init("rw", &rw_vpd,
+ physaddr + sizeof(struct vpd_cbmem) +
+ header.ro_size, header.rw_size);
+ if (ret) {
+ vpd_section_destroy(&ro_vpd);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int vpd_probe(struct coreboot_device *dev)
+{
+ int ret;
+
+ vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
+ if (!vpd_kobj)
+ return -ENOMEM;
+
+ ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);
+ if (ret) {
+ kobject_put(vpd_kobj);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vpd_remove(struct coreboot_device *dev)
+{
+ vpd_section_destroy(&ro_vpd);
+ vpd_section_destroy(&rw_vpd);
+
+ kobject_put(vpd_kobj);
+}
+
+static const struct coreboot_device_id vpd_ids[] = {
+ { .tag = CB_TAG_VPD },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(coreboot, vpd_ids);
+
+static struct coreboot_driver vpd_driver = {
+ .probe = vpd_probe,
+ .remove = vpd_remove,
+ .drv = {
+ .name = "vpd",
+ },
+ .id_table = vpd_ids,
+};
+module_coreboot_driver(vpd_driver);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_DESCRIPTION("Driver for exporting Vital Product Data content to sysfs");
+MODULE_LICENSE("GPL");