x86/efi: Add EFI framebuffer earlyprintk support

It's incredibly difficult to diagnose early EFI boot issues without
special hardware because earlyprintk=vga doesn't work on EFI systems.

Add support for writing to the EFI framebuffer, via earlyprintk=efi,
which will actually give users a chance of providing debug output.

Cc: H. Peter Anvin <hpa@zytor.com>
Acked-by: Ingo Molnar <mingo@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Jones <pjones@redhat.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
This commit is contained in:
Matt Fleming 2013-10-04 09:36:56 +01:00
parent c158c3bf59
commit 72548e836b
6 changed files with 216 additions and 3 deletions

View File

@ -792,6 +792,7 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
earlyprintk= [X86,SH,BLACKFIN,ARM] earlyprintk= [X86,SH,BLACKFIN,ARM]
earlyprintk=vga earlyprintk=vga
earlyprintk=efi
earlyprintk=xen earlyprintk=xen
earlyprintk=serial[,ttySn[,baudrate]] earlyprintk=serial[,ttySn[,baudrate]]
earlyprintk=serial[,0x...[,baudrate]] earlyprintk=serial[,0x...[,baudrate]]
@ -805,7 +806,8 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
Append ",keep" to not disable it when the real console Append ",keep" to not disable it when the real console
takes over. takes over.
Only vga or serial or usb debug port at a time. Only one of vga, efi, serial, or usb debug port can
be used at a time.
Currently only ttyS0 and ttyS1 may be specified by Currently only ttyS0 and ttyS1 may be specified by
name. Other I/O ports may be explicitly specified name. Other I/O ports may be explicitly specified
@ -819,8 +821,8 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
Interaction with the standard serial driver is not Interaction with the standard serial driver is not
very good. very good.
The VGA output is eventually overwritten by the real The VGA and EFI output is eventually overwritten by
console. the real console.
The xen output can only be used by Xen PV guests. The xen output can only be used by Xen PV guests.

View File

@ -59,6 +59,16 @@ config EARLY_PRINTK_DBGP
with klogd/syslogd or the X server. You should normally N here, with klogd/syslogd or the X server. You should normally N here,
unless you want to debug such a crash. You need usb debug device. unless you want to debug such a crash. You need usb debug device.
config EARLY_PRINTK_EFI
bool "Early printk via the EFI framebuffer"
depends on EFI && EARLY_PRINTK
select FONT_SUPPORT
---help---
Write kernel log output directly into the EFI framebuffer.
This is useful for kernel debugging when your machine crashes very
early before the console code is initialized.
config X86_PTDUMP config X86_PTDUMP
bool "Export kernel pagetable layout to userspace via debugfs" bool "Export kernel pagetable layout to userspace via debugfs"
depends on DEBUG_KERNEL depends on DEBUG_KERNEL

View File

@ -109,6 +109,8 @@ static inline bool efi_is_native(void)
return IS_ENABLED(CONFIG_X86_64) == efi_enabled(EFI_64BIT); return IS_ENABLED(CONFIG_X86_64) == efi_enabled(EFI_64BIT);
} }
extern struct console early_efi_console;
#else #else
/* /*
* IF EFI is not configured, have the EFI calls return -ENOSYS. * IF EFI is not configured, have the EFI calls return -ENOSYS.

View File

@ -17,6 +17,8 @@
#include <asm/mrst.h> #include <asm/mrst.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
#include <linux/usb/ehci_def.h> #include <linux/usb/ehci_def.h>
#include <linux/efi.h>
#include <asm/efi.h>
/* Simple VGA output */ /* Simple VGA output */
#define VGABASE (__ISA_IO_base + 0xb8000) #define VGABASE (__ISA_IO_base + 0xb8000)
@ -234,6 +236,11 @@ static int __init setup_early_printk(char *buf)
early_console_register(&early_hsu_console, keep); early_console_register(&early_hsu_console, keep);
} }
#endif #endif
#ifdef CONFIG_EARLY_PRINTK_EFI
if (!strncmp(buf, "efi", 3))
early_console_register(&early_efi_console, keep);
#endif
buf++; buf++;
} }
return 0; return 0;

View File

