13.9 About interacting with the target

To perform flash programming, the programming method might need to access the target.

The flash programmer provides access to the DTSL APIs for this and the programming method can then get a connection with the getConnection() function of class FlashMethodv1.
This is called from the setup() function of the programming method. If there is already an open connection, for example, from the DS-5 Debugger, this will be re-used.
def setup(self):
    # connect to core
    self.conn = self.getConnection()

Note

An example, flash_example-FVP-A9x4, is provided with DS-5. This example shows two ways of programming flash devices using DS-5, one using a Keil Flash Method and the other using a Custom Flash Method written in Jython. For convenience, the Cortex-A9x4 FVP model supplied with DS-5 is used as the target device. This example can be used as a template for creating new flash algorithms. The readme.html provided with the example contains basic information on how to use the example.

Accessing the core

When interacting with the target, it might be necessary to open a connection to the core. If the debugger already has an open connection, a new connection might not be always possible. A utility function, ensureDeviceOpen(), is provided that will open the connection only if required. It will return true if the connection is open and so should be closed after programming in the teardown() function.
To access the core's registers and memory, the core has to be stopped. Use the ensureDeviceStopped() function to assist with this.
def setup(self):
    # connect to core & stop
    self.conn = self.getConnection()
    coreName = self.getParameter("coreName")
    self.dev = self.conn.getDeviceInterfaces().get(coreName)
    self.deviceOpened = ensureDeviceOpen(self.dev)
    ensureDeviceStopped(self.dev)
 
def teardown(self):
    if self.deviceOpened:
        # close device connection if opened by this script
        self.dev.closeConn()

Reading/writing memory

The core's memory can be accessed using the memWrite(), memFill(), and memRead() functions of the dev object (IDevice).
from com.arm.rddi import RDDI 
from com.arm.rddi import RDDI_ACC_SIZE 
from jarray import zeros 
 
... 
 
    def program(self): 
        ... 
        self.dev.memFill(0, addr, RDDI_ACC_SIZE.RDDI_ACC_WORD, 
                         RDDI.RDDI_MRUL_NORMAL, False, words, 0) 
        self.dev.memWrite(0, addr, RDDI_ACC_SIZE.RDDI_ACC_WORD, 
                          RDDI.RDDI_MRUL_NORMAL, False, len(buf), buf) 
        ... 
 
    def verify(self): 
        ... 
        readBuf = zeros(len(buf), 'b') 
        self.dev.memRead(0, addr, RDDI_ACC_SIZE.RDDI_ACC_WORD, 
                         RDDI.RDDI_MRUL_NORMAL, len(readBuf), readBuf) 
        ...
Utility routines to make the method code clearer are provided in device_memory:
from flashprogrammer.device_memory import writeToTarget, readFromTarget 
 
... 
 
    def program(self): 
        ... 
        writeToTarget(self.dev, address, buf) 
        ... 
 
    def verify(self): 
        ... 
        readBuf = readFromTarget(self.dev, addr, count)     
        ...

Reading and writing registers

The core's registers can be read using the regReadList() and written using the regWriteList() functions of Idevice.

Note

