All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v8 1/6] dt-bindings: ethernet: eswin: relax internal delay model to range-based constraints
From: lizhi2 @ 2026-06-10  1:28 UTC (permalink / raw)
  To: devicetree, andrew+netdev, davem, edumazet, kuba, robh, krzk+dt,
	conor+dt, netdev, pabeni, mcoquelin.stm32, alexandre.torgue,
	rmk+kernel, pjw, palmer, aou, alex, linux-riscv, linux-stm32,
	linux-arm-kernel, linux-kernel, maxime.chevallier
  Cc: ningyu, linmin, pinkesh.vaghela, pritesh.patel, weishangjuan,
	horms, lee, Zhi Li
In-Reply-To: <20260610012727.848-1-lizhi2@eswincomputing.com>

From: Zhi Li <lizhi2@eswincomputing.com>

Relax internal delay constraints for EIC7700 Ethernet binding.

Replace fixed enumeration of rx-internal-delay-ps and tx-internal-delay-ps
with a range-based definition (0-2540 ps, 20 ps steps) to reflect actual
hardware capability.

Mark rx/tx internal delay properties as optional, as they are board-
specific tuning parameters rather than mandatory configuration.

Update the device tree example to align with the relaxed constraint model
and remove delay properties from the example to avoid implying they are
required.

No functional change to existing DT users.

Signed-off-by: Zhi Li <lizhi2@eswincomputing.com>
---
 .../bindings/net/eswin,eic7700-eth.yaml       | 25 ++++++++++---------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
index 65882ff79d8d..4e02fedae5c6 100644
--- a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
+++ b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
@@ -63,10 +63,14 @@ properties:
       - const: stmmaceth
 
   rx-internal-delay-ps:
-    enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400]
+    minimum: 0
+    maximum: 2540
+    multipleOf: 20
 
   tx-internal-delay-ps:
-    enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400]
+    minimum: 0
+    maximum: 2540
+    multipleOf: 20
 
   eswin,hsp-sp-csr:
     description:
@@ -105,8 +109,6 @@ required:
   - phy-mode
   - resets
   - reset-names
-  - rx-internal-delay-ps
-  - tx-internal-delay-ps
   - eswin,hsp-sp-csr
 
 unevaluatedProperties: false
@@ -116,23 +118,22 @@ examples:
     ethernet@50400000 {
         compatible = "eswin,eic7700-qos-eth", "snps,dwmac-5.20";
         reg = <0x50400000 0x10000>;
-        clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>,
-                <&d0_clock 193>;
-        clock-names = "axi", "cfg", "stmmaceth", "tx";
         interrupt-parent = <&plic>;
         interrupts = <61>;
         interrupt-names = "macirq";
-        phy-mode = "rgmii-id";
-        phy-handle = <&phy0>;
+        clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>,
+                <&d0_clock 193>;
+        clock-names = "axi", "cfg", "stmmaceth", "tx";
         resets = <&reset 95>;
         reset-names = "stmmaceth";
-        rx-internal-delay-ps = <200>;
-        tx-internal-delay-ps = <200>;
         eswin,hsp-sp-csr = <&hsp_sp_csr 0x100 0x108 0x118 0x114 0x11c>;
-        snps,axi-config = <&stmmac_axi_setup>;
+        phy-handle = <&phy0>;
+        phy-mode = "rgmii-id";
         snps,aal;
         snps,fixed-burst;
         snps,tso;
+        snps,axi-config = <&stmmac_axi_setup>;
+
         stmmac_axi_setup: stmmac-axi-config {
             snps,blen = <0 0 0 0 16 8 4>;
             snps,rd_osr_lmt = <2>;
-- 
2.25.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related

* [PATCH net-next v8 1/6] dt-bindings: ethernet: eswin: relax internal delay model to range-based constraints
From: lizhi2 @ 2026-06-10  1:28 UTC (permalink / raw)
  To: devicetree, andrew+netdev, davem, edumazet, kuba, robh, krzk+dt,
	conor+dt, netdev, pabeni, mcoquelin.stm32, alexandre.torgue,
	rmk+kernel, pjw, palmer, aou, alex, linux-riscv, linux-stm32,
	linux-arm-kernel, linux-kernel, maxime.chevallier
  Cc: ningyu, linmin, pinkesh.vaghela, pritesh.patel, weishangjuan,
	horms, lee, Zhi Li
In-Reply-To: <20260610012727.848-1-lizhi2@eswincomputing.com>

From: Zhi Li <lizhi2@eswincomputing.com>

Relax internal delay constraints for EIC7700 Ethernet binding.

Replace fixed enumeration of rx-internal-delay-ps and tx-internal-delay-ps
with a range-based definition (0-2540 ps, 20 ps steps) to reflect actual
hardware capability.

Mark rx/tx internal delay properties as optional, as they are board-
specific tuning parameters rather than mandatory configuration.

Update the device tree example to align with the relaxed constraint model
and remove delay properties from the example to avoid implying they are
required.

No functional change to existing DT users.

Signed-off-by: Zhi Li <lizhi2@eswincomputing.com>
---
 .../bindings/net/eswin,eic7700-eth.yaml       | 25 ++++++++++---------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
index 65882ff79d8d..4e02fedae5c6 100644
--- a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
+++ b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml
@@ -63,10 +63,14 @@ properties:
       - const: stmmaceth
 
   rx-internal-delay-ps:
-    enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400]
+    minimum: 0
+    maximum: 2540
+    multipleOf: 20
 
   tx-internal-delay-ps:
-    enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400]
+    minimum: 0
+    maximum: 2540
+    multipleOf: 20
 
   eswin,hsp-sp-csr:
     description:
@@ -105,8 +109,6 @@ required:
   - phy-mode
   - resets
   - reset-names
-  - rx-internal-delay-ps
-  - tx-internal-delay-ps
   - eswin,hsp-sp-csr
 
 unevaluatedProperties: false
@@ -116,23 +118,22 @@ examples:
     ethernet@50400000 {
         compatible = "eswin,eic7700-qos-eth", "snps,dwmac-5.20";
         reg = <0x50400000 0x10000>;
-        clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>,
-                <&d0_clock 193>;
-        clock-names = "axi", "cfg", "stmmaceth", "tx";
         interrupt-parent = <&plic>;
         interrupts = <61>;
         interrupt-names = "macirq";
-        phy-mode = "rgmii-id";
-        phy-handle = <&phy0>;
+        clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>,
+                <&d0_clock 193>;
+        clock-names = "axi", "cfg", "stmmaceth", "tx";
         resets = <&reset 95>;
         reset-names = "stmmaceth";
-        rx-internal-delay-ps = <200>;
-        tx-internal-delay-ps = <200>;
         eswin,hsp-sp-csr = <&hsp_sp_csr 0x100 0x108 0x118 0x114 0x11c>;
-        snps,axi-config = <&stmmac_axi_setup>;
+        phy-handle = <&phy0>;
+        phy-mode = "rgmii-id";
         snps,aal;
         snps,fixed-burst;
         snps,tso;
+        snps,axi-config = <&stmmac_axi_setup>;
+
         stmmac_axi_setup: stmmac-axi-config {
             snps,blen = <0 0 0 0 16 8 4>;
             snps,rd_osr_lmt = <2>;
-- 
2.25.1



^ permalink raw reply related

* [PATCH v3 07/10] binman: Add QCDT support
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

This vendor-specific format is used by many bootloaders on older qcom
SoCs, such as msm8916. It's a container for N FDTs. Each one is
contained in a record that includes metadata about the platform/variant
it targets. The previous bootloader picks the "right" record based on
this metadata.

This initial impl targets a streamlined v2 path, with no support for
different versions or multiple qcom,msm-id/qcom,board-id tuples. If/when
that's needed it will be implemented in a follow-up.

In the following commit, support for DTBH will also be introduced.
Because QCDT and DTBH share a lot of common behaviour, this commit also
introduces a Entry_Android_vendor_dt_table base class.

This impl was based on the lk2nd/CAF LK dtbTool script.

Link: https://github.com/msm8916-mainline/lk2nd/blob/main/lk2nd/scripts/dtbTool
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/android_vendor_dt_table.py            | 104 +++++++++++++++++++++
 tools/binman/etype/android_boot.py                 |  31 ++++++
 tools/binman/etype/qcdt.py                         |  80 ++++++++++++++++
 tools/binman/ftest.py                              |  96 +++++++++++++++++++
 tools/binman/test/qcdt.dts                         |  36 +++++++
 tools/binman/test/qcdt_bad_msm_id.dts              |  17 ++++
 tools/binman/test/qcdt_invalid_pagesize.dts        |  12 +++
 tools/binman/test/qcdt_missing_msm_id.dts          |  12 +++
 tools/binman/test/qcdt_missing_payload.dts         |  14 +++
 tools/binman/test/qcdt_missing_subnodes.dts        |  13 +++
 tools/binman/test/qcdt_multiple_dtbs.dts           |  34 +++++++
 tools/binman/test/qcdt_page_size_from_abootimg.dts |  33 +++++++
 tools/binman/test/qcdt_zero_pagesize.dts           |  12 +++
 13 files changed, 494 insertions(+)

diff --git a/tools/binman/android_vendor_dt_table.py b/tools/binman/android_vendor_dt_table.py
new file mode 100644
index 00000000000..91b785f274e
--- /dev/null
+++ b/tools/binman/android_vendor_dt_table.py
@@ -0,0 +1,104 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+from binman.entry import Entry
+from binman.etype.section import Entry_section
+from dtoc import fdt_util
+
+
+class Entry_Android_vendor_dt_table(Entry_section):
+    """Base class for legacy Android vendor DT table entries"""
+
+    @staticmethod
+    def _DtbEntryName(node):
+        return '_dtb_%s' % node.name
+
+    def ReadNode(self):
+        super().ReadNode()
+        self._page_size = fdt_util.GetInt(self._node, 'page-size')
+        if (self._page_size is not None and
+                (self._page_size <= 0 or
+                 self._page_size & (self._page_size - 1))):
+            self.Raise('page-size must be a power of two')
+
+    def _GetPayloadSubnodes(self, node):
+        return [subnode for subnode in node.subnodes
+                if not self.IsSpecialSubnode(subnode)]
+
+    def ReadEntries(self):
+        for node in self._node.subnodes:
+            if self.IsSpecialSubnode(node):
+                continue
+
+            payloads = self._GetPayloadSubnodes(node)
+            if len(payloads) > 1:
+                self.Raise("subnode '%s': must contain exactly one DTB "
+                           "payload subnode" % node.name)
+            if not payloads:
+                continue
+
+            entry = Entry.Create(self, payloads[0],
+                                 expanded=self.GetImage().use_expanded,
+                                 missing_etype=self.GetImage().missing_etype)
+            entry.ReadNode()
+            entry.SetPrefix(self._name_prefix)
+            self._entries[self._DtbEntryName(node)] = entry
+
+    def _GetPageSize(self):
+        if self._page_size is not None:
+            return self._page_size
+
+        section = self.section
+        while section:
+            if section.etype == 'android-boot':
+                return section.page_size
+            section = section.section
+
+        return 2048
+
+    def _GetU32Cells(self, node, propname):
+        prop = node.props.get(propname)
+        if not prop:
+            self.Raise("subnode '%s': Missing required property '%s'" %
+                       (node.name, propname))
+
+        values = prop.value if isinstance(prop.value, list) else [prop.value]
+        return [fdt_util.fdt32_to_cpu(value) for value in values]
+
+    def _GetU32Tuple(self, node, propname, width):
+        values = self._GetU32Cells(node, propname)
+        if len(values) != width:
+            self.Raise("subnode '%s': Property '%s' must contain exactly "
+                       "%d cells" % (node.name, propname, width))
+
+        return tuple(values)
+
+    def _GetDtbData(self, node, required):
+        entry = self._entries.get(self._DtbEntryName(node))
+        if not entry:
+            self.Raise("subnode '%s': Missing required DTB payload subnode" %
+                       node.name)
+
+        data = entry.GetData(required)
+        if data is None and not required:
+            return None
+
+        return data
+
+    def _GetDtbRecordData(self, node, required):
+        return self._GetDtbData(node, required)
+
+    def _ReadDtbRecords(self, required, read_record):
+        records = []
+        for node in self._node.subnodes:
+            if self.IsSpecialSubnode(node):
+                continue
+
+            data = self._GetDtbRecordData(node, required)
+            if data is None and not required:
+                return None
+            records.append(read_record(node, data))
+
+        if not records:
+            self.Raise('Missing required DTB subnodes')
+
+        return records
diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
index 5cfa71ee981..57900e3d523 100644
--- a/tools/binman/etype/android_boot.py
+++ b/tools/binman/etype/android_boot.py
@@ -102,6 +102,37 @@ class Entry_android_boot(Entry_section):
                 };
             };
         };
