From 50cc52a091d7b5bb4b37f13631a86c1ffdc4bbb7 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Wed, 4 May 2022 12:01:54 +0200 Subject: [PATCH] intel/display: use one capture session per display Fixes #5345 --- repos/pc/run/intel_fb.run | 119 ++- .../pc/src/driver/framebuffer/intel/pc/README | 133 ++-- repos/pc/src/driver/framebuffer/intel/pc/fb.c | 27 - .../driver/framebuffer/intel/pc/lx_emul/fb.h | 29 - .../src/driver/framebuffer/intel/pc/lx_i915.h | 15 +- .../src/driver/framebuffer/intel/pc/lx_user.c | 703 +++++++++++++++--- .../src/driver/framebuffer/intel/pc/main.cc | 288 ++++--- .../driver/framebuffer/intel/pc/target.inc | 1 - 8 files changed, 969 insertions(+), 346 deletions(-) delete mode 100644 repos/pc/src/driver/framebuffer/intel/pc/fb.c delete mode 100644 repos/pc/src/driver/framebuffer/intel/pc/lx_emul/fb.h diff --git a/repos/pc/run/intel_fb.run b/repos/pc/run/intel_fb.run index a20f2dd2e4..c4395e087f 100644 --- a/repos/pc/run/intel_fb.run +++ b/repos/pc/run/intel_fb.run @@ -68,7 +68,12 @@ append config { - + + + + + + @@ -87,7 +92,7 @@ append config { - + @@ -100,7 +105,7 @@ append config { - + @@ -116,7 +121,7 @@ append config { - + @@ -143,14 +148,20 @@ append config { - + } - - - - +append_if $use_top config { + + + + + + + + } - +append config { + @@ -181,16 +192,6 @@ append config { } -append_if $use_top config { - - - - - - - - } - append config { @@ -219,6 +220,13 @@ append config { + + + + @@ -266,7 +274,7 @@ append_if $use_gpu config { append config { - + } append_if $use_gpu config { @@ -276,6 +284,18 @@ append config { + + + + + + + + + + + + } @@ -309,7 +329,62 @@ append_if $use_fb_controller config { } append config { - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/pc/src/driver/framebuffer/intel/pc/README b/repos/pc/src/driver/framebuffer/intel/pc/README index e40a311dec..e5f3e53e9e 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/README +++ b/repos/pc/src/driver/framebuffer/intel/pc/README @@ -3,62 +3,22 @@ This driver is for Intel i915 compatible graphic cards. Default behaviour ~~~~~~~~~~~~~~~~~ -When no configuration is provided to the driver, it will switch on all devices -connected to the graphics card. It will use the highest resolution as -provided by the BIOS or EDID information from the display devices for each -connector. The virtual resolution delivered to the client is the maximum in -width and height of the active connectors. +When no configuration is provided to the driver, it will enable all connectors +with attached displays and allocate for each display a discrete framebuffer. +It will use the highest resolution as provided by the BIOS or EDID information. +For each connector a separate Capture connection will be requested labeled +according to the connector name. When newly connected displays are detected +by the driver, the new connectors are enabled and another Capture session +labeled according to the connector will be requested. -When newly connected devices are detected by the hardware, the new connectors -are enabled, probed, and again the highest resolution across all active -connectors will be chosen. By default, the current config of the driver will -be re-read and re-applied. This behaviour can be disabled by +By default, on hotplug of a display, the current config of the driver will be +re-parsed and re-applied. This behaviour can be disabled by ! Configuration ~~~~~~~~~~~~~ -Each of the connectors can be configured explicitly in terms of resolution and -whether it should be enabled or not. This looks like the following: - -! -! -! -! - -The resolution can be configured exactly by the reported mode_id or by -the width/height/hz attributes. In the latter case the driver will take the -first matching mode out of multiple matching modes potentially. - -When the configuration changes during runtime, the driver will adapt to it. In -this case, it will also change the current virtual resolution to the maximum of -the configured resolutions in width and height, and it will inform its client -about the change in resolution. - -The brightness value is in percent and takes effect only if supported by -the hardware. - -The maximal physical resolution can be enforced by: - -! -! - -The virtual resolution can be enforced by: - -! -! - -The amount of memory used by the driver for the accounting of its available -buffer space is set by: - -! -! - -The default and minimal value is '64M' and suffices for resolutions of at -least '3840x2160'. How much actual memory is used depends on the configured -resolution. - To present all available connectors and their possible resolutions to the user, the driver is able to deliver a corresponding report, which can be enabled in the configuration as follows: @@ -89,5 +49,78 @@ in millimeter per connector and if available, also per mode. The values can be used as input to DPI calculations. The currently used mode of the connector is tagged in the report explicitly. -The brightness attribute is reported only if the hardware supports it. +Each of the connectors can be configured a specific mode and +whether it should be enabled or not. This looks like the following: +! +! +! +! + +The resolution can be configured exactly by the reported mode_id or by +the width/height/hz attributes. In the latter case the driver will take the +first matching mode out of multiple matching modes potentially. + +The brightness value is in percent and takes effect only if supported by +the hardware. + +The maximal physical resolution across all connectors can be restricted by: + +! +! + +Note: All larger modes still will be reported, but are marked as unusable + by an additional attribute 'unavailable' set to true. + +The amount of memory used by the driver for the accounting of its available +buffer space is set by: + +! +! + +The default and minimal value is '64M' and suffices for resolutions of at +least '3840x2160'. How much actual memory is used depends on the configured +resolution. + + +Non-discrete usage of connectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mirrored usage of connectors can be achieved by moving those connectors into +a sub node called 'merge' of the configuration. For all those connectors, +exactly one and the same framebuffer will be used internally by the driver. +The driver will allocate the framebuffer large enough to accommodate all +non-discrete connectors. If some of the modes of the connectors are smaller, +than only a subset of the content will be visible on those displays. + +! +! +! +! ... +! +! +! ... +! +! +! +! +! ... +! +! +! ... +! +! +! + +Note: If connectors are configured as non-discrete, they will also be + reported inside a separate 'merge' node. + +Additionally, the virtual resolution for non-discrete connectors may be +restricted via: + +! +! ... +! + +Thereby, the driver will open a Genode capture session to the +GUI multiplexer with this limited dimension. diff --git a/repos/pc/src/driver/framebuffer/intel/pc/fb.c b/repos/pc/src/driver/framebuffer/intel/pc/fb.c deleted file mode 100644 index 240021045e..0000000000 --- a/repos/pc/src/driver/framebuffer/intel/pc/fb.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * \brief Linux kernel framebuffer device support - * \author Stefan Kalkowski - * \date 2021-05-03 - */ - -/* - * Copyright (C) 2021-2023 Genode Labs GmbH - * - * This file is distributed under the terms of the GNU General Public License - * version 2. - */ - -#include -#include -#include -#include - - -int register_framebuffer(struct fb_info * fb_info) -{ - lx_emul_framebuffer_ready(fb_info->screen_base, fb_info->screen_size, - fb_info->var.xres_virtual, fb_info->var.yres_virtual, - fb_info->fix.line_length / - (fb_info->var.bits_per_pixel / 8), fb_info->var.yres); - return 0; -} diff --git a/repos/pc/src/driver/framebuffer/intel/pc/lx_emul/fb.h b/repos/pc/src/driver/framebuffer/intel/pc/lx_emul/fb.h deleted file mode 100644 index 438c52bc41..0000000000 --- a/repos/pc/src/driver/framebuffer/intel/pc/lx_emul/fb.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * \brief Lx_emul support to register Linux kernel framebuffer - * \author Stefan Kalkowski - * \date 2021-05-17 - */ - -/* - * Copyright (C) 2021 Genode Labs GmbH - * - * This file is distributed under the terms of the GNU General Public License - * version 2. - */ - -#ifndef _LX_EMUL__FB_H_ -#define _LX_EMUL__FB_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -void lx_emul_framebuffer_ready(void * base, unsigned long size, - unsigned xres, unsigned yres, - unsigned virtual_width, unsigned virtual_height); - -#ifdef __cplusplus -} -#endif - -#endif /* _LX_EMUL__FB_H_ */ 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 3eb8eb5444..168af23096 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h +++ b/repos/pc/src/driver/framebuffer/intel/pc/lx_i915.h @@ -29,11 +29,13 @@ struct genode_mode { unsigned preferred; unsigned inuse; unsigned id; + char mirror; char name[32]; }; -int lx_emul_i915_blit(void); -void lx_emul_i915_report(void * genode_xml); +int lx_emul_i915_blit(unsigned); +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); void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, char const *name, char connected, @@ -43,5 +45,14 @@ void lx_emul_i915_iterate_modes(void *lx_data, void * genode_data); void lx_emul_i915_report_modes(void * genode_xml, struct genode_mode *); void lx_emul_i915_connector_config(char * name, struct genode_mode *); int lx_emul_i915_config_done_and_block(void); +void lx_emul_i915_framebuffer_ready(unsigned connector_id, + char const * const connector_name, + void * base, + unsigned long size, + unsigned xres, unsigned yres, + unsigned virtual_width, + unsigned virtual_height, + unsigned mm_width, + unsigned mm_height); #endif /* _LX_I915_H_ */ 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 4830133ff1..4e930c6d04 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c +++ b/repos/pc/src/driver/framebuffer/intel/pc/lx_user.c @@ -28,14 +28,15 @@ enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 }; - struct task_struct * lx_update_task = NULL; - struct task_struct * lx_user_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 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_mode_fb_cmd2 const * const dumb_fb, + unsigned width_mm, unsigned height_mm); static int user_attach_fb_to_crtc(struct drm_client_dev * const dev, @@ -85,11 +86,11 @@ static inline bool fb_smaller_mode(struct fb_info const * const info, /* - * Heuristic to calculate max resolution across all connectors + * Heuristic to calculate max resolution across all mirrored connectors */ -static void preferred_mode(struct drm_device const * const dev, - struct drm_display_mode * const prefer, - struct drm_display_mode * const min_mode) +static void preferred_mirror(struct drm_device const * const dev, + struct drm_display_mode * const prefer, + struct drm_display_mode * const min_mode) { struct drm_connector *connector = NULL; struct drm_display_mode *mode = NULL; @@ -101,7 +102,7 @@ static void preferred_mode(struct drm_device const * const dev, drm_connector_list_iter_begin(dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { struct drm_display_mode smallest = { .hdisplay = ~0, .vdisplay = ~0 }; - struct genode_mode conf_mode = { .enabled = 1 }; + struct genode_mode conf_mode = { }; unsigned mode_id = 0; /* check for connector configuration on Genode side */ @@ -144,7 +145,9 @@ static void preferred_mode(struct drm_device const * const dev, if (mode_id && mode_larger(&smallest, min_mode)) *min_mode = smallest; - if (conf_mode.force_width && conf_mode.force_height) { + if (conf_mode.mirror && + conf_mode.force_width && conf_mode.force_height) { + /* * Even so the force_* mode is selected, a configured mode for * a connector is considered, effectively the framebuffer content @@ -168,7 +171,7 @@ static void preferred_mode(struct drm_device const * const dev, continue; } - if (!conf_mode.width || !conf_mode.height) + if (!conf_mode.width || !conf_mode.height || !conf_mode.mirror) continue; if (conf_larger_mode(&conf_mode, prefer)) { @@ -189,6 +192,11 @@ static void preferred_mode(struct drm_device const * const dev, /* if too large or nothing configured by Genode's config */ drm_connector_list_iter_begin(dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { + + struct genode_mode conf_mode = { }; + /* check for connector configuration on Genode side */ + lx_emul_i915_connector_config(connector->name, &conf_mode); + list_for_each_entry(mode, &connector->modes, head) { if (!mode) continue; @@ -201,6 +209,9 @@ static void preferred_mode(struct drm_device const * const dev, continue; } + if (conf_mode.enabled && !conf_mode.mirror) + continue; + if (mode_larger(mode, prefer)) { prefer->hdisplay = mode->hdisplay; prefer->vdisplay = mode->vdisplay; @@ -249,35 +260,313 @@ static unsigned get_brightness(struct drm_connector * const connector, return ret * MAX_BRIGHTNESS / panel->backlight.device->props.max_brightness; } -static struct drm_mode_fb_cmd2 dumb_fb = {}; + +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; + + 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 (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 (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)); +} + + +static int kernel_register_fb(struct fb_info const * const fb_info, + unsigned const width_mm, + unsigned const height_mm) +{ + lx_emul_i915_framebuffer_ready(fb_info->node, + fb_info->par, + fb_info->screen_base, + fb_info->screen_size, + fb_info->var.xres_virtual, + fb_info->var.yres_virtual, + fb_info->fix.line_length / + (fb_info->var.bits_per_pixel / 8), + fb_info->var.yres, + width_mm, height_mm); + + return 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) { + struct fb_info info = {}; + + info.var.bits_per_pixel = 32; + info.node = connector->index; + info.par = connector->name; + + kernel_register_fb(&info, mode->width_mm, mode->height_mm); + + destroy_fb(dev, gem_dumb, fb_cmd); + } + + 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; +} + + +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; + + 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 (!unused) { + struct drm_modeset_acquire_ctx ctx; + void * fb = NULL; + int err = -1; + + DRM_MODESET_LOCK_ALL_BEGIN(dev->dev, ctx, + DRM_MODESET_ACQUIRE_INTERRUPTIBLE, + err); + + 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 = {}; + + fb_info.var.bits_per_pixel = 32; + fb_info.node = connector->index; + + kernel_register_fb(&fb_info, 0, 0); + } + } + drm_connector_list_iter_end(&conn_iter); +} + static bool reconfigure(struct drm_client_dev * const dev) { - static struct drm_mode_create_dumb gem_dumb = {}; + static struct drm_mode_create_dumb *gem_mirror = NULL; - struct drm_display_mode mode_preferred = {}; - struct drm_display_mode mode_minimum = {}; - struct drm_display_mode framebuffer = {}; - struct drm_mode_modeinfo user_mode = {}; - struct drm_display_mode *mode = NULL; - struct drm_mode_set *mode_set = NULL; - struct fb_info report_fb_info = {}; - bool report_fb = false; - bool retry = false; + struct drm_display_mode mode_preferred = {}; + struct drm_display_mode mode_minimum = {}; + struct drm_display_mode framebuffer = {}; + 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; + unsigned height_mm; + bool report; + } mirror = { { }, 0, 0, false }; - if (!dev || !dev->dev) + if (!gem_mirror) { + /* request storage for gem_mirror and mirror_fb_cmd */ + dumb_meta(dev, NULL, &gem_mirror, &mirror_fb_cmd); + } + + if (!dev || !dev->dev || !gem_mirror || !mirror_fb_cmd) return false; - preferred_mode(dev->dev, &mode_preferred, &mode_minimum); + preferred_mirror(dev->dev, &mode_preferred, &mode_minimum); if (!mode_minimum.hdisplay || !mode_minimum.vdisplay) { /* no valid modes on any connector on early boot */ - if (!dumb_fb.fb_id) + if (!mirror_fb_cmd->fb_id) return false; /* valid connectors but all are disabled by config */ - mode_minimum.hdisplay = dumb_fb.width; - mode_minimum.vdisplay = dumb_fb.height; + mode_minimum.hdisplay = mirror_fb_cmd->width; + mode_minimum.vdisplay = mirror_fb_cmd->height; mode_preferred = mode_minimum; } @@ -288,8 +577,8 @@ static bool reconfigure(struct drm_client_dev * const dev) { int const err = check_resize_fb(dev, - &gem_dumb, - &dumb_fb, + gem_mirror, + mirror_fb_cmd, framebuffer.hdisplay, framebuffer.vdisplay); @@ -302,21 +591,20 @@ static bool reconfigure(struct drm_client_dev * const dev) } /* without fb handle created by check_resize_fb we can't proceed */ - if (!dumb_fb.fb_id) + if (!mirror_fb_cmd->fb_id) return retry; - /* prepare fb info for register_framebuffer() evaluated by Genode side */ - report_fb_info.var.xres = framebuffer.hdisplay; - report_fb_info.var.yres = framebuffer.vdisplay; - report_fb_info.var.xres_virtual = mode_preferred.hdisplay; - report_fb_info.var.yres_virtual = mode_preferred.vdisplay; + /* prepare fb info for kernel_register_fb() evaluated by Genode side */ + info.var.xres = framebuffer.hdisplay; + info.var.yres = framebuffer.vdisplay; + info.var.xres_virtual = mode_preferred.hdisplay; + info.var.yres_virtual = mode_preferred.vdisplay; 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_match = NULL; + unsigned mode_id = 0; + struct drm_connector * connector = NULL; + struct genode_mode conf_mode = {}; if (!mode_set->connectors || !*mode_set->connectors) continue; @@ -337,7 +625,7 @@ static bool reconfigure(struct drm_client_dev * const dev) continue; /* allocated framebuffer smaller than mode can't be used */ - if (fb_smaller_mode(&report_fb_info, mode)) + if (fb_smaller_mode(&info, mode)) continue; /* use mode id if configured and matches exactly */ @@ -374,19 +662,35 @@ static bool reconfigure(struct drm_client_dev * const dev) /* apply new mode */ mode_id = 0; list_for_each_entry(mode, &connector->modes, head) { - int err = -1; - bool no_match = false; + struct fb_info fb_info = info; + int err = -1; + bool no_match = false; + struct drm_mode_fb_cmd2 fb_cmd = *mirror_fb_cmd; mode_id ++; if (!mode) continue; + /* use first mode for non mirrored connector in case of no match */ + if (!mode_match && !conf_mode.mirror) { + + struct drm_display_mode max = { .hdisplay = conf_mode.max_width, + .vdisplay = conf_mode.max_height }; + + 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 (fb_smaller_mode(&report_fb_info, mode)) + if (fb_smaller_mode(&fb_info, mode)) continue; /* use first smaller mode */ @@ -399,19 +703,34 @@ static bool reconfigure(struct drm_client_dev * const dev) 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 expectecd 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, dumb_fb.fb_id, + &user_mode, fb_cmd.fb_id, conf_mode.enabled); - if (err) - retry = true; - else - report_fb = true; - /* set brightness */ if (!err && conf_mode.enabled && conf_mode.brightness <= MAX_BRIGHTNESS) { drm_modeset_lock(&dev->dev->mode_config.connection_mutex, NULL); @@ -420,13 +739,25 @@ static bool reconfigure(struct drm_client_dev * const dev) drm_modeset_unlock(&dev->dev->mode_config.connection_mutex); } + if (!retry) + retry = !!err; + + if (!err && conf_mode.mirror && !mirror.report) { + /* use fb_info of first mirrored screen */ + mirror.report = true; + mirror.width_mm = mode->width_mm; + mirror.height_mm = mode->height_mm; + mirror.info = fb_info; + } + /* diagnostics */ - printk("%10s: %s name='%9s' id=%u%s mode=%4ux%4u@%u%s fb=%4ux%4u%s", + 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", mode->name ? mode->name : "noname", - mode_id, mode_id < 10 ? " " : "", mode->hdisplay, - mode->vdisplay, drm_mode_vrefresh(mode), + mode_id, mode_id < 10 ? " " : "", + conf_mode.mirror ? " mirror " : " discrete", + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), drm_mode_vrefresh(mode) < 100 ? " ": "", framebuffer.hdisplay, framebuffer.vdisplay, (err || no_match) ? "" : "\n"); @@ -438,12 +769,21 @@ static bool reconfigure(struct drm_client_dev * const dev) if (err) printk(" - failed, error=%d\n", err); + if (!err && !conf_mode.mirror) + user_register_fb(dev, &fb_info, &fb_cmd, + mode->width_mm, mode->height_mm); + break; } } - if (report_fb) - user_register_fb(dev, &report_fb_info, &dumb_fb); + if (mirror.report) { + mirror.info.par = "mirror_capture"; + user_register_fb(dev, &mirror.info, mirror_fb_cmd, mirror.width_mm, + mirror.height_mm); + } + + close_unused_captures(dev); return retry; } @@ -474,9 +814,8 @@ static int configure_connectors(void * data) } -void framebuffer_dirty(void) +static void mark_framebuffer_dirty(struct drm_framebuffer * const fb) { - struct drm_framebuffer *fb = NULL; struct drm_clip_rect *clips = NULL; struct drm_mode_fb_dirty_cmd r = { }; @@ -484,12 +823,9 @@ void framebuffer_dirty(void) int num_clips = 0; int ret = 0; - if (!dev_client || !dumb_fb.fb_id) + if (!dev_client) return; - fb = drm_framebuffer_lookup(dev_client->dev, dev_client->file, - dumb_fb.fb_id); - if (!fb || !fb->funcs || !fb->funcs->dirty) return; @@ -504,9 +840,37 @@ void framebuffer_dirty(void) 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; - if (lx_emul_i915_blit()) - framebuffer_dirty(); + 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; + + if (connector->status != connector_status_connected) + continue; + + if (!lx_emul_i915_blit(connector->index)) + continue; + + if (!connector->state || !connector->state->crtc) + continue; + + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, + DRM_MODESET_ACQUIRE_INTERRUPTIBLE, + err); + + fb = lookup_framebuffer(connector->state->crtc, &ctx); + + DRM_MODESET_LOCK_ALL_END(dev, ctx, err); + + if (fb) + mark_framebuffer_dirty(fb); + } + drm_connector_list_iter_end(&conn_iter); /* schedule_timeout(jiffes) or hrtimer or msleep */ msleep(20); @@ -523,12 +887,42 @@ void lx_user_init(void) int pid2 = kernel_thread(update_content, NULL, "lx_update", CLONE_FS | CLONE_FILES); - lx_user_task = find_task_by_pid_ns(pid, NULL);; - lx_update_task = find_task_by_pid_ns(pid2, NULL);; + lx_user_task = find_task_by_pid_ns(pid , NULL); + lx_update_task = find_task_by_pid_ns(pid2, NULL); } -void lx_emul_i915_report(void * genode_data) +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; @@ -537,6 +931,22 @@ void lx_emul_i915_report(void * genode_data) drm_connector_list_iter_begin(dev, &conn_iter); drm_client_for_each_connector_iter(connector, &conn_iter) { + + bool mirror = connector->state && connector->state->crtc && + mirrored_fb(dev_client, connector->state->crtc); + + if (!mirror && (!connector->state || !connector->state->crtc)) { + struct genode_mode conf_mode = { }; + + /* check for connector configuration on Genode side */ + lx_emul_i915_connector_config(connector->name, &conf_mode); + + mirror = conf_mode.mirror; + } + + if ((discrete && mirror) || (!discrete && !mirror)) + continue; + lx_emul_i915_report_connector(connector, genode_data, connector->name, connector->status != connector_status_disconnected, @@ -548,6 +958,18 @@ void lx_emul_i915_report(void * genode_data) } +void lx_emul_i915_report_discrete(void * genode_data) +{ + _report_connectors(genode_data, true /* discrete */); +} + + +void lx_emul_i915_report_non_discrete(void * genode_data) +{ + _report_connectors(genode_data, false /* non discrete */); +} + + void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data) { struct drm_connector *connector = lx_data; @@ -582,6 +1004,8 @@ void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data) 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); struct genode_mode conf_mode = { .width = mode->hdisplay, @@ -591,6 +1015,7 @@ void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data) .preferred = mode->type & (DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DEFAULT), .inuse = inuse, + .mirror = mirror, .hz = drm_mode_vrefresh(mode), .id = mode_id, .enabled = !max_mode || @@ -622,12 +1047,13 @@ void i915_switcheroo_unregister(struct drm_i915_private *i915) static int fb_client_hotplug(struct drm_client_dev *client) { - struct drm_mode_set *modeset = NULL; - struct drm_framebuffer *fb = NULL; - int result = -EINVAL; + struct drm_mode_set *modeset = NULL; + struct drm_framebuffer *fb_mirror = NULL; + int result = -EINVAL; - if (dumb_fb.fb_id) - fb = drm_framebuffer_lookup(client->dev, client->file, dumb_fb.fb_id); + if (mirror_fb_cmd && mirror_fb_cmd->fb_id) + fb_mirror = drm_framebuffer_lookup(client->dev, client->file, + mirror_fb_cmd->fb_id); /* * Triggers set up of display pipelines for connectors and @@ -644,22 +1070,75 @@ static int fb_client_hotplug(struct drm_client_dev *client) * (Re-)assign framebuffer to modeset (lost due to modeset_probe) and * commit the change. */ - if (fb) { - bool mode_too_large = false; + if (fb_mirror) { + struct drm_framebuffer * free_fbs[MAX_FBS] = { }; + struct drm_modeset_acquire_ctx ctx; + bool mode_too_large = false; + unsigned fb_count = 0; + + DRM_MODESET_LOCK_ALL_BEGIN(client->dev, ctx, + DRM_MODESET_ACQUIRE_INTERRUPTIBLE, + result); mutex_lock(&client->modeset_mutex); drm_client_for_each_modeset(modeset, client) { - if (!modeset || !modeset->num_connectors) + struct drm_connector *connector = NULL; + struct drm_framebuffer *fb = NULL; + + if (!modeset) continue; - if (!mode_too_large && modeset->mode && + if (modeset->crtc) + fb = lookup_framebuffer(modeset->crtc, &ctx); + + if (!mode_too_large && fb && modeset->mode && (modeset->mode->hdisplay > fb->width || modeset->mode->vdisplay > fb->height)) mode_too_large = true; - modeset->fb = fb; + 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; + } + + /* set connector */ + connector = *modeset->connectors; + + modeset->fb = fb ? fb : fb_mirror; } 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); @@ -673,8 +1152,8 @@ static int fb_client_hotplug(struct drm_client_dev *client) /* notify Genode side */ lx_emul_i915_hotplug_connector(); - if (fb) - drm_framebuffer_put(fb); + if (fb_mirror) + drm_framebuffer_put(fb_mirror); return 0; } @@ -755,15 +1234,16 @@ int user_attach_fb_to_crtc(struct drm_client_dev * const dev, 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_mode_fb_cmd2 const * const dumb_fb, + 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 }; - static struct i915_vma *vma = NULL; - static unsigned long flags = 0; 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, @@ -774,49 +1254,61 @@ static int user_register_fb(struct drm_client_dev const * const dev, return -ENODEV; } - wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); - - if (vma) { - intel_unpin_fb_vma(vma, flags); - - vma = NULL; - flags = 0; + if (!dumb_gem(dev, fb, &gem_dumb) || !gem_dumb) { + printk("%s:%u error looking up fb and vma\n", __func__, __LINE__); + return -ENODEV; } + if (gem_dumb->vma) { + intel_unpin_fb_vma(gem_dumb->vma, gem_dumb->flags); + + gem_dumb->vma = NULL; + gem_dumb->flags = 0; + } + + wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + /* Pin the GGTT vma for our access via info->screen_base. * This also validates that any existing fb inherited from the * BIOS is suitable for own access. */ - vma = intel_pin_and_fence_fb_obj(fb, false /* phys_cursor */, - &view, false /* use fences */, - &flags); + gem_dumb->vma = intel_pin_and_fence_fb_obj(fb, false /* phys_cursor */, + &view, false /* use fences */, + &gem_dumb->flags); - if (IS_ERR(vma)) { + if (IS_ERR(gem_dumb->vma)) { intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); - result = PTR_ERR(vma); - printk("%s:%u error setting vma %d\n", __func__, __LINE__, result); + result = PTR_ERR(gem_dumb->vma); + + printk("%s:%u error setting vma %d\n", __func__, __LINE__, result); + + gem_dumb->vma = NULL; return result; } - vaddr = i915_vma_pin_iomap(vma); + vaddr = i915_vma_pin_iomap(gem_dumb->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; + return result; } - /* fill framebuffer info for register_framebuffer */ + /* fill framebuffer info for kernel_register_fb */ info->screen_base = vaddr; - info->screen_size = vma->size; + info->screen_size = gem_dumb->vma->size; info->fix.line_length = fb->pitches[0]; info->var.bits_per_pixel = drm_format_info_bpp(fb->format, 0); intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); - register_framebuffer(info); + kernel_register_fb(info, width_mm, height_mm); if (fb) drm_framebuffer_put(fb); @@ -841,20 +1333,7 @@ static int check_resize_fb(struct drm_client_dev * const dev, if (gem_dumb->width && gem_dumb->height && (gem_dumb->width < width || gem_dumb->height < height)) { - result = drm_mode_rmfb(dev->dev, dumb_fb->fb_id, dev->file); - if (result) { - drm_err(dev->dev, "%s: failed to remove framebufer %d\n", - __func__, result); - } - - 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); - } - - memset(gem_dumb, 0, sizeof(*gem_dumb)); - memset(dumb_fb, 0, sizeof(*dumb_fb)); + destroy_fb(dev, gem_dumb, dumb_fb); } /* allocate dumb framebuffer, on success a GEM object handle is returned */ diff --git a/repos/pc/src/driver/framebuffer/intel/pc/main.cc b/repos/pc/src/driver/framebuffer/intel/pc/main.cc index 45a7364118..49cbce956e 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/main.cc +++ b/repos/pc/src/driver/framebuffer/intel/pc/main.cc @@ -20,7 +20,6 @@ /* emulation includes */ #include -#include #include #include #include @@ -45,6 +44,7 @@ struct Framebuffer::Driver using Attached_rom_system = Constructible; Env &env; + Heap heap { env.ram(), env.rm() }; Attached_rom_dataspace config { env, "config" }; Attached_rom_system system { }; Expanding_reporter reporter { env, "connectors", "connectors" }; @@ -61,67 +61,88 @@ struct Framebuffer::Driver bool disable_all { false }; bool disable_report_once { false }; - class Fb - { - private: + Capture::Connection::Label merge_label { "mirror" }; - Capture::Connection _capture; - Capture::Area const _size; - Capture::Area const _size_phys; - Capture::Connection::Screen _captured_screen; - void * _base; + struct Connector { + using Space = Id_space; + using Id = Space::Id; - /* - * Non_copyable - */ - Fb(const Fb&); - Fb & operator=(const Fb&); + Space::Element id_element; - public: + Connector(Space &space, Id id) : id_element(*this, space, id) { } - bool paint() - { - using Pixel = Capture::Pixel; + addr_t base { }; + Capture::Area size { }; + Capture::Area size_phys { }; + Capture::Area size_mm { }; - bool dirty = false; - - _captured_screen.with_texture([&] (Texture const &texture) { - - auto const affected = _capture.capture_at(Capture::Point(0, 0)); - - affected.for_each_rect([&] (Capture::Rect const rect) { - - Surface surface((Pixel*)_base, _size_phys); - - surface.clip(rect); - - Blit_painter::paint(surface, texture, Capture::Point(0, 0)); - - dirty = true; - }); - }); - - return dirty; - } - - Fb(Env & env, void * base, Capture::Area size, - Capture::Area size_phys) - : - _capture(env), - _size(size), - _size_phys(size_phys), - _captured_screen(_capture, env.rm(), { .px = _size, .mm = { } }), - _base(base) {} - - bool same_setup(void * base, Capture::Area &size, - Capture::Area &size_phys) - { - return ((base == _base) && (size == _size) && - (size_phys == _size_phys)); - } + Constructible capture { }; + Constructible screen { }; }; - Constructible fb {}; + Connector::Space ids { }; + + bool capture(Connector::Space &ids, Connector::Id const &id) + { + using Pixel = Capture::Pixel; + + bool dirty = false; + + ids.apply(id, [&](Connector &connector) { + + if (!connector.capture.constructed() || + !connector.screen.constructed()) + return; + + connector.screen->with_texture([&] (Texture const &texture) { + + auto const affected = connector.capture->capture_at({ 0, 0}); + + affected.for_each_rect([&] (Capture::Rect const rect) { + + Surface surface((Pixel*)connector.base, + connector.size_phys); + + surface.clip(rect); + Blit_painter::paint(surface, texture, Capture::Point(0, 0)); + + dirty = true; + }); + }); + }, [&](){ /* unknown connector id */ }); + + return dirty; + } + + bool update(Connector &conn, addr_t const base, + Capture::Area &size, Capture::Area &size_phys, + Capture::Area const mm, + auto const &label) + { + bool same = (base == conn.base) && + (size == conn.size) && + (size_phys == conn.size_phys) && + (mm == conn.size_mm); + + if (same) + return same; + + conn.base = base; + conn.size = size; + 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); + } else { + conn.screen .destruct(); + conn.capture.destruct(); + } + + return same; + } void config_update(); void system_update(); @@ -178,8 +199,7 @@ struct Framebuffer::Driver return apply_config; } - template - void with_max_enforcement(T const &fn) const + void with_max_enforcement(auto const &fn) const { unsigned max_width = config.xml().attribute_value("max_width", 0u); unsigned max_height = config.xml().attribute_value("max_height",0u); @@ -188,11 +208,10 @@ struct Framebuffer::Driver fn(max_width, max_height); } - template - void with_force(T const &fn) const + void with_force(auto const &node, auto const &fn) const { - unsigned force_width = config.xml().attribute_value("force_width", 0u); - unsigned force_height = config.xml().attribute_value("force_height", 0u); + unsigned force_width = node.attribute_value("width", 0u); + unsigned force_height = node.attribute_value("height", 0u); if (force_width && force_height) fn(force_width, force_height); @@ -247,6 +266,11 @@ void Framebuffer::Driver::config_update() if (!config.valid() || !lx_user_task) return; + config.xml().with_optional_sub_node("merge", [&](auto const &node) { + if (node.has_attribute("name")) + merge_label = node.attribute_value("name", String<160>(merge_label)); + }); + if (config.xml().attribute_value("system", false)) { system.construct(Lx_kit::env().env, "system"); system->sigh(system_handler); @@ -310,12 +334,18 @@ void Framebuffer::Driver::generate_report() xml.attribute("max_height", height); }); - with_force([&](unsigned width, unsigned height) { - xml.attribute("force_width", width); - xml.attribute("force_height", height); - }); + lx_emul_i915_report_discrete(&xml); - lx_emul_i915_report(&xml); + xml.node("merge", [&] () { + node.with_optional_sub_node("merge", [&](auto const &merge) { + with_force(merge, [&](unsigned width, unsigned height) { + xml.attribute("width", width); + xml.attribute("height", height); + }); + }); + + lx_emul_i915_report_non_discrete(&xml); + }); }); }); @@ -326,36 +356,58 @@ void Framebuffer::Driver::generate_report() void Framebuffer::Driver::lookup_config(char const * const name, struct genode_mode &mode) { + bool mirror_node = false; + /* default settings, possibly overridden by explicit configuration below */ mode.enabled = !disable_all; - mode.brightness = 70 /* percent */; + mode.brightness = 70; /* percent */ + mode.mirror = false; if (!config.valid() || disable_all) return; - /* iterate independently of force* ever to get brightness and hz */ - config.xml().for_each_sub_node("connector", [&] (Xml_node &node) { + auto for_each_node = [&](auto const &node, bool const mirror){ using Name = String<32>; Name const con_policy = node.attribute_value("name", Name()); if (con_policy != name) return; + mode.mirror = mirror; mode.enabled = node.attribute_value("enabled", true); + if (!mode.enabled) return; + mode.width = node.attribute_value("width" , 0U); + mode.height = node.attribute_value("height" , 0U); + mode.hz = node.attribute_value("hz" , 0U); + mode.id = node.attribute_value("mode_id", 0U); mode.brightness = node.attribute_value("brightness", unsigned(MAX_BRIGHTNESS + 1)); + }; - mode.width = node.attribute_value("width", 0U); - mode.height = node.attribute_value("height", 0U); - mode.hz = node.attribute_value("hz", 0U); - mode.id = node.attribute_value("mode_id", 0U); + /* lookup config of discrete connectors */ + config.xml().for_each_sub_node("connector", [&] (Xml_node const &conn) { + for_each_node(conn, false); }); - with_force([&](unsigned const width, unsigned const height) { - mode.force_width = width; - mode.force_height = height; + /* lookup config of mirrored connectors */ + config.xml().for_each_sub_node("merge", [&] (Xml_node const &merge) { + if (mirror_node) { + error("only one mirror node supported"); + return; + } + + merge.for_each_sub_node("connector", [&] (Xml_node const &conn) { + for_each_node(conn, true); + }); + + with_force(merge, [&](unsigned const width, unsigned const height) { + mode.force_width = width; + mode.force_height = height; + }); + + mirror_node = true; }); with_max_enforcement([&](unsigned const width, unsigned const height) { @@ -372,41 +424,72 @@ unsigned long long driver_max_framebuffer_memory(void) } -/** - * Can be called already as side-effect of `lx_emul_start_kernel`, - * that's why the Driver object needs to be constructed already here. - */ -extern "C" void lx_emul_framebuffer_ready(void * base, unsigned long, - unsigned xres, unsigned yres, - unsigned phys_width, - unsigned phys_height) +void lx_emul_i915_framebuffer_ready(unsigned const connector_id, + char const * const conn_name, + void * const base, + unsigned long, + unsigned const xres, + unsigned const yres, + unsigned const phys_width, + unsigned const phys_height, + unsigned const mm_width, + unsigned const mm_height) { auto &env = Lx_kit::env().env; auto &drv = driver(env); - auto &fb = drv.fb; - Capture::Area area(xres, yres); - Capture::Area area_phys(phys_width, phys_height); + using namespace Genode; - if (fb.constructed()) { - if (fb->same_setup(base, area, area_phys)) + typedef Framebuffer::Driver::Connector Connector; + + auto const id = Connector::Id { connector_id }; + + /* allocate new id for new connector */ + drv.ids.apply(id, [&](Connector &) { /* known id */ }, [&](){ + /* ignore unused connector - don't need a object for it */ + if (!base) return; - fb.destruct(); - } + new (drv.heap) Connector (drv.ids, id); + }); - /* clear artefacts */ - if (area != area_phys) - Genode::memset(base, 0, area_phys.count() * 4); + drv.ids.apply(id, [&](Connector &conn) { - fb.construct(env, base, area, area_phys); + Capture::Area area (xres, yres); + Capture::Area area_phys(phys_width, phys_height); - Genode::log("framebuffer reconstructed - virtual=", xres, "x", yres, - " physical=", phys_width, "x", phys_height); + auto const prev_size = conn.size; + + auto label = !conn_name + ? Capture::Connection::Label(conn.id_element) + : Capture::Connection::Label(conn_name) == "mirror_capture" + ? drv.merge_label + : Capture::Connection::Label(conn_name); + + bool const same = drv.update(conn, Genode::addr_t(base), area, + area_phys, { mm_width, mm_height}, label); + + if (same) + return; + + /* clear artefacts */ + if (base && (area != area_phys)) + Genode::memset(base, 0, area_phys.count() * 4); + + if (conn.size.valid()) + log("setup - framebuffer ", + " - connector id=", conn.id_element.id().value, + ", virtual=", xres, "x", yres, + ", physical=", phys_width, "x", phys_height); + else + log("free - framebuffer ", + " - connector id=", conn.id_element.id().value, + ", ", prev_size); + }, [](){ /* unknown id */ }); } -extern "C" void lx_emul_i915_hotplug_connector() +void lx_emul_i915_hotplug_connector() { Genode::Env &env = Lx_kit::env().env; driver(env).generate_report(); @@ -422,8 +505,8 @@ void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, xml.node("connector", [&] () { - xml.attribute("name", name); xml.attribute("connected", !!connected); + xml.attribute("name", name); if (width_mm) xml.attribute("width_mm" , width_mm); if (height_mm) @@ -466,14 +549,13 @@ void lx_emul_i915_report_modes(void * genode_xml, struct genode_mode *mode) } -int lx_emul_i915_blit() +int lx_emul_i915_blit(unsigned connector_id) { auto &drv = driver(Lx_kit::env().env); - if (!drv.fb.constructed()) - return false; + auto const id = Framebuffer::Driver::Connector::Id { connector_id }; - return drv.fb->paint(); + return drv.capture(drv.ids, id); } diff --git a/repos/pc/src/driver/framebuffer/intel/pc/target.inc b/repos/pc/src/driver/framebuffer/intel/pc/target.inc index 93004a960f..d69ff94f46 100644 --- a/repos/pc/src/driver/framebuffer/intel/pc/target.inc +++ b/repos/pc/src/driver/framebuffer/intel/pc/target.inc @@ -15,7 +15,6 @@ SRC_C += dummies.c SRC_C += pci.c SRC_C += lx_emul.c SRC_C += $(notdir $(wildcard $(REL_PRG_DIR)/generated_dummies.c)) -SRC_C += fb.c SRC_C += lx_user.c SRC_C += gem.c SRC_C += lx_emul/common_dummies.c