A hack to test the client API.

Signed-off-by: Noralf Trønnes <nor...@tronnes.org>
---
 drivers/gpu/drm/Makefile           |   1 +
 drivers/gpu/drm/client/Kconfig     |   5 +
 drivers/gpu/drm/client/Makefile    |   3 +
 drivers/gpu/drm/client/drm_vtcon.c | 785 +++++++++++++++++++++++++++++++++++++
 4 files changed, 794 insertions(+)
 create mode 100644 drivers/gpu/drm/client/Makefile
 create mode 100644 drivers/gpu/drm/client/drm_vtcon.c

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 388527093f80..ea63eb9e93ec 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
 obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += selftests/
 
 obj-$(CONFIG_DRM)      += drm.o
+obj-y                  += client/
 obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
 obj-$(CONFIG_DRM_PANEL_ORIENTATION_QUIRKS) += drm_panel_orientation_quirks.o
 obj-$(CONFIG_DRM_ARM)  += arm/
diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
index 6b01f2e51fb3..b03f3baf8b3f 100644
--- a/drivers/gpu/drm/client/Kconfig
+++ b/drivers/gpu/drm/client/Kconfig
@@ -6,4 +6,9 @@ config DRM_CLIENT_BOOTSPLASH
        help
          DRM Bootsplash
 
+config DRM_CLIENT_VTCON
+       tristate "DRM VT console"
+       help
+         DRM VT console
+
 endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
