linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages
@ 2025-04-07 13:42 Jocelyn Falempe
  2025-04-07 13:42 ` [PATCH v3 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-04-07 13:42 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Ryosuke Yasuoka, Javier Martinez Canillas,
	Wei Yang, Andrew Morton, David Hildenbrand, John Ogness,
	Thomas Gleixner, linux-mm, dri-devel, linux-kernel
  Cc: 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.
As kmap_local_page() is not safe to call from a panic handler,
introduce a kmap_local_page_try_from_panic() that will avoid unsafe
operations.

[1] https://lore.kernel.org/dri-devel/20250305152555.318159-1-ryasuoka@redhat.com/

v2:
 * Add kmap_local_page_try_from_panic() (Simona Vetter)
 * Correctly handle the case if kmap_local_page_try_from_panic()
   returns NULL
 * Check that the current page is not NULL before trying to map it.
 * Add a comment in struct drm_scanout_buffer, that the array of
   pages shouldn't be allocated in the get_scanout_buffer() callback.

v3:
 * Replace DRM_WARN_ONCE with pr_debug_once (Simona Vetter)
 * Add a comment in kmap_local_page_try_from_panic() (Thomas Gleixner)

Jocelyn Falempe (2):
  mm/kmap: Add kmap_local_page_try_from_panic()
  drm/panic: Add support to scanout buffer as array of pages

 drivers/gpu/drm/drm_panic.c      | 142 +++++++++++++++++++++++++++++--
 include/drm/drm_panic.h          |  12 ++-
 include/linux/highmem-internal.h |  13 +++
 3 files changed, 160 insertions(+), 7 deletions(-)


base-commit: fbe43810d563a293e3de301141d33caf1f5d5c5a
-- 
2.49.0



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 1/2] mm/kmap: Add kmap_local_page_try_from_panic()
  2025-04-07 13:42 [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
@ 2025-04-07 13:42 ` Jocelyn Falempe
  2025-04-07 13:42 ` [PATCH v3 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  2025-04-10  9:06 ` [PATCH v3 0/2] " Jocelyn Falempe
  2 siblings, 0 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-04-07 13:42 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Ryosuke Yasuoka, Javier Martinez Canillas,
	Wei Yang, Andrew Morton, David Hildenbrand, John Ogness,
	Thomas Gleixner, linux-mm, dri-devel, linux-kernel
  Cc: Jocelyn Falempe, Simona Vetter

kmap_local_page() can be unsafe to call from a panic handler, if
CONFIG_HIGHMEM is set, and the page is in the highmem zone.
So add kmap_local_page_try_from_panic() to handle this case.

Suggested-by: Simona Vetter <simona.vetter@ffwll.ch>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
---

v3:
 * Add a comment in kmap_local_page_try_from_panic() (Thomas Gleixner)
 
 include/linux/highmem-internal.h | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/include/linux/highmem-internal.h b/include/linux/highmem-internal.h
index dd100e849f5e0..9a7683d79a4b1 100644
--- a/include/linux/highmem-internal.h
+++ b/include/linux/highmem-internal.h
@@ -73,6 +73,14 @@ static inline void *kmap_local_page(struct page *page)
 	return __kmap_local_page_prot(page, kmap_prot);
 }
 
+static inline void *kmap_local_page_try_from_panic(struct page *page)
+{
+	if (!PageHighMem(page))
+		return page_address(page);
+	/* If the page is in HighMem, it's not safe to kmap it.*/
+	return NULL;
+}
+
 static inline void *kmap_local_folio(struct folio *folio, size_t offset)
 {
 	struct page *page = folio_page(folio, offset / PAGE_SIZE);
@@ -180,6 +188,11 @@ static inline void *kmap_local_page(struct page *page)
 	return page_address(page);
 }
 
