From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@pengutronix.de>
Subject: [PATCH 08/10] scripts: Add Barebox TLV Generator Tooling
Date: Fri, 11 Apr 2025 09:40:43 +0200 [thread overview]
Message-ID: <20250411074045.2019372-9-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20250411074045.2019372-1-a.fatoum@pengutronix.de>
From: Chris Fiege <cfi@pengutronix.de>
This host-tooling creates binaries in the Barebox TLV format.
It is intended to be used in the factory (either as script or library)
and creates a tlv binary from a given schema and data.
For convenience an example schema and dataset are provided.
Signed-off-by: Chris Fiege <cfi@pengutronix.de>
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
.../bareboxtlv-generator.py | 321 ++++++++++++++++++
.../bareboxtlv-generator/data-example.yaml | 12 +
scripts/bareboxtlv-generator/requirements.txt | 2 +
.../bareboxtlv-generator/schema-example.yaml | 48 +++
4 files changed, 383 insertions(+)
create mode 100755 scripts/bareboxtlv-generator/bareboxtlv-generator.py
create mode 100644 scripts/bareboxtlv-generator/data-example.yaml
create mode 100644 scripts/bareboxtlv-generator/requirements.txt
create mode 100644 scripts/bareboxtlv-generator/schema-example.yaml
diff --git a/scripts/bareboxtlv-generator/bareboxtlv-generator.py b/scripts/bareboxtlv-generator/bareboxtlv-generator.py
new file mode 100755
index 000000000000..5f9285a80630
--- /dev/null
+++ b/scripts/bareboxtlv-generator/bareboxtlv-generator.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python3
+
+import argparse
+import struct
+
+import yaml
+from crcmod.predefined import mkPredefinedCrcFun
+
+_crc32_mpeg = mkPredefinedCrcFun("crc-32-mpeg")
+
+
+class MaxSizeReachedException(Exception):
+ pass
+
+
+class FactoryDataset:
+ """
+ Generates TLV-encoded datasets that can be used with Barebox's
+ TLV support.
+
+ The generated binary structure looks like this:
+ #############################################################
+ # struct tlv { #
+ # be16 tag; /* 2 bytes */ #
+ # be16 len; /* 2 bytes */ #
+ # u8 payload[]; #
+ # }; #
+ # #
+ # struct binfile { #
+ # be32 magic_version; #
+ # be32 length_tlv; /* 4 bytes */ #
+ # be32 length_sig; /* 4 bytes */ #
+ # struct tlv tlvs[]; #
+ # be32 crc32; #
+ # }; #
+ #############################################################
+
+ Limitations:
+ * Signing is currently not supported and length_sig is always 0x0.
+ """
+
+ def __init__(self, schema):
+ """
+ Create a new Factory Dataset Object.
+
+ A ``schema`` is the python-world representation of the Barebox
+ ``struct tlv_mapping`` lists.
+ It describes the tags that can be used and define which
+ formatter must be used when (de-) serializing data.
+
+ Example schema:
+ | magic: 0x0fe01fca
+ | max_size: 0x100
+ |
+ | tags:
+ | factory-timestamp:
+ | tag: 0x0003
+ | format: "decimal"
+ | length: 8
+ | (...)
+
+ With:
+ * magic: The magic header for the TLV-encoded data.
+ * max_size: Maximum length of the TLV-encoded data in bytes.
+ If set the generated binary is tested to be less or equal this size after generation.
+ * tags: dict of tags that can be used for TLV-encoding.
+ * <key>: The human-readable name of this field.
+ * tag: (Mandatory) Tag-ID for use during TLV-encoding
+ * format: (Mandatory) Specifies the formatter to use
+ * length: (Mandatory for some formats) length of the field
+
+ Some remarks:
+ * Additional keys in a tag will be ignored. This can be used to add examples or annotations
+ to an entry.
+ * Tag-IDs can be used in multiple entries inside a schema with different human-readable names.
+ This only works reasonable if the Barebox parser knows how to handle it.
+ This will work well during encoding. But: It will need the same special knowledge for decoding.
+ Otherwise, only the last occurrence will be kept.
+ * The EEPROM will be written in the order of entries in the input data.
+
+ Supported Barebox TLV data formats:
+ * string:
+ - Arbitrary length string (up to 2^16-1 bytes)
+ - UTF-8
+ - Schema example:
+ | device-hardware-release:
+ | tag: 0x0002
+ | format: "string"
+ * decimal:
+ - unsigned int
+ - length is required and must be in [1, 2, 4, 8]
+ - Schema example:
+ | modification:
+ | tag: 0x0005
+ | format: decimal
+ | length: 1
+ * mac-sequence:
+ - Describes a consecutive number of MAC addresses
+ - Contains a starting address and a count
+ - Input data must be an iterable in the following format: (first_mac: int, count: int)
+ - MAC-addresses are represented as python ints
+ - Schema example:
+ | ethernet-address:
+ | tag: 0x0012
+ | format: "mac-sequence"
+ * mac-list:
+ - A list of MAC addresses
+ - Input data must be an iterable of MAC addresses: (first_mac: int, second_mac: int, ...)
+ - MAC-addresses are represented as python ints
+ - Schema example:
+ | ethernet-address:
+ | tag: 0x0012
+ | format: "mac-list"
+ * linear-calibration
+ - Linear calibration data for analog channels
+ - Input data must be an iterable of floats: (c1: float, c2: float, ...)
+ - Values are stored as a 4-byte float
+ - Length-field is mandatory.
+ - Schema example:
+ | usb-host1-curr:
+ | tag: 0x8001
+ | format: linear-calibration
+ | length: 2
+ """
+ self.schema = schema
+
+ def encode(self, data):
+ """
+ Generate an EEPROM image for the given data.
+
+ Data must be a dict using fields described in the schema.
+ """
+ # build payload of TLVs
+ tlvs = b""
+
+ for name, value in data.items():
+ if name not in self.schema["tags"]:
+ raise KeyError(f"No Schema defined for data with name '{name}'")
+
+ tag = self.schema["tags"][name]
+ tag_format = tag["format"]
+
+ if tag_format == "string":
+ bin = value.encode("UTF-8")
+ fmt = f"{len(bin)}s"
+ if len(bin) > 2**16 - 1:
+ raise ValueError(f"String {name} is too long!")
+
+ elif tag_format == "decimal":
+ fmtl = tag["length"]
+ if fmtl == 1:
+ fmt = "B"
+ elif fmtl == 2:
+ fmt = "H"
+ elif fmtl == 4:
+ fmt = "I"
+ elif fmtl == 8:
+ fmt = "Q"
+ else:
+ raise ValueError(f"Decimal {name} has invalid len {fmtl}. Must be in [1, 2, 4, 8]!")
+ bin = abs(int(value))
+
+ elif tag_format == "mac-sequence":
+ if len(value) != 2:
+ raise ValueError(f"mac-sequence {name} must be in format (base_mac: int, count: int)")
+ fmt = "7s"
+ mac = struct.pack(">Q", value[0])[2:]
+ bin = struct.pack(">B6s", value[1], mac)
+
+ elif tag_format == "mac-list":
+ bin = b""
+ for mac in value:
+ bin += struct.pack(">Q", mac)[2:]
+ fmt = f"{len(value) * 6}s"
+
+ elif tag_format == "calibration":
+ bin = b""
+ if len(value) != tag["length"]:
+ raise ValueError(f'linear-calibration {name} must have {tag["length"]} elements.')
+ for coeff in value:
+ bin += struct.pack(">f", coeff)
+ fmt = f"{len(value) * 4}s"
+
+ else:
+ raise Exception(f"{name}: Unknown format {tag_format}")
+
+ tlvs += struct.pack(f">HH{fmt}", tag["tag"], struct.calcsize(fmt), bin)
+
+ # Convert the framing data.
+ len_sig = 0x0 # Not implemented.
+ header = struct.pack(">3I", self.schema["magic"], len(tlvs), len_sig)
+ crc_raw = _crc32_mpeg(header + tlvs)
+ crc = struct.pack(">I", crc_raw)
+ bin = header + tlvs + crc
+
+ # Check length
+ if "max_size" in self.schema and len(bin) > self.schema["max_size"]:
+ raise MaxSizeReachedException(
+ f'Generated binary too large. Is {len(bin)} bytes but max_size is {self.schema["max_size"]}.'
+ )
+ return bin
+
+ def decode(self, bin):
+ """
+ Decode a TLV-encoded binary image.
+
+ Returns a dict with entries decoded from the binary.
+ """
+ # check minimal length
+ values = {}
+
+ if len(bin) < 16:
+ raise ValueError("Supplied binary is too small to be TLV-encoded data.")
+
+ bin_magic, bin_tlv_len, bin_sig_len = struct.unpack(">3I", bin[:12])
+ # check magic
+ if bin_magic != self.schema["magic"]:
+ raise ValueError(f'Magic missmatch. Is {hex(bin_magic)} but expected {hex(self.schema["magic"])}')
+
+ # check signature length
+ if bin_sig_len != 0:
+ raise ValueError("Signature check is not supported!")
+
+ # check crc
+ crc_offset = 12 + bin_tlv_len + bin_sig_len
+ if crc_offset > len(bin) - 4:
+ raise ValueError("crc location calculated behind binary.")
+ bin_crc = struct.unpack(">I", bin[crc_offset : crc_offset + 4])[0] # noqa E203
+ calc_crc = _crc32_mpeg(bin[:crc_offset])
+ if bin_crc != calc_crc:
+ raise ValueError(f"CRC missmatch. Is {hex(bin_crc)} but expected {hex(calc_crc)}.")
+
+ ptr = 12
+ while ptr < crc_offset:
+ tag_id, tag_len = struct.unpack_from(">HH", bin, ptr)
+ data_ptr = ptr + 4
+ ptr += tag_len + 4
+
+ name, tag_schema = self._get_tag_by_id(tag_id)
+ if not name:
+ print(f"Skipping unknown tag-id {hex(tag_id)}.")
+ continue
+
+ if tag_schema["format"] == "decimal":
+ if tag_len == 1:
+ fmt = ">B"
+ elif tag_len == 2:
+ fmt = ">H"
+ elif tag_len == 4:
+ fmt = ">I"
+ elif tag_len == 8:
+ fmt = ">Q"
+ else:
+ raise ValueError(f"Decimal {name} has invalid len {tag_len}. Must be in [1, 2, 4, 8]!")
+ value = struct.unpack_from(fmt, bin, data_ptr)[0]
+ elif tag_schema["format"] == "string":
+ value = bin[data_ptr : data_ptr + tag_len].decode("UTF-8") # noqa E203
+ elif tag_schema["format"] == "mac-sequence":
+ if tag_len != 7:
+ raise ValueError(f"Tag {name} has wrong length {hex(tag_len)} but expected 0x7.")
+ count, base_mac = struct.unpack_from(">B6s", bin, data_ptr)
+ base_mac = struct.unpack(">Q", b"\x00\x00" + base_mac)[0]
+ value = [base_mac, count]
+ elif tag_schema["format"] == "mac-list":
+ if tag_len % 6 != 0:
+ raise ValueError(f"Tag {name} has wrong length {hex(tag_id)}. Must be multiple of 0x6.")
+ value = []
+ for i in range(int(tag_len / 6)):
+ mac = struct.unpack_from(">6s", bin, data_ptr + int(i * 6))[0]
+ mac = struct.unpack(">Q", b"\x00\x00" + mac)[0]
+ value.append(mac)
+ elif tag_schema["format"] == "calibration":
+ if tag_len % 4 != 0:
+ raise ValueError(f"Tag {name} has wrong length {hex(tag_id)}. Must be multiple of 0x4.")
+ value = []
+ for i in range(int(tag_len / 4)):
+ coeff = struct.unpack_from(">f", bin, data_ptr + int(i * 4))[0]
+ value.append(coeff)
+ else:
+ raise Exception(f'{name}: Unknown format {tag_schema["format"]}')
+ values[name] = value
+
+ return values
+
+ def _get_tag_by_id(self, tag_id):
+ for name, tag_schema in self.schema["tags"].items():
+ if tag_schema["tag"] == tag_id:
+ return name, tag_schema
+ return None, None
+
+
+def _main():
+ parser = argparse.ArgumentParser(description="Generate a TLV dataset for the Barebox TLV parser")
+ parser.add_argument("schema", help="YAML file describing the data.")
+ parser.add_argument("--input-data", help="YAML file containing data to write to the binary.")
+ parser.add_argument("--output-data", help="YAML file where the contents of the binary will be written to.")
+ parser.add_argument("binary", help="Path to where export data to be copied into DUT's EEPROM.")
+ args = parser.parse_args()
+
+ # load schema
+ with open(args.schema) as schema_fh:
+ schema = yaml.load(schema_fh, Loader=yaml.SafeLoader)
+ eeprom = FactoryDataset(schema)
+
+ if args.input_data:
+ with open(args.input_data) as d_fh:
+ data = yaml.load(d_fh, Loader=yaml.SafeLoader)
+ bin = eeprom.encode(data)
+ with open(args.binary, "wb+") as out_fh:
+ out_fh.write(bin)
+
+ if args.output_data:
+ with open(args.binary, "rb") as in_fh:
+ bin = in_fh.read()
+ data = eeprom.decode(bin)
+ with open(args.output_data, "w+") as out_fh:
+ yaml.dump(data, out_fh)
+
+
+if __name__ == "__main__":
+ _main()
diff --git a/scripts/bareboxtlv-generator/data-example.yaml b/scripts/bareboxtlv-generator/data-example.yaml
new file mode 100644
index 000000000000..17c787546204
--- /dev/null
+++ b/scripts/bareboxtlv-generator/data-example.yaml
@@ -0,0 +1,12 @@
+factory-timestamp: 1636451762
+modification: 0
+featureset: "base"
+pcba-serial-number: "12345.67890"
+pcba-hardware-release: "device-S01-R02-V03-C04"
+pwr-volt:
+- 1.0
+- 0.0
+pwr-curr:
+- 1.0
+- 0.0
+
diff --git a/scripts/bareboxtlv-generator/requirements.txt b/scripts/bareboxtlv-generator/requirements.txt
new file mode 100644
index 000000000000..a1f7e3b3f2a3
--- /dev/null
+++ b/scripts/bareboxtlv-generator/requirements.txt
@@ -0,0 +1,2 @@
+crcmod
+pyaml
diff --git a/scripts/bareboxtlv-generator/schema-example.yaml b/scripts/bareboxtlv-generator/schema-example.yaml
new file mode 100644
index 000000000000..4927b869513d
--- /dev/null
+++ b/scripts/bareboxtlv-generator/schema-example.yaml
@@ -0,0 +1,48 @@
+magic: 0xc6895c21
+max_size: 0x100
+
+tags:
+ factory-timestamp:
+ tag: 0x0003
+ format: "decimal"
+ length: 8
+ example: 1636451762
+
+ modification:
+ tag: 0x0005
+ format: decimal
+ length: 1
+ example: 42
+ purpose: "0: Device unmodified; 1: Device has undocumented modifications"
+
+ featureset:
+ tag: 0x0006
+ format: "string"
+ example: "base"
+ purpose: For later use. May encode a set of features of this device.
+
+ pcba-serial-number:
+ tag: 0x0007
+ format: "string"
+ example: "12345.67890"
+ purpose: Serial number of device
+
+ pcba-hardware-release:
+ tag: 0x0008
+ format: "string"
+ example: "device-S01-R02-V03-C04"
+ purpose: Detailed release information for device
+
+ pwr-volt:
+ tag: 0x9000
+ format: calibration
+ length: 2
+ example: "(485.0032, -42.23)"
+ purpose: (Gain, Offset) tuple of floats for analog calibration of a measurement
+
+ pwr-curr:
+ tag: 0x9001
+ format: calibration
+ length: 2
+ example: "(485.0032, -42.23)"
+ purpose: (Gain, Offset) tuple of floats for analog calibration of a measurement
--
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 ` [PATCH 04/10] common: add barebox TLV support Ahmad Fatoum
2025-04-14 14:49 ` 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 ` Ahmad Fatoum [this message]
2025-04-14 15:00 ` [PATCH 08/10] scripts: Add Barebox TLV Generator Tooling 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-9-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