Message ID | 20190311174218.51899-2-noralf@tronnes.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | drm: Add panic handling | expand |
On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > This adds support for outputting kernel messages on panic(). > A kernel message dumper is used to dump the log. The dumper iterates > over each DRM device and it's crtc's to find suitable framebuffers. > > All the other dumpers are run before this one except mtdoops. > Only atomic drivers are supported. > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Bunch of comments/ideas for you or Darwish below, whoever picks this up. -Daniel > --- > drivers/gpu/drm/Kconfig | 3 + > drivers/gpu/drm/drm_drv.c | 3 + > drivers/gpu/drm/drm_framebuffer.c | 117 ++++++++++ > drivers/gpu/drm/drm_internal.h | 4 + > drivers/gpu/drm/drm_panic.c | 363 ++++++++++++++++++++++++++++++ > include/drm/drm_framebuffer.h | 41 ++++ > 6 files changed, 531 insertions(+) > create mode 100644 drivers/gpu/drm/drm_panic.c > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > index bd943a71756c..74c37542f857 100644 > --- a/drivers/gpu/drm/Kconfig > +++ b/drivers/gpu/drm/Kconfig > @@ -14,6 +14,9 @@ menuconfig DRM > select I2C_ALGOBIT > select DMA_SHARED_BUFFER > select SYNC_FILE > + select FONT_SUPPORT > + select FONT_8x8 > + select FONT_8x16 > help > Kernel-level support for the Direct Rendering Infrastructure (DRI) > introduced in XFree86 4.0. If you say Y here, you need to select > diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c > index 50d849d1bc6e..b0b870b5dd55 100644 > --- a/drivers/gpu/drm/drm_drv.c > +++ b/drivers/gpu/drm/drm_drv.c > @@ -1147,6 +1147,7 @@ static const struct file_operations drm_stub_fops = { > > static void drm_core_exit(void) > { > + drm_panic_exit(drm_debugfs_root); > unregister_chrdev(DRM_MAJOR, "drm"); > debugfs_remove(drm_debugfs_root); > drm_sysfs_destroy(); > @@ -1178,6 +1179,8 @@ static int __init drm_core_init(void) > if (ret < 0) > goto error; > > + drm_panic_init(drm_debugfs_root); > + > drm_core_init_complete = true; > > DRM_DEBUG("Initialized\n"); > diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c > index d8d75e25f6fb..da2285c5ae90 100644 > --- a/drivers/gpu/drm/drm_framebuffer.c > +++ b/drivers/gpu/drm/drm_framebuffer.c > @@ -1087,3 +1087,120 @@ int drm_framebuffer_debugfs_init(struct drm_minor *minor) > minor->debugfs_root, minor); > } > #endif > + > +/** > + * drm_framebuffer_panic_draw_xy - draw pixel on fb during panic() > + * @fb: DRM framebuffer > + * @vmap: Linear virtual mapping > + * @x: X coordinate > + * @y: Y coordinate > + * @foreground: Foreground pixel > + * > + * This function can be used to draw a pixel during panic message rendering. > + * It requires @vmap to be a linear mapping. This is the default implementation > + * used if &drm_framebuffer_funcs->panic_draw_xy is not set. > + */ > +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > + int x, int y, bool foreground) > +{ > + void *pix = vmap + fb->offsets[0] + (y * fb->pitches[0]); > + u8 *pix8 = pix; > + u16 *pix16 = pix; > + u32 *pix32 = pix; > + > + switch (fb->format->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: > + pix8[x] = foreground ? 0xff : 0x00; > + break; > + > + case DRM_FORMAT_XRGB4444: > + case DRM_FORMAT_ARGB4444: > + case DRM_FORMAT_XBGR4444: > + case DRM_FORMAT_ABGR4444: > + pix16[x] = foreground ? 0xffff : 0xf000; > + break; > + > + case DRM_FORMAT_RGBX4444: > + case DRM_FORMAT_RGBA4444: > + case DRM_FORMAT_BGRX4444: > + case DRM_FORMAT_BGRA4444: > + pix16[x] = foreground ? 0xffff : 0x000f; > + break; > + > + case DRM_FORMAT_XRGB1555: > + case DRM_FORMAT_ARGB1555: > + case DRM_FORMAT_XBGR1555: > + case DRM_FORMAT_ABGR1555: > + pix16[x] = foreground ? 0xffff : 0x8000; > + break; > + > + case DRM_FORMAT_RGBX5551: > + case DRM_FORMAT_RGBA5551: > + case DRM_FORMAT_BGRX5551: > + case DRM_FORMAT_BGRA5551: > + pix16[x] = foreground ? 0xffff : 0x0001; > + break; > + > + case DRM_FORMAT_RGB565: > + case DRM_FORMAT_BGR565: > + pix16[x] = foreground ? 0xffff : 0x0000; > + break; > + > + case DRM_FORMAT_RGB888: > + case DRM_FORMAT_BGR888: > + pix8[x * 3] = foreground ? 0xff : 0x00; > + pix8[(x * 3) + 1] = pix8[x]; > + pix8[(x * 3) + 2] = pix8[x]; > + break; > + > + case DRM_FORMAT_XRGB8888: > + case DRM_FORMAT_ARGB8888: > + case DRM_FORMAT_XBGR8888: > + case DRM_FORMAT_ABGR8888: > + pix32[x] = foreground ? 0xffffffff : 0xff000000; > + break; > + > + case DRM_FORMAT_RGBX8888: > + case DRM_FORMAT_RGBA8888: > + case DRM_FORMAT_BGRX8888: > + case DRM_FORMAT_BGRA8888: > + pix32[x] = foreground ? 0xffffffff : 0x000000ff; > + break; > + > + case DRM_FORMAT_XRGB2101010: > + case DRM_FORMAT_ARGB2101010: > + case DRM_FORMAT_XBGR2101010: > + case DRM_FORMAT_ABGR2101010: > + pix32[x] = foreground ? 0xffffffff : 0xc0000000; > + break; > + > + case DRM_FORMAT_RGBX1010102: > + case DRM_FORMAT_RGBA1010102: > + case DRM_FORMAT_BGRX1010102: > + case DRM_FORMAT_BGRA1010102: > + pix32[x] = foreground ? 0xffffffff : 0x00000003; > + break; > + } > +} > +EXPORT_SYMBOL(drm_framebuffer_panic_draw_xy); > diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h > index 251d67e04c2d..694a5803ac3c 100644 > --- a/drivers/gpu/drm/drm_internal.h > +++ b/drivers/gpu/drm/drm_internal.h > @@ -191,3 +191,7 @@ int drm_syncobj_signal_ioctl(struct drm_device *dev, void *data, > void drm_framebuffer_print_info(struct drm_printer *p, unsigned int indent, > const struct drm_framebuffer *fb); > int drm_framebuffer_debugfs_init(struct drm_minor *minor); > + > +/* drm_panic.c */ > +void drm_panic_init(struct dentry *debugfs_root); > +void drm_panic_exit(struct dentry *debugfs_root); > diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c > new file mode 100644 > index 000000000000..4bca7f0bc369 > --- /dev/null > +++ b/drivers/gpu/drm/drm_panic.c > @@ -0,0 +1,363 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// Copyright 2018 Noralf Trønnes > + > +#include <linux/debugfs.h> > +#include <linux/font.h> > +#include <linux/kernel.h> > +#include <linux/kmsg_dump.h> > +#include <linux/seq_file.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> > + > +#include <drm/drm_crtc.h> > +#include <drm/drm_device.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_file.h> > +#include <drm/drm_framebuffer.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_print.h> > +#include <drm/drmP.h> > + > +#include "drm_internal.h" > + > +/* > + * The log lines in an ARM stack dump are 92 characters long > + * and 120 is a nice multiple for HD and 4K. > + */ > +#define DRM_PANIC_COLUMN_WIDTH 120 I think we should compute this dynamically from the actual fb width and font witdth. > + > +struct drm_panic_ctx { > + struct drm_framebuffer *fb; > + unsigned int width; > + unsigned int height; > + void *vmap; > + > + const struct font_desc *font; > + unsigned int col_width; > + unsigned int bottom_y; > + size_t max_line_length; > + > + unsigned int x; > + unsigned int y; > +}; > + > +static const struct font_desc *drm_panic_font8x8, *drm_panic_font8x16; > + > +static void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > + int x, int y, bool fg) > +{ > + if (fb->funcs->panic_draw_xy) > + fb->funcs->panic_draw_xy(fb, vmap, x, y, fg); > + else > + drm_framebuffer_panic_draw_xy(fb, vmap, x, y, fg); > +} > + > +static void drm_panic_render_char(struct drm_panic_ctx *ctx, > + unsigned int offset, char c) > +{ > + unsigned int h, w, x, y; > + u8 fontline; > + > + for (h = 0; h < ctx->font->height; h++) { > + fontline = *(u8 *)(ctx->font->data + c * ctx->font->height + h); > + > + for (w = 0; w < ctx->font->width; w++) { > + x = ctx->x + (offset * ctx->font->width) + w; > + y = ctx->y + h; > + drm_panic_draw_xy(ctx->fb, ctx->vmap, x, y, > + fontline & BIT(7 - w)); > + } > + } > +} > + > +static void drm_panic_render_line(struct drm_panic_ctx *ctx, > + const char *line, size_t len) > +{ > + unsigned int i; > + > + for (i = 0; i < len; i++) > + drm_panic_render_char(ctx, i, line[i]); > + > + /* Clear out the rest of the line */ > + for (i = len; i < ctx->max_line_length; i++) > + drm_panic_render_char(ctx, i, ' '); > +} > + > +static bool drm_panic_newline(struct drm_panic_ctx *ctx) > +{ > + if (ctx->x == 0 && ctx->y == 0) > + return false; > + if (ctx->y == 0) { > + ctx->x -= ctx->col_width; > + ctx->y = ctx->bottom_y; > + } else { > + ctx->y -= ctx->font->height; > + } > + > + return true; > +} > + > +/* Render from bottom right most column */ > +static bool drm_panic_render_lines(struct drm_panic_ctx *ctx, > + const char *str, size_t len) > +{ > + size_t l, line_length; > + const char *pos; > + int i; > + > + while (len) { > + pos = str + len - 1; > + > + if (*pos == '\n') { > + len--; > + if (!drm_panic_newline(ctx)) > + return false; > + continue; > + } > + > + while (pos > str && *(pos - 1) != '\n') > + pos--; > + > + line_length = len - (pos - str); > + > + if (!line_length || len < line_length) { > + pr_err("%s: Bug! line_length=%zu len=%zu\n", > + __func__, line_length, len); > + return false; > + } > + > + len -= line_length; > + > + for (i = DIV_ROUND_UP(line_length, ctx->max_line_length) - 1; i >= 0; i--) { > + l = min(ctx->max_line_length, line_length - i * ctx->max_line_length); > + drm_panic_render_line(ctx, pos + (i * ctx->max_line_length), l); > + if (i && !drm_panic_newline(ctx)) > + return false; > + } > + } > + > + return true; > +} > + > +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, > + struct kmsg_dumper *dumper) > +{ > + struct drm_framebuffer *fb = plane->state->fb; > + bool first_iteration = true; > + struct drm_panic_ctx ctx; > + static char text[1024]; > + size_t len; > + > + ctx.vmap = fb->funcs->panic_vmap(fb); > + > + /* Print some info when testing */ > + if (dumper->max_reason == KMSG_DUMP_OOPS) { > + struct drm_format_name_buf format_name; > + > + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", > + __func__, fb->base.id, fb->width, fb->height, > + drm_get_format_name(fb->format->format, &format_name), > + ctx.vmap); > + } > + > + if (!ctx.vmap) > + return; For some framebuffers it might be useful to not require vmap, but instead have some page-by-page kind of access helper. Since preemptively setting up a vmap for all the non-continous buffers is a bit much, and we really can't set up the vmap in the panic handler. Maybe we should call this panic_prepare/panic_finish and s/vmap/cookie/, to make it entirely opaque? But a bit overengineering perhaps :-) > + > + ctx.fb = fb; > + ctx.width = drm_rect_width(&plane->state->src) >> 16; > + ctx.height = drm_rect_height(&plane->state->src) >> 16; > + > + /* > + * TODO: > + * Find which part of the fb that is visible. > + * Width and height are zero on vc4 > + */ > + if (!ctx.width || !ctx.height) { > + ctx.width = fb->width; > + ctx.height = fb->height; > + } > + > + /* Try to fit 50 lines */ > + if (ctx.height < 50 * 16 && drm_panic_font8x8) > + ctx.font = drm_panic_font8x8; > + else > + ctx.font = drm_panic_font8x16; > + > + ctx.col_width = DRM_PANIC_COLUMN_WIDTH * ctx.font->width; > + ctx.bottom_y = ((ctx.height / ctx.font->height) - 1) * ctx.font->height; > + > + if (ctx.width < 2 * ctx.col_width) > + ctx.max_line_length = ctx.width / ctx.font->width; > + else > + ctx.max_line_length = DRM_PANIC_COLUMN_WIDTH - 2; /* border=2 */ > + > + ctx.x = 0; > + ctx.y = ctx.bottom_y; > + if (ctx.width > ctx.col_width) > + ctx.x = ((ctx.width / ctx.col_width) - 1) * ctx.col_width; > + > + pr_debug("%s: font=%s %ux%u max_line_length=%zu (%u,%u)\n", > + __func__, ctx.font->name, ctx.width, ctx.height, > + ctx.max_line_length, ctx.x, ctx.y); > + > + kmsg_dump_rewind(dumper); > + > + /* The latest messages are fetched first */ > + while (kmsg_dump_get_buffer(dumper, false, text, sizeof(text), &len)) { > + /* Strip off the very last newline so we start at the bottom */ > + if (first_iteration) { > + len--; > + first_iteration = false; > + } > + > + if (!drm_panic_render_lines(&ctx, text, len)) > + break; > + } > + > + if (fb->funcs->panic_vunmap) > + fb->funcs->panic_vunmap(fb, vmap); > +} > + > +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) > +{ > + struct drm_framebuffer *fb; > + struct drm_plane *plane; > + struct drm_crtc *crtc; > + > + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) > + return; > + > + if (dumper->max_reason == KMSG_DUMP_OOPS) > + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, > + dev->primary->index); > + > + drm_for_each_crtc(crtc, dev) { > + if (!ww_mutex_trylock(&crtc->mutex.mutex)) > + continue; > + > + if (!crtc->enabled || !crtc->primary) > + goto crtc_unlock; > + > + if (!crtc->state || !crtc->state->active) > + goto crtc_unlock; > + > + plane = crtc->primary; > + if (!ww_mutex_trylock(&plane->mutex.mutex)) > + goto crtc_unlock; > + > + /* > + * TODO: Should we check plane_state->visible? > + * It is not set on vc4 I think we should. We should also check whether that primary is connected to the crtc (some hw you can reassign planes freely between crtc, and the crtc->primary pointer is only used for compat with legacy ioctl). > + if (!plane->state || !plane->state->visible) > + */ > + if (!plane->state) > + goto plane_unlock; > + > + fb = plane->state->fb; > + if (!fb || !fb->funcs->panic_vmap) Docs says this is optional, doesn't seem to be all that optional. I'd check for this or a driver-specific ->panic_draw_xy instead. > + goto plane_unlock; > + > + /* > + * fbcon puts out panic messages so stay away to avoid jumbled > + * output. If vc->vc_mode != KD_TEXT fbcon won't put out > + * messages (see vt_console_print). > + */ > + if (dev->fb_helper && dev->fb_helper->fb == fb) > + goto plane_unlock; > + > + drm_panic_kmsg_render_screen(plane, dumper); > +plane_unlock: > + ww_mutex_unlock(&plane->mutex.mutex); > +crtc_unlock: > + ww_mutex_unlock(&crtc->mutex.mutex); > + } > +} > + > +static int drm_panic_dev_iter(struct device *dev, void *data) > +{ > + struct drm_minor *minor = dev_get_drvdata(dev); > + > + if (minor && minor->type == DRM_MINOR_PRIMARY) > + drm_panic_try_dev(minor->dev, data); > + > + return 0; > +} > + > +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > + enum kmsg_dump_reason reason) > +{ > + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); class_for_each_device uses klist, which only uses an irqsave spinlock. I think that's good enough. Comment to that effect would be good e.g. /* based on klist, which uses only a spin_lock_irqsave, which we * assume still works */ If we aim for perfect this should be a trylock still, maybe using our own device list. > +} > + > +static struct kmsg_dumper drm_panic_kmsg_dumper = { > + .dump = drm_panic_kmsg_dump, > + .max_reason = KMSG_DUMP_PANIC, > +}; > + > +static ssize_t drm_panic_file_panic_write(struct file *file, > + const char __user *user_buf, > + size_t count, loff_t *ppos) > +{ > + unsigned long long val; > + char buf[24]; > + size_t size; > + ssize_t ret; > + > + 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; > + > + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > + wmb(); > + > + /* Do a real test with: echo c > /proc/sysrq-trigger */ > + > + if (val == 0) { > + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > + kmsg_dump(KMSG_DUMP_OOPS); > + } else if (val == 1) { > + char *null_pointer = NULL; > + > + pr_info("Test panic screen using NULL pointer dereference\n"); > + *null_pointer = 1; > + } else { > + return -EINVAL; > + } This isn't quite what I had in mind, since it still kills the kernel (like sysrq-trigger). Instead what I had in mind is to recreate the worst possible panic context as much as feasible (disabling interrupts should be a good start, maybe we can even do an nmi callback), and then call our panic implementation. That way we can test the panic handler in a non-destructive way (i.e. aside from last dmesg lines printed to the screen nothing bad happens to the kernel: No real panic, no oops, no tainting). And igt testcase could then exercise this, and CI run it. > + > + return count; > +} > + > +static const struct file_operations drm_panic_panic_ops = { > + .write = drm_panic_file_panic_write, > + .open = simple_open, > + .llseek = default_llseek, > +}; > + > +static struct dentry *drm_panic_d_panic; > + > +void __init drm_panic_init(struct dentry *debugfs_root) > +{ > + drm_panic_font8x8 = find_font("VGA8x8"); > + drm_panic_font8x16 = find_font("VGA8x16"); > + if (!drm_panic_font8x16) { > + DRM_WARN("Couldn't find font, panic screen disabled\n"); > + return; > + } > + > + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, > + debugfs_root, NULL, > + &drm_panic_panic_ops); > + kmsg_dump_register(&drm_panic_kmsg_dumper); > +} > + > +void drm_panic_exit(struct dentry *debugfs_root) > +{ > + kmsg_dump_unregister(&drm_panic_kmsg_dumper); > + debugfs_remove(drm_panic_d_panic); > +} > diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > index f0b34c977ec5..f3274798ecfe 100644 > --- a/include/drm/drm_framebuffer.h > +++ b/include/drm/drm_framebuffer.h > @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > struct drm_file *file_priv, unsigned flags, > unsigned color, struct drm_clip_rect *clips, > unsigned num_clips); > + > + /** > + * @panic_vmap: > + * > + * Optional callback for panic handling. > + * > + * For vmapping the selected framebuffer in a panic context. Must > + * be super careful about locking (only trylocking allowed). > + * > + * RETURNS: > + * > + * NULL if it didn't work out, otherwise 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); > + > + /** > + * @panic_vunmap: > + * > + * Optional callback for cleaning up after panic testing. > + * > + * Crtc and plane locks are released after this callback has run. > + * vmap is the cookie returned by @panic_vmap. > + */ > + void (*panic_vunmap)(struct drm_framebuffer *fb, void *vmap); > + > + /** > + * @panic_draw_xy: > + * > + * Optional callback for drawing pixels during panic. > + * > + * For drawing pixels onto a framebuffer prepared with @panic_vmap. > + * vmap is the cookie returned by @panic_vmap. > + * If it's not set, drm_framebuffer_panic_draw_xy() is used. > + */ > + void (*panic_draw_xy)(struct drm_framebuffer *fb, void *vmap, > + int x, int y, bool foreground); > }; > > /** > @@ -293,4 +331,7 @@ int drm_framebuffer_plane_width(int width, > int drm_framebuffer_plane_height(int height, > const struct drm_framebuffer *fb, int plane); > > +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > + int x, int y, bool foreground); > + > #endif > -- > 2.20.1 >
On Mon, Mar 11, 2019 at 08:23:38PM +0100, Daniel Vetter wrote: > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > > This adds support for outputting kernel messages on panic(). > > A kernel message dumper is used to dump the log. The dumper iterates > > over each DRM device and it's crtc's to find suitable framebuffers. > > > > All the other dumpers are run before this one except mtdoops. > > Only atomic drivers are supported. > > > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. > -Daniel > > > --- > > drivers/gpu/drm/Kconfig | 3 + > > drivers/gpu/drm/drm_drv.c | 3 + > > drivers/gpu/drm/drm_framebuffer.c | 117 ++++++++++ > > drivers/gpu/drm/drm_internal.h | 4 + > > drivers/gpu/drm/drm_panic.c | 363 ++++++++++++++++++++++++++++++ > > include/drm/drm_framebuffer.h | 41 ++++ > > 6 files changed, 531 insertions(+) > > create mode 100644 drivers/gpu/drm/drm_panic.c > > > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > > index bd943a71756c..74c37542f857 100644 > > --- a/drivers/gpu/drm/Kconfig > > +++ b/drivers/gpu/drm/Kconfig > > @@ -14,6 +14,9 @@ menuconfig DRM > > select I2C_ALGOBIT > > select DMA_SHARED_BUFFER > > select SYNC_FILE > > + select FONT_SUPPORT > > + select FONT_8x8 > > + select FONT_8x16 > > help > > Kernel-level support for the Direct Rendering Infrastructure (DRI) > > introduced in XFree86 4.0. If you say Y here, you need to select > > diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c > > index 50d849d1bc6e..b0b870b5dd55 100644 > > --- a/drivers/gpu/drm/drm_drv.c > > +++ b/drivers/gpu/drm/drm_drv.c > > @@ -1147,6 +1147,7 @@ static const struct file_operations drm_stub_fops = { > > > > static void drm_core_exit(void) > > { > > + drm_panic_exit(drm_debugfs_root); > > unregister_chrdev(DRM_MAJOR, "drm"); > > debugfs_remove(drm_debugfs_root); > > drm_sysfs_destroy(); > > @@ -1178,6 +1179,8 @@ static int __init drm_core_init(void) > > if (ret < 0) > > goto error; > > > > + drm_panic_init(drm_debugfs_root); > > + > > drm_core_init_complete = true; > > > > DRM_DEBUG("Initialized\n"); > > diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c > > index d8d75e25f6fb..da2285c5ae90 100644 > > --- a/drivers/gpu/drm/drm_framebuffer.c > > +++ b/drivers/gpu/drm/drm_framebuffer.c > > @@ -1087,3 +1087,120 @@ int drm_framebuffer_debugfs_init(struct drm_minor *minor) > > minor->debugfs_root, minor); > > } > > #endif > > + > > +/** > > + * drm_framebuffer_panic_draw_xy - draw pixel on fb during panic() > > + * @fb: DRM framebuffer > > + * @vmap: Linear virtual mapping > > + * @x: X coordinate > > + * @y: Y coordinate > > + * @foreground: Foreground pixel > > + * > > + * This function can be used to draw a pixel during panic message rendering. > > + * It requires @vmap to be a linear mapping. This is the default implementation > > + * used if &drm_framebuffer_funcs->panic_draw_xy is not set. > > + */ > > +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > > + int x, int y, bool foreground) > > +{ > > + void *pix = vmap + fb->offsets[0] + (y * fb->pitches[0]); > > + u8 *pix8 = pix; > > + u16 *pix16 = pix; > > + u32 *pix32 = pix; > > + > > + switch (fb->format->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: > > + pix8[x] = foreground ? 0xff : 0x00; > > + break; > > + > > + case DRM_FORMAT_XRGB4444: > > + case DRM_FORMAT_ARGB4444: > > + case DRM_FORMAT_XBGR4444: > > + case DRM_FORMAT_ABGR4444: > > + pix16[x] = foreground ? 0xffff : 0xf000; > > + break; > > + > > + case DRM_FORMAT_RGBX4444: > > + case DRM_FORMAT_RGBA4444: > > + case DRM_FORMAT_BGRX4444: > > + case DRM_FORMAT_BGRA4444: > > + pix16[x] = foreground ? 0xffff : 0x000f; > > + break; > > + > > + case DRM_FORMAT_XRGB1555: > > + case DRM_FORMAT_ARGB1555: > > + case DRM_FORMAT_XBGR1555: > > + case DRM_FORMAT_ABGR1555: > > + pix16[x] = foreground ? 0xffff : 0x8000; > > + break; > > + > > + case DRM_FORMAT_RGBX5551: > > + case DRM_FORMAT_RGBA5551: > > + case DRM_FORMAT_BGRX5551: > > + case DRM_FORMAT_BGRA5551: > > + pix16[x] = foreground ? 0xffff : 0x0001; > > + break; > > + > > + case DRM_FORMAT_RGB565: > > + case DRM_FORMAT_BGR565: > > + pix16[x] = foreground ? 0xffff : 0x0000; > > + break; > > + > > + case DRM_FORMAT_RGB888: > > + case DRM_FORMAT_BGR888: > > + pix8[x * 3] = foreground ? 0xff : 0x00; > > + pix8[(x * 3) + 1] = pix8[x]; > > + pix8[(x * 3) + 2] = pix8[x]; > > + break; > > + > > + case DRM_FORMAT_XRGB8888: > > + case DRM_FORMAT_ARGB8888: > > + case DRM_FORMAT_XBGR8888: > > + case DRM_FORMAT_ABGR8888: > > + pix32[x] = foreground ? 0xffffffff : 0xff000000; > > + break; > > + > > + case DRM_FORMAT_RGBX8888: > > + case DRM_FORMAT_RGBA8888: > > + case DRM_FORMAT_BGRX8888: > > + case DRM_FORMAT_BGRA8888: > > + pix32[x] = foreground ? 0xffffffff : 0x000000ff; > > + break; > > + > > + case DRM_FORMAT_XRGB2101010: > > + case DRM_FORMAT_ARGB2101010: > > + case DRM_FORMAT_XBGR2101010: > > + case DRM_FORMAT_ABGR2101010: > > + pix32[x] = foreground ? 0xffffffff : 0xc0000000; > > + break; > > + > > + case DRM_FORMAT_RGBX1010102: > > + case DRM_FORMAT_RGBA1010102: > > + case DRM_FORMAT_BGRX1010102: > > + case DRM_FORMAT_BGRA1010102: > > + pix32[x] = foreground ? 0xffffffff : 0x00000003; > > + break; > > + } > > +} > > +EXPORT_SYMBOL(drm_framebuffer_panic_draw_xy); > > diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h > > index 251d67e04c2d..694a5803ac3c 100644 > > --- a/drivers/gpu/drm/drm_internal.h > > +++ b/drivers/gpu/drm/drm_internal.h > > @@ -191,3 +191,7 @@ int drm_syncobj_signal_ioctl(struct drm_device *dev, void *data, > > void drm_framebuffer_print_info(struct drm_printer *p, unsigned int indent, > > const struct drm_framebuffer *fb); > > int drm_framebuffer_debugfs_init(struct drm_minor *minor); > > + > > +/* drm_panic.c */ > > +void drm_panic_init(struct dentry *debugfs_root); > > +void drm_panic_exit(struct dentry *debugfs_root); > > diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c > > new file mode 100644 > > index 000000000000..4bca7f0bc369 > > --- /dev/null > > +++ b/drivers/gpu/drm/drm_panic.c > > @@ -0,0 +1,363 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +// Copyright 2018 Noralf Trønnes > > + > > +#include <linux/debugfs.h> > > +#include <linux/font.h> > > +#include <linux/kernel.h> > > +#include <linux/kmsg_dump.h> > > +#include <linux/seq_file.h> > > +#include <linux/slab.h> > > +#include <linux/uaccess.h> > > + > > +#include <drm/drm_crtc.h> > > +#include <drm/drm_device.h> > > +#include <drm/drm_drv.h> > > +#include <drm/drm_file.h> > > +#include <drm/drm_framebuffer.h> > > +#include <drm/drm_fb_helper.h> > > +#include <drm/drm_plane.h> > > +#include <drm/drm_print.h> > > +#include <drm/drmP.h> > > + > > +#include "drm_internal.h" > > + > > +/* > > + * The log lines in an ARM stack dump are 92 characters long > > + * and 120 is a nice multiple for HD and 4K. > > + */ > > +#define DRM_PANIC_COLUMN_WIDTH 120 > > I think we should compute this dynamically from the actual fb width and > font witdth. > > > + > > +struct drm_panic_ctx { > > + struct drm_framebuffer *fb; > > + unsigned int width; > > + unsigned int height; > > + void *vmap; > > + > > + const struct font_desc *font; > > + unsigned int col_width; > > + unsigned int bottom_y; > > + size_t max_line_length; > > + > > + unsigned int x; > > + unsigned int y; > > +}; > > + > > +static const struct font_desc *drm_panic_font8x8, *drm_panic_font8x16; > > + > > +static void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > > + int x, int y, bool fg) > > +{ > > + if (fb->funcs->panic_draw_xy) > > + fb->funcs->panic_draw_xy(fb, vmap, x, y, fg); > > + else > > + drm_framebuffer_panic_draw_xy(fb, vmap, x, y, fg); > > +} > > + > > +static void drm_panic_render_char(struct drm_panic_ctx *ctx, > > + unsigned int offset, char c) > > +{ > > + unsigned int h, w, x, y; > > + u8 fontline; > > + > > + for (h = 0; h < ctx->font->height; h++) { > > + fontline = *(u8 *)(ctx->font->data + c * ctx->font->height + h); > > + > > + for (w = 0; w < ctx->font->width; w++) { > > + x = ctx->x + (offset * ctx->font->width) + w; > > + y = ctx->y + h; > > + drm_panic_draw_xy(ctx->fb, ctx->vmap, x, y, > > + fontline & BIT(7 - w)); > > + } > > + } > > +} > > + > > +static void drm_panic_render_line(struct drm_panic_ctx *ctx, > > + const char *line, size_t len) > > +{ > > + unsigned int i; > > + > > + for (i = 0; i < len; i++) > > + drm_panic_render_char(ctx, i, line[i]); > > + > > + /* Clear out the rest of the line */ > > + for (i = len; i < ctx->max_line_length; i++) > > + drm_panic_render_char(ctx, i, ' '); > > +} > > + > > +static bool drm_panic_newline(struct drm_panic_ctx *ctx) > > +{ > > + if (ctx->x == 0 && ctx->y == 0) > > + return false; > > + if (ctx->y == 0) { > > + ctx->x -= ctx->col_width; > > + ctx->y = ctx->bottom_y; > > + } else { > > + ctx->y -= ctx->font->height; > > + } > > + > > + return true; > > +} > > + > > +/* Render from bottom right most column */ > > +static bool drm_panic_render_lines(struct drm_panic_ctx *ctx, > > + const char *str, size_t len) > > +{ > > + size_t l, line_length; > > + const char *pos; > > + int i; > > + > > + while (len) { > > + pos = str + len - 1; > > + > > + if (*pos == '\n') { > > + len--; > > + if (!drm_panic_newline(ctx)) > > + return false; > > + continue; > > + } > > + > > + while (pos > str && *(pos - 1) != '\n') > > + pos--; > > + > > + line_length = len - (pos - str); > > + > > + if (!line_length || len < line_length) { > > + pr_err("%s: Bug! line_length=%zu len=%zu\n", > > + __func__, line_length, len); > > + return false; > > + } > > + > > + len -= line_length; > > + > > + for (i = DIV_ROUND_UP(line_length, ctx->max_line_length) - 1; i >= 0; i--) { > > + l = min(ctx->max_line_length, line_length - i * ctx->max_line_length); > > + drm_panic_render_line(ctx, pos + (i * ctx->max_line_length), l); > > + if (i && !drm_panic_newline(ctx)) > > + return false; > > + } > > + } > > + > > + return true; > > +} > > + > > +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, > > + struct kmsg_dumper *dumper) > > +{ > > + struct drm_framebuffer *fb = plane->state->fb; > > + bool first_iteration = true; > > + struct drm_panic_ctx ctx; > > + static char text[1024]; > > + size_t len; > > + > > + ctx.vmap = fb->funcs->panic_vmap(fb); > > + > > + /* Print some info when testing */ > > + if (dumper->max_reason == KMSG_DUMP_OOPS) { > > + struct drm_format_name_buf format_name; > > + > > + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", > > + __func__, fb->base.id, fb->width, fb->height, > > + drm_get_format_name(fb->format->format, &format_name), > > + ctx.vmap); > > + } > > + > > + if (!ctx.vmap) > > + return; > > For some framebuffers it might be useful to not require vmap, but instead > have some page-by-page kind of access helper. Since preemptively setting > up a vmap for all the non-continous buffers is a bit much, and we really > can't set up the vmap in the panic handler. > > Maybe we should call this panic_prepare/panic_finish and > s/vmap/cookie/, to make it entirely opaque? > > But a bit overengineering perhaps :-) > > > + > > + ctx.fb = fb; > > + ctx.width = drm_rect_width(&plane->state->src) >> 16; > > + ctx.height = drm_rect_height(&plane->state->src) >> 16; > > + > > + /* > > + * TODO: > > + * Find which part of the fb that is visible. > > + * Width and height are zero on vc4 > > + */ > > + if (!ctx.width || !ctx.height) { > > + ctx.width = fb->width; > > + ctx.height = fb->height; > > + } > > + > > + /* Try to fit 50 lines */ > > + if (ctx.height < 50 * 16 && drm_panic_font8x8) > > + ctx.font = drm_panic_font8x8; > > + else > > + ctx.font = drm_panic_font8x16; > > + > > + ctx.col_width = DRM_PANIC_COLUMN_WIDTH * ctx.font->width; > > + ctx.bottom_y = ((ctx.height / ctx.font->height) - 1) * ctx.font->height; > > + > > + if (ctx.width < 2 * ctx.col_width) > > + ctx.max_line_length = ctx.width / ctx.font->width; > > + else > > + ctx.max_line_length = DRM_PANIC_COLUMN_WIDTH - 2; /* border=2 */ > > + > > + ctx.x = 0; > > + ctx.y = ctx.bottom_y; > > + if (ctx.width > ctx.col_width) > > + ctx.x = ((ctx.width / ctx.col_width) - 1) * ctx.col_width; > > + > > + pr_debug("%s: font=%s %ux%u max_line_length=%zu (%u,%u)\n", > > + __func__, ctx.font->name, ctx.width, ctx.height, > > + ctx.max_line_length, ctx.x, ctx.y); > > + > > + kmsg_dump_rewind(dumper); > > + > > + /* The latest messages are fetched first */ > > + while (kmsg_dump_get_buffer(dumper, false, text, sizeof(text), &len)) { > > + /* Strip off the very last newline so we start at the bottom */ > > + if (first_iteration) { > > + len--; > > + first_iteration = false; > > + } > > + > > + if (!drm_panic_render_lines(&ctx, text, len)) > > + break; > > + } > > + > > + if (fb->funcs->panic_vunmap) > > + fb->funcs->panic_vunmap(fb, vmap); > > +} > > + > > +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) > > +{ > > + struct drm_framebuffer *fb; > > + struct drm_plane *plane; > > + struct drm_crtc *crtc; > > + > > + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) > > + return; > > + > > + if (dumper->max_reason == KMSG_DUMP_OOPS) > > + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, > > + dev->primary->index); > > + > > + drm_for_each_crtc(crtc, dev) { > > + if (!ww_mutex_trylock(&crtc->mutex.mutex)) > > + continue; > > + > > + if (!crtc->enabled || !crtc->primary) > > + goto crtc_unlock; > > + > > + if (!crtc->state || !crtc->state->active) > > + goto crtc_unlock; > > + > > + plane = crtc->primary; > > + if (!ww_mutex_trylock(&plane->mutex.mutex)) > > + goto crtc_unlock; > > + > > + /* > > + * TODO: Should we check plane_state->visible? > > + * It is not set on vc4 > > I think we should. We should also check whether that primary is connected > to the crtc (some hw you can reassign planes freely between crtc, and the > crtc->primary pointer is only used for compat with legacy ioctl). > > > + if (!plane->state || !plane->state->visible) > > + */ > > + if (!plane->state) > > + goto plane_unlock; > > + > > + fb = plane->state->fb; > > + if (!fb || !fb->funcs->panic_vmap) > > Docs says this is optional, doesn't seem to be all that optional. I'd > check for this or a driver-specific ->panic_draw_xy instead. > > + goto plane_unlock; > > + > > + /* > > + * fbcon puts out panic messages so stay away to avoid jumbled > > + * output. If vc->vc_mode != KD_TEXT fbcon won't put out > > + * messages (see vt_console_print). > > + */ > > + if (dev->fb_helper && dev->fb_helper->fb == fb) This is a bit a layering violation. Could we instead tell fbcon that it shouldn't do panic handling for drm drivers? I think that would resolve this overlap here in a much cleaner way. drm fbdev helpers already have lots of oops_in_progress checks all over to make sure fbcon doesn't do anything bad. That only leaves the actual rendering, which I think we can stop too with a simple flag. Ofc only for atomic drivers which have this panic handling mode here implemented. -Daniel > > + goto plane_unlock; > > + > > + drm_panic_kmsg_render_screen(plane, dumper); > > +plane_unlock: > > + ww_mutex_unlock(&plane->mutex.mutex); > > +crtc_unlock: > > + ww_mutex_unlock(&crtc->mutex.mutex); > > + } > > +} > > + > > +static int drm_panic_dev_iter(struct device *dev, void *data) > > +{ > > + struct drm_minor *minor = dev_get_drvdata(dev); > > + > > + if (minor && minor->type == DRM_MINOR_PRIMARY) > > + drm_panic_try_dev(minor->dev, data); > > + > > + return 0; > > +} > > + > > +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > > + enum kmsg_dump_reason reason) > > +{ > > + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > think that's good enough. Comment to that effect would be good e.g. > > /* based on klist, which uses only a spin_lock_irqsave, which we > * assume still works */ > > If we aim for perfect this should be a trylock still, maybe using our own > device list. > > > +} > > + > > +static struct kmsg_dumper drm_panic_kmsg_dumper = { > > + .dump = drm_panic_kmsg_dump, > > + .max_reason = KMSG_DUMP_PANIC, > > +}; > > + > > +static ssize_t drm_panic_file_panic_write(struct file *file, > > + const char __user *user_buf, > > + size_t count, loff_t *ppos) > > +{ > > + unsigned long long val; > > + char buf[24]; > > + size_t size; > > + ssize_t ret; > > + > > + 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; > > + > > + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > > + wmb(); > > + > > + /* Do a real test with: echo c > /proc/sysrq-trigger */ > > + > > + if (val == 0) { > > + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > > + kmsg_dump(KMSG_DUMP_OOPS); > > + } else if (val == 1) { > > + char *null_pointer = NULL; > > + > > + pr_info("Test panic screen using NULL pointer dereference\n"); > > + *null_pointer = 1; > > + } else { > > + return -EINVAL; > > + } > > This isn't quite what I had in mind, since it still kills the kernel (like > sysrq-trigger). Instead what I had in mind is to recreate the worst > possible panic context as much as feasible (disabling interrupts should be > a good start, maybe we can even do an nmi callback), and then call our > panic implementation. That way we can test the panic handler in a > non-destructive way (i.e. aside from last dmesg lines printed to the > screen nothing bad happens to the kernel: No real panic, no oops, no > tainting). > > And igt testcase could then exercise this, and CI run it. > > > + > > + return count; > > +} > > + > > +static const struct file_operations drm_panic_panic_ops = { > > + .write = drm_panic_file_panic_write, > > + .open = simple_open, > > + .llseek = default_llseek, > > +}; > > + > > +static struct dentry *drm_panic_d_panic; > > + > > +void __init drm_panic_init(struct dentry *debugfs_root) > > +{ > > + drm_panic_font8x8 = find_font("VGA8x8"); > > + drm_panic_font8x16 = find_font("VGA8x16"); > > + if (!drm_panic_font8x16) { > > + DRM_WARN("Couldn't find font, panic screen disabled\n"); > > + return; > > + } > > + > > + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, > > + debugfs_root, NULL, > > + &drm_panic_panic_ops); > > + kmsg_dump_register(&drm_panic_kmsg_dumper); > > +} > > + > > +void drm_panic_exit(struct dentry *debugfs_root) > > +{ > > + kmsg_dump_unregister(&drm_panic_kmsg_dumper); > > + debugfs_remove(drm_panic_d_panic); > > +} > > diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > > index f0b34c977ec5..f3274798ecfe 100644 > > --- a/include/drm/drm_framebuffer.h > > +++ b/include/drm/drm_framebuffer.h > > @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > > struct drm_file *file_priv, unsigned flags, > > unsigned color, struct drm_clip_rect *clips, > > unsigned num_clips); > > + > > + /** > > + * @panic_vmap: > > + * > > + * Optional callback for panic handling. > > + * > > + * For vmapping the selected framebuffer in a panic context. Must > > + * be super careful about locking (only trylocking allowed). > > + * > > + * RETURNS: > > + * > > + * NULL if it didn't work out, otherwise 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); > > + > > + /** > > + * @panic_vunmap: > > + * > > + * Optional callback for cleaning up after panic testing. > > + * > > + * Crtc and plane locks are released after this callback has run. > > + * vmap is the cookie returned by @panic_vmap. > > + */ > > + void (*panic_vunmap)(struct drm_framebuffer *fb, void *vmap); > > + > > + /** > > + * @panic_draw_xy: > > + * > > + * Optional callback for drawing pixels during panic. > > + * > > + * For drawing pixels onto a framebuffer prepared with @panic_vmap. > > + * vmap is the cookie returned by @panic_vmap. > > + * If it's not set, drm_framebuffer_panic_draw_xy() is used. > > + */ > > + void (*panic_draw_xy)(struct drm_framebuffer *fb, void *vmap, > > + int x, int y, bool foreground); > > }; > > > > /** > > @@ -293,4 +331,7 @@ int drm_framebuffer_plane_width(int width, > > int drm_framebuffer_plane_height(int height, > > const struct drm_framebuffer *fb, int plane); > > > > +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > > + int x, int y, bool foreground); > > + > > #endif > > -- > > 2.20.1 > > > > -- > Daniel Vetter > Software Engineer, Intel Corporation > http://blog.ffwll.ch
Hi Noralf. Nice! > +++ b/drivers/gpu/drm/drm_panic.c > @@ -0,0 +1,363 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// Copyright 2018 Noralf Trønnes > + > +#include <linux/debugfs.h> > +#include <linux/font.h> > +#include <linux/kernel.h> > +#include <linux/kmsg_dump.h> > +#include <linux/seq_file.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> > + > +#include <drm/drm_crtc.h> > +#include <drm/drm_device.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_file.h> > +#include <drm/drm_framebuffer.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_print.h> > +#include <drm/drmP.h> Please do not use drmP.h in new stuff. And res-sort the list of include files when add it. I did not read much further, so no further pedantic comments in this round. Sam
Den 11.03.2019 20.23, skrev Daniel Vetter: > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: >> This adds support for outputting kernel messages on panic(). >> A kernel message dumper is used to dump the log. The dumper iterates >> over each DRM device and it's crtc's to find suitable framebuffers. >> >> All the other dumpers are run before this one except mtdoops. >> Only atomic drivers are supported. >> >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. Actually it would ne nice if Darwish could pick it up since he will do it on i915 which will be useful to a much broader audience. If not I'll respin when I'm done with the drm_fb_helper refactoring. <snip> >> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c >> new file mode 100644 >> index 000000000000..4bca7f0bc369 >> --- /dev/null >> +++ b/drivers/gpu/drm/drm_panic.c >> @@ -0,0 +1,363 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +// Copyright 2018 Noralf Trønnes >> + >> +#include <linux/debugfs.h> >> +#include <linux/font.h> >> +#include <linux/kernel.h> >> +#include <linux/kmsg_dump.h> >> +#include <linux/seq_file.h> >> +#include <linux/slab.h> >> +#include <linux/uaccess.h> >> + >> +#include <drm/drm_crtc.h> >> +#include <drm/drm_device.h> >> +#include <drm/drm_drv.h> >> +#include <drm/drm_file.h> >> +#include <drm/drm_framebuffer.h> >> +#include <drm/drm_fb_helper.h> >> +#include <drm/drm_plane.h> >> +#include <drm/drm_print.h> >> +#include <drm/drmP.h> >> + >> +#include "drm_internal.h" >> + >> +/* >> + * The log lines in an ARM stack dump are 92 characters long >> + * and 120 is a nice multiple for HD and 4K. >> + */ >> +#define DRM_PANIC_COLUMN_WIDTH 120 > > I think we should compute this dynamically from the actual fb width and > font witdth. The font is picked based on whether it can fit 50 lines of text. The next rule is to decide when to add another column. I decided to use a number that gets most log lines in one line on the screen. If most lines get broken into two lines, there's not much use in an extra column. Do you have another idea on how to do this? (There's even a 16x32 font now that wasn't available when I did this.) > >> + >> +struct drm_panic_ctx { >> + struct drm_framebuffer *fb; >> + unsigned int width; >> + unsigned int height; >> + void *vmap; >> + >> + const struct font_desc *font; >> + unsigned int col_width; >> + unsigned int bottom_y; >> + size_t max_line_length; >> + >> + unsigned int x; >> + unsigned int y; >> +}; >> + >> +static const struct font_desc *drm_panic_font8x8, *drm_panic_font8x16; >> + >> +static void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, >> + int x, int y, bool fg) >> +{ >> + if (fb->funcs->panic_draw_xy) >> + fb->funcs->panic_draw_xy(fb, vmap, x, y, fg); >> + else >> + drm_framebuffer_panic_draw_xy(fb, vmap, x, y, fg); >> +} >> + >> +static void drm_panic_render_char(struct drm_panic_ctx *ctx, >> + unsigned int offset, char c) >> +{ >> + unsigned int h, w, x, y; >> + u8 fontline; >> + >> + for (h = 0; h < ctx->font->height; h++) { >> + fontline = *(u8 *)(ctx->font->data + c * ctx->font->height + h); >> + >> + for (w = 0; w < ctx->font->width; w++) { >> + x = ctx->x + (offset * ctx->font->width) + w; >> + y = ctx->y + h; >> + drm_panic_draw_xy(ctx->fb, ctx->vmap, x, y, >> + fontline & BIT(7 - w)); >> + } >> + } >> +} >> + >> +static void drm_panic_render_line(struct drm_panic_ctx *ctx, >> + const char *line, size_t len) >> +{ >> + unsigned int i; >> + >> + for (i = 0; i < len; i++) >> + drm_panic_render_char(ctx, i, line[i]); >> + >> + /* Clear out the rest of the line */ >> + for (i = len; i < ctx->max_line_length; i++) >> + drm_panic_render_char(ctx, i, ' '); >> +} >> + >> +static bool drm_panic_newline(struct drm_panic_ctx *ctx) >> +{ >> + if (ctx->x == 0 && ctx->y == 0) >> + return false; >> + if (ctx->y == 0) { >> + ctx->x -= ctx->col_width; >> + ctx->y = ctx->bottom_y; >> + } else { >> + ctx->y -= ctx->font->height; >> + } >> + >> + return true; >> +} >> + >> +/* Render from bottom right most column */ >> +static bool drm_panic_render_lines(struct drm_panic_ctx *ctx, >> + const char *str, size_t len) >> +{ >> + size_t l, line_length; >> + const char *pos; >> + int i; >> + >> + while (len) { >> + pos = str + len - 1; >> + >> + if (*pos == '\n') { >> + len--; >> + if (!drm_panic_newline(ctx)) >> + return false; >> + continue; >> + } >> + >> + while (pos > str && *(pos - 1) != '\n') >> + pos--; >> + >> + line_length = len - (pos - str); >> + >> + if (!line_length || len < line_length) { >> + pr_err("%s: Bug! line_length=%zu len=%zu\n", >> + __func__, line_length, len); >> + return false; >> + } >> + >> + len -= line_length; >> + >> + for (i = DIV_ROUND_UP(line_length, ctx->max_line_length) - 1; i >= 0; i--) { >> + l = min(ctx->max_line_length, line_length - i * ctx->max_line_length); >> + drm_panic_render_line(ctx, pos + (i * ctx->max_line_length), l); >> + if (i && !drm_panic_newline(ctx)) >> + return false; >> + } >> + } >> + >> + return true; >> +} >> + >> +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, >> + struct kmsg_dumper *dumper) >> +{ >> + struct drm_framebuffer *fb = plane->state->fb; >> + bool first_iteration = true; >> + struct drm_panic_ctx ctx; >> + static char text[1024]; >> + size_t len; >> + >> + ctx.vmap = fb->funcs->panic_vmap(fb); >> + >> + /* Print some info when testing */ >> + if (dumper->max_reason == KMSG_DUMP_OOPS) { >> + struct drm_format_name_buf format_name; >> + >> + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", >> + __func__, fb->base.id, fb->width, fb->height, >> + drm_get_format_name(fb->format->format, &format_name), >> + ctx.vmap); >> + } >> + >> + if (!ctx.vmap) >> + return; > > For some framebuffers it might be useful to not require vmap, but instead > have some page-by-page kind of access helper. Since preemptively setting > up a vmap for all the non-continous buffers is a bit much, and we really > can't set up the vmap in the panic handler. > > Maybe we should call this panic_prepare/panic_finish and > s/vmap/cookie/, to make it entirely opaque? > > But a bit overengineering perhaps :-) > I guess i915 wants something else than vmap, but I have no idea how a page-by-page solution is to be implemented. When we get bootsplash, we will at least have a kernel address during that phase for all drivers supporting it. >> + >> + ctx.fb = fb; >> + ctx.width = drm_rect_width(&plane->state->src) >> 16; >> + ctx.height = drm_rect_height(&plane->state->src) >> 16; >> + >> + /* >> + * TODO: >> + * Find which part of the fb that is visible. >> + * Width and height are zero on vc4 >> + */ >> + if (!ctx.width || !ctx.height) { >> + ctx.width = fb->width; >> + ctx.height = fb->height; >> + } >> + >> + /* Try to fit 50 lines */ >> + if (ctx.height < 50 * 16 && drm_panic_font8x8) >> + ctx.font = drm_panic_font8x8; >> + else >> + ctx.font = drm_panic_font8x16; >> + >> + ctx.col_width = DRM_PANIC_COLUMN_WIDTH * ctx.font->width; >> + ctx.bottom_y = ((ctx.height / ctx.font->height) - 1) * ctx.font->height; >> + >> + if (ctx.width < 2 * ctx.col_width) >> + ctx.max_line_length = ctx.width / ctx.font->width; >> + else >> + ctx.max_line_length = DRM_PANIC_COLUMN_WIDTH - 2; /* border=2 */ >> + >> + ctx.x = 0; >> + ctx.y = ctx.bottom_y; >> + if (ctx.width > ctx.col_width) >> + ctx.x = ((ctx.width / ctx.col_width) - 1) * ctx.col_width; >> + >> + pr_debug("%s: font=%s %ux%u max_line_length=%zu (%u,%u)\n", >> + __func__, ctx.font->name, ctx.width, ctx.height, >> + ctx.max_line_length, ctx.x, ctx.y); >> + >> + kmsg_dump_rewind(dumper); >> + >> + /* The latest messages are fetched first */ >> + while (kmsg_dump_get_buffer(dumper, false, text, sizeof(text), &len)) { >> + /* Strip off the very last newline so we start at the bottom */ >> + if (first_iteration) { >> + len--; >> + first_iteration = false; >> + } >> + >> + if (!drm_panic_render_lines(&ctx, text, len)) >> + break; >> + } >> + >> + if (fb->funcs->panic_vunmap) >> + fb->funcs->panic_vunmap(fb, vmap); >> +} >> + >> +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) >> +{ >> + struct drm_framebuffer *fb; >> + struct drm_plane *plane; >> + struct drm_crtc *crtc; >> + >> + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) >> + return; >> + >> + if (dumper->max_reason == KMSG_DUMP_OOPS) >> + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, >> + dev->primary->index); >> + >> + drm_for_each_crtc(crtc, dev) { >> + if (!ww_mutex_trylock(&crtc->mutex.mutex)) >> + continue; >> + >> + if (!crtc->enabled || !crtc->primary) >> + goto crtc_unlock; >> + >> + if (!crtc->state || !crtc->state->active) >> + goto crtc_unlock; >> + >> + plane = crtc->primary; >> + if (!ww_mutex_trylock(&plane->mutex.mutex)) >> + goto crtc_unlock; >> + >> + /* >> + * TODO: Should we check plane_state->visible? >> + * It is not set on vc4 > > I think we should. I need to recheck vc4, because when I did this a year ago ->visible was not set on vc4. > We should also check whether that primary is connected > to the crtc (some hw you can reassign planes freely between crtc, and the > crtc->primary pointer is only used for compat with legacy ioctl). Like this? /* Check that the plane has not been reassigned */ if (plane->state->crtc != crtc) goto plane_unlock; > >> + if (!plane->state || !plane->state->visible) >> + */ >> + if (!plane->state) >> + goto plane_unlock; >> + >> + fb = plane->state->fb; >> + if (!fb || !fb->funcs->panic_vmap) > > Docs says this is optional, doesn't seem to be all that optional. I'd > check for this or a driver-specific ->panic_draw_xy instead. ->panic_vmap determines whether panic is supported or not, so in that sense it's optional. ->panic_draw_xy is optional with a default fallback for the linear case. >> + goto plane_unlock; >> + >> + /* >> + * fbcon puts out panic messages so stay away to avoid jumbled >> + * output. If vc->vc_mode != KD_TEXT fbcon won't put out >> + * messages (see vt_console_print). >> + */ >> + if (dev->fb_helper && dev->fb_helper->fb == fb) >> + goto plane_unlock; >> + >> + drm_panic_kmsg_render_screen(plane, dumper); >> +plane_unlock: >> + ww_mutex_unlock(&plane->mutex.mutex); >> +crtc_unlock: >> + ww_mutex_unlock(&crtc->mutex.mutex); >> + } >> +} >> + >> +static int drm_panic_dev_iter(struct device *dev, void *data) >> +{ >> + struct drm_minor *minor = dev_get_drvdata(dev); >> + >> + if (minor && minor->type == DRM_MINOR_PRIMARY) >> + drm_panic_try_dev(minor->dev, data); >> + >> + return 0; >> +} >> + >> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, >> + enum kmsg_dump_reason reason) >> +{ >> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > think that's good enough. Comment to that effect would be good e.g. > > /* based on klist, which uses only a spin_lock_irqsave, which we > * assume still works */ > > If we aim for perfect this should be a trylock still, maybe using our own > device list. > >> +} >> + >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { >> + .dump = drm_panic_kmsg_dump, >> + .max_reason = KMSG_DUMP_PANIC, >> +}; >> + >> +static ssize_t drm_panic_file_panic_write(struct file *file, >> + const char __user *user_buf, >> + size_t count, loff_t *ppos) >> +{ >> + unsigned long long val; >> + char buf[24]; >> + size_t size; >> + ssize_t ret; >> + >> + 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; >> + >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; >> + wmb(); >> + >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ >> + >> + if (val == 0) { >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); >> + kmsg_dump(KMSG_DUMP_OOPS); >> + } else if (val == 1) { >> + char *null_pointer = NULL; >> + >> + pr_info("Test panic screen using NULL pointer dereference\n"); >> + *null_pointer = 1; >> + } else { >> + return -EINVAL; >> + } > > This isn't quite what I had in mind, since it still kills the kernel (like > sysrq-trigger). If val == 0, it doesn't kill the kernel, it only dumps the kernel log. And it doesn't taint the kernel either. > Instead what I had in mind is to recreate the worst > possible panic context as much as feasible (disabling interrupts should be > a good start, maybe we can even do an nmi callback), and then call our > panic implementation. That way we can test the panic handler in a > non-destructive way (i.e. aside from last dmesg lines printed to the > screen nothing bad happens to the kernel: No real panic, no oops, no > tainting). The interrupt case I can do, nmi I have no idea. Noralf. > > And igt testcase could then exercise this, and CI run it. > >> + >> + return count; >> +} >> + >> +static const struct file_operations drm_panic_panic_ops = { >> + .write = drm_panic_file_panic_write, >> + .open = simple_open, >> + .llseek = default_llseek, >> +}; >> + >> +static struct dentry *drm_panic_d_panic; >> + >> +void __init drm_panic_init(struct dentry *debugfs_root) >> +{ >> + drm_panic_font8x8 = find_font("VGA8x8"); >> + drm_panic_font8x16 = find_font("VGA8x16"); >> + if (!drm_panic_font8x16) { >> + DRM_WARN("Couldn't find font, panic screen disabled\n"); >> + return; >> + } >> + >> + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, >> + debugfs_root, NULL, >> + &drm_panic_panic_ops); >> + kmsg_dump_register(&drm_panic_kmsg_dumper); >> +} >> + >> +void drm_panic_exit(struct dentry *debugfs_root) >> +{ >> + kmsg_dump_unregister(&drm_panic_kmsg_dumper); >> + debugfs_remove(drm_panic_d_panic); >> +} >> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >> index f0b34c977ec5..f3274798ecfe 100644 >> --- a/include/drm/drm_framebuffer.h >> +++ b/include/drm/drm_framebuffer.h >> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >> struct drm_file *file_priv, unsigned flags, >> unsigned color, struct drm_clip_rect *clips, >> unsigned num_clips); >> + >> + /** >> + * @panic_vmap: >> + * >> + * Optional callback for panic handling. >> + * >> + * For vmapping the selected framebuffer in a panic context. Must >> + * be super careful about locking (only trylocking allowed). >> + * >> + * RETURNS: >> + * >> + * NULL if it didn't work out, otherwise 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); >> + >> + /** >> + * @panic_vunmap: >> + * >> + * Optional callback for cleaning up after panic testing. >> + * >> + * Crtc and plane locks are released after this callback has run. >> + * vmap is the cookie returned by @panic_vmap. >> + */ >> + void (*panic_vunmap)(struct drm_framebuffer *fb, void *vmap); >> + >> + /** >> + * @panic_draw_xy: >> + * >> + * Optional callback for drawing pixels during panic. >> + * >> + * For drawing pixels onto a framebuffer prepared with @panic_vmap. >> + * vmap is the cookie returned by @panic_vmap. >> + * If it's not set, drm_framebuffer_panic_draw_xy() is used. >> + */ >> + void (*panic_draw_xy)(struct drm_framebuffer *fb, void *vmap, >> + int x, int y, bool foreground); >> }; >> >> /** >> @@ -293,4 +331,7 @@ int drm_framebuffer_plane_width(int width, >> int drm_framebuffer_plane_height(int height, >> const struct drm_framebuffer *fb, int plane); >> >> +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, >> + int x, int y, bool foreground); >> + >> #endif >> -- >> 2.20.1 >> >
Den 11.03.2019 20.29, skrev Daniel Vetter: > On Mon, Mar 11, 2019 at 08:23:38PM +0100, Daniel Vetter wrote: >> On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: >>> This adds support for outputting kernel messages on panic(). >>> A kernel message dumper is used to dump the log. The dumper iterates >>> over each DRM device and it's crtc's to find suitable framebuffers. >>> >>> All the other dumpers are run before this one except mtdoops. >>> Only atomic drivers are supported. >>> >>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> <snip> >>> +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) >>> +{ >>> + struct drm_framebuffer *fb; >>> + struct drm_plane *plane; >>> + struct drm_crtc *crtc; >>> + >>> + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) >>> + return; >>> + >>> + if (dumper->max_reason == KMSG_DUMP_OOPS) >>> + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, >>> + dev->primary->index); >>> + >>> + drm_for_each_crtc(crtc, dev) { >>> + if (!ww_mutex_trylock(&crtc->mutex.mutex)) >>> + continue; >>> + >>> + if (!crtc->enabled || !crtc->primary) >>> + goto crtc_unlock; >>> + >>> + if (!crtc->state || !crtc->state->active) >>> + goto crtc_unlock; >>> + >>> + plane = crtc->primary; >>> + if (!ww_mutex_trylock(&plane->mutex.mutex)) >>> + goto crtc_unlock; >>> + >>> + /* >>> + * TODO: Should we check plane_state->visible? >>> + * It is not set on vc4 >> >> I think we should. We should also check whether that primary is connected >> to the crtc (some hw you can reassign planes freely between crtc, and the >> crtc->primary pointer is only used for compat with legacy ioctl). >> >>> + if (!plane->state || !plane->state->visible) >>> + */ >>> + if (!plane->state) >>> + goto plane_unlock; >>> + >>> + fb = plane->state->fb; >>> + if (!fb || !fb->funcs->panic_vmap) >> >> Docs says this is optional, doesn't seem to be all that optional. I'd >> check for this or a driver-specific ->panic_draw_xy instead. >>> + goto plane_unlock; >>> + >>> + /* >>> + * fbcon puts out panic messages so stay away to avoid jumbled >>> + * output. If vc->vc_mode != KD_TEXT fbcon won't put out >>> + * messages (see vt_console_print). >>> + */ >>> + if (dev->fb_helper && dev->fb_helper->fb == fb) > > This is a bit a layering violation. Could we instead tell fbcon that it > shouldn't do panic handling for drm drivers? I think that would resolve > this overlap here in a much cleaner way. drm fbdev helpers already have > lots of oops_in_progress checks all over to make sure fbcon doesn't do > anything bad. That only leaves the actual rendering, which I think we can > stop too with a simple flag. > > Ofc only for atomic drivers which have this panic handling mode here > implemented. There used to be a fbdev flag FBINFO_CAN_FORCE_OUTPUT that controlled vc->vc_panic_force_write, but it's gone now I see. Noralf. > -Daniel > >>> + goto plane_unlock; >>> + >>> + drm_panic_kmsg_render_screen(plane, dumper); >>> +plane_unlock: >>> + ww_mutex_unlock(&plane->mutex.mutex); >>> +crtc_unlock: >>> + ww_mutex_unlock(&crtc->mutex.mutex); >>> + } >>> +}
On Mon, Mar 11, 2019 at 11:40:36PM +0100, Noralf Trønnes wrote: > > > Den 11.03.2019 20.29, skrev Daniel Vetter: > > On Mon, Mar 11, 2019 at 08:23:38PM +0100, Daniel Vetter wrote: > >> On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > >>> This adds support for outputting kernel messages on panic(). > >>> A kernel message dumper is used to dump the log. The dumper iterates > >>> over each DRM device and it's crtc's to find suitable framebuffers. > >>> > >>> All the other dumpers are run before this one except mtdoops. > >>> Only atomic drivers are supported. > >>> > >>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > <snip> > > >>> +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) > >>> +{ > >>> + struct drm_framebuffer *fb; > >>> + struct drm_plane *plane; > >>> + struct drm_crtc *crtc; > >>> + > >>> + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) > >>> + return; > >>> + > >>> + if (dumper->max_reason == KMSG_DUMP_OOPS) > >>> + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, > >>> + dev->primary->index); > >>> + > >>> + drm_for_each_crtc(crtc, dev) { > >>> + if (!ww_mutex_trylock(&crtc->mutex.mutex)) > >>> + continue; > >>> + > >>> + if (!crtc->enabled || !crtc->primary) > >>> + goto crtc_unlock; > >>> + > >>> + if (!crtc->state || !crtc->state->active) > >>> + goto crtc_unlock; > >>> + > >>> + plane = crtc->primary; > >>> + if (!ww_mutex_trylock(&plane->mutex.mutex)) > >>> + goto crtc_unlock; > >>> + > >>> + /* > >>> + * TODO: Should we check plane_state->visible? > >>> + * It is not set on vc4 > >> > >> I think we should. We should also check whether that primary is connected > >> to the crtc (some hw you can reassign planes freely between crtc, and the > >> crtc->primary pointer is only used for compat with legacy ioctl). > >> > >>> + if (!plane->state || !plane->state->visible) > >>> + */ > >>> + if (!plane->state) > >>> + goto plane_unlock; > >>> + > >>> + fb = plane->state->fb; > >>> + if (!fb || !fb->funcs->panic_vmap) > >> > >> Docs says this is optional, doesn't seem to be all that optional. I'd > >> check for this or a driver-specific ->panic_draw_xy instead. > >>> + goto plane_unlock; > >>> + > >>> + /* > >>> + * fbcon puts out panic messages so stay away to avoid jumbled > >>> + * output. If vc->vc_mode != KD_TEXT fbcon won't put out > >>> + * messages (see vt_console_print). > >>> + */ > >>> + if (dev->fb_helper && dev->fb_helper->fb == fb) > > > > This is a bit a layering violation. Could we instead tell fbcon that it > > shouldn't do panic handling for drm drivers? I think that would resolve > > this overlap here in a much cleaner way. drm fbdev helpers already have > > lots of oops_in_progress checks all over to make sure fbcon doesn't do > > anything bad. That only leaves the actual rendering, which I think we can > > stop too with a simple flag. > > > > Ofc only for atomic drivers which have this panic handling mode here > > implemented. > > There used to be a fbdev flag FBINFO_CAN_FORCE_OUTPUT that controlled > vc->vc_panic_force_write, but it's gone now I see. I killed that :-) Looking at those patches again, it's not what we wanted. It was used to force panic display even when not in KD_TEXT mode. What we want instead here is a flag to tell fbcon/vt to _never_ write dmesg to our console, not even when the console is in KD_TEXT mode. Because we have a separate panic handler to display it. Heck maybe that QR code thing could be resurrected eventually again. Totally untested snippet below is what I'm thinking of: diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 7f4c22a65f63..b08c63286ed0 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -282,6 +282,9 @@ static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) { struct fbcon_ops *ops = info->fbcon_par; + if (oops_in_progress && info->flags & NO_OOPS_OUTPUT) + return false; + return (info->state != FBINFO_STATE_RUNNING || vc->vc_mode != KD_TEXT || ops->graphics); } Plus then unconditionally rendering the oops output on the drm side, even if the current fb is the fbcon one. -Daniel
On Tue, Mar 12, 2019 at 10:53:20AM +0100, Daniel Vetter wrote: > On Mon, Mar 11, 2019 at 11:40:36PM +0100, Noralf Trønnes wrote: > > > > > > Den 11.03.2019 20.29, skrev Daniel Vetter: > > > On Mon, Mar 11, 2019 at 08:23:38PM +0100, Daniel Vetter wrote: > > >> On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > > >>> This adds support for outputting kernel messages on panic(). > > >>> A kernel message dumper is used to dump the log. The dumper iterates > > >>> over each DRM device and it's crtc's to find suitable framebuffers. > > >>> > > >>> All the other dumpers are run before this one except mtdoops. > > >>> Only atomic drivers are supported. > > >>> > > >>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > <snip> > > > > >>> +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) > > >>> +{ > > >>> + struct drm_framebuffer *fb; > > >>> + struct drm_plane *plane; > > >>> + struct drm_crtc *crtc; > > >>> + > > >>> + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) > > >>> + return; > > >>> + > > >>> + if (dumper->max_reason == KMSG_DUMP_OOPS) > > >>> + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, > > >>> + dev->primary->index); > > >>> + > > >>> + drm_for_each_crtc(crtc, dev) { > > >>> + if (!ww_mutex_trylock(&crtc->mutex.mutex)) > > >>> + continue; > > >>> + > > >>> + if (!crtc->enabled || !crtc->primary) > > >>> + goto crtc_unlock; > > >>> + > > >>> + if (!crtc->state || !crtc->state->active) > > >>> + goto crtc_unlock; > > >>> + > > >>> + plane = crtc->primary; > > >>> + if (!ww_mutex_trylock(&plane->mutex.mutex)) > > >>> + goto crtc_unlock; > > >>> + > > >>> + /* > > >>> + * TODO: Should we check plane_state->visible? > > >>> + * It is not set on vc4 > > >> > > >> I think we should. We should also check whether that primary is connected > > >> to the crtc (some hw you can reassign planes freely between crtc, and the > > >> crtc->primary pointer is only used for compat with legacy ioctl). > > >> > > >>> + if (!plane->state || !plane->state->visible) > > >>> + */ > > >>> + if (!plane->state) > > >>> + goto plane_unlock; > > >>> + > > >>> + fb = plane->state->fb; > > >>> + if (!fb || !fb->funcs->panic_vmap) > > >> > > >> Docs says this is optional, doesn't seem to be all that optional. I'd > > >> check for this or a driver-specific ->panic_draw_xy instead. > > >>> + goto plane_unlock; > > >>> + > > >>> + /* > > >>> + * fbcon puts out panic messages so stay away to avoid jumbled > > >>> + * output. If vc->vc_mode != KD_TEXT fbcon won't put out > > >>> + * messages (see vt_console_print). > > >>> + */ > > >>> + if (dev->fb_helper && dev->fb_helper->fb == fb) > > > > > > This is a bit a layering violation. Could we instead tell fbcon that it > > > shouldn't do panic handling for drm drivers? I think that would resolve > > > this overlap here in a much cleaner way. drm fbdev helpers already have > > > lots of oops_in_progress checks all over to make sure fbcon doesn't do > > > anything bad. That only leaves the actual rendering, which I think we can > > > stop too with a simple flag. > > > > > > Ofc only for atomic drivers which have this panic handling mode here > > > implemented. > > > > There used to be a fbdev flag FBINFO_CAN_FORCE_OUTPUT that controlled > > vc->vc_panic_force_write, but it's gone now I see. > > I killed that :-) > > Looking at those patches again, it's not what we wanted. It was used to > force panic display even when not in KD_TEXT mode. > > What we want instead here is a flag to tell fbcon/vt to _never_ write > dmesg to our console, not even when the console is in KD_TEXT mode. > Because we have a separate panic handler to display it. Heck maybe that QR > code thing could be resurrected eventually again. > > Totally untested snippet below is what I'm thinking of: > > > diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c > index 7f4c22a65f63..b08c63286ed0 100644 > --- a/drivers/video/fbdev/core/fbcon.c > +++ b/drivers/video/fbdev/core/fbcon.c > @@ -282,6 +282,9 @@ static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) > { > struct fbcon_ops *ops = info->fbcon_par; > > + if (oops_in_progress && info->flags & NO_OOPS_OUTPUT) > + return false; > + > return (info->state != FBINFO_STATE_RUNNING || > vc->vc_mode != KD_TEXT || ops->graphics); > } > > > Plus then unconditionally rendering the oops output on the drm side, even > if the current fb is the fbcon one. Hm thinking about this some more, I think this would allow us to replace all the oops_in_progress checks we have in drm_fb_helper.c with WARN_ON(oops_in_progress). So worthy cleanup on its own. Assuming I'm seeing this right ofc, very much possible I'm missing something :-) -Daniel
On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > This adds support for outputting kernel messages on panic(). > A kernel message dumper is used to dump the log. The dumper iterates > over each DRM device and it's crtc's to find suitable framebuffers. > > All the other dumpers are run before this one except mtdoops. > Only atomic drivers are supported. > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > --- > [...] > > diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > index f0b34c977ec5..f3274798ecfe 100644 > --- a/include/drm/drm_framebuffer.h > +++ b/include/drm/drm_framebuffer.h > @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > struct drm_file *file_priv, unsigned flags, > unsigned color, struct drm_clip_rect *clips, > unsigned num_clips); > + > + /** > + * @panic_vmap: > + * > + * Optional callback for panic handling. > + * > + * For vmapping the selected framebuffer in a panic context. Must > + * be super careful about locking (only trylocking allowed). > + * > + * RETURNS: > + * > + * NULL if it didn't work out, otherwise 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); FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon drivers: Framebuffers are normally tiled, writing to them with the CPU results in garbled output. With a discrete GPU having a large amount of VRAM, the framebuffer may not be directly CPU accessible at all. There would need to be a mechanism for switching scanout to a linear, CPU accessible framebuffer.
On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > Den 11.03.2019 20.23, skrev Daniel Vetter: > > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > >> This adds support for outputting kernel messages on panic(). > >> A kernel message dumper is used to dump the log. The dumper iterates > >> over each DRM device and it's crtc's to find suitable framebuffers. > >> > >> All the other dumpers are run before this one except mtdoops. > >> Only atomic drivers are supported. > >> > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. > > Actually it would ne nice if Darwish could pick it up since he will do > it on i915 which will be useful to a much broader audience. > If not I'll respin when I'm done with the drm_fb_helper refactoring. > > <snip> > > >> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c > >> new file mode 100644 > >> index 000000000000..4bca7f0bc369 > >> --- /dev/null > >> +++ b/drivers/gpu/drm/drm_panic.c > >> @@ -0,0 +1,363 @@ > >> +// SPDX-License-Identifier: GPL-2.0 > >> +// Copyright 2018 Noralf Trønnes > >> + > >> +#include <linux/debugfs.h> > >> +#include <linux/font.h> > >> +#include <linux/kernel.h> > >> +#include <linux/kmsg_dump.h> > >> +#include <linux/seq_file.h> > >> +#include <linux/slab.h> > >> +#include <linux/uaccess.h> > >> + > >> +#include <drm/drm_crtc.h> > >> +#include <drm/drm_device.h> > >> +#include <drm/drm_drv.h> > >> +#include <drm/drm_file.h> > >> +#include <drm/drm_framebuffer.h> > >> +#include <drm/drm_fb_helper.h> > >> +#include <drm/drm_plane.h> > >> +#include <drm/drm_print.h> > >> +#include <drm/drmP.h> > >> + > >> +#include "drm_internal.h" > >> + > >> +/* > >> + * The log lines in an ARM stack dump are 92 characters long > >> + * and 120 is a nice multiple for HD and 4K. > >> + */ > >> +#define DRM_PANIC_COLUMN_WIDTH 120 > > > > I think we should compute this dynamically from the actual fb width and > > font witdth. > > The font is picked based on whether it can fit 50 lines of text. > > The next rule is to decide when to add another column. I decided to use > a number that gets most log lines in one line on the screen. If most > lines get broken into two lines, there's not much use in an extra column. > > Do you have another idea on how to do this? > > (There's even a 16x32 font now that wasn't available when I did this.) Oh I had no idea that this is for multi-column oops printing. That's indeed neat for 4k screens. > > > > >> + > >> +struct drm_panic_ctx { > >> + struct drm_framebuffer *fb; > >> + unsigned int width; > >> + unsigned int height; > >> + void *vmap; > >> + > >> + const struct font_desc *font; > >> + unsigned int col_width; > >> + unsigned int bottom_y; > >> + size_t max_line_length; > >> + > >> + unsigned int x; > >> + unsigned int y; > >> +}; > >> + > >> +static const struct font_desc *drm_panic_font8x8, *drm_panic_font8x16; > >> + > >> +static void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > >> + int x, int y, bool fg) > >> +{ > >> + if (fb->funcs->panic_draw_xy) > >> + fb->funcs->panic_draw_xy(fb, vmap, x, y, fg); > >> + else > >> + drm_framebuffer_panic_draw_xy(fb, vmap, x, y, fg); > >> +} > >> + > >> +static void drm_panic_render_char(struct drm_panic_ctx *ctx, > >> + unsigned int offset, char c) > >> +{ > >> + unsigned int h, w, x, y; > >> + u8 fontline; > >> + > >> + for (h = 0; h < ctx->font->height; h++) { > >> + fontline = *(u8 *)(ctx->font->data + c * ctx->font->height + h); > >> + > >> + for (w = 0; w < ctx->font->width; w++) { > >> + x = ctx->x + (offset * ctx->font->width) + w; > >> + y = ctx->y + h; > >> + drm_panic_draw_xy(ctx->fb, ctx->vmap, x, y, > >> + fontline & BIT(7 - w)); > >> + } > >> + } > >> +} > >> + > >> +static void drm_panic_render_line(struct drm_panic_ctx *ctx, > >> + const char *line, size_t len) > >> +{ > >> + unsigned int i; > >> + > >> + for (i = 0; i < len; i++) > >> + drm_panic_render_char(ctx, i, line[i]); > >> + > >> + /* Clear out the rest of the line */ > >> + for (i = len; i < ctx->max_line_length; i++) > >> + drm_panic_render_char(ctx, i, ' '); > >> +} > >> + > >> +static bool drm_panic_newline(struct drm_panic_ctx *ctx) > >> +{ > >> + if (ctx->x == 0 && ctx->y == 0) > >> + return false; > >> + if (ctx->y == 0) { > >> + ctx->x -= ctx->col_width; > >> + ctx->y = ctx->bottom_y; > >> + } else { > >> + ctx->y -= ctx->font->height; > >> + } > >> + > >> + return true; > >> +} > >> + > >> +/* Render from bottom right most column */ > >> +static bool drm_panic_render_lines(struct drm_panic_ctx *ctx, > >> + const char *str, size_t len) > >> +{ > >> + size_t l, line_length; > >> + const char *pos; > >> + int i; > >> + > >> + while (len) { > >> + pos = str + len - 1; > >> + > >> + if (*pos == '\n') { > >> + len--; > >> + if (!drm_panic_newline(ctx)) > >> + return false; > >> + continue; > >> + } > >> + > >> + while (pos > str && *(pos - 1) != '\n') > >> + pos--; > >> + > >> + line_length = len - (pos - str); > >> + > >> + if (!line_length || len < line_length) { > >> + pr_err("%s: Bug! line_length=%zu len=%zu\n", > >> + __func__, line_length, len); > >> + return false; > >> + } > >> + > >> + len -= line_length; > >> + > >> + for (i = DIV_ROUND_UP(line_length, ctx->max_line_length) - 1; i >= 0; i--) { > >> + l = min(ctx->max_line_length, line_length - i * ctx->max_line_length); > >> + drm_panic_render_line(ctx, pos + (i * ctx->max_line_length), l); > >> + if (i && !drm_panic_newline(ctx)) > >> + return false; > >> + } > >> + } > >> + > >> + return true; > >> +} > >> + > >> +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, > >> + struct kmsg_dumper *dumper) > >> +{ > >> + struct drm_framebuffer *fb = plane->state->fb; > >> + bool first_iteration = true; > >> + struct drm_panic_ctx ctx; > >> + static char text[1024]; > >> + size_t len; > >> + > >> + ctx.vmap = fb->funcs->panic_vmap(fb); > >> + > >> + /* Print some info when testing */ > >> + if (dumper->max_reason == KMSG_DUMP_OOPS) { > >> + struct drm_format_name_buf format_name; > >> + > >> + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", > >> + __func__, fb->base.id, fb->width, fb->height, > >> + drm_get_format_name(fb->format->format, &format_name), > >> + ctx.vmap); > >> + } > >> + > >> + if (!ctx.vmap) > >> + return; > > > > For some framebuffers it might be useful to not require vmap, but instead > > have some page-by-page kind of access helper. Since preemptively setting > > up a vmap for all the non-continous buffers is a bit much, and we really > > can't set up the vmap in the panic handler. > > > > Maybe we should call this panic_prepare/panic_finish and > > s/vmap/cookie/, to make it entirely opaque? > > > > But a bit overengineering perhaps :-) > > > > I guess i915 wants something else than vmap, but I have no idea how a > page-by-page solution is to be implemented. i915 should be mostly fine, since we have a GTT for remapping and can make it look continuous. It might not be in the part of the GTT accessible by the cpu though. Wrt implementation: My idea would be to extract the pixel writing function and export it to drivers. The driver would then implement a ->panic_draw_xy function which looks up the right page, computes it address, computes the x/y offset within (taking into account tiling and stuff like that), and then uses the helper function to draw the right pixel value for the format at the given address. That's why I suggested that drivers need to either implement ->panic_prepare or ->panic_draw_xy. Since for this approach you might not need a ->panic_prepare/cleanup implementation, since it's all done in ->panic_draw_xy on a pixel-by-pixel basis. > When we get bootsplash, we will at least have a kernel address during > that phase for all drivers supporting it. Hm, not following what you mean here. Why is bootsplash special? > >> + > >> + ctx.fb = fb; > >> + ctx.width = drm_rect_width(&plane->state->src) >> 16; > >> + ctx.height = drm_rect_height(&plane->state->src) >> 16; > >> + > >> + /* > >> + * TODO: > >> + * Find which part of the fb that is visible. > >> + * Width and height are zero on vc4 > >> + */ > >> + if (!ctx.width || !ctx.height) { > >> + ctx.width = fb->width; > >> + ctx.height = fb->height; > >> + } > >> + > >> + /* Try to fit 50 lines */ > >> + if (ctx.height < 50 * 16 && drm_panic_font8x8) > >> + ctx.font = drm_panic_font8x8; > >> + else > >> + ctx.font = drm_panic_font8x16; > >> + > >> + ctx.col_width = DRM_PANIC_COLUMN_WIDTH * ctx.font->width; > >> + ctx.bottom_y = ((ctx.height / ctx.font->height) - 1) * ctx.font->height; > >> + > >> + if (ctx.width < 2 * ctx.col_width) > >> + ctx.max_line_length = ctx.width / ctx.font->width; > >> + else > >> + ctx.max_line_length = DRM_PANIC_COLUMN_WIDTH - 2; /* border=2 */ > >> + > >> + ctx.x = 0; > >> + ctx.y = ctx.bottom_y; > >> + if (ctx.width > ctx.col_width) > >> + ctx.x = ((ctx.width / ctx.col_width) - 1) * ctx.col_width; > >> + > >> + pr_debug("%s: font=%s %ux%u max_line_length=%zu (%u,%u)\n", > >> + __func__, ctx.font->name, ctx.width, ctx.height, > >> + ctx.max_line_length, ctx.x, ctx.y); > >> + > >> + kmsg_dump_rewind(dumper); > >> + > >> + /* The latest messages are fetched first */ > >> + while (kmsg_dump_get_buffer(dumper, false, text, sizeof(text), &len)) { > >> + /* Strip off the very last newline so we start at the bottom */ > >> + if (first_iteration) { > >> + len--; > >> + first_iteration = false; > >> + } > >> + > >> + if (!drm_panic_render_lines(&ctx, text, len)) > >> + break; > >> + } > >> + > >> + if (fb->funcs->panic_vunmap) > >> + fb->funcs->panic_vunmap(fb, vmap); > >> +} > >> + > >> +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) > >> +{ > >> + struct drm_framebuffer *fb; > >> + struct drm_plane *plane; > >> + struct drm_crtc *crtc; > >> + > >> + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) > >> + return; > >> + > >> + if (dumper->max_reason == KMSG_DUMP_OOPS) > >> + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, > >> + dev->primary->index); > >> + > >> + drm_for_each_crtc(crtc, dev) { > >> + if (!ww_mutex_trylock(&crtc->mutex.mutex)) > >> + continue; > >> + > >> + if (!crtc->enabled || !crtc->primary) > >> + goto crtc_unlock; > >> + > >> + if (!crtc->state || !crtc->state->active) > >> + goto crtc_unlock; > >> + > >> + plane = crtc->primary; > >> + if (!ww_mutex_trylock(&plane->mutex.mutex)) > >> + goto crtc_unlock; > >> + > >> + /* > >> + * TODO: Should we check plane_state->visible? > >> + * It is not set on vc4 > > > > I think we should. > > I need to recheck vc4, because when I did this a year ago ->visible was > not set on vc4. I think if you look at plane_state->src (i.e. the clipped src rect) then if that is non-zero I think you can assume ->visible is also set. Becuase that's both computed by the same helpers. And if that's not the case, fall back to ->src_x/y/h/w and just assume the plane is visible (usually those are the simpler drivers anyway). > > We should also check whether that primary is connected > > to the crtc (some hw you can reassign planes freely between crtc, and the > > crtc->primary pointer is only used for compat with legacy ioctl). > > Like this? > /* Check that the plane has not been reassigned */ > if (plane->state->crtc != crtc) > goto plane_unlock; Yup. > >> + if (!plane->state || !plane->state->visible) > >> + */ > >> + if (!plane->state) > >> + goto plane_unlock; > >> + > >> + fb = plane->state->fb; > >> + if (!fb || !fb->funcs->panic_vmap) > > > > Docs says this is optional, doesn't seem to be all that optional. I'd > > check for this or a driver-specific ->panic_draw_xy instead. > > ->panic_vmap determines whether panic is supported or not, so in that > sense it's optional. ->panic_draw_xy is optional with a default fallback > for the linear case. > > >> + goto plane_unlock; > >> + > >> + /* > >> + * fbcon puts out panic messages so stay away to avoid jumbled > >> + * output. If vc->vc_mode != KD_TEXT fbcon won't put out > >> + * messages (see vt_console_print). > >> + */ > >> + if (dev->fb_helper && dev->fb_helper->fb == fb) > >> + goto plane_unlock; > >> + > >> + drm_panic_kmsg_render_screen(plane, dumper); > >> +plane_unlock: > >> + ww_mutex_unlock(&plane->mutex.mutex); > >> +crtc_unlock: > >> + ww_mutex_unlock(&crtc->mutex.mutex); > >> + } > >> +} > >> + > >> +static int drm_panic_dev_iter(struct device *dev, void *data) > >> +{ > >> + struct drm_minor *minor = dev_get_drvdata(dev); > >> + > >> + if (minor && minor->type == DRM_MINOR_PRIMARY) > >> + drm_panic_try_dev(minor->dev, data); > >> + > >> + return 0; > >> +} > >> + > >> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > >> + enum kmsg_dump_reason reason) > >> +{ > >> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > think that's good enough. Comment to that effect would be good e.g. > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > * assume still works */ > > > > If we aim for perfect this should be a trylock still, maybe using our own > > device list. > > > >> +} > >> + > >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { > >> + .dump = drm_panic_kmsg_dump, > >> + .max_reason = KMSG_DUMP_PANIC, > >> +}; > >> + > >> +static ssize_t drm_panic_file_panic_write(struct file *file, > >> + const char __user *user_buf, > >> + size_t count, loff_t *ppos) > >> +{ > >> + unsigned long long val; > >> + char buf[24]; > >> + size_t size; > >> + ssize_t ret; > >> + > >> + 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; > >> + > >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > >> + wmb(); > >> + > >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ > >> + > >> + if (val == 0) { > >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > >> + kmsg_dump(KMSG_DUMP_OOPS); > >> + } else if (val == 1) { > >> + char *null_pointer = NULL; > >> + > >> + pr_info("Test panic screen using NULL pointer dereference\n"); > >> + *null_pointer = 1; > >> + } else { > >> + return -EINVAL; > >> + } > > > > This isn't quite what I had in mind, since it still kills the kernel (like > > sysrq-trigger). > > If val == 0, it doesn't kill the kernel, it only dumps the kernel log. > And it doesn't taint the kernel either. Ah I didn't realize that. Sounds like a good option to keep. > > Instead what I had in mind is to recreate the worst > > possible panic context as much as feasible (disabling interrupts should be > > a good start, maybe we can even do an nmi callback), and then call our > > panic implementation. That way we can test the panic handler in a > > non-destructive way (i.e. aside from last dmesg lines printed to the > > screen nothing bad happens to the kernel: No real panic, no oops, no > > tainting). > > The interrupt case I can do, nmi I have no idea. I just read the printk nmi code again and it looks like there's now even more special handling for issues happening in nmi context, so we should never see an oops from nmi context. So local_irq_disable() wrapping should be good enough for testing. -Daniel > > Noralf. > > > > > And igt testcase could then exercise this, and CI run it. > > > >> + > >> + return count; > >> +} > >> + > >> +static const struct file_operations drm_panic_panic_ops = { > >> + .write = drm_panic_file_panic_write, > >> + .open = simple_open, > >> + .llseek = default_llseek, > >> +}; > >> + > >> +static struct dentry *drm_panic_d_panic; > >> + > >> +void __init drm_panic_init(struct dentry *debugfs_root) > >> +{ > >> + drm_panic_font8x8 = find_font("VGA8x8"); > >> + drm_panic_font8x16 = find_font("VGA8x16"); > >> + if (!drm_panic_font8x16) { > >> + DRM_WARN("Couldn't find font, panic screen disabled\n"); > >> + return; > >> + } > >> + > >> + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, > >> + debugfs_root, NULL, > >> + &drm_panic_panic_ops); > >> + kmsg_dump_register(&drm_panic_kmsg_dumper); > >> +} > >> + > >> +void drm_panic_exit(struct dentry *debugfs_root) > >> +{ > >> + kmsg_dump_unregister(&drm_panic_kmsg_dumper); > >> + debugfs_remove(drm_panic_d_panic); > >> +} > >> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > >> index f0b34c977ec5..f3274798ecfe 100644 > >> --- a/include/drm/drm_framebuffer.h > >> +++ b/include/drm/drm_framebuffer.h > >> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >> struct drm_file *file_priv, unsigned flags, > >> unsigned color, struct drm_clip_rect *clips, > >> unsigned num_clips); > >> + > >> + /** > >> + * @panic_vmap: > >> + * > >> + * Optional callback for panic handling. > >> + * > >> + * For vmapping the selected framebuffer in a panic context. Must > >> + * be super careful about locking (only trylocking allowed). > >> + * > >> + * RETURNS: > >> + * > >> + * NULL if it didn't work out, otherwise 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); > >> + > >> + /** > >> + * @panic_vunmap: > >> + * > >> + * Optional callback for cleaning up after panic testing. > >> + * > >> + * Crtc and plane locks are released after this callback has run. > >> + * vmap is the cookie returned by @panic_vmap. > >> + */ > >> + void (*panic_vunmap)(struct drm_framebuffer *fb, void *vmap); > >> + > >> + /** > >> + * @panic_draw_xy: > >> + * > >> + * Optional callback for drawing pixels during panic. > >> + * > >> + * For drawing pixels onto a framebuffer prepared with @panic_vmap. > >> + * vmap is the cookie returned by @panic_vmap. > >> + * If it's not set, drm_framebuffer_panic_draw_xy() is used. > >> + */ > >> + void (*panic_draw_xy)(struct drm_framebuffer *fb, void *vmap, > >> + int x, int y, bool foreground); > >> }; > >> > >> /** > >> @@ -293,4 +331,7 @@ int drm_framebuffer_plane_width(int width, > >> int drm_framebuffer_plane_height(int height, > >> const struct drm_framebuffer *fb, int plane); > >> > >> +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > >> + int x, int y, bool foreground); > >> + > >> #endif > >> -- > >> 2.20.1 > >> > >
Den 12.03.2019 11.58, skrev Daniel Vetter: > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: >> >> >> Den 11.03.2019 20.23, skrev Daniel Vetter: >>> On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: >>>> This adds support for outputting kernel messages on panic(). >>>> A kernel message dumper is used to dump the log. The dumper iterates >>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>> >>>> All the other dumpers are run before this one except mtdoops. >>>> Only atomic drivers are supported. >>>> >>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>> <snip> >>>> +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, >>>> + struct kmsg_dumper *dumper) >>>> +{ >>>> + struct drm_framebuffer *fb = plane->state->fb; >>>> + bool first_iteration = true; >>>> + struct drm_panic_ctx ctx; >>>> + static char text[1024]; >>>> + size_t len; >>>> + >>>> + ctx.vmap = fb->funcs->panic_vmap(fb); >>>> + >>>> + /* Print some info when testing */ >>>> + if (dumper->max_reason == KMSG_DUMP_OOPS) { >>>> + struct drm_format_name_buf format_name; >>>> + >>>> + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", >>>> + __func__, fb->base.id, fb->width, fb->height, >>>> + drm_get_format_name(fb->format->format, &format_name), >>>> + ctx.vmap); >>>> + } >>>> + >>>> + if (!ctx.vmap) >>>> + return; >>> >>> For some framebuffers it might be useful to not require vmap, but instead >>> have some page-by-page kind of access helper. Since preemptively setting >>> up a vmap for all the non-continous buffers is a bit much, and we really >>> can't set up the vmap in the panic handler. >>> >>> Maybe we should call this panic_prepare/panic_finish and >>> s/vmap/cookie/, to make it entirely opaque? >>> >>> But a bit overengineering perhaps :-) >>> >> >> I guess i915 wants something else than vmap, but I have no idea how a >> page-by-page solution is to be implemented. > > i915 should be mostly fine, since we have a GTT for remapping and can make > it look continuous. It might not be in the part of the GTT accessible by > the cpu though. > > Wrt implementation: My idea would be to extract the pixel writing function > and export it to drivers. The driver would then implement a > ->panic_draw_xy function which looks up the right page, computes it > address, computes the x/y offset within (taking into account tiling and > stuff like that), and then uses the helper function to draw the right > pixel value for the format at the given address. > > That's why I suggested that drivers need to either implement > ->panic_prepare or ->panic_draw_xy. Since for this approach you might not > need a ->panic_prepare/cleanup implementation, since it's all done in > ->panic_draw_xy on a pixel-by-pixel basis. > >> When we get bootsplash, we will at least have a kernel address during >> that phase for all drivers supporting it. > > Hm, not following what you mean here. Why is bootsplash special? > Special in the sense that it's framebuffer will be linear and already have a virtual address for its backing buffer since that's a prerequisite for it to render the splash image. Noralf.
On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > > This adds support for outputting kernel messages on panic(). > > A kernel message dumper is used to dump the log. The dumper iterates > > over each DRM device and it's crtc's to find suitable framebuffers. > > > > All the other dumpers are run before this one except mtdoops. > > Only atomic drivers are supported. > > > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > --- > > [...] > > > > diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > > index f0b34c977ec5..f3274798ecfe 100644 > > --- a/include/drm/drm_framebuffer.h > > +++ b/include/drm/drm_framebuffer.h > > @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > > struct drm_file *file_priv, unsigned flags, > > unsigned color, struct drm_clip_rect *clips, > > unsigned num_clips); > > + > > + /** > > + * @panic_vmap: > > + * > > + * Optional callback for panic handling. > > + * > > + * For vmapping the selected framebuffer in a panic context. Must > > + * be super careful about locking (only trylocking allowed). > > + * > > + * RETURNS: > > + * > > + * NULL if it didn't work out, otherwise 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); > > FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon > drivers: > > Framebuffers are normally tiled, writing to them with the CPU results in > garbled output. > > With a discrete GPU having a large amount of VRAM, the framebuffer may > not be directly CPU accessible at all. > > > There would need to be a mechanism for switching scanout to a linear, > CPU accessible framebuffer. I suppose panic_vmap() could just provide a linear temp buffer to the panic handler, and panic_unmap() could copy the contents over to the real fb. That said, this approach of scribbling over the primary plane's framebuffer has some clear limitations: * something may overwrite the oops message before the user can even read it * there may be other planes obscuring part or all of the primary plane Also scribbling over the user's framebuffer seems rather rude to me, so I'm thinking this approach should be limited to kernel panics only.
Den 12.03.2019 17.17, skrev Ville Syrjälä: > On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>> This adds support for outputting kernel messages on panic(). >>> A kernel message dumper is used to dump the log. The dumper iterates >>> over each DRM device and it's crtc's to find suitable framebuffers. >>> >>> All the other dumpers are run before this one except mtdoops. >>> Only atomic drivers are supported. >>> >>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>> --- >>> [...] >>> >>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>> index f0b34c977ec5..f3274798ecfe 100644 >>> --- a/include/drm/drm_framebuffer.h >>> +++ b/include/drm/drm_framebuffer.h >>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>> struct drm_file *file_priv, unsigned flags, >>> unsigned color, struct drm_clip_rect *clips, >>> unsigned num_clips); >>> + >>> + /** >>> + * @panic_vmap: >>> + * >>> + * Optional callback for panic handling. >>> + * >>> + * For vmapping the selected framebuffer in a panic context. Must >>> + * be super careful about locking (only trylocking allowed). >>> + * >>> + * RETURNS: >>> + * >>> + * NULL if it didn't work out, otherwise 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); >> >> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >> drivers: >> >> Framebuffers are normally tiled, writing to them with the CPU results in >> garbled output. >> In which case the driver needs to support the ->panic_draw_xy callback, or maybe it's possible to make a generic helper for tiled buffers. >> With a discrete GPU having a large amount of VRAM, the framebuffer may >> not be directly CPU accessible at all. >> I would have been nice to know how Windows works around this. >> >> There would need to be a mechanism for switching scanout to a linear, >> CPU accessible framebuffer. > > I suppose panic_vmap() could just provide a linear temp buffer > to the panic handler, and panic_unmap() could copy the contents > over to the real fb. > > That said, this approach of scribbling over the primary plane's > framebuffer has some clear limitations: > * something may overwrite the oops message before the user > can even read it When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have been stopped. See panic(). > * there may be other planes obscuring part or all of the > primary plane > Yeah, this is a problem, again I wonder how Windows deals with this. > Also scribbling over the user's framebuffer seems rather rude > to me, so I'm thinking this approach should be limited to kernel > panics only. > Yes this will only happen on kernel panics: panic() -> kmsg_dump() -> drm_panic_kmsg_dump() (Unless invoking through debugfs ofc) Noralf.
On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: > > > Den 12.03.2019 17.17, skrev Ville Syrjälä: > > On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > >> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > >>> This adds support for outputting kernel messages on panic(). > >>> A kernel message dumper is used to dump the log. The dumper iterates > >>> over each DRM device and it's crtc's to find suitable framebuffers. > >>> > >>> All the other dumpers are run before this one except mtdoops. > >>> Only atomic drivers are supported. > >>> > >>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > >>> --- > >>> [...] > >>> > >>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > >>> index f0b34c977ec5..f3274798ecfe 100644 > >>> --- a/include/drm/drm_framebuffer.h > >>> +++ b/include/drm/drm_framebuffer.h > >>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >>> struct drm_file *file_priv, unsigned flags, > >>> unsigned color, struct drm_clip_rect *clips, > >>> unsigned num_clips); > >>> + > >>> + /** > >>> + * @panic_vmap: > >>> + * > >>> + * Optional callback for panic handling. > >>> + * > >>> + * For vmapping the selected framebuffer in a panic context. Must > >>> + * be super careful about locking (only trylocking allowed). > >>> + * > >>> + * RETURNS: > >>> + * > >>> + * NULL if it didn't work out, otherwise 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); > >> > >> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon > >> drivers: > >> > >> Framebuffers are normally tiled, writing to them with the CPU results in > >> garbled output. > >> > > In which case the driver needs to support the ->panic_draw_xy callback, > or maybe it's possible to make a generic helper for tiled buffers. > > >> With a discrete GPU having a large amount of VRAM, the framebuffer may > >> not be directly CPU accessible at all. > >> > > I would have been nice to know how Windows works around this. > > >> > >> There would need to be a mechanism for switching scanout to a linear, > >> CPU accessible framebuffer. > > > > I suppose panic_vmap() could just provide a linear temp buffer > > to the panic handler, and panic_unmap() could copy the contents > > over to the real fb. > > > > That said, this approach of scribbling over the primary plane's > > framebuffer has some clear limitations: > > * something may overwrite the oops message before the user > > can even read it > > When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have > been stopped. See panic(). GPUs etc. may still be executing away. > > > * there may be other planes obscuring part or all of the > > primary plane > > > > Yeah, this is a problem, again I wonder how Windows deals with this. Probably just disables all other planes. Not that it uses planes all that heavily. > > > Also scribbling over the user's framebuffer seems rather rude > > to me, so I'm thinking this approach should be limited to kernel > > panics only. > > > > Yes this will only happen on kernel panics: > > panic() -> kmsg_dump() -> drm_panic_kmsg_dump() > > (Unless invoking through debugfs ofc) I thought you set the max_level or whatever to OOPS. Doesn't that mean it gets involved for non-panics as well?
Den 12.03.2019 18.25, skrev Ville Syrjälä: > On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: >> >> >> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>> This adds support for outputting kernel messages on panic(). >>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>> >>>>> All the other dumpers are run before this one except mtdoops. >>>>> Only atomic drivers are supported. >>>>> >>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>> --- >>>>> [...] >>>>> >>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>> --- a/include/drm/drm_framebuffer.h >>>>> +++ b/include/drm/drm_framebuffer.h >>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>> struct drm_file *file_priv, unsigned flags, >>>>> unsigned color, struct drm_clip_rect *clips, >>>>> unsigned num_clips); >>>>> + >>>>> + /** >>>>> + * @panic_vmap: >>>>> + * >>>>> + * Optional callback for panic handling. >>>>> + * >>>>> + * For vmapping the selected framebuffer in a panic context. Must >>>>> + * be super careful about locking (only trylocking allowed). >>>>> + * >>>>> + * RETURNS: >>>>> + * >>>>> + * NULL if it didn't work out, otherwise 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); >>>> >>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >>>> drivers: >>>> >>>> Framebuffers are normally tiled, writing to them with the CPU results in >>>> garbled output. >>>> >> >> In which case the driver needs to support the ->panic_draw_xy callback, >> or maybe it's possible to make a generic helper for tiled buffers. >> >>>> With a discrete GPU having a large amount of VRAM, the framebuffer may >>>> not be directly CPU accessible at all. >>>> >> >> I would have been nice to know how Windows works around this. >> >>>> >>>> There would need to be a mechanism for switching scanout to a linear, >>>> CPU accessible framebuffer. >>> >>> I suppose panic_vmap() could just provide a linear temp buffer >>> to the panic handler, and panic_unmap() could copy the contents >>> over to the real fb. >>> >>> That said, this approach of scribbling over the primary plane's >>> framebuffer has some clear limitations: >>> * something may overwrite the oops message before the user >>> can even read it >> >> When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have >> been stopped. See panic(). > > GPUs etc. may still be executing away. > Would it be safe to stop it in a panic situation? It would ofc be bad to crash the box even harder. >> >>> * there may be other planes obscuring part or all of the >>> primary plane >>> >> >> Yeah, this is a problem, again I wonder how Windows deals with this. > > Probably just disables all other planes. Not that it uses planes > all that heavily. > >> >>> Also scribbling over the user's framebuffer seems rather rude >>> to me, so I'm thinking this approach should be limited to kernel >>> panics only. >>> >> >> Yes this will only happen on kernel panics: >> >> panic() -> kmsg_dump() -> drm_panic_kmsg_dump() >> >> (Unless invoking through debugfs ofc) > > I thought you set the max_level or whatever to OOPS. Doesn't that mean > it gets involved for non-panics as well? > I do that in the debugfs code, but I can't remember why I lower level, I think can just change the level when invoking the dumper: drm_panic_file_panic_write(...) - kmsg_dump(KMSG_DUMP_OOPS); + kmsg_dump(KMSG_DUMP_PANIC); This is the dumper config: static struct kmsg_dumper drm_panic_kmsg_dumper = { .dump = drm_panic_kmsg_dump, .max_reason = KMSG_DUMP_PANIC, };
Den 12.03.2019 18.37, skrev Noralf Trønnes: > > > Den 12.03.2019 18.25, skrev Ville Syrjälä: >> On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: >>> >>> >>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>> This adds support for outputting kernel messages on panic(). >>>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>>> >>>>>> All the other dumpers are run before this one except mtdoops. >>>>>> Only atomic drivers are supported. >>>>>> >>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>> --- >>>>>> [...] >>>>>> >>>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>> --- a/include/drm/drm_framebuffer.h >>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>> struct drm_file *file_priv, unsigned flags, >>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>> unsigned num_clips); >>>>>> + >>>>>> + /** >>>>>> + * @panic_vmap: >>>>>> + * >>>>>> + * Optional callback for panic handling. >>>>>> + * >>>>>> + * For vmapping the selected framebuffer in a panic context. Must >>>>>> + * be super careful about locking (only trylocking allowed). >>>>>> + * >>>>>> + * RETURNS: >>>>>> + * >>>>>> + * NULL if it didn't work out, otherwise 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); >>>>> >>>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >>>>> drivers: >>>>> >>>>> Framebuffers are normally tiled, writing to them with the CPU results in >>>>> garbled output. >>>>> >>> >>> In which case the driver needs to support the ->panic_draw_xy callback, >>> or maybe it's possible to make a generic helper for tiled buffers. >>> >>>>> With a discrete GPU having a large amount of VRAM, the framebuffer may >>>>> not be directly CPU accessible at all. >>>>> >>> >>> I would have been nice to know how Windows works around this. >>> >>>>> >>>>> There would need to be a mechanism for switching scanout to a linear, >>>>> CPU accessible framebuffer. >>>> >>>> I suppose panic_vmap() could just provide a linear temp buffer >>>> to the panic handler, and panic_unmap() could copy the contents >>>> over to the real fb. >>>> >>>> That said, this approach of scribbling over the primary plane's >>>> framebuffer has some clear limitations: >>>> * something may overwrite the oops message before the user >>>> can even read it >>> >>> When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have >>> been stopped. See panic(). >> >> GPUs etc. may still be executing away. >> > > Would it be safe to stop it in a panic situation? It would ofc be bad to > crash the box even harder. > >>> >>>> * there may be other planes obscuring part or all of the >>>> primary plane >>>> >>> >>> Yeah, this is a problem, again I wonder how Windows deals with this. >> >> Probably just disables all other planes. Not that it uses planes >> all that heavily. >> >>> >>>> Also scribbling over the user's framebuffer seems rather rude >>>> to me, so I'm thinking this approach should be limited to kernel >>>> panics only. >>>> >>> >>> Yes this will only happen on kernel panics: >>> >>> panic() -> kmsg_dump() -> drm_panic_kmsg_dump() >>> >>> (Unless invoking through debugfs ofc) >> >> I thought you set the max_level or whatever to OOPS. Doesn't that mean >> it gets involved for non-panics as well? >> > > I do that in the debugfs code, but I can't remember why I lower level, Now I remember, it is so I can catch the null pointer debugfs test, but that's not necessary since this is designed for panics, so I should remove that test. Noralf. > I > think can just change the level when invoking the dumper: > drm_panic_file_panic_write(...) > - kmsg_dump(KMSG_DUMP_OOPS); > + kmsg_dump(KMSG_DUMP_PANIC); > > > This is the dumper config: > > static struct kmsg_dumper drm_panic_kmsg_dumper = { > .dump = drm_panic_kmsg_dump, > .max_reason = KMSG_DUMP_PANIC, > }; > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel >
On Tue, Mar 12, 2019 at 06:37:57PM +0100, Noralf Trønnes wrote: > > > Den 12.03.2019 18.25, skrev Ville Syrjälä: > > On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: > >> > >> > >> Den 12.03.2019 17.17, skrev Ville Syrjälä: > >>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > >>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > >>>>> This adds support for outputting kernel messages on panic(). > >>>>> A kernel message dumper is used to dump the log. The dumper iterates > >>>>> over each DRM device and it's crtc's to find suitable framebuffers. > >>>>> > >>>>> All the other dumpers are run before this one except mtdoops. > >>>>> Only atomic drivers are supported. > >>>>> > >>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > >>>>> --- > >>>>> [...] > >>>>> > >>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > >>>>> index f0b34c977ec5..f3274798ecfe 100644 > >>>>> --- a/include/drm/drm_framebuffer.h > >>>>> +++ b/include/drm/drm_framebuffer.h > >>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >>>>> struct drm_file *file_priv, unsigned flags, > >>>>> unsigned color, struct drm_clip_rect *clips, > >>>>> unsigned num_clips); > >>>>> + > >>>>> + /** > >>>>> + * @panic_vmap: > >>>>> + * > >>>>> + * Optional callback for panic handling. > >>>>> + * > >>>>> + * For vmapping the selected framebuffer in a panic context. Must > >>>>> + * be super careful about locking (only trylocking allowed). > >>>>> + * > >>>>> + * RETURNS: > >>>>> + * > >>>>> + * NULL if it didn't work out, otherwise 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); > >>>> > >>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon > >>>> drivers: > >>>> > >>>> Framebuffers are normally tiled, writing to them with the CPU results in > >>>> garbled output. > >>>> > >> > >> In which case the driver needs to support the ->panic_draw_xy callback, > >> or maybe it's possible to make a generic helper for tiled buffers. > >> > >>>> With a discrete GPU having a large amount of VRAM, the framebuffer may > >>>> not be directly CPU accessible at all. > >>>> > >> > >> I would have been nice to know how Windows works around this. > >> > >>>> > >>>> There would need to be a mechanism for switching scanout to a linear, > >>>> CPU accessible framebuffer. > >>> > >>> I suppose panic_vmap() could just provide a linear temp buffer > >>> to the panic handler, and panic_unmap() could copy the contents > >>> over to the real fb. > >>> > >>> That said, this approach of scribbling over the primary plane's > >>> framebuffer has some clear limitations: > >>> * something may overwrite the oops message before the user > >>> can even read it > >> > >> When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have > >> been stopped. See panic(). > > > > GPUs etc. may still be executing away. > > > > Would it be safe to stop it in a panic situation? It would ofc be bad to > crash the box even harder. Some drivers/devices may have working (and hopefully even reliable) gpu reset, some may not.
Hi, [[ CCing John for the trylock parts ]] On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > Den 11.03.2019 20.23, skrev Daniel Vetter: > > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > >> This adds support for outputting kernel messages on panic(). > >> A kernel message dumper is used to dump the log. The dumper iterates > >> over each DRM device and it's crtc's to find suitable framebuffers. > >> > >> All the other dumpers are run before this one except mtdoops. > >> Only atomic drivers are supported. > >> > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. > > Actually it would ne nice if Darwish could pick it up since he will do > it on i915 which will be useful to a much broader audience. > If not I'll respin when I'm done with the drm_fb_helper refactoring. > Yup, I'll be more than happy to do this.. while preserving all of Noralf's authorship and copyright notices of course. I guess it can be: - Handle the comments posted by Daniel and others (I'll post some questions too) - Add the necessary i915 specific bits - Test, post v3/v4/../vn. Rinse and repeat. Keep it local at dri-devel until getting the necessary S-o-Bs. - Post to wider audience (some feedback from distribution folks would also be nice, before posting to lkml) More comments below.. [...] > >> + > >> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > >> + enum kmsg_dump_reason reason) > >> +{ > >> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > think that's good enough. Comment to that effect would be good e.g. > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > * assume still works */ > > > > If we aim for perfect this should be a trylock still, maybe using our own > > device list. > > I definitely agree here. The lock may already be locked either by a stopped CPU, or by the very same CPU we execute panic() on (e.g. NMI panic() on the printing CPU). This is why it's very common for example in serial consoles, which are usually careful about re-entrance and panic contexts, to do: xxxxxx_console_write(...) { if (oops_in_progress) locked = spin_trylock_irqsave(&port->lock, flags); else spin_lock_irqsave(&port->lock, flags); } I'm quite positive we should do the same for panic drm drivers. John? > >> +} > >> + > >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { > >> + .dump = drm_panic_kmsg_dump, > >> + .max_reason = KMSG_DUMP_PANIC, > >> +}; > >> + > >> +static ssize_t drm_panic_file_panic_write(struct file *file, > >> + const char __user *user_buf, > >> + size_t count, loff_t *ppos) > >> +{ > >> + unsigned long long val; > >> + char buf[24]; > >> + size_t size; > >> + ssize_t ret; > >> + > >> + 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; > >> + > >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > >> + wmb(); > >> + > >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ > >> + > >> + if (val == 0) { > >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > >> + kmsg_dump(KMSG_DUMP_OOPS); > >> + } else if (val == 1) { > >> + char *null_pointer = NULL; > >> + > >> + pr_info("Test panic screen using NULL pointer dereference\n"); > >> + *null_pointer = 1; > >> + } else { > >> + return -EINVAL; > >> + } > > > > This isn't quite what I had in mind, since it still kills the kernel (like > > sysrq-trigger). > > If val == 0, it doesn't kill the kernel, it only dumps the kernel log. > And it doesn't taint the kernel either. > > > Instead what I had in mind is to recreate the worst > > possible panic context as much as feasible (disabling interrupts should be > > a good start, maybe we can even do an nmi callback), and then call our > > panic implementation. That way we can test the panic handler in a > > non-destructive way (i.e. aside from last dmesg lines printed to the > > screen nothing bad happens to the kernel: No real panic, no oops, no > > tainting). > > The interrupt case I can do, nmi I have no idea. > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP would be a nice non-destructive test-case emulation. thanks! -- darwi http://darwish.chasingpointers.com
Hi, On Tue, Mar 12, 2019 at 11:58:10AM +0100, Daniel Vetter wrote: > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > > > > Den 11.03.2019 20.23, skrev Daniel Vetter: > > > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: [...] > > >> +} > > >> + > > >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { > > >> + .dump = drm_panic_kmsg_dump, > > >> + .max_reason = KMSG_DUMP_PANIC, > > >> +}; > > >> + > > >> +static ssize_t drm_panic_file_panic_write(struct file *file, > > >> + const char __user *user_buf, > > >> + size_t count, loff_t *ppos) > > >> +{ > > >> + unsigned long long val; > > >> + char buf[24]; > > >> + size_t size; > > >> + ssize_t ret; > > >> + > > >> + 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; > > >> + > > >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > > >> + wmb(); > > >> + > > >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ > > >> + > > >> + if (val == 0) { > > >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > > >> + kmsg_dump(KMSG_DUMP_OOPS); > > >> + } else if (val == 1) { > > >> + char *null_pointer = NULL; > > >> + > > >> + pr_info("Test panic screen using NULL pointer dereference\n"); > > >> + *null_pointer = 1; > > >> + } else { > > >> + return -EINVAL; > > >> + } >> > > > > > This isn't quite what I had in mind, since it still kills the kernel (like > > > sysrq-trigger). > > > > If val == 0, it doesn't kill the kernel, it only dumps the kernel log. > > And it doesn't taint the kernel either. > > Ah I didn't realize that. Sounds like a good option to keep. > > > > Instead what I had in mind is to recreate the worst > > > possible panic context as much as feasible (disabling interrupts should be > > > a good start, maybe we can even do an nmi callback), and then call our > > > panic implementation. That way we can test the panic handler in a > > > non-destructive way (i.e. aside from last dmesg lines printed to the > > > screen nothing bad happens to the kernel: No real panic, no oops, no > > > tainting). > > > > The interrupt case I can do, nmi I have no idea. > > I just read the printk nmi code again and it looks like there's now even > more special handling for issues happening in nmi context, so we should > never see an oops from nmi context. So local_irq_disable() wrapping should > be good enough for testing. > -Daniel > Hmmm, John is on a mission to change that soon: - https://lore.kernel.org/lkml/20190212143003.48446-1-john.ogness@linutronix.de - https://lwn.net/Articles/780556 A v2 is on the way, and there's a lot of interest from different groups, including RT, to get that in. So I guess it would be nice to cover NMI contexts from the start, not necessarily from a non-destructive CI testing perspective, but at least while doing normal tests and code reviews. (Is it possible to do non-destructive NMI tests? I don't know..) thanks! -- darwi http://darwish.chasingpointers.com
Hi, [[ CCing John for the trylock parts ]] On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > Den 11.03.2019 20.23, skrev Daniel Vetter: > > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > >> This adds support for outputting kernel messages on panic(). > >> A kernel message dumper is used to dump the log. The dumper iterates > >> over each DRM device and it's crtc's to find suitable framebuffers. > >> > >> All the other dumpers are run before this one except mtdoops. > >> Only atomic drivers are supported. > >> > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. > > Actually it would ne nice if Darwish could pick it up since he will do > it on i915 which will be useful to a much broader audience. > If not I'll respin when I'm done with the drm_fb_helper refactoring. > Yup, I'll be more than happy to do this.. while preserving all of Noralf's authorship and copyright notices of course. I guess it can be: - Handle the comments posted by Daniel and others (I'll post some questions too) - Add the necessary i915 specific bits - Test, post v3/v4/../vn. Rinse and repeat. Keep it local at dri-devel until getting the necessary S-o-Bs. - Post to wider audience (some feedback from distribution folks would also be nice, before posting to lkml) More comments below.. [...] > >> + > >> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > >> + enum kmsg_dump_reason reason) > >> +{ > >> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > think that's good enough. Comment to that effect would be good e.g. > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > * assume still works */ > > > > If we aim for perfect this should be a trylock still, maybe using our own > > device list. > > I definitely agree here. The lock may already be locked either by a stopped CPU, or by the very same CPU we execute panic() on (e.g. NMI panic() on the printing CPU). This is why it's very common for example in serial consoles, which are usually careful about re-entrance and panic contexts, to do: xxxxxx_console_write(...) { if (oops_in_progress) locked = spin_trylock_irqsave(&port->lock, flags); else spin_lock_irqsave(&port->lock, flags); } I'm quite positive we should do the same for panic drm drivers. John? > >> +} > >> + > >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { > >> + .dump = drm_panic_kmsg_dump, > >> + .max_reason = KMSG_DUMP_PANIC, > >> +}; > >> + > >> +static ssize_t drm_panic_file_panic_write(struct file *file, > >> + const char __user *user_buf, > >> + size_t count, loff_t *ppos) > >> +{ > >> + unsigned long long val; > >> + char buf[24]; > >> + size_t size; > >> + ssize_t ret; > >> + > >> + 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; > >> + > >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > >> + wmb(); > >> + > >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ > >> + > >> + if (val == 0) { > >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > >> + kmsg_dump(KMSG_DUMP_OOPS); > >> + } else if (val == 1) { > >> + char *null_pointer = NULL; > >> + > >> + pr_info("Test panic screen using NULL pointer dereference\n"); > >> + *null_pointer = 1; > >> + } else { > >> + return -EINVAL; > >> + } > > > > This isn't quite what I had in mind, since it still kills the kernel (like > > sysrq-trigger). > > If val == 0, it doesn't kill the kernel, it only dumps the kernel log. > And it doesn't taint the kernel either. > > > Instead what I had in mind is to recreate the worst > > possible panic context as much as feasible (disabling interrupts should be > > a good start, maybe we can even do an nmi callback), and then call our > > panic implementation. That way we can test the panic handler in a > > non-destructive way (i.e. aside from last dmesg lines printed to the > > screen nothing bad happens to the kernel: No real panic, no oops, no > > tainting). > > The interrupt case I can do, nmi I have no idea. > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP would be a nice non-destructive test-case emulation. thanks! -- darwi http://darwish.chasingpointers.com
On 2019-03-12, Ahmed S. Darwish <darwish.07@gmail.com> wrote: >>>> + >>>> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, >>>> + enum kmsg_dump_reason reason) >>>> +{ >>>> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); >>> >>> class_for_each_device uses klist, which only uses an irqsave >>> spinlock. I think that's good enough. Comment to that effect would >>> be good e.g. >>> >>> /* based on klist, which uses only a spin_lock_irqsave, which we >>> * assume still works */ >>> >>> If we aim for perfect this should be a trylock still, maybe using >>> our own device list. >>> > > I definitely agree here. > > The lock may already be locked either by a stopped CPU, or by the > very same CPU we execute panic() on (e.g. NMI panic() on the > printing CPU). > > This is why it's very common for example in serial consoles, which > are usually careful about re-entrance and panic contexts, to do: > > xxxxxx_console_write(...) { > if (oops_in_progress) > locked = spin_trylock_irqsave(&port->lock, flags); > else > spin_lock_irqsave(&port->lock, flags); > } > > I'm quite positive we should do the same for panic drm drivers. This construction will continue, even if the trylock fails. It only makes sense to do this if the driver has a chance of being successful. Ignoring locks is a serious issue. I personally am quite unhappy that the serial drivers do this, which was part of my motivation for the new printk design I'm working on. If the driver is not capable of doing something useful on a failed trylock, then I recommend just skipping that device. Maybe trying it again later after trying all the devices? John Ogness
Am 12.03.19 um 19:02 schrieb Ville Syrjälä: > On Tue, Mar 12, 2019 at 06:37:57PM +0100, Noralf Trønnes wrote: >> >> Den 12.03.2019 18.25, skrev Ville Syrjälä: >>> On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: >>>> >>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>>>> >>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>> Only atomic drivers are supported. >>>>>>> >>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>> --- >>>>>>> [...] >>>>>>> >>>>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>> unsigned num_clips); >>>>>>> + >>>>>>> + /** >>>>>>> + * @panic_vmap: >>>>>>> + * >>>>>>> + * Optional callback for panic handling. >>>>>>> + * >>>>>>> + * For vmapping the selected framebuffer in a panic context. Must >>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>> + * >>>>>>> + * RETURNS: >>>>>>> + * >>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >>>>>> drivers: >>>>>> >>>>>> Framebuffers are normally tiled, writing to them with the CPU results in >>>>>> garbled output. >>>>>> >>>> In which case the driver needs to support the ->panic_draw_xy callback, >>>> or maybe it's possible to make a generic helper for tiled buffers. >>>> >>>>>> With a discrete GPU having a large amount of VRAM, the framebuffer may >>>>>> not be directly CPU accessible at all. >>>>>> >>>> I would have been nice to know how Windows works around this. >>>> >>>>>> There would need to be a mechanism for switching scanout to a linear, >>>>>> CPU accessible framebuffer. >>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>> to the panic handler, and panic_unmap() could copy the contents >>>>> over to the real fb. >>>>> >>>>> That said, this approach of scribbling over the primary plane's >>>>> framebuffer has some clear limitations: >>>>> * something may overwrite the oops message before the user >>>>> can even read it >>>> When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have >>>> been stopped. See panic(). >>> GPUs etc. may still be executing away. >>> >> Would it be safe to stop it in a panic situation? It would ofc be bad to >> crash the box even harder. > Some drivers/devices may have working (and hopefully even reliable) > gpu reset, some may not. Even if GPU reset is working, it certainly doesn't under a panic() condition when all other CPUs are already stopped. I don't see how this approach should ever work reliable. Christian.
On Tue, Mar 12, 2019 at 11:13:03PM +0100, Ahmed S. Darwish wrote: > Hi, > > [[ CCing John for the trylock parts ]] > > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > > > > Den 11.03.2019 20.23, skrev Daniel Vetter: > > > On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: > > >> This adds support for outputting kernel messages on panic(). > > >> A kernel message dumper is used to dump the log. The dumper iterates > > >> over each DRM device and it's crtc's to find suitable framebuffers. > > >> > > >> All the other dumpers are run before this one except mtdoops. > > >> Only atomic drivers are supported. > > >> > > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > > > Bunch of comments/ideas for you or Darwish below, whoever picks this up. > > > > Actually it would ne nice if Darwish could pick it up since he will do > > it on i915 which will be useful to a much broader audience. > > If not I'll respin when I'm done with the drm_fb_helper refactoring. > > > > Yup, I'll be more than happy to do this.. while preserving all of > Noralf's authorship and copyright notices of course. > > I guess it can be: > > - Handle the comments posted by Daniel and others (I'll post > some questions too) > > - Add the necessary i915 specific bits > > - Test, post v3/v4/../vn. Rinse and repeat. Keep it local at > dri-devel until getting the necessary S-o-Bs. > > - Post to wider audience (some feedback from distribution folks > would also be nice, before posting to lkml) > > More comments below.. > > [...] > > > >> + > > >> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > > >> + enum kmsg_dump_reason reason) > > >> +{ > > >> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > > > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > > think that's good enough. Comment to that effect would be good e.g. > > > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > > * assume still works */ > > > > > > If we aim for perfect this should be a trylock still, maybe using our own > > > device list. > > > > > I definitely agree here. > > The lock may already be locked either by a stopped CPU, or by the > very same CPU we execute panic() on (e.g. NMI panic() on the > printing CPU). > > This is why it's very common for example in serial consoles, which > are usually careful about re-entrance and panic contexts, to do: > > xxxxxx_console_write(...) { > if (oops_in_progress) > locked = spin_trylock_irqsave(&port->lock, flags); > else > spin_lock_irqsave(&port->lock, flags); > } > > I'm quite positive we should do the same for panic drm drivers. Yeah Ideally all the locking in the drm path would be trylock only. I wonder whether lockdep could help us validate this, with some "don't allow anything except trylocks in this context". It's easy to audit the core code with review, but drivers are much tougher. And often end up with really deep callchains to get at the backing buffers. > John? > > > >> +} > > >> + > > >> +static struct kmsg_dumper drm_panic_kmsg_dumper = { > > >> + .dump = drm_panic_kmsg_dump, > > >> + .max_reason = KMSG_DUMP_PANIC, > > >> +}; > > >> + > > >> +static ssize_t drm_panic_file_panic_write(struct file *file, > > >> + const char __user *user_buf, > > >> + size_t count, loff_t *ppos) > > >> +{ > > >> + unsigned long long val; > > >> + char buf[24]; > > >> + size_t size; > > >> + ssize_t ret; > > >> + > > >> + 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; > > >> + > > >> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; > > >> + wmb(); > > >> + > > >> + /* Do a real test with: echo c > /proc/sysrq-trigger */ > > >> + > > >> + if (val == 0) { > > >> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); > > >> + kmsg_dump(KMSG_DUMP_OOPS); > > >> + } else if (val == 1) { > > >> + char *null_pointer = NULL; > > >> + > > >> + pr_info("Test panic screen using NULL pointer dereference\n"); > > >> + *null_pointer = 1; > > >> + } else { > > >> + return -EINVAL; > > >> + } > > > > > > This isn't quite what I had in mind, since it still kills the kernel (like > > > sysrq-trigger). > > > > If val == 0, it doesn't kill the kernel, it only dumps the kernel log. > > And it doesn't taint the kernel either. > > > > > Instead what I had in mind is to recreate the worst > > > possible panic context as much as feasible (disabling interrupts should be > > > a good start, maybe we can even do an nmi callback), and then call our > > > panic implementation. That way we can test the panic handler in a > > > non-destructive way (i.e. aside from last dmesg lines printed to the > > > screen nothing bad happens to the kernel: No real panic, no oops, no > > > tainting). > > > > The interrupt case I can do, nmi I have no idea. > > > > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP > would be a nice non-destructive test-case emulation. See above, if we can somehow emulate "all locks are held, only allow trylock" with lockdep that would be great too. Plus nmi context, once/if that somehow becomes relevant. The thing that killed the old drm panic handling code definitely was that we flat out couldnt' test it except with real oopses. And that's just whack-a-mole and bug reporter frustration if you first have a few patch iterations around "oh sry, it's still some oops in the panic handler, not the first one that we're seeing" :-/ -Daniel
On Wed, Mar 13, 2019 at 08:49:17AM +0100, John Ogness wrote: > On 2019-03-12, Ahmed S. Darwish <darwish.07@gmail.com> wrote: > >>>> + > >>>> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, > >>>> + enum kmsg_dump_reason reason) > >>>> +{ > >>>> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); > >>> > >>> class_for_each_device uses klist, which only uses an irqsave > >>> spinlock. I think that's good enough. Comment to that effect would > >>> be good e.g. > >>> > >>> /* based on klist, which uses only a spin_lock_irqsave, which we > >>> * assume still works */ > >>> > >>> If we aim for perfect this should be a trylock still, maybe using > >>> our own device list. > >>> > > > > I definitely agree here. > > > > The lock may already be locked either by a stopped CPU, or by the > > very same CPU we execute panic() on (e.g. NMI panic() on the > > printing CPU). > > > > This is why it's very common for example in serial consoles, which > > are usually careful about re-entrance and panic contexts, to do: > > > > xxxxxx_console_write(...) { > > if (oops_in_progress) > > locked = spin_trylock_irqsave(&port->lock, flags); > > else > > spin_lock_irqsave(&port->lock, flags); > > } > > > > I'm quite positive we should do the same for panic drm drivers. > > This construction will continue, even if the trylock fails. It only > makes sense to do this if the driver has a chance of being > successful. Ignoring locks is a serious issue. I personally am quite > unhappy that the serial drivers do this, which was part of my motivation > for the new printk design I'm working on. > > If the driver is not capable of doing something useful on a failed > trylock, then I recommend just skipping that device. Maybe trying it > again later after trying all the devices? Ah yes missed that. If the trylock fails anywhere, we must bail out. Not sure retrying is useful, my experience from at least drm is that either you're lucky, and drm wasn't doing anything right when the machine blew up, and then the trylocks will all go through. Or you're unlucky, and most likely that means drm itself blew up, and no amount of retrying is going to help. I wouldn't bother. -Daniel
On Tue, Mar 12, 2019 at 08:02:56PM +0200, Ville Syrjälä wrote: > On Tue, Mar 12, 2019 at 06:37:57PM +0100, Noralf Trønnes wrote: > > > > > > Den 12.03.2019 18.25, skrev Ville Syrjälä: > > > On Tue, Mar 12, 2019 at 06:15:24PM +0100, Noralf Trønnes wrote: > > >> > > >> > > >> Den 12.03.2019 17.17, skrev Ville Syrjälä: > > >>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > > >>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > > >>>>> This adds support for outputting kernel messages on panic(). > > >>>>> A kernel message dumper is used to dump the log. The dumper iterates > > >>>>> over each DRM device and it's crtc's to find suitable framebuffers. > > >>>>> > > >>>>> All the other dumpers are run before this one except mtdoops. > > >>>>> Only atomic drivers are supported. > > >>>>> > > >>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > >>>>> --- > > >>>>> [...] > > >>>>> > > >>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > > >>>>> index f0b34c977ec5..f3274798ecfe 100644 > > >>>>> --- a/include/drm/drm_framebuffer.h > > >>>>> +++ b/include/drm/drm_framebuffer.h > > >>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > > >>>>> struct drm_file *file_priv, unsigned flags, > > >>>>> unsigned color, struct drm_clip_rect *clips, > > >>>>> unsigned num_clips); > > >>>>> + > > >>>>> + /** > > >>>>> + * @panic_vmap: > > >>>>> + * > > >>>>> + * Optional callback for panic handling. > > >>>>> + * > > >>>>> + * For vmapping the selected framebuffer in a panic context. Must > > >>>>> + * be super careful about locking (only trylocking allowed). > > >>>>> + * > > >>>>> + * RETURNS: > > >>>>> + * > > >>>>> + * NULL if it didn't work out, otherwise 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); > > >>>> > > >>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon > > >>>> drivers: > > >>>> > > >>>> Framebuffers are normally tiled, writing to them with the CPU results in > > >>>> garbled output. > > >>>> > > >> > > >> In which case the driver needs to support the ->panic_draw_xy callback, > > >> or maybe it's possible to make a generic helper for tiled buffers. I've proposed somewhere else that we rename panic_vmap to panic_prepare, and the vmap pointer to an abstract cookie. Then the driver can do whatever it wants too, e.g. in ->panic_prepare it does a few trylocks to get at the buffer and make sure it can set up a temporary pte to write into it page-by-page. ->panic_draw_xy can then do whatever it needs to do, using the opaque void *cookie. And if the trylock fails you just return NULL from ->panic_prepare And ->panic_cleanup would be just to clean up the mess for the validation use-case when running this from debugfs. > > >> > > >>>> With a discrete GPU having a large amount of VRAM, the framebuffer may > > >>>> not be directly CPU accessible at all. > > >>>> > > >> > > >> I would have been nice to know how Windows works around this. > > >> > > >>>> > > >>>> There would need to be a mechanism for switching scanout to a linear, > > >>>> CPU accessible framebuffer. > > >>> > > >>> I suppose panic_vmap() could just provide a linear temp buffer > > >>> to the panic handler, and panic_unmap() could copy the contents > > >>> over to the real fb. > > >>> > > >>> That said, this approach of scribbling over the primary plane's > > >>> framebuffer has some clear limitations: > > >>> * something may overwrite the oops message before the user > > >>> can even read it > > >> > > >> When the dumper drm_panic_kmsg_dump() runs, the other CPU's should have > > >> been stopped. See panic(). > > > > > > GPUs etc. may still be executing away. > > > > > > > Would it be safe to stop it in a panic situation? It would ofc be bad to > > crash the box even harder. > > Some drivers/devices may have working (and hopefully even reliable) > gpu reset, some may not. I don't think touching the gpu is a good idea. Even disabling planes and all that feels risky. And there's really not much working anymore in panic context, we can't even schedule a worker/timer to redraw the panic output a bit later. -Daniel
On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: > > > Den 12.03.2019 17.17, skrev Ville Syrjälä: >> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>> This adds support for outputting kernel messages on panic(). >>>> A kernel message dumper is used to dump the log. The dumper iterates >>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>> >>>> All the other dumpers are run before this one except mtdoops. >>>> Only atomic drivers are supported. >>>> >>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>> --- >>>> [...] >>>> >>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>>> index f0b34c977ec5..f3274798ecfe 100644 >>>> --- a/include/drm/drm_framebuffer.h >>>> +++ b/include/drm/drm_framebuffer.h >>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>> struct drm_file *file_priv, unsigned flags, >>>> unsigned color, struct drm_clip_rect *clips, >>>> unsigned num_clips); >>>> + >>>> + /** >>>> + * @panic_vmap: >>>> + * >>>> + * Optional callback for panic handling. >>>> + * >>>> + * For vmapping the selected framebuffer in a panic context. Must >>>> + * be super careful about locking (only trylocking allowed). >>>> + * >>>> + * RETURNS: >>>> + * >>>> + * NULL if it didn't work out, otherwise 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); >>> >>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >>> drivers: >>> >>> Framebuffers are normally tiled, writing to them with the CPU results in >>> garbled output. >>> > > In which case the driver needs to support the ->panic_draw_xy callback, > or maybe it's possible to make a generic helper for tiled buffers. I'm afraid that won't help, at least not without porting big chunks of https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib into the kernel, none of which will be used for anything else. >>> There would need to be a mechanism for switching scanout to a linear, >>> CPU accessible framebuffer. >> >> I suppose panic_vmap() could just provide a linear temp buffer >> to the panic handler, and panic_unmap() could copy the contents >> over to the real fb. Copy how? Using a GPU engine?
Den 12.03.2019 23.13, skrev Ahmed S. Darwish: > Hi, > > [[ CCing John for the trylock parts ]] > > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: >> >> >> Den 11.03.2019 20.23, skrev Daniel Vetter: >>> On Mon, Mar 11, 2019 at 06:42:16PM +0100, Noralf Trønnes wrote: >>>> This adds support for outputting kernel messages on panic(). >>>> A kernel message dumper is used to dump the log. The dumper iterates >>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>> >>>> All the other dumpers are run before this one except mtdoops. >>>> Only atomic drivers are supported. >>>> >>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>> >>> Bunch of comments/ideas for you or Darwish below, whoever picks this up. >> >> Actually it would ne nice if Darwish could pick it up since he will do >> it on i915 which will be useful to a much broader audience. >> If not I'll respin when I'm done with the drm_fb_helper refactoring. >> > > Yup, I'll be more than happy to do this.. Thanks for doing that. Noralf. > while preserving all of > Noralf's authorship and copyright notices of course. > > I guess it can be: > > - Handle the comments posted by Daniel and others (I'll post > some questions too) > > - Add the necessary i915 specific bits > > - Test, post v3/v4/../vn. Rinse and repeat. Keep it local at > dri-devel until getting the necessary S-o-Bs. > > - Post to wider audience (some feedback from distribution folks > would also be nice, before posting to lkml) > > More comments below.. > > [...] > >>>> + >>>> +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, >>>> + enum kmsg_dump_reason reason) >>>> +{ >>>> + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); >>> >>> class_for_each_device uses klist, which only uses an irqsave spinlock. I >>> think that's good enough. Comment to that effect would be good e.g. >>> >>> /* based on klist, which uses only a spin_lock_irqsave, which we >>> * assume still works */ >>> >>> If we aim for perfect this should be a trylock still, maybe using our own >>> device list. >>> > > I definitely agree here. > > The lock may already be locked either by a stopped CPU, or by the > very same CPU we execute panic() on (e.g. NMI panic() on the > printing CPU). > > This is why it's very common for example in serial consoles, which > are usually careful about re-entrance and panic contexts, to do: > > xxxxxx_console_write(...) { > if (oops_in_progress) > locked = spin_trylock_irqsave(&port->lock, flags); > else > spin_lock_irqsave(&port->lock, flags); > } > > I'm quite positive we should do the same for panic drm drivers. > John? > >>>> +} >>>> + >>>> +static struct kmsg_dumper drm_panic_kmsg_dumper = { >>>> + .dump = drm_panic_kmsg_dump, >>>> + .max_reason = KMSG_DUMP_PANIC, >>>> +}; >>>> + >>>> +static ssize_t drm_panic_file_panic_write(struct file *file, >>>> + const char __user *user_buf, >>>> + size_t count, loff_t *ppos) >>>> +{ >>>> + unsigned long long val; >>>> + char buf[24]; >>>> + size_t size; >>>> + ssize_t ret; >>>> + >>>> + 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; >>>> + >>>> + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; >>>> + wmb(); >>>> + >>>> + /* Do a real test with: echo c > /proc/sysrq-trigger */ >>>> + >>>> + if (val == 0) { >>>> + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); >>>> + kmsg_dump(KMSG_DUMP_OOPS); >>>> + } else if (val == 1) { >>>> + char *null_pointer = NULL; >>>> + >>>> + pr_info("Test panic screen using NULL pointer dereference\n"); >>>> + *null_pointer = 1; >>>> + } else { >>>> + return -EINVAL; >>>> + } >>> >>> This isn't quite what I had in mind, since it still kills the kernel (like >>> sysrq-trigger). >> >> If val == 0, it doesn't kill the kernel, it only dumps the kernel log. >> And it doesn't taint the kernel either. >> >>> Instead what I had in mind is to recreate the worst >>> possible panic context as much as feasible (disabling interrupts should be >>> a good start, maybe we can even do an nmi callback), and then call our >>> panic implementation. That way we can test the panic handler in a >>> non-destructive way (i.e. aside from last dmesg lines printed to the >>> screen nothing bad happens to the kernel: No real panic, no oops, no >>> tainting). >> >> The interrupt case I can do, nmi I have no idea. >> > > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP > would be a nice non-destructive test-case emulation. > > thanks! > > -- > darwi > http://darwish.chasingpointers.com >
On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: > On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: > > > > > > Den 12.03.2019 17.17, skrev Ville Syrjälä: > >> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > >>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > >>>> This adds support for outputting kernel messages on panic(). > >>>> A kernel message dumper is used to dump the log. The dumper iterates > >>>> over each DRM device and it's crtc's to find suitable framebuffers. > >>>> > >>>> All the other dumpers are run before this one except mtdoops. > >>>> Only atomic drivers are supported. > >>>> > >>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > >>>> --- > >>>> [...] > >>>> > >>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > >>>> index f0b34c977ec5..f3274798ecfe 100644 > >>>> --- a/include/drm/drm_framebuffer.h > >>>> +++ b/include/drm/drm_framebuffer.h > >>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >>>> struct drm_file *file_priv, unsigned flags, > >>>> unsigned color, struct drm_clip_rect *clips, > >>>> unsigned num_clips); > >>>> + > >>>> + /** > >>>> + * @panic_vmap: > >>>> + * > >>>> + * Optional callback for panic handling. > >>>> + * > >>>> + * For vmapping the selected framebuffer in a panic context. Must > >>>> + * be super careful about locking (only trylocking allowed). > >>>> + * > >>>> + * RETURNS: > >>>> + * > >>>> + * NULL if it didn't work out, otherwise 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); > >>> > >>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon > >>> drivers: > >>> > >>> Framebuffers are normally tiled, writing to them with the CPU results in > >>> garbled output. > >>> > > > > In which case the driver needs to support the ->panic_draw_xy callback, > > or maybe it's possible to make a generic helper for tiled buffers. > > I'm afraid that won't help, at least not without porting big chunks of > https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib > into the kernel, none of which will be used for anything else. > > > >>> There would need to be a mechanism for switching scanout to a linear, > >>> CPU accessible framebuffer. > >> > >> I suppose panic_vmap() could just provide a linear temp buffer > >> to the panic handler, and panic_unmap() could copy the contents > >> over to the real fb. > > Copy how? Using a GPU engine? CPU maybe? Though I suppose that won't work if the buffer isn't CPU accesible :/
Am 13.03.19 um 14:31 schrieb Ville Syrjälä: > On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>> >>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>> This adds support for outputting kernel messages on panic(). >>>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>>> >>>>>> All the other dumpers are run before this one except mtdoops. >>>>>> Only atomic drivers are supported. >>>>>> >>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>> --- >>>>>> [...] >>>>>> >>>>>> diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h >>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>> --- a/include/drm/drm_framebuffer.h >>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>> struct drm_file *file_priv, unsigned flags, >>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>> unsigned num_clips); >>>>>> + >>>>>> + /** >>>>>> + * @panic_vmap: >>>>>> + * >>>>>> + * Optional callback for panic handling. >>>>>> + * >>>>>> + * For vmapping the selected framebuffer in a panic context. Must >>>>>> + * be super careful about locking (only trylocking allowed). >>>>>> + * >>>>>> + * RETURNS: >>>>>> + * >>>>>> + * NULL if it didn't work out, otherwise 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); >>>>> FWIW, the panic_vmap hook cannot work in general with the amdgpu/radeon >>>>> drivers: >>>>> >>>>> Framebuffers are normally tiled, writing to them with the CPU results in >>>>> garbled output. >>>>> >>> In which case the driver needs to support the ->panic_draw_xy callback, >>> or maybe it's possible to make a generic helper for tiled buffers. >> I'm afraid that won't help, at least not without porting big chunks of >> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >> into the kernel, none of which will be used for anything else. >> >> >>>>> There would need to be a mechanism for switching scanout to a linear, >>>>> CPU accessible framebuffer. >>>> I suppose panic_vmap() could just provide a linear temp buffer >>>> to the panic handler, and panic_unmap() could copy the contents >>>> over to the real fb. >> Copy how? Using a GPU engine? > CPU maybe? Though I suppose that won't work if the buffer isn't CPU > accesible :/ Well we do have a debug path for accessing invisible memory with the CPU. E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can just read/write DATA over and over again if you want to access some memory. But turning of tilling etc is still extremely tricky when the system is already unstable. I mean from two miles high it looks like a nice to have feature, but up close is a different picture... Christian.
On 2019-03-13 2:37 p.m., Christian König wrote: > Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>> >>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>>>> >>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>> Only atomic drivers are supported. >>>>>>> >>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>> --- >>>>>>> [...] >>>>>>> >>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>> b/include/drm/drm_framebuffer.h >>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>> unsigned num_clips); >>>>>>> + >>>>>>> + /** >>>>>>> + * @panic_vmap: >>>>>>> + * >>>>>>> + * Optional callback for panic handling. >>>>>>> + * >>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>> Must >>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>> + * >>>>>>> + * RETURNS: >>>>>>> + * >>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>> amdgpu/radeon >>>>>> drivers: >>>>>> >>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>> results in >>>>>> garbled output. >>>>>> >>>> In which case the driver needs to support the ->panic_draw_xy callback, >>>> or maybe it's possible to make a generic helper for tiled buffers. >>> I'm afraid that won't help, at least not without porting big chunks of >>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>> into the kernel, none of which will be used for anything else. >>> >>> >>>>>> There would need to be a mechanism for switching scanout to a linear, >>>>>> CPU accessible framebuffer. >>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>> to the panic handler, and panic_unmap() could copy the contents >>>>> over to the real fb. >>> Copy how? Using a GPU engine? >> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >> accesible :/ > > Well we do have a debug path for accessing invisible memory with the CPU. > > E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can > just read/write DATA over and over again if you want to access some memory. Right. I assume that'll be very slow, but I guess it could do when the memory isn't directly CPU accessible. > But turning of tilling etc is still extremely tricky when the system is > already unstable. Maybe we could add a little hook to the display code, which just disables tiling for scanout and maybe disables non-primary planes, but doesn't touch anything else. Harry / Nicholas, does that seem feasible? I'm coming around from "this is never going to work" to "it might actually work" with our hardware...
Am 13.03.19 um 16:38 schrieb Michel Dänzer: > On 2019-03-13 2:37 p.m., Christian König wrote: >> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>> A kernel message dumper is used to dump the log. The dumper iterates >>>>>>>> over each DRM device and it's crtc's to find suitable framebuffers. >>>>>>>> >>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>> Only atomic drivers are supported. >>>>>>>> >>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>> --- >>>>>>>> [...] >>>>>>>> >>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>> unsigned num_clips); >>>>>>>> + >>>>>>>> + /** >>>>>>>> + * @panic_vmap: >>>>>>>> + * >>>>>>>> + * Optional callback for panic handling. >>>>>>>> + * >>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>> Must >>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>> + * >>>>>>>> + * RETURNS: >>>>>>>> + * >>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>> amdgpu/radeon >>>>>>> drivers: >>>>>>> >>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>> results in >>>>>>> garbled output. >>>>>>> >>>>> In which case the driver needs to support the ->panic_draw_xy callback, >>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>> I'm afraid that won't help, at least not without porting big chunks of >>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>> into the kernel, none of which will be used for anything else. >>>> >>>> >>>>>>> There would need to be a mechanism for switching scanout to a linear, >>>>>>> CPU accessible framebuffer. >>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>> over to the real fb. >>>> Copy how? Using a GPU engine? >>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>> accesible :/ >> Well we do have a debug path for accessing invisible memory with the CPU. >> >> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >> just read/write DATA over and over again if you want to access some memory. > Right. I assume that'll be very slow, but I guess it could do when the > memory isn't directly CPU accessible. Just made a quick test and reading 33423360 bytes (4096x2040x4) using that interfaces takes about 13 seconds. IIRC we don't use the auto increment optimization yet, so that can probably be improved by a factor of 3 or more. >> But turning of tilling etc is still extremely tricky when the system is >> already unstable. > Maybe we could add a little hook to the display code, which just > disables tiling for scanout and maybe disables non-primary planes, but > doesn't touch anything else. Harry / Nicholas, does that seem feasible? > > > I'm coming around from "this is never going to work" to "it might > actually work" with our hardware... Yeah, agree. It's a bit tricky, but doable. Takeaway for Noralf is that this whole vmap on panic won't even remotely work. We need to get the data byte by byte without a page mapping if that is ever going to fly. Christian. > >
On 3/13/19 11:54 AM, Christian König wrote: > Am 13.03.19 um 16:38 schrieb Michel Dänzer: >> On 2019-03-13 2:37 p.m., Christian König wrote: >>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>>> A kernel message dumper is used to dump the log. The dumper >>>>>>>>> iterates >>>>>>>>> over each DRM device and it's crtc's to find suitable >>>>>>>>> framebuffers. >>>>>>>>> >>>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>>> Only atomic drivers are supported. >>>>>>>>> >>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>>> --- >>>>>>>>> [...] >>>>>>>>> >>>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>>> unsigned num_clips); >>>>>>>>> + >>>>>>>>> + /** >>>>>>>>> + * @panic_vmap: >>>>>>>>> + * >>>>>>>>> + * Optional callback for panic handling. >>>>>>>>> + * >>>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>>> Must >>>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>>> + * >>>>>>>>> + * RETURNS: >>>>>>>>> + * >>>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>>> amdgpu/radeon >>>>>>>> drivers: >>>>>>>> >>>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>>> results in >>>>>>>> garbled output. >>>>>>>> >>>>>> In which case the driver needs to support the ->panic_draw_xy >>>>>> callback, >>>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>>> I'm afraid that won't help, at least not without porting big chunks of >>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>>> into the kernel, none of which will be used for anything else. >>>>> >>>>> >>>>>>>> There would need to be a mechanism for switching scanout to a >>>>>>>> linear, >>>>>>>> CPU accessible framebuffer. >>>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>>> over to the real fb. >>>>> Copy how? Using a GPU engine? >>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>> accesible :/ >>> Well we do have a debug path for accessing invisible memory with the >>> CPU. >>> >>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>> just read/write DATA over and over again if you want to access some >>> memory. >> Right. I assume that'll be very slow, but I guess it could do when the >> memory isn't directly CPU accessible. > > Just made a quick test and reading 33423360 bytes (4096x2040x4) using > that interfaces takes about 13 seconds. > > IIRC we don't use the auto increment optimization yet, so that can > probably be improved by a factor of 3 or more. > >>> But turning of tilling etc is still extremely tricky when the system is >>> already unstable. >> Maybe we could add a little hook to the display code, which just >> disables tiling for scanout and maybe disables non-primary planes, but >> doesn't touch anything else. Harry / Nicholas, does that seem feasible? >> >> >> I'm coming around from "this is never going to work" to "it might >> actually work" with our hardware... > > Yeah, agree. It's a bit tricky, but doable. A "disable_tiling" hook or something along those lines could work for display. It's a little bit non trivial when you want to start dealing with locking and any active DRM commits, but we have a global lock around all our hardware programming anyway that makes that easier to deal with. I think we can just re-commit and update the existing hardware state with only the tiling info for every plane reset to off. For most buffers I don't think we'd have to really consider changing anything else here as long as you respect the current FB size and pitch. Nicholas Kazlauskas > > Takeaway for Noralf is that this whole vmap on panic won't even remotely > work. We need to get the data byte by byte without a page mapping if > that is ever going to fly. > > Christian. > >> >> >
Am 13.03.19 um 17:16 schrieb Kazlauskas, Nicholas: > On 3/13/19 11:54 AM, Christian König wrote: >> Am 13.03.19 um 16:38 schrieb Michel Dänzer: >>> On 2019-03-13 2:37 p.m., Christian König wrote: >>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>>>> A kernel message dumper is used to dump the log. The dumper >>>>>>>>>> iterates >>>>>>>>>> over each DRM device and it's crtc's to find suitable >>>>>>>>>> framebuffers. >>>>>>>>>> >>>>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>>>> Only atomic drivers are supported. >>>>>>>>>> >>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>>>> --- >>>>>>>>>> [...] >>>>>>>>>> >>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>>>> unsigned num_clips); >>>>>>>>>> + >>>>>>>>>> + /** >>>>>>>>>> + * @panic_vmap: >>>>>>>>>> + * >>>>>>>>>> + * Optional callback for panic handling. >>>>>>>>>> + * >>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>>>> Must >>>>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>>>> + * >>>>>>>>>> + * RETURNS: >>>>>>>>>> + * >>>>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>>>> amdgpu/radeon >>>>>>>>> drivers: >>>>>>>>> >>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>>>> results in >>>>>>>>> garbled output. >>>>>>>>> >>>>>>> In which case the driver needs to support the ->panic_draw_xy >>>>>>> callback, >>>>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>>>> I'm afraid that won't help, at least not without porting big chunks of >>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>>>> into the kernel, none of which will be used for anything else. >>>>>> >>>>>> >>>>>>>>> There would need to be a mechanism for switching scanout to a >>>>>>>>> linear, >>>>>>>>> CPU accessible framebuffer. >>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>>>> over to the real fb. >>>>>> Copy how? Using a GPU engine? >>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>>> accesible :/ >>>> Well we do have a debug path for accessing invisible memory with the >>>> CPU. >>>> >>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>>> just read/write DATA over and over again if you want to access some >>>> memory. >>> Right. I assume that'll be very slow, but I guess it could do when the >>> memory isn't directly CPU accessible. >> Just made a quick test and reading 33423360 bytes (4096x2040x4) using >> that interfaces takes about 13 seconds. >> >> IIRC we don't use the auto increment optimization yet, so that can >> probably be improved by a factor of 3 or more. >> >>>> But turning of tilling etc is still extremely tricky when the system is >>>> already unstable. >>> Maybe we could add a little hook to the display code, which just >>> disables tiling for scanout and maybe disables non-primary planes, but >>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? >>> >>> >>> I'm coming around from "this is never going to work" to "it might >>> actually work" with our hardware... >> Yeah, agree. It's a bit tricky, but doable. > A "disable_tiling" hook or something along those lines could work for > display. It's a little bit non trivial when you want to start dealing > with locking and any active DRM commits, but we have a global lock > around all our hardware programming anyway that makes that easier to > deal with. > > I think we can just re-commit and update the existing hardware state > with only the tiling info for every plane reset to off. For most buffers > I don't think we'd have to really consider changing anything else here > as long as you respect the current FB size and pitch. That sounds like it is also already to invasive. Keep in mind that all other CPU cores and processes/threads are dead and you can be in any context you want (e.g. NMI). In other words we can't take any locks whatsoever and we need to keep the hardware interaction as minimal as possible. Christian. > > Nicholas Kazlauskas > >> Takeaway for Noralf is that this whole vmap on panic won't even remotely >> work. We need to get the data byte by byte without a page mapping if >> that is ever going to fly. >> >> Christian. >> >>>
On 2019-03-13 5:16 p.m., Kazlauskas, Nicholas wrote: > On 3/13/19 11:54 AM, Christian König wrote: >> Am 13.03.19 um 16:38 schrieb Michel Dänzer: >>> On 2019-03-13 2:37 p.m., Christian König wrote: >>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>>>> A kernel message dumper is used to dump the log. The dumper >>>>>>>>>> iterates >>>>>>>>>> over each DRM device and it's crtc's to find suitable >>>>>>>>>> framebuffers. >>>>>>>>>> >>>>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>>>> Only atomic drivers are supported. >>>>>>>>>> >>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>>>> --- >>>>>>>>>> [...] >>>>>>>>>> >>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>>>> unsigned num_clips); >>>>>>>>>> + >>>>>>>>>> + /** >>>>>>>>>> + * @panic_vmap: >>>>>>>>>> + * >>>>>>>>>> + * Optional callback for panic handling. >>>>>>>>>> + * >>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>>>> Must >>>>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>>>> + * >>>>>>>>>> + * RETURNS: >>>>>>>>>> + * >>>>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>>>> amdgpu/radeon >>>>>>>>> drivers: >>>>>>>>> >>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>>>> results in >>>>>>>>> garbled output. >>>>>>>>> >>>>>>> In which case the driver needs to support the ->panic_draw_xy >>>>>>> callback, >>>>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>>>> I'm afraid that won't help, at least not without porting big chunks of >>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>>>> into the kernel, none of which will be used for anything else. >>>>>> >>>>>> >>>>>>>>> There would need to be a mechanism for switching scanout to a >>>>>>>>> linear, >>>>>>>>> CPU accessible framebuffer. >>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>>>> over to the real fb. >>>>>> Copy how? Using a GPU engine? >>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>>> accesible :/ >>>> Well we do have a debug path for accessing invisible memory with the >>>> CPU. >>>> >>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>>> just read/write DATA over and over again if you want to access some >>>> memory. >>> Right. I assume that'll be very slow, but I guess it could do when the >>> memory isn't directly CPU accessible. >> >> Just made a quick test and reading 33423360 bytes (4096x2040x4) using >> that interfaces takes about 13 seconds. >> >> IIRC we don't use the auto increment optimization yet, so that can >> probably be improved by a factor of 3 or more. I'd assume only writes are needed, no reads. >>>> But turning of tilling etc is still extremely tricky when the system is >>>> already unstable. >>> Maybe we could add a little hook to the display code, which just >>> disables tiling for scanout and maybe disables non-primary planes, but >>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? >>> >>> >>> I'm coming around from "this is never going to work" to "it might >>> actually work" with our hardware... >> >> Yeah, agree. It's a bit tricky, but doable. > > A "disable_tiling" hook or something along those lines could work for > display. It's a little bit non trivial when you want to start dealing > with locking and any active DRM commits, but we have a global lock > around all our hardware programming anyway that makes that easier to > deal with. This code only runs when there's a kernel panic, so it doesn't have to care about such things. :) In fact, it should rather avoid them and only do the absolute minimum register writes needed to do the job. > I think we can just re-commit and update the existing hardware state > with only the tiling info for every plane reset to off. There's also a concern that non-primary planes might prevent the output from being visible. > For most buffers I don't think we'd have to really consider changing > anything else here as long as you respect the current FB size and > pitch. Hmm, e.g. compression might also need to be disabled? Or maybe that's implicitly off with no tiling?
On 3/13/19 1:33 PM, Michel Dänzer wrote: > On 2019-03-13 5:16 p.m., Kazlauskas, Nicholas wrote: >> On 3/13/19 11:54 AM, Christian König wrote: >>> Am 13.03.19 um 16:38 schrieb Michel Dänzer: >>>> On 2019-03-13 2:37 p.m., Christian König wrote: >>>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>>>>> A kernel message dumper is used to dump the log. The dumper >>>>>>>>>>> iterates >>>>>>>>>>> over each DRM device and it's crtc's to find suitable >>>>>>>>>>> framebuffers. >>>>>>>>>>> >>>>>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>>>>> Only atomic drivers are supported. >>>>>>>>>>> >>>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>>>>> --- >>>>>>>>>>> [...] >>>>>>>>>>> >>>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>>>>> unsigned num_clips); >>>>>>>>>>> + >>>>>>>>>>> + /** >>>>>>>>>>> + * @panic_vmap: >>>>>>>>>>> + * >>>>>>>>>>> + * Optional callback for panic handling. >>>>>>>>>>> + * >>>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>>>>> Must >>>>>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>>>>> + * >>>>>>>>>>> + * RETURNS: >>>>>>>>>>> + * >>>>>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>>>>> amdgpu/radeon >>>>>>>>>> drivers: >>>>>>>>>> >>>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>>>>> results in >>>>>>>>>> garbled output. >>>>>>>>>> >>>>>>>> In which case the driver needs to support the ->panic_draw_xy >>>>>>>> callback, >>>>>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>>>>> I'm afraid that won't help, at least not without porting big chunks of >>>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>>>>> into the kernel, none of which will be used for anything else. >>>>>>> >>>>>>> >>>>>>>>>> There would need to be a mechanism for switching scanout to a >>>>>>>>>> linear, >>>>>>>>>> CPU accessible framebuffer. >>>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>>>>> over to the real fb. >>>>>>> Copy how? Using a GPU engine? >>>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>>>> accesible :/ >>>>> Well we do have a debug path for accessing invisible memory with the >>>>> CPU. >>>>> >>>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>>>> just read/write DATA over and over again if you want to access some >>>>> memory. >>>> Right. I assume that'll be very slow, but I guess it could do when the >>>> memory isn't directly CPU accessible. >>> >>> Just made a quick test and reading 33423360 bytes (4096x2040x4) using >>> that interfaces takes about 13 seconds. >>> >>> IIRC we don't use the auto increment optimization yet, so that can >>> probably be improved by a factor of 3 or more. > > I'd assume only writes are needed, no reads. > > >>>>> But turning of tilling etc is still extremely tricky when the system is >>>>> already unstable. >>>> Maybe we could add a little hook to the display code, which just >>>> disables tiling for scanout and maybe disables non-primary planes, but >>>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? >>>> >>>> >>>> I'm coming around from "this is never going to work" to "it might >>>> actually work" with our hardware... >>> >>> Yeah, agree. It's a bit tricky, but doable. >> >> A "disable_tiling" hook or something along those lines could work for >> display. It's a little bit non trivial when you want to start dealing >> with locking and any active DRM commits, but we have a global lock >> around all our hardware programming anyway that makes that easier to >> deal with. > > This code only runs when there's a kernel panic, so it doesn't have to > care about such things. :) In fact, it should rather avoid them and only > do the absolute minimum register writes needed to do the job. Good point. I guess it doesn't really matter at that point. > > >> I think we can just re-commit and update the existing hardware state >> with only the tiling info for every plane reset to off. > > There's also a concern that non-primary planes might prevent the output > from being visible. I would just hope that the overlay doesn't cover the full screen, or that the panic handler writes to all buffers if possible. DC would have to swap the current context in order to disable planes and we'd have to do memory allocations / frees etc in order to do so. > > >> For most buffers I don't think we'd have to really consider changing >> anything else here as long as you respect the current FB size and >> pitch. > > Hmm, e.g. compression might also need to be disabled? Or maybe that's > implicitly off with no tiling? > > Resetting the tiling info will also disable the compression in this case, so the buffer should still be usable for this purpose. Nicholas Kazlauskas
Am 13.03.19 um 18:33 schrieb Michel Dänzer: > [SNIP] >>>>>>> Copy how? Using a GPU engine? >>>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>>>> accesible :/ >>>>> Well we do have a debug path for accessing invisible memory with the >>>>> CPU. >>>>> >>>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>>>> just read/write DATA over and over again if you want to access some >>>>> memory. >>>> Right. I assume that'll be very slow, but I guess it could do when the >>>> memory isn't directly CPU accessible. >>> Just made a quick test and reading 33423360 bytes (4096x2040x4) using >>> that interfaces takes about 13 seconds. >>> >>> IIRC we don't use the auto increment optimization yet, so that can >>> probably be improved by a factor of 3 or more. > I'd assume only writes are needed, no reads. I've played around with that for a moment and with a bit of optimization I can actually get about 20 MB/s write performance out of the debugging interface. This way overwriting a 4K framebuffer would take less than 2 seconds using this. It's not ideal, but I think for a panic screen perfectly reasonable. Christian.
[[ Adding Sebastian, who is quite experienced in intricate locking situations due to daily PREEMPT_RT work.. ]] On Wed, Mar 13, 2019 at 09:37:10AM +0100, Daniel Vetter wrote: > On Wed, Mar 13, 2019 at 08:49:17AM +0100, John Ogness wrote: > > On 2019-03-12, Ahmed S. Darwish <darwish.07@gmail.com> wrote: > > > On Wed, Mar 13, 2019 at 09:37:10AM +0100, Daniel Vetter wrote: [……] > > > > > > > > If we aim for perfect this should be a trylock still, maybe using > > > > our own device list. > > > > > > > > > > I definitely agree here. > > > > > > The lock may already be locked either by a stopped CPU, or by the > > > very same CPU we execute panic() on (e.g. NMI panic() on the > > > printing CPU). > > > > > > This is why it's very common for example in serial consoles, which > > > are usually careful about re-entrance and panic contexts, to do: > > > > > > xxxxxx_console_write(...) { > > > if (oops_in_progress) > > > locked = spin_trylock_irqsave(&port->lock, flags); > > > else > > > spin_lock_irqsave(&port->lock, flags); > > > } > > > > > > I'm quite positive we should do the same for panic drm drivers. > > > > This construction will continue, even if the trylock fails. It only > > makes sense to do this if the driver has a chance of being > > successful. Ignoring locks is a serious issue. I personally am quite > > unhappy that the serial drivers do this, which was part of my motivation > > for the new printk design I'm working on. > > > > If the driver is not capable of doing something useful on a failed > > trylock, then I recommend just skipping that device. Maybe trying it > > again later after trying all the devices? > > Ah yes missed that. If the trylock fails anywhere, we must bail out. > > Not sure retrying is useful, my experience from at least drm is that > either you're lucky, and drm wasn't doing anything right when the machine > blew up, and then the trylocks will all go through. Or you're unlucky, and > most likely that means drm itself blew up, and no amount of retrying is > going to help. I wouldn't bother. > Ack, retrying most probably won't help. I see that, at least in x86: kernel/panic.c::panic() => kernel/panic.c::smp_send_stop() => arch/x86/incluse/smp.h::smp_send_stop(0) => arch/x86/kernel/smp.c::native_stop_other_cpus(wait) /* * Don't wait longer than a second if the caller * didn't ask us to wait. */ timeout = USEC_PER_SEC; while (num_online_cpus() > 1 && (wait || timeout--)) udelay(1); So the panic()-ing core wait by default for _a full second_ until all other cores finish their non-preemptible regions. If that did not work out, it even tries more aggressively with NMIs. So if one of the other non panic()-ing cores was holding a DRM related lock through spin_lock_irqsave() for more than one second, well, it's a bug in the DRM layer code anyway.. [A] Problem is, what will happen if the non panic()-ing core was holding a DRM lock through barebone spin_lock()? Interrupts are enabled there, so the IPI will be handled, and thus that core will effectively die while the lock is forever held :-( [B] But, well, yeah, I don't think there's a solution for the [B] part except by spin_trylock() the fewest amount of locks possible, and bailing out if anyone of them is held. For reference, I asked over IRC why not just resetting GPU state, but it's not easy as it sounds to people outside of the gfx world: [abridged] <darwi> So it seems Windows is forcing driver writers to implement a gpu reset method [*] <darwi> since Windows vista <danvet> yeah <danvet> not going to do that from panic <danvet> there's a non-zero chance that gpu reset takes down the system with at least lots of hw/driver combos <danvet> I think all our drivers also have gpu reset support <danvet> if something is still rendering while we panic, mea culpa <danvet> only way around that is switching planes <danvet> which we tried with the old approach, and that's just too hard <danvet> you end up running a few 100kloc of code worst case <danvet> no way to audit/validate that for panic context <danvet> but the rendering shouldn't really matter <anholt> yeah, actually modesetting in a panic seems hopeless. <danvet> at least for modern compositors <danvet> anholt, yeah I killed that idea <danvet> so they only render to the backbuffer <danvet> and we won't show that with a panic <anholt> yep. just draw on a plane. for everything but uncomposited x11, it'll already be idle. <danvet> so only really an issue on boot splash and frontbuffer x11 <danvet> and there a few cursors/characters drawn won't really matter in most cases <danvet> anholt, yeah that's the idea, we pick the currently showing plane and have a simple draw_xy functions which knows how to draw bright/dark pixels for any drm_fourcc <danvet> so you can see the oops even if it's yuv or whatever <anholt> nice Thanks! [*] DXGKDDI_RESETFROMTIMEOUT callback function: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmddi/nc-d3dkmddi-dxgkddi_resetfromtimeout -- darwi http://darwish.chasingpointers.com
Hi, On Wed, Mar 13, 2019 at 09:35:05AM +0100, Daniel Vetter wrote: > On Tue, Mar 12, 2019 at 11:13:03PM +0100, Ahmed S. Darwish wrote: > > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > Den 11.03.2019 20.23, skrev Daniel Vetter: [……] > > > > > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > > > think that's good enough. Comment to that effect would be good e.g. > > > > > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > > > * assume still works */ > > > > > > > > If we aim for perfect this should be a trylock still, maybe using our own > > > > device list. > > > > > > > > I definitely agree here. > > > > The lock may already be locked either by a stopped CPU, or by the > > very same CPU we execute panic() on (e.g. NMI panic() on the > > printing CPU). > > > > This is why it's very common for example in serial consoles, which > > are usually careful about re-entrance and panic contexts, to do: > > > > xxxxxx_console_write(...) { > > if (oops_in_progress) > > locked = spin_trylock_irqsave(&port->lock, flags); > > else > > spin_lock_irqsave(&port->lock, flags); > > } > > > > I'm quite positive we should do the same for panic drm drivers. > > Yeah Ideally all the locking in the drm path would be trylock only. > > I wonder whether lockdep could help us validate this, with some "don't > allow anything except trylocks in this context". It's easy to audit the > core code with review, but drivers are much tougher. And often end up with > really deep callchains to get at the backing buffers. [……] > > > > Instead what I had in mind is to recreate the worst > > > > possible panic context as much as feasible (disabling interrupts should be > > > > a good start, maybe we can even do an nmi callback), and then call our > > > > panic implementation. That way we can test the panic handler in a > > > > non-destructive way (i.e. aside from last dmesg lines printed to the > > > > screen nothing bad happens to the kernel: No real panic, no oops, no > > > > tainting). > > > > > > The interrupt case I can do, nmi I have no idea. > > > > > > > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP > > would be a nice non-destructive test-case emulation. > > See above, if we can somehow emulate "all locks are held, only allow > trylock" with lockdep that would be great too. > Hmmmm, to prototype things faster, I'll do it as a hook: spin_lock(spinlock_t *lock) { might_spin_forever_under_panic_path(); ... } Just like how mutexes and DEBUG_ATOMIC_SLEEP do it today: void mutex_lock(struct mutex *lock) { might_sleep(); ... } Hopefully the locking maintainers can accept something like this. If not, let's play with lockdep (which is a bit complex). ===> Thanks to Sebastian for suggesting this! > Plus nmi context, once/if that somehow becomes relevant. > > The thing that killed the old drm panic handling code definitely was that > we flat out couldnt' test it except with real oopses. And that's just > whack-a-mole and bug reporter frustration if you first have a few patch > iterations around "oh sry, it's still some oops in the panic handler, not > the first one that we're seeing" :-/ > Oh, that's a critical point: full data > some data > no data > invalid/useless data First impressions for the feature will definitely count. Hopefully the hooks above and some heavy DRM CI testing __beforehand__ can improve things before publishing to a wider audience.. Cheers, -- darwi http://darwish.chasingpointers.com
On Thu, Mar 14, 2019 at 03:51:13AM +0100, Ahmed S. Darwish wrote: > > [[ Adding Sebastian, who is quite experienced in intricate > locking situations due to daily PREEMPT_RT work.. ]] > > On Wed, Mar 13, 2019 at 09:37:10AM +0100, Daniel Vetter wrote: > > On Wed, Mar 13, 2019 at 08:49:17AM +0100, John Ogness wrote: > > > On 2019-03-12, Ahmed S. Darwish <darwish.07@gmail.com> wrote: > > > > On Wed, Mar 13, 2019 at 09:37:10AM +0100, Daniel Vetter wrote: > [……] > > > > > > > > > > If we aim for perfect this should be a trylock still, maybe using > > > > > our own device list. > > > > > > > > > > > > > I definitely agree here. > > > > > > > > The lock may already be locked either by a stopped CPU, or by the > > > > very same CPU we execute panic() on (e.g. NMI panic() on the > > > > printing CPU). > > > > > > > > This is why it's very common for example in serial consoles, which > > > > are usually careful about re-entrance and panic contexts, to do: > > > > > > > > xxxxxx_console_write(...) { > > > > if (oops_in_progress) > > > > locked = spin_trylock_irqsave(&port->lock, flags); > > > > else > > > > spin_lock_irqsave(&port->lock, flags); > > > > } > > > > > > > > I'm quite positive we should do the same for panic drm drivers. > > > > > > This construction will continue, even if the trylock fails. It only > > > makes sense to do this if the driver has a chance of being > > > successful. Ignoring locks is a serious issue. I personally am quite > > > unhappy that the serial drivers do this, which was part of my motivation > > > for the new printk design I'm working on. > > > > > > If the driver is not capable of doing something useful on a failed > > > trylock, then I recommend just skipping that device. Maybe trying it > > > again later after trying all the devices? > > > > Ah yes missed that. If the trylock fails anywhere, we must bail out. > > > > Not sure retrying is useful, my experience from at least drm is that > > either you're lucky, and drm wasn't doing anything right when the machine > > blew up, and then the trylocks will all go through. Or you're unlucky, and > > most likely that means drm itself blew up, and no amount of retrying is > > going to help. I wouldn't bother. > > > > Ack, retrying most probably won't help. > > I see that, at least in x86: > > kernel/panic.c::panic() > => kernel/panic.c::smp_send_stop() > => arch/x86/incluse/smp.h::smp_send_stop(0) > => arch/x86/kernel/smp.c::native_stop_other_cpus(wait) > /* > * Don't wait longer than a second if the caller > * didn't ask us to wait. > */ > timeout = USEC_PER_SEC; > while (num_online_cpus() > 1 && (wait || timeout--)) > udelay(1); > > So the panic()-ing core wait by default for _a full second_ until > all other cores finish their non-preemptible regions. If that did > not work out, it even tries more aggressively with NMIs. > > So if one of the other non panic()-ing cores was holding a DRM > related lock through spin_lock_irqsave() for more than one second, > well, it's a bug in the DRM layer code anyway.. [A] > > Problem is, what will happen if the non panic()-ing core was > holding a DRM lock through barebone spin_lock()? Interrupts are > enabled there, so the IPI will be handled, and thus that core will > effectively die while the lock is forever held :-( [B] Just to hammer this in even more: If any of those locks are held, the hw/driver is in an undefined state. If you then touch the hw there's really good chances you kill the entire machine. Happened plenty with the old oops handling, which tried to do that. That's why we want none of any of these. Whethere it's modesetting or a gpu reset, gpu hw and drivers is just way too complex to make that feasible. That's why we came up with the trylock + immediate bail out design if that fails. Plus really only render the oops int whatever is the current display buffer, so that we don't have to do any hw programming at all. -Daniel > > But, well, yeah, I don't think there's a solution for the [B] part > except by spin_trylock() the fewest amount of locks possible, and > bailing out if anyone of them is held. > > For reference, I asked over IRC why not just resetting GPU state, > but it's not easy as it sounds to people outside of the gfx world: > > [abridged] > <darwi> So it seems Windows is forcing driver writers to > implement a gpu reset method [*] > <darwi> since Windows vista > <danvet> yeah > <danvet> not going to do that from panic > <danvet> there's a non-zero chance that gpu reset takes down > the system with at least lots of hw/driver combos > <danvet> I think all our drivers also have gpu reset support > <danvet> if something is still rendering while we panic, mea culpa > <danvet> only way around that is switching planes > <danvet> which we tried with the old approach, and that's just > too hard > <danvet> you end up running a few 100kloc of code worst case > <danvet> no way to audit/validate that for panic context > <danvet> but the rendering shouldn't really matter > <anholt> yeah, actually modesetting in a panic seems hopeless. > <danvet> at least for modern compositors > <danvet> anholt, yeah I killed that idea > <danvet> so they only render to the backbuffer > <danvet> and we won't show that with a panic > <anholt> yep. just draw on a plane. for everything but > uncomposited x11, it'll already be idle. > <danvet> so only really an issue on boot splash and frontbuffer x11 > <danvet> and there a few cursors/characters drawn won't really > matter in most cases > <danvet> anholt, yeah that's the idea, we pick the currently > showing plane and have a simple draw_xy functions which > knows how to draw bright/dark pixels for any drm_fourcc > <danvet> so you can see the oops even if it's yuv or whatever > <anholt> nice > > Thanks! > > [*] DXGKDDI_RESETFROMTIMEOUT callback function: > https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmddi/nc-d3dkmddi-dxgkddi_resetfromtimeout > > -- > darwi > http://darwish.chasingpointers.com
On Thu, Mar 14, 2019 at 05:45:32AM +0100, Ahmed S. Darwish wrote: > Hi, > > On Wed, Mar 13, 2019 at 09:35:05AM +0100, Daniel Vetter wrote: > > On Tue, Mar 12, 2019 at 11:13:03PM +0100, Ahmed S. Darwish wrote: > > > On Mon, Mar 11, 2019 at 11:33:15PM +0100, Noralf Trønnes wrote: > > > > Den 11.03.2019 20.23, skrev Daniel Vetter: > [……] > > > > > > > > > > class_for_each_device uses klist, which only uses an irqsave spinlock. I > > > > > think that's good enough. Comment to that effect would be good e.g. > > > > > > > > > > /* based on klist, which uses only a spin_lock_irqsave, which we > > > > > * assume still works */ > > > > > > > > > > If we aim for perfect this should be a trylock still, maybe using our own > > > > > device list. > > > > > > > > > > > I definitely agree here. > > > > > > The lock may already be locked either by a stopped CPU, or by the > > > very same CPU we execute panic() on (e.g. NMI panic() on the > > > printing CPU). > > > > > > This is why it's very common for example in serial consoles, which > > > are usually careful about re-entrance and panic contexts, to do: > > > > > > xxxxxx_console_write(...) { > > > if (oops_in_progress) > > > locked = spin_trylock_irqsave(&port->lock, flags); > > > else > > > spin_lock_irqsave(&port->lock, flags); > > > } > > > > > > I'm quite positive we should do the same for panic drm drivers. > > > > Yeah Ideally all the locking in the drm path would be trylock only. > > > > I wonder whether lockdep could help us validate this, with some "don't > > allow anything except trylocks in this context". It's easy to audit the > > core code with review, but drivers are much tougher. And often end up with > > really deep callchains to get at the backing buffers. > [……] > > > > > Instead what I had in mind is to recreate the worst > > > > > possible panic context as much as feasible (disabling interrupts should be > > > > > a good start, maybe we can even do an nmi callback), and then call our > > > > > panic implementation. That way we can test the panic handler in a > > > > > non-destructive way (i.e. aside from last dmesg lines printed to the > > > > > screen nothing bad happens to the kernel: No real panic, no oops, no > > > > > tainting). > > > > > > > > The interrupt case I can do, nmi I have no idea. > > > > > > > > > > I agree too. Disabling interrupts + CONFIG_DEBUG_ATOMIC_SLEEP > > > would be a nice non-destructive test-case emulation. > > > > See above, if we can somehow emulate "all locks are held, only allow > > trylock" with lockdep that would be great too. > > > > Hmmmm, to prototype things faster, I'll do it as a hook: > > spin_lock(spinlock_t *lock) > { > might_spin_forever_under_panic_path(); > ... > } > > Just like how mutexes and DEBUG_ATOMIC_SLEEP do it today: > > void mutex_lock(struct mutex *lock) > { > might_sleep(); > ... > } > > Hopefully the locking maintainers can accept something like this. > If not, let's play with lockdep (which is a bit complex). > > ===> Thanks to Sebastian for suggesting this! Ah yes this is a very nice idea. We only need to make sure that we don't enable any of this for a real oops. Or we just scroll away the real oops with tons of warnings. -Daniel > > > Plus nmi context, once/if that somehow becomes relevant. > > > > The thing that killed the old drm panic handling code definitely was that > > we flat out couldnt' test it except with real oopses. And that's just > > whack-a-mole and bug reporter frustration if you first have a few patch > > iterations around "oh sry, it's still some oops in the panic handler, not > > the first one that we're seeing" :-/ > > > > Oh, that's a critical point: > > full data > some data > no data > invalid/useless data > > First impressions for the feature will definitely count. Hopefully > the hooks above and some heavy DRM CI testing __beforehand__ can > improve things before publishing to a wider audience.. Yup, let's get this right! -Daniel
On Wed, Mar 13, 2019 at 04:54:50PM +0100, Christian König wrote: > Am 13.03.19 um 16:38 schrieb Michel Dänzer: > > On 2019-03-13 2:37 p.m., Christian König wrote: > > > Am 13.03.19 um 14:31 schrieb Ville Syrjälä: > > > > On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: > > > > > On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: > > > > > > Den 12.03.2019 17.17, skrev Ville Syrjälä: > > > > > > > On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > > > > > > > > On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > > > > > > > > > This adds support for outputting kernel messages on panic(). > > > > > > > > > A kernel message dumper is used to dump the log. The dumper iterates > > > > > > > > > over each DRM device and it's crtc's to find suitable framebuffers. > > > > > > > > > > > > > > > > > > All the other dumpers are run before this one except mtdoops. > > > > > > > > > Only atomic drivers are supported. > > > > > > > > > > > > > > > > > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > > > > > > > > > --- > > > > > > > > > [...] > > > > > > > > > > > > > > > > > > diff --git a/include/drm/drm_framebuffer.h > > > > > > > > > b/include/drm/drm_framebuffer.h > > > > > > > > > index f0b34c977ec5..f3274798ecfe 100644 > > > > > > > > > --- a/include/drm/drm_framebuffer.h > > > > > > > > > +++ b/include/drm/drm_framebuffer.h > > > > > > > > > @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > > > > > > > > > struct drm_file *file_priv, unsigned flags, > > > > > > > > > unsigned color, struct drm_clip_rect *clips, > > > > > > > > > unsigned num_clips); > > > > > > > > > + > > > > > > > > > + /** > > > > > > > > > + * @panic_vmap: > > > > > > > > > + * > > > > > > > > > + * Optional callback for panic handling. > > > > > > > > > + * > > > > > > > > > + * For vmapping the selected framebuffer in a panic context. > > > > > > > > > Must > > > > > > > > > + * be super careful about locking (only trylocking allowed). > > > > > > > > > + * > > > > > > > > > + * RETURNS: > > > > > > > > > + * > > > > > > > > > + * NULL if it didn't work out, otherwise 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); > > > > > > > > FWIW, the panic_vmap hook cannot work in general with the > > > > > > > > amdgpu/radeon > > > > > > > > drivers: > > > > > > > > > > > > > > > > Framebuffers are normally tiled, writing to them with the CPU > > > > > > > > results in > > > > > > > > garbled output. > > > > > > > > > > > > > > In which case the driver needs to support the ->panic_draw_xy callback, > > > > > > or maybe it's possible to make a generic helper for tiled buffers. > > > > > I'm afraid that won't help, at least not without porting big chunks of > > > > > https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib > > > > > into the kernel, none of which will be used for anything else. > > > > > > > > > > > > > > > > > > There would need to be a mechanism for switching scanout to a linear, > > > > > > > > CPU accessible framebuffer. > > > > > > > I suppose panic_vmap() could just provide a linear temp buffer > > > > > > > to the panic handler, and panic_unmap() could copy the contents > > > > > > > over to the real fb. > > > > > Copy how? Using a GPU engine? > > > > CPU maybe? Though I suppose that won't work if the buffer isn't CPU > > > > accesible :/ > > > Well we do have a debug path for accessing invisible memory with the CPU. > > > > > > E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can > > > just read/write DATA over and over again if you want to access some memory. > > Right. I assume that'll be very slow, but I guess it could do when the > > memory isn't directly CPU accessible. > > Just made a quick test and reading 33423360 bytes (4096x2040x4) using that > interfaces takes about 13 seconds. > > IIRC we don't use the auto increment optimization yet, so that can probably > be improved by a factor of 3 or more. > > > > But turning of tilling etc is still extremely tricky when the system is > > > already unstable. > > Maybe we could add a little hook to the display code, which just > > disables tiling for scanout and maybe disables non-primary planes, but > > doesn't touch anything else. Harry / Nicholas, does that seem feasible? > > > > > > I'm coming around from "this is never going to work" to "it might > > actually work" with our hardware... > > Yeah, agree. It's a bit tricky, but doable. > > Takeaway for Noralf is that this whole vmap on panic won't even remotely > work. We need to get the data byte by byte without a page mapping if that is > ever going to fly. Don't be hung up on the vmap, the main interface is a pixel-by-pixel panic_draw_xy. Plus panic_prepare/cleanup to do whatever setup you need to do (like disable tiling to avoid having a copy of the detiling library in the kernel for nothing else than oops printing). vmap is just what conveniently works for noral on his cma drivers. The important bit is that we run the minimal amount of code to draw pixels onto the current scanout buffers. For multiple I think best is to just pick the biggest visible rectangle, but not do stuff like disable planes - that sounds way too risky. Usually there's a main window that should be big enough to show the oops. -Daniel
On 2019-03-14, Daniel Vetter <daniel@ffwll.ch> wrote: > That's why we came up with the trylock + immediate bail out design if > that fails. Plus really only render the oops int whatever is the > current display buffer, so that we don't have to do any hw programming > at all. I think this is your best option. The real work will be identifying any/all spin locking that currently exists. For all of those, the code needs to change to: 1. trylock if oops_in_progress, otherwise spinlock 2. if trylock fails, the code must have a sane failure The 2nd point will be the difficult one. For example, you may have functions without a return value taking spinlocks. But now those functions could fail. John Ogness
On Wed, Mar 13, 2019 at 05:41:52PM +0000, Kazlauskas, Nicholas wrote: > On 3/13/19 1:33 PM, Michel Dänzer wrote: > > On 2019-03-13 5:16 p.m., Kazlauskas, Nicholas wrote: > >> On 3/13/19 11:54 AM, Christian König wrote: > >>> Am 13.03.19 um 16:38 schrieb Michel Dänzer: > >>>> On 2019-03-13 2:37 p.m., Christian König wrote: > >>>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: > >>>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: > >>>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: > >>>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: > >>>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > >>>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > >>>>>>>>>>> This adds support for outputting kernel messages on panic(). > >>>>>>>>>>> A kernel message dumper is used to dump the log. The dumper > >>>>>>>>>>> iterates > >>>>>>>>>>> over each DRM device and it's crtc's to find suitable > >>>>>>>>>>> framebuffers. > >>>>>>>>>>> > >>>>>>>>>>> All the other dumpers are run before this one except mtdoops. > >>>>>>>>>>> Only atomic drivers are supported. > >>>>>>>>>>> > >>>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > >>>>>>>>>>> --- > >>>>>>>>>>> [...] > >>>>>>>>>>> > >>>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h > >>>>>>>>>>> b/include/drm/drm_framebuffer.h > >>>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 > >>>>>>>>>>> --- a/include/drm/drm_framebuffer.h > >>>>>>>>>>> +++ b/include/drm/drm_framebuffer.h > >>>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >>>>>>>>>>> struct drm_file *file_priv, unsigned flags, > >>>>>>>>>>> unsigned color, struct drm_clip_rect *clips, > >>>>>>>>>>> unsigned num_clips); > >>>>>>>>>>> + > >>>>>>>>>>> + /** > >>>>>>>>>>> + * @panic_vmap: > >>>>>>>>>>> + * > >>>>>>>>>>> + * Optional callback for panic handling. > >>>>>>>>>>> + * > >>>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. > >>>>>>>>>>> Must > >>>>>>>>>>> + * be super careful about locking (only trylocking allowed). > >>>>>>>>>>> + * > >>>>>>>>>>> + * RETURNS: > >>>>>>>>>>> + * > >>>>>>>>>>> + * NULL if it didn't work out, otherwise 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); > >>>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the > >>>>>>>>>> amdgpu/radeon > >>>>>>>>>> drivers: > >>>>>>>>>> > >>>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU > >>>>>>>>>> results in > >>>>>>>>>> garbled output. > >>>>>>>>>> > >>>>>>>> In which case the driver needs to support the ->panic_draw_xy > >>>>>>>> callback, > >>>>>>>> or maybe it's possible to make a generic helper for tiled buffers. > >>>>>>> I'm afraid that won't help, at least not without porting big chunks of > >>>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib > >>>>>>> into the kernel, none of which will be used for anything else. > >>>>>>> > >>>>>>> > >>>>>>>>>> There would need to be a mechanism for switching scanout to a > >>>>>>>>>> linear, > >>>>>>>>>> CPU accessible framebuffer. > >>>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer > >>>>>>>>> to the panic handler, and panic_unmap() could copy the contents > >>>>>>>>> over to the real fb. > >>>>>>> Copy how? Using a GPU engine? > >>>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU > >>>>>> accesible :/ > >>>>> Well we do have a debug path for accessing invisible memory with the > >>>>> CPU. > >>>>> > >>>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can > >>>>> just read/write DATA over and over again if you want to access some > >>>>> memory. > >>>> Right. I assume that'll be very slow, but I guess it could do when the > >>>> memory isn't directly CPU accessible. > >>> > >>> Just made a quick test and reading 33423360 bytes (4096x2040x4) using > >>> that interfaces takes about 13 seconds. > >>> > >>> IIRC we don't use the auto increment optimization yet, so that can > >>> probably be improved by a factor of 3 or more. > > > > I'd assume only writes are needed, no reads. > > > > > >>>>> But turning of tilling etc is still extremely tricky when the system is > >>>>> already unstable. > >>>> Maybe we could add a little hook to the display code, which just > >>>> disables tiling for scanout and maybe disables non-primary planes, but > >>>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? > >>>> > >>>> > >>>> I'm coming around from "this is never going to work" to "it might > >>>> actually work" with our hardware... > >>> > >>> Yeah, agree. It's a bit tricky, but doable. > >> > >> A "disable_tiling" hook or something along those lines could work for > >> display. It's a little bit non trivial when you want to start dealing > >> with locking and any active DRM commits, but we have a global lock > >> around all our hardware programming anyway that makes that easier to > >> deal with. > > > > This code only runs when there's a kernel panic, so it doesn't have to > > care about such things. :) In fact, it should rather avoid them and only > > do the absolute minimum register writes needed to do the job. > > Good point. I guess it doesn't really matter at that point. > > > > > > >> I think we can just re-commit and update the existing hardware state > >> with only the tiling info for every plane reset to off. > > > > There's also a concern that non-primary planes might prevent the output > > from being visible. > > I would just hope that the overlay doesn't cover the full screen, or > that the panic handler writes to all buffers if possible. > > DC would have to swap the current context in order to disable planes and > we'd have to do memory allocations / frees etc in order to do so. You can't. And I'm with Michel, you don't want to run any of the DC code. Pulling in ~100kloc of dependency in panic context just doesn't work (that's what we tried before essentially). What you can do is - trylock, and bail out if any lock is locked (since that means undefined hw or sw state, and then you really shouldn't touch anything) - maybe write a few very specific registers with the most minimal amount of code (since we need to validate every line that runs in oops/panic context to make sure it still works in nmi context). Stuff like disabling tiling (on i915 that's just flipping a bit in a register). - memory writes (or that slow indirect write interface you have for cpu invisible vram) to draw the actual oops. Everything else is out of question. Especially calling existing display code just doesn't work, it's ime impossible to keep allocations/spinlocks and stuff like that out of it. -Daniel > >> For most buffers I don't think we'd have to really consider changing > >> anything else here as long as you respect the current FB size and > >> pitch. > > > > Hmm, e.g. compression might also need to be disabled? Or maybe that's > > implicitly off with no tiling? > > > > > > Resetting the tiling info will also disable the compression in this > case, so the buffer should still be usable for this purpose. > > Nicholas Kazlauskas
On 2019-03-14, John Ogness <john.ogness@linutronix.de> wrote: > On 2019-03-14, Daniel Vetter <daniel@ffwll.ch> wrote: >> That's why we came up with the trylock + immediate bail out design if >> that fails. Plus really only render the oops int whatever is the >> current display buffer, so that we don't have to do any hw >> programming at all. > > I think this is your best option. The real work will be identifying > any/all spin locking that currently exists. For all of those, the code > needs to change to: > > 1. trylock if oops_in_progress, otherwise spinlock On second thought, you shouldn't use oops_in_progress. It would be better if DRM had its own flag to signify that it is currently being used in kmsg_dump context. > 2. if trylock fails, the code must have a sane failure > > The 2nd point will be the difficult one. For example, you may have > functions without a return value taking spinlocks. But now those > functions could fail. John Ogness
On 3/14/19 5:50 AM, Daniel Vetter wrote: > On Wed, Mar 13, 2019 at 05:41:52PM +0000, Kazlauskas, Nicholas wrote: >> On 3/13/19 1:33 PM, Michel Dänzer wrote: >>> On 2019-03-13 5:16 p.m., Kazlauskas, Nicholas wrote: >>>> On 3/13/19 11:54 AM, Christian König wrote: >>>>> Am 13.03.19 um 16:38 schrieb Michel Dänzer: >>>>>> On 2019-03-13 2:37 p.m., Christian König wrote: >>>>>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: >>>>>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: >>>>>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: >>>>>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: >>>>>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: >>>>>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: >>>>>>>>>>>>> This adds support for outputting kernel messages on panic(). >>>>>>>>>>>>> A kernel message dumper is used to dump the log. The dumper >>>>>>>>>>>>> iterates >>>>>>>>>>>>> over each DRM device and it's crtc's to find suitable >>>>>>>>>>>>> framebuffers. >>>>>>>>>>>>> >>>>>>>>>>>>> All the other dumpers are run before this one except mtdoops. >>>>>>>>>>>>> Only atomic drivers are supported. >>>>>>>>>>>>> >>>>>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> >>>>>>>>>>>>> --- >>>>>>>>>>>>> [...] >>>>>>>>>>>>> >>>>>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h >>>>>>>>>>>>> b/include/drm/drm_framebuffer.h >>>>>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 >>>>>>>>>>>>> --- a/include/drm/drm_framebuffer.h >>>>>>>>>>>>> +++ b/include/drm/drm_framebuffer.h >>>>>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { >>>>>>>>>>>>> struct drm_file *file_priv, unsigned flags, >>>>>>>>>>>>> unsigned color, struct drm_clip_rect *clips, >>>>>>>>>>>>> unsigned num_clips); >>>>>>>>>>>>> + >>>>>>>>>>>>> + /** >>>>>>>>>>>>> + * @panic_vmap: >>>>>>>>>>>>> + * >>>>>>>>>>>>> + * Optional callback for panic handling. >>>>>>>>>>>>> + * >>>>>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. >>>>>>>>>>>>> Must >>>>>>>>>>>>> + * be super careful about locking (only trylocking allowed). >>>>>>>>>>>>> + * >>>>>>>>>>>>> + * RETURNS: >>>>>>>>>>>>> + * >>>>>>>>>>>>> + * NULL if it didn't work out, otherwise 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); >>>>>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the >>>>>>>>>>>> amdgpu/radeon >>>>>>>>>>>> drivers: >>>>>>>>>>>> >>>>>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU >>>>>>>>>>>> results in >>>>>>>>>>>> garbled output. >>>>>>>>>>>> >>>>>>>>>> In which case the driver needs to support the ->panic_draw_xy >>>>>>>>>> callback, >>>>>>>>>> or maybe it's possible to make a generic helper for tiled buffers. >>>>>>>>> I'm afraid that won't help, at least not without porting big chunks of >>>>>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib >>>>>>>>> into the kernel, none of which will be used for anything else. >>>>>>>>> >>>>>>>>> >>>>>>>>>>>> There would need to be a mechanism for switching scanout to a >>>>>>>>>>>> linear, >>>>>>>>>>>> CPU accessible framebuffer. >>>>>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer >>>>>>>>>>> to the panic handler, and panic_unmap() could copy the contents >>>>>>>>>>> over to the real fb. >>>>>>>>> Copy how? Using a GPU engine? >>>>>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU >>>>>>>> accesible :/ >>>>>>> Well we do have a debug path for accessing invisible memory with the >>>>>>> CPU. >>>>>>> >>>>>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can >>>>>>> just read/write DATA over and over again if you want to access some >>>>>>> memory. >>>>>> Right. I assume that'll be very slow, but I guess it could do when the >>>>>> memory isn't directly CPU accessible. >>>>> >>>>> Just made a quick test and reading 33423360 bytes (4096x2040x4) using >>>>> that interfaces takes about 13 seconds. >>>>> >>>>> IIRC we don't use the auto increment optimization yet, so that can >>>>> probably be improved by a factor of 3 or more. >>> >>> I'd assume only writes are needed, no reads. >>> >>> >>>>>>> But turning of tilling etc is still extremely tricky when the system is >>>>>>> already unstable. >>>>>> Maybe we could add a little hook to the display code, which just >>>>>> disables tiling for scanout and maybe disables non-primary planes, but >>>>>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? >>>>>> >>>>>> >>>>>> I'm coming around from "this is never going to work" to "it might >>>>>> actually work" with our hardware... >>>>> >>>>> Yeah, agree. It's a bit tricky, but doable. >>>> >>>> A "disable_tiling" hook or something along those lines could work for >>>> display. It's a little bit non trivial when you want to start dealing >>>> with locking and any active DRM commits, but we have a global lock >>>> around all our hardware programming anyway that makes that easier to >>>> deal with. >>> >>> This code only runs when there's a kernel panic, so it doesn't have to >>> care about such things. :) In fact, it should rather avoid them and only >>> do the absolute minimum register writes needed to do the job. >> >> Good point. I guess it doesn't really matter at that point. >> >>> >>> >>>> I think we can just re-commit and update the existing hardware state >>>> with only the tiling info for every plane reset to off. >>> >>> There's also a concern that non-primary planes might prevent the output >>> from being visible. >> >> I would just hope that the overlay doesn't cover the full screen, or >> that the panic handler writes to all buffers if possible. >> >> DC would have to swap the current context in order to disable planes and >> we'd have to do memory allocations / frees etc in order to do so. > > You can't. And I'm with Michel, you don't want to run any of the DC code. > Pulling in ~100kloc of dependency in panic context just doesn't work > (that's what we tried before essentially). > > What you can do is > - trylock, and bail out if any lock is locked (since that means undefined > hw or sw state, and then you really shouldn't touch anything) > - maybe write a few very specific registers with the most minimal amount > of code (since we need to validate every line that runs in oops/panic > context to make sure it still works in nmi context). Stuff like > disabling tiling (on i915 that's just flipping a bit in a register). > - memory writes (or that slow indirect write interface you have for > cpu invisible vram) to draw the actual oops. > > Everything else is out of question. Especially calling existing display > code just doesn't work, it's ime impossible to keep allocations/spinlocks > and stuff like that out of it. > -Daniel At some point we'll have to run at least some DC code since disabling tiling and compression is ASIC specific and you need to know what registers to actually write. There's no fast path that DC exposes specifically for this usecase, but locking is controlled outside of DC anyway (so we don't have to lock) and it DC itself won't be doing any memory allocations if it's just disabling tiling and compression. It's not actually that many register reads/writes, and it could probably stripped down to just writes since the reads are only used for sanity checking and the system is going down anyway. That part might need a separate interface for dc_commit_updates_for_stream or for amdgpu_dm to disable sanity checking before the call through DC's debug options. Nicholas Kazlauskas > >>>> For most buffers I don't think we'd have to really consider changing >>>> anything else here as long as you respect the current FB size and >>>> pitch. >>> >>> Hmm, e.g. compression might also need to be disabled? Or maybe that's >>> implicitly off with no tiling? >>> >>> >> >> Resetting the tiling info will also disable the compression in this >> case, so the buffer should still be usable for this purpose. >> >> Nicholas Kazlauskas >
On Thu, Mar 14, 2019 at 10:52:08AM +0100, John Ogness wrote: > On 2019-03-14, John Ogness <john.ogness@linutronix.de> wrote: > > On 2019-03-14, Daniel Vetter <daniel@ffwll.ch> wrote: > >> That's why we came up with the trylock + immediate bail out design if > >> that fails. Plus really only render the oops int whatever is the > >> current display buffer, so that we don't have to do any hw > >> programming at all. > > > > I think this is your best option. The real work will be identifying > > any/all spin locking that currently exists. For all of those, the code > > needs to change to: > > > > 1. trylock if oops_in_progress, otherwise spinlock > > On second thought, you shouldn't use oops_in_progress. It would be > better if DRM had its own flag to signify that it is currently being > used in kmsg_dump context. We use oops_in_progress because the fbcon design is a terrible layer cake. I agree that for the new code we shouldn't use it: New code paths that do trylock only. > > 2. if trylock fails, the code must have a sane failure > > > > The 2nd point will be the difficult one. For example, you may have > > functions without a return value taking spinlocks. But now those > > functions could fail. Nah, imo no code reuse for the oops path. If you do that you sooner or later end up pulling in the 100kloc of modeset code, and we've been there. Doesn't work. New, absolutely minimal oops code, with as little of it shared with normal kms code. That's also why we absolutely have to be able to test this stuff without a real oops. -Daniel
On Thu, Mar 14, 2019 at 12:44:06PM +0000, Kazlauskas, Nicholas wrote: > On 3/14/19 5:50 AM, Daniel Vetter wrote: > > On Wed, Mar 13, 2019 at 05:41:52PM +0000, Kazlauskas, Nicholas wrote: > >> On 3/13/19 1:33 PM, Michel Dänzer wrote: > >>> On 2019-03-13 5:16 p.m., Kazlauskas, Nicholas wrote: > >>>> On 3/13/19 11:54 AM, Christian König wrote: > >>>>> Am 13.03.19 um 16:38 schrieb Michel Dänzer: > >>>>>> On 2019-03-13 2:37 p.m., Christian König wrote: > >>>>>>> Am 13.03.19 um 14:31 schrieb Ville Syrjälä: > >>>>>>>> On Wed, Mar 13, 2019 at 10:35:08AM +0100, Michel Dänzer wrote: > >>>>>>>>> On 2019-03-12 6:15 p.m., Noralf Trønnes wrote: > >>>>>>>>>> Den 12.03.2019 17.17, skrev Ville Syrjälä: > >>>>>>>>>>> On Tue, Mar 12, 2019 at 11:47:04AM +0100, Michel Dänzer wrote: > >>>>>>>>>>>> On 2019-03-11 6:42 p.m., Noralf Trønnes wrote: > >>>>>>>>>>>>> This adds support for outputting kernel messages on panic(). > >>>>>>>>>>>>> A kernel message dumper is used to dump the log. The dumper > >>>>>>>>>>>>> iterates > >>>>>>>>>>>>> over each DRM device and it's crtc's to find suitable > >>>>>>>>>>>>> framebuffers. > >>>>>>>>>>>>> > >>>>>>>>>>>>> All the other dumpers are run before this one except mtdoops. > >>>>>>>>>>>>> Only atomic drivers are supported. > >>>>>>>>>>>>> > >>>>>>>>>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > >>>>>>>>>>>>> --- > >>>>>>>>>>>>> [...] > >>>>>>>>>>>>> > >>>>>>>>>>>>> diff --git a/include/drm/drm_framebuffer.h > >>>>>>>>>>>>> b/include/drm/drm_framebuffer.h > >>>>>>>>>>>>> index f0b34c977ec5..f3274798ecfe 100644 > >>>>>>>>>>>>> --- a/include/drm/drm_framebuffer.h > >>>>>>>>>>>>> +++ b/include/drm/drm_framebuffer.h > >>>>>>>>>>>>> @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { > >>>>>>>>>>>>> struct drm_file *file_priv, unsigned flags, > >>>>>>>>>>>>> unsigned color, struct drm_clip_rect *clips, > >>>>>>>>>>>>> unsigned num_clips); > >>>>>>>>>>>>> + > >>>>>>>>>>>>> + /** > >>>>>>>>>>>>> + * @panic_vmap: > >>>>>>>>>>>>> + * > >>>>>>>>>>>>> + * Optional callback for panic handling. > >>>>>>>>>>>>> + * > >>>>>>>>>>>>> + * For vmapping the selected framebuffer in a panic context. > >>>>>>>>>>>>> Must > >>>>>>>>>>>>> + * be super careful about locking (only trylocking allowed). > >>>>>>>>>>>>> + * > >>>>>>>>>>>>> + * RETURNS: > >>>>>>>>>>>>> + * > >>>>>>>>>>>>> + * NULL if it didn't work out, otherwise 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); > >>>>>>>>>>>> FWIW, the panic_vmap hook cannot work in general with the > >>>>>>>>>>>> amdgpu/radeon > >>>>>>>>>>>> drivers: > >>>>>>>>>>>> > >>>>>>>>>>>> Framebuffers are normally tiled, writing to them with the CPU > >>>>>>>>>>>> results in > >>>>>>>>>>>> garbled output. > >>>>>>>>>>>> > >>>>>>>>>> In which case the driver needs to support the ->panic_draw_xy > >>>>>>>>>> callback, > >>>>>>>>>> or maybe it's possible to make a generic helper for tiled buffers. > >>>>>>>>> I'm afraid that won't help, at least not without porting big chunks of > >>>>>>>>> https://gitlab.freedesktop.org/mesa/mesa/tree/master/src/amd/addrlib > >>>>>>>>> into the kernel, none of which will be used for anything else. > >>>>>>>>> > >>>>>>>>> > >>>>>>>>>>>> There would need to be a mechanism for switching scanout to a > >>>>>>>>>>>> linear, > >>>>>>>>>>>> CPU accessible framebuffer. > >>>>>>>>>>> I suppose panic_vmap() could just provide a linear temp buffer > >>>>>>>>>>> to the panic handler, and panic_unmap() could copy the contents > >>>>>>>>>>> over to the real fb. > >>>>>>>>> Copy how? Using a GPU engine? > >>>>>>>> CPU maybe? Though I suppose that won't work if the buffer isn't CPU > >>>>>>>> accesible :/ > >>>>>>> Well we do have a debug path for accessing invisible memory with the > >>>>>>> CPU. > >>>>>>> > >>>>>>> E.g. three registers: DATA and auto increment OFFSET_LO/HI. So you can > >>>>>>> just read/write DATA over and over again if you want to access some > >>>>>>> memory. > >>>>>> Right. I assume that'll be very slow, but I guess it could do when the > >>>>>> memory isn't directly CPU accessible. > >>>>> > >>>>> Just made a quick test and reading 33423360 bytes (4096x2040x4) using > >>>>> that interfaces takes about 13 seconds. > >>>>> > >>>>> IIRC we don't use the auto increment optimization yet, so that can > >>>>> probably be improved by a factor of 3 or more. > >>> > >>> I'd assume only writes are needed, no reads. > >>> > >>> > >>>>>>> But turning of tilling etc is still extremely tricky when the system is > >>>>>>> already unstable. > >>>>>> Maybe we could add a little hook to the display code, which just > >>>>>> disables tiling for scanout and maybe disables non-primary planes, but > >>>>>> doesn't touch anything else. Harry / Nicholas, does that seem feasible? > >>>>>> > >>>>>> > >>>>>> I'm coming around from "this is never going to work" to "it might > >>>>>> actually work" with our hardware... > >>>>> > >>>>> Yeah, agree. It's a bit tricky, but doable. > >>>> > >>>> A "disable_tiling" hook or something along those lines could work for > >>>> display. It's a little bit non trivial when you want to start dealing > >>>> with locking and any active DRM commits, but we have a global lock > >>>> around all our hardware programming anyway that makes that easier to > >>>> deal with. > >>> > >>> This code only runs when there's a kernel panic, so it doesn't have to > >>> care about such things. :) In fact, it should rather avoid them and only > >>> do the absolute minimum register writes needed to do the job. > >> > >> Good point. I guess it doesn't really matter at that point. > >> > >>> > >>> > >>>> I think we can just re-commit and update the existing hardware state > >>>> with only the tiling info for every plane reset to off. > >>> > >>> There's also a concern that non-primary planes might prevent the output > >>> from being visible. > >> > >> I would just hope that the overlay doesn't cover the full screen, or > >> that the panic handler writes to all buffers if possible. > >> > >> DC would have to swap the current context in order to disable planes and > >> we'd have to do memory allocations / frees etc in order to do so. > > > > You can't. And I'm with Michel, you don't want to run any of the DC code. > > Pulling in ~100kloc of dependency in panic context just doesn't work > > (that's what we tried before essentially). > > > > What you can do is > > - trylock, and bail out if any lock is locked (since that means undefined > > hw or sw state, and then you really shouldn't touch anything) > > - maybe write a few very specific registers with the most minimal amount > > of code (since we need to validate every line that runs in oops/panic > > context to make sure it still works in nmi context). Stuff like > > disabling tiling (on i915 that's just flipping a bit in a register). > > - memory writes (or that slow indirect write interface you have for > > cpu invisible vram) to draw the actual oops. > > > > Everything else is out of question. Especially calling existing display > > code just doesn't work, it's ime impossible to keep allocations/spinlocks > > and stuff like that out of it. > > -Daniel > > At some point we'll have to run at least some DC code since disabling > tiling and compression is ASIC specific and you need to know what > registers to actually write. > > There's no fast path that DC exposes specifically for this usecase, but > locking is controlled outside of DC anyway (so we don't have to lock) > and it DC itself won't be doing any memory allocations if it's just > disabling tiling and compression. > > It's not actually that many register reads/writes, and it could probably > stripped down to just writes since the reads are only used for sanity > checking and the system is going down anyway. That part might need a > separate interface for dc_commit_updates_for_stream or for amdgpu_dm to > disable sanity checking before the call through DC's debug options. I'd copypaste that code for oops handling. Resetting to untiled shouldn't be that much code. And the risk of accidentally pulling in a lot of other bits are imo too high. -Daniel
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index bd943a71756c..74c37542f857 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -14,6 +14,9 @@ menuconfig DRM select I2C_ALGOBIT select DMA_SHARED_BUFFER select SYNC_FILE + select FONT_SUPPORT + select FONT_8x8 + select FONT_8x16 help Kernel-level support for the Direct Rendering Infrastructure (DRI) introduced in XFree86 4.0. If you say Y here, you need to select diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 50d849d1bc6e..b0b870b5dd55 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -1147,6 +1147,7 @@ static const struct file_operations drm_stub_fops = { static void drm_core_exit(void) { + drm_panic_exit(drm_debugfs_root); unregister_chrdev(DRM_MAJOR, "drm"); debugfs_remove(drm_debugfs_root); drm_sysfs_destroy(); @@ -1178,6 +1179,8 @@ static int __init drm_core_init(void) if (ret < 0) goto error; + drm_panic_init(drm_debugfs_root); + drm_core_init_complete = true; DRM_DEBUG("Initialized\n"); diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c index d8d75e25f6fb..da2285c5ae90 100644 --- a/drivers/gpu/drm/drm_framebuffer.c +++ b/drivers/gpu/drm/drm_framebuffer.c @@ -1087,3 +1087,120 @@ int drm_framebuffer_debugfs_init(struct drm_minor *minor) minor->debugfs_root, minor); } #endif + +/** + * drm_framebuffer_panic_draw_xy - draw pixel on fb during panic() + * @fb: DRM framebuffer + * @vmap: Linear virtual mapping + * @x: X coordinate + * @y: Y coordinate + * @foreground: Foreground pixel + * + * This function can be used to draw a pixel during panic message rendering. + * It requires @vmap to be a linear mapping. This is the default implementation + * used if &drm_framebuffer_funcs->panic_draw_xy is not set. + */ +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, + int x, int y, bool foreground) +{ + void *pix = vmap + fb->offsets[0] + (y * fb->pitches[0]); + u8 *pix8 = pix; + u16 *pix16 = pix; + u32 *pix32 = pix; + + switch (fb->format->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: + pix8[x] = foreground ? 0xff : 0x00; + break; + + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR4444: + pix16[x] = foreground ? 0xffff : 0xf000; + break; + + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA4444: + pix16[x] = foreground ? 0xffff : 0x000f; + break; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ABGR1555: + pix16[x] = foreground ? 0xffff : 0x8000; + break; + + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRA5551: + pix16[x] = foreground ? 0xffff : 0x0001; + break; + + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + pix16[x] = foreground ? 0xffff : 0x0000; + break; + + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + pix8[x * 3] = foreground ? 0xff : 0x00; + pix8[(x * 3) + 1] = pix8[x]; + pix8[(x * 3) + 2] = pix8[x]; + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + pix32[x] = foreground ? 0xffffffff : 0xff000000; + break; + + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + pix32[x] = foreground ? 0xffffffff : 0x000000ff; + break; + + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ABGR2101010: + pix32[x] = foreground ? 0xffffffff : 0xc0000000; + break; + + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_BGRA1010102: + pix32[x] = foreground ? 0xffffffff : 0x00000003; + break; + } +} +EXPORT_SYMBOL(drm_framebuffer_panic_draw_xy); diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index 251d67e04c2d..694a5803ac3c 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -191,3 +191,7 @@ int drm_syncobj_signal_ioctl(struct drm_device *dev, void *data, void drm_framebuffer_print_info(struct drm_printer *p, unsigned int indent, const struct drm_framebuffer *fb); int drm_framebuffer_debugfs_init(struct drm_minor *minor); + +/* drm_panic.c */ +void drm_panic_init(struct dentry *debugfs_root); +void drm_panic_exit(struct dentry *debugfs_root); diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c new file mode 100644 index 000000000000..4bca7f0bc369 --- /dev/null +++ b/drivers/gpu/drm/drm_panic.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2018 Noralf Trønnes + +#include <linux/debugfs.h> +#include <linux/font.h> +#include <linux/kernel.h> +#include <linux/kmsg_dump.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_file.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> +#include <drm/drmP.h> + +#include "drm_internal.h" + +/* + * The log lines in an ARM stack dump are 92 characters long + * and 120 is a nice multiple for HD and 4K. + */ +#define DRM_PANIC_COLUMN_WIDTH 120 + +struct drm_panic_ctx { + struct drm_framebuffer *fb; + unsigned int width; + unsigned int height; + void *vmap; + + const struct font_desc *font; + unsigned int col_width; + unsigned int bottom_y; + size_t max_line_length; + + unsigned int x; + unsigned int y; +}; + +static const struct font_desc *drm_panic_font8x8, *drm_panic_font8x16; + +static void drm_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, + int x, int y, bool fg) +{ + if (fb->funcs->panic_draw_xy) + fb->funcs->panic_draw_xy(fb, vmap, x, y, fg); + else + drm_framebuffer_panic_draw_xy(fb, vmap, x, y, fg); +} + +static void drm_panic_render_char(struct drm_panic_ctx *ctx, + unsigned int offset, char c) +{ + unsigned int h, w, x, y; + u8 fontline; + + for (h = 0; h < ctx->font->height; h++) { + fontline = *(u8 *)(ctx->font->data + c * ctx->font->height + h); + + for (w = 0; w < ctx->font->width; w++) { + x = ctx->x + (offset * ctx->font->width) + w; + y = ctx->y + h; + drm_panic_draw_xy(ctx->fb, ctx->vmap, x, y, + fontline & BIT(7 - w)); + } + } +} + +static void drm_panic_render_line(struct drm_panic_ctx *ctx, + const char *line, size_t len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + drm_panic_render_char(ctx, i, line[i]); + + /* Clear out the rest of the line */ + for (i = len; i < ctx->max_line_length; i++) + drm_panic_render_char(ctx, i, ' '); +} + +static bool drm_panic_newline(struct drm_panic_ctx *ctx) +{ + if (ctx->x == 0 && ctx->y == 0) + return false; + if (ctx->y == 0) { + ctx->x -= ctx->col_width; + ctx->y = ctx->bottom_y; + } else { + ctx->y -= ctx->font->height; + } + + return true; +} + +/* Render from bottom right most column */ +static bool drm_panic_render_lines(struct drm_panic_ctx *ctx, + const char *str, size_t len) +{ + size_t l, line_length; + const char *pos; + int i; + + while (len) { + pos = str + len - 1; + + if (*pos == '\n') { + len--; + if (!drm_panic_newline(ctx)) + return false; + continue; + } + + while (pos > str && *(pos - 1) != '\n') + pos--; + + line_length = len - (pos - str); + + if (!line_length || len < line_length) { + pr_err("%s: Bug! line_length=%zu len=%zu\n", + __func__, line_length, len); + return false; + } + + len -= line_length; + + for (i = DIV_ROUND_UP(line_length, ctx->max_line_length) - 1; i >= 0; i--) { + l = min(ctx->max_line_length, line_length - i * ctx->max_line_length); + drm_panic_render_line(ctx, pos + (i * ctx->max_line_length), l); + if (i && !drm_panic_newline(ctx)) + return false; + } + } + + return true; +} + +static void drm_panic_kmsg_render_screen(struct drm_plane *plane, + struct kmsg_dumper *dumper) +{ + struct drm_framebuffer *fb = plane->state->fb; + bool first_iteration = true; + struct drm_panic_ctx ctx; + static char text[1024]; + size_t len; + + ctx.vmap = fb->funcs->panic_vmap(fb); + + /* Print some info when testing */ + if (dumper->max_reason == KMSG_DUMP_OOPS) { + struct drm_format_name_buf format_name; + + pr_info("%s: [FB:%d] %ux%u format=%s vmap=%p\n", + __func__, fb->base.id, fb->width, fb->height, + drm_get_format_name(fb->format->format, &format_name), + ctx.vmap); + } + + if (!ctx.vmap) + return; + + ctx.fb = fb; + ctx.width = drm_rect_width(&plane->state->src) >> 16; + ctx.height = drm_rect_height(&plane->state->src) >> 16; + + /* + * TODO: + * Find which part of the fb that is visible. + * Width and height are zero on vc4 + */ + if (!ctx.width || !ctx.height) { + ctx.width = fb->width; + ctx.height = fb->height; + } + + /* Try to fit 50 lines */ + if (ctx.height < 50 * 16 && drm_panic_font8x8) + ctx.font = drm_panic_font8x8; + else + ctx.font = drm_panic_font8x16; + + ctx.col_width = DRM_PANIC_COLUMN_WIDTH * ctx.font->width; + ctx.bottom_y = ((ctx.height / ctx.font->height) - 1) * ctx.font->height; + + if (ctx.width < 2 * ctx.col_width) + ctx.max_line_length = ctx.width / ctx.font->width; + else + ctx.max_line_length = DRM_PANIC_COLUMN_WIDTH - 2; /* border=2 */ + + ctx.x = 0; + ctx.y = ctx.bottom_y; + if (ctx.width > ctx.col_width) + ctx.x = ((ctx.width / ctx.col_width) - 1) * ctx.col_width; + + pr_debug("%s: font=%s %ux%u max_line_length=%zu (%u,%u)\n", + __func__, ctx.font->name, ctx.width, ctx.height, + ctx.max_line_length, ctx.x, ctx.y); + + kmsg_dump_rewind(dumper); + + /* The latest messages are fetched first */ + while (kmsg_dump_get_buffer(dumper, false, text, sizeof(text), &len)) { + /* Strip off the very last newline so we start at the bottom */ + if (first_iteration) { + len--; + first_iteration = false; + } + + if (!drm_panic_render_lines(&ctx, text, len)) + break; + } + + if (fb->funcs->panic_vunmap) + fb->funcs->panic_vunmap(fb, vmap); +} + +static void drm_panic_try_dev(struct drm_device *dev, struct kmsg_dumper *dumper) +{ + struct drm_framebuffer *fb; + struct drm_plane *plane; + struct drm_crtc *crtc; + + if (!drm_core_check_feature(dev, DRIVER_ATOMIC)) + return; + + if (dumper->max_reason == KMSG_DUMP_OOPS) + pr_info("%s: %s on minor %d\n", __func__, dev->driver->name, + dev->primary->index); + + drm_for_each_crtc(crtc, dev) { + if (!ww_mutex_trylock(&crtc->mutex.mutex)) + continue; + + if (!crtc->enabled || !crtc->primary) + goto crtc_unlock; + + if (!crtc->state || !crtc->state->active) + goto crtc_unlock; + + plane = crtc->primary; + if (!ww_mutex_trylock(&plane->mutex.mutex)) + goto crtc_unlock; + + /* + * TODO: Should we check plane_state->visible? + * It is not set on vc4 + if (!plane->state || !plane->state->visible) + */ + if (!plane->state) + goto plane_unlock; + + fb = plane->state->fb; + if (!fb || !fb->funcs->panic_vmap) + goto plane_unlock; + + /* + * fbcon puts out panic messages so stay away to avoid jumbled + * output. If vc->vc_mode != KD_TEXT fbcon won't put out + * messages (see vt_console_print). + */ + if (dev->fb_helper && dev->fb_helper->fb == fb) + goto plane_unlock; + + drm_panic_kmsg_render_screen(plane, dumper); +plane_unlock: + ww_mutex_unlock(&plane->mutex.mutex); +crtc_unlock: + ww_mutex_unlock(&crtc->mutex.mutex); + } +} + +static int drm_panic_dev_iter(struct device *dev, void *data) +{ + struct drm_minor *minor = dev_get_drvdata(dev); + + if (minor && minor->type == DRM_MINOR_PRIMARY) + drm_panic_try_dev(minor->dev, data); + + return 0; +} + +static void drm_panic_kmsg_dump(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason) +{ + class_for_each_device(drm_class, NULL, dumper, drm_panic_dev_iter); +} + +static struct kmsg_dumper drm_panic_kmsg_dumper = { + .dump = drm_panic_kmsg_dump, + .max_reason = KMSG_DUMP_PANIC, +}; + +static ssize_t drm_panic_file_panic_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long long val; + char buf[24]; + size_t size; + ssize_t ret; + + 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; + + drm_panic_kmsg_dumper.max_reason = KMSG_DUMP_OOPS; + wmb(); + + /* Do a real test with: echo c > /proc/sysrq-trigger */ + + if (val == 0) { + pr_info("Test panic screen using kmsg_dump(OOPS)\n"); + kmsg_dump(KMSG_DUMP_OOPS); + } else if (val == 1) { + char *null_pointer = NULL; + + pr_info("Test panic screen using NULL pointer dereference\n"); + *null_pointer = 1; + } else { + return -EINVAL; + } + + return count; +} + +static const struct file_operations drm_panic_panic_ops = { + .write = drm_panic_file_panic_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static struct dentry *drm_panic_d_panic; + +void __init drm_panic_init(struct dentry *debugfs_root) +{ + drm_panic_font8x8 = find_font("VGA8x8"); + drm_panic_font8x16 = find_font("VGA8x16"); + if (!drm_panic_font8x16) { + DRM_WARN("Couldn't find font, panic screen disabled\n"); + return; + } + + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, + debugfs_root, NULL, + &drm_panic_panic_ops); + kmsg_dump_register(&drm_panic_kmsg_dumper); +} + +void drm_panic_exit(struct dentry *debugfs_root) +{ + kmsg_dump_unregister(&drm_panic_kmsg_dumper); + debugfs_remove(drm_panic_d_panic); +} diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h index f0b34c977ec5..f3274798ecfe 100644 --- a/include/drm/drm_framebuffer.h +++ b/include/drm/drm_framebuffer.h @@ -94,6 +94,44 @@ struct drm_framebuffer_funcs { struct drm_file *file_priv, unsigned flags, unsigned color, struct drm_clip_rect *clips, unsigned num_clips); + + /** + * @panic_vmap: + * + * Optional callback for panic handling. + * + * For vmapping the selected framebuffer in a panic context. Must + * be super careful about locking (only trylocking allowed). + * + * RETURNS: + * + * NULL if it didn't work out, otherwise 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); + + /** + * @panic_vunmap: + * + * Optional callback for cleaning up after panic testing. + * + * Crtc and plane locks are released after this callback has run. + * vmap is the cookie returned by @panic_vmap. + */ + void (*panic_vunmap)(struct drm_framebuffer *fb, void *vmap); + + /** + * @panic_draw_xy: + * + * Optional callback for drawing pixels during panic. + * + * For drawing pixels onto a framebuffer prepared with @panic_vmap. + * vmap is the cookie returned by @panic_vmap. + * If it's not set, drm_framebuffer_panic_draw_xy() is used. + */ + void (*panic_draw_xy)(struct drm_framebuffer *fb, void *vmap, + int x, int y, bool foreground); }; /** @@ -293,4 +331,7 @@ int drm_framebuffer_plane_width(int width, int drm_framebuffer_plane_height(int height, const struct drm_framebuffer *fb, int plane); +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, + int x, int y, bool foreground); + #endif
This adds support for outputting kernel messages on panic(). A kernel message dumper is used to dump the log. The dumper iterates over each DRM device and it's crtc's to find suitable framebuffers. All the other dumpers are run before this one except mtdoops. Only atomic drivers are supported. Signed-off-by: Noralf Trønnes <noralf@tronnes.org> --- drivers/gpu/drm/Kconfig | 3 + drivers/gpu/drm/drm_drv.c | 3 + drivers/gpu/drm/drm_framebuffer.c | 117 ++++++++++ drivers/gpu/drm/drm_internal.h | 4 + drivers/gpu/drm/drm_panic.c | 363 ++++++++++++++++++++++++++++++ include/drm/drm_framebuffer.h | 41 ++++ 6 files changed, 531 insertions(+) create mode 100644 drivers/gpu/drm/drm_panic.c