arm64: Add simple earlyprintk support
authorCatalin Marinas <catalin.marinas@arm.com>
Tue, 23 Oct 2012 13:55:08 +0000 (14:55 +0100)
committerCatalin Marinas <catalin.marinas@arm.com>
Tue, 22 Jan 2013 17:51:01 +0000 (17:51 +0000)
This patch adds support for "earlyprintk=" parameter on the kernel
command line. The format is:

  earlyprintk=<name>[,<addr>][,<options>]

where <name> is the name of the (UART) device, e.g. "pl011", <addr> is
the I/O address. The <options> aren't currently used.

The mapping of the earlyprintk device is done very early during kernel
boot and there are restrictions on which functions it can call. A
special early_io_map() function is added which creates the mapping from
the pre-defined EARLY_IOBASE to the device I/O address passed via the
kernel parameter. The pgd entry corresponding to EARLY_IOBASE is
pre-populated in head.S during kernel boot.

Only PL011 is currently supported and it is assumed that the interface
is already initialised by the boot loader before the kernel is started.

Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Documentation/arm64/memory.txt
arch/arm64/Kconfig.debug
arch/arm64/include/asm/io.h
arch/arm64/include/asm/memory.h
arch/arm64/include/asm/mmu.h
arch/arm64/kernel/Makefile
arch/arm64/kernel/early_printk.c [new file with mode: 0644]
arch/arm64/kernel/head.S
arch/arm64/mm/mmu.c

index d758702fc03c78b0cc88ef9468b8c477cc17b8d1..5f583af0a6e184cc568b492e7853b282b8a4b4e3 100644 (file)
@@ -35,6 +35,8 @@ ffffffbc00000000      ffffffbdffffffff           8GB          vmemmap
 
 ffffffbe00000000       ffffffbffbbfffff          ~8GB          [guard, future vmmemap]
 
+ffffffbffbc00000       ffffffbffbdfffff           2MB          earlyprintk device
+
 ffffffbffbe00000       ffffffbffbe0ffff          64KB          PCI I/O space
 
 ffffffbbffff0000       ffffffbcffffffff          ~2MB          [guard]
index d7553f2bda6684ea7990524d7e096f9384132524..79871cd78da8fbabf8fb54120567e13205670a58 100644 (file)
@@ -24,4 +24,13 @@ config DEBUG_STACK_USAGE
          Enables the display of the minimum amount of free stack which each
          task has ever had available in the sysrq-T output.
 
+config EARLY_PRINTK
+       bool "Early printk support"
+       default y
+       help
+         Say Y here if you want to have an early console using the
+         earlyprintk=<name>[,<addr>][,<options>] kernel parameter. It
+         is assumed that the early console device has been initialised
+         by the boot loader prior to starting the Linux kernel.
+
 endmenu
index d2f05a608274534a08180fc3866658a225bb560c..57f12c991de2e51fd2faa99f972824322489dfb7 100644 (file)
@@ -230,6 +230,9 @@ extern void __iounmap(volatile void __iomem *addr);
 #define ioremap_wc(addr, size)         __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC))
 #define iounmap                                __iounmap
 
+#define PROT_SECT_DEFAULT      (PMD_TYPE_SECT | PMD_SECT_AF)
+#define PROT_SECT_DEVICE_nGnRE (PROT_SECT_DEFAULT | PTE_PXN | PTE_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE))
+
 #define ARCH_HAS_IOREMAP_WC
 #include <asm-generic/iomap.h>
 
index 1cac16a001cb326910f31186e23bba85ba511a31..381f556b664ef9fa0f6ad05b39396ebc5b493d6a 100644 (file)
@@ -43,6 +43,7 @@
 #define PAGE_OFFSET            UL(0xffffffc000000000)
 #define MODULES_END            (PAGE_OFFSET)
 #define MODULES_VADDR          (MODULES_END - SZ_64M)
+#define EARLYCON_IOBASE                (MODULES_VADDR - SZ_4M)
 #define VA_BITS                        (39)
 #define TASK_SIZE_64           (UL(1) << VA_BITS)
 
