Executing code from RAM in IAR products with XLINK
Technical Note 80460
Architectures:
All
Component:
linker
Updated:
5/31/2018 9:14 AM
Introduction
This text will describe how ROM-content is copied from ROM to RAM where it is executed. Some IAR compilers have support for this through keywords. This text will not assume that such support is available and use a more generally applicable approach. The most common case is copying content from ROM to RAM (where it is used) but the mechanism is more general that that, it supports linking content in one place and then placing the bytes in another.
Basics
The following are the steps to copy code to RAM:
- Place the code in its own segment.
- Use the -Q linker option to create an initializer segment
- Place the original segment in RAM.
- Place the initializer segment in ROM.
- Copy the bytes from the initializer segment to the segment.
- Debug the application
1. Placing content in segments
Place all content that you wish to copy from ROM to RAM in its own segment. This is achieved in different ways in the assembler, the compiler and for image input.
- Using the assembler, example:
In an assembler file you have explicit control over what segment that is used for any construction through the RSEG directive.
RSEG RAMCODE:CODE:NOROOT(2)
This creates a segment part of segment type CODE in the segment named RAMCODE that defines the entry do_stuff. It is not ROOT. It is 4-byte aligned.
do_stuff:
.
.
.
Note: Segment type CODE is not the same as segment name CODE, although a segment named CODE typically also would have type CODE.
- Using the compiler:
Some IAR compilers offers command line options that supply similar functionality for an entire module (consult your manual for the details of any specific compiler). Most recent IAR compilers offer a pragma (#pragma location) that controls the segment for the next statement only.
#pragma location="SPECIAL_CODE"
int do_strange_things(int a, int b)
{
.
.
.
}This places the function do_strange_things in the segment SPECIAL_CODE. It has no effect on declaration above or below it in the file.
Using a lot of #pragma location is not really something that you want to do. Having hundreds (or thousands) of those strings clutters up the code and you might be in trouble if you ever need to change the location. #pragma and macros typically does not mix well but there is an IAR extension keyword that is quite useful for these purposes, _Pragma.
-
#define RAMCODE(x) _Pragma("location=\"RAMCODE\"") x
This macro will result in the argument declaration being placed in the segment RAMCODE. Example:
-
RAMCODE(int do_strange_things(int a, int b))
{
.
.
.
}This is of course not limited to code.
-
#define CONST(x) _Pragma("location=\"CONST_SEG\"") x
This macro will result in the argument declaration being placed in the segment CONST_SEG. Example:
-
CONST(const int a = 4711;)
CONST(const int arr[3] = { 45, 34, -2};) - Using --image_input:
The linker option --image_input allows you to import any kind of file, byte by byte, and place it in a segment.
-
--image_input=image1.raw,image1_start,IMAGE_SEG,0
This imports the content of the file image1.raw into the segment IMAGE_SEG. The segment defines the symbol image1_start and is not aligned. Note that code imported through the use of --image_input should be placed on the same address that it was placed in the link job that produced the image. There are some exceptions to this, like position independent code, but unless care is taken to ensure otherwise, imported code should only be executed in the place it was originally linked.
2. Creating the initializer segment
Use the -Q linker option to create the initializer segment. -Q enables the linking of content in one place (RAM in this case) and then placing the bytes someplace else (ROM in this case).
-QRAMCODE=RAMCODE_ID
This creates an initializer segment, RAMCODE_ID, for the segment RAMCODE.
RAMCODE contains the labels and the debug information. This is where the code will reside when actually running. This is to where all references to RAMCODE are made and this is the place that makes all the references that RAMCODE makes.
RAMCODE_ID (which has the exact same size as RAMCODE) contains only the actual bytes of RAMCODE, no labels or debug information. The bytes here are only intended to be used for copying to RAMCODE. Using the bytes for any other purpose, like executing the code where it currently resides (before the copying), or copying them to another address results in undefined behavior.
3. Place the original segment in RAM.
RAMCODE is a copy initialized segment so it should be placed with the -Z segment placement command to guarantee that the exact order of the segment parts is maintained.
-Z(DATA)RAMCODE=RAM_START-RAM_END
Note that the segment placement commands are processed in the same order they are present in the .xcl-file and that every placement takes earlier placements into account. You might have to move the placements around to make sure that a certain segment is placed before (or after) some specific segment.
4. Place the initializer segment in ROM.
RAMCODE_ID is a copy initializer segment so it should be placed the Z segment placement command to guarantee that the exact order of the segment parts is maintained.
-Z(CONST)RAMCODE_ID=ROM_START-ROM_END
5. Copy the bytes from the initializer segment to the segment.
The tools make no attempt to actually copy the bytes from RAMCODE_ID to RAMCODE, the programmer has to do that at runtime. It is possible to modify the startup code to do this automatically before execution reaches main, but as long as the bytes are copied before the content of RAMCODE is used it should not matter when it is copied.
The actual copying is rather straightforward. A call to memcpy is enough, the slightly tricky part is obtaining the addresses of the segments, using IAR extensions (or possibly call to functions written in assembler). This code has been tested on a processor with a single address space. If the processor has special requirements for copying bytes from ROM to RAM (different address spaces, special instructions, special address requirements, etc) all such requirements must be fulfilled by the copy mechanism.
/* copy all bytes between s (inclusive) and e (exclusive) to d */
void activate(void * s, void * e, void * d)
{
size_t size = (size_t)e - (size_t)s;
memcpy(d,s,size);
}
/* copy the bytes from RAMCODE_ID to RAMCODE */
void activate_RAMCODE(void)
{
#pragma segment="RAMCODE"
#pragma segment="RAMCODE_ID"
activate(__sfb("RAMCODE_ID"), __sfe("RAMCODE_ID"), __sfb("RAMCODE"));
}
#pragma segment="" tells the compiler that there is a segment with that name, it has no effect other than enabling the use of __sfb and __sfe on that segment.
__sfb, segment frame begin, an IAR extension that returns the start address of the segment. The segment must have been mentioned in a previous #pragma. If the segment occupies 0x500-0x52F __sfb will return 0x500.
__sfe, segment frame end, the complement to __sfb, the address of the first free byte after the segment. If the segment occupies 0x500-0x52F __sfe will return 0x530.
After the call to activate_RAMCODE everything is ready for use.
6. Debugging
In this example, the copy-initialized code should have full debuggability after the bytes have been copied.
All product names are trademarks or registered trademarks of their respective owners.