1/*
2 * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7/* CAmkES provides a generated header that prototypes all the relevant
8 * generated symbols.
9 */
10#include <camkes.h>
11#include <camkes/io.h>
12
13#include <assert.h>
14#include <bga/bga.h>
15#include <ringbuffer/ringbuffer.h>
16#include <stdbool.h>
17#include <stdlib.h>
18#include <string.h>
19
20static ps_io_port_ops_t io_port_ops;
21
22/* The following code drives the Bochs Graphics Array (BGA) video device to
23 * display the various components of the multilevel terminal. Note that it only
24 * uses libsel4bga's simple interface (bga_set_pixel). In a real, performant
25 * system we would enable raw access to the frame buffer and memcpy directly
26 * into it or use banked mode. For more information about the BGA:
27 *  http://wiki.osdev.org/Bochs_VBE_Extensions
28 */
29
30/* Opaque pointer to the Bochs Graphics Array (VESA device) meta data. */
31static bga_p bga;
32
33/* Provided in the generated chars.c */
34extern char *to_pixels(char c);
35
36/* Pixel dimensions of a character's XBM representation. See chars.sh or
37 * chars.c.
38 */
39#define CHAR_WIDTH 14
40#define CHAR_HEIGHT 26
41
42/* Draw a single character on the screen at (x, y). */
43static void draw(unsigned int x, unsigned int y, char c)
44{
45    /* Get the character's XBM representation. */
46    char *pixels = to_pixels(c);
47    assert(pixels != NULL);
48
49    char white[] = { 255, 255, 255 };
50    char black[] = { 0, 0, 0 };
51
52    /* Slightly messy row calculation because the XBM format dictates that the
53     * row is padded to a byte boundary.
54     */
55    unsigned int row_width = CHAR_WIDTH;
56    if (row_width % 8 != 0) {
57        row_width += 8 - row_width % 8;
58    }
59
60    /* Write the representation out to the screen with white filled pixels and
61     * black empty pixels.
62     */
63    for (unsigned int i = 0; i < CHAR_HEIGHT; i++) {
64        for (unsigned int j = 0; j < CHAR_WIDTH; j++) {
65            char block = pixels[(j + i * row_width) / 8];
66            unsigned int offset = (j + i * row_width) % 8;
67            bga_set_pixel(bga, x + j, y + i,
68                          (block & (1 << offset)) ? white : black);
69        }
70    }
71}
72
73/* Draw a string on the screen starting at (x, y). */
74static void draw_string(unsigned int x, unsigned int y, const char *s)
75{
76    while (*s != '\0') {
77        draw(x, y, *s++);
78        x += CHAR_WIDTH;
79    }
80}
81
82/* Width and height of the domain windows. */
83#define WIDTH 312
84#define HEIGHT 300
85
86/* Draw coloured borders around each input's virtual framebuffer. */
87static void borders(void)
88{
89    char purple[] = { 210, 101, 141 };
90    char blue[] = { 197, 103, 0 };
91
92    /* The below is full of magic numbers, but they're all basically pixel
93     * tweaks to make sure lines and headings look semi-natural.
94     */
95
96    for (unsigned int i = 100; i < 100 + WIDTH; i++) {
97        for (unsigned int j = 100; j < 100 + 4 + CHAR_HEIGHT; j++) {
98            bga_set_pixel(bga, i, j, purple);
99        }
100        bga_set_pixel(bga, i, 100 + HEIGHT, purple);
101    }
102    for (unsigned int i = 100; i < 100 + HEIGHT; i++) {
103        bga_set_pixel(bga, 100, i, purple);
104        bga_set_pixel(bga, 100 + WIDTH, i, purple);
105    }
106    draw_string(100 + 100 + 2 * CHAR_WIDTH, 102, "Low");
107
108    for (unsigned int i = 200 + WIDTH; i < 200 + 2 * WIDTH; i++) {
109        for (unsigned int j = 100; j < 100 + 4 + CHAR_HEIGHT; j++) {
110            bga_set_pixel(bga, i, j, blue);
111        }
112        bga_set_pixel(bga, i, 100 + HEIGHT, blue);
113    }
114    for (unsigned int i = 100; i < 100 + HEIGHT; i++) {
115        bga_set_pixel(bga, 200 + WIDTH, i, blue);
116        bga_set_pixel(bga, 200 + 2 * WIDTH, i, blue);
117    }
118    draw_string(200 + WIDTH + 100 + 2 * CHAR_WIDTH, 102, "High");
119}
120
121/* Return true if we're capable of displaying this character. */
122static bool printable(char c)
123{
124    return (c >= 'A' && c <= 'Z') ||
125           (c >= 'a' && c <= 'z') ||
126           (c == ' ') ||
127           (c >= '0' && c <= '9');
128}
129
130/* Write a character to the low domain. */
131static void write_low(char c)
132{
133    if (!printable(c)) {
134        return;
135    }
136
137    /* Again, magic numbers are basically pixel alignments. */
138    static unsigned int x = 100 + 2;
139    static unsigned int y = 100 + 4 + CHAR_HEIGHT;
140    if (y >= 100 + HEIGHT - CHAR_HEIGHT) {
141        return;
142    }
143    draw(x, y, c);
144    x += CHAR_WIDTH;
145    if (x >= 100 + WIDTH - 3) {
146        x = 100 + 2;
147        y += CHAR_HEIGHT;
148    }
149}
150
151/* Write a character to the high domain. */
152static void write_high(char c)
153{
154    if (!printable(c)) {
155        return;
156    }
157    static unsigned int x = 200 + WIDTH + 2;
158    static unsigned int y = 100 + 4 + CHAR_HEIGHT;
159    if (y >= 100 + HEIGHT - CHAR_HEIGHT) {
160        return;
161    }
162    draw(x, y, c);
163    x += CHAR_WIDTH;
164    if (x >= 200 + 2 * WIDTH - 3) {
165        x = 200 + WIDTH + 2;
166        y += CHAR_HEIGHT;
167    }
168}
169
170/* Callbacks used below. */
171static void out16(uint16_t port, uint16_t value)
172{
173    ps_io_port_out(&io_port_ops, port, IOSIZE_16, value);
174}
175
176static uint16_t in16(uint16_t port)
177{
178    uint32_t result = 0;
179    int error = ps_io_port_in(&io_port_ops, port, IOSIZE_16, &result);
180    if (error) {
181        return 0;
182    }
183    return (uint16_t) result;
184}
185
186/* This function is invoked by the main CAmkES thread in this component. */
187int run(void)
188{
189    int error = camkes_io_port_ops(&io_port_ops);
190    assert(!error);
191
192    /* Use the dataport address */
193    void *bga_ptr = (void *)mock_hdmi;
194
195    bga = bga_init(bga_ptr, out16, in16);
196    bga_set_mode(bga, 1024, 768, 24); /* 1024x768 resolution at 24 BPP */
197
198    ringbuffer_t *low = rb_new((void *)low_input, sizeof(*low_input));
199    if (low == NULL) {
200        abort();
201    }
202
203    ringbuffer_t *high = rb_new((void *)high_input, sizeof(*high_input));
204    if (high == NULL) {
205        abort();
206    }
207
208    borders();
209
210    /* Check both inputs for data and pass it to the relevant framebuffer. */
211    while (true) {
212        char c;
213
214        if ((c = (char)rb_poll_byte(low)) != 0) {
215            write_low(c);
216        }
217
218        if ((c = (char)rb_poll_byte(high)) != 0) {
219            write_high(c);
220        }
221    }
222
223    return 0;
224}
225