1 /*
2  * Copyright (c) 2022, Synopsys.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * @file
9  * @brief load/store portion of DSP sharing test
10  *
11  * @ingroup all_tests
12  *
13  * This module implements the load/store portion of the  DSP sharing test. This
14  * version of this test utilizes a pair of tasks.
15  *
16  * The load/store test validates the dsp unit context
17  * save/restore mechanism. This test utilizes a pair of threads of different
18  * priorities that each use the dsp registers. The context
19  * switching that occurs exercises the kernel's ability to properly preserve
20  * the dsp registers. The test also exercises the kernel's ability
21  * to automatically enable dsp support for a task, if supported.
22  *
23  */
24 
25 #include <zephyr/ztest.h>
26 #include <zephyr/debug/gcov.h>
27 #include "dsp_regs_arc.h"
28 #include "dsp_context.h"
29 #include "test_common.h"
30 
31 /* space for dsp register load/store area used by low priority task */
32 static struct dsp_register_set dsp_reg_set_load;
33 static struct dsp_register_set dsp_reg_set_store;
34 
35 /* space for dsp register load/store area used by high priority thread */
36 static struct dsp_register_set dsp_reg_set;
37 
38 static volatile unsigned int load_store_low_count;
39 static volatile unsigned int load_store_high_count;
40 
41 /* Indicates that the load/store test exited */
42 static volatile bool test_exited;
43 
44 /* Semaphore for signaling end of test */
45 static K_SEM_DEFINE(test_exit_sem, 0, 1);
46 
47 /**
48  * @brief Low priority DSP load/store thread
49  *
50  * @ingroup kernel_dspsharing_tests
51  *
52  * @see k_sched_time_slice_set(), memset(),
53  * _load_all_dsp_registers(), _store_all_dsp_registers()
54  */
load_store_low(void)55 static void load_store_low(void)
56 {
57 	volatile unsigned int i;
58 	bool error = false;
59 	unsigned char init_byte;
60 	unsigned char *store_ptr = (unsigned char *)&dsp_reg_set_store;
61 	unsigned char *load_ptr = (unsigned char *)&dsp_reg_set_load;
62 	/*
63 	 * Initialize dsp load buffer to known values;
64 	 * these values must be different than the value used in other threads.
65 	 */
66 	init_byte = MAIN_DSP_REG_CHECK_BYTE;
67 	for (i = 0; i < SIZEOF_DSP_REGISTER_SET; i++) {
68 		load_ptr[i] = init_byte++;
69 	}
70 
71 	/* Loop until the test finishes, or an error is detected. */
72 	for (load_store_low_count = 0; !test_exited; load_store_low_count++) {
73 
74 		/*
75 		 * Clear store buffer to erase all traces of any previous
76 		 * dsp values that have been saved.
77 		 */
78 		(void)memset(&dsp_reg_set_store, 0, SIZEOF_DSP_REGISTER_SET);
79 
80 		/*
81 		 * Utilize an architecture specific function to load all the
82 		 * dsp registers with known values.
83 		 */
84 		_load_all_dsp_registers(&dsp_reg_set_load);
85 
86 		/*
87 		 * Waste some cycles to give the high priority load/store
88 		 * thread an opportunity to run when the low priority thread is
89 		 * using the dsp registers.
90 		 *
91 		 * IMPORTANT: This logic requires that sys_clock_tick_get_32() not
92 		 * perform any dsp operations!
93 		 */
94 		k_busy_wait(100);
95 
96 		/*
97 		 * Utilize an architecture specific function to dump the
98 		 * contents of all dsp registers to memory.
99 		 */
100 		_store_all_dsp_registers(&dsp_reg_set_store);
101 		/*
102 		 * Compare each byte of buffer to ensure the expected value is
103 		 * present, indicating that the dsp registers weren't
104 		 * impacted by the operation of the high priority thread(s).
105 		 *
106 		 * Display error message and terminate if discrepancies are
107 		 * detected.
108 		 */
109 		init_byte = MAIN_DSP_REG_CHECK_BYTE;
110 
111 		for (i = 0; i < SIZEOF_DSP_REGISTER_SET; i++) {
112 			if (store_ptr[i] != init_byte) {
113 				TC_ERROR("Found 0x%x instead of 0x%x @offset 0x%x\n",
114 					 store_ptr[i], init_byte, i);
115 				TC_ERROR("Discrepancy found during iteration %d\n",
116 					 load_store_low_count);
117 				error = true;
118 			}
119 			init_byte++;
120 		}
121 
122 		/* Terminate if a test error has been reported */
123 		zassert_false(error, NULL);
124 	}
125 }
126 
127 /**
128  * @brief High priority DSP load/store thread
129  *
130  * @ingroup kernel_dspsharing_tests
131  *
132  * @see _load_then_store_all_dsp_registers()
133  */
load_store_high(void)134 static void load_store_high(void)
135 {
136 	volatile unsigned int i;
137 	unsigned char init_byte;
138 	unsigned char *reg_set_ptr = (unsigned char *)&dsp_reg_set;
139 
140 	/* Run the test until the specified maximum test count is reached */
141 	for (load_store_high_count = 0;
142 	     load_store_high_count <= MAX_TESTS;
143 	     load_store_high_count++) {
144 
145 		/*
146 		 * Initialize the dsp_reg_set structure by treating it as
147 		 * a simple array of bytes (the arrangement and actual number
148 		 * of registers is not important for this generic C code).  The
149 		 * structure is initialized by using the byte value specified
150 		 * by the constant FIBER_DSP_REG_CHECK_BYTE, and then
151 		 * incrementing the value for each successive location in the
152 		 * dsp_reg_set structure.
153 		 *
154 		 * The initial byte value, and thus the contents of the entire
155 		 * dsp_reg_set structure, must be different for each
156 		 * thread to effectively test the kernel's ability to
157 		 * properly save/restore the dsp-processed values during a
158 		 * context switch.
159 		 */
160 		init_byte = FIBER_DSP_REG_CHECK_BYTE;
161 
162 		for (i = 0; i < SIZEOF_DSP_REGISTER_SET; i++) {
163 			reg_set_ptr[i] = init_byte++;
164 		}
165 
166 		/*
167 		 * Utilize an architecture specific function to load all the
168 		 * dsp registers with the contents of the
169 		 * dsp_reg_set structure.
170 		 *
171 		 * The goal of the loading all dsp registers with
172 		 * values that differ from the values used in other threads is
173 		 * to help determine whether the dsp register
174 		 * save/restore mechanism in the kernel's context switcher
175 		 * is operating correctly.
176 		 *
177 		 * When a subsequent k_timer_test() invocation is
178 		 * performed, a (cooperative) context switch back to the
179 		 * preempted task will occur. This context switch should result
180 		 * in restoring the state of the task's dsp
181 		 * registers when the task was swapped out due to the
182 		 * occurrence of the timer tick.
183 		 */
184 		_load_then_store_all_dsp_registers(&dsp_reg_set);
185 
186 		/*
187 		 * Relinquish the processor for the remainder of the current
188 		 * system clock tick, so that lower priority threads get a
189 		 * chance to run.
190 		 *
191 		 * This exercises the ability of the kernel to restore the
192 		 * DSP state of a low priority thread _and_ the ability of the
193 		 * kernel to provide a "clean" DSP state to this thread
194 		 * once the sleep ends.
195 		 */
196 		k_sleep(K_MSEC(1));
197 
198 		/* Periodically issue progress report */
199 		if ((load_store_high_count % 100) == 0) {
200 			PRINT_DATA("Load and store OK after %u (high) "
201 				   "+ %u (low) tests\n",
202 				   load_store_high_count,
203 				   load_store_low_count);
204 		}
205 	}
206 
207 	/* Signal end of test */
208 	test_exited = true;
209 	k_sem_give(&test_exit_sem);
210 }
211 
212 K_THREAD_DEFINE(load_low, THREAD_STACK_SIZE, load_store_low, NULL, NULL, NULL,
213 		THREAD_LOW_PRIORITY, THREAD_DSP_FLAGS, K_TICKS_FOREVER);
214 
215 K_THREAD_DEFINE(load_high, THREAD_STACK_SIZE, load_store_high, NULL, NULL, NULL,
216 		THREAD_HIGH_PRIORITY, THREAD_DSP_FLAGS, K_TICKS_FOREVER);
217 
ZTEST(dsp_sharing,test_load_store)218 ZTEST(dsp_sharing, test_load_store)
219 {
220 	/* Initialise test states */
221 	test_exited = false;
222 	k_sem_reset(&test_exit_sem);
223 
224 	/* Start test threads */
225 	k_thread_start(load_low);
226 	k_thread_start(load_high);
227 
228 	/* Wait for test threads to exit */
229 	k_sem_take(&test_exit_sem, K_FOREVER);
230 }
231