1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12/*
13 * Implementation of an 80x25 EGA text mode. All the functions are named with 'serial' since
14 * that is what platsupport expects. This was a quick hack for a project and includes lots of
15 * logic that is rather inspecific to the underlying frame buffer. If someone adds another text
16 * mode frambuffer (or a linear frame buffer with a font renderer) then this should be abstracted.
17 * ideally some kind of virtual terminal code could be ported over to properly deal with escape characters
18 * and command codes for moving cursors etc around. But this is a basic hack for 'The machine I
19 * want to test on has a screen and no serial port'
20 */
21
22#include <autoconf.h>
23#include <platsupport/gen_config.h>
24#include <assert.h>
25#include <string.h>
26#include <platsupport/plat/serial.h>
27#include "../../chardev.h"
28#include <string.h>
29
30/* Assumptions on the graphics mode and frame buffer location */
31#define EGA_TEXT_FB_BASE 0xB8000
32#define MODE_WIDTH 80
33#define MODE_HEIGHT 25
34
35/* How many lines to scroll by */
36#define SCROLL_LINES 1
37
38/* Hacky global state */
39static volatile short *base_ptr = NULL;
40static int cursor_x = 0;
41static int cursor_y = 0;
42
43static void
44scroll(void)
45{
46    /* number of chars we are dropping when we do the scroll */
47    int clear_chars = SCROLL_LINES * MODE_WIDTH;
48    /* number of chars we need to move to perform the scroll. This all the lines
49     * minus however many we drop */
50    int scroll_chars = MODE_WIDTH * MODE_HEIGHT - clear_chars;
51    /* copy the lines up. we skip the same number of characters that we will clear, and move the
52     * rest to the top. cannot use memcpy as the regions almost certainly overlap */
53    memmove((void*)base_ptr, (void*)&base_ptr[clear_chars], scroll_chars * sizeof(*base_ptr));
54    /* now zero out the bottom lines that we got rid of */
55    memset((void*)&base_ptr[scroll_chars], 0, clear_chars * sizeof(*base_ptr));
56    /* move the virtual cursor up */
57    cursor_y -= SCROLL_LINES;
58}
59
60static int
61text_ega_getchar(ps_chardevice_t* d UNUSED)
62{
63    assert(!"EGA framebuffer does not implement getchar");
64    return 0;
65}
66
67static int
68text_ega_putchar(ps_chardevice_t* d, int c)
69{
70    /* emulate various control characters */
71    if (c == '\t') {
72        text_ega_putchar(d, ' ');
73        while (cursor_x % 4 != 0) {
74            text_ega_putchar(d, ' ');
75        }
76    } else if (c == '\n') {
77        cursor_y ++;
78        /* assume a \r with a \n */
79        cursor_x = 0;
80    } else if (c == '\r') {
81        cursor_x = 0;
82    } else {
83        /* 7<<8 constructs a nice neutral grey color. */
84        base_ptr[cursor_y * MODE_WIDTH + cursor_x] = ((char)c) | (7 << 8);
85        cursor_x++;
86    }
87    if (cursor_x >= MODE_WIDTH) {
88        cursor_x = 0;
89        cursor_y++;
90    }
91    while (cursor_y >= MODE_HEIGHT) {
92        scroll();
93    }
94    return 0;
95}
96
97static ssize_t
98text_ega_write(ps_chardevice_t* d, const void* vdata, size_t count, chardev_callback_t rcb UNUSED, void* token UNUSED)
99{
100    const char* data = (const char*)vdata;
101    int i;
102    for (i = 0; i < count; i++) {
103        if (text_ega_putchar(d, *data++) < 0) {
104            return i;
105        }
106    }
107    return count;
108}
109
110static ssize_t
111text_ega_read(ps_chardevice_t* d, void* vdata, size_t count, chardev_callback_t rcb UNUSED, void* token UNUSED)
112{
113    char* data;
114    int ret;
115    int i;
116    data = (char*)vdata;
117    for (i = 0; i < count; i++) {
118        ret = text_ega_getchar(d);
119        if (ret != EOF) {
120            *data++ = ret;
121        } else {
122            return i;
123        }
124    }
125    return count;
126}
127
128static void
129text_ega_handle_irq(ps_chardevice_t* d)
130{
131    /* TODO */
132}
133
134int
135text_ega_init(const struct dev_defn* defn, const ps_io_ops_t* ops, ps_chardevice_t* dev)
136{
137    /* handle an insane case where the serial might get repeatedly initialized. and
138     * avoid clearing the entire screen if this is the case. This comes about due
139     * to implementing device 'sharing' by just giving every process the device */
140    int clear = !base_ptr;
141    memset(dev, 0, sizeof(*dev));
142    base_ptr = chardev_map(defn, ops);
143    assert(base_ptr);
144    /* clear the screen */
145    if (clear) {
146        memset((void*)base_ptr, 0, MODE_WIDTH * MODE_HEIGHT * sizeof(*base_ptr));
147        cursor_x = 0;
148        cursor_y = 0;
149    }
150
151    dev->id         = defn->id;
152    dev->vaddr      = (void*)base_ptr;
153    dev->read       = &text_ega_read;
154    dev->write      = &text_ega_write;
155    dev->handle_irq = &text_ega_handle_irq;
156    dev->irqs       = defn->irqs;
157    dev->ioops      = *ops;
158
159    return 0;
160}
161