[kernel] Unittests of kernel stack depth handling
Add a unittest that checks that we can receive interrupts and handle
mp_sync_exec calls when thread context has used up half of the kernel
stack.
The first test is simple and probablistic - it allocates a large
on-stack buffer and waits a bit for any (potential) interrupts. If an
interrupt were to take > 1/2 the kernel stack to handle, we would see an
architecture-specific stack overrun failure.
The second test is deterministic and ensures we can run an mp_sync_exec
handler (that signals an event) while half the kernel stack is used from
thread context by a buffer.
Bug: 64808 Panic on boot on the kasan bringup builder
Change-Id: I40d356197d38e039d999fbe09615921b08ab0a88
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/461084
Reviewed-by: Adrian Danis <adanis@google.com>
Commit-Queue: Venkatesh Srinivas <venkateshs@google.com>
diff --git a/zircon/kernel/tests/BUILD.gn b/zircon/kernel/tests/BUILD.gn
index 5c1131a..043deb8 100644
--- a/zircon/kernel/tests/BUILD.gn
+++ b/zircon/kernel/tests/BUILD.gn
@@ -79,6 +79,7 @@
"fibo.cc",
"interrupt_disable_tests.cc",
"job_tests.cc",
+ "kstack_tests.cc",
"lock_dep_tests.cc",
"mem_tests.cc",
"mmu_tests.cc",
diff --git a/zircon/kernel/tests/kstack_tests.cc b/zircon/kernel/tests/kstack_tests.cc
new file mode 100644
index 0000000..1a5ea9c
--- /dev/null
+++ b/zircon/kernel/tests/kstack_tests.cc
@@ -0,0 +1,122 @@
+// Copyright 2020 The Fuchsia Authors
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT
+
+#include <lib/unittest/unittest.h>
+
+#include <kernel/auto_preempt_disabler.h>
+#include <kernel/event.h>
+#include <kernel/mp.h>
+#include <kernel/thread.h>
+#include <lib/arch/intrin.h>
+
+namespace {
+
+// Test that the kernel is able to handle interrupts when half the kernel stack is used.
+bool kstack_interrupt_depth_test() {
+ BEGIN_TEST;
+ constexpr size_t kSize = DEFAULT_STACK_SIZE / 2;
+ volatile uint8_t buffer[kSize] = {};
+
+ // Spin for a bit while we have a large, active buffer on the kernel stack.
+ // This gives us a window for interrupts to occur; any that do will have to make do with half
+ // the stack consumed.
+ int i = 0;
+ zx_time_t now = current_time();
+ while (current_time() < now + ZX_MSEC(100)) {
+ buffer[i++ % kSize] = buffer[0]; // Touch the buffer to ensure it is not optimized out
+ arch::Yield();
+ }
+
+ END_TEST;
+}
+
+#if __has_feature(safe_stack)
+__attribute__((no_sanitize("safe-stack")))
+bool kstack_interrupt_depth_test_no_safestack() {
+ BEGIN_TEST;
+ constexpr size_t kSize = DEFAULT_STACK_SIZE / 2;
+ volatile uint8_t buffer[kSize] = {};
+
+ // Spin for a bit while we have a large, active buffer on the kernel stack.
+ // Just like the above test, though with safe-stack disabled.
+ int i = 0;
+ zx_time_t now = current_time();
+ while (current_time() < now + ZX_MSEC(100)) {
+ buffer[i++ % kSize] = buffer[0]; // Touch the buffer to ensure it is not optimized out
+ arch::Yield();
+ }
+
+ END_TEST;
+}
+#endif
+
+static unsigned get_num_cpus_online() {
+ unsigned count = 0;
+ cpu_mask_t online = mp_get_online_mask();
+ while (online) {
+ online >>= 1;
+ ++count;
+ }
+ return count;
+}
+
+struct context {
+ cpu_num_t cpu_to_wake;
+ ktl::atomic<bool> cpu_to_wake_ok;
+ ktl::atomic<bool> wake;
+};
+static int waiter_thread(void* arg) {
+ constexpr size_t kSize = DEFAULT_STACK_SIZE / 2;
+ volatile uint8_t buffer[kSize] = {};
+
+ context* const deferred = reinterpret_cast<context*>(arg);
+ AutoPreemptDisabler<APDInitialState::PREEMPT_DISABLED> preempt_disable;
+ // Lock our thread to the current CPU.
+ Thread::Current::Get()->SetCpuAffinity(cpu_num_to_mask(arch_curr_cpu_num()));
+ deferred->cpu_to_wake = arch_curr_cpu_num();
+ deferred->cpu_to_wake_ok.store(true);
+ buffer[1] = buffer[0]; // Touch the buffer to ensure it is not optimized out
+ for (;;) {
+ // Wait for a bit while we have a large, active buffer on the kernel stack.
+ // mp_sync_exec()'s callback will run on our CPU; we want to check that it succeeds even when
+ // kSize bytes of kernel stack space are consumed by (this) thread context.
+ if (deferred->wake.load() == true)
+ break;
+ arch::Yield();
+ }
+
+ return 0;
+}
+// Test that we can handle an mp_sync_exec callback while half the kernel stack is used.
+bool kstack_mp_sync_exec_test() {
+ BEGIN_TEST;
+ // We need 2 or more CPUs for this test.
+ if (get_num_cpus_online() < 2) {
+ printf("not enough online cpus\n");
+ END_TEST;
+ }
+
+ context deferred = { };
+ Thread* const thread = Thread::CreateEtc(nullptr, "waiter", waiter_thread, &deferred,
+ DEFAULT_PRIORITY, nullptr);
+ thread->Resume();
+ while (deferred.cpu_to_wake_ok.load() == false);
+ mp_sync_exec(MP_IPI_TARGET_MASK, cpu_num_to_mask(deferred.cpu_to_wake),
+ [](void* arg) { reinterpret_cast<ktl::atomic<bool>*>(arg)->store(true); }, &deferred.wake);
+
+ thread->Join(nullptr, ZX_TIME_INFINITE);
+ END_TEST;
+}
+
+} // anonymous namespace
+
+UNITTEST_START_TESTCASE(kstack_tests)
+UNITTEST("kstack-interrupt-depth", kstack_interrupt_depth_test)
+#if __has_feature(safe_stack)
+UNITTEST("kstack-interrupt-depth-no-safestack", kstack_interrupt_depth_test_no_safestack)
+#endif
+UNITTEST("kstack-mp-sync-exec", kstack_mp_sync_exec_test)
+UNITTEST_END_TESTCASE(kstack_tests, "kstack", "kernel stack tests")