This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit d9269112ee77209caf4cdb7d64371662fc1e7348
Author: Kerogit <kr....@kerogit.eu>
AuthorDate: Thu Jul 3 01:26:57 2025 +0200

    arch/avr/avrdx: do not copy const variables into RAM
    
    AVR uses Hardward architecture with separate address space for program
    memory (flash) and data memory (RAM). Normal program flow can only
    access data memory which means that all variables - including const
    variables - have to be copied into RAM to be accessible. (This happens
    automatically during startup.)
    
    It is possible to work around this limitation in software but that
    can have severe impact on performance and/or API complexity. It is hardly
    feasible to change NuttX interfaces in a way that would allow to make use
    of this workaround.
    
    On newer AVR families, there is an alternative option enabled by this patch.
    These chips map part of their program memory (a 32kB window) into data
    memory address space. This patch leverages this feature and adds support
    for placing const variables into the mapped window. No copy to RAM is done
    for them.
    
    Const variables are therefore loaded directly from flash (not consuming
    RAM) while still being available to be used by any NuttX interface.
    
    Linker script of breadxavr board is changed to make use of these changes.
    
    Tested by verifying string addresses - parameters in printf call
    in a custom application (and also by running the application and verifying
    its output.)
    
    Documentation tested by build.
    
    Signed-off-by: Kerogit <kr....@kerogit.eu>
---
 .../platforms/avr/avrdx/boards/breadxavr/index.rst |   2 +
 .../platforms/avr/common/constants-in-progmem.rst  |  45 +++++++++
 arch/avr/Kconfig                                   |   1 +
 arch/avr/src/avr/Kconfig                           | 105 ++++++++++++++++++++-
 arch/avr/src/avr/Toolchain.defs                    |  10 ++
 arch/avr/src/avrdx/avrdx_head.S                    |  55 +++++++++++
 boards/Kconfig                                     |   1 +
 boards/avr/avrdx/breadxavr/scripts/breadxavr.ld    |  56 ++++++++++-
 8 files changed, 267 insertions(+), 8 deletions(-)

diff --git a/Documentation/platforms/avr/avrdx/boards/breadxavr/index.rst 
b/Documentation/platforms/avr/avrdx/boards/breadxavr/index.rst
index 5c16449030c..1265766b284 100644
--- a/Documentation/platforms/avr/avrdx/boards/breadxavr/index.rst
+++ b/Documentation/platforms/avr/avrdx/boards/breadxavr/index.rst
@@ -1,3 +1,5 @@
+.. _breadxavr_board:
+
 ========================
 AVR128DA28 on breadboard
 ========================
diff --git a/Documentation/platforms/avr/common/constants-in-progmem.rst 
b/Documentation/platforms/avr/common/constants-in-progmem.rst
index 8daf0f58d5d..dda8ca6c6e2 100644
--- a/Documentation/platforms/avr/common/constants-in-progmem.rst
+++ b/Documentation/platforms/avr/common/constants-in-progmem.rst
@@ -178,3 +178,48 @@ Note that both ``IOBJ`` and ``IPTR`` need to be activated 
by
 If this configuration option is not set, both macros are defined
 to be empty and all strings will be copied to RAM (performance penalty
 discussed above is therefore removed as well.)
