7fec8f5379
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>
618 lines
16 KiB
C
618 lines
16 KiB
C
/*
|
|
* 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>
|
|
#include <linux/vga_switcheroo.h>
|
|
#include <linux/console.h>
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_atomic.h>
|
|
|
|
#include "nouveau_drv.h"
|
|
#include "nouveau_gem.h"
|
|
#include "nouveau_bo.h"
|
|
#include "nouveau_fbcon.h"
|
|
#include "nouveau_chan.h"
|
|
#include "nouveau_vmm.h"
|
|
|
|
#include "nouveau_crtc.h"
|
|
|
|
MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
|
|
int nouveau_nofbaccel = 0;
|
|
module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
|
|
|
|
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);
|
|
|
|
static void
|
|
nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
struct nvif_device *device = &drm->client.device;
|
|
int ret;
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
return;
|
|
|
|
ret = -ENODEV;
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
|
mutex_trylock(&drm->client.mutex)) {
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
|
ret = nv04_fbcon_fillrect(info, rect);
|
|
else
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
|
ret = nv50_fbcon_fillrect(info, rect);
|
|
else
|
|
ret = nvc0_fbcon_fillrect(info, rect);
|
|
mutex_unlock(&drm->client.mutex);
|
|
}
|
|
|
|
if (ret == 0)
|
|
return;
|
|
|
|
if (ret != -ENODEV)
|
|
nouveau_fbcon_gpu_lockup(info);
|
|
drm_fb_helper_cfb_fillrect(info, rect);
|
|
}
|
|
|
|
static void
|
|
nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
struct nvif_device *device = &drm->client.device;
|
|
int ret;
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
return;
|
|
|
|
ret = -ENODEV;
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
|
mutex_trylock(&drm->client.mutex)) {
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
|
ret = nv04_fbcon_copyarea(info, image);
|
|
else
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
|
ret = nv50_fbcon_copyarea(info, image);
|
|
else
|
|
ret = nvc0_fbcon_copyarea(info, image);
|
|
mutex_unlock(&drm->client.mutex);
|
|
}
|
|
|
|
if (ret == 0)
|
|
return;
|
|
|
|
if (ret != -ENODEV)
|
|
nouveau_fbcon_gpu_lockup(info);
|
|
drm_fb_helper_cfb_copyarea(info, image);
|
|
}
|
|
|
|
static void
|
|
nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
struct nvif_device *device = &drm->client.device;
|
|
int ret;
|
|
|
|
if (info->state != FBINFO_STATE_RUNNING)
|
|
return;
|
|
|
|
ret = -ENODEV;
|
|
if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
|
|
mutex_trylock(&drm->client.mutex)) {
|
|
if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
|
|
ret = nv04_fbcon_imageblit(info, image);
|
|
else
|
|
if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
|
|
ret = nv50_fbcon_imageblit(info, image);
|
|
else
|
|
ret = nvc0_fbcon_imageblit(info, image);
|
|
mutex_unlock(&drm->client.mutex);
|
|
}
|
|
|
|
if (ret == 0)
|
|
return;
|
|
|
|
if (ret != -ENODEV)
|
|
nouveau_fbcon_gpu_lockup(info);
|
|
drm_fb_helper_cfb_imageblit(info, image);
|
|
}
|
|
|
|
static int
|
|
nouveau_fbcon_sync(struct fb_info *info)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
struct nouveau_channel *chan = drm->channel;
|
|
int ret;
|
|
|
|
if (!chan || !chan->accel_done || in_interrupt() ||
|
|
info->state != FBINFO_STATE_RUNNING ||
|
|
info->flags & FBINFO_HWACCEL_DISABLED)
|
|
return 0;
|
|
|
|
if (!mutex_trylock(&drm->client.mutex))
|
|
return 0;
|
|
|
|
ret = nouveau_channel_idle(chan);
|
|
mutex_unlock(&drm->client.mutex);
|
|
if (ret) {
|
|
nouveau_fbcon_gpu_lockup(info);
|
|
return 0;
|
|
}
|
|
|
|
chan->accel_done = false;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nouveau_fbcon_open(struct fb_info *info, int user)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
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;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
pm_runtime_put(drm->dev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct fb_ops nouveau_fbcon_ops = {
|
|
.owner = THIS_MODULE,
|
|
DRM_FB_HELPER_DEFAULT_OPS,
|
|
.fb_open = nouveau_fbcon_open,
|
|
.fb_release = nouveau_fbcon_release,
|
|
.fb_fillrect = nouveau_fbcon_fillrect,
|
|
.fb_copyarea = nouveau_fbcon_copyarea,
|
|
.fb_imageblit = nouveau_fbcon_imageblit,
|
|
.fb_sync = nouveau_fbcon_sync,
|
|
};
|
|
|
|
static struct fb_ops nouveau_fbcon_sw_ops = {
|
|
.owner = THIS_MODULE,
|
|
DRM_FB_HELPER_DEFAULT_OPS,
|
|
.fb_open = nouveau_fbcon_open,
|
|
.fb_release = nouveau_fbcon_release,
|
|
.fb_fillrect = drm_fb_helper_cfb_fillrect,
|
|
.fb_copyarea = drm_fb_helper_cfb_copyarea,
|
|
.fb_imageblit = drm_fb_helper_cfb_imageblit,
|
|
};
|
|
|
|
void
|
|
nouveau_fbcon_accel_save_disable(struct drm_device *dev)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
if (drm->fbcon && drm->fbcon->helper.fbdev) {
|
|
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);
|
|
if (drm->fbcon && drm->fbcon->helper.fbdev) {
|
|
drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
|
|
}
|
|
}
|
|
|
|
static void
|
|
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();
|
|
if (fbcon->helper.fbdev)
|
|
fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
|
|
console_unlock();
|
|
nouveau_channel_idle(drm->channel);
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
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;
|
|
|
|
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
|
|
ret = nv04_fbcon_accel_init(info);
|
|
else
|
|
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
|
|
ret = nv50_fbcon_accel_init(info);
|
|
else
|
|
ret = nvc0_fbcon_accel_init(info);
|
|
|
|
if (ret == 0)
|
|
info->fbops = &nouveau_fbcon_ops;
|
|
}
|
|
|
|
static void
|
|
nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
|
|
{
|
|
struct fb_info *info = fbcon->helper.fbdev;
|
|
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
|
|
nouveau_fbcon_create(struct drm_fb_helper *helper,
|
|
struct drm_fb_helper_surface_size *sizes)
|
|
{
|
|
struct nouveau_fbdev *fbcon =
|
|
container_of(helper, struct nouveau_fbdev, helper);
|
|
struct drm_device *dev = fbcon->helper.dev;
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
struct nvif_device *device = &drm->client.device;
|
|
struct fb_info *info;
|
|
struct nouveau_framebuffer *fb;
|
|
struct nouveau_channel *chan;
|
|
struct nouveau_bo *nvbo;
|
|
struct drm_mode_fb_cmd2 mode_cmd;
|
|
int ret;
|
|
|
|
mode_cmd.width = sizes->surface_width;
|
|
mode_cmd.height = sizes->surface_height;
|
|
|
|
mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
|
|
mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
|
|
|
|
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
|
|
sizes->surface_depth);
|
|
|
|
ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
|
|
mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
|
|
0, 0x0000, &nvbo);
|
|
if (ret) {
|
|
NV_ERROR(drm, "failed to allocate framebuffer\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = nouveau_framebuffer_new(dev, &mode_cmd, nvbo, &fb);
|
|
if (ret)
|
|
goto out_unref;
|
|
|
|
ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM, false);
|
|
if (ret) {
|
|
NV_ERROR(drm, "failed to pin fb: %d\n", ret);
|
|
goto out_unref;
|
|
}
|
|
|
|
ret = nouveau_bo_map(nvbo);
|
|
if (ret) {
|
|
NV_ERROR(drm, "failed to map fb: %d\n", ret);
|
|
goto out_unpin;
|
|
}
|
|
|
|
chan = nouveau_nofbaccel ? NULL : drm->channel;
|
|
if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
|
|
ret = nouveau_vma_new(nvbo, &drm->client.vmm, &fb->vma);
|
|
if (ret) {
|
|
NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
|
|
chan = NULL;
|
|
}
|
|
}
|
|
|
|
info = drm_fb_helper_alloc_fbi(helper);
|
|
if (IS_ERR(info)) {
|
|
ret = PTR_ERR(info);
|
|
goto out_unlock;
|
|
}
|
|
info->skip_vt_switch = 1;
|
|
|
|
info->par = fbcon;
|
|
|
|
/* setup helper */
|
|
fbcon->helper.fb = &fb->base;
|
|
|
|
strcpy(info->fix.id, "nouveaufb");
|
|
if (!chan)
|
|
info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
|
|
else
|
|
info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
|
|
FBINFO_HWACCEL_FILLRECT |
|
|
FBINFO_HWACCEL_IMAGEBLIT;
|
|
info->flags |= FBINFO_CAN_FORCE_OUTPUT;
|
|
info->fbops = &nouveau_fbcon_sw_ops;
|
|
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;
|
|
|
|
info->screen_base = nvbo_kmap_obj_iovirtual(fb->nvbo);
|
|
info->screen_size = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
|
|
|
|
drm_fb_helper_fill_fix(info, fb->base.pitches[0],
|
|
fb->base.format->depth);
|
|
drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
|
|
|
|
/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
|
|
|
|
if (chan)
|
|
nouveau_fbcon_accel_init(dev);
|
|
nouveau_fbcon_zfill(dev, fbcon);
|
|
|
|
/* To allow resizeing without swapping buffers */
|
|
NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
|
|
fb->base.width, fb->base.height, fb->nvbo->bo.offset, nvbo);
|
|
|
|
vga_switcheroo_client_fb_set(dev->pdev, info);
|
|
return 0;
|
|
|
|
out_unlock:
|
|
if (chan)
|
|
nouveau_vma_del(&fb->vma);
|
|
nouveau_bo_unmap(fb->nvbo);
|
|
out_unpin:
|
|
nouveau_bo_unpin(fb->nvbo);
|
|
out_unref:
|
|
nouveau_bo_ref(NULL, &fb->nvbo);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
|
|
{
|
|
struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fbcon->helper.fb);
|
|
|
|
drm_fb_helper_unregister_fbi(&fbcon->helper);
|
|
drm_fb_helper_fini(&fbcon->helper);
|
|
|
|
if (nouveau_fb && nouveau_fb->nvbo) {
|
|
nouveau_vma_del(&nouveau_fb->vma);
|
|
nouveau_bo_unmap(nouveau_fb->nvbo);
|
|
nouveau_bo_unpin(nouveau_fb->nvbo);
|
|
drm_framebuffer_put(&nouveau_fb->base);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nouveau_fbcon_gpu_lockup(struct fb_info *info)
|
|
{
|
|
struct nouveau_fbdev *fbcon = info->par;
|
|
struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
|
|
|
|
NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
|
|
info->flags |= FBINFO_HWACCEL_DISABLED;
|
|
}
|
|
|
|
static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
|
|
.fb_probe = nouveau_fbcon_create,
|
|
};
|
|
|
|
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) {
|
|
nouveau_fbcon_hotplug_resume(drm->fbcon);
|
|
pm_runtime_mark_last_busy(drm->dev->dev);
|
|
pm_runtime_put_sync(drm->dev->dev);
|
|
}
|
|
}
|
|
|
|
void
|
|
nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
nouveau_fbcon_init(struct drm_device *dev)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
struct nouveau_fbdev *fbcon;
|
|
int preferred_bpp = nouveau_fbcon_bpp;
|
|
int ret;
|
|
|
|
if (!dev->mode_config.num_crtc ||
|
|
(dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
|
|
return 0;
|
|
|
|
fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
|
|
if (!fbcon)
|
|
return -ENOMEM;
|
|
|
|
drm->fbcon = fbcon;
|
|
INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
|
|
mutex_init(&fbcon->hotplug_lock);
|
|
|
|
drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
|
|
|
|
ret = drm_fb_helper_init(dev, &fbcon->helper, 4);
|
|
if (ret)
|
|
goto free;
|
|
|
|
ret = drm_fb_helper_single_add_all_connectors(&fbcon->helper);
|
|
if (ret)
|
|
goto fini;
|
|
|
|
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;
|
|
}
|
|
|
|
/* disable all the possible outputs/crtcs before entering KMS mode */
|
|
if (!drm_drv_uses_atomic_modeset(dev))
|
|
drm_helper_disable_unused_functions(dev);
|
|
|
|
ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
|
|
if (ret)
|
|
goto fini;
|
|
|
|
if (fbcon->helper.fbdev)
|
|
fbcon->helper.fbdev->pixmap.buf_align = 4;
|
|
return 0;
|
|
|
|
fini:
|
|
drm_fb_helper_fini(&fbcon->helper);
|
|
free:
|
|
kfree(fbcon);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
nouveau_fbcon_fini(struct drm_device *dev)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
|
|
if (!drm->fbcon)
|
|
return;
|
|
|
|
nouveau_fbcon_accel_fini(dev);
|
|
nouveau_fbcon_destroy(dev, drm->fbcon);
|
|
kfree(drm->fbcon);
|
|
drm->fbcon = NULL;
|
|
}
|