[RFC] drm: add kernel-log renderer

David Herrmann dh.herrmann at gmail.com
Thu Mar 6 10:40:42 PST 2014


On modern linux user-space, the VT subsystem is no longer needed for
system consoles. Although most DEs will fail if VTs are disabled, there
are several gfx-systems that support this mode. Especially the lower
system stack has been extended to work without VTs.

However, there is one major drawback if VTs are disabled: You don't get
oops/panic-screens nor early boot-debugging. The VT subsystem registers a
console-driver, thus displays the kernel log and oops/panic screens in
those situations. This patch introduces a fallback for CONFIG_VT=n.

A new DRM-Log core is added. At its heart, DRM-Log maintains a log-buffer
of kernel-log messages. It registers a console-driver and pushes new
messages into this buffer whenever they appear. The size of the log-buffer
can be changed via drm_log_ensure_size(). Initially, a suitable buffer is
chosen, but whenever drivers register high-res CRTCs, they ought to
increase that buffer to guarantee there's always enough data to render the
whole screen.
This log-buffer is managed at the character-level, not pixel-level. It is
shared across all users, supports parallel, atomic readers and writers and
supports seamless resizing.

Additionally to the log-buffer handling, a generic software renderer is
introduced. drm_log_draw() renders the current log-buffer into any
memory-mapped framebuffer you want. Note that it supports splitting lines
in case your framebuffer is smaller than the log-buffer, but also merging
continuation lines in case your framebuffer is wider. This allows
rendering an 80x25 log-buffer in full-width on high-res displays. On the
other hand, 800x250 log-buffers can be rendered without information loss
(apart from a shorter backlog) on low-res displays.

No high-level API is introduced, yet. So for now drivers need to decide
when and where to render on their own. A generic interface with
panic-handlers, scheduled redrawing and automatic modesetting will be
added in follow-ups.

A few more notes:
 - This is for *debugging*, and for debugging only! You can use this
   renderer to get a kernel-log onto your framebuffers. However, the
   renderer is horribly slow! There is no intention to speed it up. No-one
   cares whether it takes 500ms to render your panic-screen!
 - If you enable the log-renderer for a continous boot-log, you're
   supposed to redraw on vblanks. The renderer does not support
   damage-tracking as each new-line causes a scroll-up, anyway.
 - The renderer works bottom-up and right-to-left. This is required to
   avoid pre-calculating log sizes.
 - The renderer supports multiple columns. This is quite handy on high-res
   screens where you can get bigger backlogs this way. Usually 80% of the
   screen are black. Now you can reuse this for the next log-column.
 - I have some wrappers to use this in user-space. I did several tests
   with resizing and different buffer-sizes and valgrind didn't complain.
   Results looked all fine.
 - The renderer supports *any* RGB target, from 8bit to 32bit with
   big-endian and little-endian support. The related pixel-renderer will
   probably never win a beauty-contest, but it works.. Again, who cares
   for debug-log rendering speed?
 - If you want a normal boot-log, use a user-space log-renderer (like
   experimential systemd-er (no merged, yet)). They can render with proper
   speed, can do hw-scrolling and more.
   Or: Enable VTs! Seriously, if you want all the VT features, use VTs! I
   will not tell you to disable them.

On a last note:
  With this (and the high-level integration) in place, you can easily
  disable CONFIG_FB, CONFIG_VT and CONFIG_FRAMEBUFFER_CONSOLE. Combined
  with a proper user-space system-console, you will end up with more
  features, less bugs and definitely some happy kernel maintainers.

Signed-off-by: David Herrmann <dh.herrmann at gmail.com>
---
 drivers/gpu/drm/Kconfig   |    6 +
 drivers/gpu/drm/Makefile  |    1 +
 drivers/gpu/drm/drm_drv.c |    4 +
 drivers/gpu/drm/drm_log.c | 1188 +++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_log.h     |   62 +++
 5 files changed, 1261 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_log.c
 create mode 100644 include/drm/drm_log.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 8e7fa4d..4d9b7fa 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -45,6 +45,12 @@ config DRM_KMS_FB_HELPER
 	help
 	  FBDEV helpers for KMS drivers.
 
+config DRM_LOG
+	bool
+	depends on DRM && !VT
+	select FONT_SUPPORT
+	default y
+
 config DRM_LOAD_EDID_FIRMWARE
 	bool "Allow to specify an EDID data set instead of probing for it"
 	depends on DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 292a79d..9c97b58 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -19,6 +19,7 @@ drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
 drm-$(CONFIG_PCI) += ati_pcigart.o
 drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+drm-$(CONFIG_DRM_LOG) += drm_log.o
 
 drm-usb-y   := drm_usb.o
 
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 345be03..310f99d 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -51,6 +51,7 @@
 #include <linux/export.h>
 #include <drm/drmP.h>
 #include <drm/drm_core.h>
+#include <drm/drm_log.h>
 
 
 static int drm_version(struct drm_device *dev, void *data,
@@ -185,6 +186,7 @@ static int __init drm_core_init(void)
 	drm_global_init();
 	drm_connector_ida_init();
 	idr_init(&drm_minors_idr);
+	drm_log_init();
 
 	if (register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops))
 		goto err_p1;
@@ -213,6 +215,7 @@ err_p2:
 
 	idr_destroy(&drm_minors_idr);
 err_p1:
+	drm_log_exit();
 	return ret;
 }
 
@@ -223,6 +226,7 @@ static void __exit drm_core_exit(void)
 
 	unregister_chrdev(DRM_MAJOR, "drm");
 