+
+Using memory-mapped flash
+=========================
+
+Newer AVR devices - tinyAVR and AVR DA/DB family - have their program
+memory mapped into upper 32kB half of data memory address space.
+(If the program memory size exceeds 32kB, only a 32kB-sized window
+is mapped. This is controlled by NVM peripheral within the chip.
+On current chips, the top window is mapped by default.)
+
+This can be leveraged in a way that makes these AVR devices behave
+as a von Neumann architecture. With proper configuration in a linker
+script, all constants can be placed into the mapped program memory
+region where they will be accessible for both load from program memory
+instructions and load from data address space instructions.
+
+As long as these constants fit into the 32kB window, this is a best
+available option on devices that support it. It combines advantages
+of all previous options and doesn't have any of their drawbacks.
+The performance penalty is negligible (flash read is few cycles slower
+than RAM read), RAM is not consumed and all variables are fully
+available to be used as parameters for any kernel interface.
+
+Unlike previous options, using this one is fully controlled by board's
+linker script. The linker script needs to place the constants
+(eg. ``rodata`` section) to appropriate memory location.
+
+Despite that, there is still a configuration option
+:menuselection:`System Type --> Use memory-mapped access to flash`,
+which is selected by default on devices that support this method
+of not copying data from program memory to RAM. Setting it unlocks
+additional configuration options
+:menuselection:`Size of .rodata FLMAP section` and
+:menuselection:`Offset of .rodata FLMAP section` which may be used
+to further configure section sizes. Note that these values are
+only made available to the linker and board's linker script needs
+to be designed to obey them.
+
+To have these configuration options available, the board needs
+to select ``AVR_HAVE_BOARD_FLMAP`` in its configuration. It declares
+that its linker script will obey ``__RODATA_SIZE__`` and
+``__RODATA_OFFSET__`` symbols (which are set by the above-mentioned
+configuration options.)
+
+See the linker script of :ref:`breadxavr_board` for an example.
diff --git a/arch/avr/Kconfig b/arch/avr/Kconfig
index 394194e792e..771274f1c00 100644
--- a/arch/avr/Kconfig
+++ b/arch/avr/Kconfig
@@ -28,6 +28,7 @@ config ARCH_CHIP_AVRDX
        select ARCH_FAMILY_AVR
        select MM_SMALL
        select ARCH_HAVE_TICKLESS
+       select AVR_HAVE_FLMAP
        ---help---
                Atmel/Microchip AVR 32/64/128 DA/DB core family.
 
diff --git a/arch/avr/src/avr/Kconfig b/arch/avr/src/avr/Kconfig
index db39ff7acda..036494f18d4 100644
--- a/arch/avr/src/avr/Kconfig
+++ b/arch/avr/src/avr/Kconfig
@@ -68,22 +68,38 @@ config AVR_BUILDROOT_TOOLCHAIN
 
 endchoice # Toolchain
 
+choice
+       prompt "Const variable placement"
+       default AVR_CONST_TO_FLMAP if AVR_HAVE_BOARD_FLMAP && AVR_HAVE_FLMAP
+       default AVR_HAS_MEMX_PTR if ARCH_DEBUG_H
+       default AVR_CONST_TO_RAM
+
+config AVR_CONST_TO_RAM
+       bool "No special handling, copy to RAM"
+       ---help---
+               Initialization code will copy const variables into RAM.
+               This is a standard option available for all compilers and it is
+               fully supported in the kernel (because there are no special
+               requirements for such support.)
+
+               Main disadvantage of this option is that it may severely reduce
+               RAM available for the application and it may even be impossible
+               to fit all data into the RAM.
+
 config AVR_HAS_MEMX_PTR
-       bool "Enable in-flash static const strings"
+       bool "Mark const variables with __memx"
        depends on AVR_ATMEL_AVR_TOOLCHAIN || AVR_LINUXGCC_TOOLCHAIN
-       default y if ARCH_DEBUG_H
-       default n
        ---help---
                Enabling this option activates IOBJ and IPTR qualifiers
                for pointers in the source code. Compiler will then be allowed
-               to place constants into program memory without copying it to 
RAM,
+               to place constants into program memory without copying them to 
RAM,
                reducing amount of RAM needed to hold static data.
 
                The compiler then extends pointers with these qualifiers enabled
                to 24bit length with highest bit set for data that reside in 
RAM.
                Based on this bit, it will then read the data using instructions
                appropriate for the underlying storage. As such, there is
-               a performance tradeoff.
+               a potentially significant performance tradeoff.
 
                Additionally, if this is enabled, all constant strings used
                for debugging and assertion are placed into program memory,
@@ -94,8 +110,87 @@ config AVR_HAS_MEMX_PTR
                pointers in arbitrary interaction with the kernel. Not all API
                functions have these qualifiers added to their parameters.
 
