1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Procedures for drawing on the screen early on in the boot process. |
4 | * |
5 | * Benjamin Herrenschmidt <benh@kernel.crashing.org> |
6 | */ |
7 | #include <linux/kernel.h> |
8 | #include <linux/string.h> |
9 | #include <linux/init.h> |
10 | #include <linux/export.h> |
11 | #include <linux/font.h> |
12 | #include <linux/memblock.h> |
13 | #include <linux/pgtable.h> |
14 | #include <linux/of.h> |
15 | |
16 | #include <asm/sections.h> |
17 | #include <asm/btext.h> |
18 | #include <asm/page.h> |
19 | #include <asm/mmu.h> |
20 | #include <asm/io.h> |
21 | #include <asm/processor.h> |
22 | #include <asm/udbg.h> |
23 | |
24 | #define NO_SCROLL |
25 | |
26 | #ifndef NO_SCROLL |
27 | static void scrollscreen(void); |
28 | #endif |
29 | |
30 | #define __force_data __section(".data") |
31 | |
32 | static int g_loc_X __force_data; |
33 | static int g_loc_Y __force_data; |
34 | static int g_max_loc_X __force_data; |
35 | static int g_max_loc_Y __force_data; |
36 | |
37 | static int dispDeviceRowBytes __force_data; |
38 | static int dispDeviceDepth __force_data; |
39 | static int dispDeviceRect[4] __force_data; |
40 | static unsigned char *dispDeviceBase __force_data; |
41 | static unsigned char *logicalDisplayBase __force_data; |
42 | |
43 | unsigned long disp_BAT[2] __initdata = {0, 0}; |
44 | |
45 | static int boot_text_mapped __force_data; |
46 | |
47 | extern void rmci_on(void); |
48 | extern void rmci_off(void); |
49 | |
50 | static inline void rmci_maybe_on(void) |
51 | { |
52 | #if defined(CONFIG_PPC_EARLY_DEBUG_BOOTX) && defined(CONFIG_PPC64) |
53 | if (!(mfmsr() & MSR_DR)) |
54 | rmci_on(); |
55 | #endif |
56 | } |
57 | |
58 | static inline void rmci_maybe_off(void) |
59 | { |
60 | #if defined(CONFIG_PPC_EARLY_DEBUG_BOOTX) && defined(CONFIG_PPC64) |
61 | if (!(mfmsr() & MSR_DR)) |
62 | rmci_off(); |
63 | #endif |
64 | } |
65 | |
66 | |
67 | #ifdef CONFIG_PPC32 |
68 | /* Calc BAT values for mapping the display and store them |
69 | * in disp_BAT. Those values are then used from head.S to map |
70 | * the display during identify_machine() and MMU_Init() |
71 | * |
72 | * The display is mapped to virtual address 0xD0000000, rather |
73 | * than 1:1, because some CHRP machines put the frame buffer |
74 | * in the region starting at 0xC0000000 (PAGE_OFFSET). |
75 | * This mapping is temporary and will disappear as soon as the |
76 | * setup done by MMU_Init() is applied. |
77 | * |
78 | * For now, we align the BAT and then map 8Mb on 601 and 16Mb |
79 | * on other PPCs. This may cause trouble if the framebuffer |
80 | * is really badly aligned, but I didn't encounter this case |
81 | * yet. |
82 | */ |
83 | void __init btext_prepare_BAT(void) |
84 | { |
85 | unsigned long vaddr = PAGE_OFFSET + 0x10000000; |
86 | unsigned long addr; |
87 | unsigned long lowbits; |
88 | |
89 | addr = (unsigned long)dispDeviceBase; |
90 | if (!addr) { |
91 | boot_text_mapped = 0; |
92 | return; |
93 | } |
94 | lowbits = addr & ~0xFF000000UL; |
95 | addr &= 0xFF000000UL; |
96 | disp_BAT[0] = vaddr | (BL_16M<<2) | 2; |
97 | disp_BAT[1] = addr | (_PAGE_NO_CACHE | _PAGE_GUARDED | BPP_RW); |
98 | logicalDisplayBase = (void *) (vaddr + lowbits); |
99 | } |
100 | #endif |
101 | |
102 | |
103 | /* This function can be used to enable the early boot text when doing |
104 | * OF booting or within bootx init. It must be followed by a btext_unmap() |
105 | * call before the logical address becomes unusable |
106 | */ |
107 | void __init btext_setup_display(int width, int height, int depth, int pitch, |
108 | unsigned long address) |
109 | { |
110 | g_loc_X = 0; |
111 | g_loc_Y = 0; |
112 | g_max_loc_X = width / 8; |
113 | g_max_loc_Y = height / 16; |
114 | logicalDisplayBase = (unsigned char *)address; |
115 | dispDeviceBase = (unsigned char *)address; |
116 | dispDeviceRowBytes = pitch; |
117 | dispDeviceDepth = depth == 15 ? 16 : depth; |
118 | dispDeviceRect[0] = dispDeviceRect[1] = 0; |
119 | dispDeviceRect[2] = width; |
120 | dispDeviceRect[3] = height; |
121 | boot_text_mapped = 1; |
122 | } |
123 | |
124 | void __init btext_unmap(void) |
125 | { |
126 | boot_text_mapped = 0; |
127 | } |
128 | |
129 | /* Here's a small text engine to use during early boot |
130 | * or for debugging purposes |
131 | * |
132 | * todo: |
133 | * |
134 | * - build some kind of vgacon with it to enable early printk |
135 | * - move to a separate file |
136 | * - add a few video driver hooks to keep in sync with display |
137 | * changes. |
138 | */ |
139 | |
140 | void btext_map(void) |
141 | { |
142 | unsigned long base, offset, size; |
143 | unsigned char *vbase; |
144 | |
145 | /* By default, we are no longer mapped */ |
146 | boot_text_mapped = 0; |
147 | if (!dispDeviceBase) |
148 | return; |
149 | base = ((unsigned long) dispDeviceBase) & 0xFFFFF000UL; |
150 | offset = ((unsigned long) dispDeviceBase) - base; |
151 | size = dispDeviceRowBytes * dispDeviceRect[3] + offset |
152 | + dispDeviceRect[0]; |
153 | vbase = ioremap_wc(offset: base, size); |
154 | if (!vbase) |
155 | return; |
156 | logicalDisplayBase = vbase + offset; |
157 | boot_text_mapped = 1; |
158 | } |
159 | |
160 | static int __init btext_initialize(struct device_node *np) |
161 | { |
162 | unsigned int width, height, depth, pitch; |
163 | unsigned long address = 0; |
164 | const u32 *prop; |
165 | |
166 | prop = of_get_property(node: np, name: "linux,bootx-width" , NULL); |
167 | if (prop == NULL) |
168 | prop = of_get_property(node: np, name: "width" , NULL); |
169 | if (prop == NULL) |
170 | return -EINVAL; |
171 | width = *prop; |
172 | prop = of_get_property(node: np, name: "linux,bootx-height" , NULL); |
173 | if (prop == NULL) |
174 | prop = of_get_property(node: np, name: "height" , NULL); |
175 | if (prop == NULL) |
176 | return -EINVAL; |
177 | height = *prop; |
178 | prop = of_get_property(node: np, name: "linux,bootx-depth" , NULL); |
179 | if (prop == NULL) |
180 | prop = of_get_property(node: np, name: "depth" , NULL); |
181 | if (prop == NULL) |
182 | return -EINVAL; |
183 | depth = *prop; |
184 | pitch = width * ((depth + 7) / 8); |
185 | prop = of_get_property(node: np, name: "linux,bootx-linebytes" , NULL); |
186 | if (prop == NULL) |
187 | prop = of_get_property(node: np, name: "linebytes" , NULL); |
188 | if (prop && *prop != 0xffffffffu) |
189 | pitch = *prop; |
190 | if (pitch == 1) |
191 | pitch = 0x1000; |
192 | prop = of_get_property(node: np, name: "linux,bootx-addr" , NULL); |
193 | if (prop == NULL) |
194 | prop = of_get_property(node: np, name: "address" , NULL); |
195 | if (prop) |
196 | address = *prop; |
197 | |
198 | /* FIXME: Add support for PCI reg properties. Right now, only |
199 | * reliable on macs |
200 | */ |
201 | if (address == 0) |
202 | return -EINVAL; |
203 | |
204 | g_loc_X = 0; |
205 | g_loc_Y = 0; |
206 | g_max_loc_X = width / 8; |
207 | g_max_loc_Y = height / 16; |
208 | dispDeviceBase = (unsigned char *)address; |
209 | dispDeviceRowBytes = pitch; |
210 | dispDeviceDepth = depth == 15 ? 16 : depth; |
211 | dispDeviceRect[0] = dispDeviceRect[1] = 0; |
212 | dispDeviceRect[2] = width; |
213 | dispDeviceRect[3] = height; |
214 | |
215 | btext_map(); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | int __init btext_find_display(int allow_nonstdout) |
221 | { |
222 | struct device_node *np = of_stdout; |
223 | int rc = -ENODEV; |
224 | |
225 | if (!of_node_is_type(np, type: "display" )) { |
226 | printk("boot stdout isn't a display !\n" ); |
227 | np = NULL; |
228 | } |
229 | if (np) |
230 | rc = btext_initialize(np); |
231 | if (rc == 0 || !allow_nonstdout) |
232 | return rc; |
233 | |
234 | for_each_node_by_type(np, "display" ) { |
235 | if (of_property_read_bool(np, propname: "linux,opened" )) { |
236 | printk("trying %pOF ...\n" , np); |
237 | rc = btext_initialize(np); |
238 | printk("result: %d\n" , rc); |
239 | } |
240 | if (rc == 0) { |
241 | of_node_put(node: np); |
242 | break; |
243 | } |
244 | } |
245 | return rc; |
246 | } |
247 | |
248 | /* Calc the base address of a given point (x,y) */ |
249 | static unsigned char * calc_base(int x, int y) |
250 | { |
251 | unsigned char *base; |
252 | |
253 | base = logicalDisplayBase; |
254 | if (!base) |
255 | base = dispDeviceBase; |
256 | base += (x + dispDeviceRect[0]) * (dispDeviceDepth >> 3); |
257 | base += (y + dispDeviceRect[1]) * dispDeviceRowBytes; |
258 | return base; |
259 | } |
260 | |
261 | /* Adjust the display to a new resolution */ |
262 | void btext_update_display(unsigned long phys, int width, int height, |
263 | int depth, int pitch) |
264 | { |
265 | if (!dispDeviceBase) |
266 | return; |
267 | |
268 | /* check it's the same frame buffer (within 256MB) */ |
269 | if ((phys ^ (unsigned long)dispDeviceBase) & 0xf0000000) |
270 | return; |
271 | |
272 | dispDeviceBase = (__u8 *) phys; |
273 | dispDeviceRect[0] = 0; |
274 | dispDeviceRect[1] = 0; |
275 | dispDeviceRect[2] = width; |
276 | dispDeviceRect[3] = height; |
277 | dispDeviceDepth = depth; |
278 | dispDeviceRowBytes = pitch; |
279 | if (boot_text_mapped) { |
280 | iounmap(addr: logicalDisplayBase); |
281 | boot_text_mapped = 0; |
282 | } |
283 | btext_map(); |
284 | g_loc_X = 0; |
285 | g_loc_Y = 0; |
286 | g_max_loc_X = width / 8; |
287 | g_max_loc_Y = height / 16; |
288 | } |
289 | EXPORT_SYMBOL(btext_update_display); |
290 | |
291 | void __init btext_clearscreen(void) |
292 | { |
293 | unsigned int *base = (unsigned int *)calc_base(x: 0, y: 0); |
294 | unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) * |
295 | (dispDeviceDepth >> 3)) >> 2; |
296 | int i,j; |
297 | |
298 | rmci_maybe_on(); |
299 | for (i=0; i<(dispDeviceRect[3] - dispDeviceRect[1]); i++) |
300 | { |
301 | unsigned int *ptr = base; |
302 | for(j=width; j; --j) |
303 | *(ptr++) = 0; |
304 | base += (dispDeviceRowBytes >> 2); |
305 | } |
306 | rmci_maybe_off(); |
307 | } |
308 | |
309 | void __init btext_flushscreen(void) |
310 | { |
311 | unsigned int *base = (unsigned int *)calc_base(x: 0, y: 0); |
312 | unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) * |
313 | (dispDeviceDepth >> 3)) >> 2; |
314 | int i,j; |
315 | |
316 | for (i=0; i < (dispDeviceRect[3] - dispDeviceRect[1]); i++) |
317 | { |
318 | unsigned int *ptr = base; |
319 | for(j = width; j > 0; j -= 8) { |
320 | __asm__ __volatile__ ("dcbst 0,%0" :: "r" (ptr)); |
321 | ptr += 8; |
322 | } |
323 | base += (dispDeviceRowBytes >> 2); |
324 | } |
325 | __asm__ __volatile__ ("sync" ::: "memory" ); |
326 | } |
327 | |
328 | void __init btext_flushline(void) |
329 | { |
330 | unsigned int *base = (unsigned int *)calc_base(x: 0, y: g_loc_Y << 4); |
331 | unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) * |
332 | (dispDeviceDepth >> 3)) >> 2; |
333 | int i,j; |
334 | |
335 | for (i=0; i < 16; i++) |
336 | { |
337 | unsigned int *ptr = base; |
338 | for(j = width; j > 0; j -= 8) { |
339 | __asm__ __volatile__ ("dcbst 0,%0" :: "r" (ptr)); |
340 | ptr += 8; |
341 | } |
342 | base += (dispDeviceRowBytes >> 2); |
343 | } |
344 | __asm__ __volatile__ ("sync" ::: "memory" ); |
345 | } |
346 | |
347 | |
348 | #ifndef NO_SCROLL |
349 | static void scrollscreen(void) |
350 | { |
351 | unsigned int *src = (unsigned int *)calc_base(0,16); |
352 | unsigned int *dst = (unsigned int *)calc_base(0,0); |
353 | unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) * |
354 | (dispDeviceDepth >> 3)) >> 2; |
355 | int i,j; |
356 | |
357 | rmci_maybe_on(); |
358 | |
359 | for (i=0; i<(dispDeviceRect[3] - dispDeviceRect[1] - 16); i++) |
360 | { |
361 | unsigned int *src_ptr = src; |
362 | unsigned int *dst_ptr = dst; |
363 | for(j=width; j; --j) |
364 | *(dst_ptr++) = *(src_ptr++); |
365 | src += (dispDeviceRowBytes >> 2); |
366 | dst += (dispDeviceRowBytes >> 2); |
367 | } |
368 | for (i=0; i<16; i++) |
369 | { |
370 | unsigned int *dst_ptr = dst; |
371 | for(j=width; j; --j) |
372 | *(dst_ptr++) = 0; |
373 | dst += (dispDeviceRowBytes >> 2); |
374 | } |
375 | |
376 | rmci_maybe_off(); |
377 | } |
378 | #endif /* ndef NO_SCROLL */ |
379 | |
380 | static unsigned int expand_bits_8[16] = { |
381 | 0x00000000, |
382 | 0x000000ff, |
383 | 0x0000ff00, |
384 | 0x0000ffff, |
385 | 0x00ff0000, |
386 | 0x00ff00ff, |
387 | 0x00ffff00, |
388 | 0x00ffffff, |
389 | 0xff000000, |
390 | 0xff0000ff, |
391 | 0xff00ff00, |
392 | 0xff00ffff, |
393 | 0xffff0000, |
394 | 0xffff00ff, |
395 | 0xffffff00, |
396 | 0xffffffff |
397 | }; |
398 | |
399 | static unsigned int expand_bits_16[4] = { |
400 | 0x00000000, |
401 | 0x0000ffff, |
402 | 0xffff0000, |
403 | 0xffffffff |
404 | }; |
405 | |
406 | |
407 | static void draw_byte_32(const unsigned char *font, unsigned int *base, int rb) |
408 | { |
409 | int l, bits; |
410 | int fg = 0xFFFFFFFFUL; |
411 | int bg = 0x00000000UL; |
412 | |
413 | for (l = 0; l < 16; ++l) |
414 | { |
415 | bits = *font++; |
416 | base[0] = (-(bits >> 7) & fg) ^ bg; |
417 | base[1] = (-((bits >> 6) & 1) & fg) ^ bg; |
418 | base[2] = (-((bits >> 5) & 1) & fg) ^ bg; |
419 | base[3] = (-((bits >> 4) & 1) & fg) ^ bg; |
420 | base[4] = (-((bits >> 3) & 1) & fg) ^ bg; |
421 | base[5] = (-((bits >> 2) & 1) & fg) ^ bg; |
422 | base[6] = (-((bits >> 1) & 1) & fg) ^ bg; |
423 | base[7] = (-(bits & 1) & fg) ^ bg; |
424 | base = (unsigned int *) ((char *)base + rb); |
425 | } |
426 | } |
427 | |
428 | static inline void draw_byte_16(const unsigned char *font, unsigned int *base, int rb) |
429 | { |
430 | int l, bits; |
431 | int fg = 0xFFFFFFFFUL; |
432 | int bg = 0x00000000UL; |
433 | unsigned int *eb = (int *)expand_bits_16; |
434 | |
435 | for (l = 0; l < 16; ++l) |
436 | { |
437 | bits = *font++; |
438 | base[0] = (eb[bits >> 6] & fg) ^ bg; |
439 | base[1] = (eb[(bits >> 4) & 3] & fg) ^ bg; |
440 | base[2] = (eb[(bits >> 2) & 3] & fg) ^ bg; |
441 | base[3] = (eb[bits & 3] & fg) ^ bg; |
442 | base = (unsigned int *) ((char *)base + rb); |
443 | } |
444 | } |
445 | |
446 | static inline void draw_byte_8(const unsigned char *font, unsigned int *base, int rb) |
447 | { |
448 | int l, bits; |
449 | int fg = 0x0F0F0F0FUL; |
450 | int bg = 0x00000000UL; |
451 | unsigned int *eb = (int *)expand_bits_8; |
452 | |
453 | for (l = 0; l < 16; ++l) |
454 | { |
455 | bits = *font++; |
456 | base[0] = (eb[bits >> 4] & fg) ^ bg; |
457 | base[1] = (eb[bits & 0xf] & fg) ^ bg; |
458 | base = (unsigned int *) ((char *)base + rb); |
459 | } |
460 | } |
461 | |
462 | static noinline void draw_byte(unsigned char c, long locX, long locY) |
463 | { |
464 | unsigned char *base = calc_base(x: locX << 3, y: locY << 4); |
465 | unsigned int font_index = c * 16; |
466 | const unsigned char *font = font_sun_8x16.data + font_index; |
467 | int rb = dispDeviceRowBytes; |
468 | |
469 | rmci_maybe_on(); |
470 | switch(dispDeviceDepth) { |
471 | case 24: |
472 | case 32: |
473 | draw_byte_32(font, base: (unsigned int *)base, rb); |
474 | break; |
475 | case 15: |
476 | case 16: |
477 | draw_byte_16(font, base: (unsigned int *)base, rb); |
478 | break; |
479 | case 8: |
480 | draw_byte_8(font, base: (unsigned int *)base, rb); |
481 | break; |
482 | } |
483 | rmci_maybe_off(); |
484 | } |
485 | |
486 | void btext_drawchar(char c) |
487 | { |
488 | int cline = 0; |
489 | #ifdef NO_SCROLL |
490 | int x; |
491 | #endif |
492 | if (!boot_text_mapped) |
493 | return; |
494 | |
495 | switch (c) { |
496 | case '\b': |
497 | if (g_loc_X > 0) |
498 | --g_loc_X; |
499 | break; |
500 | case '\t': |
501 | g_loc_X = (g_loc_X & -8) + 8; |
502 | break; |
503 | case '\r': |
504 | g_loc_X = 0; |
505 | break; |
506 | case '\n': |
507 | g_loc_X = 0; |
508 | g_loc_Y++; |
509 | cline = 1; |
510 | break; |
511 | default: |
512 | draw_byte(c, locX: g_loc_X++, locY: g_loc_Y); |
513 | } |
514 | if (g_loc_X >= g_max_loc_X) { |
515 | g_loc_X = 0; |
516 | g_loc_Y++; |
517 | cline = 1; |
518 | } |
519 | #ifndef NO_SCROLL |
520 | while (g_loc_Y >= g_max_loc_Y) { |
521 | scrollscreen(); |
522 | g_loc_Y--; |
523 | } |
524 | #else |
525 | /* wrap around from bottom to top of screen so we don't |
526 | waste time scrolling each line. -- paulus. */ |
527 | if (g_loc_Y >= g_max_loc_Y) |
528 | g_loc_Y = 0; |
529 | if (cline) { |
530 | for (x = 0; x < g_max_loc_X; ++x) |
531 | draw_byte(c: ' ', locX: x, locY: g_loc_Y); |
532 | } |
533 | #endif |
534 | } |
535 | |
536 | void btext_drawstring(const char *c) |
537 | { |
538 | if (!boot_text_mapped) |
539 | return; |
540 | while (*c) |
541 | btext_drawchar(c: *c++); |
542 | } |
543 | |
544 | void __init btext_drawtext(const char *c, unsigned int len) |
545 | { |
546 | if (!boot_text_mapped) |
547 | return; |
548 | while (len--) |
549 | btext_drawchar(c: *c++); |
550 | } |
551 | |
552 | void __init btext_drawhex(unsigned long v) |
553 | { |
554 | if (!boot_text_mapped) |
555 | return; |
556 | #ifdef CONFIG_PPC64 |
557 | btext_drawchar(hex_asc_hi(v >> 56)); |
558 | btext_drawchar(hex_asc_lo(v >> 56)); |
559 | btext_drawchar(hex_asc_hi(v >> 48)); |
560 | btext_drawchar(hex_asc_lo(v >> 48)); |
561 | btext_drawchar(hex_asc_hi(v >> 40)); |
562 | btext_drawchar(hex_asc_lo(v >> 40)); |
563 | btext_drawchar(hex_asc_hi(v >> 32)); |
564 | btext_drawchar(hex_asc_lo(v >> 32)); |
565 | #endif |
566 | btext_drawchar(hex_asc_hi(v >> 24)); |
567 | btext_drawchar(hex_asc_lo(v >> 24)); |
568 | btext_drawchar(hex_asc_hi(v >> 16)); |
569 | btext_drawchar(hex_asc_lo(v >> 16)); |
570 | btext_drawchar(hex_asc_hi(v >> 8)); |
571 | btext_drawchar(hex_asc_lo(v >> 8)); |
572 | btext_drawchar(hex_asc_hi(v)); |
573 | btext_drawchar(hex_asc_lo(v)); |
574 | btext_drawchar(c: ' '); |
575 | } |
576 | |
577 | void __init udbg_init_btext(void) |
578 | { |
579 | /* If btext is enabled, we might have a BAT setup for early display, |
580 | * thus we do enable some very basic udbg output |
581 | */ |
582 | udbg_putc = btext_drawchar; |
583 | } |
584 | |