blob: 47a7f2269aa248bfac89e3b46fa26a0a325c53a7 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "platform/pc/debug.h"
#include <bits.h>
#include <lib/arch/intrin.h>
#include <lib/boot-options/boot-options.h>
#include <lib/cbuf.h>
#include <lib/debuglog.h>
#include <lib/uart/all.h>
#include <lib/zircon-internal/macros.h>
#include <platform.h>
#include <reg.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string-file.h>
#include <string.h>
#include <trace.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <cstdint>
#include <arch/x86.h>
#include <arch/x86/apic.h>
#include <dev/interrupt.h>
#include <kernel/lockdep.h>
#include <kernel/spinlock.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <ktl/algorithm.h>
#include <ktl/move.h>
#include <ktl/variant.h>
#include <phys/handoff.h>
#include <platform/console.h>
#include <platform/debug.h>
#include <platform/legacy_debug.h>
#include <platform/pc.h>
#include <vm/physmap.h>
#include <vm/vm_aspace.h>
#include "memory.h"
#include <ktl/enforce.h>
// Hardware details of the system's debug port.
struct DebugPort {
enum class Type {
// Unknown or disabled.
kNull,
// Debug port is a 16550-compatible UART using legacy PC ports.
kIoPort,
// Debug port is a 16550-compatible UART using MMIO.
kMmio,
};
Type type = Type::kNull;
// IRQ for UART. 0 indicates interrupts are not supported.
uint32_t irq = 0;
// State for IO port.
uint32_t io_port = 0;
// State for MMIO.
vaddr_t mem_addr = 0;
paddr_t phys_addr = 0;
};
// Debug port baud rate.
constexpr int kBaudRate = 115200;
// Hardware details of the system debug port.
static DebugPort gDebugPort;
// UART state.
static bool output_enabled = false;
Cbuf console_input_buf;
static uint32_t uart_fifo_depth;
static bool uart_tx_irq_enabled = false; // tx driven irq
static AutounsignalEvent uart_dputc_event{true};
namespace {
DECLARE_SINGLETON_SPINLOCK_WITH_TYPE(uart_tx_spinlock, MonitoredSpinLock);
} // namespace
// Read a single byte from the given UART register.
static uint8_t uart_read(uint8_t reg) {
DEBUG_ASSERT(gDebugPort.type == DebugPort::Type::kIoPort ||
gDebugPort.type == DebugPort::Type::kMmio);
switch (gDebugPort.type) {
case DebugPort::Type::kIoPort:
return (uint8_t)inp((uint16_t)(gDebugPort.io_port + reg));
case DebugPort::Type::kMmio: {
uintptr_t addr = reinterpret_cast<uintptr_t>(gDebugPort.mem_addr);
return (uint8_t)readl(addr + 4 * reg);
}
default:
return 0;
}
}
// Write a single byte to the given UART register.
static void uart_write(uint8_t reg, uint8_t val) {
DEBUG_ASSERT(gDebugPort.type == DebugPort::Type::kIoPort ||
gDebugPort.type == DebugPort::Type::kMmio);
switch (gDebugPort.type) {
case DebugPort::Type::kIoPort:
outp((uint16_t)(gDebugPort.io_port + reg), val);
break;
case DebugPort::Type::kMmio: {
uintptr_t addr = reinterpret_cast<uintptr_t>(gDebugPort.mem_addr);
writel(val, addr + 4 * reg);
break;
}
default:
break;
}
}
// Handle an interrupt from the UART.
//
// NOTE: Register access is not explicitly synchronized between the IRQ, TX, and
// RX paths. This is safe because none of the paths perform read-modify-write
// operations on the UART registers. Additionally, the TX and RX functions are
// largely independent. The only synchronization between IRQ and TX/RX is
// internal to the Cbuf and Event objects. It is critical to keep
// synchronization inside the IRQ path to a minimum, otherwise it is possible to
// introduce long spin periods in IRQ context that can seriously degrade system
// performance.
static void uart_irq_handler(void* arg) {
// see why we have gotten an irq
for (;;) {
uint8_t iir = uart_read(2);
// No interrupt pending.
if (BIT(iir, 0))
break;
// Reading LSR should clear Receiver Line Status signal, ID: 0b011
uint8_t lsr = uart_read(5);
// LSR Transmit Holder Register Empty
if (BIT(lsr, 5)) {
// disable the tx irq
uart_write(1, (1 << 0)); // just rx interrupt enable
// transmitter is empty, signal any waiting senders
uart_dputc_event.Signal();
}
// LSR Data Ready
for (; BIT(lsr, 0); lsr = uart_read(5)) {
unsigned char c = uart_read(0);
console_input_buf.WriteChar(c);
}
}
}
// Read all pending inputs from the UART.
static void platform_drain_debug_uart_rx() {
while (uart_read(5) & (1 << 0)) {
unsigned char c = uart_read(0);
console_input_buf.WriteChar(c);
}
}
static constexpr TimerSlack kSlack{ZX_MSEC(1), TIMER_SLACK_CENTER};
// Poll for inputs on the UART.
//
// Used for devices where the UART rx interrupt isn't available.
static void uart_rx_poll(Timer* t, zx_time_t now, void* arg) {
const Deadline deadline(zx_time_add_duration(now, ZX_MSEC(10)), kSlack);
t->Set(deadline, uart_rx_poll, NULL);
platform_drain_debug_uart_rx();
}
static Timer uart_rx_poll_timer;
// Create a polling thread for the UART.
static void platform_debug_start_uart_timer() {
static bool started = false;
if (!started) {
started = true;
const Deadline deadline = Deadline::after(ZX_MSEC(10), kSlack);
uart_rx_poll_timer.Set(deadline, uart_rx_poll, NULL);
}
}
// Setup the UART hardware.
static void init_uart() {
// configure the uart
int divisor = 115200 / kBaudRate;
// get basic config done so that tx functions
uart_write(1, 0); // mask all irqs
uart_write(3, 0x80); // set up to load divisor latch
uart_write(0, static_cast<uint8_t>(divisor)); // lsb
uart_write(1, static_cast<uint8_t>(divisor >> 8)); // msb
// enable FIFO, rx FIFO reset, tx FIFO reset, 16750 64 byte fifo enable,
// Rx FIFO irq trigger level at 14-bytes, must be done while divisor
// latch is enabled in order to write the 16750 64 byte fifo enable bit
uart_write(2, 0xe7);
uart_write(3, 3); // 8N1
// Drive flow control bits high since we don't actively manage them
uart_write(4, 0x3);
// figure out the fifo depth
uint8_t fcr = uart_read(2);
if (BITS_SHIFT(fcr, 7, 6) == 3 && BIT(fcr, 5)) {
// this is a 16750
uart_fifo_depth = 64;
} else if (BITS_SHIFT(fcr, 7, 6) == 3) {
// this is a 16550A
uart_fifo_depth = 16;
} else {
uart_fifo_depth = 1;
}
}
void X86UartInitEarly(const uart::all::Driver& serial) {
if (gBootOptions->experimental_serial_migration) {
return;
}
// Updates gDebugPort with the provided UART metadata, given by a variant of
// libuart driver types, each with methods to indicate the ZBI item type and
// payload.
constexpr auto set_debug_port = [](const auto& uart) {
const auto config = uart.config();
using config_type = ktl::decay_t<decltype(config)>;
if constexpr (ktl::is_same_v<config_type, zbi_dcfg_simple_t>) {
gDebugPort = {
.type = DebugPort::Type::kMmio,
.irq = config.irq,
.mem_addr = reinterpret_cast<vaddr_t>(paddr_to_physmap(config.mmio_phys)),
.phys_addr = static_cast<paddr_t>(config.mmio_phys),
};
mark_mmio_region_to_reserve(gDebugPort.phys_addr, PAGE_SIZE);
dprintf(INFO, "UART: kernel serial enabled: mmio=%#lx, irq=%#x\n", gDebugPort.phys_addr,
gDebugPort.irq);
} else if constexpr (ktl::is_same_v<config_type, zbi_dcfg_simple_pio_t>) {
gDebugPort = {
.type = DebugPort::Type::kIoPort,
.irq = config.irq,
.io_port = static_cast<uint32_t>(config.base),
};
mark_pio_region_to_reserve(gDebugPort.io_port, 8);
dprintf(INFO, "UART: kernel serial enabled: port=%#x, irq=%#x\n", gDebugPort.io_port,
gDebugPort.irq);
}
};
ktl::visit(set_debug_port, serial);
if (!platform_serial_enabled()) {
dprintf(INFO, "UART: unknown or disabled.\n");
return;
}
init_uart();
output_enabled = true;
dprintf(INFO, "UART: enabled with FIFO depth %u\n", uart_fifo_depth);
}
void X86UartInitLate() {
if (gBootOptions->experimental_serial_migration) {
return;
}
// At this stage, we have threads, interrupts, the heap, and virtual memory
// available to us, which wasn't available at earlier stages.
//
// Finish setting up the UART, including:
// - Setting up interrupts for TX and RX, or polling timers if we can't
// use interrupts;
// - RX buffers.
console_input_buf.Initialize(1024, malloc(1024));
if (!platform_serial_enabled()) {
// Need to bail after initializing the input_buf to prevent uninitialized
// access to it.
return;
}
// If we don't support interrupts, set up a polling timer.
if ((gDebugPort.irq == 0) || gBootOptions->debug_uart_poll) {
printf("debug-uart: polling enabled\n");
platform_debug_start_uart_timer();
return;
}
// Otherwise, set up interrupts.
uint32_t irq = apic_io_isa_to_global(static_cast<uint8_t>(gDebugPort.irq));
zx_status_t status = register_permanent_int_handler(irq, uart_irq_handler, NULL);
DEBUG_ASSERT(status == ZX_OK);
unmask_interrupt(irq);
uart_write(1, (1 << 0)); // enable receive data available interrupt
// modem control register: Auxiliary Output 2 is another IRQ enable bit
const uint8_t mcr = uart_read(4);
uart_write(4, mcr | 0x8);
printf("UART: started IRQ driven RX\n");
bool tx_irq_driven = !dlog_bypass();
if (tx_irq_driven) {
// start up tx driven output
printf("UART: started IRQ driven TX\n");
uart_tx_irq_enabled = true;
}
}
void pc_suspend_debug() { output_enabled = false; }
void pc_resume_debug() {
if (platform_serial_enabled()) {
init_uart();
output_enabled = true;
}
}
// This is called when the FIFO is detected to be empty. So we can write an
// entire FIFO's worth of bytes. Much more efficient than writing 1 byte
// at a time (and then checking for FIFO to drain).
static char* debug_platform_tx_FIFO_bytes(const char* str, size_t* len, bool* copied_CR,
size_t* wrote_bytes) {
size_t i, copy_bytes;
char* s = (char*)str;
copy_bytes = ktl::min(static_cast<size_t>(uart_fifo_depth), *len);
for (i = 0; i < copy_bytes; i++) {
if (*s == '\n' && !*copied_CR) {
uart_write(0, '\r');
*copied_CR = true;
if (++i == copy_bytes)
break;
uart_write(0, '\n');
} else {
uart_write(0, *s);
*copied_CR = false;
}
s++;
(*len)--;
}
if (wrote_bytes != NULL)
*wrote_bytes = i;
return s;
}
// dputs() Tx is either polling driven (if the caller is non-preemptible
// or earlyboot or panic) or blocking (and irq driven).
//
// TODO - buffered Tx support may be a win, (lopri but worth investigating)
// When we do that dputs() can be completely asynchronous, and return when
// the write has been (atomically) deposited into the buffer, except when
// we run out of room in the Tx buffer (rare) - we'd either need to spin
// (if non-blocking) or block waiting for space in the Tx buffer (adding
// support to optionally block in cbuf_write_char() would be easiest way
// to achieve that).
//
// block : Blocking vs Non-Blocking
static void platform_dputs(const char* str, size_t len, bool block) {
bool copied_CR = false;
size_t wrote;
// drop strings if we haven't initialized the uart yet
if (unlikely(!output_enabled))
return;
if (!uart_tx_irq_enabled)
block = false;
Guard<MonitoredSpinLock, IrqSave> guard{uart_tx_spinlock::Get(), SOURCE_TAG};
while (len > 0) {
// Is FIFO empty ?
while (!(uart_read(5) & (1 << 5))) {
if (block) {
// We want to Tx more and FIFO is empty, re-enable Tx interrupts before blocking.
uart_write(1, static_cast<uint8_t>((1 << 0) | ((uart_tx_irq_enabled ? 1 : 0)
<< 1))); // rx and tx interrupt enable
guard.CallUnlocked([]() { uart_dputc_event.Wait(); });
} else {
guard.CallUnlocked([]() { arch::Yield(); });
}
}
// Fifo is completely empty now, we can shove an entire
// fifo's worth of Tx...
str = debug_platform_tx_FIFO_bytes(str, &len, &copied_CR, &wrote);
if (block && wrote > 0) {
// If blocking/irq driven wakeps, enable rx/tx intrs
uart_write(1, static_cast<uint8_t>((1 << 0) | ((uart_tx_irq_enabled ? 1 : 0)
<< 1))); // rx and tx interrupt enable
}
}
}
void legacy_platform_dputs_thread(const char* str, size_t len) {
if (platform_serial_enabled()) {
platform_dputs(str, len, true);
}
}
void legacy_platform_dputs_irq(const char* str, size_t len) {
if (platform_serial_enabled()) {
platform_dputs(str, len, false);
}
}
// polling versions of debug uart read/write
static int debug_uart_getc_poll(char* c) {
// if there is a character available, read it
if (uart_read(5) & (1 << 0)) {
*c = uart_read(0);
return 0;
}
return -1;
}
static void debug_uart_putc_poll(char c) {
// while the fifo is non empty, spin
while (!(uart_read(5) & (1 << 6))) {
arch::Yield();
}
uart_write(0, c);
}
int legacy_platform_dgetc(char* c, bool wait) {
if (!platform_serial_enabled()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx::result<char> result = console_input_buf.ReadChar(wait);
if (result.is_ok()) {
*c = result.value();
return 1;
}
if (result.error_value() == ZX_ERR_SHOULD_WAIT) {
return 0;
}
return result.error_value();
}
// panic time polling IO for the panic shell
void legacy_platform_pputc(char c) {
if (platform_serial_enabled()) {
if (c == '\n')
debug_uart_putc_poll('\r');
debug_uart_putc_poll(c);
}
}
int legacy_platform_pgetc(char* c) {
if (platform_serial_enabled()) {
return debug_uart_getc_poll(c);
}
return ZX_ERR_NOT_SUPPORTED;
}