blob: 019c4434ea2e85ae2d80983aa81679f4314fa77f [file] [edit]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <lib/magma_service/test_util/platform_msd_device_helper.h>
#include <limits>
#include <gtest/gtest.h>
#include "src/graphics/drivers/msd-vsi-vip/src/instructions.h"
#include "src/graphics/drivers/msd-vsi-vip/src/msd_vsi_device.h"
#include "src/graphics/drivers/msd-vsi-vip/tests/mock/mock_mapped_batch.h"
static inline uint32_t to_uint32(uint64_t val) {
assert(val <= std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(val);
}
class TestEvents : public ::testing::Test {
public:
static constexpr uint32_t kAddressSpaceIndex = 1;
void SetUp() override {
constexpr bool kEnableSuspend = true;
device_ = MsdVsiDevice::Create(GetTestDeviceHandle(), kEnableSuspend);
EXPECT_NE(device_, nullptr);
address_space_owner_ = std::make_unique<AddressSpaceOwner>(device_->GetBusMapper());
address_space_ = AddressSpace::Create(address_space_owner_.get(), kAddressSpaceIndex);
EXPECT_NE(address_space_, nullptr);
device_->page_table_arrays()->AssignAddressSpace(kAddressSpaceIndex, address_space_.get());
std::weak_ptr<MsdVsiConnection> connection;
context_ = MsdVsiContext::Create(connection, address_space_, device_->GetRingbuffer());
EXPECT_NE(context_, nullptr);
}
protected:
// Usually the MsdVsiDevice would be the AddressSpaceOwner, however its implementation
// of |AddressSpaceReleased| would throw an assert when it attempts to free the page
// table slot.
class AddressSpaceOwner : public AddressSpace::Owner {
public:
AddressSpaceOwner(magma::PlatformBusMapper* bus_mapper) : bus_mapper_(bus_mapper) {}
virtual ~AddressSpaceOwner() = default;
void AddressSpaceReleased(AddressSpace* address_space) override {}
magma::PlatformBusMapper* GetBusMapper() override { return bus_mapper_; }
private:
magma::PlatformBusMapper* bus_mapper_;
};
void StopRingbuffer() {
device_->StopRingbuffer();
auto start = std::chrono::high_resolution_clock::now();
while (!device_->IsIdle() && std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - start)
.count() < 1000) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
auto reg = registers::IdleState::Get().ReadFrom(device_->register_io());
EXPECT_EQ(0x7FFFFFFFu, reg.reg_value());
}
// The address space owner needs to be destroyed last, as the address space destructor
// will call into the address space owner, and the device may be the one dropping the
// last address space reference. Usually this is not an issue as the device would also
// be the address space owner, but in this case we want to override that functionality.
std::unique_ptr<AddressSpaceOwner> address_space_owner_;
std::unique_ptr<MsdVsiDevice> device_;
std::shared_ptr<AddressSpace> address_space_;
std::shared_ptr<MsdVsiContext> context_;
};
TEST_F(TestEvents, AllocAndFree) {
for (unsigned int i = 0; i < 2; i++) {
uint32_t event_ids[MsdVsiDevice::kNumEvents] = {};
for (unsigned int j = 0; j < MsdVsiDevice::kNumEvents; j++) {
ASSERT_TRUE(device_->AllocInterruptEvent(false /* free_on_complete */, &event_ids[j]));
}
// We should have no events left.
uint32_t id;
ASSERT_FALSE(device_->AllocInterruptEvent(false /* free_on_complete */, &id));
ASSERT_FALSE(device_->CompleteInterruptEvent(0)); // Not yet submitted.
for (unsigned int j = 0; j < MsdVsiDevice::kNumEvents; j++) {
ASSERT_TRUE(device_->FreeInterruptEvent(event_ids[j]));
}
ASSERT_FALSE(device_->FreeInterruptEvent(0)); // Already freed.
ASSERT_FALSE(device_->FreeInterruptEvent(100)); // Out of bounds.
}
ASSERT_FALSE(device_->CompleteInterruptEvent(0)); // Not yet allocated.
}
TEST_F(TestEvents, WriteSameEvent) {
// We need to load the address space as we are writing to the ringbuffer directly,
// rather than via SubmitCommandBuffer.
ASSERT_TRUE(device_->LoadInitialAddressSpace(context_, kAddressSpaceIndex));
ASSERT_TRUE(device_->StartRingbuffer(context_));
uint32_t event_id;
ASSERT_TRUE(device_->AllocInterruptEvent(false /* free_on_complete */, &event_id));
auto mapped_batch = std::make_unique<MockMappedBatch>(nullptr);
ASSERT_TRUE(device_->WriteInterruptEvent(event_id, std::move(mapped_batch), address_space_));
// Writing the event again should fail as it is still pending.
mapped_batch = std::make_unique<MockMappedBatch>(nullptr);
ASSERT_FALSE(device_->WriteInterruptEvent(event_id, std::move(mapped_batch), address_space_));
ASSERT_TRUE(device_->CompleteInterruptEvent(event_id));
// Now that the event completed, writing should succeed.
mapped_batch = std::make_unique<MockMappedBatch>(nullptr);
ASSERT_TRUE(device_->WriteInterruptEvent(event_id, std::move(mapped_batch), address_space_));
}
TEST_F(TestEvents, WriteUnorderedEventIds) {
// We need to load the address space as we are writing to the ringbuffer directly,
// rather than via SubmitCommandBuffer.
ASSERT_TRUE(device_->LoadInitialAddressSpace(context_, kAddressSpaceIndex));
ASSERT_TRUE(device_->StartRingbuffer(context_));
auto& ringbuffer = device_->ringbuffer_;
uint64_t rb_gpu_addr;
ASSERT_TRUE(context_->exec_address_space()->GetRingbufferGpuAddress(&rb_gpu_addr));
// Allocate the maximum number of interrupt events, and corresponding semaphores.
uint32_t event_ids[MsdVsiDevice::kNumEvents] = {};
std::unique_ptr<magma::PlatformSemaphore> semaphores[MsdVsiDevice::kNumEvents] = {};
for (unsigned int i = 0; i < MsdVsiDevice::kNumEvents; i++) {
ASSERT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event_ids[i]));
auto semaphore = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphore, nullptr);
semaphores[i] = std::move(semaphore);
}
uint32_t prev_wait_link = ringbuffer->SubtractOffset(kWaitLinkDwords * sizeof(uint32_t));
// We will link to the end of the ringbuffer, where we are adding new events.
uint32_t rb_link_addr = to_uint32(rb_gpu_addr) + ringbuffer->tail();
// Write event ids in reverse order, so we can test when it does not match batch sequence order.
for (unsigned i = MsdVsiDevice::kNumEvents; i-- > 0;) {
auto copy = semaphores[i]->Clone();
ASSERT_NE(copy, nullptr);
auto mapped_batch = std::make_unique<MockMappedBatch>(std::move(copy));
ASSERT_TRUE(
device_->WriteInterruptEvent(event_ids[i], std::move(mapped_batch), address_space_));
}
ASSERT_TRUE(device_->AddRingbufferWaitLink());
// Link the ringbuffer to the newly written events.
uint32_t num_new_rb_instructions = MsdVsiDevice::kNumEvents + 2; // Add 2 for WAIT-LINK.
device_->LinkRingbuffer(prev_wait_link, rb_link_addr, num_new_rb_instructions /* prefetch */);
// Start the device thread.
device_->StartDeviceThread();
constexpr uint64_t kTimeoutMs = 5000;
for (unsigned int j = 0; j < MsdVsiDevice::kNumEvents; j++) {
ASSERT_EQ(MAGMA_STATUS_OK, semaphores[j]->Wait(kTimeoutMs).get());
}
StopRingbuffer();
}
TEST_F(TestEvents, Submit) {
device_->StartDeviceThread();
// Each EVENT WAIT LINK takes 24 bytes, so this should test the ringbuffer wrapping ~5 times.
for (int i = 0; i < 1000; i++) {
auto semaphore = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphore, nullptr);
std::vector<std::shared_ptr<magma::PlatformSemaphore>> wait_semaphores;
std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores;
signal_semaphores.emplace_back(semaphore->Clone());
auto batch = std::make_unique<EventBatch>(context_, wait_semaphores, signal_semaphores);
ASSERT_EQ(MAGMA_STATUS_OK, device_->SubmitBatch(std::move(batch), false /* do_flush */).get());
constexpr uint64_t kTimeoutMs = 1000;
ASSERT_EQ(MAGMA_STATUS_OK, semaphore->Wait(kTimeoutMs).get());
}
{
// The ringbuffer should be in WAIT-LINK until we explicitly stop it.
auto reg = registers::IdleState::Get().ReadFrom(device_->register_io());
EXPECT_NE(0x7FFFFFFFu, reg.reg_value());
}
StopRingbuffer();
}