Skip to content

Commit

Permalink
snagrecover: add Xilinx ZynqMP support
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rgantois committed Dec 18, 2024
1 parent 7ccb4cb commit d48222c
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ISP, UUU, and sunxi-fel. Snagboot is made of two separate parts:
</p>

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.

Expand Down
27 changes: 27 additions & 0 deletions docs/fw_binaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

This comment has been minimized.

Copy link
@tpetazzoni

tpetazzoni Dec 18, 2024

Contributor

Typo here


**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

3 changes: 3 additions & 0 deletions src/snagrecover/50-snagboot.rules
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions src/snagrecover/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"sama5": "03eb:6124",
"sunxi": "1f3a:efe8",
"am6x": "0451:6165",
"zynqmp": "03fd:0050",
"imx": {
"imx8qxp": "1fc9:012f",
"imx8qm": "1fc9:0129",
Expand Down
3 changes: 3 additions & 0 deletions src/snagrecover/firmware/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
175 changes: 175 additions & 0 deletions src/snagrecover/firmware/zynqmp_fw.py
Original file line number Diff line number Diff line change
@@ -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 = "<LLLLLL36sL"
size = 0x40

def update_checksum(self):
data = struct.pack(ZynqMPImageTable.fmt, *astuple(self))
self.csum = zynqmp_csum(data[:-4])

@dataclass
class ZynqMPImageHeader(Header):
next_image: int
part_hdr: int
reserved: int
part_count: int
# Other fields are ignored

fmt = "<LLLL"
size = 16

@dataclass
class ZynqMPPartHeader(Header):
enc_size: int
plain_size: int
total_size: int
next_part: int
exec_lo: int
exec_hi: int
load_lo: int
load_hi: int
start: int
attrs: int
sec_count: int
csum_word_offset: int
image_hdr: int
ac_offset: int
part_id: int
csum: int

fmt = "<LLLLLLLLLLLLLLLL"
size = 0x40

def update_checksum(self):
data = struct.pack(ZynqMPPartHeader.fmt, *astuple(self))
self.csum = zynqmp_csum(data[:-4])

def zynqmp_csum(data):
s = 0

for i in range(0, len(data), 4):
s = (s + int.from_bytes(data[i:i+4], "little")) % (1 << 32)

return s ^ 0xffffffff

def find_img_table(boot_bin):
boot_hdr = boot_bin[:0xb5]
hdr = boot_hdr

sig = hdr[0x24:0x28]
if sig != b"XNLX":
raise ValueError("Invalid ZynqMP boot header signature!")

return int.from_bytes(boot_hdr[0x98:0x9c], "little")

def drop_images(boot_bin: bytearray, keep_images):
"""
Drop partitions which aren't required for the FSBL stage from the boot
image.
"""

if keep_images == 0:
raise ValueError("Must keep at least one image!")

img_table_offset = find_img_table(boot_bin)
img_table = ZynqMPImageTable.read(boot_bin, img_table_offset)

kept_images = []
next_image = 4 * img_table.image_hdr
for _ in range(keep_images):
image = ZynqMPImageHeader.read(boot_bin, next_image)
kept_images.append(image)
next_image = 4 * image.next_image

last_kept_image = kept_images[-1]
if next_image == 0:
return

# Mark as last image
last_kept_image.next_image = 0
ZynqMPImageHeader.write(last_kept_image, boot_bin)

# Find last remaining partition
next_part = 4 * last_kept_image.part_hdr
for _ in range(last_kept_image.part_count):
part = ZynqMPPartHeader.read(boot_bin, next_part)
next_part = 4 * part.next_part

last_kept_part = part

# Drop unused partition data
cutoff = 4 * (last_kept_part.start + last_kept_part.total_size)

# Mark as last partition and update csum
last_kept_part.next_part = 0
last_kept_part.update_checksum()
ZynqMPPartHeader.write(last_kept_part, boot_bin)

# Update partition count and csum
img_table.part_count = sum([image.part_count for image in kept_images])
img_table.update_checksum()

ZynqMPImageTable.write(img_table, boot_bin)

return bytes(boot_bin[:cutoff])

def zynqmp_run(dev, fw_name, fw_blob, subfw_name):
dfu_cmd = dfu.DFU(dev, stm32=False)

if fw_name == "fsbl":
partid = 0
if fw_name == "boot":
if subfw_name == "fsbl":
partid = 0
"""
Only the FSBL and PMUFW are required for the first stage.
Keep the first two images and drop the rest, so that
the firmware blob can fit in SRAM.
"""
fw_blob = drop_images(bytearray(fw_blob), 2)
else:
partid = 1

logger.info("Downloading file...")
dfu_cmd.download_and_run(fw_blob, partid, offset=0, size=len(fw_blob))
logger.info("Done")

10 changes: 10 additions & 0 deletions src/snagrecover/protocols/dfu.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
from errno import EIO,ENODEV,EPIPE
from snagrecover import utils

def list_partids(dev: usb.core.Device):
cfg = dev.get_active_configuration()
intfs = cfg.interfaces()
partids = []

for intf in intfs:
partids.append(intf.bAlternateSetting)

return partids

def search_partid(dev: usb.core.Device, partname: str, match_prefix=False) -> int:
# search for an altsetting associated with a partition name
cfg = dev.get_active_configuration()
Expand Down
33 changes: 33 additions & 0 deletions src/snagrecover/recoveries/zynqmp.py
Original file line number Diff line number Diff line change
@@ -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")

2 changes: 2 additions & 0 deletions src/snagrecover/supported_socs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ tested:
family: stm32mp
stm32mp25:
family: stm32mp
zynqmp:
family: zynqmp
untested:
a10:
family: sunxi
Expand Down
2 changes: 2 additions & 0 deletions src/snagrecover/templates/zynqmp-generic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
boot:
path: boot.bin
3 changes: 3 additions & 0 deletions src/snagrecover/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

0 comments on commit d48222c

Please sign in to comment.