index d4f7fd5b9e335cb3e6bb3cd1b826871595523681..2494fc01896a2f0dba01f57cd87e3bd0877ab8b9 100644 (file)
@@ -26,5 +26,6 @@ typedef struct {
 
 extern void paging_init(void);
 extern void setup_mm_for_reboot(void);
+extern void __iomem *early_io_map(phys_addr_t phys, unsigned long virt);
 
 #endif
index 74239c31e25a3e1ae380ab2cf1494a185fa1d7fd..a1cace45e97076c859b6bd1a0b16f5a7c52b4c64 100644 (file)
@@ -17,6 +17,7 @@ arm64-obj-$(CONFIG_MODULES)           += arm64ksyms.o module.o
 arm64-obj-$(CONFIG_SMP)                        += smp.o
 arm64-obj-$(CONFIG_HW_PERF_EVENTS)     += perf_event.o
 arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o
+arm64-obj-$(CONFIG_EARLY_PRINTK)       += early_printk.o
 
 obj-y                                  += $(arm64-obj-y) vdso/
 obj-m                                  += $(arm64-obj-m)
diff --git a/arch/arm64/kernel/early_printk.c b/arch/arm64/kernel/early_printk.c
new file mode 100644 (file)
index 0000000..7e320a2
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Earlyprintk support.
+ *
+ * Copyright (C) 2012 ARM Ltd.
+ * Author: Catalin Marinas <catalin.marinas@arm.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+
+#include <linux/amba/serial.h>
+
+static void __iomem *early_base;
+static void (*printch)(char ch);
+
+/*
+ * PL011 single character TX.
+ */
+static void pl011_printch(char ch)
+{
+       while (readl_relaxed(early_base + UART01x_FR) & UART01x_FR_TXFF)
+               ;
+       writeb_relaxed(ch, early_base + UART01x_DR);
+       while (readl_relaxed(early_base + UART01x_FR) & UART01x_FR_BUSY)
+               ;
+}
+
+struct earlycon_match {
+       const char *name;
+       void (*printch)(char ch);
+};
+
+static const struct earlycon_match earlycon_match[] __initconst = {
+       { .name = "pl011", .printch = pl011_printch, },
+       {}
+};
+
+static void early_write(struct console *con, const char *s, unsigned n)
+{
+       while (n-- > 0) {
+               if (*s == '\n')
+                       printch('\r');
+               printch(*s);
+               s++;
+       }
+}
+
+static struct console early_console = {
+       .name =         "earlycon",
+       .write =        early_write,
+       .flags =        CON_PRINTBUFFER | CON_BOOT,
+       .index =        -1,
+};
+
+/*
+ * Parse earlyprintk=... parameter in the format:
+ *
+ *   <name>[,<addr>][,<options>]
+ *
+ * and register the early console. It is assumed that the UART has been
+ * initialised by the bootloader already.
+ */
+static int __init setup_early_printk(char *buf)
+{
+       const struct earlycon_match *match = earlycon_match;
+       phys_addr_t paddr = 0;
+
+       if (!buf) {
+               pr_warning("No earlyprintk arguments passed.\n");
+               return 0;
+       }
+
+       while (match->name) {
+               size_t len = strlen(match->name);
+               if (!strncmp(buf, match->name, len)) {
+                       buf += len;
+                       break;
+               }
+               match++;
+       }
+       if (!match->name) {
+               pr_warning("Unknown earlyprintk arguments: %s\n", buf);
+               return 0;
+       }
+
+       /* I/O address */
+       if (!strncmp(buf, ",0x", 3)) {
+               char *e;
+               paddr = simple_strtoul(buf + 1, &e, 16);
+               buf = e;
+       }
+       /* no options parsing yet */
+
+       if (paddr)
+               early_base = early_io_map(paddr, EARLYCON_IOBASE);
+
+       printch = match->printch;
+       register_console(&early_console);
+
+       return 0;
+}
+
+early_param("earlyprintk", setup_early_printk);
index 368ad1f7c36c3c9e0e14c09e77d5816beb2cc1af..0a0a4975682622f2bc8bddd90a6146247656726e 100644 (file)
 
 #ifdef CONFIG_ARM64_64K_PAGES
 #define MM_MMUFLAGS    PTE_ATTRINDX(MT_NORMAL) | PTE_FLAGS
-#define IO_MMUFLAGS    PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_XN | PTE_FLAGS
 #else
 #define MM_MMUFLAGS    PMD_ATTRINDX(MT_NORMAL) | PMD_FLAGS
-#define IO_MMUFLAGS    PMD_ATTRINDX(MT_DEVICE_nGnRE) | PMD_SECT_XN | PMD_FLAGS
 #endif
 
 /*
@@ -368,6 +366,7 @@ ENDPROC(__calc_phys_offset)
  *   - identity mapping to enable the MMU (low address, TTBR0)
  *   - first few MB of the kernel linear mapping to jump to once the MMU has
  *     been enabled, including the FDT blob (TTBR1)
+ *   - UART mapping if CONFIG_EARLY_PRINTK is enabled (TTBR1)
  */
 __create_page_tables:
        pgtbl   x25, x26, x24                   // idmap_pg_dir and swapper_pg_dir addresses
@@ -420,6 +419,15 @@ __create_page_tables:
        sub     x6, x6, #1                      // inclusive range
        create_block_map x0, x7, x3, x5, x6
 1:
+#ifdef CONFIG_EARLY_PRINTK
+       /*
+        * Create the pgd entry for the UART mapping. The full mapping is done
+        * later based earlyprintk kernel parameter.
+        */
+       ldr     x5, =EARLYCON_IOBASE            // UART virtual address
+       add     x0, x26, #2 * PAGE_SIZE         // section table address
+       create_pgd_entry x26, x0, x5, x6, x7
+#endif
        ret
 ENDPROC(__create_page_tables)
        .ltorg
index a6885d896ab661b16d90ff861eb154b231ccad21..f4dd585898c5c36529ea69290ac5baa19bc0dd4b 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/nodemask.h>
 #include <linux/memblock.h>
 #include <linux/fs.h>
+#include <linux/io.h>
 
 #include <asm/cputype.h>
 #include <asm/sections.h>
@@ -251,6 +252,47 @@ static void __init create_mapping(phys_addr_t phys, unsigned long virt,
        } while (pgd++, addr = next, addr != end);
 }
 
