| // 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 <fidl/fuchsia.hardware.display/cpp/wire.h> |
| // FIDL must come before banjo |
| #include <fidl/fuchsia.sysmem/cpp/wire.h> |
| #include <fuchsia/hardware/display/controller/c/banjo.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fit/defer.h> |
| #include <lib/image-format-llcpp/image-format-llcpp.h> |
| #include <lib/image-format/image_format.h> |
| #include <lib/service/llcpp/service.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/lib/fsl/handles/object_info.h" |
| |
| namespace fhd = fuchsia_hardware_display; |
| namespace sysmem = fuchsia_sysmem; |
| |
| static std::optional<zx::channel> device_handle; |
| |
| std::optional<fidl::WireSyncClient<fhd::Controller>> dc_client; |
| std::optional<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 std::optional<zx::vmo> vmo; |
| |
| 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(); |
| } |
| |
| #define CHECKED_CALL(func, err_msg) \ |
| { \ |
| fidl::WireResult rsp = func; \ |
| if (!(rsp).ok()) { \ |
| *err_msg_out = err_msg; \ |
| return zx::error((rsp).status()); \ |
| } \ |
| } |
| |
| static zx::status<fidl::WireSyncClient<sysmem::BufferCollection>> create_buffer_collection( |
| const char** err_msg_out) { |
| zx::status token = fidl::CreateEndpoints<sysmem::BufferCollectionToken>(); |
| if (token.is_error()) { |
| *err_msg_out = "Failed to create collection channel"; |
| return token.take_error(); |
| } |
| CHECKED_CALL(sysmem_allocator->AllocateSharedCollection(std::move(token->server)), |
| "Failed to allocate shared collection"); |
| zx::status display_token = fidl::CreateEndpoints<sysmem::BufferCollectionToken>(); |
| if (display_token.is_error()) { |
| *err_msg_out = "Failed to allocate display token"; |
| return display_token.take_error(); |
| } |
| CHECKED_CALL(fidl::WireCall(token->client) |
| .Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(display_token->server)), |
| "Failed to duplicate token"); |
| CHECKED_CALL(fidl::WireCall(token->client).Sync(), "Failed to sync token"); |
| |
| fidl::WireResult import_rsp = |
| dc_client->ImportBufferCollection(kCollectionId, std::move(display_token->client)); |
| if (!import_rsp.ok()) { |
| *err_msg_out = "Failed to import buffer collection"; |
| return zx::error(import_rsp.status()); |
| } |
| if (import_rsp->res != ZX_OK) { |
| *err_msg_out = "Import buffer collection error"; |
| return zx::error(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, |
| }; |
| fidl::WireResult set_display_constraints = |
| dc_client->SetBufferCollectionConstraints(kCollectionId, config); |
| if (!set_display_constraints.ok()) { |
| *err_msg_out = "Failed to set display constraints"; |
| return zx::error(set_display_constraints.status()); |
| } |
| if (set_display_constraints->res != ZX_OK) { |
| *err_msg_out = "Display constraints error"; |
| return zx::error(set_display_constraints->res); |
| } |
| |
| zx::status collection = fidl::CreateEndpoints<sysmem::BufferCollection>(); |
| if (collection.is_error()) { |
| *err_msg_out = "Failed to create collection channel"; |
| return collection.take_error(); |
| } |
| |
| CHECKED_CALL(sysmem_allocator->BindSharedCollection(std::move(token->client), |
| std::move(collection->server)), |
| "Failed to bind collection"); |
| |
| fidl::WireSyncClient client = fidl::BindSyncClient(std::move(collection->client)); |
| |
| constexpr uint32_t kNamePriority = 1000000; |
| const char kNameString[] = "framebuffer"; |
| CHECKED_CALL(client.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; |
| |
| client.SetConstraints(true, constraints); |
| return zx::ok(std::move(client)); |
| } |
| |
| // Not static because this function is also called from unit tests. |
| zx_status_t fb_bind_with(bool single_buffer, const char** err_msg_out, |
| fidl::ClientEnd<fhd::Controller> client); |
| |
| 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::status dc = fidl::CreateEndpoints<fhd::Controller>(); |
| if (dc.is_error()) { |
| *err_msg_out = "Failed to create controller channel"; |
| return dc.status_value(); |
| } |
| |
| fdio_cpp::FdioCaller caller(std::move(dc_fd)); |
| fidl::WireResult open_status = |
| fidl::WireCall(caller.borrow_as<fhd::Provider>()) |
| .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 = std::move(device_client); |
| return fb_bind_with(single_buffer, err_msg_out, std::move(dc->client)); |
| } |
| |
| zx_status_t fb_bind_with(bool single_buffer, const char** err_msg_out, |
| fidl::ClientEnd<fhd::Controller> client) { |
| dc_client = fidl::BindSyncClient(std::move(client)); |
| auto close_dc_handle = fit::defer([]() { |
| device_handle.reset(); |
| dc_client.reset(); |
| }); |
| |
| zx::status client_end = service::Connect<sysmem::Allocator>(); |
| if (client_end.is_error()) { |
| *err_msg_out = "Failed to connect to sysmem"; |
| return client_end.status_value(); |
| } |
| |
| sysmem_allocator = fidl::BindSyncClient(std::move(*client_end)); |
| 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()); |
| |
| fidl::WireResult 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"; |
| return create_layer_rsp->res; |
| } |
| |
| fidl::WireResult 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().lossy_description(); |
| return layers_rsp.status(); |
| } |
| |
| if (zx_status_t 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); |
| status != 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; |
| |
| zx::status collection_client = create_buffer_collection(err_msg_out); |
| if (collection_client.is_error()) { |
| return collection_client.status_value(); |
| } |
| |
| fidl::WireResult info_result = collection_client->WaitForBuffersAllocated(); |
| if (!info_result.ok()) { |
| *err_msg_out = "Couldn't wait for fidl buffers allocated"; |
| return info_result.status(); |
| } |
| 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 (zx_status_t status = fb_import_image(kCollectionId, 0, IMAGE_TYPE_SIMPLE, &image_id); |
| status != ZX_OK) { |
| *err_msg_out = "Couldn't import framebuffer"; |
| return status; |
| } |
| |
| if (zx_status_t status = fb_present_image(image_id, INVALID_ID, INVALID_ID); status != ZX_OK) { |
| *err_msg_out = "Failed to present single_buffer mode framebuffer"; |
| return status; |
| } |
| |
| in_single_buffer_mode = single_buffer; |
| |
| clear_inited.cancel(); |
| vmo = std::move(local_vmo); |
| close_dc_handle.cancel(); |
| close_sysmem_handle.cancel(); |
| |
| return ZX_OK; |
| } |
| |
| void fb_release() { |
| if (!inited) { |
| return; |
| } |
| |
| dc_client->ReleaseBufferCollection(kCollectionId); |
| |
| device_handle.reset(); |
| dc_client.reset(); |
| sysmem_allocator.reset(); |
| |
| if (in_single_buffer_mode) { |
| vmo.reset(); |
| } |
| |
| 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.value().get(); |
| } |
| |
| 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, |
| }; |
| |
| fidl::WireResult 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) { |
| fidl::WireResult 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(); |
| } |