blob: b55d6c525f0b4348f0d0d99f0fc0a4030609c00d [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 <lib/unittest/unittest.h>
#include <arch/x86/fake_msr_access.h>
#include <arch/x86/pv.h>
#include <ktl/iterator.h>
#include <ktl/unique_ptr.h>
#include <vm/physmap.h>
#include "interrupt_manager.h"
namespace {
class FakeIoApic {
private:
// Make sure we can have more interrupts than CPU vectors, so we can test
// too many allocations
static constexpr unsigned int kIrqCount = X86_INT_COUNT + 1;
public:
static bool IsValidInterrupt(unsigned int vector, uint32_t flags) { return vector < kIrqCount; }
static uint8_t FetchIrqVector(unsigned int vector) {
ZX_ASSERT(vector < kIrqCount);
return FakeIoApic::entries[vector].x86_vector;
}
static void ConfigureIrqVector(uint32_t global_irq, uint8_t x86_vector) {
ZX_ASSERT(global_irq < kIrqCount);
FakeIoApic::entries[global_irq].x86_vector = x86_vector;
}
static void ConfigureIrq(uint32_t global_irq, enum interrupt_trigger_mode trig_mode,
enum interrupt_polarity polarity,
enum apic_interrupt_delivery_mode del_mode, bool mask,
enum apic_interrupt_dst_mode dst_mode, uint8_t dst, uint8_t vector) {
ZX_ASSERT(global_irq < kIrqCount);
FakeIoApic::entries[global_irq].x86_vector = vector;
FakeIoApic::entries[global_irq].trig_mode = trig_mode;
FakeIoApic::entries[global_irq].polarity = polarity;
}
static void MaskIrq(uint32_t global_irq, bool mask) { ZX_ASSERT(global_irq < kIrqCount); }
static zx_status_t FetchIrqConfig(uint32_t global_irq, enum interrupt_trigger_mode* trig_mode,
enum interrupt_polarity* polarity) {
ZX_ASSERT(global_irq < kIrqCount);
*trig_mode = FakeIoApic::entries[global_irq].trig_mode;
*polarity = FakeIoApic::entries[global_irq].polarity;
return ZX_OK;
}
// Reset the internal state
static void Reset() {
for (auto& entry : entries) {
entry = {};
}
}
struct Entry {
uint8_t x86_vector;
enum interrupt_trigger_mode trig_mode;
enum interrupt_polarity polarity;
};
static Entry entries[kIrqCount];
};
FakeIoApic::Entry FakeIoApic::entries[kIrqCount] = {};
bool TestRegisterInterruptHandler() {
BEGIN_TEST;
FakeIoApic::Reset();
fbl::AllocChecker ac;
auto im = ktl::make_unique<InterruptManager<FakeIoApic>>(&ac);
ASSERT_TRUE(ac.check());
ASSERT_EQ(im->Init(), ZX_OK);
unsigned int kIrq1 = 1;
uint8_t handler1_arg = 0;
uintptr_t handler1 = 2;
// Register a handler for the interrupt
ASSERT_EQ(
im->RegisterInterruptHandler(kIrq1, reinterpret_cast<int_handler>(handler1), &handler1_arg),
ZX_OK);
uint8_t irq1_x86_vector = FakeIoApic::entries[kIrq1].x86_vector;
// Make sure the entry matches
int_handler handler;
void* arg;
im->GetEntryByX86Vector(irq1_x86_vector, &handler, &arg);
EXPECT_EQ(reinterpret_cast<uintptr_t>(handler), handler1);
EXPECT_EQ(arg, &handler1_arg);
// Unregister it
ASSERT_EQ(im->RegisterInterruptHandler(kIrq1, nullptr, nullptr), ZX_OK);
EXPECT_EQ(FakeIoApic::entries[kIrq1].x86_vector, 0);
// Make sure the entry matches
im->GetEntryByX86Vector(irq1_x86_vector, &handler, &arg);
EXPECT_EQ(handler, nullptr);
EXPECT_EQ(arg, nullptr);
END_TEST;
}
bool TestRegisterInterruptHandlerTwice() {
BEGIN_TEST;
FakeIoApic::Reset();
fbl::AllocChecker ac;
auto im = ktl::make_unique<InterruptManager<FakeIoApic>>(&ac);
ASSERT_TRUE(ac.check());
ASSERT_EQ(im->Init(), ZX_OK);
unsigned int kIrq = 1;
uint8_t handler1_arg = 4;
uintptr_t handler1 = 2;
uint8_t handler2_arg = 5;
uintptr_t handler2 = 3;
ASSERT_EQ(
im->RegisterInterruptHandler(kIrq, reinterpret_cast<int_handler>(handler1), &handler1_arg),
ZX_OK);
uint8_t irq_x86_vector = FakeIoApic::entries[kIrq].x86_vector;
ASSERT_EQ(
im->RegisterInterruptHandler(kIrq, reinterpret_cast<int_handler>(handler2), &handler2_arg),
ZX_ERR_ALREADY_BOUND);
ASSERT_EQ(irq_x86_vector, FakeIoApic::entries[kIrq].x86_vector);
// Make sure the entry matches the first installed
int_handler handler;
void* arg;
im->GetEntryByX86Vector(irq_x86_vector, &handler, &arg);
EXPECT_EQ(reinterpret_cast<uintptr_t>(handler), handler1);
EXPECT_EQ(arg, &handler1_arg);
// Unregister it
ASSERT_EQ(im->RegisterInterruptHandler(kIrq, nullptr, nullptr), ZX_OK);
EXPECT_EQ(FakeIoApic::entries[kIrq].x86_vector, 0);
// Make sure the entry matches
im->GetEntryByX86Vector(irq_x86_vector, &handler, &arg);
EXPECT_EQ(handler, nullptr);
EXPECT_EQ(arg, nullptr);
END_TEST;
}
bool TestUnregisterInterruptHandlerNotRegistered() {
BEGIN_TEST;
FakeIoApic::Reset();
fbl::AllocChecker ac;
auto im = ktl::make_unique<InterruptManager<FakeIoApic>>(&ac);
ASSERT_TRUE(ac.check());
ASSERT_EQ(im->Init(), ZX_OK);
unsigned int kIrq1 = 1;
// Unregister a vector we haven't yet registered. Should just be ignored.
ASSERT_EQ(im->RegisterInterruptHandler(kIrq1, nullptr, nullptr), ZX_OK);
END_TEST;
}
bool TestRegisterInterruptHandlerTooMany() {
BEGIN_TEST;
FakeIoApic::Reset();
fbl::AllocChecker ac;
auto im = ktl::make_unique<InterruptManager<FakeIoApic>>(&ac);
ASSERT_TRUE(ac.check());
ASSERT_EQ(im->Init(), ZX_OK);
uint8_t handler_arg = 0;
uintptr_t handler = 2;
static_assert(ktl::size(FakeIoApic::entries) > InterruptManager<FakeIoApic>::kNumCpuVectors);
// Register every interrupt, and store different pointers for them so we
// can validate it. All of these should succeed, but will exhaust the
// allocator.
for (unsigned i = 0; i < InterruptManager<FakeIoApic>::kNumCpuVectors; ++i) {
ASSERT_EQ(im->RegisterInterruptHandler(i, reinterpret_cast<int_handler>(handler + i),
&handler_arg + i),
ZX_OK);
}
// Make sure all of the entries are registered
for (unsigned i = 0; i < InterruptManager<FakeIoApic>::kNumCpuVectors; ++i) {
uint8_t x86_vector = FakeIoApic::entries[i].x86_vector;
int_handler installed_handler;
void* installed_arg;
im->GetEntryByX86Vector(x86_vector, &installed_handler, &installed_arg);
EXPECT_EQ(reinterpret_cast<uintptr_t>(installed_handler), handler + i);
EXPECT_EQ(installed_arg, &handler_arg + i);
}
// Try to allocate one more
ASSERT_EQ(im->RegisterInterruptHandler(InterruptManager<FakeIoApic>::kNumCpuVectors,
reinterpret_cast<int_handler>(handler), &handler_arg),
ZX_ERR_NO_RESOURCES);
// Clean up the registered handlers
for (unsigned i = 0; i < InterruptManager<FakeIoApic>::kNumCpuVectors; ++i) {
ASSERT_EQ(im->RegisterInterruptHandler(i, nullptr, nullptr), ZX_OK);
}
END_TEST;
}
} // namespace
// This test isn't in the namespace so that the InterruptManager can friend it.
bool TestHandlerAllocationAlignment() TA_NO_THREAD_SAFETY_ANALYSIS {
BEGIN_TEST;
fbl::AllocChecker ac;
auto im = ktl::make_unique<InterruptManager<FakeIoApic>>(&ac);
ASSERT_TRUE(ac.check());
ASSERT_EQ(im->Init(), ZX_OK);
uint base = 0;
// Allocation in new IM should succeed and be correctly aligned.
zx_status_t status = im->AllocHandler(32, &base);
EXPECT_EQ(status, ZX_OK);
EXPECT_EQ(base % 32, 0u);
im->FreeHandler(base, 32);
// Set a high bit such that our allocation just won't fit.
im->handler_allocated_.Set(X86_INT_PLATFORM_BASE + 31, X86_INT_PLATFORM_BASE + 31 + 1);
status = im->AllocHandler(32, &base);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(base, X86_INT_PLATFORM_BASE + 31u);
EXPECT_EQ(base % 32, 0u);
im->FreeHandler(base, 32);
im->FreeHandler(X86_INT_PLATFORM_BASE + 31, 1);
// Set a low bit ensuring that allocation happens on the next roundup up block.
im->handler_allocated_.Set(X86_INT_PLATFORM_BASE, X86_INT_PLATFORM_BASE + 1);
status = im->AllocHandler(32, &base);
EXPECT_EQ(status, ZX_OK);
EXPECT_EQ(base % 32, 0u);
im->FreeHandler(base, 32);
im->FreeHandler(X86_INT_PLATFORM_BASE, 1);
// Set two bits such that the distance between them is greater than our desired allocation
// but such that the only valid alignment requires an allocation in an even higher block.
im->handler_allocated_.Set(X86_INT_PLATFORM_BASE, X86_INT_PLATFORM_BASE + 1);
im->handler_allocated_.Set(X86_INT_PLATFORM_BASE + 34, X86_INT_PLATFORM_BASE + 34 + 1);
status = im->AllocHandler(32, &base);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(base, X86_INT_PLATFORM_BASE + 34u);
EXPECT_EQ(base % 32, 0u);
im->FreeHandler(base, 32);
im->FreeHandler(X86_INT_PLATFORM_BASE, 1);
im->FreeHandler(X86_INT_PLATFORM_BASE + 34, 1);
END_TEST;
}
bool TestPvEoi() {
BEGIN_TEST;
// Can't signal if not enabled.
{
PvEoi pv;
pv.Init();
EXPECT_FALSE(pv.Eoi());
}
// Enable, signal, then disable.
{
FakeMsrAccess msr{};
msr.msrs_[0] = {X86_MSR_KVM_PV_EOI_EN, 0U};
PvEoi pv;
pv.Init();
pv.Enable(&msr);
// Find the PvEoi's state via MSR value.
uint64_t pa = msr.msrs_[0].value;
EXPECT_TRUE(pa & X86_MSR_KVM_PV_EOI_EN_ENABLE);
pa &= ~X86_MSR_KVM_PV_EOI_EN_ENABLE;
auto state = reinterpret_cast<uint64_t*>(paddr_to_physmap(pa));
EXPECT_EQ(*state, 0U);
EXPECT_FALSE(pv.Eoi());
EXPECT_EQ(*state, 0U);
*state = 1;
EXPECT_TRUE(pv.Eoi());
EXPECT_EQ(*state, 0U);
pv.Disable(&msr);
EXPECT_EQ(msr.msrs_[0].value, 0U);
}
END_TEST;
}
UNITTEST_START_TESTCASE(pc_interrupt_tests)
UNITTEST("RegisterInterruptHandler", TestRegisterInterruptHandler)
UNITTEST("RegisterInterruptHandlerTwice", TestRegisterInterruptHandlerTwice)
UNITTEST("UnregisterInterruptHandlerNotRegistered", TestUnregisterInterruptHandlerNotRegistered)
UNITTEST("RegisterInterruptHandlerTooMany", TestRegisterInterruptHandlerTooMany)
UNITTEST("HandlerAllocationAlignment", TestHandlerAllocationAlignment)
UNITTEST("PvEoi", TestPvEoi)
UNITTEST_END_TESTCASE(pc_interrupt_tests, "pc_interrupt", "Tests for external interrupts")