Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: Jocelyn Falempe <jfalempe@redhat.com>
Link: https://patch.msgid.link/20251110154616.539328-3-tzimmermann@suse.de

+91 -45
+2 -1
drivers/gpu/drm/Makefile
··· 76 76 drm-$(CONFIG_DRM_CLIENT) += \ 77 77 drm_client.o \ 78 78 drm_client_event.o \ 79 - drm_client_modeset.o 79 + drm_client_modeset.o \ 80 + drm_client_sysrq.o 80 81 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o 81 82 drm-$(CONFIG_COMPAT) += drm_ioc32.o 82 83 drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+1
drivers/gpu/drm/drm_client.c
··· 11 11 #include <linux/slab.h> 12 12 13 13 #include <drm/drm_client.h> 14 + #include <drm/drm_client_event.h> 14 15 #include <drm/drm_device.h> 15 16 #include <drm/drm_drv.h> 16 17 #include <drm/drm_file.h>
+65
drivers/gpu/drm/drm_client_sysrq.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 or MIT 2 + 3 + #include <linux/sysrq.h> 4 + 5 + #include <drm/drm_client_event.h> 6 + #include <drm/drm_device.h> 7 + #include <drm/drm_print.h> 8 + 9 + #include "drm_internal.h" 10 + 11 + #ifdef CONFIG_MAGIC_SYSRQ 12 + static LIST_HEAD(drm_client_sysrq_dev_list); 13 + static DEFINE_MUTEX(drm_client_sysrq_dev_lock); 14 + 15 + /* emergency restore, don't bother with error reporting */ 16 + static void drm_client_sysrq_restore_work_fn(struct work_struct *ignored) 17 + { 18 + struct drm_device *dev; 19 + 20 + guard(mutex)(&drm_client_sysrq_dev_lock); 21 + 22 + list_for_each_entry(dev, &drm_client_sysrq_dev_list, client_sysrq_list) { 23 + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) 24 + continue; 25 + 26 + drm_client_dev_restore(dev, true); 27 + } 28 + } 29 + 30 + static DECLARE_WORK(drm_client_sysrq_restore_work, drm_client_sysrq_restore_work_fn); 31 + 32 + static void drm_client_sysrq_restore_handler(u8 ignored) 33 + { 34 + schedule_work(&drm_client_sysrq_restore_work); 35 + } 36 + 37 + static const struct sysrq_key_op drm_client_sysrq_restore_op = { 38 + .handler = drm_client_sysrq_restore_handler, 39 + .help_msg = "force-fb(v)", 40 + .action_msg = "Restore framebuffer console", 41 + }; 42 + 43 + void drm_client_sysrq_register(struct drm_device *dev) 44 + { 45 + guard(mutex)(&drm_client_sysrq_dev_lock); 46 + 47 + if (list_empty(&drm_client_sysrq_dev_list)) 48 + register_sysrq_key('v', &drm_client_sysrq_restore_op); 49 + 50 + list_add(&dev->client_sysrq_list, &drm_client_sysrq_dev_list); 51 + } 52 + 53 + void drm_client_sysrq_unregister(struct drm_device *dev) 54 + { 55 + guard(mutex)(&drm_client_sysrq_dev_lock); 56 + 57 + /* remove device from global restore list */ 58 + if (!drm_WARN_ON(dev, list_empty(&dev->client_sysrq_list))) 59 + list_del(&dev->client_sysrq_list); 60 + 61 + /* no devices left; unregister key */ 62 + if (list_empty(&drm_client_sysrq_dev_list)) 63 + unregister_sysrq_key('v', &drm_client_sysrq_restore_op); 64 + } 65 + #endif
+3
drivers/gpu/drm/drm_drv.c
··· 733 733 INIT_LIST_HEAD(&dev->filelist); 734 734 INIT_LIST_HEAD(&dev->filelist_internal); 735 735 INIT_LIST_HEAD(&dev->clientlist); 736 + INIT_LIST_HEAD(&dev->client_sysrq_list); 736 737 INIT_LIST_HEAD(&dev->vblank_event_list); 737 738 738 739 spin_lock_init(&dev->event_lock); ··· 1101 1100 goto err_unload; 1102 1101 } 1103 1102 drm_panic_register(dev); 1103 + drm_client_sysrq_register(dev); 1104 1104 1105 1105 DRM_INFO("Initialized %s %d.%d.%d for %s on minor %d\n", 1106 1106 driver->name, driver->major, driver->minor, ··· 1146 1144 { 1147 1145 dev->registered = false; 1148 1146 1147 + drm_client_sysrq_unregister(dev); 1149 1148 drm_panic_unregister(dev); 1150 1149 1151 1150 drm_client_dev_unregister(dev);
+1 -44
drivers/gpu/drm/drm_fb_helper.c
··· 32 32 #include <linux/console.h> 33 33 #include <linux/export.h> 34 34 #include <linux/pci.h> 35 - #include <linux/sysrq.h> 36 35 #include <linux/vga_switcheroo.h> 37 36 38 37 #include <drm/drm_atomic.h> ··· 268 269 return __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); 269 270 } 270 271 EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode_unlocked); 271 - 272 - #ifdef CONFIG_MAGIC_SYSRQ 273 - /* emergency restore, don't bother with error reporting */ 274 - static void drm_fb_helper_restore_work_fn(struct work_struct *ignored) 275 - { 276 - struct drm_fb_helper *helper; 277 - 278 - mutex_lock(&kernel_fb_helper_lock); 279 - list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) { 280 - struct drm_device *dev = helper->dev; 281 - 282 - if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) 283 - continue; 284 - 285 - mutex_lock(&helper->lock); 286 - drm_client_modeset_commit_locked(&helper->client); 287 - mutex_unlock(&helper->lock); 288 - } 289 - mutex_unlock(&kernel_fb_helper_lock); 290 - } 291 - 292 - static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn); 293 - 294 - static void drm_fb_helper_sysrq(u8 dummy1) 295 - { 296 - schedule_work(&drm_fb_helper_restore_work); 297 - } 298 - 299 - static const struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { 300 - .handler = drm_fb_helper_sysrq, 301 - .help_msg = "force-fb(v)", 302 - .action_msg = "Restore framebuffer console", 303 - }; 304 - #else 305 - static const struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { }; 306 - #endif 307 272 308 273 static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode) 309 274 { ··· 565 602 drm_fb_helper_release_info(fb_helper); 566 603 567 604 mutex_lock(&kernel_fb_helper_lock); 568 - if (!list_empty(&fb_helper->kernel_fb_list)) { 605 + if (!list_empty(&fb_helper->kernel_fb_list)) 569 606 list_del(&fb_helper->kernel_fb_list); 570 - if (list_empty(&kernel_fb_helper_list)) 571 - unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); 572 - } 573 607 mutex_unlock(&kernel_fb_helper_lock); 574 608 575 609 if (!fb_helper->client.funcs) ··· 1800 1840 info->node, info->fix.id); 1801 1841 1802 1842 mutex_lock(&kernel_fb_helper_lock); 1803 - if (list_empty(&kernel_fb_helper_list)) 1804 - register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); 1805 - 1806 1843 list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); 1807 1844 mutex_unlock(&kernel_fb_helper_lock); 1808 1845
+11
drivers/gpu/drm/drm_internal.h
··· 56 56 { } 57 57 #endif 58 58 59 + /* drm_client_sysrq.c */ 60 + #if defined(CONFIG_DRM_CLIENT) && defined(CONFIG_MAGIC_SYSRQ) 61 + void drm_client_sysrq_register(struct drm_device *dev); 62 + void drm_client_sysrq_unregister(struct drm_device *dev); 63 + #else 64 + static inline void drm_client_sysrq_register(struct drm_device *dev) 65 + { } 66 + static inline void drm_client_sysrq_unregister(struct drm_device *dev) 67 + { } 68 + #endif 69 + 59 70 /* drm_file.c */ 60 71 extern struct mutex drm_global_mutex; 61 72 bool drm_dev_needs_global_mutex(struct drm_device *dev);
+8
include/drm/drm_device.h
··· 239 239 struct list_head clientlist; 240 240 241 241 /** 242 + * @client_sysrq_list: 243 + * 244 + * Entry into list of devices registered for sysrq. Allows in-kernel 245 + * clients on this device to handle sysrq keys. 246 + */ 247 + struct list_head client_sysrq_list; 248 + 249 + /** 242 250 * @vblank_disable_immediate: 243 251 * 244 252 * If true, vblank interrupt will be disabled immediately when the