+config AVR_CONST_TO_FLMAP
+       bool "Use memory-mapped access to flash"
+       depends on AVR_HAVE_BOARD_FLMAP && AVR_HAVE_FLMAP
+       ---help---
+               Newer AVR chips - namely tinyAVR and AVR families - have (part 
of)
+               their program memory (flash) mapped into data memory address 
space.
+               The mapping is limited to 32kB window.
+
+               With this option enabled, const variables are kept in the 
program
+               memory and no copy to RAM is done. Yet it is still possible to 
use
+               such variables in any interaction with the kernel as they are
+               visible in data memory address space.
+
+               Note that this option only triggers some basic configuration
+               in the init function. It is the linker script of the board that 
needs
+               to ensure variables are placed correctly.
+
+               Beware that FLMAP bits in NVMCTRL.CTRLB I/O register which 
select
+               the segment of program memory to be mapped may not be changed 
freely
+               by the application. If the application needs to change the 
mapping,
+               it may only do so while observing these rules:
+
+               1. No kernel function must be called until the original value
+               is restored.
+
+               2. Interrupts must be disabled for as long as the value is 
changed.
+
+endchoice # Const variable placement
+
+config AVR_FLMAP_RODATA_SIZE
+       int "Size of .rodata FLMAP section"
+       depends on AVR_CONST_TO_FLMAP
+       default 4096
+       ---help---
+               Specify size of .rodata memory section, ie. the section that 
stores
+               const variables. This will be passed as a parameter to the 
linker
+               to be used by the board's linker script.
+
+               Value must be divisible by 512 and no more than 32 kilobytes
+               is possible.
+
+config AVR_FLMAP_RODATA_OFFSET
+       int "Offset of .rodata FLMAP section"
+       depends on AVR_CONST_TO_FLMAP
+       default 0
+       ---help---
+               Specify size of memory block between end of the .rodata section
+               (counting its full size as defined in AVR_FLMAP_RODATA_SIZE)
+               and the end of flash.
+
+               This value is intended to leave the end of the flash unused,
+               presumably for the purpose of placing APPDATA section in there
+               (see the chip documentation for details about subdividing
+               the program flash to BOOT, APPCODE and APPDATA sections.)
+
+               Note that this value is only passed to the linker to be used
+               by the linker script - the script then needs to place
+               the .rodata section accordingly.
+
+               Value must be divisible by 512 and no more than 31 kilobytes
+               is possible. Sum of this value and AVR_FLMAP_RODATA_SIZE must
+               also not exceed 32kB.
 
 config AVR_HAS_RAMPZ
        bool
 
+config AVR_HAVE_BOARD_FLMAP
+       bool
+       ---help---
+               This configuration option is supposed to be selected by board's
+               Kconfig if the board's linker script responds to __RODATA_SIZE__
+               and __RODATA_OFFSET__ passed by the linker and if it configures
+               .rodata section's size and position accordingly. Configuration
+               options that allow the user to configure values in these symbols
+               are unlocked if this is set and if the chip has support
+               for memory-mapped flash
+
+config AVR_HAVE_FLMAP
+       bool
+       ---help---
+               This configuration option is set by chips that have (at least
+               some part of their) flash mapped into data memory address space.
+
 endif # ARCH_FAMILY_AVR
diff --git a/arch/avr/src/avr/Toolchain.defs b/arch/avr/src/avr/Toolchain.defs
index 54ba8416ee3..24179c9b56c 100644
--- a/arch/avr/src/avr/Toolchain.defs
+++ b/arch/avr/src/avr/Toolchain.defs
@@ -106,6 +106,16 @@ ifeq ($(CONFIG_DEBUG_OPT_UNUSED_SECTIONS),y)
   ARCHOPTIMIZATION += -ffunction-sections -fdata-sections
 endif
 
