2009-12-11 17:24:15 +08:00
|
|
|
/*
|
|
|
|
* Copyright © 2007 David Airlie
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice (including the next
|
|
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
|
|
* Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
* DEALINGS IN THE SOFTWARE.
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* David Airlie
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/sysrq.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/screen_info.h>
|
2010-02-01 13:38:10 +08:00
|
|
|
#include <linux/vga_switcheroo.h>
|
2011-11-09 12:31:16 +08:00
|
|
|
#include <linux/console.h>
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2012-10-03 01:01:07 +08:00
|
|
|
#include <drm/drmP.h>
|
|
|
|
#include <drm/drm_crtc.h>
|
|
|
|
#include <drm/drm_crtc_helper.h>
|
|
|
|
#include <drm/drm_fb_helper.h>
|
2016-12-22 16:50:42 +08:00
|
|
|
#include <drm/drm_atomic.h>
|
2012-07-20 06:17:34 +08:00
|
|
|
|
2016-05-20 07:22:55 +08:00
|
|
|
#include "nouveau_drv.h"
|
2012-07-20 06:17:34 +08:00
|
|
|
#include "nouveau_gem.h"
|
|
|
|
#include "nouveau_bo.h"
|
2009-12-11 17:24:15 +08:00
|
|
|
#include "nouveau_fbcon.h"
|
2012-07-20 06:17:34 +08:00
|
|
|
#include "nouveau_chan.h"
|
2017-11-01 01:56:19 +08:00
|
|
|
#include "nouveau_vmm.h"
|
2012-07-20 06:17:34 +08:00
|
|
|
|
|
|
|
#include "nouveau_crtc.h"
|
|
|
|
|
|
|
|
MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
|
2014-08-19 04:43:24 +08:00
|
|
|
int nouveau_nofbaccel = 0;
|
2012-07-20 06:17:34 +08:00
|
|
|
module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2018-01-12 15:50:37 +08:00
|
|
|
MODULE_PARM_DESC(fbcon_bpp, "fbcon bits-per-pixel (default: auto)");
|
|
|
|
static int nouveau_fbcon_bpp;
|
|
|
|
module_param_named(fbcon_bpp, nouveau_fbcon_bpp, int, 0400);
|
|
|
|
|
2010-10-05 14:41:29 +08:00
|
|
|
static void
|
|
|
|
nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
|
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2016-05-18 11:57:42 +08:00
|
|
|
struct nvif_device *device = &drm->client.device;
|
2010-10-05 14:41:29 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = -ENODEV;
|
2010-10-14 12:55:23 +08:00
|
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_trylock(&drm->client.mutex)) {
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv04_fbcon_fillrect(info, rect);
|
|
|
|
else
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv50_fbcon_fillrect(info, rect);
|
2010-11-24 08:52:43 +08:00
|
|
|
else
|
|
|
|
ret = nvc0_fbcon_fillrect(info, rect);
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_unlock(&drm->client.mutex);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (ret != -ENODEV)
|
|
|
|
nouveau_fbcon_gpu_lockup(info);
|
2015-07-31 18:51:57 +08:00
|
|
|
drm_fb_helper_cfb_fillrect(info, rect);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
|
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2016-05-18 11:57:42 +08:00
|
|
|
struct nvif_device *device = &drm->client.device;
|
2010-10-05 14:41:29 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = -ENODEV;
|
2010-10-14 12:55:23 +08:00
|
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_trylock(&drm->client.mutex)) {
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv04_fbcon_copyarea(info, image);
|
|
|
|
else
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv50_fbcon_copyarea(info, image);
|
2010-11-24 08:52:43 +08:00
|
|
|
else
|
|
|
|
ret = nvc0_fbcon_copyarea(info, image);
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_unlock(&drm->client.mutex);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (ret != -ENODEV)
|
|
|
|
nouveau_fbcon_gpu_lockup(info);
|
2015-07-31 18:51:57 +08:00
|
|
|
drm_fb_helper_cfb_copyarea(info, image);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
|
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2016-05-18 11:57:42 +08:00
|
|
|
struct nvif_device *device = &drm->client.device;
|
2010-10-05 14:41:29 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = -ENODEV;
|
2010-10-14 12:55:23 +08:00
|
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_trylock(&drm->client.mutex)) {
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv04_fbcon_imageblit(info, image);
|
|
|
|
else
|
2014-08-10 02:10:22 +08:00
|
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
2010-10-05 14:41:29 +08:00
|
|
|
ret = nv50_fbcon_imageblit(info, image);
|
2010-11-24 08:52:43 +08:00
|
|
|
else
|
|
|
|
ret = nvc0_fbcon_imageblit(info, image);
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_unlock(&drm->client.mutex);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (ret != -ENODEV)
|
|
|
|
nouveau_fbcon_gpu_lockup(info);
|
2015-07-31 18:51:57 +08:00
|
|
|
drm_fb_helper_cfb_imageblit(info, image);
|
2010-10-05 14:41:29 +08:00
|
|
|
}
|
|
|
|
|
2009-12-11 17:24:15 +08:00
|
|
|
static int
|
|
|
|
nouveau_fbcon_sync(struct fb_info *info)
|
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_channel *chan = drm->channel;
|
2012-05-04 12:01:28 +08:00
|
|
|
int ret;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2010-10-05 14:53:48 +08:00
|
|
|
if (!chan || !chan->accel_done || in_interrupt() ||
|
2009-12-11 17:24:15 +08:00
|
|
|
info->state != FBINFO_STATE_RUNNING ||
|
|
|
|
info->flags & FBINFO_HWACCEL_DISABLED)
|
|
|
|
return 0;
|
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
if (!mutex_trylock(&drm->client.mutex))
|
2010-10-14 12:55:23 +08:00
|
|
|
return 0;
|
|
|
|
|
2012-05-04 12:01:28 +08:00
|
|
|
ret = nouveau_channel_idle(chan);
|
2012-07-20 06:17:34 +08:00
|
|
|
mutex_unlock(&drm->client.mutex);
|
2009-12-11 17:24:15 +08:00
|
|
|
if (ret) {
|
2010-01-05 02:25:09 +08:00
|
|
|
nouveau_fbcon_gpu_lockup(info);
|
2009-12-11 17:24:15 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->accel_done = false;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-10-02 12:03:19 +08:00
|
|
|
static int
|
|
|
|
nouveau_fbcon_open(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2015-10-02 12:03:19 +08:00
|
|
|
int ret = pm_runtime_get_sync(drm->dev->dev);
|
|
|
|
if (ret < 0 && ret != -EACCES)
|
|
|
|
return ret;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
nouveau_fbcon_release(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2015-10-02 12:03:19 +08:00
|
|
|
pm_runtime_put(drm->dev->dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-11 17:24:15 +08:00
|
|
|
static struct fb_ops nouveau_fbcon_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
2016-11-14 07:03:18 +08:00
|
|
|
DRM_FB_HELPER_DEFAULT_OPS,
|
2015-10-02 12:03:19 +08:00
|
|
|
.fb_open = nouveau_fbcon_open,
|
|
|
|
.fb_release = nouveau_fbcon_release,
|
2010-10-05 14:41:29 +08:00
|
|
|
.fb_fillrect = nouveau_fbcon_fillrect,
|
|
|
|
.fb_copyarea = nouveau_fbcon_copyarea,
|
|
|
|
.fb_imageblit = nouveau_fbcon_imageblit,
|
2009-12-11 17:24:15 +08:00
|
|
|
.fb_sync = nouveau_fbcon_sync,
|
|
|
|
};
|
|
|
|
|
2010-10-05 14:41:29 +08:00
|
|
|
static struct fb_ops nouveau_fbcon_sw_ops = {
|
2010-01-27 22:03:18 +08:00
|
|
|
.owner = THIS_MODULE,
|
2016-11-14 07:03:18 +08:00
|
|
|
DRM_FB_HELPER_DEFAULT_OPS,
|
2015-10-02 12:03:19 +08:00
|
|
|
.fb_open = nouveau_fbcon_open,
|
|
|
|
.fb_release = nouveau_fbcon_release,
|
2015-07-31 18:51:57 +08:00
|
|
|
.fb_fillrect = drm_fb_helper_cfb_fillrect,
|
|
|
|
.fb_copyarea = drm_fb_helper_cfb_copyarea,
|
|
|
|
.fb_imageblit = drm_fb_helper_cfb_imageblit,
|
2010-01-27 22:03:18 +08:00
|
|
|
};
|
|
|
|
|
2014-06-28 18:44:07 +08:00
|
|
|
void
|
|
|
|
nouveau_fbcon_accel_save_disable(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2017-09-24 04:10:33 +08:00
|
|
|
if (drm->fbcon && drm->fbcon->helper.fbdev) {
|
2014-06-28 18:44:07 +08:00
|
|
|
drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
|
|
|
|
drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nouveau_fbcon_accel_restore(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2017-09-24 04:10:33 +08:00
|
|
|
if (drm->fbcon && drm->fbcon->helper.fbdev) {
|
2014-06-28 18:44:07 +08:00
|
|
|
drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-10 10:36:26 +08:00
|
|
|
static void
|
2014-06-28 18:44:07 +08:00
|
|
|
nouveau_fbcon_accel_fini(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
|
|
struct nouveau_fbdev *fbcon = drm->fbcon;
|
|
|
|
if (fbcon && drm->channel) {
|
|
|
|
console_lock();
|
2017-09-24 04:10:33 +08:00
|
|
|
if (fbcon->helper.fbdev)
|
|
|
|
fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
|
2014-06-28 18:44:07 +08:00
|
|
|
console_unlock();
|
|
|
|
nouveau_channel_idle(drm->channel);
|
2014-08-10 02:10:22 +08:00
|
|
|
nvif_object_fini(&fbcon->twod);
|
|
|
|
nvif_object_fini(&fbcon->blit);
|
|
|
|
nvif_object_fini(&fbcon->gdi);
|
|
|
|
nvif_object_fini(&fbcon->patt);
|
|
|
|
nvif_object_fini(&fbcon->rop);
|
|
|
|
nvif_object_fini(&fbcon->clip);
|
|
|
|
nvif_object_fini(&fbcon->surf2d);
|
2014-06-28 18:44:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-10 10:36:26 +08:00
|
|
|
static void
|
2014-06-28 18:44:07 +08:00
|
|
|
nouveau_fbcon_accel_init(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
|
|
struct nouveau_fbdev *fbcon = drm->fbcon;
|
|
|
|
struct fb_info *info = fbcon->helper.fbdev;
|
|
|
|
int ret;
|
|
|
|
|
2016-05-18 11:57:42 +08:00
|
|
|
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
|
2014-06-28 18:44:07 +08:00
|
|
|
ret = nv04_fbcon_accel_init(info);
|
|
|
|
else
|
2016-05-18 11:57:42 +08:00
|
|
|
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
|
2014-06-28 18:44:07 +08:00
|
|
|
ret = nv50_fbcon_accel_init(info);
|
|
|
|
else
|
|
|
|
ret = nvc0_fbcon_accel_init(info);
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
info->fbops = &nouveau_fbcon_ops;
|
|
|
|
}
|
|
|
|
|
2010-03-30 13:34:13 +08:00
|
|
|
static void
|
2012-07-20 06:17:34 +08:00
|
|
|
nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
|
2009-12-11 17:24:15 +08:00
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct fb_info *info = fbcon->helper.fbdev;
|
2009-12-11 17:24:15 +08:00
|
|
|
struct fb_fillrect rect;
|
|
|
|
|
|
|
|
/* Clear the entire fbcon. The drm will program every connector
|
|
|
|
* with it's preferred mode. If the sizes differ, one display will
|
|
|
|
* quite likely have garbage around the console.
|
|
|
|
*/
|
|
|
|
rect.dx = rect.dy = 0;
|
|
|
|
rect.width = info->var.xres_virtual;
|
|
|
|
rect.height = info->var.yres_virtual;
|
|
|
|
rect.color = 0;
|
|
|
|
rect.rop = ROP_COPY;
|
|
|
|
info->fbops->fb_fillrect(info, &rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-01-22 06:42:49 +08:00
|
|
|
nouveau_fbcon_create(struct drm_fb_helper *helper,
|
2010-03-30 13:34:14 +08:00
|
|
|
struct drm_fb_helper_surface_size *sizes)
|
2009-12-11 17:24:15 +08:00
|
|
|
{
|
2014-09-15 00:40:17 +08:00
|
|
|
struct nouveau_fbdev *fbcon =
|
|
|
|
container_of(helper, struct nouveau_fbdev, helper);
|
2016-11-04 15:20:35 +08:00
|
|
|
struct drm_device *dev = fbcon->helper.dev;
|
2012-07-31 14:16:21 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2016-05-18 11:57:42 +08:00
|
|
|
struct nvif_device *device = &drm->client.device;
|
2009-12-11 17:24:15 +08:00
|
|
|
struct fb_info *info;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_framebuffer *fb;
|
2011-06-07 11:12:44 +08:00
|
|
|
struct nouveau_channel *chan;
|
2009-12-11 17:24:15 +08:00
|
|
|
struct nouveau_bo *nvbo;
|
2011-11-15 06:51:28 +08:00
|
|
|
struct drm_mode_fb_cmd2 mode_cmd;
|
2016-11-04 15:20:35 +08:00
|
|
|
int ret;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2010-03-30 13:34:13 +08:00
|
|
|
mode_cmd.width = sizes->surface_width;
|
|
|
|
mode_cmd.height = sizes->surface_height;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2011-11-15 06:51:28 +08:00
|
|
|
mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
|
|
|
|
mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2011-11-15 06:51:28 +08:00
|
|
|
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
|
|
|
|
sizes->surface_depth);
|
|
|
|
|
2016-05-24 15:29:55 +08:00
|
|
|
ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
|
|
|
|
mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
|
|
|
|
0, 0x0000, &nvbo);
|
2009-12-11 17:24:15 +08:00
|
|
|
if (ret) {
|
2012-07-20 06:17:34 +08:00
|
|
|
NV_ERROR(drm, "failed to allocate framebuffer\n");
|
2009-12-11 17:24:15 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2016-11-04 15:20:35 +08:00
|
|
|
ret = nouveau_framebuffer_new(dev, &mode_cmd, nvbo, &fb);
|
|
|
|
if (ret)
|
|
|
|
goto out_unref;
|
2016-11-04 15:20:35 +08:00
|
|
|
|
2014-11-10 09:24:27 +08:00
|
|
|
ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM, false);
|
2009-12-11 17:24:15 +08:00
|
|
|
if (ret) {
|
2012-07-20 06:17:34 +08:00
|
|
|
NV_ERROR(drm, "failed to pin fb: %d\n", ret);
|
2013-06-27 19:38:21 +08:00
|
|
|
goto out_unref;
|
2009-12-11 17:24:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = nouveau_bo_map(nvbo);
|
|
|
|
if (ret) {
|
2012-07-20 06:17:34 +08:00
|
|
|
NV_ERROR(drm, "failed to map fb: %d\n", ret);
|
2013-06-27 19:38:21 +08:00
|
|
|
goto out_unpin;
|
2009-12-11 17:24:15 +08:00
|
|
|
}
|
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
chan = nouveau_nofbaccel ? NULL : drm->channel;
|
2014-08-10 02:10:22 +08:00
|
|
|
if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
|
2019-02-19 15:21:48 +08:00
|
|
|
ret = nouveau_vma_new(nvbo, chan->vmm, &fb->vma);
|
2011-06-07 11:12:44 +08:00
|
|
|
if (ret) {
|
2012-07-20 06:17:34 +08:00
|
|
|
NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
|
2011-06-07 11:12:44 +08:00
|
|
|
chan = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-31 18:51:57 +08:00
|
|
|
info = drm_fb_helper_alloc_fbi(helper);
|
|
|
|
if (IS_ERR(info)) {
|
|
|
|
ret = PTR_ERR(info);
|
2013-06-27 19:38:21 +08:00
|
|
|
goto out_unlock;
|
2009-12-11 17:24:15 +08:00
|
|
|
}
|
2015-01-13 16:18:49 +08:00
|
|
|
info->skip_vt_switch = 1;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
info->par = fbcon;
|
2010-03-30 13:34:13 +08:00
|
|
|
|
2010-03-30 13:34:14 +08:00
|
|
|
/* setup helper */
|
2016-11-04 15:20:35 +08:00
|
|
|
fbcon->helper.fb = &fb->base;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
|
|
|
strcpy(info->fix.id, "nouveaufb");
|
2013-06-27 19:38:21 +08:00
|
|
|
if (!chan)
|
2019-01-25 00:58:31 +08:00
|
|
|
info->flags = FBINFO_HWACCEL_DISABLED;
|
2010-01-26 22:00:42 +08:00
|
|
|
else
|
2019-01-25 00:58:31 +08:00
|
|
|
info->flags = FBINFO_HWACCEL_COPYAREA |
|
2010-01-26 22:00:42 +08:00
|
|
|
FBINFO_HWACCEL_FILLRECT |
|
|
|
|
FBINFO_HWACCEL_IMAGEBLIT;
|
2010-10-05 14:41:29 +08:00
|
|
|
info->fbops = &nouveau_fbcon_sw_ops;
|
2016-11-04 15:20:35 +08:00
|
|
|
info->fix.smem_start = fb->nvbo->bo.mem.bus.base +
|
|
|
|
fb->nvbo->bo.mem.bus.offset;
|
|
|
|
info->fix.smem_len = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2016-11-04 15:20:35 +08:00
|
|
|
info->screen_base = nvbo_kmap_obj_iovirtual(fb->nvbo);
|
|
|
|
info->screen_size = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2016-12-15 05:31:35 +08:00
|
|
|
drm_fb_helper_fill_fix(info, fb->base.pitches[0],
|
|
|
|
fb->base.format->depth);
|
2012-07-20 06:17:34 +08:00
|
|
|
drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
|
2010-05-16 23:27:03 +08:00
|
|
|
|
2012-02-06 17:58:19 +08:00
|
|
|
/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2014-06-28 18:44:07 +08:00
|
|
|
if (chan)
|
|
|
|
nouveau_fbcon_accel_init(dev);
|
2012-07-20 06:17:34 +08:00
|
|
|
nouveau_fbcon_zfill(dev, fbcon);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
|
|
|
/* To allow resizeing without swapping buffers */
|
2015-03-04 13:18:38 +08:00
|
|
|
NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
|
2016-11-04 15:20:35 +08:00
|
|
|
fb->base.width, fb->base.height, fb->nvbo->bo.offset, nvbo);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2010-02-01 13:38:10 +08:00
|
|
|
vga_switcheroo_client_fb_set(dev->pdev, info);
|
2009-12-11 17:24:15 +08:00
|
|
|
return 0;
|
|
|
|
|
2013-06-27 19:38:21 +08:00
|
|
|
out_unlock:
|
|
|
|
if (chan)
|
2017-11-01 01:56:19 +08:00
|
|
|
nouveau_vma_del(&fb->vma);
|
2016-11-04 15:20:35 +08:00
|
|
|
nouveau_bo_unmap(fb->nvbo);
|
2013-06-27 19:38:21 +08:00
|
|
|
out_unpin:
|
2016-11-04 15:20:35 +08:00
|
|
|
nouveau_bo_unpin(fb->nvbo);
|
2013-06-27 19:38:21 +08:00
|
|
|
out_unref:
|
2016-11-04 15:20:35 +08:00
|
|
|
nouveau_bo_ref(NULL, &fb->nvbo);
|
2009-12-11 17:24:15 +08:00
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-07-04 00:36:39 +08:00
|
|
|
static int
|
2012-07-20 06:17:34 +08:00
|
|
|
nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
|
2009-12-11 17:24:15 +08:00
|
|
|
{
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fbcon->helper.fb);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2015-07-31 18:51:57 +08:00
|
|
|
drm_fb_helper_unregister_fbi(&fbcon->helper);
|
2016-11-04 15:20:35 +08:00
|
|
|
drm_fb_helper_fini(&fbcon->helper);
|
2009-12-11 17:24:15 +08:00
|
|
|
|
2017-11-06 23:20:33 +08:00
|
|
|
if (nouveau_fb && nouveau_fb->nvbo) {
|
2017-11-01 01:56:19 +08:00
|
|
|
nouveau_vma_del(&nouveau_fb->vma);
|
2016-11-04 15:20:35 +08:00
|
|
|
nouveau_bo_unmap(nouveau_fb->nvbo);
|
2013-06-27 19:38:21 +08:00
|
|
|
nouveau_bo_unpin(nouveau_fb->nvbo);
|
2018-06-18 20:53:10 +08:00
|
|
|
drm_framebuffer_put(&nouveau_fb->base);
|
2009-12-11 17:24:15 +08:00
|
|
|
}
|
2016-11-04 15:20:35 +08:00
|
|
|
|
2009-12-11 17:24:15 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2010-01-05 02:25:09 +08:00
|
|
|
|
|
|
|
void nouveau_fbcon_gpu_lockup(struct fb_info *info)
|
|
|
|
{
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon = info->par;
|
2016-11-04 15:20:35 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
2010-01-05 02:25:09 +08:00
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
|
2010-01-05 02:25:09 +08:00
|
|
|
info->flags |= FBINFO_HWACCEL_DISABLED;
|
|
|
|
}
|
2010-03-30 13:34:13 +08:00
|
|
|
|
2014-06-27 23:19:23 +08:00
|
|
|
static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
|
2013-01-22 06:42:49 +08:00
|
|
|
.fb_probe = nouveau_fbcon_create,
|
2010-03-30 13:34:18 +08:00
|
|
|
};
|
|
|
|
|
2017-01-12 10:25:24 +08:00
|
|
|
static void
|
|
|
|
nouveau_fbcon_set_suspend_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
|
|
|
|
int state = READ_ONCE(drm->fbcon_new_state);
|
|
|
|
|
|
|
|
if (state == FBINFO_STATE_RUNNING)
|
|
|
|
pm_runtime_get_sync(drm->dev->dev);
|
|
|
|
|
|
|
|
console_lock();
|
|
|
|
if (state == FBINFO_STATE_RUNNING)
|
|
|
|
nouveau_fbcon_accel_restore(drm->dev);
|
|
|
|
drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
|
|
|
|
if (state != FBINFO_STATE_RUNNING)
|
|
|
|
nouveau_fbcon_accel_save_disable(drm->dev);
|
|
|
|
console_unlock();
|
|
|
|
|
|
|
|
if (state == FBINFO_STATE_RUNNING) {
|
drm/nouveau/drm/nouveau: Fix deadlock with fb_helper with async RPM requests
Currently, nouveau uses the generic drm_fb_helper_output_poll_changed()
function provided by DRM as it's output_poll_changed callback.
Unfortunately however, this function doesn't grab runtime PM references
early enough and even if it did-we can't block waiting for the device to
resume in output_poll_changed() since it's very likely that we'll need
to grab the fb_helper lock at some point during the runtime resume
process. This currently results in deadlocking like so:
[ 246.669625] INFO: task kworker/4:0:37 blocked for more than 120 seconds.
[ 246.673398] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.675271] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.676527] kworker/4:0 D 0 37 2 0x80000000
[ 246.677580] Workqueue: events output_poll_execute [drm_kms_helper]
[ 246.678704] Call Trace:
[ 246.679753] __schedule+0x322/0xaf0
[ 246.680916] schedule+0x33/0x90
[ 246.681924] schedule_preempt_disabled+0x15/0x20
[ 246.683023] __mutex_lock+0x569/0x9a0
[ 246.684035] ? kobject_uevent_env+0x117/0x7b0
[ 246.685132] ? drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.686179] mutex_lock_nested+0x1b/0x20
[ 246.687278] ? mutex_lock_nested+0x1b/0x20
[ 246.688307] drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.689420] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.690462] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.691570] output_poll_execute+0x198/0x1c0 [drm_kms_helper]
[ 246.692611] process_one_work+0x231/0x620
[ 246.693725] worker_thread+0x214/0x3a0
[ 246.694756] kthread+0x12b/0x150
[ 246.695856] ? wq_pool_ids_show+0x140/0x140
[ 246.696888] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.697998] ret_from_fork+0x3a/0x50
[ 246.699034] INFO: task kworker/0:1:60 blocked for more than 120 seconds.
[ 246.700153] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.701182] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.702278] kworker/0:1 D 0 60 2 0x80000000
[ 246.703293] Workqueue: pm pm_runtime_work
[ 246.704393] Call Trace:
[ 246.705403] __schedule+0x322/0xaf0
[ 246.706439] ? wait_for_completion+0x104/0x190
[ 246.707393] schedule+0x33/0x90
[ 246.708375] schedule_timeout+0x3a5/0x590
[ 246.709289] ? mark_held_locks+0x58/0x80
[ 246.710208] ? _raw_spin_unlock_irq+0x2c/0x40
[ 246.711222] ? wait_for_completion+0x104/0x190
[ 246.712134] ? trace_hardirqs_on_caller+0xf4/0x190
[ 246.713094] ? wait_for_completion+0x104/0x190
[ 246.713964] wait_for_completion+0x12c/0x190
[ 246.714895] ? wake_up_q+0x80/0x80
[ 246.715727] ? get_work_pool+0x90/0x90
[ 246.716649] flush_work+0x1c9/0x280
[ 246.717483] ? flush_workqueue_prep_pwqs+0x1b0/0x1b0
[ 246.718442] __cancel_work_timer+0x146/0x1d0
[ 246.719247] cancel_delayed_work_sync+0x13/0x20
[ 246.720043] drm_kms_helper_poll_disable+0x1f/0x30 [drm_kms_helper]
[ 246.721123] nouveau_pmops_runtime_suspend+0x3d/0xb0 [nouveau]
[ 246.721897] pci_pm_runtime_suspend+0x6b/0x190
[ 246.722825] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.723737] __rpm_callback+0x7a/0x1d0
[ 246.724721] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.725607] rpm_callback+0x24/0x80
[ 246.726553] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.727376] rpm_suspend+0x142/0x6b0
[ 246.728185] pm_runtime_work+0x97/0xc0
[ 246.728938] process_one_work+0x231/0x620
[ 246.729796] worker_thread+0x44/0x3a0
[ 246.730614] kthread+0x12b/0x150
[ 246.731395] ? wq_pool_ids_show+0x140/0x140
[ 246.732202] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.732878] ret_from_fork+0x3a/0x50
[ 246.733768] INFO: task kworker/4:2:422 blocked for more than 120 seconds.
[ 246.734587] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.735393] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.736113] kworker/4:2 D 0 422 2 0x80000080
[ 246.736789] Workqueue: events_long drm_dp_mst_link_probe_work [drm_kms_helper]
[ 246.737665] Call Trace:
[ 246.738490] __schedule+0x322/0xaf0
[ 246.739250] schedule+0x33/0x90
[ 246.739908] rpm_resume+0x19c/0x850
[ 246.740750] ? finish_wait+0x90/0x90
[ 246.741541] __pm_runtime_resume+0x4e/0x90
[ 246.742370] nv50_disp_atomic_commit+0x31/0x210 [nouveau]
[ 246.743124] drm_atomic_commit+0x4a/0x50 [drm]
[ 246.743775] restore_fbdev_mode_atomic+0x1c8/0x240 [drm_kms_helper]
[ 246.744603] restore_fbdev_mode+0x31/0x140 [drm_kms_helper]
[ 246.745373] drm_fb_helper_restore_fbdev_mode_unlocked+0x54/0xb0 [drm_kms_helper]
[ 246.746220] drm_fb_helper_set_par+0x2d/0x50 [drm_kms_helper]
[ 246.746884] drm_fb_helper_hotplug_event.part.28+0x96/0xb0 [drm_kms_helper]
[ 246.747675] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.748544] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.749439] nv50_mstm_hotplug+0x15/0x20 [nouveau]
[ 246.750111] drm_dp_send_link_address+0x177/0x1c0 [drm_kms_helper]
[ 246.750764] drm_dp_check_and_send_link_address+0xa8/0xd0 [drm_kms_helper]
[ 246.751602] drm_dp_mst_link_probe_work+0x51/0x90 [drm_kms_helper]
[ 246.752314] process_one_work+0x231/0x620
[ 246.752979] worker_thread+0x44/0x3a0
[ 246.753838] kthread+0x12b/0x150
[ 246.754619] ? wq_pool_ids_show+0x140/0x140
[ 246.755386] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.756162] ret_from_fork+0x3a/0x50
[ 246.756847]
Showing all locks held in the system:
[ 246.758261] 3 locks held by kworker/4:0/37:
[ 246.759016] #0: 00000000f8df4d2d ((wq_completion)"events"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.759856] #1: 00000000e6065461 ((work_completion)(&(&dev->mode_config.output_poll_work)->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.760670] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.761516] 2 locks held by kworker/0:1/60:
[ 246.762274] #0: 00000000fff6be0f ((wq_completion)"pm"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.762982] #1: 000000005ab44fb4 ((work_completion)(&dev->power.work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.763890] 1 lock held by khungtaskd/64:
[ 246.764664] #0: 000000008cb8b5c3 (rcu_read_lock){....}, at: debug_show_all_locks+0x23/0x185
[ 246.765588] 5 locks held by kworker/4:2/422:
[ 246.766440] #0: 00000000232f0959 ((wq_completion)"events_long"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.767390] #1: 00000000bb59b134 ((work_completion)(&mgr->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.768154] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_restore_fbdev_mode_unlocked+0x4c/0xb0 [drm_kms_helper]
[ 246.768966] #3: 000000004c8f0b6b (crtc_ww_class_acquire){+.+.}, at: restore_fbdev_mode_atomic+0x4b/0x240 [drm_kms_helper]
[ 246.769921] #4: 000000004c34a296 (crtc_ww_class_mutex){+.+.}, at: drm_modeset_backoff+0x8a/0x1b0 [drm]
[ 246.770839] 1 lock held by dmesg/1038:
[ 246.771739] 2 locks held by zsh/1172:
[ 246.772650] #0: 00000000836d0438 (&tty->ldisc_sem){++++}, at: ldsem_down_read+0x37/0x40
[ 246.773680] #1: 000000001f4f4d48 (&ldata->atomic_read_lock){+.+.}, at: n_tty_read+0xc1/0x870
[ 246.775522] =============================================
After trying dozens of different solutions, I found one very simple one
that should also have the benefit of preventing us from having to fight
locking for the rest of our lives. So, we work around these deadlocks by
deferring all fbcon hotplug events that happen after the runtime suspend
process starts until after the device is resumed again.
Changes since v7:
- Fixup commit message - Daniel Vetter
Changes since v6:
- Remove unused nouveau_fbcon_hotplugged_in_suspend() - Ilia
Changes since v5:
- Come up with the (hopefully final) solution for solving this dumb
problem, one that is a lot less likely to cause issues with locking in
the future. This should work around all deadlock conditions with fbcon
brought up thus far.
Changes since v4:
- Add nouveau_fbcon_hotplugged_in_suspend() to workaround deadlock
condition that Lukas described
- Just move all of this out of drm_fb_helper. It seems that other DRM
drivers have already figured out other workarounds for this. If other
drivers do end up needing this in the future, we can just move this
back into drm_fb_helper again.
Changes since v3:
- Actually check if fb_helper is NULL in both new helpers
- Actually check drm_fbdev_emulation in both new helpers
- Don't fire off a fb_helper hotplug unconditionally; only do it if
the following conditions are true (as otherwise, calling this in the
wrong spot will cause Bad Things to happen):
- fb_helper hotplug handling was actually inhibited previously
- fb_helper actually has a delayed hotplug pending
- fb_helper is actually bound
- fb_helper is actually initialized
- Add __must_check to drm_fb_helper_suspend_hotplug(). There's no
situation where a driver would actually want to use this without
checking the return value, so enforce that
- Rewrite and clarify the documentation for both helpers.
- Make sure to return true in the drm_fb_helper_suspend_hotplug() stub
that's provided in drm_fb_helper.h when CONFIG_DRM_FBDEV_EMULATION
isn't enabled
- Actually grab the toplevel fb_helper lock in
drm_fb_helper_resume_hotplug(), since it's possible other activity
(such as a hotplug) could be going on at the same time the driver
calls drm_fb_helper_resume_hotplug(). We need this to check whether or
not drm_fb_helper_hotplug_event() needs to be called anyway
Signed-off-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Karol Herbst <kherbst@redhat.com>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Cc: stable@vger.kernel.org
Cc: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
2018-08-16 03:00:13 +08:00
|
|
|
nouveau_fbcon_hotplug_resume(drm->fbcon);
|
2017-01-12 10:25:24 +08:00
|
|
|
pm_runtime_mark_last_busy(drm->dev->dev);
|
|
|
|
pm_runtime_put_sync(drm->dev->dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-02 11:31:00 +08:00
|
|
|
void
|
|
|
|
nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2017-01-12 10:25:24 +08:00
|
|
|
|
|
|
|
if (!drm->fbcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
drm->fbcon_new_state = state;
|
|
|
|
/* Since runtime resume can happen as a result of a sysfs operation,
|
|
|
|
* it's possible we already have the console locked. So handle fbcon
|
|
|
|
* init/deinit from a seperate work thread
|
|
|
|
*/
|
|
|
|
schedule_work(&drm->fbcon_work);
|
2014-10-02 11:31:00 +08:00
|
|
|
}
|
|
|
|
|
drm/nouveau/drm/nouveau: Fix deadlock with fb_helper with async RPM requests
Currently, nouveau uses the generic drm_fb_helper_output_poll_changed()
function provided by DRM as it's output_poll_changed callback.
Unfortunately however, this function doesn't grab runtime PM references
early enough and even if it did-we can't block waiting for the device to
resume in output_poll_changed() since it's very likely that we'll need
to grab the fb_helper lock at some point during the runtime resume
process. This currently results in deadlocking like so:
[ 246.669625] INFO: task kworker/4:0:37 blocked for more than 120 seconds.
[ 246.673398] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.675271] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.676527] kworker/4:0 D 0 37 2 0x80000000
[ 246.677580] Workqueue: events output_poll_execute [drm_kms_helper]
[ 246.678704] Call Trace:
[ 246.679753] __schedule+0x322/0xaf0
[ 246.680916] schedule+0x33/0x90
[ 246.681924] schedule_preempt_disabled+0x15/0x20
[ 246.683023] __mutex_lock+0x569/0x9a0
[ 246.684035] ? kobject_uevent_env+0x117/0x7b0
[ 246.685132] ? drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.686179] mutex_lock_nested+0x1b/0x20
[ 246.687278] ? mutex_lock_nested+0x1b/0x20
[ 246.688307] drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.689420] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.690462] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.691570] output_poll_execute+0x198/0x1c0 [drm_kms_helper]
[ 246.692611] process_one_work+0x231/0x620
[ 246.693725] worker_thread+0x214/0x3a0
[ 246.694756] kthread+0x12b/0x150
[ 246.695856] ? wq_pool_ids_show+0x140/0x140
[ 246.696888] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.697998] ret_from_fork+0x3a/0x50
[ 246.699034] INFO: task kworker/0:1:60 blocked for more than 120 seconds.
[ 246.700153] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.701182] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.702278] kworker/0:1 D 0 60 2 0x80000000
[ 246.703293] Workqueue: pm pm_runtime_work
[ 246.704393] Call Trace:
[ 246.705403] __schedule+0x322/0xaf0
[ 246.706439] ? wait_for_completion+0x104/0x190
[ 246.707393] schedule+0x33/0x90
[ 246.708375] schedule_timeout+0x3a5/0x590
[ 246.709289] ? mark_held_locks+0x58/0x80
[ 246.710208] ? _raw_spin_unlock_irq+0x2c/0x40
[ 246.711222] ? wait_for_completion+0x104/0x190
[ 246.712134] ? trace_hardirqs_on_caller+0xf4/0x190
[ 246.713094] ? wait_for_completion+0x104/0x190
[ 246.713964] wait_for_completion+0x12c/0x190
[ 246.714895] ? wake_up_q+0x80/0x80
[ 246.715727] ? get_work_pool+0x90/0x90
[ 246.716649] flush_work+0x1c9/0x280
[ 246.717483] ? flush_workqueue_prep_pwqs+0x1b0/0x1b0
[ 246.718442] __cancel_work_timer+0x146/0x1d0
[ 246.719247] cancel_delayed_work_sync+0x13/0x20
[ 246.720043] drm_kms_helper_poll_disable+0x1f/0x30 [drm_kms_helper]
[ 246.721123] nouveau_pmops_runtime_suspend+0x3d/0xb0 [nouveau]
[ 246.721897] pci_pm_runtime_suspend+0x6b/0x190
[ 246.722825] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.723737] __rpm_callback+0x7a/0x1d0
[ 246.724721] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.725607] rpm_callback+0x24/0x80
[ 246.726553] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.727376] rpm_suspend+0x142/0x6b0
[ 246.728185] pm_runtime_work+0x97/0xc0
[ 246.728938] process_one_work+0x231/0x620
[ 246.729796] worker_thread+0x44/0x3a0
[ 246.730614] kthread+0x12b/0x150
[ 246.731395] ? wq_pool_ids_show+0x140/0x140
[ 246.732202] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.732878] ret_from_fork+0x3a/0x50
[ 246.733768] INFO: task kworker/4:2:422 blocked for more than 120 seconds.
[ 246.734587] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.735393] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.736113] kworker/4:2 D 0 422 2 0x80000080
[ 246.736789] Workqueue: events_long drm_dp_mst_link_probe_work [drm_kms_helper]
[ 246.737665] Call Trace:
[ 246.738490] __schedule+0x322/0xaf0
[ 246.739250] schedule+0x33/0x90
[ 246.739908] rpm_resume+0x19c/0x850
[ 246.740750] ? finish_wait+0x90/0x90
[ 246.741541] __pm_runtime_resume+0x4e/0x90
[ 246.742370] nv50_disp_atomic_commit+0x31/0x210 [nouveau]
[ 246.743124] drm_atomic_commit+0x4a/0x50 [drm]
[ 246.743775] restore_fbdev_mode_atomic+0x1c8/0x240 [drm_kms_helper]
[ 246.744603] restore_fbdev_mode+0x31/0x140 [drm_kms_helper]
[ 246.745373] drm_fb_helper_restore_fbdev_mode_unlocked+0x54/0xb0 [drm_kms_helper]
[ 246.746220] drm_fb_helper_set_par+0x2d/0x50 [drm_kms_helper]
[ 246.746884] drm_fb_helper_hotplug_event.part.28+0x96/0xb0 [drm_kms_helper]
[ 246.747675] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.748544] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.749439] nv50_mstm_hotplug+0x15/0x20 [nouveau]
[ 246.750111] drm_dp_send_link_address+0x177/0x1c0 [drm_kms_helper]
[ 246.750764] drm_dp_check_and_send_link_address+0xa8/0xd0 [drm_kms_helper]
[ 246.751602] drm_dp_mst_link_probe_work+0x51/0x90 [drm_kms_helper]
[ 246.752314] process_one_work+0x231/0x620
[ 246.752979] worker_thread+0x44/0x3a0
[ 246.753838] kthread+0x12b/0x150
[ 246.754619] ? wq_pool_ids_show+0x140/0x140
[ 246.755386] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.756162] ret_from_fork+0x3a/0x50
[ 246.756847]
Showing all locks held in the system:
[ 246.758261] 3 locks held by kworker/4:0/37:
[ 246.759016] #0: 00000000f8df4d2d ((wq_completion)"events"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.759856] #1: 00000000e6065461 ((work_completion)(&(&dev->mode_config.output_poll_work)->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.760670] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.761516] 2 locks held by kworker/0:1/60:
[ 246.762274] #0: 00000000fff6be0f ((wq_completion)"pm"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.762982] #1: 000000005ab44fb4 ((work_completion)(&dev->power.work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.763890] 1 lock held by khungtaskd/64:
[ 246.764664] #0: 000000008cb8b5c3 (rcu_read_lock){....}, at: debug_show_all_locks+0x23/0x185
[ 246.765588] 5 locks held by kworker/4:2/422:
[ 246.766440] #0: 00000000232f0959 ((wq_completion)"events_long"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.767390] #1: 00000000bb59b134 ((work_completion)(&mgr->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.768154] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_restore_fbdev_mode_unlocked+0x4c/0xb0 [drm_kms_helper]
[ 246.768966] #3: 000000004c8f0b6b (crtc_ww_class_acquire){+.+.}, at: restore_fbdev_mode_atomic+0x4b/0x240 [drm_kms_helper]
[ 246.769921] #4: 000000004c34a296 (crtc_ww_class_mutex){+.+.}, at: drm_modeset_backoff+0x8a/0x1b0 [drm]
[ 246.770839] 1 lock held by dmesg/1038:
[ 246.771739] 2 locks held by zsh/1172:
[ 246.772650] #0: 00000000836d0438 (&tty->ldisc_sem){++++}, at: ldsem_down_read+0x37/0x40
[ 246.773680] #1: 000000001f4f4d48 (&ldata->atomic_read_lock){+.+.}, at: n_tty_read+0xc1/0x870
[ 246.775522] =============================================
After trying dozens of different solutions, I found one very simple one
that should also have the benefit of preventing us from having to fight
locking for the rest of our lives. So, we work around these deadlocks by
deferring all fbcon hotplug events that happen after the runtime suspend
process starts until after the device is resumed again.
Changes since v7:
- Fixup commit message - Daniel Vetter
Changes since v6:
- Remove unused nouveau_fbcon_hotplugged_in_suspend() - Ilia
Changes since v5:
- Come up with the (hopefully final) solution for solving this dumb
problem, one that is a lot less likely to cause issues with locking in
the future. This should work around all deadlock conditions with fbcon
brought up thus far.
Changes since v4:
- Add nouveau_fbcon_hotplugged_in_suspend() to workaround deadlock
condition that Lukas described
- Just move all of this out of drm_fb_helper. It seems that other DRM
drivers have already figured out other workarounds for this. If other
drivers do end up needing this in the future, we can just move this
back into drm_fb_helper again.
Changes since v3:
- Actually check if fb_helper is NULL in both new helpers
- Actually check drm_fbdev_emulation in both new helpers
- Don't fire off a fb_helper hotplug unconditionally; only do it if
the following conditions are true (as otherwise, calling this in the
wrong spot will cause Bad Things to happen):
- fb_helper hotplug handling was actually inhibited previously
- fb_helper actually has a delayed hotplug pending
- fb_helper is actually bound
- fb_helper is actually initialized
- Add __must_check to drm_fb_helper_suspend_hotplug(). There's no
situation where a driver would actually want to use this without
checking the return value, so enforce that
- Rewrite and clarify the documentation for both helpers.
- Make sure to return true in the drm_fb_helper_suspend_hotplug() stub
that's provided in drm_fb_helper.h when CONFIG_DRM_FBDEV_EMULATION
isn't enabled
- Actually grab the toplevel fb_helper lock in
drm_fb_helper_resume_hotplug(), since it's possible other activity
(such as a hotplug) could be going on at the same time the driver
calls drm_fb_helper_resume_hotplug(). We need this to check whether or
not drm_fb_helper_hotplug_event() needs to be called anyway
Signed-off-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Karol Herbst <kherbst@redhat.com>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Cc: stable@vger.kernel.org
Cc: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
2018-08-16 03:00:13 +08:00
|
|
|
void
|
|
|
|
nouveau_fbcon_output_poll_changed(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
|
|
struct nouveau_fbdev *fbcon = drm->fbcon;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!fbcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&fbcon->hotplug_lock);
|
|
|
|
|
|
|
|
ret = pm_runtime_get(dev->dev);
|
|
|
|
if (ret == 1 || ret == -EACCES) {
|
|
|
|
drm_fb_helper_hotplug_event(&fbcon->helper);
|
|
|
|
|
|
|
|
pm_runtime_mark_last_busy(dev->dev);
|
|
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
|
|
} else if (ret == 0) {
|
|
|
|
/* If the GPU was already in the process of suspending before
|
|
|
|
* this event happened, then we can't block here as we'll
|
|
|
|
* deadlock the runtime pmops since they wait for us to
|
|
|
|
* finish. So, just defer this event for when we runtime
|
|
|
|
* resume again. It will be handled by fbcon_work.
|
|
|
|
*/
|
|
|
|
NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n");
|
|
|
|
fbcon->hotplug_waiting = true;
|
|
|
|
pm_runtime_put_noidle(drm->dev->dev);
|
|
|
|
} else {
|
|
|
|
DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
|
|
|
|
ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&fbcon->hotplug_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon)
|
|
|
|
{
|
|
|
|
struct nouveau_drm *drm;
|
|
|
|
|
|
|
|
if (!fbcon)
|
|
|
|
return;
|
|
|
|
drm = nouveau_drm(fbcon->helper.dev);
|
|
|
|
|
|
|
|
mutex_lock(&fbcon->hotplug_lock);
|
|
|
|
if (fbcon->hotplug_waiting) {
|
|
|
|
fbcon->hotplug_waiting = false;
|
|
|
|
|
|
|
|
NV_DEBUG(drm, "Handling deferred fbcon HPD events\n");
|
|
|
|
drm_fb_helper_hotplug_event(&fbcon->helper);
|
|
|
|
}
|
|
|
|
mutex_unlock(&fbcon->hotplug_lock);
|
|
|
|
}
|
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
int
|
|
|
|
nouveau_fbcon_init(struct drm_device *dev)
|
2010-03-30 13:34:13 +08:00
|
|
|
{
|
2012-07-31 14:16:21 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2012-07-20 06:17:34 +08:00
|
|
|
struct nouveau_fbdev *fbcon;
|
2018-01-12 15:50:37 +08:00
|
|
|
int preferred_bpp = nouveau_fbcon_bpp;
|
2010-06-06 17:50:03 +08:00
|
|
|
int ret;
|
2010-03-30 13:34:14 +08:00
|
|
|
|
2013-09-10 11:20:34 +08:00
|
|
|
if (!dev->mode_config.num_crtc ||
|
|
|
|
(dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
|
2012-07-20 06:17:34 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
|
|
|
|
if (!fbcon)
|
2010-03-30 13:34:14 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
drm->fbcon = fbcon;
|
2017-01-12 10:25:24 +08:00
|
|
|
INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
|
drm/nouveau/drm/nouveau: Fix deadlock with fb_helper with async RPM requests
Currently, nouveau uses the generic drm_fb_helper_output_poll_changed()
function provided by DRM as it's output_poll_changed callback.
Unfortunately however, this function doesn't grab runtime PM references
early enough and even if it did-we can't block waiting for the device to
resume in output_poll_changed() since it's very likely that we'll need
to grab the fb_helper lock at some point during the runtime resume
process. This currently results in deadlocking like so:
[ 246.669625] INFO: task kworker/4:0:37 blocked for more than 120 seconds.
[ 246.673398] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.675271] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.676527] kworker/4:0 D 0 37 2 0x80000000
[ 246.677580] Workqueue: events output_poll_execute [drm_kms_helper]
[ 246.678704] Call Trace:
[ 246.679753] __schedule+0x322/0xaf0
[ 246.680916] schedule+0x33/0x90
[ 246.681924] schedule_preempt_disabled+0x15/0x20
[ 246.683023] __mutex_lock+0x569/0x9a0
[ 246.684035] ? kobject_uevent_env+0x117/0x7b0
[ 246.685132] ? drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.686179] mutex_lock_nested+0x1b/0x20
[ 246.687278] ? mutex_lock_nested+0x1b/0x20
[ 246.688307] drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.689420] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.690462] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.691570] output_poll_execute+0x198/0x1c0 [drm_kms_helper]
[ 246.692611] process_one_work+0x231/0x620
[ 246.693725] worker_thread+0x214/0x3a0
[ 246.694756] kthread+0x12b/0x150
[ 246.695856] ? wq_pool_ids_show+0x140/0x140
[ 246.696888] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.697998] ret_from_fork+0x3a/0x50
[ 246.699034] INFO: task kworker/0:1:60 blocked for more than 120 seconds.
[ 246.700153] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.701182] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.702278] kworker/0:1 D 0 60 2 0x80000000
[ 246.703293] Workqueue: pm pm_runtime_work
[ 246.704393] Call Trace:
[ 246.705403] __schedule+0x322/0xaf0
[ 246.706439] ? wait_for_completion+0x104/0x190
[ 246.707393] schedule+0x33/0x90
[ 246.708375] schedule_timeout+0x3a5/0x590
[ 246.709289] ? mark_held_locks+0x58/0x80
[ 246.710208] ? _raw_spin_unlock_irq+0x2c/0x40
[ 246.711222] ? wait_for_completion+0x104/0x190
[ 246.712134] ? trace_hardirqs_on_caller+0xf4/0x190
[ 246.713094] ? wait_for_completion+0x104/0x190
[ 246.713964] wait_for_completion+0x12c/0x190
[ 246.714895] ? wake_up_q+0x80/0x80
[ 246.715727] ? get_work_pool+0x90/0x90
[ 246.716649] flush_work+0x1c9/0x280
[ 246.717483] ? flush_workqueue_prep_pwqs+0x1b0/0x1b0
[ 246.718442] __cancel_work_timer+0x146/0x1d0
[ 246.719247] cancel_delayed_work_sync+0x13/0x20
[ 246.720043] drm_kms_helper_poll_disable+0x1f/0x30 [drm_kms_helper]
[ 246.721123] nouveau_pmops_runtime_suspend+0x3d/0xb0 [nouveau]
[ 246.721897] pci_pm_runtime_suspend+0x6b/0x190
[ 246.722825] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.723737] __rpm_callback+0x7a/0x1d0
[ 246.724721] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.725607] rpm_callback+0x24/0x80
[ 246.726553] ? pci_has_legacy_pm_support+0x70/0x70
[ 246.727376] rpm_suspend+0x142/0x6b0
[ 246.728185] pm_runtime_work+0x97/0xc0
[ 246.728938] process_one_work+0x231/0x620
[ 246.729796] worker_thread+0x44/0x3a0
[ 246.730614] kthread+0x12b/0x150
[ 246.731395] ? wq_pool_ids_show+0x140/0x140
[ 246.732202] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.732878] ret_from_fork+0x3a/0x50
[ 246.733768] INFO: task kworker/4:2:422 blocked for more than 120 seconds.
[ 246.734587] Not tainted 4.18.0-rc5Lyude-Test+ #2
[ 246.735393] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 246.736113] kworker/4:2 D 0 422 2 0x80000080
[ 246.736789] Workqueue: events_long drm_dp_mst_link_probe_work [drm_kms_helper]
[ 246.737665] Call Trace:
[ 246.738490] __schedule+0x322/0xaf0
[ 246.739250] schedule+0x33/0x90
[ 246.739908] rpm_resume+0x19c/0x850
[ 246.740750] ? finish_wait+0x90/0x90
[ 246.741541] __pm_runtime_resume+0x4e/0x90
[ 246.742370] nv50_disp_atomic_commit+0x31/0x210 [nouveau]
[ 246.743124] drm_atomic_commit+0x4a/0x50 [drm]
[ 246.743775] restore_fbdev_mode_atomic+0x1c8/0x240 [drm_kms_helper]
[ 246.744603] restore_fbdev_mode+0x31/0x140 [drm_kms_helper]
[ 246.745373] drm_fb_helper_restore_fbdev_mode_unlocked+0x54/0xb0 [drm_kms_helper]
[ 246.746220] drm_fb_helper_set_par+0x2d/0x50 [drm_kms_helper]
[ 246.746884] drm_fb_helper_hotplug_event.part.28+0x96/0xb0 [drm_kms_helper]
[ 246.747675] drm_fb_helper_output_poll_changed+0x23/0x30 [drm_kms_helper]
[ 246.748544] drm_kms_helper_hotplug_event+0x2a/0x30 [drm_kms_helper]
[ 246.749439] nv50_mstm_hotplug+0x15/0x20 [nouveau]
[ 246.750111] drm_dp_send_link_address+0x177/0x1c0 [drm_kms_helper]
[ 246.750764] drm_dp_check_and_send_link_address+0xa8/0xd0 [drm_kms_helper]
[ 246.751602] drm_dp_mst_link_probe_work+0x51/0x90 [drm_kms_helper]
[ 246.752314] process_one_work+0x231/0x620
[ 246.752979] worker_thread+0x44/0x3a0
[ 246.753838] kthread+0x12b/0x150
[ 246.754619] ? wq_pool_ids_show+0x140/0x140
[ 246.755386] ? kthread_create_worker_on_cpu+0x70/0x70
[ 246.756162] ret_from_fork+0x3a/0x50
[ 246.756847]
Showing all locks held in the system:
[ 246.758261] 3 locks held by kworker/4:0/37:
[ 246.759016] #0: 00000000f8df4d2d ((wq_completion)"events"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.759856] #1: 00000000e6065461 ((work_completion)(&(&dev->mode_config.output_poll_work)->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.760670] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_hotplug_event.part.28+0x20/0xb0 [drm_kms_helper]
[ 246.761516] 2 locks held by kworker/0:1/60:
[ 246.762274] #0: 00000000fff6be0f ((wq_completion)"pm"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.762982] #1: 000000005ab44fb4 ((work_completion)(&dev->power.work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.763890] 1 lock held by khungtaskd/64:
[ 246.764664] #0: 000000008cb8b5c3 (rcu_read_lock){....}, at: debug_show_all_locks+0x23/0x185
[ 246.765588] 5 locks held by kworker/4:2/422:
[ 246.766440] #0: 00000000232f0959 ((wq_completion)"events_long"){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.767390] #1: 00000000bb59b134 ((work_completion)(&mgr->work)){+.+.}, at: process_one_work+0x1b3/0x620
[ 246.768154] #2: 00000000cb66735f (&helper->lock){+.+.}, at: drm_fb_helper_restore_fbdev_mode_unlocked+0x4c/0xb0 [drm_kms_helper]
[ 246.768966] #3: 000000004c8f0b6b (crtc_ww_class_acquire){+.+.}, at: restore_fbdev_mode_atomic+0x4b/0x240 [drm_kms_helper]
[ 246.769921] #4: 000000004c34a296 (crtc_ww_class_mutex){+.+.}, at: drm_modeset_backoff+0x8a/0x1b0 [drm]
[ 246.770839] 1 lock held by dmesg/1038:
[ 246.771739] 2 locks held by zsh/1172:
[ 246.772650] #0: 00000000836d0438 (&tty->ldisc_sem){++++}, at: ldsem_down_read+0x37/0x40
[ 246.773680] #1: 000000001f4f4d48 (&ldata->atomic_read_lock){+.+.}, at: n_tty_read+0xc1/0x870
[ 246.775522] =============================================
After trying dozens of different solutions, I found one very simple one
that should also have the benefit of preventing us from having to fight
locking for the rest of our lives. So, we work around these deadlocks by
deferring all fbcon hotplug events that happen after the runtime suspend
process starts until after the device is resumed again.
Changes since v7:
- Fixup commit message - Daniel Vetter
Changes since v6:
- Remove unused nouveau_fbcon_hotplugged_in_suspend() - Ilia
Changes since v5:
- Come up with the (hopefully final) solution for solving this dumb
problem, one that is a lot less likely to cause issues with locking in
the future. This should work around all deadlock conditions with fbcon
brought up thus far.
Changes since v4:
- Add nouveau_fbcon_hotplugged_in_suspend() to workaround deadlock
condition that Lukas described
- Just move all of this out of drm_fb_helper. It seems that other DRM
drivers have already figured out other workarounds for this. If other
drivers do end up needing this in the future, we can just move this
back into drm_fb_helper again.
Changes since v3:
- Actually check if fb_helper is NULL in both new helpers
- Actually check drm_fbdev_emulation in both new helpers
- Don't fire off a fb_helper hotplug unconditionally; only do it if
the following conditions are true (as otherwise, calling this in the
wrong spot will cause Bad Things to happen):
- fb_helper hotplug handling was actually inhibited previously
- fb_helper actually has a delayed hotplug pending
- fb_helper is actually bound
- fb_helper is actually initialized
- Add __must_check to drm_fb_helper_suspend_hotplug(). There's no
situation where a driver would actually want to use this without
checking the return value, so enforce that
- Rewrite and clarify the documentation for both helpers.
- Make sure to return true in the drm_fb_helper_suspend_hotplug() stub
that's provided in drm_fb_helper.h when CONFIG_DRM_FBDEV_EMULATION
isn't enabled
- Actually grab the toplevel fb_helper lock in
drm_fb_helper_resume_hotplug(), since it's possible other activity
(such as a hotplug) could be going on at the same time the driver
calls drm_fb_helper_resume_hotplug(). We need this to check whether or
not drm_fb_helper_hotplug_event() needs to be called anyway
Signed-off-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Karol Herbst <kherbst@redhat.com>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Cc: stable@vger.kernel.org
Cc: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
2018-08-16 03:00:13 +08:00
|
|
|
mutex_init(&fbcon->hotplug_lock);
|
2014-06-27 23:19:24 +08:00
|
|
|
|
|
|
|
drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
|
2010-03-30 13:34:14 +08:00
|
|
|
|
drm: Rely on mode_config data for fb_helper initialization
Instead of receiving the num_crts as a parameter, we can read it
directly from the mode_config structure. I audited the drivers that
invoke this helper and I believe all of them initialize the mode_config
struct accordingly, prior to calling the fb_helper.
I used the following coccinelle hack to make this transformation, except
for the function headers and comment updates. The first and second
rules are split because I couldn't find a way to remove the unused
temporary variables at the same time I removed the parameter.
// <smpl>
@r@
expression A,B,D,E;
identifier C;
@@
(
- drm_fb_helper_init(A,B,C,D)
+ drm_fb_helper_init(A,B,D)
|
- drm_fbdev_cma_init_with_funcs(A,B,C,D,E)
+ drm_fbdev_cma_init_with_funcs(A,B,D,E)
|
- drm_fbdev_cma_init(A,B,C,D)
+ drm_fbdev_cma_init(A,B,D)
)
@@
expression A,B,C,D,E;
@@
(
- drm_fb_helper_init(A,B,C,D)
+ drm_fb_helper_init(A,B,D)
|
- drm_fbdev_cma_init_with_funcs(A,B,C,D,E)
+ drm_fbdev_cma_init_with_funcs(A,B,D,E)
|
- drm_fbdev_cma_init(A,B,C,D)
+ drm_fbdev_cma_init(A,B,D)
)
@@
identifier r.C;
type T;
expression V;
@@
- T C;
<...
when != C
- C = V;
...>
// </smpl>
Changes since v1:
- Rebased on top of the tip of drm-misc-next.
- Remove mention to sti since a proper fix got merged.
Suggested-by: Daniel Vetter <daniel.vetter@intel.com>
Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.co.uk>
Reviewed-by: Eric Anholt <eric@anholt.net>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/20170202162640.27261-1-krisman@collabora.co.uk
2017-02-03 00:26:40 +08:00
|
|
|
ret = drm_fb_helper_init(dev, &fbcon->helper, 4);
|
2014-12-19 18:21:32 +08:00
|
|
|
if (ret)
|
|
|
|
goto free;
|
2010-06-06 17:50:03 +08:00
|
|
|
|
2014-12-19 18:21:32 +08:00
|
|
|
ret = drm_fb_helper_single_add_all_connectors(&fbcon->helper);
|
|
|
|
if (ret)
|
|
|
|
goto fini;
|
2011-11-07 03:32:04 +08:00
|
|
|
|
2018-01-12 15:50:37 +08:00
|
|
|
if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) {
|
|
|
|
if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
|
|
|
|
preferred_bpp = 8;
|
|
|
|
else
|
|
|
|
if (drm->client.device.info.ram_size <= 64 * 1024 * 1024)
|
|
|
|
preferred_bpp = 16;
|
|
|
|
else
|
|
|
|
preferred_bpp = 32;
|
|
|
|
}
|
2011-11-07 03:32:04 +08:00
|
|
|
|
2013-01-21 06:12:54 +08:00
|
|
|
/* disable all the possible outputs/crtcs before entering KMS mode */
|
2016-12-22 16:50:42 +08:00
|
|
|
if (!drm_drv_uses_atomic_modeset(dev))
|
2016-11-04 15:20:36 +08:00
|
|
|
drm_helper_disable_unused_functions(dev);
|
2013-01-21 06:12:54 +08:00
|
|
|
|
2014-12-19 18:21:32 +08:00
|
|
|
ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
|
|
|
|
if (ret)
|
|
|
|
goto fini;
|
|
|
|
|
2016-06-20 18:52:14 +08:00
|
|
|
if (fbcon->helper.fbdev)
|
|
|
|
fbcon->helper.fbdev->pixmap.buf_align = 4;
|
2010-03-30 13:34:13 +08:00
|
|
|
return 0;
|
2014-12-19 18:21:32 +08:00
|
|
|
|
|
|
|
fini:
|
|
|
|
drm_fb_helper_fini(&fbcon->helper);
|
|
|
|
free:
|
|
|
|
kfree(fbcon);
|
|
|
|
return ret;
|
2010-03-30 13:34:13 +08:00
|
|
|
}
|
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
void
|
|
|
|
nouveau_fbcon_fini(struct drm_device *dev)
|
2010-03-30 13:34:13 +08:00
|
|
|
{
|
2012-07-31 14:16:21 +08:00
|
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
2010-03-30 13:34:14 +08:00
|
|
|
|
2012-07-20 06:17:34 +08:00
|
|
|
if (!drm->fbcon)
|
2010-03-30 13:34:14 +08:00
|
|
|
return;
|
|
|
|
|
2014-06-28 18:44:07 +08:00
|
|
|
nouveau_fbcon_accel_fini(dev);
|
2012-07-20 06:17:34 +08:00
|
|
|
nouveau_fbcon_destroy(dev, drm->fbcon);
|
|
|
|
kfree(drm->fbcon);
|
|
|
|
drm->fbcon = NULL;
|
2010-03-30 13:34:13 +08:00
|
|
|
}
|