JTAG boot in QSPI mode

Booting a ZynqMP target over JTAG is very useful for a development cycle, as the alternative to programming the flash device is time consuming. However, while in previous Xilinx Vivado versions (<=2017.2) programming the QSPI flash was possible over JTAG, in the latest versions of the tool, the programming fails with errors.

Boot mode

The Zynq devices have 4 boot pins that are sampled at boot time and indicate which device will be used to load its binaries from. These may be set to QSPI, MMC, NAND or JTAG. The JTAG mode is generally used to stall the loading until the host provides the binaries over JTAG, for development purposes. In previous versions of the tool, JTAG would work to load binaries regardless of the boot mode pins, but on the last few versions it is mandatory to set the boot pins to JTAG, or the loading fails. Unfortunately there is already hardware in the field that hard-coded these mode pins to a particular mode (i.e. QSPI) relying on the capability of the hardware to always fall back to JTAG if development required it. With these new versions, this is not possible out of the box, so in this post we will discuss how to work around this limitation.

The load error

The target in question is hard-wired to QSPI mode, and loading the binaries over JTAG is possible… as long as the QSPI memory is empty. Once QSPI has binaries loaded in it, they will be executed while the FSBL is loading and about to run, messing up the process and ultimately leading to a program error. Ideally, the processor should be halted until the FSBL is loaded and has run, but this is not the case. In this boot log, notice the boot mode set to QSPI_MODE.

Xilinx Zynq MP First Stage Boot Loader
Release 2019.2   Aug 27 2020  -  22:26:51
NOTICE:  ATF running on XCZU5EV/silicon v4/RTL5.1 at 0xfffea000
NOTICE:  BL31: Secure code at 0x0
NOTICE:  BL31: Non secure code at 0x8000000
NOTICE:  BL31: v2.0(release):xilinx-v2019.1-12-g713dace9
NOTICE:  BL31: Built : 00:04:32, Aug 27 2020
PMUFW:  v1.1

U-Boot 2019.01 (Aug 27 2020 - 00:45:45 +0000)

Board: Xilinx ZynqMP
DRAM:  2 GiB
EL Level:       EL2
Chip ID:        zu5ev
MMC:   mmc@ff160000: 0
Loading Environment from SPI Flash... SF: Detected s25fl512s_256k with page size 256 Bytes, erase size 256 KiB, total 64 MiB
*** Warning - bad CRC, using default environment

## Error: flags type check failure for "serverip" <= "AUTO" (type: i)
himport_r: can't insert "serverip=AUTO" into hash table
In:    serial@ff000000
Out:   serial@ff000000
Err:   serial@ff000000
Board: Xilinx ZynqMP
Bootmode: QSPI_MODE
Reset reason:   DEBUG
Net:   ZYNQ GEM: ff0b0000, phyaddr 3, interface rgmii-id
eth0: ethernet@ff0b0000, eth-1: ethernet@ff0c0000, eth-1: ethernet@ff0d0000, eth-1: ethernet@ff0e0000
U-BOOT for xu5-eths

Hit any key to stop autoboot:  0
ZynqMP> "Synchronous Abort" handler, esr 0x02000000
elr: 00000000881e3008 lr : 0000000008043470 (reloc)
elr: 00000000fffea008 lr : 000000007fe4a470
x0 : 0000000030c50830 x1 : 0000000000000001
<...>
x26: 0000000000000000 x27: 0000000000000000
x28: 000000007fead97c x29: 000000007ddbacc0

Resetting CPU ...

### ERROR ### Please RESET the board ###

Workarounds

Folks have found ways to work around this issue in several ways.

  1. Use an older version of the tools to load the binaries, which is generally cumbersome.
  2. Use a modified version of the FSBL to use to load the binaries, and another version (the original) to include in the binaries to be programmed. The modified version would ignore the mode pins in the code, and assume it sampled them as JTAG, so the binary loading is skipped, i.e. stalling the target.
  3. Use a combination of Vivado and Vitis to coerce the target to the right mode. Attempt writing the bitstream using Vivado first, then using Vitis to do the same thing. And then finally, after re-starting Vivado, attempt programming the flash binaries (BOOT.BIN, image.ub) using hardware manager, to finally succeed. Somehow, this procedure sets the target to a happy mode.

The alternative

Digging through the ZynqMP memory map, the register BOOT_MODE_USER from CTL_APB module contains two undocumented (as far as I can tell) fields that can be used to fake the boot mode pins under software control on demand. The script below uses these fields to set the alternate boot mode to JTAG, regardless of the actual boot mode pins hard-wired to QSPI. In this output snippet you can see the boot mode set to JTAG, disregarding the mode pins.

U-Boot 2019.01 (Aug 27 2020 - 22:25:58 +0000)

Board: Xilinx ZynqMP
DRAM:  2 GiB
EL Level:       EL2
Chip ID:        zu5ev
MMC:   mmc@ff160000: 0, mmc@ff170000: 1
Loading Environment from FAT...
himport_r: can't insert "serverip=AUTO" into hash table
In:    serial@ff000000
Out:   serial@ff000000
Err:   serial@ff000000
Board: Xilinx ZynqMP
Bootmode: JTAG_MODE
Reset reason:   DEBUG
Net:   ZYNQ GEM: ff0b0000, phyaddr 3, interface rgmii-id
eth0: ethernet@ff0b0000, eth-1: ethernet@ff0c0000, eth-1: ethernet@ff0d0000, eth-1: ethernet@ff0e0000
U-BOOT for xu5-eths

