blob: 9f12b23165e87b4fd8732f762a9a7f9d31881297 [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.
#define MAGMA_DLOG_ENABLE 1
#include <fcntl.h>
#include <thread>
#include "magma.h"
#include "magma_util/dlog.h"
#include "magma_util/inflight_list.h"
#include "magma_util/macros.h"
#include "gtest/gtest.h"
namespace {
constexpr uint32_t kValue = 0xabcddcba;
class TestBase {
public:
TestBase() { fd_ = open("/dev/class/gpu/000", O_RDONLY); }
int fd() { return fd_; }
~TestBase() { close(fd_); }
private:
int fd_;
};
class TestConnection : public TestBase {
public:
TestConnection()
{
magma_create_connection(fd(), &connection_);
DASSERT(connection_);
magma_create_context(connection_, &context_id_);
}
~TestConnection()
{
if (connection_) {
magma_release_context(connection_, context_id_);
magma_release_connection(connection_);
}
}
enum How { NORMAL, FAULT, HANG };
static constexpr bool kUseGlobalGtt = false;
void SubmitCommandBuffer(How how)
{
ASSERT_NE(connection_, nullptr);
uint64_t size;
magma_buffer_t batch_buffer;
ASSERT_EQ(magma_create_buffer(connection_, PAGE_SIZE, &size, &batch_buffer), 0);
void* vaddr;
ASSERT_EQ(0, magma_map(connection_, batch_buffer, &vaddr));
ASSERT_TRUE(InitBatchBuffer(vaddr, size, how == HANG));
magma_buffer_t command_buffer;
ASSERT_EQ(magma_create_command_buffer(connection_, PAGE_SIZE, &command_buffer), 0);
EXPECT_TRUE(InitCommandBuffer(command_buffer, batch_buffer, size, how == FAULT));
magma_submit_command_buffer(connection_, command_buffer, context_id_);
magma::InflightList list(connection_);
switch (how) {
case NORMAL:
EXPECT_TRUE(list.WaitForCompletion(1000));
EXPECT_EQ(MAGMA_STATUS_OK, magma_get_error(connection_));
EXPECT_EQ(kValue, reinterpret_cast<uint32_t*>(vaddr)[size / 4 - 1]);
break;
case FAULT: {
// Intel won't actually fault because bad gpu addresses are valid
auto start = std::chrono::steady_clock::now();
while (std::chrono::duration<double, std::milli>(
std::chrono::high_resolution_clock::now() - start)
.count() < 2000) {
if (magma_get_error(connection_) == MAGMA_STATUS_CONNECTION_LOST) {
break;
}
}
EXPECT_EQ(MAGMA_STATUS_CONNECTION_LOST, magma_get_error(connection_));
EXPECT_TRUE(list.WaitForCompletion(1000));
EXPECT_EQ(0xdeadbeef, reinterpret_cast<uint32_t*>(vaddr)[size / 4 - 1]);
break;
}
case HANG: {
auto start = std::chrono::steady_clock::now();
while (std::chrono::duration<double, std::milli>(
std::chrono::high_resolution_clock::now() - start)
.count() < 2000) {
if (magma_get_error(connection_) == MAGMA_STATUS_CONNECTION_LOST) {
break;
}
}
EXPECT_EQ(MAGMA_STATUS_CONNECTION_LOST, magma_get_error(connection_));
EXPECT_TRUE(list.WaitForCompletion(1000));
EXPECT_EQ(kValue, reinterpret_cast<uint32_t*>(vaddr)[size / 4 - 1]);
break;
}
}
EXPECT_EQ(magma_unmap(connection_, batch_buffer), 0);
magma_release_buffer(connection_, batch_buffer);
}
bool InitBatchBuffer(void* vaddr, uint64_t size, bool hang)
{
memset(vaddr, 0, size);
// store dword
reinterpret_cast<uint32_t*>(vaddr)[0] =
(0x20 << 23) | (4 - 2) | (kUseGlobalGtt ? 1 << 22 : 0);
reinterpret_cast<uint32_t*>(vaddr)[1] =
0x1000000; // gpu address - overwritten by relocation (or not)
reinterpret_cast<uint32_t*>(vaddr)[2] = 0;
reinterpret_cast<uint32_t*>(vaddr)[3] = kValue;
// wait for semaphore - proceed if dword at given address > dword given
reinterpret_cast<uint32_t*>(vaddr)[4] =
(0x1C << 23) | (4 - 2) | (kUseGlobalGtt ? 1 << 22 : 0);
reinterpret_cast<uint32_t*>(vaddr)[5] = hang ? ~0 : 0;
reinterpret_cast<uint32_t*>(vaddr)[6] =
0x1000000; // gpu address - overwritten by relocation (or not)
reinterpret_cast<uint32_t*>(vaddr)[7] = 0;
reinterpret_cast<uint32_t*>(vaddr)[8] = 0xA << 23;
reinterpret_cast<uint32_t*>(vaddr)[size / 4 - 1] = 0xdeadbeef;
return true;
}
bool InitCommandBuffer(magma_buffer_t buffer, magma_buffer_t batch_buffer,
uint64_t batch_buffer_length, bool fault)
{
void* vaddr;
if (magma_map(connection_, buffer, &vaddr) != 0)
return DRETF(false, "couldn't map command buffer");
auto command_buffer = reinterpret_cast<struct magma_system_command_buffer*>(vaddr);
command_buffer->batch_buffer_resource_index = 0;
command_buffer->batch_start_offset = 0;
command_buffer->num_resources = 1;
auto exec_resource =
reinterpret_cast<struct magma_system_exec_resource*>(command_buffer + 1);
exec_resource->buffer_id = magma_get_buffer_id(batch_buffer);
exec_resource->num_relocations = fault ? 0 : 2;
exec_resource->offset = 0;
exec_resource->length = batch_buffer_length;
auto reloc = reinterpret_cast<struct magma_system_relocation_entry*>(exec_resource + 1);
reloc->offset = 1 * 4;
reloc->target_resource_index = 0;
reloc->target_offset = batch_buffer_length - 4;
reloc++;
reloc->offset = 6 * 4;
reloc->target_resource_index = 0;
reloc->target_offset = batch_buffer_length - 4;
EXPECT_EQ(magma_unmap(connection_, buffer), 0);
return true;
}
static void Stress(uint32_t iterations)
{
for (uint32_t i = 0; i < iterations; i++) {
DLOG("iteration %d/%d", i, iterations);
std::thread happy([] {
std::unique_ptr<TestConnection> test(new TestConnection());
for (uint32_t count = 0; count < 100; count++) {
test->SubmitCommandBuffer(TestConnection::NORMAL);
}
});
std::thread sad([] {
std::unique_ptr<TestConnection> test(new TestConnection());
for (uint32_t count = 0; count < 100; count++) {
if (count % 2 == 0) {
test->SubmitCommandBuffer(TestConnection::NORMAL);
} else if (count % 3 == 0) {
test->SubmitCommandBuffer(TestConnection::FAULT);
test.reset(new TestConnection());
} else {
test->SubmitCommandBuffer(TestConnection::HANG);
test.reset(new TestConnection());
}
}
});
happy.join();
sad.join();
}
}
void SubmitAndDisconnect()
{
uint64_t size;
magma_buffer_t batch_buffer;
ASSERT_EQ(magma_create_buffer(connection_, PAGE_SIZE, &size, &batch_buffer), 0);
void* vaddr;
ASSERT_EQ(0, magma_map(connection_, batch_buffer, &vaddr));
ASSERT_TRUE(InitBatchBuffer(vaddr, size, true));
magma_buffer_t command_buffer;
ASSERT_EQ(magma_create_command_buffer(connection_, PAGE_SIZE, &command_buffer), 0);
EXPECT_TRUE(InitCommandBuffer(command_buffer, batch_buffer, size, false));
magma_submit_command_buffer(connection_, command_buffer, context_id_);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
magma_release_connection(connection_);
connection_ = nullptr;
}
private:
magma_connection_t connection_;
uint32_t context_id_;
};
} // namespace
TEST(HangRecovery, Test)
{
std::unique_ptr<TestConnection> test;
test.reset(new TestConnection());
test->SubmitCommandBuffer(TestConnection::NORMAL);
test.reset(new TestConnection());
test->SubmitCommandBuffer(TestConnection::FAULT);
test.reset(new TestConnection());
test->SubmitCommandBuffer(TestConnection::NORMAL);
test.reset(new TestConnection());
test->SubmitCommandBuffer(TestConnection::HANG);
test.reset(new TestConnection());
test->SubmitCommandBuffer(TestConnection::NORMAL);
}
TEST(HangRecovery, DISABLED_Stress) { TestConnection::Stress(1000); }
TEST(HangRecovery, SubmitAndDisconnect) { TestConnection().SubmitAndDisconnect(); }