From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@pengutronix.de>
Subject: [PATCH 04/10] common: add barebox TLV support
Date: Fri, 11 Apr 2025 09:40:39 +0200 [thread overview]
Message-ID: <20250411074045.2019372-5-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20250411074045.2019372-1-a.fatoum@pengutronix.de>
barebox TLV is a scheme for storing factory data on non-volatile
storage. Unlike state, it's meant to be read-only and if content
is limited to already specified tags, it can be extended later on,
without modifying the bootloader binary.
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
.../bindings/nvmem/barebox,tlv.yaml | 58 +++++
common/Kconfig | 24 ++
common/Makefile | 1 +
common/tlv/Makefile | 4 +
common/tlv/barebox.c | 183 +++++++++++++++
common/tlv/bus.c | 133 +++++++++++
common/tlv/drv.c | 49 ++++
common/tlv/parser.c | 211 ++++++++++++++++++
common/tlv/register.c | 94 ++++++++
include/string.h | 5 +
include/tlv/format.h | 69 ++++++
include/tlv/tlv.h | 99 ++++++++
test/self/Kconfig | 7 +
test/self/Makefile | 1 +
test/self/tlv.c | 89 ++++++++
test/self/tlv.dts | 27 +++
16 files changed, 1054 insertions(+)
create mode 100644 Documentation/devicetree/bindings/nvmem/barebox,tlv.yaml
create mode 100644 common/tlv/Makefile
create mode 100644 common/tlv/barebox.c
create mode 100644 common/tlv/bus.c
create mode 100644 common/tlv/drv.c
create mode 100644 common/tlv/parser.c
create mode 100644 common/tlv/register.c
create mode 100644 include/tlv/format.h
create mode 100644 include/tlv/tlv.h
create mode 100644 test/self/tlv.c
create mode 100644 test/self/tlv.dts
diff --git a/Documentation/devicetree/bindings/nvmem/barebox,tlv.yaml b/Documentation/devicetree/bindings/nvmem/barebox,tlv.yaml
new file mode 100644
index 000000000000..b54ab600e309
--- /dev/null
+++ b/Documentation/devicetree/bindings/nvmem/barebox,tlv.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://barebox.org/schemas/nvmem/barebox,tlv.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: barebox TLV factory data
+
+description: |
+ barebox TLV is a scheme for storing factory data on non-volatile
+ storage. Unlike state, it's meant to be read-only and if content
+ is limited to already specified tags, it can be extended later on,
+ without modifying the bootloader binary.
+
+ Variables can not yet be defined as NVMEM device subnodes.
+
+maintainers:
+ - Ahmad Fatoum <a.fatoum@pengutronix.de>
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - barebox,tlv-v1 # magic: 0x61bb95f2
+ - const: barebox,tlv
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - label
+ - reg
+
+allOf:
+ - $ref: partition.yaml#
+
+additionalProperties: false
+
+examples:
+ - |
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ reg = <0x0 0x100000>;
+ label = "barebox";
+ read-only;
+ };
+
+ partition@100000 {
+ compatible = "barebox,tlv";
+ label = "tlv";
+ reg = <0x100000 0x10000>;
+ };
+ };
diff --git a/common/Kconfig b/common/Kconfig
index 2d2be0f7c4f6..ee2737c93eb9 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -1151,6 +1151,30 @@ config BTHREAD
scheduled within delay loops and the console idle to asynchronously
execute actions, like checking for link up or feeding a watchdog.
+config TLV
+ bool "barebox TLV support"
+ depends on OFDEVICE
+ help
+ barebox TLV is a scheme for storing factory data on non-volatile
+ storage. Unlike state, it's meant to be read-only.
+
+config TLV_DRV
+ bool "barebox TLV generic driver"
+ depends on TLV
+ default y
+ help
+ barebox,tlv devices in the device tree will be matched against
+ a compatible decoder via the 4-byte magic header.
+
+config TLV_BAREBOX
+ bool "barebox TLV common format"
+ depends on TLV
+ depends on PARAMETER
+ select PRINTF_HEXSTR
+ default y
+ help
+ Decoder support for the common barebox TLV format.
+
config STATE
bool "generic state infrastructure"
select CRC32
diff --git a/common/Makefile b/common/Makefile
index 9b67187561bf..3ff3d9ec98ba 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_RESET_SOURCE) += reset_source.o
obj-$(CONFIG_SHELL_HUSH) += hush.o
obj-$(CONFIG_SHELL_SIMPLE) += parser.o
obj-$(CONFIG_STATE) += state/
+obj-$(CONFIG_TLV) += tlv/
obj-$(CONFIG_RATP) += ratp/
obj-$(CONFIG_BOOTCHOOSER) += bootchooser.o
obj-$(CONFIG_UIMAGE) += uimage_types.o uimage.o
diff --git a/common/tlv/Makefile b/common/tlv/Makefile
new file mode 100644
index 000000000000..ea982f229a08
--- /dev/null
+++ b/common/tlv/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += parser.o bus.o register.o drv.o
+obj-$(CONFIG_TLV_DRV) += drv.o barebox.o
+obj-$(CONFIG_TLV_BAREBOX) += barebox.o
diff --git a/common/tlv/barebox.c b/common/tlv/barebox.c
new file mode 100644
index 000000000000..a5febc3b12c3
--- /dev/null
+++ b/common/tlv/barebox.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <net.h>
+#include <tlv/tlv.h>
+
+int tlv_handle_serial(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ const char *str;
+
+ str = __tlv_format_str(dev, map, len, val);
+ if (!str)
+ return -ENOMEM;
+
+ barebox_set_serial_number(str);
+ return 0;
+}
+
+int tlv_handle_eth_address(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ int i;
+
+ if (len % ETH_ALEN != 0)
+ return -EINVAL;
+
+ for (i = 0; i < len / ETH_ALEN; i++)
+ eth_register_ethaddr(i, val + i * ETH_ALEN);
+
+ return tlv_format_mac(dev, map, len, val);
+}
+
+int tlv_handle_eth_address_seq(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ u8 eth_addr[ETH_ALEN];
+ int eth_count;
+
+ eth_count = *val;
+
+ if (len != 1 + ETH_ALEN)
+ return -EINVAL;
+
+ memcpy(eth_addr, val + 1, ETH_ALEN);
+
+ for (int i = 0; i < eth_count; i++, eth_addr_inc(eth_addr)) {
+ eth_register_ethaddr(i, eth_addr);
+ tlv_format_mac(dev, map, ETH_ALEN, eth_addr);
+ }
+
+ return 0;
+}
+
+static const char *__tlv_format(struct tlv_device *dev, struct tlv_mapping *map, char *buf)
+{
+ struct param_d *param;
+ int ret;
+
+ if (!buf)
+ return NULL;
+
+ param = dev_add_param_fixed(&dev->dev, map->prop, NULL);
+ if (!IS_ERR(param))
+ param->value = buf; /* pass ownership */
+
+ ret = of_append_property(tlv_of_node(dev), map->prop, buf, strlen(buf) + 1);
+ if (ret)
+ return NULL;
+
+ return buf;
+}
+
+#define tlv_format(tlvdev, map, ...) ({ __tlv_format(tlvdev, map, basprintf(__VA_ARGS__)) ? 0 : -ENOMEM; })
+
+const char * __tlv_format_str(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ return __tlv_format(dev, map, basprintf("%.*s", len, val));
+}
+
+int tlv_format_str(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ return __tlv_format_str(dev, map, len, val) ? 0 : -ENOMEM;
+}
+
+int tlv_format_hex(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ return tlv_format(dev, map, "%*ph", len, val);
+}
+
+int tlv_format_blob(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ struct param_d *param;
+
+ param = dev_add_param_fixed(&dev->dev, map->prop, NULL);
+ if (!IS_ERR(param))
+ param->value = basprintf("%*phN", len, val);
+
+ return of_append_property(tlv_of_node(dev), map->prop, val, len);
+}
+
+static struct device_node *of_append_node(struct device_node *root, const char *name)
+{
+ struct device_node *np;
+
+ np = of_get_child_by_name(root, name);
+ if (np)
+ return np;
+
+ return of_new_node(root, name);
+}
+
+int tlv_format_mac(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ struct device_node *np = tlv_of_node(dev);
+ struct property *pp;
+ char propname[sizeof("address-4294967295")];
+ int base = 0, i, ret;
+
+ if (len % 6 != 0)
+ return -EINVAL;
+
+ np = of_append_node(np, map->prop);
+ if (!np)
+ return -ENOMEM;
+
+ for_each_property_of_node(np, pp)
+ base++;
+
+ for (i = base; i < base + len / 6; i++) {
+ snprintf(propname, sizeof(propname), "address-%u", i);
+ ret = of_property_sprintf(np, propname, "%*phC", 6, val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int tlv_format_dec(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val)
+{
+ switch (len) {
+ case 1:
+ return tlv_format(dev, map, "%u", *(u8 *)val);
+ case 2:
+ return tlv_format(dev, map, "%u", get_unaligned_be16(val));
+ case 4:
+ return tlv_format(dev, map, "%u", get_unaligned_be32(val));
+ case 8:
+ return tlv_format(dev, map, "%llu", get_unaligned_be64(val));
+ default:
+ return tlv_format_hex(dev, map, len, val);
+ }
+}
+
+struct tlv_mapping barebox_tlv_v1_mappings[] = {
+ { 0x0002, tlv_format_str, "device-hardware-release" },
+ { 0x0003, tlv_format_dec, "factory-timestamp" },
+ { 0x0004, tlv_handle_serial, "device-serial-number"},
+ { 0x0005, tlv_format_dec, "modification" },
+ { 0x0006, tlv_format_str, "featureset" },
+ { 0x0007, tlv_format_str, "pcba-serial-number"},
+ { 0x0008, tlv_format_str, "pcba-hardware-release"},
+ { 0x0011, tlv_handle_eth_address, "ethernet-address" },
+ { 0x0012, tlv_handle_eth_address_seq, "ethernet-address" },
+ { /* sentintel */ },
+};
+
+static struct tlv_mapping *mappings[] = { barebox_tlv_v1_mappings, NULL };
+static struct of_device_id of_matches[] = {
+ { .compatible = "barebox,tlv-v1" },
+ { /* sentinel */}
+};
+
+static struct tlv_decoder barebox_tlv_v1 = {
+ .magic = TLV_MAGIC_BAREBOX_V1,
+ .driver.name = "barebox-tlv-v1",
+ .driver.of_compatible = of_matches,
+ .mappings = mappings,
+};
+
+static int tlv_register_default(void)
+{
+ return tlv_register_decoder(&barebox_tlv_v1);
+}
+device_initcall(tlv_register_default);
diff --git a/common/tlv/bus.c b/common/tlv/bus.c
new file mode 100644
index 000000000000..366f67120ec2
--- /dev/null
+++ b/common/tlv/bus.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <tlv/tlv.h>
+#include <linux/err.h>
+#include <of.h>
+
+static void tlv_devinfo(struct device *dev)
+{
+ struct tlv_device *tlvdev = to_tlv_device(dev);
+
+ printf("Magic: %08x\n", tlvdev->magic);
+}
+
+struct tlv_device *tlv_register_device(struct tlv_header *header,
+ struct device *parent)
+{
+ struct tlv_device *tlvdev;
+ const char *name = NULL;
+ char *buf = NULL;
+ struct device *dev;
+ static int id = 0;
+
+ tlvdev = xzalloc(sizeof(*tlvdev));
+
+ dev = &tlvdev->dev;
+
+ dev->bus = &tlv_bus;
+ devinfo_add(dev, tlv_devinfo);
+ dev->platform_data = header;
+ tlvdev->magic = be32_to_cpu(header->magic);
+ dev->parent = parent ?: tlv_bus.dev;
+ dev->id = DEVICE_ID_SINGLE;
+
+ if (parent)
+ name = of_alias_get(parent->device_node);
+ if (!name)
+ name = buf = basprintf("tlv%u", id++);
+
+ dev->device_node = of_new_node(of_new_node(NULL, NULL), name);
+ dev->device_node->dev = dev;
+ dev_set_name(dev, name);
+ register_device(dev);
+
+ free(buf);
+ return tlvdev;
+}
+
+void tlv_free_device(struct tlv_device *tlvdev)
+{
+ tlv_of_unregister_fixup(tlvdev);
+
+ devinfo_del(&tlvdev->dev, tlv_devinfo);
+ unregister_device(&tlvdev->dev);
+
+ free(tlvdev->dev.platform_data);
+ of_delete_node(tlvdev->dev.device_node);
+
+ free(tlvdev);
+}
+
+static int tlv_bus_match(struct device *dev, struct driver *drv)
+{
+ struct tlv_decoder *decoder = to_tlv_decoder(drv);
+ struct tlv_device *tlvdev = to_tlv_device(dev);
+
+ return decoder->magic == tlvdev->magic ? 0 : -1;
+}
+
+static int tlv_bus_probe(struct device *dev)
+{
+ return dev->driver->probe(dev);
+}
+
+static void tlv_bus_remove(struct device *dev)
+{
+ if (dev->driver->remove)
+ dev->driver->remove(dev);
+}
+
+struct bus_type tlv_bus = {
+ .name = "barebox-tlv",
+ .match = tlv_bus_match,
+ .probe = tlv_bus_probe,
+ .remove = tlv_bus_remove,
+};
+
+struct tlv_device *tlv_ensure_probed_by_alias(const char *alias)
+{
+ struct device_node *np;
+ struct device *dev;
+
+ np = of_find_node_by_alias(NULL, alias);
+ if (!np)
+ return ERR_PTR(-EINVAL);
+
+ of_partition_ensure_probed(np);
+
+ bus_for_each_device(&tlv_bus, dev) {
+ if (dev->parent && dev->parent->device_node == np)
+ return to_tlv_device(dev);
+ }
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
+static void tlv_bus_info(struct device *dev)
+{
+ struct driver *drv;
+
+ puts("Registered Magic:\n");
+ bus_for_each_driver(&tlv_bus, drv) {
+ struct tlv_decoder *tlvdrv = to_tlv_decoder(drv);
+
+ printf(" %08x (%s)\n", tlvdrv->magic, tlvdrv->driver.name);
+ }
+}
+
+static int tlv_bus_register(void)
+{
+ int ret;
+
+ ret = bus_register(&tlv_bus);
+ if (ret)
+ return ret;
+
+ devinfo_add(tlv_bus.dev, tlv_bus_info);
+
+ return 0;
+}
+postcore_initcall(tlv_bus_register);
diff --git a/common/tlv/drv.c b/common/tlv/drv.c
new file mode 100644
index 000000000000..baba30808683
--- /dev/null
+++ b/common/tlv/drv.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <driver.h>
+#include <tlv/tlv.h>
+#include <init.h>
+#include <of.h>
+#include <of_device.h>
+#include <stdio.h>
+
+static int barebox_tlv_probe(struct device *dev)
+{
+ const struct of_device_id *match;
+ struct tlv_device *tlvdev;
+ struct cdev *cdev;
+ char *backend_path;
+
+ match = of_match_device(dev->driver->of_match_table, dev);
+ /*
+ * We only match with the device if this driver is the most specific match
+ * because we don't want to incorrectly bind to a device that has a more
+ * specific driver.
+ */
+ if (match && of_property_match_string(dev->of_node, "compatible",
+ match->compatible) != 0)
+ return -ENODEV;
+
+ cdev = cdev_by_device_node(dev->of_node);
+ if (!cdev)
+ return -EINVAL;
+
+ backend_path = basprintf("/dev/%s", cdev_name(cdev));
+
+ tlvdev = tlv_register_device_by_path(backend_path, dev);
+ free(backend_path);
+
+ return PTR_ERR_OR_ZERO(tlvdev);
+}
+
+static const __maybe_unused struct of_device_id tlv_ids[] = {
+ { .compatible = "barebox,tlv" },
+ { /* sentinel */ }
+};
+
+static struct driver barebox_tlv_driver = {
+ .name = "tlv",
+ .probe = barebox_tlv_probe,
+ .of_compatible = tlv_ids,
+};
+core_platform_driver(barebox_tlv_driver);
diff --git a/common/tlv/parser.c b/common/tlv/parser.c
new file mode 100644
index 000000000000..468eeafceda1
--- /dev/null
+++ b/common/tlv/parser.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "barebox-tlv: " fmt
+
+#include <common.h>
+#include <tlv/tlv.h>
+#include <fcntl.h>
+#include <libfile.h>
+#include <linux/stat.h>
+#include <crc.h>
+#include <net.h>
+
+int tlv_parse(struct tlv_device *tlvdev,
+ const struct tlv_decoder *decoder)
+{
+ const struct tlv *tlv = NULL;
+ struct tlv_mapping *map = NULL;
+ struct tlv_header *header = tlv_device_header(tlvdev);
+ u32 magic, size;
+ int ret = 0;
+ u32 crc = ~0;
+
+
+ magic = be32_to_cpu(header->magic);
+
+ size = tlv_total_len(header);
+
+ crc = crc32_be(crc, header, size - 4);
+ if (crc != tlv_crc(header)) {
+ pr_warn("Invalid CRC32. Should be %08x\n", crc);
+ return -EILSEQ;
+ }
+
+ for_each_tlv(header, tlv) {
+ struct tlv_mapping **mappings;
+ u16 tag = TLV_TAG(tlv);
+ int len = TLV_LEN(tlv);
+ const void *val = TLV_VAL(tlv);
+
+ pr_debug("[%04x] %*ph\n", tag, len, val);
+
+ for (mappings = decoder->mappings; *mappings; mappings++) {
+ for (map = *mappings; map->tag; map++) {
+ if (map->tag == tag)
+ goto done;
+ }
+ }
+
+done:
+ if (!map || !map->tag) {
+ if (tag)
+ pr_warn("skipping unknown tag: %04x\n", tag);
+ continue;
+ }
+
+ ret = map->handle(tlvdev, map, len, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return PTR_ERR_OR_ZERO(tlv);
+}
+
+struct tlv_device *tlv_register_device_by_path(const char *path, struct device *parent)
+{
+ struct tlv_header *header;
+ struct tlv_device *tlvdev;
+ size_t size;
+
+ header = tlv_read(path, &size);
+ if (IS_ERR(header))
+ return ERR_CAST(header);
+
+ tlvdev = tlv_register_device(header, parent);
+ if (IS_ERR(tlvdev))
+ free(header);
+
+ return tlvdev;
+}
+
+int of_tlv_fixup(struct device_node *root, void *ctx)
+{
+ struct device_node *chosen, *conf, *ethaddrs;
+ struct eth_ethaddr *addr;
+
+ chosen = of_create_node(root, "/chosen");
+ if (!chosen)
+ return -ENOMEM;
+
+ conf = of_copy_node(chosen, ctx);
+
+ ethaddrs = of_get_child_by_name(conf, "ethernet-address");
+ if (!ethaddrs)
+ return 0;
+
+ list_for_each_entry(addr, ðaddr_list, list) {
+ char propname[sizeof("address-4294967295")];
+ const u8 *enetaddr_a;
+ u8 enetaddr_b[ETH_ALEN];
+ struct property *pp;
+
+ if (!eth_of_get_fixup_node(root, NULL, addr->ethid))
+ continue;
+
+ snprintf(propname, sizeof(propname), "address-%u", addr->ethid);
+ pp = of_find_property(ethaddrs, propname, NULL);
+ if (!pp)
+ continue;
+
+ enetaddr_a = of_property_get_value(pp);
+ if (string_to_ethaddr(addr->ethaddr, enetaddr_b))
+ continue;
+
+ if (memcmp(enetaddr_a, enetaddr_b, ETH_ALEN))
+ continue;
+
+ of_delete_property(pp);
+ }
+
+ return 0;
+}
+
+int tlv_of_register_fixup(struct tlv_device *tlvdev)
+{
+ return of_register_fixup(of_tlv_fixup, tlv_of_node(tlvdev));
+}
+
+void tlv_of_unregister_fixup(struct tlv_device *tlvdev)
+{
+ of_unregister_fixup(of_tlv_fixup, tlv_of_node(tlvdev));
+}
+
+struct tlv_header *tlv_read(const char *filename, size_t *nread)
+{
+ struct tlv_header *header = NULL, *tmpheader;
+ int size, fd, ret;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return ERR_PTR(fd);
+
+ header = malloc(128);
+ if (!header) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = read_full(fd, header, sizeof(*header));
+ if (ret >= 0 && ret != sizeof(*header))
+ ret = -ENODATA;
+ if (ret < 0)
+ goto err;
+
+ size = tlv_total_len(header);
+
+ tmpheader = realloc(header, size);
+ if (!tmpheader) {
+ struct stat st;
+
+ ret = fstat(fd, &st);
+ if (ret)
+ ret = -EIO;
+ else if (size > st.st_size)
+ ret = -ENODATA;
+ else
+ ret = -ENOMEM;
+ goto err;
+ }
+ header = tmpheader;
+
+ ret = read_full(fd, header->tlvs, size - sizeof(*header));
+ if (ret < 0)
+ goto err;
+
+ /* file might have been truncated, but this will be handled
+ * in tlv_parse
+ */
+
+ if (nread)
+ *nread = sizeof(*header) + ret;
+
+ close(fd);
+ return header;
+err:
+ free(header);
+ close(fd);
+ return ERR_PTR(ret);
+}
+
+static struct tlv *__tlv_next(const struct tlv *tlv)
+{
+ return (void *)tlv + 4 + TLV_LEN(tlv);
+}
+
+struct tlv *tlv_next(const struct tlv_header *header,
+ const struct tlv *tlv)
+{
+ void *tlvs_start = (void *)&header->tlvs[0], *tlvs_end, *next_tlv;
+
+ tlv = tlv ? __tlv_next(tlv) : tlvs_start;
+
+ tlvs_end = tlvs_start + get_unaligned_be32(&header->length_tlv);
+ if (tlv == tlvs_end)
+ return NULL;
+
+ next_tlv = __tlv_next(tlv);
+ if (next_tlv > tlvs_end)
+ return ERR_PTR(-ENODATA);
+
+ return (void *)tlv;
+}
diff --git a/common/tlv/register.c b/common/tlv/register.c
new file mode 100644
index 000000000000..a6d95fb8e091
--- /dev/null
+++ b/common/tlv/register.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Ahmad Fatoum <a.fatoum@pengutronix.de>
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <linux/nvmem-consumer.h>
+#include <of.h>
+#include <tlv/tlv.h>
+#include <libfile.h>
+#include <fcntl.h>
+
+#include <linux/err.h>
+
+static int tlv_probe_from_magic(struct device *dev)
+{
+ struct tlv_device *tlvdev = to_tlv_device(dev);
+ int ret;
+
+ ret = tlv_parse(tlvdev, to_tlv_decoder(dev->driver));
+ if (ret)
+ return ret;
+
+ return tlv_of_register_fixup(tlvdev);
+}
+
+static int tlv_probe_from_compatible(struct device *dev)
+{
+ struct tlv_decoder *decoder = to_tlv_decoder(dev->driver);
+ struct tlv_header *header;
+ struct tlv_device *tlvdev;
+ struct cdev *cdev;
+ char *backend_path;
+ size_t size;
+ u32 magic;
+ int ret;
+
+ cdev = cdev_by_device_node(dev->of_node);
+ if (!cdev)
+ return -EINVAL;
+
+ backend_path = basprintf("/dev/%s", cdev_name(cdev));
+
+ header = tlv_read(backend_path, &size);
+ free(backend_path);
+
+ if (IS_ERR(header))
+ return PTR_ERR(header);
+
+ magic = be32_to_cpu(header->magic);
+ if (magic != decoder->magic) {
+ dev_err(dev, "got magic %08x, but %08x expected\n",
+ magic, decoder->magic);
+ ret = -EILSEQ;
+ goto err;
+ }
+
+ tlvdev = tlv_register_device(header, dev);
+ if (IS_ERR(tlvdev)) {
+ ret = PTR_ERR(tlvdev);
+ goto err;
+ }
+
+ return 0;
+err:
+ free(header);
+ return ret;
+}
+
+int tlv_register_decoder(struct tlv_decoder *decoder)
+{
+ int ret;
+
+ if (decoder->driver.bus)
+ return -EBUSY;
+
+ if (!decoder->driver.probe)
+ decoder->driver.probe = tlv_probe_from_magic;
+ decoder->driver.bus = &tlv_bus;
+
+ ret = register_driver(&decoder->driver);
+ if (ret)
+ return ret;
+
+ if (!decoder->driver.of_compatible)
+ return 0;
+
+ decoder->_platform_driver.name = basprintf("%s-pltfm", decoder->driver.name);
+ decoder->_platform_driver.of_compatible = decoder->driver.of_compatible;
+ decoder->_platform_driver.probe = tlv_probe_from_compatible;
+
+ return platform_driver_register(&decoder->_platform_driver);
+}
diff --git a/include/string.h b/include/string.h
index 986ccd83dd73..c16529c21273 100644
--- a/include/string.h
+++ b/include/string.h
@@ -45,4 +45,9 @@ static inline const char *nonempty(const char *s)
return isempty(s) ? NULL : s;
}
+static inline bool is_nul_terminated(const char *val, size_t len)
+{
+ return strnlen(val, len) != len;
+}
+
#endif /* __STRING_H */
diff --git a/include/tlv/format.h b/include/tlv/format.h
new file mode 100644
index 000000000000..a32ec917a434
--- /dev/null
+++ b/include/tlv/format.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+ *
+ * barebox TLVs are preceded by a 12 byte header: 4 bytes for magic,
+ * 4 bytes for TLV sequence length (in bytes) and 4 bytes for
+ * the length of the signature. Each tag consists of at least four
+ * bytes: 2 bytes for the tag and two bytes for the length (in bytes)
+ * and as many bytes as the length. The TLV sequence must be equal
+ * to the TLV sequence length in the header. It can be followed by a
+ * signature of the length described in the header. At the end
+ * is a (big-endian) CRC-32/MPEG-2 of the whole structure. All
+ * integers are in big-endian. Tags and magic have their MSB set
+ * if they are vendor-specific. The second MSB is 0 for tag
+ * and 1 for magic.
+ */
+
+#ifndef __TLV_FORMAT_H_
+#define __TLV_FORMAT_H_
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <asm/unaligned.h>
+#include <linux/build_bug.h>
+
+#define TLV_MAGIC_BAREBOX_V1 0x61bb95f2
+
+#define TLV_IS_VENDOR_SPECIFIC(val) ((*(u8 *)&(val) & 0x80) == 0x80)
+#define TLV_IS_GENERIC(val) ((*(u8 *)&(val) & 0x80) != 0x80)
+
+struct tlv {
+ /*
+ * _tag:15 (MSB): product specific
+ */
+ __be16 _tag;
+ __be16 _len; /* in bytes */
+ u8 _payload[];
+} __packed;
+
+
+#define TLV_TAG(tlv) get_unaligned_be16(&(tlv)->_tag)
+#define TLV_LEN(tlv) get_unaligned_be16(&(tlv)->_len)
+#define TLV_VAL(tlv) ((tlv)->_payload)
+
+struct tlv_header {
+ __be32 magic;
+ __be32 length_tlv; /* in bytes */
+ __be32 length_sig; /* in bytes */
+ struct tlv tlvs[];
+};
+static_assert(sizeof(struct tlv_header) == 3 * 4);
+
+#define for_each_tlv(tlv_head, tlv) \
+ for (tlv = tlv_next(tlv_head, NULL); !IS_ERR_OR_NULL(tlv); tlv = tlv_next(tlv_head, tlv))
+
+static inline size_t tlv_total_len(const struct tlv_header *header)
+{
+ return sizeof(struct tlv_header) + get_unaligned_be32(&header->length_tlv)
+ + get_unaligned_be32(&header->length_sig) + 4;
+}
+
+/*
+ * Retrieves the CRC-32/MPEG-2 CRC32 at the end of a TLV blob. Parameters:
+ * Poly=0x04C11DB7, Init=0xFFFFFFFF, RefIn=RefOut=false, XorOut=0x00000000
+ */
+static inline u32 tlv_crc(const struct tlv_header *header)
+{
+ return get_unaligned_be32((void *)header + tlv_total_len(header) - sizeof(__be32));
+}
+
+#endif
diff --git a/include/tlv/tlv.h b/include/tlv/tlv.h
new file mode 100644
index 000000000000..b1635fdbf500
--- /dev/null
+++ b/include/tlv/tlv.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __TLV_H_
+#define __TLV_H_
+
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <asm/unaligned.h>
+#include <unistd.h>
+#include <driver.h>
+
+#include <tlv/format.h>
+
+struct tlv *tlv_next(const struct tlv_header *header, const struct tlv *tlv);
+
+struct tlv_header *tlv_read(const char *filename, size_t *nread);
+
+struct device_node;
+struct tlv_device;
+struct tlv_mapping;
+
+struct tlv_mapping {
+ u16 tag;
+ int (*handle)(struct tlv_device *dev, struct tlv_mapping *map,
+ u16 len, const u8 *val);
+ const char *prop;
+};
+
+extern struct tlv_mapping barebox_tlv_v1_mappings[];
+
+extern const char *__tlv_format_str(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_format_str(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_format_dec(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_format_hex(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_format_mac(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_format_blob(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_handle_serial(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_handle_eth_address(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+extern int tlv_handle_eth_address_seq(struct tlv_device *dev, struct tlv_mapping *map, u16 len, const u8 *val);
+
+struct tlv_decoder {
+ u32 magic;
+ void *driverata;
+ struct tlv_mapping **mappings;
+ struct driver driver;
+ /* private members */
+ struct driver _platform_driver;
+ struct list_head list;
+};
+
+struct tlv_device {
+ struct device dev;
+ u32 magic;
+};
+
+static inline struct device_node *tlv_of_node(struct tlv_device *tlvdev)
+{
+ return tlvdev->dev.device_node;
+}
+
+struct tlv_device *tlv_register_device(struct tlv_header *header, struct device *parent);
+static inline struct tlv_header *tlv_device_header(struct tlv_device *tlvdev)
+{
+ return tlvdev->dev.platform_data;
+}
+
+void tlv_free_device(struct tlv_device *tlvdev);
+
+int tlv_register_decoder(struct tlv_decoder *decoder);
+
+int tlv_parse(struct tlv_device *tlvdev,
+ const struct tlv_decoder *decoder);
+
+int of_tlv_fixup(struct device_node *root, void *ctx);
+
+int tlv_of_register_fixup(struct tlv_device *tlvdev);
+void tlv_of_unregister_fixup(struct tlv_device *tlvdev);
+
+extern struct bus_type tlv_bus;
+
+static inline struct tlv_decoder *to_tlv_decoder(struct driver *drv)
+{
+ if (drv->bus == &tlv_bus)
+ return container_of(drv, struct tlv_decoder, driver);
+ if (drv->bus == &platform_bus)
+ return container_of(drv, struct tlv_decoder, _platform_driver);
+ return NULL;
+}
+
+static inline struct tlv_device *to_tlv_device(struct device *dev)
+{
+ return container_of(dev, struct tlv_device, dev);
+}
+
+struct tlv_device *tlv_register_device_by_path(const char *path, struct device *parent);
+struct tlv_device *tlv_ensure_probed_by_alias(const char *alias);
+
+#endif
diff --git a/test/self/Kconfig b/test/self/Kconfig
index 33e478aee882..33d05e4cf205 100644
--- a/test/self/Kconfig
+++ b/test/self/Kconfig
@@ -45,6 +45,7 @@ config SELFTEST_ENABLE_ALL
select SELFTEST_REGULATOR if REGULATOR_FIXED
select SELFTEST_TEST_COMMAND if CMD_TEST
select SELFTEST_IDR
+ select SELFTEST_TLV
help
Selects all self-tests compatible with current configuration
@@ -117,4 +118,10 @@ config SELFTEST_IDR
bool "idr selftest"
select IDR
+config SELFTEST_TLV
+ bool "TLV selftest"
+ select TLV
+ select BASE64
+ select BOARD_LXA
+
endif
diff --git a/test/self/Makefile b/test/self/Makefile
index 666a8f9ee7e2..8cd2a4c526c8 100644
--- a/test/self/Makefile
+++ b/test/self/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_SELFTEST_SETJMP) += setjmp.o
obj-$(CONFIG_SELFTEST_REGULATOR) += regulator.o test_regulator.dtbo.o
obj-$(CONFIG_SELFTEST_TEST_COMMAND) += test_command.o
obj-$(CONFIG_SELFTEST_IDR) += idr.o
+obj-$(CONFIG_SELFTEST_TLV) += tlv.o tlv.dtb.o
ifdef REGENERATE_KEYTOC
diff --git a/test/self/tlv.c b/test/self/tlv.c
new file mode 100644
index 000000000000..c22335f264c5
--- /dev/null
+++ b/test/self/tlv.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <common.h>
+#include <crypto/rsa.h>
+#include <bselftest.h>
+#include <base64.h>
+#include <console.h>
+#include <tlv/tlv.h>
+
+BSELFTEST_GLOBALS();
+
+static const char cpu_tlv_encoded[] =
+"vCiN/gAAAH4AAAAAAAIAGGd1dGVmZWUyLUQwMS1SMDEtVjAxLUM/PwADAAgAAAAAZ+"
+"Z4XgAEAAswMDA0MC4wMDAxMAAFAAEAAAYACGJhc2UsUG9FAAcACzAwMDM2LjAwMDEwA"
+"AgAGGd1dGVmZWUyLVMwMS1SMDEtVjAxLUM/PwASAAcEGHTioAPA6zhwRw==";
+
+static const char io_tlv_encoded[] =
+"3KWocAAAAEQAAAAAAAMACAAAAABn5nheAAUAAQAABgAEYmFzZQAHAAswMDAzNy4wMDA"
+"xMAAIABhndXRlZmVlMi1TMDItUjAxLVYwMS1DPz+xJ7bM";
+
+static u8 *base64_decode_alloc(const char *encoded, size_t *len)
+{
+ size_t enclen = strlen(encoded);
+ u8 *buf = xmalloc(enclen);
+ *len = decode_base64(buf, enclen, encoded);
+ return buf;
+}
+
+static void assert_equal(struct device_node *a, struct device_node *b)
+{
+ int ret;
+
+ ret = of_diff(a, b, -1);
+ if (ret == 0)
+ return;
+
+ pr_warn("comparison of %s and %s failed: no differences expected, %u found.\n",
+ a->full_name, b->full_name, ret);
+ of_diff(a, b, 1);
+ failed_tests++;
+}
+
+static void test_lxa_tlv(void)
+{
+ extern char __dtb_tlv_start[], __dtb_tlv_end[];
+ struct tlv_device *cpu_tlvdev, *io_tlvdev;
+ size_t cpu_bloblen, io_bloblen;
+ void *cpu_blob = base64_decode_alloc(cpu_tlv_encoded, &cpu_bloblen);
+ void *io_blob = base64_decode_alloc(io_tlv_encoded, &io_bloblen);
+ struct device_node *expected, *np;
+
+ total_tests = 4;
+
+ expected = of_unflatten_dtb(__dtb_tlv_start,
+ __dtb_tlv_end - __dtb_tlv_start);
+ if (WARN_ON(IS_ERR(expected))) {
+ skipped_tests = total_tests;
+ return;
+ }
+
+ cpu_tlvdev = tlv_register_device(cpu_blob, NULL);
+ if (IS_ERR(cpu_tlvdev)) {
+ free(cpu_blob);
+ failed_tests++;
+ skipped_tests++;
+ }
+
+ io_tlvdev = tlv_register_device(io_blob, NULL);
+ if (IS_ERR(io_tlvdev)) {
+ free(io_blob);
+ failed_tests++;
+ skipped_tests++;
+ }
+
+ for_each_child_of_node(expected, np) {
+ if (!IS_ERR(cpu_tlvdev) && !strcmp(np->full_name, "/tlv0"))
+ assert_equal(tlv_of_node(cpu_tlvdev), np);
+ if (!IS_ERR(io_tlvdev) && !strcmp(np->full_name, "/tlv1"))
+ assert_equal(tlv_of_node(io_tlvdev), np);
+ }
+
+ if (!IS_ERR(cpu_tlvdev))
+ tlv_free_device(cpu_tlvdev);
+ if (!IS_ERR(io_tlvdev))
+ tlv_free_device(io_tlvdev);
+}
+bselftest(parser, test_lxa_tlv);
diff --git a/test/self/tlv.dts b/test/self/tlv.dts
new file mode 100644
index 000000000000..40906279bafd
--- /dev/null
+++ b/test/self/tlv.dts
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/dts-v1/;
+/ {
+ tlv0 {
+ device-hardware-release = "gutefee2-D01-R01-V01-C??";
+ factory-timestamp = "1743157342";
+ serial-number = "00040.00010";
+ modification = "0";
+ featureset = "base,PoE";
+ pcba-serial-number = "00036.00010";
+ pcba-hardware-release = "gutefee2-S01-R01-V01-C??";
+ ethernet-address {
+ address-0 = "18:74:e2:a0:03:c0";
+ address-1 = "18:74:e2:a0:03:c1";
+ address-2 = "18:74:e2:a0:03:c2";
+ address-3 = "18:74:e2:a0:03:c3";
+ };
+ };
+
+ tlv1 {
+ factory-timestamp = "1743157342";
+ modification = "0";
+ featureset = "base";
+ pcba-serial-number = "00037.00010";
+ pcba-hardware-release = "gutefee2-S02-R01-V01-C??";
+ };
+};
--
2.39.5
next prev parent reply other threads:[~2025-04-11 7:44 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-11 7:40 [PATCH 00/10] Add barebox TLV infrastructure Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 01/10] net: factor out eth_of_get_fixup_node Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 02/10] net: export list of registered ethernet addresses Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 03/10] common: add optional systemd.hostname generation Ahmad Fatoum
2025-04-11 7:40 ` Ahmad Fatoum [this message]
2025-04-14 14:49 ` [PATCH 04/10] common: add barebox TLV support Sascha Hauer
2025-04-14 14:57 ` Ahmad Fatoum
2025-04-14 15:06 ` Sascha Hauer
2025-04-11 7:40 ` [PATCH 05/10] commands: add TLV debugging command Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 06/10] scripts: add bareboxtlv host/target tool Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 07/10] boards: add decoder for LXA TLV v1 format Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 08/10] scripts: Add Barebox TLV Generator Tooling Ahmad Fatoum
2025-04-14 15:00 ` Sascha Hauer
2025-04-11 7:40 ` [PATCH 09/10] doc: Add User-Documentation for Barebox TLV Ahmad Fatoum
2025-04-11 7:40 ` [PATCH 10/10] ARM: stm32mp: lxa: enable TLV support for TAC & FairyTux2 Ahmad Fatoum
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=20250411074045.2019372-5-a.fatoum@pengutronix.de \
--to=a.fatoum@pengutronix.de \
--cc=barebox@lists.infradead.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