From 0c84064706532e68f05c38a875024ba28cc74e43 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Thu, 11 Jan 2024 17:14:30 +0100 Subject: [PATCH] libplatsupport: separate drivers from platforms Signed-off-by: Axel Heider --- .../include/platsupport/driver/uart_ns16550.h | 117 ++++++++++++++++ .../include/platsupport/driver/uart_pl011.h | 128 ++++++++++++++++++ .../qemu-riscv-virt/platsupport/plat/serial.h | 43 +----- libplatsupport/src/plat/fvp/serial.c | 56 ++++---- libplatsupport/src/plat/hikey/serial.c | 54 ++++---- .../src/plat/qemu-arm-virt/serial.c | 58 ++++---- .../src/plat/qemu-riscv-virt/serial.c | 96 +++++-------- 7 files changed, 358 insertions(+), 194 deletions(-) create mode 100644 libplatsupport/include/platsupport/driver/uart_ns16550.h create mode 100644 libplatsupport/include/platsupport/driver/uart_pl011.h diff --git a/libplatsupport/include/platsupport/driver/uart_ns16550.h b/libplatsupport/include/platsupport/driver/uart_ns16550.h new file mode 100644 index 000000000..b6318153e --- /dev/null +++ b/libplatsupport/include/platsupport/driver/uart_ns16550.h @@ -0,0 +1,117 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Driver for a 16550 compatible UART. + */ + +#pragma once + +#include +#include +#include +#include + +#define NS16550_IER_ERBFI BIT(0) /* Enable Received Data Available Interrupt */ +#define NS16550_IER_ETBEI BIT(1) /* Enable Transmitter Holding Register Empty Interrupt */ +#define NS16550_IER_ELSI BIT(2) /* Enable Receiver Line Status Interrupt */ +#define NS16550_IER_EDSSI BIT(3) /* Enable MODEM Status Interrupt */ + +#define NS16550_FCR_ENABLE_FIFOS BIT(0) +#define NS16550_FCR_RESET_RX_FIFO BIT(1) +#define NS16550_FCR_RESET_TX_FIFO BIT(2) +#define NS16550_FCR_TRIGGER_1 (0u << 6) +#define NS16550_FCR_TRIGGER_4 (1u << 6) +#define NS16550_FCR_TRIGGER_8 (2u << 6) +#define NS16550_FCR_TRIGGER_14 (3u << 6) + +#define NS16550_LCR_DLAB BIT(7) /* Divisor Latch Access */ + +#define NS16550_LSR_DR BIT(0) /* Data Ready */ +#define NS16550_LSR_THRE BIT(5) /* Transmitter Holding Register Empty */ + +/* There are different NS16550 hardware implementations. The classic size of + * each register is just one byte, but some implementations started to use + * 32-bit registers, as this fits better with the natural alignment. + */ +#if defined(NS16550_WITH_REG32) +typedef volatile uint32_t ns16550_reg_t; +#elif defined(NS16550_WITH_REG8) +typedef volatile uint8_t ns16550_reg_t; +#else +#error "define NS16550_WITH_REG[8|32]" +#endif + +typedef struct { + /* 0x00 */ + ns16550_reg_t rbr_dll_thr; /* Receiver Buffer Register (Read Only) + * Divisor Latch (LSB) + * Transmitter Holding Register (Write Only) + */ + /* 0x01 or 0x04 */ + ns16550_reg_t dlm_ier; /* Divisor Latch (MSB) + * Interrupt Enable Register + */ + /* 0x02 or 0x08 */ + ns16550_reg_t iir_fcr; /* Interrupt Identification Register (Read Only) + * FIFO Control Register (Write Only) + */ + /* 0x03 or 0x0c */ + ns16550_reg_t lcr; /* Line Control Register */ + /* 0x04 or 0x10 */ + ns16550_reg_t mcr; /* MODEM Control Register */ + /* 0x05 or 0x14 */ + ns16550_reg_t lsr; /* Line Status Register */ + /* 0x06 or 0x18 */ + ns16550_reg_t msr; /* MODEM Status Register */ + /* 0x07 or 0x1c */ +} ns16550_regs_t; + + +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static bool ns16550_is_tx_empty(ns16550_regs_t *regs) +{ + /* The THRE bit is set when the FIFO is fully empty. There seems no way to + * detect if the FIFO is partially empty only, so we can't implement a + * "tx_ready" check. + */ + return (0 != (regs->lsr & NS16550_LSR_THRE)); +} + +static void ns16550_tx_byte(ns16550_regs_t *regs, uint8_t byte) +{ + /* Caller has to ensure TX FIFO is ready */ + regs->rbr_dll_thr = byte; +} + +static bool ns16550_is_rx_empty(ns16550_regs_t *regs) +{ + return (0 == (regs->lsr & NS16550_LSR_DR)); +} + +static int ns16550_rx_byte(ns16550_regs_t *regs) +{ + /* Caller has to ensure RX FIFO has data */ + return regs->rbr_dll_thr; +} + + +/* + ******************************************************************************* + * UART access helpers + ******************************************************************************* + */ + +/* + * Returns a char from the TX FIFO or EOF if the FIFO is empty. + */ +static int ns16550_get_char_or_EOF(ns16550_regs_t *regs) +{ + return ns16550_is_rx_empty(regs) ? EOF : ns16550_rx_byte(regs); +} diff --git a/libplatsupport/include/platsupport/driver/uart_pl011.h b/libplatsupport/include/platsupport/driver/uart_pl011.h new file mode 100644 index 000000000..119044387 --- /dev/null +++ b/libplatsupport/include/platsupport/driver/uart_pl011.h @@ -0,0 +1,128 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Driver for a ARM PL011 UART. + */ + +#pragma once + +#include +#include +#include +#include + +#define PL011_FR_BUSY BIT(3) /* UART busy */ +#define PL011_FR_RXFE BIT(4) /* Receive FIFO empty */ +#define PL011_FR_TXFF BIT(5) /* Transmit FIFO full */ +#define PL011_FR_RXFF BIT(6) /* Receive FIFO full */ +#define PL011_FR_TXFE BIT(7) /* Transmit FIFO empty */ + +#define PL011_IMSC_RXIM BIT(4) /* RX interrupt */ +#define PL011_IMSC_TXIM BIT(5) /* TX interrupt */ +#define PL011_IMSC_RTIM BIT(6) /* RX timeout interrupt */ +#define PL011_IMSC_OEIM BIT(10) /* Overrun timeout */ + +#define PL011_CR_UARTEN BIT(0) /* UART enable */ +#define PL011_CR_TXE BIT(8) /* Transmit enable */ +#define PL011_CR_RXE BIT(9) /* Receive enable */ + +// 0111 1111 0000 +#define PL011_ICR_RXIC BIT(4) +#define PL011_ICR_TXIC BIT(5) + +typedef volatile struct { + uint32_t dr; /* 0x00 */ + uint32_t _rfu_04; /* 0x04 */ + uint32_t _rfu_08; /* 0x08 */ + uint32_t _rfu_0c; /* 0x0c */ + uint32_t _rfu_10; /* 0x10 */ + uint32_t _rfu_14; /* 0x14 */ + uint32_t fr; /* 0x18 */ + uint32_t _rfu_1c; /* 0x1c */ + uint32_t _rfu_20; /* 0x20 */ + uint32_t _rfu_24; /* 0x24 */ + uint32_t _rfu_28; /* 0x28 */ + uint32_t _rfu_2c; /* 0x2c */ + uint32_t _rfu_30; /* 0x30 */ + uint32_t _rfu_34; /* 0x34 */ + uint32_t imsc; /* 0x38 */ + uint32_t _rfu_3c; /* 0x3c */ + uint32_t _rfu_40; /* 0x40 */ + uint32_t icr; /* 0x44 */ + uint32_t _rfu_48; /* 0x48 */ + uint32_t _rfu_4c; /* 0x4c */ +} pl011_regs_t; + + +/* + ******************************************************************************* + * UART access primitives + ******************************************************************************* + */ + +static bool pl011_is_rx_fifo_empty(pl011_regs_t *regs) +{ + return (0 != (regs->fr & PL011_FR_RXFE)); +} + +static bool pl011_is_tx_fifo_full(pl011_regs_t *regs) +{ + return (0 != (regs->fr & PL011_FR_TXFF)); +} + +static void pl011_tx_byte(pl011_regs_t *regs, uint8_t c) +{ + /* Caller has to ensure TX FIFO has space */ + regs->dr = c; +} + +static uint8_t pl011_rx_byte(pl011_regs_t *regs) +{ + return (uint8_t)(regs->dr & 0xFF); +} + +static void pl011_clear_interrupt(pl011_regs_t *regs) +{ + regs->icr = 0x7f0; +} + +/* + ******************************************************************************* + * UART access helpers + ******************************************************************************* + */ + +/* + * Returns a char from the TX FIFO or EOF if the FIFO is empty. + */ +static int pl011_get_char_or_EOF(pl011_regs_t *regs) +{ + return pl011_is_rx_fifo_empty(regs) ? EOF : pl011_rx_byte(regs); +} + +/* + * Block until there is space in the TX FIFO, then outputs the char. + */ +static void pl011_put_char_blocking(pl011_regs_t *regs, uint8_t c) +{ + while (pl011_is_tx_fifo_full(regs)) { + /* busy loop */ + } + pl011_tx_byte(regs, c); +} + +/* + * Block until there is space in the TX FIFO, then outputs the char. Optionally + * output a CR (\r) first in case of LF (\n) to support the terminal use case. + */ +static void pl011_put_char_blocking_auto_cr(pl011_regs_t *regs, uint8_t c, + bool is_auto_cr) +{ + if ((c == '\n') && is_auto_cr) { + pl011_put_char_blocking(regs, '\r'); + } + pl011_put_char_blocking(regs, c); +} diff --git a/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h b/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h index 47981329b..b7dad7198 100644 --- a/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h +++ b/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h @@ -2,12 +2,14 @@ * Copyright 2022, HENSOLDT Cyber GmbH * * SPDX-License-Identifier: BSD-2-Clause + * + * + * QEMU RISC-V virt emulates a 16550 compatible UART. + * */ #pragma once -#include - /* This information is taken from the device tree. */ #define UART0_PADDR 0x10000000 #define UART0_IRQ 10 @@ -22,40 +24,3 @@ enum chardev_id { #define DEFAULT_SERIAL_PADDR UART0_PADDR #define DEFAULT_SERIAL_INTERRUPT UART0_IRQ - -/* QEMU RISC-V virt emulates a 16550 compatible UART. */ - -#define UART_IER_ERBFI BIT(0) /* Enable Received Data Available Interrupt */ -#define UART_IER_ETBEI BIT(1) /* Enable Transmitter Holding Register Empty Interrupt */ -#define UART_IER_ELSI BIT(2) /* Enable Receiver Line Status Interrupt */ -#define UART_IER_EDSSI BIT(3) /* Enable MODEM Status Interrupt */ - -#define UART_FCR_ENABLE_FIFOS BIT(0) -#define UART_FCR_RESET_RX_FIFO BIT(1) -#define UART_FCR_RESET_TX_FIFO BIT(2) -#define UART_FCR_TRIGGER_1 (0u << 6) -#define UART_FCR_TRIGGER_4 (1u << 6) -#define UART_FCR_TRIGGER_8 (2u << 6) -#define UART_FCR_TRIGGER_14 (3u << 6) - -#define UART_LCR_DLAB BIT(7) /* Divisor Latch Access */ - -#define UART_LSR_DR BIT(0) /* Data Ready */ -#define UART_LSR_THRE BIT(5) /* Transmitter Holding Register Empty */ - -typedef volatile struct { - uint8_t rbr_dll_thr; /* 0x00 Receiver Buffer Register (Read Only) - * Divisor Latch (LSB) - * Transmitter Holding Register (Write Only) - */ - uint8_t dlm_ier; /* 0x04 Divisor Latch (MSB) - * Interrupt Enable Register - */ - uint8_t iir_fcr; /* 0x08 Interrupt Identification Register (Read Only) - * FIFO Control Register (Write Only) - */ - uint8_t lcr; /* 0xC Line Control Register */ - uint8_t mcr; /* 0x10 MODEM Control Register */ - uint8_t lsr; /* 0x14 Line Status Register */ - uint8_t msr; /* 0x18 MODEM Status Register */ -} uart_regs_t; diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c index e70c8da21..f172ce44c 100644 --- a/libplatsupport/src/plat/fvp/serial.c +++ b/libplatsupport/src/plat/fvp/serial.c @@ -1,54 +1,43 @@ /* + * Copyright 2022, HENSOLDT Cyber GmbH * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) * * SPDX-License-Identifier: BSD-2-Clause + * + * + * FVP emulates PL011 UARTs. + * */ -/* Mostly copy/paste from the HiKey plat. - * Should be moved to a common driver file for PL011 */ - #include #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))) +static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev) +{ + return (pl011_regs_t *)(dev->vaddr); +} 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; + pl011_regs_t *regs = get_pl011_regs(d); + return pl011_get_char_or_EOF(regs); } int uart_putchar(ps_chardevice_t *d, int c) { - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) { - /* busy loop */ - } - *REG_PTR(d->vaddr, UARTDR) = c; - + pl011_regs_t *regs = get_pl011_regs(d); + bool is_auto_cr = d->flags & SERIAL_AUTO_CR; + pl011_put_char_blocking_auto_cr(regs, (uint8_t)c, is_auto_cr); return c; } static void uart_handle_irq(ps_chardevice_t *dev) { - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; + pl011_regs_t *regs = get_pl011_regs(dev); + pl011_clear_interrupt(regs); } int uart_init(const struct dev_defn *defn, @@ -56,6 +45,8 @@ int uart_init(const struct dev_defn *defn, ps_chardevice_t *dev) { memset(dev, 0, sizeof(*dev)); + + /* Map device. */ void *vaddr = chardev_map(defn, ops); if (vaddr == NULL) { return -1; @@ -64,13 +55,16 @@ int uart_init(const struct dev_defn *defn, /* Set up all the device properties. */ dev->id = defn->id; dev->vaddr = (void *)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; + dev->read = &uart_read; /* calls uart_putchar() */ + dev->write = &uart_write; /* calls uart_getchar() */ dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; dev->flags = SERIAL_AUTO_CR; - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; + /* Enable RX and TX interrupt. */ + pl011_regs_t *regs = get_pl011_regs(dev); + regs->imsc = PL011_IMSC_RXIM | PL011_IMSC_RTIM; + return 0; } diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c index 99d6619ab..40658df52 100644 --- a/libplatsupport/src/plat/hikey/serial.c +++ b/libplatsupport/src/plat/hikey/serial.c @@ -1,51 +1,44 @@ /* + * Copyright 2022, HENSOLDT Cyber GmbH * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) * * SPDX-License-Identifier: BSD-2-Clause + * + * + * HiKey uses PL011 UARTs. + * */ #include #include +#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))) +static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev) +{ + return (pl011_regs_t *)(dev->vaddr); +} 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; + pl011_regs_t *regs = get_pl011_regs(d); + return pl011_get_char_or_EOF(regs); } int uart_putchar(ps_chardevice_t *d, int c) { - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) { - /* busy loop */ - } - *REG_PTR(d->vaddr, UARTDR) = c; - + pl011_regs_t *regs = get_pl011_regs(d); + bool is_auto_cr = d->flags & SERIAL_AUTO_CR; + pl011_put_char_blocking_auto_cr(regs, (uint8_t)c, is_auto_cr); return c; } static void uart_handle_irq(ps_chardevice_t *dev) { - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; + pl011_regs_t *regs = get_pl011_regs(dev); + pl011_clear_interrupt(regs); } int uart_init(const struct dev_defn *defn, @@ -53,6 +46,8 @@ int uart_init(const struct dev_defn *defn, ps_chardevice_t *dev) { memset(dev, 0, sizeof(*dev)); + + /* Map device. */ void *vaddr = chardev_map(defn, ops); if (vaddr == NULL) { return -1; @@ -61,13 +56,16 @@ int uart_init(const struct dev_defn *defn, /* Set up all the device properties. */ dev->id = defn->id; dev->vaddr = (void *)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; + dev->read = &uart_read; /* calls uart_putchar() */ + dev->write = &uart_write; /* calls uart_getchar() */ dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; dev->flags = SERIAL_AUTO_CR; - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; + /* Enable RX and TX interrupt. */ + pl011_regs_t *regs = get_pl011_regs(dev); + regs->imsc = PL011_IMSC_RXIM | PL011_IMSC_RTIM; + return 0; } diff --git a/libplatsupport/src/plat/qemu-arm-virt/serial.c b/libplatsupport/src/plat/qemu-arm-virt/serial.c index a18914d02..bdcfcac98 100644 --- a/libplatsupport/src/plat/qemu-arm-virt/serial.c +++ b/libplatsupport/src/plat/qemu-arm-virt/serial.c @@ -1,54 +1,43 @@ /* + * Copyright 2022, HENSOLDT Cyber GmbH * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) * * SPDX-License-Identifier: BSD-2-Clause + * + * + * QEMU arm-virt emulates a PL011 UART. + * */ -/* Mostly copy/paste from the HiKey plat. - * Should be moved to a common driver file for PL011 */ - #include #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))) +static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev) +{ + return (pl011_regs_t *)(dev->vaddr); +} 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; + pl011_regs_t *regs = get_pl011_regs(d); + return pl011_get_char_or_EOF(regs); } int uart_putchar(ps_chardevice_t *d, int c) { - if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { - uart_putchar(d, '\r'); - } - - while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) - /* busy loop */ - } - *REG_PTR(d->vaddr, UARTDR) = c; - + pl011_regs_t *regs = get_pl011_regs(d); + bool is_auto_cr = d->flags & SERIAL_AUTO_CR; + pl011_put_char_blocking_auto_cr(regs, (uint8_t)c, is_auto_cr); return c; } static void uart_handle_irq(ps_chardevice_t *dev) { - *REG_PTR(dev->vaddr, UARTICR) = 0x7f0; + pl011_regs_t *regs = get_pl011_regs(dev); + pl011_clear_interrupt(regs); } int uart_init(const struct dev_defn *defn, @@ -56,21 +45,26 @@ int uart_init(const struct dev_defn *defn, ps_chardevice_t *dev) { memset(dev, 0, sizeof(*dev)); + + /* Map device. */ void *vaddr = chardev_map(defn, ops); if (vaddr == NULL) { return -1; } - /* Set up all the device properties. */ + /* Set up all the device properties. */ dev->id = defn->id; dev->vaddr = (void *)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; + dev->read = &uart_read; /* calls uart_putchar() */ + dev->write = &uart_write; /* calls uart_getchar() */ dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; dev->flags = SERIAL_AUTO_CR; - *REG_PTR(dev->vaddr, UARTIMSC) = 0x50; + /* Enable RX and TX interrupt. */ + pl011_regs_t *regs = get_pl011_regs(dev); + regs->imsc = PL011_IMSC_RXIM | PL011_IMSC_RTIM; + return 0; } diff --git a/libplatsupport/src/plat/qemu-riscv-virt/serial.c b/libplatsupport/src/plat/qemu-riscv-virt/serial.c index b5165a6c8..95a28b053 100644 --- a/libplatsupport/src/plat/qemu-riscv-virt/serial.c +++ b/libplatsupport/src/plat/qemu-riscv-virt/serial.c @@ -2,55 +2,29 @@ * Copyright 2022, HENSOLDT Cyber GmbH * * SPDX-License-Identifier: BSD-2-Clause + * + * + * QEMU riscv-virt emulates a 16550 compatible UART, where the register width + * sticks to the classic size of 8 bits. This is fine, because it's just a + * simulation, but contradicts many actual hardware implementations. There the + * peripheral registers are usually 32-bit wide, because this fits much better + * to the natural bus transfer sizes and alignments. */ -/* QEMU RISC-V virt emulates a 16550 compatible UART. */ - #include #include #include -#include "../../chardev.h" - -static uart_regs_t *uart_get_regs(ps_chardevice_t *dev) -{ - return (uart_regs_t *)(dev->vaddr); -} -/* - ******************************************************************************* - * UART access primitives - ******************************************************************************* - */ - -static bool internal_uart_is_tx_empty(uart_regs_t *regs) -{ - /* The THRE bit is set when the FIFO is fully empty. On real hardware, there - * seems no way to detect if the FIFO is partially empty only, so we can't - * implement a "tx_ready" check. Since QEMU does not emulate a FIFO, this - * does not really matter. - */ - return (0 != (regs->lsr & UART_LSR_THRE)); -} - -static void internal_uart_tx_byte(uart_regs_t *regs, uint8_t byte) -{ - /* Caller has to ensure TX FIFO is ready */ - regs->rbr_dll_thr = byte; -} - -static bool internal_uart_is_rx_empty(uart_regs_t *regs) -{ - return (0 == (regs->lsr & UART_LSR_DR)); -} +#define NS16550_WITH_REG8 +#include +#include "../../chardev.h" -static int internal_uart_rx_byte(uart_regs_t *regs) +static ns16550_regs_t *uart_get_regs(ps_chardevice_t *dev) { - /* Caller has to ensure RX FIFO has data */ - return regs->rbr_dll_thr; + return (ns16550_regs_t *)(dev->vaddr); } - /* ******************************************************************************* * UART access API @@ -59,7 +33,7 @@ static int internal_uart_rx_byte(uart_regs_t *regs) int uart_putchar(ps_chardevice_t *dev, int c) { - uart_regs_t *regs = uart_get_regs(dev); + ns16550_regs_t *regs = uart_get_regs(dev); /* There is no way to check for "TX ready", the only thing we have is a * check for "TX FIFO empty". This is not optimal, as we might wait here @@ -71,7 +45,7 @@ int uart_putchar(ps_chardevice_t *dev, int c) * However, since QEMU does not emulate a FIFO, we can just implement a * simple model here and block - expecting to never block practically. */ - while (!internal_uart_is_tx_empty(regs)) { + while (!ns16550_is_tx_empty(regs)) { /* busy waiting loop */ } @@ -80,27 +54,21 @@ int uart_putchar(ps_chardevice_t *dev, int c) /* If SERIAL_AUTO_CR is enabled, a CR is sent before any LF. */ if ((byte == '\n') && (dev->flags & SERIAL_AUTO_CR)) { - internal_uart_tx_byte(regs, '\r'); + ns16550_tx_byte(regs, '\r'); /* Since we have blocked until the FIFO is empty, we don't have to wait * here. And QEMU does not emulate a FIFOs anyway. */ } - internal_uart_tx_byte(regs, byte); + ns16550_tx_byte(regs, byte); return byte; } int uart_getchar(ps_chardevice_t *dev) { - uart_regs_t *regs = uart_get_regs(dev); - - /* if UART is empty return an error */ - if (internal_uart_is_rx_empty(regs)) { - return EOF; - } - - return internal_uart_rx_byte(regs) & 0xFF; + ns16550_regs_t *regs = uart_get_regs(dev); + return ns16550_get_char_or_EOF(regs); } static void uart_handle_irq(ps_chardevice_t *dev) @@ -108,29 +76,28 @@ static void uart_handle_irq(ps_chardevice_t *dev) /* No IRQ handling required here. */ } -static void uart_setup(ps_chardevice_t *dev) +static void ns16550_init(ns16550_regs_t *regs) { - uart_regs_t *regs = uart_get_regs(dev); - - regs->dlm_ier = 0; // disable interrupts + /* disable interrupts */ + regs->dlm_ier = 0; /* Baudrates and serial line parameters are not emulated by QEMU, so the * divisor is just a dummy. */ uint16_t clk_divisor = 1; /* dummy, would be for 115200 baud */ - regs->lcr = UART_LCR_DLAB; /* baud rate divisor setup */ + regs->lcr = NS16550_LCR_DLAB; /* baud rate divisor setup */ regs->dlm_ier = (clk_divisor >> 8) & 0xFF; regs->rbr_dll_thr = clk_divisor & 0xFF; regs->lcr = 0x03; /* set 8N1, clear DLAB to end baud rate divisor setup */ /* enable and reset FIFOs, interrupt for each byte */ - regs->iir_fcr = UART_FCR_ENABLE_FIFOS - | UART_FCR_RESET_RX_FIFO - | UART_FCR_RESET_TX_FIFO - | UART_FCR_TRIGGER_1; + regs->iir_fcr = NS16550_FCR_ENABLE_FIFOS + | NS16550_FCR_RESET_RX_FIFO + | NS16550_FCR_RESET_TX_FIFO + | NS16550_FCR_TRIGGER_1; /* enable RX interrupts */ - regs->dlm_ier = UART_IER_ERBFI; + regs->dlm_ier = NS16550_IER_ERBFI; } int uart_init(const struct dev_defn *defn, @@ -148,15 +115,16 @@ int uart_init(const struct dev_defn *defn, /* Set up all the device properties. */ dev->id = defn->id; dev->vaddr = (void *)vaddr; - dev->read = &uart_read; - dev->write = &uart_write; + dev->read = &uart_read; /* calls uart_putchar() */ + dev->write = &uart_write; /* calls uart_getchar() */ dev->handle_irq = &uart_handle_irq; dev->irqs = defn->irqs; dev->ioops = *ops; dev->flags = SERIAL_AUTO_CR; - /* Set up the device. */ - uart_setup(dev); + /* Initialize the device. */ + ns16550_regs_t *regs = uart_get_regs(dev); + ns16550_init(regs); return 0; }