1 // Copyright 2016 The Fuchsia Authors
2 // Copyright (c) 2008-2010, 2015 Travis Geiselbrecht
3 //
4 // Use of this source code is governed by a MIT-style
5 // license that can be found in the LICENSE file or at
6 // https://opensource.org/licenses/MIT
7 
8 /**
9  * @file
10  * @brief  Manage graphics console
11  *
12  * This file contains functions to provide stdout to the graphics console.
13  *
14  * @ingroup graphics
15  */
16 
17 #include <lib/gfxconsole.h>
18 
19 #include <assert.h>
20 #include <debug.h>
21 #include <dev/display.h>
22 #include <kernel/cmdline.h>
23 #include <lib/gfx.h>
24 #include <lib/io.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #define TEXT_COLOR 0xffffffff
29 #define BACK_COLOR 0xff000000
30 
31 #define CRASH_TEXT_COLOR 0xffffffff
32 #define CRASH_BACK_COLOR 0xffe000e0
33 
34 /** @addtogroup graphics
35  * @{
36  */
37 
38 /**
39  * @brief  Represent state of graphics console
40  */
41 static struct {
42     // main surface to draw on
43     gfx_surface* surface;
44     // underlying hw surface, if different from above
45     gfx_surface* hw_surface;
46 
47     // surface to do single line sub-region flushing with
48     gfx_surface line;
49     uint linestride;
50 
51     uint rows, columns;
52     uint extray; // extra pixels left over if the rows doesn't fit precisely
53 
54     uint x, y;
55 
56     uint32_t front_color;
57     uint32_t back_color;
58 } gfxconsole;
59 
draw_char(char c,const struct gfx_font * font)60 static void draw_char(char c, const struct gfx_font* font) {
61     gfx_putchar(gfxconsole.surface, font, c,
62                 gfxconsole.x * font->width, gfxconsole.y * font->height,
63                 gfxconsole.front_color, gfxconsole.back_color);
64 }
65 
gfxconsole_putpixel(unsigned x,unsigned y,unsigned color)66 void gfxconsole_putpixel(unsigned x, unsigned y, unsigned color) {
67     gfx_putpixel(gfxconsole.surface, x, y, color);
68 }
69 
70 static const struct gfx_font* font = &font_9x16;
71 
gfxconsole_putc(char c)72 static bool gfxconsole_putc(char c) {
73     static enum { NORMAL,
74                   ESCAPE } state = NORMAL;
75     static uint32_t p_num = 0;
76     bool inval = 0;
77 
78     if (state == NORMAL) {
79         switch (c) {
80         case '\r':
81             gfxconsole.x = 0;
82             break;
83         case '\n':
84             gfxconsole.y++;
85             inval = 1;
86             break;
87         case '\b':
88             // back up one character unless we're at the left side
89             if (gfxconsole.x > 0) {
90                 gfxconsole.x--;
91             }
92             break;
93         case '\t':
94             gfxconsole.x = ROUNDUP(gfxconsole.x + 1, 8);
95             break;
96         case 0x1b:
97             p_num = 0;
98             state = ESCAPE;
99             break;
100         default:
101             draw_char(c, font);
102             gfxconsole.x++;
103             break;
104         }
105     } else if (state == ESCAPE) {
106         if (c >= '0' && c <= '9') {
107             p_num = (p_num * 10) + (c - '0');
108         } else if (c == 'D') {
109             if (p_num <= gfxconsole.x)
110                 gfxconsole.x -= p_num;
111             state = NORMAL;
112         } else if (c == '[') {
113             // eat this character
114         } else {
115             draw_char(c, font);
116             gfxconsole.x++;
117             state = NORMAL;
118         }
119     }
120 
121     if (gfxconsole.x >= gfxconsole.columns) {
122         gfxconsole.x = 0;
123         gfxconsole.y++;
124         inval = 1;
125     }
126     if (gfxconsole.y >= gfxconsole.rows) {
127         // scroll up
128         gfx_copyrect(gfxconsole.surface, 0, font->height, gfxconsole.surface->width,
129                      gfxconsole.surface->height - font->height - gfxconsole.extray, 0, 0);
130         gfxconsole.y--;
131 
132         // clear the bottom line
133         gfx_fillrect(gfxconsole.surface, 0, gfxconsole.surface->height - font->height - gfxconsole.extray,
134                      gfxconsole.surface->width, font->height, gfxconsole.back_color);
135         gfx_flush(gfxconsole.surface);
136         inval = 1;
137     }
138     return inval;
139 }
140 
gfxconsole_print_callback(print_callback_t * cb,const char * str,size_t len)141 static void gfxconsole_print_callback(print_callback_t* cb, const char* str, size_t len) {
142     int refresh_full_screen = 0;
143     for (size_t i = 0; i < len; i++) {
144         if (str[i] == '\n')
145             gfxconsole_putc('\r');
146         refresh_full_screen |= gfxconsole_putc(str[i]);
147     }
148 
149     // blit from the software surface to the hardware
150     if (gfxconsole.surface != gfxconsole.hw_surface) {
151         if (refresh_full_screen) {
152             gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0);
153         } else {
154             // Only re-blit the active console line.
155             // Since blend only works in whole surfaces, configure a sub-surface
156             // to use as the blend source.
157             gfxconsole.line.ptr = ((uint8_t*)gfxconsole.surface->ptr) +
158                                   (gfxconsole.y * gfxconsole.linestride);
159             gfx_surface_blend(gfxconsole.hw_surface, &gfxconsole.line,
160                               0, gfxconsole.y * font->height);
161         }
162         gfx_flush(gfxconsole.hw_surface);
163     } else {
164         gfx_flush(gfxconsole.surface);
165     }
166 }
167 
168 static print_callback_t cb = {
169     .entry = {},
170     .print = gfxconsole_print_callback,
171     .context = NULL};
172 
gfxconsole_setup(gfx_surface * surface,gfx_surface * hw_surface)173 static void gfxconsole_setup(gfx_surface* surface, gfx_surface* hw_surface) {
174     const char* fname = cmdline_get("gfxconsole.font");
175     if (fname != NULL) {
176         if (!strcmp(fname, "18x32")) {
177             font = &font_18x32;
178         } else if (!strcmp(fname, "9x16")) {
179             font = &font_9x16;
180         }
181     }
182     // set up the surface
183     gfxconsole.surface = surface;
184     gfxconsole.hw_surface = hw_surface;
185 
186     // set up line-height sub-surface, for line-only invalidation
187     memcpy(&gfxconsole.line, surface, sizeof(*surface));
188     gfxconsole.line.height = font->height;
189     gfxconsole.linestride = surface->stride * surface->pixelsize * font->height;
190 
191     // calculate how many rows/columns we have
192     gfxconsole.rows = surface->height / font->height;
193     gfxconsole.columns = surface->width / font->width;
194     gfxconsole.extray = surface->height - (gfxconsole.rows * font->height);
195 
196     dprintf(SPEW, "gfxconsole: rows %u, columns %u, extray %u\n", gfxconsole.rows,
197             gfxconsole.columns, gfxconsole.extray);
198 }
199 
gfxconsole_clear(bool crash_console)200 static void gfxconsole_clear(bool crash_console) {
201     // start in the upper left
202     gfxconsole.x = 0;
203     gfxconsole.y = 0;
204 
205     if (crash_console) {
206         gfxconsole.front_color = CRASH_TEXT_COLOR;
207         gfxconsole.back_color = CRASH_BACK_COLOR;
208     } else {
209         gfxconsole.front_color = TEXT_COLOR;
210         gfxconsole.back_color = BACK_COLOR;
211     }
212 
213     // fill screen with back color
214     gfx_fillrect(gfxconsole.surface, 0, 0, gfxconsole.surface->width, gfxconsole.surface->height,
215                  gfxconsole.back_color);
216     gfx_flush(gfxconsole.surface);
217 }
218 
219 /**
220  * @brief  Initialize graphics console on given drawing surface.
221  *
222  * The graphics console subsystem is initialized, and registered as
223  * an output device for debug output.
224  */
gfxconsole_start(gfx_surface * surface,gfx_surface * hw_surface)225 void gfxconsole_start(gfx_surface* surface, gfx_surface* hw_surface) {
226     DEBUG_ASSERT(gfxconsole.surface == NULL);
227 
228     gfxconsole_setup(surface, hw_surface);
229     gfxconsole_clear(false);
230 
231     // register for debug callbacks
232     register_print_callback(&cb);
233 }
234 
235 static gfx_surface hw_surface;
236 static gfx_surface sw_surface;
237 static struct display_info dispinfo;
238 
gfxconsole_display_get_info(struct display_info * info)239 zx_status_t gfxconsole_display_get_info(struct display_info* info) {
240     if (gfxconsole.surface) {
241         memcpy(info, &dispinfo, sizeof(*info));
242         return 0;
243     } else {
244         return -1;
245     }
246 }
247 
248 /**
249  * @brief  Initialize graphics console and bind to a display
250  *
251  * If the display was previously initialized, first it is shut down and
252  * detached from the print callback.
253  *
254  * If the new display_info is NULL, nothing else is done, otherwise the
255  * display is initialized against the provided display_info.
256  *
257  * If raw_sw_fb is non-NULL it is a memory large enough to be a backing
258  * surface (stride * height * pixelsize) for the provided hardware display.
259  * This is used for very early framebuffer init before the heap is alive.
260  */
gfxconsole_bind_display(struct display_info * info,void * raw_sw_fb)261 void gfxconsole_bind_display(struct display_info* info, void* raw_sw_fb) {
262     static bool active = false;
263     bool same_as_before = false;
264     struct gfx_surface hw;
265 
266     if (active) {
267         // on re-init or detach, we need to unhook from print callbacks
268         active = false;
269         unregister_print_callback(&cb);
270     }
271     if (info == NULL) {
272         return;
273     }
274 
275     if (gfx_init_surface_from_display(&hw, info)) {
276         return;
277     }
278     if (info->flags & DISPLAY_FLAG_CRASH_FRAMEBUFFER) {
279         // "bluescreen" path. no allocations allowed
280         memcpy(&hw_surface, &hw, sizeof(hw));
281         gfxconsole_setup(&hw_surface, &hw_surface);
282         memcpy(&dispinfo, info, sizeof(*info));
283         gfxconsole_clear(true);
284         register_print_callback(&cb);
285         active = true;
286         return;
287     }
288     if ((hw.format == hw_surface.format) && (hw.width == hw_surface.width) &&
289         (hw.height == hw_surface.height) && (hw.stride == hw_surface.stride) &&
290         (hw.pixelsize == hw_surface.pixelsize)) {
291         // we are binding to a new hw surface with the same properties
292         // as the existing one
293         same_as_before = true;
294     } else {
295         // we cannot re-use the sw backing surface, so destroy it
296         if (sw_surface.ptr && (sw_surface.flags & GFX_FLAG_FREE_ON_DESTROY)) {
297             free(sw_surface.ptr);
298         }
299         memset(&sw_surface, 0, sizeof(sw_surface));
300     }
301     memcpy(&hw_surface, &hw, sizeof(hw));
302 
303     gfx_surface* s = &hw_surface;
304     if (info->flags & DISPLAY_FLAG_HW_FRAMEBUFFER) {
305         if (!same_as_before) {
306             // we can't re-use the existing sw_surface, create a new one
307             if (gfx_init_surface(&sw_surface, raw_sw_fb, hw_surface.width,
308                                  hw_surface.height, hw_surface.stride, hw_surface.format, 0)) {
309                 return;
310             }
311         }
312         s = &sw_surface;
313     } else {
314         // for non-hw surfaces we're not using a backing surface
315         // so we can't be the same as before and must fully init
316         same_as_before = false;
317     }
318 
319     gfxconsole_setup(s, &hw_surface);
320 
321     if (!same_as_before) {
322         // on first init, or different-backing-buffer re-init
323         // we clear and reset to x,y @ 0,0
324         gfxconsole_clear(false);
325     }
326 
327     memcpy(&dispinfo, info, sizeof(*info));
328     register_print_callback(&cb);
329     active = true;
330 }
331 
gfxconsole_flush()332 void gfxconsole_flush() {
333     if (gfxconsole.surface != gfxconsole.hw_surface) {
334         gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0);
335         gfx_flush(gfxconsole.hw_surface);
336     } else {
337         gfx_flush(gfxconsole.surface);
338     }
339 }
340