| 1 | |
| 2 | |
| 3 | * Copyright (c) 2023 Red Hat.
|
| 4 | * Author: Jocelyn Falempe <[email protected]>
|
| 5 | * inspired by the drm_log driver from David Herrmann <[email protected]>
|
| 6 | * Tux Ascii art taken from cowsay written by Tony Monroe
|
| 7 |
|
| 8 |
|
| 9 | #include <linux/export.h>
|
| 10 | #include <linux/font.h>
|
| 11 | #include <linux/highmem.h>
|
| 12 | #include <linux/init.h>
|
| 13 | #include <linux/iosys-map.h>
|
| 14 | #include <linux/kdebug.h>
|
| 15 | #include <linux/kmsg_dump.h>
|
| 16 | #include <linux/linux_logo.h>
|
| 17 | #include <linux/list.h>
|
| 18 | #include <linux/math.h>
|
| 19 | #include <linux/module.h>
|
| 20 | #include <linux/overflow.h>
|
| 21 | #include <linux/printk.h>
|
| 22 | #include <linux/types.h>
|
| 23 | #include <linux/utsname.h>
|
| 24 | #include <linux/zlib.h>
|
| 25 |
|
| 26 | #include <drm/drm_drv.h>
|
| 27 | #include <drm/drm_fourcc.h>
|
| 28 | #include <drm/drm_framebuffer.h>
|
| 29 | #include <drm/drm_modeset_helper_vtables.h>
|
| 30 | #include <drm/drm_panic.h>
|
| 31 | #include <drm/drm_plane.h>
|
| 32 | #include <drm/drm_print.h>
|
| 33 | #include <drm/drm_rect.h>
|
| 34 |
|
| 35 | #include "drm_crtc_internal.h"
|
| 36 | #include "drm_draw_internal.h"
|
| 37 |
|
| 38 | MODULE_AUTHOR("Jocelyn Falempe");
|
| 39 | MODULE_DESCRIPTION("DRM panic handler");
|
| 40 | MODULE_LICENSE("GPL");
|
| 41 |
|
| 42 | |
| 43 | * DOC: overview
|
| 44 | *
|
| 45 | * To enable DRM panic for a driver, the primary plane must implement a
|
| 46 | * &drm_plane_helper_funcs.get_scanout_buffer helper function. It is then
|
| 47 | * automatically registered to the drm panic handler.
|
| 48 | * When a panic occurs, the &drm_plane_helper_funcs.get_scanout_buffer will be
|
| 49 | * called, and the driver can provide a framebuffer so the panic handler can
|
| 50 | * draw the panic screen on it. Currently only linear buffer and a few color
|
| 51 | * formats are supported.
|
| 52 | * Optionally the driver can also provide a &drm_plane_helper_funcs.panic_flush
|
| 53 | * callback, that will be called after that, to send additional commands to the
|
| 54 | * hardware to make the scanout buffer visible.
|
| 55 |
|
| 56 |
|
| 57 | |
| 58 | * This module displays a user friendly message on screen when a kernel panic
|
| 59 | * occurs. This is conflicting with fbcon, so you can only enable it when fbcon
|
| 60 | * is disabled.
|
| 61 | * It's intended for end-user, so have minimal technical/debug information.
|
| 62 | *
|
| 63 | * Implementation details:
|
| 64 | *
|
| 65 | * It is a panic handler, so it can't take lock, allocate memory, run tasks/irq,
|
| 66 | * or attempt to sleep. It's a best effort, and it may not be able to display
|
| 67 | * the message in all situations (like if the panic occurs in the middle of a
|
| 68 | * modesetting).
|
| 69 | * It will display only one static frame, so performance optimizations are low
|
| 70 | * priority as the machine is already in an unusable state.
|
| 71 |
|
| 72 |
|
| 73 | struct drm_panic_line {
|
| 74 | u32 len;
|
| 75 | const char *txt;
|
| 76 | };
|
| 77 |
|
| 78 | #define PANIC_LINE(s) {.len = sizeof(s) - 1, .txt = s}
|
| 79 |
|
| 80 | static struct drm_panic_line panic_msg[] = {
|
| 81 | PANIC_LINE("KERNEL PANIC!"),
|
| 82 | PANIC_LINE(""),
|
| 83 | PANIC_LINE("Please reboot your computer."),
|
| 84 | PANIC_LINE(""),
|
| 85 | PANIC_LINE(""),
|
| 86 | };
|
| 87 |
|
| 88 | static const size_t panic_msg_lines = ARRAY_SIZE(panic_msg);
|
| 89 |
|
| 90 | static const struct drm_panic_line logo_ascii[] = {
|
| 91 | PANIC_LINE(" .--. _"),
|
| 92 | PANIC_LINE(" |o_o | | |"),
|
| 93 | PANIC_LINE(" |:_/ | | |"),
|
| 94 | PANIC_LINE(" // \\ \\ |_|"),
|
| 95 | PANIC_LINE(" (| | ) _"),
|
| 96 | PANIC_LINE(" /'\\_ _/`\\ (_)"),
|
| 97 | PANIC_LINE(" \\___)=(___/"),
|
| 98 | };
|
| 99 |
|
| 100 | static const size_t logo_ascii_lines = ARRAY_SIZE(logo_ascii);
|
| 101 |
|
| 102 | #if defined(CONFIG_LOGO) && !defined(MODULE)
|
| 103 | static const struct linux_logo *logo_mono;
|
| 104 |
|
| 105 | static int drm_panic_setup_logo(void)
|
| 106 | {
|
| 107 | const struct linux_logo *logo = fb_find_logo(1);
|
| 108 | const unsigned char *logo_data;
|
| 109 | struct linux_logo *logo_dup;
|
| 110 |
|
| 111 | if (!logo || logo->type != LINUX_LOGO_MONO)
|
| 112 | return 0;
|
| 113 |
|
| 114 |
|
| 115 | logo_data = kmemdup(logo->data,
|
| 116 | size_mul(DIV_ROUND_UP(logo->width, BITS_PER_BYTE), logo->height),
|
| 117 | GFP_KERNEL);
|
| 118 | if (!logo_data)
|
| 119 | return -ENOMEM;
|
| 120 |
|
| 121 | logo_dup = kmemdup(logo, sizeof(*logo), GFP_KERNEL);
|
| 122 | if (!logo_dup) {
|
| 123 | kfree(logo_data);
|
| 124 | return -ENOMEM;
|
| 125 | }
|
| 126 |
|
| 127 | logo_dup->data = logo_data;
|
| 128 | logo_mono = logo_dup;
|
| 129 |
|
| 130 | return 0;
|
| 131 | }
|
| 132 |
|
| 133 | device_initcall(drm_panic_setup_logo);
|
| 134 | #else
|
| 135 | #define logo_mono ((const struct linux_logo *)NULL)
|
| 136 | #endif
|
| 137 |
|
| 138 | |
| 139 | * Blit & Fill functions
|
| 140 |
|
| 141 | static void drm_panic_blit_pixel(struct drm_scanout_buffer *sb, struct drm_rect *clip,
|
| 142 | const u8 *sbuf8, unsigned int spitch, unsigned int scale,
|
| 143 | u32 fg_color)
|
| 144 | {
|
| 145 | unsigned int y, x;
|
| 146 |
|
| 147 | for (y = 0; y < drm_rect_height(clip); y++)
|
| 148 | for (x = 0; x < drm_rect_width(clip); x++)
|
| 149 | if (drm_draw_is_pixel_fg(sbuf8, spitch, x / scale, y / scale))
|
| 150 | sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, fg_color);
|
| 151 | }
|
| 152 |
|
| 153 | static void drm_panic_write_pixel16(void *vaddr, unsigned int offset, u16 color)
|
| 154 | {
|
| 155 | u16 *p = vaddr + offset;
|
| 156 |
|
| 157 | *p = color;
|
| 158 | }
|
| 159 |
|
| 160 | static void drm_panic_write_pixel24(void *vaddr, unsigned int offset, u32 color)
|
| 161 | {
|
| 162 | u8 *p = vaddr + offset;
|
| 163 |
|
| 164 | *p++ = color & 0xff;
|
| 165 | color >>= 8;
|
| 166 | *p++ = color & 0xff;
|
| 167 | color >>= 8;
|
| 168 | *p = color & 0xff;
|
| 169 | }
|
| 170 |
|
| 171 | |
| 172 | * Special case if the pixel crosses page boundaries
|
| 173 |
|
| 174 | static void drm_panic_write_pixel24_xpage(void *vaddr, struct page *next_page,
|
| 175 | unsigned int offset, u32 color)
|
| 176 | {
|
| 177 | u8 *vaddr2;
|
| 178 | u8 *p = vaddr + offset;
|
| 179 |
|
| 180 | vaddr2 = kmap_local_page_try_from_panic(next_page);
|
| 181 |
|
| 182 | *p++ = color & 0xff;
|
| 183 | color >>= 8;
|
| 184 |
|
| 185 | if (offset == PAGE_SIZE - 1)
|
| 186 | p = vaddr2;
|
| 187 |
|
| 188 | *p++ = color & 0xff;
|
| 189 | color >>= 8;
|
| 190 |
|
| 191 | if (offset == PAGE_SIZE - 2)
|
| 192 | p = vaddr2;
|
| 193 |
|
| 194 | *p = color & 0xff;
|
| 195 | kunmap_local(vaddr2);
|
| 196 | }
|
| 197 |
|
| 198 | static void drm_panic_write_pixel32(void *vaddr, unsigned int offset, u32 color)
|
| 199 | {
|
| 200 | u32 *p = vaddr + offset;
|
| 201 |
|
| 202 | *p = color;
|
| 203 | }
|
| 204 |
|
| 205 | static void drm_panic_write_pixel(void *vaddr, unsigned int offset, u32 color, unsigned int cpp)
|
| 206 | {
|
| 207 | switch (cpp) {
|
| 208 | case 2:
|
| 209 | drm_panic_write_pixel16(vaddr, offset, color);
|
| 210 | break;
|
| 211 | case 3:
|
| 212 | drm_panic_write_pixel24(vaddr, offset, color);
|
| 213 | break;
|
| 214 | case 4:
|
| 215 | drm_panic_write_pixel32(vaddr, offset, color);
|
| 216 | break;
|
| 217 | default:
|
| 218 | pr_debug_once("Can't blit with pixel width %d\n", cpp);
|
| 219 | }
|
| 220 | }
|
| 221 |
|
| 222 | |
| 223 | * The scanout buffer pages are not mapped, so for each pixel,
|
| 224 | * use kmap_local_page_try_from_panic() to map the page, and write the pixel.
|
| 225 | * Try to keep the map from the previous pixel, to avoid too much map/unmap.
|
| 226 |
|
| 227 | static void drm_panic_blit_page(struct page **pages, unsigned int dpitch,
|
| 228 | unsigned int cpp, const u8 *sbuf8,
|
| 229 | unsigned int spitch, struct drm_rect *clip,
|
| 230 | unsigned int scale, u32 fg32)
|
| 231 | {
|
| 232 | unsigned int y, x;
|
| 233 | unsigned int page = ~0;
|
| 234 | unsigned int height = drm_rect_height(clip);
|
| 235 | unsigned int width = drm_rect_width(clip);
|
| 236 | void *vaddr = NULL;
|
| 237 |
|
| 238 | for (y = 0; y < height; y++) {
|
| 239 | for (x = 0; x < width; x++) {
|
| 240 | if (drm_draw_is_pixel_fg(sbuf8, spitch, x / scale, y / scale)) {
|
| 241 | unsigned int new_page;
|
| 242 | unsigned int offset;
|
| 243 |
|
| 244 | offset = (y + clip->y1) * dpitch + (x + clip->x1) * cpp;
|
| 245 | new_page = offset >> PAGE_SHIFT;
|
| 246 | offset = offset % PAGE_SIZE;
|
| 247 | if (new_page != page) {
|
| 248 | if (!pages[new_page])
|
| 249 | continue;
|
| 250 | if (vaddr)
|
| 251 | kunmap_local(vaddr);
|
| 252 | page = new_page;
|
| 253 | vaddr = kmap_local_page_try_from_panic(pages[page]);
|
| 254 | }
|
| 255 | if (!vaddr)
|
| 256 | continue;
|
| 257 |
|
| 258 | |
| 259 | if (cpp == 3 && offset + 3 > PAGE_SIZE)
|
| 260 | drm_panic_write_pixel24_xpage(vaddr, pages[page + 1],
|
| 261 | offset, fg32);
|
| 262 | else
|
| 263 | drm_panic_write_pixel(vaddr, offset, fg32, cpp);
|
| 264 | }
|
| 265 | }
|
| 266 | }
|
| 267 | if (vaddr)
|
| 268 | kunmap_local(vaddr);
|
| 269 | }
|
| 270 |
|
| 271 | |
| 272 | * drm_panic_blit - convert a monochrome image to a linear framebuffer
|
| 273 | * @sb: destination scanout buffer
|
| 274 | * @clip: destination rectangle
|
| 275 | * @sbuf8: source buffer, in monochrome format, 8 pixels per byte.
|
| 276 | * @spitch: source pitch in bytes
|
| 277 | * @scale: integer scale, source buffer is scale time smaller than destination
|
| 278 | * rectangle
|
| 279 | * @fg_color: foreground color, in destination format
|
| 280 | *
|
| 281 | * This can be used to draw a font character, which is a monochrome image, to a
|
| 282 | * framebuffer in other supported format.
|
| 283 |
|
| 284 | static void drm_panic_blit(struct drm_scanout_buffer *sb, struct drm_rect *clip,
|
| 285 | const u8 *sbuf8, unsigned int spitch,
|
| 286 | unsigned int scale, u32 fg_color)
|
| 287 |
|
| 288 | {
|
| 289 | struct iosys_map map;
|
| 290 |
|
| 291 | if (sb->set_pixel)
|
| 292 | return drm_panic_blit_pixel(sb, clip, sbuf8, spitch, scale, fg_color);
|
| 293 |
|
| 294 | if (sb->pages)
|
| 295 | return drm_panic_blit_page(sb->pages, sb->pitch[0], sb->format->cpp[0],
|
| 296 | sbuf8, spitch, clip, scale, fg_color);
|
| 297 |
|
| 298 | map = sb->map[0];
|
| 299 | iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]);
|
| 300 |
|
| 301 | switch (sb->format->cpp[0]) {
|
| 302 | case 2:
|
| 303 | drm_draw_blit16(&map, sb->pitch[0], sbuf8, spitch,
|
| 304 | drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);
|
| 305 | break;
|
| 306 | case 3:
|
| 307 | drm_draw_blit24(&map, sb->pitch[0], sbuf8, spitch,
|
| 308 | drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);
|
| 309 | break;
|
| 310 | case 4:
|
| 311 | drm_draw_blit32(&map, sb->pitch[0], sbuf8, spitch,
|
| 312 | drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);
|
| 313 | break;
|
| 314 | default:
|
| 315 | WARN_ONCE(1, "Can't blit with pixel width %d\n", sb->format->cpp[0]);
|
| 316 | }
|
| 317 | }
|
| 318 |
|
| 319 | static void drm_panic_fill_pixel(struct drm_scanout_buffer *sb,
|
| 320 | struct drm_rect *clip,
|
| 321 | u32 color)
|
| 322 | {
|
| 323 | unsigned int y, x;
|
| 324 |
|
| 325 | for (y = 0; y < drm_rect_height(clip); y++)
|
| 326 | for (x = 0; x < drm_rect_width(clip); x++)
|
| 327 | sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, color);
|
| 328 | }
|
| 329 |
|
| 330 | static void drm_panic_fill_page(struct page **pages, unsigned int dpitch,
|
| 331 | unsigned int cpp, struct drm_rect *clip,
|
| 332 | u32 color)
|
| 333 | {
|
| 334 | unsigned int y, x;
|
| 335 | unsigned int page = ~0;
|
| 336 | void *vaddr = NULL;
|
| 337 |
|
| 338 | for (y = clip->y1; y < clip->y2; y++) {
|
| 339 | for (x = clip->x1; x < clip->x2; x++) {
|
| 340 | unsigned int new_page;
|
| 341 | unsigned int offset;
|
| 342 |
|
| 343 | offset = y * dpitch + x * cpp;
|
| 344 | new_page = offset >> PAGE_SHIFT;
|
| 345 | offset = offset % PAGE_SIZE;
|
| 346 | if (new_page != page) {
|
| 347 | if (vaddr)
|
| 348 | kunmap_local(vaddr);
|
| 349 | page = new_page;
|
| 350 | vaddr = kmap_local_page_try_from_panic(pages[page]);
|
| 351 | }
|
| 352 | if (!vaddr)
|
| 353 | continue;
|
| 354 |
|
| 355 | |
| 356 | if (cpp == 3 && offset + 3 > PAGE_SIZE)
|
| 357 | drm_panic_write_pixel24_xpage(vaddr, pages[page + 1],
|
| 358 | offset, color);
|
| 359 | else
|
| 360 | drm_panic_write_pixel(vaddr, offset, color, cpp);
|
| 361 | }
|
| 362 | }
|
| 363 | if (vaddr)
|
| 364 | kunmap_local(vaddr);
|
| 365 | }
|
| 366 |
|
| 367 | |
| 368 | * drm_panic_fill - Fill a rectangle with a color
|
| 369 | * @sb: destination scanout buffer
|
| 370 | * @clip: destination rectangle
|
| 371 | * @color: foreground color, in destination format
|
| 372 | *
|
| 373 | * Fill a rectangle with a color, in a linear framebuffer.
|
| 374 |
|
| 375 | static void drm_panic_fill(struct drm_scanout_buffer *sb, struct drm_rect *clip,
|
| 376 | u32 color)
|
| 377 | {
|
| 378 | struct iosys_map map;
|
| 379 |
|
| 380 | if (sb->set_pixel)
|
| 381 | return drm_panic_fill_pixel(sb, clip, color);
|
| 382 |
|
| 383 | if (sb->pages)
|
| 384 | return drm_panic_fill_page(sb->pages, sb->pitch[0], sb->format->cpp[0],
|
| 385 | clip, color);
|
| 386 |
|
| 387 | map = sb->map[0];
|
| 388 | iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]);
|
| 389 |
|
| 390 | switch (sb->format->cpp[0]) {
|
| 391 | case 2:
|
| 392 | drm_draw_fill16(&map, sb->pitch[0], drm_rect_height(clip),
|
| 393 | drm_rect_width(clip), color);
|
| 394 | break;
|
| 395 | case 3:
|
| 396 | drm_draw_fill24(&map, sb->pitch[0], drm_rect_height(clip),
|
| 397 | drm_rect_width(clip), color);
|
| 398 | break;
|
| 399 | case 4:
|
| 400 | drm_draw_fill32(&map, sb->pitch[0], drm_rect_height(clip),
|
| 401 | drm_rect_width(clip), color);
|
| 402 | break;
|
| 403 | default:
|
| 404 | WARN_ONCE(1, "Can't fill with pixel width %d\n", sb->format->cpp[0]);
|
| 405 | }
|
| 406 | }
|
| 407 |
|
| 408 | static unsigned int get_max_line_len(const struct drm_panic_line *lines, int len)
|
| 409 | {
|
| 410 | int i;
|
| 411 | unsigned int max = 0;
|
| 412 |
|
| 413 | for (i = 0; i < len; i++)
|
| 414 | max = max(lines[i].len, max);
|
| 415 | return max;
|
| 416 | }
|
| 417 |
|
| 418 | |
| 419 | * Draw a text in a rectangle on a framebuffer. The text is truncated if it overflows the rectangle
|
| 420 |
|
| 421 | static void draw_txt_rectangle(struct drm_scanout_buffer *sb,
|
| 422 | const struct font_desc *font,
|
| 423 | const struct drm_panic_line *msg,
|
| 424 | unsigned int msg_lines,
|
| 425 | bool centered,
|
| 426 | struct drm_rect *clip,
|
| 427 | u32 color)
|
| 428 | {
|
| 429 | int i, j;
|
| 430 | const u8 *src;
|
| 431 | size_t font_pitch = DIV_ROUND_UP(font->width, 8);
|
| 432 | struct drm_rect rec;
|
| 433 |
|
| 434 | msg_lines = min(msg_lines, drm_rect_height(clip) / font->height);
|
| 435 | for (i = 0; i < msg_lines; i++) {
|
| 436 | size_t line_len = min(msg[i].len, drm_rect_width(clip) / font->width);
|
| 437 |
|
| 438 | rec.y1 = clip->y1 + i * font->height;
|
| 439 | rec.y2 = rec.y1 + font->height;
|
| 440 | rec.x1 = clip->x1;
|
| 441 |
|
| 442 | if (centered)
|
| 443 | rec.x1 += (drm_rect_width(clip) - (line_len * font->width)) / 2;
|
| 444 |
|
| 445 | for (j = 0; j < line_len; j++) {
|
| 446 | src = font_data_glyph_buf(font->data, font->width, font->height,
|
| 447 | (unsigned char)msg[i].txt[j]);
|
| 448 | rec.x2 = rec.x1 + font->width;
|
| 449 | if (src)
|
| 450 | drm_panic_blit(sb, &rec, src, font_pitch, 1, color);
|
| 451 | rec.x1 += font->width;
|
| 452 | }
|
| 453 | }
|
| 454 | }
|
| 455 |
|
| 456 | static void drm_panic_logo_rect(struct drm_rect *rect, const struct font_desc *font)
|
| 457 | {
|
| 458 | if (logo_mono) {
|
| 459 | drm_rect_init(rect, 0, 0, logo_mono->width, logo_mono->height);
|
| 460 | } else {
|
| 461 | int logo_width = get_max_line_len(logo_ascii, logo_ascii_lines) * font->width;
|
| 462 |
|
| 463 | drm_rect_init(rect, 0, 0, logo_width, logo_ascii_lines * font->height);
|
| 464 | }
|
| 465 | }
|
| 466 |
|
| 467 | static void drm_panic_logo_draw(struct drm_scanout_buffer *sb, struct drm_rect *rect,
|
| 468 | const struct font_desc *font, u32 fg_color)
|
| 469 | {
|
| 470 | if (rect->x2 > sb->width || rect->y2 > sb->height)
|
| 471 | return;
|
| 472 |
|
| 473 | if (logo_mono)
|
| 474 | drm_panic_blit(sb, rect, logo_mono->data,
|
| 475 | DIV_ROUND_UP(drm_rect_width(rect), 8), 1, fg_color);
|
| 476 | else
|
| 477 | draw_txt_rectangle(sb, font, logo_ascii, logo_ascii_lines, false, rect,
|
| 478 | fg_color);
|
| 479 | }
|
| 480 |
|
| 481 | static void draw_panic_screen_user(struct drm_scanout_buffer *sb)
|
| 482 | {
|
| 483 | u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR,
|
| 484 | sb->format->format);
|
| 485 | u32 bg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_BACKGROUND_COLOR,
|
| 486 | sb->format->format);
|
| 487 | const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL);
|
| 488 | struct drm_rect r_screen, r_logo, r_msg;
|
| 489 | unsigned int msg_width, msg_height;
|
| 490 |
|
| 491 | if (!font)
|
| 492 | return;
|
| 493 |
|
| 494 | r_screen = DRM_RECT_INIT(0, 0, sb->width, sb->height);
|
| 495 | drm_panic_logo_rect(&r_logo, font);
|
| 496 |
|
| 497 | msg_width = min(get_max_line_len(panic_msg, panic_msg_lines) * font->width, sb->width);
|
| 498 | msg_height = min(panic_msg_lines * font->height, sb->height);
|
| 499 | r_msg = DRM_RECT_INIT(0, 0, msg_width, msg_height);
|
| 500 |
|
| 501 |
|
| 502 | drm_rect_translate(&r_msg, (sb->width - r_msg.x2) / 2, (sb->height - r_msg.y2) / 2);
|
| 503 |
|
| 504 |
|
| 505 | drm_panic_fill(sb, &r_screen, bg_color);
|
| 506 |
|
| 507 | if (!drm_rect_overlap(&r_logo, &r_msg))
|
| 508 | drm_panic_logo_draw(sb, &r_logo, font, fg_color);
|
| 509 |
|
| 510 | draw_txt_rectangle(sb, font, panic_msg, panic_msg_lines, true, &r_msg, fg_color);
|
| 511 | }
|
| 512 |
|
| 513 | |
| 514 | * Draw one line of kmsg, and handle wrapping if it won't fit in the screen width.
|
| 515 | * Return the y-offset of the next line.
|
| 516 |
|
| 517 | static int draw_line_with_wrap(struct drm_scanout_buffer *sb, const struct font_desc *font,
|
| 518 | struct drm_panic_line *line, int yoffset, u32 fg_color)
|
| 519 | {
|
| 520 | int chars_per_row = sb->width / font->width;
|
| 521 | struct drm_rect r_txt = DRM_RECT_INIT(0, yoffset, sb->width, font->height);
|
| 522 | struct drm_panic_line line_wrap;
|
| 523 |
|
| 524 | if (line->len > chars_per_row) {
|
| 525 | line_wrap.len = line->len % chars_per_row;
|
| 526 | line_wrap.txt = line->txt + line->len - line_wrap.len;
|
| 527 | draw_txt_rectangle(sb, font, &line_wrap, 1, false, &r_txt, fg_color);
|
| 528 | r_txt.y1 -= font->height;
|
| 529 | if (r_txt.y1 < 0)
|
| 530 | return r_txt.y1;
|
| 531 | while (line_wrap.txt > line->txt) {
|
| 532 | line_wrap.txt -= chars_per_row;
|
| 533 | line_wrap.len = chars_per_row;
|
| 534 | draw_txt_rectangle(sb, font, &line_wrap, 1, false, &r_txt, fg_color);
|
| 535 | r_txt.y1 -= font->height;
|
| 536 | if (r_txt.y1 < 0)
|
| 537 | return r_txt.y1;
|
| 538 | }
|
| 539 | } else {
|
| 540 | draw_txt_rectangle(sb, font, line, 1, false, &r_txt, fg_color);
|
| 541 | r_txt.y1 -= font->height;
|
| 542 | }
|
| 543 | return r_txt.y1;
|
| 544 | }
|
| 545 |
|
| 546 | |
| 547 | * Draw the kmsg buffer to the screen, starting from the youngest message at the bottom,
|
| 548 | * and going up until reaching the top of the screen.
|
| 549 |
|
| 550 | static void draw_panic_screen_kmsg(struct drm_scanout_buffer *sb)
|
| 551 | {
|
| 552 | u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR,
|
| 553 | sb->format->format);
|
| 554 | u32 bg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_BACKGROUND_COLOR,
|
| 555 | sb->format->format);
|
| 556 | const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL);
|
| 557 | struct drm_rect r_screen = DRM_RECT_INIT(0, 0, sb->width, sb->height);
|
| 558 | struct kmsg_dump_iter iter;
|
| 559 | char kmsg_buf[512];
|
| 560 | size_t kmsg_len;
|
| 561 | struct drm_panic_line line;
|
| 562 | int yoffset;
|
| 563 |
|
| 564 | if (!font || font->width > sb->width)
|
| 565 | return;
|
| 566 |
|
| 567 | yoffset = sb->height - font->height - (sb->height % font->height) / 2;
|
| 568 |
|
| 569 |
|
| 570 | drm_panic_fill(sb, &r_screen, bg_color);
|
| 571 |
|
| 572 | kmsg_dump_rewind(&iter);
|
| 573 | while (kmsg_dump_get_buffer(&iter, false, kmsg_buf, sizeof(kmsg_buf), &kmsg_len)) {
|
| 574 | char *start;
|
| 575 | char *end;
|
| 576 |
|
| 577 |
|
| 578 | start = kmsg_buf + kmsg_len - 2;
|
| 579 | end = kmsg_buf + kmsg_len - 1;
|
| 580 | while (start > kmsg_buf && yoffset >= 0) {
|
| 581 | while (start > kmsg_buf && *start != '\n')
|
| 582 | start--;
|
| 583 |
|
| 584 | line.txt = start + (start == kmsg_buf ? 0 : 1);
|
| 585 | line.len = end - line.txt;
|
| 586 |
|
| 587 | yoffset = draw_line_with_wrap(sb, font, &line, yoffset, fg_color);
|
| 588 | end = start;
|
| 589 | start--;
|
| 590 | }
|
| 591 | }
|
| 592 | }
|
| 593 |
|
| 594 | #if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
|
| 595 | |
| 596 | * It is unwise to allocate memory in the panic callback, so the buffers are
|
| 597 | * pre-allocated. Only 2 buffers and the zlib workspace are needed.
|
| 598 | * Two buffers are enough, using the following buffer usage:
|
| 599 | * 1) kmsg messages are dumped in buffer1
|
| 600 | * 2) kmsg is zlib-compressed into buffer2
|
| 601 | * 3) compressed kmsg is encoded as QR-code Numeric stream in buffer1
|
| 602 | * 4) QR-code image is generated in buffer2
|
| 603 | * The Max QR code size is V40, 177x177, 4071 bytes for image, 2956 bytes for
|
| 604 | * data segments.
|
| 605 | *
|
| 606 | * Typically, ~7500 bytes of kmsg, are compressed into 2800 bytes, which fits in
|
| 607 | * a V40 QR-code (177x177).
|
| 608 | *
|
| 609 | * If CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL is not set, the kmsg data will be put
|
| 610 | * directly in the QR code.
|
| 611 | * 1) kmsg messages are dumped in buffer1
|
| 612 | * 2) kmsg message is encoded as byte stream in buffer2
|
| 613 | * 3) QR-code image is generated in buffer1
|
| 614 |
|
| 615 |
|
| 616 | static uint panic_qr_version = CONFIG_DRM_PANIC_SCREEN_QR_VERSION;
|
| 617 | module_param(panic_qr_version, uint, 0644);
|
| 618 | MODULE_PARM_DESC(panic_qr_version, "maximum version (size) of the QR code");
|
| 619 |
|
| 620 | #define MAX_QR_DATA 2956
|
| 621 | #define MAX_ZLIB_RATIO 3
|
| 622 | #define QR_BUFFER1_SIZE (MAX_ZLIB_RATIO * MAX_QR_DATA)
|
| 623 | #define QR_BUFFER2_SIZE 4096
|
| 624 | #define QR_MARGIN 4
|
| 625 |
|
| 626 |
|
| 627 | #define COMPR_LEVEL 6
|
| 628 | #define WINDOW_BITS 12
|
| 629 | #define MEM_LEVEL 4
|
| 630 |
|
| 631 | static char *qrbuf1;
|
| 632 | static char *qrbuf2;
|
| 633 | static struct z_stream_s stream;
|
| 634 |
|
| 635 | static void __init drm_panic_qr_init(void)
|
| 636 | {
|
| 637 | qrbuf1 = kmalloc(QR_BUFFER1_SIZE, GFP_KERNEL);
|
| 638 | qrbuf2 = kmalloc(QR_BUFFER2_SIZE, GFP_KERNEL);
|
| 639 | stream.workspace = kmalloc(zlib_deflate_workspacesize(WINDOW_BITS, MEM_LEVEL),
|
| 640 | GFP_KERNEL);
|
| 641 | }
|
| 642 |
|
| 643 | static void drm_panic_qr_exit(void)
|
| 644 | {
|
| 645 | kfree(qrbuf1);
|
| 646 | qrbuf1 = NULL;
|
| 647 | kfree(qrbuf2);
|
| 648 | qrbuf2 = NULL;
|
| 649 | kfree(stream.workspace);
|
| 650 | stream.workspace = NULL;
|
| 651 | }
|
| 652 |
|
| 653 | static int drm_panic_get_qr_code_url(u8 **qr_image)
|
| 654 | {
|
| 655 | struct kmsg_dump_iter iter;
|
| 656 | char url[256];
|
| 657 | size_t kmsg_len, max_kmsg_size;
|
| 658 | char *kmsg;
|
| 659 | int max_qr_data_size, url_len;
|
| 660 |
|
| 661 | url_len = snprintf(url, sizeof(url), CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL "?a=%s&v=%s&z=",
|
| 662 | utsname()->machine, utsname()->release);
|
| 663 |
|
| 664 | max_qr_data_size = drm_panic_qr_max_data_size(panic_qr_version, url_len);
|
| 665 | max_kmsg_size = min(MAX_ZLIB_RATIO * max_qr_data_size, QR_BUFFER1_SIZE);
|
| 666 |
|
| 667 |
|
| 668 | kmsg_dump_rewind(&iter);
|
| 669 | kmsg_dump_get_buffer(&iter, false, qrbuf1, max_kmsg_size, &kmsg_len);
|
| 670 |
|
| 671 | if (!kmsg_len)
|
| 672 | return -ENODATA;
|
| 673 | kmsg = qrbuf1;
|
| 674 |
|
| 675 | try_again:
|
| 676 | if (zlib_deflateInit2(&stream, COMPR_LEVEL, Z_DEFLATED, WINDOW_BITS,
|
| 677 | MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK)
|
| 678 | return -EINVAL;
|
| 679 |
|
| 680 | stream.next_in = kmsg;
|
| 681 | stream.avail_in = kmsg_len;
|
| 682 | stream.total_in = 0;
|
| 683 | stream.next_out = qrbuf2;
|
| 684 | stream.avail_out = QR_BUFFER2_SIZE;
|
| 685 | stream.total_out = 0;
|
| 686 |
|
| 687 | if (zlib_deflate(&stream, Z_FINISH) != Z_STREAM_END)
|
| 688 | return -EINVAL;
|
| 689 |
|
| 690 | if (zlib_deflateEnd(&stream) != Z_OK)
|
| 691 | return -EINVAL;
|
| 692 |
|
| 693 | if (stream.total_out > max_qr_data_size) {
|
| 694 |
|
| 695 | kmsg = strchr(kmsg, '\n');
|
| 696 | if (!kmsg)
|
| 697 | return -EINVAL;
|
| 698 |
|
| 699 | kmsg += 1;
|
| 700 | kmsg_len = strlen(kmsg);
|
| 701 | goto try_again;
|
| 702 | }
|
| 703 | *qr_image = qrbuf2;
|
| 704 |
|
| 705 |
|
| 706 | return drm_panic_qr_generate(url, qrbuf2, stream.total_out, QR_BUFFER2_SIZE,
|
| 707 | qrbuf1, QR_BUFFER1_SIZE);
|
| 708 | }
|
| 709 |
|
| 710 | static int drm_panic_get_qr_code_raw(u8 **qr_image)
|
| 711 | {
|
| 712 | struct kmsg_dump_iter iter;
|
| 713 | size_t kmsg_len;
|
| 714 | size_t max_kmsg_size = min(drm_panic_qr_max_data_size(panic_qr_version, 0),
|
| 715 | QR_BUFFER1_SIZE);
|
| 716 |
|
| 717 | kmsg_dump_rewind(&iter);
|
| 718 | kmsg_dump_get_buffer(&iter, false, qrbuf1, max_kmsg_size, &kmsg_len);
|
| 719 | if (!kmsg_len)
|
| 720 | return -ENODATA;
|
| 721 |
|
| 722 | *qr_image = qrbuf1;
|
| 723 | return drm_panic_qr_generate(NULL, qrbuf1, kmsg_len, QR_BUFFER1_SIZE,
|
| 724 | qrbuf2, QR_BUFFER2_SIZE);
|
| 725 | }
|
| 726 |
|
| 727 | static int drm_panic_get_qr_code(u8 **qr_image)
|
| 728 | {
|
| 729 | if (strlen(CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL) > 0)
|
| 730 | return drm_panic_get_qr_code_url(qr_image);
|
| 731 | else
|
| 732 | return drm_panic_get_qr_code_raw(qr_image);
|
| 733 | }
|
| 734 |
|
| 735 | |
| 736 | * Draw the panic message at the center of the screen, with a QR Code
|
| 737 |
|
| 738 | static int _draw_panic_screen_qr_code(struct drm_scanout_buffer *sb)
|
| 739 | {
|
| 740 | u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR,
|
| 741 | sb->format->format);
|
| 742 | u32 bg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_BACKGROUND_COLOR,
|
| 743 | sb->format->format);
|
| 744 | const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL);
|
| 745 | struct drm_rect r_screen, r_logo, r_msg, r_qr, r_qr_canvas;
|
| 746 | unsigned int max_qr_size, scale;
|
| 747 | unsigned int msg_width, msg_height;
|
| 748 | int qr_width, qr_canvas_width, qr_pitch, v_margin;
|
| 749 | u8 *qr_image;
|
| 750 |
|
| 751 | if (!font || !qrbuf1 || !qrbuf2 || !stream.workspace)
|
| 752 | return -ENOMEM;
|
| 753 |
|
| 754 | r_screen = DRM_RECT_INIT(0, 0, sb->width, sb->height);
|
| 755 |
|
| 756 | drm_panic_logo_rect(&r_logo, font);
|
| 757 |
|
| 758 | msg_width = min(get_max_line_len(panic_msg, panic_msg_lines) * font->width, sb->width);
|
| 759 | msg_height = min(panic_msg_lines * font->height, sb->height);
|
| 760 | r_msg = DRM_RECT_INIT(0, 0, msg_width, msg_height);
|
| 761 |
|
| 762 | max_qr_size = min(3 * sb->width / 4, 3 * sb->height / 4);
|
| 763 |
|
| 764 | qr_width = drm_panic_get_qr_code(&qr_image);
|
| 765 | if (qr_width <= 0)
|
| 766 | return -ENOSPC;
|
| 767 |
|
| 768 | qr_canvas_width = qr_width + QR_MARGIN * 2;
|
| 769 | scale = max_qr_size / qr_canvas_width;
|
| 770 |
|
| 771 | if (scale < 2)
|
| 772 | return -ENOSPC;
|
| 773 |
|
| 774 | pr_debug("QR width %d and scale %d\n", qr_width, scale);
|
| 775 | r_qr_canvas = DRM_RECT_INIT(0, 0, qr_canvas_width * scale, qr_canvas_width * scale);
|
| 776 |
|
| 777 | v_margin = sb->height - drm_rect_height(&r_qr_canvas) - drm_rect_height(&r_msg);
|
| 778 | if (v_margin < 0)
|
| 779 | return -ENOSPC;
|
| 780 | v_margin /= 5;
|
| 781 |
|
| 782 | drm_rect_translate(&r_qr_canvas, (sb->width - r_qr_canvas.x2) / 2, 2 * v_margin);
|
| 783 | r_qr = DRM_RECT_INIT(r_qr_canvas.x1 + QR_MARGIN * scale, r_qr_canvas.y1 + QR_MARGIN * scale,
|
| 784 | qr_width * scale, qr_width * scale);
|
| 785 |
|
| 786 |
|
| 787 | drm_rect_translate(&r_msg, (sb->width - r_msg.x2) / 2,
|
| 788 | 3 * v_margin + drm_rect_height(&r_qr_canvas));
|
| 789 |
|
| 790 |
|
| 791 | drm_panic_fill(sb, &r_screen, bg_color);
|
| 792 |
|
| 793 | if (!drm_rect_overlap(&r_logo, &r_msg) && !drm_rect_overlap(&r_logo, &r_qr_canvas))
|
| 794 | drm_panic_logo_draw(sb, &r_logo, font, fg_color);
|
| 795 |
|
| 796 | draw_txt_rectangle(sb, font, panic_msg, panic_msg_lines, true, &r_msg, fg_color);
|
| 797 |
|
| 798 |
|
| 799 | qr_pitch = DIV_ROUND_UP(qr_width, 8);
|
| 800 | drm_panic_fill(sb, &r_qr_canvas, fg_color);
|
| 801 | drm_panic_fill(sb, &r_qr, bg_color);
|
| 802 | drm_panic_blit(sb, &r_qr, qr_image, qr_pitch, scale, fg_color);
|
| 803 | return 0;
|
| 804 | }
|
| 805 |
|
| 806 | static void draw_panic_screen_qr_code(struct drm_scanout_buffer *sb)
|
| 807 | {
|
| 808 | if (_draw_panic_screen_qr_code(sb))
|
| 809 | draw_panic_screen_user(sb);
|
| 810 | }
|
| 811 | #else
|
| 812 | static void drm_panic_qr_init(void) {};
|
| 813 | static void drm_panic_qr_exit(void) {};
|
| 814 | #endif
|
| 815 |
|
| 816 | enum drm_panic_type {
|
| 817 | DRM_PANIC_TYPE_KMSG,
|
| 818 | DRM_PANIC_TYPE_USER,
|
| 819 | DRM_PANIC_TYPE_QR,
|
| 820 | };
|
| 821 |
|
| 822 | static enum drm_panic_type drm_panic_type = -1;
|
| 823 |
|
| 824 | static const char *drm_panic_type_map[] = {
|
| 825 | [DRM_PANIC_TYPE_KMSG] = "kmsg",
|
| 826 | [DRM_PANIC_TYPE_USER] = "user",
|
| 827 | #if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
|
| 828 | [DRM_PANIC_TYPE_QR] = "qr_code",
|
| 829 | #endif
|
| 830 | };
|
| 831 |
|
| 832 | static int drm_panic_type_set(const char *val, const struct kernel_param *kp)
|
| 833 | {
|
| 834 | unsigned int i;
|
| 835 |
|
| 836 | for (i = 0; i < ARRAY_SIZE(drm_panic_type_map); i++) {
|
| 837 | if (!strcmp(val, drm_panic_type_map[i])) {
|
| 838 | drm_panic_type = i;
|
| 839 | return 0;
|
| 840 | }
|
| 841 | }
|
| 842 |
|
| 843 | return -EINVAL;
|
| 844 | }
|
| 845 |
|
| 846 | static int drm_panic_type_get(char *buffer, const struct kernel_param *kp)
|
| 847 | {
|
| 848 | return scnprintf(buffer, PAGE_SIZE, "%s\n",
|
| 849 | drm_panic_type_map[drm_panic_type]);
|
| 850 | }
|
| 851 |
|
| 852 | static const struct kernel_param_ops drm_panic_ops = {
|
| 853 | .set = drm_panic_type_set,
|
| 854 | .get = drm_panic_type_get,
|
| 855 | };
|
| 856 |
|
| 857 | module_param_cb(panic_screen, &drm_panic_ops, NULL, 0644);
|
| 858 | MODULE_PARM_DESC(panic_screen,
|
| 859 | #if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
|
| 860 | "Choose what will be displayed by drm_panic, 'user', 'kmsg' or 'qr_code' [default="
|
| 861 | #else
|
| 862 | "Choose what will be displayed by drm_panic, 'user' or 'kmsg' [default="
|
| 863 | #endif
|
| 864 | CONFIG_DRM_PANIC_SCREEN "]");
|
| 865 |
|
| 866 | |
| 867 | * drm_panic_is_format_supported()
|
| 868 | * @format: a fourcc color code
|
| 869 | * Returns: true if supported, false otherwise.
|
| 870 | *
|
| 871 | * Check if drm_panic will be able to use this color format.
|
| 872 |
|
| 873 | static bool drm_panic_is_format_supported(const struct drm_format_info *format)
|
| 874 | {
|
| 875 | if (format->num_planes != 1)
|
| 876 | return false;
|
| 877 | return drm_draw_can_convert_from_xrgb8888(format->format);
|
| 878 | }
|
| 879 |
|
| 880 | static void draw_panic_dispatch(struct drm_scanout_buffer *sb)
|
| 881 | {
|
| 882 | switch (drm_panic_type) {
|
| 883 | case DRM_PANIC_TYPE_KMSG:
|
| 884 | draw_panic_screen_kmsg(sb);
|
| 885 | break;
|
| 886 |
|
| 887 | #if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
|
| 888 | case DRM_PANIC_TYPE_QR:
|
| 889 | draw_panic_screen_qr_code(sb);
|
| 890 | break;
|
| 891 | #endif
|
| 892 |
|
| 893 | case DRM_PANIC_TYPE_USER:
|
| 894 | default:
|
| 895 | draw_panic_screen_user(sb);
|
| 896 | }
|
| 897 | }
|
| 898 |
|
| 899 | static void drm_panic_set_description(const char *description)
|
| 900 | {
|
| 901 | u32 len;
|
| 902 |
|
| 903 | if (description) {
|
| 904 | struct drm_panic_line *desc_line = &panic_msg[panic_msg_lines - 1];
|
| 905 |
|
| 906 | desc_line->txt = description;
|
| 907 | len = strlen(description);
|
| 908 |
|
| 909 | if (len && description[len - 1] == '\n')
|
| 910 | len -= 1;
|
| 911 | desc_line->len = len;
|
| 912 | }
|
| 913 | }
|
| 914 |
|
| 915 | static void drm_panic_clear_description(void)
|
| 916 | {
|
| 917 | struct drm_panic_line *desc_line = &panic_msg[panic_msg_lines - 1];
|
| 918 |
|
| 919 | desc_line->len = 0;
|
| 920 | desc_line->txt = NULL;
|
| 921 | }
|
| 922 |
|
| 923 | static void draw_panic_plane(struct drm_plane *plane, const char *description)
|
| 924 | {
|
| 925 | struct drm_scanout_buffer sb = { };
|
| 926 | int ret;
|
| 927 | unsigned long flags;
|
| 928 |
|
| 929 | if (!drm_panic_trylock(plane->dev, flags))
|
| 930 | return;
|
| 931 |
|
| 932 | ret = plane->helper_private->get_scanout_buffer(plane, &sb);
|
| 933 |
|
| 934 | if (ret || !drm_panic_is_format_supported(sb.format))
|
| 935 | goto unlock;
|
| 936 |
|
| 937 |
|
| 938 | if (!sb.set_pixel && !sb.pages && iosys_map_is_null(&sb.map[0]))
|
| 939 | goto unlock;
|
| 940 |
|
| 941 | drm_panic_set_description(description);
|
| 942 |
|
| 943 | draw_panic_dispatch(&sb);
|
| 944 | if (plane->helper_private->panic_flush)
|
| 945 | plane->helper_private->panic_flush(plane);
|
| 946 |
|
| 947 | drm_panic_clear_description();
|
| 948 |
|
| 949 | unlock:
|
| 950 | drm_panic_unlock(plane->dev, flags);
|
| 951 | }
|
| 952 |
|
| 953 | static struct drm_plane *to_drm_plane(struct kmsg_dumper *kd)
|
| 954 | {
|
| 955 | return container_of(kd, struct drm_plane, kmsg_panic);
|
| 956 | }
|
| 957 |
|
| 958 | static void drm_panic(struct kmsg_dumper *dumper, struct kmsg_dump_detail *detail)
|
| 959 | {
|
| 960 | struct drm_plane *plane = to_drm_plane(dumper);
|
| 961 |
|
| 962 | if (detail->reason == KMSG_DUMP_PANIC)
|
| 963 | draw_panic_plane(plane, detail->description);
|
| 964 | }
|
| 965 |
|
| 966 |
|
| 967 | |
| 968 | * DEBUG FS, This is currently unsafe.
|
| 969 | * Create one file per plane, so it's possible to debug one plane at a time.
|
| 970 | * TODO: It would be better to emulate an NMI context.
|
| 971 |
|
| 972 | #ifdef CONFIG_DRM_PANIC_DEBUG
|
| 973 | #include <linux/debugfs.h>
|
| 974 |
|
| 975 | static ssize_t debugfs_trigger_write(struct file *file, const char __user *user_buf,
|
| 976 | size_t count, loff_t *ppos)
|
| 977 | {
|
| 978 | bool run;
|
| 979 |
|
| 980 | if (kstrtobool_from_user(user_buf, count, &run) == 0 && run) {
|
| 981 | struct drm_plane *plane = file->private_data;
|
| 982 |
|
| 983 | draw_panic_plane(plane, "Test from debugfs");
|
| 984 | }
|
| 985 | return count;
|
| 986 | }
|
| 987 |
|
| 988 | static const struct file_operations dbg_drm_panic_ops = {
|
| 989 | .owner = THIS_MODULE,
|
| 990 | .write = debugfs_trigger_write,
|
| 991 | .open = simple_open,
|
| 992 | };
|
| 993 |
|
| 994 | static void debugfs_register_plane(struct drm_plane *plane, int index)
|
| 995 | {
|
| 996 | char fname[32];
|
| 997 |
|
| 998 | snprintf(fname, 32, "drm_panic_plane_%d", index);
|
| 999 | debugfs_create_file(fname, 0200, plane->dev->debugfs_root,
|
| 1000 | plane, &dbg_drm_panic_ops);
|
| 1001 | }
|
| 1002 | #else
|
| 1003 | static void debugfs_register_plane(struct drm_plane *plane, int index) {}
|
| 1004 | #endif
|
| 1005 |
|
| 1006 | |
| 1007 | * drm_panic_is_enabled
|
| 1008 | * @dev: the drm device that may supports drm_panic
|
| 1009 | *
|
| 1010 | * returns true if the drm device supports drm_panic
|
| 1011 |
|
| 1012 | bool drm_panic_is_enabled(struct drm_device *dev)
|
| 1013 | {
|
| 1014 | struct drm_plane *plane;
|
| 1015 |
|
| 1016 | if (!dev->mode_config.num_total_plane)
|
| 1017 | return false;
|
| 1018 |
|
| 1019 | drm_for_each_plane(plane, dev)
|
| 1020 | if (plane->helper_private && plane->helper_private->get_scanout_buffer)
|
| 1021 | return true;
|
| 1022 | return false;
|
| 1023 | }
|
| 1024 | EXPORT_SYMBOL(drm_panic_is_enabled);
|
| 1025 |
|
| 1026 | |
| 1027 | * drm_panic_register() - Initialize DRM panic for a device
|
| 1028 | * @dev: the drm device on which the panic screen will be displayed.
|
| 1029 |
|
| 1030 | void drm_panic_register(struct drm_device *dev)
|
| 1031 | {
|
| 1032 | struct drm_plane *plane;
|
| 1033 | int registered_plane = 0;
|
| 1034 |
|
| 1035 | if (!dev->mode_config.num_total_plane)
|
| 1036 | return;
|
| 1037 |
|
| 1038 | drm_for_each_plane(plane, dev) {
|
| 1039 | if (!plane->helper_private || !plane->helper_private->get_scanout_buffer)
|
| 1040 | continue;
|
| 1041 | plane->kmsg_panic.dump = drm_panic;
|
| 1042 | plane->kmsg_panic.max_reason = KMSG_DUMP_PANIC;
|
| 1043 | if (kmsg_dump_register(&plane->kmsg_panic))
|
| 1044 | drm_warn(dev, "Failed to register panic handler\n");
|
| 1045 | else {
|
| 1046 | debugfs_register_plane(plane, registered_plane);
|
| 1047 | registered_plane++;
|
| 1048 | }
|
| 1049 | }
|
| 1050 | if (registered_plane)
|
| 1051 | drm_info(dev, "Registered %d planes with drm panic\n", registered_plane);
|
| 1052 | }
|
| 1053 |
|
| 1054 | |
| 1055 | * drm_panic_unregister()
|
| 1056 | * @dev: the drm device previously registered.
|
| 1057 |
|
| 1058 | void drm_panic_unregister(struct drm_device *dev)
|
| 1059 | {
|
| 1060 | struct drm_plane *plane;
|
| 1061 |
|
| 1062 | if (!dev->mode_config.num_total_plane)
|
| 1063 | return;
|
| 1064 |
|
| 1065 | drm_for_each_plane(plane, dev) {
|
| 1066 | if (!plane->helper_private || !plane->helper_private->get_scanout_buffer)
|
| 1067 | continue;
|
| 1068 | kmsg_dump_unregister(&plane->kmsg_panic);
|
| 1069 | }
|
| 1070 | }
|
| 1071 |
|
| 1072 | |
| 1073 | * drm_panic_init() - initialize DRM panic.
|
| 1074 |
|
| 1075 | void __init drm_panic_init(void)
|
| 1076 | {
|
| 1077 | if (drm_panic_type == -1 && drm_panic_type_set(CONFIG_DRM_PANIC_SCREEN, NULL)) {
|
| 1078 | pr_warn("Unsupported value for CONFIG_DRM_PANIC_SCREEN ('%s'), falling back to 'user'...\n",
|
| 1079 | CONFIG_DRM_PANIC_SCREEN);
|
| 1080 | drm_panic_type = DRM_PANIC_TYPE_USER;
|
| 1081 | }
|
| 1082 | drm_panic_qr_init();
|
| 1083 | }
|
| 1084 |
|
| 1085 | |
| 1086 | * drm_panic_exit() - Free the resources taken by drm_panic_exit()
|
| 1087 |
|
| 1088 | void drm_panic_exit(void)
|
| 1089 | {
|
| 1090 | drm_panic_qr_exit();
|
| 1091 | }
|
| 1092 |
|
| 1093 | #ifdef CONFIG_DRM_KUNIT_TEST
|
| 1094 | #include "tests/drm_panic_test.c"
|
| 1095 | #endif
|