This is the bulk of the Earlycon implementation, it is mostly in the decompressor. It is always built in, a requirement to simplify the various migrations to Earlycon. earlycon_setup() takes care of finding the named driver, relocate the required functions and setup a nice albeit limited environment for the driver execution. In this patchset earlycon_setup() is explicitly invoked by the platform code in mach/uncompress.h but the plan is to always invoke it using the information coming from the cmdline or DT. Once uncompress.h calls earlycon_setup() - this means it expects to use a driver provided somewhere else - it doesn't need to provide putc/flush any more and can signal this to the decompressor through flag EARLYCON_STATIC_SETUP. Said decompressor can be configured to always ignore legacy putc/flush through option CONFIG_DEBUG_LL_EARLYCON. Signed-off-by: Domenico Andreoli --- arch/arm/Kconfig.debug | 13 +++ arch/arm/boot/compressed/Makefile | 2 arch/arm/boot/compressed/misc.c | 145 +++++++++++++++++++++++++++++++++++++- arch/arm/tools/gen-mach-types | 2 + include/linux/earlycon.h | 62 ++++++++++++++++ 5 files changed, 220 insertions(+), 4 deletions(-) Index: b/include/linux/earlycon.h =================================================================== --- /dev/null +++ b/include/linux/earlycon.h @@ -0,0 +1,62 @@ +/* + * Definitions for earlycon driver implementers + * + * Copyright (C) 2012 Domenico Andreoli + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef LINUX_EARLYCON_H +#define LINUX_EARLYCON_H + +#include + +struct earlycon_desc; + +struct earlycon_drv { + const struct earlycon_desc *desc; + unsigned int machid; + unsigned long base; + void *data; + int (*probe)(struct earlycon_drv *drv); + void (*putc)(struct earlycon_drv *drv, int ch); + void (*flush)(struct earlycon_drv *drv); + void *(*reloc_text)(void *); +}; + +struct earlycon_desc { + const char name[16]; + int (*probe)(struct earlycon_drv *drv); + void (*putc)(struct earlycon_drv *drv, int ch); + void (*flush)(struct earlycon_drv *drv); +}; + +#ifndef EARLYCON_IN_DECOMPRESSOR +# define __machine_arch_type (drv->machid) +#endif + +#define RELOC_FUN(_f) ((typeof(&_f))(drv->reloc_text(_f))) + +/* + * Set of macros to define earlycon features. This is built into + * a table by the linker. + */ +#define EARLYCON_START(_name) \ +static const struct earlycon_desc __earlycon_desc \ + __used \ + __attribute__((__section__(".earlycon.info.desc"))) = { \ + .name = _name, + +#define EARLYCON_END \ +}; + +#define __earlyconinit __section(.earlycon.info.text) + +#endif /* LINUX_EARLYCON_H */ Index: b/arch/arm/boot/compressed/misc.c =================================================================== --- a/arch/arm/boot/compressed/misc.c +++ b/arch/arm/boot/compressed/misc.c @@ -22,10 +22,18 @@ unsigned int __machine_arch_type; #include #include +static void __used setup_earlycon(const char *drvname, unsigned long base); static void putstr(const char *ptr); +static void putn(unsigned long z); extern void error(char *x); +#ifndef CONFIG_DEBUG_LL_EARLYCON #include +#endif + +#define EARLYCON_IN_DECOMPRESSOR +#include +#include #ifdef CONFIG_DEBUG_ICEDCC @@ -83,17 +91,115 @@ static void icedcc_putc(int ch) #define putc(ch) icedcc_putc(ch) #endif +struct earlycon_drv earlycon; + +extern char __earlycon_text_begin; +extern void *__earlycon_text_base; +extern char __earlycon_drvdata_begin; +extern char __earlycon_drvdata_end; +extern struct earlycon_desc __earlycon_desc_begin[]; +extern struct earlycon_desc __earlycon_desc_end[]; + +#define for_each_earlycon_desc(p) \ + for (p = __earlycon_desc_begin; p < __earlycon_desc_end; p++) + +static void dummy(void) +{ +} + +static void *fix_text_ptr(void *p) +{ + if (!p) + return p; + + return &__earlycon_text_begin + (p - __earlycon_text_base); +} + +static void *alloc_drvdata(void) +{ + /* do our best to find some memory for the driver state */ + memset(&__earlycon_drvdata_begin, 0, + &__earlycon_drvdata_end - &__earlycon_drvdata_begin); + return &__earlycon_drvdata_begin; +} + +static void setup_earlycon(const char *drvname, unsigned long base) +{ + const struct earlycon_desc *p; + + /* we already have a valid candidate */ + if (earlycon.desc) + return; + + earlycon.data = alloc_drvdata(); + earlycon.machid = __machine_arch_type; + earlycon.reloc_text = fix_text_ptr; + + for_each_earlycon_desc(p) { + if (strcmp(drvname, p->name)) + continue; + + earlycon.base = base; + earlycon.probe = fix_text_ptr(p->probe); + + if (earlycon.probe && earlycon.probe(&earlycon) < 0) + continue; + + earlycon.desc = p; + earlycon.putc = fix_text_ptr(p->putc); + earlycon.flush = fix_text_ptr(p->flush); + break; + } + + if (!earlycon.putc) + earlycon.putc = (void *) dummy; + if (!earlycon.flush) + earlycon.flush = (void *) dummy; + + /* debug code */ + putstr("Earlycon drivers:"); + for_each_earlycon_desc(p) { + putstr("\n "); putstr(p->name); + + if (p == earlycon.desc) { + putstr(" [in use at "); + putn(earlycon.base); putstr("]"); + } + } + putstr("\n"); +} + +static void setup_earlycon_runtime(void) +{ + /* TODO: extract earlycon config from the kernel cmdline or DT */ + // setup_earlycon(cmdline_drvname, cmdline_base); +} + static void putstr(const char *ptr) { char c; while ((c = *ptr++) != '\0') { if (c == '\n') - putc('\r'); - putc(c); + earlycon.putc(&earlycon, '\r'); + earlycon.putc(&earlycon, c); } - flush(); + earlycon.flush(&earlycon); +} + +static void putn(unsigned long z) +{ + int i; + char x; + + earlycon.putc(&earlycon, '0'); + earlycon.putc(&earlycon, 'x'); + for (i=0;i<8;i++) { + x='0'+((z>>((7-i)*4))&0xf); + if (x>'9') x=x-'0'+'a'-10; + earlycon.putc(&earlycon, x); + } } /* @@ -129,6 +235,29 @@ asmlinkage void __div0(void) extern int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x)); +#if defined(CONFIG_DEBUG_LL_EARLYCON) || defined(EARLYCON_STATIC_SETUP) + +static inline void fallback_putc(struct earlycon_drv *drv, int ch) +{ +} + +static inline void fallback_flush(struct earlycon_drv *drv) +{ +} + +#else + +static inline void fallback_putc(struct earlycon_drv *drv, int ch) +{ + putc(ch); +} + +static inline void fallback_flush(struct earlycon_drv *drv) +{ + flush(); +} + +#endif void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, @@ -142,7 +271,17 @@ decompress_kernel(unsigned long output_s free_mem_end_ptr = free_mem_ptr_end_p; __machine_arch_type = arch_id; + setup_earlycon_runtime(); + +#ifndef CONFIG_DEBUG_LL_EARLYCON arch_decomp_setup(); +#endif + + /* we have not a valid candidate, use the fallback ops */ + if (!earlycon.desc) { + earlycon.putc = fallback_putc; + earlycon.flush = fallback_flush; + } putstr("Uncompressing Linux..."); ret = do_decompress(input_data, input_data_end - input_data, Index: b/arch/arm/boot/compressed/Makefile =================================================================== --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -23,7 +23,7 @@ endif AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET) HEAD = head.o -OBJS += misc.o decompress.o +OBJS += misc.o decompress.o earlycon.o FONTC = $(srctree)/drivers/video/console/font_acorn_8x8.c # string library code (-Os is enforced to keep it much smaller) Index: b/arch/arm/tools/gen-mach-types =================================================================== --- a/arch/arm/tools/gen-mach-types +++ b/arch/arm/tools/gen-mach-types @@ -29,8 +29,10 @@ END { printf("#ifndef __ASM_ARM_MACH_TYPE_H\n"); printf("#define __ASM_ARM_MACH_TYPE_H\n\n"); printf("#ifndef __ASSEMBLY__\n"); + printf("#ifndef __machine_arch_type\n"); printf("/* The type of machine we're running on */\n"); printf("extern unsigned int __machine_arch_type;\n"); + printf("#endif\n"); printf("#endif\n\n"); printf("/* see arch/arm/kernel/arch.c for a description of these */\n"); Index: b/arch/arm/Kconfig.debug =================================================================== --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -310,6 +310,19 @@ choice endchoice +config DEBUG_LL_EARLYCON + bool "Earlycon low-level debugging functions" + depends on DEBUG_LL + help + Earlycon is a thin I/O layer used in the early stage of the + kernel loading, before the kernel is decompressed. + + Earlycon does its best to support legacy static definitions + of the debug UARTs but it really shines on generic kernels + where no legacy support is allowed. + + Say Y here if you want to build such a generic kernel. + config EARLY_PRINTK bool "Early printk" depends on DEBUG_LL