mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	drm/panic: Add support to scanout buffer as array of pages
Some drivers like virtio-gpu, don't map the scanout buffer in the kernel. Calling vmap() in a panic handler is not safe, and writing an atomic_vmap() API is more complex than expected [1]. So instead, pass the array of pages of the scanout buffer to the panic handler, and map only one page at a time to draw the pixels. This is obviously slow, but acceptable for a panic handler. [1] https://lore.kernel.org/dri-devel/20250305152555.318159-1-ryasuoka@redhat.com/ Acked-by: Thomas Zimmermann <tzimmermann@suse.de> Acked-by: Simona Vetter <simona@ffwll.ch> Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com> Link: https://lore.kernel.org/r/20250407140138.162383-3-jfalempe@redhat.com
This commit is contained in:
		
							parent
							
								
									8702048bb8
								
							
						
					
					
						commit
						c9ff280879
					
				
					 2 changed files with 148 additions and 8 deletions
				
			
		|  | @ -7,6 +7,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <linux/font.h> | #include <linux/font.h> | ||||||
|  | #include <linux/highmem.h> | ||||||
| #include <linux/init.h> | #include <linux/init.h> | ||||||
| #include <linux/iosys-map.h> | #include <linux/iosys-map.h> | ||||||
| #include <linux/kdebug.h> | #include <linux/kdebug.h> | ||||||
|  | @ -154,6 +155,90 @@ static void drm_panic_blit_pixel(struct drm_scanout_buffer *sb, struct drm_rect | ||||||
| 				sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, fg_color); | 				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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  | 					drm_panic_write_pixel(vaddr, offset, fg32, cpp); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if (vaddr) | ||||||
|  | 		kunmap_local(vaddr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * drm_panic_blit - convert a monochrome image to a linear framebuffer |  * drm_panic_blit - convert a monochrome image to a linear framebuffer | ||||||
|  * @sb: destination scanout buffer |  * @sb: destination scanout buffer | ||||||
|  | @ -177,6 +262,10 @@ static void drm_panic_blit(struct drm_scanout_buffer *sb, struct drm_rect *clip, | ||||||
| 	if (sb->set_pixel) | 	if (sb->set_pixel) | ||||||
| 		return drm_panic_blit_pixel(sb, clip, sbuf8, spitch, scale, fg_color); | 		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]; | 	map = sb->map[0]; | ||||||
| 	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); | 	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); | ||||||
| 
 | 
 | ||||||
|  | @ -209,6 +298,35 @@ static void drm_panic_fill_pixel(struct drm_scanout_buffer *sb, | ||||||
| 			sb->set_pixel(sb, clip->x1 + x, clip->y1 + y, color); | 			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]); | ||||||
|  | 			} | ||||||
|  | 			drm_panic_write_pixel(vaddr, offset, color, cpp); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if (vaddr) | ||||||
|  | 		kunmap_local(vaddr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * drm_panic_fill - Fill a rectangle with a color |  * drm_panic_fill - Fill a rectangle with a color | ||||||
|  * @sb: destination scanout buffer |  * @sb: destination scanout buffer | ||||||
|  | @ -225,6 +343,10 @@ static void drm_panic_fill(struct drm_scanout_buffer *sb, struct drm_rect *clip, | ||||||
| 	if (sb->set_pixel) | 	if (sb->set_pixel) | ||||||
| 		return drm_panic_fill_pixel(sb, clip, color); | 		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]; | 	map = sb->map[0]; | ||||||
| 	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); | 	iosys_map_incr(&map, clip->y1 * sb->pitch[0] + clip->x1 * sb->format->cpp[0]); | ||||||
| 
 | 
 | ||||||
|  | @ -709,16 +831,24 @@ static void draw_panic_plane(struct drm_plane *plane, const char *description) | ||||||
| 	if (!drm_panic_trylock(plane->dev, flags)) | 	if (!drm_panic_trylock(plane->dev, flags)) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	drm_panic_set_description(description); |  | ||||||
| 
 |  | ||||||
| 	ret = plane->helper_private->get_scanout_buffer(plane, &sb); | 	ret = plane->helper_private->get_scanout_buffer(plane, &sb); | ||||||
| 
 | 
 | ||||||
| 	if (!ret && drm_panic_is_format_supported(sb.format)) { | 	if (ret || !drm_panic_is_format_supported(sb.format)) | ||||||
| 		draw_panic_dispatch(&sb); | 		goto unlock; | ||||||
| 		if (plane->helper_private->panic_flush) | 
 | ||||||
| 			plane->helper_private->panic_flush(plane); | 	/* One of these should be set, or it can't draw pixels */ | ||||||
| 	} | 	if (!sb.set_pixel && !sb.pages && iosys_map_is_null(&sb.map[0])) | ||||||
|  | 		goto unlock; | ||||||
|  | 
 | ||||||
|  | 	drm_panic_set_description(description); | ||||||
|  | 
 | ||||||
|  | 	draw_panic_dispatch(&sb); | ||||||
|  | 	if (plane->helper_private->panic_flush) | ||||||
|  | 		plane->helper_private->panic_flush(plane); | ||||||
|  | 
 | ||||||
| 	drm_panic_clear_description(); | 	drm_panic_clear_description(); | ||||||
|  | 
 | ||||||
|  | unlock: | ||||||
| 	drm_panic_unlock(plane->dev, flags); | 	drm_panic_unlock(plane->dev, flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -39,6 +39,16 @@ struct drm_scanout_buffer { | ||||||
| 	 */ | 	 */ | ||||||
| 	struct iosys_map map[DRM_FORMAT_MAX_PLANES]; | 	struct iosys_map map[DRM_FORMAT_MAX_PLANES]; | ||||||
| 
 | 
 | ||||||
|  | 	/**
 | ||||||
|  | 	 * @pages: Optional, if the scanout buffer is not mapped, set this field | ||||||
|  | 	 * to the array of pages of the scanout buffer. The panic code will use | ||||||
|  | 	 * kmap_local_page_try_from_panic() to map one page at a time to write | ||||||
|  | 	 * all the pixels. This array shouldn't be allocated from the | ||||||
|  | 	 * get_scanoutbuffer() callback. | ||||||
|  | 	 * The scanout buffer should be in linear format. | ||||||
|  | 	 */ | ||||||
|  | 	struct page **pages; | ||||||
|  | 
 | ||||||
| 	/**
 | 	/**
 | ||||||
| 	 * @width: Width of the scanout buffer, in pixels. | 	 * @width: Width of the scanout buffer, in pixels. | ||||||
| 	 */ | 	 */ | ||||||
|  | @ -57,7 +67,7 @@ struct drm_scanout_buffer { | ||||||
| 	/**
 | 	/**
 | ||||||
| 	 * @set_pixel: Optional function, to set a pixel color on the | 	 * @set_pixel: Optional function, to set a pixel color on the | ||||||
| 	 * framebuffer. It allows to handle special tiling format inside the | 	 * framebuffer. It allows to handle special tiling format inside the | ||||||
| 	 * driver. | 	 * driver. It takes precedence over the @map and @pages fields. | ||||||
| 	 */ | 	 */ | ||||||
| 	void (*set_pixel)(struct drm_scanout_buffer *sb, unsigned int x, | 	void (*set_pixel)(struct drm_scanout_buffer *sb, unsigned int x, | ||||||
| 			  unsigned int y, u32 color); | 			  unsigned int y, u32 color); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Jocelyn Falempe
						Jocelyn Falempe