Den 11.09.2016 20:47, skrev Noralf Trønnes: > This adds support for outputting kernel messages on panic(). > A circular buffer is used to collect kernel messages. > On panic() the notifier function loops over each DRM device and > it's crtc's to find suitable framebuffers. On the next > console->write(), the messages in the circular buffer are rendered > on each of the recorded framebuffers. > > Only atomic drivers are supported. > > Signed-off-by: Noralf Trønnes <noralf at tronnes.org> > --- [...] > diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c [...] > +static bool drm_panic_add_fb(struct drm_crtc *crtc, struct drm_plane *plane) > +{ > + unsigned int width = drm_rect_width(&plane->state->src) >> 16; > + unsigned int height = drm_rect_height(&plane->state->src) >> 16;
I just tried this on vc4 (which uses fb_cma_helper) and width/height is zero. Additionally plane->state->visible is false. Maybe something the driver should set? If I work around that (use fb->width/height), then I get panic output on vc4 also in addition to simpledrm. Noralf. > + struct drm_framebuffer *fb = plane->fb; > + const struct font_desc *font; > + struct drm_panic_fb *pfb; > + void *vmap; > + > + if (!width || !height || !fb || !fb->funcs || !fb->funcs->panic_vmap) > + return false; > + > + /* only 8-bit wide fonts are supported */ > + font = get_default_font(width, height, BIT(7), -1); > + if (!font) { > + drm_panic_log("Couldn't get font\n"); > + return false; > + } > + > + vmap = fb->funcs->panic_vmap(fb); > + if (!vmap) { > + drm_panic_log("panic_vmap() returned NULL\n"); > + return false; > + } > + > + pfb = &drm_panic_fbs[drm_panic_fbs_num++]; > + pfb->plane = plane; > + pfb->crtc = crtc; > + pfb->fb = fb; > + pfb->width = width; > + pfb->height = height; > + pfb->vmap = vmap; > + pfb->font = font; > + pfb->cols = pfb->width / font->width; > + pfb->rows = pfb->height / font->height; > + > + drm_panic_log(" [FB:%d] %ux%u->%ux%u, %s, format=0x%08x\n", > + fb->base.id, pfb->width, pfb->height, pfb->cols, > + pfb->rows, font->name, fb->pixel_format); > + > + return true; > +} > + > +static void drm_panic_add(struct drm_device *drm) > +{ > + struct drm_plane *plane; > + struct drm_crtc *crtc; > + > + if (!drm || !drm->driver || > + !(drm->driver->driver_features & DRIVER_ATOMIC)) > + return; > + > + drm_panic_log("%s on minor %d\n", drm->driver->name, > + drm->primary ? drm->primary->index : -1); > + > + drm_for_each_crtc(crtc, drm) { > + drm_panic_log(" %s\n", crtc->name); > + > + if (drm_panic_fbs_num >= DRM_PANIC_MAX_FBS) > + return; > + > + 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; > + > + if (!plane->state || !plane->state->visible) > + goto plane_unlock; > + > + if (drm_panic_add_fb(crtc, plane)) > + continue; > + > +plane_unlock: > + ww_mutex_unlock(&plane->mutex.mutex); > +crtc_unlock: > + ww_mutex_unlock(&crtc->mutex.mutex); > + } > +} > + > +static int drm_panic_class_iter(struct device *dev, void *data) > +{ > + struct drm_minor *minor; > + > + minor = dev_get_drvdata(dev); > + > + if (minor && minor->type == DRM_MINOR_PRIMARY) > + drm_panic_add(minor->dev); > + > + return 0; > +} > + > +/* > + * The panic() function makes sure that only one CPU is allowed to run it's > + * code, but a new panic can be triggered during it's processing. > + * > + * Prior to calling the panic handlers, panic() calls smp_send_stop(). If > + * that went well, there's only one CPU running, but this is no guarantee. > + */ > +static int drm_panic_handler(struct notifier_block *this, unsigned long ev, > + void *ptr) > +{ > + drm_panic_log("%s\n", __func__); > + > + /* > + * TODO > + * Maybe we need better protection here against reentrance in case > + * panic_vmap() triggered a new panic. > + */ > + > + /* Nested panic */ > + if (drm_panic_fbs_num) > + return NOTIFY_DONE; > + > + class_for_each_device(drm_class, NULL, NULL, drm_panic_class_iter); > + > + if (drm_panic_fbs_num) > + drm_panic_active = true; > + > + return NOTIFY_DONE; > +} > + > +static struct notifier_block drm_panic_block = { > + .notifier_call = drm_panic_handler, > +}; > + > +static void drm_panic_test(void) > +{ > + /* simulate calling panic_notifier_list */ > + drm_panic_handler(NULL, 0, NULL); > +} > + > +static void drm_panic_test_cleanup(void) > +{ > + struct drm_panic_fb *pfb; > + unsigned int i; > + > + drm_panic_active = false; > + > + for (i = 0; i < drm_panic_fbs_num; i++) { > + pfb = &drm_panic_fbs[i]; > + if (pfb->fb->funcs->panic_vunmap) > + pfb->fb->funcs->panic_vunmap(pfb->fb, pfb->vmap); > + ww_mutex_unlock(&pfb->plane->mutex.mutex); > + ww_mutex_unlock(&pfb->crtc->mutex.mutex); > + } > + > + drm_panic_fbs_num = 0; > + memset(drm_panic_fbs, 0, DRM_PANIC_MAX_FBS * sizeof(*drm_panic_fbs)); > +} > + > +/* > + * Partial replication of panic() for testing purposes. Some symbols are > + * only available when builtin (they're not exported). > + */ > +static void drm_panic_fake_panic(unsigned int level) > +{ > +#ifndef MODULE > + int old_loglevel = console_loglevel; > + > + if (level > 1) > + local_irq_disable(); > + > + console_verbose(); > + > + if (level > 2) > + bust_spinlocks(1); > + > + pr_emerg("Kernel panic - not syncing: FAKING=%u, oops_in_progress=%d\n", > + level, oops_in_progress); > + > + dump_stack(); > + drm_panic_test(); > + > + if (level > 2) > + bust_spinlocks(0); > + > + console_flush_on_panic(); > + > + pr_emerg("---[ end Kernel panic - not syncing: FAKING\n"); > + > + if (level > 1) > + local_irq_enable(); > + > + console_loglevel = old_loglevel; > + > +#else /* MODULE */ > + > + if (level > 1) > + local_irq_disable(); > + > + pr_emerg("Kernel panic - not syncing: FAKING=%u\n", level); > + dump_stack(); > + drm_panic_test(); > + pr_emerg("---[ end Kernel panic - not syncing: FAKING\n"); > + > + if (level > 1) > + local_irq_enable(); > + > +#endif /* MODULE */ > + > + drm_panic_test_cleanup(); > +} > + > +static void drm_panic_clear_kmsgs(void) > +{ > + console_lock(); > + memset(drm_panic_kmsgs, 0, DRM_PANIC_MAX_KMSGS); > + drm_panic_kmsgs_pos = 0; > + console_unlock(); > +} > + > +/* > + * Fake/simulate panic() at different levels: > + * 1: only trigger panic handling internally > + * 2: add local_irq_disable() > + * 3: add bust_spinlocks(); > + * > + * Test rendering code: > + * 100: clear kmsgs buffer > + * 101: call panic handler for testing > + * 102: cleanup after testing > + * > + * The real deal: > + * 200: don't fake it, do call 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; > + ssize_t ret = 0; > + char buf[24]; > + size_t size; > + > + size = min(sizeof(buf) - 1, count); > + if (copy_from_user(buf, user_buf, size)) > + return -EFAULT; > + > + buf[size] = '\0'; > + ret = kstrtoull(buf, 0, &val); > + if (ret) > + return ret; > + > + if (val && val < 4) > + drm_panic_fake_panic(val); > + else if (val == 100) > + drm_panic_clear_kmsgs(); > + else if (val == 101) > + drm_panic_test(); > + else if (val == 102) > + drm_panic_test_cleanup(); > + else if (val == 200) > + panic("TESTING"); > + 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 int drm_panic_log_show(struct seq_file *m, void *v) > +{ > + size_t pos = log_pos; > + > + if (log_buf[0] == '\0') > + return 0; > + > + if (!pos) { > + seq_write(m, log_buf, DRM_PANIC_LOG_SIZE); > + } else if (log_buf[pos] == '\0') { > + seq_write(m, log_buf, pos); > + } else { > + seq_write(m, log_buf + pos, DRM_PANIC_LOG_SIZE - pos); > + seq_write(m, log_buf, pos); > + } > + > + return 0; > +} > + > +static int drm_panic_log_open(struct inode *inode, struct file *file) > +{ > + return single_open(file, drm_panic_log_show, NULL); > +} > + > +static const struct file_operations drm_panic_log_ops = { > + .owner = THIS_MODULE, > + .open = drm_panic_log_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > +}; > + > +static struct dentry *drm_panic_d_panic; > +static struct dentry *drm_panic_d_log; > + > +void __init drm_panic_init(struct dentry *debugfs_root) > +{ > + drm_panic_kmsgs = kzalloc(DRM_PANIC_MAX_KMSGS, GFP_KERNEL); > + if (!drm_panic_kmsgs) { > + DRM_ERROR("Failed to setup panic handler\n"); > + return; > + } > + > + drm_panic_d_panic = debugfs_create_file("panic-test", 0200, > + debugfs_root, NULL, > + &drm_panic_panic_ops); > + > + drm_panic_d_log = debugfs_create_file("panic-log", 0444, debugfs_root, > + NULL, &drm_panic_log_ops); > + if (!IS_ERR_OR_NULL(drm_panic_d_log)) > + log_buf = kzalloc(DRM_PANIC_LOG_SIZE, GFP_KERNEL); > + > + register_console(&drm_panic_console); > + atomic_notifier_chain_register(&panic_notifier_list, &drm_panic_block); > +} > + > +void __exit drm_panic_exit(struct dentry *debugfs_root) > +{ > + if (!drm_panic_kmsgs) > + return; > + > + debugfs_remove(drm_panic_d_log); > + debugfs_remove(drm_panic_d_panic); > + kfree(log_buf); > + log_buf = NULL; > + > + atomic_notifier_chain_unregister(&panic_notifier_list, > + &drm_panic_block); > + unregister_console(&drm_panic_console); > + kfree(drm_panic_kmsgs); > +} > diff --git a/include/drm/drm_framebuffer.h b/include/drm/drm_framebuffer.h > index 50deb40..33f4022 100644 > --- a/include/drm/drm_framebuffer.h > +++ b/include/drm/drm_framebuffer.h > @@ -90,6 +90,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); > }; > > /** > @@ -214,6 +252,8 @@ struct drm_framebuffer *drm_framebuffer_lookup(struct > drm_device *dev, > void drm_framebuffer_remove(struct drm_framebuffer *fb); > void drm_framebuffer_cleanup(struct drm_framebuffer *fb); > void drm_framebuffer_unregister_private(struct drm_framebuffer *fb); > +void drm_framebuffer_panic_draw_xy(struct drm_framebuffer *fb, void *vmap, > + int x, int y, bool foreground); > > /** > * drm_framebuffer_reference - incr the fb refcnt