| /* |
| * Each architecture must implement puts() and exit() with the I/O |
| * devices exposed from QEMU, e.g. pl011 and chr-testdev. That's |
| * what's done here, along with initialization functions for those |
| * devices. |
| * |
| * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include <libcflat.h> |
| #include <devicetree.h> |
| #include <chr-testdev.h> |
| #include <config.h> |
| #include <asm/psci.h> |
| #include <asm/spinlock.h> |
| #include <asm/io.h> |
| |
| #include "io.h" |
| |
| static struct spinlock uart_lock; |
| /* |
| * Use this guess for the uart base in order to make an attempt at |
| * having earlier printf support. We'll overwrite it with the real |
| * base address that we read from the device tree later. This is |
| * the address we expect the virtual machine manager to put in |
| * its generated device tree. |
| */ |
| #define UART_EARLY_BASE (u8 *)(unsigned long)CONFIG_UART_EARLY_BASE |
| static volatile u8 *uart0_base = UART_EARLY_BASE; |
| bool is_pl011_uart; |
| |
| static void uart0_init_fdt(void) |
| { |
| /* |
| * kvm-unit-tests uses the uart only for output. Both uart models have |
| * the TX register at offset 0 from the base address, so there is no |
| * need to treat them separately. |
| */ |
| const char *compatible[] = {"arm,pl011", "ns16550a"}; |
| struct dt_pbus_reg base; |
| int i, ret; |
| |
| ret = dt_get_default_console_node(); |
| assert(ret >= 0 || ret == -FDT_ERR_NOTFOUND); |
| |
| if (ret == -FDT_ERR_NOTFOUND) { |
| |
| for (i = 0; i < ARRAY_SIZE(compatible); i++) { |
| ret = dt_pbus_get_base_compatible(compatible[i], &base); |
| assert(ret == 0 || ret == -FDT_ERR_NOTFOUND); |
| |
| if (ret == 0) |
| break; |
| } |
| |
| if (ret) { |
| printf("%s: Compatible uart not found in the device tree, " |
| "aborting...\n", __func__); |
| abort(); |
| } |
| |
| is_pl011_uart = (i == 0); |
| } else { |
| is_pl011_uart = !fdt_node_check_compatible(dt_fdt(), ret, |
| "arm,pl011"); |
| ret = dt_pbus_translate_node(ret, 0, &base); |
| assert(ret == 0); |
| } |
| |
| uart0_base = ioremap(base.addr, base.size); |
| } |
| |
| #ifdef CONFIG_EFI |
| |
| #include <acpi.h> |
| |
| static void uart0_init_acpi(void) |
| { |
| struct spcr_descriptor *spcr = find_acpi_table_addr(SPCR_SIGNATURE); |
| |
| assert_msg(spcr, "Unable to find ACPI SPCR"); |
| uart0_base = ioremap(spcr->serial_port.address, spcr->serial_port.bit_width); |
| } |
| #else |
| |
| static void uart0_init_acpi(void) |
| { |
| assert_msg(false, "ACPI not available"); |
| } |
| |
| #endif |
| |
| void io_init(void) |
| { |
| if (dt_available()) |
| uart0_init_fdt(); |
| else |
| uart0_init_acpi(); |
| |
| if (uart0_base != UART_EARLY_BASE) { |
| printf("WARNING: early print support may not work. " |
| "Found uart at %p, but early base is %p.\n", |
| uart0_base, UART_EARLY_BASE); |
| } |
| |
| chr_testdev_init(); |
| } |
| |
| void puts(const char *s) |
| { |
| spin_lock(&uart_lock); |
| while (*s) |
| writeb(*s++, uart0_base); |
| spin_unlock(&uart_lock); |
| } |
| |
| int __getchar(void) |
| { |
| int c = -1; |
| |
| spin_lock(&uart_lock); |
| |
| if (is_pl011_uart) { |
| if (!(readb(uart0_base + 6 * 4) & 0x10)) /* RX not empty? */ |
| c = readb(uart0_base); |
| } else { |
| if (readb(uart0_base + 5) & 0x01) /* RX data ready? */ |
| c = readb(uart0_base); |
| } |
| |
| spin_unlock(&uart_lock); |
| |
| return c; |
| } |
| |
| /* |
| * Defining halt to take 'code' as an argument guarantees that it will |
| * be in x0/r0 when we halt. That gives us a final chance to see the exit |
| * status while inspecting the halted unit test state. |
| */ |
| extern void halt(int code); |
| |
| void exit(int code) |
| { |
| /* |
| * Print the test return code in the following format which is |
| * consistent with powerpc and s390x. The runner can pick it |
| * up when chr-testdev is not present. |
| */ |
| printf("\nEXIT: STATUS=%d\n", ((code) << 1) | 1); |
| |
| chr_testdev_exit(code); |
| psci_system_off(); |
| halt(code); |
| __builtin_unreachable(); |
| } |