blob: baafa9232d4a21b1fb5fc2a4364a058b95019660 [file] [log] [blame]
// Copyright 2019 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 <zircon/errors.h>
#include <platform/timer.h>
#if ARCH_X86
#include <arch/x86/apic.h>
#endif
#include <lib/unittest/unittest.h>
#include <platform.h>
#include <zircon/syscalls-next.h>
#include <kernel/idle_power_thread.h>
#include <ktl/atomic.h>
#include <object/interrupt_dispatcher.h>
#include <object/interrupt_event_dispatcher.h>
#include <object/port_dispatcher.h>
#include <ktl/enforce.h>
namespace {
// Tests that if an irq handler fires at the same time as an interrupt dispatcher is destroyed
// the system does not deadlock.
static bool TestConcurrentIntEventDispatcherTeardown() {
BEGIN_TEST;
// Generating the interrupt events for this test is necessarily arch specific and is only
// implemented for x86 here.
#if ARCH_X86
KernelHandle<InterruptDispatcher> interrupt;
zx_rights_t rights;
uint32_t gsi;
constexpr uint32_t gsi_search_max = 24;
for (gsi = 0; gsi < gsi_search_max; gsi++) {
zx_status_t status =
InterruptEventDispatcher::Create(&interrupt, &rights, gsi, ZX_INTERRUPT_MODE_DEFAULT);
if (status == ZX_OK) {
break;
}
}
ASSERT_NE(gsi, gsi_search_max, "Failed to find free global interrupt");
// Find the local vector, put it in the low byte of our shared state.
ktl::atomic<uint16_t> state = apic_io_fetch_irq_vector(gsi);
// Spin up a thread to generate the interrupt. As IPIs cannot be masked this causes the
// associated InterruptDispatcher handler to constantly get invoked, which is what we want.
Thread* int_thread = Thread::Create(
"int",
[](void* arg) -> int {
ktl::atomic<uint16_t>* state = static_cast<ktl::atomic<uint16_t>*>(arg);
uint8_t vec = state->load(ktl::memory_order_seq_cst) & 0xffu;
// Loop until anything is set in the high byte of the state.
while ((state->load(ktl::memory_order_seq_cst) & 0xff00) == 0) {
apic_send_self_ipi(vec, DELIVERY_MODE_FIXED);
Thread::Current::Yield();
}
return -1;
},
&state, DEFAULT_PRIORITY);
int_thread->Resume();
// Remove the interrupt and if we don't deadlock and keep executing then all is well.
interrupt.reset();
// Signal the thread by setting bits in the high byte.
state.fetch_or(0xff00, ktl::memory_order_seq_cst);
// Shutdown the test.
zx_status_t status = int_thread->Join(nullptr, current_time() + ZX_SEC(5));
EXPECT_EQ(status, ZX_OK);
#endif
END_TEST;
}
bool TestPendingWakeEventBlocksSuspend() {
BEGIN_TEST;
// Generating the interrupt events for this test is necessarily arch specific and is only
// implemented for x86 here.
#if ARCH_X86
KernelHandle<InterruptDispatcher> interrupt;
zx_rights_t rights;
uint32_t gsi;
constexpr uint32_t gsi_search_max = 24;
for (gsi = 0; gsi < gsi_search_max; gsi++) {
zx_status_t status =
InterruptEventDispatcher::Create(&interrupt, &rights, gsi, ZX_INTERRUPT_WAKE_VECTOR,
/*allow_ack_without_port_for_test=*/true);
if (status == ZX_OK) {
break;
}
}
ASSERT_NE(gsi, gsi_search_max, "Failed to find free global interrupt");
// Move to the boot CPU to guarantee there are no races between handling the interrupt and calling
// suspend.
auto restore_affinity = fit::defer(
[previous_affinity = Thread::Current::Get()->SetCpuAffinity(cpu_num_to_mask(BOOT_CPU_ID))] {
Thread::Current::Get()->SetCpuAffinity(previous_affinity);
});
ASSERT_EQ(arch_curr_cpu_num(), static_cast<cpu_num_t>(BOOT_CPU_ID));
ASSERT_EQ(0u, IdlePowerThread::pending_wake_events());
// Make sure suspend entry works before testing suspend abort due to pending wake events.
zx_time_t resume_at = current_time() + ZX_SEC(5);
zx_status_t status = IdlePowerThread::TransitionAllActiveToSuspend(resume_at);
ASSERT_EQ(ZX_OK, status);
// Pend a wake event by issuing the IPI.
const uint8_t vector = apic_io_fetch_irq_vector(gsi);
apic_send_self_ipi(vector, DELIVERY_MODE_FIXED);
ASSERT_EQ(1u, IdlePowerThread::pending_wake_events());
// Suspend entry should fail due to pending wake events.
resume_at = current_time() + ZX_SEC(5);
status = IdlePowerThread::TransitionAllActiveToSuspend(resume_at);
ASSERT_EQ(ZX_ERR_BAD_STATE, status);
ASSERT_EQ(1u, IdlePowerThread::pending_wake_events());
// Wait for the interrupt to advance the state machine from TRIGGERED to NEEDACK.
zx_time_t timestamp = 0;
status = interrupt.dispatcher()->WaitForInterrupt(&timestamp);
ASSERT_EQ(ZX_OK, status);
ASSERT_EQ(1u, IdlePowerThread::pending_wake_events());
// Acknowledge the wake event to allow suspend again.
status = interrupt.dispatcher()->Ack();
ASSERT_EQ(ZX_OK, status);
ASSERT_EQ(0u, IdlePowerThread::pending_wake_events());
// Suspend entry should succeed again.
resume_at = current_time() + ZX_SEC(5);
status = IdlePowerThread::TransitionAllActiveToSuspend(resume_at);
ASSERT_EQ(ZX_OK, status);
// Check that a pending wake event is acknowledged if the interrupt event dispatcher is destroyed
// without an explicit acknowledgment.
apic_send_self_ipi(vector, DELIVERY_MODE_FIXED);
ASSERT_EQ(1u, IdlePowerThread::pending_wake_events());
// Suspend entry should fail due to pending wake events.
resume_at = current_time() + ZX_SEC(5);
status = IdlePowerThread::TransitionAllActiveToSuspend(resume_at);
ASSERT_EQ(ZX_ERR_BAD_STATE, status);
interrupt.reset();
ASSERT_EQ(0u, IdlePowerThread::pending_wake_events());
// Suspend entry should succeed.
resume_at = current_time() + ZX_SEC(5);
status = IdlePowerThread::TransitionAllActiveToSuspend(resume_at);
ASSERT_EQ(ZX_OK, status);
#endif
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(interrupt_event_dispatcher_tests)
UNITTEST("ConcurrentIntEventDispatcherTeardown", TestConcurrentIntEventDispatcherTeardown)
UNITTEST("PendingWakeEventBlocksSuspend", TestPendingWakeEventBlocksSuspend)
UNITTEST_END_TESTCASE(interrupt_event_dispatcher_tests, "interrupt_event_dispatcher_tests",
"InterruptEventDispatcher tests")