blob: f071850ed8241baf772b30ddcedc5a428d0b5bb2 [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 <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/display/llcpp/fidl.h>
#include <fuchsia/sysinfo/llcpp/fidl.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/cpp/message.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/pixelformat.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <algorithm>
#include <memory>
#include <ddk/protocol/display/controller.h>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <zxtest/zxtest.h>
#include "lib/fidl/llcpp/vector_view.h"
#include "lib/zx/event.h"
#include "lib/zx/handle.h"
#include "lib/zx/time.h"
#include "zircon/errors.h"
#include "zircon/fidl.h"
#include "zircon/rights.h"
#include "zircon/time.h"
namespace fhd = ::llcpp::fuchsia::hardware::display;
namespace sysinfo = ::llcpp::fuchsia::sysinfo;
namespace sysmem = ::llcpp::fuchsia::sysmem;
namespace {
constexpr uint64_t kEventId = 13;
constexpr uint32_t kCollectionId = 12;
constexpr uint64_t kInvalidId = 34;
class CoreDisplayTest : public zxtest::Test {
public:
void SetUp() override;
void TearDown() override;
void SetCaptureSupported(bool enable) { capture_supported_ = enable; }
bool IsCaptureSupported() { return capture_supported_; }
void ImportEvent();
void CreateToken();
void DuplicateAndImportToken();
void ImportBufferCollection();
void SetBufferConstraints();
void FinalizeClientConstraints();
uint64_t ImportCaptureImage();
zx_status_t StartCapture(uint64_t id, uint64_t e = kEventId);
zx_status_t WaitForEvent();
zx_status_t ReleaseCapture(uint64_t id);
void CaptureSetup();
std::unique_ptr<fhd::Controller::SyncClient> dc_client_;
std::unique_ptr<sysinfo::Device::SyncClient> sysinfo_;
std::unique_ptr<sysmem::Allocator::SyncClient> sysmem_allocator_;
zx::event client_event_;
std::unique_ptr<sysmem::BufferCollectionToken::SyncClient> token_;
std::unique_ptr<sysmem::BufferCollection::SyncClient> collection_;
private:
zx::channel device_client_channel_;
zx::channel dc_client_channel_;
zx::channel sysinfo_client_channel_;
zx::channel sysmem_client_channel_;
fzl::FdioCaller caller_;
fbl::Vector<fhd::Info> displays_;
bool capture_supported_ = false;
};
void CoreDisplayTest::SetUp() {
zx::channel device_server_channel;
fbl::unique_fd fd(open("/dev/class/display-controller/000", O_RDWR));
zx_status_t status = zx::channel::create(0, &device_server_channel, &device_client_channel_);
ASSERT_OK(status);
zx::channel dc_server_channel;
status = zx::channel::create(0, &dc_server_channel, &dc_client_channel_);
ASSERT_OK(status);
caller_.reset(std::move(fd));
auto open_status = fhd::Provider::Call::OpenController(
caller_.channel(), std::move(device_server_channel), std::move(dc_server_channel));
ASSERT_TRUE(open_status.ok());
ASSERT_EQ(ZX_OK, open_status.value().s);
dc_client_ = std::make_unique<fhd::Controller::SyncClient>(std::move(dc_client_channel_));
bool has_display = false;
fbl::Vector<fhd::Info> displays_tmp;
do {
status = dc_client_->HandleEvents(fhd::Controller::EventHandlers{
.displays_changed =
[&displays_tmp, &has_display](fidl::VectorView<fhd::Info> added,
fidl::VectorView<uint64_t> removed) {
for (unsigned i = 0; i < added.count(); i++) {
displays_tmp.push_back(added[i]);
}
has_display = true;
return ZX_OK;
},
.vsync = [](uint64_t display_id, uint64_t timestamp,
fidl::VectorView<uint64_t> images) { return ZX_ERR_NEXT; },
.client_ownership_change = [](bool has_ownership) { return ZX_ERR_NEXT; },
.unknown = []() { return ZX_ERR_STOP; }});
ASSERT_FALSE(status != ZX_OK && status != ZX_ERR_NEXT);
} while (!has_display);
// get sysmem
zx::channel sysmem_server_channel;
status = zx::channel::create(0, &sysmem_server_channel, &sysmem_client_channel_);
ASSERT_OK(status);
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", sysmem_server_channel.release());
ASSERT_OK(status);
sysmem_allocator_ =
std::make_unique<sysmem::Allocator::SyncClient>(std::move(sysmem_client_channel_));
// determine is capture is supported or not
zx::channel sysinfo_server_channel_;
status = zx::channel::create(0, &sysinfo_server_channel_, &sysinfo_client_channel_);
ASSERT_OK(status);
sysinfo_ = std::make_unique<sysinfo::Device::SyncClient>(std::move(sysinfo_client_channel_));
constexpr char kSysInfoPath[] = "/dev/misc/sysinfo";
fbl::unique_fd sysinfo_fd(open(kSysInfoPath, O_RDWR));
if (!sysinfo_fd) {
SetCaptureSupported(false);
return;
}
fzl::FdioCaller caller_sysinfo(std::move(sysinfo_fd));
auto result = sysinfo::Device::Call::GetBoardName(caller_sysinfo.channel());
if (!result.ok() || result.value().status != ZX_OK) {
SetCaptureSupported(false);
return;
}
printf("Found board %s\n", result.value().name.data());
if (!strcmp(result.value().name.data(), "qemu")) {
SetCaptureSupported(true);
} else {
SetCaptureSupported(false);
}
}
void CoreDisplayTest::TearDown() {
if (collection_) {
collection_->Close();
}
sysmem_allocator_.reset();
}
void CoreDisplayTest::ImportEvent() {
// First, import signal event to get notified when capture buffer has valid data
zx::event e2;
auto status = zx::event::create(0, &client_event_);
ASSERT_OK(status);
status = client_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &e2);
ASSERT_OK(status);
auto event_status = dc_client_->ImportEvent(std::move(e2), kEventId);
ASSERT_TRUE(event_status.ok());
}
void CoreDisplayTest::CreateToken() {
// Create token and keep the client
zx::channel token_server;
zx::channel token_client;
auto status = zx::channel::create(0, &token_server, &token_client);
ASSERT_OK(status);
token_ = std::make_unique<sysmem::BufferCollectionToken::SyncClient>(std::move(token_client));
// Pass token server to sysmem allocator
auto alloc_status = sysmem_allocator_->AllocateSharedCollection(std::move(token_server));
ASSERT_TRUE(alloc_status.ok());
}
void CoreDisplayTest::DuplicateAndImportToken() {
// Duplicate the token, to be passed to the display controller
zx::channel token_dup_client;
zx::channel token_dup_server;
auto status = zx::channel::create(0, &token_dup_server, &token_dup_client);
ASSERT_OK(status);
sysmem::BufferCollectionToken::SyncClient display_token(std::move(token_dup_client));
auto dup_res = token_->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_dup_server));
ASSERT_TRUE(dup_res.ok());
ASSERT_OK(dup_res.status());
// sync token
token_->Sync();
auto import_resp = dc_client_->ImportBufferCollection(
kCollectionId, std::move(*display_token.mutable_channel()));
ASSERT_TRUE(import_resp.ok());
ASSERT_OK(import_resp.value().res);
}
void CoreDisplayTest::SetBufferConstraints() {
fhd::ImageConfig image_config = {};
image_config.type = IMAGE_TYPE_CAPTURE;
auto constraints_resp = dc_client_->SetBufferCollectionConstraints(kCollectionId, image_config);
ASSERT_TRUE(constraints_resp.ok());
ASSERT_OK(constraints_resp.value().res);
}
void CoreDisplayTest::FinalizeClientConstraints() {
// now that we have provided all that's needed to the display controllers, we can
// return our token, set our own constraints and for allocation
// Before that, we need to create a channel to communicate with the buffer collection
zx::channel collection_client;
zx::channel collection_server;
auto status = zx::channel::create(0, &collection_server, &collection_client);
ASSERT_OK(status);
auto bind_resp = sysmem_allocator_->BindSharedCollection(std::move(*token_->mutable_channel()),
std::move(collection_server));
ASSERT_OK(bind_resp.status());
// token has been returned. Let's set contraints
sysmem::BufferCollectionConstraints constraints = {};
constraints.usage.cpu = sysmem::cpuUsageReadOften | sysmem::cpuUsageWriteOften;
constraints.min_buffer_count_for_camping = 1;
constraints.has_buffer_memory_constraints = false;
constraints.image_format_constraints_count = 1;
sysmem::ImageFormatConstraints& image_constraints = constraints.image_format_constraints[0];
image_constraints.pixel_format.type = sysmem::PixelFormatType::BGRA32;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] = sysmem::ColorSpace{
.type = sysmem::ColorSpaceType::SRGB,
};
image_constraints.min_coded_width = 0;
image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max();
image_constraints.min_coded_height = 0;
image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.min_bytes_per_row = 0;
image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max();
image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max();
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 1;
image_constraints.coded_height_divisor = 1;
image_constraints.bytes_per_row_divisor = 1;
image_constraints.start_offset_divisor = 1;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
collection_ =
std::make_unique<sysmem::BufferCollection::SyncClient>(std::move(collection_client));
auto collection_resp = collection_->SetConstraints(true, constraints);
ASSERT_OK(collection_resp.status());
// Token return and constraints set. Wait for allocation
auto wait_resp = collection_->WaitForBuffersAllocated();
ASSERT_OK(wait_resp.status());
}
uint64_t CoreDisplayTest::ImportCaptureImage() {
// Make the buffer available for capture
fhd::ImageConfig capture_cfg = {}; // will contain a handle
auto importcap_resp = dc_client_->ImportImageForCapture(capture_cfg, kCollectionId, 0);
if (importcap_resp.status() != ZX_OK) {
return INVALID_ID;
}
return importcap_resp.value().result.response().image_id;
}
zx_status_t CoreDisplayTest::StartCapture(uint64_t id, uint64_t e) {
auto startcap_resp = dc_client_->StartCapture(e, id);
return (startcap_resp.value().result.err());
}
zx_status_t CoreDisplayTest::ReleaseCapture(uint64_t id) {
auto releasecap_resp = dc_client_->ReleaseCapture(id);
return (releasecap_resp.value().result.err());
}
zx_status_t CoreDisplayTest::WaitForEvent() {
uint32_t observed;
auto event_res =
client_event_.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(zx::sec(1)), &observed);
if (event_res == ZX_OK) {
client_event_.signal(ZX_EVENT_SIGNALED, 0);
}
return event_res;
}
void CoreDisplayTest::CaptureSetup() {
// First, import signal event to get notified when capture buffer has valid data
ImportEvent();
CreateToken();
DuplicateAndImportToken();
// Need to set constraints for allocation to occur
SetBufferConstraints();
// Pass back our own token and set our constraints so buffers can be allocated
FinalizeClientConstraints();
}
TEST_F(CoreDisplayTest, CoreDisplayAlreadyBoundTest) {
// Setup connects to display controller. Make sure we can't bound again
fbl::unique_fd fd(open("/dev/class/display-controller/000", O_RDWR));
zx::channel device_server, device_client;
zx_status_t status = zx::channel::create(0, &device_server, &device_client);
ASSERT_EQ(status, ZX_OK);
zx::channel dc_server, dc_client;
status = zx::channel::create(0, &dc_server, &dc_client);
ASSERT_EQ(status, ZX_OK);
fzl::FdioCaller caller(std::move(fd));
auto open_status = fhd::Provider::Call::OpenController(caller.channel(), std::move(device_server),
std::move(dc_server));
EXPECT_TRUE(open_status.ok());
EXPECT_EQ(ZX_ERR_ALREADY_BOUND, open_status.value().s);
}
TEST_F(CoreDisplayTest, CreateLayer) {
auto resp = dc_client_->CreateLayer();
EXPECT_TRUE(resp.ok());
EXPECT_EQ(ZX_OK, resp.value().res);
EXPECT_EQ(1, resp.value().layer_id);
}
TEST_F(CoreDisplayTest, CaptureClientDeadAfterStart) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
// Make the buffer available for capture
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
ASSERT_OK(StartCapture(id));
// close client before capture completes
dc_client_->mutable_channel()->reset();
}
TEST_F(CoreDisplayTest, CaptureFull) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
// Make the buffer available for capture
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
ASSERT_OK(StartCapture(id));
// wait for signal
EXPECT_OK(WaitForEvent());
// stop capture
ASSERT_OK(ReleaseCapture(id));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, MultipleCaptureFull) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
// Make the buffer available for capture
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
for (int i = 0; i < 10; i++) {
ASSERT_OK(StartCapture(id));
// wait for signal
EXPECT_OK(WaitForEvent());
}
// stop capture
ASSERT_OK(ReleaseCapture(id));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, CaptureReleaseAfterStart) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
// Make the buffer available for capture
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
ASSERT_OK(StartCapture(id));
EXPECT_OK(ReleaseCapture(id));
// This will still get delivered
EXPECT_OK(WaitForEvent());
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, InvalidStartCaptureId) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartCapture(kInvalidId));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, InvalidStartEventId) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
// Make the buffer available for capture
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartCapture(id, kInvalidId));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, MultipleCapture) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
uint64_t id = ImportCaptureImage();
ASSERT_NE(INVALID_ID, id);
ASSERT_OK(StartCapture(id));
EXPECT_EQ(ZX_ERR_SHOULD_WAIT, StartCapture(id));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, InvalidReleaseCaptureId) {
if (!IsCaptureSupported()) {
printf("Test Skipped (capture not supported)\n");
return;
}
CaptureSetup();
EXPECT_EQ(ZX_ERR_INVALID_ARGS, ReleaseCapture(kInvalidId));
// done. Close sysmem
dc_client_->ReleaseBufferCollection(kCollectionId);
}
TEST_F(CoreDisplayTest, CaptureNotSupported) {
if (IsCaptureSupported()) {
printf("Test Skipped\n");
return;
}
fhd::ImageConfig image_config = {};
auto import_resp = dc_client_->ImportImageForCapture(image_config, 0, 0);
EXPECT_TRUE(import_resp.ok());
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, import_resp.value().result.err());
auto start_resp = dc_client_->StartCapture(0, 0);
EXPECT_TRUE(start_resp.ok());
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, start_resp.value().result.err());
auto release_resp = dc_client_->ReleaseCapture(0);
EXPECT_TRUE(release_resp.ok());
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, release_resp.value().result.err());
}
TEST_F(CoreDisplayTest, CreateLayerNoResource) {
for (int i = 0; i < 65536; i++) {
auto resp = dc_client_->CreateLayer();
EXPECT_TRUE(resp.ok());
EXPECT_EQ(ZX_OK, resp.value().res);
EXPECT_EQ(i + 1, resp.value().layer_id);
}
auto resp = dc_client_->CreateLayer();
EXPECT_TRUE(resp.ok());
EXPECT_EQ(ZX_ERR_NO_RESOURCES, resp.value().res);
}
#if 0
TEST_F(CoreDisplayTest, DestroyLayer) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetDisplayMode) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetDisplayColorConversion) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetDisplayLayers) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerPrimaryConfig) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerPrimaryPosition) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerPrimaryAlpha) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerCursorConfig) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerCursorPosition) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerColorConfig) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetLayerImage) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, CheckConfig) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, ApplyConfig) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, EnableVsync) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetVirtconMode) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, ImportBufferCollection) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, ReleaseBufferCollection) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, SetBufferCollectionConstraints) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, ImportImage) { EXPECT_OK(ZX_OK); }
TEST_F(CoreDisplayTest, ReleaseImage) { EXPECT_OK(ZX_OK); }
// There is no return for this defined. Cannot verify if it actually
// worked or not
TEST_F(CoreDisplayTest, ImportEvent) { EXPECT_OK(ZX_OK); }
// There is no return for this defined. Cannot verify if it actually
// worked or not
TEST_F(CoreDisplayTest, ReleaseEvent) { EXPECT_OK(ZX_OK); }
#endif
} // namespace
int main(int argc, char** argv) {
constexpr char kDriverPath[] = "/dev/display/fake-display";
if (access(kDriverPath, F_OK) != -1) {
zxtest::RunAllTests(argc, argv);
}
return 0;
}