1095 lines · 30.0 KB · C
RawDownload
// 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/font.h>#include <linux/highmem.h>#include <linux/init.h>#include <linux/iosys-map.h>#include <linux/kdebug.h>#include <linux/kmsg_dump.h>#include <linux/linux_logo.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 <linux/utsname.h>#include <linux/zlib.h> #include <drm/drm_drv.h>#include <drm/drm_fourcc.h>#include <drm/drm_framebuffer.h>#include <drm/drm_modeset_helper_vtables.h>#include <drm/drm_panic.h>#include <drm/drm_plane.h>#include <drm/drm_print.h>#include <drm/drm_rect.h> #include "drm_crtc_internal.h"#include "drm_draw_internal.h" MODULE_AUTHOR("Jocelyn Falempe");MODULE_DESCRIPTION("DRM panic handler");MODULE_LICENSE("GPL"); /** * DOC: overview * * To enable DRM panic for a driver, the primary plane must implement a * &drm_plane_helper_funcs.get_scanout_buffer helper function. It is then * automatically registered to the drm panic handler. * When a panic occurs, the &drm_plane_helper_funcs.get_scanout_buffer will be * called, and the driver can provide a framebuffer so the panic handler can * draw the panic screen on it. Currently only linear buffer and a few color * formats are supported. * Optionally the driver can also provide a &drm_plane_helper_funcs.panic_flush * callback, that will be called after that, to send additional commands to the * hardware to make the scanout buffer visible. */ /* * This module displays a user friendly message on screen when a kernel panic * occurs. This is conflicting with fbcon, so you can only enable it when fbcon * is disabled. * It's intended for end-user, so have minimal technical/debug information. * * Implementation details: * * It is a panic handler, so it can't take lock, allocate memory, run tasks/irq, * or attempt to sleep. It's a best effort, and it may not be able to display * the message in all situations (like if the panic occurs in the middle of a * modesetting). * It will display only one static frame, so performance optimizations are low * priority as the machine is already in an unusable state. */ struct drm_panic_line {	u32 len;	const char *txt;}; #define PANIC_LINE(s) {.len = sizeof(s) - 1, .txt = s} static struct drm_panic_line panic_msg[] = {	PANIC_LINE("KERNEL PANIC!"),	PANIC_LINE(""),	PANIC_LINE("Please reboot your computer."),	PANIC_LINE(""),	PANIC_LINE(""), /* will be replaced by the panic description */}; static const size_t panic_msg_lines = ARRAY_SIZE(panic_msg); static const struct drm_panic_line logo_ascii[] = {	PANIC_LINE("     .--.        _"),	PANIC_LINE("    |o_o |      | |"),	PANIC_LINE("    |:_/ |      | |"),	PANIC_LINE("   //   \\ \\     |_|"),	PANIC_LINE("  (|     | )     _"),	PANIC_LINE(" /'\\_   _/`\\    (_)"),	PANIC_LINE(" \\___)=(___/"),}; static const size_t logo_ascii_lines = ARRAY_SIZE(logo_ascii); #if defined(CONFIG_LOGO) && !defined(MODULE)static const struct linux_logo *logo_mono; static int drm_panic_setup_logo(void){	const struct linux_logo *logo = fb_find_logo(1);	const unsigned char *logo_data;	struct linux_logo *logo_dup; 	if (!logo || logo->type != LINUX_LOGO_MONO)		return 0; 	/* The logo is __init, so we must make a copy for later use */	logo_data = kmemdup(logo->data,			    size_mul(DIV_ROUND_UP(logo->width, BITS_PER_BYTE), logo->height),			    GFP_KERNEL);	if (!logo_data)		return -ENOMEM; 	logo_dup = kmemdup(logo, sizeof(*logo), GFP_KERNEL);	if (!logo_dup) {		kfree(logo_data);		return -ENOMEM;	} 	logo_dup->data = logo_data;	logo_mono = logo_dup; 	return 0;} device_initcall(drm_panic_setup_logo);#else#define logo_mono	((const struct linux_logo *)NULL)#endif /* *  Blit & Fill functions */static void drm_panic_blit_pixel(struct drm_scanout_buffer *sb, struct drm_rect *clip,				 const u8 *sbuf8, unsigned int spitch, unsigned int scale,				 u32 fg_color){	unsigned int y, x; 	for (y = 0; y < drm_rect_height(clip); y++)		for (x = 0; x < drm_rect_width(clip); x++)			if (drm_draw_is_pixel_fg(sbuf8, spitch, x / scale, y / scale))				sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, fg_color);} static void drm_panic_write_pixel16(void *vaddr, unsigned int offset, u16 color){	u16 *p = vaddr + offset; 	*p = color;} static void drm_panic_write_pixel24(void *vaddr, unsigned int offset, u32 color){	u8 *p = vaddr + offset; 	*p++ = color & 0xff;	color >>= 8;	*p++ = color & 0xff;	color >>= 8;	*p = color & 0xff;} /* * Special case if the pixel crosses page boundaries */static void drm_panic_write_pixel24_xpage(void *vaddr, struct page *next_page,					  unsigned int offset, u32 color){	u8 *vaddr2;	u8 *p = vaddr + offset; 	vaddr2 = kmap_local_page_try_from_panic(next_page); 	*p++ = color & 0xff;	color >>= 8; 	if (offset == PAGE_SIZE - 1)		p = vaddr2; 	*p++ = color & 0xff;	color >>= 8; 	if (offset == PAGE_SIZE - 2)		p = vaddr2; 	*p = color & 0xff;	kunmap_local(vaddr2);} static void drm_panic_write_pixel32(void *vaddr, unsigned int offset, u32 color){	u32 *p = vaddr + offset; 	*p = color;} static void drm_panic_write_pixel(void *vaddr, unsigned int offset, u32 color, unsigned int cpp){	switch (cpp) {	case 2:		drm_panic_write_pixel16(vaddr, offset, color);		break;	case 3:		drm_panic_write_pixel24(vaddr, offset, color);		break;	case 4:		drm_panic_write_pixel32(vaddr, offset, color);		break;	default:		pr_debug_once("Can't blit with pixel width %d\n", cpp);	}} /* * The scanout buffer pages are not mapped, so for each pixel, * use kmap_local_page_try_from_panic() to map the page, and write the pixel. * Try to keep the map from the previous pixel, to avoid too much map/unmap. */static void drm_panic_blit_page(struct page **pages, unsigned int dpitch,				unsigned int cpp, const u8 *sbuf8,				unsigned int spitch, struct drm_rect *clip,				unsigned int scale, u32 fg32){	unsigned int y, x;	unsigned int page = ~0;	unsigned int height = drm_rect_height(clip);	unsigned int width = drm_rect_width(clip);	void *vaddr = NULL; 	for (y = 0; y < height; y++) {		for (x = 0; x < width; x++) {			if (drm_draw_is_pixel_fg(sbuf8, spitch, x / scale, y / scale)) {				unsigned int new_page;				unsigned int offset; 				offset = (y + clip->y1) * dpitch + (x + clip->x1) * cpp;				new_page = offset >> PAGE_SHIFT;				offset = offset % PAGE_SIZE;				if (new_page != page) {					if (!pages[new_page])						continue;					if (vaddr)						kunmap_local(vaddr);					page = new_page;					vaddr = kmap_local_page_try_from_panic(pages[page]);				}				if (!vaddr)					continue; 				// Special case for 24bit, as a pixel might cross page boundaries				if (cpp == 3 && offset + 3 > PAGE_SIZE)					drm_panic_write_pixel24_xpage(vaddr, pages[page + 1],								      offset, fg32);				else					drm_panic_write_pixel(vaddr, offset, fg32, cpp);			}		}	}	if (vaddr)		kunmap_local(vaddr);} /* * drm_panic_blit - convert a monochrome image to a linear framebuffer * @sb: destination scanout buffer * @clip: destination rectangle * @sbuf8: source buffer, in monochrome format, 8 pixels per byte. * @spitch: source pitch in bytes * @scale: integer scale, source buffer is scale time smaller than destination *         rectangle * @fg_color: foreground color, in destination format * * This can be used to draw a font character, which is a monochrome image, to a * framebuffer in other supported format. */static void drm_panic_blit(struct drm_scanout_buffer *sb, struct drm_rect *clip,			   const u8 *sbuf8, unsigned int spitch,			   unsigned int scale, u32 fg_color) {	struct iosys_map map; 	if (sb->set_pixel)		return drm_panic_blit_pixel(sb, clip, sbuf8, spitch, scale, fg_color); 	if (sb->pages)		return drm_panic_blit_page(sb->pages, sb->pitch[0], sb->format->cpp[0],					   sbuf8, spitch, clip, scale, fg_color); 	map = sb->map[0];	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); 	switch (sb->format->cpp[0]) {	case 2:		drm_draw_blit16(&map, sb->pitch[0], sbuf8, spitch,				drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);	break;	case 3:		drm_draw_blit24(&map, sb->pitch[0], sbuf8, spitch,				drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);	break;	case 4:		drm_draw_blit32(&map, sb->pitch[0], sbuf8, spitch,				drm_rect_height(clip), drm_rect_width(clip), scale, fg_color);	break;	default:		WARN_ONCE(1, "Can't blit with pixel width %d\n", sb->format->cpp[0]);	}} static void drm_panic_fill_pixel(struct drm_scanout_buffer *sb,				 struct drm_rect *clip,				 u32 color){	unsigned int y, x; 	for (y = 0; y < drm_rect_height(clip); y++)		for (x = 0; x < drm_rect_width(clip); x++)			sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, color);} static void drm_panic_fill_page(struct page **pages, unsigned int dpitch,				unsigned int cpp, struct drm_rect *clip,				u32 color){	unsigned int y, x;	unsigned int page = ~0;	void *vaddr = NULL; 	for (y = clip->y1; y < clip->y2; y++) {		for (x = clip->x1; x < clip->x2; x++) {			unsigned int new_page;			unsigned int offset; 			offset = y * dpitch + x * cpp;			new_page = offset >> PAGE_SHIFT;			offset = offset % PAGE_SIZE;			if (new_page != page) {				if (vaddr)					kunmap_local(vaddr);				page = new_page;				vaddr = kmap_local_page_try_from_panic(pages[page]);			}			if (!vaddr)				continue; 			// Special case for 24bit, as a pixel might cross page boundaries			if (cpp == 3 && offset + 3 > PAGE_SIZE)				drm_panic_write_pixel24_xpage(vaddr, pages[page + 1],							      offset, color);			else				drm_panic_write_pixel(vaddr, offset, color, cpp);		}	}	if (vaddr)		kunmap_local(vaddr);} /* * drm_panic_fill - Fill a rectangle with a color * @sb: destination scanout buffer * @clip: destination rectangle * @color: foreground color, in destination format * * Fill a rectangle with a color, in a linear framebuffer. */static void drm_panic_fill(struct drm_scanout_buffer *sb, struct drm_rect *clip,			   u32 color){	struct iosys_map map; 	if (sb->set_pixel)		return drm_panic_fill_pixel(sb, clip, color); 	if (sb->pages)		return drm_panic_fill_page(sb->pages, sb->pitch[0], sb->format->cpp[0],					   clip, color); 	map = sb->map[0];	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); 	switch (sb->format->cpp[0]) {	case 2:		drm_draw_fill16(&map, sb->pitch[0], drm_rect_height(clip),				drm_rect_width(clip), color);	break;	case 3:		drm_draw_fill24(&map, sb->pitch[0], drm_rect_height(clip),				drm_rect_width(clip), color);	break;	case 4:		drm_draw_fill32(&map, sb->pitch[0], drm_rect_height(clip),				drm_rect_width(clip), color);	break;	default:		WARN_ONCE(1, "Can't fill with pixel width %d\n", sb->format->cpp[0]);	}} static unsigned int get_max_line_len(const struct drm_panic_line *lines, int len){	int i;	unsigned int max = 0; 	for (i = 0; i < len; i++)		max = max(lines[i].len, max);	return max;} /* * Draw a text in a rectangle on a framebuffer. The text is truncated if it overflows the rectangle */static void draw_txt_rectangle(struct drm_scanout_buffer *sb,			       const struct font_desc *font,			       const struct drm_panic_line *msg,			       unsigned int msg_lines,			       bool centered,			       struct drm_rect *clip,			       u32 color){	int i, j;	const u8 *src;	size_t font_pitch = DIV_ROUND_UP(font->width, 8);	struct drm_rect rec; 	msg_lines = min(msg_lines,  drm_rect_height(clip) / font->height);	for (i = 0; i < msg_lines; i++) {		size_t line_len = min(msg[i].len, drm_rect_width(clip) / font->width); 		rec.y1 = clip->y1 +  i * font->height;		rec.y2 = rec.y1 + font->height;		rec.x1 = clip->x1; 		if (centered)			rec.x1 += (drm_rect_width(clip) - (line_len * font->width)) / 2; 		for (j = 0; j < line_len; j++) {			src = font_data_glyph_buf(font->data, font->width, font->height,						  (unsigned char)msg[i].txt[j]);			rec.x2 = rec.x1 + font->width;			if (src)				drm_panic_blit(sb, &rec, src, font_pitch, 1, color);			rec.x1 += font->width;		}	}} static void drm_panic_logo_rect(struct drm_rect *rect, const struct font_desc *font){	if (logo_mono) {		drm_rect_init(rect, 0, 0, logo_mono->width, logo_mono->height);	} else {		int logo_width = get_max_line_len(logo_ascii, logo_ascii_lines) * font->width; 		drm_rect_init(rect, 0, 0, logo_width, logo_ascii_lines * font->height);	}} static void drm_panic_logo_draw(struct drm_scanout_buffer *sb, struct drm_rect *rect,				const struct font_desc *font, u32 fg_color){	if (rect->x2 > sb->width || rect->y2 > sb->height)		return; 	if (logo_mono)		drm_panic_blit(sb, rect, logo_mono->data,			       DIV_ROUND_UP(drm_rect_width(rect), 8), 1, fg_color);	else		draw_txt_rectangle(sb, font, logo_ascii, logo_ascii_lines, false, rect,				   fg_color);} static void draw_panic_screen_user(struct drm_scanout_buffer *sb){	u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR,						    sb->format->format);	u32 bg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_BACKGROUND_COLOR,						    sb->format->format);	const struct font_desc *font = get_default_font(sb->width, sb->height, NULL, NULL);	struct drm_rect r_screen, r_logo, r_msg;	unsigned int msg_width, msg_height; 	if (!font)		return; 	r_screen = DRM_RECT_INIT(0, 0, sb->width, sb->height);	drm_panic_logo_rect(&r_logo, font); 	msg_width = min(get_max_line_len(panic_msg, panic_msg_lines) * font->width, sb->width);	msg_height = min(panic_msg_lines * font->height, sb->height);	r_msg = DRM_RECT_INIT(0, 0, msg_width, msg_height); 

Lines 1–500 of 1095.