diff options
Diffstat (limited to 'drivers/gpu/drm/drm_fb_helper.c')
-rw-r--r-- | drivers/gpu/drm/drm_fb_helper.c | 223 |
1 files changed, 120 insertions, 103 deletions
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 03414bde1f15..e934b541feea 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -49,6 +49,7 @@ MODULE_PARM_DESC(fbdev_emulation, "Enable legacy fbdev emulation [default=true]"); static LIST_HEAD(kernel_fb_helper_list); +static DEFINE_MUTEX(kernel_fb_helper_lock); /** * DOC: fbdev helpers @@ -97,6 +98,10 @@ static LIST_HEAD(kernel_fb_helper_list); * mmap page writes. */ +#define drm_fb_helper_for_each_connector(fbh, i__) \ + for (({ lockdep_assert_held(&(fbh)->dev->mode_config.mutex); }), \ + i__ = 0; i__ < (fbh)->connector_count; i__++) + /** * drm_fb_helper_single_add_all_connectors() - add all connectors to fbdev * emulation helper @@ -130,8 +135,13 @@ int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper) mutex_unlock(&dev->mode_config.mutex); return 0; fail: - for (i = 0; i < fb_helper->connector_count; i++) { - kfree(fb_helper->connector_info[i]); + drm_fb_helper_for_each_connector(fb_helper, i) { + struct drm_fb_helper_connector *fb_helper_connector = + fb_helper->connector_info[i]; + + drm_connector_unreference(fb_helper_connector->connector); + + kfree(fb_helper_connector); fb_helper->connector_info[i] = NULL; } fb_helper->connector_count = 0; @@ -251,6 +261,9 @@ int drm_fb_helper_debug_enter(struct fb_info *info) continue; funcs = mode_set->crtc->helper_private; + if (funcs->mode_set_base_atomic == NULL) + continue; + drm_fb_helper_save_lut_atomic(mode_set->crtc, helper); funcs->mode_set_base_atomic(mode_set->crtc, mode_set->fb, @@ -304,6 +317,9 @@ int drm_fb_helper_debug_leave(struct fb_info *info) continue; } + if (funcs->mode_set_base_atomic == NULL) + continue; + drm_fb_helper_restore_lut_atomic(mode_set->crtc); funcs->mode_set_base_atomic(mode_set->crtc, fb, crtc->x, crtc->y, LEAVE_ATOMIC_MODE_SET); @@ -367,9 +383,7 @@ fail: if (ret == -EDEADLK) goto backoff; - if (ret != 0) - drm_atomic_state_free(state); - + drm_atomic_state_put(state); return ret; backoff: @@ -394,11 +408,10 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper) if (plane->type != DRM_PLANE_TYPE_PRIMARY) drm_plane_force_disable(plane); - if (dev->mode_config.rotation_property) { + if (plane->rotation_property) drm_mode_plane_set_obj_prop(plane, - dev->mode_config.rotation_property, + plane->rotation_property, DRM_ROTATE_0); - } } for (i = 0; i < fb_helper->crtc_count; i++) { @@ -557,7 +570,7 @@ static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode) continue; /* Walk the connectors & encoders on this fb turning them on/off */ - for (j = 0; j < fb_helper->connector_count; j++) { + drm_fb_helper_for_each_connector(fb_helper, j) { connector = fb_helper->connector_info[j]->connector; connector->funcs->dpms(connector, dpms_mode); drm_object_property_set_value(&connector->base, @@ -603,6 +616,24 @@ 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_unreference(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; @@ -612,10 +643,12 @@ 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++) { - kfree(helper->crtc_info[i].mode_set.connectors); - if (helper->crtc_info[i].mode_set.mode) - drm_mode_destroy(helper->dev, helper->crtc_info[i].mode_set.mode); + 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); } @@ -644,7 +677,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work) clip->x2 = clip->y2 = 0; spin_unlock_irqrestore(&helper->dirty_lock, flags); - helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1); + /* call dirty callback only when it has been really touched */ + if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2) + helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1); } /** @@ -821,12 +856,14 @@ void drm_fb_helper_fini(struct drm_fb_helper *fb_helper) if (!drm_fbdev_emulation) return; + mutex_lock(&kernel_fb_helper_lock); if (!list_empty(&fb_helper->kernel_fb_list)) { list_del(&fb_helper->kernel_fb_list); if (list_empty(&kernel_fb_helper_list)) { unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); } } + mutex_unlock(&kernel_fb_helper_lock); drm_fb_helper_crtc_free(fb_helper); @@ -1211,11 +1248,14 @@ int drm_fb_helper_check_var(struct fb_var_screeninfo *var, if (var->pixclock != 0 || in_dbg_master()) return -EINVAL; - /* Need to resize the fb object !!! */ - if (var->bits_per_pixel > fb->bits_per_pixel || - var->xres > fb->width || var->yres > fb->height || - var->xres_virtual > fb->width || var->yres_virtual > fb->height) { - DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " + /* + * Changes struct fb_var_screeninfo are currently not pushed back + * to KMS, hence fail if different settings are requested. + */ + if (var->bits_per_pixel != fb->bits_per_pixel || + var->xres != fb->width || var->yres != fb->height || + var->xres_virtual != fb->width || var->yres_virtual != fb->height) { + DRM_DEBUG("fb userspace requested width/height/bpp different than current fb " "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", var->xres, var->yres, var->bits_per_pixel, var->xres_virtual, var->yres_virtual, @@ -1361,16 +1401,13 @@ retry: info->var.xoffset = var->xoffset; info->var.yoffset = var->yoffset; - fail: drm_atomic_clean_old_fb(dev, plane_mask, ret); if (ret == -EDEADLK) goto backoff; - if (ret != 0) - drm_atomic_state_free(state); - + drm_atomic_state_put(state); return ret; backoff: @@ -1439,7 +1476,6 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, int ret = 0; int crtc_count = 0; int i; - struct fb_info *info; struct drm_fb_helper_surface_size sizes; int gamma_size = 0; @@ -1455,7 +1491,7 @@ 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 */ - for (i = 0; i < fb_helper->connector_count; i++) { + 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; @@ -1542,8 +1578,6 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, if (ret < 0) return ret; - info = fb_helper->fbdev; - /* * Set the fb pointer - usually drm_setup_crtcs does this for hotplug * events, but at init time drm_setup_crtcs needs to be called before @@ -1555,20 +1589,6 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, if (fb_helper->crtc_info[i].mode_set.num_connectors) fb_helper->crtc_info[i].mode_set.fb = fb_helper->fb; - - info->var.pixclock = 0; - if (register_framebuffer(info) < 0) - return -EINVAL; - - dev_info(fb_helper->dev->dev, "fb%d: %s frame buffer device\n", - info->node, info->fix.id); - - if (list_empty(&kernel_fb_helper_list)) { - register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); - } - - list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); - return 0; } @@ -1700,7 +1720,7 @@ static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper, int count = 0; int i; - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { connector = fb_helper->connector_info[i]->connector; count += connector->funcs->fill_modes(connector, maxX, maxY); } @@ -1800,7 +1820,7 @@ static void drm_enable_connectors(struct drm_fb_helper *fb_helper, struct drm_connector *connector; int i = 0; - for (i = 0; i < fb_helper->connector_count; i++) { + 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, @@ -1811,7 +1831,7 @@ static void drm_enable_connectors(struct drm_fb_helper *fb_helper, if (any_enabled) return; - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { connector = fb_helper->connector_info[i]->connector; enabled[i] = drm_connector_enabled(connector, false); } @@ -1832,7 +1852,7 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, return false; count = 0; - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { if (enabled[i]) count++; } @@ -1843,7 +1863,7 @@ static bool drm_target_cloned(struct drm_fb_helper *fb_helper, /* check the command line or if nothing common pick 1024x768 */ can_clone = true; - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { if (!enabled[i]) continue; fb_helper_conn = fb_helper->connector_info[i]; @@ -1869,8 +1889,7 @@ 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); - for (i = 0; i < fb_helper->connector_count; i++) { - + drm_fb_helper_for_each_connector(fb_helper, i) { if (!enabled[i]) continue; @@ -1901,7 +1920,7 @@ static int drm_get_tile_offsets(struct drm_fb_helper *fb_helper, int i; int hoffset = 0, voffset = 0; - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { fb_helper_conn = fb_helper->connector_info[i]; if (!fb_helper_conn->connector->has_tile) continue; @@ -1929,19 +1948,20 @@ static bool drm_target_preferred(struct drm_fb_helper *fb_helper, bool *enabled, int width, int height) { struct drm_fb_helper_connector *fb_helper_conn; - int i; - uint64_t conn_configured = 0, mask; + const u64 mask = BIT_ULL(fb_helper->connector_count) - 1; + u64 conn_configured = 0; int tile_pass = 0; - mask = (1 << fb_helper->connector_count) - 1; + int i; + retry: - for (i = 0; i < fb_helper->connector_count; i++) { + drm_fb_helper_for_each_connector(fb_helper, i) { fb_helper_conn = fb_helper->connector_info[i]; - if (conn_configured & (1 << i)) + if (conn_configured & BIT_ULL(i)) continue; if (enabled[i] == false) { - conn_configured |= (1 << i); + conn_configured |= BIT_ULL(i); continue; } @@ -1982,7 +2002,7 @@ retry: } DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name : "none"); - conn_configured |= (1 << i); + conn_configured |= BIT_ULL(i); } if ((conn_configured & mask) != mask) { @@ -2082,21 +2102,22 @@ out: return best_score; } -static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) +static void drm_setup_crtcs(struct drm_fb_helper *fb_helper, + u32 width, u32 height) { struct drm_device *dev = fb_helper->dev; struct drm_fb_helper_crtc **crtcs; struct drm_display_mode **modes; struct drm_fb_offset *offsets; - struct drm_mode_set *modeset; bool *enabled; - int width, height; int i; DRM_DEBUG_KMS("\n"); + if (drm_fb_helper_probe_connector_modes(fb_helper, width, height) == 0) + DRM_DEBUG_KMS("No connectors reported connected with modes\n"); - width = dev->mode_config.max_width; - height = dev->mode_config.max_height; + /* prevent concurrent modification of connector_count by hotplug */ + lockdep_assert_held(&fb_helper->dev->mode_config.mutex); crtcs = kcalloc(fb_helper->connector_count, sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); @@ -2111,7 +2132,6 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) goto out; } - drm_enable_connectors(fb_helper, enabled); if (!(fb_helper->funcs->initial_config && @@ -2136,45 +2156,35 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) /* 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++) { - modeset = &fb_helper->crtc_info[i].mode_set; - modeset->num_connectors = 0; - modeset->fb = NULL; - } + for (i = 0; i < fb_helper->crtc_count; i++) + drm_fb_helper_modeset_release(fb_helper, + &fb_helper->crtc_info[i].mode_set); - for (i = 0; i < fb_helper->connector_count; i++) { + 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_fb_offset *offset = &offsets[i]; - modeset = &fb_crtc->mode_set; + struct drm_mode_set *modeset = &fb_crtc->mode_set; if (mode && fb_crtc) { + struct drm_connector *connector = + fb_helper->connector_info[i]->connector; + DRM_DEBUG_KMS("desired mode %s set on crtc %d (%d,%d)\n", mode->name, fb_crtc->mode_set.crtc->base.id, offset->x, offset->y); + fb_crtc->desired_mode = mode; fb_crtc->x = offset->x; fb_crtc->y = offset->y; - if (modeset->mode) - drm_mode_destroy(dev, modeset->mode); modeset->mode = drm_mode_duplicate(dev, fb_crtc->desired_mode); - modeset->connectors[modeset->num_connectors++] = fb_helper->connector_info[i]->connector; + drm_connector_reference(connector); + modeset->connectors[modeset->num_connectors++] = connector; modeset->fb = fb_helper->fb; modeset->x = offset->x; modeset->y = offset->y; } } - - /* Clear out any old modes if there are no more connected outputs. */ - for (i = 0; i < fb_helper->crtc_count; i++) { - modeset = &fb_helper->crtc_info[i].mode_set; - if (modeset->num_connectors == 0) { - BUG_ON(modeset->fb); - if (modeset->mode) - drm_mode_destroy(dev, modeset->mode); - modeset->mode = NULL; - } - } out: kfree(crtcs); kfree(modes); @@ -2227,25 +2237,38 @@ out: int drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel) { struct drm_device *dev = fb_helper->dev; - int count = 0; + struct fb_info *info; + int ret; if (!drm_fbdev_emulation) return 0; mutex_lock(&dev->mode_config.mutex); - count = drm_fb_helper_probe_connector_modes(fb_helper, - dev->mode_config.max_width, - dev->mode_config.max_height); + drm_setup_crtcs(fb_helper, + dev->mode_config.max_width, + dev->mode_config.max_height); + ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel); mutex_unlock(&dev->mode_config.mutex); - /* - * we shouldn't end up with no modes here. - */ - if (count == 0) - dev_info(fb_helper->dev->dev, "No connectors reported connected with modes\n"); + if (ret) + return ret; + + info = fb_helper->fbdev; + info->var.pixclock = 0; + ret = register_framebuffer(info); + if (ret < 0) + return ret; + + dev_info(dev->dev, "fb%d: %s frame buffer device\n", + info->node, info->fix.id); + + mutex_lock(&kernel_fb_helper_lock); + if (list_empty(&kernel_fb_helper_list)) + register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); - drm_setup_crtcs(fb_helper); + list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); + mutex_unlock(&kernel_fb_helper_lock); - return drm_fb_helper_single_fb_probe(fb_helper, bpp_sel); + return 0; } EXPORT_SYMBOL(drm_fb_helper_initial_config); @@ -2273,28 +2296,22 @@ EXPORT_SYMBOL(drm_fb_helper_initial_config); int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper) { struct drm_device *dev = fb_helper->dev; - u32 max_width, max_height; if (!drm_fbdev_emulation) return 0; - mutex_lock(&fb_helper->dev->mode_config.mutex); + mutex_lock(&dev->mode_config.mutex); if (!fb_helper->fb || !drm_fb_helper_is_bound(fb_helper)) { fb_helper->delayed_hotplug = true; - mutex_unlock(&fb_helper->dev->mode_config.mutex); + mutex_unlock(&dev->mode_config.mutex); return 0; } DRM_DEBUG_KMS("\n"); - max_width = fb_helper->fb->width; - max_height = fb_helper->fb->height; + drm_setup_crtcs(fb_helper, fb_helper->fb->width, fb_helper->fb->height); - drm_fb_helper_probe_connector_modes(fb_helper, max_width, max_height); - mutex_unlock(&fb_helper->dev->mode_config.mutex); + mutex_unlock(&dev->mode_config.mutex); - drm_modeset_lock_all(dev); - drm_setup_crtcs(fb_helper); - drm_modeset_unlock_all(dev); drm_fb_helper_set_par(fb_helper->fbdev); return 0; |