mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-04-28 19:54:01 +00:00
to enable early print output by pci uart, which has mmio address above 4G, add the early pagetable map for the MMIO range. we make an assumption that only map it with 2MB page, since platform with 39bit memory width have no 1G huge page feature and we cannot get the capacity at runtime, since it is a very early code stage. v2->v1: 1. add hva2hpa_early 2. add process when *pdpte is not present Signed-off-by: hangliu1 <hang1.liu@linux.intel.com> Reviewed-by: fei1.li <fei1.li@intel.com> Tracked-On: #6690
324 lines
7.9 KiB
C
324 lines
7.9 KiB
C
/*
|
|
* Copyright (C) 2018-2022 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <types.h>
|
|
#include <asm/lib/spinlock.h>
|
|
#include <pci.h>
|
|
#include <uart16550.h>
|
|
#include <asm/io.h>
|
|
#include <asm/cpu.h>
|
|
#include <asm/mmu.h>
|
|
|
|
#define MAX_BDF_LEN 8
|
|
|
|
struct console_uart {
|
|
bool enabled;
|
|
|
|
enum serial_dev_type type;
|
|
uint16_t port_address;
|
|
void *mmio_base_vaddr;
|
|
union pci_bdf bdf;
|
|
|
|
spinlock_t rx_lock;
|
|
spinlock_t tx_lock;
|
|
|
|
uint32_t reg_width;
|
|
};
|
|
|
|
#if defined(CONFIG_SERIAL_PIO_BASE)
|
|
static struct console_uart uart = {
|
|
.enabled = true,
|
|
.type = PIO,
|
|
.port_address = CONFIG_SERIAL_PIO_BASE,
|
|
.reg_width = 1,
|
|
};
|
|
#elif defined(CONFIG_SERIAL_PCI_BDF)
|
|
static struct console_uart uart = {
|
|
.enabled = true,
|
|
.type = PCI,
|
|
.bdf.value = CONFIG_SERIAL_PCI_BDF,
|
|
.reg_width = 4,
|
|
};
|
|
#elif defined(CONFIG_SERIAL_MMIO_BASE)
|
|
static struct console_uart uart = {
|
|
.enabled = true,
|
|
.type = MMIO,
|
|
.mmio_base_vaddr = (void *)CONFIG_SERIAL_MMIO_BASE,
|
|
.reg_width = 1,
|
|
};
|
|
#endif
|
|
|
|
typedef uint32_t uart_reg_t;
|
|
|
|
/**
|
|
* @pre uart->enabled == true
|
|
*/
|
|
static inline uint32_t uart16550_read_reg(struct console_uart uart, uint16_t reg_idx)
|
|
{
|
|
if (uart.type == PIO) {
|
|
return pio_read8(uart.port_address + (reg_idx * uart.reg_width));
|
|
} else {
|
|
if (uart.reg_width == 4U) {
|
|
return mmio_read32(uart.mmio_base_vaddr + (reg_idx * uart.reg_width));
|
|
} else {
|
|
return mmio_read8(uart.mmio_base_vaddr + (reg_idx * uart.reg_width));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @pre uart->enabled == true
|
|
*/
|
|
static inline void uart16550_write_reg(struct console_uart uart, uint32_t val, uint16_t reg_idx)
|
|
{
|
|
if (uart.type == PIO) {
|
|
pio_write8(val, uart.port_address + (reg_idx * uart.reg_width));
|
|
} else {
|
|
if (uart.reg_width == 4U) {
|
|
mmio_write32(val, uart.mmio_base_vaddr + (reg_idx * uart.reg_width));
|
|
} else {
|
|
mmio_write8(val, uart.mmio_base_vaddr + (reg_idx * uart.reg_width));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void uart16550_calc_baud_div(uint32_t ref_freq, uint32_t *baud_div_ptr, uint32_t baud_rate_arg)
|
|
{
|
|
uint32_t baud_rate = baud_rate_arg;
|
|
uint32_t baud_multiplier = baud_rate < BAUD_460800 ? 16U : 13U;
|
|
|
|
if (baud_rate == 0U) {
|
|
baud_rate = BAUD_115200;
|
|
}
|
|
*baud_div_ptr = ref_freq / (baud_multiplier * baud_rate);
|
|
}
|
|
|
|
/**
|
|
* @pre uart->enabled == true
|
|
*/
|
|
static void uart16550_set_baud_rate(uint32_t baud_rate)
|
|
{
|
|
uint32_t baud_div, duart_clock = UART_CLOCK_RATE;
|
|
uart_reg_t temp_reg;
|
|
|
|
/* Calculate baud divisor */
|
|
uart16550_calc_baud_div(duart_clock, &baud_div, baud_rate);
|
|
|
|
/* Enable DLL and DLM registers for setting the Divisor */
|
|
temp_reg = uart16550_read_reg(uart, UART16550_LCR);
|
|
temp_reg |= LCR_DLAB;
|
|
uart16550_write_reg(uart, temp_reg, UART16550_LCR);
|
|
|
|
/* Write the appropriate divisor value */
|
|
uart16550_write_reg(uart, ((baud_div >> 8U) & 0xFFU), UART16550_DLM);
|
|
uart16550_write_reg(uart, (baud_div & 0xFFU), UART16550_DLL);
|
|
|
|
/* Disable DLL and DLM registers */
|
|
temp_reg &= ~LCR_DLAB;
|
|
uart16550_write_reg(uart, temp_reg, UART16550_LCR);
|
|
}
|
|
|
|
static uint8_t uart_pde_page[PAGE_SIZE]__aligned(PAGE_SIZE);
|
|
static uint8_t uart_pdpte_page[PAGE_SIZE]__aligned(PAGE_SIZE);
|
|
|
|
static void early_pgtable_map_uart(uint64_t addr)
|
|
{
|
|
uint64_t *pml4e, *pdpte, *pde;
|
|
uint64_t value;
|
|
|
|
CPU_CR_READ(cr3, &value);
|
|
/*assumpiton for map high mmio in early pagetable is that it is only used for
|
|
2MB page since 1G page may not available when memory width is 39bit */
|
|
pml4e = pml4e_offset((uint64_t *)value, addr);
|
|
/* address is above 512G */
|
|
if(!(*pml4e & PAGE_PRESENT)) {
|
|
*pml4e = hva2hpa_early(uart_pdpte_page) + (PAGE_PRESENT|PAGE_RW);
|
|
}
|
|
pdpte = pdpte_offset(pml4e, addr);
|
|
if(!(*pdpte & PAGE_PRESENT)) {
|
|
*(pdpte) = hva2hpa_early(uart_pde_page) + (PAGE_PRESENT|PAGE_RW);
|
|
pde = pde_offset(pdpte, addr);
|
|
*pde = (addr & PDE_MASK) + (PAGE_PRESENT|PAGE_RW|PAGE_PSE);
|
|
} else if(!(*pdpte & PAGE_PSE)) {
|
|
pde = pde_offset(pdpte, addr);
|
|
if(!(*pde & PAGE_PRESENT)) {
|
|
*pde = (addr & PDE_MASK) + (PAGE_PRESENT|PAGE_RW|PAGE_PSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void uart16550_init(bool early_boot)
|
|
{
|
|
void *mmio_base_va = NULL;
|
|
|
|
if (!uart.enabled) {
|
|
return;
|
|
}
|
|
|
|
if (!early_boot) {
|
|
if (uart.type == MMIO) {
|
|
mmio_base_va = hpa2hva(hva2hpa_early(uart.mmio_base_vaddr));
|
|
if (mmio_base_va != NULL) {
|
|
set_paging_supervisor((uint64_t)mmio_base_va, PDE_SIZE);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* if configure serial PCI BDF, get its base MMIO address */
|
|
if (uart.type == PCI) {
|
|
uint32_t bar0 = pci_pdev_read_cfg(uart.bdf, pci_bar_offset(0), 4U);
|
|
|
|
if ((bar0 & ~0xfU) == 0U) {
|
|
/* in case the PCI UART BAR is reset to 0 after boot */
|
|
uart.enabled = false;
|
|
return;
|
|
} else {
|
|
uint16_t cmd = (uint16_t)pci_pdev_read_cfg(uart.bdf, PCIR_COMMAND, 2U);
|
|
if ((bar0 & 0x3U) == PCIM_BAR_IO_SPACE) { /* IO Space */
|
|
uart.type = PIO;
|
|
uart.port_address = (uint16_t)(bar0 & PCI_BASE_ADDRESS_IO_MASK);
|
|
uart.reg_width = 1;
|
|
pci_pdev_write_cfg(uart.bdf, PCIR_COMMAND, 2U, cmd | PCIM_CMD_PORTEN);
|
|
} else if (((bar0 & 0x7U) == 0U) || ((bar0 & 0x7U) == 4U)) {
|
|
uart.type = MMIO;
|
|
uint32_t bar_hi = pci_pdev_read_cfg(uart.bdf, pci_bar_offset(1), 4U);
|
|
uint64_t addr = (bar0 & PCI_BASE_ADDRESS_MEM_MASK)|(((uint64_t)bar_hi) << 32U);
|
|
if (bar_hi != 0U) {
|
|
early_pgtable_map_uart(addr);
|
|
}
|
|
uart.mmio_base_vaddr = hpa2hva_early(addr);
|
|
pci_pdev_write_cfg(uart.bdf, PCIR_COMMAND, 2U, cmd | PCIM_CMD_MEMEN);
|
|
} else {
|
|
uart.enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
spinlock_init(&uart.rx_lock);
|
|
spinlock_init(&uart.tx_lock);
|
|
|
|
/* Enable TX and RX FIFOs */
|
|
uart16550_write_reg(uart, FCR_FIFOE | FCR_RFR | FCR_TFR, UART16550_FCR);
|
|
|
|
/* Set-up data bits / parity / stop bits. */
|
|
uart16550_write_reg(uart, (LCR_WL8 | LCR_NB_STOP_BITS_1 | LCR_PARITY_NONE), UART16550_LCR);
|
|
|
|
/* Disable interrupts (we use polling) */
|
|
uart16550_write_reg(uart, UART_IER_DISABLE_ALL, UART16550_IER);
|
|
|
|
/* Set baud rate */
|
|
uart16550_set_baud_rate(BAUD_115200);
|
|
|
|
/* Data terminal ready + Request to send */
|
|
uart16550_write_reg(uart, MCR_RTS | MCR_DTR, UART16550_MCR);
|
|
}
|
|
|
|
char uart16550_getc(void)
|
|
{
|
|
char ret = -1;
|
|
uint64_t rflags;
|
|
|
|
if (!uart.enabled) {
|
|
return ret;
|
|
}
|
|
|
|
spinlock_irqsave_obtain(&uart.rx_lock, &rflags);
|
|
/* If a character has been received, read it */
|
|
if ((uart16550_read_reg(uart, UART16550_LSR) & LSR_DR) == LSR_DR) {
|
|
/* Read a character */
|
|
ret = uart16550_read_reg(uart, UART16550_RBR);
|
|
|
|
}
|
|
spinlock_irqrestore_release(&uart.rx_lock, rflags);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @pre uart->enabled == true
|
|
*/
|
|
static void uart16550_putc(char c)
|
|
{
|
|
uint8_t temp;
|
|
uint32_t reg;
|
|
|
|
/* Ensure there are no further Transmit buffer write requests */
|
|
do {
|
|
reg = uart16550_read_reg(uart, UART16550_LSR);
|
|
} while ((reg & LSR_THRE) == 0U || (reg & LSR_TEMT) == 0U);
|
|
|
|
temp = (uint8_t)c;
|
|
/* Transmit the character. */
|
|
uart16550_write_reg(uart, (uint32_t)temp, UART16550_THR);
|
|
}
|
|
|
|
size_t uart16550_puts(const char *buf, uint32_t len)
|
|
{
|
|
uint32_t i;
|
|
uint64_t rflags;
|
|
|
|
if (!uart.enabled) {
|
|
return len;
|
|
}
|
|
|
|
spinlock_irqsave_obtain(&uart.tx_lock, &rflags);
|
|
for (i = 0U; i < len; i++) {
|
|
/* Transmit character */
|
|
uart16550_putc(*buf);
|
|
if (*buf == '\n') {
|
|
/* Append '\r', no need change the len */
|
|
uart16550_putc('\r');
|
|
}
|
|
buf++;
|
|
}
|
|
spinlock_irqrestore_release(&uart.tx_lock, rflags);
|
|
return len;
|
|
}
|
|
|
|
void uart16550_set_property(bool enabled, enum serial_dev_type uart_type, uint64_t data)
|
|
{
|
|
uart.enabled = enabled;
|
|
uart.type = uart_type;
|
|
uart.bdf.value = 0U;
|
|
|
|
if (uart_type == PIO) {
|
|
uart.port_address = data;
|
|
} else if (uart_type == PCI) {
|
|
uart.bdf.value = data;
|
|
uart.reg_width = 4;
|
|
} else if (uart_type == MMIO) {
|
|
uart.mmio_base_vaddr = (void *)data;
|
|
uart.reg_width = 1;
|
|
}
|
|
}
|
|
|
|
bool is_pci_dbg_uart(union pci_bdf bdf_value)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (uart.enabled && (uart.bdf.value != 0)) {
|
|
if (bdf_value.value == uart.bdf.value) {
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool get_pio_dbg_uart_cfg(uint16_t *pio_address, uint32_t *nbytes)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (uart.enabled && (uart.type == PIO)) {
|
|
*pio_address = uart.port_address;
|
|
*nbytes = 8U;
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|