You must be careful to only pass integer values and not long values.
These registers are accessed by using numeric IDs. These IDs are target specific. For example, R0 is register 1 on a Cortex-A device, but register 0 on a Cortex-M device.
execution.py provides functions that map register names to numbers and allow reading or writing by name.
  • writeRegs(device, regs) writes a number of registers to a device. regs is a list of (name, value) pairs.
    For example:
    writeRegs (self.dev, [ ("R0", 0), ("R1", 1234), ("PC", 0x8000) ]
    will set R0, R1, and PC (R15).
  • readReg(device, reg) reads a named register.
    For example:
    value = readReg ("R0")
    will read R0 and return its value.

Running code on the core

The core can be started and stopped via the go() and stop() functions. Breakpoints can be set with the setSWBreak() or setHWBreak() functions and cleared with the clearSWBreak() or clearHWBreak() functions. As it may take some time to reach the breakpoint, before accessing the target further, the script should wait for the breakpoint to be hit and the core stopped.
execution.py provides utility methods to assist with running code on the target.
  • To request the core to stop and wait for the stop status event to be received, and raise an error if no event is received before timeout elapses.
    stopDevice(device, timeout=1.0):
  • To check the device's status and calls stopDevice() if it is not stopped.
    ensureDeviceStopped(device, timeout=1.0):
  • To start the core and wait for it to stop, forces the core to stop and raise an error if it doesn't stop before timeout elapses. The caller must set the registers appropriately and have set a breakpoint or vector catch to cause the core to stop at the desired address.
    runAndWaitForStop(device, timeout=1.0):
  • To set a software breakpoint at addr, start the core and wait for it to stop by calling runAndWaitForStop(). The caller must set the registers appropriately.
    runToBreakpoint(device, addr, bpFlags = RDDI.RDDI_BRUL_STD, timeout=1.0):
Flash programming algorithms are often implemented as functions that are run on the target itself. These functions may take parameters where the parameters are passed through registers.
funcCall() allows methods to call functions that follow AAPCS (with some restrictions):
  • Up to the first four parameters are passed in registers R0-R3.
  • Any parameters above this are passed via the stack.
  • Only integers up to 32-bit or pointer parameters are supported. Floating point or 64-bit integers are not supported.
  • The result is returned in R0.
We can use the above to simulate flash programming by writing the data to RAM. See example_method_1.py. This:
  • Connects to the target on setup().
  • Fills the destination RAM with 0s to simulate erase.
  • Writes data to a write buffer in working RAM.
  • Runs a routine that copies the data from the write buffer to the destination RAM.
  • Verifies the write by reading from the destination RAM.

Loading programming algorithm images onto the target

Programming algorithms are often compiled into .elf images.
FlashMethodv1.locateFile() locates a file for example, from a parameter, resolving any FDB:// prefix to absolute paths.
symfile.py provides a class, SymbolFileReader, that allows the programming method to load an image file and get the locations of symbols. For example, to get the location of a function:
        # load the algorithm image 
        algorithmFile = self.locateFile(self.getParameter('algorithm')) 
        algoReader = SymbolFileReader(algorithmFile) 
 
        # Find the address of the Program() function 
        funcInfo = algoReader.getFunctionInfo()['Program'] 
        programAddr = funcInfo['address'] 
        if funcInfo['thumb']: 
            # set bit 0 if symbol is thumb 
            programAddr |= 1
image_loader.py provides routines to load the image to the target:
        # load algorithm into working RAM 
        algoAddr = self.ramAddr + 0x1000 # allow space for stack, buffers etc 
        loadAllCodeSegmentsToTarget(self.dev, algoReader, algoAddr)
If the algorithm binary was linked as position independent, the addresses of the symbols are relative to the load address and this offset should be applied when running the code on the target:
        programAddr += algoAddr 
        args = [ writeBuffer, destAddr, pageSize ] 
        funcCall(self.dev, programAddr, args, self.stackTop)

Progress reporting

Flash programming can be a slow process, so it is desirable to have progress reporting features. The method can do this by calling operationStarted(). This returns an object with functions:
  • progress() - update the reported progress.
  • complete() - report the operation as completed, with a success or failure.
Progress reporting can be added to the program() function in the previous example:
    def program(self, regionID, offset, data): 
        # calculate the address to write to 
        region = self.getRegion(regionID) 
        addr = region.getAddress() + offset 
         
        # Report progress, assuming erase takes 20% of the time, program 50% 
        # and verify 30% 
        progress = self.operationStarted( 
            'Programming 0x%x bytes to 0x%08x' % (data.getSize(), addr), 
            100) 
 
        self.doErase(addr, data.getSize()) 
        progress.progress('Erasing completed', 20) 
 
        self.doWrite(addr, data) 
        progress.progress('Writing completed', 20+50) 
 
        self.doVerify(addr, data) 
        progress.progress('Verifying completed', 20+50+30) 
 
        progress.completed(OperationResult.SUCCESS, 'All done') 
 
        # register values have been changed 
        return TargetStatus.STATE_LOST
The above example only has coarse progress reporting, only reporting at the end of each phase. Better resolution can be achieved by allowing each sub-task to have a progress monitor. subOperation() creates a child progress monitor.
Care should be taken to ensure completed() is called on the progress monitor when an error occurs. It is recommended that a try: except: block is placed around the code after a progress monitor is created.
    import java.lang.Exception 
    def program(self, regionID, offset, data): 
        progress = self.operationStarted( 
            'Programming 0x%x bytes to 0x%08x' % (data.getSize(), addr), 
            100) 
        try: 
            # Do programming 
        except (Exception, java.lang.Exception), e:  
            # exceptions may be derived from Java Exception or Python Exception 
            # report failure to progress monitor & rethrow 
            progress.completed(OperationResult.FAILURE, 'Failed') 
            raise

Note

import java.lang.Exception - If you omit import and a Java exception is thrown, you may get a confusing error report from Jython indicating that it cannot find the Java namespace. Further, the python line location indicated as the source of the error will not be accurate.

Cancellation

If you wish to abort a long-running flash operation, programming methods can call isCancelled() to check if the operation is canceled. If this returns true, the method stops programming.

Note

The teardown() functions are still called.

Messages

The programming method can report messages by calling the following:
  • warning() - reports a warning message.
  • info() - reports an informational message.
  • debug() - reports a debug message - not normally displayed.

Locating and resolving files

FlashMethodv1.locateFile() locates a file for example, from a parameter, resolving any FDB:// prefix to absolute paths.
This searches paths of all flash subdirectories of every configuration database configured in DS-5.
For example:
<DS5_INSTALL_DIR>/sw/debugger/configdb/Flash/
c:\MyDB\Flash

Error handling

Exceptions are thrown when errors occur. Errors from the API calls made by the programming method will be com.arm.debug.flashprogrammer.FlashProgrammerException (or derived from this). Methods may also report errors using Python's raise keyword. For example, if verification fails:
# compare contents
res = compareBuffers(buf, readBuf)
if res != len(buf):
    raise FlashProgrammerRuntimeException, "Verify failed at address: %08x" %
(addr + res)
If a programming method needs to ensure that a cleanup occurs when an exception is thrown, the following code forms a template:
    import java.lang.Exception 
         ... 
        try: 
            # Do programming 
        except (Exception, java.lang.Exception), e:  
            # exceptions may be derived from Java Exception or Python Exception 
            # report failure to progress monitor & rethrow 
             
            # Handle errors here 
             
            # Rethrow original exception 
            raise 
        finally: 
            # This is always executed on success or failure 
            # Close resources here
See the Progress handler section for example usage.

Note

import java.lang.Exception - If you omit import and a Java exception is thrown, you may get a confusing error report from Jython indicating that it cannot find the Java namespace. Further, the python line location indicated as the source of the error will not be accurate.

Running an external tool

Some targets may already have a standalone flash programming tool. It is possible to create a DS-5 Debugger programming method to call this tool, passing it to the path of the image to load. The following example shows how to do this, using the fromelf tool in place of a real flash programming tool.
from flashprogrammer.flash_method_v1 import FlashMethodv1 
from com.arm.debug.flashprogrammer.IProgress import OperationResult 
from com.arm.debug.flashprogrammer import TargetStatus 
import java.lang.Exception 
import subprocess 
 
class RunProgrammer(FlashMethodv1): 
    def __init__(self, methodServices): 
        FlashMethodv1.__init__(self, methodServices) 
 
 
    def program(self, regionID, offset, data): 
 
        progress = self.operationStarted( 
            'Programming 0x%x bytes with command %s' % (data.getSize(), ' '.join(cmd)), 
            100) 
        try: 
            # Get the path of the image file 
            file = data.getUnderlyingFile().getCanonicalPath() 
         
            cmd = [ 'fromelf', file ]  
            self.info("Running %s" % ' '.join(cmd)) 
         
     # run command 
     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 
     out, err = proc.communicate() 
      
     # pass command output to user as info message 
     self.info(out) 
         
            progress.progress('Completed', 100) 
            progress.completed(OperationResult.SUCCESS, 'All done') 
 
        except (Exception, java.lang.Exception), e:  
            # exceptions may be derived from Java Exception or Python Exception 
            # report failure to progress monitor & rethrow 
            progress.completed(OperationResult.FAILURE, 'Failed') 
            raise 
             
        return TargetStatus.STATE_RETAINED
os.environ can be used to lookup environment variables, for example, the location of a target's toolchain:
programmerTool = os.path.join(os.environ['TOOLCHAIN_INSTALL'], 'flashprogrammer')

Setup and teardown

The flash configuration file can specify scripts to be run before and after flash programming. These are termed setup and teardown scripts and are defined using setup and teardown tags. The setup script should put the target into a state ready for flash programming.
This might involve one or more of:
  • Reset the target.
  • Disable interrupts.
  • Disable peripherals that might interfere with flash programming.
  • Setup DRAM.
  • Enable flash control.
  • Setup clocks appropriately.
The teardown script should return the target to a usable state following flash programming.
In both cases, it may be necessary to reset the target. The following code can be used to stop the core on the reset vector.

Note

This example code assumes that the core supports the RSET vector catch feature.
def setup(client, services): 
    # get a connection to the core 
    conn = services.getConnection() 
    dev = conn.getDeviceInterfaces().get("Cortex-M3") 
    ensureDeviceOpen(dev) 
    ensureDeviceStopped(dev) 
     
    dev.setProcBreak("RSET") 
    dev.systemReset(0) 
    # TODO: wait for stop! 
    dev.clearProcBreak("RSET")

Other ways of providing flash method parameters

The flash configuration file can provide flash region information and flash parameter information encoded into the XML. However, for some methods, this information may need to be extracted from the flash algorithm itself.
Programming methods can extend any information in the flash configuration file (if any) with address regions and parameters for the method by overriding a pair of class methods - getDefaultRegions() and getDefaultParameters().
getDefaultParameters(). 
from com.arm.debug.flashprogrammer import FlashRegion 
 
... 
class ProgrammingMethod(FlashMethodv1): 
... 
 
    def getDefaultRegions(self): 
        return [ FlashRegion(0x00100000, 0x10000), FlashRegion(0x00200000, 0x10000) ] 
 
    def getDefaultParameters(self): 
        params = {} 
        params['param1'] = "DefaultValue1" 
        params['param2'] = "DefaultValue2" 
        return params
The above code defines two 64kB regions at 0x00100000 and 0x00200000. Regions supplied by this method are only used if no regions are specified for the device in the configuration file. The above code defines 2 extra parameters. These parameters are added to the parameters in the flash configuration. If a parameter is defined in both, the default value in the flash configuration file is used. This region and parameter information can be extracted from the algorithm binary itself (rather than being hard-coded as in the above example). The Keil algorithm images contain a data structure defining regions covered by the device and the programming parameters for the device. The Keil programming method loads the algorithm binary (specified by a parameter in the configuration file) and extracts this information to return in these calls.
Non-ConfidentialPDF file icon PDF versionARM DUI0446W
Copyright © 2010-2015 ARM. All rights reserved.