As part of moving the modesetting code out of drm_fb_helper and into drm_client, the drm_fb_helper_funcs->initial_config callback needs to go. Replace it with a drm_driver->initial_client_display callback that can work for all in-kernel clients.
TODO: - Add a patch that moves the function out of intel_fbdev.c since it's not fbdev specific anymore.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_fb_helper.c | 19 +++++-- drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_drv.h | 11 ++++ drivers/gpu/drm/i915/intel_fbdev.c | 113 ++++++++++++++++++------------------- include/drm/drm_drv.h | 21 +++++++ 5 files changed, 104 insertions(+), 61 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index b992f59dad30..5407bf6dc8c0 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -2103,6 +2103,20 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, /* prevent concurrent modification of connector_count by hotplug */ lockdep_assert_held(&fb_helper->lock);
+ mutex_lock(&dev->mode_config.mutex); + if (drm_fb_helper_probe_connector_modes(fb_helper, width, height) == 0) + DRM_DEBUG_KMS("No connectors reported connected with modes\n"); + + if (dev->driver->initial_client_display) { + display = dev->driver->initial_client_display(dev, width, height); + if (display) { + drm_client_display_free(fb_helper->display); + fb_helper->display = display; + mutex_unlock(&dev->mode_config.mutex); + return; + } + } + crtcs = kcalloc(fb_helper->connector_count, sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); modes = kcalloc(fb_helper->connector_count, @@ -2120,9 +2134,6 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, if (IS_ERR(display)) goto out;
- mutex_lock(&fb_helper->dev->mode_config.mutex); - if (drm_fb_helper_probe_connector_modes(fb_helper, width, height) == 0) - DRM_DEBUG_KMS("No connectors reported connected with modes\n"); drm_enable_connectors(fb_helper, enabled);
if (!(fb_helper->funcs->initial_config && @@ -2144,7 +2155,6 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height); } - mutex_unlock(&fb_helper->dev->mode_config.mutex);
/* need to set the modesets up here for use later */ /* fill out the connector<->crtc mappings into the modesets */ @@ -2182,6 +2192,7 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, drm_client_display_free(fb_helper->display); fb_helper->display = display; out: + mutex_unlock(&dev->mode_config.mutex); kfree(crtcs); kfree(modes); kfree(offsets); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 07c07d55398b..b746c0cbaa4b 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -2857,6 +2857,7 @@ static struct drm_driver driver = {
.dumb_create = i915_gem_dumb_create, .dumb_map_offset = i915_gem_mmap_gtt, + .initial_client_display = i915_initial_client_display, .ioctls = i915_ioctls, .num_ioctls = ARRAY_SIZE(i915_ioctls), .fops = &i915_driver_fops, diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index d4368589b355..f77f510617c5 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -1720,6 +1720,9 @@ extern void intel_fbdev_fini(struct drm_i915_private *dev_priv); extern void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous); extern void intel_fbdev_output_poll_changed(struct drm_device *dev); extern void intel_fbdev_restore_mode(struct drm_device *dev); +struct drm_client_display * +i915_initial_client_display(struct drm_device *dev, unsigned int width, + unsigned int height); #else static inline int intel_fbdev_init(struct drm_device *dev) { @@ -1749,6 +1752,14 @@ static inline void intel_fbdev_output_poll_changed(struct drm_device *dev) static inline void intel_fbdev_restore_mode(struct drm_device *dev) { } + +static inline struct drm_client_display * +i915_initial_client_display(struct drm_device *dev, unsigned int width, + unsigned int height) +{ + return NULL; +} + #endif
/* intel_fbc.c */ diff --git a/drivers/gpu/drm/i915/intel_fbdev.c b/drivers/gpu/drm/i915/intel_fbdev.c index a4ab8575a72e..b7f44c9475a8 100644 --- a/drivers/gpu/drm/i915/intel_fbdev.c +++ b/drivers/gpu/drm/i915/intel_fbdev.c @@ -38,6 +38,7 @@ #include <linux/vga_switcheroo.h>
#include <drm/drmP.h> +#include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_fb_helper.h> #include "intel_drv.h" @@ -287,18 +288,6 @@ static int intelfb_create(struct drm_fb_helper *helper, return ret; }
-static struct drm_fb_helper_crtc * -intel_fb_helper_crtc(struct drm_fb_helper *fb_helper, struct drm_crtc *crtc) -{ - int i; - - for (i = 0; i < fb_helper->crtc_count; i++) - if (fb_helper->crtc_info[i].mode_set.crtc == crtc) - return &fb_helper->crtc_info[i]; - - return NULL; -} - /* * Try to read the BIOS display configuration and use it for the initial * fb configuration. @@ -326,44 +315,48 @@ intel_fb_helper_crtc(struct drm_fb_helper *fb_helper, struct drm_crtc *crtc) * is in VGA mode we need to recalculate watermarks and set a new high-res * framebuffer anyway. */ -static bool intel_fb_initial_config(struct drm_fb_helper *fb_helper, - struct drm_fb_helper_crtc **crtcs, - struct drm_display_mode **modes, - struct drm_fb_offset *offsets, - bool *enabled, int width, int height) +struct drm_client_display * +i915_initial_client_display(struct drm_device *dev, unsigned int width, + unsigned int height) { - struct drm_i915_private *dev_priv = to_i915(fb_helper->dev); + struct drm_i915_private *dev_priv = to_i915(dev); unsigned long conn_configured, conn_seq, mask; - unsigned int count = min(fb_helper->connector_count, BITS_PER_LONG); - int i, j; - bool *save_enabled; - bool fallback = true, ret = true; + bool fallback = true, *enabled = NULL; + struct drm_client_display *display; + struct drm_connector **connectors; + int i, connector_count; + unsigned int count; int num_connectors_enabled = 0; int num_connectors_detected = 0; struct drm_modeset_acquire_ctx ctx;
- save_enabled = kcalloc(count, sizeof(bool), GFP_KERNEL); - if (!save_enabled) - return false; + display = drm_client_display_create(dev); + if (IS_ERR(display)) + return NULL;
drm_modeset_acquire_init(&ctx, 0);
- while (drm_modeset_lock_all_ctx(fb_helper->dev, &ctx) != 0) + while (drm_modeset_lock_all_ctx(dev, &ctx) != 0) drm_modeset_backoff(&ctx);
- memcpy(save_enabled, enabled, count); + connector_count = drm_connector_get_all(dev, &connectors); + if (connector_count < 1) + goto bail; + + enabled = drm_connector_get_enabled_status(connectors, connector_count); + if (!enabled) + goto bail; + + count = min(connector_count, BITS_PER_LONG); mask = GENMASK(count - 1, 0); conn_configured = 0; retry: conn_seq = conn_configured; for (i = 0; i < count; i++) { - struct drm_fb_helper_connector *fb_conn; - struct drm_connector *connector; + struct drm_connector *connector = connectors[i]; + struct drm_display_mode *mode; + struct drm_mode_set *modeset; struct drm_encoder *encoder; - struct drm_fb_helper_crtc *new_crtc; - - fb_conn = fb_helper->connector_info[i]; - connector = fb_conn->connector;
if (conn_configured & BIT(i)) continue; @@ -402,16 +395,13 @@ static bool intel_fb_initial_config(struct drm_fb_helper *fb_helper,
num_connectors_enabled++;
- new_crtc = intel_fb_helper_crtc(fb_helper, - connector->state->crtc); - /* * Make sure we're not trying to drive multiple connectors * with a single CRTC, since our cloning support may not * match the BIOS. */ - for (j = 0; j < count; j++) { - if (crtcs[j] == new_crtc) { + drm_client_display_for_each_modeset(modeset, display) { + if (modeset->connectors[0] == connector) { DRM_DEBUG_KMS("fallback: cloned configuration\n"); goto bail; } @@ -421,28 +411,26 @@ static bool intel_fb_initial_config(struct drm_fb_helper *fb_helper, connector->name);
/* go for command line mode first */ - modes[i] = drm_connector_pick_cmdline_mode(connector); + mode = drm_connector_pick_cmdline_mode(connector);
/* try for preferred next */ - if (!modes[i]) { + if (!mode) { DRM_DEBUG_KMS("looking for preferred mode on connector %s %d\n", connector->name, connector->has_tile); - modes[i] = drm_connector_has_preferred_mode(connector, - width, - height); + mode = drm_connector_has_preferred_mode(connector, + width, height); }
/* No preferred mode marked by the EDID? Are there any modes? */ - if (!modes[i] && !list_empty(&connector->modes)) { + if (!mode && !list_empty(&connector->modes)) { DRM_DEBUG_KMS("using first mode listed on connector %s\n", connector->name); - modes[i] = list_first_entry(&connector->modes, - struct drm_display_mode, - head); + mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); }
/* last resort: use current mode */ - if (!modes[i]) { + if (!mode) { /* * IMPORTANT: We want to use the adjusted mode (i.e. * after the panel fitter upscaling) as the initial @@ -458,16 +446,26 @@ static bool intel_fb_initial_config(struct drm_fb_helper *fb_helper, */ DRM_DEBUG_KMS("looking for current mode on connector %s\n", connector->name); - modes[i] = &connector->state->crtc->mode; + mode = &connector->state->crtc->mode; } - crtcs[i] = new_crtc; + + modeset = drm_client_display_find_modeset(display, connector->state->crtc); + if (WARN_ON(!modeset)) + goto bail; + + modeset->mode = drm_mode_duplicate(dev, mode); + drm_connector_get(connector); + modeset->connectors[0] = connector; + modeset->num_connectors = 1; + modeset->x = 0; + modeset->y = 0;
DRM_DEBUG_KMS("connector %s on [CRTC:%d:%s]: %dx%d%s\n", connector->name, connector->state->crtc->base.id, connector->state->crtc->name, - modes[i]->hdisplay, modes[i]->vdisplay, - modes[i]->flags & DRM_MODE_FLAG_INTERLACE ? "i" :""); + mode->hdisplay, mode->vdisplay, + mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "");
fallback = false; conn_configured |= BIT(i); @@ -492,19 +490,20 @@ static bool intel_fb_initial_config(struct drm_fb_helper *fb_helper, if (fallback) { bail: DRM_DEBUG_KMS("Not using firmware configuration\n"); - memcpy(enabled, save_enabled, count); - ret = false; + drm_client_display_free(display); + display = NULL; }
drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx);
- kfree(save_enabled); - return ret; + drm_connector_put_all(connectors, connector_count); + kfree(enabled); + + return display; }
static const struct drm_fb_helper_funcs intel_fb_helper_funcs = { - .initial_config = intel_fb_initial_config, .fb_probe = intelfb_create, };
diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index 7e545f5f94d3..13356e6fd40c 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -32,6 +32,7 @@
#include <drm/drm_device.h>
+struct drm_client_display; struct drm_file; struct drm_gem_object; struct drm_master; @@ -553,6 +554,26 @@ struct drm_driver { struct drm_device *dev, uint32_t handle);
+ /** + * @initial_client_config: + * + * Driver callback to setup an initial fbdev display configuration. + * Drivers can use this callback to tell the fbdev emulation what the + * preferred initial configuration is. This is useful to implement + * smooth booting where the fbdev (and subsequently all userspace) never + * changes the mode, but always inherits the existing configuration. + * + * This callback is optional. + * + * RETURNS: + * + * The driver should return true if a suitable initial configuration has + * been filled out and false when the fbdev helper should fall back to + * the default probing logic. + */ + struct drm_client_display *(*initial_client_display)(struct drm_device *dev, + unsigned int width, unsigned int height); + /** * @gem_vm_ops: Driver private ops for this object */
The stage is now set for a clean removal of drm_fb_helper_crtc. struct drm_client_display is doing its job now.
Also remove the drm_fb_helper_funcs->initial_config which has been superseded by drm_driver->initial_client_display.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_fb_helper.c | 124 +++++++++------------------------------- include/drm/drm_fb_helper.h | 31 ---------- 2 files changed, 26 insertions(+), 129 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 5407bf6dc8c0..ce38eadcb346 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -443,24 +443,6 @@ int drm_fb_helper_blank(int blank, struct fb_info *info) } EXPORT_SYMBOL(drm_fb_helper_blank);
-static void drm_fb_helper_modeset_release(struct drm_fb_helper *helper, - struct drm_mode_set *modeset) -{ - int i; - - for (i = 0; i < modeset->num_connectors; i++) { - drm_connector_put(modeset->connectors[i]); - modeset->connectors[i] = NULL; - } - modeset->num_connectors = 0; - - drm_mode_destroy(helper->dev, modeset->mode); - modeset->mode = NULL; - - /* FIXME should hold a ref? */ - modeset->fb = NULL; -} - static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper) { int i; @@ -470,14 +452,6 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper) kfree(helper->connector_info[i]); } kfree(helper->connector_info); - - for (i = 0; i < helper->crtc_count; i++) { - struct drm_mode_set *modeset = &helper->crtc_info[i].mode_set; - - drm_fb_helper_modeset_release(helper, modeset); - kfree(modeset->connectors); - } - kfree(helper->crtc_info); }
static void drm_fb_helper_resume_worker(struct work_struct *work) @@ -552,48 +526,18 @@ int drm_fb_helper_init(struct drm_device *dev, struct drm_fb_helper *fb_helper, int max_conn_count) { - struct drm_crtc *crtc; - struct drm_mode_config *config = &dev->mode_config; - int i; - if (!drm_fbdev_emulation) { dev->fb_helper = fb_helper; return 0; }
- if (!max_conn_count) - return -EINVAL; - - fb_helper->crtc_info = kcalloc(config->num_crtc, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL); - if (!fb_helper->crtc_info) - return -ENOMEM; - - fb_helper->crtc_count = config->num_crtc; fb_helper->connector_info = kcalloc(dev->mode_config.num_connector, sizeof(struct drm_fb_helper_connector *), GFP_KERNEL); - if (!fb_helper->connector_info) { - kfree(fb_helper->crtc_info); + if (!fb_helper->connector_info) return -ENOMEM; - } + fb_helper->connector_info_alloc_count = dev->mode_config.num_connector; fb_helper->connector_count = 0;
- for (i = 0; i < fb_helper->crtc_count; i++) { - fb_helper->crtc_info[i].mode_set.connectors = - kcalloc(max_conn_count, - sizeof(struct drm_connector *), - GFP_KERNEL); - - if (!fb_helper->crtc_info[i].mode_set.connectors) - goto out_free; - fb_helper->crtc_info[i].mode_set.num_connectors = 0; - } - - i = 0; - drm_for_each_crtc(crtc, dev) { - fb_helper->crtc_info[i].mode_set.crtc = crtc; - i++; - } - fb_helper->display = drm_client_display_create(dev); if (IS_ERR(fb_helper->display)) goto out_free; @@ -1830,7 +1774,7 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, struct drm_display_mode *dmt_mode, *mode;
/* only contemplate cloning in the single crtc case */ - if (fb_helper->crtc_count > 1) + if (fb_helper->dev->mode_config.num_crtc > 1) return false;
count = 0; @@ -1997,16 +1941,18 @@ static bool drm_target_preferred(struct drm_fb_helper *fb_helper, }
static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, - struct drm_fb_helper_crtc **best_crtcs, + struct drm_crtc **best_crtcs, struct drm_display_mode **modes, int n, int width, int height) { - int c, o; + struct drm_client_display *display = fb_helper->display; + struct drm_device *dev = display->dev; + int o, my_score, best_score, score; struct drm_connector *connector; const struct drm_connector_helper_funcs *connector_funcs; + struct drm_mode_set *modeset; struct drm_encoder *encoder; - int my_score, best_score, score; - struct drm_fb_helper_crtc **crtcs, *crtc; + struct drm_crtc **crtcs; struct drm_fb_helper_connector *fb_helper_conn;
if (n == fb_helper->connector_count) @@ -2020,8 +1966,7 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, if (modes[n] == NULL) return best_score;
- crtcs = kcalloc(fb_helper->connector_count, - sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); + crtcs = kcalloc(fb_helper->connector_count, sizeof(*crtcs), GFP_KERNEL); if (!crtcs) return best_score;
@@ -2053,10 +1998,10 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, * select a crtc for this connector and then attempt to configure * remaining connectors */ - for (c = 0; c < fb_helper->crtc_count; c++) { - crtc = &fb_helper->crtc_info[c]; + drm_client_display_for_each_modeset(modeset, display) { + struct drm_crtc *crtc = modeset->crtc;
- if ((encoder->possible_crtcs & (1 << c)) == 0) + if ((encoder->possible_crtcs & drm_crtc_mask(crtc)) == 0) continue;
for (o = 0; o < n; o++) @@ -2065,7 +2010,7 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
if (o < n) { /* ignore cloning unless only a single crtc */ - if (fb_helper->crtc_count > 1) + if (dev->mode_config.num_crtc > 1) continue;
if (!drm_mode_equal(modes[o], modes[n])) @@ -2073,14 +2018,13 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, }
crtcs[n] = crtc; - memcpy(crtcs, best_crtcs, n * sizeof(struct drm_fb_helper_crtc *)); + memcpy(crtcs, best_crtcs, n * sizeof(*crtcs)); score = my_score + drm_pick_crtcs(fb_helper, crtcs, modes, n + 1, width, height); if (score > best_score) { best_score = score; memcpy(best_crtcs, crtcs, - fb_helper->connector_count * - sizeof(struct drm_fb_helper_crtc *)); + fb_helper->connector_count * sizeof(*crtcs)); } } out: @@ -2093,9 +2037,9 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, { struct drm_device *dev = fb_helper->dev; struct drm_client_display *display; - struct drm_fb_helper_crtc **crtcs; struct drm_display_mode **modes; struct drm_fb_offset *offsets; + struct drm_crtc **crtcs; bool *enabled; int i;
@@ -2117,8 +2061,7 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, } }
- crtcs = kcalloc(fb_helper->connector_count, - sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); + crtcs = kcalloc(fb_helper->connector_count, sizeof(*crtcs), GFP_KERNEL); modes = kcalloc(fb_helper->connector_count, sizeof(struct drm_display_mode *), GFP_KERNEL); offsets = kcalloc(fb_helper->connector_count, @@ -2136,43 +2079,28 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
drm_enable_connectors(fb_helper, enabled);
- if (!(fb_helper->funcs->initial_config && - fb_helper->funcs->initial_config(fb_helper, crtcs, modes, - offsets, - enabled, width, height))) { - memset(modes, 0, fb_helper->connector_count*sizeof(modes[0])); - memset(crtcs, 0, fb_helper->connector_count*sizeof(crtcs[0])); - memset(offsets, 0, fb_helper->connector_count*sizeof(offsets[0])); + if (!drm_target_cloned(fb_helper, modes, offsets, enabled, width, height) && + !drm_target_preferred(fb_helper, modes, offsets, enabled, width, height)) + DRM_ERROR("Unable to find initial modes\n");
- if (!drm_target_cloned(fb_helper, modes, offsets, - enabled, width, height) && - !drm_target_preferred(fb_helper, modes, offsets, - enabled, width, height)) - DRM_ERROR("Unable to find initial modes\n"); + DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height);
- DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", - width, height); - - drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height); - } + drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height);
/* need to set the modesets up here for use later */ /* fill out the connector<->crtc mappings into the modesets */ - for (i = 0; i < fb_helper->crtc_count; i++) - drm_fb_helper_modeset_release(fb_helper, - &fb_helper->crtc_info[i].mode_set);
drm_fb_helper_for_each_connector(fb_helper, i) { struct drm_display_mode *mode = modes[i]; - struct drm_fb_helper_crtc *fb_crtc = crtcs[i]; + struct drm_crtc *crtc = crtcs[i]; struct drm_fb_offset *offset = &offsets[i];
- if (mode && fb_crtc) { + if (mode && crtc) { struct drm_connector *connector = fb_helper->connector_info[i]->connector; struct drm_mode_set *modeset;
- modeset = drm_client_display_find_modeset(display, fb_crtc->mode_set.crtc); + modeset = drm_client_display_find_modeset(display, crtc); if (WARN_ON(!modeset)) { drm_client_display_free(display); goto out; diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index f379ef6d6085..408931f7613f 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -45,12 +45,6 @@ struct drm_fb_offset { int x, y; };
-struct drm_fb_helper_crtc { - struct drm_mode_set mode_set; - struct drm_display_mode *desired_mode; - int x, y; -}; - /** * struct drm_fb_helper_surface_size - describes fbdev size and scanout surface size * @fb_width: fbdev width @@ -101,29 +95,6 @@ struct drm_fb_helper_funcs { */ int (*fb_probe)(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes); - - /** - * @initial_config: - * - * Driver callback to setup an initial fbdev display configuration. - * Drivers can use this callback to tell the fbdev emulation what the - * preferred initial configuration is. This is useful to implement - * smooth booting where the fbdev (and subsequently all userspace) never - * changes the mode, but always inherits the existing configuration. - * - * This callback is optional. - * - * RETURNS: - * - * The driver should return true if a suitable initial configuration has - * been filled out and false when the fbdev helper should fall back to - * the default probing logic. - */ - bool (*initial_config)(struct drm_fb_helper *fb_helper, - struct drm_fb_helper_crtc **crtcs, - struct drm_display_mode **modes, - struct drm_fb_offset *offsets, - bool *enabled, int width, int height); };
struct drm_fb_helper_connector { @@ -163,8 +134,6 @@ struct drm_fb_helper { */ struct drm_client_display *display;
- int crtc_count; - struct drm_fb_helper_crtc *crtc_info; int connector_count; int connector_info_alloc_count; /**
No need to maintain a list of registered connectors. Just use the connector iterator.
TODO: Remove: - drm_fb_helper_add_one_connector() - drm_fb_helper_single_add_all_connectors() - drm_fb_helper_remove_one_connector()
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_fb_helper.c | 359 ++++++++++------------------------------ include/drm/drm_fb_helper.h | 13 -- 2 files changed, 86 insertions(+), 286 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index ce38eadcb346..6ee61f195321 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -112,59 +112,10 @@ static DEFINE_MUTEX(kernel_fb_helper_lock); * deferred I/O (coupled with drm_fb_helper_fbdev_teardown()). */
-#define drm_fb_helper_for_each_connector(fbh, i__) \ - for (({ lockdep_assert_held(&(fbh)->lock); }), \ - i__ = 0; i__ < (fbh)->connector_count; i__++) - -static int __drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, - struct drm_connector *connector) -{ - struct drm_fb_helper_connector *fb_conn; - struct drm_fb_helper_connector **temp; - unsigned int count; - - if (!drm_fbdev_emulation) - return 0; - - lockdep_assert_held(&fb_helper->lock); - - count = fb_helper->connector_count + 1; - - if (count > fb_helper->connector_info_alloc_count) { - size_t size = count * sizeof(fb_conn); - - temp = krealloc(fb_helper->connector_info, size, GFP_KERNEL); - if (!temp) - return -ENOMEM; - - fb_helper->connector_info_alloc_count = count; - fb_helper->connector_info = temp; - } - - fb_conn = kzalloc(sizeof(*fb_conn), GFP_KERNEL); - if (!fb_conn) - return -ENOMEM; - - drm_connector_get(connector); - fb_conn->connector = connector; - fb_helper->connector_info[fb_helper->connector_count++] = fb_conn; - - return 0; -} - int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_connector *connector) { - int err; - - if (!fb_helper) - return 0; - - mutex_lock(&fb_helper->lock); - err = __drm_fb_helper_add_one_connector(fb_helper, connector); - mutex_unlock(&fb_helper->lock); - - return err; + return 0; } EXPORT_SYMBOL(drm_fb_helper_add_one_connector);
@@ -184,87 +135,14 @@ EXPORT_SYMBOL(drm_fb_helper_add_one_connector); */ int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper) { - struct drm_device *dev; - struct drm_connector *connector; - struct drm_connector_list_iter conn_iter; - int i, ret = 0; - - if (!drm_fbdev_emulation || !fb_helper) - return 0; - - dev = fb_helper->dev; - - mutex_lock(&fb_helper->lock); - drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - ret = __drm_fb_helper_add_one_connector(fb_helper, connector); - if (ret) - goto fail; - } - goto out; - -fail: - drm_fb_helper_for_each_connector(fb_helper, i) { - struct drm_fb_helper_connector *fb_helper_connector = - fb_helper->connector_info[i]; - - drm_connector_put(fb_helper_connector->connector); - - kfree(fb_helper_connector); - fb_helper->connector_info[i] = NULL; - } - fb_helper->connector_count = 0; -out: - drm_connector_list_iter_end(&conn_iter); - mutex_unlock(&fb_helper->lock); - - return ret; -} -EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors); - -static int __drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper, - struct drm_connector *connector) -{ - struct drm_fb_helper_connector *fb_helper_connector; - int i, j; - - if (!drm_fbdev_emulation) - return 0; - - lockdep_assert_held(&fb_helper->lock); - - drm_fb_helper_for_each_connector(fb_helper, i) { - if (fb_helper->connector_info[i]->connector == connector) - break; - } - - if (i == fb_helper->connector_count) - return -EINVAL; - fb_helper_connector = fb_helper->connector_info[i]; - drm_connector_put(fb_helper_connector->connector); - - for (j = i + 1; j < fb_helper->connector_count; j++) - fb_helper->connector_info[j - 1] = fb_helper->connector_info[j]; - - fb_helper->connector_count--; - kfree(fb_helper_connector); - return 0; } +EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors);
int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper, struct drm_connector *connector) { - int err; - - if (!fb_helper) - return 0; - - mutex_lock(&fb_helper->lock); - err = __drm_fb_helper_remove_one_connector(fb_helper, connector); - mutex_unlock(&fb_helper->lock); - - return err; + return 0; } EXPORT_SYMBOL(drm_fb_helper_remove_one_connector);
@@ -443,17 +321,6 @@ int drm_fb_helper_blank(int blank, struct fb_info *info) } EXPORT_SYMBOL(drm_fb_helper_blank);
-static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper) -{ - int i; - - for (i = 0; i < helper->connector_count; i++) { - drm_connector_put(helper->connector_info[i]->connector); - kfree(helper->connector_info[i]); - } - kfree(helper->connector_info); -} - static void drm_fb_helper_resume_worker(struct work_struct *work) { struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper, @@ -531,23 +398,13 @@ int drm_fb_helper_init(struct drm_device *dev, return 0; }
- fb_helper->connector_info = kcalloc(dev->mode_config.num_connector, sizeof(struct drm_fb_helper_connector *), GFP_KERNEL); - if (!fb_helper->connector_info) - return -ENOMEM; - - fb_helper->connector_info_alloc_count = dev->mode_config.num_connector; - fb_helper->connector_count = 0; - fb_helper->display = drm_client_display_create(dev); if (IS_ERR(fb_helper->display)) - goto out_free; + return PTR_ERR(fb_helper->display);
dev->fb_helper = fb_helper;
return 0; -out_free: - drm_fb_helper_crtc_free(fb_helper); - return -ENOMEM; } EXPORT_SYMBOL(drm_fb_helper_init);
@@ -651,8 +508,6 @@ void drm_fb_helper_fini(struct drm_fb_helper *fb_helper)
mutex_destroy(&fb_helper->lock); drm_client_display_free(fb_helper->display); - drm_fb_helper_crtc_free(fb_helper); - } EXPORT_SYMBOL(drm_fb_helper_fini);
@@ -1474,8 +1329,9 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, { int ret = 0; int crtc_count = 0; - int i; + struct drm_connector_list_iter conn_iter; struct drm_fb_helper_surface_size sizes; + struct drm_connector *connector; struct drm_mode_set *mode_set; int gamma_size = 0;
@@ -1490,11 +1346,9 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, sizes.surface_depth = sizes.surface_bpp = preferred_bpp;
/* first up get a count of crtcs now in use and new min/maxes width/heights */ - drm_fb_helper_for_each_connector(fb_helper, i) { - struct drm_fb_helper_connector *fb_helper_conn = fb_helper->connector_info[i]; - struct drm_cmdline_mode *cmdline_mode; - - cmdline_mode = &fb_helper_conn->connector->cmdline_mode; + drm_connector_list_iter_begin(fb_helper->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct drm_cmdline_mode *cmdline_mode = &connector->cmdline_mode;
if (cmdline_mode->bpp_specified) { switch (cmdline_mode->bpp) { @@ -1519,6 +1373,7 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, break; } } + drm_connector_list_iter_end(&conn_iter);
crtc_count = 0; drm_client_display_for_each_modeset(mode_set, fb_helper->display) { @@ -1707,70 +1562,28 @@ static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper, uint32_t maxX, uint32_t maxY) { + struct drm_connector_list_iter conn_iter; struct drm_connector *connector; - int i, count = 0; + int count = 0;
- drm_fb_helper_for_each_connector(fb_helper, i) { - connector = fb_helper->connector_info[i]->connector; + drm_connector_list_iter_begin(fb_helper->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { count += connector->funcs->fill_modes(connector, maxX, maxY); } + drm_connector_list_iter_end(&conn_iter);
return count; }
-static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector) -{ - return fb_connector->connector->cmdline_mode.specified; -} - -static bool drm_connector_enabled(struct drm_connector *connector, bool strict) -{ - bool enable; - - if (connector->display_info.non_desktop) - return false; - - if (strict) - enable = connector->status == connector_status_connected; - else - enable = connector->status != connector_status_disconnected; - - return enable; -} - -static void drm_enable_connectors(struct drm_fb_helper *fb_helper, - bool *enabled) -{ - bool any_enabled = false; - struct drm_connector *connector; - int i = 0; - - drm_fb_helper_for_each_connector(fb_helper, i) { - connector = fb_helper->connector_info[i]->connector; - enabled[i] = drm_connector_enabled(connector, true); - DRM_DEBUG_KMS("connector %d enabled? %s\n", connector->base.id, - connector->display_info.non_desktop ? "non desktop" : enabled[i] ? "yes" : "no"); - - any_enabled |= enabled[i]; - } - - if (any_enabled) - return; - - drm_fb_helper_for_each_connector(fb_helper, i) { - connector = fb_helper->connector_info[i]->connector; - enabled[i] = drm_connector_enabled(connector, false); - } -} - static bool drm_target_cloned(struct drm_fb_helper *fb_helper, + struct drm_connector **connectors, + unsigned int connector_count, struct drm_display_mode **modes, struct drm_fb_offset *offsets, bool *enabled, int width, int height) { int count, i, j; bool can_clone = false; - struct drm_fb_helper_connector *fb_helper_conn; struct drm_display_mode *dmt_mode, *mode;
/* only contemplate cloning in the single crtc case */ @@ -1778,7 +1591,7 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, return false;
count = 0; - drm_fb_helper_for_each_connector(fb_helper, i) { + for (i = 0; i < connector_count; i++) { if (enabled[i]) count++; } @@ -1789,11 +1602,10 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper,
/* check the command line or if nothing common pick 1024x768 */ can_clone = true; - drm_fb_helper_for_each_connector(fb_helper, i) { + for (i = 0; i < connector_count; i++) { if (!enabled[i]) continue; - fb_helper_conn = fb_helper->connector_info[i]; - modes[i] = drm_connector_pick_cmdline_mode(fb_helper_conn->connector); + modes[i] = drm_connector_pick_cmdline_mode(connectors[i]); if (!modes[i]) { can_clone = false; break; @@ -1815,12 +1627,11 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, can_clone = true; dmt_mode = drm_mode_find_dmt(fb_helper->dev, 1024, 768, 60, false);
- drm_fb_helper_for_each_connector(fb_helper, i) { + for (i = 0; i < connector_count; i++) { if (!enabled[i]) continue;
- fb_helper_conn = fb_helper->connector_info[i]; - list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) { + list_for_each_entry(mode, &connectors[i]->modes, head) { if (drm_mode_equal(mode, dmt_mode)) modes[i] = mode; } @@ -1836,30 +1647,31 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, return false; }
-static int drm_get_tile_offsets(struct drm_fb_helper *fb_helper, +static int drm_get_tile_offsets(struct drm_connector **connectors, + unsigned int connector_count, struct drm_display_mode **modes, struct drm_fb_offset *offsets, int idx, int h_idx, int v_idx) { - struct drm_fb_helper_connector *fb_helper_conn; int i; int hoffset = 0, voffset = 0;
- drm_fb_helper_for_each_connector(fb_helper, i) { - fb_helper_conn = fb_helper->connector_info[i]; - if (!fb_helper_conn->connector->has_tile) + for (i = 0; i < connector_count; i++) { + struct drm_connector *connector = connectors[i]; + + if (!connector->has_tile) continue;
if (!modes[i] && (h_idx || v_idx)) { DRM_DEBUG_KMS("no modes for connector tiled %d %d\n", i, - fb_helper_conn->connector->base.id); + connector->base.id); continue; } - if (fb_helper_conn->connector->tile_h_loc < h_idx) + if (connector->tile_h_loc < h_idx) hoffset += modes[i]->hdisplay;
- if (fb_helper_conn->connector->tile_v_loc < v_idx) + if (connector->tile_v_loc < v_idx) voffset += modes[i]->vdisplay; } offsets[idx].x = hoffset; @@ -1868,20 +1680,20 @@ static int drm_get_tile_offsets(struct drm_fb_helper *fb_helper, return 0; }
-static bool drm_target_preferred(struct drm_fb_helper *fb_helper, +static bool drm_target_preferred(struct drm_connector **connectors, + unsigned int connector_count, struct drm_display_mode **modes, struct drm_fb_offset *offsets, bool *enabled, int width, int height) { - struct drm_fb_helper_connector *fb_helper_conn; - const u64 mask = BIT_ULL(fb_helper->connector_count) - 1; + const u64 mask = BIT_ULL(connector_count) - 1; u64 conn_configured = 0; int tile_pass = 0; int i;
retry: - drm_fb_helper_for_each_connector(fb_helper, i) { - fb_helper_conn = fb_helper->connector_info[i]; + for (i = 0; i < connector_count; i++) { + struct drm_connector *connector = connectors[i];
if (conn_configured & BIT_ULL(i)) continue; @@ -1892,17 +1704,17 @@ static bool drm_target_preferred(struct drm_fb_helper *fb_helper, }
/* first pass over all the untiled connectors */ - if (tile_pass == 0 && fb_helper_conn->connector->has_tile) + if (tile_pass == 0 && connector->has_tile) continue;
if (tile_pass == 1) { - if (fb_helper_conn->connector->tile_h_loc != 0 || - fb_helper_conn->connector->tile_v_loc != 0) + if (connector->tile_h_loc != 0 || + connector->tile_v_loc != 0) continue;
} else { - if (fb_helper_conn->connector->tile_h_loc != tile_pass - 1 && - fb_helper_conn->connector->tile_v_loc != tile_pass - 1) + if (connector->tile_h_loc != tile_pass - 1 && + connector->tile_v_loc != tile_pass - 1) /* if this tile_pass doesn't cover any of the tiles - keep going */ continue;
@@ -1910,24 +1722,23 @@ static bool drm_target_preferred(struct drm_fb_helper *fb_helper, * find the tile offsets for this pass - need to find * all tiles left and above */ - drm_get_tile_offsets(fb_helper, modes, offsets, - i, fb_helper_conn->connector->tile_h_loc, fb_helper_conn->connector->tile_v_loc); + drm_get_tile_offsets(connectors, connector_count, modes, offsets, + i, connector->tile_h_loc, connector->tile_v_loc); } DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n", - fb_helper_conn->connector->base.id); + connector->base.id);
/* got for command line mode first */ - modes[i] = drm_connector_pick_cmdline_mode(fb_helper_conn->connector); + modes[i] = drm_connector_pick_cmdline_mode(connector); if (!modes[i]) { DRM_DEBUG_KMS("looking for preferred mode on connector %d %d\n", - fb_helper_conn->connector->base.id, fb_helper_conn->connector->tile_group ? fb_helper_conn->connector->tile_group->id : 0); - modes[i] = drm_connector_has_preferred_mode(fb_helper_conn->connector, width, height); + connector->base.id, connector->tile_group ? connector->tile_group->id : 0); + modes[i] = drm_connector_has_preferred_mode(connector, width, height); } /* No preferred modes, pick one off the list */ - if (!modes[i] && !list_empty(&fb_helper_conn->connector->modes)) { - list_for_each_entry(modes[i], &fb_helper_conn->connector->modes, head) - break; - } + if (!modes[i]) + modes[i] = list_first_entry_or_null(&connector->modes, struct drm_display_mode, head); + DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name : "none"); conn_configured |= BIT_ULL(i); @@ -1940,12 +1751,13 @@ static bool drm_target_preferred(struct drm_fb_helper *fb_helper, return true; }
-static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, +static int drm_pick_crtcs(struct drm_client_display *display, + struct drm_connector **connectors, + unsigned int connector_count, struct drm_crtc **best_crtcs, struct drm_display_mode **modes, int n, int width, int height) { - struct drm_client_display *display = fb_helper->display; struct drm_device *dev = display->dev; int o, my_score, best_score, score; struct drm_connector *connector; @@ -1953,27 +1765,26 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, struct drm_mode_set *modeset; struct drm_encoder *encoder; struct drm_crtc **crtcs; - struct drm_fb_helper_connector *fb_helper_conn;
- if (n == fb_helper->connector_count) + if (n == connector_count) return 0;
- fb_helper_conn = fb_helper->connector_info[n]; - connector = fb_helper_conn->connector; + connector = connectors[n];
best_crtcs[n] = NULL; - best_score = drm_pick_crtcs(fb_helper, best_crtcs, modes, n+1, width, height); + best_score = drm_pick_crtcs(display, connectors, connector_count, + best_crtcs, modes, n + 1, width, height); if (modes[n] == NULL) return best_score;
- crtcs = kcalloc(fb_helper->connector_count, sizeof(*crtcs), GFP_KERNEL); + crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); if (!crtcs) return best_score;
my_score = 1; if (connector->status == connector_status_connected) my_score++; - if (drm_has_cmdline_mode(fb_helper_conn)) + if (connector->cmdline_mode.specified) my_score++; if (drm_connector_has_preferred_mode(connector, width, height)) my_score++; @@ -1985,7 +1796,7 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, * NULL we fallback to the default drm_atomic_helper_best_encoder() * helper. */ - if (drm_drv_uses_atomic_modeset(fb_helper->dev) && + if (drm_drv_uses_atomic_modeset(dev) && !connector_funcs->best_encoder) encoder = drm_atomic_helper_best_encoder(connector); else @@ -2019,12 +1830,12 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
crtcs[n] = crtc; memcpy(crtcs, best_crtcs, n * sizeof(*crtcs)); - score = my_score + drm_pick_crtcs(fb_helper, crtcs, modes, n + 1, - width, height); + score = my_score + drm_pick_crtcs(display, connectors, connector_count, + crtcs, modes, n + 1, width, height); if (score > best_score) { best_score = score; memcpy(best_crtcs, crtcs, - fb_helper->connector_count * sizeof(*crtcs)); + connector_count * sizeof(*crtcs)); } } out: @@ -2037,11 +1848,12 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, { struct drm_device *dev = fb_helper->dev; struct drm_client_display *display; + struct drm_connector **connectors; struct drm_display_mode **modes; struct drm_fb_offset *offsets; struct drm_crtc **crtcs; + int i, connector_count; bool *enabled; - int i;
DRM_DEBUG_KMS("\n"); /* prevent concurrent modification of connector_count by hotplug */ @@ -2061,13 +1873,14 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, } }
- crtcs = kcalloc(fb_helper->connector_count, sizeof(*crtcs), GFP_KERNEL); - modes = kcalloc(fb_helper->connector_count, - sizeof(struct drm_display_mode *), GFP_KERNEL); - offsets = kcalloc(fb_helper->connector_count, - sizeof(struct drm_fb_offset), GFP_KERNEL); - enabled = kcalloc(fb_helper->connector_count, - sizeof(bool), GFP_KERNEL); + connector_count = drm_connector_get_all(dev, &connectors); + if (connector_count < 1) + return; + + enabled = drm_connector_get_enabled_status(connectors, connector_count); + crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); + modes = kcalloc(connector_count, sizeof(*modes), GFP_KERNEL); + offsets = kcalloc(connector_count, sizeof(*offsets), GFP_KERNEL); if (!crtcs || !modes || !enabled || !offsets) { DRM_ERROR("Memory allocation failed\n"); goto out; @@ -2077,27 +1890,26 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, if (IS_ERR(display)) goto out;
- drm_enable_connectors(fb_helper, enabled); - - if (!drm_target_cloned(fb_helper, modes, offsets, enabled, width, height) && - !drm_target_preferred(fb_helper, modes, offsets, enabled, width, height)) + if (!drm_target_cloned(fb_helper, connectors, connector_count, + modes, offsets, enabled, width, height) && + !drm_target_preferred(connectors, connector_count, + modes, offsets, enabled, width, height)) DRM_ERROR("Unable to find initial modes\n");
DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height);
- drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height); + drm_pick_crtcs(display, connectors, connector_count, crtcs, modes, 0, width, height);
/* need to set the modesets up here for use later */ /* fill out the connector<->crtc mappings into the modesets */
- drm_fb_helper_for_each_connector(fb_helper, i) { + for (i = 0; i < connector_count; i++) { + struct drm_connector *connector = connectors[i]; struct drm_display_mode *mode = modes[i]; struct drm_crtc *crtc = crtcs[i]; struct drm_fb_offset *offset = &offsets[i];
if (mode && crtc) { - struct drm_connector *connector = - fb_helper->connector_info[i]->connector; struct drm_mode_set *modeset;
modeset = drm_client_display_find_modeset(display, crtc); @@ -2121,6 +1933,7 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, fb_helper->display = display; out: mutex_unlock(&dev->mode_config.mutex); + drm_connector_put_all(connectors, connector_count); kfree(crtcs); kfree(modes); kfree(offsets); @@ -2136,10 +1949,11 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, */ static void drm_setup_crtcs_fb(struct drm_fb_helper *fb_helper) { + struct drm_connector_list_iter conn_iter; struct fb_info *info = fb_helper->fbdev; unsigned int rotation, sw_rotations = 0; + struct drm_connector *connector; struct drm_mode_set *modeset; - int i;
drm_client_display_for_each_modeset(modeset, fb_helper->display) { if (!modeset->num_connectors) @@ -2156,10 +1970,8 @@ static void drm_setup_crtcs_fb(struct drm_fb_helper *fb_helper) }
mutex_lock(&fb_helper->dev->mode_config.mutex); - drm_fb_helper_for_each_connector(fb_helper, i) { - struct drm_connector *connector = - fb_helper->connector_info[i]->connector; - + drm_connector_list_iter_begin(fb_helper->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { /* use first connected connector for the physical dimensions */ if (connector->status == connector_status_connected) { info->var.width = connector->display_info.width_mm; @@ -2167,6 +1979,7 @@ static void drm_setup_crtcs_fb(struct drm_fb_helper *fb_helper) break; } } + drm_connector_list_iter_end(&conn_iter); mutex_unlock(&fb_helper->dev->mode_config.mutex);
switch (sw_rotations) { diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 408931f7613f..a1e1ab1247c5 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -97,10 +97,6 @@ struct drm_fb_helper_funcs { struct drm_fb_helper_surface_size *sizes); };
-struct drm_fb_helper_connector { - struct drm_connector *connector; -}; - /** * struct drm_fb_helper - main structure to emulate fbdev on top of KMS * @fb: Scanout framebuffer object @@ -134,15 +130,6 @@ struct drm_fb_helper { */ struct drm_client_display *display;
- int connector_count; - int connector_info_alloc_count; - /** - * @connector_info: - * - * Array of per-connector information. Do not iterate directly, but use - * drm_fb_helper_for_each_connector. - */ - struct drm_fb_helper_connector **connector_info; const struct drm_fb_helper_funcs *funcs; struct fb_info *fbdev; u32 pseudo_palette[17];
Call the function drm_client_find_display(). No functional change apart from making width/height arguments optional. Some function name/signature changes and whitespace adjustments.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_client.c | 399 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_fb_helper.c | 371 +------------------------------------ include/drm/drm_client.h | 2 + include/drm/drm_fb_helper.h | 4 - 4 files changed, 403 insertions(+), 373 deletions(-)
diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index c85c13568cf9..27818a467b09 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -18,6 +18,10 @@ #include <drm/drm_device.h> #include <drm/drm_modes.h>
+struct drm_client_display_offset { + int x, y; +}; + /** * drm_client_display_create() - Create display structure * @dev: DRM device @@ -359,3 +363,398 @@ void drm_client_display_dpms(struct drm_client_display *display, int mode) drm_client_display_dpms_legacy(display, mode); } EXPORT_SYMBOL(drm_client_display_dpms); + +static int drm_client_probe_connector_modes(struct drm_device *dev, + u32 max_width, u32 max_height) +{ + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + int count = 0; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + count += connector->funcs->fill_modes(connector, max_width, max_height); + } + drm_connector_list_iter_end(&conn_iter); + + return count; +} + +static bool drm_target_cloned(struct drm_device *dev, + struct drm_connector **connectors, + unsigned int connector_count, + struct drm_display_mode **modes, + struct drm_client_display_offset *offsets, + bool *enabled, int width, int height) +{ + int count, i, j; + bool can_clone = false; + struct drm_display_mode *dmt_mode, *mode; + + /* only contemplate cloning in the single crtc case */ + if (dev->mode_config.num_crtc > 1) + return false; + + count = 0; + for (i = 0; i < connector_count; i++) { + if (enabled[i]) + count++; + } + + /* only contemplate cloning if more than one connector is enabled */ + if (count <= 1) + return false; + + /* check the command line or if nothing common pick 1024x768 */ + can_clone = true; + for (i = 0; i < connector_count; i++) { + if (!enabled[i]) + continue; + modes[i] = drm_connector_pick_cmdline_mode(connectors[i]); + if (!modes[i]) { + can_clone = false; + break; + } + for (j = 0; j < i; j++) { + if (!enabled[j]) + continue; + if (!drm_mode_equal(modes[j], modes[i])) + can_clone = false; + } + } + + if (can_clone) { + DRM_DEBUG_KMS("can clone using command line\n"); + return true; + } + + /* try and find a 1024x768 mode on each connector */ + can_clone = true; + dmt_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false); + + for (i = 0; i < connector_count; i++) { + if (!enabled[i]) + continue; + + list_for_each_entry(mode, &connectors[i]->modes, head) { + if (drm_mode_equal(mode, dmt_mode)) + modes[i] = mode; + } + if (!modes[i]) + can_clone = false; + } + + if (can_clone) { + DRM_DEBUG_KMS("can clone using 1024x768\n"); + return true; + } + DRM_INFO("kms: can't enable cloning when we probably wanted to.\n"); + + return false; +} + +static void drm_get_tile_offsets(struct drm_connector **connectors, + unsigned int connector_count, + struct drm_display_mode **modes, + struct drm_client_display_offset *offsets, + int idx, + int h_idx, int v_idx) +{ + int i; + int hoffset = 0, voffset = 0; + + for (i = 0; i < connector_count; i++) { + struct drm_connector *connector = connectors[i]; + + if (!connector->has_tile) + continue; + + if (!modes[i] && (h_idx || v_idx)) { + DRM_DEBUG_KMS("no modes for connector tiled %d %d\n", i, + connector->base.id); + continue; + } + if (connector->tile_h_loc < h_idx) + hoffset += modes[i]->hdisplay; + + if (connector->tile_v_loc < v_idx) + voffset += modes[i]->vdisplay; + } + offsets[idx].x = hoffset; + offsets[idx].y = voffset; + DRM_DEBUG_KMS("returned %d %d for %d %d\n", hoffset, voffset, h_idx, v_idx); +} + +static bool drm_target_preferred(struct drm_connector **connectors, + unsigned int connector_count, + struct drm_display_mode **modes, + struct drm_client_display_offset *offsets, + bool *enabled, int width, int height) +{ + const u64 mask = BIT_ULL(connector_count) - 1; + u64 conn_configured = 0; + int tile_pass = 0; + int i; + +retry: + for (i = 0; i < connector_count; i++) { + struct drm_connector *connector = connectors[i]; + + if (conn_configured & BIT_ULL(i)) + continue; + + if (!enabled[i]) { + conn_configured |= BIT_ULL(i); + continue; + } + + /* first pass over all the untiled connectors */ + if (tile_pass == 0 && connector->has_tile) + continue; + + if (tile_pass == 1) { + if (connector->tile_h_loc != 0 || + connector->tile_v_loc != 0) + continue; + + } else { + if (connector->tile_h_loc != tile_pass - 1 && + connector->tile_v_loc != tile_pass - 1) + /* if this tile_pass doesn't cover any of the tiles - keep going */ + continue; + + /* + * find the tile offsets for this pass - need to find + * all tiles left and above + */ + drm_get_tile_offsets(connectors, connector_count, modes, offsets, + i, connector->tile_h_loc, connector->tile_v_loc); + } + DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n", connector->base.id); + + /* got for command line mode first */ + modes[i] = drm_connector_pick_cmdline_mode(connector); + if (!modes[i]) { + DRM_DEBUG_KMS("looking for preferred mode on connector %d %d\n", + connector->base.id, + connector->tile_group ? connector->tile_group->id : 0); + modes[i] = drm_connector_has_preferred_mode(connector, width, height); + } + /* No preferred modes, pick one off the list */ + if (!modes[i]) + modes[i] = list_first_entry_or_null(&connector->modes, + struct drm_display_mode, head); + + DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name : "none"); + conn_configured |= BIT_ULL(i); + } + + if ((conn_configured & mask) != mask) { + tile_pass++; + goto retry; + } + return true; +} + +static int drm_pick_crtcs(struct drm_client_display *display, + struct drm_connector **connectors, + unsigned int connector_count, + struct drm_crtc **best_crtcs, + struct drm_display_mode **modes, + int n, int width, int height) +{ + struct drm_device *dev = display->dev; + int o, my_score, best_score, score; + struct drm_connector *connector; + struct drm_mode_set *modeset; + struct drm_encoder *encoder; + struct drm_crtc **crtcs; + + if (n == connector_count) + return 0; + + connector = connectors[n]; + + best_crtcs[n] = NULL; + best_score = drm_pick_crtcs(display, connectors, connector_count, + best_crtcs, modes, n + 1, width, height); + if (!modes[n]) + return best_score; + + crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); + if (!crtcs) + return best_score; + + my_score = 1; + if (connector->status == connector_status_connected) + my_score++; + if (connector->cmdline_mode.specified) + my_score++; + if (drm_connector_has_preferred_mode(connector, width, height)) + my_score++; + + /* + * If the DRM device implements atomic hooks and ->best_encoder() is + * NULL we fallback to the default drm_atomic_helper_best_encoder() + * helper. + */ + if (drm_drv_uses_atomic_modeset(dev) && + !connector->helper_private->best_encoder) + encoder = drm_atomic_helper_best_encoder(connector); + else + encoder = connector->helper_private->best_encoder(connector); + + if (!encoder) + goto out; + + /* + * select a crtc for this connector and then attempt to configure + * remaining connectors + */ + drm_client_display_for_each_modeset(modeset, display) { + struct drm_crtc *crtc = modeset->crtc; + + if ((encoder->possible_crtcs & drm_crtc_mask(crtc)) == 0) + continue; + + for (o = 0; o < n; o++) + if (best_crtcs[o] == crtc) + break; + + if (o < n) { + /* ignore cloning unless only a single crtc */ + if (dev->mode_config.num_crtc > 1) + continue; + + if (!drm_mode_equal(modes[o], modes[n])) + continue; + } + + crtcs[n] = crtc; + memcpy(crtcs, best_crtcs, n * sizeof(*crtcs)); + score = my_score + drm_pick_crtcs(display, connectors, connector_count, + crtcs, modes, n + 1, width, height); + if (score > best_score) { + best_score = score; + memcpy(best_crtcs, crtcs, + connector_count * sizeof(*crtcs)); + } + } +out: + kfree(crtcs); + + return best_score; +} + +/** + * drm_client_find_display() - Find display + * @dev: DRM device + * @width: Maximum display mode width (optional) + * @height: Maximum display mode height (optional) + * + * This function returns a display the client can use if available. + * + * Free resources by calling drm_client_display_free(). + * + * Returns: + * A &drm_client_display on success, NULL if no connectors are found + * or error pointer on failure. + */ +struct drm_client_display * +drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height) +{ + struct drm_client_display_offset *offsets; + struct drm_client_display *display; + struct drm_connector **connectors; + struct drm_display_mode **modes; + struct drm_crtc **crtcs; + int i, connector_count; + bool *enabled; + + DRM_DEBUG_KMS("\n"); + + if (!width) + width = dev->mode_config.max_width; + if (!height) + height = dev->mode_config.max_height; + + mutex_lock(&dev->mode_config.mutex); + if (!drm_client_probe_connector_modes(dev, width, height)) + DRM_DEBUG_KMS("No connectors reported connected with modes\n"); + + if (dev->driver->initial_client_display) { + display = dev->driver->initial_client_display(dev, width, height); + if (display) { + mutex_unlock(&dev->mode_config.mutex); + return display; + } + } + + connector_count = drm_connector_get_all(dev, &connectors); + if (connector_count < 1) + return NULL; + + enabled = drm_connector_get_enabled_status(connectors, connector_count); + crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); + modes = kcalloc(connector_count, sizeof(*modes), GFP_KERNEL); + offsets = kcalloc(connector_count, sizeof(*offsets), GFP_KERNEL); + if (!crtcs || !modes || !enabled || !offsets) { + DRM_ERROR("Memory allocation failed\n"); + display = ERR_PTR(-ENOMEM); + goto out; + } + + display = drm_client_display_create(dev); + if (IS_ERR(display)) + goto out; + + if (!drm_target_cloned(dev, connectors, connector_count, + modes, offsets, enabled, width, height) && + !drm_target_preferred(connectors, connector_count, + modes, offsets, enabled, width, height)) + DRM_ERROR("Unable to find initial modes\n"); + + DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height); + + drm_pick_crtcs(display, connectors, connector_count, crtcs, modes, 0, width, height); + + /* need to set the modesets up here for use later */ + /* fill out the connector<->crtc mappings into the modesets */ + + for (i = 0; i < connector_count; i++) { + struct drm_client_display_offset *offset = &offsets[i]; + struct drm_connector *connector = connectors[i]; + struct drm_display_mode *mode = modes[i]; + struct drm_crtc *crtc = crtcs[i]; + + if (mode && crtc) { + struct drm_mode_set *modeset; + + modeset = drm_client_display_find_modeset(display, crtc); + if (WARN_ON(!modeset)) { + drm_client_display_free(display); + display = ERR_PTR(-EINVAL); + goto out; + } + + DRM_DEBUG_KMS("desired mode %s set on crtc %d (%d,%d)\n", + mode->name, modeset->crtc->base.id, offset->x, offset->y); + + modeset->mode = drm_mode_duplicate(dev, mode); + drm_connector_get(connector); + modeset->connectors[modeset->num_connectors++] = connector; + modeset->x = offset->x; + modeset->y = offset->y; + } + } +out: + mutex_unlock(&dev->mode_config.mutex); + drm_connector_put_all(connectors, connector_count); + kfree(crtcs); + kfree(modes); + kfree(offsets); + kfree(enabled); + + return display; +} +EXPORT_SYMBOL(drm_client_find_display); diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 6ee61f195321..01d8840930a3 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1558,386 +1558,19 @@ void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helpe } EXPORT_SYMBOL(drm_fb_helper_fill_var);
-static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper, - uint32_t maxX, - uint32_t maxY) -{ - struct drm_connector_list_iter conn_iter; - struct drm_connector *connector; - int count = 0; - - drm_connector_list_iter_begin(fb_helper->dev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - count += connector->funcs->fill_modes(connector, maxX, maxY); - } - drm_connector_list_iter_end(&conn_iter); - - return count; -} - -static bool drm_target_cloned(struct drm_fb_helper *fb_helper, - struct drm_connector **connectors, - unsigned int connector_count, - struct drm_display_mode **modes, - struct drm_fb_offset *offsets, - bool *enabled, int width, int height) -{ - int count, i, j; - bool can_clone = false; - struct drm_display_mode *dmt_mode, *mode; - - /* only contemplate cloning in the single crtc case */ - if (fb_helper->dev->mode_config.num_crtc > 1) - return false; - - count = 0; - for (i = 0; i < connector_count; i++) { - if (enabled[i]) - count++; - } - - /* only contemplate cloning if more than one connector is enabled */ - if (count <= 1) - return false; - - /* check the command line or if nothing common pick 1024x768 */ - can_clone = true; - for (i = 0; i < connector_count; i++) { - if (!enabled[i]) - continue; - modes[i] = drm_connector_pick_cmdline_mode(connectors[i]); - if (!modes[i]) { - can_clone = false; - break; - } - for (j = 0; j < i; j++) { - if (!enabled[j]) - continue; - if (!drm_mode_equal(modes[j], modes[i])) - can_clone = false; - } - } - - if (can_clone) { - DRM_DEBUG_KMS("can clone using command line\n"); - return true; - } - - /* try and find a 1024x768 mode on each connector */ - can_clone = true; - dmt_mode = drm_mode_find_dmt(fb_helper->dev, 1024, 768, 60, false); - - for (i = 0; i < connector_count; i++) { - if (!enabled[i]) - continue; - - list_for_each_entry(mode, &connectors[i]->modes, head) { - if (drm_mode_equal(mode, dmt_mode)) - modes[i] = mode; - } - if (!modes[i]) - can_clone = false; - } - - if (can_clone) { - DRM_DEBUG_KMS("can clone using 1024x768\n"); - return true; - } - DRM_INFO("kms: can't enable cloning when we probably wanted to.\n"); - return false; -} - -static int drm_get_tile_offsets(struct drm_connector **connectors, - unsigned int connector_count, - struct drm_display_mode **modes, - struct drm_fb_offset *offsets, - int idx, - int h_idx, int v_idx) -{ - int i; - int hoffset = 0, voffset = 0; - - for (i = 0; i < connector_count; i++) { - struct drm_connector *connector = connectors[i]; - - if (!connector->has_tile) - continue; - - if (!modes[i] && (h_idx || v_idx)) { - DRM_DEBUG_KMS("no modes for connector tiled %d %d\n", i, - connector->base.id); - continue; - } - if (connector->tile_h_loc < h_idx) - hoffset += modes[i]->hdisplay; - - if (connector->tile_v_loc < v_idx) - voffset += modes[i]->vdisplay; - } - offsets[idx].x = hoffset; - offsets[idx].y = voffset; - DRM_DEBUG_KMS("returned %d %d for %d %d\n", hoffset, voffset, h_idx, v_idx); - return 0; -} - -static bool drm_target_preferred(struct drm_connector **connectors, - unsigned int connector_count, - struct drm_display_mode **modes, - struct drm_fb_offset *offsets, - bool *enabled, int width, int height) -{ - const u64 mask = BIT_ULL(connector_count) - 1; - u64 conn_configured = 0; - int tile_pass = 0; - int i; - -retry: - for (i = 0; i < connector_count; i++) { - struct drm_connector *connector = connectors[i]; - - if (conn_configured & BIT_ULL(i)) - continue; - - if (enabled[i] == false) { - conn_configured |= BIT_ULL(i); - continue; - } - - /* first pass over all the untiled connectors */ - if (tile_pass == 0 && connector->has_tile) - continue; - - if (tile_pass == 1) { - if (connector->tile_h_loc != 0 || - connector->tile_v_loc != 0) - continue; - - } else { - if (connector->tile_h_loc != tile_pass - 1 && - connector->tile_v_loc != tile_pass - 1) - /* if this tile_pass doesn't cover any of the tiles - keep going */ - continue; - - /* - * find the tile offsets for this pass - need to find - * all tiles left and above - */ - drm_get_tile_offsets(connectors, connector_count, modes, offsets, - i, connector->tile_h_loc, connector->tile_v_loc); - } - DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n", - connector->base.id); - - /* got for command line mode first */ - modes[i] = drm_connector_pick_cmdline_mode(connector); - if (!modes[i]) { - DRM_DEBUG_KMS("looking for preferred mode on connector %d %d\n", - connector->base.id, connector->tile_group ? connector->tile_group->id : 0); - modes[i] = drm_connector_has_preferred_mode(connector, width, height); - } - /* No preferred modes, pick one off the list */ - if (!modes[i]) - modes[i] = list_first_entry_or_null(&connector->modes, struct drm_display_mode, head); - - DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name : - "none"); - conn_configured |= BIT_ULL(i); - } - - if ((conn_configured & mask) != mask) { - tile_pass++; - goto retry; - } - return true; -} - -static int drm_pick_crtcs(struct drm_client_display *display, - struct drm_connector **connectors, - unsigned int connector_count, - struct drm_crtc **best_crtcs, - struct drm_display_mode **modes, - int n, int width, int height) -{ - struct drm_device *dev = display->dev; - int o, my_score, best_score, score; - struct drm_connector *connector; - const struct drm_connector_helper_funcs *connector_funcs; - struct drm_mode_set *modeset; - struct drm_encoder *encoder; - struct drm_crtc **crtcs; - - if (n == connector_count) - return 0; - - connector = connectors[n]; - - best_crtcs[n] = NULL; - best_score = drm_pick_crtcs(display, connectors, connector_count, - best_crtcs, modes, n + 1, width, height); - if (modes[n] == NULL) - return best_score; - - crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); - if (!crtcs) - return best_score; - - my_score = 1; - if (connector->status == connector_status_connected) - my_score++; - if (connector->cmdline_mode.specified) - my_score++; - if (drm_connector_has_preferred_mode(connector, width, height)) - my_score++; - - connector_funcs = connector->helper_private; - - /* - * If the DRM device implements atomic hooks and ->best_encoder() is - * NULL we fallback to the default drm_atomic_helper_best_encoder() - * helper. - */ - if (drm_drv_uses_atomic_modeset(dev) && - !connector_funcs->best_encoder) - encoder = drm_atomic_helper_best_encoder(connector); - else - encoder = connector_funcs->best_encoder(connector); - - if (!encoder) - goto out; - - /* - * select a crtc for this connector and then attempt to configure - * remaining connectors - */ - drm_client_display_for_each_modeset(modeset, display) { - struct drm_crtc *crtc = modeset->crtc; - - if ((encoder->possible_crtcs & drm_crtc_mask(crtc)) == 0) - continue; - - for (o = 0; o < n; o++) - if (best_crtcs[o] == crtc) - break; - - if (o < n) { - /* ignore cloning unless only a single crtc */ - if (dev->mode_config.num_crtc > 1) - continue; - - if (!drm_mode_equal(modes[o], modes[n])) - continue; - } - - crtcs[n] = crtc; - memcpy(crtcs, best_crtcs, n * sizeof(*crtcs)); - score = my_score + drm_pick_crtcs(display, connectors, connector_count, - crtcs, modes, n + 1, width, height); - if (score > best_score) { - best_score = score; - memcpy(best_crtcs, crtcs, - connector_count * sizeof(*crtcs)); - } - } -out: - kfree(crtcs); - return best_score; -} - static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, u32 width, u32 height) { - struct drm_device *dev = fb_helper->dev; struct drm_client_display *display; - struct drm_connector **connectors; - struct drm_display_mode **modes; - struct drm_fb_offset *offsets; - struct drm_crtc **crtcs; - int i, connector_count; - bool *enabled;
- DRM_DEBUG_KMS("\n"); - /* prevent concurrent modification of connector_count by hotplug */ lockdep_assert_held(&fb_helper->lock);
- mutex_lock(&dev->mode_config.mutex); - if (drm_fb_helper_probe_connector_modes(fb_helper, width, height) == 0) - DRM_DEBUG_KMS("No connectors reported connected with modes\n"); - - if (dev->driver->initial_client_display) { - display = dev->driver->initial_client_display(dev, width, height); - if (display) { - drm_client_display_free(fb_helper->display); - fb_helper->display = display; - mutex_unlock(&dev->mode_config.mutex); - return; - } - } - - connector_count = drm_connector_get_all(dev, &connectors); - if (connector_count < 1) + display = drm_client_find_display(fb_helper->dev, width, height); + if (IS_ERR_OR_NULL(display)) return;
- enabled = drm_connector_get_enabled_status(connectors, connector_count); - crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL); - modes = kcalloc(connector_count, sizeof(*modes), GFP_KERNEL); - offsets = kcalloc(connector_count, sizeof(*offsets), GFP_KERNEL); - if (!crtcs || !modes || !enabled || !offsets) { - DRM_ERROR("Memory allocation failed\n"); - goto out; - } - - display = drm_client_display_create(dev); - if (IS_ERR(display)) - goto out; - - if (!drm_target_cloned(fb_helper, connectors, connector_count, - modes, offsets, enabled, width, height) && - !drm_target_preferred(connectors, connector_count, - modes, offsets, enabled, width, height)) - DRM_ERROR("Unable to find initial modes\n"); - - DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height); - - drm_pick_crtcs(display, connectors, connector_count, crtcs, modes, 0, width, height); - - /* need to set the modesets up here for use later */ - /* fill out the connector<->crtc mappings into the modesets */ - - for (i = 0; i < connector_count; i++) { - struct drm_connector *connector = connectors[i]; - struct drm_display_mode *mode = modes[i]; - struct drm_crtc *crtc = crtcs[i]; - struct drm_fb_offset *offset = &offsets[i]; - - if (mode && crtc) { - struct drm_mode_set *modeset; - - modeset = drm_client_display_find_modeset(display, crtc); - if (WARN_ON(!modeset)) { - drm_client_display_free(display); - goto out; - } - - DRM_DEBUG_KMS("desired mode %s set on crtc %d (%d,%d)\n", - mode->name, modeset->crtc->base.id, offset->x, offset->y); - - modeset->mode = drm_mode_duplicate(dev, mode); - drm_connector_get(connector); - modeset->connectors[modeset->num_connectors++] = connector; - modeset->x = offset->x; - modeset->y = offset->y; - } - } - drm_client_display_free(fb_helper->display); fb_helper->display = display; -out: - mutex_unlock(&dev->mode_config.mutex); - drm_connector_put_all(connectors, connector_count); - kfree(crtcs); - kfree(modes); - kfree(offsets); - kfree(enabled); }
/* diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index ed028f5877d0..27d2a46cd94a 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -48,5 +48,7 @@ bool drm_client_display_panel_rotation(struct drm_connector *connector, unsigned int *rotation); int drm_client_display_restore(struct drm_client_display *display); void drm_client_display_dpms(struct drm_client_display *display, int mode); +struct drm_client_display * +drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height);
#endif diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index a1e1ab1247c5..5f66f253a97b 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -41,10 +41,6 @@ enum mode_set_atomic { ENTER_ATOMIC_MODE_SET, };
-struct drm_fb_offset { - int x, y; -}; - /** * struct drm_fb_helper_surface_size - describes fbdev size and scanout surface size * @fb_width: fbdev width
Make ioctl wrappers for functions that will be used by the in-kernel API. The following functions are touched: - drm_mode_create_dumb_ioctl() - drm_mode_destroy_dumb_ioctl() - drm_mode_addfb2() - drm_mode_rmfb() - drm_prime_handle_to_fd_ioctl()
drm_mode_addfb2() also gets the ability to override the debug name.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_crtc_internal.h | 18 ++++++++++--- drivers/gpu/drm/drm_dumb_buffers.c | 33 ++++++++++++++++-------- drivers/gpu/drm/drm_framebuffer.c | 50 ++++++++++++++++++++++++------------- drivers/gpu/drm/drm_internal.h | 3 +++ drivers/gpu/drm/drm_ioc32.c | 2 +- drivers/gpu/drm/drm_ioctl.c | 4 +-- drivers/gpu/drm/drm_prime.c | 13 +++++++--- 7 files changed, 84 insertions(+), 39 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h index 3c2b82865ad2..8f8886ac0e4d 100644 --- a/drivers/gpu/drm/drm_crtc_internal.h +++ b/drivers/gpu/drm/drm_crtc_internal.h @@ -62,6 +62,12 @@ int drm_mode_getresources(struct drm_device *dev,
/* drm_dumb_buffers.c */ +int drm_mode_create_dumb(struct drm_device *dev, + struct drm_mode_create_dumb *args, + struct drm_file *file_priv); +int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle, + struct drm_file *file_priv); + /* IOCTLs */ int drm_mode_create_dumb_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); @@ -163,14 +169,18 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y, const struct drm_framebuffer *fb); void drm_fb_release(struct drm_file *file_priv);
+int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r, + struct drm_file *file_priv, const char *comm); +int drm_mode_rmfb(struct drm_device *dev, u32 fb_id, + struct drm_file *file_priv);
/* IOCTL */ int drm_mode_addfb(struct drm_device *dev, void *data, struct drm_file *file_priv); -int drm_mode_addfb2(struct drm_device *dev, - void *data, struct drm_file *file_priv); -int drm_mode_rmfb(struct drm_device *dev, - void *data, struct drm_file *file_priv); +int drm_mode_addfb2_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv); +int drm_mode_rmfb_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv); int drm_mode_getfb(struct drm_device *dev, void *data, struct drm_file *file_priv); int drm_mode_dirtyfb_ioctl(struct drm_device *dev, diff --git a/drivers/gpu/drm/drm_dumb_buffers.c b/drivers/gpu/drm/drm_dumb_buffers.c index 39ac15ce4702..eed9687b8698 100644 --- a/drivers/gpu/drm/drm_dumb_buffers.c +++ b/drivers/gpu/drm/drm_dumb_buffers.c @@ -53,10 +53,10 @@ * a hardware-specific ioctl to allocate suitable buffer objects. */
-int drm_mode_create_dumb_ioctl(struct drm_device *dev, - void *data, struct drm_file *file_priv) +int drm_mode_create_dumb(struct drm_device *dev, + struct drm_mode_create_dumb *args, + struct drm_file *file_priv) { - struct drm_mode_create_dumb *args = data; u32 cpp, stride, size;
if (!dev->driver->dumb_create) @@ -91,6 +91,12 @@ int drm_mode_create_dumb_ioctl(struct drm_device *dev, return dev->driver->dumb_create(file_priv, dev, args); }
+int drm_mode_create_dumb_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) +{ + return drm_mode_create_dumb(dev, data, file_priv); +} + /** * drm_mode_mmap_dumb_ioctl - create an mmap offset for a dumb backing storage buffer * @dev: DRM device @@ -122,17 +128,22 @@ int drm_mode_mmap_dumb_ioctl(struct drm_device *dev, &args->offset); }
+int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle, + struct drm_file *file_priv) +{ + if (!dev->driver->dumb_create) + return -ENOSYS; + + if (dev->driver->dumb_destroy) + return dev->driver->dumb_destroy(file_priv, dev, handle); + else + return drm_gem_dumb_destroy(file_priv, dev, handle); +} + int drm_mode_destroy_dumb_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_destroy_dumb *args = data;
- if (!dev->driver->dumb_create) - return -ENOSYS; - - if (dev->driver->dumb_destroy) - return dev->driver->dumb_destroy(file_priv, dev, args->handle); - else - return drm_gem_dumb_destroy(file_priv, dev, args->handle); + return drm_mode_destroy_dumb(dev, args->handle, file_priv); } - diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c index 8c4d32adcc17..16769e4db5c0 100644 --- a/drivers/gpu/drm/drm_framebuffer.c +++ b/drivers/gpu/drm/drm_framebuffer.c @@ -125,7 +125,7 @@ int drm_mode_addfb(struct drm_device *dev, dev->driver->driver_features & DRIVER_PREFER_XBGR_30BPP) r.pixel_format = DRM_FORMAT_XBGR2101010;
- ret = drm_mode_addfb2(dev, &r, file_priv); + ret = drm_mode_addfb2_ioctl(dev, &r, file_priv); if (ret) return ret;
@@ -310,23 +310,23 @@ drm_internal_framebuffer_create(struct drm_device *dev,
/** * drm_mode_addfb2 - add an FB to the graphics configuration - * @dev: drm device for the ioctl - * @data: data pointer for the ioctl - * @file_priv: drm file for the ioctl call + * @dev: drm device + * @r: pointer to request structure + * @file_priv: drm file + * @comm: optionally override the allocator name used for debug output * * Add a new FB to the specified CRTC, given a user request with format. This is * the 2nd version of the addfb ioctl, which supports multi-planar framebuffers * and uses fourcc codes as pixel format specifiers. * - * Called by the user via ioctl. + * Called by the user via ioctl, or by an in-kernel client. * * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_addfb2(struct drm_device *dev, - void *data, struct drm_file *file_priv) +int drm_mode_addfb2(struct drm_device *dev, struct drm_mode_fb_cmd2 *r, + struct drm_file *file_priv, const char *comm) { - struct drm_mode_fb_cmd2 *r = data; struct drm_framebuffer *fb;
if (!drm_core_check_feature(dev, DRIVER_MODESET)) @@ -336,6 +336,9 @@ int drm_mode_addfb2(struct drm_device *dev, if (IS_ERR(fb)) return PTR_ERR(fb);
+ if (comm) + strscpy(fb->comm, comm, TASK_COMM_LEN); + DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id); r->fb_id = fb->base.id;
@@ -347,6 +350,12 @@ int drm_mode_addfb2(struct drm_device *dev, return 0; }
+int drm_mode_addfb2_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) +{ + return drm_mode_addfb2(dev, data, file_priv, NULL); +} + struct drm_mode_rmfb_work { struct work_struct work; struct list_head fbs; @@ -367,29 +376,28 @@ static void drm_mode_rmfb_work_fn(struct work_struct *w)
/** * drm_mode_rmfb - remove an FB from the configuration - * @dev: drm device for the ioctl - * @data: data pointer for the ioctl - * @file_priv: drm file for the ioctl call + * @dev: drm device + * @fb_id: id of framebuffer to remove + * @file_priv: drm file * - * Remove the FB specified by the user. + * Remove the specified FB. * - * Called by the user via ioctl. + * Called by the user via ioctl, or by an in-kernel client. * * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_rmfb(struct drm_device *dev, - void *data, struct drm_file *file_priv) +int drm_mode_rmfb(struct drm_device *dev, u32 fb_id, + struct drm_file *file_priv) { struct drm_framebuffer *fb = NULL; struct drm_framebuffer *fbl = NULL; - uint32_t *id = data; int found = 0;
if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL;
- fb = drm_framebuffer_lookup(dev, file_priv, *id); + fb = drm_framebuffer_lookup(dev, file_priv, fb_id); if (!fb) return -ENOENT;
@@ -435,6 +443,14 @@ int drm_mode_rmfb(struct drm_device *dev, return -ENOENT; }
+int drm_mode_rmfb_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) +{ + uint32_t *fb_id = data; + + return drm_mode_rmfb(dev, *fb_id, file_priv); +} + /** * drm_mode_getfb - get FB info * @dev: drm device for the ioctl diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index 40179c5fc6b8..3f5d7706bcc9 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -37,6 +37,9 @@ void drm_pci_agp_destroy(struct drm_device *dev); int drm_pci_set_busid(struct drm_device *dev, struct drm_master *master);
/* drm_prime.c */ +int drm_prime_handle_to_fd(struct drm_device *dev, + struct drm_prime_handle *args, + struct drm_file *file_priv); int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data, diff --git a/drivers/gpu/drm/drm_ioc32.c b/drivers/gpu/drm/drm_ioc32.c index f8e96e648acf..576d00b7dad5 100644 --- a/drivers/gpu/drm/drm_ioc32.c +++ b/drivers/gpu/drm/drm_ioc32.c @@ -884,7 +884,7 @@ static int compat_drm_mode_addfb2(struct file *file, unsigned int cmd, sizeof(req64.modifier))) return -EFAULT;
- err = drm_ioctl_kernel(file, drm_mode_addfb2, &req64, + err = drm_ioctl_kernel(file, drm_mode_addfb2_ioctl, &req64, DRM_CONTROL_ALLOW|DRM_UNLOCKED); if (err) return err; diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c index af782911c505..c69fda5d3875 100644 --- a/drivers/gpu/drm/drm_ioctl.c +++ b/drivers/gpu/drm/drm_ioctl.c @@ -635,8 +635,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED), DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED), - DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2, DRM_CONTROL_ALLOW|DRM_UNLOCKED), - DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED), DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED), DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED), DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, DRM_CONTROL_ALLOW|DRM_UNLOCKED), diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c index caf675e3e692..e6052ab2bec4 100644 --- a/drivers/gpu/drm/drm_prime.c +++ b/drivers/gpu/drm/drm_prime.c @@ -866,11 +866,10 @@ int drm_gem_prime_fd_to_handle(struct drm_device *dev, } EXPORT_SYMBOL(drm_gem_prime_fd_to_handle);
-int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) +int drm_prime_handle_to_fd(struct drm_device *dev, + struct drm_prime_handle *args, + struct drm_file *file_priv) { - struct drm_prime_handle *args = data; - if (!drm_core_check_feature(dev, DRIVER_PRIME)) return -EINVAL;
@@ -885,6 +884,12 @@ int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data, args->handle, args->flags, &args->fd); }
+int drm_prime_handle_to_fd_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return drm_prime_handle_to_fd(dev, data, file_priv); +} + int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) {
If there's a DRM master, return -EBUSY. Block userspace from becoming master by taking the master lock while the client is setting the mode.
Suggested-by: Daniel Vetter daniel.vetter@ffwll.ch Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_auth.c | 33 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_client.c | 34 +++++++++++++++++++++++++++++----- drivers/gpu/drm/drm_fb_helper.c | 8 ++++---- drivers/gpu/drm/drm_internal.h | 2 ++ include/drm/drm_client.h | 4 ++-- 5 files changed, 70 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c index d9c0f7573905..d656d0d93da3 100644 --- a/drivers/gpu/drm/drm_auth.c +++ b/drivers/gpu/drm/drm_auth.c @@ -366,3 +366,36 @@ void drm_master_put(struct drm_master **master) *master = NULL; } EXPORT_SYMBOL(drm_master_put); + +/** + * drm_master_block - Block DRM master operations + * @dev: DRM device + * + * This function checks if there is a master on @dev. If there is no master it + * blocks anyone from becoming master. In-kernel clients can use this to know + * when they can act as master. Use drm_master_unblock() to unblock. + * + * Returns: + * True if there is no master, false otherwise. + */ +bool drm_master_block(struct drm_device *dev) +{ + mutex_lock(&dev->master_mutex); + if (dev->master) { + mutex_unlock(&dev->master_mutex); + return false; + } + + return true; +} + +/** + * drm_master_unblock - Unblock DRM master operations + * @dev: DRM device + * + * Unblock and allow userspace to become master. + */ +void drm_master_unblock(struct drm_device *dev) +{ + mutex_unlock(&dev->master_mutex); +} diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 27818a467b09..764c556630b8 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -18,6 +18,8 @@ #include <drm/drm_device.h> #include <drm/drm_modes.h>
+#include "drm_internal.h" + struct drm_client_display_offset { int x, y; }; @@ -313,18 +315,30 @@ static int drm_client_display_restore_legacy(struct drm_client_display *display) /** * drm_client_display_restore() - Restore client display * @display: Client display + * @force: If true, restore even if there's a DRM master * * Restore client display using the current modeset configuration. * * Return: * Zero on succes or negative error code on failure. */ -int drm_client_display_restore(struct drm_client_display *display) +int drm_client_display_restore(struct drm_client_display *display, bool force) { - if (drm_drv_uses_atomic_modeset(display->dev)) - return drm_client_display_restore_atomic(display, true); + struct drm_device *dev = display->dev; + int ret; + + if (!force && !drm_master_block(dev)) + return -EBUSY; + + if (drm_drv_uses_atomic_modeset(dev)) + ret = drm_client_display_restore_atomic(display, true); else - return drm_client_display_restore_legacy(display); + ret = drm_client_display_restore_legacy(display); + + if (!force) + drm_master_unblock(dev); + + return ret; } EXPORT_SYMBOL(drm_client_display_restore);
@@ -354,13 +368,23 @@ static void drm_client_display_dpms_legacy(struct drm_client_display *display, i * drm_client_display_dpms() - Set display DPMS mode * @display: Client display * @mode: DPMS mode + * + * Returns: + * Zero on success, -EBUSY if there's a DRM master. */ -void drm_client_display_dpms(struct drm_client_display *display, int mode) +int drm_client_display_dpms(struct drm_client_display *display, int mode) { + if (!drm_master_block(display->dev)) + return -EBUSY; + if (drm_drv_uses_atomic_modeset(display->dev)) drm_client_display_restore_atomic(display, mode == DRM_MODE_DPMS_ON); else drm_client_display_dpms_legacy(display, mode); + + drm_master_unblock(display->dev); + + return 0; } EXPORT_SYMBOL(drm_client_display_dpms);
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 01d8840930a3..98e5bc92c9f2 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -181,7 +181,7 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper) return 0;
mutex_lock(&fb_helper->lock); - ret = drm_client_display_restore(fb_helper->display); + ret = drm_client_display_restore(fb_helper->display, false);
do_delayed = fb_helper->delayed_hotplug; if (do_delayed) @@ -243,7 +243,7 @@ static bool drm_fb_helper_force_kernel_mode(void) continue;
mutex_lock(&helper->lock); - ret = drm_client_display_restore(helper->display); + ret = drm_client_display_restore(helper->display, true); if (ret) error = true; mutex_unlock(&helper->lock); @@ -1254,7 +1254,7 @@ static int pan_display_atomic(struct fb_var_screeninfo *var,
pan_set(fb_helper, var->xoffset, var->yoffset);
- ret = drm_client_display_restore(fb_helper->display); + ret = drm_client_display_restore(fb_helper->display, false); if (!ret) { info->var.xoffset = var->xoffset; info->var.yoffset = var->yoffset; @@ -1423,7 +1423,7 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
/* First time: disable all crtc's.. */ if (!fb_helper->deferred_setup && !READ_ONCE(fb_helper->dev->master)) - drm_client_display_restore(fb_helper->display); + drm_client_display_restore(fb_helper->display, false); return -EAGAIN; }
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index 3f5d7706bcc9..f38dcaf139d7 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -92,6 +92,8 @@ int drm_dropmaster_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int drm_master_open(struct drm_file *file_priv); void drm_master_release(struct drm_file *file_priv); +bool drm_master_block(struct drm_device *dev); +void drm_master_unblock(struct drm_device *dev);
/* drm_sysfs.c */ extern struct class *drm_class; diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index 27d2a46cd94a..3befd879a0b0 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -46,8 +46,8 @@ drm_client_display_find_modeset(struct drm_client_display *display, struct drm_c bool drm_client_display_panel_rotation(struct drm_connector *connector, struct drm_plane *plane, unsigned int *rotation); -int drm_client_display_restore(struct drm_client_display *display); -void drm_client_display_dpms(struct drm_client_display *display, int mode); +int drm_client_display_restore(struct drm_client_display *display, bool force); +int drm_client_display_dpms(struct drm_client_display *display, int mode); struct drm_client_display * drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height);
Give clients easy access to the display modes.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_client.c | 159 +++++++++++++++++++++++++++++++++---------- include/drm/drm_client.h | 25 +++++++ 2 files changed, 148 insertions(+), 36 deletions(-)
diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 764c556630b8..bce1630a0db2 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -8,6 +8,7 @@ * Copyright (c) 2007 Dave Airlie airlied@linux.ie */
+#include <linux/list.h> #include <linux/slab.h>
#include <drm/drm_atomic.h> @@ -54,6 +55,7 @@ struct drm_client_display *drm_client_display_create(struct drm_device *dev) }
display->dev = dev; + INIT_LIST_HEAD(&display->modes); display->modeset_count = num_crtc;
drm_for_each_crtc(crtc, dev) @@ -84,12 +86,16 @@ EXPORT_SYMBOL(drm_client_display_create); */ void drm_client_display_free(struct drm_client_display *display) { + struct drm_display_mode *mode, *tmp; struct drm_mode_set *modeset; unsigned int i;
if (!display) return;
+ list_for_each_entry_safe(mode, tmp, &display->modes, head) + drm_mode_destroy(display->dev, mode); + drm_client_display_for_each_modeset(modeset, display) { if (modeset->mode) drm_mode_destroy(display->dev, modeset->mode); @@ -670,22 +676,70 @@ static int drm_pick_crtcs(struct drm_client_display *display, return best_score; }
-/** - * drm_client_find_display() - Find display - * @dev: DRM device - * @width: Maximum display mode width (optional) - * @height: Maximum display mode height (optional) - * - * This function returns a display the client can use if available. - * - * Free resources by calling drm_client_display_free(). - * - * Returns: - * A &drm_client_display on success, NULL if no connectors are found - * or error pointer on failure. - */ -struct drm_client_display * -drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height) +/* Give the client a static list of display modes */ +static int drm_client_display_copy_modes(struct drm_client_display *display) +{ + int hdisplay = 0, vdisplay = 0, vrefresh; + struct drm_device *dev = display->dev; + struct drm_display_mode *mode, *copy; + struct drm_connector *connector; + struct drm_mode_set *modeset; + unsigned int count = 0; + + drm_client_display_for_each_modeset(modeset, display) { + if (!modeset->num_connectors || !modeset->mode) + continue; + + connector = modeset->connectors[0]; + mode = modeset->mode; + count++; + + if (modeset->num_connectors == 2) { + /* Cloned output */ + copy = drm_mode_duplicate(dev, modeset->mode); + if (!copy) + return -ENOMEM; + list_add_tail(©->head, &display->modes); + display->mode = copy; + + return 0; + } + + if (!modeset->y) + hdisplay += modeset->mode->hdisplay; + if (!modeset->x) + vdisplay += modeset->mode->vdisplay; + vrefresh = modeset->mode->vrefresh; + } + + if (!count) + return 0; + + if (count == 1) { + struct drm_display_mode *iter; + + list_for_each_entry(iter, &connector->modes, head) { + copy = drm_mode_duplicate(dev, iter); + if (!copy) + return -ENOMEM; + list_add_tail(©->head, &display->modes); + if (!display->mode && drm_mode_equal(iter, mode)) + display->mode = copy; + } + } else { + /* Combined tile mode. Only the default one for now */ + copy = drm_cvt_mode(dev, hdisplay, vdisplay, vrefresh, false, false, false); + if (!copy) + return -ENOMEM; + list_add_tail(©->head, &display->modes); + display->mode = copy; + } + + return 0; +} + +static struct drm_client_display * +drm_client_find_display_default(struct drm_device *dev, unsigned int width, unsigned int height) { struct drm_client_display_offset *offsets; struct drm_client_display *display; @@ -695,25 +749,6 @@ drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int int i, connector_count; bool *enabled;
- DRM_DEBUG_KMS("\n"); - - if (!width) - width = dev->mode_config.max_width; - if (!height) - height = dev->mode_config.max_height; - - mutex_lock(&dev->mode_config.mutex); - if (!drm_client_probe_connector_modes(dev, width, height)) - DRM_DEBUG_KMS("No connectors reported connected with modes\n"); - - if (dev->driver->initial_client_display) { - display = dev->driver->initial_client_display(dev, width, height); - if (display) { - mutex_unlock(&dev->mode_config.mutex); - return display; - } - } - connector_count = drm_connector_get_all(dev, &connectors); if (connector_count < 1) return NULL; @@ -772,7 +807,6 @@ drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int } } out: - mutex_unlock(&dev->mode_config.mutex); drm_connector_put_all(connectors, connector_count); kfree(crtcs); kfree(modes); @@ -781,4 +815,57 @@ drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int
return display; } + +/** + * drm_client_find_display() - Find display + * @dev: DRM device + * @width: Maximum display mode width (optional) + * @height: Maximum display mode height (optional) + * + * This function returns a display the client can use if one is found. + * + * Free resources by calling drm_client_display_free(). + * + * Returns: + * A &drm_client_display on success, NULL if no connectors are found + * or error pointer on failure. + */ +struct drm_client_display * +drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height) +{ + struct drm_client_display *display = NULL; + int ret; + + DRM_DEBUG_KMS("\n"); + + if (!width) + width = dev->mode_config.max_width; + if (!height) + height = dev->mode_config.max_height; + + mutex_lock(&dev->mode_config.mutex); + + if (!drm_client_probe_connector_modes(dev, width, height)) + DRM_DEBUG_KMS("No connectors reported connected with modes\n"); + + if (dev->driver->initial_client_display) + display = dev->driver->initial_client_display(dev, width, height); + + if (!display) + display = drm_client_find_display_default(dev, width, height); + + if (IS_ERR_OR_NULL(display)) + goto out_unlock; + + ret = drm_client_display_copy_modes(display); + if (ret) { + drm_client_display_free(display); + display = ERR_PTR(ret); + } + +out_unlock: + mutex_unlock(&dev->mode_config.mutex); + + return display; +} EXPORT_SYMBOL(drm_client_find_display); diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index 3befd879a0b0..524f793d6e7b 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -3,9 +3,12 @@ #ifndef _DRM_CLIENT_H_ #define _DRM_CLIENT_H_
+#include <linux/types.h> + struct drm_connector; struct drm_crtc; struct drm_device; +struct drm_display_mode; struct drm_mode_set; struct drm_plane;
@@ -33,6 +36,20 @@ struct drm_client_display { * Number of modesets */ unsigned int modeset_count; + + /** + * @modes: + * + * Display modes available on this display. + */ + struct list_head modes; + + /** + * @mode: + * + * The current display mode. + */ + struct drm_display_mode *mode; };
struct drm_client_display *drm_client_display_create(struct drm_device *dev); @@ -51,4 +68,12 @@ int drm_client_display_dpms(struct drm_client_display *display, int mode); struct drm_client_display * drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int height);
+/** + * drm_client_display_for_each_mode - Iterate over the available display modes + * @mode: A @drm_display_mode loop cursor + * @display: Client display + */ +#define drm_client_display_for_each_mode(mode, display) \ + list_for_each_entry(mode, &display->modes, head) + #endif
The modesetting code is already present, this adds the rest of the API.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_client.c | 573 +++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_debugfs.c | 7 + drivers/gpu/drm/drm_drv.c | 11 + drivers/gpu/drm/drm_file.c | 3 + drivers/gpu/drm/drm_probe_helper.c | 3 + drivers/gpu/drm/drm_sysfs.c | 20 ++ include/drm/drm_client.h | 103 +++++++ include/drm/drm_device.h | 4 + 8 files changed, 724 insertions(+)
diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index bce1630a0db2..760f1795f812 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -8,7 +8,9 @@ * Copyright (c) 2007 Dave Airlie airlied@linux.ie */
+#include <linux/dma-buf.h> #include <linux/list.h> +#include <linux/mutex.h> #include <linux/slab.h>
#include <drm/drm_atomic.h> @@ -17,14 +19,280 @@ #include <drm/drm_connector.h> #include <drm/drm_crtc.h> #include <drm/drm_device.h> +#include <drm/drm_file.h> #include <drm/drm_modes.h>
+#include "drm_crtc_internal.h" #include "drm_internal.h"
struct drm_client_display_offset { int x, y; };
+static int drm_client_alloc_file(struct drm_client_dev *client) +{ + struct drm_device *dev = client->dev; + struct drm_file *file; + + file = drm_file_alloc(dev->primary); + if (IS_ERR(file)) + return PTR_ERR(file); + + drm_dev_get(dev); + + mutex_lock(&dev->filelist_mutex); + list_add(&file->lhead, &dev->filelist_internal); + mutex_unlock(&dev->filelist_mutex); + + client->file = file; + + return 0; +} + +static void drm_client_free_file(struct drm_client_dev *client) +{ + struct drm_device *dev = client->dev; + + mutex_lock(&dev->filelist_mutex); + list_del(&client->file->lhead); + mutex_unlock(&dev->filelist_mutex); + + drm_file_free(client->file); + drm_dev_put(dev); +} + +struct drm_client_dev * +drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs) +{ + struct drm_client_dev *client; + int ret; + + if (WARN_ON(!funcs->name)) + return ERR_PTR(-EINVAL); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->dev = dev; + client->funcs = funcs; + + ret = drm_client_alloc_file(client); + if (ret) { + kfree(client); + return ERR_PTR(ret); + } + + mutex_lock(&dev->clientlist_mutex); + list_add(&client->list, &dev->clientlist); + mutex_unlock(&dev->clientlist_mutex); + + return client; +} +EXPORT_SYMBOL(drm_client_new); + +struct drm_client_dev * +drm_client_new_from_id(unsigned int dev_id, const struct drm_client_funcs *funcs) +{ + struct drm_client_dev *client; + struct drm_minor *minor; + + minor = drm_minor_acquire(dev_id); + if (IS_ERR(minor)) + return ERR_CAST(minor); + + client = drm_client_new(minor->dev, funcs); + + drm_minor_release(minor); + + return client; +} +EXPORT_SYMBOL(drm_client_new_from_id); + +/** + * drm_client_free - Free DRM client resources + * @client: DRM client + * + * This is called automatically on client removal unless the client returns + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does + * this when it can't close &drm_file because userspace has an open fd. + * + * Note: + * If the client can't release it's resources on remove, it needs to hold a + * reference on the driver module to prevent the code from going away. + */ +void drm_client_free(struct drm_client_dev *client) +{ + DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name); + drm_client_free_file(client); + kfree(client); +} +EXPORT_SYMBOL(drm_client_free); + +static void drm_client_remove_locked(struct drm_client_dev *client) +{ + list_del(&client->list); + + if (!client->funcs->remove || !client->funcs->remove(client)) + drm_client_free(client); +} + +static void drm_client_remove_safe(struct drm_device *dev, + struct drm_client_dev *client) +{ + struct drm_client_dev *iter; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(iter, &dev->clientlist, list) { + if (iter == client) { + drm_client_remove_locked(client); + break; + } + } + mutex_unlock(&dev->clientlist_mutex); +} + +/** + * drm_client_remove - Remove client + * @client: Client + * + * Remove a client. + */ +void drm_client_remove(struct drm_client_dev *client) +{ + struct drm_device *dev; + + if (!client) + return; + + dev = client->dev; + drm_dev_get(dev); + drm_client_remove_safe(dev, client); + drm_dev_put(dev); +} +EXPORT_SYMBOL(drm_client_remove); + +struct drm_client_remove_defer { + struct list_head list; + struct drm_device *dev; + struct drm_client_dev *client; +}; + +static LIST_HEAD(drm_client_remove_defer_list); +static DEFINE_MUTEX(drm_client_remove_defer_list_lock); + +static void drm_client_remove_defer_work_fn(struct work_struct *work) +{ + struct drm_client_remove_defer *defer, *tmp; + + mutex_lock(&drm_client_remove_defer_list_lock); + list_for_each_entry_safe(defer, tmp, &drm_client_remove_defer_list, list) { + drm_client_remove_safe(defer->dev, defer->client); + drm_dev_put(defer->dev); + list_del(&defer->list); + kfree(defer); + } + mutex_unlock(&drm_client_remove_defer_list_lock); +} + +static DECLARE_WORK(drm_client_remove_defer_work, drm_client_remove_defer_work_fn); + +/** + * drm_client_remove_defer - Deferred client removal + * @client: Client + * + * Defer client removal to a worker. This makes it possible for a client running + * in a worker to remove itself. + * + * Returns: + * Zero on success, or -ENOMEM on allocation failure. + */ +int drm_client_remove_defer(struct drm_client_dev *client) +{ + struct drm_client_remove_defer *defer; + + if (!client) + return 0; + + defer = kzalloc(sizeof(*defer), GFP_KERNEL); + if (!defer) + return -ENOMEM; + + defer->dev = client->dev; + defer->client = client; + drm_dev_get(client->dev); + + mutex_lock(&drm_client_remove_defer_list_lock); + list_add(&defer->list, &drm_client_remove_defer_list); + mutex_unlock(&drm_client_remove_defer_list_lock); + + schedule_work(&drm_client_remove_defer_work); + + return 0; +} +EXPORT_SYMBOL(drm_client_remove_defer); + +void drm_client_init(void) +{ +} + +void drm_client_exit(void) +{ + flush_work(&drm_client_remove_defer_work); +} + +void drm_client_dev_unregister(struct drm_device *dev) +{ + struct drm_client_dev *client, *tmp; + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry_safe(client, tmp, &dev->clientlist, list) + drm_client_remove_locked(client); + mutex_unlock(&dev->clientlist_mutex); +} + +void drm_client_dev_hotplug(struct drm_device *dev) +{ + struct drm_client_dev *client; + int ret; + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) { + if (!client->funcs->hotplug) + continue; + + ret = client->funcs->hotplug(client); + DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->name, ret); + } + mutex_unlock(&dev->clientlist_mutex); +} +EXPORT_SYMBOL(drm_client_dev_hotplug); + +void drm_client_dev_lastclose(struct drm_device *dev) +{ + struct drm_client_dev *client; + int ret; + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) { + if (!client->funcs->lastclose) + continue; + + ret = client->funcs->lastclose(client); + DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->name, ret); + } + mutex_unlock(&dev->clientlist_mutex); +} + /** * drm_client_display_create() - Create display structure * @dev: DRM device @@ -348,6 +616,90 @@ int drm_client_display_restore(struct drm_client_display *display, bool force) } EXPORT_SYMBOL(drm_client_display_restore);
+/** + * drm_client_display_commit_mode - Commit a mode/fb to the CRTC(s) + * @display: Client display + * @fb: Framebuffer (if NULL the current fb is used) + * @mode: Display mode (if NULL the current mode is used) + * + * Returns: + * Zero on success, negative error code on failure. + */ +int drm_client_display_commit(struct drm_client_display *display, + struct drm_framebuffer *fb, struct drm_display_mode *mode) +{ + struct drm_display_mode *use_mode = NULL; + struct drm_mode_set *modeset; + unsigned int count = 0; + + if (mode) { + struct drm_display_mode *iter; + + drm_client_display_for_each_mode(iter, display) { + if (!use_mode && drm_mode_equal(iter, mode)) + use_mode = iter; + count++; + } + + if (!use_mode) + return -EINVAL; + + /* + * Don't actually set the mode in the single mode case since it + * might be a tiled display which consists of multiple modes. + * Just keep the current mode. + */ + if (count == 1) + use_mode = NULL; + } + + count = 0; + drm_client_display_for_each_modeset(modeset, display) { + if (!modeset->num_connectors) + continue; + + if (fb) + modeset->fb = fb; + + if (use_mode) { + if (WARN_ON(++count > 1)) + return -EINVAL; + + if (modeset->mode) + drm_mode_destroy(display->dev, modeset->mode); + modeset->mode = drm_mode_duplicate(display->dev, use_mode); + if (!modeset->mode) + return -ENOMEM; + } + } + + return drm_client_display_restore(display, false); +} +EXPORT_SYMBOL(drm_client_display_commit); + +struct drm_framebuffer *drm_client_display_current_fb(struct drm_client_display *display) +{ + struct drm_mode_set *modeset; + + drm_client_display_for_each_modeset(modeset, display) { + struct drm_crtc *crtc = modeset->crtc; + struct drm_framebuffer *fb = NULL; + + drm_modeset_lock(&crtc->primary->mutex, NULL); + if (crtc->primary->state && crtc->primary->state->fb) + fb = crtc->primary->state->fb; + else if (!crtc->primary->state && crtc->primary->fb) + fb = crtc->primary->fb; + drm_modeset_unlock(&crtc->primary->mutex); + + if (fb) + return fb; + } + + return NULL; +} +EXPORT_SYMBOL(drm_client_display_current_fb); + static void drm_client_display_dpms_legacy(struct drm_client_display *display, int dpms_mode) { struct drm_device *dev = display->dev; @@ -869,3 +1221,224 @@ drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int return display; } EXPORT_SYMBOL(drm_client_find_display); + +static void drm_client_buffer_delete(struct drm_client_buffer *buffer) +{ + if (!buffer) + return; + + if (buffer->vaddr) + dma_buf_vunmap(buffer->dma_buf, buffer->vaddr); + + if (buffer->dma_buf) + dma_buf_put(buffer->dma_buf); + + drm_mode_destroy_dumb(buffer->client->dev, buffer->handle, buffer->client->file); + kfree(buffer); +} + +/* For testing __close_fd() */ +#include <linux/fdtable.h> + +static struct drm_client_buffer * +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format) +{ + struct drm_mode_create_dumb dumb_args = { }; + struct drm_prime_handle prime_args = { }; + struct drm_client_buffer *buffer; + struct dma_buf *dma_buf; + void *vaddr; + int ret; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + + buffer->client = client; + buffer->width = width; + buffer->height = height; + buffer->format = format; + + dumb_args.width = buffer->width; + dumb_args.height = buffer->height; + dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8; + ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file); + if (ret) + goto err_free; + + buffer->handle = dumb_args.handle; + buffer->pitch = dumb_args.pitch; + buffer->size = dumb_args.size; + + prime_args.handle = dumb_args.handle; + ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file); + if (ret) + goto err_delete; + + dma_buf = dma_buf_get(prime_args.fd); + if (IS_ERR(dma_buf)) { + ret = PTR_ERR(dma_buf); + goto err_delete; + } + + /* + * If called from a worker the dmabuf fd isn't closed and the ref + * doesn't drop to zero on free. + * If I use __close_fd() it's all fine, but that function is not exported. + * + * How do I get rid of this fd when in a worker/kernel thread? + * The fd isn't used beyond this function. + */ +// WARN_ON(__close_fd(current->files, prime_args.fd)); + + pr_info("%s: PF_KTHREAD=%u\n", __func__, !!(current->flags & PF_KTHREAD)); + + buffer->dma_buf = dma_buf; + + vaddr = dma_buf_vmap(dma_buf); + if (!vaddr) { + ret = -ENOMEM; + goto err_delete; + } + + buffer->vaddr = vaddr; + + return buffer; + +err_delete: + drm_client_buffer_delete(buffer); +err_free: + kfree(buffer); + + return ERR_PTR(ret); +} + +static int drm_client_buffer_rmfb(struct drm_client_buffer *buffer) +{ + int ret; + + if (!buffer || !buffer->fb) + return 0; + + ret = drm_mode_rmfb(buffer->client->dev, buffer->fb->base.id, buffer->client->file); + if (ret) + DRM_DEV_ERROR(buffer->client->dev->dev, + "Error removing FB:%u (%d)\n", buffer->fb->base.id, ret); + + buffer->fb = NULL; + + return 0; +} + +static int drm_client_buffer_addfb(struct drm_client_buffer *buffer, + struct drm_display_mode *mode) +{ + struct drm_client_dev *client = buffer->client; + struct drm_mode_fb_cmd2 fb_req = { }; + int ret; + + if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height) + return -EINVAL; + + fb_req.width = mode->hdisplay; + fb_req.height = mode->vdisplay; + fb_req.pixel_format = buffer->format; + fb_req.handles[0] = buffer->handle; + fb_req.pitches[0] = buffer->pitch; + + ret = drm_mode_addfb2(client->dev, &fb_req, client->file, client->funcs->name); + if (ret) + return ret; + + buffer->fb = drm_framebuffer_lookup(client->dev, buffer->client->file, fb_req.fb_id); + if (WARN_ON(!buffer->fb)) + return -ENOENT; + + /* drop the reference we picked up in framebuffer lookup */ + drm_framebuffer_put(buffer->fb); + + return 0; +} + +/** + * drm_client_framebuffer_create - Create a client framebuffer + * @client: DRM client + * @mode: Display mode to create a buffer for + * @format: Buffer format + * + * This function creates a &drm_client_buffer which consists of a + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf + * exported to aquire a virtual address which is stored in + * &drm_client_buffer->vaddr. + * Call drm_client_framebuffer_delete() to free the buffer. + * + * Returns: + * Pointer to a client buffer or an error pointer on failure. + */ +struct drm_client_buffer * +drm_client_framebuffer_create(struct drm_client_dev *client, + struct drm_display_mode *mode, u32 format) +{ + struct drm_client_buffer *buffer; + int ret; + + buffer = drm_client_buffer_create(client, mode->hdisplay, + mode->vdisplay, format); + if (IS_ERR(buffer)) + return buffer; + + ret = drm_client_buffer_addfb(buffer, mode); + if (ret) { + drm_client_buffer_delete(buffer); + return ERR_PTR(ret); + } + + return buffer; +} +EXPORT_SYMBOL(drm_client_framebuffer_create); + +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer) +{ + drm_client_buffer_rmfb(buffer); + drm_client_buffer_delete(buffer); +} +EXPORT_SYMBOL(drm_client_framebuffer_delete); + +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, + struct drm_clip_rect *rect) +{ + if (!buffer->fb || !buffer->fb->funcs->dirty) + return 0; + + return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file, + 0, 0, rect, rect ? 1 : 0); +} +EXPORT_SYMBOL(drm_client_framebuffer_flush); + +#ifdef CONFIG_DEBUG_FS +static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data) +{ + struct drm_info_node *node = m->private; + struct drm_device *dev = node->minor->dev; + struct drm_printer p = drm_seq_file_printer(m); + struct drm_client_dev *client; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) + drm_printf(&p, "%s\n", client->funcs->name); + mutex_unlock(&dev->clientlist_mutex); + + return 0; +} + +static const struct drm_info_list drm_client_debugfs_list[] = { + { "internal_clients", drm_client_debugfs_internal_clients, 0 }, +}; + +int drm_client_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(drm_client_debugfs_list, + ARRAY_SIZE(drm_client_debugfs_list), + minor->debugfs_root, minor); +} +#endif diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c index b2482818fee8..50a20bfc07ea 100644 --- a/drivers/gpu/drm/drm_debugfs.c +++ b/drivers/gpu/drm/drm_debugfs.c @@ -28,6 +28,7 @@ #include <linux/slab.h> #include <linux/export.h>
+#include <drm/drm_client.h> #include <drm/drm_debugfs.h> #include <drm/drm_edid.h> #include <drm/drm_atomic.h> @@ -164,6 +165,12 @@ int drm_debugfs_init(struct drm_minor *minor, int minor_id, DRM_ERROR("Failed to create framebuffer debugfs file\n"); return ret; } + + ret = drm_client_debugfs_init(minor); + if (ret) { + DRM_ERROR("Failed to create client debugfs file\n"); + return ret; + } }
if (dev->driver->debugfs_init) { diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 32a83b41ab61..6f21bafb29be 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -34,6 +34,7 @@ #include <linux/slab.h> #include <linux/srcu.h>
+#include <drm/drm_client.h> #include <drm/drm_drv.h> #include <drm/drmP.h>
@@ -507,6 +508,8 @@ int drm_dev_init(struct drm_device *dev, dev->driver = driver;
INIT_LIST_HEAD(&dev->filelist); + INIT_LIST_HEAD(&dev->filelist_internal); + INIT_LIST_HEAD(&dev->clientlist); INIT_LIST_HEAD(&dev->ctxlist); INIT_LIST_HEAD(&dev->vmalist); INIT_LIST_HEAD(&dev->maplist); @@ -516,6 +519,7 @@ int drm_dev_init(struct drm_device *dev, spin_lock_init(&dev->event_lock); mutex_init(&dev->struct_mutex); mutex_init(&dev->filelist_mutex); + mutex_init(&dev->clientlist_mutex); mutex_init(&dev->ctxlist_mutex); mutex_init(&dev->master_mutex);
@@ -572,6 +576,7 @@ int drm_dev_init(struct drm_device *dev, err_free: mutex_destroy(&dev->master_mutex); mutex_destroy(&dev->ctxlist_mutex); + mutex_destroy(&dev->clientlist_mutex); mutex_destroy(&dev->filelist_mutex); mutex_destroy(&dev->struct_mutex); return ret; @@ -607,6 +612,7 @@ void drm_dev_fini(struct drm_device *dev)
mutex_destroy(&dev->master_mutex); mutex_destroy(&dev->ctxlist_mutex); + mutex_destroy(&dev->clientlist_mutex); mutex_destroy(&dev->filelist_mutex); mutex_destroy(&dev->struct_mutex); kfree(dev->unique); @@ -862,6 +868,8 @@ void drm_dev_unregister(struct drm_device *dev) { struct drm_map_list *r_list, *list_temp;
+ drm_client_dev_unregister(dev); + if (drm_core_check_feature(dev, DRIVER_LEGACY)) drm_lastclose(dev);
@@ -968,6 +976,7 @@ static const struct file_operations drm_stub_fops = {
static void drm_core_exit(void) { + drm_client_exit(); unregister_chrdev(DRM_MAJOR, "drm"); debugfs_remove(drm_debugfs_root); drm_sysfs_destroy(); @@ -1001,6 +1010,8 @@ static int __init drm_core_init(void) if (ret < 0) goto error;
+ drm_client_init(); + drm_core_init_complete = true;
DRM_DEBUG("Initialized\n"); diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c index 55505378df47..bcc688e58776 100644 --- a/drivers/gpu/drm/drm_file.c +++ b/drivers/gpu/drm/drm_file.c @@ -35,6 +35,7 @@ #include <linux/slab.h> #include <linux/module.h>
+#include <drm/drm_client.h> #include <drm/drm_file.h> #include <drm/drmP.h>
@@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
if (drm_core_check_feature(dev, DRIVER_LEGACY)) drm_legacy_dev_reinit(dev); + + drm_client_dev_lastclose(dev); }
/** diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index 527743394150..26be57e28a9d 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -33,6 +33,7 @@ #include <linux/moduleparam.h>
#include <drm/drmP.h> +#include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_fourcc.h> #include <drm/drm_crtc_helper.h> @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev) drm_sysfs_hotplug_event(dev); if (dev->mode_config.funcs->output_poll_changed) dev->mode_config.funcs->output_poll_changed(dev); + + drm_client_dev_hotplug(dev); } EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index 1c5b5ce1fd7f..1fc066c41861 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -18,6 +18,7 @@ #include <linux/err.h> #include <linux/export.h>
+#include <drm/drm_client.h> #include <drm/drm_sysfs.h> #include <drm/drmP.h> #include "drm_internal.h" @@ -320,6 +321,24 @@ void drm_sysfs_hotplug_event(struct drm_device *dev) } EXPORT_SYMBOL(drm_sysfs_hotplug_event);
+static ssize_t remove_internal_clients_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct drm_minor *minor = dev_get_drvdata(dev); + + drm_client_dev_unregister(minor->dev); + + return len; +} +static DEVICE_ATTR_WO(remove_internal_clients); + +static struct attribute *minor_attrs[] = { + &dev_attr_remove_internal_clients.attr, + NULL, +}; +ATTRIBUTE_GROUPS(minor); + static void drm_sysfs_release(struct device *dev) { kfree(dev); @@ -347,6 +366,7 @@ struct device *drm_sysfs_minor_alloc(struct drm_minor *minor) kdev->class = drm_class; kdev->type = &drm_sysfs_device_minor; kdev->parent = minor->dev->dev; + kdev->groups = minor_groups; kdev->release = drm_sysfs_release; dev_set_drvdata(kdev, minor);
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index 524f793d6e7b..6fd2fcaae826 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -5,13 +5,91 @@
#include <linux/types.h>
+struct drm_clip_rect; struct drm_connector; struct drm_crtc; struct drm_device; struct drm_display_mode; +struct drm_framebuffer; +struct drm_minor; struct drm_mode_set; struct drm_plane;
+struct drm_client_dev; + +/** + * struct drm_client_funcs - DRM client callbacks + */ +struct drm_client_funcs { + /** + * @name: + * + * Name of the client. Mandatory. + */ + const char *name; + + /** + * @remove: + * + * Called when a &drm_device is unregistered or the client is + * unregistered. If zero is returned drm_client_free() is called + * automatically. If the client can't drop it's resources it should + * return non-zero and call drm_client_free() later. + * + * This callback is optional. + */ + int (*remove)(struct drm_client_dev *client); + + /** + * @lastclose: + * + * Called on drm_lastclose(). The first client instance in the list + * that returns zero gets the privilege to restore and no more clients + * are called. + * + * This callback is optional. + */ + int (*lastclose)(struct drm_client_dev *client); + + /** + * @hotplug: + * + * Called on drm_kms_helper_hotplug_event(). + * + * This callback is optional. + */ + int (*hotplug)(struct drm_client_dev *client); +}; + +/** + * struct drm_client_dev - DRM client instance + */ +struct drm_client_dev { + struct list_head list; + struct drm_device *dev; + const struct drm_client_funcs *funcs; + struct drm_file *file; + unsigned int file_ref_count; + void *private; +}; + +struct drm_client_dev * +drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs); +struct drm_client_dev * +drm_client_new_from_id(unsigned int dev_id, const struct drm_client_funcs *funcs); +void drm_client_remove(struct drm_client_dev *client); +int drm_client_remove_defer(struct drm_client_dev *client); +void drm_client_free(struct drm_client_dev *client); + +void drm_client_dev_unregister(struct drm_device *dev); +void drm_client_dev_hotplug(struct drm_device *dev); +void drm_client_dev_lastclose(struct drm_device *dev); + +void drm_client_init(void); +void drm_client_exit(void); + +int drm_client_debugfs_init(struct drm_minor *minor); + /** * struct drm_client_display - DRM client display */ @@ -76,4 +154,29 @@ drm_client_find_display(struct drm_device *dev, unsigned int width, unsigned int #define drm_client_display_for_each_mode(mode, display) \ list_for_each_entry(mode, &display->modes, head)
+int drm_client_display_commit(struct drm_client_display *display, + struct drm_framebuffer *fb, struct drm_display_mode *mode); +struct drm_framebuffer *drm_client_display_current_fb(struct drm_client_display *display); + +struct drm_client_buffer { + struct drm_client_dev *client; + u32 width; + u32 height; + u32 format; + u32 handle; + u32 pitch; + u64 size; + struct dma_buf *dma_buf; + void *vaddr; + struct drm_framebuffer *fb; +}; + +struct drm_client_buffer * +drm_client_framebuffer_create(struct drm_client_dev *client, + struct drm_display_mode *mode, u32 format); +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer); + +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, + struct drm_clip_rect *rect); + #endif diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h index 3a0eac2885b7..17edadf8b691 100644 --- a/include/drm/drm_device.h +++ b/include/drm/drm_device.h @@ -74,6 +74,10 @@ struct drm_device {
struct mutex filelist_mutex; struct list_head filelist; + struct list_head filelist_internal; + + struct mutex clientlist_mutex; + struct list_head clientlist;
/** \name Memory management */ /*@{ */
Avoid pinning the module when exporting a GEM object as a dmabuf. This makes it possible to unload drivers that has in-kernel clients using it. The client is removed on drm_dev_unregister() so no need to pin the driver.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_prime.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c index e6052ab2bec4..f9dbe3b9db20 100644 --- a/drivers/gpu/drm/drm_prime.c +++ b/drivers/gpu/drm/drm_prime.c @@ -567,6 +567,30 @@ struct dma_buf *drm_gem_prime_export(struct drm_device *dev, .flags = flags, .priv = obj, }; + bool is_internal = false; + struct drm_file *file; + + mutex_lock(&dev->filelist_mutex); + list_for_each_entry(file, &dev->filelist_internal, lhead) { + struct drm_gem_object *iter; + int id; + + spin_lock(&file->table_lock); + idr_for_each_entry(&file->object_idr, iter, id) { + if (iter == obj) { + is_internal = true; + break; + } + } + spin_unlock(&file->table_lock); + + if (is_internal) + break; + } + mutex_unlock(&dev->filelist_mutex); + + if (is_internal) + exp_info.owner = NULL;
if (dev->driver->gem_prime_res_obj) exp_info.resv = dev->driver->gem_prime_res_obj(obj);
These helpers keep track of fbdev users and drm_driver.last_close will only restore fbdev when actually in use. Additionally the display is turned off when the last user is closing. fbcon is a user in this context.
If struct fb_ops is defined in a library, fb_open() takes a ref on the library (fb_ops.owner) instead of the driver module. Fix that by ensuring that the driver module is pinned.
The functions are not added to the DRM_FB_HELPER_DEFAULT_OPS() macro, because some of its users do set fb_open/release themselves.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_fb_helper.c | 54 ++++++++++++++++++++++++++++++++++++++++- include/drm/drm_fb_helper.h | 29 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 98e5bc92c9f2..b1124c08b1ed 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -177,7 +177,7 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper) if (!drm_fbdev_emulation || !fb_helper) return -ENODEV;
- if (READ_ONCE(fb_helper->deferred_setup)) + if (READ_ONCE(fb_helper->deferred_setup) || !READ_ONCE(fb_helper->open_count)) return 0;
mutex_lock(&fb_helper->lock); @@ -368,6 +368,7 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, INIT_WORK(&helper->dirty_work, drm_fb_helper_dirty_work); helper->dirty_clip.x1 = helper->dirty_clip.y1 = ~0; mutex_init(&helper->lock); + helper->open_count = 1; helper->funcs = funcs; helper->dev = dev; } @@ -620,6 +621,53 @@ int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper) } EXPORT_SYMBOL(drm_fb_helper_defio_init);
+/** + * drm_fb_helper_fb_open - implementation for &fb_ops.fb_open + * @info: fbdev registered by the helper + * @user: 1=userspace, 0=fbcon + * + * Increase fbdev use count. + * If &fb_ops is wrapped in a library, pin the driver module. + */ +int drm_fb_helper_fb_open(struct fb_info *info, int user) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_device *dev = fb_helper->dev; + + if (info->fbops->owner != dev->driver->fops->owner) { + if (!try_module_get(dev->driver->fops->owner)) + return -ENODEV; + } + + fb_helper->open_count++; + + return 0; +} +EXPORT_SYMBOL(drm_fb_helper_fb_open); + +/** + * drm_fb_helper_fb_release - implementation for &fb_ops.fb_release + * @info: fbdev registered by the helper + * @user: 1=userspace, 0=fbcon + * + * Decrease fbdev use count and turn off if there are no users left. + * If &fb_ops is wrapped in a library, unpin the driver module. + */ +int drm_fb_helper_fb_release(struct fb_info *info, int user) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_device *dev = fb_helper->dev; + + if (!(--fb_helper->open_count)) + drm_fb_helper_dpms(info, DRM_MODE_DPMS_OFF); + + if (info->fbops->owner != dev->driver->fops->owner) + module_put(dev->driver->fops->owner); + + return 0; +} +EXPORT_SYMBOL(drm_fb_helper_fb_release); + /** * drm_fb_helper_sys_read - wrapper around fb_sys_read * @info: fb_info struct pointer @@ -1436,6 +1484,10 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, if (ret < 0) return ret;
+ /* Block restore without users if we do track it */ + if (fb_helper->fbdev->fbops->fb_open == drm_fb_helper_fb_open) + fb_helper->open_count = 0; + strcpy(fb_helper->fb->comm, "[fbcon]"); return 0; } diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 5f66f253a97b..330983975d5e 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -184,6 +184,22 @@ struct drm_fb_helper { * See also: @deferred_setup */ int preferred_bpp; + + /** + * @open_count: + * + * Keeps track of fbdev use to know when to not restore fbdev and to + * disable the pipeline when the last user is gone. + * + * Drivers that use drm_fb_helper_fb_open() as their .fb_open + * callback will get an initial value of 0 and get restore based on + * actual use. Others will get an initial value of 1 which means that + * fbdev will always be restored. Drivers that call + * drm_fb_helper_fb_open() in their .fb_open, thus needs to set the + * initial value to 0 themselves in their &drm_fb_helper_funcs->fb_probe + * callback. + */ + unsigned int open_count; };
/** @@ -230,6 +246,9 @@ void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagelist); int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper);
+int drm_fb_helper_fb_open(struct fb_info *info, int user); +int drm_fb_helper_fb_release(struct fb_info *info, int user); + ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos); ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf, @@ -376,6 +395,16 @@ static inline int drm_fb_helper_defio_init(struct drm_fb_helper *fb_helper) return -ENODEV; }
+static inline int drm_fb_helper_fb_open(struct fb_info *info, int user) +{ + return -ENODEV; +} + +static inline int drm_fb_helper_fb_release(struct fb_info *info, int user) +{ + return -ENODEV; +} + static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos)
This adds generic fbdev emulation for drivers that supports dumb buffers which they can export.
All the driver has to do is call drm_fbdev_generic_setup().
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_fb_helper.c | 255 ++++++++++++++++++++++++++++++++++++++++ include/drm/drm_fb_helper.h | 20 ++++ 2 files changed, 275 insertions(+)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index b1124c08b1ed..1954de5b13e0 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -30,6 +30,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/console.h> +#include <linux/dma-buf.h> #include <linux/kernel.h> #include <linux/sysrq.h> #include <linux/slab.h> @@ -1995,6 +1996,260 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev) } EXPORT_SYMBOL(drm_fb_helper_output_poll_changed);
+static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *fb_helper = info->par; + + return dma_buf_mmap(fb_helper->buffer->dma_buf, vma, 0); +} + +/* + * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of + * unregister_framebuffer() or fb_release(). + */ +static void drm_fbdev_fb_destroy(struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct fb_ops *fbops = NULL; + + DRM_DEBUG("\n"); + + if (fb_helper->fbdev->fbdefio) + fbops = fb_helper->fbdev->fbops; + + drm_fb_helper_fini(fb_helper); + drm_client_framebuffer_delete(fb_helper->buffer); + drm_client_free(fb_helper->client); + kfree(fb_helper); + kfree(fbops); +} + +static struct fb_ops drm_fbdev_fb_ops = { + /* + * No need to set owner, this module is already pinned by the driver. + * A reference is taken on the driver module in drm_fb_helper_fb_open() + * to prevent the driver going away with open fd's. + */ + DRM_FB_HELPER_DEFAULT_OPS, + .fb_open = drm_fb_helper_fb_open, + .fb_release = drm_fb_helper_fb_release, + .fb_destroy = drm_fbdev_fb_destroy, + .fb_mmap = drm_fbdev_fb_mmap, + .fb_read = drm_fb_helper_sys_read, + .fb_write = drm_fb_helper_sys_write, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, +}; + +static struct fb_deferred_io drm_fbdev_defio = { + .delay = HZ / 20, + .deferred_io = drm_fb_helper_deferred_io, +}; + +/* Hack to test tinydrm before converting to vmalloc buffers */ +static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + fb_deferred_io_mmap(info, vma); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} + +static int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_client_dev *client = fb_helper->client; + struct drm_display_mode sizes_mode = { + .hdisplay = sizes->surface_width, + .vdisplay = sizes->surface_height, + }; + struct drm_client_buffer *buffer; + struct drm_framebuffer *fb; + struct fb_info *fbi; + u32 format; + int ret; + + DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + + format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + buffer = drm_client_framebuffer_create(client, &sizes_mode, format); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + fb_helper->buffer = buffer; + fb_helper->fb = buffer->fb; + fb = buffer->fb; + + fbi = drm_fb_helper_alloc_fbi(fb_helper); + if (IS_ERR(fbi)) { + ret = PTR_ERR(fbi); + goto err_free_buffer; + } + + fbi->par = fb_helper; + fbi->fbops = &drm_fbdev_fb_ops; + fbi->screen_size = fb->height * fb->pitches[0]; + fbi->fix.smem_len = fbi->screen_size; + fbi->screen_buffer = buffer->vaddr; + strcpy(fbi->fix.id, "DRM emulated"); + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth); + drm_fb_helper_fill_var(fbi, fb_helper, sizes->fb_width, sizes->fb_height); + + /* + * Drivers that set the dirty callback: + * - Doesn't use defio: + * i915, virtio, rockchip + * - defio with vmalloc buffer blitted on the real one: + * vmwgfx + * - defio is disabled because it doesn't work with shmem: + * udl + * - defio with special dirty callback for fbdev, uses vmalloc for fbdev: + * qxl + * - defio with cma buffer, will move to vmalloc buffers: + * tinydrm + * + * TODO: + * Maybe add vmalloc shadow buffer support. + */ + + if (fb->funcs->dirty) { + struct fb_ops *fbops; + + /* + * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per + * instance version is necessary. + */ + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbops) { + ret = -ENOMEM; + goto err_fb_info_destroy; + } + + *fbops = *fbi->fbops; + fbi->fbops = fbops; + + fbi->fbdefio = &drm_fbdev_defio; + + /* Hack so I can test with tinydrm */ + fbi->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr)); + + fb_deferred_io_init(fbi); + + /* Hack so I can test with tinydrm */ + fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap; + } + + return 0; + +err_fb_info_destroy: + drm_fb_helper_fini(fb_helper); +err_free_buffer: + drm_client_framebuffer_delete(buffer); + + return ret; +} + +static const struct drm_fb_helper_funcs drm_fb_helper_generic_funcs = { + .fb_probe = drm_fb_helper_generic_probe, +}; + +static int drm_fbdev_client_remove(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + if (!fb_helper->fbdev) { + kfree(fb_helper); + return 0; + } + + unregister_framebuffer(fb_helper->fbdev); + + /* + * If userspace is closed the client is now freed by + * drm_fbdev_fb_destroy(), otherwise it will be freed on the last close. + * Return 1 to tell that freeing is taken care of. + */ + + return 1; +} + +static int drm_fbdev_client_lastclose(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper); + + return 0; +} + +static int drm_fbdev_client_hotplug(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + if (fb_helper->fbdev) + return 0; + + return drm_fb_helper_fbdev_setup(client->dev, fb_helper, + &drm_fb_helper_generic_funcs, + fb_helper->preferred_bpp, 0); +} + +static const struct drm_client_funcs drm_fbdev_client_funcs = { + .name = "fbdev", + .remove = drm_fbdev_client_remove, + .lastclose = drm_fbdev_client_lastclose, + .hotplug = drm_fbdev_client_hotplug, +}; + +/** + * drm_fb_helper_generic_fbdev_setup() - Setup generic fbdev emulation + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device. + * @dev->mode_config.preferred_depth is used if this is zero. + * + * This function sets up generic fbdev emulation for drivers that supports + * dumb buffers which can be exported. + * + * Restore, hotplug events and teardown are all taken care of. Drivers that does + * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. + * Simple drivers might use drm_mode_config_helper_suspend(). + * + * Returns: + * Zero on success or negative error code on failure. + */ +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ + struct drm_fb_helper *fb_helper; + struct drm_client_dev *client; + + if (!drm_fbdev_emulation) + return 0; + + fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); + if (!fb_helper) + return -ENOMEM; + + client = drm_client_new(dev, &drm_fbdev_client_funcs); + if (IS_ERR(client)) { + kfree(fb_helper); + return PTR_ERR(client); + } + + client->private = fb_helper; + fb_helper->client = client; + fb_helper->preferred_bpp = preferred_bpp; + + drm_fbdev_client_hotplug(client); + + return 0; +} +EXPORT_SYMBOL(drm_fbdev_generic_setup); + /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT) * but the module doesn't depend on any fb console symbols. At least * attempt to load fbcon to avoid leaving the system without a usable console. diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 330983975d5e..711da1747836 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -126,6 +126,20 @@ struct drm_fb_helper { */ struct drm_client_display *display;
+ /** + * @client: + * + * DRM client used by the generic fbdev emulation. + */ + struct drm_client_dev *client; + + /** + * @buffer: + * + * Framebuffer used by the generic fbdev emulation. + */ + struct drm_client_buffer *buffer; + const struct drm_fb_helper_funcs *funcs; struct fb_info *fbdev; u32 pseudo_palette[17]; @@ -219,6 +233,7 @@ struct drm_fb_helper { .fb_ioctl = drm_fb_helper_ioctl
#ifdef CONFIG_DRM_FBDEV_EMULATION +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp); void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, const struct drm_fb_helper_funcs *funcs); int drm_fb_helper_init(struct drm_device *dev, @@ -297,6 +312,11 @@ void drm_fb_helper_fbdev_teardown(struct drm_device *dev); void drm_fb_helper_lastclose(struct drm_device *dev); void drm_fb_helper_output_poll_changed(struct drm_device *dev); #else +static inline int +drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ +} + static inline void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, const struct drm_fb_helper_funcs *funcs)
Add a notifier that fires when a new DRM device is registered. This can be used by the bootsplash client to connect to all devices.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_drv.c | 32 ++++++++++++++++++++++++++++++++ include/drm/drm_drv.h | 4 ++++ 2 files changed, 36 insertions(+)
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 6f21bafb29be..e42ce320ad07 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -31,6 +31,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/mount.h> +#include <linux/notifier.h> #include <linux/slab.h> #include <linux/srcu.h>
@@ -79,6 +80,8 @@ static struct dentry *drm_debugfs_root;
DEFINE_STATIC_SRCU(drm_unplug_srcu);
+static BLOCKING_NOTIFIER_HEAD(drm_dev_notifier); + /* * DRM Minors * A DRM device can provide several char-dev interfaces on the DRM-Major. Each @@ -837,6 +840,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags) dev->dev ? dev_name(dev->dev) : "virtual device", dev->primary->index);
+ blocking_notifier_call_chain(&drm_dev_notifier, 0, dev); + goto out_unlock;
err_minors: @@ -894,6 +899,33 @@ void drm_dev_unregister(struct drm_device *dev) } EXPORT_SYMBOL(drm_dev_unregister);
+/** + * drm_dev_register_notifier - Register a notifier for new DRM devices + * @nb: Notifier block + * + * Register a notifier that fires when a new &drm_device is registered. + * + * Note: + * Users of this function has to be linked into drm.ko. This is done to make + * life simple avoiding tricky race situations. + */ +void drm_dev_register_notifier(struct notifier_block *nb) +{ + /* Currently this can't fail, but catch it in case this changes */ + WARN_ON(blocking_notifier_chain_register(&drm_dev_notifier, nb)); +} + +/** + * drm_dev_unregister_notifier - Unregister DRM device notifier + * @nb: Notifier block + * + * This is a no-op if the notifier isn't registered. + */ +void drm_dev_unregister_notifier(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&drm_dev_notifier, nb); +} + /** * drm_dev_set_unique - Set the unique name of a DRM device * @dev: device of which to set the unique name diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index 13356e6fd40c..5e6c6ed0d59d 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -40,6 +40,7 @@ struct drm_minor; struct dma_buf_attachment; struct drm_display_mode; struct drm_mode_create_dumb; +struct notifier_block; struct drm_printer;
/* driver capabilities and requirements mask */ @@ -641,6 +642,9 @@ struct drm_device *drm_dev_alloc(struct drm_driver *driver, int drm_dev_register(struct drm_device *dev, unsigned long flags); void drm_dev_unregister(struct drm_device *dev);
+void drm_dev_register_notifier(struct notifier_block *nb); +void drm_dev_unregister_notifier(struct notifier_block *nb); + void drm_dev_get(struct drm_device *dev); void drm_dev_put(struct drm_device *dev); void drm_dev_unref(struct drm_device *dev);
A hack to test the client API.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/client/Kconfig | 9 ++ drivers/gpu/drm/client/drm_bootsplash.c | 248 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/client/internal.h | 19 +++ drivers/gpu/drm/drm_client.c | 4 + 6 files changed, 283 insertions(+) create mode 100644 drivers/gpu/drm/client/Kconfig create mode 100644 drivers/gpu/drm/client/drm_bootsplash.c create mode 100644 drivers/gpu/drm/client/internal.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 757825ac60df..1328202ce17d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -154,6 +154,8 @@ config DRM_SCHED tristate depends on DRM
+source "drivers/gpu/drm/client/Kconfig" + source "drivers/gpu/drm/i2c/Kconfig"
source "drivers/gpu/drm/arm/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index d25afa136d8f..388527093f80 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -30,6 +30,7 @@ drm-$(CONFIG_OF) += drm_of.o drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o
drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_probe_helper.o \ drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \ diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig new file mode 100644 index 000000000000..6b01f2e51fb3 --- /dev/null +++ b/drivers/gpu/drm/client/Kconfig @@ -0,0 +1,9 @@ +menu "DRM Clients" + depends on DRM + +config DRM_CLIENT_BOOTSPLASH + bool "DRM Bootsplash" + help + DRM Bootsplash + +endmenu diff --git a/drivers/gpu/drm/client/drm_bootsplash.c b/drivers/gpu/drm/client/drm_bootsplash.c new file mode 100644 index 000000000000..bec3105f9b02 --- /dev/null +++ b/drivers/gpu/drm/client/drm_bootsplash.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/keyboard.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <drm/drm_client.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_modes.h> +#include <drm/drm_print.h> + +// drm_lastclose() +#include <drm/drmP.h> +#include "drm_internal.h" + +static bool drm_bootsplash_enabled = true; +module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600); +MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client [default=true]"); + +struct drm_bootsplash { + struct drm_client_dev *client; + struct drm_client_display *display; + struct drm_client_buffer *buffer[2]; + struct work_struct worker; + bool stop; +}; + +static bool drm_bootsplash_key_pressed; + +static int drm_bootsplash_keyboard_notifier_call(struct notifier_block *blk, + unsigned long code, void *_param) +{ + /* Any key is good */ + drm_bootsplash_key_pressed = true; + + return NOTIFY_OK; +} + +static struct notifier_block drm_bootsplash_keyboard_notifier_block = { + .notifier_call = drm_bootsplash_keyboard_notifier_call, +}; + +static u32 drm_bootsplash_color_table[3] = { + 0x00ff0000, 0x0000ff00, 0x000000ff, +}; + +/* Draw a box with changing colors */ +static void +drm_bootsplash_draw(struct drm_client_buffer *buffer, unsigned int sequence) +{ + unsigned int x, y; + u32 *pix; + + pix = buffer->vaddr; + pix += ((buffer->height / 2) - 50) * buffer->width; + pix += (buffer->width / 2) - 50; + + for (y = 0; y < 100; y++) { + for (x = 0; x < 100; x++) + *pix++ = drm_bootsplash_color_table[sequence]; + pix += buffer->width - 100; + } +} + +static void drm_bootsplash_worker(struct work_struct *work) +{ + struct drm_bootsplash *splash = container_of(work, struct drm_bootsplash, worker); + struct drm_device *dev = splash->client->dev; + unsigned int i = 0, sequence = 0; + struct drm_framebuffer *fb; + int ret = 0; + + while (!splash->stop && !drm_bootsplash_key_pressed) { + /* Did someone take over, like another in-kernel client, except fbdev? */ + fb = drm_client_display_current_fb(splash->display); + if (splash->buffer[i]->fb != fb && + !(dev->fb_helper && dev->fb_helper->fb == fb)) + break; + + i = !i; + drm_bootsplash_draw(splash->buffer[i], sequence++); + if (sequence == 3) + sequence = 0; + + ret = drm_client_display_commit(splash->display, splash->buffer[i]->fb, NULL); + /* Is userspace in charge or is the device unplugged? */ + if (ret == -EBUSY || ret == -ENODEV) + break; + + msleep(500); + } + + /* Restore fbdev (or other) on key press. */ + /* TODO: Check if it's OK to call drm_lastclose here. */ + if (drm_bootsplash_key_pressed) + drm_lastclose(dev); + + for (i = 0; i < 2; i++) + drm_client_framebuffer_delete(splash->buffer[i]); + drm_client_display_free(splash->display); + drm_client_remove_defer(splash->client); + DRM_DEV_DEBUG_KMS(dev->dev, "Bootsplash has stopped (key=%u stop=%u ret=%d).\n", + drm_bootsplash_key_pressed, splash->stop, ret); +} + +static int drm_bootsplash_setup(struct drm_bootsplash *splash) +{ + struct drm_client_dev *client = splash->client; + struct drm_client_buffer *buffer[2]; + struct drm_client_display *display; + int ret, i; + + display = drm_client_find_display(client->dev, 0, 0); + if (IS_ERR(display)) + return PTR_ERR(display); + if (!display) + return -ENOENT; + + if (WARN_ON(!display->mode)) + return -ENOENT; + + for (i = 0; i < 2; i++) { + buffer[i] = drm_client_framebuffer_create(client, display->mode, + DRM_FORMAT_XRGB8888); + if (IS_ERR(buffer[i])) { + ret = PTR_ERR(buffer[i]); + goto err_free_buffer; + } + } + + ret = drm_client_display_commit(display, buffer[0]->fb, display->mode); + if (ret) + goto err_free_buffer; + + splash->display = display; + splash->buffer[0] = buffer[0]; + splash->buffer[1] = buffer[1]; + + schedule_work(&splash->worker); + + return 0; + +err_free_buffer: + for (i--; i >= 0; i--) + drm_client_framebuffer_delete(buffer[i]); + drm_client_display_free(display); + + return ret; +} + +static int drm_bootsplash_client_hotplug(struct drm_client_dev *client) +{ + struct drm_bootsplash *splash = client->private; + int ret = 0; + + if (splash->display) + return 0; + + ret = drm_bootsplash_setup(splash); + if (ret) { + DRM_DEV_DEBUG_KMS(client->dev->dev, "ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int drm_bootsplash_client_remove(struct drm_client_dev *client) +{ + struct drm_bootsplash *splash = client->private; + + /* Don't hook up to any new devices showing up */ + drm_bootsplash_enabled = false; + + splash->stop = true; + flush_work(&splash->worker); + kfree(splash); + + return 0; +} + +static const struct drm_client_funcs drm_bootsplash_client_funcs = { + .name = "bootsplash", + .remove = drm_bootsplash_client_remove, + .hotplug = drm_bootsplash_client_hotplug, +}; + +static int drm_bootsplash_dev_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct drm_device *dev = data; + struct drm_client_dev *client; + struct drm_bootsplash *splash; + + if (!drm_bootsplash_enabled) + return 0; + + splash = kzalloc(sizeof(*splash), GFP_KERNEL); + if (!splash) + return 0; + + client = drm_client_new(dev, &drm_bootsplash_client_funcs); + if (IS_ERR(client)) { + DRM_DEV_ERROR(dev->dev, "Failed to create client, ret=%ld\n", PTR_ERR(client)); + kfree(splash); + return 0; + } + + INIT_WORK(&splash->worker, drm_bootsplash_worker); + + splash->client = client; + client->private = splash; + + /* + * vc4 isn't done with it's setup when drm_dev_register() is called. + * It should have shouldn't it? + * So to keep it from crashing defer setup to hotplug... + */ + if (client->dev->mode_config.max_width) + drm_bootsplash_client_hotplug(client); + + return 0; +} + +static struct notifier_block drm_bootsplash_dev_notifier = { + .notifier_call = drm_bootsplash_dev_notify, +}; + +void drm_bootsplash_register(void) +{ + register_keyboard_notifier(&drm_bootsplash_keyboard_notifier_block); + + if (!drm_bootsplash_enabled) + return; + + drm_dev_register_notifier(&drm_bootsplash_dev_notifier); +} + +void drm_bootsplash_unregister(void) +{ + drm_dev_unregister_notifier(&drm_bootsplash_dev_notifier); + unregister_keyboard_notifier(&drm_bootsplash_keyboard_notifier_block); +} diff --git a/drivers/gpu/drm/client/internal.h b/drivers/gpu/drm/client/internal.h new file mode 100644 index 000000000000..22e2120c493b --- /dev/null +++ b/drivers/gpu/drm/client/internal.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _DRM_CLIENT_INTERNAL_H_ +#define _DRM_CLIENT_INTERNAL_H_ + +#ifdef CONFIG_DRM_CLIENT_BOOTSPLASH +void drm_bootsplash_register(void); +void drm_bootsplash_unregister(void); +#else +static inline void drm_bootsplash_register(void) +{ +} + +static inline void drm_bootsplash_unregister(void) +{ +} +#endif + +#endif diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 760f1795f812..cd8c084c8801 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -24,6 +24,7 @@
#include "drm_crtc_internal.h" #include "drm_internal.h" +#include "client/internal.h"
struct drm_client_display_offset { int x, y; @@ -234,10 +235,13 @@ EXPORT_SYMBOL(drm_client_remove_defer);
void drm_client_init(void) { + drm_bootsplash_register(); }
void drm_client_exit(void) { + drm_bootsplash_unregister(); + flush_work(&drm_client_remove_defer_work); }
dri-devel@lists.freedesktop.org