@ -1,2 +1,3 @@
obj-$(CONFIG_EFI) += efi.o efi_$(BITS).o efi_stub_$(BITS).o obj-$(CONFIG_EFI) += efi.o efi_$(BITS).o efi_stub_$(BITS).o
obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
obj-$(CONFIG_EARLY_PRINTK_EFI) += early_printk.o

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2013 Intel Corporation; author Matt Fleming
*
* This file is part of the Linux kernel, and is made available under
* the terms of the GNU General Public License version 2.
*/
#include <linux/console.h>
#include <linux/efi.h>
#include <linux/font.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <asm/setup.h>
static const struct font_desc *font;
static u32 efi_x, efi_y;
static __init void early_efi_clear_scanline(unsigned int y)
{
unsigned long base, *dst;
u16 len;
base = boot_params.screen_info.lfb_base;
len = boot_params.screen_info.lfb_linelength;
dst = early_ioremap(base + y*len, len);
if (!dst)
return;
memset(dst, 0, len);
early_iounmap(dst, len);
}
static __init void early_efi_scroll_up(void)
{
unsigned long base, *dst, *src;
u16 len;
u32 i, height;
base = boot_params.screen_info.lfb_base;
len = boot_params.screen_info.lfb_linelength;
height = boot_params.screen_info.lfb_height;
for (i = 0; i < height - font->height; i++) {
dst = early_ioremap(base + i*len, len);
if (!dst)
return;
src = early_ioremap(base + (i + font->height) * len, len);
if (!src) {
early_iounmap(dst, len);
return;
}
memmove(dst, src, len);
early_iounmap(src, len);
early_iounmap(dst, len);
}
}
static void early_efi_write_char(u32 *dst, unsigned char c, unsigned int h)
{
const u32 color_black = 0x00000000;
const u32 color_white = 0x00ffffff;
const u8 *src;
u8 s8;
int m;
src = font->data + c * font->height;
s8 = *(src + h);
for (m = 0; m < 8; m++) {
if ((s8 >> (7 - m)) & 1)
*dst = color_white;
else
*dst = color_black;
dst++;
}
}
static __init void
early_efi_write(struct console *con, const char *str, unsigned int num)
{
struct screen_info *si;
unsigned long base;
unsigned int len;
const char *s;
void *dst;
base = boot_params.screen_info.lfb_base;
si = &boot_params.screen_info;
len = si->lfb_linelength;
while (num) {
unsigned int linemax;
unsigned int h, count = 0;
for (s = str; *s && *s != '\n'; s++) {
if (count == num)
break;
count++;
}
linemax = (si->lfb_width - efi_x) / font->width;
if (count > linemax)
count = linemax;
for (h = 0; h < font->height; h++) {
unsigned int n, x;
dst = early_ioremap(base + (efi_y + h) * len, len);
if (!dst)
return;
s = str;
n = count;
x = efi_x;
while (n-- > 0) {
early_efi_write_char(dst + x*4, *s, h);
x += font->width;
s++;
}
early_iounmap(dst, len);
}
num -= count;
efi_x += count * font->width;
str += count;
if (num > 0 && *s == '\n') {
efi_x = 0;
efi_y += font->height;
str++;
num--;
}
if (efi_x >= si->lfb_width) {
efi_x = 0;
efi_y += font->height;
}
if (efi_y + font->height >= si->lfb_height) {
u32 i;
efi_y -= font->height;
early_efi_scroll_up();
for (i = 0; i < font->height; i++)
early_efi_clear_scanline(efi_y + i);
}
}
}
static __init int early_efi_setup(struct console *con, char *options)
{
struct screen_info *si;
u16 xres, yres;
u32 i;
si = &boot_params.screen_info;
xres = si->lfb_width;
yres = si->lfb_height;
/*
* early_efi_write_char() implicitly assumes a framebuffer with
* 32-bits per pixel.
*/
if (si->lfb_depth != 32)
return -ENODEV;
font = get_default_font(xres, yres, -1, -1);
if (!font)
return -ENODEV;
efi_y = rounddown(yres, font->height) - font->height;
for (i = 0; i < (yres - efi_y) / font->height; i++)
early_efi_scroll_up();
return 0;
}
struct console early_efi_console = {
.name = "earlyefi",
.write = early_efi_write,
.setup = early_efi_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
};