+	drm_log_exit();
 	drm_connector_ida_destroy();
 	idr_destroy(&drm_minors_idr);
 }
diff --git a/drivers/gpu/drm/drm_log.c b/drivers/gpu/drm/drm_log.c
new file mode 100644
index 0000000..ab2b743
--- /dev/null
+++ b/drivers/gpu/drm/drm_log.c
@@ -0,0 +1,1188 @@
+/*
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann at gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <asm/unaligned.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <linux/atomic.h>
+#include <linux/console.h>
+#include <linux/font.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/swab.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+
+/**
+ * DOC: drm log
+ *
+ * The DRM-log helpers keep an internal screen-buffer of the global kernel-log
+ * and provide render functions to draw the current log-buffer into a
+ * memory-mapped framebuffer. The whole subsystem is disabled if the kernel is
+ * compiled with CONFIG_VT. You should use the VT layer to draw the kernel-log
+ * in that case. DRM-log provides a minimal fallback if VTs are disabled.
+ *
+ * DRM-log is meant for debugging only! The main use-cases include:
+ *   - render panic screens
+ *   - render oops screens
+ *   - render kernel-log for debugging
+ * Rendering should be enabled for debugging only! Enabling DRM-log during boot
+ * will slow everything down considerably! If you want a fast boot-log, use some
+ * user-space renderer. DRM-log only makes sense to debug
+ * early-boot/late-shutdown issues and oops/panics. Note that the DRM-log core
+ * just provides the helpers, it does not apply any policy. It is up to the
+ * users of this API and the more high-level DRM-log interfaces to render only
+ * if appropriate.
+ *
+ * The DRM-log core keeps an internal kernel-log buffer which can be shared
+ * across drivers and devices. This buffer is always kept up-to-date, allows
+ * atomic updates during kernel panics/oopses and should never be accessed
+ * directly from a driver.
+ * The log-buffer always stays allocated so any kernel-log writes do not need to
+ * allocate any memory (which might be fragile during kernel-panics). However,
+ * this means that we need to limit the line-length to a maximum. A sane default
+ * is provided by drm-log, but you need to call drm_log_ensure_size() to keep
+ * the buffer big enough for bigger screens. Otherwise, you might end up with
+ * blank margins due to lack of data. Note that the renderer is smart enough to
+ * render a log-buffer of any size onto a framebuffer of any size. But if the
+ * backlog is too small, you obviously will see blank margins. See
+ * drm_log_ensure_size() for more.
+ * Kernel-code can directly write into the DRM-log buffer via drm_log_write().
+ * However, you *really* should be using printk() instead! There are only very
+ * few reasons to skip the kernel-log, so you should know what you're doing.
+ * DRM-log registers a console-driver to get notified of any printk() and then
+ * writes it into its log via drm_log_write() itself.
+ *
+ * On top of the core log-buffer, the most low-level entry point for drivers is
+ * drm_log_draw(). This renders the kernel log onto any memory-mapped
+ * framebuffer. Any more high-level interface of DRM-log calls this at some
+ * point to render the actual buffer. drm_log_draw() does no scheduling,
+ * redrawing or any other fancy logic. It just renders the text-data via an
+ * in-kernel font onto the framebuffer and returns. Multiple renderers can run
+ * in parallel just fine (and even allow parallel writers). However, rendering
+ * is slow as we need to support any pixel-format we have. We also don't want
+ * any fancy (and possibly fragile) optimizations that make the code harder to
+ * read. This is a debug-feature, remember? Nobody cares whether it takes 200ms
+ * to render your panic-screen.
+ *
+ * Higher-level interfaces will follow later on.
+ */
+
+struct dlog_line {
+	atomic_t length;
+	atomic_t cont;
+	char *cells;
+};
+
+struct dlog_buf {
+	size_t width;
+	size_t height;
+	atomic_t pos;
+	struct dlog_line lines[];
+};
+
+/* console-buffer management */
+static struct dlog_buf __rcu *dlog_buf;
+static const struct font_desc *dlog_font;
+static DEFINE_MUTEX(dlog_wlock);
+
+/* caller must hold either rcu-read-lock or dlog_wlock */
+static void dlog__write_line(struct dlog_buf *buf,
+			     const char *data,
+			     size_t len,
+			     bool continuation)
+{
+	struct dlog_line *line;
+	size_t pos;
+
+	if (!buf || !data || !len)
+		return;
+
+	/*
+	 * Get pointer to the next line and asynchronously write into it. This
+	 * is *unlocked* against any readers, so:
+	 *  - smp_wmb() does not protect against garbage on the screen, but
+	 *    reduces conflicts slightly.
+	 *  - Our buffers usually contain more lines than shown on screen, thus
+	 *    garbage will only occur during huge writes.
+	 *  - Caller has to redraw, so garbage will always get corrected. Any
+	 *    crap we might end up with will get corrected shortly after.
+	 */
+
+	pos = atomic_read(&buf->pos) + 1;
+	if (pos >= buf->height)
+		pos = 0;
+
+	line = &buf->lines[pos];
+	memcpy(line->cells, data, min(buf->width, len));
+	atomic_set(&line->length, len);
+	atomic_set(&line->cont, continuation);
+
+	smp_wmb();
+	atomic_set(&buf->pos, pos);
+}
+
+/* caller must hold either rcu-read-lock or dlog_wlock */
+static void dlog__write(struct dlog_buf *buf, const char *data, size_t len)
+{
+	size_t i;
+	char c;
+	bool cont = false;
+
+	if (!buf || !data || !len)
+		return;
+
+	for (i = 0; i < len; ) {
+		c = data[i++];
+
+		if (c == '\n' || i >= buf->width) {
+			if (c == '\n') {
+				dlog__write_line(buf, data, i - 1, cont);
+				cont = false;
+			} else {
+				dlog__write_line(buf, data, i, cont);
+				cont = true;
+			}
+
+			data += i;
+			len -= i;
+			i = 0;
+		}
+	}
+
+	dlog__write_line(buf, data, i, cont);
+}
+
+#define DLOG__WRITE(_buf, _str) dlog__write((_buf), (_str), sizeof(_str) - 1)
+
+/**
+ * drm_log_write() - Write lines into log-buffer
+ * @data: ASCII input data
+ * @len: length of input data in bytes
+ * @atomic: avoid waiting for locks
+ *
+ * Write the message from @data into the log-buffer. The message is put on a
+ * new line and wrapped for each newline-character. Further line-wrapping is
+ * performed in case a line is longer than our internal buffer-width. The
+ * caller shouldn't care, though. The renderer is smart enough to draw the
+ * global log-buffer onto framebuffers of any size.
+ *
+ * The caller can, but is not required to, hold the console-lock.
+ *
+ * This function is locked against other writers and may sleep to acquire a
+ * mutex. In case an oops or panic is in progress, this function avoids
+ * waiting for locks and allows safe parallel writes in exchange for some minor
+ * drawbacks (set atomic==true to always take this fast-path).
+ *
+ * The drm-log subsystem registers its own console-driver, so usually there is
+ * no reason to write directly into the log. Use printk()! This helper might be
+ * useful for some special debugging paths, though.
+ */
+void drm_log_write(const char *data, size_t len, bool atomic)
+{
+	struct dlog_buf *buf;
+	bool in_oops;
+
+	if (!data || !len)
+		return;
+
+	/*
+	 * If an oops/panic is in progress, we avoid taking the mutex and
+	 * instead write data directly. Note that this is totally safe as the
+	 * rcu-protected buffer has a pre-allocated static size. Things that
+	 * might go wrong are:
+	 *  - resize is ongoing in parallel, we might loose messages
+	 *  - parallel writes might overwrite each other
+	 * Both are negligible. Besides, during panics only one CPU is active
+	 * and the normal message-stream is locked by console_lock. Therefore,
+	 * there's currently no reason to optimize this for proper parallel
+	 * writes. If this is needed at some point, we can add this.
+	 */
+
+	in_oops = atomic || ACCESS_ONCE(oops_in_progress);
+	if (in_oops) {
+		if (mutex_trylock(&dlog_wlock))
+			in_oops = false;
+		else
+			rcu_read_lock();
+	} else {
+		mutex_lock(&dlog_wlock);
+	}
+
+	if (in_oops)
+		buf = rcu_dereference(dlog_buf);
+	else
+		buf = rcu_dereference_protected(dlog_buf,
+						lockdep_is_held(&dlog_wlock));
+
+	dlog__write(buf, data, len);
+
+	if (in_oops)
+		rcu_read_unlock();
+	else
+		mutex_unlock(&dlog_wlock);
+}
+EXPORT_SYMBOL(drm_log_write);
+
+/* caller must own @buf exclusively! */
+static void dlog_free_buf(struct dlog_buf *buf)
+{
+	size_t i;
+
+	if (!buf)
+		return;
+
+	for (i = 0; i < buf->height; ++i)
+		kfree(buf->lines[i].cells);
+
+	kfree(buf);
+}
+
+/**
+ * drm_log_ensure_size() - Try to ensure log-buffer size
+ * @width_px: Width of your framebuffer in pixels
+ * @width_py: Height of your framebuffer in pixels
+ *
+ * Whenever a new framebuffer is added, we try to make sure the log-buffer has
+ * at least the required dimension. If the buffer is already big enough, there
+ * is nothing to do. Otherwise, we simply allocate a new buffer and put it in
+ * place. Callers should pass in the pixel-sizes of their framebuffer. We
+ * calculate the cell-table dimensions internally and update the log-buffer
+ * accordingly.
+ * If we have to resize the buffer, we always allocate twice the required size.
+ * This guarantees that adding bigger framebuffers later on avoids
+ * re-allocations if they are in a suitable range. Furthermore, if a driver can,
+ * in a semi-reliable manner, predict the maximum screen-size of any CRTC it
+ * has, it is recommended to call drm_log_ensure_size() during device-probing to
+ * get a reasonably sized buffer early on. This is not required, though.
+ *
+ * Note: Our renderer handles any buffer size just fine, so we can safely
+ * ignore mem-alloc errors here. If we render onto a framebuffer that is
+ * *bigger* than our internal log-buffer, split lines will be merged again. On
+ * the other hand, if the framebuffer is *smaller* than our internal log-buffer,
+ * we simply split buffer-entries into multiple screen-lines.
+ *
+ * This call is locked against parallel calls.
+ *
+ * This function may sleep.
+ */
+void drm_log_ensure_size(size_t width_px, size_t height_px)
+{
+	struct dlog_buf *buf, *old;
+	size_t pos, i, x, y;
+
+	mutex_lock(&dlog_wlock);
+
+	if (!dlog_font)
+		goto out_unlock;
+
+	/* ensure 80x25 buffer size (FBs can still be smaller!) */
+	x = max_t(size_t, 80, width_px / dlog_font->width);
+	y = max_t(size_t, 25, height_px / dlog_font->height);
+
+	old = rcu_dereference_protected(dlog_buf,
+					lockdep_is_held(&dlog_wlock));
+
+	/* make sure we're bigger than prev buffer, or bail out */
+	if (old) {
+		if (x < old->width)
+			x = old->width;
+		if (y < old->height)
+			y = old->height;
+		if (x == old->width && y == old->height)
+			goto out_unlock;
+	}
+
+	/* double size to avoid repeated resizing */
+	x *= 2;
+	y *= 2;
+
+	buf = kzalloc(sizeof(*buf) + y * sizeof(*buf->lines), GFP_KERNEL);
+	if (!buf)
+		goto out_unlock;
+
+	buf->width = x;
+	buf->height = y;
+
+	for (i = 0; i < buf->height; ++i) {
+		buf->lines[i].cells = kzalloc(buf->width, GFP_KERNEL);
+		if (!buf->lines[i].cells)
+			goto out_free;
+	}
+
+	/* copy over old messages */
+	if (old) {
+		pos = atomic_read(&old->pos);
+		for (i = 0; i < old->height; ++i) {
+			atomic_set(&buf->lines[i].length,
+				   atomic_read(&old->lines[pos].length));
+			memcpy(buf->lines[i].cells,
+			       old->lines[pos].cells,
+			       atomic_read(&old->lines[pos].length));
+			if (++pos >= old->height)
+				pos = 0;
+		}
+		atomic_set(&buf->pos, (i < buf->height) ? i : 0);
+	}
+
+	/* place resize message */
+	DLOG__WRITE(buf, "drm: log resized");
+
+	/* replace old buffer */
+	rcu_assign_pointer(dlog_buf, buf);
+	synchronize_rcu();
+
+	/* free old buf */
+	buf = old;
+
+out_free:
+	dlog_free_buf(buf);
+out_unlock:
+	mutex_unlock(&dlog_wlock);
+}
+EXPORT_SYMBOL(drm_log_ensure_size);
+
+/*
+ * Draw Pixel
+ * This writes a single pixel of format @pixel_format at the given destination.
+ * You can pass high-res 32bit color as input, which is downscaled to the given
+ * target bitrate.
+ *
+ * This is horribly slow and awful but supports all known RGB formats. We might
+ * wanna add fast-paths for XRGB8888 and other common formats, but why bother
+ * for debug logs.. Furthermore, the compiler should be able to optimize
+ * "pixel_format" for all rendering-paths if it inlines this helper. Same is
+ * true for put_unaligned(), but it might not be that smart..
+ */
+static inline void dlog__draw_px(u8 *dst,
+				 u32 pixel_format,
+				 u32 a,
+				 u32 r,
+				 u32 g,
+				 u32 b)
+{
+	bool need_swap = false;
+	u32 val32;
+	u16 val16;
+
+#ifdef __LITTLE_ENDIAN
+	if (pixel_format & DRM_FORMAT_BIG_ENDIAN)
+		need_swap = true;
+#elif defined(__BIG_ENDIAN)
+	if (!(pixel_format & DRM_FORMAT_BIG_ENDIAN))
+		need_swap = true;
+#else
+#  error "arch not supported: neither little nor big endian"
+#endif
+
+	switch (pixel_format & ~DRM_FORMAT_BIG_ENDIAN) {
+
+	/*
+	 * C8
+	 */
+
+	case DRM_FORMAT_C8:
+		/* We have no access to the color-palette, so use 0x00 for
+		 * black, 0xff for everything else */
+		*dst = 0xff * !!(r | g | b);
+		break;
+
+	/*
+	 * RGB332 and friends
+	 */
+
+	case DRM_FORMAT_RGB332:
+		r = (r >> 29) & 0x07;
+		g = (g >> 29) & 0x07;
+		b = (b >> 30) & 0x03;
+		*dst = (r << 5) | (g << 2) | b;
+		break;
+	case DRM_FORMAT_BGR233:
+		r = (r >> 29) & 0x07;
+		g = (g >> 29) & 0x07;
+		b = (b >> 30) & 0x03;
+		*dst = (b << 6) | (g << 3) | r;
+		break;
+
+	/*
+	 * XRGB4444 and friends
+	 */
+
+	case DRM_FORMAT_XRGB4444:
+	case DRM_FORMAT_ARGB4444:
+		a = (a >> 28) & 0x0f;
+		r = (r >> 28) & 0x0f;
+		g = (g >> 28) & 0x0f;
+		b = (b >> 28) & 0x0f;
+		val16 = (a << 12) | (r << 8) | (g << 4) | b;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_ABGR4444:
+		a = (a >> 28) & 0x0f;
+		r = (r >> 28) & 0x0f;
+		g = (g >> 28) & 0x0f;
+		b = (b >> 28) & 0x0f;
+		val16 = (a << 12) | (b << 8) | (g << 4) | r;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_RGBA4444:
+		a = (a >> 28) & 0x0f;
+		r = (r >> 28) & 0x0f;
+		g = (g >> 28) & 0x0f;
+		b = (b >> 28) & 0x0f;
+		val16 = (r << 12) | (g << 8) | (b << 4) | a;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_BGRX4444:
+	case DRM_FORMAT_BGRA4444:
+		a = (a >> 28) & 0x0f;
+		r = (r >> 28) & 0x0f;
+		g = (g >> 28) & 0x0f;
+		b = (b >> 28) & 0x0f;
+		val16 = (b << 12) | (g << 8) | (r << 4) | a;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+
+	/*
+	 * XRGB1555 and friends
+	 */
+
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		a = (a >> 31) & 0x01;
+		r = (r >> 27) & 0x1f;
+		g = (g >> 27) & 0x1f;
+		b = (b >> 27) & 0x1f;
+		val16 = (a << 15) | (r << 10) | (g << 5) | b;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		a = (a >> 31) & 0x01;
+		r = (r >> 27) & 0x1f;
+		g = (g >> 27) & 0x1f;
+		b = (b >> 27) & 0x1f;
+		val16 = (a << 15) | (b << 10) | (g << 5) | r;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_RGBA5551:
+		a = (a >> 31) & 0x01;
+		r = (r >> 27) & 0x1f;
+		g = (g >> 27) & 0x1f;
+		b = (b >> 27) & 0x1f;
+		val16 = (r << 15) | (g << 10) | (b << 5) | a;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_BGRX5551:
+	case DRM_FORMAT_BGRA5551:
+		a = (a >> 31) & 0x01;
+		r = (r >> 27) & 0x1f;
+		g = (g >> 27) & 0x1f;
+		b = (b >> 27) & 0x1f;
+		val16 = (b << 15) | (g << 10) | (r << 5) | a;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+
+	/*
+	 * RGB565 and friends
+	 */
+
+	case DRM_FORMAT_RGB565:
+		r = (r >> 27) & 0x1f;
+		g = (g >> 26) & 0x3f;
+		b = (b >> 27) & 0x1f;
+		val16 = (r << 11) | (g << 5) | b;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_BGR565:
+		r = (r >> 27) & 0x1f;
+		g = (g >> 26) & 0x3f;
+		b = (b >> 27) & 0x1f;
+		val16 = (b << 11) | (g << 5) | r;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+
+	/*
+	 * RGB888 and friends
+	 */
+
+	case DRM_FORMAT_RGB888:
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val16 = (r << 11) | (g << 5) | b;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+	case DRM_FORMAT_BGR888:
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val16 = (b << 11) | (g << 5) | r;
+		if (unlikely(need_swap))
+			val16 = swab16(val16);
+		put_unaligned(val16, (u16*)dst);
+		break;
+
+	/*
+	 * XRGB8888 and friends
+	 */
+
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		a = (a >> 24) & 0xff;
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val32 = (a << 24) | (r << 16) | (g << 8) | b;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		a = (a >> 24) & 0xff;
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val32 = (a << 24) | (b << 16) | (g << 8) | r;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+		a = (a >> 24) & 0xff;
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val32 = (r << 24) | (g << 16) | (b << 8) | a;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+		a = (a >> 24) & 0xff;
+		r = (r >> 24) & 0xff;
+		g = (g >> 24) & 0xff;
+		b = (b >> 24) & 0xff;
+		val32 = (b << 24) | (g << 16) | (r << 8) | a;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+
+	/*
+	 * XRGB2101010 and friends
+	 */
+
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		a = (a >> 30) & 0x0003;
+		r = (r >> 22) & 0x03ff;
+		g = (g >> 22) & 0x03ff;
+		b = (b >> 22) & 0x03ff;
+		val32 = (a << 30) | (r << 20) | (g << 10) | b;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_XBGR2101010:
+	case DRM_FORMAT_ABGR2101010:
+		a = (a >> 30) & 0x0003;
+		r = (r >> 22) & 0x03ff;
+		g = (g >> 22) & 0x03ff;
+		b = (b >> 22) & 0x03ff;
+		val32 = (a << 30) | (b << 20) | (g << 10) | r;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_RGBX1010102:
+	case DRM_FORMAT_RGBA1010102:
+		a = (a >> 30) & 0x0003;
+		r = (r >> 22) & 0x03ff;
+		g = (g >> 22) & 0x03ff;
+		b = (b >> 22) & 0x03ff;
+		val32 = (r << 30) | (g << 20) | (b << 10) | a;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	case DRM_FORMAT_BGRX1010102:
+	case DRM_FORMAT_BGRA1010102:
+		a = (a >> 30) & 0x0003;
+		r = (r >> 22) & 0x03ff;
+		g = (g >> 22) & 0x03ff;
+		b = (b >> 22) & 0x03ff;
+		val32 = (b << 30) | (g << 20) | (r << 10) | a;
+		if (unlikely(need_swap))
+			val32 = swab32(val32);
+		put_unaligned(val32, (u32*)dst);
+		break;
+	}
+}
+
+/* fill region with given color */
+static inline void dlog__fill(u8 *dst,
+			      size_t width,
+			      size_t height,
+			      size_t stride,
+			      size_t cpp,
+			      u32 pixel_format,
+			      u32 a, u32 r, u32 g, u32 b)
+{
+	size_t i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i)
+			dlog__draw_px(&dst[i * cpp], pixel_format, a, r, g, b);
+		dst += stride;
+	}
+}
+
+/* fill region with background color (opaque solid black) */
+static inline void dlog__clear(u8 *dst,
+			       size_t width,
+			       size_t height,
+			       size_t stride,
+			       size_t cpp,
+			       u32 pixel_format)
+{
+	return dlog__fill(dst, width, height, stride, cpp, pixel_format,
+			  0xffffffffUL, 0, 0, 0);
+}
+
+/* draw single character at given destination */
+static inline void dlog__draw_char(char ch,
+				   u8 *dst,
+				   size_t dst_stride,
+				   size_t dst_cpp,
+				   u32 pixel_format)
+{
+	size_t src_width, src_height, src_stride, i;
+	const u8 *src;
+	u32 col;
+
+	src_width = dlog_font->width;
+	src_height = dlog_font->height;
+	src_stride = DIV_ROUND_UP(src_width, 8);
+
+	src = dlog_font->data;
+	src += ch * src_height * src_stride;
+
+	while (src_height--) {
+		for (i = 0; i < src_width; ++i) {
+			col = src[i / 8] & (0x80 >> (i % 8));
+			col = col ? 0xffffffffUL : 0;
+
+			/* draw opaque black or white pixel */
+			dlog__draw_px(&dst[dst_cpp * i], pixel_format,
+				      0xffffffffUL, col, col, col);
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+/* draw line of text at given destination */
+static inline void dlog__draw_line(const char *log,
+				   size_t log_len,
+				   size_t row_len,
+				   u8 *dst,
+				   size_t dst_stride,
+				   size_t dst_cpp,
+				   u32 pixel_format)
+{
+	size_t i;
+
+	for (i = 0; i < log_len; ++i)
+		dlog__draw_char(log[i],
+				dst + i * dlog_font->width * dst_cpp,
+				dst_stride,
+				dst_cpp,
+				pixel_format);
+
+	/* clear remaining line */
+	if (i < row_len)
+		dlog__clear(dst + i * dlog_font->width * dst_cpp,
+			    (row_len - i) * dlog_font->width,
+			    dlog_font->height,
+			    dst_stride,
+			    dst_cpp,
+			    pixel_format);
+}
+
+/*
+ * Internal Rendering Entry-Point
+ * This is the main internal entry-point for rendering. The caller must
+ * guarantee the lifetime of @buf and @kern_map (see drm_log_draw() for
+ * parameter-descriptions).
+ *
+ * This helper supports multi-column layouts. On modern high-res wide displays,
+ * you can render the log into multiple columns to increase the backlog size.
+ * This is very useful for long backtraces during kernel panics and alike.
+ * Furthermore, we support splitting lines if the target is too small and
+ * merging lines if they are marked as "continuation" if the target is wider
+ * than the internal log.
+ *
+ * Our algorithm basically is:
+ *   - calculate column/screen/.. dimensions
+ *   - as long as there are free screen-lines on the framebuffer:
+ *      - get the next log-entry and calculate the line-length including all
+ *        continuation lines
+ *      - calculate how many screen-lines are needed for that log-entry
+ *      - for each needed line:
+ *         - get next free screen-line
+ *         - if it's the last line, render possibly trailing blanks
+ *         - for each char in this line:
+ *            - try to get the next char from the log-buffer
+ *            - parallel-writers might have changed lenghts, break if so
+ *            - render the character
+ *         - if column is full, advance column
+ *         - if out of lines, break
+ *   - fill remaining space with black
+ *   - fill any margins with black
+ *   - draw column-separators
+ *
+ * The renderer works bottom-up, not top-down! So we start at the lower right
+ * corner and render the newest message first. This allows us to skip
+ * line-calculations (as log-entries might occupy multiple-lines depending on
+ * the screen-width).
+ *
+ * Note that this renderer is not optimized for speed! It is suprisingly fast
+ * and works well for moderate refresh-rates. However, it is *not* suitable for
+ * terminal-emulators etc. It is meant for panic-screens, oops-logs and
+ * boot-debugging. Users are highly inclined to call this on vblanks, instead
+ * of on each change (or use some 16ms delayed workqueue). Furthermore,
+ * production systems should disable any verbose logging during normal
+ * operation.
+ *
+ * To support continuation lines, we always need to calculate the real length
+ * of the line before rendering it (because we are bottom-up!). When rendering
+ * the characters, we need to advance the logs one-by-one again, but parallel
+ * writers might have changed the line-lengths in between. If we detect such
+ * a situation (i.e., running out of chars), we skip rendering as the writer
+ * must have rescheduled a new render-pass already.
+ */
+static void dlog__draw(struct dlog_buf *buf,
+		       void *kern_map,
+		       size_t width,
+		       size_t height,
+		       size_t stride,
+		       size_t cpp,
+		       u32 pixel_format,
+		       size_t columns)
+{
+	const size_t col_padding = 5;
+	size_t log_pos, log_i, t, pos;
+	size_t line_i, col_i, col_line_i;
+	size_t col_width, lines_per_col, lines_per_screen, chars_per_line;
+	size_t col_offset, lines_needed, k, l;
+	size_t entry_pos, entry_len, entry_overlen, entry_cnt;
+	struct dlog_line *entry;
+	u8 *map, *line, *ch;
+
+	if (!columns)
+		columns = 1;
+
+	t = (width - col_padding * (columns - 1)) / columns;
+	chars_per_line = t / dlog_font->width;
+	col_width = chars_per_line * dlog_font->width;
+	col_offset = col_width + col_padding;
+
+	lines_per_col = height / dlog_font->height;
+	lines_per_screen = lines_per_col * columns;
+
+	log_i = 0;
+	line_i = 0;
+	col_i = 0;
+	col_line_i = 0;
+	log_pos = atomic_read(&buf->pos);
+
+	/* safety checks for invalid divisors */
+	if (!chars_per_line)
+		goto skip;
+
+	/* draw as long as screen-lines are left */
+	while (line_i < lines_per_screen) {
+
+		/* get next log-entry, combine all continuation entries */
+		entry = NULL;
+		entry_cnt = 0;
+		entry_len = 0;
+		entry_pos = log_pos;
+
+		do {
+			/* out-of-lines? (don't care for continuation) */
+			if (log_i >= buf->height)
+				break;
+
+			++log_i;
+			entry = &buf->lines[log_pos];
+			if (!log_pos--)
+				log_pos = buf->height - 1;
+
+			entry_len += atomic_read(&entry->length);
+			++entry_cnt;
+		} while (atomic_read(&entry->cont));
+
+		/* out of log-entries? */
+		if (!entry_cnt)
+			break;
+
+		/* how many lines are needed for this entry? */
+		lines_needed = DIV_ROUND_UP(entry_len, chars_per_line);
+		/* how long's the last line? (all others are full-lines) */
+		entry_overlen = entry_len % chars_per_line;
+
+		/* draw all needed lines (bottom up) */
+		entry = &buf->lines[entry_pos];
+		pos = atomic_read(&entry->length);
+		for (k = 0; k < lines_needed; ++k) {
+
+			/* get next line; we know there's at least one free */
+			line = kern_map;
+			/* jump to current column */
+			line += (columns - col_i - 1) * col_offset * cpp;
+			/* jump to current line */
+			line += (lines_per_col - col_line_i - 1) *
+				dlog_font->height * stride;
+
+			/* if in last line; draw blanks */
+			t = chars_per_line - entry_overlen;
+			if (!k && t > 0) {
+				/* last line and @t blanks at the end */
+
+				/* get char position */
+				ch = line;
+				ch += (chars_per_line - t) *
+					dlog_font->width * cpp;
+
+				/* clear @t chars at @ch */
+				dlog__clear(ch,
+					    t * dlog_font->width,
+					    dlog_font->height,
+					    stride,
+					    cpp,
+					    pixel_format);
+
+				/* skip @t blanks */
+				l = t;
+			} else {
+				/* not last line or no blanks; draw all */
+				l = 0;
+			}
+
+			/* draw all remaining chars (right to left) */
+			for ( ; l < chars_per_line; ++l) {
+				/* get char position */
+				ch = line;
+				ch += (chars_per_line - l - 1) *
+					dlog_font->width * cpp;
+
+				/* get next char */
+				while (!pos--) {
+					/* There must be entry_len chars in the
+					 * log-buffer to write. But parallel
+					 * writers might have shortened the
+					 * lines. Stop rendering as a redraw
+					 * must have been scheduled already. */
+					if (!--entry_cnt)
+						goto skip;
+
+					if (!entry_pos--)
+						entry_pos = buf->height - 1;
+
+					entry = &buf->lines[entry_pos];
+					pos = atomic_read(&entry->length);
+				}
+
+				dlog__draw_char(entry->cells[pos],
+						ch,
+						stride,
+						cpp,
+						pixel_format);
+			}
+
+			/* if out-of-lines, go to next column */
+			++col_line_i;
+			if (col_line_i >= lines_per_col) {
+				col_line_i = 0;
+				++col_i;
+			}
+
+			/* we might be out-of-lines (or out-of-cols) */
+			++line_i;
+			if (line_i >= lines_per_screen)
+				break;
+		}
+	}
+
+skip:
+	/*
+	 * Any amount of data might have been drawn. Here we need to make sure
+	 * to clear the remaining parts to black.
+	 */
+
+	/* clear remaining parts of partially used column */
+	if (col_line_i > 0) {
+		map = kern_map;
+		map += (columns - col_i - 1) * col_offset * cpp;
+		dlog__clear(map,
+			    col_width,
+			    (lines_per_col - col_line_i) * dlog_font->height,
+			    stride,
+			    cpp,
+			    pixel_format);
+
+		col_line_i = 0;
+		++col_i;
+	}
+
+	/* clear remaining columns */
+	if (col_i < columns) {
+		dlog__clear(kern_map,
+			    (columns - col_i) * col_offset,
+			    height,
+			    stride,
+			    cpp,
+			    pixel_format);
+	}
+
+	/* clear right margin */
+	t = columns * col_offset - col_padding;
+	if (t < width) {
+		map = kern_map;
+		map += t * cpp;
+		dlog__clear(map, width - t, height, stride, cpp, pixel_format);
+	}
+
+	/* clear bottom margin */
+	t = lines_per_col * dlog_font->height;
+	if (t < height) {
+		map = kern_map;
+		map += t * stride;
+		dlog__clear(map, width, height - t, stride, cpp, pixel_format);
+	}
+
+	/* draw column separators */
+	for (k = 1; k < columns; ++k) {
+		map = kern_map;
+		map += (k * col_offset - col_padding) * cpp;
+		dlog__clear(map,
+			    col_padding,
+			    height,
+			    stride,
+			    cpp,
+			    pixel_format);
+	}
+}
+
+/**
+ * drm_log_draw: Render current kernel log into framebuffer
+ * @kern_map: kernel-address of framebuffer region
+ * @width: FB width in pixels
+ * @height: FB height in pixels
+ * @stride: FB stride in *bytes*
+ * @cpp: FB chars-per-pixel (if 0, autodetected from @pixel_format)
+ * @pixel_format: Pixel format of FB (four-CC)
+ * @columns: Number of log-columns to render
+ *
+ * This is the main rendering entry-point for the kernel log. It renders the
+ * current log-contents into the given memory area. It must be mapped for write
+ * access from kernel-space and can be unmapped after this returns.
+ *
+ * @columns contains the maximum number of columns to use to render the log
+ * contents. Usually you want to set this to 1, but higher values can be used
+ * to increase the visible backlog on wide displays. Note that this value is
+ * limited internally so columns will always have a suitable width.
+ *
+ * This function may be called at *any* times (even if drm-log is not
+ * initialized or currently initializing). Multiple renderers can run in
+ * parallel just fine.
+ *
+ * Note that currently only RGB formats are supported (but all of them). There
+ * is currently no plan to support multi-plane YUV formats and alike.
+ */
+void drm_log_draw(void *kern_map,
+		  size_t width,
+		  size_t height,
+		  size_t stride,
+		  size_t cpp,
+		  u32 pixel_format,
+		  size_t columns)
+{
+	struct dlog_buf *buf;
+	unsigned int depth;
+	int bpp;
+
+	if (!kern_map || !width || !height || !stride)
+		return;
+	if (!pixel_format || !columns)
+		return;
+
+	if (!cpp) {
+		drm_fb_get_bpp_depth(pixel_format, &depth, &bpp);
+		if (!bpp)
+			return;
+
+		cpp = DIV_ROUND_UP(bpp, 8);
+	}
+
+	rcu_read_lock();
+	buf = rcu_dereference(dlog_buf);
+	if (buf) {
+		/* make each column at least 80 chars wide */
+		columns = min(columns, width / (dlog_font->width * 80));
+		columns = columns ? : 1;
+
+		dlog__draw(buf,
+			   kern_map,
+			   width,
+			   height,
+			   stride,
+			   cpp,
+			   pixel_format,
+			   columns);
+	}
+	rcu_read_unlock();
+}
+EXPORT_SYMBOL(drm_log_draw);
+
+static void dlog_con_write(struct console *con,
+			   const char *buf,
+			   unsigned int size)
+{
+	drm_log_write(buf, size, false);
+}
+
+static struct console dlog_con_driver = {
+	.name = "drmlog",
+	.write = dlog_con_write,
+	.flags = CON_PRINTBUFFER | CON_ENABLED,
+};
+
+/**
+ * drm_log_init() - Initialize drm-log subsystem
+ *
+ * Initialize drm-log subsystem, allocate initial buffers and register a
+ * console-driver. Errors are handled internally, so the caller can assume this
+ * always succeeds.
+ * You can safely call this helper multiple times. It's a no-op if the subsystem
+ * is already initialized. However, no locking is done, so you usually have to
+ * call this from within your module_init() callback.
+ *
+ * You must call drm_log_exit() to clean up any allocated memory.
+ */
+void drm_log_init(void)
+{
+	size_t def_width, def_height;
+
+	if (dlog_font)
+		return;
+
+	/* use 800x600 as initial value and as global hint */
+	def_width = 800;
+	def_height = 600;
+
+	/* prefer fonts with w/h multiple of 8 */
+	dlog_font = get_default_font(def_width, def_height, 0x8080, 0x8080);
+	if (!dlog_font) {
+		DRM_ERROR("cannot get font-description, disabling drm-log");
+		return;
+	}
+
+	/* provide initial buffer so we ca start logging */
+	drm_log_ensure_size(def_width, def_height);
+
+	register_console(&dlog_con_driver);
+}
+
+/**
+ * drm_log_exit() - Shutdown drm-log subsystem
+ *
+ * Deinitialize the drm-log subsystem, unregister the console driver and free
+ * allocated buffers.
+ *
+ * You must make sure there are no other users of this subsystem when calling
+ * this! Usually, it's *not* safe to call this outside of your module_exit()
+ * callback.
+ *
+ * You can call this helper multiple times. If the subsytem is already
+ * deinitialized, this is a no-op.
+ */
+void drm_log_exit(void)
+{
+	struct dlog_buf *buf;
+
+	if (!dlog_font)
+		return;
+
+	unregister_console(&dlog_con_driver);
+
+	/* Make buffer-destruction safe against pending readers in case of
+	 * buggy drivers and pending workqueues. */
+	mutex_lock(&dlog_wlock);
+	buf = rcu_dereference_protected(dlog_buf,
+					lockdep_is_held(&dlog_wlock));
+	if (buf) {
+		rcu_assign_pointer(dlog_buf, NULL);
+		synchronize_rcu();
+		dlog_free_buf(buf);
+	}
+	dlog_font = NULL;
+	mutex_unlock(&dlog_wlock);
+}
diff --git a/include/drm/drm_log.h b/include/drm/drm_log.h
new file mode 100644
index 0000000..4cee7a8
--- /dev/null
+++ b/include/drm/drm_log.h
@@ -0,0 +1,62 @@
+#ifndef __DRM_LOG_H__
+#define __DRM_LOG_H__
+
+/*
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann at gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#ifdef CONFIG_DRM_LOG
+
+void drm_log_init(void);
+void drm_log_exit(void);
+
+void drm_log_write(const char *data, size_t len, bool atomic);
+void drm_log_ensure_size(size_t width_px, size_t height_px);
+void drm_log_draw(void *kern_map,
+		  size_t width,
+		  size_t height,
+		  size_t stride,
+		  size_t cpp,
+		  u32 pixel_format,
+		  size_t columns);
+
+#else
+
+static inline void drm_log_init(void) { }
+static inline void drm_log_exit(void) { }
+
+static inline void drm_log_write(const char *data, size_t len, bool atomic) { }
+static inline void drm_log_ensure_size(size_t x, size_t y) { }
+static inline void drm_log_draw(void *kern_map,
+				size_t width,
+				size_t height,
+				size_t stride,
+				size_t cpp,
+				u32 pixel_format,
+				size_t columns) { }
+
+#endif
+
+#endif /* __DRM_LOG_H__ */
-- 
1.9.0



More information about the dri-devel mailing list