+# .rodata size for FLMAP configuration
+ifeq ($(CONFIG_AVR_CONST_TO_FLMAP),y)
+  LDFLAGS += --defsym=__RODATA_SIZE__=$(CONFIG_AVR_FLMAP_RODATA_SIZE)
+endif
+
+# .rodata offset for FLMAP configuration
+ifeq ($(CONFIG_AVR_CONST_TO_FLMAP),y)
+  LDFLAGS += --defsym=__RODATA_OFFSET__=$(CONFIG_AVR_FLMAP_RODATA_OFFSET)
+endif
+
 ifeq ($(CONFIG_DEBUG_LINK_MAP),y)
   LDFLAGS += -Map=$(call CONVERT_PATH,$(TOPDIR)$(DELIM)nuttx.map)
 endif
diff --git a/arch/avr/src/avrdx/avrdx_head.S b/arch/avr/src/avrdx/avrdx_head.S
index 823542cc2b6..63e101070c0 100644
--- a/arch/avr/src/avrdx/avrdx_head.S
+++ b/arch/avr/src/avrdx/avrdx_head.S
@@ -492,6 +492,61 @@ __start:
        out             _SFR_IO_ADDR(SPH), r29
        out             _SFR_IO_ADDR(SPL), r28
 
+
+#ifdef CONFIG_AVR_HAVE_FLMAP
+
+       /* Configure FLMAP bits in NVMCTRL.CTRLB in case this was software
+        * reset and they are not in default state.
+        *
+        * Don't care if the application actually changes the register. These
+        * bits are not under configuration protection (CCP) and runaway
+        * application can possibly change them.
+        *
+        * This is executed if the chip supports it, regardless of if the board
+        * declared it is using the feature by setting AVR_HAVE_BOARD_FLMAP
+        * in Kconfig (it may be using it without setting that option.)
+        */
+
+       ldi             r26, lo8(NVMCTRL_CTRLB)
+       ldi             r27, hi8(NVMCTRL_CTRLB)
+       ld              r16, X
+
+       /* Default value (on current chips anyway) uses all bits set to one. */
+
+#  if !defined(NVMCTRL_FLMAP_1_bm)
+
+       /* Might be there won't be such (supported) device ever. */
+
+#    error Chips without FLMAP bit 1 are not supported
+
+#  elif !defined(NVMCTRL_FLMAP_2_bm)
+
+       /* Expected case */
+
+       ldi             r17, NVMCTRL_FLMAP_0_bm | NVMCTRL_FLMAP_1_bm;
+
+#  else
+
+       /* Future device with more than 128kB RAM. The default is expected
+        * to be all bits set to one but it needs to be verified when
+        * support for such chip is added. Issue a warning.
+        */
+
+#    warning Support for more than 128kB flash was done blindly here
+
+       ldi             r17, NVMCTRL_FLMAP_0_bm | NVMCTRL_FLMAP_1_bm | 
NVMCTRL_FLMAP_2_bm;
+
+#  endif
+
+       /* As long as we are always setting bits to one, we don't need
+        * to clear them in the original value
+        */
+
+       or              r16, r17
+       st              X, r16
+
+#endif
+
        /* Copy initial global data values from FLASH into RAM */
 
        .global __do_copy_data;                 /* Required to suppress 
dragging in logic from libgcc */
diff --git a/boards/Kconfig b/boards/Kconfig
index 6b1bbfdac77..348f38bfdfa 100644
--- a/boards/Kconfig
+++ b/boards/Kconfig
@@ -94,6 +94,7 @@ config ARCH_BOARD_AVRDX_BREADXAVR
        select ARCH_HAVE_LEDS
        select ARCH_HAVE_BUTTONS
        select ARCH_HAVE_IRQBUTTONS
+       select AVR_HAVE_BOARD_FLMAP
        ---help---
                This is a board used to make something use an AVRnDx core
                so it can be developed and tested.
diff --git a/boards/avr/avrdx/breadxavr/scripts/breadxavr.ld 
b/boards/avr/avrdx/breadxavr/scripts/breadxavr.ld
index abcb08e6b86..ff391b26282 100644
--- a/boards/avr/avrdx/breadxavr/scripts/breadxavr.ld
+++ b/boards/avr/avrdx/breadxavr/scripts/breadxavr.ld
@@ -30,11 +30,55 @@
  * *Memory configuration A
  */
 
