[RFC 2/3] drm: Add panic handling

Noralf Trønnes noralf at tronnes.org
Thu Aug 11 20:46:53 UTC 2016


Den 10.08.2016 11:15, skrev Daniel Vetter:
> On Tue, Aug 09, 2016 at 07:45:41PM +0200, Noralf Trønnes wrote:
>> This adds support for outputting kernel messages on panic().
>> The drivers that supports it, provides a framebuffer that the
>> messages can be rendered on.
>>
>> Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
> Thinking about how we should implement this in a full-blown driver, I
> think we will need a bit more than this. Here's the additional
> requirements I've come up that a driver more complex than sdrm would need
> for reliable panic handling:
>
> - Multiple outputs, with multiple framebuffer (or one framebuffer and
>    multiple offsets within). And since one display might be dead we should
>    try really hard to show the oops on all of them. Consequence: I think we
>    need to also loop over all drm_crtc in a drm_device, to be able to
>    support them all.

How about this:

/*
  * The panic() function makes sure that only one CPU is allowed to run it's
  * code. So this handler is only called once.
  *
  * Prior to calling the panic handlers, panic() calls smp_send_stop(). If
  * that went well, there's only one CPU running, but this is no guarantee.
  */
static int drm_panic_handler(struct notifier_block *this, unsigned long ev,
                              void *ptr)
{
<declarations>

         drm_for_each_device(drm, id) {
                 if (!drm->driver ||
                     !(drm->driver->driver_features & DRIVER_ATOMIC))
                         continue;

                 drm_for_each_crtc(crtc, drm) {
                         crtc_locked = ww_mutex_trylock(&crtc->mutex.mutex);

                         if (!crtc->enabled || !crtc->primary)
                                 goto crtc_unlock;

                         if (!crtc->state || !crtc->state->active ||
                             !crtc->state->state)
                                 goto crtc_unlock;

                         state = crtc->state->state;
                         plane = crtc->primary;
                         plane_locked = 
ww_mutex_trylock(&plane->mutex.mutex);

                         plane_state = 
drm_atomic_get_existing_plane_state(state, plane);
                         if (!plane_state || !plane_state->visible)
                                 goto plane_unlock;

                         fb = plane->fb;

                         if (!fb || !fb->funcs || !fb->funcs->panic_vmap)
                                 goto plane_unlock;

                         /* only 8-bit wide fonts are supported */
                         font = get_default_font(fb->width, fb->height, 
BIT(7), -1);
                         if (!font)
                                 goto plane_unlock;

                         vmap = fb->funcs->panic_vmap(fb);
                         if (!vmap)
                                 goto plane_unlock;

                 /*
                  * How do I find the actual width/height from the plane 
state
                  * as you mention further down?
                  */
                         width = ??
                         height = ??

                         pfb = &drm_panic_fbs[fbs++];
                         pfb->fb = fb;
                         pfb->vmap = vmap;
                         pfb->font = font;
                         pfb->cols = width / font->width;
                         pfb->rows = height / font->height;

                 /*
                  * how to unlock?
                  * ww_mutex_unlock() doc says:
                  * This function must not be used in interrupt context
                  */
                 plane_unlock:
                         if (plane_locked)
                                 somehow_unlock();
                 crtc_unlock:
                         if (crtc_locked)
                                 somehow_unlock();
                 }
         }

         drm_panic_active = true;
}



> - With desktop gpus you pretty much always end up with a tiled
>    framebuffer. And from an oops context it's going to be impossible to
>    detile that quickly. I think for this we need a helper to draw x/y
>    pixels (it's going to be dead slow, but meh), with a default
>    implementation which assumes linear layout. Drivers could then frob x/y
>    coordinates first in their own logic and call into that function.
>
> - There's a good chance you're showing a video on a yuv buffer
>    full-screen. If we don't bother too much with what it looks like, but
>    only care about a foreground/background color then it's also easy to
>    implement a draw_xy_pixel for these framebuffers. That means though that
>    you can't clear things with memset, but that you need to call the
>    interface func for each pixel. Yes this is going to be dead slow, but
>    who cares as long as the oops eventually shows up ;-)

Let's give it a try, the yuv part is just guesswork:

