diff options
Diffstat (limited to 'common/console.c')
| -rw-r--r-- | common/console.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/common/console.c b/common/console.c new file mode 100644 index 0000000..2e9bfa6 --- /dev/null +++ b/common/console.c @@ -0,0 +1,191 @@ +/* + * console.c - the console dprintf writes to. the dtb picks the uart, + * chosen/stdout-path first, any arm,pl011 node as fallback. + * + * Copyright (c) 2008 Travis Geiselbrecht + * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <sys/types.h> +#include <stdint.h> +#include <libfdt.h> +#include <printf.h> +#include <debug.h> +#include <reg.h> +#include <drivers/serial/pl011.h> + +static uintptr_t tb_uart_base; +static uint32 tb_uart_clock; +static bool tb_uart_ready; + +/* + * read one reg pair out of a node. cells are u32s in the blob, address + * and size widths come from the parent #address-cells/#size-cells, + * 2/1 is what every board we care about uses, qemu virt included. + */ +static int tb_read_reg(const void *blob, int node, int index, + uintptr_t *base, uint32 *size) +{ + const fdt32_t *prop; + int len; + int offset; + + prop = fdt_getprop(blob, node, "reg", &len); + if (!prop) + return -1; + + /* + * a reg pair here is <addr_hi addr_lo size>, 3 cells, 2 for the + * address and 1 for the size. index 0 is the pair we want. + */ + offset = index * 3; + if (offset + 3 > len / 4) + return -1; + + *base = ((uintptr_t)fdt32_to_cpu(prop[offset]) << 32) | + fdt32_to_cpu(prop[offset + 1]); + *size = fdt32_to_cpu(prop[offset + 2]); + return 0; +} + +static int tb_console_from_stdout_path(const void *blob) +{ + const char *path; + const char *p; + char buf[32]; + int node; + int chosen; + int len; + + chosen = fdt_path_offset(blob, "/chosen"); + if (chosen < 0) + return -1; + + path = fdt_getprop(blob, chosen, "stdout-path", &len); + if (!path || len <= 0) + return -1; + + /* the path can carry :baud after the node, cut it */ + p = strchr(path, ':'); + len = p ? (int)(p - path) : strlen(path); + if (len >= (int)sizeof(buf)) + len = sizeof(buf) - 1; + memcpy(buf, path, len); + buf[len] = 0; + + /* a leading / is a full path, otherwise it's an alias */ + if (buf[0] == '/') + node = fdt_path_offset(blob, buf); + else + node = fdt_path_offset(blob, fdt_get_alias(blob, buf)); + if (node < 0) + return -1; + + return node; +} + +static int tb_console_scan(const void *blob) +{ + const char *compat; + int node; + int len; + + fdt_for_each_subnode(node, blob, 0) { + compat = fdt_getprop(blob, node, "compatible", &len); + if (!compat) + continue; + if (fdt_stringlist_contains(compat, len, "arm,pl011")) + return node; + } + return -1; +} + +int tb_console_init(const void *dtb) +{ + const fdt32_t *clk; + int node; + uint32 size; + uint32 baud; + int len; + + node = tb_console_from_stdout_path(dtb); + if (node < 0) + node = tb_console_scan(dtb); + if (node < 0) + return -1; + + if (tb_read_reg(dtb, node, 0, &tb_uart_base, &size)) + return -1; + + clk = fdt_getprop(dtb, node, "clock-frequency", &len); + tb_uart_clock = (clk && len == 4) ? fdt32_to_cpu(*clk) : 24000000; + + { + const fdt32_t *cs; + int cslen; + + cs = fdt_getprop(dtb, node, "current-speed", &cslen); + baud = (cs && cslen == 4) ? fdt32_to_cpu(*cs) : 115200; + } + + pl011_init(tb_uart_base, tb_uart_clock, baud); + tb_uart_ready = true; + return 0; +} + +static int tb_console_out(char c, void *state) +{ + if (c == '\n') + pl011_putc(tb_uart_base, '\r'); + pl011_putc(tb_uart_base, c); + return 1; +} + +int _dprintf(const char *fmt, ...) +{ + va_list ap; + int ret; + + if (!tb_uart_ready) + return 0; + + va_start(ap, fmt); + ret = _dvprintf(fmt, ap); + va_end(ap); + return ret; +} + +int _dvprintf(const char *fmt, va_list ap) +{ + if (!tb_uart_ready) + return 0; + return _printf_engine(tb_console_out, NULL, fmt, ap); +} + +void platform_halt(void) +{ + for (;;) + __asm__ volatile("wfe"); +} |
