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