intel/display: improve disrete/mirror swap support

Issue #5345
This commit is contained in:
Alexander Boettcher 2024-10-21 14:37:55 +02:00 committed by Christian Helmuth
parent 1a5f3a2210
commit 71f3e5f82a
2 changed files with 153 additions and 139 deletions

View File

@ -33,6 +33,10 @@ static struct task_struct * lx_update_task = NULL;
static struct drm_client_dev * dev_client = NULL;
enum { CONNECTOR_ID_MIRROR = 15 };
static bool mirrored [16] = { };
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,
@ -86,32 +90,32 @@ static inline bool fb_smaller_mode(struct fb_info const * const info,
/*
* Heuristic to calculate max resolution across all mirrored connectors
* Heuristic to calculate mixed resolution across all mirrored connectors
*/
static void preferred_mirror(struct drm_device const * const dev,
struct drm_display_mode * const prefer,
static void mirror_heuristic(struct drm_device const * const dev,
struct drm_display_mode * const virtual,
struct drm_display_mode * const compound,
struct drm_display_mode * const min_mode)
{
struct drm_connector *connector = NULL;
struct drm_display_mode *mode = NULL;
unsigned connector_usable = 0;
struct drm_display_mode max_enforcement = { };
struct drm_connector_list_iter conn_iter;
/* read Genode's config per connector */
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 drm_display_mode usable = { };
struct genode_mode conf_mode = { };
unsigned mode_id = 0;
/* check for connector configuration on Genode side */
lx_emul_i915_connector_config(connector->name, &conf_mode);
if (!conf_mode.enabled)
if (!conf_mode.enabled || !conf_mode.mirror)
continue;
/* look for smallest possible mode or if a specific mode is forced */
/* look for smallest possible mode or if a specific mode is specified */
list_for_each_entry(mode, &connector->modes, head) {
mode_id ++;
@ -123,21 +127,22 @@ static void preferred_mirror(struct drm_device const * const dev,
smallest.vdisplay = mode->vdisplay;
}
if (!conf_mode.id)
continue;
/* maximal resolution enforcement */
if (conf_mode.max_width && conf_mode.max_height) {
if (conf_smaller_max_mode(&conf_mode, mode))
continue;
}
if (!mode || conf_mode.id != mode_id)
continue;
if (!usable.hdisplay && !usable.vdisplay)
usable = *mode;
conf_mode.width = mode->hdisplay;
conf_mode.height = mode->vdisplay;
break;
if (conf_mode.id == mode_id) {
conf_mode.width = mode->hdisplay;
conf_mode.height = mode->vdisplay;
break;
}
}
if (mode_id)
connector_usable ++;
/*
* If at least on mode is available, store smallest mode if it
* is larger than min_mode of other connectors
@ -145,8 +150,7 @@ static void preferred_mirror(struct drm_device const * const dev,
if (mode_id && mode_larger(&smallest, min_mode))
*min_mode = smallest;
if (conf_mode.mirror &&
conf_mode.force_width && conf_mode.force_height) {
if (conf_mode.force_width && conf_mode.force_height) {
/*
* Even so the force_* mode is selected, a configured mode for
@ -159,24 +163,23 @@ static void preferred_mirror(struct drm_device const * const dev,
}
/* enforce the force mode */
conf_mode.width = conf_mode.force_width;
conf_mode.height = conf_mode.force_height;
virtual->hdisplay = conf_mode.force_width;
virtual->vdisplay = conf_mode.force_height;
}
/* maximal resolution enforcement */
if (conf_mode.max_width && conf_mode.max_height) {
max_enforcement.hdisplay = conf_mode.max_width;
max_enforcement.vdisplay = conf_mode.max_height;
if (conf_smaller_max_mode(&conf_mode, prefer))
continue;
}
if (!conf_mode.width || !conf_mode.height || !conf_mode.mirror)
continue;
if (conf_larger_mode(&conf_mode, prefer)) {
prefer->hdisplay = conf_mode.width;
prefer->vdisplay = conf_mode.height;
/* compound calculation */
if (conf_mode.width && conf_mode.height) {
if (conf_mode.width > compound->hdisplay)
compound->hdisplay = conf_mode.width;
if (conf_mode.height > compound->vdisplay)
compound->vdisplay = conf_mode.height;
} else {
if (usable.hdisplay && usable.vdisplay) {
if (usable.hdisplay > compound->hdisplay)
compound->hdisplay = usable.hdisplay;
if (usable.vdisplay > compound->vdisplay)
compound->vdisplay = usable.vdisplay;
}
}
}
drm_connector_list_iter_end(&conn_iter);
@ -185,44 +188,9 @@ static void preferred_mirror(struct drm_device const * const dev,
if (!min_mode->hdisplay || !min_mode->vdisplay)
return;
/* we got a preferred resolution */
if (prefer->hdisplay && prefer->vdisplay)
return;
/* 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;
if (mode_larger(min_mode, mode))
continue;
if (max_enforcement.hdisplay && max_enforcement.vdisplay) {
if (mode_larger(mode, &max_enforcement))
continue;
}
if (conf_mode.enabled && !conf_mode.mirror)
continue;
if (mode_larger(mode, prefer)) {
prefer->hdisplay = mode->hdisplay;
prefer->vdisplay = mode->vdisplay;
}
}
}
drm_connector_list_iter_end(&conn_iter);
/* handle the "never should happen case" gracefully */
if (!prefer->hdisplay || !prefer->vdisplay)
*prefer = *min_mode;
/* if no mirrored connectors are enabled, compound is the minimal mode */
if (!compound->hdisplay || !compound->vdisplay)
*compound = *min_mode;
}
@ -492,6 +460,7 @@ 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;
drm_connector_list_iter_begin(dev->dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) {
@ -520,6 +489,10 @@ static void close_unused_captures(struct drm_client_dev * const dev)
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;
@ -527,6 +500,24 @@ static void close_unused_captures(struct drm_client_dev * const dev)
}
}
drm_connector_list_iter_end(&conn_iter);
for (unsigned i = 0; i < sizeof(mirrored) / sizeof(mirrored[0]); i++) {
if (!mirrored[i])
continue;
mirror_in_use = true;
break;
}
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;
kernel_register_fb(&fb_info, 0, 0);
}
}
@ -534,14 +525,15 @@ static bool reconfigure(struct drm_client_dev * const dev)
{
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 info = {};
bool retry = false;
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;
@ -557,34 +549,35 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (!dev || !dev->dev || !gem_mirror || !mirror_fb_cmd)
return false;
preferred_mirror(dev->dev, &mode_preferred, &mode_minimum);
mirror_heuristic(dev->dev, &mirror_force, &mirror_compound,
&mirror_minimum);
if (!mode_minimum.hdisplay || !mode_minimum.vdisplay) {
if (!mirror_minimum.hdisplay || !mirror_minimum.vdisplay) {
/* no valid modes on any connector on early boot */
if (!mirror_fb_cmd->fb_id)
return false;
/* valid connectors but all are disabled by config */
mode_minimum.hdisplay = mirror_fb_cmd->width;
mode_minimum.vdisplay = mirror_fb_cmd->height;
mode_preferred = mode_minimum;
mirror_minimum.hdisplay = mirror_fb_cmd->width;
mirror_minimum.vdisplay = mirror_fb_cmd->height;
mirror_compound = mirror_minimum;
}
if (mode_larger(&mode_preferred, &mode_minimum))
framebuffer = mode_preferred;
if (mode_larger(&mirror_compound, &mirror_minimum))
mirror_fb = mirror_compound;
else
framebuffer = mode_minimum;
mirror_fb = mirror_minimum;
{
int const err = check_resize_fb(dev,
gem_mirror,
mirror_fb_cmd,
framebuffer.hdisplay,
framebuffer.vdisplay);
mirror_fb.hdisplay,
mirror_fb.vdisplay);
if (err) {
printk("setting up framebuffer of %ux%u failed - error=%d\n",
framebuffer.hdisplay, framebuffer.vdisplay, err);
printk("setting up mirrored framebuffer of %ux%u failed - error=%d\n",
mirror_fb.hdisplay, mirror_fb.vdisplay, err);
return true;
}
@ -595,10 +588,10 @@ static bool reconfigure(struct drm_client_dev * const dev)
return retry;
/* 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;
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_client_for_each_modeset(mode_set, dev) {
struct drm_display_mode * mode_match = NULL;
@ -624,8 +617,8 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (!mode)
continue;
/* allocated framebuffer smaller than mode can't be used */
if (fb_smaller_mode(&info, mode))
/* 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 */
@ -634,13 +627,13 @@ static bool reconfigure(struct drm_client_dev * const dev)
continue;
mode_match = mode;
break;
}
/* if invalid, mode is configured in second loop below */
if (conf_mode.width == 0 || conf_mode.height == 0) {
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) ||
@ -659,6 +652,10 @@ static bool reconfigure(struct drm_client_dev * const dev)
mode_match = mode;
}
/* track whether connector is used mirrored or discrete */
if (connector->index < sizeof(mirrored) / sizeof(*mirrored))
mirrored[connector->index] = conf_mode.enabled && conf_mode.mirror;
/* apply new mode */
mode_id = 0;
list_for_each_entry(mode, &connector->modes, head) {
@ -690,7 +687,7 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (!mode_match) {
/* fb smaller than mode is denied by drm_mode_setcrtc */
if (fb_smaller_mode(&fb_info, mode))
if (conf_mode.enabled && fb_smaller_mode(&fb_info, mode))
continue;
/* use first smaller mode */
@ -723,7 +720,7 @@ static bool reconfigure(struct drm_client_dev * const dev)
mode, fb, connector);
}
/* convert kernel internal mode to user mode expectecd via ioctl */
/* 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 */
@ -742,12 +739,13 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (!retry)
retry = !!err;
if (!err && conf_mode.mirror && !mirror.report) {
if (!err && conf_mode.enabled && 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;
mirror.info.node = CONNECTOR_ID_MIRROR;
}
/* diagnostics */
@ -759,7 +757,7 @@ static bool reconfigure(struct drm_client_dev * const dev)
conf_mode.mirror ? " mirror " : " discrete",
mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
drm_mode_vrefresh(mode) < 100 ? " ": "",
framebuffer.hdisplay, framebuffer.vdisplay,
fb_info.var.xres, fb_info.var.yres,
(err || no_match) ? "" : "\n");
if (no_match)
@ -769,7 +767,7 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (err)
printk(" - failed, error=%d\n", err);
if (!err && !conf_mode.mirror)
if (!err && !conf_mode.mirror && conf_mode.enabled)
user_register_fb(dev, &fb_info, &fb_cmd,
mode->width_mm, mode->height_mm);
@ -841,6 +839,7 @@ static void mark_framebuffer_dirty(struct drm_framebuffer * const fb)
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);
@ -864,6 +863,7 @@ static int update_content(void *)
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) {
@ -873,20 +873,29 @@ static int update_content(void *)
int err = -1;
bool may_sleep = false;
bool const valid_id = connector->index < sizeof(unchanged) / sizeof(*unchanged);
unsigned index = connector->index;
if (connector->status != connector_status_connected)
continue;
if (valid_id)
unchanged[connector->index] ++;
else
printk("%s: connector id invalid %d\n", __func__, connector->index);
if (connector->index >= sizeof(unchanged) / sizeof(*unchanged)) {
printk("%s: connector id invalid %d\n", __func__, index);
index = CONNECTOR_ID_MIRROR; /* should never happen case */
}
if (valid_id)
may_sleep = unchanged[connector->index] >= ATTEMPTS_BEFORE_STOP;
if (mirrored[index] && mirror_run)
continue;
if (!lx_emul_i915_blit(connector->index, may_sleep)) {
if (mirrored[index]) {
mirror_run = true;
index = CONNECTOR_ID_MIRROR;
}
unchanged[index] ++;
may_sleep = unchanged[index] >= ATTEMPTS_BEFORE_STOP;
if (!lx_emul_i915_blit(index, may_sleep)) {
if (!may_sleep)
block_task = false;
@ -895,8 +904,7 @@ static int update_content(void *)
block_task = false;
if (valid_id)
unchanged[connector->index] = 0;
unchanged[index] = 0;
if (!connector->state || !connector->state->crtc)
continue;
@ -1112,8 +1120,8 @@ static int fb_client_hotplug(struct drm_client_dev *client)
}
/*
* (Re-)assign framebuffer to modeset (lost due to modeset_probe) and
* commit the change.
* (Re-)assign mirrored framebuffer to modeset (lost due to modeset_probe)
* and commit the change.
*/
if (fb_mirror) {
struct drm_framebuffer * free_fbs[MAX_FBS] = { };

View File

@ -345,6 +345,7 @@ void Framebuffer::Driver::generate_report()
lx_emul_i915_report_discrete(&xml);
xml.node("merge", [&] () {
xml.attribute("name", merge_label);
node.with_optional_sub_node("merge", [&](auto const &merge) {
with_force(merge, [&](unsigned width, unsigned height) {
xml.attribute("width", width);
@ -371,7 +372,15 @@ void Framebuffer::Driver::lookup_config(char const * const name,
mode.brightness = 70; /* percent */
mode.mirror = true;
if (!config.valid() || disable_all)
if (!config.valid())
return;
with_max_enforcement([&](unsigned const width, unsigned const height) {
mode.max_width = width;
mode.max_height = height;
});
if (disable_all)
return;
auto for_each_node = [&](auto const &node, bool const mirror){
@ -417,11 +426,6 @@ void Framebuffer::Driver::lookup_config(char const * const name,
mirror_node = true;
});
with_max_enforcement([&](unsigned const width, unsigned const height) {
mode.max_width = width;
mode.max_height = height;
});
}
@ -459,8 +463,6 @@ void lx_emul_i915_framebuffer_ready(unsigned const connector_id,
return;
new (drv.heap) Connector (env, drv.ids, id);
lx_emul_i915_wakeup(unsigned(id.value));
});
drv.ids.apply<Connector>(id, [&](Connector &conn) {
@ -468,8 +470,6 @@ void lx_emul_i915_framebuffer_ready(unsigned const connector_id,
Capture::Area area (xres, yres);
Capture::Area area_phys(phys_width, 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"
@ -479,22 +479,28 @@ void lx_emul_i915_framebuffer_ready(unsigned const connector_id,
bool const same = drv.update(conn, Genode::addr_t(base), area,
area_phys, { mm_width, mm_height}, label);
if (same)
if (same) {
lx_emul_i915_wakeup(unsigned(id.value));
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);
String<12> space { };
for (auto i = label.length(); i < space.capacity() - 1; i++) {
space = String<12>(" ", space);
}
if (conn.size.valid()) {
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 ");
}, [](){ /* unknown id */ });
}