Skip to content

Commit 0d8a8bf

Browse files
tests: application_development: add new test for ram-based isr
This commit introduces the new test ram_context_for_isr that shows how to configure an application (prj.conf, CMakeLists.txt, etc.) in order to enable full ISR execution with a RAM context. It resolves the issue of flash latency affecting real-time constraints during ISR execution. Signed-off-by: Martin Hoff <martin.hoff@silabs.com>
1 parent acef096 commit 0d8a8bf

File tree

10 files changed

+379
-0
lines changed

10 files changed

+379
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) 2025 Silicon Laboratories Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
6+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
7+
8+
project(ram_context_for_isr)
9+
10+
FILE(GLOB app_sources src/*.c)
11+
target_sources(app PRIVATE ${app_sources})
12+
13+
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
14+
15+
if(CONFIG_CPU_CORTEX_M_HAS_VTOR)
16+
zephyr_code_relocate(FILES ${ZEPHYR_BASE}/arch/arm/core/cortex_m/isr_wrapper.c LOCATION RAM)
17+
zephyr_code_relocate(FILES ${ZEPHYR_BASE}/arch/arm/core/cortex_m/exc_exit.c LOCATION RAM)
18+
endif()
19+
20+
zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c FILTER ".test_irq_callback" LOCATION RAM )
21+
zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/fake_driver.c FILTER ".fake_driver_isr" LOCATION RAM )
22+
23+
# Only needed because the fake driver is defined in tests folder
24+
zephyr_linker_sources(SECTIONS sections-rom.ld)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.. _vector_table_relocation:
2+
3+
RAM Context for ISR
4+
###################
5+
6+
Overview
7+
********
8+
A test that verifies the ISR vector table relocation and
9+
full IRQ execution under RAM context.
10+
11+
12+
Test Description
13+
****************
14+
15+
This test verifies that interrupt service routines (ISRs), their callbacks, and the driver code
16+
can be fully relocated to RAM, ensuring that all interrupt handling occurs from SRAM rather than
17+
flash. This is important for real-time applications where minimizing interrupt latency is critical.
18+
19+
The test uses a simple "fake driver" that registers an IRQ callback. The following aspects are
20+
validated:
21+
22+
- The vector table and ISR wrapper are relocated to SRAM.
23+
- The fake driver's ISR and the registered callback are also located in SRAM.
24+
- When the interrupt is triggered, the callback and all relevant code execute from RAM, not flash.
25+
- The test asserts at runtime that the addresses of the callback, driver ISR, and ISR wrapper are
26+
within the SRAM address range.
27+
28+
Test Steps
29+
**********
30+
31+
1. The test registers a callback with the fake driver.
32+
2. It triggers the interrupt (IRQ 27).
33+
3. The callback checks (using assertions) that:
34+
- Its own address,
35+
- The driver's ISR address,
36+
- The ISR wrapper address,
37+
- And the device structure,
38+
are all within the SRAM region.
39+
4. The test passes if all assertions succeed and the callback is executed.
40+
41+
Configuration
42+
*************
43+
44+
The test is configured with the following options in prj.conf:
45+
46+
- ``CONFIG_SRAM_VECTOR_TABLE=y``: Relocate the vector table to SRAM.
47+
- ``CONFIG_SRAM_SW_ISR_TABLE=y``: Relocate the software ISR table to SRAM.
48+
- ``CONFIG_CODE_DATA_RELOCATION=y``: Enable code/data relocation to SRAM.
49+
- ``CONFIG_DEVICE_MUTABLE=y``: Allow device structures to be mutable (in RAM).
50+
- ``CONFIG_ZTEST=y``: Enable Zephyr's test framework.
51+
52+
The test is only supported on ARM Cortex-M platforms with VTOR (Vector Table Offset Register) support.
53+
54+
Advanced Configuration and Limitations
55+
**************************************
56+
57+
Configuration Options in testcase.yaml
58+
======================================
59+
60+
By default, the test disables several options in testcase.yaml:
61+
62+
- ``CONFIG_TRACING_ISR=n``: Disables ISR tracing.
63+
**If enabled:** Enabling ISR tracing adds hooks to trace ISR entry/exit, which may introduce
64+
additional code paths not relocated to RAM. This can increase interrupt latency and may cause
65+
some tracing code to execute from flash, partially defeating the purpose of full RAM relocation.
66+
67+
- ``CONFIG_STACK_SENTINEL=n``: Disables stack overflow detection sentinels.
68+
**If enabled:** Enabling stack sentinels adds extra checks to detect stack overflows. These
69+
checks may reside in flash and be called during interrupt handling, potentially increasing
70+
latency and causing some code to execute from flash.
71+
72+
- ``CONFIG_PM=n``: Disables power management.
73+
**If enabled:** Enabling power management may cause the system to enter low-power states. Some
74+
wake-up interrupts may not use the relocated vector table in RAM, and the system may revert to
75+
using the flash-based vector table to exit idle states. This can result in some ISRs or their
76+
wrappers executing from flash, especially during wake-up from deep sleep.
77+
78+
**Summary:**
79+
If you enable any of these options, ensure all code paths executed during interrupt handling are
80+
also relocated to RAM to maintain the lowest possible interrupt latency. Otherwise, some code may
81+
still execute from flash, increasing latency and partially defeating the test's purpose.
82+
83+
Driver API Location and Limitations
84+
===================================
85+
86+
**Important Note:**
87+
While the test ensures ISR, callback, and device structures are relocated to RAM, the driver API
88+
structure (``struct fake_driver_api``)—containing function pointers for driver methods—is not
89+
relocated to RAM by default. This structure typically resides in flash (ROM) as device constant data.
90+
91+
As long as you don't call the driver API during ISR execution, the current configuration is sufficient.
92+
However, if you need to call driver API functions from within an ISR, you must relocate the driver API
93+
structure to RAM. The simplest approach is to relocate the entire driver file to RAM rather than just
94+
the ISR symbol.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) 2025 Silicon Laboratories Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/ {
8+
#address-cells = <1>;
9+
#size-cells = <1>;
10+
11+
fakedriver: fakedriver@E0000000 {
12+
compatible = "fakedriver";
13+
reg = <0xE0000000 0x2000>;
14+
zephyr,mutable;
15+
status = "okay";
16+
};
17+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 Silicon Laboratories
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: Properties for fake driver.
5+
6+
compatible: "fakedriver"
7+
8+
include: [base.yaml, mutable.yaml]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2025 Silicon Laboratories Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_
8+
#define ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
typedef void (*fake_driver_irq_callback_t)(const struct device *dev, void *user_data);
15+
16+
struct fake_driver_config {
17+
void (*irq_config_func)(void);
18+
uint8_t irq_num;
19+
uint8_t irq_priority;
20+
};
21+
22+
struct fake_driver_data {
23+
fake_driver_irq_callback_t irq_callback;
24+
void *user_data;
25+
};
26+
27+
__subsystem struct fake_driver_api {
28+
int (*configure)(const struct device *dev, int config);
29+
int (*register_irq_callback)(const struct device *dev, fake_driver_irq_callback_t cb,
30+
void *user_data);
31+
};
32+
33+
#ifdef __cplusplus
34+
}
35+
#endif
36+
37+
#endif /* ZEPHYR_INCLUDE_DRIVERS_FAKE_DRIVER_H_ */
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CONFIG_COVERAGE=n
2+
CONFIG_ZTEST=y
3+
CONFIG_SRAM_VECTOR_TABLE=y
4+
CONFIG_SRAM_SW_ISR_TABLE=y
5+
CONFIG_CODE_DATA_RELOCATION=y
6+
CONFIG_DEVICE_MUTABLE=y
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright (c) 2025 Silicon Laboratories Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/linker/iterable_sections.h>
8+
9+
/* Only needed because the driver is defined in tests folder */
10+
ITERABLE_SECTION_ROM(fake_driver_api, Z_LINK_ITERABLE_SUBALIGN)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025 Silicon Laboratories Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/device.h>
8+
#include <zephyr/irq.h>
9+
#include "fake_driver.h"
10+
11+
#define DT_DRV_COMPAT fakedriver
12+
13+
#define TEST_IRQ_NUM 27
14+
#define TEST_IRQ_PRIO 4
15+
16+
static void fake_driver_isr(const void *arg)
17+
{
18+
const struct device *dev = (const struct device *)arg;
19+
struct fake_driver_data *data = dev->data;
20+
21+
/* Store the address of fake_driver_isr in user_data because it will be optimized by the
22+
* compiler (even with __noinline__ attribute)
23+
*/
24+
data->user_data = (void *)&fake_driver_isr;
25+
26+
if (data->irq_callback != NULL) {
27+
data->irq_callback(dev, data->user_data);
28+
}
29+
}
30+
31+
static int fake_driver_configure(const struct device *dev, int config)
32+
{
33+
return 0;
34+
}
35+
36+
static int fake_driver_register_irq_callback(const struct device *dev,
37+
fake_driver_irq_callback_t cb, void *user_data)
38+
{
39+
struct fake_driver_data *data = dev->data;
40+
41+
data->irq_callback = cb;
42+
data->user_data = user_data;
43+
44+
return 0;
45+
}
46+
47+
DEVICE_API(fake, fake_driver_func) = {
48+
.configure = fake_driver_configure,
49+
.register_irq_callback = fake_driver_register_irq_callback,
50+
};
51+
52+
static int fake_driver_init(const struct device *dev)
53+
{
54+
const struct fake_driver_config *config = dev->config;
55+
struct fake_driver_data *data = dev->data;
56+
57+
data->irq_callback = NULL;
58+
data->user_data = NULL;
59+
60+
config->irq_config_func();
61+
62+
return 0;
63+
}
64+
65+
#define FAKE_INIT(inst) \
66+
static struct fake_driver_data fake_driver_data_##inst; \
67+
static void fake_driver_irq_config_func_##inst(void) \
68+
{ \
69+
IRQ_CONNECT(TEST_IRQ_NUM, TEST_IRQ_PRIO, fake_driver_isr, \
70+
DEVICE_DT_INST_GET(inst), 0); \
71+
irq_enable(TEST_IRQ_NUM); \
72+
} \
73+
static struct fake_driver_config fake_driver_config_##inst = { \
74+
.irq_config_func = fake_driver_irq_config_func_##inst, \
75+
.irq_num = TEST_IRQ_NUM, \
76+
.irq_priority = TEST_IRQ_PRIO, \
77+
}; \
78+
DEVICE_DT_INST_DEFINE(inst, &fake_driver_init, NULL, &fake_driver_data_##inst, \
79+
&fake_driver_config_##inst, PRE_KERNEL_1, \
80+
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &fake_driver_func);
81+
82+
DT_INST_FOREACH_STATUS_OKAY(FAKE_INIT)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025 Silicon Laboratories Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @file
9+
* @brief Test for driver relocation to RAM for ISRs
10+
*
11+
* This test demonstrates how to use the fake_driver and verify
12+
* that the callback are properly relocated to RAM.
13+
*/
14+
15+
#if !defined(CONFIG_CPU_CORTEX_M)
16+
#error project can only run on Cortex-M for now
17+
#endif
18+
19+
#include <zephyr/ztest.h>
20+
#include "fake_driver.h"
21+
22+
static volatile bool test_flag;
23+
24+
static void test_irq_callback(const struct device *dev, void *user_data)
25+
{
26+
uintptr_t func_addr = (uintptr_t)test_irq_callback;
27+
uintptr_t driver_isr_addr, arch_isr_wrapper_addr;
28+
29+
test_flag = true;
30+
31+
/* Retrieve the caller address (the arch specific isr wrapper since driver isr will be
32+
* optimized by the compiler)
33+
*/
34+
__asm__ volatile("mov %0, lr" : "=r"(arch_isr_wrapper_addr));
35+
36+
/* retrieve the fake_driver_isr function address that was stored in user_data */
37+
driver_isr_addr = (uintptr_t)user_data;
38+
39+
/* Check that the function and its call stack are in RAM */
40+
zassert_true(func_addr >= CONFIG_SRAM_BASE_ADDRESS &&
41+
func_addr <= CONFIG_SRAM_BASE_ADDRESS + CONFIG_SRAM_SIZE * 1024,
42+
"%s is not in RAM! Address: 0x%x", __func__, func_addr);
43+
44+
zassert_true(driver_isr_addr >= CONFIG_SRAM_BASE_ADDRESS &&
45+
driver_isr_addr <= CONFIG_SRAM_BASE_ADDRESS + CONFIG_SRAM_SIZE * 1024,
46+
"fake_driver_isr is not in RAM! Address: 0x%x", driver_isr_addr);
47+
48+
zassert_true(arch_isr_wrapper_addr >= CONFIG_SRAM_BASE_ADDRESS &&
49+
arch_isr_wrapper_addr <=
50+
CONFIG_SRAM_BASE_ADDRESS + CONFIG_SRAM_SIZE * 1024,
51+
"arch_isr_wrapper_addr is not in RAM! Address: 0x%x", arch_isr_wrapper_addr);
52+
53+
TC_PRINT("Callback function address: 0x%lx\n", func_addr);
54+
TC_PRINT("Driver ISR address: 0x%lx\n", driver_isr_addr);
55+
TC_PRINT("Arch ISR wrapper address: 0x%lx\n", arch_isr_wrapper_addr);
56+
}
57+
58+
ZTEST(ram_context_for_isr, test_fake_driver_in_ram)
59+
{
60+
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(fakedriver));
61+
const struct fake_driver_api *api = DEVICE_API_GET(fake, dev);
62+
uintptr_t dev_addr = (uintptr_t)dev;
63+
64+
zassert_true(dev_addr >= CONFIG_SRAM_BASE_ADDRESS &&
65+
dev_addr <= CONFIG_SRAM_BASE_ADDRESS + CONFIG_SRAM_SIZE * 1024,
66+
"fake driver device is not in RAM! Address: 0x%x", dev_addr);
67+
68+
TC_PRINT("Fake driver device address: 0x%lx\n", dev_addr);
69+
70+
zassert_not_null(api, "Failed to get fake driver API");
71+
72+
api->register_irq_callback(dev, test_irq_callback, NULL);
73+
test_flag = false;
74+
75+
NVIC_SetPendingIRQ(27);
76+
77+
k_busy_wait(1000);
78+
79+
zassert_true(test_flag, "ISR callback was not called");
80+
}
81+
82+
ZTEST_SUITE(ram_context_for_isr, NULL, NULL, NULL, NULL, NULL);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
common:
2+
tags: linker
3+
tests:
4+
application_development.ram_context_for_isr.arm:
5+
arch_allow: arm
6+
filter: CONFIG_CPU_CORTEX_M_HAS_VTOR
7+
extra_configs:
8+
- CONFIG_CODE_DATA_RELOCATION_SRAM=y
9+
# In case you want to add this config, place all the possible called code in RAM
10+
- CONFIG_TRACING_ISR=n
11+
- CONFIG_STACK_SENTINEL=n
12+
- CONFIG_PM=n
13+
# Exclude mps3/corstone310 because it uses another mechanism to support relocation
14+
# of the vector table (CONFIG_ROMSTART_RELOCATION_ROM).
15+
platform_exclude:
16+
- mps3/corstone310/an555
17+
- mps3/corstone310/fvp
18+
- mps4/corstone315/fvp
19+
- mps4/corstone320/fvp

0 commit comments

Comments
 (0)