Files
Thomas Zimmermann 5c3d323984 drm/panic: Move panic display code into helper library
Move drm_plane_helper_display_panic_screen() and the existing panic-
display code into drm_panic_helper.c. Rename symbols accordingly.
Also update the QR-related code and the bindings accordingly.

Link the rsp. object file into drm_kms_helper.ko. Eventually the code
will become a DRM drawing library, together with DRM's format and draw
helpers. This requires more preparation. until then, KMS helpers serve
as a module. All DRM drivers link it anyway.

Add a new section about drawing helpers to the documentation of the KMS
helpers. List format and panic helpers there.

v2:
- fix dependencies on other DRM modules (Randy, Sashiko)
- provide placeholders for drm_panic_helper_{init,exit}() (Sashiko)

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: Jocelyn Falempe <jfalempe@redhat.com>
Link: https://patch.msgid.link/20260911124540.279841-14-tzimmermann@suse.de
2026-09-17 09:16:36 +02:00

265 lines
7.0 KiB
C

// SPDX-License-Identifier: GPL-2.0 or MIT
/*
* Copyright (c) 2023 Red Hat.
* Author: Jocelyn Falempe <jfalempe@redhat.com>
* inspired by the drm_log driver from David Herrmann <dh.herrmann@gmail.com>
* Tux Ascii art taken from cowsay written by Tony Monroe
*/
#include <linux/export.h>
#include <linux/init.h>
#include <linux/kdebug.h>
#include <linux/kmsg_dump.h>
#include <linux/list.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <drm/drm_drv.h>
#include <drm/drm_panic.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include "drm_crtc_internal.h"
#include "drm_panic_internal.h"
MODULE_AUTHOR("Jocelyn Falempe");
MODULE_DESCRIPTION("DRM panic handler");
MODULE_LICENSE("GPL");
/**
* DOC: overview
*
* This module displays a user friendly message on screen when a kernel
* panic occurs. It's intended for end users and therefore have minimal
* technical/debug information.
*
* To enable DRM panic for a driver, the at least one primary plane must
* implement struct &drm_plane_funcs.display_panic_screen. The plane is
* then automatically registered to the drm panic handler.
*
* When a panic occurs, the DRM panic handler calls struct
* &drm_plane_funcs.display_panic_screen. See
* drm_plane_helper_display_panic_screen() for a generic implementation.
*/
#if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
static uint panic_qr_version = CONFIG_DRM_PANIC_SCREEN_QR_VERSION;
module_param(panic_qr_version, uint, 0644);
MODULE_PARM_DESC(panic_qr_version, "maximum version (size) of the QR code");
#endif
static enum drm_panic_type drm_panic_type = -1;
static const char *drm_panic_type_map[] = {
[DRM_PANIC_TYPE_KMSG] = "kmsg",
[DRM_PANIC_TYPE_USER] = "user",
#if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
[DRM_PANIC_TYPE_QR] = "qr_code",
#endif
};
static int drm_panic_type_set(const char *val, const struct kernel_param *kp)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(drm_panic_type_map); i++) {
if (!strcmp(val, drm_panic_type_map[i])) {
drm_panic_type = i;
return 0;
}
}
return -EINVAL;
}
static int drm_panic_type_get(char *buffer, const struct kernel_param *kp)
{
return scnprintf(buffer, PAGE_SIZE, "%s\n",
drm_panic_type_map[drm_panic_type]);
}
static const struct kernel_param_ops drm_panic_ops = {
.set = drm_panic_type_set,
.get = drm_panic_type_get,
};
module_param_cb(panic_screen, &drm_panic_ops, NULL, 0644);
MODULE_PARM_DESC(panic_screen,
#if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
"Choose what will be displayed by drm_panic, 'user', 'kmsg' or 'qr_code' [default="
#else
"Choose what will be displayed by drm_panic, 'user' or 'kmsg' [default="
#endif
CONFIG_DRM_PANIC_SCREEN "]");
#define drm_panic_trylock(dev, flags) \
raw_spin_trylock_irqsave(&(dev)->mode_config.panic_lock, flags)
static void drm_panic_display_panic_screen(struct drm_plane *plane, const char *description)
{
#if defined(CONFIG_DRM_PANIC_FOREGROUND_COLOR)
u32 fg_color = CONFIG_DRM_PANIC_FOREGROUND_COLOR;
#else
u32 fg_color = 0x00ffffff;
#endif
#if defined(CONFIG_DRM_PANIC_BACKGROUND_COLOR)
u32 bg_color = CONFIG_DRM_PANIC_BACKGROUND_COLOR;
#else
u32 bg_color = 0x00000000;
#endif
#if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
unsigned int qr_version = panic_qr_version;
#else
unsigned int qr_version = 0;
#endif
struct drm_device *dev = plane->dev;
unsigned long flags;
if (drm_panic_trylock(dev, flags)) {
plane->funcs->display_panic_screen(plane, description, drm_panic_type,
fg_color, bg_color, qr_version);
drm_panic_unlock(dev, flags);
}
}
static struct drm_plane *to_drm_plane(struct kmsg_dumper *kd)
{
return container_of(kd, struct drm_plane, kmsg_panic);
}
static void drm_panic(struct kmsg_dumper *dumper, struct kmsg_dump_detail *detail)
{
struct drm_plane *plane = to_drm_plane(dumper);
if (detail->reason == KMSG_DUMP_PANIC)
drm_panic_display_panic_screen(plane, detail->description);
}
/*
* DEBUG FS, This is currently unsafe.
* Create one file per plane, so it's possible to debug one plane at a time.
* TODO: It would be better to emulate an NMI context.
*/
#ifdef CONFIG_DRM_PANIC_DEBUG
#include <linux/debugfs.h>
static ssize_t debugfs_trigger_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
bool run;
if (kstrtobool_from_user(user_buf, count, &run) == 0 && run) {
struct drm_plane *plane = file->private_data;
drm_panic_display_panic_screen(plane, "Test from debugfs");
}
return count;
}
static const struct file_operations dbg_drm_panic_ops = {
.owner = THIS_MODULE,
.write = debugfs_trigger_write,
.open = simple_open,
};
static void debugfs_register_plane(struct drm_plane *plane, int index)
{
char fname[32];
snprintf(fname, 32, "drm_panic_plane_%d", index);
debugfs_create_file(fname, 0200, plane->dev->debugfs_root,
plane, &dbg_drm_panic_ops);
}
#else
static void debugfs_register_plane(struct drm_plane *plane, int index) {}
#endif /* CONFIG_DRM_PANIC_DEBUG */
/**
* drm_panic_is_enabled
* @dev: the drm device that may supports drm_panic
*
* returns true if the drm device supports drm_panic
*/
bool drm_panic_is_enabled(struct drm_device *dev)
{
struct drm_plane *plane;
if (!dev->mode_config.num_total_plane)
return false;
drm_for_each_plane(plane, dev) {
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
continue;
if (!plane->funcs || !plane->funcs->display_panic_screen)
continue;
return true;
}
return false;
}
EXPORT_SYMBOL(drm_panic_is_enabled);
/**
* drm_panic_register() - Initialize DRM panic for a device
* @dev: the drm device on which the panic screen will be displayed.
*/
void drm_panic_register(struct drm_device *dev)
{
struct drm_plane *plane;
int registered_plane = 0;
if (!dev->mode_config.num_total_plane)
return;
drm_for_each_plane(plane, dev) {
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
continue;
if (!plane->funcs || !plane->funcs->display_panic_screen)
continue;
plane->kmsg_panic.dump = drm_panic;
plane->kmsg_panic.max_reason = KMSG_DUMP_PANIC;
if (kmsg_dump_register(&plane->kmsg_panic))
drm_warn(dev, "Failed to register panic handler\n");
else {
debugfs_register_plane(plane, registered_plane);
registered_plane++;
}
}
if (registered_plane)
drm_info(dev, "Registered %d planes with drm panic\n", registered_plane);
}
/**
* drm_panic_unregister()
* @dev: the drm device previously registered.
*/
void drm_panic_unregister(struct drm_device *dev)
{
struct drm_plane *plane;
if (!dev->mode_config.num_total_plane)
return;
drm_for_each_plane(plane, dev) {
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
continue;
if (!plane->funcs || !plane->funcs->display_panic_screen)
continue;
kmsg_dump_unregister(&plane->kmsg_panic);
}
}
/**
* drm_panic_init() - initialize DRM panic.
*/
void __init drm_panic_init(void)
{
if (drm_panic_type == -1 && drm_panic_type_set(CONFIG_DRM_PANIC_SCREEN, NULL)) {
pr_warn("Unsupported value for CONFIG_DRM_PANIC_SCREEN ('%s'), falling back to 'user'...\n",
CONFIG_DRM_PANIC_SCREEN);
drm_panic_type = DRM_PANIC_TYPE_USER;
}
}