All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] drm/panic: Add support to scanout buffer as array of pages
@ 2025-03-21 11:16 Jocelyn Falempe
  2025-03-21 11:16 ` [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
  2025-03-21 11:16 ` [PATCH v2 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  0 siblings, 2 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-03-21 11:16 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.

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 |  12 +++
 3 files changed, 159 insertions(+), 7 deletions(-)


base-commit: f24d1d4a7a425e67551ca8d86a89df7102766ac9
-- 
2.47.1


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

* [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic()
  2025-03-21 11:16 [PATCH v2 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
@ 2025-03-21 11:16 ` Jocelyn Falempe
  2025-04-04  7:33   ` Thomas Gleixner
  2025-03-21 11:16 ` [PATCH v2 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  1 sibling, 1 reply; 4+ messages in thread
From: Jocelyn Falempe @ 2025-03-21 11:16 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>
Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
---
 include/linux/highmem-internal.h | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/include/linux/highmem-internal.h b/include/linux/highmem-internal.h
index dd100e849f5e0..5d089b0ca56de 100644
--- a/include/linux/highmem-internal.h
+++ b/include/linux/highmem-internal.h
@@ -73,6 +73,13 @@ 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);
+	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 +187,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.47.1


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

* [PATCH v2 2/2] drm/panic: Add support to scanout buffer as array of pages
  2025-03-21 11:16 [PATCH v2 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
  2025-03-21 11:16 ` [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
@ 2025-03-21 11:16 ` Jocelyn Falempe
  1 sibling, 0 replies; 4+ messages in thread
From: Jocelyn Falempe @ 2025-03-21 11:16 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/

Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 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..3f22ea2f61c73 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:
+		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_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.47.1


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

* Re: [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic()
  2025-03-21 11:16 ` [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
@ 2025-04-04  7:33   ` Thomas Gleixner
  0 siblings, 0 replies; 4+ messages in thread
From: Thomas Gleixner @ 2025-04-04  7:33 UTC (permalink / raw)
  To: Jocelyn Falempe, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Ryosuke Yasuoka,
	Javier Martinez Canillas, Wei Yang, Andrew Morton,
	David Hildenbrand, John Ogness, linux-mm, dri-devel, linux-kernel
  Cc: Jocelyn Falempe, Simona Vetter

On Fri, Mar 21 2025 at 12:16, Jocelyn Falempe wrote:
> 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.

I think this is a reasonable solution and the highmem case can suffer
from not getting the reliable panic output.

> Suggested-by: Simona Vetter <simona.vetter@ffwll.ch>
> Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
> ---
>  include/linux/highmem-internal.h | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
>
> diff --git a/include/linux/highmem-internal.h b/include/linux/highmem-internal.h
> index dd100e849f5e0..5d089b0ca56de 100644
> --- a/include/linux/highmem-internal.h
> +++ b/include/linux/highmem-internal.h
> @@ -73,6 +73,13 @@ 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);
> +	return NULL;

A comment explaining the reason why the highmem mapping cannot work here
would be appreciated.

Aside of that:

      Reviewed-by: Thomas Gleixner <tglx@linutronix.de>

Thanks,

        tglx

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

end of thread, other threads:[~2025-04-04  7:42 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-21 11:16 [PATCH v2 0/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe
2025-03-21 11:16 ` [PATCH v2 1/2] mm/kmap: Add kmap_local_page_try_from_panic() Jocelyn Falempe
2025-04-04  7:33   ` Thomas Gleixner
2025-03-21 11:16 ` [PATCH v2 2/2] drm/panic: Add support to scanout buffer as array of pages Jocelyn Falempe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.