Commit 6915190a authored by Thomas Zimmermann's avatar Thomas Zimmermann
Browse files

drm/client: Support emergency restore via sysrq for all clients



Move the sysrq functionality from DRM fbdev helpers to the DRM device
and in-kernel clients, so that it becomes available on all clients.

DRM fbdev helpers support emergency restoration of the console output
via a special key combination. Press SysRq+v to replace the current
compositor with the kernel's output on the framebuffer console. This
allows users to see the log messages during system emergencies.

By moving the functionality from fbdev helpers to the DRM device, any
in-kernel client can serve as emergency output. This can be used to
bring up drm_log, for example.

Each DRM device registers itself to the list of possible sysrq handlers.
On receiving SysRq+v, the DRM core goes over all registered devices and
restores an in-kernel DRM client for each of them.

See Documentation/admin-guide/sysrq.rst on how to invoke SysRq. Switch
VTs to bring back the user-space compositor.

v2:
- declare placeholders as 'static inline' (kernel test robot)
- fix grammar in commit description

Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: default avatarJocelyn Falempe <jfalempe@redhat.com>
Link: https://patch.msgid.link/20251110154616.539328-3-tzimmermann@suse.de
parent 943240d3
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -76,7 +76,8 @@ drm-y := \
drm-$(CONFIG_DRM_CLIENT) += \
	drm_client.o \
	drm_client_event.o \
	drm_client_modeset.o
	drm_client_modeset.o \
	drm_client_sysrq.o
drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
drm-$(CONFIG_COMPAT) += drm_ioc32.o
drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#include <linux/slab.h>

#include <drm/drm_client.h>
#include <drm/drm_client_event.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
+65 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0 or MIT

#include <linux/sysrq.h>

#include <drm/drm_client_event.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>

#include "drm_internal.h"

#ifdef CONFIG_MAGIC_SYSRQ
static LIST_HEAD(drm_client_sysrq_dev_list);
static DEFINE_MUTEX(drm_client_sysrq_dev_lock);

/* emergency restore, don't bother with error reporting */
static void drm_client_sysrq_restore_work_fn(struct work_struct *ignored)
{
	struct drm_device *dev;

	guard(mutex)(&drm_client_sysrq_dev_lock);

	list_for_each_entry(dev, &drm_client_sysrq_dev_list, client_sysrq_list) {
		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
			continue;

		drm_client_dev_restore(dev, true);
	}
}

static DECLARE_WORK(drm_client_sysrq_restore_work, drm_client_sysrq_restore_work_fn);

static void drm_client_sysrq_restore_handler(u8 ignored)
{
	schedule_work(&drm_client_sysrq_restore_work);
}

static const struct sysrq_key_op drm_client_sysrq_restore_op = {
	.handler = drm_client_sysrq_restore_handler,
	.help_msg = "force-fb(v)",
	.action_msg = "Restore framebuffer console",
};

void drm_client_sysrq_register(struct drm_device *dev)
{
	guard(mutex)(&drm_client_sysrq_dev_lock);

	if (list_empty(&drm_client_sysrq_dev_list))
		register_sysrq_key('v', &drm_client_sysrq_restore_op);

	list_add(&dev->client_sysrq_list, &drm_client_sysrq_dev_list);
}

void drm_client_sysrq_unregister(struct drm_device *dev)
{
	guard(mutex)(&drm_client_sysrq_dev_lock);

	/* remove device from global restore list */
	if (!drm_WARN_ON(dev, list_empty(&dev->client_sysrq_list)))
		list_del(&dev->client_sysrq_list);

	/* no devices left; unregister key */
	if (list_empty(&drm_client_sysrq_dev_list))
		unregister_sysrq_key('v', &drm_client_sysrq_restore_op);
}
#endif
+3 −0
Original line number Diff line number Diff line
@@ -733,6 +733,7 @@ static int drm_dev_init(struct drm_device *dev,
	INIT_LIST_HEAD(&dev->filelist);
	INIT_LIST_HEAD(&dev->filelist_internal);
	INIT_LIST_HEAD(&dev->clientlist);
	INIT_LIST_HEAD(&dev->client_sysrq_list);
	INIT_LIST_HEAD(&dev->vblank_event_list);

	spin_lock_init(&dev->event_lock);
@@ -1100,6 +1101,7 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
			goto err_unload;
	}
	drm_panic_register(dev);
	drm_client_sysrq_register(dev);

	DRM_INFO("Initialized %s %d.%d.%d for %s on minor %d\n",
		 driver->name, driver->major, driver->minor,
@@ -1144,6 +1146,7 @@ void drm_dev_unregister(struct drm_device *dev)
{
	dev->registered = false;

	drm_client_sysrq_unregister(dev);
	drm_panic_unregister(dev);

	drm_client_dev_unregister(dev);
+1 −44
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@
#include <linux/console.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/sysrq.h>
#include <linux/vga_switcheroo.h>

#include <drm/drm_atomic.h>
@@ -270,42 +269,6 @@ int drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper, b
}
EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode_unlocked);

#ifdef CONFIG_MAGIC_SYSRQ
/* emergency restore, don't bother with error reporting */
static void drm_fb_helper_restore_work_fn(struct work_struct *ignored)
{
	struct drm_fb_helper *helper;

	mutex_lock(&kernel_fb_helper_lock);
	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
		struct drm_device *dev = helper->dev;

		if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
			continue;

		mutex_lock(&helper->lock);
		drm_client_modeset_commit_locked(&helper->client);
		mutex_unlock(&helper->lock);
	}
	mutex_unlock(&kernel_fb_helper_lock);
}

static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn);

static void drm_fb_helper_sysrq(u8 dummy1)
{
	schedule_work(&drm_fb_helper_restore_work);
}

static const struct sysrq_key_op sysrq_drm_fb_helper_restore_op = {
	.handler = drm_fb_helper_sysrq,
	.help_msg = "force-fb(v)",
	.action_msg = "Restore framebuffer console",
};
#else
static const struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { };
#endif

static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode)
{
	struct drm_fb_helper *fb_helper = info->par;
@@ -602,11 +565,8 @@ void drm_fb_helper_fini(struct drm_fb_helper *fb_helper)
	drm_fb_helper_release_info(fb_helper);

	mutex_lock(&kernel_fb_helper_lock);
	if (!list_empty(&fb_helper->kernel_fb_list)) {
	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);

	if (!fb_helper->client.funcs)
@@ -1840,9 +1800,6 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper)
		 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);

	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
	mutex_unlock(&kernel_fb_helper_lock);

Loading