/src/barebox/drivers/video/fbconsole.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-only |
2 | | |
3 | | #define pr_fmt(fmt) "fbconsole: " fmt |
4 | | |
5 | | #include <common.h> |
6 | | #include <errno.h> |
7 | | #include <malloc.h> |
8 | | #include <getopt.h> |
9 | | #include <fb.h> |
10 | | #include <gui/image_renderer.h> |
11 | | #include <gui/graphic_utils.h> |
12 | | #include <linux/font.h> |
13 | | #include <linux/ctype.h> |
14 | | #include <charset.h> |
15 | | |
16 | | enum state_t { |
17 | | LIT, /* Literal input */ |
18 | | ESC, /* Start of escape sequence */ |
19 | | CSI, /* Reading arguments in "CSI Pn ;...*/ |
20 | | }; |
21 | | |
22 | | enum fbconsole_rotation { |
23 | | FBCONSOLE_ROTATE_0, |
24 | | FBCONSOLE_ROTATE_90, |
25 | | FBCONSOLE_ROTATE_180, |
26 | | FBCONSOLE_ROTATE_270, |
27 | | }; |
28 | | |
29 | | static const char * const rotation_names[] = { |
30 | | [FBCONSOLE_ROTATE_0] = "0", |
31 | | [FBCONSOLE_ROTATE_90] = "90", |
32 | | [FBCONSOLE_ROTATE_180] = "180", |
33 | | [FBCONSOLE_ROTATE_270] = "270", |
34 | | }; |
35 | | |
36 | | enum ansi_color { |
37 | | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, |
38 | | BRIGHT |
39 | | }; |
40 | | |
41 | 0 | #define DEFAULT_COLOR WHITE |
42 | 0 | #define DEFAULT_BGCOLOR BLACK |
43 | | |
44 | | struct fbc_screen_state { |
45 | | unsigned int x, y; /* cursor position */ |
46 | | |
47 | | int color; |
48 | | int bgcolor; |
49 | | |
50 | | u32 color_rgb; |
51 | | u32 bgcolor_rgb; |
52 | | |
53 | 0 | #define ANSI_FLAG_INVERT (1 << 0) |
54 | 0 | #define ANSI_FLAG_BRIGHT (1 << 1) |
55 | 0 | #define SGR_ATTRIBUTES (ANSI_FLAG_INVERT | ANSI_FLAG_BRIGHT) |
56 | 0 | #define HIDE_CURSOR (1 << 2) |
57 | | unsigned flags; |
58 | | |
59 | | bool pending_wrap; /* deferred wrap: cursor at last column */ |
60 | | }; |
61 | | |
62 | | struct fbc_priv { |
63 | | struct console_device cdev; |
64 | | struct fb_info *fb; |
65 | | struct { |
66 | | u32 top; |
67 | | u32 left; |
68 | | u32 bottom; |
69 | | u32 right; |
70 | | } margin; |
71 | | |
72 | | struct screen *sc; |
73 | | |
74 | | struct param_d *par_font; |
75 | | int par_font_val; |
76 | | |
77 | | const struct font_desc *font; |
78 | | |
79 | | unsigned int cols, rows; |
80 | | |
81 | | struct fbc_screen_state cur; |
82 | | struct fbc_screen_state saved; /* DEC cursor save (\e7) */ |
83 | | |
84 | | struct fbc_screen_state altscreen_saved; |
85 | | void *altscreen_buf; /* pixel snapshot while in alternate screen */ |
86 | | size_t altscreen_size; |
87 | | |
88 | | unsigned int rotation; |
89 | | enum state_t state; |
90 | | |
91 | | int csipos; |
92 | | u8 csi[256]; |
93 | | unsigned char csi_cmd; |
94 | | |
95 | | int active; |
96 | | int in_console; |
97 | | |
98 | | char utf8_buf[5]; /* UTF-8 stream decoder buffer */ |
99 | | }; |
100 | | |
101 | | static int fbc_getc(struct console_device *cdev) |
102 | 0 | { |
103 | 0 | return 0; |
104 | 0 | } |
105 | | |
106 | | static int fbc_tstc(struct console_device *cdev) |
107 | 0 | { |
108 | 0 | return 0; |
109 | 0 | } |
110 | | |
111 | | static int fbc_get_size(struct console_device *cdev, int *width, int *height) |
112 | 0 | { |
113 | 0 | struct fbc_priv *priv = container_of(cdev, struct fbc_priv, cdev); |
114 | |
|
115 | 0 | *width = priv->cols; |
116 | 0 | *height = priv->rows; |
117 | 0 | return 0; |
118 | 0 | } |
119 | | |
120 | | static void cls(struct fbc_priv *priv) |
121 | 0 | { |
122 | 0 | void *buf = gui_screen_render_buffer(priv->sc); |
123 | 0 | struct fb_info *fb = priv->fb; |
124 | 0 | int width = fb->xres - priv->margin.left - priv->margin.right; |
125 | 0 | int height = fb->yres - priv->margin.top - priv->margin.bottom; |
126 | 0 | void *adr; |
127 | |
|
128 | 0 | adr = buf + priv->fb->line_length * priv->margin.top; |
129 | |
|
130 | 0 | if (!priv->margin.left && !priv->margin.right) { |
131 | 0 | memset(adr, 0, priv->fb->line_length * height); |
132 | 0 | } else { |
133 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
134 | 0 | int y; |
135 | |
|
136 | 0 | for (y = 0; y < height; y++) { |
137 | 0 | memset(adr + priv->margin.left * bpp, 0, width * bpp); |
138 | 0 | adr += priv->fb->line_length; |
139 | 0 | } |
140 | 0 | } |
141 | 0 | gu_screen_blit_area(priv->sc, priv->margin.left, priv->margin.top, |
142 | 0 | width, height); |
143 | 0 | } |
144 | | |
145 | | struct rgb { |
146 | | u8 r, g, b; |
147 | | }; |
148 | | |
149 | | static struct rgb colors[] = { |
150 | | [BLACK] = { 0, 0, 0 }, |
151 | | [RED] = { 205, 0, 0 }, |
152 | | [GREEN] = { 0, 205, 0 }, |
153 | | [YELLOW] = { 205, 205, 0 }, |
154 | | [BLUE] = { 0, 0, 238 }, |
155 | | [MAGENTA] = { 205, 0, 205 }, |
156 | | [CYAN] = { 0, 205, 205 }, |
157 | | [WHITE] = { 205, 205, 205 }, |
158 | | [BRIGHT + BLACK] = { 127, 127, 127 }, |
159 | | [BRIGHT + RED] = { 255, 0, 0 }, |
160 | | [BRIGHT + GREEN] = { 0, 255, 0 }, |
161 | | [BRIGHT + YELLOW] = { 255, 255, 0 }, |
162 | | [BRIGHT + BLUE] = { 92, 92, 255 }, |
163 | | [BRIGHT + MAGENTA] = { 255, 0, 255 }, |
164 | | [BRIGHT + CYAN] = { 0, 255, 255 }, |
165 | | [BRIGHT + WHITE] = { 255, 255, 255 }, |
166 | | }; |
167 | | |
168 | | static void fb_blit_area(struct fbc_priv *priv, int x, int y) |
169 | 0 | { |
170 | 0 | int startx, starty, width, height, fw, fh; |
171 | 0 | int mx, my; |
172 | |
|
173 | 0 | mx = priv->margin.left; |
174 | 0 | my = priv->margin.top; |
175 | |
|
176 | 0 | fw = priv->font->width; |
177 | 0 | fh = priv->font->height; |
178 | |
|
179 | 0 | switch (priv->rotation) { |
180 | 0 | case FBCONSOLE_ROTATE_0: |
181 | 0 | startx = mx + x * fw; |
182 | 0 | starty = my + y * fh; |
183 | 0 | width = fw; |
184 | 0 | height = fh; |
185 | 0 | break; |
186 | 0 | case FBCONSOLE_ROTATE_90: |
187 | 0 | startx = mx + (priv->rows - y - 1) * fh; |
188 | 0 | starty = my + x * fw; |
189 | 0 | width = fh; |
190 | 0 | height = fw; |
191 | 0 | break; |
192 | 0 | case FBCONSOLE_ROTATE_180: |
193 | 0 | startx = mx + (priv->cols - x - 1) * fw; |
194 | 0 | starty = my + (priv->rows - y - 1) * fh; |
195 | 0 | width = fw; |
196 | 0 | height = fh; |
197 | 0 | break; |
198 | 0 | case FBCONSOLE_ROTATE_270: |
199 | 0 | startx = mx + y * fh; |
200 | 0 | starty = my + (priv->cols - x - 1) * fw; |
201 | 0 | width = fh; |
202 | 0 | height = fw; |
203 | 0 | break; |
204 | 0 | default: |
205 | 0 | return; |
206 | 0 | } |
207 | | |
208 | 0 | gu_screen_blit_area(priv->sc, startx, starty, width, height); |
209 | 0 | } |
210 | | |
211 | | static void drawchar(struct fbc_priv *priv, int x, int y, int c) |
212 | 0 | { |
213 | 0 | void *buf; |
214 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
215 | 0 | void *adr; |
216 | 0 | int i; |
217 | 0 | const uint8_t *inbuf; |
218 | 0 | int line_length; |
219 | 0 | int xstep; |
220 | 0 | int ystep; |
221 | 0 | int startx; |
222 | 0 | int starty; |
223 | |
|
224 | 0 | buf = gui_screen_render_buffer(priv->sc); |
225 | |
|
226 | 0 | i = find_font_index(priv->font, c); |
227 | 0 | inbuf = priv->font->data + i; |
228 | |
|
229 | 0 | line_length = priv->fb->line_length; |
230 | |
|
231 | 0 | adr = buf; |
232 | |
|
233 | 0 | switch (priv->rotation) { |
234 | 0 | case FBCONSOLE_ROTATE_0: |
235 | 0 | xstep = bpp; |
236 | 0 | ystep = line_length; |
237 | 0 | startx = x * priv->font->width; |
238 | 0 | starty = y * priv->font->height; |
239 | 0 | break; |
240 | 0 | case FBCONSOLE_ROTATE_90: |
241 | 0 | xstep = line_length; |
242 | 0 | ystep = -bpp; |
243 | 0 | startx = (priv->rows - y) * priv->font->height - 1; |
244 | 0 | starty = x * priv->font->width; |
245 | 0 | break; |
246 | 0 | case FBCONSOLE_ROTATE_180: |
247 | 0 | xstep = -bpp; |
248 | 0 | ystep = -line_length; |
249 | 0 | startx = (priv->cols - x) * priv->font->width - 1; |
250 | 0 | starty = (priv->rows - y) * priv->font->height - 1; |
251 | 0 | break; |
252 | 0 | case FBCONSOLE_ROTATE_270: |
253 | 0 | xstep = -line_length; |
254 | 0 | ystep = bpp; |
255 | 0 | startx = priv->font->height * y; |
256 | 0 | starty = (priv->cols - x) * priv->font->width - 1; |
257 | 0 | break; |
258 | 0 | default: |
259 | 0 | return; |
260 | 0 | } |
261 | | |
262 | 0 | adr += (priv->margin.left + startx) * bpp; |
263 | 0 | adr += (priv->margin.top + starty) * line_length; |
264 | |
|
265 | 0 | for (i = 0; i < priv->font->height; i++) { |
266 | 0 | uint8_t mask = 0x80; |
267 | 0 | int j; |
268 | |
|
269 | 0 | for (j = 0; j < priv->font->width; j++) { |
270 | 0 | if (!mask) { |
271 | 0 | inbuf++; |
272 | 0 | mask = 0x80; |
273 | 0 | } |
274 | |
|
275 | 0 | if (*inbuf & mask) |
276 | 0 | gu_set_pixel(priv->fb, adr + j * xstep, |
277 | 0 | priv->cur.color_rgb); |
278 | 0 | else |
279 | 0 | gu_set_pixel(priv->fb, adr + j * xstep, |
280 | 0 | priv->cur.bgcolor_rgb); |
281 | |
|
282 | 0 | mask >>= 1; |
283 | |
|
284 | 0 | } |
285 | |
|
286 | 0 | adr += ystep; |
287 | |
|
288 | 0 | inbuf++; |
289 | 0 | mask = 0x80; |
290 | 0 | } |
291 | |
|
292 | 0 | fb_blit_area(priv, x, y); |
293 | 0 | } |
294 | | |
295 | | static void clear_chars(struct fbc_priv *priv, |
296 | | int start_x, int start_y, int end_x, int end_y) |
297 | 0 | { |
298 | 0 | for (int y = start_y; y <= end_y; y++) { |
299 | 0 | int xs = (y == start_y) ? start_x : 0; |
300 | 0 | int xe = (y == end_y) ? end_x : (int)priv->cols - 1; |
301 | |
|
302 | 0 | for (int x = xs; x <= xe; x++) |
303 | 0 | drawchar(priv, x, y, ' '); |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | | static void video_invertchar(struct fbc_priv *priv, int x, int y) |
308 | 0 | { |
309 | 0 | int startx, starty, width, height, fw, fh; |
310 | 0 | void *buf; |
311 | |
|
312 | 0 | buf = gui_screen_render_buffer(priv->sc); |
313 | 0 | buf += priv->margin.top * priv->fb->line_length; |
314 | 0 | buf += priv->margin.left * (priv->fb->bits_per_pixel >> 3); |
315 | |
|
316 | 0 | fw = priv->font->width; |
317 | 0 | fh = priv->font->height; |
318 | |
|
319 | 0 | switch (priv->rotation) { |
320 | 0 | case FBCONSOLE_ROTATE_0: |
321 | 0 | startx = x * fw; |
322 | 0 | starty = y * fh; |
323 | 0 | width = fw; |
324 | 0 | height = fh; |
325 | 0 | break; |
326 | 0 | case FBCONSOLE_ROTATE_90: |
327 | 0 | startx = (priv->rows - y - 1) * fh; |
328 | 0 | starty = x * fw; |
329 | 0 | width = fh; |
330 | 0 | height = fw; |
331 | 0 | break; |
332 | 0 | case FBCONSOLE_ROTATE_180: |
333 | 0 | startx = (priv->cols - x - 1) * fw; |
334 | 0 | starty = (priv->rows - y - 1) * fh; |
335 | 0 | width = fw; |
336 | 0 | height = fh; |
337 | 0 | break; |
338 | 0 | case FBCONSOLE_ROTATE_270: |
339 | 0 | startx = y * fh; |
340 | 0 | starty = (priv->cols - x - 1) * fw; |
341 | 0 | width = fh; |
342 | 0 | height = fw; |
343 | 0 | break; |
344 | 0 | default: |
345 | 0 | return; |
346 | 0 | } |
347 | | |
348 | 0 | gu_invert_area(priv->fb, buf, startx, starty, width, height); |
349 | 0 | fb_blit_area(priv, x, y); |
350 | 0 | } |
351 | | |
352 | | static void toggle_cursor_visibility(struct fbc_priv *priv) |
353 | 0 | { |
354 | 0 | if (!(priv->cur.flags & HIDE_CURSOR)) |
355 | 0 | video_invertchar(priv, priv->cur.x, priv->cur.y); |
356 | 0 | } |
357 | | |
358 | | static void fb_scroll_up_0(struct fbc_priv *priv, void *adr, int width, int height) |
359 | 0 | { |
360 | 0 | u32 line_length = priv->fb->line_length; |
361 | 0 | int line_height = line_length * priv->font->height; |
362 | |
|
363 | 0 | if (!priv->margin.left && !priv->margin.right) { |
364 | 0 | memcpy(adr, adr + line_height, line_height * (priv->rows - 1)); |
365 | 0 | memset(adr + line_height * (priv->rows - 1), 0, line_height); |
366 | 0 | } else { |
367 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
368 | 0 | int y; |
369 | |
|
370 | 0 | for (y = 0; y < height - priv->font->height; y++) { |
371 | 0 | memcpy(adr, adr + line_height, width * bpp); |
372 | 0 | adr += line_length; |
373 | 0 | } |
374 | |
|
375 | 0 | for (y = height - priv->font->height; y < height; y++) { |
376 | 0 | memset(adr, 0, width * bpp); |
377 | 0 | adr += line_length; |
378 | 0 | } |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | static void fb_scroll_up_180(struct fbc_priv *priv, void *adr, int width, int height) |
383 | 0 | { |
384 | 0 | u32 line_length = priv->fb->line_length; |
385 | 0 | int line_height = line_length * priv->font->height; |
386 | |
|
387 | 0 | if (!priv->margin.left && !priv->margin.right) { |
388 | 0 | memmove(adr + line_height, adr, line_height * (priv->rows - 1)); |
389 | 0 | memset(adr, 0, line_height); |
390 | 0 | } else { |
391 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
392 | 0 | int y; |
393 | |
|
394 | 0 | adr += (height - 1) * line_length; |
395 | |
|
396 | 0 | for (y = height; y > priv->font->height; y--) { |
397 | 0 | memcpy(adr, adr - line_height, width * bpp); |
398 | 0 | adr -= line_length; |
399 | 0 | } |
400 | |
|
401 | 0 | for (y = 0; y < priv->font->height; y++) { |
402 | 0 | memset(adr, 0, width * bpp); |
403 | 0 | adr -= line_length; |
404 | 0 | } |
405 | 0 | } |
406 | 0 | } |
407 | | |
408 | | static void fb_scroll_up_90(struct fbc_priv *priv, void *adr, int width, int height) |
409 | 0 | { |
410 | 0 | u32 line_length = priv->fb->line_length; |
411 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
412 | 0 | int y; |
413 | |
|
414 | 0 | for (y = 0; y < priv->cols * priv->font->width; y++) { |
415 | 0 | memmove(adr + priv->font->height * bpp, |
416 | 0 | adr, |
417 | 0 | (priv->rows - 1) * priv->font->height * bpp); |
418 | 0 | memset(adr, 0, priv->font->height * bpp); |
419 | 0 | adr += line_length; |
420 | 0 | } |
421 | 0 | } |
422 | | |
423 | | static void fb_scroll_up_270(struct fbc_priv *priv, void *adr, int width, int height) |
424 | 0 | { |
425 | 0 | u32 line_length = priv->fb->line_length; |
426 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
427 | 0 | int y; |
428 | |
|
429 | 0 | for (y = 0; y < priv->cols * priv->font->width; y++) { |
430 | 0 | memmove(adr, |
431 | 0 | adr + priv->font->height * bpp, |
432 | 0 | (priv->rows - 1) * priv->font->height * bpp); |
433 | 0 | memset(adr + priv->font->height * bpp * (priv->rows - 1), |
434 | 0 | 0x0, |
435 | 0 | priv->font->height * bpp); |
436 | 0 | adr += line_length; |
437 | 0 | } |
438 | 0 | } |
439 | | |
440 | | static void fb_scroll_up(struct fbc_priv *priv) |
441 | 0 | { |
442 | 0 | int width = priv->fb->xres - priv->margin.left - priv->margin.right; |
443 | 0 | int height = priv->fb->yres - priv->margin.top - priv->margin.bottom; |
444 | 0 | int bpp = priv->fb->bits_per_pixel >> 3; |
445 | 0 | void *adr; |
446 | |
|
447 | 0 | adr = gui_screen_render_buffer(priv->sc); |
448 | 0 | adr += priv->margin.top * priv->fb->line_length; |
449 | 0 | adr += priv->margin.left * bpp; |
450 | |
|
451 | 0 | switch (priv->rotation) { |
452 | 0 | case FBCONSOLE_ROTATE_0: |
453 | 0 | fb_scroll_up_0(priv, adr, width, height); |
454 | 0 | break; |
455 | 0 | case FBCONSOLE_ROTATE_90: |
456 | 0 | fb_scroll_up_90(priv, adr, width, height); |
457 | 0 | break; |
458 | 0 | case FBCONSOLE_ROTATE_180: |
459 | 0 | fb_scroll_up_180(priv, adr, width, height); |
460 | 0 | break; |
461 | 0 | case FBCONSOLE_ROTATE_270: |
462 | 0 | fb_scroll_up_270(priv, adr, width, height); |
463 | 0 | break; |
464 | 0 | } |
465 | | |
466 | 0 | gu_screen_blit_area(priv->sc, priv->margin.left, priv->margin.top, |
467 | 0 | width, height); |
468 | 0 | } |
469 | | |
470 | | static void printchar(struct fbc_priv *priv, int c) |
471 | 0 | { |
472 | 0 | struct fbc_screen_state *cur = &priv->cur; |
473 | |
|
474 | 0 | toggle_cursor_visibility(priv); |
475 | |
|
476 | 0 | switch (c) { |
477 | 0 | case '\007': /* bell: ignore */ |
478 | 0 | break; |
479 | 0 | case '\b': |
480 | 0 | cur->pending_wrap = false; |
481 | 0 | if (cur->x > 0) { |
482 | 0 | cur->x--; |
483 | 0 | } else if (cur->y > 0) { |
484 | 0 | cur->x = priv->cols - 1; |
485 | 0 | cur->y--; |
486 | 0 | } |
487 | 0 | break; |
488 | 0 | case '\n': |
489 | 0 | case '\013': /* Vertical tab is the same as Line Feed */ |
490 | 0 | cur->pending_wrap = false; |
491 | 0 | cur->y++; |
492 | 0 | break; |
493 | | |
494 | 0 | case '\r': |
495 | 0 | cur->pending_wrap = false; |
496 | 0 | cur->x = 0; |
497 | 0 | break; |
498 | | |
499 | 0 | case '\t': |
500 | 0 | cur->pending_wrap = false; |
501 | 0 | cur->x = (cur->x + 8) & ~0x3; |
502 | 0 | break; |
503 | | |
504 | 0 | default: |
505 | | /* |
506 | | * VT100 deferred wrap: if the previous character landed at |
507 | | * the last column, perform the wrap NOW before drawing the |
508 | | * next character. This prevents a scroll from occurring |
509 | | * merely by writing to the bottom-right corner cell. |
510 | | */ |
511 | 0 | if (cur->pending_wrap) { |
512 | 0 | cur->pending_wrap = false; |
513 | 0 | cur->x = 0; |
514 | 0 | cur->y++; |
515 | |
|
516 | 0 | if (cur->y >= priv->rows) { |
517 | 0 | fb_scroll_up(priv); |
518 | 0 | cur->y = priv->rows - 1; |
519 | 0 | } |
520 | 0 | } |
521 | |
|
522 | 0 | drawchar(priv, cur->x, cur->y, c); |
523 | |
|
524 | 0 | cur->x++; |
525 | 0 | if (cur->x >= priv->cols) { |
526 | | /* Defer the wrap to the next printable character */ |
527 | 0 | cur->x = priv->cols - 1; |
528 | 0 | cur->pending_wrap = true; |
529 | 0 | } |
530 | 0 | break; |
531 | 0 | } |
532 | | |
533 | 0 | if (cur->y >= priv->rows) { |
534 | 0 | fb_scroll_up(priv); |
535 | 0 | cur->y = priv->rows - 1; |
536 | 0 | } |
537 | |
|
538 | 0 | toggle_cursor_visibility(priv); |
539 | 0 | } |
540 | | |
541 | | static void fbc_update_colors(struct fbc_priv *priv, int color, int bgcolor) |
542 | 0 | { |
543 | 0 | struct fbc_screen_state *cur = &priv->cur; |
544 | 0 | struct rgb *rgb; |
545 | |
|
546 | 0 | if (color >= 0) |
547 | 0 | cur->color = color; |
548 | 0 | if (bgcolor >= 0) |
549 | 0 | cur->bgcolor = bgcolor; |
550 | |
|
551 | 0 | if (cur->flags & ANSI_FLAG_INVERT) { |
552 | 0 | color = cur->bgcolor; |
553 | 0 | bgcolor = cur->color; |
554 | 0 | } else { |
555 | 0 | color = cur->color; |
556 | 0 | bgcolor = cur->bgcolor; |
557 | 0 | } |
558 | |
|
559 | 0 | if (cur->flags & ANSI_FLAG_BRIGHT) |
560 | 0 | color += BRIGHT; |
561 | |
|
562 | 0 | rgb = &colors[color]; |
563 | 0 | cur->color_rgb = gu_rgb_to_pixel(priv->fb, rgb->r, rgb->g, rgb->b, 0xff); |
564 | |
|
565 | 0 | rgb = &colors[bgcolor]; |
566 | 0 | cur->bgcolor_rgb = gu_rgb_to_pixel(priv->fb, rgb->r, rgb->g, rgb->b, 0x0); |
567 | 0 | } |
568 | | |
569 | | static void fbc_reset_colors(struct fbc_priv *priv) |
570 | 0 | { |
571 | 0 | fbc_update_colors(priv, DEFAULT_COLOR, DEFAULT_BGCOLOR); |
572 | 0 | } |
573 | | |
574 | | static void fbc_parse_colors(struct fbc_priv *priv) |
575 | 0 | { |
576 | 0 | struct fbc_screen_state *cur = &priv->cur; |
577 | 0 | int color = -1, bgcolor = -1; |
578 | 0 | int code; |
579 | 0 | char *str; |
580 | |
|
581 | 0 | str = priv->csi; |
582 | |
|
583 | 0 | while (1) { |
584 | 0 | code = simple_strtoul(str, &str, 10); |
585 | 0 | switch (code) { |
586 | 0 | case 0: |
587 | 0 | cur->flags &= ~SGR_ATTRIBUTES; |
588 | 0 | color = DEFAULT_COLOR; |
589 | 0 | bgcolor = DEFAULT_BGCOLOR; |
590 | 0 | break; |
591 | 0 | case 1: |
592 | 0 | cur->flags |= ANSI_FLAG_BRIGHT; |
593 | 0 | break; |
594 | 0 | case 7: |
595 | 0 | cur->flags |= ANSI_FLAG_INVERT; |
596 | 0 | break; |
597 | 0 | case 30 ... 37: |
598 | 0 | color = code - 30; |
599 | 0 | break; |
600 | 0 | case 39: |
601 | 0 | color = DEFAULT_COLOR; |
602 | 0 | break; |
603 | 0 | case 40 ... 47: |
604 | 0 | bgcolor = code - 40; |
605 | 0 | break; |
606 | 0 | case 49: |
607 | 0 | bgcolor = DEFAULT_BGCOLOR; |
608 | 0 | break; |
609 | 0 | case 90 ... 97: |
610 | 0 | cur->flags |= ANSI_FLAG_BRIGHT; |
611 | 0 | color = code - 90; |
612 | 0 | break; |
613 | 0 | } |
614 | | |
615 | 0 | if (*str != ';') |
616 | 0 | break; |
617 | 0 | str++; |
618 | 0 | } |
619 | | |
620 | 0 | fbc_update_colors(priv, color, bgcolor); |
621 | 0 | } |
622 | | |
623 | | static void fbc_set_cursor_row(struct fbc_priv *priv, int y) |
624 | 0 | { |
625 | 0 | priv->cur.y = clamp_t(int, y, 0, priv->rows - 1); |
626 | 0 | } |
627 | | |
628 | | static void fbc_set_cursor_col(struct fbc_priv *priv, unsigned int x) |
629 | 0 | { |
630 | 0 | priv->cur.x = clamp_t(int, x, 0, priv->cols - 1); |
631 | 0 | } |
632 | | |
633 | | static bool fbc_parse_csi(struct fbc_priv *priv) |
634 | 0 | { |
635 | 0 | char *end; |
636 | 0 | unsigned char last; |
637 | 0 | int pos; |
638 | |
|
639 | 0 | last = priv->csi[priv->csipos - 1]; |
640 | |
|
641 | 0 | switch (last) { |
642 | 0 | case 'm': |
643 | 0 | fbc_parse_colors(priv); |
644 | 0 | break; |
645 | 0 | case 'h': |
646 | | /* suffix for vt100 "[?25h" */ |
647 | 0 | if (priv->csi_cmd != '?') |
648 | 0 | break; |
649 | 0 | pos = simple_strtoul(priv->csi + 1, NULL, 10); |
650 | 0 | switch (pos) { |
651 | 0 | case 25: /* cursor visible */ |
652 | 0 | if (!(priv->cur.flags & HIDE_CURSOR)) |
653 | 0 | break; |
654 | 0 | priv->cur.flags &= ~HIDE_CURSOR; |
655 | | /* show cursor now */ |
656 | 0 | toggle_cursor_visibility(priv); |
657 | 0 | return true; |
658 | 0 | case 1049: /* alternate screen: save pixel buffer and state */ |
659 | 0 | if (!priv->altscreen_buf) { |
660 | 0 | void *src = gui_screen_render_buffer(priv->sc); |
661 | 0 | size_t sz = priv->fb->line_length * priv->fb->yres; |
662 | |
|
663 | 0 | priv->altscreen_buf = memdup(src, sz); |
664 | 0 | if (priv->altscreen_buf) { |
665 | 0 | priv->altscreen_size = sz; |
666 | 0 | priv->altscreen_saved = priv->cur; |
667 | 0 | } |
668 | 0 | priv->cur = (struct fbc_screen_state){}; |
669 | 0 | fbc_reset_colors(priv); |
670 | 0 | cls(priv); |
671 | 0 | } |
672 | 0 | return true; |
673 | 0 | } |
674 | 0 | break; |
675 | 0 | case 'l': |
676 | | /* suffix for vt100 "[?25l" */ |
677 | 0 | if (priv->csi_cmd != '?') |
678 | 0 | break; |
679 | 0 | pos = simple_strtoul(priv->csi + 1, NULL, 10); |
680 | 0 | switch (pos) { |
681 | 0 | case 25: /* cursor invisible */ |
682 | 0 | toggle_cursor_visibility(priv); |
683 | | /* hide cursor now */ |
684 | 0 | priv->cur.flags |= HIDE_CURSOR; |
685 | 0 | return true; |
686 | 0 | case 1049: /* alternate screen: restore pixel buffer and state */ |
687 | 0 | if (priv->altscreen_buf) { |
688 | 0 | void *dst = gui_screen_render_buffer(priv->sc); |
689 | 0 | size_t sz = priv->fb->line_length * priv->fb->yres; |
690 | |
|
691 | 0 | if (sz == priv->altscreen_size) { |
692 | 0 | memcpy(dst, priv->altscreen_buf, sz); |
693 | 0 | gu_screen_blit(priv->sc); |
694 | 0 | } else { |
695 | 0 | cls(priv); |
696 | 0 | } |
697 | 0 | free(priv->altscreen_buf); |
698 | 0 | priv->altscreen_buf = NULL; |
699 | 0 | priv->altscreen_size = 0; |
700 | 0 | priv->cur = priv->altscreen_saved; |
701 | 0 | } else { |
702 | 0 | priv->cur = (struct fbc_screen_state){}; |
703 | 0 | fbc_reset_colors(priv); |
704 | 0 | cls(priv); |
705 | 0 | } |
706 | 0 | return true; |
707 | 0 | } |
708 | 0 | break; |
709 | 0 | case 'J': |
710 | 0 | pos = simple_strtoul(priv->csi, &end, 10); |
711 | 0 | toggle_cursor_visibility(priv); |
712 | 0 | switch (pos) { |
713 | 0 | case 0: /* clear from cursor to end of screen */ |
714 | 0 | clear_chars(priv, priv->cur.x, priv->cur.y, |
715 | 0 | priv->cols - 1, priv->rows - 1); |
716 | 0 | break; |
717 | 0 | case 1: /* clear from start of screen to cursor */ |
718 | 0 | clear_chars(priv, 0, 0, priv->cur.x, priv->cur.y); |
719 | 0 | break; |
720 | 0 | case 2: /* clear entire screen */ |
721 | 0 | cls(priv); |
722 | 0 | break; |
723 | 0 | } |
724 | 0 | toggle_cursor_visibility(priv); |
725 | 0 | return true; |
726 | 0 | case 'H': |
727 | 0 | toggle_cursor_visibility(priv); |
728 | 0 | priv->cur.pending_wrap = false; |
729 | |
|
730 | 0 | pos = simple_strtoul(priv->csi, &end, 10); |
731 | 0 | fbc_set_cursor_row(priv, pos - 1); |
732 | |
|
733 | 0 | pos = simple_strtoul(end + 1, NULL, 10); |
734 | 0 | fbc_set_cursor_col(priv, pos - 1); |
735 | |
|
736 | 0 | toggle_cursor_visibility(priv); |
737 | 0 | return true; |
738 | 0 | case 'A' ... 'D': { |
739 | 0 | pos = simple_strtoul(priv->csi, &end, 10) ?: 1; |
740 | 0 | toggle_cursor_visibility(priv); |
741 | 0 | priv->cur.pending_wrap = false; |
742 | |
|
743 | 0 | switch (last) { |
744 | 0 | case 'A': /* cursor up */ |
745 | 0 | fbc_set_cursor_row(priv, priv->cur.y - pos); |
746 | 0 | break; |
747 | 0 | case 'B': /* cursor down */ |
748 | 0 | fbc_set_cursor_row(priv, priv->cur.y + pos); |
749 | 0 | break; |
750 | 0 | case 'C': /* cursor forward */ |
751 | 0 | fbc_set_cursor_col(priv, priv->cur.x + pos); |
752 | 0 | break; |
753 | 0 | case 'D': /* cursor back */ |
754 | 0 | fbc_set_cursor_col(priv, priv->cur.x - pos); |
755 | 0 | break; |
756 | 0 | } |
757 | | |
758 | 0 | toggle_cursor_visibility(priv); |
759 | 0 | return true; |
760 | 0 | } |
761 | 0 | case 'K': |
762 | 0 | pos = simple_strtoul(priv->csi, &end, 10); |
763 | 0 | toggle_cursor_visibility(priv); |
764 | 0 | switch (pos) { |
765 | 0 | case 0: |
766 | 0 | clear_chars(priv, priv->cur.x, priv->cur.y, |
767 | 0 | priv->cols - 1, priv->cur.y); |
768 | 0 | break; |
769 | 0 | case 1: |
770 | 0 | clear_chars(priv, 0, priv->cur.y, |
771 | 0 | priv->cur.x, priv->cur.y); |
772 | 0 | break; |
773 | 0 | case 2: |
774 | 0 | clear_chars(priv, 0, priv->cur.y, |
775 | 0 | priv->cols - 1, priv->cur.y); |
776 | 0 | break; |
777 | 0 | } |
778 | 0 | toggle_cursor_visibility(priv); |
779 | 0 | return true; |
780 | 0 | } |
781 | | |
782 | 0 | return false; |
783 | 0 | } |
784 | | |
785 | | static void fbc_putc(struct console_device *cdev, char c) |
786 | 0 | { |
787 | 0 | struct fbc_priv *priv = container_of(cdev, |
788 | 0 | struct fbc_priv, cdev); |
789 | 0 | struct fb_info *fb = priv->fb; |
790 | 0 | bool queue_flush = false; |
791 | |
|
792 | 0 | if (priv->in_console) |
793 | 0 | return; |
794 | 0 | priv->in_console = 1; |
795 | |
|
796 | 0 | switch (priv->state) { |
797 | 0 | case LIT: |
798 | 0 | switch (c) { |
799 | 0 | case '\033': |
800 | 0 | priv->utf8_buf[0] = '\0'; |
801 | 0 | priv->state = ESC; |
802 | 0 | break; |
803 | 0 | case '\0': |
804 | 0 | break; |
805 | 0 | default: { |
806 | 0 | int cp = c; |
807 | |
|
808 | 0 | if (IS_ENABLED(CONFIG_FRAMEBUFFER_CONSOLE_UTF8)) { |
809 | | /* All our fonts are CP437, so convert it |
810 | | * directly to that code page. |
811 | | */ |
812 | 0 | cp = utf8_to_cp437_stream(c, priv->utf8_buf); |
813 | 0 | if (!cp) |
814 | 0 | break; |
815 | 0 | } |
816 | | |
817 | 0 | printchar(priv, cp); |
818 | 0 | queue_flush = true; |
819 | 0 | break; |
820 | 0 | } |
821 | 0 | } |
822 | 0 | break; |
823 | 0 | case ESC: |
824 | 0 | switch (c) { |
825 | 0 | case '[': |
826 | 0 | priv->state = CSI; |
827 | 0 | priv->csipos = 0; |
828 | 0 | memset(priv->csi, 0, 6); |
829 | 0 | break; |
830 | 0 | case '7': /* DEC save cursor position */ |
831 | 0 | priv->saved = priv->cur; |
832 | 0 | priv->state = LIT; |
833 | 0 | break; |
834 | 0 | case '8': /* DEC restore cursor position */ |
835 | 0 | toggle_cursor_visibility(priv); |
836 | 0 | priv->cur = priv->saved; |
837 | 0 | toggle_cursor_visibility(priv); |
838 | 0 | priv->state = LIT; |
839 | 0 | queue_flush = true; |
840 | 0 | break; |
841 | 0 | default: |
842 | 0 | priv->state = LIT; |
843 | 0 | break; |
844 | 0 | } |
845 | 0 | break; |
846 | 0 | case CSI: |
847 | 0 | if (c == '\033') { |
848 | 0 | priv->state = ESC; |
849 | 0 | priv->csi_cmd = -1; |
850 | 0 | break; |
851 | 0 | } |
852 | | |
853 | 0 | priv->csi[priv->csipos++] = c; |
854 | 0 | if (priv->csipos == 255) { |
855 | 0 | priv->csipos = 0; |
856 | 0 | priv->state = LIT; |
857 | 0 | priv->csi_cmd = -1; |
858 | 0 | break; |
859 | 0 | } |
860 | | |
861 | 0 | switch (c) { |
862 | 0 | case '?': |
863 | | /* DEC private sequences use '?' as a parameter prefix byte. |
864 | | * Record '?' in csi_cmd and continue CSI parameter |
865 | | * accumulation for digits and the final byte that follows. |
866 | | */ |
867 | 0 | priv->csi_cmd = c; |
868 | 0 | break; |
869 | 0 | case '0' ... '9': |
870 | 0 | case ';': |
871 | 0 | case ':': |
872 | 0 | break; |
873 | 0 | default: |
874 | 0 | queue_flush = fbc_parse_csi(priv); |
875 | 0 | priv->state = LIT; |
876 | 0 | priv->csi_cmd = -1; |
877 | 0 | } |
878 | 0 | break; |
879 | 0 | } |
880 | 0 | priv->in_console = 0; |
881 | |
|
882 | 0 | if (queue_flush) |
883 | 0 | fb_flush(fb); |
884 | 0 | } |
885 | | |
886 | | static int setup_font(struct fbc_priv *priv) |
887 | 0 | { |
888 | 0 | const struct font_desc *font; |
889 | 0 | unsigned int height = priv->fb->yres - priv->margin.top - priv->margin.bottom; |
890 | 0 | unsigned int width = priv->fb->xres - priv->margin.left - priv->margin.right; |
891 | 0 | unsigned int newrows, newcols; |
892 | |
|
893 | 0 | font = find_font_enum(priv->par_font_val); |
894 | 0 | if (!font) { |
895 | 0 | return -ENOENT; |
896 | 0 | } |
897 | | |
898 | 0 | priv->font = font; |
899 | |
|
900 | 0 | switch (priv->rotation) { |
901 | 0 | case FBCONSOLE_ROTATE_0: |
902 | 0 | case FBCONSOLE_ROTATE_180: |
903 | 0 | newrows = height / priv->font->height; |
904 | 0 | newcols = width / priv->font->width; |
905 | 0 | break; |
906 | 0 | case FBCONSOLE_ROTATE_90: |
907 | 0 | case FBCONSOLE_ROTATE_270: |
908 | 0 | newrows = width / priv->font->height; |
909 | 0 | newcols = height / priv->font->width; |
910 | 0 | break; |
911 | 0 | default: |
912 | 0 | return -EINVAL; |
913 | 0 | } |
914 | | |
915 | 0 | if (priv->rows != newrows || priv->cols != newcols) { |
916 | 0 | priv->rows = newrows; |
917 | 0 | priv->cols = newcols; |
918 | 0 | priv->cur.x = priv->cur.y = 0; |
919 | 0 | priv->saved.x = priv->saved.y = 0; |
920 | 0 | } |
921 | |
|
922 | 0 | return 0; |
923 | 0 | } |
924 | | |
925 | | static int fbc_open(struct console_device *cdev, unsigned activate) |
926 | 0 | { |
927 | 0 | struct fbc_priv *priv = container_of(cdev, |
928 | 0 | struct fbc_priv, cdev); |
929 | 0 | struct fb_info *fb = priv->fb; |
930 | 0 | int ret; |
931 | |
|
932 | 0 | ret = setup_font(priv); |
933 | 0 | if (ret) |
934 | 0 | return ret; |
935 | | |
936 | 0 | priv->sc = fb_create_screen(fb); |
937 | 0 | if (IS_ERR(priv->sc)) |
938 | 0 | return PTR_ERR(priv->sc); |
939 | | |
940 | 0 | fb_enable(fb); |
941 | |
|
942 | 0 | priv->state = LIT; |
943 | |
|
944 | 0 | priv->active = true; |
945 | |
|
946 | 0 | if (activate) |
947 | 0 | dev_info(priv->cdev.dev, "framebuffer console %dx%d activated\n", |
948 | 0 | priv->cols, priv->rows); |
949 | |
|
950 | 0 | if (activate & CONSOLE_STDERR) |
951 | 0 | log_print(&priv->cdev, 0, GENMASK(LOGLEVEL, 0)); |
952 | |
|
953 | 0 | return 0; |
954 | 0 | } |
955 | | |
956 | | static int fbc_close(struct console_device *cdev) |
957 | 0 | { |
958 | 0 | struct fbc_priv *priv = container_of(cdev, |
959 | 0 | struct fbc_priv, cdev); |
960 | |
|
961 | 0 | if (priv->altscreen_buf) { |
962 | 0 | free(priv->altscreen_buf); |
963 | 0 | priv->altscreen_buf = NULL; |
964 | 0 | priv->altscreen_size = 0; |
965 | 0 | } |
966 | |
|
967 | 0 | if (priv->active) { |
968 | 0 | fb_close(priv->sc); |
969 | 0 | priv->active = false; |
970 | |
|
971 | 0 | return 0; |
972 | 0 | } |
973 | | |
974 | 0 | return -EINVAL; |
975 | 0 | } |
976 | | |
977 | | static int set_font(struct param_d *p, void *vpriv) |
978 | 0 | { |
979 | 0 | struct fbc_priv *priv = vpriv; |
980 | 0 | struct console_device *cdev = &priv->cdev; |
981 | |
|
982 | 0 | if (cdev->f_active & (CONSOLE_STDOUT | CONSOLE_STDERR)) { |
983 | 0 | cls(priv); |
984 | 0 | setup_font(priv); |
985 | 0 | } |
986 | |
|
987 | 0 | return 0; |
988 | 0 | } |
989 | | |
990 | | static int set_margin(struct param_d *p, void *vpriv) |
991 | 0 | { |
992 | 0 | struct fbc_priv *priv = vpriv; |
993 | 0 | struct console_device *cdev = &priv->cdev; |
994 | 0 | int ret; |
995 | |
|
996 | 0 | if (!priv->font) { |
997 | 0 | ret = setup_font(priv); |
998 | 0 | if (ret) |
999 | 0 | return ret; |
1000 | 0 | } |
1001 | | |
1002 | 0 | priv->margin.left = min(priv->margin.left, |
1003 | 0 | priv->fb->xres - priv->margin.right - priv->font->width); |
1004 | 0 | priv->margin.top = min(priv->margin.top, |
1005 | 0 | priv->fb->yres - priv->margin.bottom - priv->font->height); |
1006 | 0 | priv->margin.right = min(priv->margin.right, |
1007 | 0 | priv->fb->xres - priv->margin.left - priv->font->width); |
1008 | 0 | priv->margin.bottom = min(priv->margin.bottom, |
1009 | 0 | priv->fb->yres - priv->margin.top - priv->font->height); |
1010 | |
|
1011 | 0 | if (cdev->f_active & (CONSOLE_STDOUT | CONSOLE_STDERR)) { |
1012 | 0 | cls(priv); |
1013 | 0 | setup_font(priv); |
1014 | 0 | } |
1015 | |
|
1016 | 0 | return 0; |
1017 | 0 | } |
1018 | | |
1019 | | static int set_rotation(struct param_d *p, void *vpriv) |
1020 | 0 | { |
1021 | 0 | struct fbc_priv *priv = vpriv; |
1022 | |
|
1023 | 0 | cls(priv); |
1024 | 0 | priv->cur.x = priv->cur.y = 0; |
1025 | 0 | priv->saved.x = priv->saved.y = 0; |
1026 | 0 | setup_font(priv); |
1027 | |
|
1028 | 0 | return 0; |
1029 | 0 | } |
1030 | | |
1031 | | int register_fbconsole(struct fb_info *fb) |
1032 | 0 | { |
1033 | 0 | struct fbc_priv *priv; |
1034 | 0 | struct console_device *cdev; |
1035 | 0 | int ret; |
1036 | 0 | const char *fbname; |
1037 | |
|
1038 | 0 | priv = xzalloc(sizeof(*priv)); |
1039 | | |
1040 | | /* |
1041 | | * fbconsoles are usually just numbered and happen to use the same |
1042 | | * number as the fb they are registered on, so we have a nice match. |
1043 | | * With overlays we can also have fb names like fb0_0 (for the first |
1044 | | * overlay on fb0), and we want to have this name in the corresponding |
1045 | | * fbconsole. Skip the "fb" though to keep the existing fbconsole name |
1046 | | * for the non overlay framebuffers for compatibility. |
1047 | | */ |
1048 | 0 | fbname = dev_name(&fb->dev); |
1049 | 0 | if (strncmp(fbname, "fb", 2)) |
1050 | 0 | pr_notice("unexpected fb name %s\n", fbname); |
1051 | 0 | else |
1052 | 0 | fbname += 2; |
1053 | |
|
1054 | 0 | priv->fb = fb; |
1055 | 0 | priv->cur.x = 0; |
1056 | 0 | priv->cur.y = 0; |
1057 | |
|
1058 | 0 | fbc_reset_colors(priv); |
1059 | |
|
1060 | 0 | cdev = &priv->cdev; |
1061 | 0 | cdev->dev = &fb->dev; |
1062 | 0 | cdev->tstc = fbc_tstc; |
1063 | 0 | cdev->putc = fbc_putc; |
1064 | 0 | cdev->getc = fbc_getc; |
1065 | 0 | cdev->get_size = fbc_get_size; |
1066 | 0 | cdev->devname = basprintf("fbconsole%s", fbname); |
1067 | 0 | cdev->devid = DEVICE_ID_SINGLE; |
1068 | 0 | cdev->open = fbc_open; |
1069 | 0 | cdev->close = fbc_close; |
1070 | |
|
1071 | 0 | ret = console_register(cdev); |
1072 | 0 | if (ret) { |
1073 | 0 | pr_err("registering failed with %pe\n", ERR_PTR(ret)); |
1074 | 0 | kfree(priv); |
1075 | 0 | return ret; |
1076 | 0 | } |
1077 | | |
1078 | 0 | priv->par_font_val = 0; |
1079 | 0 | priv->par_font = add_param_font(&cdev->class_dev, |
1080 | 0 | set_font, NULL, |
1081 | 0 | &priv->par_font_val, priv); |
1082 | |
|
1083 | 0 | dev_add_param_uint32(&cdev->class_dev, "margin.top", set_margin, |
1084 | 0 | NULL, &priv->margin.top, "%u", priv); |
1085 | 0 | dev_add_param_uint32(&cdev->class_dev, "margin.left", set_margin, |
1086 | 0 | NULL, &priv->margin.left, "%u", priv); |
1087 | 0 | dev_add_param_uint32(&cdev->class_dev, "margin.bottom", set_margin, |
1088 | 0 | NULL, &priv->margin.bottom, "%u", priv); |
1089 | 0 | dev_add_param_uint32(&cdev->class_dev, "margin.right", set_margin, |
1090 | 0 | NULL, &priv->margin.right, "%u", priv); |
1091 | 0 | dev_add_param_enum(&cdev->class_dev, "rotation", set_rotation, NULL, |
1092 | 0 | &priv->rotation, rotation_names, |
1093 | 0 | ARRAY_SIZE(rotation_names), priv); |
1094 | |
|
1095 | 0 | pr_info("registered as %s%d\n", cdev->class_dev.name, cdev->class_dev.id); |
1096 | |
|
1097 | 0 | return 0; |
1098 | 0 | } |