new file mode 100644
index 000000000000..7d39f1bc2b68
--- /dev/null
+++ b/drivers/gpu/drm/client/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DRM_CLIENT_VTCON) += drm_vtcon.o
diff --git a/drivers/gpu/drm/client/drm_vtcon.c 
b/drivers/gpu/drm/client/drm_vtcon.c
new file mode 100644
index 000000000000..910b4ec1a686
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_vtcon.c
@@ -0,0 +1,785 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/console.h>
+#include <linux/font.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/vt_buffer.h>
+#include <linux/vt_kern.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+
+/* TODO: Scrolling */
+
+/*
+ * The code consists of 3 parts:
+ *
+ * 1. The DRM client
+ *    Gets a display, uses the first mode to find a font,
+ *    sets the max cols/rows and a matching text buffer.
+ *
+ * 2. The VT console
+ *    Writes to the text buffer which consists of CGA colored characters.
+ *    Schedules the worker when it needs rendering or blanking.
+ *
+ * 3. Worker
+ *    Does modesetting, blanking and rendering.
+ *    It takes a snapshot of the VT text buffer and renders the changes since
+ *    last.
+ */
+
+struct drm_vtcon_vc {
+       struct mutex lock;
+
+       u16 *text_buf;
+       size_t buf_len;
+
+       unsigned int rows;
+       unsigned int cols;
+       unsigned int max_rows;
+       unsigned int max_cols;
+       const struct font_desc *font;
+       bool blank;
+       unsigned long cursor_blink_jiffies;
+};
+
+static struct drm_vtcon_vc *drm_vtcon_vc;
+
+struct drm_vtcon {
+       struct drm_client_dev *client;
+       struct drm_client_display *display;
+       struct drm_client_buffer *buffer;
+
+       unsigned int rows;
+       unsigned int cols;
+
+       u16 *text_buf[2];
+       size_t buf_len;
+       unsigned int buf_idx;
+       bool blank;
+};
+
+static struct drm_vtcon *vtcon_instance;
+
+static int drm_vtcon_dev_id;
+module_param_named(dev, drm_vtcon_dev_id, int, 0600);
+MODULE_PARM_DESC(drm_vtcon_dev_id, "DRM device id [default=0]");
+
+#define drm_vtcon_debug(vc, fmt, ...) \
+       pr_debug("%s[%u]: " fmt, __func__, vc->vc_num, ##__VA_ARGS__)
+
+/* CGA color palette: 4-bit RGBI: intense red green blue */
+static const u32 drm_vtcon_paletteX888[16] = {
+       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 void
+drm_vtcon_render_char(struct drm_vtcon *vtcon, unsigned int x, unsigned int y,
+                     u16 cc, const struct font_desc *font)
+{
+       struct drm_client_buffer *buffer = vtcon->buffer;
+       unsigned int h, w;
+       const u8 *src;
+       void *dst;
+       u32 *pix;
+       u32 fg_col = drm_vtcon_paletteX888[(cc & 0x0f00) >> 8];
+       u32 bg_col = drm_vtcon_paletteX888[cc >> 12];
+
+       src = font->data + (cc & 0xff) * font->height;
+       dst = vtcon->buffer->vaddr + y * buffer->pitch + x * sizeof(u32);
+
+       for (h = 0; h < font->height; h++) {
+               u8 fontline = *(src + h);
+
+               pix = dst;
+               for (w = 0; w < font->width; w++)
+                       pix[w] = fontline & BIT(7 - w) ? fg_col : bg_col;
+               dst += buffer->pitch;
+       }
+}
+
+static int drm_vtcon_modeset(struct drm_vtcon *vtcon, unsigned int cols,
+                            unsigned int rows, const struct font_desc *font)
+{
+       struct drm_client_display *display = vtcon->display;
+       struct drm_client_dev *client = vtcon->client;
+       struct drm_display_mode *mode, *best_mode = NULL;
+       unsigned int best_cols = ~0, best_rows = ~0;
+       struct drm_client_buffer *buffer;
+       int ret;
+
+       DRM_DEV_INFO(client->dev->dev, "IN %ux%u\n", cols, rows);
+
+       /* Find the smallest mode that fits */
+       drm_client_display_for_each_mode(mode, display) {
+               unsigned int mode_cols = mode->hdisplay / font->width;
+               unsigned int mode_rows = mode->vdisplay / font->height;
+
+               DRM_DEV_INFO(client->dev->dev,
+                            "try: %ux%u\n", mode_cols, mode_rows);
+
+               if (mode_cols < cols || mode_rows < rows)
+                       break;
+
+               if (mode_cols >= best_cols || mode_rows >= best_rows)
+                       continue;
+
+               best_cols = mode_cols;
+               best_rows = mode_rows;
+               best_mode = mode;
+       }
+
+       if (!best_mode) {
+               DRM_DEV_INFO(client->dev->dev, "Couldn't find mode for 
%ux%u\n", cols, rows);
+               return -EINVAL;
+       }
+
+       DRM_DEV_INFO(client->dev->dev, "Chosen: %ux%u\n", best_cols, best_rows);
+
+       buffer = drm_client_framebuffer_create(client, best_mode, 
DRM_FORMAT_XRGB8888);
+       if (IS_ERR(buffer)) {
+               ret = PTR_ERR(buffer);
+               DRM_DEV_ERROR(client->dev->dev,
+                             "Failed to create framebuffer: %d\n", ret);
+               return ret;
+       }
+
+       ret = drm_client_display_commit(display, buffer->fb, best_mode);
+       if (ret) {
+               DRM_DEV_ERROR(client->dev->dev,
+                             "Failed to commit mode: %d\n", ret);
+               goto err_free_buffer;
+       }
+
+       drm_client_framebuffer_delete(vtcon->buffer);
+
+       vtcon->buffer = buffer;
+       vtcon->cols = cols;
+       vtcon->rows = rows;
+
+       DRM_DEV_INFO(client->dev->dev, "OUT\n");
+
+       return 0;
+
+err_free_buffer:
+       drm_client_framebuffer_delete(buffer);
+
+       DRM_DEV_INFO(client->dev->dev, "OUT ret=%d\n", ret);
+
+       return ret;
+}
+
+static void drm_vtcon_blank(struct drm_vtcon *vtcon, bool blank)
+{
+       int mode = blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON;
+
+       drm_client_display_dpms(vtcon->display, mode);
+       vtcon->blank = blank;
+}
+
+static int drm_vtcon_resize_buf(struct drm_vtcon *vtcon, size_t len)
+{
+       u16 *text_buf[2];
+
+       text_buf[0] = kzalloc(len, GFP_KERNEL);
+       text_buf[1] = kzalloc(len, GFP_KERNEL);
+       if (!text_buf[0] || !text_buf[1]) {
+               kfree(text_buf[0]);
+               kfree(text_buf[1]);
+               return -ENOMEM;
+       }
+
+       kfree(vtcon->text_buf[0]);
+       kfree(vtcon->text_buf[1]);
+
+       vtcon->text_buf[0] = text_buf[0];
+       vtcon->text_buf[1] = text_buf[1];
+       vtcon->buf_len = len;
+
+       return 0;
+}
+
+static void drm_vtcon_work_fn(struct work_struct *work)
+{
+       struct drm_vtcon *vtcon = vtcon_instance;
+       unsigned int vc_cols, vc_rows, col, row, x, y;
+       const struct font_desc *font;
+       u16 prev, curr;
+       bool blank, render_all = false;
+       struct drm_clip_rect clip = {
+               .x1 = ~0,
+               .y1 = ~0,
+               .x2 = 0,
+               .y2 = 0,
+       };
+       char *str;
+       int ret;
+
+       if (!vtcon->display)
+               return;
+
+       mutex_lock(&drm_vtcon_vc->lock);
+
+       vc_cols = drm_vtcon_vc->cols;
+       vc_rows = drm_vtcon_vc->rows;
+       font = drm_vtcon_vc->font;
+       blank = drm_vtcon_vc->blank;
+
+       if (vtcon->buf_len != drm_vtcon_vc->buf_len) {
+               ret = drm_vtcon_resize_buf(vtcon, drm_vtcon_vc->buf_len);
+               if (ret) {
+                       mutex_unlock(&drm_vtcon_vc->lock);
+                       return;
+               }
+               render_all = true;
+       }
+
+       vtcon->buf_idx = !vtcon->buf_idx;
+       memcpy(vtcon->text_buf[vtcon->buf_idx], drm_vtcon_vc->text_buf,
+              vc_cols * vc_rows * sizeof(u16));
+
+       mutex_unlock(&drm_vtcon_vc->lock);
+
+       if (vtcon->cols != vc_cols || vtcon->rows != vc_rows) {
+               ret = drm_vtcon_modeset(vtcon, vc_cols, vc_rows, font);
+               if (ret)
+                       return;
+               render_all = true;
+       } else if (vtcon->blank != blank) {
+               drm_vtcon_blank(vtcon, blank);
+       }
+
+       str = kmalloc(vc_cols + 1, GFP_KERNEL);
+       if (!str)
+               return;
+
+       for (row = 0; row < vc_rows; row++) {
+               for (col = 0; col < vc_cols; col++) {
+                       prev = vtcon->text_buf[!vtcon->buf_idx][col + (row * 
vc_cols)];
+                       curr = vtcon->text_buf[vtcon->buf_idx][col + (row * 
vc_cols)];
+
+                       if (render_all || prev != curr) {
+                               str[col] = curr;
+                               x = col * font->width;
+                               y = row * font->height;
+
+                               clip.x1 = min_t(u32, clip.x1, x);
+                               clip.y1 = min_t(u32, clip.y1, y);
+                               clip.x2 = max_t(u32, clip.x2, x + font->width);
+                               clip.y2 = max_t(u32, clip.y2, y + font->height);
+
+                               drm_vtcon_render_char(vtcon, x, y, curr, font);
+                       } else {
+                               str[col] = ' ';
+                       }
+               }
+               str[vc_cols] = '\0';
+//             pr_info("%02u|%s\n", row, str);
+       }
+
+       kfree(str);
+
+       if (clip.x1 < clip.x2)
+               drm_client_framebuffer_flush(vtcon->buffer, &clip);
+}
+
+static DECLARE_WORK(drm_vtcon_work, drm_vtcon_work_fn);
+
+static const char *drm_vtcon_con_startup(void)
+{
+       return "drm-vt";
+}
+
+static void drm_vtcon_con_init(struct vc_data *vc, int init)
+{
+       drm_vtcon_debug(vc, "(init=%d) drm_vtcon_vc=%p\n", init, drm_vtcon_vc);
+
+       vc->vc_can_do_color = 1;
+
+       if (init) {
+               vc->vc_cols = drm_vtcon_vc->cols;
+               vc->vc_rows = drm_vtcon_vc->rows;
+       } else {
+               vc_resize(vc, drm_vtcon_vc->cols, drm_vtcon_vc->rows);
+       }
+}
+
+static void drm_vtcon_con_deinit(struct vc_data *vc)
+{
+       drm_vtcon_debug(vc, "\n");
+}
+
+static void drm_vtcon_con_putcs(struct vc_data *vc, const unsigned short *s,
+                               int count, int y, int x)
+{
+       u16 *dest;
+
+       dest = &drm_vtcon_vc->text_buf[x + (y * drm_vtcon_vc->cols)];
+
+       for (; count > 0; count--)
+               scr_writew(scr_readw(s++), dest++);
+
+       schedule_work(&drm_vtcon_work);
+}
+
+static void drm_vtcon_con_putc(struct vc_data *vc, int ch, int y, int x)
+{
+       unsigned short chr;
+
+       scr_writew(ch, &chr);
+       drm_vtcon_con_putcs(vc, &chr, 1, y, x);
+}
+
+/* TODO: How do I actually test this? */
+static void drm_vtcon_con_clear(struct vc_data *vc, int y, int x,
+                               int height, int width)
+{
+       drm_vtcon_debug(vc, "\n");
+
+       scr_memcpyw(drm_vtcon_vc->text_buf, (unsigned short *)vc->vc_pos,
+                   vc->vc_cols * vc->vc_rows);
+       schedule_work(&drm_vtcon_work);
+}
+
+static int drm_vtcon_con_switch(struct vc_data *vc)
+{
+       drm_vtcon_debug(vc, "%ux%u\n", vc->vc_cols, vc->vc_rows);
+
+       mutex_lock(&drm_vtcon_vc->lock);
+       drm_vtcon_vc->cols = vc->vc_cols;
+       drm_vtcon_vc->rows = vc->vc_rows;
+       mutex_unlock(&drm_vtcon_vc->lock);
+
+       return 1; /* redraw */
+}
+
+static int drm_vtcon_con_resize(struct vc_data *vc, unsigned int width,
+                               unsigned int height, unsigned int user)
+{
+       int ret = 0;
+
+       drm_vtcon_debug(vc, "width=%u, height=%u, user=%u\n", width, height, 
user);
+
+       mutex_lock(&drm_vtcon_vc->lock);
+
+       if (width > drm_vtcon_vc->max_cols || height > drm_vtcon_vc->max_rows)
+               ret = -EINVAL;
+
+       mutex_unlock(&drm_vtcon_vc->lock);
+
+       drm_vtcon_debug(vc, "ret=%d\n", ret);
+
+       return ret;
+}
+
+static void drm_vtcon_con_set_palette(struct vc_data *vc,
+                                     const unsigned char *table)
+{
+       drm_vtcon_debug(vc, "\n");
+}
+
+static int drm_vtcon_con_blank(struct vc_data *vc, int blank, int mode_switch)
+{
+       drm_vtcon_debug(vc, "(blank=%d, mode_switch=%d)\n", blank, mode_switch);
+
+       mutex_lock(&drm_vtcon_vc->lock);
+       drm_vtcon_vc->blank = blank;
+       mutex_unlock(&drm_vtcon_vc->lock);
+
+       schedule_work(&drm_vtcon_work);
+
+       return 0;
+}
+
+static void drm_vtcon_con_scrolldelta(struct vc_data *vc, int lines)
+{
+       drm_vtcon_debug(vc, "(lines=%d)\n", lines);
+}
+
+static void drm_vtcon_con_cursor_draw(bool show)
+{
+       static unsigned short set_chr, saved_chr;
+       static int prev_x, prev_y;
+       struct vc_data *vc = vc_cons[fg_console].d;
+       unsigned short *pos;
+
+       if (saved_chr) {
+               pos = &drm_vtcon_vc->text_buf[prev_x + (prev_y * 
drm_vtcon_vc->cols)];
+               if (*pos == set_chr)
+                       *pos = saved_chr;
+               saved_chr = 0;
+       }
+
+       if (show) {
+               pos = &drm_vtcon_vc->text_buf[vc->vc_x + (vc->vc_y * 
drm_vtcon_vc->cols)];
+               *pos = scr_readw((u16 *)vc->vc_pos);
+               saved_chr = *pos;
+               set_chr = *pos & 0xff00;
+               set_chr |= '_';
+               *pos = set_chr;
+               prev_x = vc->vc_x;
+               prev_y = vc->vc_y;
+       }
+
+       schedule_work(&drm_vtcon_work);
+}
+
+static void drm_vtcon_con_cursor_timer_handler(struct timer_list *t)
+{
+       static bool show;
+
+       show = !show;
+       drm_vtcon_con_cursor_draw(show);
+       mod_timer(t, jiffies + drm_vtcon_vc->cursor_blink_jiffies);
+}
+
+static DEFINE_TIMER(drm_vtcon_con_cursor_timer, 
drm_vtcon_con_cursor_timer_handler);
+
+static void drm_vtcon_con_cursor(struct vc_data *vc, int mode)
+{
+       //drm_vtcon_debug(vc, "(mode=%d)\n", mode);
+
+       switch (mode) {
+       case CM_ERASE:
+               drm_vtcon_con_cursor_draw(false);
+               del_timer_sync(&drm_vtcon_con_cursor_timer);
+               break;
+       case CM_MOVE:
+       case CM_DRAW:
+               drm_vtcon_vc->cursor_blink_jiffies = 
msecs_to_jiffies(vc->vc_cur_blink_ms);
+               mod_timer(&drm_vtcon_con_cursor_timer,
+                         jiffies + drm_vtcon_vc->cursor_blink_jiffies);
+               break;
+       }
+}
+
+static bool drm_vtcon_con_scroll(struct vc_data *vc, unsigned int top,
+                                unsigned int bottom, enum con_scroll dir,
+                                unsigned int lines)
+{
+       size_t count;
+
+       switch (dir) {
+       case SM_UP:
+               count = vc->vc_cols * (vc->vc_rows - lines);
+               memmove(drm_vtcon_vc->text_buf, drm_vtcon_vc->text_buf + 
vc->vc_cols, count * sizeof(u16));
+               memset(drm_vtcon_vc->text_buf + count, 0, vc->vc_cols * lines * 
sizeof(u16));
+               break;
+       case SM_DOWN:
+               drm_vtcon_debug(vc, "TODO\n");
+               break;
+       }
+
+       return false;
+}
+
+static const struct consw drm_vtcon_consw = {
+       /* owner is not set, it blocks module unload */
+       .con_startup            = drm_vtcon_con_startup,
+       .con_init               = drm_vtcon_con_init,
+       .con_deinit             = drm_vtcon_con_deinit,
+       .con_clear              = drm_vtcon_con_clear,
+       .con_putc               = drm_vtcon_con_putc,
+       .con_putcs              = drm_vtcon_con_putcs,
+       .con_cursor             = drm_vtcon_con_cursor,
+       .con_scroll             = drm_vtcon_con_scroll,
+       .con_switch             = drm_vtcon_con_switch,
+       .con_blank              = drm_vtcon_con_blank,
+       .con_resize             = drm_vtcon_con_resize,
+       .con_set_palette        = drm_vtcon_con_set_palette,
+       .con_scrolldelta        = drm_vtcon_con_scrolldelta,
+};
+
+static int drm_vtcon_vc_set_max(struct drm_display_mode *mode)
+{
+       u16 *new_buf = NULL, *old_buf = NULL;
+       const struct font_desc *font;
+       unsigned int cols, rows, i;
+       size_t buf_len;
+
+       /* only 8 bit wide and 8 or 16-bit high */
+       font = get_default_font(mode->hdisplay, mode->vdisplay,
+                               BIT(8 - 1), BIT(8 - 1) | BIT(16 - 1));
+       if (!font)
+               return -ENODEV;
+
+       pr_info("%s: font: %s\n", __func__, font->name);
+
+       cols = mode->hdisplay / font->width;
+       rows = mode->vdisplay / font->height;
+
+       pr_info("%s: ASKED: cols=%u, rows=%u\n", __func__, cols, rows);
+
+       if (drm_vtcon_vc->max_cols == cols && drm_vtcon_vc->max_rows == rows &&
+           drm_vtcon_vc->font == font)
+               return 0;
+
+       buf_len = cols * rows * sizeof(u16);
+       if (buf_len > drm_vtcon_vc->buf_len) {
+               new_buf = kzalloc(buf_len, GFP_KERNEL);
+               if (!new_buf)
+                       return -ENOMEM;
+       }
+
+       mutex_lock(&drm_vtcon_vc->lock);
+
+       if (new_buf) {
+               DRM_INFO("Allocated new buf: buf_len=%zu\n", buf_len);
+               old_buf = drm_vtcon_vc->text_buf;
+               drm_vtcon_vc->text_buf = new_buf;
+               drm_vtcon_vc->buf_len = buf_len;
+       }
+
+       drm_vtcon_vc->max_cols = cols;
+       drm_vtcon_vc->max_rows = rows;
+       drm_vtcon_vc->font = font;
+
+       mutex_unlock(&drm_vtcon_vc->lock);
+
+       pr_info("%s: max_cols=%u, max_rows=%u\n", __func__, 
drm_vtcon_vc->max_cols,
+               drm_vtcon_vc->max_rows);
+
+       console_lock();
+       for (i = 0; i < 2; i++)
+               vc_resize(vc_cons[i].d, drm_vtcon_vc->max_cols,
+                         drm_vtcon_vc->max_rows);
+       console_unlock();
+
+       kfree(old_buf);
+
+       return 0;
+}
+
+static int drm_vtcon_setup_dev(struct drm_vtcon *vtcon)
+{
+       struct drm_client_dev *client = vtcon->client;
+       struct drm_client_display *display;
+       struct drm_display_mode *mode;
+       int ret;
+
+       display = drm_client_find_display(client->dev, 0, 0);
+       if (IS_ERR(display))
+               return PTR_ERR(display);
+       if (!display)
+               return -ENOENT;
+
+       mode = display->mode;
+       if (!mode) {
+               ret = -EINVAL;
+               goto err_free_display;
+       }
+
+       ret = drm_vtcon_vc_set_max(mode);
+       if (ret)
+               goto err_free_display;
+
+       pr_info("%s: cols=%u, rows=%u\n", __func__, drm_vtcon_vc->cols, 
drm_vtcon_vc->rows);
+
+       vtcon->display = display;
+
+       schedule_work(&drm_vtcon_work);
+
+       return 0;
+
+err_free_display:
+       drm_client_display_free(display);
+
+       return ret;
+}
+
+static int drm_vtcon_client_hotplug(struct drm_client_dev *client)
+{
+       struct drm_vtcon *vtcon = client->private;
+       int ret = 0;
+
+       if (!vtcon->display)
+               ret = drm_vtcon_setup_dev(vtcon);
+
+       return ret;
+}
+
+static struct drm_client_dev *vtcon_client;
+static DEFINE_MUTEX(vtcon_client_lock);
+
+static int drm_vtcon_client_remove(struct drm_client_dev *client)
+{
+       struct drm_vtcon *vtcon = client->private;
+       struct drm_client_display *display = vtcon->display;
+
+       mutex_lock(&vtcon_client_lock);
+
+       vtcon_client = NULL;
+
+       if (!display)
+               goto out_unlock;
+
+       vtcon->display = NULL;
+       flush_work(&drm_vtcon_work);
+       kfree(vtcon->text_buf[0]);
+       kfree(vtcon->text_buf[1]);
+       drm_client_framebuffer_delete(vtcon->buffer);
+       vtcon->buffer = NULL;
+       drm_client_display_free(display);
+
+out_unlock:
+       mutex_unlock(&vtcon_client_lock);
+
+       return 0;
+}
+
+static const struct drm_client_funcs drm_vtcon_client_funcs = {
+       .name           = "vtcon",
+       .remove         = drm_vtcon_client_remove,
+       .hotplug        = drm_vtcon_client_hotplug,
+};
+
+static struct drm_client_dev *drm_vtcon_client_new(unsigned int dev_id)
+{
+       struct drm_vtcon *vtcon = vtcon_instance;
+       struct drm_client_dev *client;
+
+       if (vtcon->client)
+               return ERR_PTR(-EBUSY);
+
+       client = drm_client_new_from_id(dev_id, &drm_vtcon_client_funcs);
+       if (IS_ERR(client))
+               return client;
+
+       vtcon->client = client;
+       client->private = vtcon;
+
+       /*
+        * vc4 isn't done with it's setup when drm_dev_register() is called.
+        * It should have shouldn't it?
+        * So to keep it from crashing defer setup to hotplug...
+        */
+       if (client->dev->mode_config.max_width)
+               drm_vtcon_client_hotplug(client);
+
+       return 0;
+}
+
+static void drm_vtcon_teardown(void)
+{
+       struct drm_vtcon *vtcon = vtcon_instance;
+
+       if (!drm_vtcon_vc)
+               return;
+
+       kfree(drm_vtcon_vc->text_buf);
+       mutex_destroy(&drm_vtcon_vc->lock);
+       kfree(drm_vtcon_vc);
+       kfree(vtcon);
+}
+
+static int __init drm_vtcon_setup(void)
+{
+       struct drm_vtcon *vtcon;
+
+       drm_vtcon_vc = kzalloc(sizeof(*drm_vtcon_vc), GFP_KERNEL);
+       vtcon = kzalloc(sizeof(*vtcon), GFP_KERNEL);
+       if (!drm_vtcon_vc || !vtcon)
+               goto err_free;
+
+       drm_vtcon_vc->max_cols = 80;
+       drm_vtcon_vc->max_rows = 25;
+       drm_vtcon_vc->cols = drm_vtcon_vc->max_cols;
+       drm_vtcon_vc->rows = drm_vtcon_vc->max_rows;
+       pr_info("%s: cols=%u, rows=%u\n", __func__, drm_vtcon_vc->cols, 
drm_vtcon_vc->rows);
+
+       drm_vtcon_vc->buf_len = drm_vtcon_vc->max_cols * drm_vtcon_vc->max_rows 
* sizeof(u16);
+       drm_vtcon_vc->text_buf = kzalloc(drm_vtcon_vc->buf_len, GFP_KERNEL);
+       if (!drm_vtcon_vc->text_buf)
+               goto err_free;
+
+       mutex_init(&drm_vtcon_vc->lock);
+       vtcon_instance = vtcon;
+
+       return 0;
+
+err_free:
+       kfree(drm_vtcon_vc);
+       kfree(vtcon);
+
+       return -ENOMEM;
+}
+
+static int __init drm_vtcon_module_init(void)
+{
+       int ret;
+
+       if (drm_vtcon_dev_id < 0)
+               return 0;
+
+       ret = drm_vtcon_setup();
+       if (ret)
+               return ret;
+
+       vtcon_client = drm_vtcon_client_new(drm_vtcon_dev_id);
+       if (IS_ERR(vtcon_client)) {
+               ret = PTR_ERR(vtcon_client);
+               drm_vtcon_dev_id = ret;
+               goto err_free;
+       }
+
+       console_lock();
+       ret = do_take_over_console(&drm_vtcon_consw, 0, 1, 0);
+       console_unlock();
+       if (ret)
+               goto err_remove;
+
+       return 0;
+
+err_remove:
+       drm_client_remove(vtcon_client);
+err_free:
+       drm_vtcon_teardown();
+
+       return ret;
+}
+module_init(drm_vtcon_module_init);
+
+static void __exit drm_vtcon_module_exit(void)
+{
+       if (drm_vtcon_dev_id < 0)
+               return;
+
+       mutex_lock(&vtcon_client_lock);
+       drm_client_remove(vtcon_client);
+       mutex_unlock(&vtcon_client_lock);
+
+       console_lock();
+       do_unbind_con_driver(&drm_vtcon_consw, 0, 1, 0);
+       do_unregister_con_driver(&drm_vtcon_consw);
+       console_unlock();
+
+       del_timer_sync(&drm_vtcon_con_cursor_timer);
+       flush_work(&drm_vtcon_work);
+       drm_vtcon_teardown();
+}
+module_exit(drm_vtcon_module_exit);
+
+MODULE_LICENSE("GPL");
-- 
2.15.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to