blob: 3de2e39046e5a8346527b8dec56b220a2c11f474 [file] [log] [blame]
// 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 <debug.h>
#include <lib/unittest/unittest.h>
#include <platform.h>
#include <zircon/errors.h>
#include <zircon/time.h>
#include <utility>
#include <fbl/ref_ptr.h>
#include <kernel/thread.h>
#include <object/handle.h>
#include <object/msi_allocation.h>
#include <object/msi_dispatcher.h>
#include <vm/pmm.h>
#include <vm/vm_address_region.h>
#include <vm/vm_object.h>
#include <vm/vm_object_paged.h>
namespace {
bool MsiIsSupportedTrue() { return true; }
zx_status_t MsiAllocate(uint requested_irqs, bool /*unused*/, bool /*unused*/,
msi_block_t* out_block) {
out_block->allocated = true;
out_block->base_irq_id = 128;
out_block->num_irq = requested_irqs;
out_block->tgt_addr = 0x1234u;
out_block->tgt_addr = 0x4321u;
out_block->platform_ctx = nullptr;
return ZX_OK;
}
// These functions are used to verify we properly bail out if MSI isn't supported.
void MsiFree(msi_block_t* block) { block->allocated = false; }
bool MsiIsSupportedFalse() { return false; }
zx_status_t MsiAllocateAssert(uint32_t /* unused */, bool /* unused */, bool /* unused */,
msi_block_t* /* unused */) {
assert(false);
return ZX_ERR_NOT_SUPPORTED;
}
void MsiFreeAssert(msi_block_t* /* unused */) { assert(false); }
} // namespace
const uint32_t kVectorMax = 256u;
static bool allocation_creation_and_info_test() {
BEGIN_TEST;
const uint32_t test_irq_cnt = 5;
ResourceDispatcher::ResourceStorage rsrc_storage;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, 0, kVectorMax,
&rsrc_storage));
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(test_irq_cnt, &alloc, MsiAllocate, MsiFree,
MsiIsSupportedTrue, &rsrc_storage));
ASSERT_EQ(1u, rsrc_storage.resource_list.size_slow());
zx_info_msi_t info = {};
alloc->GetInfo(&info);
// Grab the lock and compare the block values and info values to both our test
// data and info data.
Guard<SpinLock, IrqSave> guard{&alloc->lock()};
ASSERT_EQ(test_irq_cnt, alloc->block().num_irq);
ASSERT_EQ(true, alloc->block().allocated);
ASSERT_EQ(info.base_irq_id, alloc->block().base_irq_id);
ASSERT_EQ(info.num_irq, alloc->block().num_irq);
ASSERT_EQ(info.target_addr, alloc->block().tgt_addr);
ASSERT_EQ(info.target_data, alloc->block().tgt_data);
END_TEST;
}
static bool allocation_irq_count_test() {
BEGIN_TEST;
ResourceDispatcher::ResourceStorage rsrc_storage;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, 0, kVectorMax,
&rsrc_storage));
// Check that the valid range of allocation sizes work.
for (uint32_t cnt = 1; cnt < MsiAllocation::kMsiAllocationCountMax; cnt++) {
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(cnt, &alloc, MsiAllocate, MsiFree, MsiIsSupportedTrue,
&rsrc_storage));
}
fbl::RefPtr<MsiAllocation> alloc;
// And check the failure cases.
ASSERT_EQ(ZX_ERR_INVALID_ARGS, MsiAllocation::Create(0, &alloc, MsiAllocate, MsiFree,
MsiIsSupportedTrue, &rsrc_storage));
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
MsiAllocation::Create(MsiAllocation::kMsiAllocationCountMax + 1, &alloc, MsiAllocate,
MsiFree, MsiIsSupportedTrue, &rsrc_storage));
END_TEST;
}
static bool allocation_reservation_test() {
BEGIN_TEST;
ResourceDispatcher::ResourceStorage rsrc_storage;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, 0, kVectorMax,
&rsrc_storage));
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(MsiAllocation::kMsiAllocationCountMax, &alloc, MsiAllocate,
MsiFree, MsiIsSupportedTrue, &rsrc_storage));
// Verify the bounds checking and state of id reservations.
ASSERT_EQ(ZX_ERR_BAD_STATE, alloc->ReleaseId(0));
ASSERT_EQ(ZX_OK, alloc->ReserveId(0));
ASSERT_EQ(ZX_ERR_ALREADY_BOUND, alloc->ReserveId(0));
ASSERT_EQ(ZX_OK, alloc->ReleaseId(0));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, alloc->ReserveId(MsiAllocation::kMsiAllocationCountMax));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, alloc->ReleaseId(MsiAllocation::kMsiAllocationCountMax));
END_TEST;
}
static bool allocation_support_test() {
BEGIN_TEST;
ResourceDispatcher::ResourceStorage rsrc_storage;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, 0, kVectorMax,
&rsrc_storage));
{
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED,
MsiAllocation::Create(1, &alloc, MsiAllocateAssert, MsiFreeAssert,
MsiIsSupportedFalse, &rsrc_storage));
ASSERT_EQ(0u, rsrc_storage.resource_list.size_slow());
}
END_TEST;
}
int interrupt_waiter(void* arg) {
auto dispatcher = reinterpret_cast<InterruptDispatcher*>(arg);
zx_time_t out;
return (dispatcher->WaitForInterrupt(&out) == ZX_OK);
}
// Use a static var for tracking calls rather than a lambda to avoid storage issues with lambda
// captures and function pointers without having to increase complexity in the dispatcher.
static uint32_t register_call_count = 0;
void register_fn(const msi_block_t*, uint, int_handler, void*) { register_call_count++; }
static bool interrupt_creation_test() {
BEGIN_TEST;
ResourceDispatcher::ResourceStorage rsrc_storage;
const uint32_t msi_cnt = 8;
uint32_t msi_id = 3;
const uint32_t reg_offset = 16;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, msi_id, kVectorMax,
&rsrc_storage));
// Create an MsiAllocation and Interrupt that will attempt to use masking at the MSI Capability
// level.
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(msi_cnt, &alloc, MsiAllocate, MsiFree, MsiIsSupportedTrue,
&rsrc_storage));
ASSERT_EQ(1u, rsrc_storage.resource_list.size_slow());
fbl::RefPtr<VmObject> vmo;
size_t vmo_size = 48u;
ASSERT_EQ(ZX_OK,
VmObjectPaged::CreateContiguous(PMM_ALLOC_FLAG_ANY, vmo_size, 0 /* options */, &vmo));
ASSERT_EQ(ZX_OK, vmo->SetMappingCachePolicy(ZX_CACHE_POLICY_UNCACHED_DEVICE));
// This mapping must be created after the MsiDispatcher because the VMO's
// cache policy is set within Create().
fbl::RefPtr<VmMapping> mapping;
ASSERT_EQ(ZX_OK, VmAspace::kernel_aspace()->RootVmar()->CreateVmMapping(
0 /* offset */, vmo_size, 0 /* align_pow2 */, 0 /* vmar_flags */, vmo,
0 /* vmo offset */, ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
nullptr, &mapping));
auto* reg_ptr = reinterpret_cast<uint32_t*>(mapping->base() + reg_offset);
// This test emulates a block of MSI interrupts each taking up a given bit in a register for
// their own masking. It validates that the MsiDispatcher masks / unmasks the correct bit, and
// that the operation of the inherited InterruptDispatcher side of things behaves correctly when
// interrupts are triggered.
register_call_count = 0;
for (uint32_t msi_id = 0; msi_id < msi_cnt; msi_id++) {
zx_rights_t rights;
KernelHandle<InterruptDispatcher> interrupt;
ASSERT_EQ(ZX_OK,
MsiDispatcher::Create(alloc, msi_id, vmo, reg_offset, MSI_FLAG_HAS_PVM, &rights,
&interrupt, register_fn, true /* virtual interrupt */));
// What the register should look like when |msi_id| is presently masked.
uint32_t msi_id_masked = (1u << msi_id);
// The mask bit should be set from creation of the object.
EXPECT_EQ(msi_id_masked, *reg_ptr);
// Now have a child thread wait on the interrupt and report success back.
auto thread =
Thread::Create("msi_object_waiter", interrupt_waiter,
reinterpret_cast<void*>(interrupt.dispatcher().get()), DEFAULT_PRIORITY);
thread->Resume();
// Now that the child is waiting on the interrupt it should be unmasked.
EXPECT_EQ(msi_id_masked, *reg_ptr);
// Finally, trigger the interrupt, check for success, then ensure it was masked again.
interrupt.dispatcher()->Trigger(current_time());
int ret = 0;
EXPECT_EQ(ZX_OK, thread->Join(&ret, current_time() + ZX_SEC(1)));
EXPECT_EQ(1, ret);
EXPECT_EQ(msi_id_masked, *reg_ptr);
}
// the register fn should be called once for registering and once for
// unregistering when we created and destroyed the dispatcher. This is done
// |msi_cnt| times.
ASSERT_EQ(register_call_count, 2 * msi_cnt);
END_TEST;
}
static bool interrupt_duplication_test() {
BEGIN_TEST;
// Overhead to get into a place where we can create MsiDispatchers.
ResourceDispatcher::ResourceStorage rsrc_storage;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, 0, kVectorMax,
&rsrc_storage));
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(MsiAllocation::kMsiAllocationCountMax, &alloc, MsiAllocate,
MsiFree, MsiIsSupportedTrue, &rsrc_storage));
ASSERT_EQ(1u, rsrc_storage.resource_list.size_slow());
size_t vmo_size = 48u;
fbl::RefPtr<VmObject> vmo;
ASSERT_EQ(ZX_OK,
VmObjectPaged::CreateContiguous(PMM_ALLOC_FLAG_ANY, vmo_size, 0 /* options */, &vmo));
ASSERT_EQ(ZX_OK, vmo->SetMappingCachePolicy(ZX_CACHE_POLICY_UNCACHED_DEVICE));
// Now to the meat of the test. Ensure that two MsiDispatchers cannot share
// the same MSI id, and that when a dispatcher is cleaned up it releases the
// Id reservation in the allocation.
zx_rights_t rights;
KernelHandle<InterruptDispatcher> d1, d2;
ASSERT_EQ(ZX_OK, MsiDispatcher::Create(alloc, 0, vmo, 0, 0, &rights, &d1, register_fn, true));
ASSERT_EQ(ZX_ERR_ALREADY_BOUND,
MsiDispatcher::Create(alloc, 0, vmo, 0, 0, &rights, &d2, register_fn, true));
d1.reset();
ASSERT_EQ(ZX_OK, MsiDispatcher::Create(alloc, 0, vmo, 0, 0, &rights, &d2, register_fn, true));
END_TEST;
}
static bool interrupt_vmo_test() {
BEGIN_TEST;
ResourceDispatcher::ResourceStorage rsrc_storage;
const uint32_t msi_cnt = 8;
ASSERT_EQ(ZX_OK, ResourceDispatcher::InitializeAllocator(ZX_RSRC_KIND_IRQ, msi_cnt, kVectorMax,
&rsrc_storage));
// Create an MsiAllocation and Interrupt that will attempt to use masking at the MSI Capability
// level.
fbl::RefPtr<MsiAllocation> alloc;
ASSERT_EQ(ZX_OK, MsiAllocation::Create(msi_cnt, &alloc, MsiAllocate, MsiFree, MsiIsSupportedTrue,
&rsrc_storage));
ASSERT_EQ(1u, rsrc_storage.resource_list.size_slow());
// This test emulates a block of MSI interrupts each taking up a given bit in a register for
// their own masking. It validates that the MsiDispatcher masks / unmasks the correct bit, and
// that the operation of the inherited InterruptDispatcher side of things behaves correctly when
// interrupts are triggered.
fbl::RefPtr<VmObject> vmo, vmo_noncontig;
size_t vmo_size = 48u;
ASSERT_EQ(ZX_OK,
VmObjectPaged::CreateContiguous(PMM_ALLOC_FLAG_ANY, vmo_size, 0 /* options */, &vmo));
ASSERT_EQ(ZX_OK,
VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0 /* options */, vmo_size, &vmo_noncontig));
zx_rights_t rights;
KernelHandle<InterruptDispatcher> interrupt;
// This should fail because the VMO is non-contiguous.
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
MsiDispatcher::Create(alloc, 0, vmo_noncontig, 0, MSI_FLAG_HAS_PVM, &rights, &interrupt,
register_fn, true /* virtual interrupt */));
// This should fail because the VMO has not had a cache policy set.
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
MsiDispatcher::Create(alloc, 0, vmo, 0, MSI_FLAG_HAS_PVM, &rights, &interrupt,
register_fn, true /* virtual interrupt */));
ASSERT_EQ(ZX_OK, vmo->SetMappingCachePolicy(ZX_CACHE_POLICY_UNCACHED_DEVICE));
// Now Create() should succeed.
ASSERT_EQ(ZX_OK, MsiDispatcher::Create(alloc, 0, vmo, 0, MSI_FLAG_HAS_PVM, &rights, &interrupt,
register_fn, true /* virtual interrupt */));
END_TEST;
}
UNITTEST_START_TESTCASE(msi_object)
UNITTEST("Test that Create() and get_info() operate properly", allocation_creation_and_info_test)
UNITTEST("Test allocation limits", allocation_irq_count_test)
UNITTEST("Test reservations for MSI ids", allocation_reservation_test)
UNITTEST("Test for MSI platform support hooks", allocation_support_test)
// TODO(fxb/46894): Disable test flake until root cause is found and resolved.
// UNITTEST("Test MsiDispatcher operation", interrupt_creation_test)
UNITTEST("Test that MsiDispatchers cannot overlap on an MSI id", interrupt_duplication_test)
UNITTEST("Test that MsiDispatcher validates the VMO", interrupt_vmo_test)
UNITTEST_END_TESTCASE(msi_object, "msi", "Tests for MSI objects")