From patchwork Tue Aug 9 17:45:41 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Noralf_Tr=C3=B8nnes?= X-Patchwork-Id: 9271969 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 082796075A for ; Tue, 9 Aug 2016 17:46:06 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EF6A128364 for ; Tue, 9 Aug 2016 17:46:05 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E43672838C; Tue, 9 Aug 2016 17:46:05 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 076AB28364 for ; Tue, 9 Aug 2016 17:46:05 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 5040B6E618; Tue, 9 Aug 2016 17:46:02 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from smtp.domeneshop.no (smtp.domeneshop.no [IPv6:2a01:5b40:0:3005::1]) by gabe.freedesktop.org (Postfix) with ESMTPS id EA06B6E619 for ; Tue, 9 Aug 2016 17:45:59 +0000 (UTC) Received: from 211.81-166-168.customer.lyse.net ([81.166.168.211]:7996 helo=localhost.localdomain) by smtp.domeneshop.no with esmtpsa (TLS1.1:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.80) (envelope-from ) id 1bXB6E-0002JN-BE; Tue, 09 Aug 2016 19:45:58 +0200 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org Subject: [RFC 2/3] drm: Add panic handling Date: Tue, 9 Aug 2016 19:45:41 +0200 Message-Id: <1470764742-20323-3-git-send-email-noralf@tronnes.org> X-Mailer: git-send-email 2.8.2 In-Reply-To: <1470764742-20323-1-git-send-email-noralf@tronnes.org> References: <1470764742-20323-1-git-send-email-noralf@tronnes.org> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP This adds support for outputting kernel messages on panic(). The drivers that supports it, provides a framebuffer that the messages can be rendered on. Signed-off-by: Noralf Trønnes --- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_drv.c | 3 + drivers/gpu/drm/drm_internal.h | 4 + drivers/gpu/drm/drm_panic.c | 606 +++++++++++++++++++++++++++++++++++++++++ include/drm/drmP.h | 22 ++ 5 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_panic.c diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index eba32ad..ff04e41 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -12,7 +12,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_info.o drm_debugfs.o drm_encoder_slave.o \ drm_trace_points.o drm_global.o drm_prime.o \ drm_rect.o drm_vma_manager.o drm_flip_work.o \ - drm_modeset_lock.o drm_atomic.o drm_bridge.o + drm_modeset_lock.o drm_atomic.o drm_bridge.o drm_panic.o drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 3b14366..457ee91 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -861,6 +861,8 @@ static int __init drm_core_init(void) goto err_p3; } + drm_panic_init(); + DRM_INFO("Initialized %s %d.%d.%d %s\n", CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE); return 0; @@ -876,6 +878,7 @@ err_p1: static void __exit drm_core_exit(void) { + drm_panic_exit(); debugfs_remove(drm_debugfs_root); drm_sysfs_destroy(); diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index b86dc9b..7463d9d 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -90,6 +90,10 @@ int drm_gem_open_ioctl(struct drm_device *dev, void *data, void drm_gem_open(struct drm_device *dev, struct drm_file *file_private); void drm_gem_release(struct drm_device *dev, struct drm_file *file_private); +/* drm_panic.c */ +void drm_panic_init(void); +void drm_panic_exit(void); + /* drm_debugfs.c */ #if defined(CONFIG_DEBUG_FS) int drm_debugfs_init(struct drm_minor *minor, int minor_id, diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c new file mode 100644 index 0000000..e185c9d --- /dev/null +++ b/drivers/gpu/drm/drm_panic.c @@ -0,0 +1,606 @@ +/* + * Copyright 2016 Noralf Trønnes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct drm_panic_fb { + struct drm_framebuffer *fb; + void *vmem; + const struct font_desc *font; + unsigned int cols; + unsigned int rows; + unsigned int xpos; + unsigned int ypos; +}; + +#define DRM_PANIC_MAX_FBS 64 +static struct drm_panic_fb drm_panic_fbs[DRM_PANIC_MAX_FBS]; + +#define DRM_PANIC_MAX_KMSGS SZ_4K +static char *drm_panic_kmsgs; +static size_t drm_panic_kmsgs_pos; + +static bool drm_panic_active; + +static void drm_panic_log(const char *fmt, ...); + +static inline void drm_panic_draw_pixel(u8 *dst, u32 pixel_format, bool val) +{ + switch (pixel_format & ~DRM_FORMAT_BIG_ENDIAN) { + + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + *dst = val ? 0xff : 0x00; + break; + + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR4444: + put_unaligned(val ? 0x0fff : 0x0000, (u16 *)dst); + break; + + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA4444: + put_unaligned(val ? 0xfff0 : 0x0000, (u16 *)dst); + break; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ABGR1555: + put_unaligned(val ? 0x7fff : 0x0000, (u16 *)dst); + break; + + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRA5551: + put_unaligned(val ? 0xfffe : 0x0000, (u16 *)dst); + break; + + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + put_unaligned(val ? 0xffff : 0x0000, (u16 *)dst); + break; + + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + dst[0] = val ? 0xff : 0x00; + dst[1] = val ? 0xff : 0x00; + dst[2] = val ? 0xff : 0x00; + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + put_unaligned(val ? 0x00ffffff : 0x00000000, (u32 *)dst); + break; + + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + put_unaligned(val ? 0xffffff00 : 0x00000000, (u32 *)dst); + break; + + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ABGR2101010: + put_unaligned(val ? 0x3fffffff : 0x00000000, (u32 *)dst); + break; + + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_BGRA1010102: + put_unaligned(val ? 0xfffffffc : 0x00000000, (u32 *)dst); + break; + } +} + +static void drm_panic_render(struct drm_panic_fb *pfb, + const char *text, unsigned int len) +{ + const struct font_desc *font = pfb->font; + unsigned int pix_depth, pix_bpp, cpp; + unsigned int col = pfb->xpos; + unsigned int row = pfb->ypos; + unsigned int i, h, w; + void *dst, *pos; + u8 fontline; + + if ((row + 1) * font->height > pfb->fb->height) + return; + + if ((col + len) * font->width > pfb->fb->width) + return; + + drm_fb_get_bpp_depth(pfb->fb->pixel_format, &pix_depth, &pix_bpp); + cpp = DIV_ROUND_UP(pix_bpp, 8); + + /* TODO: should fb->offsets[0] be added here? */ + dst = pfb->vmem + (row * font->height * pfb->fb->pitches[0]) + + (col * font->width * cpp); + + for (h = 0; h < font->height; h++) { + pos = dst; + + for (i = 0; i < len; i++) { + fontline = *(u8 *)(font->data + text[i] * font->height + h); + + for (w = 0; w < font->width; w++) { + drm_panic_draw_pixel(pos, pfb->fb->pixel_format, + fontline & BIT(7 - w)); + pos += cpp; + } + } + + dst += pfb->fb->pitches[0]; + } +} + +static void drm_panic_scroll_up(struct drm_panic_fb *pfb) +{ + void *src = pfb->vmem + (pfb->font->height * pfb->fb->pitches[0]); + size_t len = (pfb->fb->height - pfb->font->height) * + pfb->fb->pitches[0]; + + drm_panic_log("%s\n", __func__); + + memmove(pfb->vmem, src, len); + memset(pfb->vmem + len, 0, pfb->font->height * pfb->fb->pitches[0]); +} + +static void drm_panic_clear_screen(struct drm_panic_fb *pfb) +{ + memset(pfb->vmem, 0, pfb->fb->height * pfb->fb->pitches[0]); +} + +static void drm_panic_log_msg(char *pre, const char *str, unsigned int len) +{ + char buf[512]; + + if (len > 510) + len = 510; + + memcpy(buf, str, len); + buf[len] = '\n'; + buf[len + 1] = '\0'; + + drm_panic_log("%s%s", pre, buf); +} + +static void drm_panic_putcs_no_lf(struct drm_panic_fb *pfb, + const char *str, unsigned int len) +{ + drm_panic_log("%s(len=%u) x=%u, y=%u\n", __func__, len, + pfb->xpos, pfb->ypos); + + if (len <= 0) + return; + + drm_panic_log_msg("", str, len); + + drm_panic_render(pfb, str, len); + +} + +static void drm_panic_putcs(struct drm_panic_fb *pfb, + const char *str, unsigned int num) +{ + unsigned int slen; + int len = num; + char *lf; + + drm_panic_log("%s(num=%u)\n", __func__, num); + + while (len > 0) { + + if (pfb->ypos == pfb->rows) { + pfb->ypos--; + drm_panic_scroll_up(pfb); + } + + lf = strpbrk(str, "\n"); + if (lf) + slen = lf - str; + else + slen = len; + + if (pfb->xpos + slen > pfb->cols) + slen = pfb->cols - pfb->xpos; + + drm_panic_putcs_no_lf(pfb, str, slen); + + len -= slen; + str += slen; + pfb->xpos += slen; + + if (lf) { + str++; + len--; + pfb->xpos = 0; + pfb->ypos++; + } + } +} + +static void drm_panic_write(const char *str, unsigned int num) +{ + unsigned int i; + + if (!num) + return; + + drm_panic_log("%s(num=%u)\n", __func__, num); + + for (i = 0; i < DRM_PANIC_MAX_FBS; i++) { + if (!drm_panic_fbs[i].fb) + break; + drm_panic_putcs(&drm_panic_fbs[i], str, num); + } +} + +/* this one is serialized by console_lock() */ +static void drm_panic_console_write(struct console *con, + const char *str, unsigned int num) +{ + unsigned int i; + + drm_panic_log_msg("->", str, num); + + /* Buffer up messages to be replayed on panic */ + if (!drm_panic_active) { + for (i = 0; i < num; i++) { + drm_panic_kmsgs[drm_panic_kmsgs_pos++] = *str++; + if (drm_panic_kmsgs_pos == DRM_PANIC_MAX_KMSGS) + drm_panic_kmsgs_pos = 0; + } + return; + } + + drm_panic_write(str, num); +} + +static struct console drm_panic_console = { + .name = "drmpanic", + .write = drm_panic_console_write, + .flags = CON_PRINTBUFFER | CON_ENABLED, + .index = 0, +}; + +/* + * The panic() function makes sure that only one CPU is allowed to run it's + * code. So when this handler is called, we're alone. No racing with + * console.write() is possible. + */ +static int drm_panic_handler(struct notifier_block *this, unsigned long ev, + void *ptr) +{ + const struct font_desc *font; + struct drm_framebuffer *fb; + struct drm_panic_fb *pfb; + struct drm_minor *minor; + unsigned int fbs = 0; + void *vmem; + int i; + + drm_panic_log("%s\n", __func__); + + drm_panic_active = true; + + drm_minor_for_each(minor, DRM_MINOR_LEGACY, i) { + drm_panic_log("Found minor=%d\n", minor->index); + if (!minor->dev || !minor->dev->driver || + !minor->dev->driver->panic) { + drm_panic_log("Skipping: No panic handler\n"); + continue; + } + + fb = minor->dev->driver->panic(minor->dev, &vmem); + if (!fb) { + drm_panic_log("Skipping: Driver returned NULL\n"); + continue; + } + + if (!fb || !vmem || fb->dev != minor->dev || !fb->pitches[0]) { + drm_panic_log("Skipping: Failed check\n"); + continue; + } + + /* only 8-bit wide fonts are supported */ + font = get_default_font(fb->width, fb->height, BIT(7), -1); + if (!font) { + drm_panic_log("Skipping: No font available\n"); + continue; + } + + pfb = &drm_panic_fbs[fbs++]; + + pfb->fb = fb; + pfb->vmem = vmem; + pfb->font = font; + pfb->cols = fb->width / font->width; + pfb->rows = fb->height / font->height; + + drm_panic_clear_screen(pfb); + + drm_panic_log(" %ux%u -> %ux%u, %s, %s\n", fb->width, + fb->height, pfb->cols, pfb->rows, font->name, + drm_get_format_name(fb->pixel_format)); + } + + if (drm_panic_kmsgs[0]) { + /* safeguard in case we interrupted drm_panic_console_write */ + if (drm_panic_kmsgs_pos >= DRM_PANIC_MAX_KMSGS) + drm_panic_kmsgs_pos = 0; + + drm_panic_write(&drm_panic_kmsgs[drm_panic_kmsgs_pos], + DRM_PANIC_MAX_KMSGS - drm_panic_kmsgs_pos); + drm_panic_write(drm_panic_kmsgs, drm_panic_kmsgs_pos); + } + + return NOTIFY_DONE; +} + +static struct notifier_block drm_panic_block = { + .notifier_call = drm_panic_handler, +}; + + + +#ifdef CONFIG_DEBUG_FS + +/* Out of band logging is useful at least in the initial development phase */ +#define DRM_PANIC_LOG_SIZE SZ_64K +#define DRM_PANIC_LOG_LINE 128 +#define DRM_PANIC_LOG_ENTRIES (DRM_PANIC_LOG_SIZE / DRM_PANIC_LOG_LINE) + +static char *log_buf; +static size_t log_pos; +static struct dentry *drm_panic_logfs_root; + +static void drm_panic_log(const char *fmt, ...) +{ + va_list args; + u32 rem_nsec; + char *text; + size_t len; + u64 sec; + + if (!log_buf || oops_in_progress) + return; + + va_start(args, fmt); + + if (log_pos >= DRM_PANIC_LOG_ENTRIES) + log_pos = 0; + + text = log_buf + (log_pos++ * DRM_PANIC_LOG_LINE); + if (log_pos == DRM_PANIC_LOG_ENTRIES) + log_pos = 0; + + sec = div_u64_rem(local_clock(), 1000000000, &rem_nsec); + + len = scnprintf(text, DRM_PANIC_LOG_LINE, "[%5llu.%06u] ", sec, + rem_nsec / 1000); + + vscnprintf(text + len, DRM_PANIC_LOG_LINE - len, fmt, args); + + /* Make sure to always have a newline in case of overflow */ + if (text[DRM_PANIC_LOG_LINE - 2] != '\0') + text[DRM_PANIC_LOG_LINE - 2] = '\n'; + + va_end(args); +} + +static int drm_panic_log_show(struct seq_file *m, void *v) +{ + size_t pos = log_pos; + unsigned int i; + char *text; + + for (i = 0; i < DRM_PANIC_LOG_ENTRIES; i++) { + text = log_buf + (pos++ * DRM_PANIC_LOG_LINE); + if (pos == DRM_PANIC_LOG_ENTRIES) + pos = 0; + if (*text == '\0') + continue; + seq_puts(m, text); + } + + return 0; +} + +static int drm_panic_log_open(struct inode *inode, struct file *file) +{ + return single_open(file, drm_panic_log_show, NULL); +} + +static const struct file_operations drm_panic_log_ops = { + .owner = THIS_MODULE, + .open = drm_panic_log_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Fake/simulate panic() at different levels: + * 1: only trigger panic handling internally + * 2: add local_irq_disable() + * 3: add bust_spinlocks(); + * 100: don't fake it, do call panic() + */ +static int drm_text_fake_panic(unsigned int level) +{ +#ifndef MODULE + int old_loglevel = console_loglevel; +#endif + + if (!level && level != 100 && level > 3) + return -EINVAL; + + if (level == 100) + panic("TESTING"); + + if (level > 1) + local_irq_disable(); + +#ifndef MODULE + console_verbose(); +#endif + if (level > 2) + bust_spinlocks(1); + + pr_emerg("Kernel panic - not syncing: FAKING=%u, oops_in_progress=%d\n", + level, oops_in_progress); + +#ifdef CONFIG_DEBUG_BUGVERBOSE + dump_stack(); +#endif + /* simulate calling panic_notifier_list */ + drm_panic_handler(NULL, 0, NULL); + + if (level > 2) + bust_spinlocks(0); + +#ifndef MODULE + console_flush_on_panic(); +#endif + pr_emerg("---[ end Kernel panic - not syncing: FAKING\n"); + + if (level > 1) + local_irq_enable(); + +#ifndef MODULE + console_loglevel = old_loglevel; +#endif + + return 0; +} + +static ssize_t drm_text_panic_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long long val; + ssize_t ret = 0; + char buf[24]; + size_t size; + + size = min(sizeof(buf) - 1, count); + if (copy_from_user(buf, user_buf, size)) + return -EFAULT; + + buf[size] = '\0'; + ret = kstrtoull(buf, 0, &val); + if (ret) + return ret; + + ret = drm_text_fake_panic(val); + + return ret < 0 ? ret : count; +} + +static const struct file_operations drm_text_panic_ops = { + .write = drm_text_panic_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static int drm_panic_logfs_init(void) +{ + drm_panic_logfs_root = debugfs_create_dir("drm-panic", NULL); + if (!drm_panic_logfs_root) + return -ENOMEM; + + if (!debugfs_create_file("log", S_IRUGO, drm_panic_logfs_root, NULL, + &drm_panic_log_ops)) + goto err_remove; + + log_buf = kzalloc(DRM_PANIC_LOG_SIZE, GFP_KERNEL); + if (!log_buf) + goto err_remove; + + debugfs_create_file("panic", S_IWUSR, drm_panic_logfs_root, NULL, + &drm_text_panic_ops); + + return 0; + +err_remove: + debugfs_remove_recursive(drm_panic_logfs_root); + + return -ENOMEM; +} + +static void drm_panic_logfs_exit(void) +{ + debugfs_remove_recursive(drm_panic_logfs_root); + kfree(log_buf); + log_buf = NULL; +} + +#else + +static int drm_panic_logfs_init(void) +{ +} + +static void drm_panic_logfs_exit(void) +{ +} + +static void drm_panic_log(const char *fmt, ...) +{ +} + +#endif + + +void __init drm_panic_init(void) +{ + drm_panic_kmsgs = kzalloc(DRM_PANIC_MAX_KMSGS, GFP_KERNEL); + if (!drm_panic_kmsgs) { + DRM_ERROR("Failed to setup panic handler\n"); + return; + } + + drm_panic_logfs_init(); +drm_panic_log("%s\n", __func__); + register_console(&drm_panic_console); + atomic_notifier_chain_register(&panic_notifier_list, + &drm_panic_block); +} + +void __exit drm_panic_exit(void) +{ + if (!drm_panic_kmsgs) + return; + + drm_panic_logfs_exit(); + atomic_notifier_chain_unregister(&panic_notifier_list, + &drm_panic_block); + unregister_console(&drm_panic_console); + kfree(drm_panic_kmsgs); +} diff --git a/include/drm/drmP.h b/include/drm/drmP.h index bc7006e..4e84654 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -550,6 +550,28 @@ struct drm_driver { bool from_open); void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv); + /** + * @panic: + * + * This function is called on panic() asking for a framebuffer to + * display the panic messages on. It also needs the virtual address + * of the backing buffer. + * This function is optional. + * + * NOTE: + * + * This function is called in an atomic notifier chain and it cannot + * sleep. Care must be taken so the machine is not killed even harder, + * preventing output from going out on serial/netconsole. + * + * RETURNS: + * + * Framebuffer that should be used to display the panic messages, + * alongside the virtual address of the backing buffer, or NULL if + * the driver is unable to provide this. + */ + struct drm_framebuffer *(*panic)(struct drm_device *dev, void **vmem); + int (*debugfs_init)(struct drm_minor *minor); void (*debugfs_cleanup)(struct drm_minor *minor);