blob: 8b0b36d7f8071d1b2eb0df1f6e5266603dacf910 [file] [log] [blame]
// Copyright 2016 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 "device_request.h"
#include "gtest/gtest.h"
#include "helper/platform_device_helper.h"
#include "mock/mock_mmio.h"
#include "msd_intel_device.h"
#include "registers.h"
class TestEngineCommandStreamer {
public:
static bool ExecBatch(RenderEngineCommandStreamer* engine,
std::unique_ptr<MappedBatch> mapped_batch) {
return engine->ExecBatch(std::move(mapped_batch));
}
static bool SubmitContext(RenderEngineCommandStreamer* engine, MsdIntelContext* context,
uint32_t tail) {
return engine->SubmitContext(context, tail);
}
};
// These tests are unit testing the functionality of MsdIntelDevice.
// All of these tests instantiate the device in test mode, that is without the device thread active.
class TestMsdIntelDevice {
public:
void CreateAndDestroy() {
for (uint32_t i = 0; i < 100; i++) {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device =
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false);
EXPECT_NE(device, nullptr);
EXPECT_TRUE(device->WaitIdle());
// check that the render init batch succeeded.
EXPECT_EQ(device->global_context()
->hardware_status_page(RENDER_COMMAND_STREAMER)
->read_sequence_number(),
0x1001u);
// test register access
uint32_t expected = 0xabcd1234;
device->register_io()->Write32(0x4f100, expected);
uint32_t value = device->register_io()->Read32(0x4f100);
EXPECT_EQ(expected, value);
}
}
class FormattedString {
public:
FormattedString(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int size = std::vsnprintf(nullptr, 0, fmt, args);
buf_ = std::vector<char>(size + 1);
std::vsnprintf(buf_.data(), buf_.size(), fmt, args);
va_end(args);
}
char* data() { return buf_.data(); }
private:
std::vector<char> buf_;
};
void Dump() {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device =
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false);
EXPECT_NE(device, nullptr);
EXPECT_TRUE(device->WaitIdle());
MsdIntelDevice::DumpState dump_state;
device->Dump(&dump_state);
EXPECT_EQ(dump_state.render_cs.sequence_number,
device->global_context()
->hardware_status_page(RENDER_COMMAND_STREAMER)
->read_sequence_number());
EXPECT_EQ(dump_state.render_cs.active_head_pointer,
device->render_engine_cs()->GetActiveHeadPointer());
EXPECT_FALSE(dump_state.fault_present);
EXPECT_TRUE(dump_state.render_cs.inflight_batches.empty());
dump_state.fault_present = true;
dump_state.fault_engine = 0;
dump_state.fault_src = 3;
dump_state.fault_type = 10;
dump_state.fault_gpu_address = 0xaabbccdd11223344;
dump_state.global = 1;
std::vector<std::string> dump_string;
device->FormatDump(dump_state, dump_string);
bool foundit = false;
for (auto& str : dump_string) {
if (strstr(str.c_str(),
FormattedString("sequence_number 0x%x", dump_state.render_cs.sequence_number)
.data())) {
foundit = true;
break;
}
}
EXPECT_TRUE(foundit);
foundit = false;
for (auto& str : dump_string) {
if (strstr(str.c_str(), FormattedString("active head pointer: 0x%llx",
dump_state.render_cs.active_head_pointer)
.data())) {
foundit = true;
break;
}
}
EXPECT_TRUE(foundit);
foundit = false;
for (auto& str : dump_string) {
if (strstr(
str.c_str(),
FormattedString("engine 0x%x src 0x%x type 0x%x gpu_address 0x%lx global %d",
dump_state.fault_engine, dump_state.fault_src, dump_state.fault_type,
dump_state.fault_gpu_address, dump_state.global)
.data())) {
foundit = true;
break;
}
}
EXPECT_TRUE(foundit);
}
void MockDump() {
auto reg_io = std::make_unique<magma::RegisterIo>(MockMmio::Create(2 * 1024 * 1024));
reg_io->Write32(registers::FaultTlbReadData::kOffset0, 0xabcd1234);
reg_io->Write32(registers::FaultTlbReadData::kOffset1, 0x1f);
MsdIntelDevice::DumpState dump_state;
MsdIntelDevice::DumpFaultAddress(&dump_state, reg_io.get());
EXPECT_EQ(0xfabcd1234000ull, dump_state.fault_gpu_address);
EXPECT_TRUE(dump_state.global);
reg_io->Write32(registers::FaultTlbReadData::kOffset1, 0xf);
MsdIntelDevice::DumpFaultAddress(&dump_state, reg_io.get());
EXPECT_EQ(0xfabcd1234000ull, dump_state.fault_gpu_address);
EXPECT_FALSE(dump_state.global);
uint32_t engine = 0;
uint32_t src = 0xff;
uint32_t type = 0x3;
uint32_t valid = 0x1;
MsdIntelDevice::DumpFault(&dump_state, (engine << 12) | (src << 3) | (type << 1) | valid);
EXPECT_EQ(dump_state.fault_present, valid);
EXPECT_EQ(dump_state.fault_engine, engine);
EXPECT_EQ(dump_state.fault_src, src);
EXPECT_EQ(dump_state.fault_type, type);
EXPECT_TRUE(dump_state.render_cs.inflight_batches.empty());
}
void BatchBuffer(bool should_wrap_ringbuffer) {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device(
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false));
ASSERT_NE(device, nullptr);
EXPECT_TRUE(device->WaitIdle());
bool ringbuffer_wrapped = false;
// num_iterations updated after one iteration in case we're wrapping the ringbuffer
uint32_t num_iterations = 1;
for (uint32_t iteration = 0; iteration < num_iterations; iteration++) {
auto dst_mapping =
AddressSpace::MapBufferGpu(device->gtt(), MsdIntelBuffer::Create(PAGE_SIZE, "dst"));
ASSERT_NE(dst_mapping, nullptr);
void* dst_cpu_addr;
EXPECT_TRUE(dst_mapping->buffer()->platform_buffer()->MapCpu(&dst_cpu_addr));
auto batch_buffer =
std::shared_ptr<MsdIntelBuffer>(MsdIntelBuffer::Create(PAGE_SIZE, "batchbuffer"));
ASSERT_NE(batch_buffer, nullptr);
void* batch_cpu_addr;
ASSERT_TRUE(batch_buffer->platform_buffer()->MapCpu(&batch_cpu_addr));
uint32_t* batch_ptr = reinterpret_cast<uint32_t*>(batch_cpu_addr);
auto batch_mapping = AddressSpace::MapBufferGpu(device->gtt(), batch_buffer);
ASSERT_NE(batch_mapping, nullptr);
uint32_t expected_val = 0x8000000 + iteration;
uint64_t offset =
(iteration * sizeof(uint32_t)) % dst_mapping->buffer()->platform_buffer()->size();
static constexpr uint32_t kScratchRegOffset = 0x02600;
uint32_t i = 0;
batch_ptr[i++] = (0x22 << 23) | (3 - 2); // store to mmio
batch_ptr[i++] = kScratchRegOffset;
batch_ptr[i++] = expected_val;
static constexpr uint32_t kDwordCount = 4;
static constexpr uint32_t kAddressSpaceGtt = 1 << 22;
batch_ptr[i++] = (0x20 << 23) | (kDwordCount - 2) | kAddressSpaceGtt; // store dword
batch_ptr[i++] = magma::lower_32_bits(dst_mapping->gpu_addr() + offset);
batch_ptr[i++] = magma::upper_32_bits(dst_mapping->gpu_addr() + offset);
batch_ptr[i++] = expected_val;
batch_ptr[i++] = (0xA << 23); // batch end
auto ringbuffer = device->global_context()->get_ringbuffer(device->render_engine_cs()->id());
uint32_t tail_start = ringbuffer->tail();
// Initialize the target
reinterpret_cast<uint32_t*>(dst_cpu_addr)[offset / sizeof(uint32_t)] = 0xdeadbeef;
device->register_io()->Write32(kScratchRegOffset, 0xdeadbeef);
EXPECT_TRUE(TestEngineCommandStreamer::ExecBatch(
device->render_engine_cs(), std::unique_ptr<SimpleMappedBatch>(new SimpleMappedBatch(
device->global_context(), std::move(batch_mapping)))));
EXPECT_TRUE(device->WaitIdle());
EXPECT_EQ(ringbuffer->head(), ringbuffer->tail());
EXPECT_EQ(expected_val, device->register_io()->Read32(kScratchRegOffset));
uint32_t target_val = reinterpret_cast<uint32_t*>(dst_cpu_addr)[offset / sizeof(uint32_t)];
EXPECT_EQ(target_val, expected_val);
if (ringbuffer->tail() < tail_start) {
DLOG("ringbuffer wrapped tail_start 0x%x tail 0x%x", tail_start, ringbuffer->tail());
ringbuffer_wrapped = true;
}
if (should_wrap_ringbuffer && num_iterations == 1)
num_iterations = (ringbuffer->size() - tail_start) / (ringbuffer->tail() - tail_start) + 10;
}
if (should_wrap_ringbuffer)
EXPECT_TRUE(ringbuffer_wrapped);
DLOG("Finished, num_iterations %u", num_iterations);
}
void RegisterWrite() {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device(new MsdIntelDevice());
ASSERT_NE(device, nullptr);
constexpr bool kExecInitBatch = false;
ASSERT_TRUE(device->Init(platform_device->GetDeviceHandle(), kExecInitBatch));
auto ringbuffer = device->global_context()->get_ringbuffer(device->render_engine_cs()->id());
static constexpr uint32_t kScratchRegOffset = 0x02600;
device->register_io()->Write32(kScratchRegOffset, 0xdeadbeef);
static constexpr uint32_t expected_val = 0x8000000;
constexpr uint32_t kCommandType = 0x22; // store to mmio
constexpr uint32_t kEncodedLength = 3 - 2; // per instruction encoding
ringbuffer->Write32((kCommandType << 23) | kEncodedLength);
ringbuffer->Write32(kScratchRegOffset);
ringbuffer->Write32(expected_val);
ringbuffer->Write32(0);
TestEngineCommandStreamer::SubmitContext(device->render_engine_cs(),
device->global_context().get(), ringbuffer->tail());
auto start = std::chrono::high_resolution_clock::now();
while (device->render_engine_cs()->GetActiveHeadPointer() != ringbuffer->tail() &&
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - start)
.count() < 100) {
std::this_thread::yield();
}
EXPECT_EQ(ringbuffer->tail(), device->render_engine_cs()->GetActiveHeadPointer());
EXPECT_EQ(expected_val, device->register_io()->Read32(kScratchRegOffset));
}
void ProcessRequest() {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device(
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false));
ASSERT_NE(device, nullptr);
class TestRequest : public MsdIntelDevice::DeviceRequest {
public:
TestRequest(std::shared_ptr<bool> processing_complete)
: processing_complete_(processing_complete) {}
protected:
magma::Status Process(MsdIntelDevice* 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);
}
void MaxFreq() {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device =
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false);
EXPECT_NE(device, nullptr);
constexpr uint32_t kMaxFreq = 1100;
uint32_t current_freq = device->GetCurrentFrequency();
DLOG("current_freq %u max_freq %u", current_freq, kMaxFreq);
EXPECT_LE(current_freq, kMaxFreq);
device->RequestMaxFreq();
uint32_t freq = device->GetCurrentFrequency();
EXPECT_LE(freq, kMaxFreq);
EXPECT_GE(freq, current_freq);
}
void QuerySliceInfo() {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device =
MsdIntelDevice::Create(platform_device->GetDeviceHandle(), false);
EXPECT_NE(device, nullptr);
uint32_t subslice_total = 0, eu_total = 0;
device->QuerySliceInfo(&subslice_total, &eu_total);
EXPECT_EQ(3u, subslice_total);
// Expected values for eu_total are either 24 (for the Acer Switch
// Alpha 12) or 23 (for the Intel NUC).
if (eu_total != 24u)
EXPECT_EQ(23u, eu_total);
}
class FakeSemaphore : public magma::PlatformSemaphore {
public:
uint64_t id() override { return 1; }
bool duplicate_handle(uint32_t* handle_out) override { return false; }
void Signal() override {
signal_sem_->Signal();
if (pass_thru_) {
sem_->Signal();
}
}
void Reset() override {}
magma::Status WaitNoReset(uint64_t timeout_ms) override { return MAGMA_STATUS_UNIMPLEMENTED; }
magma::Status Wait(uint64_t timeout_ms) override {
wait_sem_->Signal();
sem_->Wait();
return wait_return_.load();
}
bool WaitAsync(magma::PlatformPort* port, uint64_t* key_out) override { return false; }
std::unique_ptr<magma::PlatformSemaphore> sem_ = magma::PlatformSemaphore::Create();
std::unique_ptr<magma::PlatformSemaphore> signal_sem_ = magma::PlatformSemaphore::Create();
std::unique_ptr<magma::PlatformSemaphore> wait_sem_ = magma::PlatformSemaphore::Create();
std::atomic<uint64_t> wait_return_ = MAGMA_STATUS_OK;
bool pass_thru_ = false;
};
void HangcheckTimeout(bool spurious) {
magma::PlatformPciDevice* platform_device = TestPlatformPciDevice::GetInstance();
ASSERT_NE(platform_device, nullptr);
std::unique_ptr<MsdIntelDevice> device = std::unique_ptr<MsdIntelDevice>(new MsdIntelDevice());
constexpr bool kExecInitBatch = false;
ASSERT_TRUE(device->Init(platform_device->GetDeviceHandle(), kExecInitBatch));
EXPECT_EQ(device->suspected_gpu_hang_count_.load(), 0u);
device->device_request_semaphore_ = std::make_unique<FakeSemaphore>();
auto semaphore = static_cast<FakeSemaphore*>(device->device_request_semaphore_.get());
device->StartDeviceThread();
// Wait for device thread to idle
while (true) {
EXPECT_EQ(MAGMA_STATUS_OK, semaphore->wait_sem_->Wait(2000).get());
magma::Status status = semaphore->signal_sem_->Wait(2000);
if (status.get() == MAGMA_STATUS_TIMED_OUT)
break;
semaphore->sem_->Signal();
}
if (spurious) {
// If work is enqueued then we should not hangcheck
device->EnqueueDeviceRequest(std::make_unique<DeviceRequest<MsdIntelDevice>>());
}
semaphore->wait_return_ = MAGMA_STATUS_TIMED_OUT;
semaphore->sem_->Signal();
EXPECT_EQ(MAGMA_STATUS_OK, semaphore->wait_sem_->Wait(2000).get());
EXPECT_EQ(device->suspected_gpu_hang_count_.load(), spurious ? 0u : 1u);
semaphore->pass_thru_ = true;
semaphore->wait_return_ = MAGMA_STATUS_OK;
semaphore->sem_->Signal();
}
};
TEST(MsdIntelDevice, CreateAndDestroy) {
TestMsdIntelDevice test;
test.CreateAndDestroy();
}
TEST(MsdIntelDevice, Dump) {
TestMsdIntelDevice test;
test.Dump();
}
TEST(MsdIntelDevice, MockDump) {
TestMsdIntelDevice test;
test.MockDump();
}
TEST(MsdIntelDevice, RegisterWrite) {
TestMsdIntelDevice test;
test.RegisterWrite();
}
TEST(MsdIntelDevice, BatchBuffer) {
TestMsdIntelDevice test;
test.BatchBuffer(false);
}
TEST(MsdIntelDevice, WrapRingbuffer) {
TestMsdIntelDevice test;
test.BatchBuffer(true);
}
TEST(MsdIntelDevice, ProcessRequest) {
TestMsdIntelDevice test;
test.ProcessRequest();
}
TEST(MsdIntelDevice, MaxFreq) {
TestMsdIntelDevice test;
test.MaxFreq();
}
TEST(MsdIntelDevice, QuerySliceInfo) {
TestMsdIntelDevice test;
test.QuerySliceInfo();
}
TEST(MsdIntelDevice, HangcheckTimeout) { TestMsdIntelDevice().HangcheckTimeout(false); }
TEST(MsdIntelDevice, SpuriousHangcheckTimeout) { TestMsdIntelDevice().HangcheckTimeout(true); }