Skip to content

New example for the use of the INTForwarder plugin #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions INT-Rehosting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Rehosting the Raspberry Pi Pico blink example with timer interrupts active
This example is an addition to the original Raspberry Pi Pico example with added interrupt forwarding capabilities.

### Purpose

This example shows the following additional concepts of the avatar² framework:
* Orchestrating interrupts during rehosting
* Interrupt state transfers
* Interrupt forwarding
* Interrupt state and event introspection

### Requirements
- Two [Raspberry Pi Picos](https://www.raspberrypi.com/products/raspberry-pi-pico/)
, one flashed with the provided blink.elf, the other one setup as a picoprobe debugger for the first one
- [Avatar²-core](https://github.com/avatartwo/avatar2) (at least December 2023)
- [OpenOCD](http://openocd.org/) (Requires at least OpenOCD version 12.0)
- [Avatar²-qemu](https://github.com/avatartwo/avatar-qemu) (at least December 2023)

### Contents
- `blink_INTForwarder.py` - the avatar²-script for rehosting using the INTForwarder plugin and QEmu
- `blink-int.bin` - Dump of the flash of the pico for quicker state transfer
- `blink-int.elf` - Compiled firmware (with debug symbols) for flashing and debugging
- `pico_bootrom.bin` - Dumped image of the Picos bootrom (v2.0)
- `pico-picoprobe.cfg` - the OpenOCD configuration file for the pico-picoprobe combination
- `util.py` - Utility functions for logging
Binary file added INT-Rehosting/blink-int.bin
Binary file not shown.
Binary file added INT-Rehosting/blink-int.elf
Binary file not shown.
156 changes: 156 additions & 0 deletions INT-Rehosting/blink_INTForwarder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from datetime import datetime

import IPython

from avatar2 import *

import util

VECTOR_TABLE_OFFSET_REG = 0xe000ed08

HARD_FAULT_ISR = 0x100001c4

IRQ_MAP = {
0x100001f6: "<_reset_handler>",
0x100001c0: "<isr_invalid>",
0x100001c2: "<isr_nmi>",
0x100001c4: "<isr_hardfault>",
0x100001c6: "<isr_svcall>",
0x100001c8: "<isr_pendsv>",
0x100001ca: "<isr_systick>",
0x100001cc: "<__unhandled_user_irq>",
0x100012f0: "<hardware_alarm_irq_handler>",
0x20041fff: "__END_OF_RAM__"
}

FIRMWARE = "./blink-uart.bin"
OPENOCD_CONFIG = "./pico-picoprobe.cfg"
AFTER_INIT_LOC = 0x1000037e # end of loop


def main():
# Configure the location of config and binary files
firmware = abspath(FIRMWARE)
bootrom = abspath('./pico_bootrom.bin')
openocd_config = abspath(OPENOCD_CONFIG)

# Initiate the avatar-object
avatar = Avatar(arch=ARM_CORTEX_M3,
output_directory='/tmp/avatar', log_to_stdout=False)
loggerAvatar, logger, _ = util.setup_loggers(emulated_logger=True)

logger.info("Setting up targets")
# Create the target-objects
hardware = avatar.add_target(
OpenOCDTarget, openocd_script=openocd_config, name='hardware')

# Create controlled qemu instance
qemu = avatar.add_target(QemuTarget, gdb_port=1236,
entry_address=0x1000000, name='qemu')

logger.info("Loading interrupt plugins")
avatar.load_plugin('arm.INTForwarder')
avatar.load_plugin('assembler')
avatar.load_plugin('disassembler')

# Memory mapping from https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#_address_map
rom = avatar.add_memory_range(
0x00000000, 0x00004000, file=bootrom, name='ROM') # 16kB ROM with bootloader
# 2MB Flash, Accessing using XIP with cache (most common)
xip_cached = avatar.add_memory_range(
0x10000000, 0x00200000, file=firmware, name='Flash (XIP) - cache')

ram = avatar.add_memory_range(
0x20000000, 0x00042000, name='RAM') # 264kB of RAM
# We will need the apb system registers, esp the timer
apb_peripherals = avatar.add_memory_range(
0x40000000, 0x00070000, name='APB', forwarded=True, forwarded_to=hardware)
# To the USB registers are mapped in the 0x50 range
usb_peripherals = avatar.add_memory_range(
0x50100000, 0x00120000, name='USB', forwarded=True, forwarded_to=hardware)
# To blink the LED we will need the SIO registers (contains GPIO registers)
sio = avatar.add_memory_range(
0xd0000000, 0x0000017c, name='SIO', forwarded=True, forwarded_to=hardware) # SIO registers
# Internal peripherals of the Cortex M0+, esp VTOR
arm_peripherals = avatar.add_memory_range(0xe0000000, 0x00010000, name='Cortex Peripherals-1')

# Initialize the targets
logger.debug("Initializing targets")
avatar.init_targets()

# Set breakpoint right at the start of the loop
hardware.set_breakpoint(AFTER_INIT_LOC, hardware=True)
hardware.cont()
hardware.wait()
print("breakpoint hit")
util.printVT(hardware, IRQ_MAP)
vt_offset = util.getVTOR(hardware)

logger.debug("Syncing targets")
avatar.transfer_state(hardware, qemu, sync_regs=True, synced_ranges=[ram])

###############################################################################################
# print("\n=================== Dropping in interactive session =========================\n")
# IPython.embed()
###############################################################################################
irq_trace = []

def record_interrupt_enter(avatar, message, **kwargs):
isr = message.interrupt_num
isr_addr = message.isr_addr - 1
irq = IRQ_MAP[isr_addr] if isr_addr in IRQ_MAP else 'unknown'
logger.warning(f">>>>>>>>>>> ENTER IRQ-num={isr} irq={irq}")
irq_trace.append(
{'id': message.id, 'event': 'enter', 'isr': isr, 'irq': irq, 'timestamp': datetime.now().isoformat()})

def record_interrupt_exit(avatar, message, **kwargs):
isr = message.interrupt_num
logger.warning(f">>>>>>>>>>> EXIT IRQ-num={isr}")
irq_trace.append(
{'id': message.id, 'event': 'exit', 'isr': isr, 'irq': '__LAST_ENTER__',
'timestamp': datetime.now().isoformat()})

logger.debug("Registering interrupt handlers")
avatar.watchmen.add_watchman('TargetInterruptEnter', 'after', record_interrupt_enter)
avatar.watchmen.add_watchman('RemoteInterruptExit', 'after', record_interrupt_exit)

logger.debug("Enabling interrupts")
# NOTE: Here we can edit breakpoints in the hardware target without running into bug hell
hardware.remove_breakpoint(0) # Remove our initial breakpoint
hardware.remove_breakpoint(1) # Remove our initial breakpoint
qemu.set_breakpoint(HARD_FAULT_ISR) # Break on hardfault

avatar.transfer_interrupt_state(hardware, qemu)
avatar.enable_interrupt_forwarding(hardware, qemu)

print("\n=================== Finished setting up interrupt rehosting =========================\n")
STUB_LOC = hardware.protocols.interrupts._monitor_stub_isr
# hardware.set_breakpoint(STUB_LOC) # Break on stub
IRQ_MAP[STUB_LOC] = "STUB"
util.printVT(hardware, IRQ_MAP)

print("\n=================== Continuing... =========================\n")
print(f"HARDWARE: 0x{hardware.read_memory(VECTOR_TABLE_OFFSET_REG, 4):08x}")
print(f"QEmu : 0x{qemu.read_memory(VECTOR_TABLE_OFFSET_REG, 4):08x}")
print()

qemu.cont()
sleep(2)
hardware.cont()

print("\n=================== Dropping in interactive session =========================\n")
IPython.embed()
# Executing `qemu.cont(); qemu.wait()` in the ipython shell, will cause the LED
# to either turn on or off because we step from sleep() to sleep()

print("=================== Done =========================")
if hardware.state == TargetStates.RUNNING:
hardware.stop()
if qemu.state == TargetStates.RUNNING:
qemu.stop()
avatar.shutdown()
return


if __name__ == '__main__':
main()
126 changes: 126 additions & 0 deletions INT-Rehosting/pico-picoprobe.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Generated from
# $ cat /usr/local/share/openocd/scripts/interface/cmsis-dap.cfg /usr/local/share/openocd/scripts/target/rp2040.cfg > pico-picoprobe.cfg
# SPDX-License-Identifier: GPL-2.0-or-later

#
# ARM CMSIS-DAP compliant adapter
#
# http://www.keil.com/support/man/docs/dapdebug/
#
source [find mem_helper.tcl]

adapter driver cmsis-dap
adapter speed 6000

# Optionally specify the serial number of CMSIS-DAP usb device.
# adapter serial 02200201E6661E601B98E3B9
# SPDX-License-Identifier: GPL-2.0-or-later

# RP2040 is a microcontroller with dual Cortex-M0+ core.
# https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html

# The device requires multidrop SWD for debug.
transport select swd

source [find target/swj-dp.tcl]

if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME rp2040
}

if { [info exists WORKAREASIZE] } {
set _WORKAREASIZE $WORKAREASIZE
} else {
set _WORKAREASIZE 0x10000
}

if { [info exists CPUTAPID] } {
set _CPUTAPID $CPUTAPID
} else {
set _CPUTAPID 0x01002927
}

# Set to '1' to start rescue mode
if { [info exists RESCUE] } {
set _RESCUE $RESCUE
} else {
set _RESCUE 0
}

# Set to '0' or '1' for single core configuration, 'SMP' for -rtos hwthread
# handling of both cores, anything else for isolated debugging of both cores
if { [info exists USE_CORE] } {
set _USE_CORE $USE_CORE
} else {
set _USE_CORE SMP
}
set _BOTH_CORES [expr { $_USE_CORE != 0 && $_USE_CORE != 1 }]

swj_newdap $_CHIPNAME cpu -expected-id $_CPUTAPID

# The rescue debug port uses the DP CTRL/STAT bit DBGPWRUPREQ to reset the
# PSM (power on state machine) of the RP2040 with a flag set in the
# VREG_AND_POR_CHIP_RESET register. Once the reset is released
# (by clearing the DBGPWRUPREQ flag), the bootrom will run, see this flag,
# and halt. Allowing the user to load some fresh code, rather than loading
# the potentially broken code stored in flash
if { $_RESCUE } {
dap create $_CHIPNAME.rescue_dap -chain-position $_CHIPNAME.cpu -dp-id $_CPUTAPID -instance-id 0xf -ignore-syspwrupack
init

# Clear DBGPWRUPREQ
$_CHIPNAME.rescue_dap dpreg 0x4 0x00000000

# Verifying CTRL/STAT is 0
set _CTRLSTAT [$_CHIPNAME.rescue_dap dpreg 0x4]
if {[expr {$_CTRLSTAT & 0xf0000000}]} {
echo "Rescue failed, DP CTRL/STAT readback $_CTRLSTAT"
} else {
echo "Now restart OpenOCD without RESCUE flag and load code to RP2040"
}
shutdown
}

# core 0
if { $_USE_CORE != 1 } {
dap create $_CHIPNAME.dap0 -chain-position $_CHIPNAME.cpu -dp-id $_CPUTAPID -instance-id 0
set _TARGETNAME_0 $_CHIPNAME.core0
target create $_TARGETNAME_0 cortex_m -dap $_CHIPNAME.dap0 -coreid 0
# srst does not exist; use SYSRESETREQ to perform a soft reset
$_TARGETNAME_0 cortex_m reset_config sysresetreq
}

# core 1
if { $_USE_CORE != 0 } {
dap create $_CHIPNAME.dap1 -chain-position $_CHIPNAME.cpu -dp-id $_CPUTAPID -instance-id 1
set _TARGETNAME_1 $_CHIPNAME.core1
target create $_TARGETNAME_1 cortex_m -dap $_CHIPNAME.dap1 -coreid 1
$_TARGETNAME_1 cortex_m reset_config sysresetreq
}

if {[string compare $_USE_CORE SMP] == 0} {
$_TARGETNAME_0 configure -rtos hwthread
$_TARGETNAME_1 configure -rtos hwthread
target smp $_TARGETNAME_0 $_TARGETNAME_1
}

if { $_USE_CORE == 1 } {
set _FLASH_TARGET $_TARGETNAME_1
} else {
set _FLASH_TARGET $_TARGETNAME_0
}
# Backup the work area. The flash probe runs an algorithm on the target CPU.
# The flash is probed during gdb connect if gdb_memory_map is enabled (by default).
$_FLASH_TARGET configure -work-area-phys 0x20010000 -work-area-size $_WORKAREASIZE -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME rp2040_flash 0x10000000 0 0 0 $_FLASH_TARGET

if { $_BOTH_CORES } {
# Alias to ensure gdb connecting to core 1 gets the correct memory map
flash bank $_CHIPNAME.alias virtual 0x10000000 0 0 0 $_TARGETNAME_1 $_FLASHNAME

# Select core 0
targets $_TARGETNAME_0
}
Binary file added INT-Rehosting/pico_bootrom.bin
Binary file not shown.
Loading