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

drm/panic: Add a drm panic handler

This module displays a user friendly message when a kernel panic
occurs. It currently doesn't contain any debug information,
but that can be added later.

v2
* Use get_scanout_buffer() instead of the drm client API.
(Thomas Zimmermann)
* Add the panic reason to the panic message (Nerdopolis)
* Add an exclamation mark (Nerdopolis)

v3
* Rework the drawing functions, to write the pixels line by line and
to use the drm conversion helper to support other formats.
(Thomas Zimmermann)

v4
* Use drm_fb_r1_to_32bit for fonts (Thomas Zimmermann)
* Remove the default y to DRM_PANIC config option (Thomas Zimmermann)
* Add foreground/background color config option
* Fix the bottom lines not painted if the framebuffer height
is not a multiple of the font height.
* Automatically register the device to drm_panic, if the function
get_scanout_buffer exists. (Thomas Zimmermann)

v5
* Change the drawing API, use drm_fb_blit_from_r1() to draw the font.
* Also add drm_fb_fill() to fill area with background color.
* Add draw_pixel_xy() API for drivers that can't provide a linear buffer.
* Add a flush() callback for drivers that needs to synchronize the buffer.
* Add a void *private field, so drivers can pass private data to
draw_pixel_xy() and flush().

v6
* Fix sparse warning for panic_msg and logo.

v7
* Add select DRM_KMS_HELPER for the color conversion functions.

v8
* Register directly each plane to the panic notifier (Sima)
* Add raw_spinlock to properly handle concurrency (Sima)
* Register plane instead of device, to avoid looping through plane
list, and simplify code.
* Replace get_scanout_buffer() logic with drm_panic_set_buffer()
(Thomas Zimmermann)
* Removed the draw_pixel_xy() API, will see later if it can be added back.

v9
* Revert to using get_scanout_buffer() (Sima)
* Move get_scanout_buffer() and panic_flush() to the plane helper
functions (Thomas Zimmermann)
* Register all planes with get_scanout_buffer() to the panic notifier
* Use drm_panic_lock() to protect against race (Sima)

v10
* Move blit and fill functions back in drm_panic (Thomas Zimmermann).
* Simplify the text drawing functions.
* Use kmsg_dumper instead of panic_notifier (Sima).

v12
* Use array for map and pitch in struct drm_scanout_buffer
to support multi-planar format later. (Thomas Zimmermann)
* Better indent struct drm_scanout_buffer declaration. (Thomas Zimmermann)

Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20240409163432.352518-3-jfalempe@redhat.com
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>

