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 /* 85 * Color conversion 86 */ 87 88 static u16 convert_xrgb8888_to_rgb565(u32 pix) 89 { 90 return ((pix & 0x00F80000) >> 8) | 91 ((pix & 0x0000FC00) >> 5) | 92 ((pix & 0x000000F8) >> 3); 93 } 94 95 static u16 convert_xrgb8888_to_rgba5551(u32 pix) 96 { 97 return ((pix & 0x00f80000) >> 8) | 98 ((pix & 0x0000f800) >> 5) | 99 ((pix & 0x000000f8) >> 2) | 100 BIT(0); /* set alpha bit */ 101 } 102 103 static u16 convert_xrgb8888_to_xrgb1555(u32 pix) 104 { 105 return ((pix & 0x00f80000) >> 9) | 106 ((pix & 0x0000f800) >> 6) | 107 ((pix & 0x000000f8) >> 3); 108 } 109 110 static u16 convert_xrgb8888_to_argb1555(u32 pix) 111 { 112 return BIT(15) | /* set alpha bit */ 113 ((pix & 0x00f80000) >> 9) | 114 ((pix & 0x0000f800) >> 6) | 115 ((pix & 0x000000f8) >> 3); 116 } 117 118 static u32 convert_xrgb8888_to_argb8888(u32 pix) 119 { 120 return pix | GENMASK(31, 24); /* fill alpha bits */ 121 } 122 123 static u32 convert_xrgb8888_to_xbgr8888(u32 pix) 124 { 125 return ((pix & 0x00ff0000) >> 16) << 0 | 126 ((pix & 0x0000ff00) >> 8) << 8 | 127 ((pix & 0x000000ff) >> 0) << 16 | 128 ((pix & 0xff000000) >> 24) << 24; 129 } 130 131 static u32 convert_xrgb8888_to_abgr8888(u32 pix) 132 { 133 return ((pix & 0x00ff0000) >> 16) << 0 | 134 ((pix & 0x0000ff00) >> 8) << 8 | 135 ((pix & 0x000000ff) >> 0) << 16 | 136 GENMASK(31, 24); /* fill alpha bits */ 137 } 138 139 static u32 convert_xrgb8888_to_xrgb2101010(u32 pix) 140 { 141 pix = ((pix & 0x000000FF) << 2) | 142 ((pix & 0x0000FF00) << 4) | 143 ((pix & 0x00FF0000) << 6); 144 return pix | ((pix >> 8) & 0x00300C03); 145 } 146 147 static u32 convert_xrgb8888_to_argb2101010(u32 pix) 148 { 149 pix = ((pix & 0x000000FF) << 2) | 150 ((pix & 0x0000FF00) << 4) | 151 ((pix & 0x00FF0000) << 6); 152 return GENMASK(31, 30) /* set alpha bits */ | pix | ((pix >> 8) & 0x00300C03); 153 } 154 155 /* 156 * convert_from_xrgb8888 - convert one pixel from xrgb8888 to the desired format 157 * @color: input color, in xrgb8888 format 158 * @format: output format 159 * 160 * Returns: 161 * Color in the format specified, casted to u32. 162 * Or 0 if the format is not supported. 163 */ 164 static u32 convert_from_xrgb8888(u32 color, u32 format) 165 { 166 switch (format) { 167 case DRM_FORMAT_RGB565: 168 return convert_xrgb8888_to_rgb565(color); 169 case DRM_FORMAT_RGBA5551: 170 return convert_xrgb8888_to_rgba5551(color); 171 case DRM_FORMAT_XRGB1555: 172 return convert_xrgb8888_to_xrgb1555(color); 173 case DRM_FORMAT_ARGB1555: 174 return convert_xrgb8888_to_argb1555(color); 175 case DRM_FORMAT_RGB888: 176 case DRM_FORMAT_XRGB8888: 177 return color; 178 case DRM_FORMAT_ARGB8888: 179 return convert_xrgb8888_to_argb8888(color); 180 case DRM_FORMAT_XBGR8888: 181 return convert_xrgb8888_to_xbgr8888(color); 182 case DRM_FORMAT_ABGR8888: 183 return convert_xrgb8888_to_abgr8888(color); 184 case DRM_FORMAT_XRGB2101010: 185 return convert_xrgb8888_to_xrgb2101010(color); 186 case DRM_FORMAT_ARGB2101010: 187 return convert_xrgb8888_to_argb2101010(color); 188 default: 189 WARN_ONCE(1, "Can't convert to %p4cc\n", &format); 190 return 0; 191 } 192 } 193 194 /* 195 * Blit & Fill 196 */ 197 static void drm_panic_blit16(struct iosys_map *dmap, unsigned int dpitch, 198 const u8 *sbuf8, unsigned int spitch, 199 unsigned int height, unsigned int width, 200 u16 fg16, u16 bg16) 201 { 202 unsigned int y, x; 203 u16 val16; 204 205 for (y = 0; y < height; y++) { 206 for (x = 0; x < width; x++) { 207 val16 = (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) ? fg16 : bg16; 208 iosys_map_wr(dmap, y * dpitch + x * sizeof(u16), u16, val16); 209 } 210 } 211 } 212 213 static void drm_panic_blit24(struct iosys_map *dmap, unsigned int dpitch, 214 const u8 *sbuf8, unsigned int spitch, 215 unsigned int height, unsigned int width, 216 u32 fg32, u32 bg32) 217 { 218 unsigned int y, x; 219 u32 val32; 220 221 for (y = 0; y < height; y++) { 222 for (x = 0; x < width; x++) { 223 u32 off = y * dpitch + x * 3; 224 225 val32 = (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) ? fg32 : bg32; 226 227 /* write blue-green-red to output in little endianness */ 228 iosys_map_wr(dmap, off, u8, (val32 & 0x000000FF) >> 0); 229 iosys_map_wr(dmap, off + 1, u8, (val32 & 0x0000FF00) >> 8); 230 iosys_map_wr(dmap, off + 2, u8, (val32 & 0x00FF0000) >> 16); 231 } 232 } 233 } 234 235 static void drm_panic_blit32(struct iosys_map *dmap, unsigned int dpitch, 236 const u8 *sbuf8, unsigned int spitch, 237 unsigned int height, unsigned int width, 238 u32 fg32, u32 bg32) 239 { 240 unsigned int y, x; 241 u32 val32; 242 243 for (y = 0; y < height; y++) { 244 for (x = 0; x < width; x++) { 245 val32 = (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) ? fg32 : bg32; 246 iosys_map_wr(dmap, y * dpitch + x * sizeof(u32), u32, val32); 247 } 248 } 249 } 250 251 /* 252 * drm_panic_blit - convert a monochrome image to a linear framebuffer 253 * @dmap: destination iosys_map 254 * @dpitch: destination pitch in bytes 255 * @sbuf8: source buffer, in monochrome format, 8 pixels per byte. 256 * @spitch: source pitch in bytes 257 * @height: height of the image to copy, in pixels 258 * @width: width of the image to copy, in pixels 259 * @fg_color: foreground color, in destination format 260 * @bg_color: background color, in destination format 261 * @pixel_width: pixel width in bytes. 262 * 263 * This can be used to draw a font character, which is a monochrome image, to a 264 * framebuffer in other supported format. 265 */ 266 static void drm_panic_blit(struct iosys_map *dmap, unsigned int dpitch, 267 const u8 *sbuf8, unsigned int spitch, 268 unsigned int height, unsigned int width, 269 u32 fg_color, u32 bg_color, 270 unsigned int pixel_width) 271 { 272 switch (pixel_width) { 273 case 2: 274 drm_panic_blit16(dmap, dpitch, sbuf8, spitch, 275 height, width, fg_color, bg_color); 276 break; 277 case 3: 278 drm_panic_blit24(dmap, dpitch, sbuf8, spitch, 279 height, width, fg_color, bg_color); 280 break; 281 case 4: 282 drm_panic_blit32(dmap, dpitch, sbuf8, spitch, 283 height, width, fg_color, bg_color); 284 break; 285 default: 286 WARN_ONCE(1, "Can't blit with pixel width %d\n", pixel_width); 287 } 288 } 289 290 static void drm_panic_fill16(struct iosys_map *dmap, unsigned int dpitch, 291 unsigned int height, unsigned int width, 292 u16 color) 293 { 294 unsigned int y, x; 295 296 for (y = 0; y < height; y++) 297 for (x = 0; x < width; x++) 298 iosys_map_wr(dmap, y * dpitch + x * sizeof(u16), u16, color); 299 } 300 301 static void drm_panic_fill24(struct iosys_map *dmap, unsigned int dpitch, 302 unsigned int height, unsigned int width, 303 u32 color) 304 { 305 unsigned int y, x; 306 307 for (y = 0; y < height; y++) { 308 for (x = 0; x < width; x++) { 309 unsigned int off = y * dpitch + x * 3; 310 311 /* write blue-green-red to output in little endianness */ 312 iosys_map_wr(dmap, off, u8, (color & 0x000000FF) >> 0); 313 iosys_map_wr(dmap, off + 1, u8, (color & 0x0000FF00) >> 8); 314 iosys_map_wr(dmap, off + 2, u8, (color & 0x00FF0000) >> 16); 315 } 316 } 317 } 318 319 static void drm_panic_fill32(struct iosys_map *dmap, unsigned int dpitch, 320 unsigned int height, unsigned int width, 321 u32 color) 322 { 323 unsigned int y, x; 324 325 for (y = 0; y < height; y++) 326 for (x = 0; x < width; x++) 327 iosys_map_wr(dmap, y * dpitch + x * sizeof(u32), u32, color); 328 } 329 330 /* 331 * drm_panic_fill - Fill a rectangle with a color 332 * @dmap: destination iosys_map, pointing to the top left corner of the rectangle 333 * @dpitch: destination pitch in bytes 334 * @height: height of the rectangle, in pixels 335 * @width: width of the rectangle, in pixels 336 * @color: color to fill the rectangle. 337 * @pixel_width: pixel width in bytes 338 * 339 * Fill a rectangle with a color, in a linear framebuffer. 340 */ 341 static void drm_panic_fill(struct iosys_map *dmap, unsigned int dpitch, 342 unsigned int height, unsigned int width, 343 u32 color, unsigned int pixel_width) 344 { 345 switch (pixel_width) { 346 case 2: 347 drm_panic_fill16(dmap, dpitch, height, width, color); 348 break; 349 case 3: 350 drm_panic_fill24(dmap, dpitch, height, width, color); 351 break; 352 case 4: 353 drm_panic_fill32(dmap, dpitch, height, width, color); 354 break; 355 default: 356 WARN_ONCE(1, "Can't fill with pixel width %d\n", pixel_width); 357 } 358 } 359 360 static const u8 *get_char_bitmap(const struct font_desc *font, char c, size_t font_pitch) 361 { 362 return font->data + (c * font->height) * font_pitch; 363 } 364 365 static unsigned int get_max_line_len(const struct drm_panic_line *lines, int len) 366 { 367 int i; 368 unsigned int max = 0; 369 370 for (i = 0; i < len; i++) 371 max = max(lines[i].len, max); 372 return max; 373 } 374 375 /* 376 * Draw a text in a rectangle on a framebuffer. The text is truncated if it overflows the rectangle 377 */ 378 static void draw_txt_rectangle(struct drm_scanout_buffer *sb, 379 const struct font_desc *font, 380 const struct drm_panic_line *msg, 381 unsigned int msg_lines, 382 bool centered, 383 struct drm_rect *clip, 384 u32 fg_color, 385 u32 bg_color) 386 { 387 int i, j; 388 const u8 *src; 389 size_t font_pitch = DIV_ROUND_UP(font->width, 8); 390 struct iosys_map dst; 391 unsigned int px_width = sb->format->cpp[0]; 392 int left = 0; 393 394 msg_lines = min(msg_lines, drm_rect_height(clip) / font->height); 395 for (i = 0; i < msg_lines; i++) { 396 size_t line_len = min(msg[i].len, drm_rect_width(clip) / font->width); 397 398 if (centered) 399 left = (drm_rect_width(clip) - (line_len * font->width)) / 2; 400 401 dst = sb->map[0]; 402 iosys_map_incr(&dst, (clip->y1 + i * font->height) * sb->pitch[0] + 403 (clip->x1 + left) * px_width); 404 for (j = 0; j < line_len; j++) { 405 src = get_char_bitmap(font, msg[i].txt[j], font_pitch); 406 drm_panic_blit(&dst, sb->pitch[0], src, font_pitch, 407 font->height, font->width, 408 fg_color, bg_color, px_width); 409 iosys_map_incr(&dst, font->width * px_width); 410 } 411 } 412 } 413 414 /* 415 * Draw the panic message at the center of the screen 416 */ 417 static void draw_panic_static(struct drm_scanout_buffer *sb) 418 { 419 size_t msg_lines = ARRAY_SIZE(panic_msg); 420 size_t logo_lines = ARRAY_SIZE(logo); 421 u32 fg_color = CONFIG_DRM_PANIC_FOREGROUND_COLOR; 422 u32 bg_color = CONFIG_DRM_PANIC_BACKGROUND_COLOR; 423 const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL); 424 struct drm_rect r_logo, r_msg; 425 426 if (!font) 427 return; 428 429 fg_color = convert_from_xrgb8888(fg_color, sb->format->format); 430 bg_color = convert_from_xrgb8888(bg_color, sb->format->format); 431 432 r_logo = DRM_RECT_INIT(0, 0, 433 get_max_line_len(logo, logo_lines) * font->width, 434 logo_lines * font->height); 435 r_msg = DRM_RECT_INIT(0, 0, 436 min(get_max_line_len(panic_msg, msg_lines) * font->width, sb->width), 437 min(msg_lines * font->height, sb->height)); 438 439 /* Center the panic message */ 440 drm_rect_translate(&r_msg, (sb->width - r_msg.x2) / 2, (sb->height - r_msg.y2) / 2); 441 442 /* Fill with the background color, and draw text on top */ 443 drm_panic_fill(&sb->map[0], sb->pitch[0], sb->height, sb->width, 444 bg_color, sb->format->cpp[0]); 445 446 if ((r_msg.x1 >= drm_rect_width(&r_logo) || r_msg.y1 >= drm_rect_height(&r_logo)) && 447 drm_rect_width(&r_logo) < sb->width && drm_rect_height(&r_logo) < sb->height) { 448 draw_txt_rectangle(sb, font, logo, logo_lines, false, &r_logo, fg_color, bg_color); 449 } 450 draw_txt_rectangle(sb, font, panic_msg, msg_lines, true, &r_msg, fg_color, bg_color); 451 } 452 453 /* 454 * drm_panic_is_format_supported() 455 * @format: a fourcc color code 456 * Returns: true if supported, false otherwise. 457 * 458 * Check if drm_panic will be able to use this color format. 459 */ 460 static bool drm_panic_is_format_supported(const struct drm_format_info *format) 461 { 462 if (format->num_planes != 1) 463 return false; 464 return convert_from_xrgb8888(0xffffff, format->format) != 0; 465 } 466 467 static void draw_panic_plane(struct drm_plane *plane) 468 { 469 struct drm_scanout_buffer sb; 470 int ret; 471 unsigned long flags; 472 473 if (!drm_panic_trylock(plane->dev, flags)) 474 return; 475 476 ret = plane->helper_private->get_scanout_buffer(plane, &sb); 477 478 if (!ret && drm_panic_is_format_supported(sb.format)) { 479 draw_panic_static(&sb); 480 if (plane->helper_private->panic_flush) 481 plane->helper_private->panic_flush(plane); 482 } 483 drm_panic_unlock(plane->dev, flags); 484 } 485 486 static struct drm_plane *to_drm_plane(struct kmsg_dumper *kd) 487 { 488 return container_of(kd, struct drm_plane, kmsg_panic); 489 } 490 491 static void drm_panic(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason) 492 { 493 struct drm_plane *plane = to_drm_plane(dumper); 494 495 if (reason == KMSG_DUMP_PANIC) 496 draw_panic_plane(plane); 497 } 498 499 500 /* 501 * DEBUG FS, This is currently unsafe. 502 * Create one file per plane, so it's possible to debug one plane at a time. 503 * TODO: It would be better to emulate an NMI context. 504 */ 505 #ifdef CONFIG_DRM_PANIC_DEBUG 506 #include <linux/debugfs.h> 507 508 static ssize_t debugfs_trigger_write(struct file *file, const char __user *user_buf, 509 size_t count, loff_t *ppos) 510 { 511 bool run; 512 513 if (kstrtobool_from_user(user_buf, count, &run) == 0 && run) { 514 struct drm_plane *plane = file->private_data; 515 516 draw_panic_plane(plane); 517 } 518 return count; 519 } 520 521 static const struct file_operations dbg_drm_panic_ops = { 522 .owner = THIS_MODULE, 523 .write = debugfs_trigger_write, 524 .open = simple_open, 525 }; 526 527 static void debugfs_register_plane(struct drm_plane *plane, int index) 528 { 529 char fname[32]; 530 531 snprintf(fname, 32, "drm_panic_plane_%d", index); 532 debugfs_create_file(fname, 0200, plane->dev->debugfs_root, 533 plane, &dbg_drm_panic_ops); 534 } 535 #else 536 static void debugfs_register_plane(struct drm_plane *plane, int index) {} 537 #endif /* CONFIG_DRM_PANIC_DEBUG */ 538 539 /** 540 * drm_panic_register() - Initialize DRM panic for a device 541 * @dev: the drm device on which the panic screen will be displayed. 542 */ 543 void drm_panic_register(struct drm_device *dev) 544 { 545 struct drm_plane *plane; 546 int registered_plane = 0; 547 548 if (!dev->mode_config.num_total_plane) 549 return; 550 551 drm_for_each_plane(plane, dev) { 552 if (!plane->helper_private || !plane->helper_private->get_scanout_buffer) 553 continue; 554 plane->kmsg_panic.dump = drm_panic; 555 plane->kmsg_panic.max_reason = KMSG_DUMP_PANIC; 556 if (kmsg_dump_register(&plane->kmsg_panic)) 557 drm_warn(dev, "Failed to register panic handler\n"); 558 else { 559 debugfs_register_plane(plane, registered_plane); 560 registered_plane++; 561 } 562 } 563 if (registered_plane) 564 drm_info(dev, "Registered %d planes with drm panic\n", registered_plane); 565 } 566 EXPORT_SYMBOL(drm_panic_register); 567 568 /** 569 * drm_panic_unregister() 570 * @dev: the drm device previously registered. 571 */ 572 void drm_panic_unregister(struct drm_device *dev) 573 { 574 struct drm_plane *plane; 575 576 if (!dev->mode_config.num_total_plane) 577 return; 578 579 drm_for_each_plane(plane, dev) { 580 if (!plane->helper_private || !plane->helper_private->get_scanout_buffer) 581 continue; 582 kmsg_dump_unregister(&plane->kmsg_panic); 583 } 584 } 585 EXPORT_SYMBOL(drm_panic_unregister); 586