+
+    Example::
+        A legacy QCDT abootimg, the kind msm8916 bootloaders expect:
+
+        android-boot {
+            base = <0x80000000>;
+
+            kernel {
+                u-boot {
+                    no-expanded;
+                };
+            };
+
+            ramdisk {
+                fill {
+                    size = <1>;
+                };
+            };
+
+            vendor-dt {
+                qcdt {
+                    dtb-0 {
+                        qcom,msm-id = <206 0>;
+                        qcom,board-id = <0xce08ff01 1>;
+
+                        u-boot-dtb {
+                        };
+                    };
+                };
+            };
+        };
     """
 
     def ReadNode(self):
diff --git a/tools/binman/etype/qcdt.py b/tools/binman/etype/qcdt.py
new file mode 100644
index 00000000000..ccf566af29f
--- /dev/null
+++ b/tools/binman/etype/qcdt.py
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Entry-type module for Qualcomm Android device tree tables
+
+import struct
+
+from binman.android_vendor_dt_table import Entry_Android_vendor_dt_table
+
+
+QCDT_MAGIC = b'QCDT'
+QCDT_VERSION = 2
+QCDT_HEADER = '<4sII'
+QCDT_HEADER_SIZE = struct.calcsize(QCDT_HEADER)
+QCDT_RECORD = '<IIIIII'
+QCDT_RECORD_SIZE = struct.calcsize(QCDT_RECORD)
+
+
+class Entry_qcdt(Entry_Android_vendor_dt_table):
+    """Qualcomm Android device tree table
+
+    This creates a QCDT table, the legacy device-tree table format used by
+    some Qualcomm Android bootloaders.
+
+    Properties / Entry arguments:
+        - page-size: QCDT page size, defaults to 2048, unless there's a parent
+          android-boot node with an explicit page-size
+
+    This entry uses the following subnodes:
+        - dtb-*: DTB records, each containing qcom,msm-id, qcom,board-id and
+          exactly one DTB payload entry
+
+    Example::
+
+        qcdt {
+            dtb-0 {
+                qcom,msm-id = <206 0>;
+                qcom,board-id = <0xce08ff01 1>;
+
+                u-boot-dtb {
+                };
+            };
+        };
+    """
+
+    def _GetDtbRecordData(self, node, required):
+        msm_id = self._GetU32Tuple(node, 'qcom,msm-id', 2)
+        board_id = self._GetU32Tuple(node, 'qcom,board-id', 2)
+        data = super()._GetDtbRecordData(node, required)
+        if data is None and not required:
+            return None
+
+        return (msm_id, board_id, data)
+
+    def _ReadDtbRecord(self, node, data):
+        return data
+
+    def BuildSectionData(self, required):
+        page_size = self._GetPageSize()
+        dtbs = self._ReadDtbRecords(required, self._ReadDtbRecord)
+        if dtbs is None:
+            return None
+
+        size = QCDT_HEADER_SIZE + len(dtbs) * QCDT_RECORD_SIZE
+        dtb_offset = self.AlignUp(size, page_size)
+        records = []
+        payloads = bytearray()
+        for msm_id, board_id, dtb in dtbs:
+            platform_id, soc_rev = msm_id
+            variant_id, board_hw_subtype = board_id
+            dtb_size = self.AlignUp(len(dtb), page_size)
+            records.append((platform_id, variant_id, board_hw_subtype,
+                            soc_rev, dtb_offset, dtb_size))
+            payloads += self.PadToAlignment(dtb, page_size)
+            dtb_offset += dtb_size
+
+        qcdt = bytearray(struct.pack(QCDT_HEADER, QCDT_MAGIC, QCDT_VERSION,
+                                     len(records)))
+        for record in records:
+            qcdt += struct.pack(QCDT_RECORD, *record)
+
+        return self.PadToAlignment(qcdt, page_size) + bytes(payloads)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index bbdcb721eca..b18d584c688 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5761,6 +5761,102 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
                          data[vendor_dt_offset:vendor_dt_offset + page_size])
         self.assertEqual(vendor_dt_offset + page_size, len(data))
 
+    def testQcdt(self):
+        """Test that binman can produce a QCDT container"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'qcdt.dts', use_real_dtb=True)
+
+        dtb_size = tools.align(len(dtb_data), 0x800)
+
+        self.assertEqual(b'QCDT', data[:4])
+        self.assertEqual((2, 2), struct.unpack_from('<II', data, 4))
+        self.assertEqual((0xce, 0xce08ff01, 1, 0, 0x800, dtb_size),
+                          struct.unpack_from('<IIIIII', data, 12))
+        self.assertEqual((0xcf, 0xce08ff02, 2, 1, 0x800 + dtb_size,
+                          dtb_size), struct.unpack_from('<IIIIII', data, 36))
+        self.assertEqual(0xd00dfeed,
+                          struct.unpack_from('>I', data, 0x800)[0])
+        self.assertEqual(dtb_data, data[0x800:0x800 + len(dtb_data)])
+        self.assertEqual(dtb_data, data[0x800 + dtb_size:0x800 + dtb_size +
+                                         len(dtb_data)])
+
+    def testQcdtPageSizeFromParent(self):
+        """Test that QCDT inherits page-size from parent android-boot node"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'qcdt_page_size_from_abootimg.dts')
+
+        # header+kernel are aligned to 4096, vendor-dt follows after that.
+        vendor_dt_offset = 4096*2
+
+        self.assertEqual(b'QCDT', data[vendor_dt_offset:vendor_dt_offset + 4])
+        self.assertEqual((4096, 4096),
+                         struct.unpack_from('<16xII', data,
+                                            vendor_dt_offset + 12))
+
+    def testQcdtBadMsmId(self):
+        """Test that QCDT rejects invalid msm-id properties"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_bad_msm_id.dts')
+        self.assertIn("Property 'qcom,msm-id' must contain exactly 2 cells",
+                      str(exc.exception))
+
+    def testQcdtMissingMsmId(self):
+        """Test that QCDT rejects missing qcom,msm-id"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_msm_id.dts')
+        self.assertIn("Missing required property 'qcom,msm-id'",
+                      str(exc.exception))
+
+    def testQcdtMissingDTBPayload(self):
+        """Test that QCDT rejects missing DTB payload"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_payload.dts')
+        self.assertIn("Missing required DTB payload subnode",
+                      str(exc.exception))
+
+    def testQcdtMissingSubnodes(self):
+        """Test that QCDT rejects missing dtb subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_subnodes.dts')
+        self.assertIn("Missing required DTB subnodes",
+                      str(exc.exception))
+
+    def testQcdtInvalidPageSize(self):
+        """Test that QCDT rejects invalid page-size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_invalid_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testQcdtZeroPageSize(self):
+        """Test that QCDT rejects zero page-size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_zero_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testQcdtMultipleDTBs(self):
+        """Test that QCDT handles multiple embedded DTBs"""
+        data = self._DoReadFile('qcdt_multiple_dtbs.dts')
+
+        page_size = 0x100
+        payload_size = page_size
+        payload_pad = tools.get_bytes(0, page_size - 1)
+
+        self.assertEqual(b'QCDT', data[:4])
+        self.assertEqual((2, 2), struct.unpack_from('<II', data, 4))
+        self.assertEqual((0xce, 0xce08ff01, 1, 0, page_size,
+                          payload_size),
+                         struct.unpack_from('<IIIIII', data, 12))
+        self.assertEqual((0xcf, 0xce08ff02, 3, 2,
+                          page_size + payload_size, payload_size),
+                         struct.unpack_from('<IIIIII', data, 36))
+        self.assertEqual(tools.get_bytes(0x11, 1) + payload_pad,
+                         data[page_size:page_size + payload_size])
+        self.assertEqual(tools.get_bytes(0x22, 1) + payload_pad,
+                         data[page_size + payload_size:
+                              page_size + payload_size * 2])
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/qcdt.dts b/tools/binman/test/qcdt.dts
new file mode 100644
index 00000000000..cdbd1a85379
--- /dev/null
+++ b/tools/binman/test/qcdt.dts
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that qcdt can be referenced before it's built */
+		collection {
+			content = <&qcdt>;
+		};
+
+		qcdt: qcdt {
+			hash {
+			};
+
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+
+				u-boot-dtb {
+				};
+			};
+
+			dtb-1 {
+				qcom,msm-id = <0xcf 1>;
+				qcom,board-id = <0xce08ff02 2>;
+
+				u-boot-dtb {
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_bad_msm_id.dts b/tools/binman/test/qcdt_bad_msm_id.dts
new file mode 100644
index 00000000000..1c3d4ec1a2e
--- /dev/null
+++ b/tools/binman/test/qcdt_bad_msm_id.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+			dtb-0 {
+				qcom,msm-id = <0xce 0 1>;
+				qcom,board-id = <0xce08ff01 1>;
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_invalid_pagesize.dts b/tools/binman/test/qcdt_invalid_pagesize.dts
new file mode 100644
index 00000000000..d8eff98c7ac
--- /dev/null
+++ b/tools/binman/test/qcdt_invalid_pagesize.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			page-size = <2049>;
+			dtb-0 {};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_msm_id.dts b/tools/binman/test/qcdt_missing_msm_id.dts
new file mode 100644
index 00000000000..3eda1acb6c2
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_msm_id.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			dtb-0 {
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_payload.dts b/tools/binman/test/qcdt_missing_payload.dts
new file mode 100644
index 00000000000..ae2c41cbcf8
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_payload.dts
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_subnodes.dts b/tools/binman/test/qcdt_missing_subnodes.dts
new file mode 100644
index 00000000000..4b1af9570b6
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_subnodes.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_multiple_dtbs.dts b/tools/binman/test/qcdt_multiple_dtbs.dts
new file mode 100644
index 00000000000..db04c122a6e
--- /dev/null
+++ b/tools/binman/test/qcdt_multiple_dtbs.dts
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+			page-size = <0x100>;
+
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+
+				fill {
+					size = <1>;
+					fill-byte = [11];
+				};
+			};
+
+			dtb-1 {
+				qcom,msm-id = <0xcf 2>;
+				qcom,board-id = <0xce08ff02 3>;
+
+				fill {
+					size = <1>;
+					fill-byte = [22];
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_page_size_from_abootimg.dts b/tools/binman/test/qcdt_page_size_from_abootimg.dts
new file mode 100644
index 00000000000..557863d8834
--- /dev/null
+++ b/tools/binman/test/qcdt_page_size_from_abootimg.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		android-boot {
+			page-size = <4096>;
+
+			kernel {
+				fill {
+					size = <1>;
+				};
+			};
+
+			vendor-dt {
+				qcdt {
+					dtb-0 {
+						qcom,msm-id = <0 0>;
+						qcom,board-id = <0 0>;
+
+						fill {
+							size = <1>;
+						};
+					};
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_zero_pagesize.dts b/tools/binman/test/qcdt_zero_pagesize.dts
new file mode 100644
index 00000000000..8ca802719f0
--- /dev/null
+++ b/tools/binman/test/qcdt_zero_pagesize.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			page-size = <0>;
+			dtb-0 {};
+		};
+	};
+};

-- 
2.54.0



^ permalink raw reply related

* Re: [RFC PATCH v3 1/4] samples/damon/wsse: handle damon_start() failure
From: sashiko-bot @ 2026-06-10  1:29 UTC (permalink / raw)
  To: SeongJae Park; +Cc: damon
In-Reply-To: <20260610011420.3018-2-sj@kernel.org>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Unhandled failure of `damon_call()` leads to a permanent memory leak of the DAMON context.
- [High] Concurrent execution of `module_init` and sysfs store causes Use-After-Free and Double Free on the global `ctx`.
--

commit 6215f97f729272927aafd065cdad15da2af100a8
Author: SeongJae Park <sj@kernel.org>

samples/damon/wsse: handle damon_start() failure

This commit fixes a memory leak in damon_sample_wsse_start() by explicitly
freeing the DAMON context if damon_start() fails.

Link: https://lore.kernel.org/20260609145814.70163-1-sj@kernel.org

> diff --git a/samples/damon/wsse.c b/samples/damon/wsse.c
> index 799ad44439434..bbd9392ab5b36 100644
> --- a/samples/damon/wsse.c
> +++ b/samples/damon/wsse.c
> @@ -87,8 +87,10 @@ static int damon_sample_wsse_start(void)
>  	target->pid = target_pidp;
>  
>  	err = damon_start(&ctx, 1, true);
> -	if (err)
> +	if (err) {
> +		damon_destroy_ctx(ctx);
>  		return err;
> +	}

[Severity: High]
This is a pre-existing issue, but does this new error path leave the global
ctx pointer dangling?

If the module is loaded with enabled=1, damon_sample_wsse_init() calls
damon_sample_wsse_start(). Concurrently, user space can write 0 to the
enabled sysfs parameter, which triggers damon_sample_wsse_stop().

If damon_start() encounters an error, this new path calls
damon_destroy_ctx(ctx) but ctx is not set to NULL. A concurrent
damon_sample_wsse_stop() would see ctx != NULL and call
damon_destroy_ctx(ctx) again, leading to a double free.

Similarly, if damon_sample_wsse_stop() executes while
damon_sample_wsse_start() is running, stop() could call
damon_destroy_ctx(ctx) while start() is actively using it, causing a
use-after-free.

>  	repeat_call_control.data = ctx;
>  	return damon_call(ctx, &repeat_call_control);
>  }

[Severity: High]
This is a pre-existing issue, but does this return path leak the newly
created ctx if damon_call() fails?

If damon_call() returns an error (for example, -ECANCELED if the kdamond
thread exits immediately and sets ctx->call_controls_obsolete to true), the
error is returned directly to the caller.

Because the kdamond thread does not free the context itself when exiting,
is damon_destroy_ctx(ctx) also needed here to prevent leaking the DAMON
context and its associated objects?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260610011420.3018-1-sj@kernel.org?part=1

^ permalink raw reply

* Re: [PATCH v4 1/6] alloc_tag: add ioctl to /proc/allocinfo
From: Hao Ge @ 2026-06-10  1:28 UTC (permalink / raw)
  To: Abhishek Bapat, Suren Baghdasaryan, Andrew Morton,
	Kent Overstreet
  Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
	Sourav Panda
In-Reply-To: <b58a0d01c8bb6d7c1d2350599c1b0170be161489.1781042698.git.abhishekbapat@google.com>


On 2026/6/10 08:12, Abhishek Bapat wrote:
> From: Suren Baghdasaryan <surenb@google.com>
>
> Add the following ioctl commands for /proc/allocinfo file:
>
> ALLOCINFO_IOC_CONTENT_ID - gets content identifier which can be used
> to check whether the file content has changed specifically due to module
> load/unload. Every time a module is loaded / unloaded, the returned
> value will be different. By comparing the identifier value at the
> beginning and at the end of the content retrieval operation, users can
> validate retrieved information for consistency.
>
> ALLOCINFO_IOC_GET_AT - gets the record at the specified position. This
> is the position of a record in /proc/allocinfo.
>
> ALLOCINFO_IOC_GET_NEXT - gets the record next to the last retrieved
> one. If no records were previously retrieved, returns the first
> record.
>
> Signed-off-by: Suren Baghdasaryan <surenb@google.com>
> Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>


Acked-by: Hao Ge <hao.ge@linux.dev>


> ---
>   Documentation/mm/allocation-profiling.rst     |   5 +
>   .../userspace-api/ioctl/ioctl-number.rst      |   2 +
>   MAINTAINERS                                   |   1 +
>   include/linux/codetag.h                       |   2 +
>   include/uapi/linux/alloc_tag.h                |  60 +++++
>   lib/alloc_tag.c                               | 232 +++++++++++++++++-
>   lib/codetag.c                                 |  18 ++
>   7 files changed, 318 insertions(+), 2 deletions(-)
>   create mode 100644 include/uapi/linux/alloc_tag.h
>
> diff --git a/Documentation/mm/allocation-profiling.rst b/Documentation/mm/allocation-profiling.rst
> index 5389d241176a..c3a28467955f 100644
> --- a/Documentation/mm/allocation-profiling.rst
> +++ b/Documentation/mm/allocation-profiling.rst
> @@ -46,6 +46,11 @@ sysctl:
>   Runtime info:
>     /proc/allocinfo
>   
> +  Profiling data can be retrieved either by reading `/proc/allocinfo` directly as
> +  text or programmatically via `ioctl()` calls defined in `<uapi/linux/alloc_tag.h>`.
> +  The ioctl interface supports structured binary data extraction as well as filtering
> +  by module name, function, file, line number, accuracy, or allocation size limits.
> +
>   Example output::
>   
>     root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index 331223761fff..84f6808a8578 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -349,6 +349,8 @@ Code  Seq#    Include File                                             Comments
>                                                                          <mailto:luzmaximilian@gmail.com>
>   0xA5  20-2F  linux/surface_aggregator/dtx.h                            Microsoft Surface DTX driver
>                                                                          <mailto:luzmaximilian@gmail.com>
> +0xA6  00-0F  uapi/linux/alloc_tag.h                                    Memory allocation profiling
> +                                                                       <mailto:surenb@google.com>
>   0xAA  00-3F  linux/uapi/linux/userfaultfd.h
>   0xAB  00-1F  linux/nbd.h
>   0xAC  00-1F  linux/raw.h
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 65bd4328fe05..019cc4c285a3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16713,6 +16713,7 @@ S:	Maintained
>   F:	Documentation/mm/allocation-profiling.rst
>   F:	include/linux/alloc_tag.h
>   F:	include/linux/pgalloc_tag.h
> +F:	include/uapi/linux/alloc_tag.h
>   F:	lib/alloc_tag.c
>   
>   MEMORY CONTROLLER DRIVERS
> diff --git a/include/linux/codetag.h b/include/linux/codetag.h
> index ddae7484ca45..a25a085c2df1 100644
> --- a/include/linux/codetag.h
> +++ b/include/linux/codetag.h
> @@ -77,6 +77,8 @@ struct codetag_iterator {
>   void codetag_lock_module_list(struct codetag_type *cttype);
>   bool codetag_trylock_module_list(struct codetag_type *cttype);
>   void codetag_unlock_module_list(struct codetag_type *cttype);
> +unsigned long codetag_get_content_id(struct codetag_type *cttype);
> +unsigned int codetag_get_count(struct codetag_type *cttype);
>   struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype);
>   struct codetag *codetag_next_ct(struct codetag_iterator *iter);
>   
> diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
> new file mode 100644
> index 000000000000..0928e1a48d49
> --- /dev/null
> +++ b/include/uapi/linux/alloc_tag.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * alloc_tag IOCTL API definition
> + *
> + * Copyright (C) 2026 Google, LLC.  All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef _UAPI_ALLOC_TAG_H
> +#define _UAPI_ALLOC_TAG_H
> +
> +#include <linux/types.h>
> +
> +#define ALLOCINFO_STR_SIZE	64
> +
> +struct allocinfo_content_id {
> +	__u64 id;
> +};
> +
> +struct allocinfo_tag {
> +	/* Longer names are trimmed */
> +	char modname[ALLOCINFO_STR_SIZE];
> +	char function[ALLOCINFO_STR_SIZE];
> +	char filename[ALLOCINFO_STR_SIZE];
> +	__u64 lineno;
> +};
> +
> +/* The alignment ensures 32-bit compatible interfaces are not broken */
> +struct allocinfo_counter {
> +	__u64 bytes;
> +	__u64 calls;
> +	__u8 accurate;
> +} __attribute__((aligned(8)));
> +
> +struct allocinfo_tag_data {
> +	struct allocinfo_tag tag;
> +	struct allocinfo_counter counter;
> +};
> +
> +struct allocinfo_get_at {
> +	__u64 pos;	/* input */
> +	struct allocinfo_tag_data data;
> +};
> +
> +#define _ALLOCINFO_IOC_CONTENT_ID	0
> +#define _ALLOCINFO_IOC_GET_AT		1
> +#define _ALLOCINFO_IOC_GET_NEXT		2
> +
> +#define ALLOCINFO_IOC_BASE		0xA6
> +#define ALLOCINFO_IOC_CONTENT_ID	_IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_CONTENT_ID,	\
> +					     struct allocinfo_content_id)
> +#define ALLOCINFO_IOC_GET_AT		_IOWR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_AT,	\
> +					      struct allocinfo_get_at)
> +#define ALLOCINFO_IOC_GET_NEXT		_IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_NEXT,	\
> +					     struct allocinfo_tag_data)
> +
> +#endif /* _UAPI_ALLOC_TAG_H */
> diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
> index d9be1cf5187d..a0577215eb3d 100644
> --- a/lib/alloc_tag.c
> +++ b/lib/alloc_tag.c
> @@ -5,6 +5,7 @@
>   #include <linux/gfp.h>
>   #include <linux/kallsyms.h>
>   #include <linux/module.h>
> +#include <linux/mutex.h>
>   #include <linux/page_ext.h>
>   #include <linux/pgalloc_tag.h>
>   #include <linux/proc_fs.h>
> @@ -14,6 +15,7 @@
>   #include <linux/string_choices.h>
>   #include <linux/vmalloc.h>
>   #include <linux/kmemleak.h>
> +#include <uapi/linux/alloc_tag.h>
>   
>   #define ALLOCINFO_FILE_NAME		"allocinfo"
>   #define MODULE_ALLOC_TAG_VMAP_SIZE	(100000UL * sizeof(struct alloc_tag))
> @@ -47,6 +49,10 @@ struct allocinfo_private {
>   	struct codetag_iterator iter;
>   	struct codetag_iterator reported_iter;
>   	bool print_header;
> +	/* ioctl uses a separate iterator not to interfere with reads */
> +	struct codetag_iterator ioctl_iter;
> +	bool positioned; /* seq_open_private() sets to 0 */
> +	struct mutex ioctl_lock;
>   };
>   
>   static void *allocinfo_start(struct seq_file *m, loff_t *pos)
> @@ -130,6 +136,229 @@ static const struct seq_operations allocinfo_seq_op = {
>   	.show	= allocinfo_show,
>   };
>   
> +/*
> + * Initializes seq_file operations and allocates private state when opening
> + * the /proc/allocinfo procfs entry.
> + */
> +static int allocinfo_open(struct inode *inode, struct file *file)
> +{
> +	int ret;
> +
> +	ret = seq_open_private(file, &allocinfo_seq_op,
> +			       sizeof(struct allocinfo_private));
> +	if (!ret) {
> +		struct seq_file *m = file->private_data;
> +		struct allocinfo_private *priv = m->private;
> +
> +		mutex_init(&priv->ioctl_lock);
> +	}
> +	return ret;
> +}
> +
> +/*
> + * Cleans up the seq_file state and frees up the private state allocated in
> + * allocinfo_open() when closing the /proc/allocinfo file descriptor.
> + */
> +static int allocinfo_release(struct inode *inode, struct file *file)
> +{
> +	return seq_release_private(inode, file);
> +}
> +
> +/*
> + * Returns a pointer to the suffix of a string so that its length fits within
> + * ALLOCINFO_STR_SIZE, preserving the trailing characters.
> + */
> +static const char *allocinfo_str(const char *str)
> +{
> +	size_t len = strlen(str);
> +
> +	/* Keep an extra space for the trailing NULL. */
> +	if (len >= ALLOCINFO_STR_SIZE)
> +		str += (len - ALLOCINFO_STR_SIZE) + 1;
> +	return str;
> +}
> +
> +/* Copy a string and trim from the beginning if it's too long */
> +static void allocinfo_copy_str(char *dest, const char *src)
> +{
> +	strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
> +}
> +
> +/*
> + * Populates the UAPI allocinfo_tag_data structure with active runtime
> + * profiling counters extracted from the given kernel codetag.
> + */
> +static void allocinfo_to_params(struct codetag *ct,
> +				struct allocinfo_tag_data *data)
> +{
> +	struct alloc_tag *tag = ct_to_alloc_tag(ct);
> +	struct alloc_tag_counters counter = alloc_tag_read(tag);
> +
> +	if (ct->modname)
> +		allocinfo_copy_str(data->tag.modname, ct->modname);
> +	else
> +		data->tag.modname[0] = '\0';
> +	allocinfo_copy_str(data->tag.function, ct->function);
> +	allocinfo_copy_str(data->tag.filename, ct->filename);
> +	data->tag.lineno = ct->lineno;
> +	data->counter.bytes = counter.bytes;
> +	data->counter.calls = counter.calls;
> +	data->counter.accurate = !alloc_tag_is_inaccurate(tag);
> +}
> +
> +/*
> + * Retrieves the unique content ID representing the current allocation tag module
> + * layout, allowing userspace to detect if modules were loaded / unloaded.
> + */
> +static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
> +{
> +	struct allocinfo_content_id params;
> +
> +	codetag_lock_module_list(alloc_tag_cttype);
> +	params.id = codetag_get_content_id(alloc_tag_cttype);
> +	codetag_unlock_module_list(alloc_tag_cttype);
> +	if (copy_to_user(arg, &params, sizeof(params)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +/*
> + * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
> + * profiling data and returns it to userspace.
> + */
> +static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
> +{
> +	struct allocinfo_private *priv;
> +	struct codetag *ct;
> +	__u64 pos;
> +	struct allocinfo_get_at params = {0};
> +
> +	if (copy_from_user(&params, arg, sizeof(params)))
> +		return -EFAULT;
> +
> +	priv = m->private;
> +	pos = params.pos;
> +
> +	mutex_lock(&priv->ioctl_lock);
> +	codetag_lock_module_list(alloc_tag_cttype);
> +
> +	if (pos >= codetag_get_count(alloc_tag_cttype)) {
> +		codetag_unlock_module_list(alloc_tag_cttype);
> +		mutex_unlock(&priv->ioctl_lock);
> +		return -ENOENT;
> +	}
> +
> +	/* Find the codetag */
> +	priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
> +	ct = codetag_next_ct(&priv->ioctl_iter);
> +	while (ct && pos--)
> +		ct = codetag_next_ct(&priv->ioctl_iter);
> +	if (ct) {
> +		allocinfo_to_params(ct, &params.data);
> +		priv->positioned = true;
> +	}
> +
> +	codetag_unlock_module_list(alloc_tag_cttype);
> +	mutex_unlock(&priv->ioctl_lock);
> +
> +	if (!ct)
> +		return -ENOENT;
> +
> +	if (copy_to_user(arg, &params, sizeof(params)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +/*
> + * Advances the ioctl iterator to the next allocation tag in the sequence and
> + * returns its profiling data to userspace.
> + */
> +static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
> +{
> +	struct allocinfo_private *priv;
> +	struct codetag *ct;
> +	struct allocinfo_tag_data params;
> +	int ret = 0;
> +
> +	memset(&params, 0, sizeof(params));
> +	priv = m->private;
> +
> +	mutex_lock(&priv->ioctl_lock);
> +	codetag_lock_module_list(alloc_tag_cttype);
> +
> +	if (!priv->positioned) {
> +		priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
> +		priv->positioned = true;
> +	}
> +
> +	ct = codetag_next_ct(&priv->ioctl_iter);
> +	if (ct)
> +		allocinfo_to_params(ct, &params);
> +
> +	if (!ct) {
> +		priv->positioned = false;
> +		ret = -ENOENT;
> +	}
> +	codetag_unlock_module_list(alloc_tag_cttype);
> +	mutex_unlock(&priv->ioctl_lock);
> +
> +	if (ret == 0) {
> +		if (copy_to_user(arg, &params, sizeof(params)))
> +			return -EFAULT;
> +	}
> +	return ret;
> +}
> +
> +/*
> + * Entry point ioctl function for /proc/allocinfo routing requests to fetch the
> + * layout content ID, seek to a specific tag, or read sequential tags.
> + */
> +static long allocinfo_ioctl(struct file *file, unsigned int cmd,
> +			    unsigned long __arg)
> +{
> +	void __user *arg = (void __user *)__arg;
> +	int ret;
> +
> +	switch (cmd) {
> +	case ALLOCINFO_IOC_CONTENT_ID:
> +		ret = allocinfo_ioctl_get_content_id(file->private_data, arg);
> +		break;
> +	case ALLOCINFO_IOC_GET_AT:
> +		ret = allocinfo_ioctl_get_at(file->private_data, arg);
> +		break;
> +	case ALLOCINFO_IOC_GET_NEXT:
> +		ret = allocinfo_ioctl_get_next(file->private_data, arg);
> +		break;
> +	default:
> +		ret = -ENOIOCTLCMD;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd,
> +				   unsigned long arg)
> +{
> +	return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> +}
> +#endif
> +
> +static const struct proc_ops allocinfo_proc_ops = {
> +	.proc_open		= allocinfo_open,
> +	.proc_read_iter		= seq_read_iter,
> +	.proc_lseek		= seq_lseek,
> +	.proc_release		= allocinfo_release,
> +	.proc_ioctl		= allocinfo_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.proc_compat_ioctl	= allocinfo_compat_ioctl,
> +#endif
> +
> +};
> +
>   size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
>   {
>   	struct codetag_iterator iter;
> @@ -993,8 +1222,7 @@ static int __init alloc_tag_init(void)
>   		return 0;
>   	}
>   
> -	if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op,
> -				     sizeof(struct allocinfo_private), NULL)) {
> +	if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) {
>   		pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
>   		shutdown_mem_profiling(false);
>   		return -ENOMEM;
> diff --git a/lib/codetag.c b/lib/codetag.c
> index 4001a7ea6675..a9cda4c962a3 100644
> --- a/lib/codetag.c
> +++ b/lib/codetag.c
> @@ -19,6 +19,8 @@ struct codetag_type {
>   	struct codetag_type_desc desc;
>   	/* generates unique sequence number for module load */
>   	unsigned long next_mod_seq;
> +	/* bumped on every module load and unload */
> +	unsigned long content_id;
>   };
>   
>   struct codetag_range {
> @@ -50,6 +52,20 @@ void codetag_unlock_module_list(struct codetag_type *cttype)
>   	up_read(&cttype->mod_lock);
>   }
>   
> +unsigned long codetag_get_content_id(struct codetag_type *cttype)
> +{
> +	lockdep_assert_held(&cttype->mod_lock);
> +
> +	return cttype->content_id;
> +}
> +
> +unsigned int codetag_get_count(struct codetag_type *cttype)
> +{
> +	lockdep_assert_held(&cttype->mod_lock);
> +
> +	return cttype->count;
> +}
> +
>   struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype)
>   {
>   	struct codetag_iterator iter = {
> @@ -204,6 +220,7 @@ static int codetag_module_init(struct codetag_type *cttype, struct module *mod)
>   
>   	down_write(&cttype->mod_lock);
>   	cmod->mod_seq = ++cttype->next_mod_seq;
> +	++cttype->content_id;
>   	mod_id = idr_alloc(&cttype->mod_idr, cmod, 0, 0, GFP_KERNEL);
>   	if (mod_id >= 0) {
>   		if (cttype->desc.module_load) {
> @@ -368,6 +385,7 @@ void codetag_unload_module(struct module *mod)
>   			cttype->count -= range_size(cttype, &cmod->range);
>   			idr_remove(&cttype->mod_idr, mod_id);
>   			kfree(cmod);
> +			++cttype->content_id;
>   		}
>   		up_write(&cttype->mod_lock);
>   		if (found && cttype->desc.free_section_mem)

^ permalink raw reply

* [PATCH v3 06/10] binman: android_boot: vendor-dt support
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

There's many Android bootloaders out there that expect non-standard
abootimgs. Samsung and Qualcomm, and likely many others. This quirk has
been codified in the widely used osm0sis fork/rewrite of mkbootimg.

Presumably, these vendor-specific abootimgs were conceived before the v2
format was widely available or specified. The evidence for this is their
hijacking of the header_version field to specify the length of a payload
that follows the main abootimg.

At least QCDT and DTBH (both of which will be implemented as binman
etypes in following commits) use this approach.

Link: https://github.com/osm0sis/mkbootimg
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/etype/android_boot.py           | 41 ++++++++++++++++++++++++++--
 tools/binman/ftest.py                        | 15 ++++++++++
 tools/binman/test/android_boot_vendor_dt.dts | 27 ++++++++++++++++++
 3 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
index 8b72d90acc5..5cfa71ee981 100644
--- a/tools/binman/etype/android_boot.py
+++ b/tools/binman/etype/android_boot.py
@@ -33,6 +33,9 @@ class Entry_android_boot(Entry_section):
     A kernel payload, optional ramdisk payload can be supplied. A DTB payload
     can also be provided when header_version == v2.
 
+    Vendor-specific payloads are also supported. These are non-standard
+    v0 images with a special DT container format appended.
+
     Properties / Entry arguments:
         - header-version: Android boot image header version, must be 0 or 2,
           defaults to 0
@@ -50,6 +53,7 @@ class Entry_android_boot(Entry_section):
         - kernel: section containing the executable payload
         - dtb: section containing the DTB payload, used by header version 2 only
         - ramdisk: optional section containing a ramdisk payload
+        - vendor-dt: legacy vendor DT payload, used by header version 0 only
 
     Example::
         A v2 abootimg with control FDT placed in the DTB section:
@@ -112,6 +116,7 @@ class Entry_android_boot(Entry_section):
         self.os_version = fdt_util.GetInt(self._node, 'os-version', 0)
         self.boot_name = fdt_util.GetString(self._node, 'boot-name', '')
         self.cmdline = fdt_util.GetString(self._node, 'cmdline', '')
+        self.vendor_dt_node = self._node.FindNode('vendor-dt')
 
         if self.header_version not in (0, 2):
             self.Raise('Only Android boot image header versions 0 and 2 are '
@@ -132,9 +137,15 @@ class Entry_android_boot(Entry_section):
                 self.Raise('page-size must fit the Android boot image header')
             if 'dtb' not in self._entries:
                 self.Raise("Missing required subnode 'dtb'")
+            if self.vendor_dt_node:
+                self.Raise("Subnode 'vendor-dt' requires header-version 0")
 
     def ReadEntries(self):
         for node in self._node.subnodes:
+            if node.name == 'vendor-dt':
+                self._ReadVendorDtEntries(node)
+                continue
+
             if node.name not in ('kernel', 'ramdisk', 'dtb'):
                 self.Raise("Unexpected subnode '%s'" % node.name)
 
@@ -145,6 +156,14 @@ class Entry_android_boot(Entry_section):
             entry.SetPrefix(self._name_prefix)
             self._entries[node.name] = entry
 
+    def _ReadVendorDtEntries(self, vendor_dt_node):
+        entry = Entry.Create(self, vendor_dt_node, etype='section',
+                             expanded=self.GetImage().use_expanded,
+                             missing_etype=self.GetImage().missing_etype)
+        entry.ReadNode()
+        entry.SetPrefix(self._name_prefix)
+        self._entries[vendor_dt_node.name] = entry
+
     def _GetIntCells(self, propname, default):
         prop = self._node.props.get(propname)
         if not prop:
@@ -198,8 +217,14 @@ class Entry_android_boot(Entry_section):
             return default
         return entry.GetData(required)
 
+    def _BuildVendorDt(self, required):
+        if not self.vendor_dt_node:
+            return b''
+        return self._GetEntryData('vendor-dt', required)
+
     def _BuildV0SectionData(self, required):
         kernel = self._GetEntryData('kernel', required)
+        vendor_dt = self._BuildVendorDt(required)
         ramdisk = self._GetEntryData('ramdisk', required, b'')
         if not required and (kernel is None or vendor_dt is None or
                              ramdisk is None):
@@ -211,8 +236,18 @@ class Entry_android_boot(Entry_section):
                                  BOOT_ARGS_SIZE)
 
         boot_id_payloads = [kernel, ramdisk, b'']
+        if self.vendor_dt_node:
+            boot_id_payloads.append(vendor_dt)
         image_id = self._BootId(*boot_id_payloads)
 
+        overloaded_header_version =  self.header_version
+        if self.vendor_dt_node:
+            # vendor DTs overload the header_version field to store the length
+            # of the appended payload. Hopefully AOSP abootimg never progresses
+            # to v8192-ish or we might have some real specificity problems on
+            # our hands.
+            overloaded_header_version = len(vendor_dt)
+
         header = struct.pack(BOOT_IMAGE_HEADER_V0,
                              BOOT_MAGIC,
                              len(kernel),
@@ -223,7 +258,7 @@ class Entry_android_boot(Entry_section):
                              0, # second_offset
                              self._GetAddr(self.tags_offset, 'tags'),
                              self.page_size,
-                             self.header_version,
+                             overloaded_header_version,
                              self.os_version,
                              boot_name,
                              cmdline,
@@ -233,6 +268,7 @@ class Entry_android_boot(Entry_section):
         image += self.PadToAlignment(header, self.page_size)
         image += self.PadToAlignment(kernel, self.page_size)
         image += self.PadToAlignment(ramdisk, self.page_size)
+        image += self.PadToAlignment(vendor_dt, self.page_size)
 
         return bytes(image)
 
@@ -240,7 +276,8 @@ class Entry_android_boot(Entry_section):
         kernel = self._GetEntryData('kernel', required)
         dtb = self._GetEntryData('dtb', required)
         ramdisk = self._GetEntryData('ramdisk', required, b'')
-        if not required and (kernel is None or dtb is None):
+        if not required and (kernel is None or dtb is None or
+                             ramdisk is None):
             return None
 
         boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 71740205c72..bbdcb721eca 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5746,6 +5746,21 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
         self.assertIn("Subnode 'vendor-dt' requires header-version 0",
                       str(exc.exception))
 
+    def testAndroidBootVendorDt(self):
+        """Test that android-boot can embed an arbitrary vendor-dt section"""
+        data = self._DoReadFile('android_boot_vendor_dt.dts')
+        header = struct.unpack_from('<8s10I16s512s32s', data, 0)
+        page_size = 2048
+        vendor_dt_offset = page_size * 3
+        vendor_dt = b'howdy'
+        self.assertEqual(len(vendor_dt), header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'\0', b'',
+                                             vendor_dt), header[13])
+        self.assertEqual(vendor_dt + b'\0' * (page_size - len(vendor_dt)),
+                         data[vendor_dt_offset:vendor_dt_offset + page_size])
+        self.assertEqual(vendor_dt_offset + page_size, len(data))
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/android_boot_vendor_dt.dts b/tools/binman/test/android_boot_vendor_dt.dts
new file mode 100644
index 00000000000..194396a0880
--- /dev/null
+++ b/tools/binman/test/android_boot_vendor_dt.dts
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+	binman {
+		android-boot {
+			header-version = <0>;
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+			ramdisk {
+				fill {
+					size = <1>;
+				};
+			};
+			vendor-dt {
+				text {
+					size = <5>;
+					text = "howdy";
+				};
+			};
+		};
+	};
+};

-- 
2.54.0



^ permalink raw reply related

* Re: [PATCH] Revert "selftests: timers: Remove local NSEC_PER_SEC and USEC_PER_SEC defines"
From: John Stultz @ 2026-06-10  1:28 UTC (permalink / raw)
  To: Wake Liu
  Cc: anna-maria, frederic, tglx, shuah, sboyd, cmllamas, linux-kernel,
	linux-kselftest
In-Reply-To: <20260609075605.234798-1-wakel@google.com>

On Tue, Jun 9, 2026 at 12:56 AM Wake Liu <wakel@google.com> wrote:
>
> This reverts commit 80fa614e2fbcf11069f0995e1601fb2e5702e2f4.
>
> The reverted commit replaced local definitions of NSEC_PER_SEC and
> USEC_PER_SEC with inclusion of <include/vdso/time64.h>. However,
> NSEC_PER_SEC in vdso/time64.h is defined as 1000000000L, which is
> 32-bit on 32-bit architectures. This causes integer overflow warnings
> in several timer tests when doing arithmetic like NSEC_PER_SEC * 10.
>
> Reverting this change restores the local definitions which use
> unsigned long long (ULL) or long long (LL) suffixes, ensuring 64-bit
> multiplication and avoiding overflows on 32-bit systems.
> This also decouples the userspace selftests from internal kernel
> headers.

Hey Wake,
  Thanks for sending this out. While I agree reverting back to local
definitions to ensure we're using 64bit values is better then adding
lots of casting, I think a straight revert isn't maybe the best, since
the original patch did have value in cleaning up the inconsistencies
between tests.

Maybe a "partial" revert would be best, re-adding the local
definitions but avoiding the quirks and inconsistencies between the
tests.


> diff --git a/tools/testing/selftests/timers/adjtick.c b/tools/testing/selftests/timers/adjtick.c
> index 5b3ef708d6e9..0053c6c5bfc0 100644
> --- a/tools/testing/selftests/timers/adjtick.c
> +++ b/tools/testing/selftests/timers/adjtick.c
> @@ -22,10 +22,12 @@
>  #include <sys/time.h>
>  #include <sys/timex.h>
>  #include <time.h>
> -#include <include/vdso/time64.h>
>
>  #include "kselftest.h"
>
> +#define NSEC_PER_SEC           1000000000LL
> +#define USEC_PER_SEC           1000000
> +
>  #define MILLION                        1000000
>
>  long systick;
> diff --git a/tools/testing/selftests/timers/alarmtimer-suspend.c b/tools/testing/selftests/timers/alarmtimer-suspend.c
> index aa66c805f6a4..ba277a708aba 100644
> --- a/tools/testing/selftests/timers/alarmtimer-suspend.c
> +++ b/tools/testing/selftests/timers/alarmtimer-suspend.c
> @@ -28,10 +28,10 @@
>  #include <signal.h>
>  #include <stdlib.h>
>  #include <pthread.h>
> -#include <include/vdso/time64.h>
>  #include <errno.h>
>  #include "kselftest.h"
>
> +#define NSEC_PER_SEC 1000000000ULL


It seems like picking LL across the board would probably be a good idea.


> diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
> index 38512623622a..6f78b6068589 100644
> --- a/tools/testing/selftests/timers/posix_timers.c
> +++ b/tools/testing/selftests/timers/posix_timers.c
> @@ -16,13 +16,14 @@
>  #include <string.h>
>  #include <unistd.h>
>  #include <time.h>
> -#include <include/vdso/time64.h>
>  #include <pthread.h>
>  #include <stdbool.h>
>
>  #include "kselftest.h"
>
>  #define DELAY 2
> +#define USECS_PER_SEC 1000000
> +#define NSECS_PER_SEC 1000000000
>
>  static void __fatal_error(const char *test, const char *name, const char *what)
>  {
> @@ -87,9 +88,9 @@ static int check_diff(struct timeval start, struct timeval end)
>         long long diff;
>
>         diff = end.tv_usec - start.tv_usec;
> -       diff += (end.tv_sec - start.tv_sec) * USEC_PER_SEC;
> +       diff += (end.tv_sec - start.tv_sec) * USECS_PER_SEC;
>
> -       if (llabs(diff - DELAY * USEC_PER_SEC) > USEC_PER_SEC / 2) {
> +       if (llabs(diff - DELAY * USECS_PER_SEC) > USECS_PER_SEC / 2) {
>                 printf("Diff too high: %lld..", diff);
>                 return -1;
>         }

Similarly I don't think re-introducing the plural definitions to the
code here is useful. Lets keep the cleanups, but just re-add the local
definition.

thanks
-john

^ permalink raw reply

* [PATCH v3 10/10] configs: exynos-mobile: pull in binman
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

All Exynos7 mobile devices need abootimg+DTBH wrapping to be bootable
from the fused S-BOOT bootloader. So we enable binman everywhere.
Ideally we'll have full coverage of these devices with binman configs in
the overlay DTSIs but, for now, we ensure there's a placeholder node.
That way no existing devices will regress their cleanly buildable state.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Sam Day <me@samcday.com>
---
 arch/arm/dts/exynos-mobile.dtsi | 5 +++++
 arch/arm/mach-exynos/Kconfig    | 1 +
 configs/exynos-mobile_defconfig | 1 +
 3 files changed, 7 insertions(+)

diff --git a/arch/arm/dts/exynos-mobile.dtsi b/arch/arm/dts/exynos-mobile.dtsi
new file mode 100644
index 00000000000..9982e996993
--- /dev/null
+++ b/arch/arm/dts/exynos-mobile.dtsi
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/ {
+	binman {
+	};
+};
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig
index b084b7284aa..d4ae182030d 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -255,6 +255,7 @@ endif
 config TARGET_EXYNOS_MOBILE
 	bool "Samsung Exynos Generic Boards (for mobile devices)"
 	select ARM64
+	select BINMAN
 	select BOARD_EARLY_INIT_F
 	select CLK_EXYNOS
 	select LINUX_KERNEL_IMAGE_HEADER
diff --git a/configs/exynos-mobile_defconfig b/configs/exynos-mobile_defconfig
index 45ab998bb45..fcaff3d1608 100644
--- a/configs/exynos-mobile_defconfig
+++ b/configs/exynos-mobile_defconfig
@@ -32,6 +32,7 @@ CONFIG_EFI_PARTITION=y
 CONFIG_OF_UPSTREAM=y
 CONFIG_OF_UPSTREAM_BUILD_VENDOR=y
 CONFIG_OF_BOARD=y
+CONFIG_DEVICE_TREE_INCLUDES="exynos-mobile.dtsi"
 CONFIG_BLKMAP=y
 CONFIG_BUTTON_REMAP_PHONE_KEYS=y
 CONFIG_CLK_EXYNOS7870=y

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v8 0/6] net: stmmac: eic7700: add eth1 variant support and update delay bindings
From: lizhi2 @ 2026-06-10  1:27 UTC (permalink / raw)
  To: devicetree, andrew+netdev, davem, edumazet, kuba, robh, krzk+dt,
	conor+dt, netdev, pabeni, mcoquelin.stm32, alexandre.torgue,
	rmk+kernel, pjw, palmer, aou, alex, linux-riscv, linux-stm32,
	linux-arm-kernel, linux-kernel, maxime.chevallier
  Cc: ningyu, linmin, pinkesh.vaghela, pritesh.patel, weishangjuan,
	horms, lee, Zhi Li

From: Zhi Li <lizhi2@eswincomputing.com>

v7 -> v8:
  - eth0-related fixes were moved into separate series [1], [2]:
    - All eth0-related fixes have been removed from this series to avoid mixing
      MAC variants and RX timing logic in a single review context.
    - Reference:
      [1]https://lore.kernel.org/lkml/20260602014528.2076-1-lizhi2@eswincomputing.com/
      [2]https://lore.kernel.org/lkml/20260518021919.404-1-lizhi2@eswincomputing.com/

  - Update cover letter and overall series scope description:
    - Replace previous wording "EIC7700 eth1 RX sampling timing fix"
      with a more accurate description:
        - Add eth1 MAC variant support.
        - Update RGMII delay binding model.
    - This reflects the structural nature of the series rather than a pure
      bug fix.

  -  Split DT bindings changes into two patches:
    - patch1:
        - Relax RGMII internal delay constraints.
        - Change rx/tx internal delay from enum-based model to range-based
          model.
        - Mark delay properties as optional.
    - patch2:
        - Introduce EIC7700 eth1 MAC variant compatible string
          "eswin,eic7700-qos-eth-clk-inversion".
        - Model silicon-specific RX clock inversion requirement via SoC
          variant instead of board-level properties.

    - Due to this restructuring:
        - Patch structure and commit messages have changed significantly
          compared to v7.
        - The previously received Acked-by from Conor Dooley is not
          carried forward because the binding patches were substantially
          reworked and split.

  - Split driver changes into two patches. No functional changes to eth1
    compared to v7:
    - patch 3:
        - Make rx-internal-delay-ps and tx-internal-delay-ps optional.
        - Remove mandatory DT property requirement in probe path.
        - Allow zero-delay default when properties are absent.
    - patch 4:
        - Add support for eth1 MAC variant using compatible-specific
          match data.
        - Introduce RX clock inversion handling for eth1 at runtime.
        - Apply speed-dependent configuration via fix_mac_speed()
          callback.

  - Note:
    - These patches (5/6 and 6/6) are included only to facilitate review
      of the overall Ethernet integration across bindings, driver, and
      device tree.
      A cleaned-up, upstream-ready DTS series will be submitted separately
      once all dependencies and final hardware integration are completed.

  - Link to v7:
    https://lore.kernel.org/lkml/20260427072353.1114-1-lizhi2@eswincomputing.com/

v6 -> v7:
  - Address checkpatch.pl --strict warnings for DTS changes:
    - Split DT binding documentation and DTS board description into separate patches
    - Fix DTS style issues reported by checkpatch:
      - Reduce line length where applicable
      - Add required description for rgmii-rxid

  - DTS changes in this series are split into:
    - Patch 3/4: syscon binding update (documentation / reference only)
    - Patch 4/4: board DTS changes (architecture overview only)

    These patches (3/4 and 4/4) are provided to facilitate review of the overall
    Ethernet integration across binding, driver, and device tree, and are not
    intended as final upstream submission in their current form.

    A cleaned-up, upstream-ready DTS series will be submitted separately once
    all dependencies and final hardware integration are completed.

  - Note:
    - Clock-related bindings referenced in earlier revisions are now already merged
      into net-next, so dtbs_check warnings related to clock are no longer present
      and are not relevant to this revision.

  - No functional changes in the stmmac driver or binding semantics in this revision.

  - Link to v6:
    https://lore.kernel.org/lkml/20260423085501.760-1-lizhi2@eswincomputing.com/

v5 -> v6:
  - Update DTS/DTSI descriptions to fix invalid phandle references reported by DTC:
    - Add missing GMAC provider nodes required for proper hardware description:
      - HSP power domain: GMAC nodes moved under this domain to reflect
        hardware power hierarchy.
      - Clock nodes: added to provide clk phandles referenced by GMAC.
      - Reset nodes: added to provide reset phandles referenced by GMAC.
      - Pinctrl nodes: defines pinctrl settings for GMAC signals
        (pinctrl_gpio106, pinctrl_gpio111).
    - Move GMAC nodes under the correct HSP power domain.
    - Ensure DTS builds without dtc errors and all phandle references
      (clk/reset/pinctrl/power-domain) are valid.
    - This update does not change runtime behavior; it only improves DTS
      consistency and resolves issues reported by dtc.

  - Note:
    - The patch 3/3 for DTS changes in this series provide an overview of the GMAC
      integration and its dependencies, as discussed previously:
      https://lore.kernel.org/lkml/64bf6b40-b947-4ffa-8d48-4d6341931327@lunn.ch/

    - It is **not intended for upstream inclusion** in its current form,
      and is provided solely for architecture overview and integration
      context.

    - A fully cleaned and upstream-ready DTS series will be submitted
      separately once all related components (pinctrl, clock, power-domain,
      etc.) are finalized.

  - dtbs_check has been run on top of net-next for reference purposes.
    Remaining warnings are expected due to missing EIC7700 clock bindings[1]
    in net-next and do not reflect issues in the DTS design itself.

  - One remaining warning:
    - eswin,eic7700-clock

  - The clock binding has already been applied to upstream and is present
    in mainline, but not yet available in net-next.

  - The syscon binding is extended in this series to include the
    eswin,eic7700-syscfg compatible.

  - Any further refinement of the syscfg binding will be handled in
    separate patches if needed.

  - Dependencies:
    - [1]EIC7700 clock binding:
      https://lore.kernel.org/lkml/20260303080637.2100-1-dongxuyang@eswincomputing.com/
      (already applied to upstream)

  - Link to v5:
    https://lore.kernel.org/lkml/20260324073017.376-1-lizhi2@eswincomputing.com/

v4 -> v5:
  - eswin,eic7700-eth.yaml:
    - Add Acked-by from Conor Dooley
    - No functional changes

  - Update dwmac-eic7700.c:
    - Disable clocks on the error path to fix a clock leak in
      eic7700_dwmac_init() when regmap_set_bits() fails
      (reported by Simon Horman <horms@kernel.org>)

  - Link to v4:
    https://lore.kernel.org/lkml/20260313075234.1567-1-lizhi2@eswincomputing.com/

v3 -> v4:
  - Update eswin,eic7700-eth.yaml:
    - Improve commit message in dt-bindings patch to clarify the
      hardware difference of the eth1 MAC and why a new compatible
      string is required.
    - Move the newly added eswin,hsp-sp-csr item to the end of the list
      to avoid inserting entries in the middle of the binding schema.
    - Simplify the compatible schema by replacing the previous oneOf
      construct with an enum.

  - Update dwmac-eic7700.c:
    - Fix build issues.
    - Adjust code to match the updated binding definition.

  - Update DTS/DTSI descriptions:
    - Move SoC-level descriptions to the .dtsi file.
    - Keep board-specific configuration in the .dts file.

  - Link to v3:
    https://lore.kernel.org/lkml/20260303061525.846-1-lizhi2@eswincomputing.com/

v2 -> v3:
  - Update eswin,eic7700-eth.yaml:
    - Extend rx-internal-delay-ps and tx-internal-delay-ps range
      from 0-2400 to 0-2540 to match the full 7-bit hardware delay
      field (127 * 20 ps).
    - Add "multipleOf: 20" constraint to reflect the 20 ps hardware
      step size.
    - Make rx-internal-delay-ps and tx-internal-delay-ps optional.
      A well-designed board should not require internal delay tuning.
    - Remove rx-internal-delay-ps and tx-internal-delay-ps from the
      example to avoid encouraging blind copy into board DTs.

  - Update dwmac-eic7700.c:
    - Treat rx-internal-delay-ps and tx-internal-delay-ps as optional
      DT properties.
    - Apply delay configuration only when properties are present.
    - Keep TX/RX delay registers cleared by default to ensure a
      deterministic state when no delay is specified.

  - Describe Ethernet configuration for the HiFive Premier P550 board:
    - Add GMAC controller nodes for the HiFive Premier P550 board
      to describe the on-board Ethernet configuration.

      The Ethernet controller depends on clock, reset, pinctrl
      and HSP subsystem providers which are currently under
      upstream review. These dependent nodes will be submitted
      separately once the corresponding drivers are merged.

      Due to these missing dependencies, dt-binding-check may
      report warnings or failures for this series.

  - No functional changes to RX clock inversion logic.

  - Link to v2:
    https://lore.kernel.org/lkml/20260209094628.886-1-lizhi2@eswincomputing.com/

  - This series is based on the EIC7700 clock support series:
    https://lore.kernel.org/all/20260210095008.726-1-dongxuyang@eswincomputing.com/
    The clock series is currently under review.

v1 -> v2:
  - Update eswin,eic7700-eth.yaml:
    - Drop the vendor-specific properties eswin,rx-clk-invert and
      eswin,tx-clk-invert.
    - Introduce a distinct compatible string
      "eswin,eic7700-qos-eth-clk-inversion" to describe MAC instances that
      require internal RGMII clock inversion.
      This models the SoC-specific hardware difference directly via the
      compatible string and avoids per-board configuration properties.
    - Change rx-internal-delay-ps and tx-internal-delay-ps from enum to
      minimum/maximum to reflect the actual delay range (0-2400 ps)
    - Add reference to High-Speed Subsystem documentation in eswin,hsp-sp-csr
      description. The HSP CSR block is described in Chapter 10
      ("High-Speed Interface") of the EIC7700X SoC Technical Reference Manual,
      Part 4 (EIC7700X_SoC_Technical_Reference_Manual_Part4.pdf):
      https://github.com/eswincomputing/EIC7700X-SoC-Technical-Reference-Manual/releases

  - Update dwmac-eic7700.c:
    - Remove handling of eswin,rx-clk-invert and eswin,tx-clk-invert
      properties.
    - Select RX clock inversion based on the new
      "eswin,eic7700-qos-eth-clk-inversion" compatible string, using
      match data to apply the required configuration for affected MAC
      instances (eth1).

  - Link to v1:
    https://lore.kernel.org/lkml/20260109080601.1262-1-lizhi2@eswincomputing.com/

Zhi Li (6):
  dt-bindings: ethernet: eswin: relax internal delay model to
    range-based constraints
  dt-bindings: ethernet: eswin: add EIC7700 eth1 RX clock inversion
    variant
  net: stmmac: eic7700: make RGMII delay properties optional
  net: stmmac: eic7700: add support for eth1 clock inversion variant
  dt-bindings: mfd: syscon: add ESWIN EIC7700 compatible
  riscv: dts: eswin: eic7700-hifive-premier-p550: enable Ethernet
    controller

 .../devicetree/bindings/mfd/syscon.yaml       |   2 +
 .../bindings/net/eswin,eic7700-eth.yaml       |  74 ++++--
 .../dts/eswin/eic7700-hifive-premier-p550.dts | 240 ++++++++++++++++++
 arch/riscv/boot/dts/eswin/eic7700.dtsi        | 105 ++++++++
 .../ethernet/stmicro/stmmac/dwmac-eic7700.c   | 113 ++++++++-
 5 files changed, 507 insertions(+), 27 deletions(-)

-- 
2.25.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply

* [PATCH v3 04/10] binman: Android boot image support
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

Introduce initial support for Android boot images (abootimgs).

The AOSP implementation was used as a reference for this work.

We start with just v0 and v2 support. v1/v3/v4 are deliberately left
unimplemented for now as no use-case has been found for them yet.

Since we're targeting U-Boot use cases here, a couple of things were
omitted from this impl, namely "second" and recovery_dtbo support.

Link: https://android.googlesource.com/platform/system/tools/mkbootimg/
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/etype/android_boot.py                 | 285 +++++++++++++++++++++
 tools/binman/ftest.py                              | 148 +++++++++++
 tools/binman/test/android_boot_chonky_cells.dts    |  13 +
 tools/binman/test/android_boot_dtb_in_v0.dts       |  12 +
 tools/binman/test/android_boot_invalid_addr.dts    |  13 +
 .../binman/test/android_boot_invalid_pagesize.dts  |  11 +
 tools/binman/test/android_boot_invalid_subnode.dts |  12 +
 tools/binman/test/android_boot_missing_kernel.dts  |   9 +
 .../test/android_boot_oversized_bootname.dts       |  12 +
 .../test/android_boot_unsupported_version.dts      |  11 +
 tools/binman/test/android_boot_v0.dts              |  34 +++
 .../test/android_boot_v0_pagesize_too_smol.dts     |  12 +
 tools/binman/test/android_boot_v2.dts              |  50 ++++
 tools/binman/test/android_boot_v2_missing_dtb.dts  |  12 +
 .../test/android_boot_v2_pagesize_too_smol.dts     |  13 +
 tools/binman/test/android_boot_v2_vendor_dt.dts    |  14 +
 16 files changed, 661 insertions(+)

diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
new file mode 100644
index 00000000000..8b72d90acc5
--- /dev/null
+++ b/tools/binman/etype/android_boot.py
@@ -0,0 +1,285 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Entry-type module for Android boot images
+
+import hashlib
+import struct
+
+from binman.entry import Entry
+from binman.etype.section import Entry_section
+from dtoc import fdt_util
+
+
+BOOT_MAGIC = b'ANDROID!'
+BOOT_NAME_SIZE = 16
+BOOT_ARGS_SIZE = 512
+IMAGE_ID_SIZE = 32
+BOOT_EXTRA_ARGS_SIZE = 1024
+
+BOOT_IMAGE_HEADER_V0 = '<{}s10I{}s{}s{}s'.format(len(BOOT_MAGIC),
+                                                    BOOT_NAME_SIZE,
+                                                    BOOT_ARGS_SIZE,
+                                                    IMAGE_ID_SIZE)
+BOOT_IMAGE_HEADER_V0_SIZE = struct.calcsize(BOOT_IMAGE_HEADER_V0)
+BOOT_IMAGE_HEADER_V2 = (BOOT_IMAGE_HEADER_V0 +
+                        '{}sIQIIQ'.format(BOOT_EXTRA_ARGS_SIZE))
+BOOT_IMAGE_HEADER_V2_SIZE = struct.calcsize(BOOT_IMAGE_HEADER_V2)
+
+
+class Entry_android_boot(Entry_section):
+    """Android boot image
+
+    This creates an Android v0 or v2 boot image.
+
+    A kernel payload, optional ramdisk payload can be supplied. A DTB payload
+    can also be provided when header_version == v2.
+
+    Properties / Entry arguments:
+        - header-version: Android boot image header version, must be 0 or 2,
+          defaults to 0
+        - page-size: Image page size, defaults to 2048
+        - base: Base address added to the offsets below, defaults to 0x10000000
+        - kernel-offset: Kernel load offset from base, defaults to 0x00008000
+        - ramdisk-offset: Ramdisk load offset from base, defaults to 0x01000000
+        - tags-offset: ATAGS/FDT offset from base, defaults to 0x00000100
+        - dtb-offset: DTB load offset from base, defaults to 0x01f00000
+        - os-version: Encoded Android OS version and patch level, defaults to 0
+        - boot-name: Android boot image board name
+        - cmdline: Android boot command line
+
+    This entry uses the following subnodes:
+        - kernel: section containing the executable payload
+        - dtb: section containing the DTB payload, used by header version 2 only
+        - ramdisk: optional section containing a ramdisk payload
+
+    Example::
+        A v2 abootimg with control FDT placed in the DTB section:
+
+        android-boot {
+            header-version = <2>;
+            page-size = <4096>;
+            base = <0x12345678>;
+            kernel-offset = <0xCAFED00D>;
+            ramdisk-offset = <0xBEEFBABE>;
+            tags-offset = <0xFEEDDEAD>;
+            dtb-offset = <0x06660666>;
+            cmdline = "foo bar";
+
+            kernel {
+                u-boot-nodtb {
+                    # Many Android bootloaders support gzipped kernels
+                    compress = "gzip";
+                };
+            };
+
+            dtb {
+                u-boot-dtb {
+                };
+            };
+        };
+
+    Example::
+        A v0 abootimg with embedded control FDT (v0 doesn't support DTBs) and
+        an empty ramdisk (some bootloaders insist on a ramdisk being present):
+
+        android-boot {
+            header-version = <0>;
+            page-size = <2048>;
+            base = <0x80200000>;
+
+            kernel {
+                u-boot {
+                    no-expanded;
+                };
+            };
+
+            ramdisk {
+                fill {
+                    size = <1>;
+                };
+            };
+        };
+    """
+
+    def ReadNode(self):
+        super().ReadNode()
+        self.header_version = fdt_util.GetInt(self._node, 'header-version', 0)
+        self.page_size = fdt_util.GetInt(self._node, 'page-size', 2048)
+        self.base = self._GetIntCells('base', 0x10000000)
+        self.kernel_offset = self._GetIntCells('kernel-offset', 0x00008000)
+        self.ramdisk_offset = self._GetIntCells('ramdisk-offset', 0x01000000)
+        self.tags_offset = self._GetIntCells('tags-offset', 0x00000100)
+        self.dtb_offset = self._GetIntCells('dtb-offset', 0x01f00000)
+        self.os_version = fdt_util.GetInt(self._node, 'os-version', 0)
+        self.boot_name = fdt_util.GetString(self._node, 'boot-name', '')
+        self.cmdline = fdt_util.GetString(self._node, 'cmdline', '')
+
+        if self.header_version not in (0, 2):
+            self.Raise('Only Android boot image header versions 0 and 2 are '
+                       'supported')
+        if self.page_size <= 0 or self.page_size & (self.page_size - 1):
+            self.Raise('page-size must be a power of two')
+        if 'kernel' not in self._entries:
+            self.Raise("Missing required subnode 'kernel'")
+
+        if self.header_version == 0:
+            if self.page_size < BOOT_IMAGE_HEADER_V0_SIZE:
+                self.Raise('page-size must fit the Android boot image header')
+            if 'dtb' in self._entries:
+                self.Raise("Subnode 'dtb' requires header-version 2")
+        else:
+            # v2
+            if self.page_size < BOOT_IMAGE_HEADER_V2_SIZE:
+                self.Raise('page-size must fit the Android boot image header')
+            if 'dtb' not in self._entries:
+                self.Raise("Missing required subnode 'dtb'")
+
+    def ReadEntries(self):
+        for node in self._node.subnodes:
+            if node.name not in ('kernel', 'ramdisk', 'dtb'):
+                self.Raise("Unexpected subnode '%s'" % node.name)
+
+            entry = Entry.Create(self, node, etype='section',
+                                 expanded=self.GetImage().use_expanded,
+                                 missing_etype=self.GetImage().missing_etype)
+            entry.ReadNode()
+            entry.SetPrefix(self._name_prefix)
+            self._entries[node.name] = entry
+
+    def _GetIntCells(self, propname, default):
+        prop = self._node.props.get(propname)
+        if not prop:
+            return default
+
+        values = prop.value if isinstance(prop.value, list) else [prop.value]
+        if len(values) > 2:
+            self.Raise("Property '%s' must contain one or two cells" %
+                       propname)
+
+        value = 0
+        for cell in values:
+            value = value << 32 | fdt_util.fdt32_to_cpu(cell)
+
+        return value
+
+    def _GetAddr(self, offset, name, size=32):
+        addr = self.base + offset
+        if addr >= 1 << size:
+            self.Raise('%s address %#x does not fit in %d bits' %
+                       (name, addr, size))
+
+        return addr
+
+    def _CheckFit(self, name, data, size):
+        if len(data) > size:
+            self.Raise('%s is %d bytes, maximum is %d' %
+                       (name, len(data), size))
+
+        return data + b'\0' * (size - len(data))
+
+    @staticmethod
+    def _BootId(*payloads):
+        digest = hashlib.sha1()
+        for data in payloads:
+            digest.update(data)
+            digest.update(struct.pack('<I', len(data)))
+
+        return digest.digest() + b'\0' * 12
+
+    def _SplitCmdline(self):
+        cmdline = self.cmdline.encode('ascii') + b'\0'
+        return (self._CheckFit('cmdline', cmdline[:BOOT_ARGS_SIZE],
+                               BOOT_ARGS_SIZE),
+                self._CheckFit('extra-cmdline', cmdline[BOOT_ARGS_SIZE:],
+                               BOOT_EXTRA_ARGS_SIZE))
+
+    def _GetEntryData(self, name, required, default=None):
+        entry = self._entries.get(name)
+        if not entry and default is not None:
+            return default
+        return entry.GetData(required)
+
+    def _BuildV0SectionData(self, required):
+        kernel = self._GetEntryData('kernel', required)
+        ramdisk = self._GetEntryData('ramdisk', required, b'')
+        if not required and (kernel is None or vendor_dt is None or
+                             ramdisk is None):
+            return None
+
+        boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
+                                   BOOT_NAME_SIZE)
+        cmdline = self._CheckFit('cmdline', self.cmdline.encode('ascii'),
+                                 BOOT_ARGS_SIZE)
+
+        boot_id_payloads = [kernel, ramdisk, b'']
+        image_id = self._BootId(*boot_id_payloads)
+
+        header = struct.pack(BOOT_IMAGE_HEADER_V0,
+                             BOOT_MAGIC,
+                             len(kernel),
+                             self._GetAddr(self.kernel_offset, 'kernel'),
+                             len(ramdisk),
+                             self._GetAddr(self.ramdisk_offset, 'ramdisk'),
+                             0, # second_len
+                             0, # second_offset
+                             self._GetAddr(self.tags_offset, 'tags'),
+                             self.page_size,
+                             self.header_version,
+                             self.os_version,
+                             boot_name,
+                             cmdline,
+                             image_id)
+
+        image = bytearray()
+        image += self.PadToAlignment(header, self.page_size)
+        image += self.PadToAlignment(kernel, self.page_size)
+        image += self.PadToAlignment(ramdisk, self.page_size)
+
+        return bytes(image)
+
+    def _BuildV2SectionData(self, required):
+        kernel = self._GetEntryData('kernel', required)
+        dtb = self._GetEntryData('dtb', required)
+        ramdisk = self._GetEntryData('ramdisk', required, b'')
+        if not required and (kernel is None or dtb is None):
+            return None
+
+        boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
+                                   BOOT_NAME_SIZE)
+        cmdline, extra_cmdline = self._SplitCmdline()
+        image_id = self._BootId(kernel, ramdisk, b'', b'', dtb)
+
+        header = struct.pack(BOOT_IMAGE_HEADER_V2,
+                             BOOT_MAGIC,
+                             len(kernel),
+                             self._GetAddr(self.kernel_offset, 'kernel'),
+                             len(ramdisk),
+                             self._GetAddr(self.ramdisk_offset, 'ramdisk'),
+                             0, # second_len
+                             0, # second_offset
+                             self._GetAddr(self.tags_offset, 'tags'),
+                             self.page_size,
+                             self.header_version,
+                             self.os_version,
+                             boot_name,
+                             cmdline,
+                             image_id,
+                             extra_cmdline,
+                             0, # recovery_dtbo_len
+                             0, # recovery_dtbo_offset
+                             BOOT_IMAGE_HEADER_V2_SIZE,
+                             len(dtb),
+                             self._GetAddr(self.dtb_offset, 'dtb', size=64))
+
+        image = bytearray()
+        image += self.PadToAlignment(header, self.page_size)
+        image += self.PadToAlignment(kernel, self.page_size)
+        image += self.PadToAlignment(ramdisk, self.page_size)
+        image += self.PadToAlignment(dtb, self.page_size)
+
+        return bytes(image)
+
+    def BuildSectionData(self, required):
+        if self.header_version == 0:
+            return self._BuildV0SectionData(required)
+
+        return self._BuildV2SectionData(required)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index bf98b268ac1..71740205c72 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5598,6 +5598,154 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
         self.assertIn("Node '/binman/renesas-rcar4-sa0': SRAM data longer than 966656 Bytes",
                       str(exc.exception))
 
+    @staticmethod
+    def _AndroidBootId(*payloads):
+        digest = hashlib.sha1()
+        for data in payloads:
+            digest.update(data)
+            digest.update(struct.pack('<I', len(data)))
+
+        return digest.digest() + b'\0' * 12
+
+    def testAndroidBootUnsupportedVersion(self):
+        """Test that binman rejects versions other than v0 and v2"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_unsupported_version.dts')
+        self.assertIn("Only Android boot image header versions 0 and 2 are supported",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidPageSize(self):
+        """Test that binman rejects page sizes that are not a power of 2"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testAndroidBootV0PageSizeTooSmol(self):
+        """Test that binman rejects page sizes that are smaller than header size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v0_pagesize_too_smol.dts')
+        self.assertIn("page-size must fit the Android boot image header",
+                      str(exc.exception))
+
+    def testAndroidBootMissingKernel(self):
+        """Test that binman rejects configurations missing a kernel{} subnode"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_missing_kernel.dts')
+        self.assertIn("Missing required subnode 'kernel'",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidSubnode(self):
+        """Test that binman rejects invalid subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_subnode.dts')
+        self.assertIn("Unexpected subnode 'bacon'",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidAddr(self):
+        """Test that binman rejects invalid addresses"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_addr.dts')
+        self.assertIn("kernel address 0xdeadbeefdafed00d does not fit in 32 bits",
+                      str(exc.exception))
+
+    def testAndroidBootOversizedBootName(self):
+        """Test that binman rejects boot-name exceeding 16 chars"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_oversized_bootname.dts')
+        self.assertIn("boot-name is 38 bytes, maximum is 16",
+                      str(exc.exception))
+
+    def testAndroidBootChonkyCells(self):
+        """Test that binman rejects >2 cell addresses"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_chonky_cells.dts')
+        self.assertIn("Property 'base' must contain one or two cells",
+                      str(exc.exception))
+
+    def testAndroidBootV0(self):
+        """Test that binman can produce a plain legacy Android boot image"""
+        data = self._DoReadFile('android_boot_v0.dts')
+        header = struct.unpack_from('<8s10I16s512s32s', data, 0)
+
+        self.assertEqual(b'ANDROID!', header[0])
+        self.assertEqual(len(U_BOOT_DATA), header[1])
+        self.assertEqual(0x80208000, header[2])
+        self.assertEqual(1, header[3])
+        self.assertEqual(0x81200000, header[4])
+        self.assertEqual(0, header[5])
+        self.assertEqual(0, header[6])
+        self.assertEqual(0x80200100, header[7])
+        self.assertEqual(0x800, header[8])
+        self.assertEqual(0, header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(b'foo', header[12].split(b'\0', 1)[0])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'\0', b''),
+                         header[13])
+
+    def testAndroidBootV0WithDTB(self):
+        """Test that binman rejects v0 abootimgs containing a dtb section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_dtb_in_v0.dts')
+        self.assertIn("Subnode 'dtb' requires header-version 2",
+                      str(exc.exception))
+
+    def testAndroidBootV2(self):
+        """Test that binman can produce an Android boot image"""
+        data = self._DoReadFile('android_boot_v2.dts')
+        header = struct.unpack_from('<8s10I16s512s32s1024sIQIIQ', data, 0)
+
+        self.assertEqual(b'ANDROID!', header[0])
+        self.assertEqual(len(U_BOOT_DATA), header[1])
+        self.assertEqual(0x80008000, header[2])
+        self.assertEqual(0, header[3])
+        self.assertEqual(0x81000000, header[4])
+        self.assertEqual(0, header[5])
+        self.assertEqual(0, header[6])
+        self.assertEqual(0x80000100, header[7])
+        self.assertEqual(0x800, header[8])
+        self.assertEqual(2, header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(b'test-board', header[11].split(b'\0', 1)[0])
+        self.assertEqual(0, header[15])
+        self.assertEqual(0, header[16])
+        self.assertEqual(1660, header[17])
+        self.assertEqual(len(U_BOOT_DTB_DATA), header[18])
+        self.assertEqual(0x81f00000, header[19])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'', b'', b'',
+                                             U_BOOT_DTB_DATA), header[13])
+
+        cmdline = header[12].split(b'\0', 1)[0]
+        extra_cmdline = header[14].split(b'\0', 1)[0]
+        self.assertEqual(b"tests.. ", cmdline[-8:])
+        self.assertEqual(512, len(cmdline))
+        self.assertEqual(b'sup', extra_cmdline)
+
+        self.assertEqual(U_BOOT_DATA, data[0x800:0x800 + len(U_BOOT_DATA)])
+        self.assertEqual(U_BOOT_DTB_DATA,
+                         data[0x1000:0x1000 + len(U_BOOT_DTB_DATA)])
+
+    def testAndroidBootV2PageSizeTooSmol(self):
+        """Test that binman rejects page sizes that are smaller than header size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_pagesize_too_smol.dts')
+        self.assertIn("page-size must fit the Android boot image header",
+                      str(exc.exception))
+
+    def testAndroidBootV2MissingDTB(self):
+        """Test that binman rejects v2 abootimgs missing a DTB section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_missing_dtb.dts')
+        self.assertIn("Missing required subnode 'dtb'",
+                      str(exc.exception))
+
+    def testAndroidBootV2VendorDt(self):
+        """Test that binman rejects v2 abootimgs with a vendor-dt section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_vendor_dt.dts')
+        self.assertIn("Subnode 'vendor-dt' requires header-version 0",
+                      str(exc.exception))
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/android_boot_chonky_cells.dts b/tools/binman/test/android_boot_chonky_cells.dts
new file mode 100644
index 00000000000..7fdc1c86f6b
--- /dev/null
+++ b/tools/binman/test/android_boot_chonky_cells.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			base = <0xDEADBEEF 0xCAFED00D 0xDECAF>;
+
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_dtb_in_v0.dts b/tools/binman/test/android_boot_dtb_in_v0.dts
new file mode 100644
index 00000000000..24b91f9a33c
--- /dev/null
+++ b/tools/binman/test/android_boot_dtb_in_v0.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel {};
+			dtb {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_addr.dts b/tools/binman/test/android_boot_invalid_addr.dts
new file mode 100644
index 00000000000..0d7cb051921
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_addr.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel-offset = <0xDEADBEEF 0xCAFED00D>;
+
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_pagesize.dts b/tools/binman/test/android_boot_invalid_pagesize.dts
new file mode 100644
index 00000000000..01925187475
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_pagesize.dts
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			page-size = <2049>;
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_subnode.dts b/tools/binman/test/android_boot_invalid_subnode.dts
new file mode 100644
index 00000000000..747f95068be
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_subnode.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel {};
+			bacon {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_missing_kernel.dts b/tools/binman/test/android_boot_missing_kernel.dts
new file mode 100644
index 00000000000..fe30eb5cbb3
--- /dev/null
+++ b/tools/binman/test/android_boot_missing_kernel.dts
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {};
+	};
+};
diff --git a/tools/binman/test/android_boot_oversized_bootname.dts b/tools/binman/test/android_boot_oversized_bootname.dts
new file mode 100644
index 00000000000..5f5564840f8
--- /dev/null
+++ b/tools/binman/test/android_boot_oversized_bootname.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			boot-name = "this is decidedly longer than 16 bytes";
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_unsupported_version.dts b/tools/binman/test/android_boot_unsupported_version.dts
new file mode 100644
index 00000000000..9843b368b3a
--- /dev/null
+++ b/tools/binman/test/android_boot_unsupported_version.dts
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <1>;
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v0.dts b/tools/binman/test/android_boot_v0.dts
new file mode 100644
index 00000000000..18813ff3613
--- /dev/null
+++ b/tools/binman/test/android_boot_v0.dts
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that android-boot can be referenced before it's built */
+		collection {
+			content = <&abootimg>;
+		};
+
+		abootimg: android-boot {
+			header-version = <0>;
+			page-size = <0x800>;
+			base = <0x80200000>;
+			cmdline = "foo";
+
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+
+			ramdisk {
+				fill {
+					size = <1>;
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v0_pagesize_too_smol.dts b/tools/binman/test/android_boot_v0_pagesize_too_smol.dts
new file mode 100644
index 00000000000..2c617f12a1e
--- /dev/null
+++ b/tools/binman/test/android_boot_v0_pagesize_too_smol.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			page-size = <32>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2.dts b/tools/binman/test/android_boot_v2.dts
new file mode 100644
index 00000000000..55fab329443
--- /dev/null
+++ b/tools/binman/test/android_boot_v2.dts
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#define CMDLINE(...) #__VA_ARGS__
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that android-boot can be referenced before it's built */
+		collection {
+			content = <&abootimg>;
+		};
+
+		abootimg: android-boot {
+			header-version = <2>;
+			page-size = <0x800>;
+			base = <0x80000000>;
+			kernel-offset = <0x00008000>;
+			ramdisk-offset = <0x01000000>;
+			tags-offset = <0x00000100>;
+			dtb-offset = <0x01f00000>;
+			boot-name = "test-board";
+			cmdline = CMDLINE(
+					  This is a very long commandline that is sure to exceed the
+					  512 chars that is allotted to the cmdline and this should
+					  spillover into extra_cmdline which is useful from a
+					  function testing standpoint. Gosh, it sure it hard to come
+					  up with enough filler text here to get over the 512 char
+					  limit though, huh? Even for someone as loquacious as
+					  myself. So anyway. How's your day going? I wrote a binman
+					  functional test today. It was fun. Did you know that
+					  binman is great. I like binman. I also like functional
+					  tests.. sup);
+
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+
+			dtb {
+				u-boot-dtb {
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_missing_dtb.dts b/tools/binman/test/android_boot_v2_missing_dtb.dts
new file mode 100644
index 00000000000..bf7bee622c4
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_missing_dtb.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_pagesize_too_smol.dts b/tools/binman/test/android_boot_v2_pagesize_too_smol.dts
new file mode 100644
index 00000000000..0761ff20543
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_pagesize_too_smol.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			page-size = <32>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_vendor_dt.dts b/tools/binman/test/android_boot_v2_vendor_dt.dts
new file mode 100644
index 00000000000..a7684d8492a
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_vendor_dt.dts
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			kernel {};
+			dtb {};
+			vendor-dt {};
+		};
+	};
+};

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 00/10] Generate Android boot images with binman
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day

U-Boot is seeing increasing adoption on pocket computers, many of which
(sadly) have fused bootloader chains. Many of these bootloaders have a
very rigid definition of what a "valid" downstream payload looks like.
Sometimes it's just a boring old v0/v2 Android boot image. Sometimes
it's something decidedly more grotesque.

To date, this last-mile packaging step has been trapped in downstream CI
pipelines, blogposts, documentation, etc. This patch series aims to
gather up all that esoterica and institutional knowledge and codify it
in U-Boot's build system, using binman.

Put differently: an overwhelming majority of these pocket computer
devices have a "canonical" payload format that U-Boot currently has no
support for. Let's fix that.

The first patches in this series introduce an `android_boot` etype. To
begin with, this is a "typical" abootimg, as defined by canonical AOSP
sources and reference `fastboot`/`mkbootimg` implementation. There's
plenty of devices out there with a sane(-ish, nothing in Android-land is
ever truly sane) bootloader that will happily chain a U-Boot rolled into
the kernel section of an abootimg.

With that out of the way, the cursed bootloaders are next to be
supported. Binman etypes for Qualcomm's "QCDT" and Samsung's "DTBH"
formats are implemented. These are non-standard vendor-specific
devicetree containers, which the previous bootloader uses to pick a FDT
to boot the downstream with.

In both cases, these vendor-specific formats are tacked on to the end of
a v0 abootimg, with the header_version being hijacked to encode the
length of this following payload. Thus, the android_boot etype is
retrofitted to allow these shenanigans.

Binman configs that produce flashable boot artifacts are introduced for
the following devices:

 * google-sargo (vanilla v2)
 * oneplus-fajita (vanilla v2)
 * samsung-a5u-eur (QCDT v0)
 * samsung-gt510 (QCDT v0)
 * samsung-j7xelte (DTBH v0)

I also successfully tested a vanilla v0 on my samsung-expressltexx, in
conjunction with the qcom-armv7 series already on the list. However,
since that work is still in-flight and the expressltexx DTS is
downstream, I opted not to include it here.

Note that, I dropped the commits that introduce this support for
Qualcomm devices, as it was NAK'd by "Qualcomm". I still think this is
worth inclusion upstream for a couple of reasons:

 * It's still useful for Exynos devices.
 * If/when other non-qcom devices with fused Android bootloaders come
   along, it will likely be useful there as well.
 * Downstreams can easily carry DTSI overlays for qcom devices to take
   advantage of this new native U-Boot support there.
 * Maybe the hard position against binman will change/soften on the
   Qualcomm side, in which case we can simply start dropping in DTSIs to
   add support for those devices.

Signed-off-by: Sam Day <me@samcday.com>
---
Changes in v3:
- android_boot etype:
  - collapse _GetOptionalEntryData into _GetEntryData
  - use self.Raise consistently for sane errors
  - clean up and expand tests for 100% coverage
  - fix docstring examples formatting
  - remove unnecessary restriction on os-version in conjunction with
    vendor-dt
- introduce Entry_Android_vendor_dt_table shared base class for
  QCDT+DTBH.
- qcdt+dtbh etypes:
  - formalize header/record format/size constants
  - rework tests and hit 100% coverage
- binman:
  - fix support for listing multiple names to `binman test`
  - add fnmatch globbing support to `binman test`
- Link to v2: https://lore.kernel.org/r/20260608-android-binman-v2-0-b8203682507f@samcday.com

Changes in v2:
- Rebase on next
- Drop all qcom-related work as it has been NAK'd from mach-snapdragon
- Link to v1: https://lore.kernel.org/r/20260606-android-binman-v1-0-c7a3c14b8a3d@samcday.com

---
Sam Day (10):
      binman: support running multiple tests
      binman: test name globbing support
      binman: section: add AlignUp+PadToAlignment helpers
      binman: Android boot image support
      .gitignore: ignore binman-generated blobs
      binman: android_boot: vendor-dt support
      binman: Add QCDT support
      binman: Add DTBH support
      arch: arm: exynos: add j7xelte binman config
      configs: exynos-mobile: pull in binman

 .gitignore                                         |   1 +
 arch/arm/dts/exynos-mobile.dtsi                    |   5 +
 arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi        |  26 ++
 arch/arm/mach-exynos/Kconfig                       |   1 +
 configs/exynos-mobile_defconfig                    |   1 +
 tools/binman/android_vendor_dt_table.py            | 104 ++++++
 tools/binman/cmdline.py                            |   2 +-
 tools/binman/etype/android_boot.py                 | 376 +++++++++++++++++++++
 tools/binman/etype/dtbh.py                         | 108 ++++++
 tools/binman/etype/qcdt.py                         |  80 +++++
 tools/binman/etype/section.py                      |   9 +
 tools/binman/ftest.py                              | 365 ++++++++++++++++++++
 tools/binman/main.py                               |   8 +-
 tools/binman/test/android_boot_chonky_cells.dts    |  13 +
 tools/binman/test/android_boot_dtb_in_v0.dts       |  12 +
 tools/binman/test/android_boot_invalid_addr.dts    |  13 +
 .../binman/test/android_boot_invalid_pagesize.dts  |  11 +
 tools/binman/test/android_boot_invalid_subnode.dts |  12 +
 tools/binman/test/android_boot_missing_kernel.dts  |   9 +
 .../test/android_boot_oversized_bootname.dts       |  12 +
 .../test/android_boot_unsupported_version.dts      |  11 +
 tools/binman/test/android_boot_v0.dts              |  34 ++
 .../test/android_boot_v0_pagesize_too_smol.dts     |  12 +
 tools/binman/test/android_boot_v2.dts              |  50 +++
 tools/binman/test/android_boot_v2_missing_dtb.dts  |  12 +
 .../test/android_boot_v2_pagesize_too_smol.dts     |  13 +
 tools/binman/test/android_boot_v2_vendor_dt.dts    |  14 +
 tools/binman/test/android_boot_vendor_dt.dts       |  27 ++
 tools/binman/test/dtbh.dts                         |  22 ++
 tools/binman/test/dtbh_bad_model_info.dts          |  20 ++
 tools/binman/test/dtbh_invalid_pagesize.dts        |  12 +
 tools/binman/test/dtbh_missing_model_info.dts      |  16 +
 tools/binman/test/dtbh_missing_payload.dts         |  15 +
 tools/binman/test/dtbh_missing_subnodes.dts        |  10 +
 tools/binman/test/dtbh_multi.dts                   |  29 ++
 tools/binman/test/dtbh_multiple_dtbs.dts           |  22 ++
 tools/binman/test/dtbh_page_size_from_abootimg.dts |  30 ++
 tools/binman/test/dtbh_special_subnodes.dts        |  11 +
 tools/binman/test/qcdt.dts                         |  36 ++
 tools/binman/test/qcdt_bad_msm_id.dts              |  17 +
 tools/binman/test/qcdt_invalid_pagesize.dts        |  12 +
 tools/binman/test/qcdt_missing_msm_id.dts          |  12 +
 tools/binman/test/qcdt_missing_payload.dts         |  14 +
 tools/binman/test/qcdt_missing_subnodes.dts        |  13 +
 tools/binman/test/qcdt_multiple_dtbs.dts           |  34 ++
 tools/binman/test/qcdt_page_size_from_abootimg.dts |  33 ++
 tools/binman/test/qcdt_zero_pagesize.dts           |  12 +
 tools/u_boot_pylib/test_util.py                    |  25 +-
 48 files changed, 1724 insertions(+), 12 deletions(-)
---
base-commit: e91911169bc737ee4a79963a1cba8db2aab7c1c0
change-id: 20260604-android-binman-ad7a43f4e99d

Best regards,
-- 
Sam Day <me@samcday.com>



^ permalink raw reply

* [PATCH net-next v8 0/6] net: stmmac: eic7700: add eth1 variant support and update delay bindings
From: lizhi2 @ 2026-06-10  1:27 UTC (permalink / raw)
  To: devicetree, andrew+netdev, davem, edumazet, kuba, robh, krzk+dt,
	conor+dt, netdev, pabeni, mcoquelin.stm32, alexandre.torgue,
	rmk+kernel, pjw, palmer, aou, alex, linux-riscv, linux-stm32,
	linux-arm-kernel, linux-kernel, maxime.chevallier
  Cc: ningyu, linmin, pinkesh.vaghela, pritesh.patel, weishangjuan,
	horms, lee, Zhi Li

From: Zhi Li <lizhi2@eswincomputing.com>

v7 -> v8:
  - eth0-related fixes were moved into separate series [1], [2]:
    - All eth0-related fixes have been removed from this series to avoid mixing
      MAC variants and RX timing logic in a single review context.
    - Reference:
      [1]https://lore.kernel.org/lkml/20260602014528.2076-1-lizhi2@eswincomputing.com/
      [2]https://lore.kernel.org/lkml/20260518021919.404-1-lizhi2@eswincomputing.com/

  - Update cover letter and overall series scope description:
    - Replace previous wording "EIC7700 eth1 RX sampling timing fix"
      with a more accurate description:
        - Add eth1 MAC variant support.
        - Update RGMII delay binding model.
    - This reflects the structural nature of the series rather than a pure
      bug fix.

  -  Split DT bindings changes into two patches:
    - patch1:
        - Relax RGMII internal delay constraints.
        - Change rx/tx internal delay from enum-based model to range-based
          model.
        - Mark delay properties as optional.
    - patch2:
        - Introduce EIC7700 eth1 MAC variant compatible string
          "eswin,eic7700-qos-eth-clk-inversion".
        - Model silicon-specific RX clock inversion requirement via SoC
          variant instead of board-level properties.

    - Due to this restructuring:
        - Patch structure and commit messages have changed significantly
          compared to v7.
        - The previously received Acked-by from Conor Dooley is not
          carried forward because the binding patches were substantially
          reworked and split.

  - Split driver changes into two patches. No functional changes to eth1
    compared to v7:
    - patch 3:
        - Make rx-internal-delay-ps and tx-internal-delay-ps optional.
        - Remove mandatory DT property requirement in probe path.
        - Allow zero-delay default when properties are absent.
    - patch 4:
        - Add support for eth1 MAC variant using compatible-specific
          match data.
        - Introduce RX clock inversion handling for eth1 at runtime.
        - Apply speed-dependent configuration via fix_mac_speed()
          callback.

  - Note:
    - These patches (5/6 and 6/6) are included only to facilitate review
      of the overall Ethernet integration across bindings, driver, and
      device tree.
      A cleaned-up, upstream-ready DTS series will be submitted separately
      once all dependencies and final hardware integration are completed.

  - Link to v7:
    https://lore.kernel.org/lkml/20260427072353.1114-1-lizhi2@eswincomputing.com/

v6 -> v7:
  - Address checkpatch.pl --strict warnings for DTS changes:
    - Split DT binding documentation and DTS board description into separate patches
    - Fix DTS style issues reported by checkpatch:
      - Reduce line length where applicable
      - Add required description for rgmii-rxid

  - DTS changes in this series are split into:
    - Patch 3/4: syscon binding update (documentation / reference only)
    - Patch 4/4: board DTS changes (architecture overview only)

    These patches (3/4 and 4/4) are provided to facilitate review of the overall
    Ethernet integration across binding, driver, and device tree, and are not
    intended as final upstream submission in their current form.

    A cleaned-up, upstream-ready DTS series will be submitted separately once
    all dependencies and final hardware integration are completed.

  - Note:
    - Clock-related bindings referenced in earlier revisions are now already merged
      into net-next, so dtbs_check warnings related to clock are no longer present
      and are not relevant to this revision.

  - No functional changes in the stmmac driver or binding semantics in this revision.

  - Link to v6:
    https://lore.kernel.org/lkml/20260423085501.760-1-lizhi2@eswincomputing.com/

v5 -> v6:
  - Update DTS/DTSI descriptions to fix invalid phandle references reported by DTC:
    - Add missing GMAC provider nodes required for proper hardware description:
      - HSP power domain: GMAC nodes moved under this domain to reflect
        hardware power hierarchy.
      - Clock nodes: added to provide clk phandles referenced by GMAC.
      - Reset nodes: added to provide reset phandles referenced by GMAC.
      - Pinctrl nodes: defines pinctrl settings for GMAC signals
        (pinctrl_gpio106, pinctrl_gpio111).
    - Move GMAC nodes under the correct HSP power domain.
    - Ensure DTS builds without dtc errors and all phandle references
      (clk/reset/pinctrl/power-domain) are valid.
    - This update does not change runtime behavior; it only improves DTS
      consistency and resolves issues reported by dtc.

  - Note:
    - The patch 3/3 for DTS changes in this series provide an overview of the GMAC
      integration and its dependencies, as discussed previously:
      https://lore.kernel.org/lkml/64bf6b40-b947-4ffa-8d48-4d6341931327@lunn.ch/

    - It is **not intended for upstream inclusion** in its current form,
      and is provided solely for architecture overview and integration
      context.

    - A fully cleaned and upstream-ready DTS series will be submitted
      separately once all related components (pinctrl, clock, power-domain,
      etc.) are finalized.

  - dtbs_check has been run on top of net-next for reference purposes.
    Remaining warnings are expected due to missing EIC7700 clock bindings[1]
    in net-next and do not reflect issues in the DTS design itself.

  - One remaining warning:
    - eswin,eic7700-clock

  - The clock binding has already been applied to upstream and is present
    in mainline, but not yet available in net-next.

  - The syscon binding is extended in this series to include the
    eswin,eic7700-syscfg compatible.

  - Any further refinement of the syscfg binding will be handled in
    separate patches if needed.

  - Dependencies:
    - [1]EIC7700 clock binding:
      https://lore.kernel.org/lkml/20260303080637.2100-1-dongxuyang@eswincomputing.com/
      (already applied to upstream)

  - Link to v5:
    https://lore.kernel.org/lkml/20260324073017.376-1-lizhi2@eswincomputing.com/

v4 -> v5:
  - eswin,eic7700-eth.yaml:
    - Add Acked-by from Conor Dooley
    - No functional changes

  - Update dwmac-eic7700.c:
    - Disable clocks on the error path to fix a clock leak in
      eic7700_dwmac_init() when regmap_set_bits() fails
      (reported by Simon Horman <horms@kernel.org>)

  - Link to v4:
    https://lore.kernel.org/lkml/20260313075234.1567-1-lizhi2@eswincomputing.com/

v3 -> v4:
  - Update eswin,eic7700-eth.yaml:
    - Improve commit message in dt-bindings patch to clarify the
      hardware difference of the eth1 MAC and why a new compatible
      string is required.
    - Move the newly added eswin,hsp-sp-csr item to the end of the list
      to avoid inserting entries in the middle of the binding schema.
    - Simplify the compatible schema by replacing the previous oneOf
      construct with an enum.

  - Update dwmac-eic7700.c:
    - Fix build issues.
    - Adjust code to match the updated binding definition.

  - Update DTS/DTSI descriptions:
    - Move SoC-level descriptions to the .dtsi file.
    - Keep board-specific configuration in the .dts file.

  - Link to v3:
    https://lore.kernel.org/lkml/20260303061525.846-1-lizhi2@eswincomputing.com/

v2 -> v3:
  - Update eswin,eic7700-eth.yaml:
    - Extend rx-internal-delay-ps and tx-internal-delay-ps range
      from 0-2400 to 0-2540 to match the full 7-bit hardware delay
      field (127 * 20 ps).
    - Add "multipleOf: 20" constraint to reflect the 20 ps hardware
      step size.
    - Make rx-internal-delay-ps and tx-internal-delay-ps optional.
      A well-designed board should not require internal delay tuning.
    - Remove rx-internal-delay-ps and tx-internal-delay-ps from the
      example to avoid encouraging blind copy into board DTs.

  - Update dwmac-eic7700.c:
    - Treat rx-internal-delay-ps and tx-internal-delay-ps as optional
      DT properties.
    - Apply delay configuration only when properties are present.
    - Keep TX/RX delay registers cleared by default to ensure a
      deterministic state when no delay is specified.

  - Describe Ethernet configuration for the HiFive Premier P550 board:
    - Add GMAC controller nodes for the HiFive Premier P550 board
      to describe the on-board Ethernet configuration.

      The Ethernet controller depends on clock, reset, pinctrl
      and HSP subsystem providers which are currently under
      upstream review. These dependent nodes will be submitted
      separately once the corresponding drivers are merged.

      Due to these missing dependencies, dt-binding-check may
      report warnings or failures for this series.

  - No functional changes to RX clock inversion logic.

  - Link to v2:
    https://lore.kernel.org/lkml/20260209094628.886-1-lizhi2@eswincomputing.com/

  - This series is based on the EIC7700 clock support series:
    https://lore.kernel.org/all/20260210095008.726-1-dongxuyang@eswincomputing.com/
    The clock series is currently under review.

v1 -> v2:
  - Update eswin,eic7700-eth.yaml:
    - Drop the vendor-specific properties eswin,rx-clk-invert and
      eswin,tx-clk-invert.
    - Introduce a distinct compatible string
      "eswin,eic7700-qos-eth-clk-inversion" to describe MAC instances that
      require internal RGMII clock inversion.
      This models the SoC-specific hardware difference directly via the
      compatible string and avoids per-board configuration properties.
    - Change rx-internal-delay-ps and tx-internal-delay-ps from enum to
      minimum/maximum to reflect the actual delay range (0-2400 ps)
    - Add reference to High-Speed Subsystem documentation in eswin,hsp-sp-csr
      description. The HSP CSR block is described in Chapter 10
      ("High-Speed Interface") of the EIC7700X SoC Technical Reference Manual,
      Part 4 (EIC7700X_SoC_Technical_Reference_Manual_Part4.pdf):
      https://github.com/eswincomputing/EIC7700X-SoC-Technical-Reference-Manual/releases

  - Update dwmac-eic7700.c:
    - Remove handling of eswin,rx-clk-invert and eswin,tx-clk-invert
      properties.
    - Select RX clock inversion based on the new
      "eswin,eic7700-qos-eth-clk-inversion" compatible string, using
      match data to apply the required configuration for affected MAC
      instances (eth1).

  - Link to v1:
    https://lore.kernel.org/lkml/20260109080601.1262-1-lizhi2@eswincomputing.com/

Zhi Li (6):
  dt-bindings: ethernet: eswin: relax internal delay model to
    range-based constraints
  dt-bindings: ethernet: eswin: add EIC7700 eth1 RX clock inversion
    variant
  net: stmmac: eic7700: make RGMII delay properties optional
  net: stmmac: eic7700: add support for eth1 clock inversion variant
  dt-bindings: mfd: syscon: add ESWIN EIC7700 compatible
  riscv: dts: eswin: eic7700-hifive-premier-p550: enable Ethernet
    controller

 .../devicetree/bindings/mfd/syscon.yaml       |   2 +
 .../bindings/net/eswin,eic7700-eth.yaml       |  74 ++++--
 .../dts/eswin/eic7700-hifive-premier-p550.dts | 240 ++++++++++++++++++
 arch/riscv/boot/dts/eswin/eic7700.dtsi        | 105 ++++++++
 .../ethernet/stmicro/stmmac/dwmac-eic7700.c   | 113 ++++++++-
 5 files changed, 507 insertions(+), 27 deletions(-)

-- 
2.25.1



^ permalink raw reply

* [PATCH v3 09/10] arch: arm: exynos: add j7xelte binman config
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

Note that, as of this commit, j7xelte does not yet exist in U-Boot's
upstream DTS tree. It was accepted into next so it should appear here
eventually.

Link: https://lore.kernel.org/all/177209522223.26390.6219893536178441080.b4-ty@kernel.org/
Signed-off-by: Sam Day <me@samcday.com>
---
 arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi b/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi
new file mode 100644
index 00000000000..0cd4c12a2e4
--- /dev/null
+++ b/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/ {
+	binman {
+		filename = "u-boot-samsung-j7xelte.img";
+
+		android-boot {
+			/* S-BOOT ignores offsets, so they are not provided here */
+			kernel {
+				u-boot-nodtb { };
+			};
+
+			vendor-dt {
+				dtbh {
+					dtb-0 {
+						model_info-chip = <7870>;
+						model_info-hw_rev = <0>;
+						model_info-hw_rev_end = <255>;
+
+						u-boot-dtb { };
+					};
+				};
+			};
+		};
+	};
+};

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 01/10] binman: support running multiple tests
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

The helptext for `binman test` suggests that this was already supported,
but it wasn't properly wired up.
---
 tools/binman/main.py            |  8 ++++----
 tools/u_boot_pylib/test_util.py | 23 ++++++++++++++++-------
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/tools/binman/main.py b/tools/binman/main.py
index a0c775fd629..5473789361d 100755
--- a/tools/binman/main.py
+++ b/tools/binman/main.py
@@ -58,8 +58,8 @@ def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath):
             the output directory for this test. Both directories are displayed
             on the command line.
         processes: Number of processes to use to run tests (None=same as #CPUs)
-        args: List of positional args provided to binman. This can hold a test
-            name to execute (as in 'binman test testSections', for example)
+        args: List of positional args provided to binman. This can hold test
+            names to execute (as in 'binman test testSections', for example)
         toolpath: List of paths to use for tools
     """
     from binman import bintool_test
@@ -72,13 +72,13 @@ def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath):
     from binman import image_test
     import doctest
 
-    test_name = args and args[0] or None
+    test_names = args or None
 
     # Run the entry tests first ,since these need to be the first to import the
     # 'entry' module.
     result = test_util.run_test_suites(
         'binman', debug, verbosity, False, test_preserve_dirs, processes,
-        test_name, toolpath,
+        test_names, toolpath,
         [bintool_test.TestBintool, entry_test.TestEntry, ftest.TestFunctional,
          fdt_test.TestFdt, elf_test.TestElf, image_test.TestImage,
          cbfs_util_test.TestCbfs, fip_util_test.TestFip])
diff --git a/tools/u_boot_pylib/test_util.py b/tools/u_boot_pylib/test_util.py
index 364596d7a0c..ff275a7b7c0 100644
--- a/tools/u_boot_pylib/test_util.py
+++ b/tools/u_boot_pylib/test_util.py
@@ -157,7 +157,7 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
             the output directory for this test. Both directories are displayed
             on the command line.
         processes: Number of processes to use to run tests (None=same as #CPUs)
-        test_name: Name of test to run, or None for all
+        test_name: Name of test, or list of test names, to run; None for all
         toolpath: List of paths to use for tools
         class_and_module_list: List of test classes (type class) and module
            names (type str) to run
@@ -182,12 +182,19 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
         resultclass=FullTextTestResult,
     )
 
-    if use_concurrent and processes != 1 and not test_name:
+    if isinstance(test_name, str):
+        test_names = [test_name]
+    else:
+        test_names = list(test_name or [])
+    test_name_set = set(test_names)
+
+    if use_concurrent and processes != 1 and not test_names:
         suite = ConcurrentTestSuite(suite,
                 fork_for_tests(processes or multiprocessing.cpu_count()))
 
     for module in class_and_module_list:
-        if isinstance(module, str) and (not test_name or test_name == module):
+        if (isinstance(module, str) and
+                (not test_names or module in test_name_set)):
             suite.addTests(doctest.DocTestSuite(module))
 
     for module in class_and_module_list:
@@ -197,15 +204,17 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
         if hasattr(module, 'setup_test_args'):
             setup_test_args = getattr(module, 'setup_test_args')
             setup_test_args(preserve_indir=test_preserve_dirs,
-                preserve_outdirs=test_preserve_dirs and test_name is not None,
+                preserve_outdirs=test_preserve_dirs and bool(test_names),
                 toolpath=toolpath, verbosity=verbosity, no_capture=no_capture)
-        if test_name:
+        if test_names:
             # Since Python v3.5 If an ImportError or AttributeError occurs
             # while traversing a name then a synthetic test that raises that
             # error when run will be returned. Check that the requested test
             # exists, otherwise these errors are included in the results.
-            if test_name in loader.getTestCaseNames(module):
-                suite.addTests(loader.loadTestsFromName(test_name, module))
+            module_test_names = loader.getTestCaseNames(module)
+            for name in test_names:
+                if name in module_test_names:
+                    suite.addTests(loader.loadTestsFromName(name, module))
         else:
             suite.addTests(loader.loadTestsFromTestCase(module))
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 05/10] .gitignore: ignore binman-generated blobs
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

