Skip to content

Commit ad4a5c7

Browse files
author
Nicolas Pitre
committed
tests: arm64: Add SVE context switching validation test
Add test to validate SVE (Scalable Vector Extension) context switching implementation, ensuring proper register preservation across thread switches in multi-threaded environments. Signed-off-by: Nicolas Pitre <[email protected]>
1 parent 22b80d2 commit ad4a5c7

File tree

4 files changed

+344
-0
lines changed

4 files changed

+344
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(arm64_sve_ctx)
6+
7+
target_sources(app PRIVATE src/main.c)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_FPU=y
3+
CONFIG_FPU_SHARING=y
4+
CONFIG_ARM64_SVE=y
5+
CONFIG_ARM64_SVE_VL_MAX=256
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/*
2+
* Copyright (c) 2025 BayLibre SAS
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
#include <zephyr/kernel.h>
9+
#include <zephyr/arch/arm64/lib_helpers.h>
10+
#include <stdint.h>
11+
12+
/* Helper function for SVE vector length */
13+
static inline uint32_t sve_get_vl(void)
14+
{
15+
uint32_t vl;
16+
17+
__asm__("rdvl %0, #1" : "=r"(vl));
18+
return vl;
19+
}
20+
21+
ZTEST(arm64_sve_ctx, test_sve_basic_instructions)
22+
{
23+
/* Check if SVE is actually available */
24+
uint64_t pfr0 = read_id_aa64pfr0_el1();
25+
bool sve = is_sve_implemented();
26+
27+
TC_PRINT("=== SVE Feature Check ===\n");
28+
TC_PRINT("ID_AA64PFR0_EL1: 0x%016llx\n", pfr0);
29+
TC_PRINT("SVE support: %s\n", sve ? "YES" : "NO");
30+
zassert_true(sve, "SVE support required for this test");
31+
32+
/* Simple test: just try to read SVE vector length */
33+
TC_PRINT("About to test SVE access...\n");
34+
uint32_t vl = sve_get_vl();
35+
36+
TC_PRINT("SVE vector length: %u bytes\n", vl);
37+
zassert_not_equal(vl, 0, "SVE vector length should not be zero");
38+
39+
/* Verify vector length is within expected bounds */
40+
zassert_true(vl >= 16, "SVE vector length must be at least 16 bytes");
41+
zassert_true(vl <= CONFIG_ARM64_SVE_VL_MAX,
42+
"SVE vector length %u exceeds maximum %u", vl, CONFIG_ARM64_SVE_VL_MAX);
43+
if (vl < CONFIG_ARM64_SVE_VL_MAX) {
44+
TC_PRINT("Warning: CONFIG_ARM64_SVE_VL_MAX=%u while the hardware "
45+
"vector length is %u.\n", CONFIG_ARM64_SVE_VL_MAX, vl);
46+
TC_PRINT("Warning: This will waste memory in struct k_thread.\n");
47+
}
48+
}
49+
50+
#define STACK_SIZE 1024
51+
#define THREAD_PRIORITY 1
52+
53+
K_THREAD_STACK_DEFINE(thread1_stack, STACK_SIZE);
54+
K_THREAD_STACK_DEFINE(thread2_stack, STACK_SIZE);
55+
56+
static struct k_thread thread1_data;
57+
static struct k_thread thread2_data;
58+
59+
/* Synchronization */
60+
static struct k_sem sync_sem;
61+
static struct k_sem done_sem;
62+
63+
/* Test data for validation */
64+
static volatile bool thread1_sve_ok;
65+
static volatile bool thread2_sve_ok;
66+
67+
/* Set unique patterns in SVE Z registers for thread identification */
68+
static inline void sve_set_thread_pattern(uint32_t thread_id)
69+
{
70+
/* Create unique 32-bit pattern based on thread ID */
71+
uint32_t pattern = 0x12340000 | (thread_id & 0xFFF);
72+
73+
/* Use SVE DUP instruction to fill Z registers with pattern */
74+
__asm__ volatile (
75+
"mov w0, %w0\n"
76+
"sve_pattern_loop_%=:\n"
77+
"dup z0.s, w0\n"
78+
"add w0, w0, #0x1000\n"
79+
"dup z1.s, w0\n"
80+
"add w0, w0, #0x1000\n"
81+
"dup z2.s, w0\n"
82+
"add w0, w0, #0x1000\n"
83+
"dup z3.s, w0\n"
84+
"add w0, w0, #0x1000\n"
85+
"dup z4.s, w0\n"
86+
"add w0, w0, #0x1000\n"
87+
"dup z5.s, w0\n"
88+
"add w0, w0, #0x1000\n"
89+
"dup z6.s, w0\n"
90+
"add w0, w0, #0x1000\n"
91+
"dup z7.s, w0\n"
92+
:
93+
: "r" (pattern)
94+
: "w0", "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "memory"
95+
);
96+
}
97+
98+
/* Set patterns in SVE P (predicate) registers */
99+
static inline void sve_set_predicate_pattern(uint32_t thread_id)
100+
{
101+
/* Set alternating patterns in predicate registers */
102+
if (thread_id & 1) {
103+
__asm__ volatile (
104+
"ptrue p0.b\n"
105+
"pfalse p1.b\n"
106+
"ptrue p2.s\n"
107+
"pfalse p3.b\n"
108+
::: "p0", "p1", "p2", "p3", "memory"
109+
);
110+
} else {
111+
__asm__ volatile (
112+
"pfalse p0.b\n"
113+
"ptrue p1.h\n"
114+
"pfalse p2.b\n"
115+
"ptrue p3.d\n"
116+
::: "p0", "p1", "p2", "p3", "memory"
117+
);
118+
}
119+
}
120+
121+
/* Verify SVE Z register patterns */
122+
static inline bool sve_verify_z_pattern(uint32_t thread_id)
123+
{
124+
/* because of "static" this can be used by only one thread at a time */
125+
static uint32_t actual_buffer[8 * 256/4] __aligned(8);
126+
uint32_t *actual_p = actual_buffer;
127+
uint32_t vl = sve_get_vl();
128+
uint32_t expected_base = 0x12340000 | (thread_id & 0xFFF);
129+
bool result = true;
130+
131+
/* Store elements from Z registers to memory, then read back */
132+
__asm__ volatile (
133+
"str z0, [%0, #0, MUL VL]\n"
134+
"str z1, [%0, #1, MUL VL]\n"
135+
"str z2, [%0, #2, MUL VL]\n"
136+
"str z3, [%0, #3, MUL VL]\n"
137+
"str z4, [%0, #4, MUL VL]\n"
138+
"str z5, [%0, #5, MUL VL]\n"
139+
"str z6, [%0, #6, MUL VL]\n"
140+
"str z7, [%0, #7, MUL VL]\n"
141+
:
142+
: "r" (actual_buffer)
143+
: "memory"
144+
);
145+
146+
/* Verify each register has expected sequential pattern */
147+
for (int i = 0; i < 8; i++) {
148+
for (int j = 0; j < vl/4; j++) {
149+
uint32_t expected = expected_base + (i * 0x1000);
150+
uint32_t actual = *actual_p++;
151+
152+
if (actual != expected) {
153+
TC_PRINT("Thread %u: Z%d mismatch - "
154+
"expected 0x%x, got 0x%x\n",
155+
thread_id, i, expected, actual);
156+
result = false;
157+
}
158+
}
159+
}
160+
161+
return result;
162+
}
163+
164+
/* Verify SVE P register patterns */
165+
static inline bool sve_verify_p_pattern(uint32_t thread_id)
166+
{
167+
uint8_t p_buffer[4 * 256/8];
168+
uint8_t *p_p = p_buffer;
169+
uint32_t vl = sve_get_vl();
170+
bool result = true;
171+
172+
/* Store predicate registers to memory and read them back */
173+
__asm__ volatile (
174+
"str p0, [%0, #0, MUL VL]\n"
175+
"str p1, [%0, #1, MUL VL]\n"
176+
"str p2, [%0, #2, MUL VL]\n"
177+
"str p3, [%0, #3, MUL VL]\n"
178+
:
179+
: "r" (p_buffer)
180+
: "memory"
181+
);
182+
183+
/* Check expected patterns based on thread ID */
184+
for (int i = 0; i < 4; i++) {
185+
for (int j = 0; j < vl/8; j++) {
186+
/* Thread 1: p0=true, p1=false, p2=true, p3=false */
187+
/* Thread 2: p0=false, p1=true, p2=false, p3=true */
188+
/* p0 = b, p1 = h, p2 = s, p3 = d */
189+
static const uint8_t patterns[4] = { 0xff, 0x55, 0x11, 0x01 };
190+
uint8_t expected = ((thread_id ^ i) & 1) ? patterns[i] : 0;
191+
uint8_t actual = *p_p++;
192+
193+
if (actual != expected) {
194+
TC_PRINT("Thread %u: P%d mismatch - "
195+
"expected 0x%x, got 0x%x\n",
196+
thread_id, i, expected, actual);
197+
result = false;
198+
}
199+
}
200+
}
201+
202+
return result;
203+
}
204+
205+
/*
206+
* Test thread functions
207+
*/
208+
static void sve_test_thread1(void *arg1, void *arg2, void *arg3)
209+
{
210+
const uint32_t thread_id = 1;
211+
212+
TC_PRINT("Thread 1: Starting SVE context test\n");
213+
214+
/* Set initial SVE patterns */
215+
sve_set_thread_pattern(thread_id);
216+
sve_set_predicate_pattern(thread_id);
217+
218+
/* Immediate validation after setting patterns - NO function calls in between */
219+
zassert_true(sve_verify_z_pattern(thread_id),
220+
"Thread 1: Initial Z pattern validation failed");
221+
zassert_true(sve_verify_p_pattern(thread_id),
222+
"Thread 1: Initial P pattern validation failed");
223+
224+
TC_PRINT("Thread 1: Set initial SVE patterns\n");
225+
226+
/* Signal that we're ready and wait for other thread */
227+
k_sem_give(&sync_sem);
228+
k_msleep(1);
229+
k_sem_take(&sync_sem, K_FOREVER);
230+
231+
/* Verify our patterns are still intact */
232+
bool z_ok = sve_verify_z_pattern(thread_id);
233+
bool p_ok = sve_verify_p_pattern(thread_id);
234+
235+
thread1_sve_ok = z_ok && p_ok;
236+
237+
TC_PRINT("Thread 1: SVE verification %s (Z:%s P:%s)\n",
238+
thread1_sve_ok ? "PASSED" : "FAILED",
239+
z_ok ? "OK" : "FAIL", p_ok ? "OK" : "FAIL");
240+
241+
k_sem_give(&sync_sem);
242+
}
243+
244+
static void sve_test_thread2(void *arg1, void *arg2, void *arg3)
245+
{
246+
const uint32_t thread_id = 2;
247+
248+
TC_PRINT("Thread 2: Starting SVE context test\n");
249+
250+
/* Wait for thread 1 to be ready */
251+
k_sem_take(&sync_sem, K_FOREVER);
252+
253+
/* Set our own SVE patterns */
254+
sve_set_thread_pattern(thread_id);
255+
sve_set_predicate_pattern(thread_id);
256+
257+
/* Immediate validation after setting patterns - NO function calls in between */
258+
zassert_true(sve_verify_z_pattern(thread_id),
259+
"Thread 2: Initial Z pattern validation failed");
260+
zassert_true(sve_verify_p_pattern(thread_id),
261+
"Thread 2: Initial P pattern validation failed");
262+
263+
TC_PRINT("Thread 2: Set initial SVE patterns\n");
264+
265+
/* Signal thread 1 to continue */
266+
k_sem_give(&sync_sem);
267+
k_msleep(1);
268+
k_sem_take(&sync_sem, K_FOREVER);
269+
270+
/* Verify our patterns are still intact */
271+
bool z_ok = sve_verify_z_pattern(thread_id);
272+
bool p_ok = sve_verify_p_pattern(thread_id);
273+
274+
thread2_sve_ok = z_ok && p_ok;
275+
276+
TC_PRINT("Thread 2: SVE verification %s (Z:%s P:%s)\n",
277+
thread2_sve_ok ? "PASSED" : "FAILED",
278+
z_ok ? "OK" : "FAIL", p_ok ? "OK" : "FAIL");
279+
280+
k_sem_give(&done_sem);
281+
}
282+
283+
/*
284+
* Test suite setup and tests
285+
*/
286+
static void *sve_ctx_setup(void)
287+
{
288+
k_sem_init(&sync_sem, 0, 1);
289+
k_sem_init(&done_sem, 0, 1);
290+
return NULL;
291+
}
292+
293+
ZTEST(arm64_sve_ctx, test_sve_context_switching)
294+
{
295+
/* Reset test results */
296+
thread1_sve_ok = false;
297+
thread2_sve_ok = false;
298+
299+
/* Create threads that will use SVE */
300+
k_thread_create(&thread1_data, thread1_stack, STACK_SIZE,
301+
sve_test_thread1, NULL, NULL, NULL,
302+
THREAD_PRIORITY, 0, K_NO_WAIT);
303+
k_thread_name_set(&thread1_data, "sve_thread1");
304+
305+
k_thread_create(&thread2_data, thread2_stack, STACK_SIZE,
306+
sve_test_thread2, NULL, NULL, NULL,
307+
THREAD_PRIORITY, 0, K_NO_WAIT);
308+
k_thread_name_set(&thread2_data, "sve_thread2");
309+
310+
/* Wait for both threads to complete */
311+
k_sem_take(&done_sem, K_FOREVER);
312+
313+
/* Clean up */
314+
k_thread_join(&thread1_data, K_FOREVER);
315+
k_thread_join(&thread2_data, K_FOREVER);
316+
317+
/* Verify both threads maintained their SVE context */
318+
zassert_true(thread1_sve_ok, "Thread 1 SVE context was corrupted");
319+
zassert_true(thread2_sve_ok, "Thread 2 SVE context was corrupted");
320+
}
321+
322+
ZTEST_SUITE(arm64_sve_ctx, NULL, sve_ctx_setup, NULL, NULL, NULL);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests:
2+
arch.arm64.sve_ctx:
3+
platform_allow:
4+
- fvp_base_revc_2xaemv9a
5+
arch_allow: arm64
6+
filter: CONFIG_ARM64_SVE
7+
tags:
8+
- sve
9+
- arm64
10+
- context_switch

0 commit comments

Comments
 (0)