void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap,
                        int x, int y, bool foreground)
{
         void *dst;

         dst = vmap + fb->offsets[0] + (y * fb->pitches[0]);

         switch (fb->pixel_format & ~DRM_FORMAT_BIG_ENDIAN) {

         case DRM_FORMAT_C8:

         case DRM_FORMAT_RGB332:
         case DRM_FORMAT_BGR233:

         case DRM_FORMAT_NV12:
         case DRM_FORMAT_NV21:
         case DRM_FORMAT_NV16:
         case DRM_FORMAT_NV61:
         case DRM_FORMAT_NV24:
         case DRM_FORMAT_NV42:

         case DRM_FORMAT_YUV410:
         case DRM_FORMAT_YVU410:
         case DRM_FORMAT_YUV411:
         case DRM_FORMAT_YVU411:
         case DRM_FORMAT_YUV420:
         case DRM_FORMAT_YVU420:
         case DRM_FORMAT_YUV422:
         case DRM_FORMAT_YVU422:
         case DRM_FORMAT_YUV444:
         case DRM_FORMAT_YVU444:
                 dst += x;
                 *(u8 *)dst = foreground ? 0xff : 0x00;
                 break;

         case DRM_FORMAT_YUYV:
         case DRM_FORMAT_YVYU:
                 dst += x * sizeof(u32);
                 put_unaligned(foreground ? 0xff00ff00 : 0x00000000, 
(u32 *)dst);
                 break;

         case DRM_FORMAT_UYVY:
         case DRM_FORMAT_VYUY:
                 dst += x * sizeof(u32);
                 put_unaligned(foreground ? 0x00ff00ff : 0x00000000, 
(u32 *)dst);
                 break;

         case DRM_FORMAT_XRGB8888:
         case DRM_FORMAT_ARGB8888:
         case DRM_FORMAT_XBGR8888:
         case DRM_FORMAT_ABGR8888:
                 dst += x * sizeof(u32);
                 put_unaligned(foreground ? 0x00ffffff : 0x00000000, 
(u32 *)dst);
                 break;

         /* and then the other rgb formats */
         }
}

> - The framebuffer size doesn't necessarily match the size of the visible
>    part. Probably best if we dig this out from the plane_state directly
>    (less fragile code in drivers). Relying on drm_plane_state means this
>    will only work on atomic drivers (in legacy drivers this information is
>    hidden in driver-private corners), but I think that's totally ok.
>
> - We need locking. One of the biggest problems with the old oops handling
>    was that it was very good at trampling over driver state, causing more
>    (unrelated) oopses in kms code and making sure the original oops was no
>    longer visible. I think the shared code must take care of all the
>    locking needs to avoid fragile code in drivers. ww_mutex_trylock on the
>    drm_crtc and drm_plane should be enough (we need both for drivers where
>    planes can be reassigned between drivers).

Is this for the case where panic() fails to stop the other cpus?
Or can preemption happen even though panic() calls local_irq_disable()?

> - Multiple planes which might occlude the primary plane: I think we should
>    just bother with the primary plane, and maybe give drivers a
>    panic_commit hook where they could try to disable any additional planes.
>    But that's not something we need in the first version here at all.
>
> - I think some helpers for creating the vmap would be nice. Should be
>    simple for cma-backed framebuffers, and also simple if you use a normal
>    shmem backed gem buffer. For cma probably best if drivers don't even
>    need to bother with this.

I don't know about shmem, but cma could look like this:

/**
  * drm_fb_cma_panic_vmap - Helper function for the
  *                         &drm_framebuffer_funcs->panic_vmap callback
  * @fb: DRM framebuffer
  *
  * This function is used in a panic() situation to get to the virtual 
address
  * of the backing buffer for rendering kernel messages.
  * There's no need to set the &drm_framebuffer_funcs->panic_draw_xy 
callback,
  * since the default implementation will suffice.
  *
  * Note: A PRIME imported buffer will _not_ have it's vaddr set.
  *
  * Returns:
  * The virtual address of the backing object, or NULL.
  */
void *drm_fb_cma_panic_vmap(struct drm_framebuffer *fb)
{
         struct drm_fb_cma *fb_cma = to_fb_cma(fb);
         struct drm_gem_cma_object *cma_obj = fb_cma->obj[0];

         return cma_obj ? cma_obj->vaddr : NULL;
}
EXPORT_SYMBOL(drm_fb_cma_panic_vmap);

  static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
         .destroy        = drm_fb_cma_destroy,
         .create_handle  = drm_fb_cma_create_handle,
+       .panic_vmap     = drm_fb_cma_panic_vmap,
  };

