diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-03-05 17:34:49 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-03-07 11:50:20 +0100 |
commit | 06bdc9aa3cd3a045b4a94bdc4c6c582338cf7992 (patch) | |
tree | 27203a1565f1eeff0aa7bb322d6b13b44a0cf584 | |
parent | 32afb1337bdffd1df2ac4ae749eace37528c747f (diff) |
drm: add kernel-log rendererdrm-log
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.
- The renderer supports integer-scaling for high-DPI screens. Instead of
using high-res fonts we simply scale fonts. This is *not* intended to
increase resolution but rather to make the log readable.
- 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@gmail.com>
-rw-r--r-- | drivers/gpu/drm/Kconfig | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/drm_drv.c | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/drm_log.c | 1179 | ||||
-rw-r--r-- | include/drm/drm_log.h | 62 |
5 files changed, 1252 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 8e7fa4dbaed8..4d9b7fad225f 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 292a79d64146..9c97b582dcd2 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 345be03c23db..310f99dbe07c 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 000000000000..be7aad583d4d --- /dev/null +++ b/drivers/gpu/drm/drm_log.c @@ -0,0 +1,1179 @@ +/* + * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@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 scale) +{ + size_t src_width, src_height, src_stride, i, j; + 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 */ + for (j = 0; j < scale; ++j) + dlog__draw_px(&dst[dst_cpp * (scale * i + j)], + pixel_format, + 0xffffffffUL, col, col, col); + } + + for (i = 1; i < scale; ++i) { + memcpy(dst + dst_stride, dst, + scale * src_width * dst_cpp); + dst += dst_stride; + } + + src += src_stride; + dst += dst_stride; + } +} + +/* + * 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, + size_t scale) +{ + const size_t col_padding = 5; + size_t ch_width, ch_height; + 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; + + ch_width = dlog_font->width; + ch_height = dlog_font->height; + + /* avoid mult-overflows with insanely huge scale-factors */ + scale = clamp_t(size_t, scale, 1, 256); + if (scale > 1) { + /* If fonts are scaled, trick renderer to think fonts are + * x-times as big. The final char-renderer does the real work + * later. */ + ch_width *= scale; + ch_height *= scale; + } + + /* make each column at least 80 chars wide */ + columns = min(columns, width / (ch_width * 80)); + columns = columns ? : 1; + + t = (width - col_padding * (columns - 1)) / columns; + chars_per_line = t / ch_width; + col_width = chars_per_line * ch_width; + col_offset = col_width + col_padding; + + lines_per_col = height / ch_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) * + ch_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) * ch_width * cpp; + + /* clear @t chars at @ch */ + dlog__clear(ch, + t * ch_width, + ch_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) * ch_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, + scale); + } + + /* 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) * ch_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 * ch_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* + * @pixel_format: Pixel format of FB (four-CC) + * @columns: Number of log-columns to render + * @scale: integer-scaling for font-renderer (use for high-DPI displays) + * + * 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, + u32 pixel_format, + size_t columns, + size_t scale) +{ + struct dlog_buf *buf; + unsigned int depth; + size_t cpp; + int bpp; + + if (!kern_map || !width || !height || !stride) + return; + if (!pixel_format || !columns || !scale) + return; + + 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) + dlog__draw(buf, + kern_map, + width, + height, + stride, + cpp, + pixel_format, + columns, + scale); + 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 000000000000..89c0e69eaa5d --- /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@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, + u32 pixel_format, + size_t columns, + size_t scale); + +#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, + u32 pixel_format, + size_t columns, + size_t scale) { } + +#endif + +#endif /* __DRM_LOG_H__ */ |