This adds a text representation of a drm_framebuffer with backing object. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- 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 diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index e3dba6f..94a846d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -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/ diff --git a/drivers/gpu/drm/drm-text/Makefile b/drivers/gpu/drm/drm-text/Makefile new file mode 100644 index 0000000..48f55bc --- /dev/null +++ b/drivers/gpu/drm/drm-text/Makefile @@ -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 diff --git a/drivers/gpu/drm/drm-text/drm-text-buffer.c b/drivers/gpu/drm/drm-text/drm-text-buffer.c new file mode 100644 index 0000000..187dd4b --- /dev/null +++ b/drivers/gpu/drm/drm-text/drm-text-buffer.c @@ -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"); diff --git a/drivers/gpu/drm/drm-text/drm-text-debugfs.c b/drivers/gpu/drm/drm-text/drm-text-debugfs.c new file mode 100644 index 0000000..d01f995 --- /dev/null +++ b/drivers/gpu/drm/drm-text/drm-text-debugfs.c @@ -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; +} diff --git a/drivers/gpu/drm/drm-text/drm-text.h b/drivers/gpu/drm/drm-text/drm-text.h new file mode 100644 index 0000000..77e7429 --- /dev/null +++ b/drivers/gpu/drm/drm-text/drm-text.h @@ -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 -- 2.8.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel