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

Reply via email to