avr-gcc: Locate .rodata in Flash for AVR Devices like AVR64 and AVR128 (original) (raw)
avr-gcc: Locate .rodata in Flash for AVR Devices like AVR64 and AVR128
**Abstract:**Modern AVR microcontrollers from families AVR64 and AVR128, like AVR128DA32, map a 32 KiB portion of their flash memory into the RAM address space. This makes it possible to locate read-only data in flash memory, and not in RAM like it is currently the case with GNU Tools for AVR.
Locating .rodata
in flash can be done without touching or extending the tools, and by means of the following steps:
- Specify the start address of the .rodata segment.
- Tell the AVR to use that segment for .rodata.
- Locate .rodata in the specified segment.
- Adjust the build process.
- Check that it all works as expected.
Resources
Part 3 requires a custom linker description file like
- avrxmega2-rodata.ld for AVR64* devices from avrxmega2
- avrxmega4-rodata.ld for AVR128* devices from avrxmega4
A C file that accomplishes parts 1 and 2: Specify the start address for .rodata
and initialize the AVR to use it
- rodata-test.c Compile and link with, e.g.
> avr-gcc rodata-test.c -o rodata-test.elf -Os -mmcu=avr128da32 -T avrxmega4-rodata.ld
FAQ
- What about AVR16 and AV32 and similar devices?
- How to fix errors from the linker like: .../avr/bin/ld: address 0xa19880 of elf section `.rodata' is not within region `rodata'`?
- Why is .rodata located in RAM to begin with?
- Why is all this not part of the GNU Toolchain?
- How to integrate this into the GNU Tools for AVR?
1. Specifying the Flash Segment for .rodata
In order to specify the start address of the 32 KiB rodata segment, we define a symbol __RODATA_FLASH_START__
. Symbols are entities used and defined by the linker. The value of the symbol must be a multiple of 32 KiB. An easy way is to define it on the command line by linking with:
> avr-gcc ... -Wl,--defsym,__RODATA_FLASH_START__=96K
We can also define it in a C/C++ module by means of the AVR specificaddress
attribute. Notice that address
does not work as expected with -fdata-sections
and -fno-common
(which is the default since v10) due to PR112952.
#include <avr/io.h> // FLASHEND
// Define symbol SYM to value VAL.
#define DEFINE_SYMBOL(SYM, VAL)
attribute((address((VAL))) char SYM
// The byte address of the beginning of the rodata segment. // We use the last 32 KiB segment at the end of flash memory. DEFINE_SYMBOL (RODATA_FLASH_START, 1ul + FLASHEND - 32 * 1024ul);
It can also be defined in an assembly module:
#include <avr/io.h> // FLASHEND
.global RODATA_FLASH_START RODATA_FLASH_START = FLASHEND+1 - 32*1024
Use extension .sx
or .S
, or assemble with, say avr-gcc -x assembler-with-cpp file.asm ...
2. Telling the AVR to use the specified .rodata
Segment
Which 32 KiB flash segment is visble in the RAM address space can be set in bitfield FLMAP
in special function register NVMCTRL_CTRLB
. The function below will be implicitly executed by the startup code, and it must not be call by hand:
attribute((naked, section(".init3")))
attribute((used, unused, no_instrument_function))
attribute((error("Don't call this function by hand!")))
static void init_FLMAP (void)
{
#if (defined NVMCTRL_CTRLB
&& defined NVMCTRL_FLMAP_0_bp
&& defined NVMCTRL_FLMAP_1_bp)
uint8_t tmp, ctrlb;
__asm volatile ("lds %[ctrlb], %[ctrlb_addr]" "\n\t"
// Bits 4 and 5 of __rodata_flash_start_lsr11 hold FLMAP.
#if NVMCTRL_FLMAP_0_bp == 4 && NVMCTRL_FLMAP_1_bp == 5
"cbr %[ctrlb], 0x30" "\n\t"
"ori %[ctrlb], __rodata_flash_start_lsr11" "\n\t"
#else
"ldi %0, __rodata_flash_start_lsr11" "\n\t"
"bst %0, 4" "\n\t"
"bld %[ctrlb], %[bpos0]" "\n\t"
"bst %0, 5" "\n\t"
"bld %[ctrlb], %[bpos1]" "\n\t"
#endif
"sts %[ctrlb_addr], %[ctrlb]"
: "=d" (tmp), [ctrlb] "=d" (ctrlb)
: [bpos0] "n" (NVMCTRL_FLMAP_0_bp),
[bpos1] "n" (NVMCTRL_FLMAP_1_bp),
[ctrlb_addr] "n" (& NVMCTRL_CTRLB));
#endif
}
As the function is not called explicitly
- it is marked as
used
so that the compiler won't throw it away, and - it is marked as
unused
so that the compiler won't diagnose that the function is never called and seems to be unused.
Symbol __rodata_flash_start_lsr11
is defined in the linker description file as explained in the next section. It's defined to __RODATA_FLASH_START__ >> 11
.
3. Locating .rodata
in the specified Segment
This is the tricky part. It requires a custom linker description file. For brevity, we just explain which changes are needed. To that end, start by copying from install avr/lib/ldscripts/
. The default linker description file for AVR64* is avrxmega2.x
, and the one for AVR128* is avrxmega4.x
.
Then provide the adjusted file to the linker by means of, say
> avr-gcc ... -T avrxmega4-rodata.ld
The following adjustments have to be made:
- Introduce a new MEMORY region
rodata
. - Remove the old placement of
.rodata
input sections indata
. - Locate .rodata at the specified flash address.
The result will look something like avrxmega2-rodata.ldor avrxmega4-rodata.ld.
3.1 Introducing a new MEMORY Region for .rodata
Right at the top of the linker description, add a new MEMORY region rodata
and some symbols to avoid magic numbers.
RODATA_VMA = 0xa00000; RODATA_LDS_OFFSET = 0x8000; RODATA_ORIGIN = RODATA_VMA +RODATA_LDS_OFFSET; RODATA_LENGTH = 32K; MEMORY { ... rodata (r!x) : ORIGIN = RODATA_ORIGIN, LENGTH = RODATA_LENGTH }
While introducing a new memory region is not strictly required, it yields better diagnostics in the case when something goes wrong, for example when there is so much program code that .rodata
does not fit into (the remaining part of) the chosen flash segment.
We use a Virtual Memory Address (VMA, the address the code will be using) of 0xa00000 which is required in order to linearize memory. This works similar to other regions like data
with a VMA of somewhere above 0x800000.
3.2 Removing the old Placement of .rodata
The rule for output section .data
reads something like:
.data : { ... (.rodata) / We need to include .rodata here if gcc is used / (.rodata) / with -fdata-sections. */ (.gnu.linkonce.r) ... } > data AT> text
Remove the three lines for the three rodata input sections.rodata
, .rodata*
and *.gnu.linkonce.r*
.
3.3 Locating .rodata
at the specified Flash Address
Somewhere after handling of the RAM sections, for example prior to the.eeprom
output section, insert new symbol definitions and rules for output section .rodata
:
/* User can specify position of .rodata in flash (LMA) by supplying RODATA_FLASH_START. We'd prefer .rodata at the very flashend per default, but theres's no reliable way to get the flash size here, hence default start at 32K. */ RODATA_FLASH_START = DEFINED(RODATA_FLASH_START) ? RODATA_FLASH_START : 32K; ASSERT (RODATA_FLASH_START % 32K == 0, "RODATA_FLASH_START must be a multiple of 32KiB")
/* Actual .rodata LMA. */ __rodata_flash_start = MAX (__data_load_end, RODATA_FLASH_START);
/* This might be convenient to initialize NVMCTRL_CTRLB.FLMAP. */ __rodata_flash_start_lsr11 = RODATA_FLASH_START >> 11;
/* .rodata VMA. This is what LDS etc. will use. */ __rodata_start = RODATA_ORIGIN + __rodata_flash_start - RODATA_FLASH_START;
.rodata ABSOLUTE(__rodata_start) : AT (ABSOLUTE(__rodata_flash_start)) { *(.rodata) (.rodata) (.gnu.linkonce.r) __rodata_end = ABSOLUTE(.); } > rodata
__rodata_flash_end = __rodata_flash_start + __rodata_end - __rodata_start;
4. Adjusting the Build Process
- Provide your customized linker-description file to the linker by means of
> avr-gcc ... -T <your-ld-script>
- If you chose to specify the flash segment on the command line, link with
> avr-gcc ... -Wl,--defsym,__RODATA_FLASH_START__=<byte-address>
- Even though modern flash tools like avrdude support flashing ELF files, some people still prefer to use intermediate dumb formats like IntelHEX. When you create such intermediate formats for the flash memory with tools like
avr-objcopy
, make sure that the.rodata
output section is part of the intermediate format flash file by means ofavr-objcopy -j .rodata ...
or similar.
5. Checking that it all works
Some static checks can be performed. In the following example, the program code and initializers for data extend to 0x1929b and into the rodata segment from 0x18000 to 0x1ffff. Thus, rodata starts at 0x1929c which is all fine because there are only 2 bytes of rodata. Notice how the VMA aligns with the LMA.
> avr-objdump -h ... main.elf
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 0000001c 00804000 00019280 00019334 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 00019280 00000000 00000000 000000b4 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 00000002 00a0929c 0001929c 00019350 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
Symbol values are also shown in the map file, like with
avr-gcc ... -Wl,-Map,main.map
This works even in many cases where the linker terminates with an error, and when no disiassembly resp. ELF file is available.
The disassembly of the startup code will show the setup of NVMCTRL_CTRLB.FLMAP
:
0000012e <init_FLMAP>:
12e: 90 91 01 10 lds r25, 0x1001
132: 9f 7c andi r25, 0xCF
134: 90 63 ori r25, 0x30
136: 90 93 01 10 sts 0x1001, r25
The values of the symbols can be displayed with
> avr-nm main.elf | grep -i rodata
00018000 A __RODATA_FLASH_START__
00008000 A __RODATA_LDS_OFFSET__
00008000 A __RODATA_LENGTH__
00a08000 A __RODATA_ORIGIN__
...
FAQ
What about Devices like AVR16* and AVR32*?
For devices from the avrxmega3and avrtinyfamilies, .rodata
is located in flash. This works without any preparations or command line options. Features like__flash,pgm_read_xxxor PROGMEMare not needed.
These devices see the complete program memory in the RAM address space. They have a combined size of program memory and RAM of no more than 64 KiB:
- AVR16DD14, AVR16DD20, AVR16DD28, AVR16DD32, AVR16EA28, AVR16EA32, AVR16EA48, AVR16EB14, AVR16EB20, AVR16EB28, AVR16EB32
- AVR32DA28, AVR32DA32, AVR32DA48, AVR32DB28, AVR32DB32, AVR32DB48, AVR32DD14, AVR32DD20, AVR32DD28, AVR32DD32, AVR32EA28, AVR32EA32, AVR32EA48
- 0-series: ATtiny202, ATtiny204, ATtiny402, ATtiny404, ATtiny406, ATtiny804, ATtiny806, ATtiny807, ATtiny1604, ATtiny1606, ATtiny1607, ATmega808, ATmega809, ATmega1608, ATmega1609, ATmega3208, ATmega3209, ATmega4808, ATmega4809
- 1-series: ATtiny212, ATtiny214, ATtiny412, ATtiny414, ATtiny416, ATtiny417, ATtiny814, ATtiny816, ATtiny817, ATtiny1614, ATtiny1616, ATtiny1617, ATtiny3214, ATtiny3216, ATtiny3217
- 2-series: ATtiny424, ATtiny426, ATtiny427, ATtiny824, ATtiny826, ATtiny827, ATtiny1624, ATtiny1626, ATtiny1627, ATtiny3224, ATtiny3226, ATtiny3227
- Reduced Tiny: ATtiny4, ATtiny5, ATtiny9, ATtiny10, ATtiny102, ATtiny104, ATtiny20, ATtiny40
How to fix "address of section .rodata is not within region rodata"?
Error messages from the linker like
.../avr/bin/ld: address 0xa1995a of main.elf section `.rodata' is not within region `rodata'
means that the read-only data in input sections like .rodata
do not fit (completely) into the rodata
memory region.
There is too much program code + read-only data, and the following fixes are possible:
- Locate the
rodata
region at a higher address by adjusting the value of symbol__RODATA_FLASH_START__
. - If that does not help, there might be too much code as to fit into the AVR's flash memory. Consider measures that reduce code size.
- Orphan sectionsor other custom sections introduced in the linker description might bump the location counter, so that memory below that section is unused. Consider re-organizing your code / data, e.g. by turning orphan sections into proper sections that allow exact specification of their location.
Why is .rodata
located in RAM to begin with?
The address space of AVR microcontrollers is not linear, in partticular
- in order to access RAM locations, instructions like
LD
,LDD
,LDS
andST
,STD
,STS
must be used, whereas - in order to access program memory (flash), instructions like
LPM
orELPM
have to be used.
For example, address 0x100 might be located in RAM, or it might be located in flash. You have to know which one it is in order to write correct code.
The problem in programs is that when a function gets a const address, like the source operand ofmemcpy
you don't know which is which because it is completely fine to pass a pointer to RAM as, say, const char*
. This means that the function cannot know which instruction to use. The following solutions are possible:
- Put
.rodata
into RAM, so we know which load instruction to use. This is what avr-gcc implements. - Let the user decide how an address is accessed. This is how macros like
pgm_read_byte()
from AVR-LibC are used: The user must know whether to use that accessor or not. - Encode the address space somehow in the address. This is very expensive because the decision which load instuction to use can only be taken at run-time. avr-gcc uses this approach for address space __memxbut not for data in the generic address space.
There are devices like ATmega4808 or AVR32DA28 from families avrxmega3
or avrtiny
that see the complete flash memory in the RAM address space. On such devices, .rodata
is located in flash and it works like on any reasonable hardware: There is no need for address spaces like __flash
or special accessors like pgm_read_xxx
, and all reads can use load intructions for RAM.avrtiny
doesn't even support LPM
.
For devices like AVR64DA32 or AVR128DB64 this is more complicated, because
- RAM + flash occupies more than 64 KiB of addresses, so that not all of the flash memory can be visible in the RAM address space. Only a 32 KiB portion is visible.
- There is no canonical placement of
.rodata
in the flash region, and it might rival with other custom sections.
Why is all this not Part of the GNU Toolchain?
Because nobody integrated it.
How to integrate this into the GNU Tools for AVR?
Changes to Binutils
- As a new linker description file is required, a new emulation like
avrxmega8
is needed.
Changes to avr-gcc
- The configure script has to be extended. The following points depend on whether Binutils support the new emulation(s) or not:
- In case, provide a new build-in macro so code like AVR-LibC can consume it.
- The new emulation(s) must be supported as command line options, e.g.
-mmcu=avrxmega8
. - These new options must be multilib options. Map them to their old couterpart(s) in multilib selection so that no new multilib variants are needed (we already have so many of them).
- The devices in question have to be assigned to the appropriate multilib set.
- Scripts have to cope with the new flexibility:
.rodata
should not trigger__do_copy_data
: Adjustavr_need_copy_data_p
in avr.cc.- Adjust the documentation.
Changes to AVR-LibC
- The configure script has to be extended. The following points depend on whether avr-gcc supports the new option(s) or not:
- Define a weak symbol
__RODATA_FLASH_START__
that defaults to the last 32 KiB segment of flash. - The startup code has to set up
NVMCTRL_CTRLB.FLMAP
according to__RODATA_FLASH_START__
. - Decide what to do with
NVMCTRL_CTRLB.FLMAPLOCK
. - Adjust the documentation.