1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/hwinfo.h>
8 #include <zephyr/drivers/watchdog.h>
9 #include <zephyr/sys/reboot.h>
10 #include <zephyr/cache.h>
11 #include <errno.h>
12 
13 #include <zephyr/logging/log.h>
14 #include <zephyr/logging/log_ctrl.h>
15 LOG_MODULE_REGISTER(resetreason, LOG_LEVEL_INF);
16 
17 static const struct device *const my_wdt_device = DEVICE_DT_GET(DT_ALIAS(watchdog0));
18 static struct wdt_timeout_cfg m_cfg_wdt0;
19 static int my_wdt_channel;
20 
21 #define NOINIT_SECTION ".noinit.test_wdt"
22 volatile uint32_t machine_state __attribute__((section(NOINIT_SECTION)));
23 volatile uint32_t supported __attribute__((section(NOINIT_SECTION)));
24 volatile uint32_t wdt_status __attribute__((section(NOINIT_SECTION)));
25 volatile uint32_t reboot_status __attribute__((section(NOINIT_SECTION)));
26 volatile uint32_t cpu_lockup_status __attribute__((section(NOINIT_SECTION)));
27 
28 /* Value used to indicate that the watchdog has fired. */
29 #define WDT_HAS_FIRED (0x12345678U)
30 #define REBOOT_WAS_DONE (0x87654321U)
31 #define CPU_LOCKUP_WAS_DONE (0x19283746U)
32 
33 /* Highest value in the switch statement in the main() */
34 #define LAST_STATE (3)
35 
wdt_int_cb(const struct device * wdt_dev,int channel_id)36 static void wdt_int_cb(const struct device *wdt_dev, int channel_id)
37 {
38 	ARG_UNUSED(wdt_dev);
39 	ARG_UNUSED(channel_id);
40 	wdt_status = WDT_HAS_FIRED;
41 
42 	/* Flush cache as reboot may invalidate all lines. */
43 	sys_cache_data_flush_range((void *) &wdt_status, sizeof(wdt_status));
44 }
45 
46 /* Print LOG delimiter. */
print_bar(void)47 static void print_bar(void)
48 {
49 	LOG_INF("===================================================================");
50 }
51 
print_supported_reset_cause(void)52 static void print_supported_reset_cause(void)
53 {
54 	int32_t ret;
55 
56 	/* Store supported reset causes in global variable placed at NOINIT_SECTION. */
57 	ret = hwinfo_get_supported_reset_cause((uint32_t *) &supported);
58 
59 	/* Print which reset causes are supported. */
60 	if (ret == 0) {
61 		LOG_INF("Supported reset causes are:");
62 		if (supported & RESET_PIN) {
63 			LOG_INF(" 0: RESET_PIN is supported");
64 		} else {
65 			LOG_INF(" 0: no support for RESET_PIN");
66 		}
67 		if (supported & RESET_SOFTWARE) {
68 			LOG_INF(" 1: RESET_SOFTWARE is supported");
69 		} else {
70 			LOG_INF(" 1: no support for RESET_SOFTWARE");
71 		}
72 		if (supported & RESET_BROWNOUT) {
73 			LOG_INF(" 2: RESET_BROWNOUT is supported");
74 		} else {
75 			LOG_INF(" 2: no support for RESET_BROWNOUT");
76 		}
77 		if (supported & RESET_POR) {
78 			LOG_INF(" 3: RESET_POR is supported");
79 		} else {
80 			LOG_INF(" 3: no support for RESET_POR");
81 		}
82 		if (supported & RESET_WATCHDOG) {
83 			LOG_INF(" 4: RESET_WATCHDOG is supported");
84 		} else {
85 			LOG_INF(" 4: no support for RESET_WATCHDOG");
86 		}
87 		if (supported & RESET_DEBUG) {
88 			LOG_INF(" 5: RESET_DEBUG is supported");
89 		} else {
90 			LOG_INF(" 5: no support for RESET_DEBUG");
91 		}
92 		if (supported & RESET_SECURITY) {
93 			LOG_INF(" 6: RESET_SECURITY is supported");
94 		} else {
95 			LOG_INF(" 6: no support for RESET_SECURITY");
96 		}
97 		if (supported & RESET_LOW_POWER_WAKE) {
98 			LOG_INF(" 7: RESET_LOW_POWER_WAKE is supported");
99 		} else {
100 			LOG_INF(" 7: no support for RESET_LOW_POWER_WAKE");
101 		}
102 		if (supported & RESET_CPU_LOCKUP) {
103 			LOG_INF(" 8: RESET_CPU_LOCKUP is supported");
104 		} else {
105 			LOG_INF(" 8: no support for RESET_CPU_LOCKUP");
106 		}
107 		if (supported & RESET_PARITY) {
108 			LOG_INF(" 9: RESET_PARITY is supported");
109 		} else {
110 			LOG_INF(" 9: no support for RESET_PARITY");
111 		}
112 		if (supported & RESET_PLL) {
113 			LOG_INF("10: RESET_PLL is supported");
114 		} else {
115 			LOG_INF("10: no support for RESET_PLL");
116 		}
117 		if (supported & RESET_CLOCK) {
118 			LOG_INF("11: RESET_CLOCK is supported");
119 		} else {
120 			LOG_INF("11: no support for RESET_CLOCK");
121 		}
122 		if (supported & RESET_HARDWARE) {
123 			LOG_INF("12: RESET_HARDWARE is supported");
124 		} else {
125 			LOG_INF("12: no support for RESET_HARDWARE");
126 		}
127 		if (supported & RESET_USER) {
128 			LOG_INF("13: RESET_USER is supported");
129 		} else {
130 			LOG_INF("13: no support for RESET_USER");
131 		}
132 		if (supported & RESET_TEMPERATURE) {
133 			LOG_INF("14: RESET_TEMPERATURE is supported");
134 		} else {
135 			LOG_INF("14: no support for RESET_TEMPERATURE");
136 		}
137 	} else if (ret == -ENOSYS) {
138 		LOG_INF("hwinfo_get_supported_reset_cause() is NOT supported");
139 		supported = 0;
140 	} else {
141 		LOG_ERR("hwinfo_get_supported_reset_cause() failed (ret = %d)", ret);
142 	}
143 	sys_cache_data_flush_range((void *) &supported, sizeof(supported));
144 	print_bar();
145 }
146 
147 /* Print current value of reset cause. */
print_current_reset_cause(uint32_t * cause)148 static void print_current_reset_cause(uint32_t *cause)
149 {
150 	int32_t ret;
151 
152 	ret = hwinfo_get_reset_cause(cause);
153 	if (ret == 0) {
154 		LOG_INF("Current reset cause is:");
155 		if (*cause & RESET_PIN) {
156 			LOG_INF(" 0: reset due to RESET_PIN");
157 		}
158 		if (*cause & RESET_SOFTWARE) {
159 			LOG_INF(" 1: reset due to RESET_SOFTWARE");
160 		}
161 		if (*cause & RESET_BROWNOUT) {
162 			LOG_INF(" 2: reset due to RESET_BROWNOUT");
163 		}
164 		if (*cause & RESET_POR) {
165 			LOG_INF(" 3: reset due to RESET_POR");
166 		}
167 		if (*cause & RESET_WATCHDOG) {
168 			LOG_INF(" 4: reset due to RESET_WATCHDOG");
169 		}
170 		if (*cause & RESET_DEBUG) {
171 			LOG_INF(" 5: reset due to RESET_DEBUG");
172 		}
173 		if (*cause & RESET_SECURITY) {
174 			LOG_INF(" 6: reset due to RESET_SECURITY");
175 		}
176 		if (*cause & RESET_LOW_POWER_WAKE) {
177 			LOG_INF(" 7: reset due to RESET_LOW_POWER_WAKE");
178 		}
179 		if (*cause & RESET_CPU_LOCKUP) {
180 			LOG_INF(" 8: reset due to RESET_CPU_LOCKUP");
181 		}
182 		if (*cause & RESET_PARITY) {
183 			LOG_INF(" 9: reset due to RESET_PARITY");
184 		}
185 		if (*cause & RESET_PLL) {
186 			LOG_INF("10: reset due to RESET_PLL");
187 		}
188 		if (*cause & RESET_CLOCK) {
189 			LOG_INF("11: reset due to RESET_CLOCK");
190 		}
191 		if (*cause & RESET_HARDWARE) {
192 			LOG_INF("12: reset due to RESET_HARDWARE");
193 		}
194 		if (*cause & RESET_USER) {
195 			LOG_INF("13: reset due to RESET_USER");
196 		}
197 		if (*cause & RESET_TEMPERATURE) {
198 			LOG_INF("14: reset due to RESET_TEMPERATURE");
199 		}
200 	} else if (ret == -ENOSYS) {
201 		LOG_INF("hwinfo_get_reset_cause() is NOT supported");
202 		*cause = 0;
203 	} else {
204 		LOG_ERR("hwinfo_get_reset_cause() failed (ret = %d)", ret);
205 	}
206 	print_bar();
207 }
208 
209 /* Clear reset cause. */
test_clear_reset_cause(void)210 static void test_clear_reset_cause(void)
211 {
212 	int32_t ret, temp;
213 
214 	ret = hwinfo_clear_reset_cause();
215 	if (ret == 0) {
216 		LOG_INF("hwinfo_clear_reset_cause() was executed");
217 	} else if (ret == -ENOSYS) {
218 		LOG_INF("hwinfo_get_reset_cause() is NOT supported");
219 	} else {
220 		LOG_ERR("hwinfo_get_reset_cause() failed (ret = %d)", ret);
221 	}
222 	print_bar();
223 
224 	/* Print current value of reset causes -> expected 0 */
225 	print_current_reset_cause(&temp);
226 	LOG_INF("TEST that all reset causes were cleared");
227 	if (temp == 0) {
228 		LOG_INF("PASS: reset causes were cleared");
229 	} else {
230 		LOG_ERR("FAIL: reset case = %u while expected is 0", temp);
231 	}
232 	print_bar();
233 }
234 
test_reset_software(uint32_t cause)235 void test_reset_software(uint32_t cause)
236 {
237 	/* Check that reset cause from sys_reboot is detected. */
238 	if (supported & RESET_SOFTWARE) {
239 		if (reboot_status != REBOOT_WAS_DONE) {
240 			/* If software reset hasn't happen yet, do it. */
241 			reboot_status = REBOOT_WAS_DONE;
242 			LOG_INF("Test RESET_SOFTWARE");
243 
244 			/* Flush cache as reboot may invalidate all lines. */
245 			sys_cache_data_flush_range((void *) &machine_state, sizeof(machine_state));
246 			sys_cache_data_flush_range((void *) &reboot_status, sizeof(reboot_status));
247 			sys_reboot(SYS_REBOOT_COLD);
248 		} else {
249 			/* Software reset was done */
250 			LOG_INF("TEST that RESET_SOFTWARE was detected");
251 			if (cause & RESET_SOFTWARE) {
252 				LOG_INF("PASS: RESET_SOFTWARE detected");
253 				print_bar();
254 				/* Check RESET_SOFTWARE can be cleared */
255 				test_clear_reset_cause();
256 			} else {
257 				LOG_ERR("FAIL: RESET_SOFTWARE not set");
258 				print_bar();
259 			}
260 			/* Cleanup */
261 			reboot_status = 0;
262 			sys_cache_data_flush_range((void *) &reboot_status, sizeof(reboot_status));
263 		}
264 	}
265 }
266 
test_reset_watchdog(uint32_t cause)267 void test_reset_watchdog(uint32_t cause)
268 {
269 	int32_t ret;
270 
271 	/* Check that reset cause from expired watchdog is detected. */
272 	if (supported & RESET_WATCHDOG) {
273 		if (wdt_status != WDT_HAS_FIRED) {
274 			/* If watchdog hasn't fired yet, configure it do so. */
275 			uint32_t watchdog_window = 2000U;
276 
277 			if (!device_is_ready(my_wdt_device)) {
278 				LOG_ERR("WDT device %s is not ready", my_wdt_device->name);
279 				return;
280 			}
281 
282 			m_cfg_wdt0.callback = wdt_int_cb;
283 			m_cfg_wdt0.flags = WDT_FLAG_RESET_SOC;
284 			m_cfg_wdt0.window.max = watchdog_window;
285 			m_cfg_wdt0.window.min = 0U;
286 			my_wdt_channel = wdt_install_timeout(my_wdt_device, &m_cfg_wdt0);
287 			if (my_wdt_channel < 0) {
288 				LOG_ERR("wdt_install_timeout() returned %d", my_wdt_channel);
289 				return;
290 			}
291 
292 			ret = wdt_setup(my_wdt_device, WDT_OPT_PAUSE_HALTED_BY_DBG);
293 			if (ret < 0) {
294 				LOG_ERR("wdt_setup() returned %d", ret);
295 				return;
296 			}
297 
298 			/* Flush cache as reboot may invalidate all lines. */
299 			sys_cache_data_flush_range((void *) &machine_state, sizeof(machine_state));
300 			LOG_INF("Watchdog shall fire in ~%u miliseconds", watchdog_window);
301 			print_bar();
302 			k_sleep(K_FOREVER);
303 		} else {
304 			/* Watchdog has fired. */
305 			LOG_INF("TEST that RESET_WATCHDOG was detected");
306 			if (cause & RESET_WATCHDOG) {
307 				LOG_INF("PASS: RESET_WATCHDOG detected");
308 				print_bar();
309 				/* Check RESET_WATCHDOG can be cleared */
310 				test_clear_reset_cause();
311 			} else {
312 				LOG_ERR("FAIL: RESET_WATCHDOG not set");
313 				print_bar();
314 			}
315 			/* Cleanup */
316 			wdt_status = 0;
317 			sys_cache_data_flush_range((void *) &wdt_status, sizeof(wdt_status));
318 		}
319 	}
320 }
321 
k_sys_fatal_error_handler(unsigned int reason,const struct arch_esf * pEsf)322 void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *pEsf)
323 {
324 	LOG_INF("%s(%d)", __func__, reason);
325 	LOG_PANIC();
326 
327 	/* Assert inside Assert handler - shall result in reset due to cpu lockup. */
328 	__ASSERT(0, "Intentionally failed assert inside kernel panic");
329 }
330 
test_reset_cpu_lockup(uint32_t cause)331 void test_reset_cpu_lockup(uint32_t cause)
332 {
333 	/* Check that reset cause from cpu lockup is detected. */
334 	if (supported & RESET_CPU_LOCKUP) {
335 		if (cpu_lockup_status != CPU_LOCKUP_WAS_DONE) {
336 			/* If reset due to cpu lockup hasn't happen yet, do it. */
337 			cpu_lockup_status = CPU_LOCKUP_WAS_DONE;
338 			LOG_INF("Test RESET_CPU_LOCKUP");
339 
340 			/* Flush cache as reboot may invalidate all lines. */
341 			sys_cache_data_flush_range((void *) &machine_state, sizeof(machine_state));
342 			sys_cache_data_flush_range((void *) &cpu_lockup_status,
343 				sizeof(cpu_lockup_status));
344 			__ASSERT(0, "Intentionally failed assertion");
345 		} else {
346 			/* Reset due to CPU Lockup was done */
347 			LOG_INF("TEST that RESET_CPU_LOCKUP was detected");
348 			if (cause & RESET_CPU_LOCKUP) {
349 				LOG_INF("PASS: RESET_CPU_LOCKUP detected");
350 				print_bar();
351 				/* Check RESET_SOFTWARE can be cleared */
352 				test_clear_reset_cause();
353 			} else {
354 				LOG_ERR("FAIL: RESET_CPU_LOCKUP not set");
355 				print_bar();
356 			}
357 			/* Cleanup */
358 			cpu_lockup_status = 0;
359 			sys_cache_data_flush_range((void *) &cpu_lockup_status,
360 				sizeof(cpu_lockup_status));
361 		}
362 	}
363 }
364 
main(void)365 int main(void)
366 {
367 	uint32_t cause;
368 
369 	LOG_INF("HW Info reset reason test on %s", CONFIG_BOARD_TARGET);
370 	if (wdt_status == WDT_HAS_FIRED) {
371 		LOG_INF("This boot is due to expected watchdog reset");
372 	}
373 	if (reboot_status == REBOOT_WAS_DONE) {
374 		LOG_INF("This boot is due to expected software reset");
375 	}
376 	if (cpu_lockup_status == CPU_LOCKUP_WAS_DONE) {
377 		LOG_INF("This boot is due to expected cpu lockup reset");
378 	}
379 	print_bar();
380 
381 	/* Test relies on REST_PIN to correctly start. */
382 	print_current_reset_cause(&cause);
383 	if (cause & RESET_PIN) {
384 		LOG_INF("TEST that RESET_PIN was detected");
385 		LOG_INF("PASS: RESET_PIN detected");
386 		print_bar();
387 		/* Check RESET_PIN can be cleared */
388 		test_clear_reset_cause();
389 		machine_state = 0;
390 		reboot_status = 0;
391 		wdt_status = 0;
392 		cpu_lockup_status = 0;
393 	}
394 
395 	while (machine_state <= LAST_STATE) {
396 		LOG_DBG("machine_state = %u", machine_state);
397 		LOG_DBG("reboot_status = %u", reboot_status);
398 		LOG_DBG("wdt_status = %u", wdt_status);
399 		LOG_DBG("cpu_lockup_status = %u", cpu_lockup_status);
400 
401 		switch (machine_state) {
402 		case 0: /* Print (an store) which reset causes are supported. */
403 			print_supported_reset_cause();
404 			machine_state++;
405 			break;
406 		case 1: /* Test RESET_SOFTWARE. */
407 			test_reset_software(cause);
408 			machine_state++;
409 		case 2: /* Test RESET_WATCHDOG. */
410 			test_reset_watchdog(cause);
411 			machine_state++;
412 		case 3: /* Test CPU_LOCKUP. */
413 			test_reset_cpu_lockup(cause);
414 			machine_state++;
415 		}
416 	}
417 
418 	LOG_INF("All tests done");
419 	return 0;
420 }
421