+435
+15
Documentation/gpu/drm-kms.rst
··· 398 398 .. kernel-doc:: include/drm/drm_damage_helper.h 399 399 :internal: 400 400 401 + Plane Panic Feature 402 + ------------------- 403 + 404 + .. kernel-doc:: drivers/gpu/drm/drm_panic.c 405 + :doc: overview 406 + 407 + Plane Panic Functions Reference 408 + ------------------------------- 409 + 410 + .. kernel-doc:: include/drm/drm_panic.h 411 + :internal: 412 + 413 + .. kernel-doc:: drivers/gpu/drm/drm_panic.c 414 + :export: 415 + 401 416 Display Modes Function Reference 402 417 ================================ 403 418
+23
drivers/gpu/drm/Kconfig
··· 104 104 help 105 105 CRTC helpers for KMS drivers. 106 106 107 + config DRM_PANIC 108 + bool "Display a user-friendly message when a kernel panic occurs" 109 + depends on DRM && !FRAMEBUFFER_CONSOLE 110 + select DRM_KMS_HELPER 111 + select FONT_SUPPORT 112 + help 113 + Enable a drm panic handler, which will display a user-friendly message 114 + when a kernel panic occurs. It's useful when using a user-space 115 + console instead of fbcon. 116 + It will only work if your graphic driver supports this feature. 117 + To support Hi-DPI Display, you can enable bigger fonts like 118 + FONT_TER16x32 119 + 120 + config DRM_PANIC_FOREGROUND_COLOR 121 + hex "Drm panic screen foreground color, in RGB" 122 + depends on DRM_PANIC 123 + default 0xffffff 124 + 125 + config DRM_PANIC_BACKGROUND_COLOR 126 + hex "Drm panic screen background color, in RGB" 127 + depends on DRM_PANIC 128 + default 0x000000 129 + 107 130 config DRM_DEBUG_DP_MST_TOPOLOGY_REFS 108 131 bool "Enable refcount backtrace history in the DP MST helpers" 109 132 depends on STACKTRACE_SUPPORT
+1
drivers/gpu/drm/Makefile
··· 88 88 drm_privacy_screen.o \ 89 89 drm_privacy_screen_x86.o 90 90 drm-$(CONFIG_DRM_ACCEL) += ../../accel/drm_accel.o 91 + drm-$(CONFIG_DRM_PANIC) += drm_panic.o 91 92 obj-$(CONFIG_DRM) += drm.o 92 93 93 94 obj-$(CONFIG_DRM_PANEL_ORIENTATION_QUIRKS) += drm_panel_orientation_quirks.o
+4
drivers/gpu/drm/drm_drv.c
··· 43 43 #include <drm/drm_file.h> 44 44 #include <drm/drm_managed.h> 45 45 #include <drm/drm_mode_object.h> 46 + #include <drm/drm_panic.h> 46 47 #include <drm/drm_print.h> 47 48 #include <drm/drm_privacy_screen_machine.h> 48 49 ··· 945 944 if (ret) 946 945 goto err_unload; 947 946 } 947 + drm_panic_register(dev); 948 948 949 949 DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n", 950 950 driver->name, driver->major, driver->minor, ··· 989 987 void drm_dev_unregister(struct drm_device *dev) 990 988 { 991 989 dev->registered = false; 990 + 991 + drm_panic_unregister(dev); 992 992 993 993 drm_client_dev_unregister(dev); 994 994
+290
drivers/gpu/drm/drm_panic.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 or MIT 2 + /* 3 + * Copyright (c) 2023 Red Hat. 4 + * Author: Jocelyn Falempe <jfalempe@redhat.com> 5 + * inspired by the drm_log driver from David Herrmann <dh.herrmann@gmail.com> 6 + * Tux Ascii art taken from cowsay written by Tony Monroe 7 + */ 8 + 9 + #include <linux/font.h> 10 + #include <linux/iosys-map.h> 11 + #include <linux/kdebug.h> 12 + #include <linux/kmsg_dump.h> 13 + #include <linux/list.h> 14 + #include <linux/module.h> 15 + #include <linux/types.h> 16 + 17 + #include <drm/drm_drv.h> 18 + #include <drm/drm_format_helper.h> 19 + #include <drm/drm_fourcc.h> 20 + #include <drm/drm_framebuffer.h> 21 + #include <drm/drm_modeset_helper_vtables.h> 22 + #include <drm/drm_panic.h> 23 + #include <drm/drm_plane.h> 24 + #include <drm/drm_print.h> 25 + 26 + MODULE_AUTHOR("Jocelyn Falempe"); 27 + MODULE_DESCRIPTION("DRM panic handler"); 28 + MODULE_LICENSE("GPL"); 29 + 30 + /** 31 + * DOC: overview 32 + * 33 + * To enable DRM panic for a driver, the primary plane must implement a 34 + * &drm_plane_helper_funcs.get_scanout_buffer helper function. It is then 35 + * automatically registered to the drm panic handler. 36 + * When a panic occurs, the &drm_plane_helper_funcs.get_scanout_buffer will be 37 + * called, and the driver can provide a framebuffer so the panic handler can 38 + * draw the panic screen on it. Currently only linear buffer and a few color 39 + * formats are supported. 40 + * Optionally the driver can also provide a &drm_plane_helper_funcs.panic_flush 41 + * callback, that will be called after that, to send additional commands to the 42 + * hardware to make the scanout buffer visible. 43 + */ 44 + 45 + /* 46 + * This module displays a user friendly message on screen when a kernel panic 47 + * occurs. This is conflicting with fbcon, so you can only enable it when fbcon 48 + * is disabled. 49 + * It's intended for end-user, so have minimal technical/debug information. 50 + * 51 + * Implementation details: 52 + * 53 + * It is a panic handler, so it can't take lock, allocate memory, run tasks/irq, 54 + * or attempt to sleep. It's a best effort, and it may not be able to display 55 + * the message in all situations (like if the panic occurs in the middle of a 56 + * modesetting). 57 + * It will display only one static frame, so performance optimizations are low 58 + * priority as the machine is already in an unusable state. 59 + */ 60 + 61 + struct drm_panic_line { 62 + u32 len; 63 + const char *txt; 64 + }; 65 + 66 + #define PANIC_LINE(s) {.len = sizeof(s) - 1, .txt = s} 67 + 68 + static struct drm_panic_line panic_msg[] = { 69 + PANIC_LINE("KERNEL PANIC !"), 70 + PANIC_LINE(""), 71 + PANIC_LINE("Please reboot your computer."), 72 + }; 73 + 74 + static const struct drm_panic_line logo[] = { 75 + PANIC_LINE(" .--. _"), 76 + PANIC_LINE(" |o_o | | |"), 77 + PANIC_LINE(" |:_/ | | |"), 78 + PANIC_LINE(" // \\ \\ |_|"), 79 + PANIC_LINE(" (| | ) _"), 80 + PANIC_LINE(" /'\\_ _/`\\ (_)"), 81 + PANIC_LINE(" \\___)=(___/"), 82 + }; 83 + 84 + static void drm_panic_fill32(struct iosys_map *map, unsigned int pitch, 85 + unsigned int height, unsigned int width, 86 + u32 color) 87 + { 88 + unsigned int y, x; 89 + 90 + for (y = 0; y < height; y++) 91 + for (x = 0; x < width; x++) 92 + iosys_map_wr(map, y * pitch + x * sizeof(u32), u32, color); 93 + } 94 + 95 + static void drm_panic_blit32(struct iosys_map *dmap, unsigned int dpitch, 96 + const u8 *sbuf8, unsigned int spitch, 97 + unsigned int height, unsigned int width, 98 + u32 fg32, u32 bg32) 99 + { 100 + unsigned int y, x; 101 + u32 val32; 102 + 103 + for (y = 0; y < height; y++) { 104 + for (x = 0; x < width; x++) { 105 + val32 = (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) ? fg32 : bg32; 106 + iosys_map_wr(dmap, y * dpitch + x * sizeof(u32), u32, val32); 107 + } 108 + } 109 + } 110 + 111 + static const u8 *get_char_bitmap(const struct font_desc *font, char c, size_t font_pitch) 112 + { 113 + return font->data + (c * font->height) * font_pitch; 114 + } 115 + 116 + static unsigned int get_max_line_len(const struct drm_panic_line *lines, int len) 117 + { 118 + int i; 119 + unsigned int max = 0; 120 + 121 + for (i = 0; i < len; i++) 122 + max = max(lines[i].len, max); 123 + return max; 124 + } 125 + 126 + /* 127 + * Draw a text in a rectangle on a framebuffer. The text is truncated if it overflows the rectangle 128 + */ 129 + static void draw_txt_rectangle(struct drm_scanout_buffer *sb, 130 + const struct font_desc *font, 131 + const struct drm_panic_line *msg, 132 + unsigned int msg_lines, 133 + bool centered, 134 + struct drm_rect *clip, 135 + u32 fg_color, 136 + u32 bg_color) 137 + { 138 + int i, j; 139 + const u8 *src; 140 + size_t font_pitch = DIV_ROUND_UP(font->width, 8); 141 + struct iosys_map dst; 142 + unsigned int px_width = sb->format->cpp[0]; 143 + int left = 0; 144 + 145 + msg_lines = min(msg_lines, drm_rect_height(clip) / font->height); 146 + for (i = 0; i < msg_lines; i++) { 147 + size_t line_len = min(msg[i].len, drm_rect_width(clip) / font->width); 148 + 149 + if (centered) 150 + left = (drm_rect_width(clip) - (line_len * font->width)) / 2; 151 + 152 + dst = sb->map[0]; 153 + iosys_map_incr(&dst, (clip->y1 + i * font->height) * sb->pitch[0] + 154 + (clip->x1 + left) * px_width); 155 + for (j = 0; j < line_len; j++) { 156 + src = get_char_bitmap(font, msg[i].txt[j], font_pitch); 157 + drm_panic_blit32(&dst, sb->pitch[0], src, font_pitch, 158 + font->height, font->width, 159 + fg_color, bg_color); 160 + iosys_map_incr(&dst, font->width * px_width); 161 + } 162 + } 163 + } 164 + 165 + /* 166 + * Draw the panic message at the center of the screen 167 + */ 168 + static void draw_panic_static(struct drm_scanout_buffer *sb) 169 + { 170 + size_t msg_lines = ARRAY_SIZE(panic_msg); 171 + size_t logo_lines = ARRAY_SIZE(logo); 172 + u32 fg_color = CONFIG_DRM_PANIC_FOREGROUND_COLOR; 173 + u32 bg_color = CONFIG_DRM_PANIC_BACKGROUND_COLOR; 174 + const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL); 175 + struct drm_rect r_logo, r_msg; 176 + 177 + if (!font) 178 + return; 179 + 180 + r_logo = DRM_RECT_INIT(0, 0, 181 + get_max_line_len(logo, logo_lines) * font->width, 182 + logo_lines * font->height); 183 + r_msg = DRM_RECT_INIT(0, 0, 184 + min(get_max_line_len(panic_msg, msg_lines) * font->width, sb->width), 185 + min(msg_lines * font->height, sb->height)); 186 + 187 + /* Center the panic message */ 188 + drm_rect_translate(&r_msg, (sb->width - r_msg.x2) / 2, (sb->height - r_msg.y2) / 2); 189 + 190 + /* Fill with the background color, and draw text on top */ 191 + drm_panic_fill32(&sb->map[0], sb->pitch[0], sb->height, sb->width, bg_color); 192 + 193 + if ((r_msg.x1 >= drm_rect_width(&r_logo) || r_msg.y1 >= drm_rect_height(&r_logo)) && 194 + drm_rect_width(&r_logo) < sb->width && drm_rect_height(&r_logo) < sb->height) { 195 + draw_txt_rectangle(sb, font, logo, logo_lines, false, &r_logo, fg_color, bg_color); 196 + } 197 + draw_txt_rectangle(sb, font, panic_msg, msg_lines, true, &r_msg, fg_color, bg_color); 198 + } 199 + 200 + /* 201 + * drm_panic_is_format_supported() 202 + * @format: a fourcc color code 203 + * Returns: true if supported, false otherwise. 204 + * 205 + * Check if drm_panic will be able to use this color format. 206 + */ 207 + static bool drm_panic_is_format_supported(const struct drm_format_info *format) 208 + { 209 + if (format->num_planes != 1) 210 + return false; 211 + return format->format == DRM_FORMAT_XRGB8888; 212 + } 213 + 214 + static void draw_panic_plane(struct drm_plane *plane) 215 + { 216 + struct drm_scanout_buffer sb; 217 + int ret; 218 + unsigned long flags; 219 + 220 + if (!drm_panic_trylock(plane->dev, flags)) 221 + return; 222 + 223 + ret = plane->helper_private->get_scanout_buffer(plane, &sb); 224 + 225 + if (!ret && drm_panic_is_format_supported(sb.format)) { 226 + draw_panic_static(&sb); 227 + if (plane->helper_private->panic_flush) 228 + plane->helper_private->panic_flush(plane); 229 + } 230 + drm_panic_unlock(plane->dev, flags); 231 + } 232 + 233 + static struct drm_plane *to_drm_plane(struct kmsg_dumper *kd) 234 + { 235 + return container_of(kd, struct drm_plane, kmsg_panic); 236 + } 237 + 238 + static void drm_panic(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason) 239 + { 240 + struct drm_plane *plane = to_drm_plane(dumper); 241 + 242 + if (reason == KMSG_DUMP_PANIC) 243 + draw_panic_plane(plane); 244 + } 245 + 246 + /** 247 + * drm_panic_register() - Initialize DRM panic for a device 248 + * @dev: the drm device on which the panic screen will be displayed. 249 + */ 250 + void drm_panic_register(struct drm_device *dev) 251 + { 252 + struct drm_plane *plane; 253 + int registered_plane = 0; 254 + 255 + if (!dev->mode_config.num_total_plane) 256 + return; 257 + 258 + drm_for_each_plane(plane, dev) { 259 + if (!plane->helper_private || !plane->helper_private->get_scanout_buffer) 260 + continue; 261 + plane->kmsg_panic.dump = drm_panic; 262 + plane->kmsg_panic.max_reason = KMSG_DUMP_PANIC; 263 + if (kmsg_dump_register(&plane->kmsg_panic)) 264 + drm_warn(dev, "Failed to register panic handler\n"); 265 + else 266 + registered_plane++; 267 + } 268 + if (registered_plane) 269 + drm_info(dev, "Registered %d planes with drm panic\n", registered_plane); 270 + } 271 + EXPORT_SYMBOL(drm_panic_register); 272 + 273 + /** 274 + * drm_panic_unregister() 275 + * @dev: the drm device previously registered. 276 + */ 277 + void drm_panic_unregister(struct drm_device *dev) 278 + { 279 + struct drm_plane *plane; 280 + 281 + if (!dev->mode_config.num_total_plane) 282 + return; 283 + 284 + drm_for_each_plane(plane, dev) { 285 + if (!plane->helper_private || !plane->helper_private->get_scanout_buffer) 286 + continue; 287 + kmsg_dump_unregister(&plane->kmsg_panic); 288 + } 289 + } 290 + EXPORT_SYMBOL(drm_panic_unregister);
+39
include/drm/drm_modeset_helper_vtables.h
··· 48 48 * To make this clear all the helper vtables are pulled together in this location here. 49 49 */ 50 50 51 + struct drm_scanout_buffer; 51 52 struct drm_writeback_connector; 52 53 struct drm_writeback_job; 53 54 ··· 1444 1443 */ 1445 1444 void (*atomic_async_update)(struct drm_plane *plane, 1446 1445 struct drm_atomic_state *state); 1446 + 1447 + /** 1448 + * @get_scanout_buffer: 1449 + * 1450 + * Get the current scanout buffer, to display a message with drm_panic. 1451 + * The driver should do the minimum changes to provide a buffer, 1452 + * that can be used to display the panic screen. Currently only linear 1453 + * buffers are supported. Non-linear buffer support is on the TODO list. 1454 + * The device &dev.mode_config.panic_lock is taken before calling this 1455 + * function, so you can safely access the &plane.state 1456 + * It is called from a panic callback, and must follow its restrictions. 1457 + * Please look the documentation at drm_panic_trylock() for an in-depth 1458 + * discussions of what's safe and what is not allowed. 1459 + * It's a best effort mode, so it's expected that in some complex cases 1460 + * the panic screen won't be displayed. 1461 + * The returned &drm_scanout_buffer.map must be valid if no error code is 1462 + * returned. 1463 + * 1464 + * Return: 1465 + * %0 on success, negative errno on failure. 1466 + */ 1467 + int (*get_scanout_buffer)(struct drm_plane *plane, 1468 + struct drm_scanout_buffer *sb); 1469 + 1470 + /** 1471 + * @panic_flush: 1472 + * 1473 + * It is used by drm_panic, and is called after the panic screen is 1474 + * drawn to the scanout buffer. In this function, the driver 1475 + * can send additional commands to the hardware, to make the scanout 1476 + * buffer visible. 1477 + * It is only called if get_scanout_buffer() returned successfully, and 1478 + * the &dev.mode_config.panic_lock is held during the entire sequence. 1479 + * It is called from a panic callback, and must follow its restrictions. 1480 + * Please look the documentation at drm_panic_trylock() for an in-depth 1481 + * discussions of what's safe and what is not allowed. 1482 + */ 1483 + void (*panic_flush)(struct drm_plane *plane); 1447 1484 }; 1448 1485 1449 1486 /**
+57
include/drm/drm_panic.h
··· 2 2 #ifndef __DRM_PANIC_H__ 3 3 #define __DRM_PANIC_H__ 4 4 5 + #include <linux/module.h> 6 + #include <linux/types.h> 7 + #include <linux/iosys-map.h> 8 + 5 9 #include <drm/drm_device.h> 10 + #include <drm/drm_fourcc.h> 6 11 /* 7 12 * Copyright (c) 2024 Intel 8 13 */ 14 + 15 + /** 16 + * struct drm_scanout_buffer - DRM scanout buffer 17 + * 18 + * This structure holds the information necessary for drm_panic to draw the 19 + * panic screen, and display it. 20 + */ 21 + struct drm_scanout_buffer { 22 + /** 23 + * @format: 24 + * 25 + * drm format of the scanout buffer. 26 + */ 27 + const struct drm_format_info *format; 28 + 29 + /** 30 + * @map: 31 + * 32 + * Virtual address of the scanout buffer, either in memory or iomem. 33 + * The scanout buffer should be in linear format, and can be directly 34 + * sent to the display hardware. Tearing is not an issue for the panic 35 + * screen. 36 + */ 37 + struct iosys_map map[DRM_FORMAT_MAX_PLANES]; 38 + 39 + /** 40 + * @width: Width of the scanout buffer, in pixels. 41 + */ 42 + unsigned int width; 43 + 44 + /** 45 + * @height: Height of the scanout buffer, in pixels. 46 + */ 47 + unsigned int height; 48 + 49 + /** 50 + * @pitch: Length in bytes between the start of two consecutive lines. 51 + */ 52 + unsigned int pitch[DRM_FORMAT_MAX_PLANES]; 53 + }; 9 54 10 55 /** 11 56 * drm_panic_trylock - try to enter the panic printing critical section ··· 136 91 */ 137 92 #define drm_panic_unlock(dev, flags) \ 138 93 raw_spin_unlock_irqrestore(&(dev)->mode_config.panic_lock, flags) 94 + 95 + #ifdef CONFIG_DRM_PANIC 96 + 97 + void drm_panic_register(struct drm_device *dev); 98 + void drm_panic_unregister(struct drm_device *dev); 99 + 100 + #else 101 + 102 + static inline void drm_panic_register(struct drm_device *dev) {} 103 + static inline void drm_panic_unregister(struct drm_device *dev) {} 104 + 105 + #endif 139 106 140 107 #endif /* __DRM_PANIC_H__ */
+6
include/drm/drm_plane.h
··· 25 25 26 26 #include <linux/list.h> 27 27 #include <linux/ctype.h> 28 + #include <linux/kmsg_dump.h> 28 29 #include <drm/drm_mode_object.h> 29 30 #include <drm/drm_color_mgmt.h> 30 31 #include <drm/drm_rect.h> ··· 781 780 * @hotspot_y_property: property to set mouse hotspot y offset. 782 781 */ 783 782 struct drm_property *hotspot_y_property; 783 + 784 + /** 785 + * @kmsg_panic: Used to register a panic notifier for this plane 786 + */ 787 + struct kmsg_dumper kmsg_panic; 784 788 }; 785 789 786 790 #define obj_to_plane(x) container_of(x, struct drm_plane, base)