0ebc7feae7
PowerPC has a 'btext' font used for the console which is almost identical
to the shared font_sun8x16, so use it rather than duplicating the data.
They were actually identical until about a decade ago when
commit bcfbeecea1
("drivers: console: font_: Change a glyph from
"broken bar" to "vertical line"")
which changed the | in the shared font to be a solid
bar rather than a broken bar. That's the only difference.
This was originally spotted by the PMF source code analyser, which
noticed that sparc does the same thing with the same data, and they
also share a bunch of functions to manipulate the data. I've previously
posted a near identical patch for sparc.
Tested very lightly with a boot without FS in qemu.
Signed-off-by: "Dr. David Alan Gilbert" <linux@treblig.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20230825142754.1487900-1-linux@treblig.org
584 lines
13 KiB
C
584 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Procedures for drawing on the screen early on in the boot process.
|
|
*
|
|
* Benjamin Herrenschmidt <benh@kernel.crashing.org>
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/init.h>
|
|
#include <linux/export.h>
|
|
#include <linux/font.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/pgtable.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <asm/sections.h>
|
|
#include <asm/btext.h>
|
|
#include <asm/page.h>
|
|
#include <asm/mmu.h>
|
|
#include <asm/io.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/udbg.h>
|
|
|
|
#define NO_SCROLL
|
|
|
|
#ifndef NO_SCROLL
|
|
static void scrollscreen(void);
|
|
#endif
|
|
|
|
#define __force_data __section(".data")
|
|
|
|
static int g_loc_X __force_data;
|
|
static int g_loc_Y __force_data;
|
|
static int g_max_loc_X __force_data;
|
|
static int g_max_loc_Y __force_data;
|
|
|
|
static int dispDeviceRowBytes __force_data;
|
|
static int dispDeviceDepth __force_data;
|
|
static int dispDeviceRect[4] __force_data;
|
|
static unsigned char *dispDeviceBase __force_data;
|
|
static unsigned char *logicalDisplayBase __force_data;
|
|
|
|
unsigned long disp_BAT[2] __initdata = {0, 0};
|
|
|
|
static int boot_text_mapped __force_data;
|
|
|
|
extern void rmci_on(void);
|
|
extern void rmci_off(void);
|
|
|
|
static inline void rmci_maybe_on(void)
|
|
{
|
|
#if defined(CONFIG_PPC_EARLY_DEBUG_BOOTX) && defined(CONFIG_PPC64)
|
|
if (!(mfmsr() & MSR_DR))
|
|
rmci_on();
|
|
#endif
|
|
}
|
|
|
|
static inline void rmci_maybe_off(void)
|
|
{
|
|
#if defined(CONFIG_PPC_EARLY_DEBUG_BOOTX) && defined(CONFIG_PPC64)
|
|
if (!(mfmsr() & MSR_DR))
|
|
rmci_off();
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_PPC32
|
|
/* Calc BAT values for mapping the display and store them
|
|
* in disp_BAT. Those values are then used from head.S to map
|
|
* the display during identify_machine() and MMU_Init()
|
|
*
|
|
* The display is mapped to virtual address 0xD0000000, rather
|
|
* than 1:1, because some CHRP machines put the frame buffer
|
|
* in the region starting at 0xC0000000 (PAGE_OFFSET).
|
|
* This mapping is temporary and will disappear as soon as the
|
|
* setup done by MMU_Init() is applied.
|
|
*
|
|
* For now, we align the BAT and then map 8Mb on 601 and 16Mb
|
|
* on other PPCs. This may cause trouble if the framebuffer
|
|
* is really badly aligned, but I didn't encounter this case
|
|
* yet.
|
|
*/
|
|
void __init btext_prepare_BAT(void)
|
|
{
|
|
unsigned long vaddr = PAGE_OFFSET + 0x10000000;
|
|
unsigned long addr;
|
|
unsigned long lowbits;
|
|
|
|
addr = (unsigned long)dispDeviceBase;
|
|
if (!addr) {
|
|
boot_text_mapped = 0;
|
|
return;
|
|
}
|
|
lowbits = addr & ~0xFF000000UL;
|
|
addr &= 0xFF000000UL;
|
|
disp_BAT[0] = vaddr | (BL_16M<<2) | 2;
|
|
disp_BAT[1] = addr | (_PAGE_NO_CACHE | _PAGE_GUARDED | BPP_RW);
|
|
logicalDisplayBase = (void *) (vaddr + lowbits);
|
|
}
|
|
#endif
|
|
|
|
|
|
/* This function can be used to enable the early boot text when doing
|
|
* OF booting or within bootx init. It must be followed by a btext_unmap()
|
|
* call before the logical address becomes unusable
|
|
*/
|
|
void __init btext_setup_display(int width, int height, int depth, int pitch,
|
|
unsigned long address)
|
|
{
|
|
g_loc_X = 0;
|
|
g_loc_Y = 0;
|
|
g_max_loc_X = width / 8;
|
|
g_max_loc_Y = height / 16;
|
|
logicalDisplayBase = (unsigned char *)address;
|
|
dispDeviceBase = (unsigned char *)address;
|
|
dispDeviceRowBytes = pitch;
|
|
dispDeviceDepth = depth == 15 ? 16 : depth;
|
|
dispDeviceRect[0] = dispDeviceRect[1] = 0;
|
|
dispDeviceRect[2] = width;
|
|
dispDeviceRect[3] = height;
|
|
boot_text_mapped = 1;
|
|
}
|
|
|
|
void __init btext_unmap(void)
|
|
{
|
|
boot_text_mapped = 0;
|
|
}
|
|
|
|
/* Here's a small text engine to use during early boot
|
|
* or for debugging purposes
|
|
*
|
|
* todo:
|
|
*
|
|
* - build some kind of vgacon with it to enable early printk
|
|
* - move to a separate file
|
|
* - add a few video driver hooks to keep in sync with display
|
|
* changes.
|
|
*/
|
|
|
|
void btext_map(void)
|
|
{
|
|
unsigned long base, offset, size;
|
|
unsigned char *vbase;
|
|
|
|
/* By default, we are no longer mapped */
|
|
boot_text_mapped = 0;
|
|
if (!dispDeviceBase)
|
|
return;
|
|
base = ((unsigned long) dispDeviceBase) & 0xFFFFF000UL;
|
|
offset = ((unsigned long) dispDeviceBase) - base;
|
|
size = dispDeviceRowBytes * dispDeviceRect[3] + offset
|
|
+ dispDeviceRect[0];
|
|
vbase = ioremap_wc(base, size);
|
|
if (!vbase)
|
|
return;
|
|
logicalDisplayBase = vbase + offset;
|
|
boot_text_mapped = 1;
|
|
}
|
|
|
|
static int __init btext_initialize(struct device_node *np)
|
|
{
|
|
unsigned int width, height, depth, pitch;
|
|
unsigned long address = 0;
|
|
const u32 *prop;
|
|
|
|
prop = of_get_property(np, "linux,bootx-width", NULL);
|
|
if (prop == NULL)
|
|
prop = of_get_property(np, "width", NULL);
|
|
if (prop == NULL)
|
|
return -EINVAL;
|
|
width = *prop;
|
|
prop = of_get_property(np, "linux,bootx-height", NULL);
|
|
if (prop == NULL)
|
|
prop = of_get_property(np, "height", NULL);
|
|
if (prop == NULL)
|
|
return -EINVAL;
|
|
height = *prop;
|
|
prop = of_get_property(np, "linux,bootx-depth", NULL);
|
|
if (prop == NULL)
|
|
prop = of_get_property(np, "depth", NULL);
|
|
if (prop == NULL)
|
|
return -EINVAL;
|
|
depth = *prop;
|
|
pitch = width * ((depth + 7) / 8);
|
|
prop = of_get_property(np, "linux,bootx-linebytes", NULL);
|
|
if (prop == NULL)
|
|
prop = of_get_property(np, "linebytes", NULL);
|
|
if (prop && *prop != 0xffffffffu)
|
|
pitch = *prop;
|
|
if (pitch == 1)
|
|
pitch = 0x1000;
|
|
prop = of_get_property(np, "linux,bootx-addr", NULL);
|
|
if (prop == NULL)
|
|
prop = of_get_property(np, "address", NULL);
|
|
if (prop)
|
|
address = *prop;
|
|
|
|
/* FIXME: Add support for PCI reg properties. Right now, only
|
|
* reliable on macs
|
|
*/
|
|
if (address == 0)
|
|
return -EINVAL;
|
|
|
|
g_loc_X = 0;
|
|
g_loc_Y = 0;
|
|
g_max_loc_X = width / 8;
|
|
g_max_loc_Y = height / 16;
|
|
dispDeviceBase = (unsigned char *)address;
|
|
dispDeviceRowBytes = pitch;
|
|
dispDeviceDepth = depth == 15 ? 16 : depth;
|
|
dispDeviceRect[0] = dispDeviceRect[1] = 0;
|
|
dispDeviceRect[2] = width;
|
|
dispDeviceRect[3] = height;
|
|
|
|
btext_map();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __init btext_find_display(int allow_nonstdout)
|
|
{
|
|
struct device_node *np = of_stdout;
|
|
int rc = -ENODEV;
|
|
|
|
if (!of_node_is_type(np, "display")) {
|
|
printk("boot stdout isn't a display !\n");
|
|
np = NULL;
|
|
}
|
|
if (np)
|
|
rc = btext_initialize(np);
|
|
if (rc == 0 || !allow_nonstdout)
|
|
return rc;
|
|
|
|
for_each_node_by_type(np, "display") {
|
|
if (of_property_read_bool(np, "linux,opened")) {
|
|
printk("trying %pOF ...\n", np);
|
|
rc = btext_initialize(np);
|
|
printk("result: %d\n", rc);
|
|
}
|
|
if (rc == 0) {
|
|
of_node_put(np);
|
|
break;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Calc the base address of a given point (x,y) */
|
|
static unsigned char * calc_base(int x, int y)
|
|
{
|
|
unsigned char *base;
|
|
|
|
base = logicalDisplayBase;
|
|
if (!base)
|
|
base = dispDeviceBase;
|
|
base += (x + dispDeviceRect[0]) * (dispDeviceDepth >> 3);
|
|
base += (y + dispDeviceRect[1]) * dispDeviceRowBytes;
|
|
return base;
|
|
}
|
|
|
|
/* Adjust the display to a new resolution */
|
|
void btext_update_display(unsigned long phys, int width, int height,
|
|
int depth, int pitch)
|
|
{
|
|
if (!dispDeviceBase)
|
|
return;
|
|
|
|
/* check it's the same frame buffer (within 256MB) */
|
|
if ((phys ^ (unsigned long)dispDeviceBase) & 0xf0000000)
|
|
return;
|
|
|
|
dispDeviceBase = (__u8 *) phys;
|
|
dispDeviceRect[0] = 0;
|
|
dispDeviceRect[1] = 0;
|
|
dispDeviceRect[2] = width;
|
|
dispDeviceRect[3] = height;
|
|
dispDeviceDepth = depth;
|
|
dispDeviceRowBytes = pitch;
|
|
if (boot_text_mapped) {
|
|
iounmap(logicalDisplayBase);
|
|
boot_text_mapped = 0;
|
|
}
|
|
btext_map();
|
|
g_loc_X = 0;
|
|
g_loc_Y = 0;
|
|
g_max_loc_X = width / 8;
|
|
g_max_loc_Y = height / 16;
|
|
}
|
|
EXPORT_SYMBOL(btext_update_display);
|
|
|
|
void __init btext_clearscreen(void)
|
|
{
|
|
unsigned int *base = (unsigned int *)calc_base(0, 0);
|
|
unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) *
|
|
(dispDeviceDepth >> 3)) >> 2;
|
|
int i,j;
|
|
|
|
rmci_maybe_on();
|
|
for (i=0; i<(dispDeviceRect[3] - dispDeviceRect[1]); i++)
|
|
{
|
|
unsigned int *ptr = base;
|
|
for(j=width; j; --j)
|
|
*(ptr++) = 0;
|
|
base += (dispDeviceRowBytes >> 2);
|
|
}
|
|
rmci_maybe_off();
|
|
}
|
|
|
|
void __init btext_flushscreen(void)
|
|
{
|
|
unsigned int *base = (unsigned int *)calc_base(0, 0);
|
|
unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) *
|
|
(dispDeviceDepth >> 3)) >> 2;
|
|
int i,j;
|
|
|
|
for (i=0; i < (dispDeviceRect[3] - dispDeviceRect[1]); i++)
|
|
{
|
|
unsigned int *ptr = base;
|
|
for(j = width; j > 0; j -= 8) {
|
|
__asm__ __volatile__ ("dcbst 0,%0" :: "r" (ptr));
|
|
ptr += 8;
|
|
}
|
|
base += (dispDeviceRowBytes >> 2);
|
|
}
|
|
__asm__ __volatile__ ("sync" ::: "memory");
|
|
}
|
|
|
|
void __init btext_flushline(void)
|
|
{
|
|
unsigned int *base = (unsigned int *)calc_base(0, g_loc_Y << 4);
|
|
unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) *
|
|
(dispDeviceDepth >> 3)) >> 2;
|
|
int i,j;
|
|
|
|
for (i=0; i < 16; i++)
|
|
{
|
|
unsigned int *ptr = base;
|
|
for(j = width; j > 0; j -= 8) {
|
|
__asm__ __volatile__ ("dcbst 0,%0" :: "r" (ptr));
|
|
ptr += 8;
|
|
}
|
|
base += (dispDeviceRowBytes >> 2);
|
|
}
|
|
__asm__ __volatile__ ("sync" ::: "memory");
|
|
}
|
|
|
|
|
|
#ifndef NO_SCROLL
|
|
static void scrollscreen(void)
|
|
{
|
|
unsigned int *src = (unsigned int *)calc_base(0,16);
|
|
unsigned int *dst = (unsigned int *)calc_base(0,0);
|
|
unsigned long width = ((dispDeviceRect[2] - dispDeviceRect[0]) *
|
|
(dispDeviceDepth >> 3)) >> 2;
|
|
int i,j;
|
|
|
|
rmci_maybe_on();
|
|
|
|
for (i=0; i<(dispDeviceRect[3] - dispDeviceRect[1] - 16); i++)
|
|
{
|
|
unsigned int *src_ptr = src;
|
|
unsigned int *dst_ptr = dst;
|
|
for(j=width; j; --j)
|
|
*(dst_ptr++) = *(src_ptr++);
|
|
src += (dispDeviceRowBytes >> 2);
|
|
dst += (dispDeviceRowBytes >> 2);
|
|
}
|
|
for (i=0; i<16; i++)
|
|
{
|
|
unsigned int *dst_ptr = dst;
|
|
for(j=width; j; --j)
|
|
*(dst_ptr++) = 0;
|
|
dst += (dispDeviceRowBytes >> 2);
|
|
}
|
|
|
|
rmci_maybe_off();
|
|
}
|
|
#endif /* ndef NO_SCROLL */
|
|
|
|
static unsigned int expand_bits_8[16] = {
|
|
0x00000000,
|
|
0x000000ff,
|
|
0x0000ff00,
|
|
0x0000ffff,
|
|
0x00ff0000,
|
|
0x00ff00ff,
|
|
0x00ffff00,
|
|
0x00ffffff,
|
|
0xff000000,
|
|
0xff0000ff,
|
|
0xff00ff00,
|
|
0xff00ffff,
|
|
0xffff0000,
|
|
0xffff00ff,
|
|
0xffffff00,
|
|
0xffffffff
|
|
};
|
|
|
|
static unsigned int expand_bits_16[4] = {
|
|
0x00000000,
|
|
0x0000ffff,
|
|
0xffff0000,
|
|
0xffffffff
|
|
};
|
|
|
|
|
|
static void draw_byte_32(const unsigned char *font, unsigned int *base, int rb)
|
|
{
|
|
int l, bits;
|
|
int fg = 0xFFFFFFFFUL;
|
|
int bg = 0x00000000UL;
|
|
|
|
for (l = 0; l < 16; ++l)
|
|
{
|
|
bits = *font++;
|
|
base[0] = (-(bits >> 7) & fg) ^ bg;
|
|
base[1] = (-((bits >> 6) & 1) & fg) ^ bg;
|
|
base[2] = (-((bits >> 5) & 1) & fg) ^ bg;
|
|
base[3] = (-((bits >> 4) & 1) & fg) ^ bg;
|
|
base[4] = (-((bits >> 3) & 1) & fg) ^ bg;
|
|
base[5] = (-((bits >> 2) & 1) & fg) ^ bg;
|
|
base[6] = (-((bits >> 1) & 1) & fg) ^ bg;
|
|
base[7] = (-(bits & 1) & fg) ^ bg;
|
|
base = (unsigned int *) ((char *)base + rb);
|
|
}
|
|
}
|
|
|
|
static inline void draw_byte_16(const unsigned char *font, unsigned int *base, int rb)
|
|
{
|
|
int l, bits;
|
|
int fg = 0xFFFFFFFFUL;
|
|
int bg = 0x00000000UL;
|
|
unsigned int *eb = (int *)expand_bits_16;
|
|
|
|
for (l = 0; l < 16; ++l)
|
|
{
|
|
bits = *font++;
|
|
base[0] = (eb[bits >> 6] & fg) ^ bg;
|
|
base[1] = (eb[(bits >> 4) & 3] & fg) ^ bg;
|
|
base[2] = (eb[(bits >> 2) & 3] & fg) ^ bg;
|
|
base[3] = (eb[bits & 3] & fg) ^ bg;
|
|
base = (unsigned int *) ((char *)base + rb);
|
|
}
|
|
}
|
|
|
|
static inline void draw_byte_8(const unsigned char *font, unsigned int *base, int rb)
|
|
{
|
|
int l, bits;
|
|
int fg = 0x0F0F0F0FUL;
|
|
int bg = 0x00000000UL;
|
|
unsigned int *eb = (int *)expand_bits_8;
|
|
|
|
for (l = 0; l < 16; ++l)
|
|
{
|
|
bits = *font++;
|
|
base[0] = (eb[bits >> 4] & fg) ^ bg;
|
|
base[1] = (eb[bits & 0xf] & fg) ^ bg;
|
|
base = (unsigned int *) ((char *)base + rb);
|
|
}
|
|
}
|
|
|
|
static noinline void draw_byte(unsigned char c, long locX, long locY)
|
|
{
|
|
unsigned char *base = calc_base(locX << 3, locY << 4);
|
|
unsigned int font_index = c * 16;
|
|
const unsigned char *font = font_sun_8x16.data + font_index;
|
|
int rb = dispDeviceRowBytes;
|
|
|
|
rmci_maybe_on();
|
|
switch(dispDeviceDepth) {
|
|
case 24:
|
|
case 32:
|
|
draw_byte_32(font, (unsigned int *)base, rb);
|
|
break;
|
|
case 15:
|
|
case 16:
|
|
draw_byte_16(font, (unsigned int *)base, rb);
|
|
break;
|
|
case 8:
|
|
draw_byte_8(font, (unsigned int *)base, rb);
|
|
break;
|
|
}
|
|
rmci_maybe_off();
|
|
}
|
|
|
|
void btext_drawchar(char c)
|
|
{
|
|
int cline = 0;
|
|
#ifdef NO_SCROLL
|
|
int x;
|
|
#endif
|
|
if (!boot_text_mapped)
|
|
return;
|
|
|
|
switch (c) {
|
|
case '\b':
|
|
if (g_loc_X > 0)
|
|
--g_loc_X;
|
|
break;
|
|
case '\t':
|
|
g_loc_X = (g_loc_X & -8) + 8;
|
|
break;
|
|
case '\r':
|
|
g_loc_X = 0;
|
|
break;
|
|
case '\n':
|
|
g_loc_X = 0;
|
|
g_loc_Y++;
|
|
cline = 1;
|
|
break;
|
|
default:
|
|
draw_byte(c, g_loc_X++, g_loc_Y);
|
|
}
|
|
if (g_loc_X >= g_max_loc_X) {
|
|
g_loc_X = 0;
|
|
g_loc_Y++;
|
|
cline = 1;
|
|
}
|
|
#ifndef NO_SCROLL
|
|
while (g_loc_Y >= g_max_loc_Y) {
|
|
scrollscreen();
|
|
g_loc_Y--;
|
|
}
|
|
#else
|
|
/* wrap around from bottom to top of screen so we don't
|
|
waste time scrolling each line. -- paulus. */
|
|
if (g_loc_Y >= g_max_loc_Y)
|
|
g_loc_Y = 0;
|
|
if (cline) {
|
|
for (x = 0; x < g_max_loc_X; ++x)
|
|
draw_byte(' ', x, g_loc_Y);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void btext_drawstring(const char *c)
|
|
{
|
|
if (!boot_text_mapped)
|
|
return;
|
|
while (*c)
|
|
btext_drawchar(*c++);
|
|
}
|
|
|
|
void __init btext_drawtext(const char *c, unsigned int len)
|
|
{
|
|
if (!boot_text_mapped)
|
|
return;
|
|
while (len--)
|
|
btext_drawchar(*c++);
|
|
}
|
|
|
|
void __init btext_drawhex(unsigned long v)
|
|
{
|
|
if (!boot_text_mapped)
|
|
return;
|
|
#ifdef CONFIG_PPC64
|
|
btext_drawchar(hex_asc_hi(v >> 56));
|
|
btext_drawchar(hex_asc_lo(v >> 56));
|
|
btext_drawchar(hex_asc_hi(v >> 48));
|
|
btext_drawchar(hex_asc_lo(v >> 48));
|
|
btext_drawchar(hex_asc_hi(v >> 40));
|
|
btext_drawchar(hex_asc_lo(v >> 40));
|
|
btext_drawchar(hex_asc_hi(v >> 32));
|
|
btext_drawchar(hex_asc_lo(v >> 32));
|
|
#endif
|
|
btext_drawchar(hex_asc_hi(v >> 24));
|
|
btext_drawchar(hex_asc_lo(v >> 24));
|
|
btext_drawchar(hex_asc_hi(v >> 16));
|
|
btext_drawchar(hex_asc_lo(v >> 16));
|
|
btext_drawchar(hex_asc_hi(v >> 8));
|
|
btext_drawchar(hex_asc_lo(v >> 8));
|
|
btext_drawchar(hex_asc_hi(v));
|
|
btext_drawchar(hex_asc_lo(v));
|
|
btext_drawchar(' ');
|
|
}
|
|
|
|
void __init udbg_init_btext(void)
|
|
{
|
|
/* If btext is enabled, we might have a BAT setup for early display,
|
|
* thus we do enable some very basic udbg output
|
|
*/
|
|
udbg_putc = btext_drawchar;
|
|
}
|