The new android_boot etype is causing these /comp*u-boot-nodtb artifacts
to be generated, which aren't currently picked up by any ignore rules.
This issue only occurs when doing in-tree builds, but it's still worth
fixing regardless.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Sam Day <me@samcday.com>
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index d57d3be0291..6e274de77ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,6 +84,7 @@ fit-dtb.blob*
 /test/fdt_overlay/test-fdt-overlay-stacked.dtbo.S
 /test/fdt_overlay/test-fdt-overlay.dtbo.S
 capsule_esl_file
+/comp.*.u-boot-nodtb
 
 #
 # Generated include files

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 02/10] binman: test name globbing support
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

This makes invocations like `binman test testAndroid*` possible. It also
composes well with the previous commit that adds support for multiple
test names: `binman test 'testAndroid*' 'testQcdt*'` also works as you'd
expect.
---
 tools/binman/cmdline.py         |  2 +-
 tools/u_boot_pylib/test_util.py | 12 +++++++-----
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py
index 9632ec115e5..b3be9f79b20 100644
--- a/tools/binman/cmdline.py
+++ b/tools/binman/cmdline.py
@@ -201,7 +201,7 @@ controlled by a description in the board device tree.'''
                  'preserve the output directory if a single test is run (pass '
                  'test name at the end of the command line')
         test_parser.add_argument('tests', nargs='*',
-                                 help='Test names to run (omit for all)')
+                                 help='Test names/patterns to run (omit for all)')
 
     tool_parser = subparsers.add_parser('tool', help='Check bintools')
     tool_parser.add_argument('-l', '--list', action='store_true',
diff --git a/tools/u_boot_pylib/test_util.py b/tools/u_boot_pylib/test_util.py
index ff275a7b7c0..2089b3574ca 100644
--- a/tools/u_boot_pylib/test_util.py
+++ b/tools/u_boot_pylib/test_util.py
@@ -4,6 +4,7 @@
 #
 
 import doctest
+import fnmatch
 import glob
 import multiprocessing
 import os
@@ -186,7 +187,9 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
         test_names = [test_name]
     else:
         test_names = list(test_name or [])
-    test_name_set = set(test_names)
+
+    def _match_name(name):
+        return any(fnmatch.fnmatchcase(name, pattern) for pattern in test_names)
 
     if use_concurrent and processes != 1 and not test_names:
         suite = ConcurrentTestSuite(suite,
@@ -194,7 +197,7 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
 
     for module in class_and_module_list:
         if (isinstance(module, str) and
-                (not test_names or module in test_name_set)):
+                (not test_names or _match_name(module))):
             suite.addTests(doctest.DocTestSuite(module))
 
     for module in class_and_module_list:
@@ -211,9 +214,8 @@ def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs,
             # while traversing a name then a synthetic test that raises that
             # error when run will be returned. Check that the requested test
             # exists, otherwise these errors are included in the results.
-            module_test_names = loader.getTestCaseNames(module)
-            for name in test_names:
-                if name in module_test_names:
+            for name in loader.getTestCaseNames(module):
+                if _match_name(name):
                     suite.addTests(loader.loadTestsFromName(name, module))
         else:
             suite.addTests(loader.loadTestsFromTestCase(module))

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 03/10] binman: section: add AlignUp+PadToAlignment helpers
From: Sam Day via B4 Relay @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

From: Sam Day <me@samcday.com>

In the following commits we will be introducing new etypes derived from
Entry_section which need to align values and pad data. To avoid
duplication we introduce the shared helpers here.
---
 tools/binman/etype/section.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index 8530b7ee17f..a8eca3eaf5f 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -1040,3 +1040,12 @@ class Entry_section(Entry):
             for entry in entries.values():
                 return entry.read_elf_segments()
         return None
+
+    def AlignUp(self, value, align):
+        """Return value aligned to given power of 2"""
+        return (value + align - 1) & ~(align - 1)
+
+
+    def PadToAlignment(self, data, align):
+        """Null-pads given data to given power of 2"""
+        return data + b'\0' * (self.AlignUp(len(data), align) - len(data))

-- 
2.54.0



^ permalink raw reply related

* [PATCH v3 10/10] configs: exynos-mobile: pull in binman
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

All Exynos7 mobile devices need abootimg+DTBH wrapping to be bootable
from the fused S-BOOT bootloader. So we enable binman everywhere.
Ideally we'll have full coverage of these devices with binman configs in
the overlay DTSIs but, for now, we ensure there's a placeholder node.
That way no existing devices will regress their cleanly buildable state.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Sam Day <me@samcday.com>
---
 arch/arm/dts/exynos-mobile.dtsi | 5 +++++
 arch/arm/mach-exynos/Kconfig    | 1 +
 configs/exynos-mobile_defconfig | 1 +
 3 files changed, 7 insertions(+)

diff --git a/arch/arm/dts/exynos-mobile.dtsi b/arch/arm/dts/exynos-mobile.dtsi
new file mode 100644
index 00000000000..9982e996993
--- /dev/null
+++ b/arch/arm/dts/exynos-mobile.dtsi
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/ {
+	binman {
+	};
+};
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig
index b084b7284aa..d4ae182030d 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -255,6 +255,7 @@ endif
 config TARGET_EXYNOS_MOBILE
 	bool "Samsung Exynos Generic Boards (for mobile devices)"
 	select ARM64
+	select BINMAN
 	select BOARD_EARLY_INIT_F
 	select CLK_EXYNOS
 	select LINUX_KERNEL_IMAGE_HEADER
diff --git a/configs/exynos-mobile_defconfig b/configs/exynos-mobile_defconfig
index 45ab998bb45..fcaff3d1608 100644
--- a/configs/exynos-mobile_defconfig
+++ b/configs/exynos-mobile_defconfig
@@ -32,6 +32,7 @@ CONFIG_EFI_PARTITION=y
 CONFIG_OF_UPSTREAM=y
 CONFIG_OF_UPSTREAM_BUILD_VENDOR=y
 CONFIG_OF_BOARD=y
+CONFIG_DEVICE_TREE_INCLUDES="exynos-mobile.dtsi"
 CONFIG_BLKMAP=y
 CONFIG_BUTTON_REMAP_PHONE_KEYS=y
 CONFIG_CLK_EXYNOS7870=y

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 09/10] arch: arm: exynos: add j7xelte binman config
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

Note that, as of this commit, j7xelte does not yet exist in U-Boot's
upstream DTS tree. It was accepted into next so it should appear here
eventually.

Link: https://lore.kernel.org/all/177209522223.26390.6219893536178441080.b4-ty@kernel.org/
Signed-off-by: Sam Day <me@samcday.com>
---
 arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi b/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi
new file mode 100644
index 00000000000..0cd4c12a2e4
--- /dev/null
+++ b/arch/arm/dts/exynos7870-j7xelte-u-boot.dtsi
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/ {
+	binman {
+		filename = "u-boot-samsung-j7xelte.img";
+
+		android-boot {
+			/* S-BOOT ignores offsets, so they are not provided here */
+			kernel {
+				u-boot-nodtb { };
+			};
+
+			vendor-dt {
+				dtbh {
+					dtb-0 {
+						model_info-chip = <7870>;
+						model_info-hw_rev = <0>;
+						model_info-hw_rev_end = <255>;
+
+						u-boot-dtb { };
+					};
+				};
+			};
+		};
+	};
+};

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 08/10] binman: Add DTBH support
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

DTBH, used by Samsung S-BOOT bootloaders, is similar to QCDT. That is,
it's a multi-record container that carries vendor-specific magic values
to assist the previous bootloader in picking the appropriate FDT for the
booting device.

dtbTool-exynos was used as a reference for this implementation.

Link: https://github.com/dsankouski/dtbtool-exynos
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/etype/android_boot.py                 |  23 +++++
 tools/binman/etype/dtbh.py                         | 108 +++++++++++++++++++++
 tools/binman/ftest.py                              | 106 ++++++++++++++++++++
 tools/binman/test/dtbh.dts                         |  22 +++++
 tools/binman/test/dtbh_bad_model_info.dts          |  20 ++++
 tools/binman/test/dtbh_invalid_pagesize.dts        |  12 +++
 tools/binman/test/dtbh_missing_model_info.dts      |  16 +++
 tools/binman/test/dtbh_missing_payload.dts         |  15 +++
 tools/binman/test/dtbh_missing_subnodes.dts        |  10 ++
 tools/binman/test/dtbh_multi.dts                   |  29 ++++++
 tools/binman/test/dtbh_multiple_dtbs.dts           |  22 +++++
 tools/binman/test/dtbh_page_size_from_abootimg.dts |  30 ++++++
 tools/binman/test/dtbh_special_subnodes.dts        |  11 +++
 13 files changed, 424 insertions(+)

diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
index 57900e3d523..d6bc4774dab 100644
--- a/tools/binman/etype/android_boot.py
+++ b/tools/binman/etype/android_boot.py
@@ -127,6 +127,29 @@ class Entry_android_boot(Entry_section):
                         qcom,msm-id = <206 0>;
                         qcom,board-id = <0xce08ff01 1>;
 
+                        u-boot-dtb {
+                        };
+                    };
+                };
+            };
+        };
+
+    Example::
+        A legacy DTBH abootimg, the kind some Samsung bootloaders expect:
+
+        android-boot {
+            kernel {
+                u-boot-nodtb {
+                };
+            };
+
+            vendor-dt {
+                dtbh {
+                    dtb-0 {
+                        model_info-chip = <7870>;
+                        model_info-hw_rev = <123>;
+                        model_info-hw_rev_end = <321>;
+
                         u-boot-dtb {
                         };
                     };
diff --git a/tools/binman/etype/dtbh.py b/tools/binman/etype/dtbh.py
new file mode 100644
index 00000000000..29e7fbec0c9
--- /dev/null
+++ b/tools/binman/etype/dtbh.py
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Entry-type module for Samsung Android DTBH tables
+
+import struct
+
+from binman.android_vendor_dt_table import Entry_Android_vendor_dt_table
+from dtoc import fdt_util
+
+
+DTBH_MAGIC = b'DTBH'
+DTBH_VERSION = 2
+DTBH_PLATFORM_CODE_DEF = 0x50a6
+DTBH_SUBTYPE_CODE_DEF = 0x217584da
+DTBH_HEADER = '<4sII'
+DTBH_HEADER_SIZE = struct.calcsize(DTBH_HEADER)
+DTBH_RECORD = '<8I'
+DTBH_RECORD_SIZE = struct.calcsize(DTBH_RECORD)
+# Fixed per-record "space delimiter" used by dtbTool-exynos.
+# It's unclear what the 0x20 magic actually means, if anything.
+DTBH_RECORD_SPACE = 0x20
+# Zero end-of-table marker emitted after all DTBH records.
+DTBH_EOT = '<I'
+DTBH_EOT_SIZE = struct.calcsize(DTBH_EOT)
+
+
+class Entry_dtbh(Entry_Android_vendor_dt_table):
+    """Samsung Android device tree table
+
+    This creates a DTBH table, the legacy device-tree table format used by
+    some Samsung Android bootloaders.
+
+    Properties / Entry arguments:
+        - page-size: DTBH page size, defaults to the parent android-boot page
+          size or 2048 when used elsewhere
+        - platform: DTBH platform code, defaults to 0x50a6
+        - subtype: DTBH subtype code, defaults to 0x217584da
+
+    This entry uses the following subnodes:
+        - dtb-*: DTB records, each containing model_info-chip,
+          model_info-hw_rev, model_info-hw_rev_end and exactly one DTB payload
+          entry
+
+    Each dtb-* subnode must contain these properties:
+        - model_info-chip
+        - model_info-hw_rev
+        - model_info-hw_rev_end
+
+    Example::
+
+        dtbh {
+            dtb-0 {
+                model_info-chip = <7870>;
+                model_info-hw_rev = <123>;
+                model_info-hw_rev_end = <321>;
+
+                u-boot-dtb {
+                };
+            };
+        };
+    """
+
+    def ReadNode(self):
+        super().ReadNode()
+        self.platform = fdt_util.GetInt(self._node, 'platform',
+                                        DTBH_PLATFORM_CODE_DEF)
+        self.subtype = fdt_util.GetInt(self._node, 'subtype',
+                                       DTBH_SUBTYPE_CODE_DEF)
+
+    def _GetDtbRecordData(self, node, required):
+        chip = self._GetU32Tuple(node, 'model_info-chip', 1)[0]
+        hw_rev = self._GetU32Tuple(node, 'model_info-hw_rev', 1)[0]
+        hw_rev_end = self._GetU32Tuple(node, 'model_info-hw_rev_end', 1)[0]
+        data = super()._GetDtbRecordData(node, required)
+        if data is None and not required:
+            return None
+
+        return (chip, hw_rev, hw_rev_end, data)
+
+    def _ReadDtbRecord(self, node, data):
+        chip, hw_rev, hw_rev_end, data = data
+        return (chip, self.platform, self.subtype, hw_rev, hw_rev_end, data)
+
+    def BuildSectionData(self, required):
+        page_size = self._GetPageSize()
+        dtbs = self._ReadDtbRecords(required, self._ReadDtbRecord)
+        if dtbs is None:
+            return None
+
+        size = (DTBH_HEADER_SIZE + len(dtbs) * DTBH_RECORD_SIZE +
+                DTBH_EOT_SIZE)
+        header_size = self.AlignUp(size, page_size)
+        dtb_offset = header_size
+        records = []
+        payloads = bytearray()
+        for chip, platform, subtype, hw_rev, hw_rev_end, dtb in dtbs:
+            dtb_size = self.AlignUp(len(dtb), page_size)
+            records.append((chip, platform, subtype, hw_rev, hw_rev_end,
+                            dtb_offset, dtb_size, DTBH_RECORD_SPACE))
+            payloads += self.PadToAlignment(dtb, page_size)
+            dtb_offset += dtb_size
+
+        dtbh = bytearray(struct.pack(DTBH_HEADER, DTBH_MAGIC, DTBH_VERSION,
+                                     len(records)))
+        for record in records:
+            dtbh += struct.pack(DTBH_RECORD, *record)
+        dtbh += struct.pack(DTBH_EOT, 0)
+
+        return self.PadToAlignment(dtbh, page_size) + bytes(payloads)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index b18d584c688..9e92a40ef07 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5857,6 +5857,112 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
                          data[page_size + payload_size:
                               page_size + payload_size * 2])
 
+    def testDtbh(self):
+        """Test that binman can produce a DTBH container"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'dtbh.dts', use_real_dtb=True)
+
+        dtb_size = tools.align(len(dtb_data), 0x800)
+
+        self.assertEqual(b'DTBH', data[:4])
+        self.assertEqual((2, 1), struct.unpack_from('<II', data, 4))
+        self.assertEqual((7870, 0x50a6, 0x217584da, 123, 321, 0x800,
+                          dtb_size, 0x20),
+                         struct.unpack_from('<8I', data, 12))
+        self.assertEqual(0, struct.unpack_from('<I', data, 44)[0])
+        self.assertEqual(0xd00dfeed,
+                          struct.unpack_from('>I', data, 0x800)[0])
+        self.assertEqual(dtb_data, data[0x800:0x800 + len(dtb_data)])
+
+    def testDtbhMulti(self):
+        """Test that DTBH handles explicit properties and multiple DTBs"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'dtbh_multi.dts', use_real_dtb=True)
+
+        page_size = 0x100
+        dtb_size = tools.align(len(dtb_data), page_size)
+
+        self.assertEqual(b'DTBH', data[:4])
+        self.assertEqual((2, 2), struct.unpack_from('<II', data, 4))
+        self.assertEqual((7870, 0x1234, 0x56789abc, 123, 321, page_size,
+                          dtb_size, 0x20),
+                         struct.unpack_from('<8I', data, 12))
+        self.assertEqual((7871, 0x1234, 0x56789abc, 124, 322,
+                          page_size + dtb_size, dtb_size, 0x20),
+                         struct.unpack_from('<8I', data, 44))
+        self.assertEqual(0, struct.unpack_from('<I', data, 76)[0])
+        self.assertEqual(0xd00dfeed,
+                          struct.unpack_from('>I', data, page_size)[0])
+        self.assertEqual(dtb_data, data[page_size:page_size + len(dtb_data)])
+        self.assertEqual(dtb_data,
+                         data[page_size + dtb_size:page_size + dtb_size +
+                              len(dtb_data)])
+
+    def testDtbhPageSizeFromParent(self):
+        """Test that DTBH inherits page-size from parent android-boot node"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'dtbh_page_size_from_abootimg.dts', use_real_dtb=True)
+
+        # header+kernel are aligned to 4096, vendor-dt follows after that.
+        vendor_dt_offset = 4096 * 2
+        dtb_size = tools.align(len(dtb_data), 4096)
+
+        self.assertEqual(b'DTBH', data[vendor_dt_offset:vendor_dt_offset+4])
+        self.assertEqual((4096, dtb_size),
+                         struct.unpack_from('<20x2I', data,
+                                            vendor_dt_offset + 12))
+
+    def testDtbhBadModelInfo(self):
+        """Test that DTBH rejects invalid model_info properties"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFileDtb('dtbh_bad_model_info.dts',
+                                use_real_dtb=True)
+        self.assertIn("subnode 'dtb-0': Property 'model_info-chip' must "
+                      "contain exactly 1 cells", str(exc.exception))
+
+    def testDtbhMissingModelInfo(self):
+        """Test that DTBH rejects missing model_info properties"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFileDtb('dtbh_missing_model_info.dts',
+                                use_real_dtb=True)
+        self.assertIn("subnode 'dtb-0': Missing required property "
+                      "'model_info-chip'", str(exc.exception))
+
+    def testDtbhInvalidPageSize(self):
+        """Test that DTBH rejects invalid page-size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('dtbh_invalid_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testDtbhMultipleDTBs(self):
+        """Test that DTBH rejects multiple embedded DTBs"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('dtbh_multiple_dtbs.dts')
+        self.assertIn("must contain exactly one DTB",
+                      str(exc.exception))
+
+    def testDtbhMissingDTBPayload(self):
+        """Test that DTBH rejects missing DTB payload"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('dtbh_missing_payload.dts')
+        self.assertIn("Missing required DTB payload subnode",
+                      str(exc.exception))
+
+    def testDtbhMissingSubnodes(self):
+        """Test that DTBH rejects missing dtb subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('dtbh_missing_subnodes.dts')
+        self.assertIn("Missing required DTB subnodes",
+                      str(exc.exception))
+
+    def testDtbhOnlySpecialSubnodes(self):
+        """Test that DTBH rejects special-only subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('dtbh_special_subnodes.dts')
+        self.assertIn("Missing required DTB subnodes",
+                      str(exc.exception))
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/dtbh.dts b/tools/binman/test/dtbh.dts
new file mode 100644
index 00000000000..911ca1954d8
--- /dev/null
+++ b/tools/binman/test/dtbh.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		/* confirm that dtbh can be referenced before it's built */
+		collection {
+			content = <&dtbh>;
+		};
+
+		dtbh: dtbh {
+			dtb-0 {
+				model_info-chip = <7870>;
+				model_info-hw_rev = <123>;
+				model_info-hw_rev_end = <321>;
+
+				u-boot-dtb {};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_bad_model_info.dts b/tools/binman/test/dtbh_bad_model_info.dts
new file mode 100644
index 00000000000..9179f7c3761
--- /dev/null
+++ b/tools/binman/test/dtbh_bad_model_info.dts
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <2>;
+	#size-cells = <1>;
+
+	binman {
+		dtbh {
+			dtb-0 {
+				model_info-chip = <7870 1>;
+				model_info-hw_rev = <6>;
+				model_info-hw_rev_end = <6>;
+
+				u-boot-dtb {};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_invalid_pagesize.dts b/tools/binman/test/dtbh_invalid_pagesize.dts
new file mode 100644
index 00000000000..ec0534dc6a8
--- /dev/null
+++ b/tools/binman/test/dtbh_invalid_pagesize.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			page-size = <2049>;
+			dtb-0 {};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_missing_model_info.dts b/tools/binman/test/dtbh_missing_model_info.dts
new file mode 100644
index 00000000000..9c92d9cf39a
--- /dev/null
+++ b/tools/binman/test/dtbh_missing_model_info.dts
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			dtb-0 {
+				model_info-hw_rev = <6>;
+				model_info-hw_rev_end = <6>;
+
+				u-boot-dtb {};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_missing_payload.dts b/tools/binman/test/dtbh_missing_payload.dts
new file mode 100644
index 00000000000..1ea5014ad36
--- /dev/null
+++ b/tools/binman/test/dtbh_missing_payload.dts
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			dtb-0 {
+				model_info-chip = <7870>;
+				model_info-hw_rev = <6>;
+				model_info-hw_rev_end = <6>;
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_missing_subnodes.dts b/tools/binman/test/dtbh_missing_subnodes.dts
new file mode 100644
index 00000000000..6d3eac173d5
--- /dev/null
+++ b/tools/binman/test/dtbh_missing_subnodes.dts
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_multi.dts b/tools/binman/test/dtbh_multi.dts
new file mode 100644
index 00000000000..9a89bbbada4
--- /dev/null
+++ b/tools/binman/test/dtbh_multi.dts
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			page-size = <0x100>;
+			platform = <0x1234>;
+			subtype = <0x56789abc>;
+
+			dtb-0 {
+				model_info-chip = <7870>;
+				model_info-hw_rev = <123>;
+				model_info-hw_rev_end = <321>;
+
+				u-boot-dtb {};
+			};
+
+			dtb-1 {
+				model_info-chip = <7871>;
+				model_info-hw_rev = <124>;
+				model_info-hw_rev_end = <322>;
+
+				u-boot-dtb {};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_multiple_dtbs.dts b/tools/binman/test/dtbh_multiple_dtbs.dts
new file mode 100644
index 00000000000..c991564fab0
--- /dev/null
+++ b/tools/binman/test/dtbh_multiple_dtbs.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			dtb-0 {
+				model_info-chip = <7870>;
+				model_info-hw_rev = <6>;
+				model_info-hw_rev_end = <6>;
+
+				fill {
+					size = <1>;
+				};
+				text {
+					text = "x";
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_page_size_from_abootimg.dts b/tools/binman/test/dtbh_page_size_from_abootimg.dts
new file mode 100644
index 00000000000..1512b8b5e8b
--- /dev/null
+++ b/tools/binman/test/dtbh_page_size_from_abootimg.dts
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			page-size = <4096>;
+
+			kernel {
+				fill {
+					size = <1>;
+				};
+			};
+
+			vendor-dt {
+				dtbh {
+					dtb-0 {
+						model_info-chip = <7870>;
+						model_info-hw_rev = <6>;
+						model_info-hw_rev_end = <6>;
+
+						u-boot-dtb {
+						};
+					};
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/dtbh_special_subnodes.dts b/tools/binman/test/dtbh_special_subnodes.dts
new file mode 100644
index 00000000000..ce698ce00f2
--- /dev/null
+++ b/tools/binman/test/dtbh_special_subnodes.dts
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		dtbh {
+			hash {};
+		};
+	};
+};

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 07/10] binman: Add QCDT support
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

This vendor-specific format is used by many bootloaders on older qcom
SoCs, such as msm8916. It's a container for N FDTs. Each one is
contained in a record that includes metadata about the platform/variant
it targets. The previous bootloader picks the "right" record based on
this metadata.

This initial impl targets a streamlined v2 path, with no support for
different versions or multiple qcom,msm-id/qcom,board-id tuples. If/when
that's needed it will be implemented in a follow-up.

In the following commit, support for DTBH will also be introduced.
Because QCDT and DTBH share a lot of common behaviour, this commit also
introduces a Entry_Android_vendor_dt_table base class.

This impl was based on the lk2nd/CAF LK dtbTool script.

Link: https://github.com/msm8916-mainline/lk2nd/blob/main/lk2nd/scripts/dtbTool
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/android_vendor_dt_table.py            | 104 +++++++++++++++++++++
 tools/binman/etype/android_boot.py                 |  31 ++++++
 tools/binman/etype/qcdt.py                         |  80 ++++++++++++++++
 tools/binman/ftest.py                              |  96 +++++++++++++++++++
 tools/binman/test/qcdt.dts                         |  36 +++++++
 tools/binman/test/qcdt_bad_msm_id.dts              |  17 ++++
 tools/binman/test/qcdt_invalid_pagesize.dts        |  12 +++
 tools/binman/test/qcdt_missing_msm_id.dts          |  12 +++
 tools/binman/test/qcdt_missing_payload.dts         |  14 +++
 tools/binman/test/qcdt_missing_subnodes.dts        |  13 +++
 tools/binman/test/qcdt_multiple_dtbs.dts           |  34 +++++++
 tools/binman/test/qcdt_page_size_from_abootimg.dts |  33 +++++++
 tools/binman/test/qcdt_zero_pagesize.dts           |  12 +++
 13 files changed, 494 insertions(+)

diff --git a/tools/binman/android_vendor_dt_table.py b/tools/binman/android_vendor_dt_table.py
new file mode 100644
index 00000000000..91b785f274e
--- /dev/null
+++ b/tools/binman/android_vendor_dt_table.py
@@ -0,0 +1,104 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+from binman.entry import Entry
+from binman.etype.section import Entry_section
+from dtoc import fdt_util
+
+
+class Entry_Android_vendor_dt_table(Entry_section):
+    """Base class for legacy Android vendor DT table entries"""
+
+    @staticmethod
+    def _DtbEntryName(node):
+        return '_dtb_%s' % node.name
+
+    def ReadNode(self):
+        super().ReadNode()
+        self._page_size = fdt_util.GetInt(self._node, 'page-size')
+        if (self._page_size is not None and
+                (self._page_size <= 0 or
+                 self._page_size & (self._page_size - 1))):
+            self.Raise('page-size must be a power of two')
+
+    def _GetPayloadSubnodes(self, node):
+        return [subnode for subnode in node.subnodes
+                if not self.IsSpecialSubnode(subnode)]
+
+    def ReadEntries(self):
+        for node in self._node.subnodes:
+            if self.IsSpecialSubnode(node):
+                continue
+
+            payloads = self._GetPayloadSubnodes(node)
+            if len(payloads) > 1:
+                self.Raise("subnode '%s': must contain exactly one DTB "
+                           "payload subnode" % node.name)
+            if not payloads:
+                continue
+
+            entry = Entry.Create(self, payloads[0],
+                                 expanded=self.GetImage().use_expanded,
+                                 missing_etype=self.GetImage().missing_etype)
+            entry.ReadNode()
+            entry.SetPrefix(self._name_prefix)
+            self._entries[self._DtbEntryName(node)] = entry
+
+    def _GetPageSize(self):
+        if self._page_size is not None:
+            return self._page_size
+
+        section = self.section
+        while section:
+            if section.etype == 'android-boot':
+                return section.page_size
+            section = section.section
+
+        return 2048
+
+    def _GetU32Cells(self, node, propname):
+        prop = node.props.get(propname)
+        if not prop:
+            self.Raise("subnode '%s': Missing required property '%s'" %
+                       (node.name, propname))
+
+        values = prop.value if isinstance(prop.value, list) else [prop.value]
+        return [fdt_util.fdt32_to_cpu(value) for value in values]
+
+    def _GetU32Tuple(self, node, propname, width):
+        values = self._GetU32Cells(node, propname)
+        if len(values) != width:
+            self.Raise("subnode '%s': Property '%s' must contain exactly "
+                       "%d cells" % (node.name, propname, width))
+
+        return tuple(values)
+
+    def _GetDtbData(self, node, required):
+        entry = self._entries.get(self._DtbEntryName(node))
+        if not entry:
+            self.Raise("subnode '%s': Missing required DTB payload subnode" %
+                       node.name)
+
+        data = entry.GetData(required)
+        if data is None and not required:
+            return None
+
+        return data
+
+    def _GetDtbRecordData(self, node, required):
+        return self._GetDtbData(node, required)
+
+    def _ReadDtbRecords(self, required, read_record):
+        records = []
+        for node in self._node.subnodes:
+            if self.IsSpecialSubnode(node):
+                continue
+
+            data = self._GetDtbRecordData(node, required)
+            if data is None and not required:
+                return None
+            records.append(read_record(node, data))
+
+        if not records:
+            self.Raise('Missing required DTB subnodes')
+
+        return records
diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
index 5cfa71ee981..57900e3d523 100644
--- a/tools/binman/etype/android_boot.py
+++ b/tools/binman/etype/android_boot.py
@@ -102,6 +102,37 @@ class Entry_android_boot(Entry_section):
                 };
             };
         };
+
+    Example::
+        A legacy QCDT abootimg, the kind msm8916 bootloaders expect:
+
+        android-boot {
+            base = <0x80000000>;
+
+            kernel {
+                u-boot {
+                    no-expanded;
+                };
+            };
+
+            ramdisk {
+                fill {
+                    size = <1>;
+                };
+            };
+
+            vendor-dt {
+                qcdt {
+                    dtb-0 {
+                        qcom,msm-id = <206 0>;
+                        qcom,board-id = <0xce08ff01 1>;
+
+                        u-boot-dtb {
+                        };
+                    };
+                };
+            };
+        };
     """
 
     def ReadNode(self):
diff --git a/tools/binman/etype/qcdt.py b/tools/binman/etype/qcdt.py
new file mode 100644
index 00000000000..ccf566af29f
--- /dev/null
+++ b/tools/binman/etype/qcdt.py
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Entry-type module for Qualcomm Android device tree tables
+
+import struct
+
+from binman.android_vendor_dt_table import Entry_Android_vendor_dt_table
+
+
+QCDT_MAGIC = b'QCDT'
+QCDT_VERSION = 2
+QCDT_HEADER = '<4sII'
+QCDT_HEADER_SIZE = struct.calcsize(QCDT_HEADER)
+QCDT_RECORD = '<IIIIII'
+QCDT_RECORD_SIZE = struct.calcsize(QCDT_RECORD)
+
+
+class Entry_qcdt(Entry_Android_vendor_dt_table):
+    """Qualcomm Android device tree table
+
+    This creates a QCDT table, the legacy device-tree table format used by
+    some Qualcomm Android bootloaders.
+
+    Properties / Entry arguments:
+        - page-size: QCDT page size, defaults to 2048, unless there's a parent
+          android-boot node with an explicit page-size
+
+    This entry uses the following subnodes:
+        - dtb-*: DTB records, each containing qcom,msm-id, qcom,board-id and
+          exactly one DTB payload entry
+
+    Example::
+
+        qcdt {
+            dtb-0 {
+                qcom,msm-id = <206 0>;
+                qcom,board-id = <0xce08ff01 1>;
+
+                u-boot-dtb {
+                };
+            };
+        };
+    """
+
+    def _GetDtbRecordData(self, node, required):
+        msm_id = self._GetU32Tuple(node, 'qcom,msm-id', 2)
+        board_id = self._GetU32Tuple(node, 'qcom,board-id', 2)
+        data = super()._GetDtbRecordData(node, required)
+        if data is None and not required:
+            return None
+
+        return (msm_id, board_id, data)
+
+    def _ReadDtbRecord(self, node, data):
+        return data
+
+    def BuildSectionData(self, required):
+        page_size = self._GetPageSize()
+        dtbs = self._ReadDtbRecords(required, self._ReadDtbRecord)
+        if dtbs is None:
+            return None
+
+        size = QCDT_HEADER_SIZE + len(dtbs) * QCDT_RECORD_SIZE
+        dtb_offset = self.AlignUp(size, page_size)
+        records = []
+        payloads = bytearray()
+        for msm_id, board_id, dtb in dtbs:
+            platform_id, soc_rev = msm_id
+            variant_id, board_hw_subtype = board_id
+            dtb_size = self.AlignUp(len(dtb), page_size)
+            records.append((platform_id, variant_id, board_hw_subtype,
+                            soc_rev, dtb_offset, dtb_size))
+            payloads += self.PadToAlignment(dtb, page_size)
+            dtb_offset += dtb_size
+
+        qcdt = bytearray(struct.pack(QCDT_HEADER, QCDT_MAGIC, QCDT_VERSION,
+                                     len(records)))
+        for record in records:
+            qcdt += struct.pack(QCDT_RECORD, *record)
+
+        return self.PadToAlignment(qcdt, page_size) + bytes(payloads)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index bbdcb721eca..b18d584c688 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5761,6 +5761,102 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
                          data[vendor_dt_offset:vendor_dt_offset + page_size])
         self.assertEqual(vendor_dt_offset + page_size, len(data))
 
+    def testQcdt(self):
+        """Test that binman can produce a QCDT container"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'qcdt.dts', use_real_dtb=True)
+
+        dtb_size = tools.align(len(dtb_data), 0x800)
+
+        self.assertEqual(b'QCDT', data[:4])
+        self.assertEqual((2, 2), struct.unpack_from('<II', data, 4))
+        self.assertEqual((0xce, 0xce08ff01, 1, 0, 0x800, dtb_size),
+                          struct.unpack_from('<IIIIII', data, 12))
+        self.assertEqual((0xcf, 0xce08ff02, 2, 1, 0x800 + dtb_size,
+                          dtb_size), struct.unpack_from('<IIIIII', data, 36))
+        self.assertEqual(0xd00dfeed,
+                          struct.unpack_from('>I', data, 0x800)[0])
+        self.assertEqual(dtb_data, data[0x800:0x800 + len(dtb_data)])
+        self.assertEqual(dtb_data, data[0x800 + dtb_size:0x800 + dtb_size +
+                                         len(dtb_data)])
+
+    def testQcdtPageSizeFromParent(self):
+        """Test that QCDT inherits page-size from parent android-boot node"""
+        data, dtb_data, _map, _dtb = self._DoReadFileDtb(
+            'qcdt_page_size_from_abootimg.dts')
+
+        # header+kernel are aligned to 4096, vendor-dt follows after that.
+        vendor_dt_offset = 4096*2
+
+        self.assertEqual(b'QCDT', data[vendor_dt_offset:vendor_dt_offset + 4])
+        self.assertEqual((4096, 4096),
+                         struct.unpack_from('<16xII', data,
+                                            vendor_dt_offset + 12))
+
+    def testQcdtBadMsmId(self):
+        """Test that QCDT rejects invalid msm-id properties"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_bad_msm_id.dts')
+        self.assertIn("Property 'qcom,msm-id' must contain exactly 2 cells",
+                      str(exc.exception))
+
+    def testQcdtMissingMsmId(self):
+        """Test that QCDT rejects missing qcom,msm-id"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_msm_id.dts')
+        self.assertIn("Missing required property 'qcom,msm-id'",
+                      str(exc.exception))
+
+    def testQcdtMissingDTBPayload(self):
+        """Test that QCDT rejects missing DTB payload"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_payload.dts')
+        self.assertIn("Missing required DTB payload subnode",
+                      str(exc.exception))
+
+    def testQcdtMissingSubnodes(self):
+        """Test that QCDT rejects missing dtb subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_missing_subnodes.dts')
+        self.assertIn("Missing required DTB subnodes",
+                      str(exc.exception))
+
+    def testQcdtInvalidPageSize(self):
+        """Test that QCDT rejects invalid page-size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_invalid_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testQcdtZeroPageSize(self):
+        """Test that QCDT rejects zero page-size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('qcdt_zero_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testQcdtMultipleDTBs(self):
+        """Test that QCDT handles multiple embedded DTBs"""
+        data = self._DoReadFile('qcdt_multiple_dtbs.dts')
+
+        page_size = 0x100
+        payload_size = page_size
+        payload_pad = tools.get_bytes(0, page_size - 1)
+
+        self.assertEqual(b'QCDT', data[:4])
+        self.assertEqual((2, 2), struct.unpack_from('<II', data, 4))
+        self.assertEqual((0xce, 0xce08ff01, 1, 0, page_size,
+                          payload_size),
+                         struct.unpack_from('<IIIIII', data, 12))
+        self.assertEqual((0xcf, 0xce08ff02, 3, 2,
+                          page_size + payload_size, payload_size),
+                         struct.unpack_from('<IIIIII', data, 36))
+        self.assertEqual(tools.get_bytes(0x11, 1) + payload_pad,
+                         data[page_size:page_size + payload_size])
+        self.assertEqual(tools.get_bytes(0x22, 1) + payload_pad,
+                         data[page_size + payload_size:
+                              page_size + payload_size * 2])
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/qcdt.dts b/tools/binman/test/qcdt.dts
new file mode 100644
index 00000000000..cdbd1a85379
--- /dev/null
+++ b/tools/binman/test/qcdt.dts
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that qcdt can be referenced before it's built */
+		collection {
+			content = <&qcdt>;
+		};
+
+		qcdt: qcdt {
+			hash {
+			};
+
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+
+				u-boot-dtb {
+				};
+			};
+
+			dtb-1 {
+				qcom,msm-id = <0xcf 1>;
+				qcom,board-id = <0xce08ff02 2>;
+
+				u-boot-dtb {
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_bad_msm_id.dts b/tools/binman/test/qcdt_bad_msm_id.dts
new file mode 100644
index 00000000000..1c3d4ec1a2e
--- /dev/null
+++ b/tools/binman/test/qcdt_bad_msm_id.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+			dtb-0 {
+				qcom,msm-id = <0xce 0 1>;
+				qcom,board-id = <0xce08ff01 1>;
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_invalid_pagesize.dts b/tools/binman/test/qcdt_invalid_pagesize.dts
new file mode 100644
index 00000000000..d8eff98c7ac
--- /dev/null
+++ b/tools/binman/test/qcdt_invalid_pagesize.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			page-size = <2049>;
+			dtb-0 {};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_msm_id.dts b/tools/binman/test/qcdt_missing_msm_id.dts
new file mode 100644
index 00000000000..3eda1acb6c2
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_msm_id.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			dtb-0 {
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_payload.dts b/tools/binman/test/qcdt_missing_payload.dts
new file mode 100644
index 00000000000..ae2c41cbcf8
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_payload.dts
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_missing_subnodes.dts b/tools/binman/test/qcdt_missing_subnodes.dts
new file mode 100644
index 00000000000..4b1af9570b6
--- /dev/null
+++ b/tools/binman/test/qcdt_missing_subnodes.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_multiple_dtbs.dts b/tools/binman/test/qcdt_multiple_dtbs.dts
new file mode 100644
index 00000000000..db04c122a6e
--- /dev/null
+++ b/tools/binman/test/qcdt_multiple_dtbs.dts
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		qcdt {
+			page-size = <0x100>;
+
+			dtb-0 {
+				qcom,msm-id = <0xce 0>;
+				qcom,board-id = <0xce08ff01 1>;
+
+				fill {
+					size = <1>;
+					fill-byte = [11];
+				};
+			};
+
+			dtb-1 {
+				qcom,msm-id = <0xcf 2>;
+				qcom,board-id = <0xce08ff02 3>;
+
+				fill {
+					size = <1>;
+					fill-byte = [22];
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_page_size_from_abootimg.dts b/tools/binman/test/qcdt_page_size_from_abootimg.dts
new file mode 100644
index 00000000000..557863d8834
--- /dev/null
+++ b/tools/binman/test/qcdt_page_size_from_abootimg.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		android-boot {
+			page-size = <4096>;
+
+			kernel {
+				fill {
+					size = <1>;
+				};
+			};
+
+			vendor-dt {
+				qcdt {
+					dtb-0 {
+						qcom,msm-id = <0 0>;
+						qcom,board-id = <0 0>;
+
+						fill {
+							size = <1>;
+						};
+					};
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/qcdt_zero_pagesize.dts b/tools/binman/test/qcdt_zero_pagesize.dts
new file mode 100644
index 00000000000..8ca802719f0
--- /dev/null
+++ b/tools/binman/test/qcdt_zero_pagesize.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		qcdt {
+			page-size = <0>;
+			dtb-0 {};
+		};
+	};
+};

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 06/10] binman: android_boot: vendor-dt support
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

There's many Android bootloaders out there that expect non-standard
abootimgs. Samsung and Qualcomm, and likely many others. This quirk has
been codified in the widely used osm0sis fork/rewrite of mkbootimg.

Presumably, these vendor-specific abootimgs were conceived before the v2
format was widely available or specified. The evidence for this is their
hijacking of the header_version field to specify the length of a payload
that follows the main abootimg.

At least QCDT and DTBH (both of which will be implemented as binman
etypes in following commits) use this approach.

Link: https://github.com/osm0sis/mkbootimg
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/etype/android_boot.py           | 41 ++++++++++++++++++++++++++--
 tools/binman/ftest.py                        | 15 ++++++++++
 tools/binman/test/android_boot_vendor_dt.dts | 27 ++++++++++++++++++
 3 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
index 8b72d90acc5..5cfa71ee981 100644
--- a/tools/binman/etype/android_boot.py
+++ b/tools/binman/etype/android_boot.py
@@ -33,6 +33,9 @@ class Entry_android_boot(Entry_section):
     A kernel payload, optional ramdisk payload can be supplied. A DTB payload
     can also be provided when header_version == v2.
 
+    Vendor-specific payloads are also supported. These are non-standard
+    v0 images with a special DT container format appended.
+
     Properties / Entry arguments:
         - header-version: Android boot image header version, must be 0 or 2,
           defaults to 0
@@ -50,6 +53,7 @@ class Entry_android_boot(Entry_section):
         - kernel: section containing the executable payload
         - dtb: section containing the DTB payload, used by header version 2 only
         - ramdisk: optional section containing a ramdisk payload
+        - vendor-dt: legacy vendor DT payload, used by header version 0 only
 
     Example::
         A v2 abootimg with control FDT placed in the DTB section:
@@ -112,6 +116,7 @@ class Entry_android_boot(Entry_section):
         self.os_version = fdt_util.GetInt(self._node, 'os-version', 0)
         self.boot_name = fdt_util.GetString(self._node, 'boot-name', '')
         self.cmdline = fdt_util.GetString(self._node, 'cmdline', '')
+        self.vendor_dt_node = self._node.FindNode('vendor-dt')
 
         if self.header_version not in (0, 2):
             self.Raise('Only Android boot image header versions 0 and 2 are '
@@ -132,9 +137,15 @@ class Entry_android_boot(Entry_section):
                 self.Raise('page-size must fit the Android boot image header')
             if 'dtb' not in self._entries:
                 self.Raise("Missing required subnode 'dtb'")
+            if self.vendor_dt_node:
+                self.Raise("Subnode 'vendor-dt' requires header-version 0")
 
     def ReadEntries(self):
         for node in self._node.subnodes:
+            if node.name == 'vendor-dt':
+                self._ReadVendorDtEntries(node)
+                continue
+
             if node.name not in ('kernel', 'ramdisk', 'dtb'):
                 self.Raise("Unexpected subnode '%s'" % node.name)
 
@@ -145,6 +156,14 @@ class Entry_android_boot(Entry_section):
             entry.SetPrefix(self._name_prefix)
             self._entries[node.name] = entry
 
+    def _ReadVendorDtEntries(self, vendor_dt_node):
+        entry = Entry.Create(self, vendor_dt_node, etype='section',
+                             expanded=self.GetImage().use_expanded,
+                             missing_etype=self.GetImage().missing_etype)
+        entry.ReadNode()
+        entry.SetPrefix(self._name_prefix)
+        self._entries[vendor_dt_node.name] = entry
+
     def _GetIntCells(self, propname, default):
         prop = self._node.props.get(propname)
         if not prop:
@@ -198,8 +217,14 @@ class Entry_android_boot(Entry_section):
             return default
         return entry.GetData(required)
 
+    def _BuildVendorDt(self, required):
+        if not self.vendor_dt_node:
+            return b''
+        return self._GetEntryData('vendor-dt', required)
+
     def _BuildV0SectionData(self, required):
         kernel = self._GetEntryData('kernel', required)
+        vendor_dt = self._BuildVendorDt(required)
         ramdisk = self._GetEntryData('ramdisk', required, b'')
         if not required and (kernel is None or vendor_dt is None or
                              ramdisk is None):
@@ -211,8 +236,18 @@ class Entry_android_boot(Entry_section):
                                  BOOT_ARGS_SIZE)
 
         boot_id_payloads = [kernel, ramdisk, b'']
+        if self.vendor_dt_node:
+            boot_id_payloads.append(vendor_dt)
         image_id = self._BootId(*boot_id_payloads)
 
+        overloaded_header_version =  self.header_version
+        if self.vendor_dt_node:
+            # vendor DTs overload the header_version field to store the length
+            # of the appended payload. Hopefully AOSP abootimg never progresses
+            # to v8192-ish or we might have some real specificity problems on
+            # our hands.
+            overloaded_header_version = len(vendor_dt)
+
         header = struct.pack(BOOT_IMAGE_HEADER_V0,
                              BOOT_MAGIC,
                              len(kernel),
@@ -223,7 +258,7 @@ class Entry_android_boot(Entry_section):
                              0, # second_offset
                              self._GetAddr(self.tags_offset, 'tags'),
                              self.page_size,
-                             self.header_version,
+                             overloaded_header_version,
                              self.os_version,
                              boot_name,
                              cmdline,
@@ -233,6 +268,7 @@ class Entry_android_boot(Entry_section):
         image += self.PadToAlignment(header, self.page_size)
         image += self.PadToAlignment(kernel, self.page_size)
         image += self.PadToAlignment(ramdisk, self.page_size)
+        image += self.PadToAlignment(vendor_dt, self.page_size)
 
         return bytes(image)
 
@@ -240,7 +276,8 @@ class Entry_android_boot(Entry_section):
         kernel = self._GetEntryData('kernel', required)
         dtb = self._GetEntryData('dtb', required)
         ramdisk = self._GetEntryData('ramdisk', required, b'')
-        if not required and (kernel is None or dtb is None):
+        if not required and (kernel is None or dtb is None or
+                             ramdisk is None):
             return None
 
         boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 71740205c72..bbdcb721eca 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5746,6 +5746,21 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
         self.assertIn("Subnode 'vendor-dt' requires header-version 0",
                       str(exc.exception))
 
+    def testAndroidBootVendorDt(self):
+        """Test that android-boot can embed an arbitrary vendor-dt section"""
+        data = self._DoReadFile('android_boot_vendor_dt.dts')
+        header = struct.unpack_from('<8s10I16s512s32s', data, 0)
+        page_size = 2048
+        vendor_dt_offset = page_size * 3
+        vendor_dt = b'howdy'
+        self.assertEqual(len(vendor_dt), header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'\0', b'',
+                                             vendor_dt), header[13])
+        self.assertEqual(vendor_dt + b'\0' * (page_size - len(vendor_dt)),
+                         data[vendor_dt_offset:vendor_dt_offset + page_size])
+        self.assertEqual(vendor_dt_offset + page_size, len(data))
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/android_boot_vendor_dt.dts b/tools/binman/test/android_boot_vendor_dt.dts
new file mode 100644
index 00000000000..194396a0880
--- /dev/null
+++ b/tools/binman/test/android_boot_vendor_dt.dts
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+	binman {
+		android-boot {
+			header-version = <0>;
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+			ramdisk {
+				fill {
+					size = <1>;
+				};
+			};
+			vendor-dt {
+				text {
+					size = <5>;
+					text = "howdy";
+				};
+			};
+		};
+	};
+};

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 05/10] .gitignore: ignore binman-generated blobs
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

The new android_boot etype is causing these /comp*u-boot-nodtb artifacts
to be generated, which aren't currently picked up by any ignore rules.
This issue only occurs when doing in-tree builds, but it's still worth
fixing regardless.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Sam Day <me@samcday.com>
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index d57d3be0291..6e274de77ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,6 +84,7 @@ fit-dtb.blob*
 /test/fdt_overlay/test-fdt-overlay-stacked.dtbo.S
 /test/fdt_overlay/test-fdt-overlay.dtbo.S
 capsule_esl_file
+/comp.*.u-boot-nodtb
 
 #
 # Generated include files

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 04/10] binman: Android boot image support
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

Introduce initial support for Android boot images (abootimgs).

The AOSP implementation was used as a reference for this work.

We start with just v0 and v2 support. v1/v3/v4 are deliberately left
unimplemented for now as no use-case has been found for them yet.

Since we're targeting U-Boot use cases here, a couple of things were
omitted from this impl, namely "second" and recovery_dtbo support.

Link: https://android.googlesource.com/platform/system/tools/mkbootimg/
Signed-off-by: Sam Day <me@samcday.com>
---
 tools/binman/etype/android_boot.py                 | 285 +++++++++++++++++++++
 tools/binman/ftest.py                              | 148 +++++++++++
 tools/binman/test/android_boot_chonky_cells.dts    |  13 +
 tools/binman/test/android_boot_dtb_in_v0.dts       |  12 +
 tools/binman/test/android_boot_invalid_addr.dts    |  13 +
 .../binman/test/android_boot_invalid_pagesize.dts  |  11 +
 tools/binman/test/android_boot_invalid_subnode.dts |  12 +
 tools/binman/test/android_boot_missing_kernel.dts  |   9 +
 .../test/android_boot_oversized_bootname.dts       |  12 +
 .../test/android_boot_unsupported_version.dts      |  11 +
 tools/binman/test/android_boot_v0.dts              |  34 +++
 .../test/android_boot_v0_pagesize_too_smol.dts     |  12 +
 tools/binman/test/android_boot_v2.dts              |  50 ++++
 tools/binman/test/android_boot_v2_missing_dtb.dts  |  12 +
 .../test/android_boot_v2_pagesize_too_smol.dts     |  13 +
 tools/binman/test/android_boot_v2_vendor_dt.dts    |  14 +
 16 files changed, 661 insertions(+)

diff --git a/tools/binman/etype/android_boot.py b/tools/binman/etype/android_boot.py
new file mode 100644
index 00000000000..8b72d90acc5
--- /dev/null
+++ b/tools/binman/etype/android_boot.py
@@ -0,0 +1,285 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Entry-type module for Android boot images
+
+import hashlib
+import struct
+
+from binman.entry import Entry
+from binman.etype.section import Entry_section
+from dtoc import fdt_util
+
+
+BOOT_MAGIC = b'ANDROID!'
+BOOT_NAME_SIZE = 16
+BOOT_ARGS_SIZE = 512
+IMAGE_ID_SIZE = 32
+BOOT_EXTRA_ARGS_SIZE = 1024
+
+BOOT_IMAGE_HEADER_V0 = '<{}s10I{}s{}s{}s'.format(len(BOOT_MAGIC),
+                                                    BOOT_NAME_SIZE,
+                                                    BOOT_ARGS_SIZE,
+                                                    IMAGE_ID_SIZE)
+BOOT_IMAGE_HEADER_V0_SIZE = struct.calcsize(BOOT_IMAGE_HEADER_V0)
+BOOT_IMAGE_HEADER_V2 = (BOOT_IMAGE_HEADER_V0 +
+                        '{}sIQIIQ'.format(BOOT_EXTRA_ARGS_SIZE))
+BOOT_IMAGE_HEADER_V2_SIZE = struct.calcsize(BOOT_IMAGE_HEADER_V2)
+
+
+class Entry_android_boot(Entry_section):
+    """Android boot image
+
+    This creates an Android v0 or v2 boot image.
+
+    A kernel payload, optional ramdisk payload can be supplied. A DTB payload
+    can also be provided when header_version == v2.
+
+    Properties / Entry arguments:
+        - header-version: Android boot image header version, must be 0 or 2,
+          defaults to 0
+        - page-size: Image page size, defaults to 2048
+        - base: Base address added to the offsets below, defaults to 0x10000000
+        - kernel-offset: Kernel load offset from base, defaults to 0x00008000
+        - ramdisk-offset: Ramdisk load offset from base, defaults to 0x01000000
+        - tags-offset: ATAGS/FDT offset from base, defaults to 0x00000100
+        - dtb-offset: DTB load offset from base, defaults to 0x01f00000
+        - os-version: Encoded Android OS version and patch level, defaults to 0
+        - boot-name: Android boot image board name
+        - cmdline: Android boot command line
+
+    This entry uses the following subnodes:
+        - kernel: section containing the executable payload
+        - dtb: section containing the DTB payload, used by header version 2 only
+        - ramdisk: optional section containing a ramdisk payload
+
+    Example::
+        A v2 abootimg with control FDT placed in the DTB section:
+
+        android-boot {
+            header-version = <2>;
+            page-size = <4096>;
+            base = <0x12345678>;
+            kernel-offset = <0xCAFED00D>;
+            ramdisk-offset = <0xBEEFBABE>;
+            tags-offset = <0xFEEDDEAD>;
+            dtb-offset = <0x06660666>;
+            cmdline = "foo bar";
+
+            kernel {
+                u-boot-nodtb {
+                    # Many Android bootloaders support gzipped kernels
+                    compress = "gzip";
+                };
+            };
+
+            dtb {
+                u-boot-dtb {
+                };
+            };
+        };
+
+    Example::
+        A v0 abootimg with embedded control FDT (v0 doesn't support DTBs) and
+        an empty ramdisk (some bootloaders insist on a ramdisk being present):
+
+        android-boot {
+            header-version = <0>;
+            page-size = <2048>;
+            base = <0x80200000>;
+
+            kernel {
+                u-boot {
+                    no-expanded;
+                };
+            };
+
+            ramdisk {
+                fill {
+                    size = <1>;
+                };
+            };
+        };
+    """
+
+    def ReadNode(self):
+        super().ReadNode()
+        self.header_version = fdt_util.GetInt(self._node, 'header-version', 0)
+        self.page_size = fdt_util.GetInt(self._node, 'page-size', 2048)
+        self.base = self._GetIntCells('base', 0x10000000)
+        self.kernel_offset = self._GetIntCells('kernel-offset', 0x00008000)
+        self.ramdisk_offset = self._GetIntCells('ramdisk-offset', 0x01000000)
+        self.tags_offset = self._GetIntCells('tags-offset', 0x00000100)
+        self.dtb_offset = self._GetIntCells('dtb-offset', 0x01f00000)
+        self.os_version = fdt_util.GetInt(self._node, 'os-version', 0)
+        self.boot_name = fdt_util.GetString(self._node, 'boot-name', '')
+        self.cmdline = fdt_util.GetString(self._node, 'cmdline', '')
+
+        if self.header_version not in (0, 2):
+            self.Raise('Only Android boot image header versions 0 and 2 are '
+                       'supported')
+        if self.page_size <= 0 or self.page_size & (self.page_size - 1):
+            self.Raise('page-size must be a power of two')
+        if 'kernel' not in self._entries:
+            self.Raise("Missing required subnode 'kernel'")
+
+        if self.header_version == 0:
+            if self.page_size < BOOT_IMAGE_HEADER_V0_SIZE:
+                self.Raise('page-size must fit the Android boot image header')
+            if 'dtb' in self._entries:
+                self.Raise("Subnode 'dtb' requires header-version 2")
+        else:
+            # v2
+            if self.page_size < BOOT_IMAGE_HEADER_V2_SIZE:
+                self.Raise('page-size must fit the Android boot image header')
+            if 'dtb' not in self._entries:
+                self.Raise("Missing required subnode 'dtb'")
+
+    def ReadEntries(self):
+        for node in self._node.subnodes:
+            if node.name not in ('kernel', 'ramdisk', 'dtb'):
+                self.Raise("Unexpected subnode '%s'" % node.name)
+
+            entry = Entry.Create(self, node, etype='section',
+                                 expanded=self.GetImage().use_expanded,
+                                 missing_etype=self.GetImage().missing_etype)
+            entry.ReadNode()
+            entry.SetPrefix(self._name_prefix)
+            self._entries[node.name] = entry
+
+    def _GetIntCells(self, propname, default):
+        prop = self._node.props.get(propname)
+        if not prop:
+            return default
+
+        values = prop.value if isinstance(prop.value, list) else [prop.value]
+        if len(values) > 2:
+            self.Raise("Property '%s' must contain one or two cells" %
+                       propname)
+
+        value = 0
+        for cell in values:
+            value = value << 32 | fdt_util.fdt32_to_cpu(cell)
+
+        return value
+
+    def _GetAddr(self, offset, name, size=32):
+        addr = self.base + offset
+        if addr >= 1 << size:
+            self.Raise('%s address %#x does not fit in %d bits' %
+                       (name, addr, size))
+
+        return addr
+
+    def _CheckFit(self, name, data, size):
+        if len(data) > size:
+            self.Raise('%s is %d bytes, maximum is %d' %
+                       (name, len(data), size))
+
+        return data + b'\0' * (size - len(data))
+
+    @staticmethod
+    def _BootId(*payloads):
+        digest = hashlib.sha1()
+        for data in payloads:
+            digest.update(data)
+            digest.update(struct.pack('<I', len(data)))
+
+        return digest.digest() + b'\0' * 12
+
+    def _SplitCmdline(self):
+        cmdline = self.cmdline.encode('ascii') + b'\0'
+        return (self._CheckFit('cmdline', cmdline[:BOOT_ARGS_SIZE],
+                               BOOT_ARGS_SIZE),
+                self._CheckFit('extra-cmdline', cmdline[BOOT_ARGS_SIZE:],
+                               BOOT_EXTRA_ARGS_SIZE))
+
+    def _GetEntryData(self, name, required, default=None):
+        entry = self._entries.get(name)
+        if not entry and default is not None:
+            return default
+        return entry.GetData(required)
+
+    def _BuildV0SectionData(self, required):
+        kernel = self._GetEntryData('kernel', required)
+        ramdisk = self._GetEntryData('ramdisk', required, b'')
+        if not required and (kernel is None or vendor_dt is None or
+                             ramdisk is None):
+            return None
+
+        boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
+                                   BOOT_NAME_SIZE)
+        cmdline = self._CheckFit('cmdline', self.cmdline.encode('ascii'),
+                                 BOOT_ARGS_SIZE)
+
+        boot_id_payloads = [kernel, ramdisk, b'']
+        image_id = self._BootId(*boot_id_payloads)
+
+        header = struct.pack(BOOT_IMAGE_HEADER_V0,
+                             BOOT_MAGIC,
+                             len(kernel),
+                             self._GetAddr(self.kernel_offset, 'kernel'),
+                             len(ramdisk),
+                             self._GetAddr(self.ramdisk_offset, 'ramdisk'),
+                             0, # second_len
+                             0, # second_offset
+                             self._GetAddr(self.tags_offset, 'tags'),
+                             self.page_size,
+                             self.header_version,
+                             self.os_version,
+                             boot_name,
+                             cmdline,
+                             image_id)
+
+        image = bytearray()
+        image += self.PadToAlignment(header, self.page_size)
+        image += self.PadToAlignment(kernel, self.page_size)
+        image += self.PadToAlignment(ramdisk, self.page_size)
+
+        return bytes(image)
+
+    def _BuildV2SectionData(self, required):
+        kernel = self._GetEntryData('kernel', required)
+        dtb = self._GetEntryData('dtb', required)
+        ramdisk = self._GetEntryData('ramdisk', required, b'')
+        if not required and (kernel is None or dtb is None):
+            return None
+
+        boot_name = self._CheckFit('boot-name', self.boot_name.encode('ascii'),
+                                   BOOT_NAME_SIZE)
+        cmdline, extra_cmdline = self._SplitCmdline()
+        image_id = self._BootId(kernel, ramdisk, b'', b'', dtb)
+
+        header = struct.pack(BOOT_IMAGE_HEADER_V2,
+                             BOOT_MAGIC,
+                             len(kernel),
+                             self._GetAddr(self.kernel_offset, 'kernel'),
+                             len(ramdisk),
+                             self._GetAddr(self.ramdisk_offset, 'ramdisk'),
+                             0, # second_len
+                             0, # second_offset
+                             self._GetAddr(self.tags_offset, 'tags'),
+                             self.page_size,
+                             self.header_version,
+                             self.os_version,
+                             boot_name,
+                             cmdline,
+                             image_id,
+                             extra_cmdline,
+                             0, # recovery_dtbo_len
+                             0, # recovery_dtbo_offset
+                             BOOT_IMAGE_HEADER_V2_SIZE,
+                             len(dtb),
+                             self._GetAddr(self.dtb_offset, 'dtb', size=64))
+
+        image = bytearray()
+        image += self.PadToAlignment(header, self.page_size)
+        image += self.PadToAlignment(kernel, self.page_size)
+        image += self.PadToAlignment(ramdisk, self.page_size)
+        image += self.PadToAlignment(dtb, self.page_size)
+
+        return bytes(image)
+
+    def BuildSectionData(self, required):
+        if self.header_version == 0:
+            return self._BuildV0SectionData(required)
+
+        return self._BuildV2SectionData(required)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index bf98b268ac1..71740205c72 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -5598,6 +5598,154 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
         self.assertIn("Node '/binman/renesas-rcar4-sa0': SRAM data longer than 966656 Bytes",
                       str(exc.exception))
 
+    @staticmethod
+    def _AndroidBootId(*payloads):
+        digest = hashlib.sha1()
+        for data in payloads:
+            digest.update(data)
+            digest.update(struct.pack('<I', len(data)))
+
+        return digest.digest() + b'\0' * 12
+
+    def testAndroidBootUnsupportedVersion(self):
+        """Test that binman rejects versions other than v0 and v2"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_unsupported_version.dts')
+        self.assertIn("Only Android boot image header versions 0 and 2 are supported",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidPageSize(self):
+        """Test that binman rejects page sizes that are not a power of 2"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_pagesize.dts')
+        self.assertIn("page-size must be a power of two",
+                      str(exc.exception))
+
+    def testAndroidBootV0PageSizeTooSmol(self):
+        """Test that binman rejects page sizes that are smaller than header size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v0_pagesize_too_smol.dts')
+        self.assertIn("page-size must fit the Android boot image header",
+                      str(exc.exception))
+
+    def testAndroidBootMissingKernel(self):
+        """Test that binman rejects configurations missing a kernel{} subnode"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_missing_kernel.dts')
+        self.assertIn("Missing required subnode 'kernel'",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidSubnode(self):
+        """Test that binman rejects invalid subnodes"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_subnode.dts')
+        self.assertIn("Unexpected subnode 'bacon'",
+                      str(exc.exception))
+
+    def testAndroidBootInvalidAddr(self):
+        """Test that binman rejects invalid addresses"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_invalid_addr.dts')
+        self.assertIn("kernel address 0xdeadbeefdafed00d does not fit in 32 bits",
+                      str(exc.exception))
+
+    def testAndroidBootOversizedBootName(self):
+        """Test that binman rejects boot-name exceeding 16 chars"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_oversized_bootname.dts')
+        self.assertIn("boot-name is 38 bytes, maximum is 16",
+                      str(exc.exception))
+
+    def testAndroidBootChonkyCells(self):
+        """Test that binman rejects >2 cell addresses"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_chonky_cells.dts')
+        self.assertIn("Property 'base' must contain one or two cells",
+                      str(exc.exception))
+
+    def testAndroidBootV0(self):
+        """Test that binman can produce a plain legacy Android boot image"""
+        data = self._DoReadFile('android_boot_v0.dts')
+        header = struct.unpack_from('<8s10I16s512s32s', data, 0)
+
+        self.assertEqual(b'ANDROID!', header[0])
+        self.assertEqual(len(U_BOOT_DATA), header[1])
+        self.assertEqual(0x80208000, header[2])
+        self.assertEqual(1, header[3])
+        self.assertEqual(0x81200000, header[4])
+        self.assertEqual(0, header[5])
+        self.assertEqual(0, header[6])
+        self.assertEqual(0x80200100, header[7])
+        self.assertEqual(0x800, header[8])
+        self.assertEqual(0, header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(b'foo', header[12].split(b'\0', 1)[0])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'\0', b''),
+                         header[13])
+
+    def testAndroidBootV0WithDTB(self):
+        """Test that binman rejects v0 abootimgs containing a dtb section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_dtb_in_v0.dts')
+        self.assertIn("Subnode 'dtb' requires header-version 2",
+                      str(exc.exception))
+
+    def testAndroidBootV2(self):
+        """Test that binman can produce an Android boot image"""
+        data = self._DoReadFile('android_boot_v2.dts')
+        header = struct.unpack_from('<8s10I16s512s32s1024sIQIIQ', data, 0)
+
+        self.assertEqual(b'ANDROID!', header[0])
+        self.assertEqual(len(U_BOOT_DATA), header[1])
+        self.assertEqual(0x80008000, header[2])
+        self.assertEqual(0, header[3])
+        self.assertEqual(0x81000000, header[4])
+        self.assertEqual(0, header[5])
+        self.assertEqual(0, header[6])
+        self.assertEqual(0x80000100, header[7])
+        self.assertEqual(0x800, header[8])
+        self.assertEqual(2, header[9])
+        self.assertEqual(0, header[10])
+        self.assertEqual(b'test-board', header[11].split(b'\0', 1)[0])
+        self.assertEqual(0, header[15])
+        self.assertEqual(0, header[16])
+        self.assertEqual(1660, header[17])
+        self.assertEqual(len(U_BOOT_DTB_DATA), header[18])
+        self.assertEqual(0x81f00000, header[19])
+        self.assertEqual(self._AndroidBootId(U_BOOT_DATA, b'', b'', b'',
+                                             U_BOOT_DTB_DATA), header[13])
+
+        cmdline = header[12].split(b'\0', 1)[0]
+        extra_cmdline = header[14].split(b'\0', 1)[0]
+        self.assertEqual(b"tests.. ", cmdline[-8:])
+        self.assertEqual(512, len(cmdline))
+        self.assertEqual(b'sup', extra_cmdline)
+
+        self.assertEqual(U_BOOT_DATA, data[0x800:0x800 + len(U_BOOT_DATA)])
+        self.assertEqual(U_BOOT_DTB_DATA,
+                         data[0x1000:0x1000 + len(U_BOOT_DTB_DATA)])
+
+    def testAndroidBootV2PageSizeTooSmol(self):
+        """Test that binman rejects page sizes that are smaller than header size"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_pagesize_too_smol.dts')
+        self.assertIn("page-size must fit the Android boot image header",
+                      str(exc.exception))
+
+    def testAndroidBootV2MissingDTB(self):
+        """Test that binman rejects v2 abootimgs missing a DTB section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_missing_dtb.dts')
+        self.assertIn("Missing required subnode 'dtb'",
+                      str(exc.exception))
+
+    def testAndroidBootV2VendorDt(self):
+        """Test that binman rejects v2 abootimgs with a vendor-dt section"""
+        with self.assertRaises(ValueError) as exc:
+            self._DoReadFile('android_boot_v2_vendor_dt.dts')
+        self.assertIn("Subnode 'vendor-dt' requires header-version 0",
+                      str(exc.exception))
+
     def testFitFdtOper(self):
         """Check handling of a specified FIT operation"""
         entry_args = {
diff --git a/tools/binman/test/android_boot_chonky_cells.dts b/tools/binman/test/android_boot_chonky_cells.dts
new file mode 100644
index 00000000000..7fdc1c86f6b
--- /dev/null
+++ b/tools/binman/test/android_boot_chonky_cells.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			base = <0xDEADBEEF 0xCAFED00D 0xDECAF>;
+
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_dtb_in_v0.dts b/tools/binman/test/android_boot_dtb_in_v0.dts
new file mode 100644
index 00000000000..24b91f9a33c
--- /dev/null
+++ b/tools/binman/test/android_boot_dtb_in_v0.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel {};
+			dtb {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_addr.dts b/tools/binman/test/android_boot_invalid_addr.dts
new file mode 100644
index 00000000000..0d7cb051921
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_addr.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel-offset = <0xDEADBEEF 0xCAFED00D>;
+
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_pagesize.dts b/tools/binman/test/android_boot_invalid_pagesize.dts
new file mode 100644
index 00000000000..01925187475
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_pagesize.dts
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			page-size = <2049>;
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_invalid_subnode.dts b/tools/binman/test/android_boot_invalid_subnode.dts
new file mode 100644
index 00000000000..747f95068be
--- /dev/null
+++ b/tools/binman/test/android_boot_invalid_subnode.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			kernel {};
+			bacon {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_missing_kernel.dts b/tools/binman/test/android_boot_missing_kernel.dts
new file mode 100644
index 00000000000..fe30eb5cbb3
--- /dev/null
+++ b/tools/binman/test/android_boot_missing_kernel.dts
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {};
+	};
+};
diff --git a/tools/binman/test/android_boot_oversized_bootname.dts b/tools/binman/test/android_boot_oversized_bootname.dts
new file mode 100644
index 00000000000..5f5564840f8
--- /dev/null
+++ b/tools/binman/test/android_boot_oversized_bootname.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			boot-name = "this is decidedly longer than 16 bytes";
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_unsupported_version.dts b/tools/binman/test/android_boot_unsupported_version.dts
new file mode 100644
index 00000000000..9843b368b3a
--- /dev/null
+++ b/tools/binman/test/android_boot_unsupported_version.dts
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <1>;
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v0.dts b/tools/binman/test/android_boot_v0.dts
new file mode 100644
index 00000000000..18813ff3613
--- /dev/null
+++ b/tools/binman/test/android_boot_v0.dts
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that android-boot can be referenced before it's built */
+		collection {
+			content = <&abootimg>;
+		};
+
+		abootimg: android-boot {
+			header-version = <0>;
+			page-size = <0x800>;
+			base = <0x80200000>;
+			cmdline = "foo";
+
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+
+			ramdisk {
+				fill {
+					size = <1>;
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v0_pagesize_too_smol.dts b/tools/binman/test/android_boot_v0_pagesize_too_smol.dts
new file mode 100644
index 00000000000..2c617f12a1e
--- /dev/null
+++ b/tools/binman/test/android_boot_v0_pagesize_too_smol.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			page-size = <32>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2.dts b/tools/binman/test/android_boot_v2.dts
new file mode 100644
index 00000000000..55fab329443
--- /dev/null
+++ b/tools/binman/test/android_boot_v2.dts
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#define CMDLINE(...) #__VA_ARGS__
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		/* confirm that android-boot can be referenced before it's built */
+		collection {
+			content = <&abootimg>;
+		};
+
+		abootimg: android-boot {
+			header-version = <2>;
+			page-size = <0x800>;
+			base = <0x80000000>;
+			kernel-offset = <0x00008000>;
+			ramdisk-offset = <0x01000000>;
+			tags-offset = <0x00000100>;
+			dtb-offset = <0x01f00000>;
+			boot-name = "test-board";
+			cmdline = CMDLINE(
+					  This is a very long commandline that is sure to exceed the
+					  512 chars that is allotted to the cmdline and this should
+					  spillover into extra_cmdline which is useful from a
+					  function testing standpoint. Gosh, it sure it hard to come
+					  up with enough filler text here to get over the 512 char
+					  limit though, huh? Even for someone as loquacious as
+					  myself. So anyway. How's your day going? I wrote a binman
+					  functional test today. It was fun. Did you know that
+					  binman is great. I like binman. I also like functional
+					  tests.. sup);
+
+			kernel {
+				u-boot {
+					no-expanded;
+				};
+			};
+
+			dtb {
+				u-boot-dtb {
+				};
+			};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_missing_dtb.dts b/tools/binman/test/android_boot_v2_missing_dtb.dts
new file mode 100644
index 00000000000..bf7bee622c4
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_missing_dtb.dts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_pagesize_too_smol.dts b/tools/binman/test/android_boot_v2_pagesize_too_smol.dts
new file mode 100644
index 00000000000..0761ff20543
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_pagesize_too_smol.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			page-size = <32>;
+			kernel {};
+		};
+	};
+};
diff --git a/tools/binman/test/android_boot_v2_vendor_dt.dts b/tools/binman/test/android_boot_v2_vendor_dt.dts
new file mode 100644
index 00000000000..a7684d8492a
--- /dev/null
+++ b/tools/binman/test/android_boot_v2_vendor_dt.dts
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		android-boot {
+			header-version = <2>;
+			kernel {};
+			dtb {};
+			vendor-dt {};
+		};
+	};
+};

-- 
2.54.0


^ permalink raw reply related

* [PATCH v3 03/10] binman: section: add AlignUp+PadToAlignment helpers
From: Sam Day @ 2026-06-10  1:27 UTC (permalink / raw)
  To: u-boot
  Cc: Tom Rini, Simon Glass, Alper Nebi Yasak, Quentin Schulz,
	Bryan Brattlof, Yannic Moog, Wojciech Dubowik, Yegor Yefremov,
	Patrice Chotard, Heinrich Schuchardt, Marek Vasut,
	Rasmus Villemoes, Javier Tia, Minkyu Kang, Kaustabh Chakraborty,
	Henrik Grimler, yan wang, Marek Vasut, Denis Mukhin, Sam Day
In-Reply-To: <20260610-android-binman-v3-0-710298a38fcc@samcday.com>

In the following commits we will be introducing new etypes derived from
Entry_section which need to align values and pad data. To avoid
duplication we introduce the shared helpers here.
---
 tools/binman/etype/section.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index 8530b7ee17f..a8eca3eaf5f 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -1040,3 +1040,12 @@ class Entry_section(Entry):
             for entry in entries.values():
                 return entry.read_elf_segments()
         return None
+
+    def AlignUp(self, value, align):
+        """Return value aligned to given power of 2"""
+        return (value + align - 1) & ~(align - 1)
+
+
+    def PadToAlignment(self, data, align):
+        """Null-pads given data to given power of 2"""
+        return data + b'\0' * (self.AlignUp(len(data), align) - len(data))

-- 
2.54.0


^ permalink raw reply related


This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.