intel/display: use one capture session per display

Fixes #5345
This commit is contained in:
Alexander Boettcher 2022-05-04 12:01:54 +02:00 committed by Christian Helmuth
parent e69c01aad3
commit 50cc52a091
8 changed files with 969 additions and 346 deletions

View File

@ -68,7 +68,12 @@ append config {
</default-route> </default-route>
<default caps="100"/> <default caps="100"/>
<start name="report_rom" caps="100"> <start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="report_rom" caps="100" priority="-1">
<resource name="RAM" quantum="2M"/> <resource name="RAM" quantum="2M"/>
<provides> <provides>
<service name="ROM" /> <service name="ROM" />
@ -87,7 +92,7 @@ append config {
</route> </route>
</start> </start>
<start name="acpi" caps="250"> <start name="acpi" caps="250" priority="-1">
<resource name="RAM" quantum="6M"/> <resource name="RAM" quantum="6M"/>
<route> <route>
<service name="IO_MEM"> <parent/> </service> <service name="IO_MEM"> <parent/> </service>
@ -100,7 +105,7 @@ append config {
</route> </route>
</start> </start>
<start name="pci_decode" caps="350"> <start name="pci_decode" caps="350" priority="-1">
<resource name="RAM" quantum="2M"/> <resource name="RAM" quantum="2M"/>
<route> <route>
<service name="Report"> <service name="Report">
@ -116,7 +121,7 @@ append config {
</route> </route>
</start> </start>
<start name="platform" caps="100" managing_system="yes"> <start name="platform" caps="100" managing_system="yes" priority="-1">
<resource name="RAM" quantum="2M"/> <resource name="RAM" quantum="2M"/>
<provides> <service name="Platform"/> </provides> <provides> <service name="Platform"/> </provides>
<route> <route>
@ -143,14 +148,20 @@ append config {
<pci class="USB"/> <pci class="USB"/>
</policy> </policy>
</config> </config>
</start> </start>}
<start name="timer"> append_if $use_top config {
<resource name="RAM" quantum="1M"/> <start name="top" priority="-1">
<provides><service name="Timer"/></provides> <resource name="RAM" quantum="2M"/>
</start> <config period_ms="40000"/>
<route>
<service name="TRACE"> <parent label=""/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>}
<start name="init_dynamic" caps="10000"> append config {
<start name="init_dynamic" caps="10000" priority="-2">
<binary name="init"/> <binary name="init"/>
<resource name="RAM" quantum="1000M"/> <resource name="RAM" quantum="1000M"/>
<route> <route>
@ -181,16 +192,6 @@ append config {
<default caps="100"/> <default caps="100"/>
<report init_ram="yes" child_ram="yes" delay_ms="10000"/>} <report init_ram="yes" child_ram="yes" delay_ms="10000"/>}
append_if $use_top config {
<start name="top">
<resource name="RAM" quantum="2M"/>
<config period_ms="40000"/>
<route>
<service name="TRACE"> <parent label=""/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>}
append config { append config {
<start name="report_rom" priority="-1"> <start name="report_rom" priority="-1">
<resource name="RAM" quantum="2M"/> <resource name="RAM" quantum="2M"/>
@ -219,6 +220,13 @@ append config {
<inline name="fb.config"> <inline name="fb.config">
<config ld_verbose="yes" apply_on_hotplug="} $apply_on_hotplug {"> <config ld_verbose="yes" apply_on_hotplug="} $apply_on_hotplug {">
<report connectors="yes"/> <report connectors="yes"/>
<merge name="mirror">
<!-- all connectors in merge node gets mirrored -->
<!--
<connector name="DP-1"/>
<connector name="HDMI-A-1"/>
-->
</merge>
</config> </config>
</inline> </inline>
</import> </import>
@ -266,7 +274,7 @@ append_if $use_gpu config {
append config { append config {
<start name="intel_fb" caps="1000"> <start name="intel_fb" caps="1000">
<binary name="pc_intel_fb"/> <binary name="pc_intel_fb"/>
<resource name="RAM" quantum="90M"/> <resource name="RAM" quantum="128M"/>
<route>} <route>}
append_if $use_gpu config { append_if $use_gpu config {
@ -276,6 +284,18 @@ append config {
<service name="ROM" label="config"> <service name="ROM" label="config">
<child name="config_rom" label="fb.config"/> </service> <child name="config_rom" label="fb.config"/> </service>
<service name="Report"> <child name="report_rom"/> </service> <service name="Report"> <child name="report_rom"/> </service>
<service name="Capture" label="eDP-1"> <child name="test-framebuffer-eDP-1"/> </service>
<service name="Capture" label="HDMI-A-1"> <child name="test-framebuffer-HDMI-A-1"/> </service>
<service name="Capture" label="HDMI-A-2"> <child name="test-framebuffer-HDMI-A-2"/> </service>
<service name="Capture" label="HDMI-A-3"> <child name="test-framebuffer-HDMI-A-3"/> </service>
<service name="Capture" label="DP-1"> <child name="test-framebuffer-DP-1"/> </service>
<service name="Capture" label="DP-2"> <child name="test-framebuffer-DP-2"/> </service>
<service name="Capture" label="DP-3"> <child name="test-framebuffer-DP-3"/> </service>
<service name="Capture" label="DP-4"> <child name="test-framebuffer-DP-4"/> </service>
<service name="Capture" label="VGA-1"> <child name="test-framebuffer-VGA-1"/> </service>
<service name="Capture" label="mirror"> <child name="test-framebuffer-mirror"/> </service>
<any-service> <parent/> <any-child /> </any-service> <any-service> <parent/> <any-child /> </any-service>
</route> </route>
</start>} </start>}
@ -309,7 +329,62 @@ append_if $use_fb_controller config {
</start>} </start>}
append config { append config {
<start name="test-framebuffer" priority="-1"> <start name="test-framebuffer-eDP-1" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-HDMI-A-1" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-HDMI-A-2" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-HDMI-A-3" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-DP-1" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-DP-2" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-DP-3" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-DP-4" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-VGA-1" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides>
<config/>
</start>
<start name="test-framebuffer-mirror" priority="-1">
<binary name="test-framebuffer"/>
<resource name="RAM" quantum="10M"/> <resource name="RAM" quantum="10M"/>
<provides> <service name="Capture"/> </provides> <provides> <service name="Capture"/> </provides>
<config/> <config/>

View File

@ -3,62 +3,22 @@ This driver is for Intel i915 compatible graphic cards.
Default behaviour Default behaviour
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
When no configuration is provided to the driver, it will switch on all devices When no configuration is provided to the driver, it will enable all connectors
connected to the graphics card. It will use the highest resolution as with attached displays and allocate for each display a discrete framebuffer.
provided by the BIOS or EDID information from the display devices for each It will use the highest resolution as provided by the BIOS or EDID information.
connector. The virtual resolution delivered to the client is the maximum in For each connector a separate Capture connection will be requested labeled
width and height of the active connectors. 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 By default, on hotplug of a display, the current config of the driver will be
are enabled, probed, and again the highest resolution across all active re-parsed and re-applied. This behaviour can be disabled by
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
! <config apply_on_hotplug="no"/> ! <config apply_on_hotplug="no"/>
Configuration 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:
! <config>
! <connector name="eDP-1" width="1920" height="1080" hz="60" brightness="75" enabled="true"/>
! <connector name="DP-1" mode_id="2" enabled="true"/>
! </config>
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:
! <config max_width="2560" max_height="1440">
! </config>
The virtual resolution can be enforced by:
! <config force_width="1024" force_height="768">
! </config>
The amount of memory used by the driver for the accounting of its available
buffer space is set by:
! <config max_framebuffer_memory="64M">
! </config>
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, 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 the driver is able to deliver a corresponding report, which can be enabled
in the configuration as follows: 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 be used as input to DPI calculations. The currently used mode of the connector
is tagged in the report explicitly. 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:
! <config>
! <connector name="eDP-1" enabled="true" width="1920" height="1080" hz="60" brightness="75"/>
! <connector name="DP-1" enabled="true" mode_id="2"/>
! </config>
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:
! <config max_width="2560" max_height="1440">
! </config>
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:
! <config max_framebuffer_memory="64M">
! </config>
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.
! <connectors>
!
! <connector name="eDP-1" ...> <!-- discrete, not mirrored -->
! ...
! </connector>
! <connector name="DP-1" ...> <!.. discrete, not mirrored -->
! ...
! </connector>
!
! <merge name="mirror">
! <connector name="HDMI-A-1" ...>
! ...
! </connector>
! <connector name="VGA--1" ...>
! ...
! </connector>
! </merge>
! </connectors>
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:
! <merge name="mirror" width="1024" height="768">
! ...
! </merge>
Thereby, the driver will open a Genode capture session to the
GUI multiplexer with this limited dimension.

View File

@ -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 <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <lx_emul/fb.h>
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;
}

View File

@ -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_ */

View File

@ -29,11 +29,13 @@ struct genode_mode {
unsigned preferred; unsigned preferred;
unsigned inuse; unsigned inuse;
unsigned id; unsigned id;
char mirror;
char name[32]; char name[32];
}; };
int lx_emul_i915_blit(void); int lx_emul_i915_blit(unsigned);
void lx_emul_i915_report(void * genode_xml); 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_hotplug_connector(void);
void lx_emul_i915_report_connector(void * lx_data, void * genode_xml, void lx_emul_i915_report_connector(void * lx_data, void * genode_xml,
char const *name, char connected, 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_report_modes(void * genode_xml, struct genode_mode *);
void lx_emul_i915_connector_config(char * name, struct genode_mode *); void lx_emul_i915_connector_config(char * name, struct genode_mode *);
int lx_emul_i915_config_done_and_block(void); 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_ */ #endif /* _LX_I915_H_ */

View File

@ -28,14 +28,15 @@
enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 }; enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 };
struct task_struct * lx_update_task = NULL;
struct task_struct * lx_user_task = 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 struct drm_client_dev * dev_client = NULL;
static int user_register_fb(struct drm_client_dev const * const dev, static int user_register_fb(struct drm_client_dev const * const dev,
struct fb_info * const info, 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, static int user_attach_fb_to_crtc(struct drm_client_dev * const dev,
@ -85,9 +86,9 @@ 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, static void preferred_mirror(struct drm_device const * const dev,
struct drm_display_mode * const prefer, struct drm_display_mode * const prefer,
struct drm_display_mode * const min_mode) struct drm_display_mode * const min_mode)
{ {
@ -101,7 +102,7 @@ static void preferred_mode(struct drm_device const * const dev,
drm_connector_list_iter_begin(dev, &conn_iter); drm_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) { drm_client_for_each_connector_iter(connector, &conn_iter) {
struct drm_display_mode smallest = { .hdisplay = ~0, .vdisplay = ~0 }; 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; unsigned mode_id = 0;
/* check for connector configuration on Genode side */ /* 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)) if (mode_id && mode_larger(&smallest, min_mode))
*min_mode = smallest; *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 * Even so the force_* mode is selected, a configured mode for
* a connector is considered, effectively the framebuffer content * a connector is considered, effectively the framebuffer content
@ -168,7 +171,7 @@ static void preferred_mode(struct drm_device const * const dev,
continue; continue;
} }
if (!conf_mode.width || !conf_mode.height) if (!conf_mode.width || !conf_mode.height || !conf_mode.mirror)
continue; continue;
if (conf_larger_mode(&conf_mode, prefer)) { 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 */ /* if too large or nothing configured by Genode's config */
drm_connector_list_iter_begin(dev, &conn_iter); drm_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &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) { list_for_each_entry(mode, &connector->modes, head) {
if (!mode) if (!mode)
continue; continue;
@ -201,6 +209,9 @@ static void preferred_mode(struct drm_device const * const dev,
continue; continue;
} }
if (conf_mode.enabled && !conf_mode.mirror)
continue;
if (mode_larger(mode, prefer)) { if (mode_larger(mode, prefer)) {
prefer->hdisplay = mode->hdisplay; prefer->hdisplay = mode->hdisplay;
prefer->vdisplay = mode->vdisplay; prefer->vdisplay = mode->vdisplay;
@ -249,11 +260,279 @@ static unsigned get_brightness(struct drm_connector * const connector,
return ret * MAX_BRIGHTNESS / panel->backlight.device->props.max_brightness; 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 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_preferred = {};
struct drm_display_mode mode_minimum = {}; struct drm_display_mode mode_minimum = {};
@ -261,23 +540,33 @@ static bool reconfigure(struct drm_client_dev * const dev)
struct drm_mode_modeinfo user_mode = {}; struct drm_mode_modeinfo user_mode = {};
struct drm_display_mode * mode = NULL; struct drm_display_mode * mode = NULL;
struct drm_mode_set * mode_set = NULL; struct drm_mode_set * mode_set = NULL;
struct fb_info report_fb_info = {}; struct fb_info info = {};
bool report_fb = false;
bool retry = false; 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; 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) { if (!mode_minimum.hdisplay || !mode_minimum.vdisplay) {
/* no valid modes on any connector on early boot */ /* no valid modes on any connector on early boot */
if (!dumb_fb.fb_id) if (!mirror_fb_cmd->fb_id)
return false; return false;
/* valid connectors but all are disabled by config */ /* valid connectors but all are disabled by config */
mode_minimum.hdisplay = dumb_fb.width; mode_minimum.hdisplay = mirror_fb_cmd->width;
mode_minimum.vdisplay = dumb_fb.height; mode_minimum.vdisplay = mirror_fb_cmd->height;
mode_preferred = mode_minimum; mode_preferred = mode_minimum;
} }
@ -288,8 +577,8 @@ static bool reconfigure(struct drm_client_dev * const dev)
{ {
int const err = check_resize_fb(dev, int const err = check_resize_fb(dev,
&gem_dumb, gem_mirror,
&dumb_fb, mirror_fb_cmd,
framebuffer.hdisplay, framebuffer.hdisplay,
framebuffer.vdisplay); framebuffer.vdisplay);
@ -302,20 +591,19 @@ static bool reconfigure(struct drm_client_dev * const dev)
} }
/* without fb handle created by check_resize_fb we can't proceed */ /* 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; return retry;
/* prepare fb info for register_framebuffer() evaluated by Genode side */ /* prepare fb info for kernel_register_fb() evaluated by Genode side */
report_fb_info.var.xres = framebuffer.hdisplay; info.var.xres = framebuffer.hdisplay;
report_fb_info.var.yres = framebuffer.vdisplay; info.var.yres = framebuffer.vdisplay;
report_fb_info.var.xres_virtual = mode_preferred.hdisplay; info.var.xres_virtual = mode_preferred.hdisplay;
report_fb_info.var.yres_virtual = mode_preferred.vdisplay; info.var.yres_virtual = mode_preferred.vdisplay;
drm_client_for_each_modeset(mode_set, dev) { drm_client_for_each_modeset(mode_set, dev) {
struct drm_display_mode * mode_match = NULL; struct drm_display_mode * mode_match = NULL;
unsigned mode_id = 0; unsigned mode_id = 0;
struct drm_connector * connector = NULL; struct drm_connector * connector = NULL;
struct genode_mode conf_mode = {}; struct genode_mode conf_mode = {};
if (!mode_set->connectors || !*mode_set->connectors) if (!mode_set->connectors || !*mode_set->connectors)
@ -337,7 +625,7 @@ static bool reconfigure(struct drm_client_dev * const dev)
continue; continue;
/* allocated framebuffer smaller than mode can't be used */ /* allocated framebuffer smaller than mode can't be used */
if (fb_smaller_mode(&report_fb_info, mode)) if (fb_smaller_mode(&info, mode))
continue; continue;
/* use mode id if configured and matches exactly */ /* use mode id if configured and matches exactly */
@ -374,19 +662,35 @@ static bool reconfigure(struct drm_client_dev * const dev)
/* apply new mode */ /* apply new mode */
mode_id = 0; mode_id = 0;
list_for_each_entry(mode, &connector->modes, head) { list_for_each_entry(mode, &connector->modes, head) {
struct fb_info fb_info = info;
int err = -1; int err = -1;
bool no_match = false; bool no_match = false;
struct drm_mode_fb_cmd2 fb_cmd = *mirror_fb_cmd;
mode_id ++; mode_id ++;
if (!mode) if (!mode)
continue; 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 ? */ /* no matching mode ? */
if (!mode_match) { if (!mode_match) {
/* fb smaller than mode is denied by drm_mode_setcrtc */ /* 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; continue;
/* use first smaller mode */ /* use first smaller mode */
@ -399,19 +703,34 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (mode_match != mode) if (mode_match != mode)
continue; 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 */ /* convert kernel internal mode to user mode expectecd via ioctl */
drm_mode_convert_to_umode(&user_mode, mode); drm_mode_convert_to_umode(&user_mode, mode);
/* assign fb & connector to crtc with specified mode */ /* assign fb & connector to crtc with specified mode */
err = user_attach_fb_to_crtc(dev, connector, mode_set->crtc, 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); conf_mode.enabled);
if (err)
retry = true;
else
report_fb = true;
/* set brightness */ /* set brightness */
if (!err && conf_mode.enabled && conf_mode.brightness <= MAX_BRIGHTNESS) { if (!err && conf_mode.enabled && conf_mode.brightness <= MAX_BRIGHTNESS) {
drm_modeset_lock(&dev->dev->mode_config.connection_mutex, NULL); 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); 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 */ /* 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", connector->name ? connector->name : "unnamed",
conf_mode.enabled ? " enable" : "disable", conf_mode.enabled ? " enable" : "disable",
mode->name ? mode->name : "noname", mode->name ? mode->name : "noname",
mode_id, mode_id < 10 ? " " : "", mode->hdisplay, mode_id, mode_id < 10 ? " " : "",
mode->vdisplay, drm_mode_vrefresh(mode), conf_mode.mirror ? " mirror " : " discrete",
mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
drm_mode_vrefresh(mode) < 100 ? " ": "", drm_mode_vrefresh(mode) < 100 ? " ": "",
framebuffer.hdisplay, framebuffer.vdisplay, framebuffer.hdisplay, framebuffer.vdisplay,
(err || no_match) ? "" : "\n"); (err || no_match) ? "" : "\n");
@ -438,12 +769,21 @@ static bool reconfigure(struct drm_client_dev * const dev)
if (err) if (err)
printk(" - failed, error=%d\n", 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; break;
} }
} }
if (report_fb) if (mirror.report) {
user_register_fb(dev, &report_fb_info, &dumb_fb); 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; 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_clip_rect *clips = NULL;
struct drm_mode_fb_dirty_cmd r = { }; struct drm_mode_fb_dirty_cmd r = { };
@ -484,12 +823,9 @@ void framebuffer_dirty(void)
int num_clips = 0; int num_clips = 0;
int ret = 0; int ret = 0;
if (!dev_client || !dumb_fb.fb_id) if (!dev_client)
return; return;
fb = drm_framebuffer_lookup(dev_client->dev, dev_client->file,
dumb_fb.fb_id);
if (!fb || !fb->funcs || !fb->funcs->dirty) if (!fb || !fb->funcs || !fb->funcs->dirty)
return; return;
@ -504,9 +840,37 @@ void framebuffer_dirty(void)
static int update_content(void *) static int update_content(void *)
{ {
while (true) { 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()) drm_connector_list_iter_begin(dev, &conn_iter);
framebuffer_dirty(); 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 */ /* schedule_timeout(jiffes) or hrtimer or msleep */
msleep(20); msleep(20);
@ -523,12 +887,42 @@ void lx_user_init(void)
int pid2 = kernel_thread(update_content, NULL, "lx_update", int pid2 = kernel_thread(update_content, NULL, "lx_update",
CLONE_FS | CLONE_FILES); CLONE_FS | CLONE_FILES);
lx_user_task = find_task_by_pid_ns(pid, NULL);; lx_user_task = find_task_by_pid_ns(pid , NULL);
lx_update_task = find_task_by_pid_ns(pid2, 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; 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_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &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, lx_emul_i915_report_connector(connector, genode_data,
connector->name, connector->name,
connector->status != connector_status_disconnected, 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) void lx_emul_i915_iterate_modes(void * lx_data, void * genode_data)
{ {
struct drm_connector *connector = lx_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 && bool const inuse = connector->state && connector->state->crtc &&
connector->state->crtc->state && connector->state->crtc->state &&
drm_mode_equal(&connector->state->crtc->state->mode, mode); 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 = { struct genode_mode conf_mode = {
.width = mode->hdisplay, .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 | .preferred = mode->type & (DRM_MODE_TYPE_PREFERRED |
DRM_MODE_TYPE_DEFAULT), DRM_MODE_TYPE_DEFAULT),
.inuse = inuse, .inuse = inuse,
.mirror = mirror,
.hz = drm_mode_vrefresh(mode), .hz = drm_mode_vrefresh(mode),
.id = mode_id, .id = mode_id,
.enabled = !max_mode || .enabled = !max_mode ||
@ -623,11 +1048,12 @@ 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 *client)
{ {
struct drm_mode_set *modeset = NULL; struct drm_mode_set *modeset = NULL;
struct drm_framebuffer *fb = NULL; struct drm_framebuffer *fb_mirror = NULL;
int result = -EINVAL; int result = -EINVAL;
if (dumb_fb.fb_id) if (mirror_fb_cmd && mirror_fb_cmd->fb_id)
fb = drm_framebuffer_lookup(client->dev, client->file, dumb_fb.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 * 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 * (Re-)assign framebuffer to modeset (lost due to modeset_probe) and
* commit the change. * commit the change.
*/ */
if (fb) { if (fb_mirror) {
bool mode_too_large = false; 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); mutex_lock(&client->modeset_mutex);
drm_client_for_each_modeset(modeset, client) { 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; 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->hdisplay > fb->width ||
modeset->mode->vdisplay > fb->height)) modeset->mode->vdisplay > fb->height))
mode_too_large = true; 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); 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 */ /* triggers disablement of encoders attached to disconnected ports */
result = drm_client_modeset_commit(client); result = drm_client_modeset_commit(client);
@ -673,8 +1152,8 @@ static int fb_client_hotplug(struct drm_client_dev *client)
/* notify Genode side */ /* notify Genode side */
lx_emul_i915_hotplug_connector(); lx_emul_i915_hotplug_connector();
if (fb) if (fb_mirror)
drm_framebuffer_put(fb); drm_framebuffer_put(fb_mirror);
return 0; 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, static int user_register_fb(struct drm_client_dev const * const dev,
struct fb_info * const info, 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; intel_wakeref_t wakeref;
int result = -EINVAL; int result = -EINVAL;
struct i915_gtt_view const view = { .type = I915_GTT_VIEW_NORMAL }; 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; void __iomem *vaddr = NULL;
struct gem_dumb *gem_dumb = NULL;
struct drm_i915_private *dev_priv = to_i915(dev->dev); struct drm_i915_private *dev_priv = to_i915(dev->dev);
struct drm_framebuffer *fb = drm_framebuffer_lookup(dev->dev, struct drm_framebuffer *fb = drm_framebuffer_lookup(dev->dev,
dev->file, dev->file,
@ -774,49 +1254,61 @@ static int user_register_fb(struct drm_client_dev const * const dev,
return -ENODEV; return -ENODEV;
} }
wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); if (!dumb_gem(dev, fb, &gem_dumb) || !gem_dumb) {
printk("%s:%u error looking up fb and vma\n", __func__, __LINE__);
if (vma) { return -ENODEV;
intel_unpin_fb_vma(vma, flags);
vma = NULL;
flags = 0;
} }
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. /* Pin the GGTT vma for our access via info->screen_base.
* This also validates that any existing fb inherited from the * This also validates that any existing fb inherited from the
* BIOS is suitable for own access. * BIOS is suitable for own access.
*/ */
vma = intel_pin_and_fence_fb_obj(fb, false /* phys_cursor */, gem_dumb->vma = intel_pin_and_fence_fb_obj(fb, false /* phys_cursor */,
&view, false /* use fences */, &view, false /* use fences */,
&flags); &gem_dumb->flags);
if (IS_ERR(vma)) { if (IS_ERR(gem_dumb->vma)) {
intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref);
result = PTR_ERR(vma); result = PTR_ERR(gem_dumb->vma);
printk("%s:%u error setting vma %d\n", __func__, __LINE__, result); printk("%s:%u error setting vma %d\n", __func__, __LINE__, result);
gem_dumb->vma = NULL;
return result; return result;
} }
vaddr = i915_vma_pin_iomap(vma); vaddr = i915_vma_pin_iomap(gem_dumb->vma);
if (IS_ERR(vaddr)) { if (IS_ERR(vaddr)) {
intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref);
result = PTR_ERR(vaddr); result = PTR_ERR(vaddr);
printk("%s:%u error pin iomap %d\n", __func__, __LINE__, result); 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; return result;
} }
/* fill framebuffer info for register_framebuffer */ /* fill framebuffer info for kernel_register_fb */
info->screen_base = vaddr; info->screen_base = vaddr;
info->screen_size = vma->size; info->screen_size = gem_dumb->vma->size;
info->fix.line_length = fb->pitches[0]; info->fix.line_length = fb->pitches[0];
info->var.bits_per_pixel = drm_format_info_bpp(fb->format, 0); info->var.bits_per_pixel = drm_format_info_bpp(fb->format, 0);
intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref);
register_framebuffer(info); kernel_register_fb(info, width_mm, height_mm);
if (fb) if (fb)
drm_framebuffer_put(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 && if (gem_dumb->width && gem_dumb->height &&
(gem_dumb->width < width || gem_dumb->height < height)) { (gem_dumb->width < width || gem_dumb->height < height)) {
result = drm_mode_rmfb(dev->dev, dumb_fb->fb_id, dev->file); destroy_fb(dev, gem_dumb, dumb_fb);
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));
} }
/* allocate dumb framebuffer, on success a GEM object handle is returned */ /* allocate dumb framebuffer, on success a GEM object handle is returned */

View File

@ -20,7 +20,6 @@
/* emulation includes */ /* emulation includes */
#include <lx_emul/init.h> #include <lx_emul/init.h>
#include <lx_emul/fb.h>
#include <lx_emul/task.h> #include <lx_emul/task.h>
#include <lx_kit/env.h> #include <lx_kit/env.h>
#include <lx_kit/init.h> #include <lx_kit/init.h>
@ -45,6 +44,7 @@ struct Framebuffer::Driver
using Attached_rom_system = Constructible<Attached_rom_dataspace>; using Attached_rom_system = Constructible<Attached_rom_dataspace>;
Env &env; Env &env;
Heap heap { env.ram(), env.rm() };
Attached_rom_dataspace config { env, "config" }; Attached_rom_dataspace config { env, "config" };
Attached_rom_system system { }; Attached_rom_system system { };
Expanding_reporter reporter { env, "connectors", "connectors" }; Expanding_reporter reporter { env, "connectors", "connectors" };
@ -61,67 +61,88 @@ struct Framebuffer::Driver
bool disable_all { false }; bool disable_all { false };
bool disable_report_once { false }; bool disable_report_once { false };
class Fb Capture::Connection::Label merge_label { "mirror" };
{
private:
Capture::Connection _capture; struct Connector {
Capture::Area const _size; using Space = Id_space<Connector>;
Capture::Area const _size_phys; using Id = Space::Id;
Capture::Connection::Screen _captured_screen;
void * _base;
/* Space::Element id_element;
* Non_copyable
*/
Fb(const Fb&);
Fb & operator=(const Fb&);
public: Connector(Space &space, Id id) : id_element(*this, space, id) { }
bool paint() addr_t base { };
Capture::Area size { };
Capture::Area size_phys { };
Capture::Area size_mm { };
Constructible<Capture::Connection> capture { };
Constructible<Capture::Connection::Screen> screen { };
};
Connector::Space ids { };
bool capture(Connector::Space &ids, Connector::Id const &id)
{ {
using Pixel = Capture::Pixel; using Pixel = Capture::Pixel;
bool dirty = false; bool dirty = false;
_captured_screen.with_texture([&] (Texture<Pixel> const &texture) { ids.apply<Connector>(id, [&](Connector &connector) {
auto const affected = _capture.capture_at(Capture::Point(0, 0)); if (!connector.capture.constructed() ||
!connector.screen.constructed())
return;
connector.screen->with_texture([&] (Texture<Pixel> const &texture) {
auto const affected = connector.capture->capture_at({ 0, 0});
affected.for_each_rect([&] (Capture::Rect const rect) { affected.for_each_rect([&] (Capture::Rect const rect) {
Surface<Pixel> surface((Pixel*)_base, _size_phys); Surface<Pixel> surface((Pixel*)connector.base,
connector.size_phys);
surface.clip(rect); surface.clip(rect);
Blit_painter::paint(surface, texture, Capture::Point(0, 0)); Blit_painter::paint(surface, texture, Capture::Point(0, 0));
dirty = true; dirty = true;
}); });
}); });
}, [&](){ /* unknown connector id */ });
return dirty; return dirty;
} }
Fb(Env & env, void * base, Capture::Area size, bool update(Connector &conn, addr_t const base,
Capture::Area size_phys) Capture::Area &size, Capture::Area &size_phys,
: Capture::Area const mm,
_capture(env), auto const &label)
_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) && bool same = (base == conn.base) &&
(size_phys == _size_phys)); (size == conn.size) &&
} (size_phys == conn.size_phys) &&
}; (mm == conn.size_mm);
Constructible<Fb> fb {}; 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 config_update();
void system_update(); void system_update();
@ -178,8 +199,7 @@ struct Framebuffer::Driver
return apply_config; return apply_config;
} }
template <typename T> void with_max_enforcement(auto const &fn) const
void with_max_enforcement(T const &fn) const
{ {
unsigned max_width = config.xml().attribute_value("max_width", 0u); unsigned max_width = config.xml().attribute_value("max_width", 0u);
unsigned max_height = config.xml().attribute_value("max_height",0u); unsigned max_height = config.xml().attribute_value("max_height",0u);
@ -188,11 +208,10 @@ struct Framebuffer::Driver
fn(max_width, max_height); fn(max_width, max_height);
} }
template <typename T> void with_force(auto const &node, auto const &fn) const
void with_force(T const &fn) const
{ {
unsigned force_width = config.xml().attribute_value("force_width", 0u); unsigned force_width = node.attribute_value("width", 0u);
unsigned force_height = config.xml().attribute_value("force_height", 0u); unsigned force_height = node.attribute_value("height", 0u);
if (force_width && force_height) if (force_width && force_height)
fn(force_width, force_height); fn(force_width, force_height);
@ -247,6 +266,11 @@ void Framebuffer::Driver::config_update()
if (!config.valid() || !lx_user_task) if (!config.valid() || !lx_user_task)
return; 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)) { if (config.xml().attribute_value("system", false)) {
system.construct(Lx_kit::env().env, "system"); system.construct(Lx_kit::env().env, "system");
system->sigh(system_handler); system->sigh(system_handler);
@ -310,12 +334,18 @@ void Framebuffer::Driver::generate_report()
xml.attribute("max_height", height); xml.attribute("max_height", height);
}); });
with_force([&](unsigned width, unsigned height) { lx_emul_i915_report_discrete(&xml);
xml.attribute("force_width", width);
xml.attribute("force_height", height); 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(&xml); lx_emul_i915_report_non_discrete(&xml);
});
}); });
}); });
@ -326,38 +356,60 @@ void Framebuffer::Driver::generate_report()
void Framebuffer::Driver::lookup_config(char const * const name, void Framebuffer::Driver::lookup_config(char const * const name,
struct genode_mode &mode) struct genode_mode &mode)
{ {
bool mirror_node = false;
/* default settings, possibly overridden by explicit configuration below */ /* default settings, possibly overridden by explicit configuration below */
mode.enabled = !disable_all; mode.enabled = !disable_all;
mode.brightness = 70 /* percent */; mode.brightness = 70; /* percent */
mode.mirror = false;
if (!config.valid() || disable_all) if (!config.valid() || disable_all)
return; return;
/* iterate independently of force* ever to get brightness and hz */ auto for_each_node = [&](auto const &node, bool const mirror){
config.xml().for_each_sub_node("connector", [&] (Xml_node &node) {
using Name = String<32>; using Name = String<32>;
Name const con_policy = node.attribute_value("name", Name()); Name const con_policy = node.attribute_value("name", Name());
if (con_policy != name) if (con_policy != name)
return; return;
mode.mirror = mirror;
mode.enabled = node.attribute_value("enabled", true); mode.enabled = node.attribute_value("enabled", true);
if (!mode.enabled) if (!mode.enabled)
return; return;
mode.brightness = node.attribute_value("brightness",
unsigned(MAX_BRIGHTNESS + 1));
mode.width = node.attribute_value("width" , 0U); mode.width = node.attribute_value("width" , 0U);
mode.height = node.attribute_value("height" , 0U); mode.height = node.attribute_value("height" , 0U);
mode.hz = node.attribute_value("hz" , 0U); mode.hz = node.attribute_value("hz" , 0U);
mode.id = node.attribute_value("mode_id", 0U); mode.id = node.attribute_value("mode_id", 0U);
mode.brightness = node.attribute_value("brightness",
unsigned(MAX_BRIGHTNESS + 1));
};
/* 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) { /* 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_width = width;
mode.force_height = height; mode.force_height = height;
}); });
mirror_node = true;
});
with_max_enforcement([&](unsigned const width, unsigned const height) { with_max_enforcement([&](unsigned const width, unsigned const height) {
mode.max_width = width; mode.max_width = width;
mode.max_height = height; mode.max_height = height;
@ -372,41 +424,72 @@ unsigned long long driver_max_framebuffer_memory(void)
} }
/** void lx_emul_i915_framebuffer_ready(unsigned const connector_id,
* Can be called already as side-effect of `lx_emul_start_kernel`, char const * const conn_name,
* that's why the Driver object needs to be constructed already here. void * const base,
*/ unsigned long,
extern "C" void lx_emul_framebuffer_ready(void * base, unsigned long, unsigned const xres,
unsigned xres, unsigned yres, unsigned const yres,
unsigned phys_width, unsigned const phys_width,
unsigned phys_height) unsigned const phys_height,
unsigned const mm_width,
unsigned const mm_height)
{ {
auto &env = Lx_kit::env().env; auto &env = Lx_kit::env().env;
auto &drv = driver(env); auto &drv = driver(env);
auto &fb = drv.fb;
using namespace Genode;
typedef Framebuffer::Driver::Connector Connector;
auto const id = Connector::Id { connector_id };
/* allocate new id for new connector */
drv.ids.apply<Connector>(id, [&](Connector &) { /* known id */ }, [&](){
/* ignore unused connector - don't need a object for it */
if (!base)
return;
new (drv.heap) Connector (drv.ids, id);
});
drv.ids.apply<Connector>(id, [&](Connector &conn) {
Capture::Area area (xres, yres); Capture::Area area (xres, yres);
Capture::Area area_phys(phys_width, phys_height); Capture::Area area_phys(phys_width, phys_height);
if (fb.constructed()) { auto const prev_size = conn.size;
if (fb->same_setup(base, area, area_phys))
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; return;
fb.destruct();
}
/* clear artefacts */ /* clear artefacts */
if (area != area_phys) if (base && (area != area_phys))
Genode::memset(base, 0, area_phys.count() * 4); Genode::memset(base, 0, area_phys.count() * 4);
fb.construct(env, base, area, area_phys); if (conn.size.valid())
log("setup - framebuffer ",
Genode::log("framebuffer reconstructed - virtual=", xres, "x", yres, " - connector id=", conn.id_element.id().value,
" physical=", phys_width, "x", phys_height); ", 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; Genode::Env &env = Lx_kit::env().env;
driver(env).generate_report(); driver(env).generate_report();
@ -422,8 +505,8 @@ void lx_emul_i915_report_connector(void * lx_data, void * genode_xml,
xml.node("connector", [&] () xml.node("connector", [&] ()
{ {
xml.attribute("name", name);
xml.attribute("connected", !!connected); xml.attribute("connected", !!connected);
xml.attribute("name", name);
if (width_mm) if (width_mm)
xml.attribute("width_mm" , width_mm); xml.attribute("width_mm" , width_mm);
if (height_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); auto &drv = driver(Lx_kit::env().env);
if (!drv.fb.constructed()) auto const id = Framebuffer::Driver::Connector::Id { connector_id };
return false;
return drv.fb->paint(); return drv.capture(drv.ids, id);
} }

View File

@ -15,7 +15,6 @@ SRC_C += dummies.c
SRC_C += pci.c SRC_C += pci.c
SRC_C += lx_emul.c SRC_C += lx_emul.c
SRC_C += $(notdir $(wildcard $(REL_PRG_DIR)/generated_dummies.c)) SRC_C += $(notdir $(wildcard $(REL_PRG_DIR)/generated_dummies.c))
SRC_C += fb.c
SRC_C += lx_user.c SRC_C += lx_user.c
SRC_C += gem.c SRC_C += gem.c
SRC_C += lx_emul/common_dummies.c SRC_C += lx_emul/common_dummies.c