> - For driver convenience I think we could check a few things about
>    visibility of each plane (e.g. crtc_state->active), and skip if there's
>    no chance it can be seen. Doing less means less chances to blow up, and
>    higher chances that the one screen which is on actually ends up with the
>    oops on it.
>
> - Minimal cleanup. I think we can just leak the vmapping, no harm in that.
>    But the kms locks need to be dropped again. Dropping them again might
>    increase the odds that the system limps along long enough for logs to
>    hit the disks.
>
> Taking this all together, I think the driver interface should be
> restructured a bit, and that most of the handling should be on the
> drm_framebuffer directly:
>
> struct drm_framebuffer_funcs {
> 	/* For vmapping the selected framebuffer in a panic context. Must
> 	 * be super careful about locking (only trylocking allowed), can
> 	 * return NULL if it didn't work out. The return value is an
> 	 * opaque cookie which is passed to @panic_draw_xy, it can be
> 	 * anything: vmap area, structure with more details, just a few
> 	 * flags, ...
> 	 */
> 	void *(panic_vmap)(struct drm_framebuffer *fb);
>
> 	/* For drawing pixels onto a framebuffer mapping with @panic_vmap.
> 	 * This is optional, the default implementation assumes that vmap
> 	 * points at a linear mapping of the framebuffer.
> 	 */
> 	void (panic_draw_xy)(struct drm_framebuffer *fb, void *vmap,
> 			     int x, int y, bool foreground);
> };
>
> Ofc comments need to be fleshed out some more, but the idea is that all
> the fb selection and lookup is handled in shared code (and with proper
> locking, but only for atomic drivers).
>
> Thoughts?

I like this, it appears to be very flexible.
Will be interesting to see how fast/slow this is, in the linear case,
compared to what I did.


Noralf.