+static inline void *kmap_local_page_try_from_panic(struct page *page)
+{
+	return page_address(page);
+}
+
 static inline void *kmap_local_folio(struct folio *folio, size_t offset)
 {
 	return page_address(&folio->page) + offset;
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v3 2/2] drm/panic: Add support to scanout buffer as array of pages
  2025-04-07 13:42 [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  2025-04-07 13:42 ` [PATCH v3 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
@ 2025-04-07 13:42 ` Jocelyn Falempe
  2025-04-10  9:06 ` [PATCH v3 0/2] " Jocelyn Falempe
  2 siblings, 0 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-04-07 13:42 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Ryosuke Yasuoka, Javier Martinez Canillas,
	Wei Yang, Andrew Morton, David Hildenbrand, John Ogness,
	Thomas Gleixner, linux-mm, dri-devel, linux-kernel
  Cc: 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.

[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>
---

v3:
 * Replace DRM_WARN_ONCE with pr_debug_once (Simona Vetter)

 drivers/gpu/drm/drm_panic.c | 142 ++++++++++++++++++++++++++++++++++--
 include/drm/drm_panic.h     |  12 ++-
 2 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
index ab42a2b1567d0..ed27d1ef4a9ca 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,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);
 }
 
+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
  * @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)
 		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 +298,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_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
  * @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)
 		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 +836,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 f4e1fa9ae607a..a00bf3cbf62f1 100644
--- a/include/drm/drm_panic.h
+++ b/include/drm/drm_panic.h
@@ -39,6 +39,16 @@ 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_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.
 	 */
@@ -57,7 +67,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);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages
  2025-04-07 13:42 [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  2025-04-07 13:42 ` [PATCH v3 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
  2025-04-07 13:42 ` [PATCH v3 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
@ 2025-04-10  9:06 ` Jocelyn Falempe
  2 siblings, 0 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-04-10  9:06 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Ryosuke Yasuoka, Javier Martinez Canillas,
	Wei Yang, Andrew Morton, David Hildenbrand, John Ogness,
	Thomas Gleixner, linux-mm, dri-devel, linux-kernel

On 07/04/2025 15:42, Jocelyn Falempe wrote:
> 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.
> As kmap_local_page() is not safe to call from a panic handler,
> introduce a kmap_local_page_try_from_panic() that will avoid unsafe
> operations.

I applied both patches to drm-misc/drm-misc-next.

Thanks for the reviews.

> 
> [1] https://lore.kernel.org/dri-devel/20250305152555.318159-1-ryasuoka@redhat.com/
> 
> v2:
>   * Add kmap_local_page_try_from_panic() (Simona Vetter)
>   * Correctly handle the case if kmap_local_page_try_from_panic()
>     returns NULL
>   * Check that the current page is not NULL before trying to map it.
>   * Add a comment in struct drm_scanout_buffer, that the array of
>     pages shouldn't be allocated in the get_scanout_buffer() callback.
> 
> v3:
>   * Replace DRM_WARN_ONCE with pr_debug_once (Simona Vetter)
>   * Add a comment in kmap_local_page_try_from_panic() (Thomas Gleixner)
> 
> Jocelyn Falempe (2):
>    mm/kmap: Add kmap_local_page_try_from_panic()
>    drm/panic: Add support to scanout buffer as array of pages
> 
>   drivers/gpu/drm/drm_panic.c      | 142 +++++++++++++++++++++++++++++--
>   include/drm/drm_panic.h          |  12 ++-
>   include/linux/highmem-internal.h |  13 +++
>   3 files changed, 160 insertions(+), 7 deletions(-)
> 
> 
> base-commit: fbe43810d563a293e3de301141d33caf1f5d5c5a



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-04-10  9:07 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-07 13:42 [PATCH v3 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
2025-04-07 13:42 ` [PATCH v3 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
2025-04-07 13:42 ` [PATCH v3 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
2025-04-10  9:06 ` [PATCH v3 0/2] " Jocelyn Falempe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).