diff --git a/src/libply-splash-core/ply-device-manager.c b/src/libply-splash-core/ply-device-manager.c index 8ceee10d..e2a9eaee 100644 --- a/src/libply-splash-core/ply-device-manager.c +++ b/src/libply-splash-core/ply-device-manager.c @@ -51,6 +51,9 @@ static bool create_devices_for_terminal_and_renderer_type (ply_device_manager_t const char *device_path, ply_terminal_t *terminal, ply_renderer_type_t renderer_type); +static void create_pixel_displays_for_renderer (ply_device_manager_t *manager, + ply_renderer_t *renderer); + struct _ply_device_manager { ply_device_manager_flags_t flags; @@ -372,6 +375,39 @@ create_devices_for_subsystem (ply_device_manager_t *manager, } static void +on_drm_udev_add_or_change (ply_device_manager_t *manager, + const char *action, + struct udev_device *device) +{ + const char *device_path = udev_device_get_devnode (device); + ply_renderer_t *renderer; + bool changed; + + if (device_path == NULL) + return; + + renderer = ply_hashtable_lookup (manager->renderers, (void *) device_path); + if (renderer == NULL) { + /* We also try to create the renderer again on change events, + * renderer creation fails when no outputs are connected and + * this may have changed. + */ + create_devices_for_udev_device (manager, device); + return; + } + + /* Renderer exists, bail if this is not a change event */ + if (strcmp (action, "change")) + return; + + changed = ply_renderer_handle_change_event (renderer); + if (changed) { + free_displays_for_renderer (manager, renderer); + create_pixel_displays_for_renderer (manager, renderer); + } +} + +static bool on_udev_event (ply_device_manager_t *manager) { struct udev_device *device; @@ -379,16 +415,16 @@ on_udev_event (ply_device_manager_t *manager) device = udev_monitor_receive_device (manager->udev_monitor); if (device == NULL) - return; + return false; action = udev_device_get_action (device); ply_trace ("got %s event for device %s", action, udev_device_get_sysname (device)); if (action == NULL) - return; + return false; - if (strcmp (action, "add") == 0) { + if (strcmp (action, "add") == 0 || strcmp (action, "change") == 0) { const char *subsystem; subsystem = udev_device_get_subsystem (device); @@ -397,7 +433,7 @@ on_udev_event (ply_device_manager_t *manager) if (manager->local_console_managed && manager->local_console_is_text) ply_trace ("ignoring since we're already using text splash for local console"); else - create_devices_for_udev_device (manager, device); + on_drm_udev_add_or_change (manager, action, device); } else { ply_trace ("ignoring since we only handle subsystem %s devices after timeout", subsystem); } @@ -406,6 +442,14 @@ on_udev_event (ply_device_manager_t *manager) } udev_device_unref (device); + return true; +} + +static void +on_udev_event_loop (ply_device_manager_t *manager) +{ + /* Call on_udev_event until all events are consumed */ + while (on_udev_event (manager)) {} } static void @@ -435,7 +479,7 @@ watch_for_udev_events (ply_device_manager_t *manager) fd, PLY_EVENT_LOOP_FD_STATUS_HAS_DATA, (ply_event_handler_t) - on_udev_event, + on_udev_event_loop, NULL, manager); } diff --git a/src/libply-splash-core/ply-renderer-plugin.h b/src/libply-splash-core/ply-renderer-plugin.h index db18d195..6b6ff25a 100644 --- a/src/libply-splash-core/ply-renderer-plugin.h +++ b/src/libply-splash-core/ply-renderer-plugin.h @@ -43,6 +43,7 @@ typedef struct bool (*open_device)(ply_renderer_backend_t *backend); void (*close_device)(ply_renderer_backend_t *backend); bool (*query_device)(ply_renderer_backend_t *backend); + bool (*handle_change_event)(ply_renderer_backend_t *backend); bool (*map_to_device)(ply_renderer_backend_t *backend); void (*unmap_from_device)(ply_renderer_backend_t *backend); void (*activate)(ply_renderer_backend_t *backend); diff --git a/src/libply-splash-core/ply-renderer.c b/src/libply-splash-core/ply-renderer.c index 5e83627a..e0a0784d 100644 --- a/src/libply-splash-core/ply-renderer.c +++ b/src/libply-splash-core/ply-renderer.c @@ -297,6 +297,15 @@ ply_renderer_close (ply_renderer_t *renderer) renderer->is_active = false; } +bool +ply_renderer_handle_change_event (ply_renderer_t *renderer) +{ + if (renderer->plugin_interface->handle_change_event) + return renderer->plugin_interface->handle_change_event (renderer->backend); + + return false; +} + void ply_renderer_activate (ply_renderer_t *renderer) { diff --git a/src/libply-splash-core/ply-renderer.h b/src/libply-splash-core/ply-renderer.h index c0e0ed59..820cdb63 100644 --- a/src/libply-splash-core/ply-renderer.h +++ b/src/libply-splash-core/ply-renderer.h @@ -55,6 +55,8 @@ ply_renderer_t *ply_renderer_new (ply_renderer_type_t renderer_type, void ply_renderer_free (ply_renderer_t *renderer); bool ply_renderer_open (ply_renderer_t *renderer); void ply_renderer_close (ply_renderer_t *renderer); +/* Returns true when the heads have changed as a result of the change event */ +bool ply_renderer_handle_change_event (ply_renderer_t *renderer); void ply_renderer_activate (ply_renderer_t *renderer); void ply_renderer_deactivate (ply_renderer_t *renderer); bool ply_renderer_is_active (ply_renderer_t *renderer); diff --git a/src/libply/ply-array.c b/src/libply/ply-array.c index 7d239534..26532cf7 100644 --- a/src/libply/ply-array.c +++ b/src/libply/ply-array.c @@ -169,4 +169,22 @@ ply_array_steal_uint32_elements (ply_array_t *array) return data; } +bool +ply_array_contains_uint32_element (ply_array_t *array, const uint32_t element) +{ + uint32_t const *elements; + int i, size; + + assert (array->element_type == PLY_ARRAY_ELEMENT_TYPE_UINT32); + + elements = (uint32_t const *) ply_buffer_get_bytes (array->buffer); + size = (ply_buffer_get_size (array->buffer) / sizeof(const uint32_t)) - 1; + + for (i = 0; i < size; i++) + if (elements[i] == element) + return true; + + return false; +} + /* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */ diff --git a/src/libply/ply-array.h b/src/libply/ply-array.h index 777485cc..3d8e9c47 100644 --- a/src/libply/ply-array.h +++ b/src/libply/ply-array.h @@ -23,6 +23,7 @@ #define PLY_ARRAY_H #include +#include typedef struct _ply_array ply_array_t; typedef enum _ply_array_element_type ply_array_element_type_t; @@ -46,6 +47,8 @@ uint32_t const *ply_array_get_uint32_elements (ply_array_t *array); void **ply_array_steal_pointer_elements (ply_array_t *array); uint32_t *ply_array_steal_uint32_elements (ply_array_t *array); +bool ply_array_contains_uint32_element (ply_array_t *array, + const uint32_t element); #endif #endif /* PLY_ARRAY_H */ diff --git a/src/plugins/renderers/drm/plugin.c b/src/plugins/renderers/drm/plugin.c index 20543eaa..0151f473 100644 --- a/src/plugins/renderers/drm/plugin.c +++ b/src/plugins/renderers/drm/plugin.c @@ -77,12 +77,12 @@ struct _ply_renderer_head unsigned long row_stride; ply_array_t *connector_ids; - drmModeConnector *connector0; - drmModeModeInfo *connector0_mode; + drmModeModeInfo connector0_mode; uint32_t controller_id; uint32_t console_buffer_id; uint32_t scan_out_buffer_id; + bool scan_out_buffer_needs_reset; int gamma_size; uint16_t *gamma; @@ -117,12 +117,16 @@ typedef struct typedef struct { - drmModeConnector *connector; - drmModeModeInfo *mode; + drmModeModeInfo mode; + uint32_t connector_id; + uint32_t connector_type; uint32_t controller_id; uint32_t possible_controllers; + int device_scale; + int link_status; ply_pixel_buffer_rotation_t rotation; bool tiled; + bool connected; } ply_output_t; struct _ply_renderer_backend @@ -136,10 +140,14 @@ struct _ply_renderer_backend ply_renderer_input_source_t input_source; ply_list_t *heads; - ply_hashtable_t *heads_by_connector_id; + ply_hashtable_t *heads_by_controller_id; ply_hashtable_t *output_buffers; + ply_output_t *outputs; + int outputs_len; + int connected_count; + int32_t dither_red; int32_t dither_green; int32_t dither_blue; @@ -164,19 +172,9 @@ static bool reset_scan_out_buffer_if_needed (ply_renderer_backend_t *backend, static void flush_head (ply_renderer_backend_t *backend, ply_renderer_head_t *head); -static bool efi_enabled (void) -{ - return ply_directory_exists ("/sys/firmware/efi/efivars"); -} - /* A small helper to determine if we should try to keep the current mode - * or pick the best mode ourselves, we keep the current mode if: - * 1. The user specified a specific mode using video= on the commandline - * 2. The code to pick the best mode was added because with flicker-free boot - * we can no longer rely on the kernel's fbcon code setting things up. - * We should be able to do a better job then fbcon regardless, but for - * now lets only use the new code on flicker-free systems until it is - * more mature, this means only using it on UEFI systems. + * or pick the best mode ourselves, we keep the current mode only if the + * user specified a specific mode using video= on the commandline. */ static bool should_use_preferred_mode (void) @@ -186,9 +184,6 @@ should_use_preferred_mode (void) if (ply_kernel_command_line_get_string_after_prefix ("video=")) use_preferred_mode = false; - if (!efi_enabled ()) - use_preferred_mode = false; - ply_trace ("should_use_preferred_mode: %d", use_preferred_mode); return use_preferred_mode; @@ -445,6 +440,7 @@ connector_orientation_prop_to_rotation (drmModePropertyPtr prop, static void ply_renderer_connector_get_rotation_and_tiled (ply_renderer_backend_t *backend, + drmModeConnector *connector, ply_output_t *output) { drmModePropertyPtr prop; @@ -453,52 +449,60 @@ ply_renderer_connector_get_rotation_and_tiled (ply_renderer_backend_t *back output->rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT; output->tiled = false; - for (i = 0; i < output->connector->count_props; i++) { - prop = drmModeGetProperty (backend->device_fd, output->connector->props[i]); + for (i = 0; i < connector->count_props; i++) { + prop = drmModeGetProperty (backend->device_fd, connector->props[i]); if (!prop) continue; if ((prop->flags & DRM_MODE_PROP_ENUM) && strcmp (prop->name, "panel orientation") == 0) - output->rotation = connector_orientation_prop_to_rotation (prop, output->connector->prop_values[i]); + output->rotation = connector_orientation_prop_to_rotation (prop, connector->prop_values[i]); if ((prop->flags & DRM_MODE_PROP_BLOB) && strcmp (prop->name, "TILE") == 0) output->tiled = true; + if ((prop->flags & DRM_MODE_PROP_ENUM) && + strcmp (prop->name, "link-status") == 0) { + output->link_status = connector->prop_values[i]; + ply_trace ("link-status %d", output->link_status); + } + drmModeFreeProperty (prop); } } static bool ply_renderer_head_add_connector (ply_renderer_head_t *head, - drmModeConnector *connector, - drmModeModeInfo *mode) + ply_output_t *output) { - if (mode->hdisplay != head->area.width || mode->vdisplay != head->area.height) { + if (output->link_status == DRM_MODE_LINK_STATUS_BAD) + head->scan_out_buffer_needs_reset = true; + + if (output->mode.hdisplay != head->area.width || output->mode.vdisplay != head->area.height) { ply_trace ("Tried to add connector with resolution %dx%d to %dx%d head", - (int) mode->hdisplay, (int) mode->vdisplay, + (int) output->mode.hdisplay, (int) output->mode.vdisplay, (int) head->area.width, (int) head->area.height); return false; - } else { - ply_trace ("Adding connector with id %d to %dx%d head", - (int) connector->connector_id, - (int) head->area.width, (int) head->area.height); } - ply_array_add_uint32_element (head->connector_ids, connector->connector_id); + if (ply_array_contains_uint32_element (head->connector_ids, output->connector_id)) { + ply_trace ("Head already contains connector with id %d", output->connector_id); + return false; + } + + ply_trace ("Adding connector with id %d to %dx%d head", + (int) output->connector_id, (int) head->area.width, (int) head->area.height); + ply_array_add_uint32_element (head->connector_ids, output->connector_id); return true; } static ply_renderer_head_t * ply_renderer_head_new (ply_renderer_backend_t *backend, - drmModeConnector *connector, - drmModeModeInfo *mode, - uint32_t controller_id, + ply_output_t *output, uint32_t console_buffer_id, - int gamma_size, - ply_pixel_buffer_rotation_t rotation) + int gamma_size) { ply_renderer_head_t *head; int i, step; @@ -507,16 +511,14 @@ ply_renderer_head_new (ply_renderer_backend_t *backend, head->backend = backend; head->connector_ids = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_UINT32); - head->controller_id = controller_id; + head->controller_id = output->controller_id; head->console_buffer_id = console_buffer_id; - - head->connector0 = connector; - head->connector0_mode = mode; + head->connector0_mode = output->mode; head->area.x = 0; head->area.y = 0; - head->area.width = mode->hdisplay; - head->area.height = mode->vdisplay; + head->area.width = output->mode.hdisplay; + head->area.height = output->mode.vdisplay; if (gamma_size) { head->gamma_size = gamma_size; @@ -530,29 +532,29 @@ ply_renderer_head_new (ply_renderer_backend_t *backend, } } - ply_renderer_head_add_connector (head, connector, mode); + ply_renderer_head_add_connector (head, output); assert (ply_array_get_size (head->connector_ids) > 0); - head->pixel_buffer = ply_pixel_buffer_new_with_device_rotation (head->area.width, head->area.height, rotation); - ply_pixel_buffer_set_device_scale (head->pixel_buffer, - ply_get_device_scale (head->area.width, - head->area.height, - connector->mmWidth, - connector->mmHeight)); + head->pixel_buffer = ply_pixel_buffer_new_with_device_rotation (head->area.width, head->area.height, output->rotation); + ply_pixel_buffer_set_device_scale (head->pixel_buffer, output->device_scale); ply_trace ("Creating %ldx%ld renderer head", head->area.width, head->area.height); ply_pixel_buffer_fill_with_color (head->pixel_buffer, NULL, 0.0, 0.0, 0.0, 1.0); - if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS || - connector->connector_type == DRM_MODE_CONNECTOR_eDP || - connector->connector_type == DRM_MODE_CONNECTOR_DSI) { - backend->panel_width = mode->hdisplay; - backend->panel_height = mode->vdisplay; - backend->panel_rotation = rotation; - backend->panel_scale = ply_pixel_buffer_get_device_scale (head->pixel_buffer); + if (output->connector_type == DRM_MODE_CONNECTOR_LVDS || + output->connector_type == DRM_MODE_CONNECTOR_eDP || + output->connector_type == DRM_MODE_CONNECTOR_DSI) { + backend->panel_width = output->mode.hdisplay; + backend->panel_height = output->mode.vdisplay; + backend->panel_rotation = output->rotation; + backend->panel_scale = output->device_scale; } + ply_list_append_data (backend->heads, head); + ply_hashtable_insert (backend->heads_by_controller_id, + (void *) (intptr_t) output->controller_id, + head); return head; } @@ -562,7 +564,6 @@ ply_renderer_head_free (ply_renderer_head_t *head) ply_trace ("freeing %ldx%ld renderer head", head->area.width, head->area.height); ply_pixel_buffer_free (head->pixel_buffer); - drmModeFreeConnector (head->connector0); ply_array_free (head->connector_ids); free (head->gamma); free (head); @@ -652,7 +653,7 @@ ply_renderer_head_set_scan_out_buffer (ply_renderer_backend_t *backend, ply_renderer_head_t *head, uint32_t buffer_id) { - drmModeModeInfo *mode = head->connector0_mode; + drmModeModeInfo *mode = &head->connector0_mode; uint32_t *connector_ids; int number_of_connectors; @@ -712,11 +713,7 @@ ply_renderer_head_map (ply_renderer_backend_t *backend, return false; } - /* FIXME: Maybe we should blit the fbcon contents instead of the (blank) - * shadow buffer? - */ - ply_renderer_head_redraw (backend, head); - + head->scan_out_buffer_needs_reset = true; return true; } @@ -731,6 +728,47 @@ ply_renderer_head_unmap (ply_renderer_backend_t *backend, head->scan_out_buffer_id = 0; } +static void +ply_renderer_head_remove (ply_renderer_backend_t *backend, + ply_renderer_head_t *head) +{ + if (head->scan_out_buffer_id) + ply_renderer_head_unmap (backend, head); + + ply_hashtable_remove (backend->heads_by_controller_id, + (void *) (intptr_t) head->controller_id); + ply_list_remove_data (backend->heads, head); + ply_renderer_head_free (head); +} + +static void +ply_renderer_head_remove_connector (ply_renderer_backend_t *backend, + ply_renderer_head_t *head, + uint32_t connector_id) +{ + int i, size = ply_array_get_size (head->connector_ids); + uint32_t *connector_ids; + + if (!ply_array_contains_uint32_element (head->connector_ids, connector_id)) { + ply_trace ("Head does not contain connector %u, cannot remove", connector_id); + return; + } + + if (size == 1) { + ply_renderer_head_remove (backend, head); + return; + } + + /* Empty the array and re-add all connectors except the one being removed */ + connector_ids = ply_array_steal_uint32_elements (head->connector_ids); + for (i = 0; i < size; i++) { + if (connector_ids[i] != connector_id) + ply_array_add_uint32_element (head->connector_ids, + connector_ids[i]); + } + free (connector_ids); +} + static void flush_area (const char *src, unsigned long src_row_stride, @@ -816,6 +854,7 @@ create_backend (const char *device_name, backend->requires_explicit_flushing = true; backend->output_buffers = ply_hashtable_new (ply_hashtable_direct_hash, ply_hashtable_direct_compare); + backend->heads_by_controller_id = ply_hashtable_new (NULL, NULL); backend->use_preferred_mode = should_use_preferred_mode (); return backend; @@ -835,9 +874,9 @@ destroy_backend (ply_renderer_backend_t *backend) free (backend->device_name); ply_hashtable_free (backend->output_buffers); + ply_hashtable_free (backend->heads_by_controller_id); - drmModeFreeResources (backend->resources); - + free (backend->outputs); free (backend); } @@ -981,6 +1020,7 @@ close_device (ply_renderer_backend_t *backend) static void output_get_controller_info (ply_renderer_backend_t *backend, + drmModeConnector *connector, ply_output_t *output) { int i; @@ -990,16 +1030,16 @@ output_get_controller_info (ply_renderer_backend_t *backend, output->possible_controllers = 0xffffffff; - for (i = 0; i < output->connector->count_encoders; i++) { + for (i = 0; i < connector->count_encoders; i++) { encoder = drmModeGetEncoder (backend->device_fd, - output->connector->encoders[i]); + connector->encoders[i]); if (encoder == NULL) continue; - if (encoder->encoder_id == output->connector->encoder_id && encoder->crtc_id) { + if (encoder->encoder_id == connector->encoder_id && encoder->crtc_id) { ply_trace ("Found already lit monitor on connector %u using controller %u", - output->connector->connector_id, encoder->crtc_id); + connector->connector_id, encoder->crtc_id); output->controller_id = encoder->crtc_id; } @@ -1008,7 +1048,7 @@ output_get_controller_info (ply_renderer_backend_t *backend, */ output->possible_controllers &= encoder->possible_crtcs; ply_trace ("connector %u encoder %u possible controllers 0x%08x/0x%08x", - output->connector->connector_id, encoder->encoder_id, + connector->connector_id, encoder->encoder_id, encoder->possible_crtcs, output->possible_controllers); drmModeFreeEncoder (encoder); } @@ -1071,6 +1111,7 @@ get_preferred_mode (drmModeConnector *connector) static drmModeModeInfo * get_active_mode (ply_renderer_backend_t *backend, + drmModeConnector *connector, ply_output_t *output) { drmModeCrtc *controller; @@ -1085,13 +1126,55 @@ get_active_mode (ply_renderer_backend_t *backend, ply_trace ("Looking for connector mode index of active mode %dx%d", controller->mode.hdisplay, controller->mode.vdisplay); - mode = find_matching_connector_mode (backend, output->connector, &controller->mode); + mode = find_matching_connector_mode (backend, connector, &controller->mode); drmModeFreeCrtc (controller); return mode; } +static void +get_output_info (ply_renderer_backend_t *backend, + uint32_t connector_id, + ply_output_t *output) +{ + drmModeModeInfo *mode = NULL; + drmModeConnector *connector; + + memset (output, 0, sizeof(*output)); + output->connector_id = connector_id; + + connector = drmModeGetConnector (backend->device_fd, connector_id); + if (connector == NULL) + return; + + if (connector->connection != DRM_MODE_CONNECTED || + connector->count_modes <= 0) + goto out; + + output_get_controller_info (backend, connector, output); + ply_renderer_connector_get_rotation_and_tiled (backend, connector, output); + + if (!output->tiled && backend->use_preferred_mode) + mode = get_preferred_mode (connector); + + if (!mode && output->controller_id) + mode = get_active_mode (backend, connector, output); + + /* If we couldn't find the current active mode, fall back to the first available. */ + if (!mode) { + ply_trace ("falling back to first available mode"); + mode = &connector->modes[0]; + } + output->mode = *mode; + output->device_scale = ply_get_device_scale (mode->hdisplay, mode->vdisplay, + connector->mmWidth, connector->mmHeight); + output->connector_type = connector->connector_type; + output->connected = true; +out: + drmModeFreeConnector (connector); +} + /* Some controllers can only drive some outputs, we want to find a combination * where all (connected) outputs get a controller. To do this setup_outputs * picks which output to assign a controller for first (trying all outputs), so @@ -1158,9 +1241,9 @@ setup_outputs (ply_renderer_backend_t *backend, best_count = count_setup_controllers (outputs, outputs_len); best_outputs = outputs; - for (i = 0; i < outputs_len && best_count < outputs_len; i++) { - /* Already assigned? */ - if (outputs[i].controller_id) + for (i = 0; i < outputs_len && best_count < backend->connected_count; i++) { + /* Not connected or already assigned? */ + if (!outputs[i].connected || outputs[i].controller_id) continue; /* Assign controller for connector i */ @@ -1199,62 +1282,73 @@ setup_outputs (ply_renderer_backend_t *backend, return (ply_output_t *)best_outputs; } -static bool -create_heads_for_active_connectors (ply_renderer_backend_t *backend) +static void +remove_output (ply_renderer_backend_t *backend, ply_output_t *output) { - ply_hashtable_t *heads_by_controller_id; - ply_output_t *outputs; - int i, j, found, number_of_setup_outputs, outputs_len; + ply_renderer_head_t *head; - heads_by_controller_id = ply_hashtable_new (NULL, NULL); + head = ply_hashtable_lookup (backend->heads_by_controller_id, + (void *) (intptr_t) output->controller_id); + if (head == NULL) { + ply_trace ("Could not find head for connector %u, controller %u, cannot remove", + output->connector_id, output->controller_id); + return; + } - outputs = calloc (backend->resources->count_connectors, sizeof(*outputs)); + ply_renderer_head_remove_connector (backend, head, output->connector_id); +} + +/* Update our outputs array to match the hardware state and + * create and/or remove heads as necessary. + * Returns true if any heads were modified. + */ +static bool +create_heads_for_active_connectors (ply_renderer_backend_t *backend, bool change) +{ + int i, j, number_of_setup_outputs, outputs_len; + ply_output_t output, *outputs; + bool changed = false; /* Step 1: - * Build a list of connected outputs and get pre-configured controllers. + * Remove existing outputs from heads if they have changed. */ - found = 0; - for (i = 0; i < backend->resources->count_connectors; i++) { - drmModeConnector *connector; - - connector = drmModeGetConnector (backend->device_fd, - backend->resources->connectors[i]); - if (connector == NULL) + ply_trace ("Checking currently connected outputs for changes"); + for (i = 0; i < backend->outputs_len; i++) { + if (!backend->outputs[i].controller_id) continue; - if (connector->connection != DRM_MODE_CONNECTED) { - drmModeFreeConnector (connector); - continue; + get_output_info (backend, backend->outputs[i].connector_id, &output); + + if (memcmp(&backend->outputs[i], &output, sizeof(ply_output_t))) { + ply_trace ("Output for connector %u changed, removing", + backend->outputs[i].connector_id); + remove_output (backend, &backend->outputs[i]); + changed = true; } - - if (connector->count_modes <= 0) { - drmModeFreeConnector (connector); - continue; - } - - outputs[found].connector = connector; - - output_get_controller_info (backend, &outputs[found]); - ply_renderer_connector_get_rotation_and_tiled (backend, &outputs[found]); - - if (!outputs[found].tiled && backend->use_preferred_mode) - outputs[found].mode = get_preferred_mode (connector); - - if (!outputs[found].mode && outputs[found].controller_id) - outputs[found].mode = get_active_mode (backend, &outputs[found]); - - /* If we couldn't find the current active mode, fall back to the first available. - */ - if (!outputs[found].mode) { - ply_trace ("falling back to first available mode"); - outputs[found].mode = &connector->modes[0]; - } - - found++; } - outputs_len = found; /* outputs now contains found valid entries */ /* Step 2: + * Now that we've removed changed connectors from the heads, we can + * simply rebuild the outputs array from scratch. For any unchanged + * outputs for which we already have a head, we will end up in + * ply_renderer_head_add_connector which will ignore the already + * added connector. + */ + ply_trace ("(Re)enumerating all outputs"); + free (backend->outputs); + backend->outputs = NULL; + + outputs = calloc (backend->resources->count_connectors, sizeof(*outputs)); + outputs_len = backend->resources->count_connectors; + + backend->connected_count = 0; + for (i = 0; i < outputs_len; i++) { + get_output_info (backend, backend->resources->connectors[i], &outputs[i]); + if (outputs[i].connected) + backend->connected_count++; + } + + /* Step 3: * Drop controllers for clones for which we've picked different modes. */ for (i = 0; i < outputs_len; i++) { @@ -1266,28 +1360,31 @@ create_heads_for_active_connectors (ply_renderer_backend_t *backend) continue; if (outputs[i].controller_id == outputs[j].controller_id && - (outputs[i].mode->hdisplay != outputs[j].mode->hdisplay || - outputs[i].mode->vdisplay != outputs[j].mode->vdisplay)) { + (outputs[i].mode.hdisplay != outputs[j].mode.hdisplay || + outputs[i].mode.vdisplay != outputs[j].mode.vdisplay)) { ply_trace ("connector %u uses same controller as %u and modes differ, unlinking controller", - outputs[j].connector->connector_id, - outputs[i].connector->connector_id); + outputs[j].connector_id, outputs[i].connector_id); outputs[j].controller_id = 0; } } } - /* Step 3: + /* Step 4: * Assign controllers to outputs without a controller */ number_of_setup_outputs = count_setup_controllers (outputs, outputs_len); - if (number_of_setup_outputs != outputs_len) { + if (number_of_setup_outputs != backend->connected_count) { /* First try, try to assign controllers to outputs without one */ ply_trace ("Some outputs don't have controllers, picking controllers"); outputs = setup_outputs (backend, outputs, outputs_len); number_of_setup_outputs = count_setup_controllers (outputs, outputs_len); } - if (number_of_setup_outputs != outputs_len) { - /* Second try, re-assing controller for all outputs */ + /* Try again if necessary, re-assing controllers for all outputs. + * Note this is skipped when processing change events, as we don't + * want to mess with the controller assignment of already lit monitors + * in that case. + */ + if (!change && number_of_setup_outputs != backend->connected_count) { ply_trace ("Some outputs still don't have controllers, re-assigning controllers for all outputs"); for (i = 0; i < outputs_len; i++) outputs[i].controller_id = 0; @@ -1295,55 +1392,50 @@ create_heads_for_active_connectors (ply_renderer_backend_t *backend) } for (i = 0; i < outputs_len; i++) ply_trace ("Using controller %u for connector %u", - outputs[i].controller_id, outputs[i].connector->connector_id); + outputs[i].controller_id, outputs[i].connector_id); - /* Step 4: + /* Step 5: * Create heads for all valid outputs */ for (i = 0; i < outputs_len; i++) { - drmModeConnector *connector = outputs[i].connector; drmModeCrtc *controller; ply_renderer_head_t *head; uint32_t controller_id; uint32_t console_buffer_id; int gamma_size; - controller = drmModeGetCrtc (backend->device_fd, outputs[i].controller_id); - if (!controller) { - drmModeFreeConnector (connector); + if (!outputs[i].controller_id) + continue; + + controller = drmModeGetCrtc (backend->device_fd, outputs[i].controller_id); + if (!controller) continue; - } controller_id = controller->crtc_id; console_buffer_id = controller->buffer_id; gamma_size = controller->gamma_size; drmModeFreeCrtc (controller); - head = ply_hashtable_lookup (heads_by_controller_id, + head = ply_hashtable_lookup (backend->heads_by_controller_id, (void *) (intptr_t) controller_id); if (head == NULL) { - head = ply_renderer_head_new (backend, connector, outputs[i].mode, - controller_id, console_buffer_id, - gamma_size, outputs[i].rotation); - - ply_list_append_data (backend->heads, head); - - ply_hashtable_insert (heads_by_controller_id, - (void *) (intptr_t) controller_id, - head); + head = ply_renderer_head_new (backend, &outputs[i], + console_buffer_id, + gamma_size); + changed = true; } else { - if (!ply_renderer_head_add_connector (head, connector, outputs[i].mode)) - ply_trace ("couldn't connect monitor to existing head"); - - drmModeFreeConnector (connector); + if (ply_renderer_head_add_connector (head, &outputs[i])) + changed = true; } } - ply_hashtable_free (heads_by_controller_id); - free (outputs); + backend->outputs_len = outputs_len; + backend->outputs = outputs; - return ply_list_get_length (backend->heads) > 0; + ply_trace ("outputs %schanged\n", changed ? "" : "un"); + + return changed; } static bool @@ -1383,6 +1475,8 @@ has_32bpp_support (ply_renderer_backend_t *backend) static bool query_device (ply_renderer_backend_t *backend) { + bool ret = true; + assert (backend != NULL); assert (backend->device_fd >= 0); @@ -1393,17 +1487,37 @@ query_device (ply_renderer_backend_t *backend) return false; } - if (!create_heads_for_active_connectors (backend)) { + if (!create_heads_for_active_connectors (backend, false)) { ply_trace ("Could not initialize heads"); - return false; - } - - if (!has_32bpp_support (backend)) { + ret = false; + } else if (!has_32bpp_support (backend)) { ply_trace ("Device doesn't support 32bpp framebuffer"); + ret = false; + } + + drmModeFreeResources (backend->resources); + backend->resources = NULL; + + return ret; +} + +static bool +handle_change_event (ply_renderer_backend_t *backend) +{ + bool ret = true; + + backend->resources = drmModeGetResources (backend->device_fd); + if (backend->resources == NULL) { + ply_trace ("Could not get card resources for change event"); return false; } - return true; + ret = create_heads_for_active_connectors (backend, true); + + drmModeFreeResources (backend->resources); + backend->resources = NULL; + + return ret; } static bool @@ -1421,8 +1535,13 @@ map_to_device (ply_renderer_backend_t *backend) head = (ply_renderer_head_t *) ply_list_node_get_data (node); next_node = ply_list_get_next_node (backend->heads, node); - if (ply_renderer_head_map (backend, head)) + if (ply_renderer_head_map (backend, head)) { + /* FIXME: Maybe we should blit the fbcon contents instead of the (blank) + * shadow buffer? + */ + ply_renderer_head_redraw (backend, head); head_mapped = true; + } node = next_node; } @@ -1469,6 +1588,13 @@ reset_scan_out_buffer_if_needed (ply_renderer_backend_t *backend, if (!ply_terminal_is_active (backend->terminal)) return false; + if (head->scan_out_buffer_needs_reset) { + ply_renderer_head_set_scan_out_buffer (backend, head, + head->scan_out_buffer_id); + head->scan_out_buffer_needs_reset = false; + return true; + } + controller = drmModeGetCrtc (backend->device_fd, head->controller_id); if (controller == NULL) @@ -1508,6 +1634,12 @@ flush_head (ply_renderer_backend_t *backend, updated_region = ply_pixel_buffer_get_updated_areas (pixel_buffer); areas_to_flush = ply_region_get_sorted_rectangle_list (updated_region); + /* A hotplugged head may not be mapped yet, map it now. */ + if (!head->scan_out_buffer_id) { + if (!ply_renderer_head_map (backend, head)) + return; + } + map_address = begin_flush (backend, head->scan_out_buffer_id); node = ply_list_get_first_node (areas_to_flush); @@ -1673,6 +1805,7 @@ ply_renderer_backend_get_interface (void) .open_device = open_device, .close_device = close_device, .query_device = query_device, + .handle_change_event = handle_change_event, .map_to_device = map_to_device, .unmap_from_device = unmap_from_device, .activate = activate,