blob: 0098aab818b16969b80ccbe8cc5f9a9301ae90f2 [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 "vc-display.h"
#include <fcntl.h>
#include <fuchsia/hardware/display/llcpp/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fidl/coding.h>
#include <lib/image-format-llcpp/image-format-llcpp.h>
#include <lib/statusor/endpoint-or-error.h>
#include <lib/statusor/status-macros.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <ddk/protocol/display/controller.h>
#include <fbl/unique_fd.h>
#include "vc.h"
namespace fhd = ::llcpp::fuchsia::hardware::display;
namespace sysmem = ::llcpp::fuchsia::sysmem;
// At any point, |dc_ph| will either be waiting on the display controller device directory
// for a display controller instance or it will be waiting on a display controller interface
// for messages.
static port_handler_t dc_ph;
static std::unique_ptr<fhd::Controller::SyncClient> dc_client;
static std::unique_ptr<sysmem::Allocator::SyncClient> sysmem_allocator;
static uint64_t next_buffer_collection_id = 1;
static struct list_node display_list = LIST_INITIAL_VALUE(display_list);
static bool primary_bound = false;
// remember whether the virtual console controls the display
bool g_vc_owns_display = false;
static void vc_find_display_controller();
bool is_primary_bound() { return primary_bound; }
#if BUILD_FOR_DISPLAY_TEST
struct list_node* get_display_list() {
return &display_list;
}
sysmem::Allocator::SyncClient* get_sysmem_allocator() { return sysmem_allocator.get(); }
#endif // BUILD_FOR_DISPLAY_TEST
static constexpr const char* kDisplayControllerDir = "/dev/class/display-controller";
static int dc_dir_fd;
static zx_handle_t dc_device;
static zx_status_t vc_set_mode(fhd::VirtconMode mode) {
return dc_client->SetVirtconMode(static_cast<uint8_t>(mode)).status();
}
void vc_attach_to_main_display(vc_t* vc) {
if (list_is_empty(&display_list)) {
return;
}
display_info_t* primary = list_peek_head_type(&display_list, display_info_t, node);
vc->graphics = primary->graphics;
vc_attach_gfx(vc);
}
void vc_toggle_framebuffer() {
if (list_is_empty(&display_list)) {
return;
}
zx_status_t status =
vc_set_mode(!g_vc_owns_display ? fhd::VirtconMode::FORCED : fhd::VirtconMode::FALLBACK);
if (status != ZX_OK) {
printf("vc: Failed to toggle ownership %d\n", status);
}
}
static void handle_ownership_change(bool has_ownership) {
g_vc_owns_display = has_ownership;
#if !BUILD_FOR_DISPLAY_TEST
// If we've gained it, repaint
if (g_vc_owns_display && g_active_vc) {
vc_full_repaint(g_active_vc);
vc_render(g_active_vc);
}
#endif // !BUILD_FOR_DISPLAY_TEST
}
zx_status_t create_layer(uint64_t display_id, uint64_t* layer_id) {
auto rsp = dc_client->CreateLayer();
RETURN_IF_ERROR(rsp, "vc: Create layer call failed");
if (rsp->res != ZX_OK) {
printf("vc: Failed to create layer %d\n", rsp->res);
return rsp->res;
}
*layer_id = rsp->layer_id;
return ZX_OK;
}
void destroy_layer(uint64_t layer_id) {
if (!dc_client->DestroyLayer(layer_id).ok()) {
printf("vc: Failed to destroy layer\n");
}
}
void release_image(uint64_t image_id) {
if (!dc_client->ReleaseImage(image_id).ok()) {
printf("vc: Failed to release image\n");
}
}
static zx_status_t handle_display_added(fhd::Info* info) {
display_info_t* display_info =
reinterpret_cast<display_info_t*>(calloc(1, sizeof(display_info_t)));
if (!display_info) {
printf("vc: failed to alloc display info\n");
return ZX_ERR_NO_MEMORY;
}
zx_status_t status;
if ((status = create_layer(info->id, &display_info->layer_id)) != ZX_OK) {
printf("vc: failed to create display layer\n");
free(display_info);
return status;
}
display_info->id = info->id;
display_info->width = info->modes[0].horizontal_resolution;
display_info->height = info->modes[0].vertical_resolution;
display_info->format = static_cast<int32_t>(info->pixel_format[0]);
display_info->image_id = 0;
display_info->image_vmo = ZX_HANDLE_INVALID;
display_info->buffer_collection_id = 0;
display_info->bound = false;
display_info->log_vc = nullptr;
display_info->graphics = nullptr;
list_add_tail(&display_list, &display_info->node);
return ZX_OK;
}
void handle_display_removed(uint64_t id) {
if (list_is_empty(&display_list)) {
printf("vc: No displays when removing %ld\n", id);
return;
}
bool was_primary = list_peek_head_type(&display_list, display_info_t, node)->id == id;
display_info_t* info = nullptr;
display_info_t* temp = nullptr;
list_for_every_entry_safe (&display_list, info, temp, display_info_t, node) {
if (info->id == id) {
destroy_layer(info->layer_id);
release_image(info->image_id);
list_delete(&info->node);
zx_handle_close(info->image_vmo);
if (info->buffer_collection_id) {
dc_client->ReleaseBufferCollection(info->buffer_collection_id);
}
if (info->graphics) {
free(info->graphics);
}
if (info->log_vc) {
log_delete_vc(info->log_vc);
}
free(info);
}
}
if (was_primary) {
set_log_listener_active(false);
primary_bound = false;
}
}
static zx_status_t get_single_framebuffer(zx_handle_t* vmo_out, uint32_t* stride_out) {
auto rsp = dc_client->GetSingleBufferFramebuffer();
RETURN_IF_ERROR(rsp, "vc: Failed to get single framebuffer");
if (rsp->res != ZX_OK) {
// Don't print an error since this can happen on non-single-framebuffer
// systems.
return rsp->res;
}
if (!rsp->vmo) {
return ZX_ERR_INTERNAL;
}
*vmo_out = rsp->vmo.release();
*stride_out = rsp->stride;
return ZX_OK;
}
zx_status_t import_buffer_collection(uint64_t collection_id, fhd::ImageConfig* config,
uint64_t* id) {
constexpr uint32_t kImageIndex = 0;
auto import_rsp = dc_client->ImportImage(*config, collection_id, kImageIndex);
RETURN_IF_ERROR(import_rsp, "vc: Failed to import image call");
if (import_rsp->res != ZX_OK) {
printf("vc: Failed to import vmo %d\n", import_rsp->res);
return import_rsp->res;
}
*id = import_rsp->image_id;
return ZX_OK;
}
zx_status_t import_vmo(zx_handle_t vmo, fhd::ImageConfig* config, uint64_t* id) {
zx_handle_t vmo_dup;
zx_status_t status;
if ((status = zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, &vmo_dup)) != ZX_OK) {
printf("vc: Failed to dup fb handle %d\n", status);
return status;
}
auto import_rsp = dc_client->ImportVmoImage(*config, zx::vmo(vmo_dup), 0);
RETURN_IF_ERROR(import_rsp, "vc: Failed to import vmo call");
if (import_rsp->res != ZX_OK) {
printf("vc: Failed to import vmo %d\n", import_rsp->res);
return import_rsp->res;
}
*id = import_rsp->image_id;
return ZX_OK;
}
zx_status_t set_display_layer(uint64_t display_id, uint64_t layer_id) {
RETURN_IF_ERROR(
dc_client->SetDisplayLayers(
display_id, fidl::VectorView<uint64_t>(fidl::unowned_ptr(&layer_id), layer_id ? 1 : 0)),
"vc: Failed to set display layers");
return ZX_OK;
}
zx_status_t configure_layer(display_info_t* display, uint64_t layer_id, uint64_t image_id,
fhd::ImageConfig* config) {
RETURN_IF_ERROR(dc_client->SetLayerPrimaryConfig(layer_id, *config),
"vc: Failed to set layer config");
RETURN_IF_ERROR(dc_client->SetLayerPrimaryPosition(
layer_id, fhd::Transform::IDENTITY,
fhd::Frame{.width = config->width, .height = config->height},
fhd::Frame{.width = display->width, .height = display->height}),
"vc: Failed to set layer position");
RETURN_IF_ERROR(dc_client->SetLayerImage(layer_id, image_id, 0, 0), "vc: Failed to set image");
return ZX_OK;
}
zx_status_t apply_configuration() {
// Validate and then apply the new configuration
auto check_rsp = dc_client->CheckConfig(false);
RETURN_IF_ERROR(check_rsp, "vc: Failed to validate display config");
if (check_rsp->res != fhd::ConfigResult::OK) {
printf("vc: Config not valid %d\n", static_cast<int>(check_rsp->res));
return ZX_ERR_INTERNAL;
}
RETURN_IF_ERROR(dc_client->ApplyConfig(), "Applying config failed");
return ZX_OK;
}
static zx_status_t create_buffer_collection(
display_info_t* display, uint64_t id,
std::unique_ptr<sysmem::BufferCollection::SyncClient>* collection_client) {
ASSIGN_OR_RETURN(auto token, EndpointOrError<sysmem::BufferCollectionToken::SyncClient>::Create(),
"vc: Failed to create collection channel");
RETURN_IF_ERROR(sysmem_allocator->AllocateSharedCollection(token.TakeServer()),
"vc: Failed to allocate shared collection");
ASSIGN_OR_RETURN(auto display_token,
EndpointOrError<sysmem::BufferCollectionToken::SyncClient>::Create(),
"vc: Failed to allocate display token");
RETURN_IF_ERROR(token->Duplicate(ZX_RIGHT_SAME_RIGHTS, display_token.TakeServer()),
"vc: Failed to duplicate token");
ASSIGN_OR_RETURN(auto collection, EndpointOrError<sysmem::BufferCollection::SyncClient>::Create(),
"vc: Failed to create collection channel");
RETURN_IF_ERROR(sysmem_allocator->BindSharedCollection(std::move(*token->mutable_channel()),
collection.TakeServer()),
"vc: Failed to bind collection");
RETURN_IF_ERROR(collection->Sync(), "vc: Failed to sync collection");
auto import_rsp =
dc_client->ImportBufferCollection(id, std::move(*display_token->mutable_channel()));
RETURN_IF_ERROR(import_rsp, "vc: Failed to import buffer collection");
if (import_rsp->res != ZX_OK) {
printf("vc: Import buffer collection error\n");
return import_rsp->res;
}
auto set_display_constraints =
dc_client->SetBufferCollectionConstraints(id, display->image_config);
RETURN_IF_ERROR(set_display_constraints, "vc: Failed to set display constraints");
if (set_display_constraints->res != ZX_OK) {
printf("vc: Display constraints error\n");
return set_display_constraints->res;
}
sysmem::BufferCollectionConstraints constraints;
constraints.usage.cpu = sysmem::cpuUsageWriteOften | sysmem::cpuUsageRead;
constraints.min_buffer_count = 1;
constraints.has_buffer_memory_constraints = true;
constraints.buffer_memory_constraints = image_format::GetDefaultBufferMemoryConstraints();
constraints.buffer_memory_constraints.ram_domain_supported = true;
constraints.image_format_constraints_count = 1;
auto& image_constraints = constraints.image_format_constraints[0];
image_constraints = image_format::GetDefaultImageFormatConstraints();
fuchsia_sysmem_PixelFormat pixel_format;
if (!ImageFormatConvertZxToSysmem(display->format, &pixel_format)) {
printf("vc: Unsupported pixel format");
return ZX_ERR_INVALID_ARGS;
}
image_constraints.pixel_format = image_format::GetCppPixelFormat(pixel_format);
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = sysmem::ColorSpaceType::SRGB;
image_constraints.min_coded_width = display->width;
image_constraints.min_coded_height = display->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;
RETURN_IF_ERROR(collection->SetConstraints(true, constraints), "vc: Failed to set constraints");
*collection_client =
std::make_unique<sysmem::BufferCollection::SyncClient>(std::move(*collection));
return ZX_OK;
}
zx_status_t alloc_display_info_vmo(display_info_t* display) {
display->buffer_collection_id = 0;
display->image_config.height = display->height;
display->image_config.width = display->width;
display->image_config.pixel_format = display->format;
display->image_config.type = IMAGE_TYPE_SIMPLE;
if (get_single_framebuffer(&display->image_vmo, &display->stride) != ZX_OK) {
uint64_t buffer_collection_id = next_buffer_collection_id++;
std::unique_ptr<sysmem::BufferCollection::SyncClient> collection_client;
zx_status_t status =
create_buffer_collection(display, buffer_collection_id, &collection_client);
if (status != ZX_OK) {
return status;
}
display->buffer_collection_id = buffer_collection_id;
auto info_result = collection_client->WaitForBuffersAllocated();
RETURN_IF_ERROR(info_result, "vc: Couldn't wait for buffers allocated");
if (info_result->status != ZX_OK) {
printf("vc: Couldn't wait for buffers allocated\n");
return info_result->status;
}
uint32_t bytes_per_row;
bool got_stride = image_format::GetMinimumRowBytes(
info_result->buffer_collection_info.settings.image_format_constraints, display->width,
&bytes_per_row);
if (!got_stride) {
return ZX_ERR_INVALID_ARGS;
}
display->stride = bytes_per_row / ZX_PIXEL_FORMAT_BYTES(display->format);
display->image_vmo = info_result->buffer_collection_info.buffers[0].vmo.release();
collection_client->Close();
}
return ZX_OK;
}
zx_status_t rebind_display(bool use_all) {
// Arbitrarily pick the oldest display as the primary dispay
display_info* primary = list_peek_head_type(&display_list, display_info, node);
if (primary == nullptr) {
printf("vc: No display to bind to\n");
return ZX_ERR_NO_RESOURCES;
}
zx_status_t status;
// This happens when the last primary disconnected and a new, already
// bound display becomes primary. We must un-bind the display and
// rebind.
if (!primary_bound && primary->bound) {
// Remove the primary display's log console.
if (primary->log_vc) {
log_delete_vc(primary->log_vc);
primary->log_vc = nullptr;
}
// Switch all of the current vcs to using this display.
vc_change_graphics(primary->graphics);
}
display_info_t* info = nullptr;
list_for_every_entry (&display_list, info, display_info_t, node) {
if (!use_all && info != primary) {
// If we're not showing anything on this display, remove its layer.
if ((status = set_display_layer(info->id, 0)) != ZX_OK) {
break;
}
} else if (info->image_id == 0) {
// If we want to display something but aren't, configure the display.
if ((status = alloc_display_info_vmo(info)) != ZX_OK) {
printf("vc: failed to allocate vmo for new display %d\n", status);
break;
}
info->graphics = reinterpret_cast<vc_gfx_t*>(calloc(1, sizeof(vc_gfx_t)));
if ((status = vc_init_gfx(info->graphics, info->image_vmo, info->width, info->height,
info->format, info->stride)) != ZX_OK) {
printf("vc: failed to initialize graphics for new display %d\n", status);
break;
}
// If this is not the primary display then create the log console
// for the display.
if (info != primary) {
if ((status = log_create_vc(info->graphics, &info->log_vc)) != ZX_OK) {
break;
}
}
info->bound = true;
fhd::ImageConfig config = info->image_config;
if (info->buffer_collection_id) {
if ((status = import_buffer_collection(info->buffer_collection_id, &config,
&info->image_id)) != ZX_OK) {
break;
}
} else {
if ((status = import_vmo(info->image_vmo, &config, &info->image_id)) != ZX_OK) {
break;
}
}
if ((status = set_display_layer(info->id, info->layer_id)) != ZX_OK) {
break;
}
if ((status = configure_layer(info, info->layer_id, info->image_id, &config) != ZX_OK)) {
break;
}
}
}
if (status == ZX_OK && apply_configuration() == ZX_OK) {
// Only listen for logs when we have somewhere to print them. Also,
// use a repeating wait so that we don't add/remove observers for each
// log message (which is helpful when tracing the addition/removal of
// observers).
set_log_listener_active(true);
vc_change_graphics(primary->graphics);
printf("vc: Successfully attached to display %ld\n", primary->id);
primary_bound = true;
return ZX_OK;
} else {
display_info_t* info = nullptr;
list_for_every_entry (&display_list, info, display_info_t, node) {
if (info->image_id) {
release_image(info->image_id);
info->image_id = 0;
}
if (info->image_vmo) {
zx_handle_close(info->image_vmo);
info->image_vmo = 0;
}
if (info->graphics) {
free(info->graphics);
info->graphics = nullptr;
}
}
if (use_all) {
return rebind_display(false);
} else {
printf("vc: Failed to bind to displays\n");
return ZX_ERR_INTERNAL;
}
}
}
static zx_status_t handle_displays_changed(fidl::VectorView<fhd::Info> added,
fidl::VectorView<uint64_t> removed) {
for (auto& display : added) {
zx_status_t status = handle_display_added(&display);
if (status != ZX_OK) {
return status;
}
}
for (auto& display_id : removed) {
handle_display_removed(display_id);
}
return rebind_display(true);
}
zx_status_t dc_callback_handler(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
if (signals & ZX_CHANNEL_PEER_CLOSED) {
printf("vc: Displays lost\n");
while (!list_is_empty(&display_list)) {
handle_display_removed(list_peek_head_type(&display_list, display_info_t, node)->id);
}
vc_change_graphics(nullptr);
zx_handle_close(dc_device);
dc_ph.handle = ZX_HANDLE_INVALID;
dc_client.reset();
vc_find_display_controller();
return ZX_ERR_STOP;
}
ZX_DEBUG_ASSERT(signals & ZX_CHANNEL_READABLE);
return dc_client->HandleEvents(fhd::Controller::EventHandlers{
.on_displays_changed =
[](fidl::VectorView<fhd::Info> added, fidl::VectorView<uint64_t> removed) {
handle_displays_changed(std::move(added), std::move(removed));
return ZX_OK;
},
.on_vsync = [](uint64_t display_id, uint64_t timestamp, fidl::VectorView<uint64_t> images,
uint64_t cookie) { return ZX_OK; },
.on_client_ownership_change =
[](bool has_ownership) {
handle_ownership_change(has_ownership);
return ZX_OK;
},
.unknown =
[]() {
printf("vc: Unknown display callback message\n");
return ZX_OK;
}});
}
#if BUILD_FOR_DISPLAY_TEST
void initialize_display_channel(zx::channel channel) {
dc_client = std::make_unique<fhd::Controller::SyncClient>(std::move(channel));
dc_ph.handle = dc_client->channel().get();
}
#else
static zx_status_t vc_dc_event(uint32_t evt, const char* name) {
if ((evt != fuchsia_io_WATCH_EVENT_EXISTING) && (evt != fuchsia_io_WATCH_EVENT_ADDED)) {
return ZX_OK;
}
printf("vc: new display device %s/%s\n", kDisplayControllerDir, name);
char buf[64];
snprintf(buf, 64, "%s/%s", kDisplayControllerDir, name);
fbl::unique_fd fd(open(buf, O_RDWR));
if (!fd) {
printf("vc: failed to open display controller device\n");
return ZX_OK;
}
zx::channel device_server, device_client;
zx_status_t status = zx::channel::create(0, &device_server, &device_client);
if (status != ZX_OK) {
return status;
}
zx::channel dc_server, dc_client_channel;
status = zx::channel::create(0, &dc_server, &dc_client_channel);
if (status != ZX_OK) {
return status;
}
fdio_cpp::FdioCaller caller(std::move(fd));
auto open_rsp = fhd::Provider::Call::OpenVirtconController(
caller.channel(), std::move(device_server), std::move(dc_server));
if (!open_rsp.ok()) {
return open_rsp.status();
}
if (open_rsp->s != ZX_OK) {
return open_rsp->s;
}
dc_device = device_client.release();
zx_handle_close(dc_ph.handle);
dc_client = std::make_unique<fhd::Controller::SyncClient>(std::move(dc_client_channel));
dc_ph.handle = dc_client->channel().get();
status = vc_set_mode(getenv("virtcon.hide-on-boot") == nullptr ? fhd::VirtconMode::FALLBACK
: fhd::VirtconMode::INACTIVE);
if (status != ZX_OK) {
printf("vc: Failed to set initial ownership %d\n", status);
vc_find_display_controller();
return ZX_ERR_STOP;
}
dc_ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
dc_ph.func = dc_callback_handler;
if ((status = port_wait(&port, &dc_ph)) != ZX_OK) {
printf("vc: Failed to set port waiter %d\n", status);
vc_find_display_controller();
}
return ZX_ERR_STOP;
}
#endif // BUILD_FOR_DISPLAY_TEST
static zx_status_t vc_dc_dir_event_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
#if BUILD_FOR_DISPLAY_TEST
return ZX_ERR_NOT_SUPPORTED;
#else
return handle_device_dir_event(ph, signals, vc_dc_event);
#endif
}
static void vc_find_display_controller() {
zx::channel client, server;
if (zx::channel::create(0, &client, &server) != ZX_OK) {
printf("vc: Failed to create dc watcher channel\n");
return;
}
fdio_t* fdio = fdio_unsafe_fd_to_io(dc_dir_fd);
zx_status_t status;
zx_status_t io_status = fuchsia_io_DirectoryWatch(
fdio_unsafe_borrow_channel(fdio), fuchsia_io_WATCH_MASK_ALL, 0, server.release(), &status);
fdio_unsafe_release(fdio);
if (io_status != ZX_OK || status != ZX_OK) {
printf("vc: Failed to watch dc directory\n");
return;
}
ZX_DEBUG_ASSERT(dc_ph.handle == ZX_HANDLE_INVALID);
dc_ph.handle = client.release();
dc_ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
dc_ph.func = vc_dc_dir_event_cb;
if (port_wait(&port, &dc_ph) != ZX_OK) {
printf("vc: Failed to wait on dc directory\n");
}
}
bool vc_display_init() {
fbl::unique_fd fd(open(kDisplayControllerDir, O_DIRECTORY | O_RDONLY));
if (!fd) {
return false;
}
dc_dir_fd = fd.release();
vc_find_display_controller();
return true;
}
bool vc_sysmem_connect() {
zx_status_t status;
zx::channel sysmem_server, sysmem_client;
status = zx::channel::create(0, &sysmem_server, &sysmem_client);
if (status != ZX_OK) {
return false;
}
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", sysmem_server.release());
if (status != ZX_OK) {
return false;
}
sysmem_allocator = std::make_unique<sysmem::Allocator::SyncClient>(std::move(sysmem_client));
return true;
}