+#ifdef CONFIG_EARLY_PRINTK
+/*
+ * Create an early I/O mapping using the pgd/pmd entries already populated
+ * in head.S as this function is called too early to allocated any memory. The
+ * mapping size is 2MB with 4KB pages or 64KB or 64KB pages.
+ */
+void __iomem * __init early_io_map(phys_addr_t phys, unsigned long virt)
+{
+       unsigned long size, mask;
+       bool page64k = IS_ENABLED(ARM64_64K_PAGES);
+       pgd_t *pgd;
+       pud_t *pud;
+       pmd_t *pmd;
+       pte_t *pte;
+
+       /*
+        * No early pte entries with !ARM64_64K_PAGES configuration, so using
+        * sections (pmd).
+        */
+       size = page64k ? PAGE_SIZE : SECTION_SIZE;
+       mask = ~(size - 1);
+
+       pgd = pgd_offset_k(virt);
+       pud = pud_offset(pgd, virt);
+       if (pud_none(*pud))
+               return NULL;
+       pmd = pmd_offset(pud, virt);
+
+       if (page64k) {
+               if (pmd_none(*pmd))
+                       return NULL;
+               pte = pte_offset_kernel(pmd, virt);
+               set_pte(pte, __pte((phys & mask) | PROT_DEVICE_nGnRE));
+       } else {
+               set_pmd(pmd, __pmd((phys & mask) | PROT_SECT_DEVICE_nGnRE));
+       }
+
+       return (void __iomem *)((virt & mask) + (phys & ~mask));
+}
+#endif
+
 static void __init map_mem(void)
 {
        struct memblock_region *reg;