> -Daniel
>
>> ---
>>   drivers/gpu/drm/Makefile       |   2 +-
>>   drivers/gpu/drm/drm_drv.c      |   3 +
>>   drivers/gpu/drm/drm_internal.h |   4 +
>>   drivers/gpu/drm/drm_panic.c    | 606 +++++++++++++++++++++++++++++++++++++++++
>>   include/drm/drmP.h             |  22 ++
>>   5 files changed, 636 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/gpu/drm/drm_panic.c
>>
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index eba32ad..ff04e41 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -12,7 +12,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>>   		drm_info.o drm_debugfs.o drm_encoder_slave.o \
>>   		drm_trace_points.o drm_global.o drm_prime.o \
>>   		drm_rect.o drm_vma_manager.o drm_flip_work.o \
>> -		drm_modeset_lock.o drm_atomic.o drm_bridge.o
>> +		drm_modeset_lock.o drm_atomic.o drm_bridge.o drm_panic.o
>>   
>>   drm-$(CONFIG_COMPAT) += drm_ioc32.o
>>   drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
>> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
>> index 3b14366..457ee91 100644
>> --- a/drivers/gpu/drm/drm_drv.c
>> +++ b/drivers/gpu/drm/drm_drv.c
>> @@ -861,6 +861,8 @@ static int __init drm_core_init(void)
>>   		goto err_p3;
>>   	}
>>   
>> +	drm_panic_init();
>> +
>>   	DRM_INFO("Initialized %s %d.%d.%d %s\n",
>>   		 CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
>>   	return 0;
>> @@ -876,6 +878,7 @@ err_p1:
>>   
>>   static void __exit drm_core_exit(void)
>>   {
>> +	drm_panic_exit();
>>   	debugfs_remove(drm_debugfs_root);
>>   	drm_sysfs_destroy();
>>   
>> diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
>> index b86dc9b..7463d9d 100644
>> --- a/drivers/gpu/drm/drm_internal.h
>> +++ b/drivers/gpu/drm/drm_internal.h
>> @@ -90,6 +90,10 @@ int drm_gem_open_ioctl(struct drm_device *dev, void *data,
>>   void drm_gem_open(struct drm_device *dev, struct drm_file *file_private);
>>   void drm_gem_release(struct drm_device *dev, struct drm_file *file_private);
>>   
>> +/* drm_panic.c */
>> +void drm_panic_init(void);
>> +void drm_panic_exit(void);
>> +
>>   /* drm_debugfs.c */
>>   #if defined(CONFIG_DEBUG_FS)
>>   int drm_debugfs_init(struct drm_minor *minor, int minor_id,
>> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
>> new file mode 100644
>> index 0000000..e185c9d
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_panic.c
>> @@ -0,0 +1,606 @@
>> +/*
>> + * Copyright 2016 Noralf Trønnes
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +
>> +#include <asm/unaligned.h>
>> +#include <drm/drmP.h>
>> +#include <linux/console.h>
>> +#include <linux/debugfs.h>
>> +#include <linux/font.h>
>> +#include <linux/kernel.h>
>> +#include <linux/seq_file.h>
>> +#include <linux/slab.h>
>> +#include <linux/uaccess.h>
>> +
>> +struct drm_panic_fb {
>> +	struct drm_framebuffer *fb;
>> +	void *vmem;
>> +	const struct font_desc *font;
>> +	unsigned int cols;
>> +	unsigned int rows;
>> +	unsigned int xpos;
>> +	unsigned int ypos;
>> +};
>> +
>> +#define DRM_PANIC_MAX_FBS	64
>> +static struct drm_panic_fb drm_panic_fbs[DRM_PANIC_MAX_FBS];
>> +
>> +#define DRM_PANIC_MAX_KMSGS	SZ_4K
>> +static char *drm_panic_kmsgs;
>> +static size_t drm_panic_kmsgs_pos;
>> +
>> +static bool drm_panic_active;
>> +
>> +static void drm_panic_log(const char *fmt, ...);
>> +
>> +static inline void drm_panic_draw_pixel(u8 *dst, u32 pixel_format, bool val)
>> +{
>> +	switch (pixel_format & ~DRM_FORMAT_BIG_ENDIAN) {
>> +
>> +	case DRM_FORMAT_C8:
>> +	case DRM_FORMAT_RGB332:
>> +	case DRM_FORMAT_BGR233:
>> +		*dst = val ? 0xff : 0x00;
>> +		break;
>> +
>> +	case DRM_FORMAT_XRGB4444:
>> +	case DRM_FORMAT_ARGB4444:
>> +	case DRM_FORMAT_XBGR4444:
>> +	case DRM_FORMAT_ABGR4444:
>> +		put_unaligned(val ? 0x0fff : 0x0000, (u16 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGBX4444:
>> +	case DRM_FORMAT_RGBA4444:
>> +	case DRM_FORMAT_BGRX4444:
>> +	case DRM_FORMAT_BGRA4444:
>> +		put_unaligned(val ? 0xfff0 : 0x0000, (u16 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_XRGB1555:
>> +	case DRM_FORMAT_ARGB1555:
>> +	case DRM_FORMAT_XBGR1555:
>> +	case DRM_FORMAT_ABGR1555:
>> +		put_unaligned(val ? 0x7fff : 0x0000, (u16 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGBX5551:
>> +	case DRM_FORMAT_RGBA5551:
>> +	case DRM_FORMAT_BGRX5551:
>> +	case DRM_FORMAT_BGRA5551:
>> +		put_unaligned(val ? 0xfffe : 0x0000, (u16 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGB565:
>> +	case DRM_FORMAT_BGR565:
>> +		put_unaligned(val ? 0xffff : 0x0000, (u16 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGB888:
>> +	case DRM_FORMAT_BGR888:
>> +		dst[0] = val ? 0xff : 0x00;
>> +		dst[1] = val ? 0xff : 0x00;
>> +		dst[2] = val ? 0xff : 0x00;
>> +		break;
>> +
>> +	case DRM_FORMAT_XRGB8888:
>> +	case DRM_FORMAT_ARGB8888:
>> +	case DRM_FORMAT_XBGR8888:
>> +	case DRM_FORMAT_ABGR8888:
>> +		put_unaligned(val ? 0x00ffffff : 0x00000000, (u32 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGBX8888:
>> +	case DRM_FORMAT_RGBA8888:
>> +	case DRM_FORMAT_BGRX8888:
>> +	case DRM_FORMAT_BGRA8888:
>> +		put_unaligned(val ? 0xffffff00 : 0x00000000, (u32 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_XRGB2101010:
>> +	case DRM_FORMAT_ARGB2101010:
>> +	case DRM_FORMAT_XBGR2101010:
>> +	case DRM_FORMAT_ABGR2101010:
>> +		put_unaligned(val ? 0x3fffffff : 0x00000000, (u32 *)dst);
>> +		break;
>> +
>> +	case DRM_FORMAT_RGBX1010102:
>> +	case DRM_FORMAT_RGBA1010102:
>> +	case DRM_FORMAT_BGRX1010102:
>> +	case DRM_FORMAT_BGRA1010102:
>> +		put_unaligned(val ? 0xfffffffc : 0x00000000, (u32 *)dst);
>> +		break;
>> +	}
>> +}
>> +
>> +static void drm_panic_render(struct drm_panic_fb *pfb,
>> +			     const char *text, unsigned int len)
>> +{
>> +	const struct font_desc *font = pfb->font;
>> +	unsigned int pix_depth, pix_bpp, cpp;
>> +	unsigned int col = pfb->xpos;
>> +	unsigned int row = pfb->ypos;
>> +	unsigned int i, h, w;
>> +	void *dst, *pos;
>> +	u8 fontline;
>> +
>> +	if ((row + 1) * font->height > pfb->fb->height)
>> +		return;
>> +
>> +	if ((col + len) * font->width > pfb->fb->width)
>> +		return;
>> +
>> +	drm_fb_get_bpp_depth(pfb->fb->pixel_format, &pix_depth, &pix_bpp);
>> +	cpp = DIV_ROUND_UP(pix_bpp, 8);
>> +
>> +	/* TODO: should fb->offsets[0] be added here? */
>> +	dst = pfb->vmem + (row * font->height * pfb->fb->pitches[0]) +
>> +	      (col * font->width * cpp);
>> +
>> +	for (h = 0; h < font->height; h++) {
>> +		pos = dst;
>> +
>> +		for (i = 0; i < len; i++) {
>> +			fontline = *(u8 *)(font->data + text[i] * font->height + h);
>> +
>> +			for (w = 0; w < font->width; w++) {
>> +				drm_panic_draw_pixel(pos, pfb->fb->pixel_format,
>> +						     fontline & BIT(7 - w));
>> +				pos += cpp;
>> +			}
>> +		}
>> +
>> +		dst += pfb->fb->pitches[0];
>> +	}
>> +}
>> +
>> +static void drm_panic_scroll_up(struct drm_panic_fb *pfb)
>> +{
>> +	void *src = pfb->vmem + (pfb->font->height * pfb->fb->pitches[0]);
>> +	size_t len = (pfb->fb->height - pfb->font->height) *
>> +		     pfb->fb->pitches[0];
>> +
>> +	drm_panic_log("%s\n", __func__);
>> +
>> +	memmove(pfb->vmem, src, len);
>> +	memset(pfb->vmem + len, 0, pfb->font->height * pfb->fb->pitches[0]);
>> +}
>> +
>> +static void drm_panic_clear_screen(struct drm_panic_fb *pfb)
>> +{
>> +	memset(pfb->vmem, 0, pfb->fb->height * pfb->fb->pitches[0]);
>> +}
>> +
>> +static void drm_panic_log_msg(char *pre, const char *str, unsigned int len)
>> +{
>> +	char buf[512];
>> +
>> +	if (len > 510)
>> +		len = 510;
>> +
>> +	memcpy(buf, str, len);
>> +	buf[len] = '\n';
>> +	buf[len + 1] = '\0';
>> +
>> +	drm_panic_log("%s%s", pre, buf);
>> +}
>> +
>> +static void drm_panic_putcs_no_lf(struct drm_panic_fb *pfb,
>> +				  const char *str, unsigned int len)
>> +{
>> +	drm_panic_log("%s(len=%u) x=%u, y=%u\n", __func__, len,
>> +			pfb->xpos, pfb->ypos);
>> +
>> +	if (len <= 0)
>> +		return;
>> +
>> +	drm_panic_log_msg("", str, len);
>> +
>> +	drm_panic_render(pfb, str, len);
>> +
>> +}
>> +
>> +static void drm_panic_putcs(struct drm_panic_fb *pfb,
>> +			    const char *str, unsigned int num)
>> +{
>> +	unsigned int slen;
>> +	int len = num;
>> +	char *lf;
>> +
>> +	drm_panic_log("%s(num=%u)\n", __func__, num);
>> +
>> +	while (len > 0) {
>> +
>> +		if (pfb->ypos == pfb->rows) {
>> +			pfb->ypos--;
>> +			drm_panic_scroll_up(pfb);
>> +		}
>> +
>> +		lf = strpbrk(str, "\n");
>> +		if (lf)
>> +			slen = lf - str;
>> +		else
>> +			slen = len;
>> +
>> +		if (pfb->xpos + slen > pfb->cols)
>> +			slen = pfb->cols - pfb->xpos;
>> +
>> +		drm_panic_putcs_no_lf(pfb, str, slen);
>> +
>> +		len -= slen;
>> +		str += slen;
>> +		pfb->xpos += slen;
>> +
>> +		if (lf) {
>> +			str++;
>> +			len--;
>> +			pfb->xpos = 0;
>> +			pfb->ypos++;
>> +		}
>> +	}
>> +}
>> +
>> +static void drm_panic_write(const char *str, unsigned int num)
>> +{
>> +	unsigned int i;
>> +
>> +	if (!num)
>> +		return;
>> +
>> +	drm_panic_log("%s(num=%u)\n", __func__, num);
>> +
>> +	for (i = 0; i < DRM_PANIC_MAX_FBS; i++) {
>> +		if (!drm_panic_fbs[i].fb)
>> +			break;
>> +		drm_panic_putcs(&drm_panic_fbs[i], str, num);
>> +	}
>> +}
>> +
>> +/* this one is serialized by console_lock() */
>> +static void drm_panic_console_write(struct console *con,
>> +				    const char *str, unsigned int num)
>> +{
>> +	unsigned int i;
>> +
>> +	drm_panic_log_msg("->", str, num);
>> +
>> +	/* Buffer up messages to be replayed on panic */
>> +	if (!drm_panic_active) {
>> +		for (i = 0; i < num; i++) {
>> +			drm_panic_kmsgs[drm_panic_kmsgs_pos++] = *str++;
>> +			if (drm_panic_kmsgs_pos == DRM_PANIC_MAX_KMSGS)
>> +				drm_panic_kmsgs_pos = 0;
>> +		}
>> +		return;
>> +	}
>> +
>> +	drm_panic_write(str, num);
>> +}
>> +
>> +static struct console drm_panic_console = {
>> +	.name =         "drmpanic",
>> +	.write =        drm_panic_console_write,
>> +	.flags =        CON_PRINTBUFFER | CON_ENABLED,
>> +	.index =        0,
>> +};
>> +
>> +/*
>> + * The panic() function makes sure that only one CPU is allowed to run it's
>> + * code. So when this handler is called, we're alone. No racing with
>> + * console.write() is possible.
>> + */
>> +static int drm_panic_handler(struct notifier_block *this, unsigned long ev,
>> +			     void *ptr)
>> +{
>> +	const struct font_desc *font;
>> +	struct drm_framebuffer *fb;
>> +	struct drm_panic_fb *pfb;
>> +	struct drm_minor *minor;
>> +	unsigned int fbs = 0;
>> +	void *vmem;
>> +	int i;
>> +
>> +	drm_panic_log("%s\n", __func__);
>> +
>> +	drm_panic_active = true;
>> +
>> +	drm_minor_for_each(minor, DRM_MINOR_LEGACY, i) {
>> +		drm_panic_log("Found minor=%d\n", minor->index);
>> +		if (!minor->dev || !minor->dev->driver ||
>> +		    !minor->dev->driver->panic) {
>> +			drm_panic_log("Skipping: No panic handler\n");
>> +			continue;
>> +		}
>> +
>> +		fb = minor->dev->driver->panic(minor->dev, &vmem);
>> +		if (!fb) {
>> +			drm_panic_log("Skipping: Driver returned NULL\n");
>> +			continue;
>> +		}
>> +
>> +		if (!fb || !vmem || fb->dev != minor->dev || !fb->pitches[0]) {
>> +			drm_panic_log("Skipping: Failed check\n");
>> +			continue;
>> +		}
>> +
>> +		/* only 8-bit wide fonts are supported */
>> +		font = get_default_font(fb->width, fb->height, BIT(7), -1);
>> +		if (!font) {
>> +			drm_panic_log("Skipping: No font available\n");
>> +			continue;
>> +		}
>> +
>> +		pfb = &drm_panic_fbs[fbs++];
>> +
>> +		pfb->fb = fb;
>> +		pfb->vmem = vmem;
>> +		pfb->font = font;
>> +		pfb->cols = fb->width / font->width;
>> +		pfb->rows = fb->height / font->height;
>> +
>> +		drm_panic_clear_screen(pfb);
>> +
>> +		drm_panic_log("    %ux%u -> %ux%u, %s, %s\n", fb->width,
>> +				fb->height, pfb->cols, pfb->rows, font->name,
>> +				drm_get_format_name(fb->pixel_format));
>> +	}
>> +
>> +	if (drm_panic_kmsgs[0]) {
>> +		/* safeguard in case we interrupted drm_panic_console_write */
>> +		if (drm_panic_kmsgs_pos >= DRM_PANIC_MAX_KMSGS)
>> +			drm_panic_kmsgs_pos = 0;
>> +
>> +		drm_panic_write(&drm_panic_kmsgs[drm_panic_kmsgs_pos],
>> +				DRM_PANIC_MAX_KMSGS - drm_panic_kmsgs_pos);
>> +		drm_panic_write(drm_panic_kmsgs, drm_panic_kmsgs_pos);
>> +	}
>> +
>> +	return NOTIFY_DONE;
>> +}
>> +
>> +static struct notifier_block drm_panic_block = {
>> +	.notifier_call = drm_panic_handler,
>> +};
>> +
>> +
>> +
>> +#ifdef CONFIG_DEBUG_FS
>> +
>> +/* Out of band logging is useful at least in the initial development phase */
>> +#define DRM_PANIC_LOG_SIZE	SZ_64K
>> +#define DRM_PANIC_LOG_LINE	128
>> +#define DRM_PANIC_LOG_ENTRIES	(DRM_PANIC_LOG_SIZE / DRM_PANIC_LOG_LINE)
>> +
>> +static char *log_buf;
>> +static size_t log_pos;
>> +static struct dentry *drm_panic_logfs_root;
>> +
>> +static void drm_panic_log(const char *fmt, ...)
>> +{
>> +	va_list args;
>> +	u32 rem_nsec;
>> +	char *text;
>> +	size_t len;
>> +	u64 sec;
>> +
>> +	if (!log_buf || oops_in_progress)
>> +		return;
>> +
>> +	va_start(args, fmt);
>> +
>> +	if (log_pos >= DRM_PANIC_LOG_ENTRIES)
>> +		log_pos = 0;
>> +
>> +	text = log_buf + (log_pos++ * DRM_PANIC_LOG_LINE);
>> +	if (log_pos == DRM_PANIC_LOG_ENTRIES)
>> +		log_pos = 0;
>> +
>> +	sec = div_u64_rem(local_clock(), 1000000000, &rem_nsec);
>> +
>> +	len = scnprintf(text, DRM_PANIC_LOG_LINE, "[%5llu.%06u] ", sec,
>> +			rem_nsec / 1000);
>> +
>> +	vscnprintf(text + len, DRM_PANIC_LOG_LINE - len, fmt, args);
>> +
>> +	/* Make sure to always have a newline in case of overflow */
>> +	if (text[DRM_PANIC_LOG_LINE - 2] != '\0')
>> +		text[DRM_PANIC_LOG_LINE - 2] = '\n';
>> +
>> +	va_end(args);
>> +}
>> +
>> +static int drm_panic_log_show(struct seq_file *m, void *v)
>> +{
>> +	size_t pos = log_pos;
>> +	unsigned int i;
>> +	char *text;
>> +
>> +	for (i = 0; i < DRM_PANIC_LOG_ENTRIES; i++) {
>> +		text = log_buf + (pos++ * DRM_PANIC_LOG_LINE);
>> +		if (pos == DRM_PANIC_LOG_ENTRIES)
>> +			pos = 0;
>> +		if (*text == '\0')
>> +			continue;
>> +		seq_puts(m, text);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int drm_panic_log_open(struct inode *inode, struct file *file)
>> +{
>> +	return single_open(file, drm_panic_log_show, NULL);
>> +}
>> +
>> +static const struct file_operations drm_panic_log_ops = {
>> +	.owner   = THIS_MODULE,
>> +	.open    = drm_panic_log_open,
>> +	.read    = seq_read,
>> +	.llseek  = seq_lseek,
>> +	.release = single_release,
>> +};
>> +
>> +/*
>> + * Fake/simulate panic() at different levels:
>> + * 1: only trigger panic handling internally
>> + * 2: add local_irq_disable()
>> + * 3: add bust_spinlocks();
>> + * 100: don't fake it, do call panic()
>> + */
>> +static int drm_text_fake_panic(unsigned int level)
>> +{
>> +#ifndef MODULE
>> +	int old_loglevel = console_loglevel;
>> +#endif
>> +
>> +	if (!level && level != 100 && level > 3)
>> +		return -EINVAL;
>> +
>> +	if (level == 100)
>> +		panic("TESTING");
>> +
>> +	if (level > 1)
>> +		local_irq_disable();
>> +
>> +#ifndef MODULE
>> +	console_verbose();
>> +#endif
>> +	if (level > 2)
>> +		bust_spinlocks(1);
>> +
>> +	pr_emerg("Kernel panic - not syncing: FAKING=%u, oops_in_progress=%d\n",
>> +		 level, oops_in_progress);
>> +
>> +#ifdef CONFIG_DEBUG_BUGVERBOSE
>> +	dump_stack();
>> +#endif
>> +	/* simulate calling panic_notifier_list */
>> +	drm_panic_handler(NULL, 0, NULL);
>> +
>> +	if (level > 2)
>> +		bust_spinlocks(0);
>> +
>> +#ifndef MODULE
>> +	console_flush_on_panic();
>> +#endif
>> +	pr_emerg("---[ end Kernel panic - not syncing: FAKING\n");
>> +
>> +	if (level > 1)
>> +		local_irq_enable();
>> +
>> +#ifndef MODULE
>> +	console_loglevel = old_loglevel;
>> +#endif
>> +
>> +	return 0;
>> +}
>> +
>> +static ssize_t drm_text_panic_write(struct file *file,
>> +				    const char __user *user_buf,
>> +				    size_t count, loff_t *ppos)
>> +{
>> +	unsigned long long val;
>> +	ssize_t ret = 0;
>> +	char buf[24];
>> +	size_t size;
>> +
>> +	size = min(sizeof(buf) - 1, count);
>> +	if (copy_from_user(buf, user_buf, size))
>> +		return -EFAULT;
>> +
>> +	buf[size] = '\0';
>> +	ret = kstrtoull(buf, 0, &val);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = drm_text_fake_panic(val);
>> +
>> +	return ret < 0 ? ret : count;
>> +}
>> +
>> +static const struct file_operations drm_text_panic_ops = {
>> +	.write =        drm_text_panic_write,
>> +	.open =         simple_open,
>> +	.llseek =       default_llseek,
>> +};
>> +
>> +static int drm_panic_logfs_init(void)
>> +{
>> +	drm_panic_logfs_root = debugfs_create_dir("drm-panic", NULL);
>> +	if (!drm_panic_logfs_root)
>> +		return -ENOMEM;
>> +
>> +	if (!debugfs_create_file("log", S_IRUGO, drm_panic_logfs_root, NULL,
>> +			    &drm_panic_log_ops))
>> +		goto err_remove;
>> +
>> +	log_buf = kzalloc(DRM_PANIC_LOG_SIZE, GFP_KERNEL);
>> +	if (!log_buf)
>> +		goto err_remove;
>> +
>> +	debugfs_create_file("panic", S_IWUSR, drm_panic_logfs_root, NULL,
>> +			    &drm_text_panic_ops);
>> +
>> +	return 0;
>> +
>> +err_remove:
>> +	debugfs_remove_recursive(drm_panic_logfs_root);
>> +
>> +	return -ENOMEM;
>> +}
>> +
>> +static void drm_panic_logfs_exit(void)
>> +{
>> +	debugfs_remove_recursive(drm_panic_logfs_root);
>> +	kfree(log_buf);
>> +	log_buf = NULL;
>> +}
>> +
>> +#else
>> +
>> +static int drm_panic_logfs_init(void)
>> +{
>> +}
>> +
>> +static void drm_panic_logfs_exit(void)
>> +{
>> +}
>> +
>> +static void drm_panic_log(const char *fmt, ...)
>> +{
>> +}
>> +
>> +#endif
>> +
>> +
>> +void __init drm_panic_init(void)
>> +{
>> +	drm_panic_kmsgs = kzalloc(DRM_PANIC_MAX_KMSGS, GFP_KERNEL);
>> +	if (!drm_panic_kmsgs) {
>> +		DRM_ERROR("Failed to setup panic handler\n");
>> +		return;
>> +	}
>> +
>> +	drm_panic_logfs_init();
>> +drm_panic_log("%s\n", __func__);
>> +	register_console(&drm_panic_console);
>> +	atomic_notifier_chain_register(&panic_notifier_list,
>> +				       &drm_panic_block);
>> +}
>> +
>> +void __exit drm_panic_exit(void)
>> +{
>> +	if (!drm_panic_kmsgs)
>> +		return;
>> +
>> +	drm_panic_logfs_exit();
>> +	atomic_notifier_chain_unregister(&panic_notifier_list,
>> +					 &drm_panic_block);
>> +	unregister_console(&drm_panic_console);
>> +	kfree(drm_panic_kmsgs);
>> +}
>> diff --git a/include/drm/drmP.h b/include/drm/drmP.h
>> index bc7006e..4e84654 100644
>> --- a/include/drm/drmP.h
>> +++ b/include/drm/drmP.h
>> @@ -550,6 +550,28 @@ struct drm_driver {
>>   			  bool from_open);
>>   	void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);
>>   
>> +	/**
>> +	 * @panic:
>> +	 *
>> +	 * This function is called on panic() asking for a framebuffer to
>> +	 * display the panic messages on. It also needs the virtual address
>> +	 * of the backing buffer.
>> +	 * This function is optional.
>> +	 *
>> +	 * NOTE:
>> +	 *
>> +	 * This function is called in an atomic notifier chain and it cannot
>> +	 * sleep. Care must be taken so the machine is not killed even harder,
>> +	 * preventing output from going out on serial/netconsole.
>> +	 *
>> +	 * RETURNS:
>> +	 *
>> +	 * Framebuffer that should be used to display the panic messages,
>> +	 * alongside the virtual address of the backing buffer, or NULL if
>> +	 * the driver is unable to provide this.
>> +	 */
>> +	struct drm_framebuffer *(*panic)(struct drm_device *dev, void **vmem);
>> +
>>   	int (*debugfs_init)(struct drm_minor *minor);
>>   	void (*debugfs_cleanup)(struct drm_minor *minor);
>>   
>> -- 
>> 2.8.2
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel



More information about the dri-devel mailing list