The secret sauce

The script writes to the BOOT_MODE_USER register before running the FSBL using these commands:

# Read boot mode
set mode [expr [mrd -value 0xFF5E0200] & 0xf]
puts stderr "INFO: Boot mode set to $mode"
puts stderr "INFO: Forcing Alternate boot mode to JTAG"
mwr 0xFF5E0200 0x100

The script expects the binaries to be found under ./images/linux, i.e. the Petalinux project root directory. It’s also capable of loading the bitstream and the FIT image (image.ub) but the code is commented out.

$ ls images/linux/
pmufw.elf  system.dtb  bl31.elf  system.dts  zynqmp_fsbl.elf
BOOT.BIN  image.ub  system.bit  u-boot.elf <...>

$ xsct jtag-boot.tcl [<hw_server_IP>]

Conclusion

This script uses an undocumented feature to bypass sampling the boot mode pins, and instead rely on a software workaround to use an alternate boot mode value.

Credits due to @jhartfiel @thammer @jott from Xilinx forum post Programming QSPI flash failing for insight and inspiration.

The code

# XSCT script
#
# The script expects an argument to the host connected to the target,
# and it will assume "localhost" if no argument is passed.
# i.e.
# /opt/petalinux/tools/xsct/bin/xsct jtag-boot.tcl 192.168.1.18
#
# The script should be run from the Petalinux project directory
# and will expect the binaries to be under ./images/linux
# from its current directory.

# Location of binaries
set images {./images/linux}
set hwdesc {./project-spec/hw-description}

if {[file exist "$images/image.ub"] == 0} {
  puts stderr "ERROR: File image.ub not found at path $images"
  exit 1
}

puts stderr "--------------------------------------------------"
puts stderr " JTAG boot, regardless of boot mode"
puts stderr "--------------------------------------------------"

# Set hardware server or 'localhost' if none given
if {[llength $argv] == 0} {
  set hwserver localhost
  puts stderr "WARNING: No host or IP argument was entered, assuming localhost."
} else {
  set hwserver [lindex $argv 0]
}
set url $hwserver:3121

if { [catch {connect -url $url -symbols}] != 0} {
  puts stderr "ERROR: Could not connect to $url host."
  exit 1
}
puts stderr "INFO: Connection to target established."
puts stderr "--------------------------------------------------"

#puts stderr "INFO: Configuring the FPGA..."
#puts stderr "INFO: Downloading bitstream: $images/system.bit to the target."
#fpga $images/system.bit

# Disable security gates for DAP, PL TAP & PMU
targets -set -nocase -filter {name =~ "*PSU*"}
mask_write 0xFFCA0038 0x1C0 0x1C0

# Download PMU firmware to MicroBlaze PMU
targets -set -nocase -filter {name =~ "*MicroBlaze PMU*"}
catch {stop}; after 1000
puts stderr "INFO: Downloading PMU firmware $images/pmufw.elf"
dow $images/pmufw.elf
after 2000
con

targets -set -nocase -filter {name =~ "*APU*"}
mwr 0xffff0000 0x14000000
mask_write 0xFD1A0104 0x501 0x0

# Read boot mode
set mode [expr [mrd -value 0xFF5E0200] & 0xf]
puts stderr "INFO: Boot mode set to $mode"
puts stderr "INFO: Forcing Alternate boot mode to JTAG"
mwr 0xFF5E0200 0x100

# Reset APU#0 and run psu_init
targets -set -nocase -filter {name =~ "*A53*#0"}
rst -processor
puts stderr "INFO: Run PSU initialization"
source $hwdesc/psu_init.tcl

# Download FSBL
rst -processor
puts stderr "INFO: Downloading FSBL $images/zynqmp_fsbl.elf"
dow $images/zynqmp_fsbl.elf
after 2000
set bp_45_33_fsbl_bp [bpadd -addr &XFsbl_Exit]
con -block -timeout 50
bpremove $bp_45_33_fsbl_bp
after 5000;
catch {stop};
catch {stop};
psu_ps_pl_isolation_removal; psu_ps_pl_reset_config

puts stderr "INFO: Downloading device tree blob: $images/system.dtb at 0x00100000"
dow -data "$images/system.dtb" 0x00100000
after 2000

# Download and run u-boot
puts stderr "INFO: Downloading U-Boot $images/u-boot.elf"
dow $images/u-boot.elf

# Download and run ATF
puts stderr "INFO: Downloading ATF $images/bl31.elf"
dow $images/bl31.elf

#puts stderr "INFO: Downloading Kernel: $images/image.ub at 0x05000000"
#dow -data "$images/image.ub" 0x05000000

con
Linux Userspace Memory & I/O

In this post we’ll be looking at how Linux handles accesses to memory devices and I/O from userspace, and comparing...

Previous
U-Boot 2020.1 changes to distro boot

One of the disrupting changes that Petalinux 2020.1 has brought, includes U-Boot moving to what's called "distro boot". In this...

Next