blob: 8f0fc69e4119283e42d8938f6b1b43986d97e075 [file] [log] [blame]
// 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/framebuffer/framebuffer.h"
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/display/llcpp/fidl.h>
// FIDL must come before banjo
#include <fuchsia/hardware/display/controller/c/banjo.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/coding.h>
#include <lib/fit/defer.h>
#include <lib/image-format-llcpp/image-format-llcpp.h>
#include <lib/image-format/image_format.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/pixelformat.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <variant>
#include <fbl/unique_fd.h>
#include "src/lib/fsl/handles/object_info.h"
namespace fhd = fuchsia_hardware_display;
namespace sysmem = fuchsia_sysmem;
static zx_handle_t device_handle = ZX_HANDLE_INVALID;
std::unique_ptr<fidl::WireSyncClient<fhd::Controller>> dc_client;
std::unique_ptr<fidl::WireSyncClient<sysmem::Allocator>> sysmem_allocator;
static uint64_t display_id;
static uint64_t layer_id;
static int32_t width;
static int32_t height;
static int32_t stride;
static zx_pixel_format_t format;
static bool type_set;
static uint32_t image_type;
static zx_handle_t vmo = ZX_HANDLE_INVALID;
static bool inited = false;
static bool in_single_buffer_mode;
static zx_status_t fb_import_image(uint64_t collection_id, uint32_t index, uint32_t type,
uint64_t* id_out);
// Always import to collection id 1.
static const uint32_t kCollectionId = 1;
// Presents the image identified by |image_id|.
//
// If |wait_event_id| corresponds to an imported event, then driver will wait for
// for ZX_EVENT_SIGNALED before using the buffer. If |signal_event_id| corresponds
// to an imported event, then the driver will signal ZX_EVENT_SIGNALED when it is
// done with the image.
static zx_status_t fb_present_image(uint64_t image_id, uint64_t wait_event_id,
uint64_t signal_event_id);
static zx_status_t set_layer_config(uint64_t layer_id, uint32_t width, uint32_t height,
zx_pixel_format_t format, int32_t type) {
fhd::wire::ImageConfig config = {
.width = width,
.height = height,
.pixel_format = format,
.type = static_cast<uint32_t>(type),
};
return dc_client->SetLayerPrimaryConfig(layer_id, config).status();
}
template <typename T>
class EndpointOrError {
public:
static EndpointOrError<T> Create() {
zx::channel token_server, token_client;
zx_status_t status = zx::channel::create(0, &token_server, &token_client);
if (status != ZX_OK) {
return EndpointOrError<T>(status);
}
return EndpointOrError<T>(std::move(token_server), std::move(token_client));
}
EndpointOrError(zx_status_t status) : internal_(status) {}
EndpointOrError(zx::channel server, zx::channel client)
: internal_(std::make_pair(std::move(server), T(std::move(client)))) {}
bool ok() { return internal_.index() == 1; }
zx_status_t status() { return std::get<0>(internal_); }
zx::channel TakeServer() { return std::move(std::get<1>(internal_).first); }
T& operator*() { return std::get<1>(internal_).second; }
T* operator->() { return &std::get<1>(internal_).second; }
private:
std::variant<zx_status_t, std::pair<zx::channel, T>> internal_;
};
#define CHECK_RSP(rsp, err_msg) \
if (!(rsp).ok()) { \
*err_msg_out = err_msg; \
return (rsp).status(); \
}
#define CHECKED_CALL(func, err_msg) \
{ \
auto rsp = func; \
CHECK_RSP(rsp, err_msg); \
}
static zx_status_t create_buffer_collection(
const char** err_msg_out,
std::unique_ptr<fidl::WireSyncClient<sysmem::BufferCollection>>* collection_client) {
auto token = EndpointOrError<fidl::WireSyncClient<sysmem::BufferCollectionToken>>::Create();
CHECK_RSP(token, "Failed to create collection channel");
CHECKED_CALL(sysmem_allocator->AllocateSharedCollection(token.TakeServer()),
"Failed to allocate shared collection");
auto display_token =
EndpointOrError<fidl::WireSyncClient<sysmem::BufferCollectionToken>>::Create();
CHECK_RSP(display_token, "Failed to allocate display token");
CHECKED_CALL(token->Duplicate(ZX_RIGHT_SAME_RIGHTS, display_token.TakeServer()),
"Failed to duplicate token");
CHECKED_CALL(token->Sync(), "Failed to sync token");
auto import_rsp = dc_client->ImportBufferCollection(kCollectionId,
std::move(*display_token->mutable_channel()));
CHECK_RSP(import_rsp, "Failed to import buffer collection");
if (import_rsp->res != ZX_OK) {
*err_msg_out = "Import buffer collection error";
return import_rsp->res;
}
fhd::wire::ImageConfig config = {
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
.pixel_format = format,
.type = IMAGE_TYPE_SIMPLE,
};
auto set_display_constraints = dc_client->SetBufferCollectionConstraints(kCollectionId, config);
CHECK_RSP(set_display_constraints, "Failed to set display constraints");
if (set_display_constraints->res != ZX_OK) {
*err_msg_out = "Display constraints error";
return set_display_constraints->res;
}
auto collection = EndpointOrError<fidl::WireSyncClient<sysmem::BufferCollection>>::Create();
CHECK_RSP(collection, "Failed to create collection channel");
CHECKED_CALL(sysmem_allocator->BindSharedCollection(std::move(*token->mutable_channel()),
collection.TakeServer()),
"Failed to bind collection");
constexpr uint32_t kNamePriority = 1000000;
const char kNameString[] = "framebuffer";
CHECKED_CALL(collection->SetName(kNamePriority, fidl::StringView(kNameString)),
"Failed to set framebuffer name");
sysmem::wire::BufferCollectionConstraints constraints;
constraints.usage.cpu = sysmem::wire::kCpuUsageWriteOften | sysmem::wire::kCpuUsageRead;
constraints.min_buffer_count = 1;
constraints.image_format_constraints_count = 1;
auto& image_constraints = constraints.image_format_constraints[0];
image_constraints = image_format::GetDefaultImageFormatConstraints();
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::kBgra32;
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = sysmem::wire::kFormatModifierLinear;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = sysmem::wire::ColorSpaceType::kSrgb;
image_constraints.min_coded_width = width;
image_constraints.min_coded_height = height;
image_constraints.max_coded_width = 0xffffffff;
image_constraints.max_coded_height = 0xffffffff;
image_constraints.min_bytes_per_row = 0;
image_constraints.max_bytes_per_row = 0xffffffff;
constraints.has_buffer_memory_constraints = true;
constraints.buffer_memory_constraints = image_format::GetDefaultBufferMemoryConstraints();
constraints.buffer_memory_constraints.ram_domain_supported = true;
collection->SetConstraints(true, constraints);
*collection_client =
std::make_unique<fidl::WireSyncClient<sysmem::BufferCollection>>(std::move(*collection));
return ZX_OK;
}
// Not static because this function is also called from unit tests.
zx_status_t fb_bind_with_channel(bool single_buffer, const char** err_msg_out,
zx::channel dc_client_channel);
zx_status_t fb_bind(bool single_buffer, const char** err_msg_out) {
const char* err_msg;
if (!err_msg_out) {
err_msg_out = &err_msg;
}
*err_msg_out = "";
if (inited) {
*err_msg_out = "framebuffer already initialized";
return ZX_ERR_ALREADY_BOUND;
}
// TODO(stevensd): Don't hardcode display controller 0
fbl::unique_fd dc_fd(open("/dev/class/display-controller/000", O_RDWR));
if (!dc_fd) {
*err_msg_out = "Failed to open display controller";
return ZX_ERR_NO_RESOURCES;
}
zx::channel device_server, device_client;
zx_status_t status = zx::channel::create(0, &device_server, &device_client);
if (status != ZX_OK) {
*err_msg_out = "Failed to create device channel";
return status;
}
zx::channel dc_server, dc_client_channel;
status = zx::channel::create(0, &dc_server, &dc_client_channel);
if (status != ZX_OK) {
*err_msg_out = "Failed to create controller channel";
return status;
}
fdio_cpp::FdioCaller caller(std::move(dc_fd));
auto open_status = fidl::WireCall<fhd::Provider>(caller.channel())
.OpenController(std::move(device_server), std::move(dc_server));
if (open_status.status() != ZX_OK) {
*err_msg_out = "Failed to call service handle";
return open_status.status();
}
if (status != ZX_OK) {
*err_msg_out = "Failed to open controller";
return status;
}
device_handle = device_client.release();
return fb_bind_with_channel(single_buffer, err_msg_out, std::move(dc_client_channel));
}
zx_status_t fb_bind_with_channel(bool single_buffer, const char** err_msg_out,
zx::channel dc_client_channel) {
dc_client = std::make_unique<fidl::WireSyncClient<fhd::Controller>>(std::move(dc_client_channel));
auto close_dc_handle = fit::defer([]() {
zx_handle_close(device_handle);
dc_client.reset();
device_handle = ZX_HANDLE_INVALID;
});
zx_status_t status;
zx::channel sysmem_server, sysmem_client;
status = zx::channel::create(0, &sysmem_server, &sysmem_client);
if (status != ZX_OK) {
*err_msg_out = "Failed to create sysmem channel";
return status;
}
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", sysmem_server.release());
if (status != ZX_OK) {
*err_msg_out = "Failed to connect to sysmem";
return status;
}
sysmem_allocator =
std::make_unique<fidl::WireSyncClient<sysmem::Allocator>>(std::move(sysmem_client));
sysmem_allocator->SetDebugClientInfo(
fidl::StringView::FromExternal(fsl::GetCurrentProcessName() + "-framebuffer"),
fsl::GetCurrentProcessKoid());
auto close_sysmem_handle = fit::defer([]() { sysmem_allocator.reset(); });
class EventHandler : public fidl::WireSyncEventHandler<fhd::Controller> {
public:
EventHandler() = default;
zx_pixel_format_t pixel_format() const { return pixel_format_; }
bool has_display() const { return has_display_; }
fhd::wire::Mode mode() const { return mode_; }
void OnDisplaysChanged(fidl::WireResponse<fhd::Controller::OnDisplaysChanged>* event) override {
has_display_ = true;
// We're guaranteed that added contains at least one display, since we haven't
// been notified of any displays to remove.
display_id = event->added[0].id;
mode_ = event->added[0].modes[0];
pixel_format_ = event->added[0].pixel_format[0];
}
void OnVsync(fidl::WireResponse<fhd::Controller::OnVsync>* event) override {}
void OnClientOwnershipChange(
fidl::WireResponse<fhd::Controller::OnClientOwnershipChange>* event) override {}
zx_status_t Unknown() override { return ZX_ERR_STOP; }
private:
zx_pixel_format_t pixel_format_;
bool has_display_ = false;
fhd::wire::Mode mode_;
};
EventHandler event_handler;
do {
::fidl::Result result = dc_client->HandleOneEvent(event_handler);
if (!result.ok()) {
return result.status();
}
} while (!event_handler.has_display());
auto create_layer_rsp = dc_client->CreateLayer();
if (!create_layer_rsp.ok()) {
*err_msg_out = "Create layer call failed";
return create_layer_rsp.status();
}
if (create_layer_rsp->res != ZX_OK) {
*err_msg_out = "Failed to create layer";
status = create_layer_rsp->res;
return status;
}
auto layers_rsp = dc_client->SetDisplayLayers(
display_id, fidl::VectorView<uint64_t>::FromExternal(&create_layer_rsp->layer_id, 1));
if (!layers_rsp.ok()) {
*err_msg_out = layers_rsp.error();
return layers_rsp.status();
}
if ((status =
set_layer_config(create_layer_rsp->layer_id, event_handler.mode().horizontal_resolution,
event_handler.mode().vertical_resolution, event_handler.pixel_format(),
IMAGE_TYPE_SIMPLE)) != ZX_OK) {
*err_msg_out = "Failed to set layer config";
return status;
}
layer_id = create_layer_rsp->layer_id;
width = event_handler.mode().horizontal_resolution;
height = event_handler.mode().vertical_resolution;
format = event_handler.pixel_format();
type_set = false;
inited = true;
auto clear_inited = fit::defer([]() { inited = false; });
zx::vmo local_vmo;
std::unique_ptr<fidl::WireSyncClient<sysmem::BufferCollection>> collection_client;
status = create_buffer_collection(err_msg_out, &collection_client);
if (status != ZX_OK) {
return status;
}
auto info_result = collection_client->WaitForBuffersAllocated();
CHECK_RSP(info_result, "Couldn't wait for fidl buffers allocated");
if (info_result->status != ZX_OK) {
*err_msg_out = "Couldn't wait for buffers allocated";
return info_result->status;
}
local_vmo = std::move(info_result->buffer_collection_info.buffers[0].vmo);
uint32_t bytes_per_row;
bool got_stride = image_format::GetMinimumRowBytes(
info_result->buffer_collection_info.settings.image_format_constraints, width, &bytes_per_row);
if (!got_stride) {
*err_msg_out = "Couldn't get stride";
return ZX_ERR_INVALID_ARGS;
}
stride = bytes_per_row / ZX_PIXEL_FORMAT_BYTES(event_handler.pixel_format());
// Ignore error.
collection_client->Close();
// Failure to set the cache policy isn't a fatal error
zx_vmo_set_cache_policy(local_vmo.get(), ZX_CACHE_POLICY_WRITE_COMBINING);
uint64_t image_id;
if ((status = fb_import_image(kCollectionId, 0, IMAGE_TYPE_SIMPLE, &image_id)) != ZX_OK) {
*err_msg_out = "Couldn't import framebuffer";
return status;
}
if ((status = fb_present_image(image_id, INVALID_ID, INVALID_ID)) != ZX_OK) {
*err_msg_out = "Failed to present single_buffer mode framebuffer";
return status;
}
in_single_buffer_mode = single_buffer;
clear_inited.cancel();
vmo = local_vmo.release();
close_dc_handle.cancel();
close_sysmem_handle.cancel();
return ZX_OK;
}
void fb_release() {
if (!inited) {
return;
}
dc_client->ReleaseBufferCollection(kCollectionId);
zx_handle_close(device_handle);
dc_client.reset();
sysmem_allocator.reset();
device_handle = ZX_HANDLE_INVALID;
if (in_single_buffer_mode) {
zx_handle_close(vmo);
vmo = ZX_HANDLE_INVALID;
}
inited = false;
}
void fb_get_config(uint32_t* width_out, uint32_t* height_out, uint32_t* linear_stride_px_out,
zx_pixel_format_t* format_out) {
ZX_ASSERT(inited);
*width_out = width;
*height_out = height;
*format_out = format;
*linear_stride_px_out = stride;
}
zx_handle_t fb_get_single_buffer() {
ZX_ASSERT(inited && in_single_buffer_mode);
return vmo;
}
zx_status_t fb_import_image(uint64_t collection_id, uint32_t index, uint32_t type,
uint64_t* id_out) {
zx_status_t status;
if (type_set && type != image_type) {
return ZX_ERR_BAD_STATE;
} else if (!type_set && type != IMAGE_TYPE_SIMPLE) {
if ((status = set_layer_config(layer_id, width, height, format, type)) != ZX_OK) {
return status;
}
image_type = type;
type_set = true;
}
fhd::wire::ImageConfig config = {
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
.pixel_format = format,
.type = type,
};
auto import_rsp = dc_client->ImportImage(config, collection_id, index);
if (!import_rsp.ok()) {
return import_rsp.status();
}
if (import_rsp->res != ZX_OK) {
return import_rsp->res;
}
*id_out = import_rsp->image_id;
return ZX_OK;
}
zx_status_t fb_present_image(uint64_t image_id, uint64_t wait_event_id, uint64_t signal_event_id) {
auto rsp = dc_client->SetLayerImage(layer_id, image_id, wait_event_id, signal_event_id);
if (!rsp.ok()) {
return rsp.status();
}
return dc_client->ApplyConfig().status();
}