1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * efi_selftest_watchdog
4  *
5  * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6  *
7  * The 'watchdog timer' unit test checks that the watchdog timer
8  * will not cause a system restart during the timeout period after
9  * a timer reset.
10  *
11  * The 'watchdog reboot' unit test checks that the watchdog timer
12  * actually reboots the system after a timeout. The test is only
13  * executed on explicit request. Use the following commands:
14  *
15  *	setenv efi_selftest watchdog reboot
16  *	bootefi selftest
17  */
18 
19 #include <efi_selftest.h>
20 
21 /*
22  * This is the communication structure for the notification function.
23  */
24 struct notify_context {
25 	/* Status code returned when resetting watchdog */
26 	efi_status_t status;
27 	/* Number of invocations of the notification function */
28 	unsigned int timer_ticks;
29 };
30 
31 static struct efi_event *efi_st_event_notify;
32 static struct efi_event *efi_st_event_wait;
33 static struct efi_boot_services *boottime;
34 static struct notify_context notification_context;
35 static bool watchdog_reset;
36 
37 /*
38  * Notification function, increments the notification count if parameter
39  * context is provided.
40  *
41  * @event	notified event
42  * @context	pointer to the timeout
43  */
notify(struct efi_event * event,void * context)44 static void EFIAPI notify(struct efi_event *event, void *context)
45 {
46 	struct notify_context *notify_context = context;
47 	efi_status_t ret = EFI_SUCCESS;
48 
49 	if (!notify_context)
50 		return;
51 
52 	/* Reset watchdog timer to one second */
53 	ret = boottime->set_watchdog_timer(1, 0, 0, NULL);
54 	if (ret != EFI_SUCCESS)
55 		notify_context->status = ret;
56 	/* Count number of calls */
57 	notify_context->timer_ticks++;
58 }
59 
60 /*
61  * Setup unit test.
62  *
63  * Create two timer events.
64  * One with EVT_NOTIFY_SIGNAL, the other with EVT_NOTIFY_WAIT.
65  *
66  * @handle:	handle of the loaded image
67  * @systable:	system table
68  * Return:	EFI_ST_SUCCESS for success
69  */
setup(const efi_handle_t handle,const struct efi_system_table * systable)70 static int setup(const efi_handle_t handle,
71 		 const struct efi_system_table *systable)
72 {
73 	efi_status_t ret;
74 
75 	boottime = systable->boottime;
76 
77 	notification_context.status = EFI_SUCCESS;
78 	notification_context.timer_ticks = 0;
79 	ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL,
80 				     TPL_CALLBACK, notify,
81 				     (void *)&notification_context,
82 				     &efi_st_event_notify);
83 	if (ret != EFI_SUCCESS) {
84 		efi_st_error("could not create event\n");
85 		return EFI_ST_FAILURE;
86 	}
87 	ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_WAIT,
88 				     TPL_CALLBACK, notify, NULL,
89 				     &efi_st_event_wait);
90 	if (ret != EFI_SUCCESS) {
91 		efi_st_error("could not create event\n");
92 		return EFI_ST_FAILURE;
93 	}
94 	return EFI_ST_SUCCESS;
95 }
96 
97 /*
98  * Execute the test resetting the watchdog in a timely manner. No reboot occurs.
99  *
100  * @handle:	handle of the loaded image
101  * @systable:	system table
102  * Return:	EFI_ST_SUCCESS for success
103  */
setup_timer(const efi_handle_t handle,const struct efi_system_table * systable)104 static int setup_timer(const efi_handle_t handle,
105 		       const struct efi_system_table *systable)
106 {
107 	watchdog_reset = true;
108 	return setup(handle, systable);
109 }
110 
111 /*
112  * Execute the test without resetting the watchdog. A system reboot occurs.
113  *
114  * @handle:	handle of the loaded image
115  * @systable:	system table
116  * Return:	EFI_ST_SUCCESS for success
117  */
setup_reboot(const efi_handle_t handle,const struct efi_system_table * systable)118 static int setup_reboot(const efi_handle_t handle,
119 			const struct efi_system_table *systable)
120 {
121 	watchdog_reset = false;
122 	return setup(handle, systable);
123 }
124 
125 /*
126  * Tear down unit test.
127  *
128  * Close the events created in setup.
129  *
130  * Return:	EFI_ST_SUCCESS for success
131  */
teardown(void)132 static int teardown(void)
133 {
134 	efi_status_t ret;
135 
136 	/* Set the watchdog timer to the five minute default value */
137 	ret = boottime->set_watchdog_timer(300, 0, 0, NULL);
138 	if (ret != EFI_SUCCESS) {
139 		efi_st_error("Setting watchdog timer failed\n");
140 		return EFI_ST_FAILURE;
141 	}
142 	if (efi_st_event_notify) {
143 		ret = boottime->close_event(efi_st_event_notify);
144 		efi_st_event_notify = NULL;
145 		if (ret != EFI_SUCCESS) {
146 			efi_st_error("Could not close event\n");
147 			return EFI_ST_FAILURE;
148 		}
149 	}
150 	if (efi_st_event_wait) {
151 		ret = boottime->close_event(efi_st_event_wait);
152 		efi_st_event_wait = NULL;
153 		if (ret != EFI_SUCCESS) {
154 			efi_st_error("Could not close event\n");
155 			return EFI_ST_FAILURE;
156 		}
157 	}
158 	return EFI_ST_SUCCESS;
159 }
160 
161 /*
162  * Execute unit test.
163  *
164  * Run a 600 ms periodic timer that resets the watchdog to one second
165  * on every timer tick.
166  *
167  * Run a 1350 ms single shot timer and check that the 600ms timer has
168  * been called 2 times.
169  *
170  * Return:	EFI_ST_SUCCESS for success
171  */
execute(void)172 static int execute(void)
173 {
174 	size_t index;
175 	efi_status_t ret;
176 
177 	/* Set the watchdog timeout to one second */
178 	ret = boottime->set_watchdog_timer(1, 0, 0, NULL);
179 	if (ret != EFI_SUCCESS) {
180 		efi_st_error("Setting watchdog timer failed\n");
181 		return EFI_ST_FAILURE;
182 	}
183 	if (watchdog_reset) {
184 		/* Set 600 ms timer */
185 		ret = boottime->set_timer(efi_st_event_notify,
186 					  EFI_TIMER_PERIODIC, 6000000);
187 		if (ret != EFI_SUCCESS) {
188 			efi_st_error("Could not set timer\n");
189 			return EFI_ST_FAILURE;
190 		}
191 	}
192 	/* Set 1350 ms timer */
193 	ret = boottime->set_timer(efi_st_event_wait, EFI_TIMER_RELATIVE,
194 				  13500000);
195 	if (ret != EFI_SUCCESS) {
196 		efi_st_error("Could not set timer\n");
197 		return EFI_ST_FAILURE;
198 	}
199 
200 	ret = boottime->wait_for_event(1, &efi_st_event_wait, &index);
201 	if (ret != EFI_SUCCESS) {
202 		efi_st_error("Could not wait for event\n");
203 		return EFI_ST_FAILURE;
204 	}
205 	if (notification_context.status != EFI_SUCCESS) {
206 		efi_st_error("Setting watchdog timer failed\n");
207 		return EFI_ST_FAILURE;
208 	}
209 	if (notification_context.timer_ticks != 2) {
210 		efi_st_error("The timer was called %u times, expected 2.\n",
211 			     notification_context.timer_ticks);
212 		return EFI_ST_FAILURE;
213 	}
214 	return EFI_ST_SUCCESS;
215 }
216 
217 EFI_UNIT_TEST(watchdog1) = {
218 	.name = "watchdog timer",
219 	.phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
220 	.setup = setup_timer,
221 	.execute = execute,
222 	.teardown = teardown,
223 };
224 
225 EFI_UNIT_TEST(watchdog2) = {
226 	.name = "watchdog reboot",
227 	.phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
228 	.setup = setup_reboot,
229 	.execute = execute,
230 	.teardown = teardown,
231 	.on_request = true,
232 };
233