blob: dc6b2501e076689a9e7890219b08f5b40907ed76 [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 <stdarg.h>
#include <reg.h>
#include <stdio.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <vm/pmm.h>
#include <lk/init.h>
#include <arch/x86.h>
#include <arch/x86/apic.h>
#include <lib/cbuf.h>
#include <dev/interrupt.h>
#include <kernel/cmdline.h>
#include <platform.h>
#include <platform/pc.h>
#include <platform/pc/bootloader.h>
#include <platform/console.h>
#include <platform/debug.h>
#include <trace.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;
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 enum handler_return platform_drain_debug_uart_rx(void)
{
unsigned char c;
bool resched = false;
while (uart_read(5) & (1<<0)) {
c = uart_read(0);
cbuf_write_char(&console_input_buf, c, false);
resched = true;
}
return resched ? INT_RESCHEDULE : INT_NO_RESCHEDULE;
}
static enum handler_return uart_irq_handler(void *arg)
{
return platform_drain_debug_uart_rx();
}
// for devices where the uart rx interrupt doesn't seem to work
static enum handler_return uart_rx_poll(struct timer *t, zx_time_t now, void *arg)
{
timer_set(t, now + ZX_MSEC(10), TIMER_SLACK_CENTER, ZX_MSEC(1), uart_rx_poll, NULL);
return platform_drain_debug_uart_rx();
}
// also called from the pixel2 quirk file
void platform_debug_start_uart_timer(void);
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);
timer_set(&uart_rx_poll_timer, current_time() + ZX_MSEC(10),
TIMER_SLACK_CENTER, ZX_MSEC(1), uart_rx_poll, NULL);
}
}
#include <dev/pcie_bus_driver.h>
extern "C" void uart_reinit(void) {
auto cfg = PcieBusDriver::GetDriver()->GetConfig(0, 0x19, 0);
uint64_t bar0 = cfg->Read(PciConfig::kBAR(0)) | ((uint64_t)cfg->Read(PciConfig::kBAR(1)) >> 32);
bar0 &= ~7;
uart_mem_addr = (uint64_t)paddr_to_kvaddr(bar0);
if (uart_irq) {
mask_interrupt(uart_irq);
uart_irq = 0;
platform_debug_start_uart_timer();
}
/* 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
uart_write(2, 0xc7); // enable FIFO, clear, 14-byte threshold
uart_write(1, 0x1); // enable receive data available interrupt
// modem control register: Axiliary Output 2 is another IRQ enable bit
const uint8_t mcr = uart_read(4);
uart_write(4, mcr | 0x8);
}
void platform_init_debug_early(void)
{
switch (bootloader.uart.type) {
case BOOTDATA_UART_PC_PORT:
uart_io_port = static_cast<uint32_t>(bootloader.uart.base);
uart_irq = bootloader.uart.irq;
break;
case BOOTDATA_UART_PC_MMIO:
uart_mem_addr = (uint64_t)paddr_to_kvaddr(bootloader.uart.base);
uart_irq = bootloader.uart.irq;
break;
}
/* 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
uart_write(2, 0xc7); // enable FIFO, clear, 14-byte threshold
output_enabled = true;
}
void platform_init_debug(void)
{
/* finish uart init to get rx going */
cbuf_initialize(&console_input_buf, 1024);
if ((uart_irq == 0) || cmdline_get_bool("kernel.debug_uart_poll", false)) {
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));
register_int_handler(uart_irq, uart_irq_handler, NULL);
unmask_interrupt(uart_irq);
uart_write(1, 0x1); // enable receive data available interrupt
// modem control register: Axiliary Output 2 is another IRQ enable bit
const uint8_t mcr = uart_read(4);
uart_write(4, mcr | 0x8);
}
}
static void debug_uart_putc(char c)
{
#if WITH_LEGACY_PC_CONSOLE
cputc(c);
#endif
if (unlikely(!output_enabled))
return;
while ((uart_read(5) & (1<<6)) == 0) {
arch_spinloop_pause();
}
uart_write(0, c);
}
void platform_dputs(const char* str, size_t len)
{
while (len-- > 0) {
char c = *str++;
if (c == '\n') {
debug_uart_putc('\r');
}
debug_uart_putc(c);
}
}
int platform_dgetc(char *c, bool wait)
{
return static_cast<int>(cbuf_read_char(&console_input_buf, c, wait));
}
// panic time polling IO for the panic shell
void platform_pputc(char c)
{
platform_dputc(c);
}
int platform_pgetc(char *c, bool wait)
{
if (uart_read(5) & (1<<0)) {
*c = uart_read(0);
return 0;
}
return -1;
}