+/* Use this instead of repeated magical number */
+__CHIP_FLASH_SIZE__ = 128K;
+
+/* VMA above 0x800000, must not collide with anything else,
+ * arbitrary value otherwise.
+ */
+__RODATA_VMA__ = 0xa00000;
+/* Application configuration is supposed to pass this value
+ * to linker so the default should not be used.
+ */
+__RODATA_SIZE__ = DEFINED(__RODATA_SIZE__) ? __RODATA_SIZE__ : 4K;
+/* Size increment matches chip's memory organization (boot, application
+ * code and application data can be sized with 512B increments.)
+ */
+ASSERT (__RODATA_SIZE__ % 512 == 0,
+        "__RODATA_SIZE__ must be a multiple of 512")
+/* Only 32kB of flash is mapped to data memory */
+ASSERT (__RODATA_SIZE__ <= 32K,
+        "__RODATA_SIZE__ must not be larger than 32kB")
+
+/* Same as for __RODATA_SIZE__ */
+__RODATA_OFFSET__ = DEFINED(__RODATA_OFFSET__) ? __RODATA_OFFSET__ : 0;
+/* Size increment matches chip's memory organization (boot, application
+ * code and application data can be sized with 512B increments.)
+ */
+ASSERT (__RODATA_OFFSET__ % 512 == 0,
+        "__RODATA_OFFSET__ must be a multiple of 512")
+/* Only 32kB of flash is mapped to data memory */
+ASSERT (__RODATA_OFFSET__ <= 31K,
+        "__RODATA_OFFSET__ must not be larger than 31kB")
+
+/* Also verify sum of size and offset */
+ASSERT (__RODATA_SIZE__ + __RODATA_OFFSET__ <= 32K,
+        "Sum of __RODATA_SIZE__ and __RODATA_OFFSET__ must not be larger than 
32kB")
+
+/* Set the origin to the very end of flash. 64K is a magical number
+ * that constitutes of 32K (start of memory-mapped flash in data
+ * memory address space) and 32K (end of the area, size of the rodata
+ * section is subtracted from it.)
+ */
+__RODATA_ORIGIN__ = __RODATA_VMA__ + 64K - __RODATA_SIZE__ - __RODATA_OFFSET__;
+__RODATA_FLASH_START = __CHIP_FLASH_SIZE__ - __RODATA_SIZE__ - 
__RODATA_OFFSET__;
+
 MEMORY
 {
-  flash    (rx) : ORIGIN = 0, LENGTH = 128K
+  flash    (rx) : ORIGIN = 0, LENGTH = __CHIP_FLASH_SIZE__
   sram   (rw!x) : ORIGIN = 0x804000, LENGTH = 16K
   eeprom (rw!x) : ORIGIN = 0x801400, LENGTH = 512
+  rodata  (r!x) : ORIGIN = __RODATA_ORIGIN__, LENGTH = __RODATA_SIZE__
 }
 
 ENTRY(__start)
@@ -129,8 +173,6 @@ SECTIONS
   {
     _sdata = ABSOLUTE(.);
     *(.data .data.*)
-    *(.rodata)
-    *(.rodata*)
     *(.gnu.linkonce.d.*)
     CONSTRUCTORS
     _edata = ABSOLUTE(.);
@@ -154,6 +196,14 @@ SECTIONS
     _enoinit = ABSOLUTE(.);
   } > sram
 
+  .rodata ABSOLUTE(__RODATA_ORIGIN__) : AT (ABSOLUTE(__RODATA_FLASH_START))
+  {
+    *(.rodata)
+    *(.rodata*)
+    *(.gnu.linkonce.r*)
+    _rodata_end = ABSOLUTE(.);
+  } > rodata
+
   .eeprom     :
   {
     _seeprom = ABSOLUTE(.);

Reply via email to