// SPDX-License-Identifier: GPL-2.0+ /* * efi_selftest_watchdog * * Copyright (c) 2017 Heinrich Schuchardt * * The 'watchdog timer' unit test checks that the watchdog timer * will not cause a system restart during the timeout period after * a timer reset. * * The 'watchdog reboot' unit test checks that the watchdog timer * actually reboots the system after a timeout. The test is only * executed on explicit request. Use the following commands: * * setenv efi_selftest watchdog reboot * bootefi selftest */ #include /* * This is the communication structure for the notification function. */ struct notify_context { /* Status code returned when resetting watchdog */ efi_status_t status; /* Number of invocations of the notification function */ unsigned int timer_ticks; }; static struct efi_event *event_notify; static struct efi_event *event_wait; static struct efi_boot_services *boottime; static struct notify_context notification_context; static bool watchdog_reset; /* * Notification function, increments the notfication count if parameter * context is provided. * * @event notified event * @context pointer to the timeout */ static void EFIAPI notify(struct efi_event *event, void *context) { struct notify_context *notify_context = context; efi_status_t ret = EFI_SUCCESS; if (!notify_context) return; /* Reset watchdog timer to one second */ ret = boottime->set_watchdog_timer(1, 0, 0, NULL); if (ret != EFI_SUCCESS) notify_context->status = ret; /* Count number of calls */ notify_context->timer_ticks++; } /* * Setup unit test. * * Create two timer events. * One with EVT_NOTIFY_SIGNAL, the other with EVT_NOTIFY_WAIT. * * @handle: handle of the loaded image * @systable: system table * @return: EFI_ST_SUCCESS for success */ static int setup(const efi_handle_t handle, const struct efi_system_table *systable) { efi_status_t ret; boottime = systable->boottime; notification_context.status = EFI_SUCCESS; notification_context.timer_ticks = 0; ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, (void *)¬ification_context, &event_notify); if (ret != EFI_SUCCESS) { efi_st_error("could not create event\n"); return EFI_ST_FAILURE; } ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_WAIT, TPL_CALLBACK, notify, NULL, &event_wait); if (ret != EFI_SUCCESS) { efi_st_error("could not create event\n"); return EFI_ST_FAILURE; } return EFI_ST_SUCCESS; } /* * Execute the test resetting the watchdog in a timely manner. No reboot occurs. * * @handle: handle of the loaded image * @systable: system table * @return: EFI_ST_SUCCESS for success */ static int setup_timer(const efi_handle_t handle, const struct efi_system_table *systable) { watchdog_reset = true; return setup(handle, systable); } /* * Execute the test without resetting the watchdog. A system reboot occurs. * * @handle: handle of the loaded image * @systable: system table * @return: EFI_ST_SUCCESS for success */ static int setup_reboot(const efi_handle_t handle, const struct efi_system_table *systable) { watchdog_reset = false; return setup(handle, systable); } /* * Tear down unit test. * * Close the events created in setup. * * @return: EFI_ST_SUCCESS for success */ static int teardown(void) { efi_status_t ret; /* Set the watchdog timer to the five minute default value */ ret = boottime->set_watchdog_timer(300, 0, 0, NULL); if (ret != EFI_SUCCESS) { efi_st_error("Setting watchdog timer failed\n"); return EFI_ST_FAILURE; } if (event_notify) { ret = boottime->close_event(event_notify); event_notify = NULL; if (ret != EFI_SUCCESS) { efi_st_error("Could not close event\n"); return EFI_ST_FAILURE; } } if (event_wait) { ret = boottime->close_event(event_wait); event_wait = NULL; if (ret != EFI_SUCCESS) { efi_st_error("Could not close event\n"); return EFI_ST_FAILURE; } } return EFI_ST_SUCCESS; } /* * Execute unit test. * * Run a 600 ms periodic timer that resets the watchdog to one second * on every timer tick. * * Run a 1350 ms single shot timer and check that the 600ms timer has * been called 2 times. * * @return: EFI_ST_SUCCESS for success */ static int execute(void) { size_t index; efi_status_t ret; /* Set the watchdog timeout to one second */ ret = boottime->set_watchdog_timer(1, 0, 0, NULL); if (ret != EFI_SUCCESS) { efi_st_error("Setting watchdog timer failed\n"); return EFI_ST_FAILURE; } if (watchdog_reset) { /* Set 600 ms timer */ ret = boottime->set_timer(event_notify, EFI_TIMER_PERIODIC, 6000000); if (ret != EFI_SUCCESS) { efi_st_error("Could not set timer\n"); return EFI_ST_FAILURE; } } /* Set 1350 ms timer */ ret = boottime->set_timer(event_wait, EFI_TIMER_RELATIVE, 13500000); if (ret != EFI_SUCCESS) { efi_st_error("Could not set timer\n"); return EFI_ST_FAILURE; } ret = boottime->wait_for_event(1, &event_wait, &index); if (ret != EFI_SUCCESS) { efi_st_error("Could not wait for event\n"); return EFI_ST_FAILURE; } if (notification_context.status != EFI_SUCCESS) { efi_st_error("Setting watchdog timer failed\n"); return EFI_ST_FAILURE; } if (notification_context.timer_ticks != 2) { efi_st_error("The timer was called %u times, expected 2.\n", notification_context.timer_ticks); return EFI_ST_FAILURE; } return EFI_ST_SUCCESS; } EFI_UNIT_TEST(watchdog1) = { .name = "watchdog timer", .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, .setup = setup_timer, .execute = execute, .teardown = teardown, }; EFI_UNIT_TEST(watchdog2) = { .name = "watchdog reboot", .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, .setup = setup_reboot, .execute = execute, .teardown = teardown, .on_request = true, };