| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <fbl/auto_call.h> |
| #include <fbl/unique_fd.h> |
| #include <lib/fidl/coding.h> |
| #include <lib/fzl/fdio.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/device/display-controller.h> |
| #include <zircon/pixelformat.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include "fuchsia/hardware/display/c/fidl.h" |
| #include "lib/framebuffer/framebuffer.h" |
| |
| static zx_handle_t device_handle = ZX_HANDLE_INVALID; |
| static zx_handle_t dc_handle = ZX_HANDLE_INVALID; |
| |
| static int32_t txid; |
| 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(zx_handle_t handle, uint32_t type, uint64_t* id_out); |
| static void fb_release_image(uint64_t id); |
| |
| // Imports an event handle to use for image synchronization. This function |
| // always consumes |handle|. Id must be unique and not equal to FB_INVALID_ID. |
| static zx_status_t fb_import_event(zx_handle_t handle, uint64_t id); |
| static void fb_release_event(uint64_t id); |
| |
| // 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) { |
| fuchsia_hardware_display_ControllerSetLayerPrimaryConfigRequest layer_cfg_msg = {}; |
| layer_cfg_msg.hdr.ordinal = fuchsia_hardware_display_ControllerSetLayerPrimaryConfigOrdinal; |
| layer_cfg_msg.layer_id = layer_id; |
| layer_cfg_msg.image_config.width = width; |
| layer_cfg_msg.image_config.height = height; |
| layer_cfg_msg.image_config.pixel_format = format; |
| layer_cfg_msg.image_config.type = type; |
| |
| return zx_channel_write(dc_handle, 0, &layer_cfg_msg, sizeof(layer_cfg_msg), NULL, 0); |
| } |
| |
| 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 = "framebufer already initialzied"; |
| 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; |
| status = zx::channel::create(0, &dc_server, &dc_client); |
| if (status != ZX_OK) { |
| *err_msg_out = "Failed to create controller channel"; |
| return status; |
| } |
| |
| fzl::FdioCaller caller(std::move(dc_fd)); |
| zx_status_t fidl_status = fuchsia_hardware_display_ProviderOpenController( |
| caller.borrow_channel(), device_server.release(), dc_server.release(), &status); |
| if (fidl_status != ZX_OK) { |
| *err_msg_out = "Failed to call service handle"; |
| return fidl_status; |
| } |
| if (status != ZX_OK) { |
| *err_msg_out = "Failed to open controller"; |
| return status; |
| } |
| |
| device_handle = device_client.release(); |
| dc_handle = dc_client.release(); |
| fbl::AutoCall close_dc_handle([]() { |
| zx_handle_close(device_handle); |
| zx_handle_close(dc_handle); |
| device_handle = ZX_HANDLE_INVALID; |
| dc_handle = ZX_HANDLE_INVALID; |
| }); |
| |
| uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES]; |
| uint32_t actual_bytes, actual_handles; |
| bool has_display = false; |
| do { |
| zx_handle_t observed; |
| uint32_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| if ((status = zx_object_wait_one(dc_handle, |
| signals, ZX_TIME_INFINITE, &observed)) != ZX_OK) { |
| *err_msg_out = "Failed waiting for display"; |
| return status; |
| } |
| if (observed & ZX_CHANNEL_PEER_CLOSED) { |
| *err_msg_out = "Display controller connection closed"; |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| if ((status = zx_channel_read(dc_handle, 0, bytes, NULL, ZX_CHANNEL_MAX_MSG_BYTES, 0, |
| &actual_bytes, &actual_handles)) != ZX_OK || |
| actual_bytes < sizeof(fidl_message_header_t)) { |
| *err_msg_out = "Reading display addded callback failed"; |
| return status; |
| } |
| |
| fidl_message_header_t* hdr = (fidl_message_header_t*)bytes; |
| if (hdr->ordinal == fuchsia_hardware_display_ControllerDisplaysChangedOrdinal) { |
| if ((status = fidl_decode(&fuchsia_hardware_display_ControllerDisplaysChangedEventTable, |
| bytes, actual_bytes, NULL, 0, err_msg_out)) != ZX_OK) { |
| return status; |
| } |
| has_display = true; |
| } |
| } while (!has_display); |
| |
| // We're guaranteed that added contains at least one display, since we haven't |
| // been notified of any displays to remove. |
| fuchsia_hardware_display_ControllerDisplaysChangedEvent* changes = |
| (fuchsia_hardware_display_ControllerDisplaysChangedEvent*)bytes; |
| fuchsia_hardware_display_Info* display = (fuchsia_hardware_display_Info*)changes->added.data; |
| fuchsia_hardware_display_Mode* mode = (fuchsia_hardware_display_Mode*)display->modes.data; |
| zx_pixel_format_t pixel_format = ((int32_t*)(display->pixel_format.data))[0]; |
| |
| fuchsia_hardware_display_ControllerComputeLinearImageStrideRequest stride_msg; |
| stride_msg.hdr.ordinal = fuchsia_hardware_display_ControllerComputeLinearImageStrideOrdinal; |
| stride_msg.hdr.txid = txid++; |
| stride_msg.width = mode->horizontal_resolution; |
| stride_msg.pixel_format = pixel_format; |
| |
| fuchsia_hardware_display_ControllerComputeLinearImageStrideResponse stride_rsp; |
| zx_channel_call_args_t stride_call = {}; |
| stride_call.wr_bytes = &stride_msg; |
| stride_call.rd_bytes = &stride_rsp; |
| stride_call.wr_num_bytes = sizeof(stride_msg); |
| stride_call.rd_num_bytes = sizeof(stride_rsp); |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &stride_call, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| *err_msg_out = "Failed to get linear stride"; |
| return status; |
| } |
| |
| fuchsia_hardware_display_ControllerCreateLayerRequest create_layer_msg; |
| create_layer_msg.hdr.ordinal = fuchsia_hardware_display_ControllerCreateLayerOrdinal; |
| |
| fuchsia_hardware_display_ControllerCreateLayerResponse create_layer_rsp; |
| zx_channel_call_args_t call_args = {}; |
| call_args.wr_bytes = &create_layer_msg; |
| call_args.rd_bytes = &create_layer_rsp; |
| call_args.wr_num_bytes = sizeof(create_layer_msg); |
| call_args.rd_num_bytes = sizeof(create_layer_rsp); |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| *err_msg_out = "Create layer call failed"; |
| return status; |
| } |
| if (create_layer_rsp.res != ZX_OK) { |
| *err_msg_out = "Failed to create layer"; |
| status = create_layer_rsp.res; |
| return status; |
| } |
| |
| uint8_t fidl_bytes[sizeof(fuchsia_hardware_display_ControllerSetDisplayLayersRequest) + |
| FIDL_ALIGN(sizeof(uint64_t))]; |
| fuchsia_hardware_display_ControllerSetDisplayLayersRequest* set_display_layer_request = |
| (fuchsia_hardware_display_ControllerSetDisplayLayersRequest*)fidl_bytes; |
| *(uint64_t*)(fidl_bytes + sizeof(fuchsia_hardware_display_ControllerSetDisplayLayersRequest)) = |
| create_layer_rsp.layer_id; |
| |
| set_display_layer_request->hdr.ordinal = |
| fuchsia_hardware_display_ControllerSetDisplayLayersOrdinal; |
| set_display_layer_request->display_id = display->id; |
| set_display_layer_request->layer_ids.count = 1; |
| set_display_layer_request->layer_ids.data = (void*)FIDL_ALLOC_PRESENT; |
| |
| if ((status = zx_channel_write(dc_handle, 0, fidl_bytes, |
| sizeof(fidl_bytes), NULL, 0)) != ZX_OK) { |
| *err_msg_out = "Failed to set display layers"; |
| return status; |
| } |
| |
| if ((status = set_layer_config(create_layer_rsp.layer_id, mode->horizontal_resolution, |
| mode->vertical_resolution, pixel_format, |
| IMAGE_TYPE_SIMPLE)) != ZX_OK) { |
| *err_msg_out = "Failed to set layer config"; |
| return status; |
| } |
| |
| display_id = display->id; |
| layer_id = create_layer_rsp.layer_id; |
| |
| width = mode->horizontal_resolution; |
| height = mode->vertical_resolution; |
| format = pixel_format; |
| stride = stride_rsp.stride; |
| |
| type_set = false; |
| |
| inited = true; |
| |
| fbl::AutoCall clear_inited([]() { |
| inited = false; |
| }); |
| |
| zx::vmo local_vmo; |
| |
| if (single_buffer) { |
| uint32_t size = stride * height * ZX_PIXEL_FORMAT_BYTES(format); |
| fuchsia_hardware_display_ControllerAllocateVmoRequest alloc_msg; |
| alloc_msg.hdr.ordinal = fuchsia_hardware_display_ControllerAllocateVmoOrdinal; |
| alloc_msg.hdr.txid = txid++; |
| alloc_msg.size = size; |
| |
| fuchsia_hardware_display_ControllerAllocateVmoResponse alloc_rsp; |
| zx_channel_call_args_t call_args = {}; |
| call_args.wr_bytes = &alloc_msg; |
| call_args.rd_bytes = &alloc_rsp; |
| call_args.rd_handles = local_vmo.reset_and_get_address(); |
| call_args.wr_num_bytes = sizeof(alloc_msg); |
| call_args.rd_num_bytes = sizeof(alloc_rsp); |
| call_args.rd_num_handles = 1; |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| *err_msg_out = "Failed vmo alloc call"; |
| return status; |
| } |
| if (alloc_rsp.res != ZX_OK) { |
| status = alloc_rsp.res; |
| *err_msg_out = "Failed to alloc vmo"; |
| return status; |
| } |
| |
| // Failure to set the cache policy isn't a fatal error |
| zx_vmo_set_cache_policy(local_vmo.get(), ZX_CACHE_POLICY_WRITE_COMBINING); |
| |
| zx_handle_t dup; |
| if ((status = zx_handle_duplicate(local_vmo.get(), ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) { |
| *err_msg_out = "Couldn't duplicate vmo\n"; |
| return status; |
| } |
| |
| // fb_(present|import)_image expect to not be in single buffer |
| // mode, so make sure this is false for now. It will get set properly later. |
| in_single_buffer_mode = false; |
| |
| uint64_t image_id; |
| if ((status = fb_import_image(dup, 0, &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(); |
| |
| return ZX_OK; |
| } |
| |
| void fb_release() { |
| if (!inited) { |
| return; |
| } |
| |
| zx_handle_close(device_handle); |
| zx_handle_close(dc_handle); |
| device_handle = ZX_HANDLE_INVALID; |
| dc_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(zx_handle_t handle, uint32_t type, uint64_t* id_out) { |
| ZX_ASSERT(inited && !in_single_buffer_mode); |
| 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; |
| } |
| |
| fuchsia_hardware_display_ControllerImportVmoImageRequest import_msg = {}; |
| import_msg.hdr.ordinal = fuchsia_hardware_display_ControllerImportVmoImageOrdinal; |
| import_msg.hdr.txid = txid++; |
| import_msg.image_config.height = height; |
| import_msg.image_config.width = width; |
| import_msg.image_config.pixel_format = format; |
| import_msg.image_config.type = type; |
| import_msg.vmo = FIDL_HANDLE_PRESENT; |
| import_msg.offset = 0; |
| |
| fuchsia_hardware_display_ControllerImportVmoImageResponse import_rsp; |
| zx_channel_call_args_t import_call = {}; |
| import_call.wr_bytes = &import_msg; |
| import_call.wr_handles = &handle; |
| import_call.rd_bytes = &import_rsp; |
| import_call.wr_num_bytes = sizeof(import_msg); |
| import_call.wr_num_handles = 1; |
| import_call.rd_num_bytes = sizeof(import_rsp); |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &import_call, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return status; |
| } |
| |
| if (import_rsp.res != ZX_OK) { |
| return import_rsp.res; |
| } |
| |
| *id_out = import_rsp.image_id; |
| return ZX_OK; |
| } |
| |
| void fb_release_image(uint64_t image_id) { |
| ZX_ASSERT(inited && !in_single_buffer_mode); |
| |
| fuchsia_hardware_display_ControllerReleaseEventRequest release_img_msg; |
| release_img_msg.hdr.ordinal = fuchsia_hardware_display_ControllerReleaseEventOrdinal; |
| release_img_msg.hdr.txid = txid++; |
| release_img_msg.id = image_id; |
| |
| // There's nothing meaningful to do if this call fails |
| zx_channel_write(dc_handle, 0, &release_img_msg, sizeof(release_img_msg), NULL, 0); |
| } |
| |
| zx_status_t fb_import_event(zx_handle_t handle, uint64_t id) { |
| ZX_ASSERT(inited && !in_single_buffer_mode); |
| |
| fuchsia_hardware_display_ControllerImportEventRequest import_evt_msg; |
| import_evt_msg.hdr.ordinal = fuchsia_hardware_display_ControllerImportEventOrdinal; |
| import_evt_msg.hdr.txid = txid++; |
| import_evt_msg.id = id; |
| import_evt_msg.event = FIDL_HANDLE_PRESENT; |
| |
| return zx_channel_write(dc_handle, 0, &import_evt_msg, sizeof(import_evt_msg), &handle, 1); |
| } |
| |
| void fb_release_event(uint64_t id) { |
| ZX_ASSERT(inited && !in_single_buffer_mode); |
| |
| fuchsia_hardware_display_ControllerReleaseEventRequest release_evt_msg; |
| release_evt_msg.hdr.ordinal = fuchsia_hardware_display_ControllerReleaseEventOrdinal; |
| release_evt_msg.hdr.txid = txid++; |
| release_evt_msg.id = id; |
| |
| // There's nothing meaningful we can do if this call fails |
| zx_channel_write(dc_handle, 0, &release_evt_msg, sizeof(release_evt_msg), NULL, 0); |
| } |
| |
| zx_status_t fb_present_image(uint64_t image_id, uint64_t wait_event_id, |
| uint64_t signal_event_id) { |
| ZX_ASSERT(inited && !in_single_buffer_mode); |
| zx_status_t status; |
| |
| fuchsia_hardware_display_ControllerSetLayerImageRequest set_msg; |
| set_msg.hdr.ordinal = fuchsia_hardware_display_ControllerSetLayerImageOrdinal; |
| set_msg.hdr.txid = txid++; |
| set_msg.layer_id = layer_id; |
| set_msg.image_id = image_id; |
| set_msg.wait_event_id = wait_event_id; |
| set_msg.signal_event_id = signal_event_id; |
| if ((status = zx_channel_write(dc_handle, 0, &set_msg, sizeof(set_msg), NULL, 0)) != ZX_OK) { |
| return status; |
| } |
| |
| // It's not necessary to validate the configuration, since we're guaranteed that a single |
| // fullscreen framebuffer on a single monitor will work. |
| fuchsia_hardware_display_ControllerApplyConfigRequest apply_msg; |
| apply_msg.hdr.txid = txid++; |
| apply_msg.hdr.ordinal = fuchsia_hardware_display_ControllerApplyConfigOrdinal; |
| return zx_channel_write(dc_handle, 0, &apply_msg, sizeof(apply_msg), NULL, 0); |
| } |
| |
| zx_status_t fb_enable_vsync(bool enable) { |
| fuchsia_hardware_display_ControllerEnableVsyncRequest enable_vsync; |
| enable_vsync.hdr.ordinal = fuchsia_hardware_display_ControllerEnableVsyncOrdinal; |
| enable_vsync.enable = enable; |
| zx_status_t status; |
| if ((status = zx_channel_write(dc_handle, 0, &enable_vsync, sizeof(enable_vsync), |
| NULL, 0)) != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t fb_wait_for_vsync(zx_time_t* timestamp, uint64_t* image_id) { |
| zx_status_t status; |
| |
| zx_handle_t observed; |
| uint32_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| if ((status = zx_object_wait_one(dc_handle, signals, ZX_TIME_INFINITE, |
| &observed)) != ZX_OK) { |
| return status; |
| } |
| if (observed & ZX_CHANNEL_PEER_CLOSED) { |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES]; |
| uint32_t actual_bytes, actual_handles; |
| if ((status = zx_channel_read(dc_handle, 0, bytes, NULL, ZX_CHANNEL_MAX_MSG_BYTES, 0, |
| &actual_bytes, &actual_handles)) != ZX_OK) { |
| return ZX_ERR_STOP; |
| } |
| |
| if (actual_bytes < sizeof(fidl_message_header_t)) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fidl_message_header_t* header = (fidl_message_header_t*)bytes; |
| |
| switch (header->ordinal) { |
| case fuchsia_hardware_display_ControllerDisplaysChangedOrdinal: |
| return ZX_ERR_STOP; |
| case fuchsia_hardware_display_ControllerClientOwnershipChangeOrdinal: |
| return ZX_ERR_NEXT; |
| case fuchsia_hardware_display_ControllerVsyncOrdinal: |
| break; |
| default: |
| return ZX_ERR_STOP; |
| } |
| |
| const char* err_msg; |
| if ((status = fidl_decode(&fuchsia_hardware_display_ControllerVsyncEventTable, bytes, |
| actual_bytes, NULL, 0, &err_msg)) != ZX_OK) { |
| return ZX_ERR_STOP; |
| } |
| |
| fuchsia_hardware_display_ControllerVsyncEvent* vsync = |
| (fuchsia_hardware_display_ControllerVsyncEvent*)bytes; |
| *timestamp = vsync->timestamp; |
| *image_id = vsync->images.count ? *((uint64_t*)vsync->images.data) : FB_INVALID_ID; |
| return ZX_OK; |
| } |