@@ -83,3 +83,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
obj-$(CONFIG_DRM_ARCPGU)+= arc/
obj-y += hisilicon/
+obj-y += drm-text/
new file mode 100644
@@ -0,0 +1,4 @@
+drm-text-y := drm-text-buffer.o
+drm-text-$(CONFIG_DEBUG_FS) += drm-text-debugfs.o
+
+obj-m += drm-text.o
new file mode 100644
@@ -0,0 +1,336 @@
+#define DEBUG
+/*
+ * 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 <drm/drmP.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <linux/font.h>
+#include <linux/module.h>
+
+#include "drm-text.h"
+
+struct drm_text_buffer *drm_text_buffers[MAX_DRM_TEXT_BUFFERS];
+
+/**
+ * DOC: overview
+ *
+ * Text buffer format 16-bit: 4-bit bg color, 4-bit fg color, 8-bit character
+ * The CGA color palette is used: 4-bit RGBI: intense red green blue
+ *
+ * drm_text_get(): get a text buffer for a dev->primary->index
+ * Clients can write directly to the text buffer
+ * drm_text_write(): write string to text buffer (handles wrapping)
+ * drm_text_scroll(): scroll text buffer
+ * drm_text_flush(): flush text buffer to pixel buffer (uses worker unless panic)
+ * drm_text_enable/disable(): enabled/disable text buffer (mode setting)
+ *
+ */
+
+static const u32 drm_text_palette888[] = {
+ 0x00000000, /* 0 black */
+ 0x000000aa, /* 1 blue */
+ 0x0000aa00, /* 2 green */
+ 0x0000aaaa, /* 3 cyan */
+ 0x00aa0000, /* 4 red */
+ 0x00aa00aa, /* 5 magenta */
+ 0x00aa5500, /* 6 brown */
+ 0x00aaaaaa, /* 7 light gray */
+ 0x00555555, /* 8 dark gray */
+ 0x005555ff, /* 9 bright blue */
+ 0x0055ff55, /* 10 bright green */
+ 0x0055ffff, /* 11 bright cyan */
+ 0x00ff5555, /* 12 bright red */
+ 0x00ff55ff, /* 13 bright magenta */
+ 0x00ffff55, /* 14 yellow */
+ 0x00ffffff /* 15 white */
+};
+
+static const u16 drm_text_palette565[] = {
+ 0x0000, /* 0 black */
+ 0x0015, /* 1 blue */
+ 0x0540, /* 2 green */
+ 0x0555, /* 3 cyan */
+ 0xa800, /* 4 red */
+ 0xa815, /* 5 magenta */
+ 0xaaa0, /* 6 brown */
+ 0xad55, /* 7 light gray */
+ 0x52aa, /* 8 dark gray */
+ 0x52bf, /* 9 bright blue */
+ 0x57ea, /* 10 bright green */
+ 0x57ff, /* 11 bright cyan */
+ 0xfaaa, /* 12 bright red */
+ 0xfabf, /* 13 bright magenta */
+ 0xffea, /* 14 yellow */
+ 0xffff /* 15 white */
+};
+
+static void drm_text_render_char(struct drm_text_buffer *text, size_t x, size_t y)
+{
+ u16 cc = text->text_buf[x + (y * text->cols)];
+ const struct font_desc *font = text->font;
+ u16 *vmem = text->pixel_buf;
+ char c = cc;
+ unsigned int h, w;
+ const u8 *src;
+ u16 fg_col = drm_text_palette565[(cc & 0x0f00) >> 8];
+ u16 bg_col = drm_text_palette565[cc >> 12];
+
+ src = font->data + c * font->height;
+
+ for (h = 0; h < font->height; h++) {
+ u8 fontline = *(src + h);
+
+ for (w = 0; w < font->width; w++) {
+ size_t i = ((x * font->width) + w) + (((y * font->height) + h) * text->fb->width);
+
+ vmem[i] = fontline & BIT(7 - w) ? fg_col : bg_col;
+ }
+ }
+}
+
+static void drm_text_do_flush(struct drm_text_buffer *text)
+{
+ size_t x, y;
+
+ drm_text_debug("%s - IN\n", __func__);
+
+ for (y = 0; y < text->rows; y++) {
+ for (x = 0; x < text->cols; x++) {
+ /*
+ * TODO
+ * A copy of the previously rendered text_buf
+ * could help speed this up by skipping characters
+ * that haven't changed
+ */
+ drm_text_render_char(text, x, y);
+ }
+ }
+
+ drm_text_debug("%s - OUT\n", __func__);
+}
+
+static void drm_text_flush_work(struct work_struct *work)
+{
+ struct drm_text_buffer *text = container_of(work, struct drm_text_buffer,
+ flush_work);
+ struct drm_framebuffer *fb = text->fb;
+
+ drm_text_debug("%s\n", __func__);
+ drm_text_do_flush(text);
+
+ if (fb->funcs->dirty)
+ fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+}
+
+void drm_text_flush(struct drm_text_buffer *text, bool panic)
+{
+
+ drm_text_debug("%s(panic=%u)\n", __func__, panic);
+ if (panic)
+ drm_text_do_flush(text);
+ else
+ schedule_work(&text->flush_work);
+}
+
+void drm_text_write(struct drm_text_buffer *text, const char *str,
+ unsigned int num)
+{
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ char c = *str++;
+
+ if (text->y_pos == text->rows) {
+ drm_text_scroll(text, 0, text->rows, 1);
+ text->y_pos--;
+ }
+
+ if (c == '\n') {
+ text->x_pos = 0;
+ text->y_pos++;
+ continue;
+ }
+
+ text->text_buf[text->x_pos + (text->y_pos * text->cols)] = 0x0f00 | c;
+ text->x_pos++;
+
+ if (text->x_pos == text->cols) {
+ text->x_pos = 0;
+ text->y_pos++;
+ }
+ }
+}
+
+/* TODO: Is top and bottom necessary? From VT */
+void drm_text_scroll(struct drm_text_buffer *text, unsigned int top,
+ unsigned int bottom, int lines)
+{
+ size_t count;
+
+ drm_text_debug("%s(top=%u, bottom=%u, lines=%d)\n", __func__, top, bottom, lines);
+
+ if (lines > 0) {
+ count = text->cols * (text->rows - lines);
+ memmove(text->text_buf, text->text_buf + text->cols, count * sizeof(u16));
+ memset(text->text_buf + count, 0, text->cols * lines * sizeof(u16));
+ } else if (lines < 0) {
+ drm_text_log("%s: TODO\n", __func__);
+ }
+
+}
+
+int drm_text_enable(struct drm_text_buffer *text)
+{
+ int ret;
+
+ ret = drm_fb_helper_set_par(text->fbi);
+ if (ret)
+ drm_text_log("%s: failed to enable %d\n", __func__, ret);
+
+ return ret;
+}
+
+int drm_text_disable(struct drm_text_buffer *text)
+{
+ int ret;
+
+ ret = fb_blank(text->fbi, FB_BLANK_POWERDOWN);
+ if (ret)
+ drm_text_log("%s: failed to disable %d\n", __func__, ret);
+
+ return ret;
+}
+
+struct drm_text_buffer *drm_text_get(unsigned int index)
+{
+ if (index >= MAX_DRM_TEXT_BUFFERS)
+ return NULL;
+
+ return drm_text_buffers[index];
+}
+
+static struct drm_text_buffer *drm_text_create_from_fb_helper(struct drm_fb_helper *fb_helper)
+{
+ struct drm_framebuffer *fb = fb_helper->fb;
+ struct drm_gem_cma_object *cma_obj;
+ struct drm_text_buffer *text;
+ int ret;
+
+ switch (fb->pixel_format) {
+ case DRM_FORMAT_RGB565:
+ break;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ text = kzalloc(sizeof(*text), GFP_KERNEL);
+ if (!text)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_WORK(&text->flush_work, drm_text_flush_work);
+ text->fb = fb;
+ text->fbi = fb_helper->fbdev;
+
+ DRM_DEBUG_KMS("DRM minor: %d\n", fb->dev->primary->index);
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ text->pixel_buf = cma_obj->vaddr;
+
+ DRM_DEBUG_KMS("[FB:%d] pixel_format: %s, vaddr=%p\n", fb->base.id,
+ drm_get_format_name(fb->pixel_format), cma_obj->vaddr);
+
+ /* TODO: 10 and 12 width fonts are not supported */
+ text->font = get_default_font(fb->width, fb->height, -1, -1);
+ if (!text->font) {
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ DRM_DEBUG_KMS("font: %s\n", text->font->name);
+ text->cols = text->fb->width / text->font->width;
+ text->rows = text->fb->height / text->font->height;
+ DRM_DEBUG_KMS("cols=%zu, rows=%zu\n", text->cols, text->rows);
+
+ text->text_buf = kcalloc(text->cols * text->rows, sizeof(*text->text_buf), GFP_KERNEL);
+ if (!text->text_buf) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ drm_text_debugfs_buffer_init(text);
+/* REMOVE, helpful while testing on display that needs fb flushing */
+drm_text_flush(text, false);
+
+ return text;
+
+err_free:
+ DRM_DEBUG_KMS("Error creating buffer %d\n", ret);
+ kfree(text->text_buf);
+ kfree(text);
+
+ return ERR_PTR(ret);
+}
+
+static void drm_text_scan_fbdev(void)
+{
+ struct drm_fb_helper *fb_helper;
+ struct drm_text_buffer *text;
+ unsigned int i;
+
+ for (i = 0; i < FB_MAX; i++) {
+ struct fb_info *info = registered_fb[i];
+
+ if (!info)
+ continue;
+
+ fb_helper = info->par;
+ if (fb_helper && fb_helper->fbdev == info) {
+ text = drm_text_create_from_fb_helper(fb_helper);
+ if (!IS_ERR(text))
+ drm_text_buffers[text->fb->dev->primary->index] = text;
+ }
+ }
+}
+
+static void drm_text_free(struct drm_text_buffer *text)
+{
+ kfree(text->text_buf);
+ kfree(text);
+}
+
+static int __init drm_text_init(void)
+{
+ int ret = 0;
+
+ ret = drm_text_debugfs_init();
+ if (ret)
+ pr_err("Failed to create debugfs entry\n");
+
+ drm_text_scan_fbdev();
+
+
+ return ret;
+}
+module_init(drm_text_init);
+
+static void __exit drm_text_exit(void)
+{
+ unsigned int i;
+
+ drm_text_debugfs_exit();
+
+ for (i = 0; i < MAX_DRM_TEXT_BUFFERS; i++)
+ if (drm_text_buffers[i])
+ drm_text_free(drm_text_buffers[i]);
+}
+module_exit(drm_text_exit);
+
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,206 @@
+/*
+ * 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 <drm/drmP.h>
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "drm-text.h"
+
+#define HACK_NEED_FLUSHING
+
+#define LOG_LINE 64
+#define LOG_ENTRIES PAGE_SIZE / LOG_LINE
+
+static char *log_buf;
+static size_t log_pos;
+static struct dentry *drm_text_debugfs_root;
+
+static void drm_text_vprintk(int level, const char *fmt, va_list args)
+{
+ u32 rem_nsec;
+ char *text;
+ size_t len;
+ u64 sec;
+
+ if (log_pos >= LOG_ENTRIES)
+ log_pos = 0;
+
+ text = log_buf + (log_pos++ * LOG_LINE);
+ if (log_pos == LOG_ENTRIES)
+ log_pos = 0;
+
+ sec = div_u64_rem(local_clock(), 1000000000, &rem_nsec);
+
+ len = scnprintf(text, LOG_LINE, "[%5llu.%06u] ", sec, rem_nsec / 1000);
+
+ vscnprintf(text + len, LOG_LINE - len, fmt, args);
+
+ /* Make sure to always have a newline in case of overflow */
+ if (text[LOG_LINE - 2] != '\0')
+ text[LOG_LINE - 2] = '\n';
+}
+
+void drm_text_log(const char *fmt, ...)
+{
+ va_list args;
+
+ if (!log_buf)
+ return;
+
+ va_start(args, fmt);
+ drm_text_vprintk(LOGLEVEL_DEFAULT, fmt, args);
+ va_end(args);
+}
+
+static int drm_text_log_show(struct seq_file *m, void *v)
+{
+ size_t pos = log_pos;
+ unsigned int i;
+ char *text;
+
+ for (i = 0; i < LOG_ENTRIES; i++) {
+ text = log_buf + (pos++ * LOG_LINE);
+ if (pos == LOG_ENTRIES)
+ pos = 0;
+ if (*text == '\0')
+ continue;
+ seq_puts(m, text);
+ }
+
+ return 0;
+}
+
+static int drm_text_log_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, drm_text_log_show, NULL);
+}
+
+static const struct file_operations drm_text_log_ops = {
+ .owner = THIS_MODULE,
+ .open = drm_text_log_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int drm_text_buffer_show(struct seq_file *m, void *v)
+{
+ struct drm_text_buffer *text = m->private;
+ size_t x, y;
+
+ for (y = 0; y < text->rows; y++) {
+ for (x = 0; x < text->cols; x++) {
+ char c = text->text_buf[x + (y * text->cols)];
+
+ if (c < 32 || c == 127 || c == 255)
+ c = ' ';
+ seq_putc(m, c);
+ }
+ seq_putc(m, '\n');
+ }
+
+ return 0;
+}
+
+static int drm_text_buffer_open(struct inode *inode, struct file *file)
+{
+ struct drm_text_buffer *text = inode->i_private;
+
+ return single_open(file, drm_text_buffer_show, text);
+}
+
+static const struct file_operations drm_text_buffer_ops = {
+ .owner = THIS_MODULE,
+ .open = drm_text_buffer_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void drm_text_debugfs_buffer_init(struct drm_text_buffer *text)
+{
+ char name[64];
+
+ snprintf(name, sizeof(name), "%d", text->fb->dev->primary->index);
+ debugfs_create_file(name, S_IRUGO, drm_text_debugfs_root, text,
+ &drm_text_buffer_ops);
+}
+
+static ssize_t drm_text_flush_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct drm_text_buffer *text;
+ unsigned long long val;
+ char buf[24];
+ ssize_t ret = 0;
+ 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;
+
+ text = drm_text_get(val);
+ if (!text)
+ return -EINVAL;
+
+ //drm_text_flush(text, false);
+ if (text->fb->funcs->dirty)
+ text->fb->funcs->dirty(text->fb, NULL, 0, 0, NULL, 0);
+
+ return count;
+}
+
+static const struct file_operations drm_text_flush_ops = {
+ .write = drm_text_flush_write,
+ .open = simple_open,
+ .llseek = default_llseek,
+};
+
+int drm_text_debugfs_init(void)
+{
+ drm_text_debugfs_root = debugfs_create_dir("drm-text", NULL);
+ if (!drm_text_debugfs_root)
+ return -ENOMEM;
+
+ if (!debugfs_create_file("log", S_IRUGO, drm_text_debugfs_root, NULL,
+ &drm_text_log_ops))
+ goto err_remove;
+
+ log_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!log_buf)
+ goto err_remove;
+
+ debugfs_create_file("flush", S_IWUSR, drm_text_debugfs_root, NULL,
+ &drm_text_flush_ops);
+
+ return 0;
+
+err_remove:
+ debugfs_remove_recursive(drm_text_debugfs_root);
+
+ return -ENOMEM;
+}
+
+void drm_text_debugfs_exit(void)
+{
+ debugfs_remove_recursive(drm_text_debugfs_root);
+ kfree(log_buf);
+ log_buf = NULL;
+}
new file mode 100644
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef __LINUX_DRM_TEXT_H
+#define __LINUX_DRM_TEXT_H
+
+#include <linux/workqueue.h>
+
+#define MAX_DRM_TEXT_BUFFERS 64
+
+extern struct drm_text_buffer *drm_text_buffers[];
+
+struct drm_text_buffer {
+ struct fb_info *fbi;
+
+ struct work_struct flush_work;
+ const struct font_desc *font;
+ struct drm_framebuffer *fb;
+ void *pixel_buf;
+ u16 *text_buf;
+ size_t rows;
+ size_t cols;
+ size_t x_pos;
+ size_t y_pos;
+};
+
+void drm_text_flush(struct drm_text_buffer *text, bool panic);
+void drm_text_write(struct drm_text_buffer *text, const char *str,
+ unsigned int num);
+void drm_text_scroll(struct drm_text_buffer *text, unsigned int top,
+ unsigned int bottom, int lines);
+int drm_text_enable(struct drm_text_buffer *text);
+int drm_text_disable(struct drm_text_buffer *text);
+struct drm_text_buffer *drm_text_get(unsigned int index);
+
+#ifdef DEBUG
+#define drm_text_debug(fmt, ...) \
+ drm_text_log(fmt, ##__VA_ARGS__)
+#else
+#define drm_text_debug(fmt, ...) \
+do { \
+ if (0) \
+ drm_text_log(fmt, ##__VA_ARGS__); \
+} while (0)
+#endif
+
+#ifdef CONFIG_DEBUG_FS
+
+void drm_text_log(const char *fmt, ...) __printf(1, 2);
+void drm_text_debugfs_buffer_init(struct drm_text_buffer *text);
+int drm_text_debugfs_init(void);
+void drm_text_debugfs_exit(void);
+
+#else
+
+static inline void drm_text_log(const char *fmt, ...) __printf(1, 2)
+{
+}
+
+void drm_text_debugfs_buffer_init(struct drm_text_buffer *text)
+{
+}
+
+static inline int drm_text_debugfs_init(void)
+{
+ return 0;
+}
+
+static inline void drm_text_debugfs_exit(void);
+{
+}
+
+#endif
+
+#endif
This adds a text representation of a drm_framebuffer with backing object. Signed-off-by: Noralf Trønnes <noralf@tronnes.org> --- drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm-text/Makefile | 4 + drivers/gpu/drm/drm-text/drm-text-buffer.c | 336 ++++++++++++++++++++++++++++ drivers/gpu/drm/drm-text/drm-text-debugfs.c | 206 +++++++++++++++++ drivers/gpu/drm/drm-text/drm-text.h | 81 +++++++ 5 files changed, 628 insertions(+) create mode 100644 drivers/gpu/drm/drm-text/Makefile create mode 100644 drivers/gpu/drm/drm-text/drm-text-buffer.c create mode 100644 drivers/gpu/drm/drm-text/drm-text-debugfs.c create mode 100644 drivers/gpu/drm/drm-text/drm-text.h