From 97e638a2ac0fb53a4bae0ef24b8804c6928a504a Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Wed, 13 Nov 2024 17:17:06 +0100 Subject: [PATCH] intel/display: avoid mode detection in irq task On hotplug, the C++ side of the driver now gets just notified, without any further detection. On the C++ side, now specific actions are scheduled explicitly, which will be executed step by step. New appearing actions will be queued and are not eligible to run before the former actions of the list are processed. Fixe #5392 --- repos/pc/run/intel_fb.run | 2 +- .../src/driver/framebuffer/intel/pc/lx_i915.h | 17 +- .../src/driver/framebuffer/intel/pc/lx_user.c | 1293 +++++++---------- .../src/driver/framebuffer/intel/pc/main.cc | 252 +++- 4 files changed, 737 insertions(+), 827 deletions(-) diff --git a/repos/pc/run/intel_fb.run b/repos/pc/run/intel_fb.run index c4395e087f..eeb4457638 100644 --- a/repos/pc/run/intel_fb.run +++ b/repos/pc/run/intel_fb.run @@ -398,7 +398,7 @@ install_config $config build_boot_image [build_artifacts] if { [get_cmd_switch --autopilot] } { - run_genode_until {\[init -\> init_dynamic -\> intel_fb\] HDMI-A-3: enable.*} 30 + run_genode_until {\[init -\> init_dynamic -\> intel_fb\].*connector connected="true" name="HDMI-A-3"} 30 run_genode_until {\} 20 [output_spawn_id] run_genode_until {green} 30 [output_spawn_id] } else { diff --git a/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h b/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h index 47d5676846..e94d9fb90c 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h +++ b/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2022 Genode Labs GmbH + * Copyright (C) 2022-2024 Genode Labs GmbH * * This file is distributed under the terms of the GNU General Public License * version 2. @@ -33,15 +33,28 @@ struct genode_mode { char name[32]; }; +enum Action { + ACTION_IDLE = 0, + ACTION_DETECT_MODES = 1, + ACTION_CONFIGURE = 2, + ACTION_REPORT = 3, + ACTION_NEW_CONFIG = 4, + ACTION_READ_CONFIG = 5, + ACTION_HOTPLUG = 6, + ACTION_EXIT = 7, + ACTION_FAILED = 9, +}; + int lx_emul_i915_blit(unsigned const connector_id, char const may_sleep); void lx_emul_i915_wakeup(unsigned connector_id); void lx_emul_i915_report_discrete(void * genode_xml); void lx_emul_i915_report_non_discrete(void * genode_xml); void lx_emul_i915_hotplug_connector(void); +int lx_emul_i915_action_to_process(int); void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, char const *name, char connected, - char modes, + char valid_fb, unsigned brightness, unsigned width_mm, unsigned height_mm); void lx_emul_i915_iterate_modes(void *lx_data, void * genode_data); diff --git a/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c b/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c index 78ecb794e1..563b729022 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c +++ b/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c @@ -25,34 +25,41 @@ #include "lx_emul.h" -enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 }; +enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 }; +enum { MAX_CONNECTORS = 32, CONNECTOR_ID_MIRROR = MAX_CONNECTORS - 1 }; +enum { CAPTURE_RATE_MS = 10, ATTEMPTS_BEFORE_STOP = 7 }; + struct task_struct * lx_user_task = NULL; +static struct task_struct * lx_update_task = NULL; +static struct drm_client_dev * dev_client = NULL; - struct task_struct * lx_user_task = NULL; -static struct task_struct * lx_update_task = NULL; -static struct drm_client_dev * dev_client = NULL; +static bool const verbose = false; - -enum { CONNECTOR_ID_MIRROR = 15 }; -static bool mirrored [16] = { }; +static struct state { + struct drm_mode_create_dumb fb_dumb; + struct drm_mode_fb_cmd2 fb_cmd; + struct drm_framebuffer * fbs; + struct i915_vma * vma; + unsigned long vma_flags; + uint8_t mode_id; + uint8_t unchanged; + bool mirrored; + bool enabled; +} states [MAX_CONNECTORS] = { }; static int user_register_fb(struct drm_client_dev const * const dev, struct fb_info * const info, - struct drm_mode_fb_cmd2 const * const dumb_fb, + struct drm_framebuffer * const fb, + struct i915_vma ** vma, + unsigned long * vma_flags, unsigned width_mm, unsigned height_mm); -static int user_attach_fb_to_crtc(struct drm_client_dev * const dev, - struct drm_connector const * const connector, - struct drm_crtc const * const crtc, - struct drm_mode_modeinfo const * const mode, - unsigned const fb_id, - bool const enable); - static int check_resize_fb(struct drm_client_dev * const dev, struct drm_mode_create_dumb * const gem_dumb, struct drm_mode_fb_cmd2 * const dumb_fb, + bool * const resized, unsigned const width, unsigned const height); @@ -81,14 +88,16 @@ static inline bool conf_larger_mode(struct genode_mode const * const g, } -static inline bool fb_smaller_mode(struct fb_info const * const info, - struct drm_display_mode const * const mode) +static inline bool fb_mirror_compatible(struct drm_display_mode const * const a, + struct drm_display_mode const * const b) { - return (uint64_t)info->var.xres * (uint64_t)info->var.yres < - (uint64_t)mode->vdisplay * (uint64_t)mode->hdisplay; + return a->vdisplay <= b->vdisplay && a->hdisplay <= b->hdisplay; } +static int probe_and_apply_fbs(struct drm_client_dev *client, bool); + + /* * Heuristic to calculate mixed resolution across all mirrored connectors */ @@ -229,141 +238,28 @@ static unsigned get_brightness(struct drm_connector * const connector, } -static struct drm_mode_fb_cmd2 *mirror_fb_cmd; - - -static struct drm_framebuffer * lookup_framebuffer(struct drm_crtc *crtc, - struct drm_modeset_acquire_ctx *ctx) -{ - struct drm_atomic_state *state; - struct drm_plane_state *plane; - struct drm_crtc_state *crtc_state; - - if (!crtc || !crtc->dev || !ctx) - return NULL; - - state = drm_atomic_state_alloc(crtc->dev); - if (!state) - return NULL; - - state->acquire_ctx = ctx; - - crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(crtc_state)) { - drm_atomic_state_put(state); - return NULL; - } - - plane = drm_atomic_get_plane_state(state, crtc->primary); - - drm_atomic_state_put(state); - - return plane ? plane->fb : NULL; -} - - -enum { MAX_FBS = 8 }; - - -/* own data structure for tracking dumb buffers, e.g. GEM handles, flags, vma */ -struct gem_dumb { - struct drm_mode_create_dumb fb_dumb; - struct drm_mode_fb_cmd2 fb_cmd; - struct i915_vma * vma; - unsigned long flags; -}; - - -/* allocator and lookup helper for our own meta data */ -static bool _meta_data(struct drm_client_dev const * const dev, - struct drm_framebuffer const * const fb, - struct drm_mode_create_dumb ** fb_dumb, /* out */ - struct drm_mode_fb_cmd2 ** fb_cmd, /* out */ - struct gem_dumb ** gem_dumb) /* out */ -{ - static struct gem_dumb gem_dumb_list [MAX_FBS] = { }; - - struct gem_dumb * free_slot = NULL; - - if (!dev) - return false; - - for (unsigned i = 0; i < MAX_FBS; i++) { - struct gem_dumb * slot = &gem_dumb_list[i]; - struct drm_framebuffer * cmp = NULL; - - if (!slot->fb_cmd.fb_id) { - if (!free_slot) - free_slot = slot; - - continue; - } - - if (!fb) - continue; - - cmp = drm_framebuffer_lookup(dev->dev, dev->file, - slot->fb_cmd.fb_id); - - if (cmp) - drm_framebuffer_put(cmp); - - /* lookup case */ - if (cmp == fb) { - if (fb_dumb) *fb_dumb = &slot->fb_dumb; - if (fb_cmd) *fb_cmd = &slot->fb_cmd; - if (gem_dumb) *gem_dumb = slot; - return true; - } - } - - /* allocate case */ - if (free_slot) { - if (fb_dumb) *fb_dumb = &free_slot->fb_dumb; - if (fb_cmd) *fb_cmd = &free_slot->fb_cmd; - if (gem_dumb) *gem_dumb = free_slot; - } - - return !!free_slot; -} - - -static bool dumb_meta(struct drm_client_dev const * const dev, - struct drm_framebuffer const * const fb, - struct drm_mode_create_dumb ** fb_dumb, /* out */ - struct drm_mode_fb_cmd2 ** fb_cmd) /* out */ -{ - return _meta_data(dev, fb, fb_dumb, fb_cmd, NULL); -} - - -static bool dumb_gem(struct drm_client_dev const * const dev, - struct drm_framebuffer const * const fb, - struct gem_dumb ** gem_dumb) /* out */ -{ - return _meta_data(dev, fb, NULL, NULL, gem_dumb); -} - - static void destroy_fb(struct drm_client_dev * const dev, struct drm_mode_create_dumb * const gem_dumb, struct drm_mode_fb_cmd2 * const dumb_fb) { - int result = drm_mode_rmfb(dev->dev, dumb_fb->fb_id, dev->file); + if (dumb_fb->fb_id) { + int result = drm_mode_rmfb(dev->dev, dumb_fb->fb_id, dev->file); - if (result) { - drm_err(dev->dev, "%s: failed to remove framebuffer %d\n", - __func__, result); + if (result) { + drm_err(dev->dev, "%s: failed to remove framebuffer %d\n", + __func__, result); + } } - result = drm_mode_destroy_dumb(dev->dev, gem_dumb->handle, dev->file); + if (gem_dumb->handle) { + int result = drm_mode_destroy_dumb(dev->dev, gem_dumb->handle, dev->file); - if (result) { - drm_err(dev->dev, "%s: failed to destroy framebuffer %d\n", - __func__, result); + if (result) { + drm_err(dev->dev, "%s: failed to destroy framebuffer %d\n", + __func__, result); + } } - /* frees the entry in _meta_data allocator */ memset(gem_dumb, 0, sizeof(*gem_dumb)); memset(dumb_fb, 0, sizeof(*dumb_fb)); } @@ -390,21 +286,9 @@ static int kernel_register_fb(struct fb_info const * const fb_info, static void destroy_fb_and_capture(struct drm_client_dev * const dev, struct drm_connector const * const connector, - struct drm_framebuffer * const fb) + struct state * const state) { - struct drm_mode_create_dumb * dumb_create = NULL; - struct drm_mode_fb_cmd2 * fb_cmd = NULL; - struct gem_dumb * gem_dumb = NULL; - struct fb_info info = {}; - - if (!fb) - return; - - if (!dumb_meta(dev, fb, &dumb_create, &fb_cmd)) - return; - - if (!dumb_gem(dev, fb, &gem_dumb)) - return; + struct fb_info info = {}; info.var.bits_per_pixel = 32; info.node = connector->index; @@ -412,130 +296,47 @@ static void destroy_fb_and_capture(struct drm_client_dev * const dev, kernel_register_fb(&info, 0, 0); - if (gem_dumb->vma) { - intel_unpin_fb_vma(gem_dumb->vma, gem_dumb->flags); + if (state->vma) { + intel_unpin_fb_vma(state->vma, + state->vma_flags); - gem_dumb->vma = NULL; - gem_dumb->flags = 0; + state->vma = NULL; + state->vma_flags = 0; } - destroy_fb(dev, dumb_create, fb_cmd); -} + state->enabled = false; + state->unchanged = 0; - -struct drm_mode_fb_cmd2 fb_of_screen(struct drm_client_dev * const dev, - struct genode_mode const * const conf_mode, - struct fb_info * const fb_info, - struct drm_mode_fb_cmd2 const * const dumb_fb_mirror, - struct drm_display_mode const * const mode, - struct drm_framebuffer * fb, - struct drm_connector const * const connector) -{ - int err = -EINVAL; - struct drm_mode_create_dumb *gem_dumb = NULL; - struct drm_mode_fb_cmd2 *fb_cmd = NULL; - struct drm_framebuffer *fb_mirror = drm_framebuffer_lookup(dev->dev, - dev->file, - dumb_fb_mirror->fb_id); - - /* during hotplug the mirrored fb is used for non mirrored connectors temporarily */ - if (fb && !conf_mode->mirror && fb == fb_mirror) { - fb = NULL; - } - - if (!dumb_meta(dev, fb, &gem_dumb, &fb_cmd) || !gem_dumb || !fb_cmd) { - struct drm_mode_fb_cmd2 invalid = { }; - printk("could not create dumb buffer\n"); - return invalid; - } - - /* notify genode side about switch from connector specific fb to mirror fb */ - if (fb && conf_mode->mirror && fb != fb_mirror) { - - destroy_fb_and_capture(dev, connector, fb); - - } - - if (fb_mirror) - drm_framebuffer_put(fb_mirror); - - fb_info->node = connector->index; - - if (!conf_mode->enabled) { - struct drm_mode_fb_cmd2 invalid = { }; - return invalid; - } - - if (conf_mode->mirror) - return *dumb_fb_mirror; - - err = check_resize_fb(dev, gem_dumb, fb_cmd, - mode->hdisplay, mode->vdisplay); - - if (err) { - printk("setting up framebuffer of %ux%u failed - error=%d\n", - mode->hdisplay, mode->vdisplay, err); - return *dumb_fb_mirror; - } - - fb_info->var.xres = mode->hdisplay; - fb_info->var.yres = mode->vdisplay; - fb_info->var.xres_virtual = mode->hdisplay; - fb_info->var.yres_virtual = mode->vdisplay; - - return *fb_cmd; + destroy_fb(dev, &(state->fb_dumb), &(state->fb_cmd)); } static void close_unused_captures(struct drm_client_dev * const dev) { /* report disconnected connectors to close capture connections */ - struct drm_connector_list_iter conn_iter; - struct drm_connector *connector = NULL; - bool mirror_in_use = false; + struct drm_connector_list_iter conn_iter; + struct drm_connector * connector = NULL; + bool mirror_in_use = false; drm_connector_list_iter_begin(dev->dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { - bool unused = connector->status != connector_status_connected; - if (!unused) { - unused = !connector->state || !connector->state->crtc; + if (connector->index >= MAX_CONNECTORS) + continue; - if (!unused) { - struct drm_modeset_acquire_ctx ctx; - void * fb = NULL; - int err = -1; + if (connector->index == CONNECTOR_ID_MIRROR) + continue; - DRM_MODESET_LOCK_ALL_BEGIN(dev->dev, ctx, - DRM_MODESET_ACQUIRE_INTERRUPTIBLE, - err); - - if (connector->state && connector->state->crtc) - fb = lookup_framebuffer(connector->state->crtc, &ctx); - - DRM_MODESET_LOCK_ALL_END(dev->dev, ctx, err); - - unused = !fb; - } - } - - if (unused) { - struct fb_info fb_info = {}; - - if (connector->index < sizeof(mirrored) / sizeof(*mirrored)) - mirrored[connector->index] = false; - - fb_info.par = connector->name; - fb_info.var.bits_per_pixel = 32; - fb_info.node = connector->index; - - kernel_register_fb(&fb_info, 0, 0); - } + if (!states[connector->index].fbs) + destroy_fb_and_capture(dev, connector, &states[connector->index]); } drm_connector_list_iter_end(&conn_iter); - for (unsigned i = 0; i < sizeof(mirrored) / sizeof(mirrored[0]); i++) { - if (!mirrored[i]) + for (unsigned i = 0; i < MAX_CONNECTORS; i++) { + if (i == CONNECTOR_ID_MIRROR) + continue; + + if (!states[i].enabled || !states[i].mirrored) continue; mirror_in_use = true; @@ -545,28 +346,128 @@ static void close_unused_captures(struct drm_client_dev * const dev) if (!mirror_in_use) { struct fb_info fb_info = {}; - fb_info.par = "mirror_capture"; fb_info.var.bits_per_pixel = 32; fb_info.node = CONNECTOR_ID_MIRROR; + fb_info.par = "mirror_capture"; kernel_register_fb(&fb_info, 0, 0); } } -static bool reconfigure(struct drm_client_dev * const dev) +static struct drm_display_mode * best_mode(struct genode_mode const * const conf, + struct drm_connector const * const connector, + struct drm_display_mode const * const mirror_mode, + bool * const no_match, + unsigned * const id_mode) { - static struct drm_mode_create_dumb *gem_mirror = NULL; + struct drm_display_mode * mode = NULL; + struct drm_display_mode * mode_match = NULL; + unsigned mode_id = 0; + + /* heuristics to find matching mode */ + list_for_each_entry(mode, &connector->modes, head) { + mode_id ++; + + if (!mode) + continue; + + /* mode larger mirrored_mode will fail */ + if (conf->mirror && !fb_mirror_compatible(mode, mirror_mode)) + continue; + + /* use mode id if configured and matches exactly */ + if (conf->id) { + if (conf->id != mode_id) + continue; + + mode_match = mode; + break; + } + + /* if invalid, mode is configured in second loop below */ + if (conf->width == 0 || conf->height == 0) + break; + + /* no exact match by mode id -> try matching by size */ + if ((mode->hdisplay != conf->width) || (mode->vdisplay != conf->height)) + continue; + + /* take as default any mode with matching resolution */ + if (!mode_match) { + mode_match = mode; + continue; + } + + /* replace matching mode iif hz matches exactly */ + if ((conf->hz != drm_mode_vrefresh(mode_match)) && + (conf->hz == drm_mode_vrefresh(mode))) + mode_match = mode; + } + + mode_id = 0; + + /* second chance loop */ + list_for_each_entry(mode, &connector->modes, head) { + + mode_id ++; + + if (!mode) + continue; + + /* use first mode for non mirrored connector in case of no match */ + if (!mode_match && !conf->mirror) { + + struct drm_display_mode max = { .hdisplay = conf->max_width, + .vdisplay = conf->max_height }; + + if (conf->max_width && conf->max_height) { + if (conf_larger_mode(conf, &max)) + continue; + } + + mode_match = mode; + } + + /* no matching mode ? */ + if (!mode_match) { + + /* mode larger mirrored_mode will fail */ + if (conf->mirror && !fb_mirror_compatible(mode, mirror_mode)) + continue; + + /* use first smaller mode */ + mode_match = mode; + + if (conf->id) + *no_match = true; + } + + if (mode_match != mode) + continue; + + *id_mode = mode_id; + + break; + } + + return mode_match; +} + + +static void reconfigure(struct drm_client_dev * const dev) +{ + static struct drm_mode_create_dumb * gem_mirror = NULL; + static struct drm_mode_fb_cmd2 * mirror_fb_cmd = NULL; + + struct drm_connector_list_iter conn_iter; + struct drm_connector * connector = NULL; struct drm_display_mode mirror_force = {}; struct drm_display_mode mirror_compound = {}; struct drm_display_mode mirror_minimum = {}; struct drm_display_mode mirror_fb = {}; - struct drm_mode_modeinfo user_mode = {}; - struct drm_display_mode * mode = NULL; - struct drm_mode_set * mode_set = NULL; - struct fb_info info = {}; - bool retry = false; + struct { struct fb_info info; unsigned width_mm; @@ -574,25 +475,19 @@ static bool reconfigure(struct drm_client_dev * const dev) bool report; } mirror = { { }, 0, 0, false }; - if (!gem_mirror) { - /* request storage for gem_mirror and mirror_fb_cmd */ - dumb_meta(dev, NULL, &gem_mirror, &mirror_fb_cmd); - } + gem_mirror = &states[CONNECTOR_ID_MIRROR].fb_dumb; + mirror_fb_cmd = &states[CONNECTOR_ID_MIRROR].fb_cmd; if (!dev || !dev->dev || !gem_mirror || !mirror_fb_cmd) - return false; - - mutex_lock(&dev->modeset_mutex); + return; mirror_heuristic(dev->dev, &mirror_force, &mirror_compound, &mirror_minimum); if (!mirror_minimum.hdisplay || !mirror_minimum.vdisplay) { /* no valid modes on any connector on early boot */ - if (!mirror_fb_cmd->fb_id) { - mutex_unlock(&dev->modeset_mutex); - return false; - } + if (!mirror_fb_cmd->fb_id) + return; /* valid connectors but all are disabled by config */ mirror_minimum.hdisplay = mirror_fb_cmd->width; @@ -606,190 +501,116 @@ static bool reconfigure(struct drm_client_dev * const dev) mirror_fb = mirror_minimum; { - int const err = check_resize_fb(dev, - gem_mirror, - mirror_fb_cmd, - mirror_fb.hdisplay, - mirror_fb.vdisplay); + struct state * state_mirror = &states[CONNECTOR_ID_MIRROR]; + bool resized = false; + + int const err = check_resize_fb( dev, + gem_mirror, + mirror_fb_cmd, + &resized, + mirror_fb.hdisplay, + mirror_fb.vdisplay); if (err) { printk("setting up mirrored framebuffer of %ux%u failed - error=%d\n", mirror_fb.hdisplay, mirror_fb.vdisplay, err); - mutex_unlock(&dev->modeset_mutex); - return true; + return; } + + if (verbose) { + printk("mirror: compound %ux%u force=%ux%u fb=%ux%u\n", + mirror_compound.hdisplay, mirror_compound.vdisplay, + mirror_force.hdisplay, mirror_force.vdisplay, + mirror_fb.hdisplay, mirror_fb.vdisplay); + } + + /* if mirrored fb changed, drop reference and get new framebuffer */ + if (resized) { + if (state_mirror->fbs) + drm_framebuffer_put(state_mirror->fbs); + + state_mirror->fbs = drm_framebuffer_lookup(dev->dev, dev->file, + mirror_fb_cmd->fb_id); + } + + mirror.info.var.xres = mirror_fb.hdisplay; + mirror.info.var.yres = mirror_fb.vdisplay; + mirror.info.var.xres_virtual = mirror_force.hdisplay ? : mirror_compound.hdisplay; + mirror.info.var.yres_virtual = mirror_force.vdisplay ? : mirror_compound.vdisplay; + mirror.info.node = CONNECTOR_ID_MIRROR; + mirror.info.par = "mirror_capture"; } /* without fb handle created by check_resize_fb we can't proceed */ if (!mirror_fb_cmd->fb_id) { printk("%s:%u no mirror fb id\n", __func__, __LINE__); - mutex_unlock(&dev->modeset_mutex); - return retry; + return; } - /* prepare fb info for kernel_register_fb() evaluated by Genode side */ - info.var.xres = mirror_fb.hdisplay; - info.var.yres = mirror_fb.vdisplay; - info.var.xres_virtual = mirror_force.hdisplay ? : mirror_compound.hdisplay; - info.var.yres_virtual = mirror_force.vdisplay ? : mirror_compound.vdisplay; + drm_connector_list_iter_begin(dev_client->dev, &conn_iter); + drm_client_for_each_connector_iter(connector, &conn_iter) { - drm_client_for_each_modeset(mode_set, dev) { - struct drm_display_mode * mode_match = NULL; - unsigned mode_id = 0; - struct drm_connector * connector = NULL; - struct genode_mode conf_mode = {}; + struct drm_display_mode * mode = NULL; + unsigned mode_id = 0; + bool no_match = false; + bool same_state = false; + bool resized = false; + int err = -EINVAL; + struct genode_mode conf_mode = {}; + struct fb_info fb_info = {}; + struct state * state = &states[connector->index]; - if (!mode_set->connectors || !*mode_set->connectors) + if (connector->index >= MAX_CONNECTORS) { + printk("connector id too large %s %u\n", + connector->name, connector->index); continue; - - BUG_ON(!mode_set->crtc); - - /* set connector */ - connector = *mode_set->connectors; + } /* read configuration of connector */ lx_emul_i915_connector_config(connector->name, &conf_mode); - /* heuristics to find matching mode */ - list_for_each_entry(mode, &connector->modes, head) { - mode_id ++; - - if (!mode) - continue; - - /* allocated mirrored framebufer smaller than mode can't be used */ - if (conf_mode.mirror && fb_smaller_mode(&info, mode)) - continue; - - /* use mode id if configured and matches exactly */ - if (conf_mode.id) { - if (conf_mode.id != mode_id) - continue; - - mode_match = mode; - - break; - } - - /* if invalid, mode is configured in second loop below */ - if (conf_mode.width == 0 || conf_mode.height == 0) - break; - - /* no exact match by mode id -> try matching by size */ - if ((mode->hdisplay != conf_mode.width) || - (mode->vdisplay != conf_mode.height)) - continue; - - /* take as default any mode with matching resolution */ - if (!mode_match) { - mode_match = mode; - continue; - } - - /* replace matching mode iif hz matches exactly */ - if ((conf_mode.hz != drm_mode_vrefresh(mode_match)) && - (conf_mode.hz == drm_mode_vrefresh(mode))) - mode_match = mode; + /* drop old fb reference, taken again later */ + if (state->fbs) { + drm_framebuffer_put(state->fbs); + state->fbs = NULL; } - /* track whether connector is used mirrored or discrete */ - if (connector->index < sizeof(mirrored) / sizeof(*mirrored)) - mirrored[connector->index] = conf_mode.enabled && conf_mode.mirror; + /* lookup next mode */ + mode = best_mode(&conf_mode, connector, &mirror_fb, &no_match, &mode_id); - /* apply new mode */ - mode_id = 0; - list_for_each_entry(mode, &connector->modes, head) { - struct fb_info fb_info = info; - int err = -1; - bool no_match = false; - struct drm_mode_fb_cmd2 fb_cmd = *mirror_fb_cmd; + /* reduce flickering if in same state */ + same_state = conf_mode.mirror == state->mirrored && + conf_mode.enabled == state->enabled && + mode_id == state->mode_id; - mode_id ++; + /* close capture on change of discrete -> mirror */ + if (!state->mirrored && conf_mode.mirror) + destroy_fb_and_capture(dev, connector, state); - if (!mode) - continue; + state->mirrored = conf_mode.mirror; + state->enabled = conf_mode.enabled; + state->mode_id = mode_id; - /* use first mode for non mirrored connector in case of no match */ - if (!mode_match && !conf_mode.mirror) { + if (!mode) + continue; - struct drm_display_mode max = { .hdisplay = conf_mode.max_width, - .vdisplay = conf_mode.max_height }; + /* prepare fb info for kernel_register_fb() evaluated by Genode side */ + if (conf_mode.mirror) { + if (conf_mode.enabled) + mirror.report = true; + fb_info = mirror.info; + } else { + fb_info.var.xres = mode->hdisplay; + fb_info.var.yres = mode->vdisplay; + fb_info.var.xres_virtual = mode->hdisplay; + fb_info.var.yres_virtual = mode->vdisplay; + fb_info.node = connector->index; + fb_info.par = connector->name; + } - if (conf_mode.max_width && conf_mode.max_height) { - if (conf_larger_mode(&conf_mode, &max)) - continue; - } - - mode_match = mode; - } - - /* no matching mode ? */ - if (!mode_match) { - - /* fb smaller than mode is denied by drm_mode_setcrtc */ - if (conf_mode.enabled && fb_smaller_mode(&fb_info, mode)) - continue; - - /* use first smaller mode */ - mode_match = mode; - - if (conf_mode.enabled && conf_mode.id) - no_match = true; - } - - if (mode_match != mode) - continue; - - /* Genode side prefer to have a name for the connector */ - fb_info.par = connector->name; - - { - struct drm_modeset_acquire_ctx ctx; - struct drm_framebuffer *fb = NULL; - - DRM_MODESET_LOCK_ALL_BEGIN(dev->dev, ctx, - DRM_MODESET_ACQUIRE_INTERRUPTIBLE, - err); - - fb = lookup_framebuffer(mode_set->crtc, &ctx); - - DRM_MODESET_LOCK_ALL_END(dev->dev, ctx, err); - - /* check for mirrored fb or specific one for connector */ - fb_cmd = fb_of_screen(dev, &conf_mode, &fb_info, mirror_fb_cmd, - mode, fb, connector); - } - - /* convert kernel internal mode to user mode expected via ioctl */ - drm_mode_convert_to_umode(&user_mode, mode); - - /* assign fb & connector to crtc with specified mode */ - err = user_attach_fb_to_crtc(dev, connector, mode_set->crtc, - &user_mode, fb_cmd.fb_id, - conf_mode.enabled); - - /* set brightness */ - if (!err && conf_mode.enabled && conf_mode.brightness <= MAX_BRIGHTNESS) { - drm_modeset_lock(&dev->dev->mode_config.connection_mutex, NULL); - set_brightness(conf_mode.enabled ? conf_mode.brightness : 0, - connector); - drm_modeset_unlock(&dev->dev->mode_config.connection_mutex); - } - - if (!retry) - retry = !!err; - - if (!err && conf_mode.enabled && conf_mode.mirror && !mirror.report) { - /* use fb_info of first mirrored screen */ - mirror.report = true; - mirror.width_mm = 0; - mirror.height_mm = 0; - mirror.info = fb_info; - mirror.info.node = CONNECTOR_ID_MIRROR; - } - - /* diagnostics */ + /* diagnostics */ + if (verbose) printk("%10s: %s name='%9s' id=%u%s%s mode=%4ux%4u@%u%s fb=%4ux%4u%s", connector->name ? connector->name : "unnamed", conf_mode.enabled ? " enable" : "disable", @@ -799,69 +620,113 @@ static bool reconfigure(struct drm_client_dev * const dev) mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), drm_mode_vrefresh(mode) < 100 ? " ": "", fb_info.var.xres, fb_info.var.yres, - (err || no_match) ? "" : "\n"); + (no_match) ? "" : "\n"); - if (no_match) - printk(" - no mode match: %ux%u\n", - conf_mode.width, - conf_mode.height); - if (err) - printk(" - failed, error=%d\n", err); + if (verbose && no_match) + printk(" - no mode match: %ux%u\n", + conf_mode.width, + conf_mode.height); - if (!err && !conf_mode.mirror && conf_mode.enabled) { - unsigned width_mm = mode->width_mm ? : connector->display_info.width_mm; - unsigned height_mm = mode->height_mm ? : connector->display_info.height_mm; + if (!conf_mode.enabled) + continue; - int err = user_register_fb(dev, &fb_info, &fb_cmd, width_mm, height_mm); + /* set brightness */ + if (conf_mode.brightness <= MAX_BRIGHTNESS) { + drm_modeset_lock(&dev->dev->mode_config.connection_mutex, NULL); + set_brightness(conf_mode.enabled ? conf_mode.brightness : 0, + connector); + drm_modeset_unlock(&dev->dev->mode_config.connection_mutex); + } - if (err == -ENOSPC) { + if (conf_mode.mirror) { + /* get new fb reference for mirrored fb */ + state->fbs = drm_framebuffer_lookup(dev->dev, dev->file, + mirror_fb_cmd->fb_id); + continue; + } - struct drm_framebuffer *fb = drm_framebuffer_lookup(dev->dev, - dev->file, - fb_cmd.fb_id); - if (fb) - drm_framebuffer_put(fb); + /* discrete case handling */ - destroy_fb_and_capture(dev, connector, fb); + err = check_resize_fb(dev, &state->fb_dumb, &state->fb_cmd, + &resized, mode->hdisplay, mode->vdisplay); + if (err) { + printk("setting up framebuffer of %ux%u failed - error=%d\n", + mode->hdisplay, mode->vdisplay, err); + } + + /* get new fb reference after check_resize_fb */ + state->fbs = drm_framebuffer_lookup(dev->dev, dev->file, + state->fb_cmd.fb_id); + + if (verbose) + printk("%s:%u %s %s %s\n", __func__, __LINE__, connector->name, + same_state ? " same state " : " different state", + resized ? " resized " : "not resized"); + + if (state->fbs && (!same_state || resized)) { + unsigned width_mm = mode->width_mm ? : connector->display_info.width_mm; + unsigned height_mm = mode->height_mm ? : connector->display_info.height_mm; + + int err = user_register_fb(dev, &fb_info, state->fbs, + &state->vma, &state->vma_flags, + width_mm, height_mm); + + if (err == -ENOSPC) { + if (state->fbs) { + drm_framebuffer_put(state->fbs); + state->fbs = NULL; } + destroy_fb_and_capture(dev, connector, state); } - - break; } } - mutex_unlock(&dev->modeset_mutex); + drm_connector_list_iter_end(&conn_iter); if (mirror.report) { - mirror.info.par = "mirror_capture"; - user_register_fb(dev, &mirror.info, mirror_fb_cmd, mirror.width_mm, - mirror.height_mm); + user_register_fb(dev, &mirror.info, + states[CONNECTOR_ID_MIRROR].fbs, + &states[CONNECTOR_ID_MIRROR].vma, + &states[CONNECTOR_ID_MIRROR].vma_flags, + mirror.width_mm, mirror.height_mm); } close_unused_captures(dev); - return retry; + return; } -static int configure_connectors(void * data) +static int do_action_loop(void * data) { - unsigned retry_count = 0; + int status_last_action = !ACTION_FAILED; while (true) { - bool retry = reconfigure(dev_client); - if (retry && retry_count < 3) { - retry_count ++; + int const action = lx_emul_i915_action_to_process(status_last_action); - printk("retry applying configuration in 1s\n"); - msleep(1000); - continue; - } + switch (action) { + case ACTION_DETECT_MODES: + /* probe new modes of connectors and apply very same previous fbs */ + status_last_action = probe_and_apply_fbs(dev_client, true) + ? ACTION_FAILED : !ACTION_FAILED; + break; + case ACTION_CONFIGURE: + /* re-read Genode configuration and resize fbs depending on config */ + reconfigure(dev_client); - retry_count = 0; + /* + * Apply current fbs to connectors. It may fail when + * the reconfigure decision is outdated. By invoking the Linux + * probe code we may "see" already new hardware state. + */ + status_last_action = probe_and_apply_fbs(dev_client, false) + ? ACTION_FAILED : !ACTION_FAILED; - if (lx_emul_i915_config_done_and_block()) + break; + default: lx_emul_task_schedule(true /* block task */); + break; + } } return 0; @@ -891,21 +756,16 @@ static void mark_framebuffer_dirty(struct drm_framebuffer * const fb) } -/* track per connector (16 max) the empty capture attempts before stopping */ -enum { CAPTURE_RATE_MS = 10, ATTEMPTS_BEFORE_STOP = 7 }; -static unsigned unchanged[16] = { }; - - void lx_emul_i915_wakeup(unsigned const connector_id) { - bool const valid_id = connector_id < sizeof(unchanged) / sizeof(*unchanged); + bool const valid_id = connector_id < MAX_CONNECTORS; if (!valid_id) { printk("%s: connector id invalid %d\n", __func__, connector_id); return; } - unchanged[connector_id] = 0; + states[connector_id].unchanged = 0; /* wake potential sleeping update task */ lx_emul_task_unblock(lx_update_task); @@ -915,41 +775,40 @@ void lx_emul_i915_wakeup(unsigned const connector_id) static int update_content(void *) { while (true) { - struct drm_connector_list_iter conn_iter; - struct drm_connector *connector = NULL; - struct drm_device const *dev = dev_client->dev; - bool block_task = true; - bool mirror_run = false; + struct drm_connector_list_iter conn_iter; + struct drm_connector * connector = NULL; + struct drm_device const * dev = dev_client->dev; + bool block_task = true; + bool mirror_run = false; drm_connector_list_iter_begin(dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { - struct drm_modeset_acquire_ctx ctx; - struct drm_framebuffer *fb = NULL; - - int err = -1; bool may_sleep = false; unsigned index = connector->index; if (connector->status != connector_status_connected) continue; - if (connector->index >= sizeof(unchanged) / sizeof(*unchanged)) { + if (connector->index >= MAX_CONNECTORS) { printk("%s: connector id invalid %d\n", __func__, index); index = CONNECTOR_ID_MIRROR; /* should never happen case */ } - if (mirrored[index] && mirror_run) + if (!states[index].enabled) continue; - if (mirrored[index]) { + if (states[index].mirrored) { + if (mirror_run) + continue; + mirror_run = true; index = CONNECTOR_ID_MIRROR; } - unchanged[index] ++; + states[index].unchanged ++; - may_sleep = unchanged[index] >= ATTEMPTS_BEFORE_STOP; + may_sleep = states[index].unchanged >= ATTEMPTS_BEFORE_STOP; if (!lx_emul_i915_blit(index, may_sleep)) { if (!may_sleep) @@ -960,19 +819,10 @@ static int update_content(void *) block_task = false; - unchanged[index] = 0; + states[index].unchanged = 0; - DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, - DRM_MODESET_ACQUIRE_INTERRUPTIBLE, - err); - - if (connector->state && connector->state->crtc) - fb = lookup_framebuffer(connector->state->crtc, &ctx); - - DRM_MODESET_LOCK_ALL_END(dev, ctx, err); - - if (fb) - mark_framebuffer_dirty(fb); + if (states[index].fbs) + mark_framebuffer_dirty(states[index].fbs); } drm_connector_list_iter_end(&conn_iter); @@ -989,7 +839,7 @@ static int update_content(void *) void lx_user_init(void) { - int pid = kernel_thread(configure_connectors, NULL, "lx_user", + int pid = kernel_thread(do_action_loop, NULL, "lx_user", CLONE_FS | CLONE_FILES); int pid2 = kernel_thread(update_content, NULL, "lx_update", CLONE_FS | CLONE_FILES); @@ -999,97 +849,29 @@ void lx_user_init(void) } -static bool mirrored_fb(struct drm_client_dev * client, - struct drm_crtc const * const crtc) -{ - struct drm_modeset_acquire_ctx ctx; - struct drm_framebuffer const * fb = NULL; - struct drm_framebuffer const * fb_mirror = NULL; - int result = -1; - - if (mirror_fb_cmd && mirror_fb_cmd->fb_id) - fb_mirror = drm_framebuffer_lookup(client->dev, client->file, - mirror_fb_cmd->fb_id); - - if (!fb_mirror || !crtc) - return false; - - if (fb_mirror) - drm_framebuffer_put(fb_mirror); - - DRM_MODESET_LOCK_ALL_BEGIN(client->dev, ctx, - DRM_MODESET_ACQUIRE_INTERRUPTIBLE, - result); - - fb = lookup_framebuffer(crtc, &ctx); - - DRM_MODESET_LOCK_ALL_END(client->dev, ctx, result); - - return fb && fb_mirror == fb; -} - - static void _report_connectors(void * genode_data, bool const discrete) { - struct drm_connector_list_iter conn_iter; - struct drm_connector *connector = NULL; - struct drm_display_mode *mode = NULL; - struct drm_mode_set *modeset = NULL; + struct drm_connector_list_iter conn_iter; + struct drm_connector * connector = NULL; - mutex_lock(&dev_client->modeset_mutex); - drm_client_for_each_modeset(modeset, dev_client) { - - struct genode_mode conf_mode = {}; - bool has_modes = false; - - if (!modeset->connectors || !*modeset->connectors) - continue; - - /* set connector */ - connector = *modeset->connectors; - - /* read configuration for connector */ - lx_emul_i915_connector_config(connector->name, &conf_mode); - - if ((discrete && conf_mode.mirror) || (!discrete && !conf_mode.mirror)) - continue; - - list_for_each_entry(mode, &connector->modes, head) { - - if (!mode) - continue; - - has_modes = true; - } - - lx_emul_i915_report_connector(connector, genode_data, - connector->name, - connector->status != connector_status_disconnected, - has_modes, - get_brightness(connector, INVALID_BRIGHTNESS), - connector->display_info.width_mm, - connector->display_info.height_mm); - } - mutex_unlock(&dev_client->modeset_mutex); - - /* report disconnected connectors */ drm_connector_list_iter_begin(dev_client->dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { - /* read configuration for connector */ + bool const valid_fb = connector->index < MAX_CONNECTORS + ? states[connector->index].fbs : false; + struct genode_mode conf_mode = {}; + + /* read configuration for connector */ lx_emul_i915_connector_config(connector->name, &conf_mode); if ((discrete && conf_mode.mirror) || (!discrete && !conf_mode.mirror)) continue; - if (connector->status != connector_status_disconnected) - continue; - lx_emul_i915_report_connector(connector, genode_data, connector->name, connector->status != connector_status_disconnected, - false, /* has modes */ + valid_fb, get_brightness(connector, INVALID_BRIGHTNESS), connector->display_info.width_mm, connector->display_info.height_mm); @@ -1112,15 +894,21 @@ void lx_emul_i915_report_non_discrete(void * genode_data) void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data) { - struct drm_connector *connector = lx_data; - struct drm_display_mode *mode = NULL; - struct drm_display_mode *prev_mode = NULL; - unsigned mode_id = 0; + struct drm_connector * connector = lx_data; + struct drm_display_mode * mode = NULL; + struct drm_display_mode * prev_mode = NULL; + unsigned mode_id = 0; + bool quirk_inuse = false; + struct state * state = &states[connector->index]; + struct genode_mode conf_mode = { }; - /* mark modes as unavailable due to max_resolution enforcement */ - struct genode_mode conf_max_mode = { }; - lx_emul_i915_connector_config("dummy", &conf_max_mode); + if (connector->index >= MAX_CONNECTORS) + return; + lx_emul_i915_connector_config(connector->name, &conf_mode); + + /* no fb and conf_mode.enabled is a temporary inconsistent state */ + quirk_inuse = conf_mode.enabled && !state->fbs; list_for_each_entry(mode, &connector->modes, head) { bool skip = false; @@ -1130,45 +918,57 @@ void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data) if (!mode) continue; - /* skip duplicates - actually not really, some parameters varies ?! */ + /* skip consecutive similar modes */ if (prev_mode) { + static_assert(sizeof(mode->name) == DRM_DISPLAY_MODE_LEN); skip = (mode->hdisplay == prev_mode->hdisplay) && (mode->vdisplay == prev_mode->vdisplay) && (drm_mode_vrefresh(mode) == drm_mode_vrefresh(prev_mode)) && !strncmp(mode->name, prev_mode->name, DRM_DISPLAY_MODE_LEN); } - if (!skip) { - bool const max_mode = conf_max_mode.max_width && - conf_max_mode.max_height; - bool const inuse = connector->state && connector->state->crtc && - connector->state->crtc->state && - drm_mode_equal(&connector->state->crtc->state->mode, mode); - bool const mirror = connector->state && connector->state->crtc && - mirrored_fb(dev_client, connector->state->crtc); + prev_mode = mode; - struct genode_mode conf_mode = { + { + bool const max_mode = conf_mode.max_width && conf_mode.max_height; + + struct genode_mode config_report = { .width = mode->hdisplay, .height = mode->vdisplay, .width_mm = mode->width_mm, .height_mm = mode->height_mm, .preferred = mode->type & (DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DEFAULT), - .inuse = inuse, - .mirror = mirror, + .inuse = !quirk_inuse && state->mode_id == mode_id, + .mirror = state->mirrored, .hz = drm_mode_vrefresh(mode), .id = mode_id, .enabled = !max_mode || - !conf_smaller_max_mode(&conf_max_mode, mode) + !conf_smaller_max_mode(&conf_mode, mode) }; - static_assert(sizeof(conf_mode.name) == DRM_DISPLAY_MODE_LEN); - memcpy(conf_mode.name, mode->name, sizeof(conf_mode.name)); + /* + * Report first usable mode as used mode in the quirk state to + * avoid sending a mode list with no used mode at all, which + * external configuration components may trigger to disable the + * connector. + */ + if (quirk_inuse && config_report.enabled) { + config_report.inuse = true; + quirk_inuse = false; + } - lx_emul_i915_report_modes(genode_data, &conf_mode); + /* skip similar mode and if it is not the used one */ + if (skip && !config_report.inuse) + continue; + + { + static_assert(sizeof(config_report.name) == DRM_DISPLAY_MODE_LEN); + memcpy(config_report.name, mode->name, sizeof(config_report.name)); + } + + lx_emul_i915_report_modes(genode_data, &config_report); } - - prev_mode = mode; } } @@ -1185,15 +985,20 @@ void i915_switcheroo_unregister(struct drm_i915_private *i915) } -static int fb_client_hotplug(struct drm_client_dev *client) +static int fb_client_hotplug(struct drm_client_dev *) { - struct drm_mode_set *modeset = NULL; - struct drm_framebuffer *fb_mirror = NULL; - int result = -EINVAL; + /* notify Genode side */ + lx_emul_i915_hotplug_connector(); - if (mirror_fb_cmd && mirror_fb_cmd->fb_id) - fb_mirror = drm_framebuffer_lookup(client->dev, client->file, - mirror_fb_cmd->fb_id); + return 0; +} + + +static int probe_and_apply_fbs(struct drm_client_dev *client, bool const detect) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_mode_set *modeset = NULL; + int result = -EINVAL; /* * Triggers set up of display pipelines for connectors and @@ -1203,113 +1008,100 @@ static int fb_client_hotplug(struct drm_client_dev *client) 0 /* auto height */); if (result) { printk("%s: error on modeset probe %d\n", __func__, result); - return 0; + return result; } + /* (re-)assign framebuffers to enabled connectors */ + DRM_MODESET_LOCK_ALL_BEGIN(client->dev, ctx, + DRM_MODESET_ACQUIRE_INTERRUPTIBLE, + result); + /* * (Re-)assign framebuffers to modeset (lost due to modeset_probe) * and commit the change. */ - { - struct drm_framebuffer * free_fbs[MAX_FBS] = { }; - struct drm_modeset_acquire_ctx ctx; + mutex_lock(&client->modeset_mutex); + drm_client_for_each_modeset(modeset, client) { + struct drm_connector * connector = NULL; - unsigned fb_count = 0; + if (!modeset) + continue; - DRM_MODESET_LOCK_ALL_BEGIN(client->dev, ctx, - DRM_MODESET_ACQUIRE_INTERRUPTIBLE, - result); + if (!modeset->num_connectors || !modeset->connectors || !*modeset->connectors) + continue; - mutex_lock(&client->modeset_mutex); - drm_client_for_each_modeset(modeset, client) { - struct drm_connector *connector = NULL; - struct drm_framebuffer *fb = NULL; + /* set connector */ + connector = *modeset->connectors; - if (!modeset) - continue; + if (verbose) + printk("%s:%u %s fb=%px i=%u fbs[i]=%px %s\n", + __func__, __LINE__, + connector->name, modeset->fb, + connector->index, + connector->index < MAX_CONNECTORS ? + states[connector->index].fbs : NULL, + detect ? " - detect run" : " - configure run"); - if (modeset->crtc) - fb = lookup_framebuffer(modeset->crtc, &ctx); + modeset->fb = connector->index < MAX_CONNECTORS + ? states[connector->index].fbs + : NULL; - if (!modeset->num_connectors || !modeset->connectors || !*modeset->connectors) { - - struct drm_mode_fb_cmd2 *fb_cmd = NULL; - struct drm_mode_create_dumb *fb_dumb = NULL; - - if (!fb || fb == fb_mirror) - continue; - - if (!dumb_meta(client, fb, &fb_dumb, &fb_cmd) || !fb_dumb || !fb_cmd) - continue; - - if (fb_count >= MAX_FBS) { - printk("leaking framebuffer memory\n"); - continue; - } - - free_fbs[fb_count++] = fb; - - continue; + if (!modeset->fb) { + /* + * If no fb is available for the (new) connector, we have to + * explicitly revert structures prepared by + * drm_client_modeset_probe so that drm_client_modeset_commit + * will accept the modeset. + */ + for (unsigned i = 0; i < modeset->num_connectors; i++) { + drm_connector_put(modeset->connectors[i]); + modeset->connectors[i] = NULL; } - /* set connector */ - connector = *modeset->connectors; + modeset->num_connectors = 0; - modeset->fb = fb ? fb : fb_mirror; + if (modeset->mode) { + kfree(modeset->mode); + modeset->mode = NULL; + } + } else { + struct drm_display_mode * mode = NULL; + unsigned mode_id = 0; - /* try to avoid -ENOSPC by using next smaller resolution */ - if (fb_mirror && !fb && - (modeset->mode->hdisplay > fb_mirror->width || - modeset->mode->vdisplay > fb_mirror->height)) - { - struct drm_display_mode * mode = NULL; - list_for_each_entry(mode, &connector->modes, head) { + /* check and select right mode in modeset as configured by state[] */ + list_for_each_entry(mode, &connector->modes, head) { + mode_id ++; - if (mode->hdisplay > fb_mirror->width || - mode->vdisplay > fb_mirror->height) - continue; + if (!mode) + continue; - kfree(modeset->mode); - modeset->mode = drm_mode_duplicate(client->dev, mode); + if (states[connector->index].mode_id != mode_id) + continue; + if (drm_mode_equal(mode, modeset->mode)) break; - } + + if (modeset->mode) + kfree(modeset->mode); + + modeset->mode = drm_mode_duplicate(client->dev, mode); + + break; } } - mutex_unlock(&client->modeset_mutex); - DRM_MODESET_LOCK_ALL_END(client->dev, ctx, result); - - for (unsigned i = 0; i < fb_count; i++) { - struct drm_mode_fb_cmd2 *fb_cmd = NULL; - struct drm_mode_create_dumb *fb_dumb = NULL; - - if (!free_fbs[i]) - continue; - - if (!dumb_meta(client, free_fbs[i], &fb_dumb, &fb_cmd) || !fb_dumb || !fb_cmd) - continue; - - destroy_fb(client, fb_dumb, fb_cmd); - - free_fbs[i] = NULL; - } - - /* triggers disablement of encoders attached to disconnected ports */ - result = drm_client_modeset_commit(client); - - if (result) { - printk("%s: error on modeset commit %d%s\n", __func__, result, - (result == -ENOSPC) ? " - ENOSPC" : " - unknown error"); - } } + mutex_unlock(&client->modeset_mutex); + DRM_MODESET_LOCK_ALL_END(client->dev, ctx, result); - /* notify Genode side */ - lx_emul_i915_hotplug_connector(); + /* triggers also disablement of encoders attached to disconnected ports */ + result = drm_client_modeset_commit(client); - if (fb_mirror) - drm_framebuffer_put(fb_mirror); + if (result) { + printk("%s: error on modeset commit %d%s\n", __func__, result, + (result == -ENOSPC) ? " - ENOSPC" : " - unknown error"); + } - return 0; + return result; } @@ -1357,67 +1149,31 @@ static int register_drm_client(struct drm_device * const dev) } -int user_attach_fb_to_crtc(struct drm_client_dev * const dev, - struct drm_connector const * const connector, - struct drm_crtc const * const crtc, - struct drm_mode_modeinfo const * const mode, - unsigned const fb_id, - bool const enable) -{ - int result = -EINVAL; - uint32_t connectors [1] = { connector->base.id }; - struct drm_mode_crtc crtc_req = { - .set_connectors_ptr = (uintptr_t)(&connectors), - .count_connectors = enable ? 1 : 0, - .crtc_id = crtc->base.id, - .fb_id = fb_id, - .x = 0, - .y = 0, /* position on the framebuffer */ - .gamma_size = 0, - .mode_valid = enable, - .mode = *mode, - }; - - result = drm_mode_setcrtc(dev->dev, &crtc_req, dev->file); - if (result) - drm_err(dev->dev, "%s: failed to set crtc %d\n", __func__, result); - - return result; -} - - -static int user_register_fb(struct drm_client_dev const * const dev, - struct fb_info * const info, - struct drm_mode_fb_cmd2 const * const dumb_fb, - unsigned const width_mm, - unsigned const height_mm) +static int user_register_fb(struct drm_client_dev const * const dev, + struct fb_info * const info, + struct drm_framebuffer * const fb, + struct i915_vma ** const vma, + unsigned long * const vma_flags, + unsigned const width_mm, + unsigned const height_mm) { intel_wakeref_t wakeref; int result = -EINVAL; struct i915_gtt_view const view = { .type = I915_GTT_VIEW_NORMAL }; void __iomem *vaddr = NULL; - struct gem_dumb *gem_dumb = NULL; struct drm_i915_private *dev_priv = to_i915(dev->dev); - struct drm_framebuffer *fb = drm_framebuffer_lookup(dev->dev, - dev->file, - dumb_fb->fb_id); - if (!info || !fb) { + if (!info || !fb || !dev_priv || !vma || !vma_flags) { printk("%s:%u error setting up info and fb\n", __func__, __LINE__); return -ENODEV; } - if (!dumb_gem(dev, fb, &gem_dumb) || !gem_dumb) { - printk("%s:%u error looking up fb and vma\n", __func__, __LINE__); - return -ENODEV; - } + if (*vma) { + intel_unpin_fb_vma(*vma, *vma_flags); - if (gem_dumb->vma) { - intel_unpin_fb_vma(gem_dumb->vma, gem_dumb->flags); - - gem_dumb->vma = NULL; - gem_dumb->flags = 0; + *vma = NULL; + *vma_flags = 0; } wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); @@ -1426,49 +1182,54 @@ static int user_register_fb(struct drm_client_dev const * const dev, * This also validates that any existing fb inherited from the * BIOS is suitable for own access. */ - gem_dumb->vma = intel_pin_and_fence_fb_obj(fb, false /* phys_cursor */, - &view, false /* use fences */, - &gem_dumb->flags); + *vma = intel_pin_and_fence_fb_obj( fb, false /* phys_cursor */, + &view, false /* use fences */, + vma_flags); - if (IS_ERR(gem_dumb->vma)) { + if (IS_ERR(*vma)) { intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); - result = PTR_ERR(gem_dumb->vma); + result = PTR_ERR(*vma); printk("%s:%u error setting vma %d\n", __func__, __LINE__, result); - gem_dumb->vma = NULL; + *vma = NULL; + *vma_flags = 0; + return result; } - if (!i915_vma_is_map_and_fenceable(gem_dumb->vma)) { + if (!i915_vma_is_map_and_fenceable(*vma)) { printk("%s: framebuffer not mappable in aperture -> destroying framebuffer\n", (info && info->par) ? (char *)info->par : "unknown"); - intel_unpin_fb_vma(gem_dumb->vma, gem_dumb->flags); + intel_unpin_fb_vma(*vma, *vma_flags); - gem_dumb->vma = NULL; - gem_dumb->flags = 0; + *vma = NULL; + *vma_flags = 0; return -ENOSPC; } - vaddr = i915_vma_pin_iomap(gem_dumb->vma); + vaddr = i915_vma_pin_iomap(*vma); + if (IS_ERR(vaddr)) { intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); result = PTR_ERR(vaddr); printk("%s:%u error pin iomap %d\n", __func__, __LINE__, result); - intel_unpin_fb_vma(gem_dumb->vma, gem_dumb->flags); - gem_dumb->vma = NULL; + intel_unpin_fb_vma(*vma, *vma_flags); + + *vma = NULL; + *vma_flags = 0; return result; } /* fill framebuffer info for kernel_register_fb */ info->screen_base = vaddr; - info->screen_size = gem_dumb->vma->size; + info->screen_size = (*vma)->size; info->fix.line_length = fb->pitches[0]; info->var.bits_per_pixel = drm_format_info_bpp(fb->format, 0); @@ -1476,9 +1237,6 @@ static int user_register_fb(struct drm_client_dev const * const dev, kernel_register_fb(info, width_mm, height_mm); - if (fb) - drm_framebuffer_put(fb); - return 0; } @@ -1486,20 +1244,25 @@ static int user_register_fb(struct drm_client_dev const * const dev, static int check_resize_fb(struct drm_client_dev * const dev, struct drm_mode_create_dumb * const gem_dumb, struct drm_mode_fb_cmd2 * const dumb_fb, + bool * const resized, unsigned const width, unsigned const height) { int result = -EINVAL; /* paranoia */ - if (!dev || !dev->dev || !dev->file || !gem_dumb || !dumb_fb) + if (!dev || !dev->dev || !dev->file || !gem_dumb || !dumb_fb || !resized) return -ENODEV; + *resized = false; + /* if requested size is smaller, free up current dumb buffer */ if (gem_dumb->width && gem_dumb->height && (gem_dumb->width < width || gem_dumb->height < height)) { destroy_fb(dev, gem_dumb, dumb_fb); + + *resized = true; } /* allocate dumb framebuffer, on success a GEM object handle is returned */ @@ -1517,6 +1280,8 @@ static int check_resize_fb(struct drm_client_dev * const dev, memset(gem_dumb, 0, sizeof(*gem_dumb)); return -ENODEV; } + + *resized = true; } /* bind framebuffer(GEM object) to drm client */ @@ -1539,6 +1304,8 @@ static int check_resize_fb(struct drm_client_dev * const dev, memset(dumb_fb, 0, sizeof(*dumb_fb)); return -ENODEV; } + + *resized = true; } return 0; diff --git a/repos/pc/src/driver/framebuffer/intel/pc/main.cc b/repos/pc/src/driver/framebuffer/intel/pc/main.cc index 61461e96ff..f017107475 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/main.cc +++ b/repos/pc/src/driver/framebuffer/intel/pc/main.cc @@ -49,6 +49,8 @@ struct Framebuffer::Driver Attached_rom_system system { }; Expanding_reporter reporter { env, "connectors", "connectors" }; + Signal_handler process_handler { env.ep(), *this, + &Driver::process_action }; Signal_handler config_handler { env.ep(), *this, &Driver::config_update }; Signal_handler scheduler_handler { env.ep(), *this, @@ -56,14 +58,87 @@ struct Framebuffer::Driver Signal_handler system_handler { env.ep(), *this, &Driver::system_update }; - bool update_in_progress { false }; - bool new_config_rom { false }; bool disable_all { false }; - bool disable_report_once { false }; bool merge_label_changed { false }; + bool verbose { false }; Capture::Connection::Label merge_label { "mirror" }; + char const * action_name(enum Action const action) + { + switch (action) { + case ACTION_IDLE : return "IDLE"; + case ACTION_DETECT_MODES : return "DETECT_MODES"; + case ACTION_CONFIGURE : return "CONFIGURE"; + case ACTION_REPORT : return "REPORT"; + case ACTION_NEW_CONFIG : return "NEW_CONFIG"; + case ACTION_READ_CONFIG : return "READ_CONFIG"; + case ACTION_HOTPLUG : return "HOTPLUG"; + case ACTION_EXIT : return "EXIT"; + case ACTION_FAILED : return "FAILED"; + } + return "UNKNOWN"; + } + + enum Action active { }; + enum Action pending[31] { }; + + void add_action(enum Action const add, bool may_squash = false) + { + Action * prev { }; + + for (auto &entry : pending) { + if (entry != ACTION_IDLE) { + prev = &entry; + continue; + } + + /* skip in case the last entry contains the very same to be added */ + if (may_squash && prev && add == *prev) { + if (verbose) + error("action already queued - '", action_name(add), "'"); + return; + } + + entry = add; + + if (verbose) + error("action added to queue - '", action_name(add), "'"); + + return; + } + + error("action ", action_name(add), " NOT QUEUED - trouble ahead"); + } + + auto next_action() + { + Action next { ACTION_IDLE }; + Action * prev { }; + + for (auto &entry : pending) { + if (next == ACTION_IDLE) + next = entry; + + if (prev) + *prev = entry; + + prev = &entry; + } + + if (prev) + *prev = ACTION_IDLE; + + if (verbose) + error("action now executing - '", action_name(next), "'"); + + active = next; + + return active; + } + + bool action_in_execution() const { return active != ACTION_IDLE; } + struct Connector { using Space = Id_space; using Id = Space::Id; @@ -116,7 +191,7 @@ struct Framebuffer::Driver if (!dirty && may_stop) connector.capture->capture_stopped(); - }, [&](){ /* unknown connector id */ }); + }, [&] () { /* unknown connector id -> no dirty content */ }); return dirty; } @@ -143,21 +218,26 @@ struct Framebuffer::Driver conn.size_phys = size_phys; conn.size_mm = mm; - if (conn.size.valid()) { - Capture::Connection::Screen::Attr attr = { .px = conn.size, .mm = conn.size_mm }; - conn.capture.construct(env, label); - conn.screen .construct(*conn.capture, env.rm(), attr); + conn.screen .destruct(); + conn.capture.destruct(); - conn.capture->wakeup_sigh(conn.capture_wakeup); - } else { - conn.screen .destruct(); - conn.capture.destruct(); - } + if (!conn.size.valid()) + return same; + + Capture::Connection::Screen::Attr attr = { .px = conn.size, + .mm = conn.size_mm }; + + conn.capture.construct(env, label); + conn.screen .construct(*conn.capture, env.rm(), attr); + + conn.capture->wakeup_sigh(conn.capture_wakeup); return same; } + void process_action(); void config_update(); + void config_read(); void system_update(); void generate_report(); void lookup_config(char const *, struct genode_mode &mode); @@ -193,6 +273,8 @@ struct Framebuffer::Driver }); config.sigh(config_handler); + + config_read(); } void start() @@ -272,18 +354,43 @@ struct Framebuffer::Driver enum { MAX_BRIGHTNESS = 100u }; +void Framebuffer::Driver::process_action() +{ + if (action_in_execution()) + return; + + if (!lx_user_task) { + error("no lx user task"); + return; + } + + lx_emul_task_unblock(lx_user_task); + Lx_kit::env().scheduler.execute(); +} + + void Framebuffer::Driver::config_update() +{ + add_action(Action::ACTION_NEW_CONFIG, true); + + if (action_in_execution()) + return; + + Genode::Signal_transmitter(process_handler).submit(); +} + + +void Framebuffer::Driver::config_read() { config.update(); - if (!config.valid() || !lx_user_task) + if (!config.valid()) return; config.xml().with_optional_sub_node("merge", [&](auto const &node) { auto const merge_label_before = merge_label; - if (node.has_attribute("name")) - merge_label = node.attribute_value("name", String<160>(merge_label)); + merge_label = node.attribute_value("name", String<160>("mirror")); merge_label_changed = merge_label_before != merge_label; }); @@ -293,14 +400,6 @@ void Framebuffer::Driver::config_update() system->sigh(system_handler); } else system.destruct(); - - if (update_in_progress) - new_config_rom = true; - else - update_in_progress = true; - - lx_emul_task_unblock(lx_user_task); - Lx_kit::env().scheduler.execute(); } @@ -329,12 +428,8 @@ static Framebuffer::Driver & driver(Genode::Env & env) void Framebuffer::Driver::generate_report() { - if (!config.valid()) - return; - - if (apply_config_on_hotplug() && !disable_report_once) { - disable_report_once = true; - Genode::Signal_transmitter(config_handler).submit(); + if (!config.valid()) { + error("no valid config - report is dropped"); return; } @@ -366,8 +461,6 @@ void Framebuffer::Driver::generate_report() }); }); }); - - disable_report_once = false; } @@ -508,12 +601,15 @@ void lx_emul_i915_framebuffer_ready(unsigned const connector_id, } if (conn.size.valid()) { - log(space, label, ": capture ", xres, "x", yres, " with " - " framebuffer ", phys_width, "x", phys_height); + if (drv.verbose) + log(space, label, ": capture ", xres, "x", yres, " with " + " framebuffer ", phys_width, "x", phys_height); lx_emul_i915_wakeup(unsigned(id.value)); } else - log(space, label, ": capture closed "); + if (drv.verbose) + log(space, label, ": capture closed ", + merge ? "(was mirror capture)" : ""); }, [](){ /* unknown id */ }); } @@ -521,14 +617,70 @@ void lx_emul_i915_framebuffer_ready(unsigned const connector_id, void lx_emul_i915_hotplug_connector() { - Genode::Env &env = Lx_kit::env().env; - driver(env).generate_report(); + auto & drv = driver(Lx_kit::env().env); + + drv.add_action(Action::ACTION_HOTPLUG, true); + + Genode::Signal_transmitter(drv.process_handler).submit(); +} + + +int lx_emul_i915_action_to_process(int const action_failed) +{ + auto & env = Lx_kit::env().env; + + while (true) { + + auto const action = driver(env).next_action(); + + switch (action) { + case Action::ACTION_HOTPLUG: + if (driver(env).apply_config_on_hotplug()) { + driver(env).add_action(Action::ACTION_DETECT_MODES); + driver(env).add_action(Action::ACTION_CONFIGURE); + driver(env).add_action(Action::ACTION_REPORT); + } else { + driver(env).add_action(Action::ACTION_DETECT_MODES); + driver(env).add_action(Action::ACTION_REPORT); + } + break; + case Action::ACTION_NEW_CONFIG: + driver(env).add_action(Action::ACTION_READ_CONFIG); + driver(env).add_action(Action::ACTION_CONFIGURE); + driver(env).add_action(Action::ACTION_REPORT); + if (driver(env).disable_all) + driver(env).add_action(Action::ACTION_EXIT); + + break; + case Action::ACTION_READ_CONFIG: + driver(env).config_read(); + break; + case Action::ACTION_REPORT: + if (action_failed) { + if (driver(env).verbose) + Genode::warning("previous action failed"); + + /* retry */ + driver(env).add_action(Action::ACTION_HOTPLUG, true); + } else + driver(env).generate_report(); + break; + case Action::ACTION_EXIT: + /* good bye world */ + driver(env).disable_all = false; + Lx_kit::env().env.parent().exit(0); + break; + default: + /* other actions are handled by Linux code */ + return action; + } + } } void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, char const *name, char const connected, - char const modes_available, + char const /* fb_available */, unsigned brightness, unsigned width_mm, unsigned height_mm) { @@ -536,7 +688,7 @@ void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, xml.node("connector", [&] () { - xml.attribute("connected", connected && modes_available); + xml.attribute("connected", !!connected); xml.attribute("name", name); if (width_mm) xml.attribute("width_mm" , width_mm); @@ -600,28 +752,6 @@ void lx_emul_i915_connector_config(char * name, struct genode_mode * mode) } -int lx_emul_i915_config_done_and_block(void) -{ - auto &state = driver(Lx_kit::env().env); - - bool const new_config = state.new_config_rom; - - state.update_in_progress = false; - state.new_config_rom = false; - - if (state.disable_all) { - state.disable_all = false; - Lx_kit::env().env.parent().exit(0); - } - - if (!new_config) - driver(Lx_kit::env().env).generate_report(); - - /* true if linux task should block, otherwise continue due to new config */ - return !new_config; -} - - void Component::construct(Genode::Env &env) { driver(env).start();