x86/efi: Add EFI framebuffer earlyprintk support
authorMatt Fleming <matt.fleming@intel.com>
Fri, 4 Oct 2013 08:36:56 +0000 (09:36 +0100)
committerMatt Fleming <matt.fleming@intel.com>
Mon, 28 Oct 2013 18:09:58 +0000 (18:09 +0000)
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>
Documentation/kernel-parameters.txt
arch/x86/Kconfig.debug
arch/x86/include/asm/efi.h
arch/x86/kernel/early_printk.c
arch/x86/platform/efi/Makefile
arch/x86/platform/efi/early_printk.c [new file with mode: 0644]

index 7f9d4f5..7a0f202 100644 (file)
@@ -792,6 +792,7 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 
        earlyprintk=    [X86,SH,BLACKFIN,ARM]
                        earlyprintk=vga
+                       earlyprintk=efi
                        earlyprintk=xen
                        earlyprintk=serial[,ttySn[,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
                        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
                        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
                        very good.
 
-                       The VGA output is eventually overwritten by the real
-                       console.
+                       The VGA and EFI output is eventually overwritten by
+                       the real console.
 
                        The xen output can only be used by Xen PV guests.
 
index 78d91af..0f3621e 100644 (file)
@@ -59,6 +59,16 @@ config EARLY_PRINTK_DBGP
          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.
 
+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
        bool "Export kernel pagetable layout to userspace via debugfs"
        depends on DEBUG_KERNEL
index 0062a01..65c6e6e 100644 (file)
@@ -109,6 +109,8 @@ static inline bool efi_is_native(void)
        return IS_ENABLED(CONFIG_X86_64) == efi_enabled(EFI_64BIT);
 }
 
+extern struct console early_efi_console;
+
 #else
 /*
  * IF EFI is not configured, have the EFI calls return -ENOSYS.
index d15f575..6d3d200 100644 (file)
@@ -17,6 +17,8 @@
 #include <asm/mrst.h>
 #include <asm/pgtable.h>
 #include <linux/usb/ehci_def.h>
+#include <linux/efi.h>
+#include <asm/efi.h>
 
 /* Simple VGA output */
 #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);
                }
 #endif
+#ifdef CONFIG_EARLY_PRINTK_EFI
+               if (!strncmp(buf, "efi", 3))
+                       early_console_register(&early_efi_console, keep);
+#endif
+
                buf++;
        }
        return 0;
index 6db1cc4..b7b0b35 100644 (file)
@@ -1,2 +1,3 @@
 obj-$(CONFIG_EFI)              += efi.o efi_$(BITS).o efi_stub_$(BITS).o
 obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
+obj-$(CONFIG_EARLY_PRINTK_EFI) += early_printk.o
diff --git a/arch/x86/platform/efi/early_printk.c b/arch/x86/platform/efi/early_printk.c
new file mode 100644 (file)
index 0000000..6599a00
--- /dev/null
@@ -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,
+};