diff --git a/libplatsupport/CMakeLists.txt b/libplatsupport/CMakeLists.txt index 0bae33f2c..126afe774 100644 --- a/libplatsupport/CMakeLists.txt +++ b/libplatsupport/CMakeLists.txt @@ -150,6 +150,16 @@ if(KernelPlatformZynqmp) target_include_directories(platsupport PUBLIC plat_include/zynqmp) endif() +if(KernelPlatformHikey OR KernelPlatformFVP OR KernelPlatformQEMUArmVirt) + target_sources(platsupport PRIVATE "src/drivers/uart_pl011/uart_pl011.c") +endif() + +# ToDo: unify RasPi3 and RasPi4 +# if(KernelPlatformRpi3 OR KernelPlatformRpi4) +# target_sources(platsupport PRIVATE "src/drivers/uart_raspi/uart_raspi.c") +# target_include_directories(platsupport PUBLIC "driver-include/uart_raspi") +# endif() + if(NOT "${LibPlatSupportMach}" STREQUAL "") target_include_directories(platsupport PUBLIC mach_include/${LibPlatSupportMach}) endif() diff --git a/libplatsupport/include/platsupport/serial.h b/libplatsupport/include/platsupport/serial.h index aaee340cb..8e15dcfe7 100644 --- a/libplatsupport/include/platsupport/serial.h +++ b/libplatsupport/include/platsupport/serial.h @@ -13,9 +13,21 @@ **** Serial device flags **** *****************************/ -/* Auto-send CR(Carriage Return) after each "\n". - * NOTE: This flag should be set by default. */ -#define SERIAL_AUTO_CR BIT(0) +/* Auto-send CR (Carriage Return, "\r") before each "\n". All UART drivers + * should set this flag by default, so the UART can be used as a console. + */ +#define SERIAL_AUTO_CR BIT(0) + +/* Do not block if the TX FIFO is full, but return an error. When SERIAL_AUTO_CR + * is enabled, CR+LF is considered as an atom, ie either nothing is sent or both + * CR and LF are sent. If the underlying UART implementation can't ensure the TX + * FIFO has space for both chars, it is allowed to block after CR has been sent + * to ensure LF can also be sent. Rational for this is, that SERIAL_AUTO_CR + * usually implies that the UART is used as a console. Blocking in this corner + * case can be neglected considering the issues caused by a missing LF and a CR + * getting sent twice then. + */ +#define SERIAL_TX_NONBLOCKING BIT(1) /*****************************/ diff --git a/libplatsupport/src/mach/bcm/pl011_uart.c b/libplatsupport/src/drivers/uart_pl011/pl011_uart.c similarity index 100% rename from libplatsupport/src/mach/bcm/pl011_uart.c rename to libplatsupport/src/drivers/uart_pl011/pl011_uart.c diff --git a/libplatsupport/src/drivers/uart_pl011/uart_pl011.c b/libplatsupport/src/drivers/uart_pl011/uart_pl011.c new file mode 100644 index 000000000..b008854e8 --- /dev/null +++ b/libplatsupport/src/drivers/uart_pl011/uart_pl011.c @@ -0,0 +1,164 @@ +/* + * ARM PL011 UART driver + * + * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include "../../chardev.h" + + +#define PL011_UART_RHR_MASK MASK(8) +#define PL011_UART_FR_TXFF BIT(5) +#define PL011_UART_FR_RXFE BIT(4) + +typedef volatile struct { + uint32_t dr; /* 0x00 */ + uint32_t reg_04; /* 0x04 */ + uint32_t reg_08; /* 0x08 */ + uint32_t reg_0C; /* 0x0C */ + uint32_t reg_10; /* 0x10 */ + uint32_t reg_14; /* 0x14 */ + uint32_t fr; /* 0x18 */ + uint32_t reg_1C; /* 0x1C */ + uint32_t reg_20; /* 0x20 */ + uint32_t reg_24; /* 0x24 */ + uint32_t reg_28; /* 0x28 */ + uint32_t reg_2C; /* 0x2C */ + uint32_t reg_30; /* 0x30 */ + uint32_t reg_34; /* 0x34 */ + uint32_t imsc; /* 0x38 */ + uint32_t reg_3C; /* 0x3C */ + uint32_t reg_40; /* 0x40 */ + uint32_t cr; /* 0x44 */ +} uart_pl011_regs_t; + + + +static uart_pl011_regs_t* get_uart_regs(ps_chardevice_t *d) +{ + return (uart_pl011_regs_t *)(d->vaddr); +} + +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static int internal_uart_is_tx_fifo_full(uart_pl011_regs_t *regs) +{ + return regs->fr & PL011_UARTFR_TX_FF; +} + +static void internal_uart_tx_byte(uart_pl011_regs_t *regs, uint8_t byte) +{ + regs->dr = byte; +} + +static uint8_t internal_uart_rx_byte(uart_pl011_regs_t *regs) +{ + return (uint8_t)(regs->dr & PL011_UART_RHR_MASK); +} + +static int internal_uart_is_rx_empty(uart_pl011_regs_t *regs) +{ + return reg->fr & PL011_UART_FR_RXFE; +} + +static void internal_uart_busy_wait_tx_ready(uart_pl011_regs_t *regs) +{ + while (internal_uart_is_tx_fifo_full(regs)) { + /* busy waiting loop */ + } +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_getchar(ps_chardevice_t *dev) +{ + uart_pl011_regs_t *regs = get_uart_regs(dev); + + if (internal_uart_is_rx_empty(regs)) { + return -1; + } + + return internal_uart_rx_byte(regs); +} + +int uart_putchar(ps_chardevice_t* dev, int c) +{ + uart_pl011_regs_t *regs = get_uart_regs(dev); + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (internal_uart_is_tx_fifo_full(regs)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; + } + internal_uart_busy_wait_tx_ready(regs); + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + internal_uart_busy_wait_tx_ready(regs); + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if (byte == '\n' && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx_byte(regs, '\r'); + internal_uart_busy_wait_tx_ready(regs); + } + + internal_uart_tx_byte(regs, byte); + + return byte; +} + +static void +uart_handle_irq(ps_chardevice_t* dev) +{ + uart_pl011_regs_t *regs = get_uart_regs(dev); + + regs->cr = 0x7f0; +} + +int uart_init(const struct dev_defn* defn, + const ps_io_ops_t* ops, + ps_chardevice_t* dev) +{ + memset(dev, 0, sizeof(*dev)); + + uart_pl011_regs_t *regs = (uart_pl011_regs_t *)chardev_map(defn, ops); + if (regs == NULL) { + return -1; + } + + /* Set up all the device properties. */ + dev->id = defn->id; + dev->vaddr = (void *)regs; + dev->read = &uart_read; + dev->write = &uart_write; + dev->handle_irq = &uart_handle_irq; + dev->irqs = defn->irqs; + dev->ioops = *ops; + dev->flags = SERIAL_AUTO_CR; + + regs->imsc = 0x50; + + return 0; +} diff --git a/libplatsupport/src/mach/exynos/serial.c b/libplatsupport/src/mach/exynos/serial.c index 0b9e7b7a9..05277da1b 100644 --- a/libplatsupport/src/mach/exynos/serial.c +++ b/libplatsupport/src/mach/exynos/serial.c @@ -87,7 +87,9 @@ #define INT_ERR BIT(1) #define INT_RX BIT(0) -#define REG_PTR(base, offset) ((volatile uint32_t *)((char*)(base) + (offset))) +#define REG_PTR(base, offset) ( (volatile uint32_t *)( \ + (uintptr_t)(base) + (offset) ) ) + static clk_t *clk; @@ -134,26 +136,77 @@ static const struct dev_defn dev_defn[] = { UART_DEFN(3), }; +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static int internal_uart_tx_busy(void* reg_base) +{ + return *REG_PTR(reg_base, UFRSTAT) & FRSTAT_TX_FULL; +} + +static int internal_uart_tx(void* reg_base, int c) +{ + *REG_PTR(reg_base, UTXH) = c; +} + +static uint8_t internal_uart_rx_byte(void *reg_base) +{ + return (uint8_t)(*REG_PTR(reg_base, URXH)); +} + +static int internal_uart_is_rx_ready(void *reg_base) +{ + return *REG_PTR(reg_base, UTRSTAT) & TRSTAT_RXBUF_READY; +} + +static void internal_uart_busy_wait_tx_ready(void* reg_base) +{ + while (internal_uart_tx_busy(reg_base)) { + /* busy waiting loop */ + } +} + +/* + ******************************************************************************* + * UART access helpers + ******************************************************************************* + */ + static int exynos_uart_putchar(ps_chardevice_t *d, int c) { - if (*REG_PTR(d->vaddr, UFRSTAT) & FRSTAT_TX_FULL) { - /* abort: no room in FIFO */ - return -1; - } else { - /* Write out the next character. */ - *REG_PTR(d->vaddr, UTXH) = c; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - /* In this case, We should have checked that we had two free bytes in - * the FIFO before we submitted the first char, however, the fifo size - * would need to be considered and this differs between UARTs. - * To keep things simple, we recognise that it is rare for a '\n' to - * be sent when there is insufficient FIFO space and accept the - * inefficiencies of spinning, waiting for space. - */ - while (exynos_uart_putchar(d, '\r') < 0); + void* reg_base = d->vaddr; + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (internal_uart_tx_busy(reg_base)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; } - return c; + internal_uart_busy_wait_tx_ready(reg_base); } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx(vaddr, '\r'); + internal_uart_busy_wait_tx_ready(reg_base); + } + + internal_uart_tx(reg_base, byte); + + return byte; } static int uart_fill_fifo(ps_chardevice_t *d, const char *data, size_t len) @@ -235,11 +288,13 @@ static void uart_handle_tx_irq(ps_chardevice_t *d) static int exynos_uart_getchar(ps_chardevice_t *d) { - if (*REG_PTR(d->vaddr, UTRSTAT) & TRSTAT_RXBUF_READY) { - return *REG_PTR(d->vaddr, URXH); - } else { + void* reg_base = d->vaddr; + + if (!internal_uart_is_rx_ready(reg_base)) { return -1; } + + return internal_uart_rx_byte(reg_base); } static int uart_read_fifo(ps_chardevice_t *d, char *data, size_t len) diff --git a/libplatsupport/src/mach/imx/serial/serial.c b/libplatsupport/src/mach/imx/serial/serial.c index 280196587..3a5921807 100644 --- a/libplatsupport/src/mach/imx/serial/serial.c +++ b/libplatsupport/src/mach/imx/serial/serial.c @@ -261,8 +261,8 @@ int uart_init( #ifdef CONFIG_PLAT_IMX6 #include - /* The UART1 on the IMX6 has the problem that the MUX is not correctly set, - * and the RX PIN is not routed correctly. + /* The UART1 on the IMX6 has the problem that the MUX is not correctly set, and the RX PIN is + * not routed correctly. */ if ((defn->id == IMX_UART1) && mux_sys_valid(&ops->mux_sys)) { if (mux_feature_enable(&ops->mux_sys, MUX_UART1, 0)) { diff --git a/libplatsupport/src/mach/nvidia/serial.c b/libplatsupport/src/mach/nvidia/serial.c index 825a276bf..807c63dd8 100644 --- a/libplatsupport/src/mach/nvidia/serial.c +++ b/libplatsupport/src/mach/nvidia/serial.c @@ -94,6 +94,12 @@ struct tk1_uart_regs { }; typedef volatile struct tk1_uart_regs tk1_uart_regs_t; +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + static inline tk1_uart_regs_t* tk1_uart_get_priv(ps_chardevice_t *d) { @@ -139,6 +145,31 @@ tk1_uart_set_rbr_irq(tk1_uart_regs_t *regs, bool enable) regs->ier_dlab = ier; } +static int +internal_uart_tx_busy(tk1_uart_regs_t* regs) +{ + return ((regs->lsr & LSR_THRE_EMPTY) != LSR_THRE_EMPTY); +} + +static int +internal_uart_tx(tk1_uart_regs_t* regs, uint8_t c) +{ + regs->thr_dlab = c; +} + +static void internal_uart_busy_wait_tx_ready(tk1_uart_regs_t* regs) +{ + while (internal_uart_tx_busy(regs)) { + /* busy waiting loop */ + } +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + int uart_getchar(ps_chardevice_t *d) { tk1_uart_regs_t* regs = tk1_uart_get_priv(d); @@ -155,19 +186,35 @@ int uart_getchar(ps_chardevice_t *d) int uart_putchar(ps_chardevice_t* d, int c) { tk1_uart_regs_t* regs = tk1_uart_get_priv(d); - uint32_t lsr = regs->lsr; - if (((lsr & LSR_THRE_EMPTY) == LSR_THRE_EMPTY)) { - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (internal_uart_tx_busy(regs)) { + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; } + internal_uart_busy_wait_tx_ready(reg_base); + } - regs->thr_dlab = (uint8_t) c; + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; - return c; - } else { - return -1; + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx(regs, '\r'); + internal_uart_busy_wait_tx_ready(regs); } + + internal_uart_tx(regs, byte); + + return byte; } static void diff --git a/libplatsupport/src/mach/zynq/serial.c b/libplatsupport/src/mach/zynq/serial.c index d38adbdfa..906b5feee 100644 --- a/libplatsupport/src/mach/zynq/serial.c +++ b/libplatsupport/src/mach/zynq/serial.c @@ -213,33 +213,71 @@ int uart_putchar( ps_chardevice_t *d, int c) { - int ret = -1; zynq_uart_regs_t *regs = zynq_uart_get_priv(d); + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + int send_cr = ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)); + + /* Usually we send one char only and check UART_SR_TFUL to see if the TX + * FIFO is full. If we have to send CR+LF, then check if there is space for + * 2 bytes in the TX FIFO via UART_SR_TTRIG. This works because the TX + * trigger level is 63 and this bit is set if the FIFO level is greater or + * equal the trigger level. + */ + unsigned int fifo_flag = send_cr ? UART_SR_TTRIG : UART_SR_TFUL; + if (!(regs->sr & fifo_flag)) + { + if (d->flags & SERIAL_TX_NONBLOCKING) + { + return -1; /* not enough space in the FIFO */ + } + while (!(regs->sr & fifo_flag)) { + /* do a busy-waiting loop */ + } + } + + /* save imr */ uint32_t imr = regs->imr; regs->idr = imr; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - /* check if 2 bytes are free - tx trigger level is 63 and - * this bit is set if the fifo level is >= the trigger level + if (send_cr) + { + /* Send CR+LF (='\r\n'). We have checked above that there is enough + * space in the FIFO to send two chars. */ - if (!(regs->sr & UART_SR_TTRIG)) { + regs->fifo = '\r'; + } - regs->fifo = '\r'; - regs->fifo = '\n'; + regs->fifo = byte; + + /* It is questionable why the initial implementation drains the FIFO now, + * but we keep this behavior. Maybe this is intended to have a guarantee + * that any char accepted by putchar() is really printed before the + * function returns. Thus no log data shows up asynchronously because the + * FIFO drains while the CPU is doing something else. + * + * Potential improvement for the serial subsystem is defining more flags + * besides SERIAL_AUTO_CR that allow controlling the behavior externally + * and thus align all platform implementation: + * + * - SERIAL_DO_NOT_BLOCK could disable putchar() blocking initially if the + * FIFO is full. It would return an error in this case. The flag could + * also be SERIAL_BLOCK and the recommendation is to always set it. + * + * - SERIAL_DRAIN_FIFO could make putchar() always drain the FIFO before + * leaving. For a UART used for logging, this could be useful to give a + * guarantee that logs printed before the code continues. + */ - ret = '\n'; - } - } else if (!(regs->sr & UART_SR_TFUL)) { - regs->fifo = c; - ret = c; + while ((regs->sr & (UART_SR_TEMPTY | UART_SR_TACTIVE)) != UART_SR_TEMPTY) + { + /* busy waiting loop */ } - while ((regs->sr & (UART_SR_TEMPTY | UART_SR_TACTIVE)) != UART_SR_TEMPTY); - regs->ier = imr; - return ret; + return byte; } static void uart_handle_irq( diff --git a/libplatsupport/src/plat/am335x/serial.c b/libplatsupport/src/plat/am335x/serial.c index 2c215243c..48f18303a 100644 --- a/libplatsupport/src/plat/am335x/serial.c +++ b/libplatsupport/src/plat/am335x/serial.c @@ -30,16 +30,22 @@ int uart_getchar(ps_chardevice_t *d) return ch; } -int uart_putchar(ps_chardevice_t* d, int c) +static void internal_uart_putchar(void* vaddr, int c) { - while (!(*REG_PTR(d->vaddr, LSR) & LSR_TXFIFOE)) { + while (!(*REG_PTR(vaddr, LSR) & LSR_TXFIFOE)) { continue; } - *REG_PTR(d->vaddr, THR) = c; + *REG_PTR(vaddr, THR) = c; +} + +int uart_putchar(ps_chardevice_t* d, int c) +{ + void* vaddr = d->vaddr; + /* SERIAL_AUTO_CR: Send '\r' (CR) before every '\n' (LF). */ if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); + internal_uart_putchar(vaddr, '\r'); } - + internal_uart_putchar(vaddr, c); return c; } diff --git a/libplatsupport/src/plat/apq8064/serial.c b/libplatsupport/src/plat/apq8064/serial.c index 51884047b..2fb8756fd 100644 --- a/libplatsupport/src/plat/apq8064/serial.c +++ b/libplatsupport/src/plat/apq8064/serial.c @@ -28,15 +28,23 @@ uart_handle_irq(ps_chardevice_t* d UNUSED) { } -int uart_putchar(ps_chardevice_t* d, int c) +static void internal_uart_putchar(void* vaddr, int c) { - while (!(*UART_REG(d->vaddr, USR) & USR_TXEMP)); + while (!(*UART_REG(vaddr, USR) & USR_TXEMP)) { + continue; + } + *UART_REG(vaddr, UNTX) = 1; + *UART_REG(vaddr, UTF) = c & 0xff; +} - *UART_REG(d->vaddr, UNTX) = 1; - *UART_REG(d->vaddr, UTF) = c & 0xff; +int uart_putchar(ps_chardevice_t* d, int c) +{ + void* vaddr = d->vaddr; + /* SERIAL_AUTO_CR: Send '\r' (CR) before every '\n' (LF). */ if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); + internal_uart_putchar(vaddr, '\r'); } + internal_uart_putchar(vaddr, c); return 0; } diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c deleted file mode 100644 index 5b8db1ad3..000000000 --- a/libplatsupport/src/plat/fvp/serial.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -/* Mostly copy/paste from the HiKey plat. - * Should be moved to a common driver file for PL011 */ - -#include -#include -#include -#include "../../chardev.h" - -#define RHR_MASK MASK(8) -#define UARTDR 0x000 -#define UARTFR 0x018 -#define UARTIMSC 0x038 -#define UARTICR 0x044 -#define PL011_UARTFR_TXFF BIT(5) -#define PL011_UARTFR_RXFE BIT(4) - -#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) - -int uart_getchar(ps_chardevice_t *d) -{ - int ch = EOF; - - if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) { - ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK; - } - return ch; -} - -int uart_putchar(ps_chardevice_t* d, int c) -{ - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0); - - *REG_PTR(d->vaddr, UARTDR) = c; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - return c; -} - -static void -uart_handle_irq(ps_chardevice_t* dev) -{ - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; -} - -int uart_init(const struct dev_defn* defn, - const ps_io_ops_t* ops, - ps_chardevice_t* dev) -{ - memset(dev, 0, sizeof(*dev)); - void* vaddr = chardev_map(defn, ops); - if (vaddr == NULL) { - return -1; - } - - /* Set up all the device properties. */ - dev->id = defn->id; - dev->vaddr = (void*)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; - dev->handle_irq = &uart_handle_irq; - dev->irqs = defn->irqs; - dev->ioops = *ops; - dev->flags = SERIAL_AUTO_CR; - - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; - return 0; -} diff --git a/libplatsupport/src/plat/hifive/uart.c b/libplatsupport/src/plat/hifive/uart.c index b6abdcb82..6db3865d7 100644 --- a/libplatsupport/src/plat/hifive/uart.c +++ b/libplatsupport/src/plat/hifive/uart.c @@ -36,37 +36,86 @@ struct uart { }; typedef volatile struct uart uart_regs_t; -static inline uart_regs_t* -uart_get_priv(ps_chardevice_t *d) +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static uart_regs_t* uart_get_regs(ps_chardevice_t *d) { return (uart_regs_t*)d->vaddr; } +static int internal_uart_is_tx_fifo_full(uart_regs_t* regs) +{ + return regs->txdata & UART_TX_DATA_FULL; +} + +static int internal_uart_tx_byte(uart_regs_t* regs, uint8_t c) +{ + regs->txdata = c & UART_TX_DATA_MASK; +} + +static void internal_uart_busy_wait_tx_ready(uart_regs_t* regs) +{ + while (internal_uart_is_tx_fifo_full(regs)) { + /* busy waiting loop */ + } +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + int uart_getchar(ps_chardevice_t *d) { - uart_regs_t* regs = uart_get_priv(d); - uint32_t reg = regs->rxdata; - int c = -1; + uart_regs_t* regs = uart_get_regs(d); - if (!(reg & UART_RX_DATA_EMPTY)) { - c = reg & UART_RX_DATA_MASK; + uint32_t rxdata = regs->rxdata; + if (rxdata & UART_RX_DATA_EMPTY) + { + return -1; } - return c; + + /* return only the lowest 8 bits */ + return (uint8_t)(rxdata & UART_RX_DATA_MASK); } int uart_putchar(ps_chardevice_t* d, int c) { - uart_regs_t* regs = uart_get_priv(d); - if (!(regs->txdata & UART_TX_DATA_FULL)) { - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - regs->txdata = '\r' & UART_TX_DATA_MASK; - while(regs->txdata & UART_TX_DATA_FULL) {} + uart_regs_t* regs = uart_get_regs(d); + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (internal_uart_is_tx_fifo_full(regs)) { + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; } - regs->txdata = c & UART_TX_DATA_MASK; - return c; - } else { - return -1; + internal_uart_busy_wait_tx_ready(regs); } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx_byte(regs, '\r'); + internal_uart_busy_wait_tx_ready(regs); + } + + internal_uart_tx_byte(regs, byte); + + return byte; } static void @@ -80,7 +129,6 @@ int uart_init(const struct dev_defn* defn, const ps_io_ops_t* ops, ps_chardevice_t* dev) { - uart_regs_t* regs; /* Attempt to map the virtual address, assure this works */ void* vaddr = chardev_map(defn, ops); if (vaddr == NULL) { @@ -99,7 +147,7 @@ int uart_init(const struct dev_defn* defn, dev->ioops = *ops; dev->flags = SERIAL_AUTO_CR; - regs = uart_get_priv(dev); + uart_regs_t* regs = uart_get_regs(dev); /* * Enable TX and RX and don't set any watermark levels. diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c deleted file mode 100644 index e9f30d70b..000000000 --- a/libplatsupport/src/plat/hikey/serial.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include "../../chardev.h" - -#define RHR_MASK MASK(8) -#define UARTDR 0x000 -#define UARTFR 0x018 -#define UARTIMSC 0x038 -#define UARTICR 0x044 -#define PL011_UARTFR_TXFF BIT(5) -#define PL011_UARTFR_RXFE BIT(4) - -#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) - -int uart_getchar(ps_chardevice_t *d) -{ - int ch = EOF; - - if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) { - ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK; - } - return ch; -} - -int uart_putchar(ps_chardevice_t* d, int c) -{ - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0); - - *REG_PTR(d->vaddr, UARTDR) = c; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - return c; -} - -static void -uart_handle_irq(ps_chardevice_t* dev) -{ - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; -} - -int uart_init(const struct dev_defn* defn, - const ps_io_ops_t* ops, - ps_chardevice_t* dev) -{ - memset(dev, 0, sizeof(*dev)); - void* vaddr = chardev_map(defn, ops); - if (vaddr == NULL) { - return -1; - } - - /* Set up all the device properties. */ - dev->id = defn->id; - dev->vaddr = (void*)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; - dev->handle_irq = &uart_handle_irq; - dev->irqs = defn->irqs; - dev->ioops = *ops; - dev->flags = SERIAL_AUTO_CR; - - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; - return 0; -} diff --git a/libplatsupport/src/plat/odroidc2/serial.c b/libplatsupport/src/plat/odroidc2/serial.c index 92cd38865..e14315380 100644 --- a/libplatsupport/src/plat/odroidc2/serial.c +++ b/libplatsupport/src/plat/odroidc2/serial.c @@ -16,29 +16,95 @@ #define UART_TX_FULL BIT(21) #define UART_RX_EMPTY BIT(20) -#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) +#define REG_PTR(base, offset) ( (volatile uint32_t *)( \ + (uintptr_t)(base) + (offset) ) ) -int uart_getchar(ps_chardevice_t *d) +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static int internal_uart_is_tx_fifo_full(void *reg_base) +{ + return *REG_PTR(reg_base, UART_STATUS) & UART_TX_FULL; +} + +static void internal_uart_tx_byte(void *reg_base, uint8_t byte) +{ + *REG_PTR(reg_base, UART_WFIFO) = byte; +} + +static int internal_uart_is_rx_empty(void *reg_base) +{ + return *REG_PTR(d->reg_base, UART_STATUS) & UART_RX_EMPTY; +} + +static uint8_t internal_uart_rx_byte(void *reg_base) +{ + return (uint8_t)(*REG_PTR(reg_base, UART_RFIFO)); +} + +static void internal_uart_busy_wait_tx_ready(void *reg_base) { - while ((*REG_PTR(d->vaddr, UART_STATUS) & UART_RX_EMPTY)); - return *REG_PTR(d->vaddr, UART_RFIFO); + while (internal_uart_is_tx_fifo_full(reg_base)) { + /* busy waiting loop */ + } } -int uart_putchar(ps_chardevice_t *d, int c) +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_getchar(ps_chardevice_t *dev) { - while ((*REG_PTR(d->vaddr, UART_STATUS) & UART_TX_FULL)); + void* reg_base = dev->vaddr; - /* Add character to the buffer. */ - *REG_PTR(d->vaddr, UART_WFIFO) = c & 0x7f; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); + if (internal_uart_is_rx_empty(reg_base) { + return -1; } - return c; + return internal_uart_rx_byte(reg_base); +} + +int uart_putchar(ps_chardevice_t *dev, int c) +{ + void* reg_base = dev->vaddr; + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (internal_uart_is_tx_fifo_full(regs)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; + } + internal_uart_busy_wait_tx_ready(reg_base); + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx_byte(reg_base, '\r'); + internal_uart_busy_wait_tx_ready(reg_base); + } + + internal_uart_putchar(vaddr, byte); + + return byte; } -static void uart_handle_irq(ps_chardevice_t *dev) +static void uart_handle_irq(UNUSED ps_chardevice_t *dev) { /* nothing to do, interrupts are not used */ } diff --git a/libplatsupport/src/plat/omap3/serial.c b/libplatsupport/src/plat/omap3/serial.c index 25bbdcffd..827ec55f2 100644 --- a/libplatsupport/src/plat/omap3/serial.c +++ b/libplatsupport/src/plat/omap3/serial.c @@ -22,25 +22,93 @@ #define IMXUART_LSR_TXSRE (1<<6) #define IMXUART_LSR_RXFIFOSTS (1<<7) -#define REG_PTR(base, offset) ((volatile uint32_t *)((char*)(base) + (offset))) +#define REG_PTR(base, offset) ( (volatile uint32_t *)( \ + (uintptr_t)(base) + (offset) ) ) -int uart_getchar(ps_chardevice_t* d) + +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static int internal_uart_is_tx_idle(void *reg_base) { - if (*REG_PTR(d->vaddr, IMXUART_LSR) & IMXUART_LSR_RXFIFIOE) { - return *REG_PTR(d->vaddr, IMXUART_RHR); - } else { - return -1; + return *REG_PTR(d->vaddr, MU_LSR) & MU_LSR_TXIDLE; +} + +static void internal_uart_tx_byte(void *reg_base, uint8_t byte) +{ + *REG_PTR(d->vaddr, IMXUART_THR) = byte; +} + +static int internal_uart_is_rx_available(void *reg_base) +{ + return *REG_PTR(d->vaddr, IMXUART_LSR) & IMXUART_LSR_RXFIFIOE; +} + +static uint8_t internal_uart_rx_byte(void *reg_base) +{ + return (uint8_t)(*REG_PTR(d->vaddr, IMXUART_RHR)) +} + +static void internal_uart_busy_wait_tx_ready(void *reg_base) +{ + while (!internal_uart_is_tx_idle(reg_base)) { + /* busy waiting loop */ } } +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + int uart_putchar(ps_chardevice_t* d, int c) { - if (*REG_PTR(d->vaddr, IMXUART_LSR) & IMXUART_LSR_TXFIFOE) { - *REG_PTR(d->vaddr, IMXUART_THR) = c; - return c; - } else { + void *reg_base = d->vaddr; + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (!internal_uart_is_tx_idle(reg_base)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; + } + internal_uart_is_tx_idle(reg_base); + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx_byte(reg_base, '\r'); + internal_uart_is_tx_idle(reg_base); + } + + internal_uart_tx_byte(reg_base, byte); + + return byte; +} + +int uart_getchar(ps_chardevice_t* d) +{ + void *reg_base = d->vaddr; + + /* if UART is does not have data return an error */ + if (!internal_uart_is_rx_available(reg_base)) { return -1; } + + return internal_uart_rx_byte(reg_base); } static void uart_handle_irq(ps_chardevice_t* d) @@ -65,6 +133,7 @@ uart_init(const struct dev_defn* defn, dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; + dev->flags = SERIAL_AUTO_CR; return 0; } diff --git a/libplatsupport/src/plat/pc99/serial.c b/libplatsupport/src/plat/pc99/serial.c index 22888c803..9041b7e13 100644 --- a/libplatsupport/src/plat/pc99/serial.c +++ b/libplatsupport/src/plat/pc99/serial.c @@ -30,65 +30,134 @@ #define SERIAL_LSR 5 /* Line Status Register (R ) */ #define SERIAL_MSR 6 /* Modem Status Register (R ) */ #define SERIAL_SR 7 /* Scratch Register (RW) */ -#define CONSOLE(port, label) ((port) + (SERIAL_##label)) + #define SERIAL_DLAB BIT(7) #define SERIAL_LSR_DATA_READY BIT(0) #define SERIAL_LSR_TRANSMITTER_EMPTY BIT(5) -int uart_getchar(ps_chardevice_t *device) +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static uint32_t get_console_io_port( + ps_chardevice_t *device, + unsigned int offset) { - uint32_t res; - uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr; + /* Casting points to a specific integer directly is not allowed, must cast + * to uintptr_t and then cast to a specific integer type. + */ + return (uint32_t)((uintptr_t)device->vaddr) + offset; +} - /* Check if character is available. */ - int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res); - if (error != 0) { - return -1; - } - if (!(res & SERIAL_LSR_DATA_READY)) { - return -1; - } +static void console_io_port_read( + ps_chardevice_t *device, + unsigned int port_offset, + uint32_t *data) +{ + return ps_io_port_in( + &device->ioops.io_port_ops, + get_console_io_port(device, port_offset), + 1, /* io_size */ + data); +} - /* retrieve character */ - error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &res); - if (error != 0) { - return -1; +static void console_io_port_write( + ps_chardevice_t *device, + unsigned int port_offset, + uint32_t data) +{ + return ps_io_port_out( + &device->ioops.io_port_ops, + get_console_io_port(device, port_offset), + 1, /* io_size */ + data); +} + +static int serial_is_tx_ready(ps_chardevice_t* device) +{ + uint32_t data; + int ret = console_io_port_read(device, SERIAL_LSR, &data); + if (ret != 0) { + return 0; /* claim transmitter is not ready */ } + return data & SERIAL_LSR_TRANSMITTER_EMPTY; +} - return (int) res; +static int serial_tx_byte(ps_chardevice_t* device, uint8_t byte) +{ + return console_io_port_write(device, SERIAL_THR, byte); } -static int serial_ready(ps_chardevice_t* device) +static void internal_serial_busy_wait_tx_ready(ps_chardevice_t* device) { - uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr; - uint32_t res; - int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res); - if (error != 0) { - return 0; + while (!serial_is_tx_ready(device)) { + /* busy waiting loop */ } - return res & SERIAL_LSR_TRANSMITTER_EMPTY; } -int uart_putchar(ps_chardevice_t* device, int c) +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_getchar(ps_chardevice_t *device) { - uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr; + int ret; + uint32_t data; - /* Check if serial is ready. */ - if (!serial_ready(device)) { + /* Check if character is available. */ + ret = console_io_port_read(device, SERIAL_LSR, &data); + if (ret != 0) { + return -1; + } + if (!(data & SERIAL_LSR_DATA_READY)) { return -1; } - /* Write out the next character. */ - ps_io_port_out(&device->ioops.io_port_ops, CONSOLE(io_port, THR), 1, c); - - if (c == '\n') { - /* If we output immediately then odds are the transmit buffer - * will be full, so we have to wait */ - while (!serial_ready(device)); - uart_putchar(device, '\r'); + /* retrieve character */ + ret = console_io_port_read(device, SERIAL_RBR, &data); + if (ret != 0) { + return -1; } - return c; + return (uint8_t)data; +} + +int uart_putchar(ps_chardevice_t* device, int c) +{ + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (!serial_is_tx_ready(device)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; + } + internal_serial_busy_wait_tx_ready(device); + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + /* Write CR, ignore the return code. */ + (void)serial_tx_byte(device, '\r'); + internal_serial_busy_wait_tx_ready(device); + } + + /* Write out the character, ignore return code. */ + (void)serial_tx_byte(device, byte); + + return byte; } static void uart_handle_irq(ps_chardevice_t* device UNUSED) @@ -108,58 +177,58 @@ uart_init(const struct dev_defn* defn, const ps_io_ops_t* ops, ps_chardevice_t* dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; + dev->flags = SERIAL_AUTO_CR; /* Initialise the device. */ - uint32_t io_port = (uint32_t) (uintptr_t)dev->vaddr; /* clear DLAB - Divisor Latch Access Bit */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 & ~SERIAL_DLAB) != 0) { + if (console_io_port_write(dev, SERIAL_LCR, 0x00 & ~SERIAL_DLAB) != 0) { return -1; } /* disable generating interrupts */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x00) != 0) { + if (console_io_port_write(dev, SERIAL_IER, 0x00) != 0) { return -1; } /* set DLAB to*/ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 | SERIAL_DLAB) != 0) { + if (console_io_port_write(dev, SERIAL_LCR, 0x00 | SERIAL_DLAB) != 0) { return -1; } /* set low byte of divisor to 0x01 = 115200 baud */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLL), 1, 0x01) != 0) { + if (console_io_port_write(dev, SERIAL_DLL, 0x01) != 0) { return -1; } /* set high byte of divisor to 0x00 */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLH), 1, 0x00) != 0) { + if (console_io_port_write(dev, SERIAL_DLH, 0x00) != 0) { return -1; } /* line control register: set 8 bit, no parity, 1 stop bit; clear DLAB */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x03 & ~SERIAL_DLAB) != 0) { + if (console_io_port_write(dev, SERIAL_LCR, 0x03 & ~SERIAL_DLAB) != 0) { return -1; } /* modem control register: set DTR/RTS/OUT2 */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, MCR), 1, 0x0b) != 0) { + if (console_io_port_write(dev, SERIAL_MCR, 0x0b) != 0) { return -1; } uint32_t temp; /* clear receiver port */ - if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &temp) != 0) { + if (console_io_port_write(dev, SERIAL_RBR, &temp) != 0) { return -1; } /* clear line status port */ - if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &temp) != 0) { + if (console_io_port_write(dev, SERIAL_LSR, &temp) != 0) { return -1; } /* clear modem status port */ - if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, MSR), 1, &temp) != 0) { + if (console_io_port_write(dev, SERIAL_MSR, &temp) != 0) { return -1; } /* Enable the receiver interrupt. */ - if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x01) != 0) { + if (console_io_port_write(dev, SERIAL_IER, 0x01) != 0) { return -1; } diff --git a/libplatsupport/src/plat/qemu-arm-virt/serial.c b/libplatsupport/src/plat/qemu-arm-virt/serial.c deleted file mode 100644 index 47b665e16..000000000 --- a/libplatsupport/src/plat/qemu-arm-virt/serial.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -/* Mostly copy/paste from the HiKey plat. - * Should be moved to a common driver file for PL011 */ - -#include -#include -#include -#include "../../chardev.h" - -#define RHR_MASK MASK(8) -#define UARTDR 0x000 -#define UARTFR 0x018 -#define UARTIMSC 0x038 -#define UARTICR 0x044 -#define PL011_UARTFR_TXFF BIT(5) -#define PL011_UARTFR_RXFE BIT(4) - -#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) - -int uart_getchar(ps_chardevice_t *d) -{ - int ch = EOF; - - if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) { - ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK; - } - return ch; -} - -int uart_putchar(ps_chardevice_t *d, int c) -{ - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0); - - *REG_PTR(d->vaddr, UARTDR) = c; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - return c; -} - -static void uart_handle_irq(ps_chardevice_t *dev) -{ - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; -} - -int uart_init(const struct dev_defn *defn, - const ps_io_ops_t *ops, - ps_chardevice_t *dev) -{ - memset(dev, 0, sizeof(*dev)); - void *vaddr = chardev_map(defn, ops); - if (vaddr == NULL) { - return -1; - } - - /* Set up all the device properties. */ - dev->id = defn->id; - dev->vaddr = (void *)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; - dev->handle_irq = &uart_handle_irq; - dev->irqs = defn->irqs; - dev->ioops = *ops; - dev->flags = SERIAL_AUTO_CR; - - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; - return 0; -} diff --git a/libplatsupport/src/plat/rockpro64/serial.c b/libplatsupport/src/plat/rockpro64/serial.c index a7a246250..95cf01471 100644 --- a/libplatsupport/src/plat/rockpro64/serial.c +++ b/libplatsupport/src/plat/rockpro64/serial.c @@ -18,50 +18,113 @@ #define LSR_TXFIFOE BIT(5) #define LSR_RXFIFOE BIT(0) -#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) +#define REG_PTR(base, offset) ( (volatile uint32_t *)( \ + (uintptr_t)(base) + (offset) ) ) -int uart_getchar(ps_chardevice_t *d) +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static int internal_uart_is_tx_fifo_empty(void *reg_base) +{ + return *REG_PTR(vaddr, LSR) & LSR_TXFIFOE; +} + +static void internal_uart_tx_byte(void *reg_base, uint8_t c) +{ + *REG_PTR(vaddr, THR) = c; +} + +static int internal_uart_is_rx_available(void *reg_base) +{ + return *REG_PTR(d->vaddr, LSR) & LSR_RXFIFOE; +} + +static uint8_t internal_uart_rx_byte(void *reg_base) +{ + return (uint8_t)(REG_PTR(d->vaddr, RHR) & RHR_MASK); +} + +static void internal_uart_busy_wait_tx_ready(void *reg_base) { - int ch = EOF; + while (!internal_uart_is_tx_fifo_empty(reg_base)) { + /* busy waiting loop */ + } +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_getchar(ps_chardevice_t *dev) +{ + void *reg_base = dev->vaddr; - if (*REG_PTR(d->vaddr, LSR) & LSR_RXFIFOE) { - ch = *REG_PTR(d->vaddr, RHR) & RHR_MASK; + if (!internal_uart_is_rx_available(reg_base)) { + return -1; } - return ch; + + return internal_uart_rx_byte(reg_base); } -int uart_putchar(ps_chardevice_t* d, int c) +int uart_putchar(ps_chardevice_t *dev, int c) { - while (!(*REG_PTR(d->vaddr, LSR) & LSR_TXFIFOE)) { - continue; + void *reg_base = dev->vaddr; + + /* Check if the TX FIFO has space. If not and SERIAL_TX_NONBLOCKING is set, + * then fail the call, otherwise do busy waiting. + */ + if (!internal_uart_is_tx_fifo_empty(reg_base)) + if (d->flags & SERIAL_TX_NONBLOCKING) { + return -1; + } + internal_uart_busy_wait_tx_ready(reg_base); } - *REG_PTR(d->vaddr, THR) = c; - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* SERIAL_AUTO_CR enables sending a CR before any LF, which is the common + * thing to do for a serial terminal. CR/LR are considered an atom, thus a + * blocking wait will be used even if SERIAL_TX_NONBLOCKING is set to ensure + * LF is sent. + * TODO: Check in advance if the TX FIFO has space for two chars if + * SERIAL_TX_NONBLOCKING is set. + */ + if ((byte == '\n') && (d->flags & SERIAL_AUTO_CR)) { + internal_uart_tx_byte(reg_base, '\r'); + internal_uart_busy_wait_tx_ready(reg_base); } - return c; + internal_uart_tx_byte(reg_base, c); + + return byte; } static void -uart_handle_irq(ps_chardevice_t* d UNUSED) +uart_handle_irq(ps_chardevice_t *dev UNUSED) { /* nothing to do */ } -int uart_init(const struct dev_defn* defn, - const ps_io_ops_t* ops, - ps_chardevice_t* dev) +int uart_init(const struct dev_defn *defn, + const ps_io_ops_t *ops, + ps_chardevice_t *dev) { memset(dev, 0, sizeof(*dev)); - void* vaddr = chardev_map(defn, ops); + + void *vaddr = chardev_map(defn, ops); if (vaddr == NULL) { return -1; } /* Set up all the device properties. */ dev->id = defn->id; - dev->vaddr = (void*)vaddr; + dev->vaddr = (void *)vaddr; dev->read = &uart_read; dev->write = &uart_write; dev->handle_irq = &uart_handle_irq; @@ -70,5 +133,6 @@ int uart_init(const struct dev_defn* defn, dev->flags = SERIAL_AUTO_CR; *REG_PTR(dev->vaddr, IER) = IER_RHRIT; + return 0; } diff --git a/libplatsupport/src/serial.c b/libplatsupport/src/serial.c index 2b356ce47..fbdcbaf4e 100644 --- a/libplatsupport/src/serial.c +++ b/libplatsupport/src/serial.c @@ -17,8 +17,18 @@ ssize_t uart_write( void *token UNUSED) { const unsigned char *data = (const unsigned char *)vdata; - for (int i = 0; i < count; i++) { - if (uart_putchar(d, data[i]) < 0) { + for (unsigned int i = 0; i < count; i++) { + /* Call the UART driver, it is supposed to implement the handling for + * the flags SERIAL_TX_NONBLOCKING and SERIAL_AUTO_CR properly. + */ + int ret = uart_putchar(d, data[i]); + if (ret < 0) { + /* There is nothing we can do, so abort and return how much data we + * could send. Unfortunately, we can return the actual error code, + * so the caller wont know what exactly failed. However, when + * SERIAL_TX_NONBLOCKING is enabled, it's likely that the TX FIFO is + * full so the caller should wait and send the remaining data. + */ return i; } } @@ -33,7 +43,7 @@ ssize_t uart_read( void *token UNUSED) { char *data = (char *)vdata; - for (int i = 0; i < count; i++) { + for (unsigned int i = 0; i < count; i++) { int ret = uart_getchar(d); if (EOF == ret) { return i;