[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