blob: 6f841bd963f4286517c6efed7589213d08c33986 [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 <arch/x86.h>
#include <arch/x86/apic.h>
#include <bits.h>
#include <dev/interrupt.h>
#include <kernel/cmdline.h>
#include <kernel/spinlock.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <lib/cbuf.h>
#include <lib/debuglog.h>
#include <lk/init.h>
#include <platform.h>
#include <platform/console.h>
#include <platform/debug.h>
#include <platform/pc.h>
#include <platform/pc/bootloader.h>
#include <reg.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <vm/physmap.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include "platform_p.h"
static const int uart_baud_rate = 115200;
static int uart_io_port = 0x3f8;
static uint64_t uart_mem_addr = 0;
static uint32_t uart_irq = ISA_IRQ_SERIAL1;
cbuf_t console_input_buf;
static bool output_enabled = false;
uint32_t uart_fifo_depth;
// tx driven irq
static bool uart_tx_irq_enabled = false;
static event_t uart_dputc_event = EVENT_INITIAL_VALUE(uart_dputc_event, true,
EVENT_FLAG_AUTOUNSIGNAL);
static spin_lock_t uart_spinlock = SPIN_LOCK_INITIAL_VALUE;
static uint8_t uart_read(uint8_t reg) {
if (uart_mem_addr) {
return (uint8_t)readl(uart_mem_addr + 4 * reg);
} else {
return (uint8_t)inp((uint16_t)(uart_io_port + reg));
}
}
static void uart_write(uint8_t reg, uint8_t val) {
if (uart_mem_addr) {
writel(val, uart_mem_addr + 4 * reg);
} else {
outp((uint16_t)(uart_io_port + reg), val);
}
}
static interrupt_eoi uart_irq_handler(void *arg) {
spin_lock(&uart_spinlock);
// see why we have gotten an irq
for (;;) {
uint8_t iir = uart_read(2);
if (BIT(iir, 0))
break; // no valid interrupt
// 3 bit identification field
uint ident = BITS(iir, 3, 0);
switch (ident) {
case 0b0100:
case 0b1100: {
// rx fifo is non empty, drain it
unsigned char c = uart_read(0);
cbuf_write_char(&console_input_buf, c);
break;
}
case 0b0010:
// transmitter is empty, signal any waiting senders
event_signal(&uart_dputc_event, true);
// disable the tx irq
uart_write(1, (1<<0)); // just rx interrupt enable
break;
case 0b0110: // receiver line status
uart_read(5); // read the LSR
break;
default:
spin_unlock(&uart_spinlock);
panic("UART: unhandled ident %#x\n", ident);
}
}
spin_unlock(&uart_spinlock);
return IRQ_EOI_DEACTIVATE;
}
static void platform_drain_debug_uart_rx(void) {
while (uart_read(5) & (1<<0)) {
unsigned char c = uart_read(0);
cbuf_write_char(&console_input_buf, c);
}
}
static constexpr TimerSlack kSlack{ZX_MSEC(1), TIMER_SLACK_CENTER};
// for devices where the uart rx interrupt doesn't seem to work
static void uart_rx_poll(timer_t* t, zx_time_t now, void* arg) {
const Deadline deadline(zx_time_add_duration(now, ZX_MSEC(10)), kSlack);
timer_set(t, deadline, uart_rx_poll, NULL);
platform_drain_debug_uart_rx();
}
void platform_debug_start_uart_timer();
void platform_debug_start_uart_timer(void) {
static timer_t uart_rx_poll_timer;
static bool started = false;
if (!started) {
started = true;
timer_init(&uart_rx_poll_timer);
const Deadline deadline(zx_time_add_duration(current_time(), ZX_MSEC(10)), kSlack);
timer_set(&uart_rx_poll_timer, deadline, uart_rx_poll, NULL);
}
}
static void init_uart() {
/* configure the uart */
int divisor = 115200 / uart_baud_rate;
/* 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
uart_write(3, 3); // 8N1
// enable FIFO, rx FIFO reset, tx FIFO reset, 16750 64 byte fifo enable,
// Rx FIFO irq trigger level at 14-bytes
uart_write(2, 0xe7);
// 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;
}
}
bool platform_serial_enabled() {
return bootloader.uart.type != ZBI_UART_NONE;
}
// This just initializes the default serial console to be disabled.
// It's in a function rather than just a compile-time assignment to the
// bootloader structure because our C++ compiler doesn't support non-trivial
// designated initializers.
void pc_init_debug_default_early() {
bootloader.uart.type = ZBI_UART_NONE;
}
static void handle_serial_cmdline() {
const char* serial_mode = cmdline_get("kernel.serial");
if (serial_mode == NULL) {
return;
}
if (!strcmp(serial_mode, "none")) {
bootloader.uart.type = ZBI_UART_NONE;
return;
}
if (!strcmp(serial_mode, "legacy")) {
bootloader.uart.type = ZBI_UART_PC_PORT;
bootloader.uart.base = 0x3f8;
bootloader.uart.irq = ISA_IRQ_SERIAL1;
return;
}
// type can be "ioport" or "mmio"
constexpr size_t kMaxTypeLen = 6 + 1;
char type_buf[kMaxTypeLen];
// Addr can be up to 32 characters (numeric in any base strtoul will take),
// and + 1 for \0
constexpr size_t kMaxAddrLen = 32 + 1;
char addr_buf[kMaxAddrLen];
char* endptr;
const char* addr_start;
const char* irq_start;
size_t addr_len, type_len;
unsigned long irq_val;
addr_start = strchr(serial_mode, ',');
if (addr_start == nullptr) {
goto bail;
}
addr_start++;
irq_start = strchr(addr_start, ',');
if (irq_start == nullptr) {
goto bail;
}
irq_start++;
// Parse out the type part
type_len = addr_start - serial_mode - 1;
if (type_len + 1 > kMaxTypeLen) {
goto bail;
}
memcpy(type_buf, serial_mode, type_len);
type_buf[type_len] = 0;
if (!strcmp(type_buf, "ioport")) {
bootloader.uart.type = ZBI_UART_PC_PORT;
} else if (!strcmp(type_buf, "mmio")) {
bootloader.uart.type = ZBI_UART_PC_MMIO;
} else {
goto bail;
}
// Parse out the address part
addr_len = irq_start - addr_start - 1;
if (addr_len + 1 > kMaxAddrLen) {
goto bail;
}
memcpy(addr_buf, addr_start, addr_len);
addr_buf[addr_len] = 0;
bootloader.uart.base = strtoul(addr_buf, &endptr, 0);
if (endptr != addr_buf + addr_len) {
goto bail;
}
// Parse out the IRQ part
irq_val = strtoul(irq_start, &endptr, 0);
if (*endptr != '\0' || irq_val > UINT32_MAX) {
goto bail;
}
// For now, we don't support non-ISA IRQs
if (irq_val >= NUM_ISA_IRQS) {
goto bail;
}
bootloader.uart.irq = static_cast<uint32_t>(irq_val);
return;
bail:
bootloader.uart.type = ZBI_UART_NONE;
return;
}
void pc_init_debug_early() {
handle_serial_cmdline();
switch (bootloader.uart.type) {
case ZBI_UART_PC_PORT:
uart_io_port = static_cast<uint32_t>(bootloader.uart.base);
uart_irq = bootloader.uart.irq;
break;
case ZBI_UART_PC_MMIO:
uart_mem_addr = (uint64_t)paddr_to_physmap(bootloader.uart.base);
uart_irq = bootloader.uart.irq;
break;
case ZBI_UART_NONE: // fallthrough
default:
bootloader.uart.type = ZBI_UART_NONE;
return;
}
init_uart();
output_enabled = true;
dprintf(INFO, "UART: FIFO depth %u\n", uart_fifo_depth);
}
void pc_init_debug(void) {
bool tx_irq_driven = false;
/* finish uart init to get rx going */
cbuf_initialize(&console_input_buf, 1024);
if (!platform_serial_enabled()) {
// Need to bail after initializing the input_buf to prevent uninitialized
// access to it.
return;
}
if ((uart_irq == 0) || cmdline_get_bool("kernel.debug_uart_poll", false) ||
dlog_bypass()) {
printf("debug-uart: polling enabled\n");
platform_debug_start_uart_timer();
} else {
uart_irq = apic_io_isa_to_global(static_cast<uint8_t>(uart_irq));
zx_status_t status = register_int_handler(uart_irq, uart_irq_handler, NULL);
DEBUG_ASSERT(status == ZX_OK);
unmask_interrupt(uart_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");
tx_irq_driven = true;
}
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(void) {
output_enabled = false;
}
void pc_resume_debug(void) {
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,
bool map_NL)
{
size_t i, copy_bytes;;
char *s = (char *)str;
copy_bytes = MIN(uart_fifo_depth, *len);
for (i = 0 ; i < copy_bytes ; i++) {
if (*s == '\n' && map_NL && !*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
* map_NL : If true, map a '\n' to '\r'+'\n'
*/
static void platform_dputs(const char* str, size_t len,
bool block, bool map_NL) {
spin_lock_saved_state_t state;
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;
spin_lock_irqsave(&uart_spinlock, state);
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, (1<<0)|(1<<1)); // rx and tx interrupt enable
spin_unlock_irqrestore(&uart_spinlock, state);
event_wait(&uart_dputc_event);
} else {
spin_unlock_irqrestore(&uart_spinlock, state);
arch_spinloop_pause();
}
spin_lock_irqsave(&uart_spinlock, state);
}
// 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, map_NL);
if (block && wrote > 0) {
// If blocking/irq driven wakeps, enable rx/tx intrs
uart_write(1, (1<<0)|(1<<1)); // rx and tx interrupt enable
}
}
spin_unlock_irqrestore(&uart_spinlock, state);
}
void platform_dputs_thread(const char* str, size_t len) {
if (platform_serial_enabled()) {
platform_dputs(str, len, true, true);
}
}
void platform_dputs_irq(const char* str, size_t len) {
if (platform_serial_enabled()) {
platform_dputs(str, len, false, true);
}
}
// 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_spinloop_pause();
}
uart_write(0, c);
}
int platform_dgetc(char* c, bool wait) {
if (platform_serial_enabled()) {
return static_cast<int>(cbuf_read_char(&console_input_buf, c, wait));
}
return ZX_ERR_NOT_SUPPORTED;
}
// panic time polling IO for the panic shell
void platform_pputc(char c) {
if (platform_serial_enabled()) {
if (c == '\n')
debug_uart_putc_poll('\r');
debug_uart_putc_poll(c);
}
}
int platform_pgetc(char *c, bool wait) {
if (platform_serial_enabled()) {
return debug_uart_getc_poll(c);
}
return ZX_ERR_NOT_SUPPORTED;
}
/*
* Called on start of a panic.
* When we do Tx buffering, drain the Tx buffer here in polling mode.
* Turn off Tx interrupts, so force Tx be polling from this point
*/
void platform_debug_panic_start(void) {
uart_tx_irq_enabled = false;
}