| // Copyright 2018 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 <lib/magma_service/test_util/platform_device_helper.h> |
| #include <lib/magma_service/test_util/platform_msd_device_helper.h> |
| |
| #include <chrono> |
| #include <thread> |
| |
| #include <gtest/gtest.h> |
| |
| #include "magma_vendor_queries.h" |
| #include "src/graphics/drivers/msd-vsi-vip/src/address_space.h" |
| #include "src/graphics/drivers/msd-vsi-vip/src/address_space_layout.h" |
| #include "src/graphics/drivers/msd-vsi-vip/src/instructions.h" |
| #include "src/graphics/drivers/msd-vsi-vip/src/msd_vsi_device.h" |
| |
| namespace { |
| |
| const size_t kPageSize = zx_system_get_page_size(); |
| |
| } // namespace |
| |
| // These tests are unit testing the functionality of MsdVsiDevice. |
| // All of these tests instantiate the device in test mode, that is without the device thread active. |
| class MsdVsiDeviceTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| constexpr bool kEnableSuspend = true; |
| device_ = MsdVsiDevice::Create(GetTestDeviceHandle(), kEnableSuspend); |
| EXPECT_NE(device_, nullptr); |
| } |
| |
| protected: |
| std::unique_ptr<MsdVsiDevice> device_; // Device should be destroyed last. |
| }; |
| |
| TEST_F(MsdVsiDeviceTest, CreateAndDestroy) {} |
| |
| TEST_F(MsdVsiDeviceTest, Shutdown) { |
| device_->StartDeviceThread(); |
| EXPECT_TRUE(device_->Shutdown()); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, DeviceId) { EXPECT_TRUE(device_->IsValidDeviceId()); } |
| |
| TEST_F(MsdVsiDeviceTest, ChipIdentity) { |
| magma_vsi_vip_chip_identity identity; |
| ASSERT_EQ(MAGMA_STATUS_OK, device_->ChipIdentity(&identity)); |
| EXPECT_GT(identity.chip_model, 0u); |
| EXPECT_GT(identity.chip_revision, 0u); |
| EXPECT_GT(identity.chip_date, 0u); |
| EXPECT_GT(identity.product_id, 0u); |
| |
| // Now try to get it as a buffer. |
| zx::vmo identity_buffer; |
| EXPECT_EQ(MAGMA_STATUS_OK, |
| device_->MsdQuery(kMsdVsiVendorQueryChipIdentity, &identity_buffer, nullptr)); |
| magma_vsi_vip_chip_identity identity_from_buf; |
| auto buffer = magma::PlatformBuffer::Import(std::move(identity_buffer)); |
| EXPECT_TRUE(buffer); |
| EXPECT_TRUE(buffer->Read(&identity_from_buf, 0, sizeof(identity_from_buf))); |
| |
| EXPECT_EQ(0, memcmp(&identity, &identity_from_buf, sizeof(identity_from_buf))); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, QueryBadId) { |
| uint64_t result; |
| EXPECT_NE(MAGMA_STATUS_OK, device_->MsdQuery(0xabcd1234 /* id */, nullptr, &result)); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, ChipOption) { |
| magma_vsi_vip_chip_option option; |
| ASSERT_EQ(MAGMA_STATUS_OK, device_->ChipOption(&option)); |
| |
| // Now try to get it as a buffer. |
| zx::vmo option_buffer; |
| EXPECT_EQ(MAGMA_STATUS_OK, |
| device_->MsdQuery(kMsdVsiVendorQueryChipOption, &option_buffer, nullptr)); |
| magma_vsi_vip_chip_option option_from_buf; |
| auto buffer = magma::PlatformBuffer::Import(std::move(option_buffer)); |
| EXPECT_TRUE(buffer); |
| EXPECT_TRUE(buffer->Read(&option_from_buf, 0, sizeof(option_from_buf))); |
| |
| EXPECT_EQ(0, memcmp(&option, &option_from_buf, sizeof(option_from_buf))); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, QuerySram) { |
| if (!device_->HasAxiSram()) { |
| GTEST_SKIP(); |
| } |
| zx::vmo sram_buffer; |
| EXPECT_EQ(MAGMA_STATUS_OK, |
| device_->MsdQuery(kMsdVsiVendorQueryExternalSram, &sram_buffer, nullptr)); |
| |
| auto buffer = magma::PlatformBuffer::Import(std::move(sram_buffer)); |
| ASSERT_TRUE(buffer); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, FetchEngineDma) { |
| constexpr uint32_t kPageCount = 1; |
| |
| EXPECT_TRUE(device_->IsIdle()); |
| |
| std::unique_ptr<magma::PlatformBuffer> buffer = |
| magma::PlatformBuffer::Create(kPageSize * kPageCount, "test"); |
| ASSERT_NE(buffer, nullptr); |
| |
| auto bus_mapping = device_->GetBusMapper()->MapPageRangeBus(buffer.get(), 0, kPageCount); |
| ASSERT_NE(bus_mapping, nullptr); |
| |
| uint32_t length = 0; |
| { |
| uint32_t* cmd_ptr; |
| ASSERT_TRUE(buffer->MapCpu(reinterpret_cast<void**>(&cmd_ptr))); |
| |
| cmd_ptr[length++] = (2 << 27); // end |
| |
| EXPECT_TRUE(buffer->UnmapCpu()); |
| EXPECT_TRUE(buffer->CleanCache(0, kPageSize * kPageCount, false)); |
| } |
| |
| length *= sizeof(uint32_t); |
| uint16_t prefetch = 0; |
| |
| EXPECT_TRUE(device_->SubmitCommandBufferNoMmu(bus_mapping->Get()[0], length, &prefetch)); |
| EXPECT_EQ(magma::round_up(length, static_cast<uint32_t>(sizeof(uint64_t))) / sizeof(uint64_t), |
| prefetch); |
| |
| constexpr uint32_t kTimeoutMs = 100; |
| EXPECT_TRUE(device_->WaitUntilIdle(kTimeoutMs)); |
| |
| // This test accesses registers directly, need to ensure device is powered on. |
| device_->PowerOn(); |
| |
| auto dma_addr = registers::DmaAddress::Get().ReadFrom(device_->register_io()); |
| EXPECT_EQ(dma_addr.reg_value(), bus_mapping->Get()[0] + prefetch * sizeof(uint64_t)); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, LoadAddressSpace) { |
| class AddressSpaceOwner : public AddressSpace::Owner { |
| public: |
| AddressSpaceOwner(magma::PlatformBusMapper* bus_mapper) : bus_mapper_(bus_mapper) {} |
| |
| magma::PlatformBusMapper* GetBusMapper() override { return bus_mapper_; } |
| |
| void AddressSpaceReleased(AddressSpace* address_space) override {} |
| |
| private: |
| magma::PlatformBusMapper* bus_mapper_; |
| }; |
| |
| // Make sure the automatically created device is destructed, so that registering |
| // interrupts does not fail. |
| device_ = nullptr; |
| |
| // Ensure we can do this > once |
| for (uint32_t i = 0; i < 2; i++) { |
| constexpr bool kEnableSuspend = true; |
| std::unique_ptr<MsdVsiDevice> device = |
| MsdVsiDevice::Create(GetTestDeviceHandle(), kEnableSuspend); |
| ASSERT_NE(device, nullptr); |
| |
| EXPECT_TRUE(device->IsIdle()); |
| |
| AddressSpaceOwner owner(device->GetBusMapper()); |
| |
| static constexpr uint32_t kAddressSpaceIndex = 1; |
| |
| std::unique_ptr<AddressSpace> address_space = AddressSpace::Create(&owner, kAddressSpaceIndex); |
| ASSERT_NE(device, nullptr); |
| |
| device->page_table_arrays()->AssignAddressSpace(kAddressSpaceIndex, address_space.get()); |
| |
| // Switch to the address space with a command buffer. |
| |
| static constexpr uint32_t kPageCount = 1; |
| |
| std::unique_ptr<magma::PlatformBuffer> buffer = |
| magma::PlatformBuffer::Create(kPageSize * kPageCount, "test"); |
| ASSERT_NE(buffer, nullptr); |
| |
| auto bus_mapping = device->GetBusMapper()->MapPageRangeBus(buffer.get(), 0, kPageCount); |
| ASSERT_NE(bus_mapping, nullptr); |
| |
| uint32_t length = 0; |
| { |
| uint32_t* cmd_ptr; |
| ASSERT_TRUE(buffer->MapCpu(reinterpret_cast<void**>(&cmd_ptr))); |
| |
| cmd_ptr[length++] = |
| (1 << 27) // load state |
| | (1 << 16) // count |
| | (registers::MmuPageTableArrayConfig::Get().addr() >> 2); // register to be written |
| cmd_ptr[length++] = kAddressSpaceIndex; |
| cmd_ptr[length++] = (2 << 27); // end |
| |
| EXPECT_TRUE(buffer->UnmapCpu()); |
| EXPECT_TRUE(buffer->CleanCache(0, kPageSize * kPageCount, false)); |
| } |
| |
| length *= sizeof(uint32_t); |
| uint16_t prefetch = 0; |
| |
| EXPECT_TRUE(device->SubmitCommandBufferNoMmu(bus_mapping->Get()[0], length, &prefetch)); |
| EXPECT_EQ(magma::round_up(length, static_cast<uint32_t>(sizeof(uint64_t))) / sizeof(uint64_t), |
| prefetch); |
| |
| constexpr uint32_t kTimeoutMs = 100; |
| EXPECT_TRUE(device->WaitUntilIdle(kTimeoutMs)); |
| |
| // This test accesses registers directly, need to ensure device is powered on. |
| device->PowerOn(); |
| |
| auto dma_addr = registers::DmaAddress::Get().ReadFrom(device->register_io()); |
| EXPECT_EQ(dma_addr.reg_value(), bus_mapping->Get()[0] + prefetch * sizeof(uint64_t)); |
| |
| device->page_table_arrays()->Enable(device->register_io(), true); |
| } |
| } |
| |
| TEST_F(MsdVsiDeviceTest, Connections) { |
| std::vector<std::unique_ptr<MsdVsiConnection>> connections; |
| for (uint32_t i = 0; i < PageTableArrays::size(); i++) { |
| auto connection = device_->OpenVsiConnection(i); |
| EXPECT_TRUE(connection); |
| EXPECT_EQ(connection->client_id(), i); |
| connections.push_back(std::move(connection)); |
| } |
| // Reached the limit |
| auto connection = device_->MsdOpen(0); |
| EXPECT_FALSE(connection); |
| connections.clear(); |
| // Ok to create more now |
| connection = device_->MsdOpen(0); |
| EXPECT_TRUE(connection); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, RingbufferCanHoldMaxEvents) { |
| // The ringbuffer starts off with a WAIT-LINK instruction, so subtract this from the total space. |
| uint32_t wait_link_size = 2 * kInstructionDwords * sizeof(uint32_t); |
| uint32_t available_space = AddressSpaceLayout::ringbuffer_size() - wait_link_size; |
| uint32_t max_used_space = |
| MsdVsiDevice::kRbMaxInstructionsPerEvent * sizeof(uint64_t) * MsdVsiDevice::kNumEvents; |
| ASSERT_GE(available_space, max_used_space); |
| } |
| |
| TEST_F(MsdVsiDeviceTest, PulseEater) { |
| // This test accesses registers directly, need to ensure device is powered on. |
| device_->PowerOn(); |
| |
| uint32_t pulse_eater = device_->register_io()->Read32(0x10C); |
| EXPECT_TRUE(pulse_eater & (1 << 18)) << "missing performance fix"; |
| } |
| |
| class FakePlatformDevice : public MsdVsiPlatformDevice { |
| public: |
| FakePlatformDevice() : MsdVsiPlatformDevice(nullptr) {} |
| |
| std::optional<uint64_t> GetExternalSramPhysicalBase() const override { return std::nullopt; } |
| magma_status_t ResetPower() override { return MAGMA_STATUS_OK; } |
| }; |
| |
| TEST_F(MsdVsiDeviceTest, UnmapInvalidSram) { |
| if (!device_->HasAxiSram()) { |
| GTEST_SKIP(); |
| } |
| auto fake_platform_device = std::make_unique<FakePlatformDevice>(); |
| device_->platform_device_ = std::move(fake_platform_device); |
| ASSERT_NE(device_->QuerySram(nullptr), MAGMA_STATUS_OK); |
| ASSERT_FALSE(device_->external_sram_->UnmapCpu()); // Should already be unmapped. |
| } |