| // 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 <array> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/graphics/drivers/msd-vsi-vip/src/address_space_layout.h" |
| #include "src/graphics/drivers/msd-vsi-vip/src/command_buffer.h" |
| #include "test_command_buffer.h" |
| |
| class TestDeviceDump : public TestCommandBuffer { |
| protected: |
| 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); |
| } |
| |
| const char* data() const { return buf_.data(); } |
| |
| private: |
| std::vector<char> buf_; |
| }; |
| |
| // Creates the buffer and command buffer from |buffer_desc| and |sequence_number|. |
| void CreateCommandBuffer(std::shared_ptr<MsdVsiContext> context, const BufferDesc& buffer_desc, |
| uint32_t sequence_number, std::shared_ptr<MsdVsiBuffer>* out_buffer, |
| std::unique_ptr<CommandBuffer>* out_batch); |
| |
| // Returns whether |match_strings| are present in the same order in |dump_string|. |
| static bool FindStrings(const std::vector<std::string>& dump_string, |
| const std::vector<FormattedString>& match_strings) { |
| DASSERT(match_strings.size() > 0); |
| |
| unsigned int num_matched = 0; |
| for (auto& str : dump_string) { |
| if (strstr(str.c_str(), match_strings[num_matched].data())) { |
| num_matched++; |
| if (num_matched == match_strings.size()) { |
| return true; |
| } |
| } |
| } |
| DMESSAGE("Could not find %s, dump contains: \n", match_strings[num_matched].data()); |
| for (auto& str : dump_string) { |
| DMESSAGE("%s", str.c_str()); |
| } |
| return false; |
| } |
| }; |
| |
| void TestDeviceDump::CreateCommandBuffer(std::shared_ptr<MsdVsiContext> context, |
| const BufferDesc& buffer_desc, uint32_t sequence_number, |
| std::shared_ptr<MsdVsiBuffer>* out_buffer, |
| std::unique_ptr<CommandBuffer>* out_batch) { |
| std::shared_ptr<MsdVsiBuffer> buffer; |
| ASSERT_NO_FATAL_FAILURE(CreateAndMapBuffer( |
| context, buffer_desc.buffer_size, buffer_desc.map_page_count, buffer_desc.gpu_addr, &buffer)); |
| |
| std::unique_ptr<CommandBuffer> batch; |
| ASSERT_NO_FATAL_FAILURE(CreateAndPrepareBatch(context, buffer, buffer_desc.data_size, |
| buffer_desc.batch_offset, nullptr /* signal */, |
| std::nullopt /* csb */, &batch)); |
| ASSERT_TRUE(batch->IsValidBatch()); |
| batch->SetSequenceNumber(sequence_number); |
| |
| *out_buffer = buffer; |
| *out_batch = std::move(batch); |
| } |
| |
| TEST_F(TestDeviceDump, DumpBasic) { |
| MsdVsiDevice::DumpState dump_state; |
| device_->Dump(&dump_state, false /* fault_present */); |
| EXPECT_EQ(dump_state.last_completed_sequence_number, 0u); |
| EXPECT_EQ(dump_state.last_submitted_sequence_number, 0u); |
| EXPECT_TRUE(dump_state.idle); |
| EXPECT_FALSE(dump_state.page_table_arrays_enabled); |
| EXPECT_TRUE(dump_state.inflight_batches.empty()); |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| // The exec address should only be printed after the page table arrays have been enabled. |
| std::vector<FormattedString> match_strings = { |
| FormattedString("idle: true"), |
| FormattedString("current_execution_address: N/A", dump_state.exec_addr), |
| FormattedString("No mmu exception detected")}; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| |
| dump_state.idle = false; |
| dump_state.page_table_arrays_enabled = true; |
| dump_state.exec_addr = 0x10000; |
| |
| dump_state.fault_present = true; |
| dump_state.fault_type = 2; |
| dump_state.fault_gpu_address = 0x1234; |
| |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| { |
| std::vector<FormattedString> match_strings = { |
| FormattedString("idle: false"), |
| FormattedString("current_execution_address: 0x%x", dump_state.exec_addr), |
| FormattedString("MMU EXCEPTION DETECTED\n" |
| "type 0x2 (page not present) npu_address 0x1234")}; |
| |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |
| } |
| |
| TEST_F(TestDeviceDump, DumpCommandBuffer) { |
| BufferDesc desc = {.buffer_size = 0x2000, |
| .map_page_count = 2, |
| .data_size = 0x1000, |
| .batch_offset = 0x0, |
| .gpu_addr = 0x10000}; |
| constexpr uint32_t seq_num = 1; |
| |
| std::shared_ptr<MsdVsiBuffer> buf; |
| std::unique_ptr<CommandBuffer> batch; |
| ASSERT_NO_FATAL_FAILURE(CreateCommandBuffer(default_context(), desc, seq_num, &buf, &batch)); |
| |
| uint32_t event; |
| EXPECT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event)); |
| EXPECT_TRUE(device_->WriteInterruptEvent(event, std::move(batch), default_address_space())); |
| |
| MsdVsiDevice::DumpState dump_state; |
| device_->Dump(&dump_state, false /* fault_present */); |
| |
| // Set the exec address to lie within the batch buffer. |
| dump_state.exec_addr = 0x10000; |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("Exec NPU Address 0x%lx", dump_state.exec_addr), |
| }; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| |
| // Should not see any fault information. |
| match_strings = { |
| FormattedString("FAULTING BATCH"), |
| }; |
| ASSERT_FALSE(FindStrings(dump_string, match_strings)); |
| } |
| |
| TEST_F(TestDeviceDump, DumpCommandBufferWithFault) { |
| // Add some in-flight batches at different gpu addresses. |
| BufferDesc desc1 = {.buffer_size = 0x1000, |
| .map_page_count = 1, |
| .data_size = 0x10, |
| .batch_offset = 0x0, |
| .gpu_addr = 0x10000}; |
| BufferDesc desc2 = {.buffer_size = 0x2000, |
| .map_page_count = 2, |
| .data_size = 0x10, |
| .batch_offset = 0x1000, |
| .gpu_addr = 0x20000}; |
| |
| constexpr uint32_t seq_num1 = 10; |
| constexpr uint32_t seq_num2 = 11; |
| |
| std::shared_ptr<MsdVsiBuffer> buf1; |
| std::unique_ptr<CommandBuffer> batch1; |
| ASSERT_NO_FATAL_FAILURE(CreateCommandBuffer(default_context(), desc1, seq_num1, &buf1, &batch1)); |
| |
| std::shared_ptr<MsdVsiBuffer> buf2; |
| std::unique_ptr<CommandBuffer> batch2; |
| ASSERT_NO_FATAL_FAILURE(CreateCommandBuffer(default_context(), desc2, seq_num2, &buf2, &batch2)); |
| |
| uint32_t event1; |
| uint32_t event2; |
| EXPECT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event1)); |
| EXPECT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event2)); |
| // Write the event numbers in opposite order to the batch sequence numbers to verify the |
| // batches are still outputted in the correct order. |
| EXPECT_TRUE(device_->WriteInterruptEvent(event2, std::move(batch1), default_address_space())); |
| EXPECT_TRUE(device_->WriteInterruptEvent(event1, std::move(batch2), default_address_space())); |
| |
| MsdVsiDevice::DumpState dump_state; |
| device_->Dump(&dump_state, true /* fault_present */); |
| // Set the exec address to lie within the second batch buffer. |
| dump_state.exec_addr = 0x20004; |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("Batch %lu (Command)", seq_num1), |
| FormattedString("Exec NPU Address 0x%lx", desc1.gpu_addr + desc1.batch_offset), |
| FormattedString("buffer 0x%lx", buf1->platform_buffer()->id()), |
| FormattedString("Batch %lu (Command)", seq_num2), |
| FormattedString("FAULTING BATCH"), |
| FormattedString("Exec NPU Address 0x%lx", desc2.gpu_addr + desc2.batch_offset), |
| FormattedString("buffer 0x%lx", buf2->platform_buffer()->id()), |
| }; |
| |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |
| |
| TEST_F(TestDeviceDump, DumpEventBatch) { |
| const uint64_t seq_num = 1; |
| |
| std::vector<std::shared_ptr<magma::PlatformSemaphore>> wait_semaphores; |
| std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores; |
| auto batch = std::make_unique<EventBatch>(default_context(), std::move(wait_semaphores), |
| std::move(signal_semaphores)); |
| batch->SetSequenceNumber(seq_num); |
| |
| uint32_t event; |
| EXPECT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event)); |
| EXPECT_TRUE(device_->WriteInterruptEvent(event, std::move(batch), default_address_space())); |
| |
| MsdVsiDevice::DumpState dump_state; |
| device_->Dump(&dump_state, false /* fault_present */); |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("Batch %lu (Event)", seq_num), |
| }; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |
| |
| TEST_F(TestDeviceDump, DumpCommandBufferMultipleResources) { |
| // Create one command buffer with three resources. |
| constexpr uint32_t kResourcesCount = 3; |
| |
| BufferDesc desc1 = {.buffer_size = 0x2000, |
| .map_page_count = 1, |
| .data_size = 0x1000, |
| .batch_offset = 0x0, |
| .gpu_addr = 0x20000}; |
| BufferDesc desc2 = {.buffer_size = 0x2000, |
| .map_page_count = 1, |
| .data_size = 0x1000, |
| .batch_offset = 0x0, |
| .gpu_addr = 0x40000}; |
| BufferDesc desc3 = {.buffer_size = 0x2000, |
| .map_page_count = 1, |
| .data_size = 0x1000, |
| .batch_offset = 0x0, |
| .gpu_addr = 0x30000}; |
| |
| std::array<BufferDesc, kResourcesCount> descs = {desc1, desc2, desc3}; |
| |
| std::array<std::shared_ptr<MsdVsiBuffer>, kResourcesCount> bufs; |
| |
| for (unsigned int i = 0; i < kResourcesCount; i++) { |
| const auto& desc = descs[i]; |
| ASSERT_NO_FATAL_FAILURE(CreateAndMapBuffer(default_context(), desc.buffer_size, |
| desc.map_page_count, desc.gpu_addr, &bufs[i])); |
| } |
| |
| auto command_buffer = std::make_unique<msd::magma_command_buffer>(msd::magma_command_buffer{ |
| .resource_count = kResourcesCount, |
| .batch_buffer_resource_index = 0, |
| .batch_start_offset = 0, |
| .wait_semaphore_count = 0, |
| .signal_semaphore_count = 0, |
| .flags = 0, |
| }); |
| auto batch = std::make_unique<CommandBuffer>(default_context(), 0, std::move(command_buffer)); |
| ASSERT_NE(batch, nullptr); |
| |
| std::vector<CommandBuffer::ExecResource> resources; |
| for (unsigned int i = 0; i < kResourcesCount; i++) { |
| resources.emplace_back( |
| CommandBuffer::ExecResource{.buffer = bufs[i], .offset = 0, .length = descs[i].data_size}); |
| } |
| |
| std::vector<std::shared_ptr<magma::PlatformSemaphore>> wait_semaphores; |
| std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores; |
| ASSERT_TRUE(batch->InitializeResources(std::move(resources), std::move(wait_semaphores), |
| std::move(signal_semaphores))); |
| ASSERT_TRUE(batch->PrepareForExecution()); |
| |
| std::vector<GpuMappingView*> mappings; |
| batch->GetMappings(&mappings); |
| |
| uint32_t event; |
| EXPECT_TRUE(device_->AllocInterruptEvent(true /* free_on_complete */, &event)); |
| EXPECT_TRUE(device_->WriteInterruptEvent(event, std::move(batch), default_address_space())); |
| |
| MsdVsiDevice::DumpState dump_state; |
| device_->Dump(&dump_state, true /* fault_present */); |
| |
| // Set the fault address to lie within the third resource. |
| dump_state.fault_gpu_address = descs[2].gpu_addr; |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("Fault address appears to be within mapping %p", mappings[2]), |
| }; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| |
| // Set the fault address to lie past the end of the second resource. |
| dump_state.fault_gpu_address = 0x50000; |
| |
| dump_string.clear(); |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| uint32_t mapping_end = descs[1].gpu_addr + descs[1].map_page_count * magma::page_size(); |
| match_strings = { |
| FormattedString("Fault address is 0x%lx past the end of mapping %p", |
| dump_state.fault_gpu_address - mapping_end, mappings[1]), |
| }; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |
| |
| // Tests the decoding for each instruction type. |
| TEST_F(TestDeviceDump, DumpDecodedBuffer) { |
| constexpr uint32_t link_addr = 0x10000; |
| |
| constexpr uint32_t buf_size_dwords = 7 * kInstructionDwords; |
| uint32_t buf[buf_size_dwords]; |
| BufferWriter buf_writer(buf, buf_size_dwords * sizeof(uint32_t), 0); |
| |
| MiLink::write(&buf_writer, 8 /* prefetch */, link_addr); |
| MiWait::write(&buf_writer); |
| MiLoadState::write(&buf_writer, 1, 2); |
| MiEvent::write(&buf_writer, 1); |
| MiSemaphore::write(&buf_writer, 1, 2, 3); |
| MiStall::write(&buf_writer, 1, 2, 3); |
| MiEnd::write(&buf_writer); |
| |
| std::vector<std::string> dump_string; |
| device_->DumpDecodedBuffer(&dump_string, buf, buf_size_dwords, 0 /* start_dword */, |
| buf_size_dwords /* dword_count */, 4 /* active_head_dword */); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("LINK"), FormattedString("%08lx", link_addr), |
| FormattedString("WAIT"), FormattedString("LOAD_STATE"), |
| FormattedString("===>"), // matches active_head_dword |
| FormattedString("EVENT"), FormattedString("SEMAPHORE"), |
| FormattedString("STALL"), FormattedString("END")}; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |
| |
| TEST_F(TestDeviceDump, DumpRingbufferWithWraparound) { |
| // Ringbuffer layout: |
| // SEMAPHORE STALL END ....... EVENT LINK |
| // | | |
| // active_head last_completed_event |
| const uint32_t active_head = AddressSpaceLayout::system_gpu_addr_base() + 0x8; |
| // Start the ringbuffer at 2 instructions from the end. |
| device_->ringbuffer_->Reset(4080); |
| MiEvent::write(device_->ringbuffer_.get(), 1); |
| MiLink::write(device_->ringbuffer_.get(), 8, 0x10000); |
| MiSemaphore::write(device_->ringbuffer_.get(), 1, 2, 3); |
| MiStall::write(device_->ringbuffer_.get(), 1, 2, 3); |
| MiEnd::write(device_->ringbuffer_.get()); |
| // Update the head past the event. |
| device_->ringbuffer_->update_head(4088); |
| |
| MsdVsiDevice::DumpState dump_state; |
| dump_state.exec_addr = active_head; |
| dump_state.page_table_arrays_enabled = true; |
| |
| std::vector<std::string> dump_string; |
| device_->FormatDump(&dump_state, &dump_string); |
| |
| std::vector<FormattedString> match_strings = { |
| FormattedString("LINK"), FormattedString("SEMAPHORE"), FormattedString("STALL"), |
| FormattedString("===>"), // matches active_head_dword |
| FormattedString("END")}; |
| ASSERT_TRUE(FindStrings(dump_string, match_strings)); |
| } |