// 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 #include #include #include #include #include #include #include #include #include #include "fuchsia/display/c/fidl.h" #include "vc.h" static port_handler_t dc_ph; static int dc_fd; typedef struct display_info { uint64_t id; uint32_t width; uint32_t height; uint32_t stride; zx_pixel_format_t format; uint64_t image_id; uint64_t layer_id; struct list_node node; } display_info_t; static struct list_node display_list = LIST_INITIAL_VALUE(display_list); static bool displays_bound = false; // Owned by vc_gfx, only valid when displays_bound is true static zx_handle_t image_vmo = ZX_HANDLE_INVALID; static fuchsia_display_ImageConfig image_config; // remember whether the virtual console controls the display bool g_vc_owns_display = false; static zx_status_t vc_set_mode(uint8_t mode) { fuchsia_display_ControllerSetVirtconModeRequest request; request.hdr.ordinal = fuchsia_display_ControllerSetVirtconModeOrdinal; request.mode = mode; return zx_channel_write(dc_ph.handle, 0, &request, sizeof(request), nullptr, 0); } void vc_toggle_framebuffer() { if (list_is_empty(&display_list)) { return; } zx_status_t status = vc_set_mode(!g_vc_owns_display ? fuchsia_display_VirtconMode_FORCED : fuchsia_display_VirtconMode_FALLBACK); if (status != ZX_OK) { printf("vc: Failed to toggle ownership %d\n", status); } } static zx_status_t decode_message(void* bytes, uint32_t num_bytes) { fidl_message_header_t* header = (fidl_message_header_t*) bytes; if (num_bytes < sizeof(fidl_message_header_t)) { printf("vc: Unexpected short message (size=%d)\n", num_bytes); return ZX_ERR_INTERNAL; } zx_status_t res; const fidl_type_t* table = nullptr; if (header->ordinal == fuchsia_display_ControllerDisplaysChangedOrdinal) { table = &fuchsia_display_ControllerDisplaysChangedEventTable; } else if (header->ordinal == fuchsia_display_ControllerClientOwnershipChangeOrdinal) { table = &fuchsia_display_ControllerClientOwnershipChangeEventTable; } if (table != nullptr) { const char* err; if ((res = fidl_decode(table, bytes, num_bytes, nullptr, 0, &err)) != ZX_OK) { printf("vc: Error decoding message %d: %s\n", header->ordinal, err); } } else { printf("vc: Error unknown ordinal %d\n", header->ordinal); res = ZX_ERR_NOT_SUPPORTED; } return res; } static void handle_ownership_change(fuchsia_display_ControllerClientOwnershipChangeEvent* evt) { g_vc_owns_display = evt->has_ownership; // 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); } } static zx_status_t create_layer(uint64_t display_id, uint64_t* layer_id) { fuchsia_display_ControllerCreateLayerRequest create_layer_msg; create_layer_msg.hdr.ordinal = fuchsia_display_ControllerCreateLayerOrdinal; fuchsia_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); uint32_t actual_bytes, actual_handles; zx_status_t status; if ((status = zx_channel_call(dc_ph.handle, 0, ZX_TIME_INFINITE, &call_args, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Create layer call failed: %d (%s)\n", status, zx_status_get_string(status)); return status; } if (create_layer_rsp.res != ZX_OK) { printf("vc: Failed to create layer %d\n", create_layer_rsp.res); return create_layer_rsp.res; } *layer_id = create_layer_rsp.layer_id; return ZX_OK; } static void destroy_layer(uint64_t layer_id) { fuchsia_display_ControllerDestroyLayerRequest destroy_msg; destroy_msg.hdr.ordinal = fuchsia_display_ControllerDestroyLayerOrdinal; destroy_msg.layer_id = layer_id; if (zx_channel_write(dc_ph.handle, 0, &destroy_msg, sizeof(destroy_msg), nullptr, 0) != ZX_OK) { printf("vc: Failed to destroy layer\n"); } } static void release_image(uint64_t image_id) { fuchsia_display_ControllerReleaseImageRequest release_msg; release_msg.hdr.ordinal = fuchsia_display_ControllerReleaseImageOrdinal; release_msg.image_id = image_id; if (zx_channel_write(dc_ph.handle, 0, &release_msg, sizeof(release_msg), nullptr, 0)) { printf("vc: Failed to release image\n"); } } static zx_status_t handle_display_added(fuchsia_display_Info* info, fuchsia_display_Mode* mode, int32_t pixel_format) { fuchsia_display_ControllerComputeLinearImageStrideRequest stride_msg; stride_msg.hdr.ordinal = fuchsia_display_ControllerComputeLinearImageStrideOrdinal; stride_msg.width = mode->horizontal_resolution; stride_msg.pixel_format = pixel_format; fuchsia_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); uint32_t actual_bytes, actual_handles; zx_status_t status; if ((status = zx_channel_call(dc_ph.handle, 0, ZX_TIME_INFINITE, &stride_call, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Failed to compute fb stride: %d (%s)\n", status, zx_status_get_string(status)); return status; } if (stride_rsp.stride < mode->horizontal_resolution) { printf("vc: Got bad stride\n"); return ZX_ERR_INVALID_ARGS; } display_info_t* display_info = reinterpret_cast(malloc(sizeof(display_info_t))); if (!display_info) { printf("vc: failed to alloc display info\n"); return ZX_ERR_NO_MEMORY; } 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 = mode->horizontal_resolution; display_info->height = mode->vertical_resolution; display_info->stride = stride_rsp.stride; display_info->format = reinterpret_cast(info->pixel_format.data)[0]; display_info->image_id = 0; list_add_tail(&display_list, &display_info->node); return ZX_OK; } static 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); free(info); } else if (was_primary) { release_image(info->image_id); info->image_id = 0; } } if (was_primary) { set_log_listener_active(false); vc_free_gfx(); displays_bound = false; } } static zx_status_t allocate_vmo(uint32_t size, zx_handle_t* vmo_out) { fuchsia_display_ControllerAllocateVmoRequest alloc_msg; alloc_msg.hdr.ordinal = fuchsia_display_ControllerAllocateVmoOrdinal; alloc_msg.size = size; fuchsia_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 = vmo_out; 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; zx_status_t status; if ((status = zx_channel_call(dc_ph.handle, 0, ZX_TIME_INFINITE, &call_args, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Failed to alloc vmo: %d (%s)\n", status, zx_status_get_string(status)); return status; } if (alloc_rsp.res != ZX_OK) { printf("vc: Failed to alloc vmo %d\n", alloc_rsp.res); return alloc_rsp.res; } return actual_handles == 1 ? ZX_OK : ZX_ERR_INTERNAL; } static zx_status_t import_vmo(zx_handle_t vmo, fuchsia_display_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; } fuchsia_display_ControllerImportVmoImageRequest import_msg = {}; import_msg.hdr.ordinal = fuchsia_display_ControllerImportVmoImageOrdinal; import_msg.image_config = *config; import_msg.vmo = FIDL_HANDLE_PRESENT; import_msg.offset = 0; fuchsia_display_ControllerImportVmoImageResponse import_rsp; zx_channel_call_args_t call_args = {}; call_args.wr_bytes = &import_msg; call_args.wr_handles = &vmo_dup; call_args.rd_bytes = &import_rsp; call_args.wr_num_bytes = sizeof(import_msg); call_args.wr_num_handles = 1; call_args.rd_num_bytes = sizeof(import_rsp); uint32_t actual_bytes, actual_handles; if ((status = zx_channel_call(dc_ph.handle, 0, ZX_TIME_INFINITE, &call_args, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Failed to import vmo call %d (%s)\n", status, zx_status_get_string(status)); return status; } 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; } static zx_status_t set_display_layer(uint64_t display_id, uint64_t layer_id) { zx_status_t status; // Put the layer on the display uint8_t fidl_bytes[sizeof(fuchsia_display_ControllerSetDisplayLayersRequest) + FIDL_ALIGN(sizeof(uint64_t))]; auto set_display_layer_request = reinterpret_cast(fidl_bytes); set_display_layer_request->hdr.ordinal = fuchsia_display_ControllerSetDisplayLayersOrdinal; set_display_layer_request->display_id = display_id; set_display_layer_request->layer_ids.data = reinterpret_cast(FIDL_ALLOC_PRESENT); uint32_t size; if (layer_id) { set_display_layer_request->layer_ids.count = 1; *reinterpret_cast(set_display_layer_request + 1) = layer_id; size = sizeof(fidl_bytes); } else { set_display_layer_request->layer_ids.count = 0; size = sizeof(fuchsia_display_ControllerSetDisplayLayersRequest); } if ((status = zx_channel_write(dc_ph.handle, 0, fidl_bytes, size, nullptr, 0)) != ZX_OK) { printf("vc: Failed to set display layers %d\n", status); return status; } return ZX_OK; } static zx_status_t configure_layer(display_info_t* display, uint64_t layer_id, uint64_t image_id, fuchsia_display_ImageConfig* config) { zx_status_t status; fuchsia_display_ControllerSetLayerPrimaryConfigRequest layer_cfg_msg; layer_cfg_msg.hdr.ordinal = fuchsia_display_ControllerSetLayerPrimaryConfigOrdinal; layer_cfg_msg.layer_id = layer_id; layer_cfg_msg.image_config = *config; if ((status = zx_channel_write(dc_ph.handle, 0, &layer_cfg_msg, sizeof(layer_cfg_msg), nullptr, 0)) != ZX_OK) { printf("vc: Failed to set layer config %d\n", status); return status; } fuchsia_display_ControllerSetLayerPrimaryPositionRequest layer_pos_msg = {}; layer_pos_msg.hdr.ordinal = fuchsia_display_ControllerSetLayerPrimaryPositionOrdinal; layer_pos_msg.layer_id = layer_id; layer_pos_msg.transform = fuchsia_display_Transform_IDENTITY; layer_pos_msg.src_frame.width = config->width; layer_pos_msg.src_frame.height = config->height; layer_pos_msg.dest_frame.width = display->width; layer_pos_msg.dest_frame.height = display->height; if ((status = zx_channel_write(dc_ph.handle, 0, &layer_pos_msg, sizeof(layer_pos_msg), nullptr, 0)) != ZX_OK) { printf("vc: Failed to set layer position %d\n", status); return status; } fuchsia_display_ControllerSetLayerImageRequest set_msg; set_msg.hdr.ordinal = fuchsia_display_ControllerSetLayerImageOrdinal; set_msg.layer_id = layer_id; set_msg.image_id = image_id; if ((status = zx_channel_write(dc_ph.handle, 0, &set_msg, sizeof(set_msg), nullptr, 0)) != ZX_OK) { printf("vc: Failed to set image %d\n", status); return status; } return ZX_OK; } static zx_status_t apply_configuration() { // Validate and then apply the new configuration zx_status_t status; fuchsia_display_ControllerCheckConfigRequest check_msg; uint8_t check_rsp_bytes[ZX_CHANNEL_MAX_MSG_BYTES]; auto check_rsp = reinterpret_cast(check_rsp_bytes); check_msg.discard = false; check_msg.hdr.ordinal = fuchsia_display_ControllerCheckConfigOrdinal; zx_channel_call_args_t call_args = {}; call_args.wr_bytes = &check_msg; call_args.rd_bytes = check_rsp; call_args.wr_num_bytes = sizeof(check_msg); call_args.rd_num_bytes = sizeof(check_rsp_bytes); uint32_t actual_bytes, actual_handles; if ((status = zx_channel_call(dc_ph.handle, 0, ZX_TIME_INFINITE, &call_args, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Failed to validate display config: %d (%s)\n", status, zx_status_get_string(status)); return status; } if (check_rsp->res != fuchsia_display_ConfigResult_OK) { printf("vc: Config not valid %d\n", check_rsp->res); return ZX_ERR_INTERNAL; } fuchsia_display_ControllerApplyConfigRequest apply_msg; apply_msg.hdr.ordinal = fuchsia_display_ControllerApplyConfigOrdinal; if ((status = zx_channel_write(dc_ph.handle, 0, &apply_msg, sizeof(apply_msg), nullptr, 0)) != ZX_OK) { printf("vc: Applying config failed %d\n", status); return status; } return ZX_OK; } static 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; if (!displays_bound) { uint32_t size = primary->stride * primary->height * ZX_PIXEL_FORMAT_BYTES(primary->format); if ((status = allocate_vmo(size, &image_vmo)) != ZX_OK) { return ZX_ERR_NO_MEMORY; } image_config.height = primary->height; image_config.width = primary->width; image_config.pixel_format = primary->format; image_config.type = IMAGE_TYPE_SIMPLE; if ((status = vc_init_gfx(image_vmo, primary->width, primary->height, primary->format, primary->stride)) != ZX_OK) { printf("vc: failed to initialize graphics for new display %d\n", status); zx_handle_close(image_vmo); return status; } } 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 = import_vmo(image_vmo, &image_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, &image_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_show_active(); printf("vc: Successfully attached to display %ld\n", primary->id); displays_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; } } vc_free_gfx(); 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_display_changed(fuchsia_display_ControllerDisplaysChangedEvent* evt) { for (unsigned i = 0; i < evt->added.count; i++) { fuchsia_display_Info* info = reinterpret_cast(evt->added.data) + i; fuchsia_display_Mode* mode = reinterpret_cast(info->modes.data); int32_t pixel_format = reinterpret_cast(info->pixel_format.data)[0]; zx_status_t status = handle_display_added(info, mode, pixel_format); if (status != ZX_OK) { return status; } } for (unsigned i = 0; i < evt->removed.count; i++) { handle_display_removed(reinterpret_cast(evt->removed.data)[i]); } return rebind_display(true); } static 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); } close(dc_fd); zx_handle_close(dc_ph.handle); return ZX_ERR_STOP; } ZX_DEBUG_ASSERT(signals & ZX_CHANNEL_READABLE); zx_status_t status; uint32_t actual_bytes, actual_handles; uint8_t fidl_buffer[ZX_CHANNEL_MAX_MSG_BYTES]; if ((status = zx_channel_read(dc_ph.handle, 0, fidl_buffer, nullptr, ZX_CHANNEL_MAX_MSG_BYTES, 0, &actual_bytes, &actual_handles)) != ZX_OK) { printf("vc: Error reading display message %d\n", status); return ZX_OK; } if (decode_message(fidl_buffer, actual_bytes) != ZX_OK) { return ZX_OK; } fidl_message_header_t* header = (fidl_message_header_t*) fidl_buffer; switch (header->ordinal) { case fuchsia_display_ControllerDisplaysChangedOrdinal: { handle_display_changed( reinterpret_cast(fidl_buffer)); break; } case fuchsia_display_ControllerClientOwnershipChangeOrdinal: { auto evt = reinterpret_cast( fidl_buffer); handle_ownership_change(evt); break; } default: printf("vc: Unknown display callback message %d\n", header->ordinal); break; } return ZX_OK; } bool vc_display_init() { for (;;) { if ((dc_fd= open("/dev/class/display-controller/000/virtcon", O_RDWR)) >= 0) { break; } usleep(100000); } if (ioctl_display_controller_get_handle(dc_fd, &dc_ph.handle) != sizeof(zx_handle_t)) { printf("vc: failed to get display controller handle\n"); return false; } zx_status_t status = vc_set_mode(getenv("virtcon.hide-on-boot") == nullptr ? fuchsia_display_VirtconMode_FALLBACK : fuchsia_display_VirtconMode_INACTIVE); if (status != ZX_OK) { printf("vc: Failed to set initial ownership %d\n", status); return false; } 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); return false; } return true; }