[PATCH] drm/panic: Add support to scanout buffer as array of pages
Thomas Zimmermann
tzimmermann at suse.de
Mon Mar 17 13:47:50 UTC 2025
Hi
Am 13.03.25 um 10:42 schrieb Jocelyn Falempe:
> 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.
It seems this could still be optimized by writing multiple pixels at
once if they are located on the same pages. Although it's not really
necessary.
>
> [1] https://lore.kernel.org/dri-devel/20250305152555.318159-1-ryasuoka@redhat.com/
>
> Signed-off-by: Jocelyn Falempe <jfalempe at redhat.com>
Acked-by: Thomas Zimmermann <tzimmermann at suse.de>
Best regards
Thomas
> ---
> drivers/gpu/drm/drm_panic.c | 139 ++++++++++++++++++++++++++++++++++--
> include/drm/drm_panic.h | 10 ++-
> 2 files changed, 142 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
> index ab42a2b1567d..e10ab8fe847c 100644
> --- a/drivers/gpu/drm/drm_panic.c
> +++ b/drivers/gpu/drm/drm_panic.c
> @@ -7,6 +7,7 @@
> */
>
> #include <linux/font.h>
> +#include <linux/highmem.h>
> #include <linux/init.h>
> #include <linux/iosys-map.h>
> #include <linux/kdebug.h>
> @@ -154,6 +155,87 @@ 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);
> }
>
> +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:
> + DRM_WARN_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() to map the page, and write the pixel.
> + * Tries 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 (vaddr)
> + kunmap_local(vaddr);
> + page = new_page;
> + vaddr = kmap_local_page(pages[page]);
> + }
> + 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
> @@ -177,6 +259,10 @@ static void drm_panic_blit(struct drm_scanout_buffer *sb, struct drm_rect *clip,
> 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]);
>
> @@ -209,6 +295,35 @@ static void drm_panic_fill_pixel(struct drm_scanout_buffer *sb,
> 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(pages[page]);
> + }
> + 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
> @@ -225,6 +340,10 @@ static void drm_panic_fill(struct drm_scanout_buffer *sb, struct drm_rect *clip,
> 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]);
>
> @@ -714,16 +833,24 @@ static void draw_panic_plane(struct drm_plane *plane, const char *description)
> if (!drm_panic_trylock(plane->dev, flags))
> return;
>
> + ret = plane->helper_private->get_scanout_buffer(plane, &sb);
> +
> + if (ret || !drm_panic_is_format_supported(sb.format))
> + goto unlock;
> +
> + /* 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);
>
> - ret = plane->helper_private->get_scanout_buffer(plane, &sb);
> + draw_panic_dispatch(&sb);
> + if (plane->helper_private->panic_flush)
> + plane->helper_private->panic_flush(plane);
>
> - if (!ret && drm_panic_is_format_supported(sb.format)) {
> - draw_panic_dispatch(&sb);
> - if (plane->helper_private->panic_flush)
> - plane->helper_private->panic_flush(plane);
> - }
> drm_panic_clear_description();
> +
> +unlock:
> drm_panic_unlock(plane->dev, flags);
> }
>
> diff --git a/include/drm/drm_panic.h b/include/drm/drm_panic.h
> index f4e1fa9ae607..8b91a13347b9 100644
> --- a/include/drm/drm_panic.h
> +++ b/include/drm/drm_panic.h
> @@ -39,6 +39,14 @@ struct drm_scanout_buffer {
> */
> 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() to map one page at a time to write all the pixels.
> + * The scanout buffer should be in linear format.
> + */
> + struct page **pages;
> +
> /**
> * @width: Width of the scanout buffer, in pixels.
> */
> @@ -57,7 +65,7 @@ struct drm_scanout_buffer {
> /**
> * @set_pixel: Optional function, to set a pixel color on 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,
> unsigned int y, u32 color);
>
> base-commit: 9e75b6ef407fee5d4ed8021cd7ddd9d6a8f7b0e8
--
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstrasse 146, 90461 Nuernberg, Germany
GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman
HRB 36809 (AG Nuernberg)
More information about the dri-devel
mailing list