mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
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
265 lines
7.0 KiB
C
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;
|
|
}
|
|
}
|