Skip to content

arch: Add SRAM based context for ISR #88883

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: main
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
7 changes: 7 additions & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ config ISR_TABLES_LOCAL_DECLARATION

config DYNAMIC_INTERRUPTS
bool "Installation of IRQs at runtime"
select SRAM_SW_ISR_TABLE
help
Enable installation of interrupts at runtime, which will move some
interrupt-related data structures to RAM instead of ROM, and
Expand Down Expand Up @@ -598,6 +599,12 @@ config SRAM_VECTOR_TABLE
When XiP is enabled, this option will result in the vector table being
relocated from Flash to SRAM.

config SRAM_SW_ISR_TABLE
bool "Place the software ISR table in SRAM instead of flash"
help
The option specifies that the software interrupts vector table will be
placed inside SRAM instead of the flash.

config IRQ_OFFLOAD_NESTED
bool "irq_offload() supports nested IRQs"
depends on IRQ_OFFLOAD
Expand Down
2 changes: 1 addition & 1 deletion arch/arm/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ zephyr_linker_sources(ROM_START SORT_KEY 0x1vectors cortex_m/vector_table_pad.ld
endif()

if(CONFIG_GEN_SW_ISR_TABLE)
if(CONFIG_DYNAMIC_INTERRUPTS)
if(CONFIG_SRAM_SW_ISR_TABLE)
zephyr_linker_sources(RWDATA swi_tables.ld)
else()
zephyr_linker_sources(RODATA swi_tables.ld)
Expand Down
2 changes: 1 addition & 1 deletion arch/arm64/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ endif()
add_subdirectory_ifdef(CONFIG_XEN xen)

if(CONFIG_GEN_SW_ISR_TABLE)
if(CONFIG_DYNAMIC_INTERRUPTS)
if(CONFIG_SRAM_SW_ISR_TABLE)
zephyr_linker_sources(RWDATA swi_tables.ld)
else()
zephyr_linker_sources(RODATA swi_tables.ld)
Expand Down
2 changes: 1 addition & 1 deletion cmake/linker_script/common/common-ram.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# The contents of this file is based on include/zephyr/linker/common-ram.ld
# Please keep in sync

if(CONFIG_GEN_SW_ISR_TABLE AND CONFIG_DYNAMIC_INTERRUPTS)
if(CONFIG_GEN_SW_ISR_TABLE AND CONFIG_SRAM_SW_ISR_TABLE)
# ld align has been changed to subalign to provide identical behavior scatter vs. ld.
zephyr_linker_section(NAME sw_isr_table
GROUP DATA_REGION
Expand Down
2 changes: 1 addition & 1 deletion cmake/linker_script/common/common-rom.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ zephyr_linker_section_obj_level(SECTION init LEVEL SMP)
zephyr_iterable_section(NAME device NUMERIC KVMA RAM_REGION GROUP RODATA_REGION)
zephyr_iterable_section(NAME service NUMERIC KVMA RAM_REGION GROUP RODATA_REGION)

if(CONFIG_GEN_SW_ISR_TABLE AND NOT CONFIG_DYNAMIC_INTERRUPTS)
if(CONFIG_GEN_SW_ISR_TABLE AND NOT CONFIG_SRAM_SW_ISR_TABLE)
# ld align has been changed to subalign to provide identical behavior scatter vs. ld.
zephyr_linker_section(NAME sw_isr_table KVMA FLASH GROUP RODATA_REGION SUBALIGN ${CONFIG_ARCH_SW_ISR_TABLE_ALIGN} NOINPUT)
zephyr_linker_section_configure(
Expand Down
3 changes: 3 additions & 0 deletions doc/releases/migration-guide-4.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ Modules

Architectures
*************

* The :kconfig:option:`CONFIG_DYNAMIC_INTERRUPTS` option has a new dependency on
:kconfig:option:`CONFIG_SRAM_SW_ISR_TABLE`.
4 changes: 4 additions & 0 deletions doc/releases/release-notes-4.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ New APIs and options
like you need to add more details, add them in the API documentation code
instead.
* Architectures

* :kconfig:option:`CONFIG_SRAM_SW_ISR_TABLE`

New Boards
**********

Expand Down
2 changes: 1 addition & 1 deletion include/zephyr/linker/common-ram.ld
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
ITERABLE_SECTION_RAM(scmi_protocol, Z_LINK_ITERABLE_SUBALIGN)
#endif /* CONFIG_ARM_SCMI */

#if defined(CONFIG_GEN_SW_ISR_TABLE) && defined(CONFIG_DYNAMIC_INTERRUPTS)
#if defined(CONFIG_GEN_SW_ISR_TABLE) && defined(CONFIG_SRAM_SW_ISR_TABLE)
SECTION_DATA_PROLOGUE(sw_isr_table,,)
{
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif

#if defined(CONFIG_GEN_SW_ISR_TABLE) && !defined(CONFIG_DYNAMIC_INTERRUPTS)
#if defined(CONFIG_GEN_SW_ISR_TABLE) && !defined(CONFIG_SRAM_SW_ISR_TABLE)
SECTION_PROLOGUE(sw_isr_table,,)
{
/*
Expand Down
24 changes: 24 additions & 0 deletions tests/application_development/ram_context_for_isr/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2025 Silicon Laboratories Inc.
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(ram_context_for_isr)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

if(CONFIG_CPU_CORTEX_M_HAS_VTOR)
zephyr_code_relocate(FILES ${ZEPHYR_BASE}/arch/arm/core/cortex_m/isr_wrapper.c LOCATION RAM)
zephyr_code_relocate(FILES ${ZEPHYR_BASE}/arch/arm/core/cortex_m/exc_exit.c LOCATION RAM)
endif()

zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c FILTER ".test_irq_callback" LOCATION RAM )
zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/fake_driver.c FILTER ".fake_driver_isr" LOCATION RAM )

# Only needed because the fake driver is defined in tests folder
zephyr_linker_sources(SECTIONS sections-rom.ld)
94 changes: 94 additions & 0 deletions tests/application_development/ram_context_for_isr/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
.. _vector_table_relocation:

RAM Context for ISR
###################

Overview
********
A test that verifies the ISR vector table relocation and
full IRQ execution under RAM context.


Test Description
****************

This test verifies that interrupt service routines (ISRs), their callbacks, and the driver code
can be fully relocated to RAM, ensuring that all interrupt handling occurs from SRAM rather than
flash. This is important for real-time applications where minimizing interrupt latency is critical.

The test uses a simple "fake driver" that registers an IRQ callback. The following aspects are
validated:

- The vector table and ISR wrapper are relocated to SRAM.
- The fake driver's ISR and the registered callback are also located in SRAM.
- When the interrupt is triggered, the callback and all relevant code execute from RAM, not flash.
- The test asserts at runtime that the addresses of the callback, driver ISR, and ISR wrapper are
within the SRAM address range.

Test Steps
**********

1. The test registers a callback with the fake driver.
2. It triggers the interrupt (IRQ 27).
3. The callback checks (using assertions) that:
- Its own address,
- The driver's ISR address,
- The ISR wrapper address,
- And the device structure,
are all within the SRAM region.
4. The test passes if all assertions succeed and the callback is executed.

Configuration
*************

The test is configured with the following options in prj.conf:

- ``CONFIG_SRAM_VECTOR_TABLE=y``: Relocate the vector table to SRAM.
- ``CONFIG_SRAM_SW_ISR_TABLE=y``: Relocate the software ISR table to SRAM.
- ``CONFIG_CODE_DATA_RELOCATION=y``: Enable code/data relocation to SRAM.
- ``CONFIG_DEVICE_MUTABLE=y``: Allow device structures to be mutable (in RAM).
- ``CONFIG_ZTEST=y``: Enable Zephyr's test framework.

The test is only supported on ARM Cortex-M platforms with VTOR (Vector Table Offset Register) support.

Advanced Configuration and Limitations
**************************************

Configuration Options in testcase.yaml
======================================

By default, the test disables several options in testcase.yaml:

- ``CONFIG_TRACING_ISR=n``: Disables ISR tracing.
**If enabled:** Enabling ISR tracing adds hooks to trace ISR entry/exit, which may introduce
additional code paths not relocated to RAM. This can increase interrupt latency and may cause
some tracing code to execute from flash, partially defeating the purpose of full RAM relocation.

- ``CONFIG_STACK_SENTINEL=n``: Disables stack overflow detection sentinels.
**If enabled:** Enabling stack sentinels adds extra checks to detect stack overflows. These
checks may reside in flash and be called during interrupt handling, potentially increasing
latency and causing some code to execute from flash.

- ``CONFIG_PM=n``: Disables power management.
**If enabled:** Enabling power management may cause the system to enter low-power states. Some
wake-up interrupts may not use the relocated vector table in RAM, and the system may revert to
using the flash-based vector table to exit idle states. This can result in some ISRs or their
wrappers executing from flash, especially during wake-up from deep sleep.

**Summary:**
If you enable any of these options, ensure all code paths executed during interrupt handling are
also relocated to RAM to maintain the lowest possible interrupt latency. Otherwise, some code may
still execute from flash, increasing latency and partially defeating the test's purpose.

Driver API Location and Limitations
===================================

**Important Note:**
While the test ensures ISR, callback, and device structures are relocated to RAM, the driver API
structure (``struct fake_driver_api``)—containing function pointers for driver methods—is not
relocated to RAM by default. This structure typically resides in flash (ROM) as device constant data.

As long as you don't call the driver API during ISR execution, the current configuration is sufficient.
However, if you need to call driver API functions from within an ISR, you must relocate the driver API
structure to RAM. The simplest approach is to relocate the entire driver file to RAM rather than just
the ISR symbol.
17 changes: 17 additions & 0 deletions tests/application_development/ram_context_for_isr/app.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

/ {
#address-cells = <1>;
#size-cells = <1>;

fakedriver: fakedriver@E0000000 {
compatible = "fakedriver";
reg = <0xE0000000 0x2000>;
zephyr,mutable;
status = "okay";
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2025 Silicon Laboratories
# SPDX-License-Identifier: Apache-2.0

description: Properties for fake driver.

compatible: "fakedriver"

include: [base.yaml, mutable.yaml]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_
#define ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*fake_driver_irq_callback_t)(const struct device *dev, void *user_data);

struct fake_driver_config {
void (*irq_config_func)(void);
uint8_t irq_num;
uint8_t irq_priority;
};

struct fake_driver_data {
fake_driver_irq_callback_t irq_callback;
void *user_data;
};

__subsystem struct fake_driver_api {
int (*configure)(const struct device *dev, int config);
int (*register_irq_callback)(const struct device *dev, fake_driver_irq_callback_t cb,
void *user_data);
};

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_ */
6 changes: 6 additions & 0 deletions tests/application_development/ram_context_for_isr/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CONFIG_COVERAGE=n
CONFIG_ZTEST=y
CONFIG_SRAM_VECTOR_TABLE=y
CONFIG_SRAM_SW_ISR_TABLE=y
CONFIG_CODE_DATA_RELOCATION=y
CONFIG_DEVICE_MUTABLE=y
10 changes: 10 additions & 0 deletions tests/application_development/ram_context_for_isr/sections-rom.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/linker/iterable_sections.h>

/* Only needed because the driver is defined in tests folder */
ITERABLE_SECTION_ROM(fake_driver_api, Z_LINK_ITERABLE_SUBALIGN)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/device.h>
#include <zephyr/irq.h>
#include "fake_driver.h"

#define DT_DRV_COMPAT fakedriver

#define TEST_IRQ_NUM 27
#define TEST_IRQ_PRIO 4

static void fake_driver_isr(const void *arg)
{
const struct device *dev = (const struct device *)arg;
struct fake_driver_data *data = dev->data;

/* Store the address of fake_driver_isr in user_data because it will be optimized by the
* compiler (even with __noinline__ attribute)
*/
data->user_data = (void *)&fake_driver_isr;

if (data->irq_callback != NULL) {
data->irq_callback(dev, data->user_data);
}
}

static int fake_driver_configure(const struct device *dev, int config)
{
return 0;
}

static int fake_driver_register_irq_callback(const struct device *dev,
fake_driver_irq_callback_t cb, void *user_data)
{
struct fake_driver_data *data = dev->data;

data->irq_callback = cb;
data->user_data = user_data;

return 0;
}

DEVICE_API(fake, fake_driver_func) = {
.configure = fake_driver_configure,
.register_irq_callback = fake_driver_register_irq_callback,
};

static int fake_driver_init(const struct device *dev)
{
const struct fake_driver_config *config = dev->config;
struct fake_driver_data *data = dev->data;

data->irq_callback = NULL;
data->user_data = NULL;

config->irq_config_func();

return 0;
}

#define FAKE_INIT(inst) \
static struct fake_driver_data fake_driver_data_##inst; \
static void fake_driver_irq_config_func_##inst(void) \
{ \
IRQ_CONNECT(TEST_IRQ_NUM, TEST_IRQ_PRIO, fake_driver_isr, \
DEVICE_DT_INST_GET(inst), 0); \
irq_enable(TEST_IRQ_NUM); \
} \
static struct fake_driver_config fake_driver_config_##inst = { \
.irq_config_func = fake_driver_irq_config_func_##inst, \
.irq_num = TEST_IRQ_NUM, \
.irq_priority = TEST_IRQ_PRIO, \
}; \
DEVICE_DT_INST_DEFINE(inst, &fake_driver_init, NULL, &fake_driver_data_##inst, \
&fake_driver_config_##inst, PRE_KERNEL_1, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &fake_driver_func);

DT_INST_FOREACH_STATUS_OKAY(FAKE_INIT)
Loading