// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 Broadcom Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * The log console implements both a bootconsole (earlycon) and a console that * writes the kernel log buffer to volatile memory. It's configured with a * kernel boot parameter (earlylog=address,size). Both an earlycon and earlylog * can be configured at the same time. Any output to earlycon will also be sent * to earlylog. earlylog is disabled when the "normal" console is available and * initialized by the kernel. */ #include #include #include #include #include /* logging signature */ #define LOG_SIG_OFFSET 0x0000 #define LOG_SIG_VAL 0x75767971 /* current logging offset that points to where new logs should be added */ #define LOG_OFF_OFFSET 0x0004 /* current logging length (excluding header) */ #define LOG_LEN_OFFSET 0x0008 #define LOG_HEADER_LEN 12 /* * @base: CPU virtual address of the memory where log is saved * @is_initialized: flag that indicates logging has been initialized * @addr: physical address of the memory where log is saved * @max_size: maximum log buffer size */ struct console_log_data { void *base; bool is_initialized; phys_addr_t addr; u32 max_size; }; static struct console_log_data log_data; static struct console earlycon_log = { .name = "earlycon_log", .flags = CON_PRINTBUFFER | CON_BOOT, .index = 0, .data = &log_data, }; static void log_write(struct console *console, const char *s, unsigned int count) { /* Code removed - doesn't matter. */ } static int early_log_init(struct console *console) { struct console_log_data *log = console->data; u32 val; /* Bail out if invalid parameters are seen */ if (log->is_initialized || !log->addr || !log->max_size) return -EINVAL; log->base = early_memremap(log->addr, log->max_size); if (!log->base) return -ENOMEM; /* Bail out if no header signature can be found */ val = readl(log->base + LOG_SIG_OFFSET); if (val != LOG_SIG_VAL) { early_memunmap(log->base, log->max_size); return -EINVAL; } /* Register the boot console and ensure it was enabled. */ console->write = log_write; register_console(console); if (!(console->flags & CON_ENABLED)) { early_memunmap(log->base, log->max_size); return -EINVAL; } log->is_initialized = true; return 0; } static int __init param_setup_earlylog(char *buf) { struct console_log_data *log = &log_data; phys_addr_t addr; u32 size; char *end; int err = -EINVAL; if (!buf || !buf[0]) return err; /* Parse boot param (eg. earlylog=address,size) */ addr = memparse(buf, &end); if (*end == ',') { size = memparse(end + 1, NULL); if (addr && size) { log->addr = addr; log->max_size = size; } else return err; } err = early_log_init(&earlycon_log); if (err < 0) return err; return 0; } early_param("earlylog", param_setup_earlylog); /* * The console is enabled by default because it doesn't need to be selected on * the kernel command line (eg- console=) and therefore wouldn't be enabled * automatically when registered. */ static struct console console_log = { .name = "console_log", .flags = CON_PRINTBUFFER | CON_ENABLED, .index = 0, .write = log_write, .data = &log_data, }; static int log_console_init(void) { struct console_log_data *log = console_log.data; /* console_log depends on earlycon_log being initialized. */ if (!log->is_initialized || !log->addr || !log->max_size) return -EINVAL; /* Clean up earlycon_log. */ unregister_console(&earlycon_log); early_memunmap(log->base, log->max_size); /* * The early base address is no longer usable and must be mapped with * memremap now that it's available. */ log->base = memremap(log->addr, log->max_size, MEMREMAP_WB); if (!log->base) return -ENOMEM; register_console(&console_log); return 0; } console_initcall(log_console_init);