From d48222cb5dd6d2c0e45a966f1b4fce7a00262f53 Mon Sep 17 00:00:00 2001
From: Romain Gantois
Date: Wed, 18 Dec 2024 16:40:35 +0100
Subject: [PATCH] snagrecover: add Xilinx ZynqMP support
Add support for Xilinx ZynqMP platforms. The USB recovery procedure should
be identical for all ZynqMP SoCs so only one SoC model is registered.
Signed-off-by: Romain Gantois
---
README.md | 2 +-
docs/fw_binaries.md | 27 +++
src/snagrecover/50-snagboot.rules | 3 +
src/snagrecover/config.py | 1 +
src/snagrecover/firmware/firmware.py | 3 +
src/snagrecover/firmware/zynqmp_fw.py | 175 ++++++++++++++++++
src/snagrecover/protocols/dfu.py | 10 +
src/snagrecover/recoveries/zynqmp.py | 33 ++++
src/snagrecover/supported_socs.yaml | 2 +
src/snagrecover/templates/zynqmp-generic.yaml | 2 +
src/snagrecover/utils.py | 3 +
11 files changed, 260 insertions(+), 1 deletion(-)
create mode 100644 src/snagrecover/firmware/zynqmp_fw.py
create mode 100644 src/snagrecover/recoveries/zynqmp.py
create mode 100644 src/snagrecover/templates/zynqmp-generic.yaml
diff --git a/README.md b/README.md
index 783c518..00ec0aa 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ ISP, UUU, and sunxi-fel. Snagboot is made of two separate parts:
The currently supported SoC families are ST STM32MP1/2, Microchip SAMA5, NXP
-i.MX6/7/8/93, TI AM335x, Allwinner SUNXI, TI AM62x and TI AM64x. Please check
+i.MX6/7/8/93, TI AM335x, Allwinner SUNXI, TI AM62x, TI AM64x and Xilinx ZynqMP. Please check
[supported_socs.yaml](https://github.com/bootlin/snagboot/blob/main/src/snagrecover/supported_socs.yaml) or run `snagrecover
--list-socs` for a more precise list of supported SoCs.
diff --git a/docs/fw_binaries.md b/docs/fw_binaries.md
index 45b7eb6..fb1020f 100644
--- a/docs/fw_binaries.md
+++ b/docs/fw_binaries.md
@@ -258,3 +258,30 @@ DM firmware, U-Boot SPL for A53 and device tree blobs
configuration:
* path
+## For Xilinx ZynqMP devices
+
+[example](../src/snagrecover/templates/zynqmp-generic.yaml)
+
+Detailed instructions for building the required boot images can be found in the
+[Xilinx documentation](https://xilinx.github.io/Embedded-Design-Tutorials), in
+the "boot-and-configuration" section for ZynqMP SoCs. Please note that the
+first boot image containing only the FSBL and PMUFW should not be required, as
+Snagboot is capable of extracting a working first-stage boot image from the
+full boot image.
+
+The following images are required for all AM6xx SoCs:
+
+**boot:** Xilinx ZynqMP boot image containing the Xilinx FSBL, PMUFW, ATF,
+control DT and U-Boot proper. The FSBL should be compiled with USB support
+enabled.
+
+configuration:
+ * path
+
+**fsbl:** (optional) Xilinx ZynqMP boot image containing the Xilinx FSBL and
+PMUFW. This is optional, and should only be provided if Snagboot fails to
+extract the FSBL and PMUFW from the complete boot image.
+
+configuration:
+ * path
+
diff --git a/src/snagrecover/50-snagboot.rules b/src/snagrecover/50-snagboot.rules
index 58e6df4..d4f4dde 100644
--- a/src/snagrecover/50-snagboot.rules
+++ b/src/snagrecover/50-snagboot.rules
@@ -73,3 +73,6 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="1f3a", ATTRS{idProduct}=="efe8", MODE="0660"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="6165", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="6141", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="d022", MODE="0660", TAG+="uaccess"
+
+#Xilinx rules
+SUBSYSTEM=="usb", ATTRS{idVendor}=="03fd", ATTRS{idProduct}=="0050", MODE="0660", TAG+="uaccess"
diff --git a/src/snagrecover/config.py b/src/snagrecover/config.py
index e290da4..db09fb3 100644
--- a/src/snagrecover/config.py
+++ b/src/snagrecover/config.py
@@ -29,6 +29,7 @@
"sama5": "03eb:6124",
"sunxi": "1f3a:efe8",
"am6x": "0451:6165",
+ "zynqmp": "03fd:0050",
"imx": {
"imx8qxp": "1fc9:012f",
"imx8qm": "1fc9:0129",
diff --git a/src/snagrecover/firmware/firmware.py b/src/snagrecover/firmware/firmware.py
index 1b1767b..4a855e0 100644
--- a/src/snagrecover/firmware/firmware.py
+++ b/src/snagrecover/firmware/firmware.py
@@ -119,6 +119,9 @@ def run_firmware(port, fw_name: str, subfw_name: str = ""):
sunxi_run(port, fw_name, fw_blob)
elif soc_family == "am6x":
am6x_run(port, fw_name, fw_blob)
+ elif soc_family == "zynqmp":
+ from snagrecover.firmware.zynqmp_fw import zynqmp_run
+ zynqmp_run(port, fw_name, fw_blob, subfw_name)
else:
raise Exception(f"Unsupported SoC family {soc_family}")
logger.info(f"Done installing firmware {fw_name}")
diff --git a/src/snagrecover/firmware/zynqmp_fw.py b/src/snagrecover/firmware/zynqmp_fw.py
new file mode 100644
index 0000000..2aff71d
--- /dev/null
+++ b/src/snagrecover/firmware/zynqmp_fw.py
@@ -0,0 +1,175 @@
+"""
+Handle ZynqMP boot images, as generated by the Xilinx bootgen tool.
+"""
+
+import logging
+logger = logging.getLogger("snagrecover")
+from snagrecover.protocols import dfu
+from dataclasses import dataclass, astuple
+import struct
+
+class Header():
+ """
+ Generic class representing a packed C struct embedded in a bytearray.
+ """
+
+ fmt = ""
+ size = 0
+
+ @classmethod
+ def read(cls, data, offset=0):
+ obj = cls(*struct.unpack(cls.fmt, data[offset:offset + cls.size]))
+ obj.offset = offset
+
+ return obj
+
+ @classmethod
+ def write(cls, self, data):
+ offset = self.offset
+ data[offset:offset + cls.size] = struct.pack(cls.fmt, *astuple(self))
+
+@dataclass
+class ZynqMPImageTable(Header):
+ version: int
+ part_count: int
+ part_hdr: int
+ image_hdr: int
+ auth_cert: int
+ aux_bootdev: int
+ padding: bytes
+ csum: int
+
+ fmt = " int:
# search for an altsetting associated with a partition name
cfg = dev.get_active_configuration()
diff --git a/src/snagrecover/recoveries/zynqmp.py b/src/snagrecover/recoveries/zynqmp.py
new file mode 100644
index 0000000..3af2867
--- /dev/null
+++ b/src/snagrecover/recoveries/zynqmp.py
@@ -0,0 +1,33 @@
+import usb
+import logging
+logger = logging.getLogger("snagrecover")
+from snagrecover.firmware.firmware import run_firmware
+from snagrecover.utils import get_usb
+from snagrecover.config import recovery_config
+from snagrecover.protocols import dfu
+import time
+
+def altmode1_check(dev: usb.core.Device):
+ try:
+ return 1 in dfu.list_partids(dev)
+ except usb.core.USBError:
+ logger.warning("USB device is unavailable")
+
+ return False
+
+def main():
+ usb_addr = recovery_config["usb_path"]
+ dev = get_usb(usb_addr)
+
+ if "fsbl" not in recovery_config["firmware"]:
+ logger.warning("No FSBL image given, will attempt to extract it from full boot image")
+ run_firmware(dev, "boot", "fsbl")
+ else:
+ run_firmware(dev, "fsbl")
+
+ time.sleep(0.5)
+
+ dev = get_usb(usb_addr, ready_check=altmode1_check)
+
+ run_firmware(dev, "boot")
+
diff --git a/src/snagrecover/supported_socs.yaml b/src/snagrecover/supported_socs.yaml
index fe3fa4d..c36f5d0 100644
--- a/src/snagrecover/supported_socs.yaml
+++ b/src/snagrecover/supported_socs.yaml
@@ -53,6 +53,8 @@ tested:
family: stm32mp
stm32mp25:
family: stm32mp
+ zynqmp:
+ family: zynqmp
untested:
a10:
family: sunxi
diff --git a/src/snagrecover/templates/zynqmp-generic.yaml b/src/snagrecover/templates/zynqmp-generic.yaml
new file mode 100644
index 0000000..d129017
--- /dev/null
+++ b/src/snagrecover/templates/zynqmp-generic.yaml
@@ -0,0 +1,2 @@
+boot:
+ path: boot.bin
diff --git a/src/snagrecover/utils.py b/src/snagrecover/utils.py
index ca8731e..aca0f93 100644
--- a/src/snagrecover/utils.py
+++ b/src/snagrecover/utils.py
@@ -183,6 +183,9 @@ def get_recovery(soc_family: str):
elif soc_family == "am6x":
from snagrecover.recoveries.am6x import main as am6x_recovery
return am6x_recovery
+ elif soc_family == "zynqmp":
+ from snagrecover.recoveries.zynqmp import main as zynqmp_recovery
+ return zynqmp_recovery
else:
cli_error(f"unsupported board family {soc_family}")