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();