blob: 5f7ee193ba2ea0569438e90d5cf1eefbf0afed6a [file] [log] [blame]
// Copyright 2017 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 "gtest/gtest.h"
#include "helper/platform_device_helper.h"
#include "mock/mock_mmio.h"
#include "msd_arm_device.h"
#include "registers.h"
bool IsStringInDump(const std::vector<std::string>& dump, const std::string& str) {
return std::any_of(dump.begin(), dump.end(), [str](const std::string& input_str) {
return input_str.find(str) != std::string::npos;
});
}
// These tests are unit testing the functionality of MsdArmDevice.
// All of these tests instantiate the device in test mode, that is without the device thread active.
class TestMsdArmDevice {
public:
void CreateAndDestroy() {
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), false);
EXPECT_NE(device, nullptr);
}
void Dump() {
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), false);
EXPECT_NE(device, nullptr);
MsdArmDevice::DumpState dump_state;
device->Dump(&dump_state, true);
ASSERT_EQ(12u, dump_state.power_states.size());
EXPECT_EQ(std::string("L2 Cache"), dump_state.power_states[0].core_type);
EXPECT_EQ(std::string("Present"), dump_state.power_states[0].status_type);
EXPECT_EQ(1lu, dump_state.power_states[0].bitmask);
EXPECT_EQ(0u, dump_state.gpu_fault_status);
EXPECT_EQ(0u, dump_state.gpu_fault_address);
EXPECT_EQ(3u, dump_state.job_slot_status.size());
for (size_t i = 0; i < dump_state.job_slot_status.size(); i++)
EXPECT_EQ(0u, dump_state.job_slot_status[i].status);
EXPECT_EQ(8u, dump_state.address_space_status.size());
for (size_t i = 0; i < dump_state.address_space_status.size(); i++)
EXPECT_EQ(0u, dump_state.address_space_status[i].status);
std::vector<std::string> dump_string;
device->FormatDump(dump_state, &dump_string);
EXPECT_TRUE(IsStringInDump(dump_string, "Core type L2 Cache state Present bitmap: 0x1"));
EXPECT_TRUE(IsStringInDump(dump_string, "Job slot 2 status 0x0 head 0x0 tail 0x0 config 0x0"));
EXPECT_TRUE(IsStringInDump(dump_string, "AS 7 status 0x0 fault status 0x0 fault address 0x0"));
EXPECT_TRUE(IsStringInDump(
dump_string, "Fault source_id 0, access type \"unknown\", exception type: \"Unknown\""));
EXPECT_TRUE(IsStringInDump(dump_string, "Time since last IRQ handler"));
EXPECT_TRUE(IsStringInDump(dump_string, "Last job interrupt time:"));
}
void MockDump() {
auto reg_io = std::make_unique<magma::RegisterIo>(MockMmio::Create(1024 * 1024));
uint32_t offset = static_cast<uint32_t>(registers::CoreReadyState::CoreType::kShader) +
static_cast<uint32_t>(registers::CoreReadyState::StatusType::kReady);
reg_io->Write32(offset, 2);
reg_io->Write32(offset + 4, 5);
static constexpr uint64_t kFaultAddress = 0xffffffff88888888lu;
registers::GpuFaultAddress::Get().FromValue(kFaultAddress).WriteTo(reg_io.get());
registers::GpuFaultStatus::Get().FromValue(5).WriteTo(reg_io.get());
registers::JobIrqFlags::GetRawStat().FromValue(0).set_failed_slots(1).WriteTo(reg_io.get());
registers::AsRegisters(7).Status().FromValue(5).WriteTo(reg_io.get());
registers::AsRegisters(7).FaultStatus().FromValue(12).WriteTo(reg_io.get());
registers::AsRegisters(7).FaultAddress().FromValue(kFaultAddress).WriteTo(reg_io.get());
registers::JobSlotRegisters(2).Status().FromValue(10).WriteTo(reg_io.get());
registers::JobSlotRegisters(1).Head().FromValue(9).WriteTo(reg_io.get());
registers::JobSlotRegisters(0).Tail().FromValue(8).WriteTo(reg_io.get());
registers::JobSlotRegisters(0).Config().FromValue(7).WriteTo(reg_io.get());
MsdArmDevice::DumpState dump_state;
GpuFeatures features;
features.address_space_count = 9;
features.job_slot_count = 7;
MsdArmDevice::DumpRegisters(features, reg_io.get(), &dump_state);
bool found = false;
for (auto& pstate : dump_state.power_states) {
if (std::string("Shader") == pstate.core_type && std::string("Ready") == pstate.status_type) {
EXPECT_EQ(0x500000002ul, pstate.bitmask);
found = true;
}
}
EXPECT_EQ(5u, dump_state.gpu_fault_status);
EXPECT_EQ(kFaultAddress, dump_state.gpu_fault_address);
EXPECT_EQ(5u, dump_state.address_space_status[7].status);
EXPECT_EQ(12u, dump_state.address_space_status[7].fault_status);
EXPECT_EQ(kFaultAddress, dump_state.address_space_status[7].fault_address);
EXPECT_EQ(10u, dump_state.job_slot_status[2].status);
EXPECT_EQ(9u, dump_state.job_slot_status[1].head);
EXPECT_EQ(8u, dump_state.job_slot_status[0].tail);
EXPECT_EQ(7u, dump_state.job_slot_status[0].config);
EXPECT_TRUE(found);
EXPECT_EQ(1u << 16, dump_state.job_irq_rawstat);
}
void ProcessRequest() {
std::unique_ptr<MsdArmDevice> device(MsdArmDevice::Create(GetTestDeviceHandle(), false));
ASSERT_NE(device, nullptr);
class TestRequest : public DeviceRequest {
public:
TestRequest(std::shared_ptr<bool> processing_complete)
: processing_complete_(processing_complete) {}
protected:
magma::Status Process(MsdArmDevice* device) override {
*processing_complete_ = true;
return MAGMA_STATUS_OK;
}
private:
std::shared_ptr<bool> processing_complete_;
};
auto processing_complete = std::make_shared<bool>(false);
auto request = std::make_unique<TestRequest>(processing_complete);
request->ProcessAndReply(device.get());
EXPECT_TRUE(processing_complete);
}
// Check that if there's a waiting request for the device thread and it's
// descheduled for a long time for some reason that it doesn't immediately
// think the GPU's hung before processing the request.
void HangTimerRequest() {
std::unique_ptr<MsdArmDevice> device(MsdArmDevice::Create(GetTestDeviceHandle(), false));
ASSERT_NE(device, nullptr);
class FakeJobScheduler : public JobScheduler {
public:
FakeJobScheduler(Owner* owner) : JobScheduler(owner, 3) {}
~FakeJobScheduler() override {}
Clock::duration GetCurrentTimeoutDuration() override {
if (got_timeout_check_)
return Clock::duration::max();
got_timeout_check_ = true;
return Clock::duration::zero();
}
void HandleTimedOutAtoms() override {
// The first hang check should be aborted since the semaphore pretended to
// be scheduled.
EXPECT_TRUE(false);
}
private:
bool got_timeout_check_ = false;
};
device->scheduler_ = std::make_unique<FakeJobScheduler>(device.get());
class FakeSemaphore : public magma::PlatformSemaphore {
public:
FakeSemaphore() : real_semaphore_(magma::PlatformSemaphore::Create()) {}
void Signal() override {
if (signal_count_++ > 0) {
// After the first one we need to pass through a signal to ensure
// the device thread receives its shutdown signal.
real_semaphore_->Signal();
}
}
void Reset() override {}
magma::Status WaitNoReset(uint64_t timeout_ms) override {
// After one time through the loop, pretend that the semaphore is signaled.
real_semaphore_->Signal();
return MAGMA_STATUS_OK;
}
magma::Status Wait(uint64_t timeout_ms) override { return MAGMA_STATUS_OK; }
bool WaitAsync(magma::PlatformPort* port, uint64_t* key_out) override {
return real_semaphore_->WaitAsync(port, key_out);
}
uint64_t id() override { return real_semaphore_->id(); }
bool duplicate_handle(uint32_t* handle_out) override {
return real_semaphore_->duplicate_handle(handle_out);
}
private:
std::unique_ptr<magma::PlatformSemaphore> real_semaphore_;
uint32_t signal_count_ = 0;
};
auto semaphore = std::make_unique<FakeSemaphore>();
device->device_request_semaphore_ = std::move(semaphore);
class TestRequest : public DeviceRequest {
public:
TestRequest(std::shared_ptr<std::atomic_bool> processing_complete)
: processing_complete_(processing_complete) {}
protected:
magma::Status Process(MsdArmDevice* device) override {
*processing_complete_ = true;
return MAGMA_STATUS_OK;
}
private:
std::shared_ptr<std::atomic_bool> processing_complete_;
};
auto processing_complete = std::make_shared<std::atomic_bool>(false);
std::thread device_thread([&device]() { device->DeviceThreadLoop(); });
auto request = std::make_unique<TestRequest>(processing_complete);
device->EnqueueDeviceRequest(std::move(request));
while (!*processing_complete)
;
device.reset();
device_thread.join();
EXPECT_TRUE(processing_complete);
}
void MockExecuteAtom() {
auto register_io = std::make_unique<magma::RegisterIo>(MockMmio::Create(1024 * 1024));
magma::RegisterIo* reg_io = register_io.get();
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), false);
EXPECT_NE(device, nullptr);
device->set_register_io(std::move(register_io));
auto connection = MsdArmConnection::Create(0, device.get());
auto null_atom =
std::make_unique<MsdArmAtom>(connection, 0, 0, 0, magma_arm_mali_user_data(), 0);
device->scheduler_->EnqueueAtom(std::move(null_atom));
device->scheduler_->TryToSchedule();
// Atom has 0 job chain address and should be thrown out.
EXPECT_EQ(0u, device->scheduler_->GetAtomListSize());
MsdArmAtom atom(connection, 5, 0, 0, magma_arm_mali_user_data(), 0);
atom.set_require_cycle_counter();
device->ExecuteAtomOnDevice(&atom, reg_io);
EXPECT_EQ(registers::GpuCommand::kCmdCycleCountStart,
reg_io->Read32(registers::GpuCommand::kOffset));
constexpr uint32_t kJobSlot = 1;
auto connection1 = MsdArmConnection::Create(0, device.get());
MsdArmAtom atom1(connection1, 100, kJobSlot, 0, magma_arm_mali_user_data(), 0);
device->ExecuteAtomOnDevice(&atom1, reg_io);
registers::JobSlotRegisters regs(kJobSlot);
EXPECT_EQ(0xffffffffffffffffu, regs.AffinityNext().ReadFrom(reg_io).reg_value());
EXPECT_EQ(100u, regs.HeadNext().ReadFrom(reg_io).reg_value());
constexpr uint32_t kCommandStart = registers::JobSlotCommand::kCommandStart;
EXPECT_EQ(kCommandStart, regs.CommandNext().ReadFrom(reg_io).reg_value());
auto config_next = regs.ConfigNext().ReadFrom(reg_io);
// connection should get address slot 0, and connection1 should get
// slot 1.
EXPECT_EQ(1u, config_next.address_space().get());
EXPECT_EQ(1u, config_next.start_flush_clean().get());
EXPECT_EQ(1u, config_next.start_flush_invalidate().get());
EXPECT_EQ(0u, config_next.job_chain_flag().get());
EXPECT_EQ(1u, config_next.end_flush_clean().get());
EXPECT_EQ(1u, config_next.end_flush_invalidate().get());
EXPECT_EQ(0u, config_next.enable_flush_reduction().get());
EXPECT_EQ(0u, config_next.disable_descriptor_write_back().get());
EXPECT_EQ(8u, config_next.thread_priority().get());
EXPECT_EQ(registers::GpuCommand::kCmdCycleCountStart,
reg_io->Read32(registers::GpuCommand::kOffset));
device->AtomCompleted(&atom, kArmMaliResultSuccess);
EXPECT_EQ(registers::GpuCommand::kCmdCycleCountStop,
reg_io->Read32(registers::GpuCommand::kOffset));
}
void TestIdle() {
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), false);
EXPECT_NE(device, nullptr);
MsdArmDevice::DumpState dump_state;
device->Dump(&dump_state, false);
// Ensure that the GPU is idle and not doing anything at this point. A
// failure in this may be caused by a previous test.
EXPECT_EQ(0u, dump_state.gpu_status);
}
void MockInitializeQuirks() {
auto reg_io = std::make_unique<magma::RegisterIo>(MockMmio::Create(1024 * 1024));
GpuFeatures features;
features.gpu_id.set_reg_value(0x72120000);
MsdArmDevice::InitializeHardwareQuirks(&features, reg_io.get());
EXPECT_EQ(1u << 17, reg_io->Read32(0xf04));
features.gpu_id.set_reg_value(0x08201000); // T820 R1P0
MsdArmDevice::InitializeHardwareQuirks(&features, reg_io.get());
EXPECT_EQ(1u << 16, reg_io->Read32(0xf04));
features.gpu_id.set_reg_value(0x9990000);
MsdArmDevice::InitializeHardwareQuirks(&features, reg_io.get());
EXPECT_EQ(0u, reg_io->Read32(0xf04));
}
void ProtectedMode() {
// Use device thread so the test can wait for a reset interrupt.
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), true);
ASSERT_NE(nullptr, device);
if (!device->IsProtectedModeSupported()) {
printf("Protected mode not supported, skipping test\n");
return;
}
EXPECT_FALSE(device->IsInProtectedMode());
EXPECT_EQ(1u, device->power_manager_->l2_ready_status());
EXPECT_TRUE(device->perf_counters_->Enable());
device->EnterProtectedMode();
EXPECT_EQ(1u, device->power_manager_->l2_ready_status());
EXPECT_TRUE(device->IsInProtectedMode());
EXPECT_TRUE(device->perf_counters_->running());
EXPECT_TRUE(device->ExitProtectedMode());
EXPECT_EQ(1u, device->power_manager_->l2_ready_status());
EXPECT_FALSE(device->IsInProtectedMode());
// Exiting protected mode should disable performance counters.
EXPECT_FALSE(device->perf_counters_->running());
}
void PowerDownL2() {
// Use device thread so the test can wait for a power down interrupt.
std::unique_ptr<MsdArmDevice> device = MsdArmDevice::Create(GetTestDeviceHandle(), true);
ASSERT_NE(nullptr, device);
// In theory this could work without protected mode, but it's not needed. On the amlogic
// T820 in the VIM2, powering down the L2 seems to cause GPU faults when the shader cores
// are later powered back up again.
if (!device->IsProtectedModeSupported()) {
printf("Protected mode not supported, skipping test\n");
return;
}
EXPECT_TRUE(device->PowerDownL2());
EXPECT_EQ(0u, device->power_manager_->l2_ready_status());
}
};
TEST(MsdArmDevice, CreateAndDestroy) {
TestMsdArmDevice test;
test.CreateAndDestroy();
}
TEST(MsdArmDevice, Dump) {
TestMsdArmDevice test;
test.Dump();
}
TEST(MsdArmDevice, MockDump) {
TestMsdArmDevice test;
test.MockDump();
}
TEST(MsdArmDevice, ProcessRequest) {
TestMsdArmDevice test;
test.ProcessRequest();
}
TEST(MsdArmDevice, HangTimerRequest) {
TestMsdArmDevice test;
test.HangTimerRequest();
}
TEST(MsdArmDevice, MockExecuteAtom) {
TestMsdArmDevice test;
test.MockExecuteAtom();
}
TEST(MsdArmDevice, Idle) {
TestMsdArmDevice test;
test.TestIdle();
}
TEST(MsdArmDevice, MockInitializeQuirks) {
TestMsdArmDevice test;
test.MockInitializeQuirks();
}
TEST(MsdArmDevice, ProtectMode) {
TestMsdArmDevice test;
test.ProtectedMode();
}
TEST(MsdArmDevice, PowerDownL2) {
TestMsdArmDevice test;
test.PowerDownL2();
}