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-ryasu...@redhat.com/

Signed-off-by: Jocelyn Falempe <jfale...@redhat.com>
---
 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
-- 
2.47.1

Reply via email to