blob: a910fc0d1461e5986d75e8e45f257e1f283474c6 [file] [log] [blame]
// Copyright 2019 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 "src/graphics/display/drivers/aml-canvas/aml-canvas.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/fake-bti/bti.h>
#include <vector>
#include <gtest/gtest.h>
#include <mock-mmio-range/mock-mmio-range.h>
#include "src/graphics/display/drivers/aml-canvas/dmc-regs.h"
#include "src/lib/testing/predicates/status.h"
namespace aml_canvas {
namespace {
constexpr uint32_t kVmoTestSize = PAGE_SIZE;
constexpr fuchsia_hardware_amlogiccanvas::wire::CanvasInfo test_canvas_info = {
.height = 240,
.stride_bytes = 16,
.blkmode = fuchsia_hardware_amlogiccanvas::CanvasBlockMode::kLinear,
};
constexpr fuchsia_hardware_amlogiccanvas::wire::CanvasInfo invalid_canvas_info = {
.height = 240,
.stride_bytes = 15,
.blkmode = fuchsia_hardware_amlogiccanvas::CanvasBlockMode::kLinear,
};
// Register addresses from the A311D datasgeet section 13.1.2 "DDR" >
// "Register description". DMC is typo-ed as DC.
constexpr int kCanvasLutDataLowOffset = 0x0012 * 4;
constexpr int kCanvasLutDataHighOffset = 0x0013 * 4;
constexpr int kCanvasLutAddressOffset = 0x0014 * 4;
class AmlCanvasTest : public testing::Test {
public:
void SetUp() override {
logger_ = std::make_unique<fdf::Logger>("aml-canvas-test", FUCHSIA_LOG_INFO, zx::socket{},
fidl::WireClient<fuchsia_logger::LogSink>{});
fdf::Logger::SetGlobalInstance(logger_.get());
zx::bti bti;
EXPECT_OK(fake_bti_create(bti.reset_and_get_address()));
auto endpoints = fidl::Endpoints<fuchsia_hardware_amlogiccanvas::Device>::Create();
// TODO(136015): This test should invoke ::Create(), which requires a FakePDevFidl.
canvas_ = std::make_unique<AmlCanvas>(mmio_range_.GetMmioBuffer(), std::move(bti),
inspect::Inspector{});
binding_.emplace(fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(endpoints.server),
canvas_.get(), fidl::kIgnoreBindingClosure);
canvas_client_.Bind(std::move(endpoints.client));
}
void TearDown() override { mmio_range_.CheckAllAccessesReplayed(); }
// Because this test is using a fidl::WireSyncClient, we need to run any ops on the client on
// their own thread because the testing thread is shared with the client end.
static void RunSyncClientTask(fit::closure task) {
async::Loop loop{&kAsyncLoopConfigNeverAttachToThread};
loop.StartThread();
zx::result result = fdf::RunOnDispatcherSync(loop.dispatcher(), std::move(task));
ASSERT_EQ(ZX_OK, result.status_value());
}
zx_status_t CreateNewCanvas() {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(kVmoTestSize, 0, &vmo));
zx_status_t status{ZX_OK};
RunSyncClientTask([&]() {
fidl::WireResult result = canvas_client_->Config(std::move(vmo), 0, test_canvas_info);
if (!result.ok()) {
status = result.error().status();
} else if (result->is_error()) {
status = result->error_value();
} else {
canvas_indices_.push_back(result->value()->canvas_idx);
}
});
return status;
}
zx_status_t CreateNewCanvasInvalid() {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(kVmoTestSize, 0, &vmo));
zx_status_t status = ZX_OK;
RunSyncClientTask([&]() {
fidl::WireResult result = canvas_client_->Config(std::move(vmo), 0, invalid_canvas_info);
if (!result.ok()) {
status = result.error().status();
} else if (result->is_error()) {
status = result->error_value();
}
});
// We should be returning an error since we are creating an invalid canvas.
EXPECT_NE(ZX_OK, status);
return status;
}
zx_status_t FreeCanvas(uint8_t index) {
auto it = std::find(canvas_indices_.begin(), canvas_indices_.end(), index);
if (it != canvas_indices_.end()) {
canvas_indices_.erase(it);
}
zx_status_t status{ZX_OK};
RunSyncClientTask([&]() {
fidl::WireResult result = canvas_client_->Free(index);
if (!result.ok()) {
status = result.error().status();
} else if (result->is_error()) {
status = result->error_value();
}
});
return status;
}
zx_status_t FreeAllCanvases() {
zx_status_t status{ZX_OK};
while (!canvas_indices_.empty()) {
uint8_t index = canvas_indices_.back();
canvas_indices_.pop_back();
RunSyncClientTask([&]() {
auto result = canvas_client_->Free(index);
if (!result.ok()) {
status = result.error().status();
} else if (result->is_error()) {
status = result->error_value();
};
});
if (status != ZX_OK) {
return status;
}
}
return status;
}
void SetRegisterExpectations() {
// TODO(costan): Remove the read expectations when we get rid of the unnecessary R/M/W.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kCanvasLutDataLowOffset, .value = CanvasLutDataLowValue(), .write = true},
{.address = kCanvasLutDataHighOffset, .value = CanvasLutDataHighValue(), .write = true},
{.address = kCanvasLutAddressOffset,
.value = CanvasLutAddrValue(NextCanvasIndex()),
.write = true},
{.address = kCanvasLutDataHighOffset, .value = CanvasLutDataHighValue()},
}));
}
void SetRegisterExpectations(uint8_t index) {
// TODO(costan): Remove the read expectations when we get rid of the unnecessary R/M/W.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kCanvasLutDataLowOffset, .value = CanvasLutDataLowValue(), .write = true},
{.address = kCanvasLutDataHighOffset, .value = CanvasLutDataHighValue(), .write = true},
{.address = kCanvasLutAddressOffset, .value = CanvasLutAddrValue(index), .write = true},
{.address = kCanvasLutDataHighOffset, .value = CanvasLutDataHighValue()},
}));
}
private:
uint8_t NextCanvasIndex() { return static_cast<uint8_t>(canvas_indices_.size()); }
uint32_t CanvasLutDataLowValue() {
auto data_low = CanvasLutDataLow::Get().FromValue(0);
data_low.SetDmcCavWidth(test_canvas_info.stride_bytes >> 3);
data_low.set_dmc_cav_addr(FAKE_BTI_PHYS_ADDR >> 3);
return data_low.reg_value();
}
uint32_t CanvasLutDataHighValue() {
auto data_high = CanvasLutDataHigh::Get().FromValue(0);
data_high.SetDmcCavWidth(test_canvas_info.stride_bytes >> 3);
data_high.set_dmc_cav_height(test_canvas_info.height);
data_high.set_dmc_cav_blkmode(static_cast<uint32_t>(test_canvas_info.blkmode));
data_high.set_dmc_cav_xwrap(
test_canvas_info.flags & fuchsia_hardware_amlogiccanvas::CanvasFlags::kWrapHorizontal ? 1
: 0);
data_high.set_dmc_cav_ywrap(
test_canvas_info.flags & fuchsia_hardware_amlogiccanvas::CanvasFlags::kWrapVertical ? 1
: 0);
data_high.set_dmc_cav_endianness(static_cast<uint32_t>(test_canvas_info.endianness));
return data_high.reg_value();
}
uint32_t CanvasLutAddrValue(uint8_t index) {
auto lut_addr = CanvasLutAddr::Get().FromValue(0);
lut_addr.set_dmc_cav_addr_index(index);
lut_addr.set_dmc_cav_addr_wr(1);
return lut_addr.reg_value();
}
fdf_testing::DriverRuntime runtime_;
std::unique_ptr<fdf::Logger> logger_;
std::vector<uint8_t> canvas_indices_;
constexpr static int kMmioRangeSize = 0x100;
ddk_mock::MockMmioRange mmio_range_{kMmioRangeSize, ddk_mock::MockMmioRange::Size::k32};
// Note, set up to execute on testing thread.
std::unique_ptr<AmlCanvas> canvas_;
std::optional<fidl::ServerBinding<fuchsia_hardware_amlogiccanvas::Device>> binding_;
fidl::WireSyncClient<fuchsia_hardware_amlogiccanvas::Device> canvas_client_;
};
TEST_F(AmlCanvasTest, CanvasConfigFreeSingle) {
SetRegisterExpectations();
EXPECT_OK(CreateNewCanvas());
EXPECT_OK(FreeAllCanvases());
}
TEST_F(AmlCanvasTest, CanvasConfigFreeMultipleSequential) {
// Create 5 canvases in sequence and verify that their indices are 0 through 4.
for (int i = 0; i < 5; i++) {
SetRegisterExpectations();
EXPECT_OK(CreateNewCanvas());
}
// Free all 5 canvases created above.
EXPECT_OK(FreeAllCanvases());
}
TEST_F(AmlCanvasTest, CanvasConfigFreeMultipleInterleaved) {
// Create 5 canvases in sequence.
for (int i = 0; i < 5; i++) {
SetRegisterExpectations();
EXPECT_OK(CreateNewCanvas());
}
// Free canvas index 1, so the next one created has index 1.
EXPECT_OK(FreeCanvas(1));
SetRegisterExpectations(1);
EXPECT_OK(CreateNewCanvas());
// Free canvas index 3, so the next one created has index 3.
EXPECT_OK(FreeCanvas(3));
SetRegisterExpectations(3);
EXPECT_OK(CreateNewCanvas());
EXPECT_OK(FreeAllCanvases());
}
TEST_F(AmlCanvasTest, CanvasFreeInvalidIndex) {
// Free a canvas without having created any.
EXPECT_EQ(FreeCanvas(0), ZX_ERR_INVALID_ARGS);
}
TEST_F(AmlCanvasTest, CanvasConfigMaxLimit) {
// Create canvases until the look-up table is full.
for (size_t i = 0; i < kNumCanvasEntries; i++) {
SetRegisterExpectations();
EXPECT_OK(CreateNewCanvas());
}
// Try to create another canvas, and verify that it fails.
EXPECT_EQ(CreateNewCanvas(), ZX_ERR_NOT_FOUND);
EXPECT_OK(FreeAllCanvases());
}
TEST_F(AmlCanvasTest, CanvasConfigUnaligned) {
// Try to create a canvas with unaligned fuchsia_hardware_amlogiccanvas::wire::CanvasInfo width,
// and verify that it fails.
EXPECT_EQ(CreateNewCanvasInvalid(), ZX_ERR_INVALID_ARGS);
}
} // namespace
} // namespace aml_canvas