tcl/target: Add helpers for booting Xilinx ZynqMP from JTAG

Add some helpers for booting ZynqMPs over JTAG. Normally, the CSU ROM
will load boot.bin from the boot medium. However, when booting from JTAG
we have to do this ourselves. There are generally two parts to this.
First, we need to load the PMU firmware. Xilinx's tools do this by
attaching to the PMU (a Microblaze CPU) over JTAG. However, the TAP is
undocumented and we don't have any microblaze support in-tree. So
instead we do it the same way FSBL does it:

- We ask the PMU to halt
- We load the firmware into the PMU RAM
- We ask the PMU to resume

The second thing we need to do is start one of the APU cores. When an
APU is released from reset, it starts executing at the value of its
RVBARADDR. While we could load the APU firmware over the AXI target,
it is faster to load it over the APU target. To do this, we put the APU
into an infinite loop before halting it. As an aside, I chose to use the
"APU" terminology as opposed to "core" to make it clear that these
commands operate on the A53 cores and not the R5F cores.

Typical usage of these commands could look something like

	targets uscale.axi
	boot_pmu /path/to/pmu-firmware.bin
	boot_apu /path/to/u-boot-spl.bin

But of course there is always the option to call lower-level commands
individually if your boot process is more unusual.

Signed-off-by: Sean Anderson <sean.anderson@seco.com>
Change-Id: I816940c2022ccca0fabb489aa75d682edd0f6138
Reviewed-on: https://review.openocd.org/c/openocd/+/8133
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
Tested-by: jenkins
This commit is contained in:
Sean Anderson 2024-02-22 12:18:36 -05:00 committed by Antonio Borneo
parent 3eba7b53bf
commit a84d1b5f5e
1 changed files with 134 additions and 0 deletions

View File

@ -104,3 +104,137 @@ proc core_up { args } {
$_TARGETNAME.$core arp_examine
}
}
proc BIT {n} {
return [expr {1 << $n}]
}
set IPI_BASE 0xff300000
set IPI_PMU_0_TRIG [expr {$IPI_BASE + 0x30000}]
set IPI_PMU_0_IER [expr {$IPI_BASE + 0x30018}]
set IPI_PMU_0 [BIT 16]
set CRF_APB_BASE 0xfd1a0000
set CRF_APB_RST_FPD_APU [expr {$CRF_APB_BASE + 0x104}]
set CRF_APB_RST_FPD_APU_ACPU0_PWRON_RESET [BIT 10]
set CRF_APB_RST_FPD_APU_L2_RESET [BIT 8]
set CRF_APB_RST_FPD_APU_ACPU0_RESET [BIT 0]
set APU_BASE 0xfd5c0000
set APU_RVBARADDR_BASE [expr {$APU_BASE + 0x40}]
set PMU_BASE 0xffd80000
set PMU_GLOBAL $PMU_BASE
set PMU_GLOBAL_MB_SLEEP [BIT 16]
set PMU_GLOBAL_FW_IS_PRESENT [BIT 4]
set PMU_GLOBAL_DONT_SLEEP [BIT 0]
set PMU_RAM_BASE 0xffdc0000
set OCM_RAM_BASE 0xfffc0000
rename BIT {}
add_help_text halt_pmu "Halt the PMU in preparation for loading new firmware.\
This should be matched with a call to resume_pmu."
proc halt_pmu {} {
set axi $::_CHIPNAME.axi
set val [$axi read_memory $::IPI_PMU_0_IER 32 1]
$axi write_memory $::IPI_PMU_0_IER 32 [expr {$val | $::IPI_PMU_0}]
set val [$axi read_memory $::IPI_PMU_0_TRIG 32 1]
$axi write_memory $::IPI_PMU_0_TRIG 32 [expr {$val | $::IPI_PMU_0}]
set start [ms]
while {!([$axi read_memory $::PMU_GLOBAL 32 1] & $::PMU_GLOBAL_MB_SLEEP)} {
if {[ms] - $start > 1000} {
error "Timed out waiting for PMU to halt"
}
}
}
add_help_text resume_pmu "Resume the PMU after loading new firmware. This\
should be matched with a call to halt_pmu."
proc resume_pmu {} {
set axi $::_CHIPNAME.axi
set val [$axi read_memory $::PMU_GLOBAL 32 1]
$axi write_memory $::PMU_GLOBAL 32 [expr {$val | $::PMU_GLOBAL_DONT_SLEEP}]
set start [ms]
while {!([$axi read_memory $::PMU_GLOBAL 32 1] & $::PMU_GLOBAL_FW_IS_PRESENT)} {
if {[ms] - $start > 5000} {
error "Timed out waiting for PMU firmware"
}
}
}
add_usage_text release_apu {apu}
add_help_text release_apu "Release an APU from reset. It will start executing\
at RVBARADDR. You probably want resume_apu or start_apu instead."
proc release_apu {apu} {
set axi $::_CHIPNAME.axi
set val [$axi read_memory $::CRF_APB_RST_FPD_APU 32 1]
set mask [expr {
(($::CRF_APB_RST_FPD_APU_ACPU0_PWRON_RESET | \
$::CRF_APB_RST_FPD_APU_ACPU0_RESET) << $apu) | \
$::CRF_APB_RST_FPD_APU_L2_RESET
}]
$axi write_memory $::CRF_APB_RST_FPD_APU 32 [expr {$val & ~$mask}]
core_up $apu
$::_TARGETNAME.$apu aarch64 dbginit
}
proc _rvbaraddr {apu} {
return [expr {$::APU_RVBARADDR_BASE + 8 * $apu}]
}
add_usage_text resume_apu {apu addr}
add_help_text resume_apu "Resume an APU at a given address."
proc resume_apu {apu addr} {
set addrl [expr {$addr & 0xffffffff}]
set addrh [expr {$addr >> 32}]
$::_CHIPNAME.axi write_memory [_rvbaraddr $apu] 32 [list $addrl $addrh]
release_apu $apu
}
add_usage_text start_apu {apu}
add_help_text start_apu "Start an APU and put it into an infinite loop at\
RVBARADDR. This can be convenient if you just want to halt the APU\
(since it won't execute anything unusual)."
proc start_apu {apu} {
set axi $::_CHIPNAME.axi
foreach {addrl addrh} [$axi read_memory [_rvbaraddr $apu] 32 2] {
set addr [expr {($addrh << 32) | $addrl}]
}
# write the infinite loop instruction
$axi write_memory $addr 32 0x14000000
release_apu $apu
}
add_usage_text boot_pmu {image}
add_help_text boot_pmu "Boot the PMU with a given firmware image, loading it\
to the beginning of PMU RAM. The PMU ROM will jump to this location\
after we resume it."
proc boot_pmu {image} {
halt_pmu
echo "Info : Loading PMU firmware $image to $::PMU_RAM_BASE"
load_image $image $::PMU_RAM_BASE
resume_pmu
}
add_usage_text boot_apu "image \[apu=0 \[addr=$OCM_RAM_BASE\]\]"
add_help_text boot_apu "Boot an APU with a given firmware image. The default\
address is the beginning of OCM RAM. Upon success, the default target\
will be changed to the (running) apu."
proc boot_apu [list image {apu 0} [list addr $OCM_RAM_BASE]] {
start_apu $apu
targets $::_TARGETNAME.$apu
halt
echo "Info : Loading APU$apu firmware $image to $addr"
load_image $image $addr
resume $addr
}