From 01b62b6497616e451bbc356070f4671a3a0ca0ac Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Sat, 26 Nov 2022 20:57:35 +0100 Subject: [PATCH 1/6] libplatsupport: fix style issues Signed-off-by: Axel Heider --- libplatsupport/src/plat/fvp/serial.c | 15 +++++++-------- libplatsupport/src/plat/hikey/serial.c | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c index 5b8db1ad3..47b665e16 100644 --- a/libplatsupport/src/plat/fvp/serial.c +++ b/libplatsupport/src/plat/fvp/serial.c @@ -32,7 +32,7 @@ int uart_getchar(ps_chardevice_t *d) return ch; } -int uart_putchar(ps_chardevice_t* d, int c) +int uart_putchar(ps_chardevice_t *d, int c) { while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0); @@ -44,25 +44,24 @@ int uart_putchar(ps_chardevice_t* d, int c) return c; } -static void -uart_handle_irq(ps_chardevice_t* dev) +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) +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; diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c index e9f30d70b..b92241f0c 100644 --- a/libplatsupport/src/plat/hikey/serial.c +++ b/libplatsupport/src/plat/hikey/serial.c @@ -29,7 +29,7 @@ int uart_getchar(ps_chardevice_t *d) return ch; } -int uart_putchar(ps_chardevice_t* d, int c) +int uart_putchar(ps_chardevice_t *d, int c) { while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0); @@ -41,25 +41,24 @@ int uart_putchar(ps_chardevice_t* d, int c) return c; } -static void -uart_handle_irq(ps_chardevice_t* dev) +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) +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; From b3ff8314c5127d541ab5d991dcb2d449cca3bb67 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Thu, 11 Jan 2024 17:35:25 +0100 Subject: [PATCH 2/6] libplatsupport: remove duplicate space Signed-off-by: Axel Heider --- libplatsupport/src/plat/fvp/serial.c | 2 +- libplatsupport/src/plat/hikey/serial.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c index 47b665e16..29a122a41 100644 --- a/libplatsupport/src/plat/fvp/serial.c +++ b/libplatsupport/src/plat/fvp/serial.c @@ -59,7 +59,7 @@ int uart_init(const struct dev_defn *defn, 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; diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c index b92241f0c..6518cf511 100644 --- a/libplatsupport/src/plat/hikey/serial.c +++ b/libplatsupport/src/plat/hikey/serial.c @@ -56,7 +56,7 @@ int uart_init(const struct dev_defn *defn, 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; From 740e4cc38d70d943797fae6ea21d72846380db83 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Sat, 26 Nov 2022 22:23:44 +0100 Subject: [PATCH 3/6] libplatsupport: fix CR/LF handling Fix the bug that LF CR is printed instead of CR LF. Signed-off-by: Axel Heider --- libplatsupport/src/plat/fvp/serial.c | 8 +++++--- libplatsupport/src/plat/hikey/serial.c | 8 +++++--- libplatsupport/src/plat/qemu-arm-virt/serial.c | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c index 29a122a41..e70c8da21 100644 --- a/libplatsupport/src/plat/fvp/serial.c +++ b/libplatsupport/src/plat/fvp/serial.c @@ -34,13 +34,15 @@ int uart_getchar(ps_chardevice_t *d) 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'); } + while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) { + /* busy loop */ + } + *REG_PTR(d->vaddr, UARTDR) = c; + return c; } diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c index 6518cf511..99d6619ab 100644 --- a/libplatsupport/src/plat/hikey/serial.c +++ b/libplatsupport/src/plat/hikey/serial.c @@ -31,13 +31,15 @@ int uart_getchar(ps_chardevice_t *d) 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'); } + while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) { + /* busy loop */ + } + *REG_PTR(d->vaddr, UARTDR) = c; + return c; } diff --git a/libplatsupport/src/plat/qemu-arm-virt/serial.c b/libplatsupport/src/plat/qemu-arm-virt/serial.c index 47b665e16..a18914d02 100644 --- a/libplatsupport/src/plat/qemu-arm-virt/serial.c +++ b/libplatsupport/src/plat/qemu-arm-virt/serial.c @@ -34,13 +34,15 @@ int uart_getchar(ps_chardevice_t *d) 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'); } + while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) + /* busy loop */ + } + *REG_PTR(d->vaddr, UARTDR) = c; + return c; } From a06c6bfc057b5546242779538ead6f1f9d9c8d55 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Thu, 11 Jan 2024 17:14:30 +0100 Subject: [PATCH 4/6] 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; } From b44f4ab8a61fb90c91851f121be96cc02c258a65 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Tue, 9 May 2023 17:32:59 +0200 Subject: [PATCH 5/6] wip JH7110 (VisionFive, Star64) --- .../star64/platsupport/plat/serial.h | 46 +++++++ libplatsupport/src/plat/star64/chardev.c | 48 +++++++ libplatsupport/src/plat/star64/serial.c | 126 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 libplatsupport/plat_include/star64/platsupport/plat/serial.h create mode 100644 libplatsupport/src/plat/star64/chardev.c create mode 100644 libplatsupport/src/plat/star64/serial.c diff --git a/libplatsupport/plat_include/star64/platsupport/plat/serial.h b/libplatsupport/plat_include/star64/platsupport/plat/serial.h new file mode 100644 index 000000000..a815367c5 --- /dev/null +++ b/libplatsupport/plat_include/star64/platsupport/plat/serial.h @@ -0,0 +1,46 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + + +#define UART0_PADDR 0x10000000 +#define UART1_PADDR 0x10010000 +#define UART2_PADDR 0x10020000 +#define UART3_PADDR 0x12000000 +#define UART4_PADDR 0x12010000 +#define UART5_PADDR 0x12020000 + +#define UART0_IRQ 32 +#define UART1_IRQ 33 +#define UART2_IRQ 34 +#define UART3_IRQ 45 +#define UART4_IRQ 46 +#define UART5_IRQ 47 + +enum chardev_id { + UART0, + UART1, + UART2, + UART3, + UART4, + UART5, + /* Aliases */ + PS_SERIAL0 = UART0, + PS_SERIAL1 = UART1, + PS_SERIAL2 = UART2, + PS_SERIAL3 = UART3, + PS_SERIAL4 = UART4, + PS_SERIAL5 = UART5, + /* defaults */ + PS_SERIAL_DEFAULT = PS_SERIAL0 +}; + + +/* The default serial device corresponds to the UART available via the GPIO + * pins of the Star64 Model-A. */ +#define DEFAULT_SERIAL_PADDR UART0_PADDR +#define DEFAULT_SERIAL_INTERRUPT UART0_IRQ diff --git a/libplatsupport/src/plat/star64/chardev.c b/libplatsupport/src/plat/star64/chardev.c new file mode 100644 index 000000000..0b78dfdf1 --- /dev/null +++ b/libplatsupport/src/plat/star64/chardev.c @@ -0,0 +1,48 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "../../chardev.h" +#include "../../common.h" +#include + +static const int uart0_irqs[] = {UART0_IRQ, -1}; +static const int uart1_irqs[] = {UART1_IRQ, -1}; +static const int uart2_irqs[] = {UART2_IRQ, -1}; +static const int uart3_irqs[] = {UART3_IRQ, -1}; +static const int uart4_irqs[] = {UART4_IRQ, -1}; +static const int uart5_irqs[] = {UART5_IRQ, -1}; + +/* + * Despite each UART being 0x10000 in size (according to the device tree) we + * only need to map in the first page for the driver to functon. + */ +#define UART_DEFN(devid) { \ + .id = UART##devid, \ + .paddr = UART##devid##_PADDR, \ + .size = PAGE_SIZE_4K, \ + .irqs = uart##devid##_irqs, \ + .init_fn = &uart_init \ +} + +const struct dev_defn dev_defn[] = { + UART_DEFN(0), + UART_DEFN(1), + UART_DEFN(2), + UART_DEFN(3), + UART_DEFN(4), + UART_DEFN(5), +}; + +struct ps_chardevice* +ps_cdev_init(enum chardev_id id, const ps_io_ops_t* o, struct ps_chardevice* d) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(dev_defn); i++) { + if (dev_defn[i].id == id) { + return (dev_defn[i].init_fn(dev_defn + i, o, d)) ? NULL : d; + } + } + return NULL; +} diff --git a/libplatsupport/src/plat/star64/serial.c b/libplatsupport/src/plat/star64/serial.c new file mode 100644 index 000000000..30d119d6e --- /dev/null +++ b/libplatsupport/src/plat/star64/serial.c @@ -0,0 +1,126 @@ +/* + * Copyright 2023, UNSW + * Copyright 2022, HENSOLDT Cyber GmbH + * + * SPDX-License-Identifier: BSD-2-Clause + * + * + * The JH7110 SoC use on the Star64 emulates a 16550 compatible UART, where the + * register width is 32-bit. + */ + +#include +#include +#include + +#define NS16550_WITH_REG32 +#include + +#include "../../chardev.h" + +static ns16550_regs_t *uart_get_regs(ps_chardevice_t *dev) +{ + return (ns16550_regs_t *)(dev->vaddr); +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_putchar(ps_chardevice_t *dev, int c) +{ + 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 + * even if there is space in the FIFO. Seems the 16550 was built based on + * the idea that software keeps track of the FIFO usage. A driver would + * know how much space is left in the FIFO, so it can write new data + * either immediately or buffer it. If the FIFO empty interrupt arrives, + * data can be written from the buffer to fill the FIFO. + * However, since QEMU does not emulate a FIFO, we can just implement a + * simple model here and block - expecting to never block practically. + */ + while (!ns16550_is_tx_empty(regs)) { + /* busy waiting loop */ + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* If SERIAL_AUTO_CR is enabled, a CR is sent before any LF. */ + if ((byte == '\n') && (dev->flags & SERIAL_AUTO_CR)) { + ns16550_tx_byte(regs, '\r'); + /* Since we have blocked until the FIFO is empty, we don't have to wait + * here. + */ + } + + ns16550_tx_byte(regs, byte); + + return byte; +} + +int uart_getchar(ps_chardevice_t *dev) +{ + ns16550_regs_t *regs = uart_get_regs(dev); + return ns16550_get_char_or_EOF(regs); +} + +static void uart_handle_irq(ps_chardevice_t *dev) +{ + /* No IRQ handling required here. */ +} + +static void ns16550_init(ns16550_regs_t *regs) +{ + /* disable interrupts */ + regs->dlm_ier = 0; + + + regs->lcr = NS16550_LCR_DLAB; /* baud rate divisor setup */ + // uint16_t clk_divisor = ??? + // 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 = NS16550_FCR_ENABLE_FIFOS + | NS16550_FCR_RESET_RX_FIFO + | NS16550_FCR_RESET_TX_FIFO + | NS16550_FCR_TRIGGER_1; + + /* enable RX interrupts */ + regs->dlm_ier = NS16550_IER_ERBFI; +} + +int uart_init(const struct dev_defn *defn, + const ps_io_ops_t *ops, + 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. */ + 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; + + /* Initialize the device. */ + ns16550_regs_t *regs = uart_get_regs(dev); + ns16550_init(regs); + + return 0; +} From 5ef20c0bb744ec7f204c3dad3981f5e6de769d82 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Tue, 29 Nov 2022 19:57:04 +0100 Subject: [PATCH 6/6] libplatsupport: separate drivers from patforms Separate timer drivers Signed-off-by: Axel Heider --- libplatsupport/CMakeLists.txt | 4 + .../include/platsupport/driver/sp804/sp804.h | 118 +++++++ .../platsupport/driver/sp804/sp804_ltimer.h | 17 + .../plat_include/fvp/platsupport/plat/sp804.h | 78 ----- .../plat_include/hikey/platsupport/plat/dmt.h | 73 ----- .../src/driver/sp804/sp804_ltimer.c | 292 ++++++++++++++++++ libplatsupport/src/plat/fvp/ltimer.c | 141 +-------- libplatsupport/src/plat/fvp/sp804.c | 198 ------------ libplatsupport/src/plat/hikey/dmt.c | 287 ----------------- libplatsupport/src/plat/hikey/ltimer.c | 142 +-------- 10 files changed, 450 insertions(+), 900 deletions(-) create mode 100644 libplatsupport/include/platsupport/driver/sp804/sp804.h create mode 100644 libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h delete mode 100644 libplatsupport/plat_include/fvp/platsupport/plat/sp804.h delete mode 100644 libplatsupport/plat_include/hikey/platsupport/plat/dmt.h create mode 100644 libplatsupport/src/driver/sp804/sp804_ltimer.c delete mode 100644 libplatsupport/src/plat/fvp/sp804.c delete mode 100644 libplatsupport/src/plat/hikey/dmt.c diff --git a/libplatsupport/CMakeLists.txt b/libplatsupport/CMakeLists.txt index ab7799f70..3e5cd8302 100644 --- a/libplatsupport/CMakeLists.txt +++ b/libplatsupport/CMakeLists.txt @@ -90,6 +90,10 @@ if(KernelPlatformQEMUArmVirt OR KernelPlatformQuartz64) endif() endif() +if(KernelPlatformHikey OR KernelPlatformFVP) + list(APPEND deps "src/driver/sp804/sp804_ltimer.c") +endif() + if(KernelPlatformExynos5422) list(APPEND deps src/mach/${LibPlatSupportMach}/clock/exynos_5422_clock.c) elseif(KernelPlatformExynos4 OR KernelPlatformExynos5410 OR KernelPlatformExynos5250) diff --git a/libplatsupport/include/platsupport/driver/sp804/sp804.h b/libplatsupport/include/platsupport/driver/sp804/sp804.h new file mode 100644 index 000000000..91c5823dd --- /dev/null +++ b/libplatsupport/include/platsupport/driver/sp804/sp804.h @@ -0,0 +1,118 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include + + +#define TCLR_ONESHOT BIT(0) +#define TCLR_VALUE_32 BIT(1) +/* Bit 2 and 3: Prescaler + * 00 = clock is divided by 1 (default) + * 01 = clock is divided by 16 + * 10 = clock is divided by 256 + * 11 = reserved, do not use. + * Bit 4: reserved + */ +#define TCLR_INTENABLE BIT(5) +#define TCLR_AUTORELOAD BIT(6) +#define TCLR_STARTTIMER BIT(7) + +/* + * The sp840 contains two identical timers: + * + * 0x000 - 0x01f sp804_regs_t timer1; + * 0x020 - 0x03f sp804_regs_t timer2; + * 0x040 - 0xefc Reserved + * 0xf00 TimerITCR + * 0xf04 TimerITOP + * 0xf08 - 0xfdf Reserved + * 0xfe0 - 0xfef TimerPeriphID[4] + * 0xff0 - 0xfff TimerPCellID[4] + */ + +#define SP804_TIMER2_OFFSET 0x20 + +typedef volatile struct { + uint32_t load; /* 0x00 */ + uint32_t value; /* 0x04 */ + uint32_t control; /* 0x08 TCLR */ + uint32_t intclr; /* 0x0c Interrupt Clear */ + uint32_t ris; /* 0x10 Raw interrupt Status */ + uint32_t mis; /* 0x14 Masked Interrupt Status */ + uint32_t bgload; /* 0x18 Background Load */ + uint32_t _rfu; /* 0x1c (unused) */ +} sp804_regs_t; + +compile_time_assert(sp804_timer_size , sizeof(sp804_regs_t) <= SP804_TIMER2_OFFSET); + +#define SP804_TIMER1_REGS(base) ((sp804_regs_t *)(base)) +#define SP804_TIMER2_REGS(base) ((sp804_regs_t *)((uintptr_t)(base) + SP804_TIMER2_OFFSET)) + +static void sp804_reset(sp804_regs_t *regs) +{ + regs->control = 0; +} + +static void sp804_stop(sp804_regs_t *regs) +{ + regs->control &= ~TCLR_STARTTIMER; +} + +static void sp804_start(sp804_regs_t *regs) +{ + regs->control |= TCLR_STARTTIMER; +} + +static bool sp804_is_irq_pending(sp804_regs_t *regs) +{ + /* return the raw interrupt status and not the masted interrupt status */ + return !!regs->ris; +} + +static bool sp804_clear_intr(sp804_regs_t *regs) +{ + regs->intclr = 0x1; +} + +static uint32_t sp804_get_ticks(sp804_regs_t *regs) +{ + return regs->value; +} + +static void sp804_set_timeout(sp804_regs_t *regs, uint32_t ticks, + bool is_periodic, bool enable_intr) +{ + regs->control = 0; /* stop timer */ + + /* If 'ticks' is 0, then writing to 'load' will generate an interrupt + * immediately. + * + * The "Hikey Application Processor Function Description" says in + * section 2.3: + * The minimum valid value of TIMERN_LOAD is 1. If 0 is written to + * TIMERN_LOAD, a timing interrupt is generated immediately. + * TIMERN_BGLOAD is an initial count value register in periodic mode. In + * periodic mode, when the value of TIMERN_BGLOAD is updated, the + * value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However, + * the timer counter does not restart counting. After the counter + * decreases to 0, the value of TIMERN_LOAD (that is, + * the value of TIMERN_BGLOAD) is reloaded to the counter. + * + * In other words, for periodic mode, load BGLOAD first, then write to + * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0 + * to BGLOAD. + */ + regs->bgload = is_periodic ? ticks : 0; + regs->load = ticks; + + /* The TIMERN_VALUE register is read-only. */ + regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 + | (is_periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT) + | (enable_intr ? TCLR_INTENABLE : 0); +} diff --git a/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h b/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h new file mode 100644 index 000000000..9f58d92e8 --- /dev/null +++ b/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h @@ -0,0 +1,17 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#include "../../ltimer.h" + +int ltimer_sp804_init(ltimer_t *ltimer, const char *ftd_path, uint64_t freq, + ps_io_ops_t ops, ltimer_callback_fn_t callback, + void *callback_token); diff --git a/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h b/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h deleted file mode 100644 index 3d61f1436..000000000 --- a/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#pragma once - -#include -#include -#include - -/* Each SP804 has two timers, but we only use one timer on eace device page. - * This is because the two timers on the same page share the same interrupt, - * and using one timer on each page saves us from identifying the sources of - * interrupts. - * */ -#define SP804_TIMER1_PATH "/soc/timer@1c110000" -#define SP804_TIMER2_PATH "/soc/timer@1c120000" - -#define SP804_REG_CHOICE 0 -#define SP804_IRQ_CHOICE 0 - -static UNUSED timer_properties_t sp804_timer_props = { - .upcounter = false, - .timeouts = true, - .absolute_timeouts = false, - .relative_timeouts = true, - .periodic_timeouts = true, - .bit_width = 32, - .irqs = 1 -}; - -typedef volatile struct sp804_regs { - uint32_t load; - uint32_t value; - uint32_t control; - uint32_t intclr; - uint32_t ris; - uint32_t mis; - uint32_t bgload; -} sp804_regs_t; - -typedef struct { - /* set in init */ - ps_io_ops_t ops; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; /* what are we being used for? */ - - /* set in fdt helper */ - volatile sp804_regs_t *sp804_map; - pmem_region_t pmem; - irq_id_t irq_id; - - /* set in setup */ - uint32_t time_h; -} sp804_t; - -typedef struct { - const char *fdt_path; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; -} sp804_config_t; - -int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config); -/* convert between dmt ticks and ns */ -uint64_t sp804_ticks_to_ns(uint64_t ticks); -/* return true if an overflow irq is pending */ -bool sp804_is_irq_pending(sp804_t *sp804); -int sp804_set_timeout_ticks(sp804_t *timer, uint32_t ticks, bool periodic, bool irqs); -/* set a timeout in nano seconds */ -int sp804_set_timeout(sp804_t *timer, uint64_t ns, bool periodic, bool irqs); -int sp804_start(sp804_t *timer); -int sp804_stop(sp804_t *timer); -uint64_t sp804_get_time(sp804_t *timer); -uint64_t sp804_get_ticks(sp804_t *timer); -void sp804_destroy(sp804_t *sp804); diff --git a/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h b/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h deleted file mode 100644 index a661f3fc1..000000000 --- a/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#pragma once - -#include -#include - -#define DMT_PATH "/soc/timer@f8008000" - -#define DMT_REG_CHOICE 0 -#define DMT_IRQ_CHOICE 0 - -static UNUSED timer_properties_t dmtimer_props = { - .upcounter = false, - .timeouts = true, - .absolute_timeouts = false, - .relative_timeouts = true, - .periodic_timeouts = true, - .bit_width = 32, - .irqs = 1 -}; - -typedef volatile struct dmt_regs { - uint32_t load; - uint32_t value; - uint32_t control; - uint32_t intclr; - uint32_t ris; - uint32_t mis; - uint32_t bgload; -} dmt_regs_t; - -typedef struct { - /* set in init */ - ps_io_ops_t ops; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; /* what are we being used for? */ - - /* set in fdt helper */ - volatile dmt_regs_t *dmt_map; - void *dmt_map_base; - pmem_region_t pmem; - irq_id_t irq_id; - - /* set in setup */ - uint32_t time_h; -} dmt_t; - -typedef struct { - const char *fdt_path; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; -} dmt_config_t; - -int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config); -int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config); -/* convert between dmt ticks and ns */ -uint64_t dmt_ticks_to_ns(uint64_t ticks); -/* return true if an overflow irq is pending */ -bool dmt_is_irq_pending(dmt_t *dmt); -int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs); -/* set a timeout in nano seconds */ -int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs); -int dmt_start(dmt_t *dmt); -int dmt_stop(dmt_t *dmt); -uint64_t dmt_get_time(dmt_t *dmt); -uint64_t dmt_get_ticks(dmt_t *dmt); -void dmt_destroy(dmt_t *dmt); diff --git a/libplatsupport/src/driver/sp804/sp804_ltimer.c b/libplatsupport/src/driver/sp804/sp804_ltimer.c new file mode 100644 index 000000000..c5f16d723 --- /dev/null +++ b/libplatsupport/src/driver/sp804/sp804_ltimer.c @@ -0,0 +1,292 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "../../ltimer.h" + +/* + * A each sp804 timer peripheral has two timers. Timer #1 keeps track of the + * timeouts and timer #2 tracks the absolute time. It depends on the platform + * if there is one interrupt for both timers or separate interrupts. + */ + +typedef struct { + ps_io_ops_t ops; + ltimer_callback_fn_t callback_func; + void *callback_token; + uint32_t time_h; + pmem_region_t pmem; + void *mapping; + uint64_t freq_100KHz; + irq_id_t irq_id; + irq_id_t irq2_id; +} ltimer_sp804_t; + +static sp804_regs_t *ltimer_sp804_get_regs_timeout(ltimer_sp804_t *ltimer) +{ + return SP804_TIMER1_REGS(ltimer->mapping); +} + +static sp804_regs_t *ltimer_sp804_get_regs_timestamp(ltimer_sp804_t *ltimer) +{ + return SP804_TIMER2_REGS(ltimer->mapping); +} + +static uint64_t ltimer_sp804_get_time(ltimer_sp804_t *ltimer) +{ + sp804_regs_t *regs = ltimer_sp804_get_regs_timestamp(ltimer); + + /* sp804 is a down counter, invert the result */ + uint32_t high = ltimer->time_h; + uint32_t low = UINT32_MAX - sp804_get_ticks(regs); + + /* check after fetching low to see if we've missed a high bit */ + if (sp804_is_irq_pending(regs)) { + high += 1; + assert(high != 0); + } + + uint64_t ticks = ((uint64_t)high << 32) | low; + uint64_t ns = (ticks * 10000) / ltimer->freq_100KHz; + return ns; +} + +int ltimer_sp804_simple_set_timeout(void *data, uint64_t ns, timeout_type_t type) +{ + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + /* calculate relative timeout for absolute timeout */ + if (TIMEOUT_ABSOLUTE == type) { + uint64_t time_ns_now = ltimer_sp804_get_time(ltimer); + /* The timeout can't be in the past */ + if (time_ns_now > ns) { + return ETIME; + } + ns -= time_ns_now; + } + + uint64_t ticks64 = (ns * ltimer->freq_100KHz) / 10000; + /* There is no trivial way to set values greater 32-bit. */ + if (ticks64 > UINT32_MAX) { + ZF_LOGE("timout of %"PRIu64" ns (%"PRIu64" ticks) exceeds 32-bit range", + ns, ticks64); + return ETIME; + } + + /* The value 0 makes the interrupt trigger immediately. + * ToDo: might be better to reject a periodic timer with a timeout of 0. + */ + sp804_set_timeout(ltimer_sp804_get_regs_timeout(ltimer), + (uint32_t)ticks64, (type == TIMEOUT_PERIODIC), true); + return 0; +} + +static int ltimer_sp804_simple_get_time(void *data, uint64_t *time) +{ + assert(data != NULL); + assert(time != NULL); + ltimer_sp804_t *ltimer = data; + *time = ltimer_sp804_get_time(ltimer); + return 0; +} + +static int ltimer_sp804_simple_reset(void *data) +{ + ltimer_sp804_t *ltimer = data; + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer); + sp804_reset(regs_timeout); + + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer); + sp804_reset(regs_timestamp); + sp804_start(regs_timestamp); + + return 0; +} + +static void ltimer_sp804_simple_destroy(void *data) +{ + int error; + + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + if (ltimer->irq_id != PS_INVALID_IRQ_ID) { + error = ps_irq_unregister(<imer->ops.irq_ops, ltimer->irq_id); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to unregister IRQ (%d)", error); + UNREACHABLE(); + } + } + + if (ltimer->irq2_id != PS_INVALID_IRQ_ID) { + error = ps_irq_unregister(<imer->ops.irq_ops, ltimer->irq2_id); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to unregister IRQ (%d)", error); + UNREACHABLE(); + } + } + + ps_pmem_unmap(<imer->ops, ltimer->pmem, (void *)ltimer->mapping); + + ps_free(<imer->ops.malloc_ops, sizeof(ltimer_sp804_t), ltimer); +} + +static void ltimer_sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t ack_fn, + void *ack_data) +{ + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer); + bool is_overflow = false; + if (sp804_is_irq_pending(regs_timestamp)) { + is_overflow = true; + sp804_clear_intr(regs_timestamp); + ltimer->time_h++; + } + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer); + bool is_timeout = false; + if (sp804_is_irq_pending(regs_timeout)) { + is_timeout = true; + sp804_clear_intr(regs_timeout); + } + + if (ack_fn) { + int error = ack_fn(ack_data); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to acknowledge the timer's interrupts (%d)", error); + UNREACHABLE(); + } + } + + if (ltimer->callback_func) { + if (is_timeout) { + ltimer->callback_func(ltimer->callback_token, LTIMER_TIMEOUT_EVENT); + } + if (is_overflow) { + ltimer->callback_func(ltimer->callback_token, LTIMER_OVERFLOW_EVENT); + } + } +} + +int ltimer_sp804_init(ltimer_t *ltimer, const char *ftd_path, + uint64_t freq, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) +{ + int error; + + if (ltimer == NULL) { + ZF_LOGE("ltimer cannot be NULL"); + return EINVAL; + } + + error = create_ltimer_simple(ltimer, ops, sizeof(ltimer_sp804_t), + ltimer_sp804_simple_get_time, + ltimer_sp804_simple_set_timeout, + ltimer_sp804_simple_reset, + ltimer_sp804_simple_destroy); + if (error) { + ZF_LOGE("Failed to create ltimer simple (%d)", error); + return error; + } + + ltimer_sp804_t *ltimer_sp804 = ltimer->data; + + ltimer_sp804->ops = ops; + ltimer_sp804->callback_func = callback, + ltimer_sp804->callback_token = callback_token, + + /* Assume the frequency is a multiple of 100 KHz so the numbers do not get + * too big during calculations and there are less rounding errors. + */ + assert(0 == (freq % 100000)); + ltimer_sp804->freq_100KHz = freq / 100000; + + /* Set up timer1 for timeouts and timer2 for timestamps. We can't use + * helper_fdt_alloc_simple() here, because there are platforms where the + * timers have independent interrupts. + * Set safe default to allow cleanup if init fails + */ + ltimer_sp804->mapping = NULL; + ltimer_sp804->irq_id = PS_INVALID_IRQ_ID; + ltimer_sp804->irq2_id = PS_INVALID_IRQ_ID; + + /* Gather FDT info */ + ps_fdt_cookie_t *cookie = NULL; + error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, ftd_path, &cookie); + if (error) { + ZF_LOGE("failed to read path (%d, %s)", error, ftd_path); + goto init_cleanup; + } + + ltimer_sp804->mapping = ps_fdt_index_map_register(&ops, cookie, 0, + <imer_sp804->pmem); + if (ltimer_sp804->mapping == NULL) { + ZF_LOGE("failed to map registers"); + goto init_cleanup; + } + + ltimer_sp804->irq_id = ps_fdt_index_register_irq(&ops, cookie, 0, + ltimer_sp804_handle_irq, + ltimer_sp804); + if (ltimer_sp804->irq_id <= PS_INVALID_IRQ_ID) { + ZF_LOGE("failed to register irq (%d)", ltimer_sp804->irq_id); + goto init_cleanup; + } + +#ifdef SP804_LTIMER_MULTIPLE_INTR + ltimer_sp804->irq2_id = ps_fdt_index_register_irq(&ops, cookie, 1, + ltimer_sp804_handle_irq, + ltimer_sp804); + if (ltimer_sp804->irq2_id <= PS_INVALID_IRQ_ID) { + ZF_LOGE("failed to register irq 2 (%d)", ltimer_sp804->irq2_id); + goto init_cleanup; + } +#endif /* SP804_LTIMER_MULTIPLE_INTR */ + +init_cleanup: + if (cookie) { + int error2 = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); + if (error2) { + ZF_LOGE("failed to clean up FTD cookie (%d)", error2); + error = error2; + } + } + if (error) { + ltimer_sp804_simple_destroy(ltimer_sp804); + ZF_LOGE("sp804 ltimer init failed (%d)", error); + return error; + } + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer_sp804); + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer_sp804); + + sp804_reset(regs_timeout); + sp804_reset(regs_timestamp); + + /* Start the timestamp counter (setting a value implies starting). */ + sp804_set_timeout(regs_timestamp, UINT32_MAX, true, true); + + return 0; +} diff --git a/libplatsupport/src/plat/fvp/ltimer.c b/libplatsupport/src/plat/fvp/ltimer.c index 6ec6cdbac..afa534f5e 100644 --- a/libplatsupport/src/plat/fvp/ltimer.c +++ b/libplatsupport/src/plat/fvp/ltimer.c @@ -3,144 +3,25 @@ * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include - -#include -#include +#include #include -#include -#include +#include #include "../../ltimer.h" -/* - * We use two sp804 timers: one to keep track of an absolute time, the other for timeouts. +/* FVP has two sp804 timers, each running at 35 MHz + * /soc/timer@1c110000 + * /soc/timer@1c120000 */ +#define SP804_LTIMER_PATH "/soc/timer@1c110000" +#define SP804_LTIMER_FREQ 35000000 -typedef struct { - /* fvp sp804 have 2 timers per frame, we just use one per each */ - sp804_t sp804_timeout; - sp804_t sp804_timestamp; - ps_io_ops_t ops; -} fvp_ltimer_t; - -static int get_time(void *data, uint64_t *time) -{ - fvp_ltimer_t *fvp_ltimer = data; - assert(data != NULL); - assert(time != NULL); - - *time = sp804_get_time(&fvp_ltimer->sp804_timestamp); - return 0; -} - -int set_timeout(void *data, uint64_t ns, timeout_type_t type) -{ - if (type == TIMEOUT_ABSOLUTE) { - uint64_t time; - int error = get_time(data, &time); - if (error) { - return error; - } - if (time > ns) { - return ETIME; - } - ns -= time; - } - - fvp_ltimer_t *fvp_ltimer = data; - return sp804_set_timeout(&fvp_ltimer->sp804_timeout, ns, type == TIMEOUT_PERIODIC, true); -} - -static int reset(void *data) -{ - fvp_ltimer_t *fvp_ltimer = data; - /* restart the rtc */ - sp804_stop(&fvp_ltimer->sp804_timeout); - sp804_start(&fvp_ltimer->sp804_timeout); - return 0; -} - -static void destroy(void *data) +int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) { - assert(data != NULL); - fvp_ltimer_t *fvp_ltimer = data; - sp804_destroy(&fvp_ltimer->sp804_timeout); - sp804_destroy(&fvp_ltimer->sp804_timestamp); - ps_free(&fvp_ltimer->ops.malloc_ops, sizeof(fvp_ltimer_t), fvp_ltimer); -} - -int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) -{ - int error; - - if (ltimer == NULL) { - ZF_LOGE("ltimer cannot be NULL"); - return EINVAL; - } - - error = create_ltimer_simple( - ltimer, ops, sizeof(fvp_ltimer_t), - get_time, set_timeout, reset, destroy - ); - if (error) { - ZF_LOGE("Failed to create ltimer simple"); - return error; - } - - fvp_ltimer_t *fvp_ltimer = ltimer->data; - fvp_ltimer->ops = ops; - - /* set up an SP804 for timeouts */ - sp804_config_t sp804_config = { - .fdt_path = SP804_TIMER1_PATH, - .user_cb_fn = callback, - .user_cb_token = callback_token, - .user_cb_event = LTIMER_TIMEOUT_EVENT - }; - - error = sp804_init(&fvp_ltimer->sp804_timeout, ops, sp804_config); - if (error) { - ZF_LOGE("Failed to init timeout timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_start(&fvp_ltimer->sp804_timeout); - if (error) { - ZF_LOGE("Failed to start timeout timer"); - destroy(&fvp_ltimer); - return error; - } - - /* another for timestamps */ - sp804_config.fdt_path = SP804_TIMER2_PATH; - sp804_config.user_cb_event = LTIMER_OVERFLOW_EVENT; - - error = sp804_init(&fvp_ltimer->sp804_timestamp, ops, sp804_config); - if (error) { - ZF_LOGE("Failed to init timestamp timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_start(&fvp_ltimer->sp804_timestamp); - if (error) { - ZF_LOGE("Failed to start timestamp timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_set_timeout_ticks(&fvp_ltimer->sp804_timestamp, UINT32_MAX, true, true); - if (error) { - ZF_LOGE("Failed to set timeout ticks for timer"); - destroy(&fvp_ltimer); - return error; - } - - return 0; + return ltimer_sp804_init(ltimer, SP804_LTIMER_PATH, SP804_LTIMER_FREQ, ops, + callback, callback_token); } /* This function is intended to be deleted, diff --git a/libplatsupport/src/plat/fvp/sp804.c b/libplatsupport/src/plat/fvp/sp804.c deleted file mode 100644 index b2d860989..000000000 --- a/libplatsupport/src/plat/fvp/sp804.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#include -#include -#include - -#include -#include - -#include - -#include "../../ltimer.h" - -/* This file is mostly the same as the dmt.c file for the hikey. - * Consider to merge the two files as a single driver file for - * SP804. - */ - -#define TCLR_ONESHOT BIT(0) -#define TCLR_VALUE_32 BIT(1) -#define TCLR_INTENABLE BIT(5) -#define TCLR_AUTORELOAD BIT(6) -#define TCLR_STARTTIMER BIT(7) -/* It looks like the FVP does not emulate time accurately. Thus, pick - * a small Hz that triggers interrupts in a reasonable time */ -#define TICKS_PER_SECOND 35000 -#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) - -static void sp804_timer_reset(sp804_t *sp804) -{ - assert(sp804 != NULL && sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = 0; - - sp804->time_h = 0; -} - -int sp804_stop(sp804_t *sp804) -{ - if (sp804 == NULL) { - return EINVAL; - } - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = sp804_regs->control & ~TCLR_STARTTIMER; - return 0; -} - -int sp804_start(sp804_t *sp804) -{ - if (sp804 == NULL) { - return EINVAL; - } - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = sp804_regs->control | TCLR_STARTTIMER; - return 0; -} - -uint64_t sp804_ticks_to_ns(uint64_t ticks) -{ - return ticks / TICKS_PER_MS * NS_IN_MS; -} - -bool sp804_is_irq_pending(sp804_t *sp804) -{ - if (sp804) { - assert(sp804->sp804_map != NULL); - return !!sp804->sp804_map->ris; - } - return false; -} - -int sp804_set_timeout(sp804_t *sp804, uint64_t ns, bool periodic, bool irqs) -{ - uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; - if (ticks64 > UINT32_MAX) { - return ETIME; - } - return sp804_set_timeout_ticks(sp804, ticks64, periodic, irqs); -} - -int sp804_set_timeout_ticks(sp804_t *sp804, uint32_t ticks, bool periodic, bool irqs) -{ - if (sp804 == NULL) { - return EINVAL; - } - int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; - flags |= irqs ? TCLR_INTENABLE : 0; - - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = 0; - - if (flags & TCLR_AUTORELOAD) { - sp804_regs->bgload = ticks; - } else { - sp804_regs->bgload = 0; - } - sp804_regs->load = ticks; - - /* The TIMERN_VALUE register is read-only. */ - sp804_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 - | flags; - - return 0; -} - -static void sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) -{ - assert(data != NULL); - sp804_t *sp804 = data; - - if (sp804->user_cb_event == LTIMER_OVERFLOW_EVENT) { - sp804->time_h++; - } - - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->intclr = 0x1; - - ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); - if (sp804->user_cb_fn) { - sp804->user_cb_fn(sp804->user_cb_token, sp804->user_cb_event); - } -} - -uint64_t sp804_get_ticks(sp804_t *sp804) -{ - assert(sp804 != NULL && sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - return sp804_regs->value; -} - -uint64_t sp804_get_time(sp804_t *sp804) -{ - uint32_t high, low; - - /* timer must be being used for timekeeping */ - assert(sp804->user_cb_event == LTIMER_OVERFLOW_EVENT); - - /* sp804 is a down counter, invert the result */ - high = sp804->time_h; - low = UINT32_MAX - sp804_get_ticks(sp804); - - /* check after fetching low to see if we've missed a high bit */ - if (sp804_is_irq_pending(sp804)) { - high += 1; - assert(high != 0); - } - - uint64_t ticks = (((uint64_t) high << 32llu) | low); - return sp804_ticks_to_ns(ticks); -} - -void sp804_destroy(sp804_t *sp804) -{ - int error; - if (sp804->irq_id != PS_INVALID_IRQ_ID) { - error = ps_irq_unregister(&sp804->ops.irq_ops, sp804->irq_id); - ZF_LOGF_IF(error, "Failed to unregister IRQ"); - } - if (sp804->sp804_map != NULL) { - sp804_stop(sp804); - ps_pmem_unmap(&sp804->ops, sp804->pmem, (void *) sp804->sp804_map); - } -} - -int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config) -{ - int error; - - if (sp804 == NULL) { - ZF_LOGE("sp804 cannot be null"); - return EINVAL; - } - - sp804->ops = ops; - sp804->user_cb_fn = config.user_cb_fn; - sp804->user_cb_token = config.user_cb_token; - sp804->user_cb_event = config.user_cb_event; - - error = helper_fdt_alloc_simple( - &ops, config.fdt_path, - SP804_REG_CHOICE, SP804_IRQ_CHOICE, - (void *) &sp804->sp804_map, &sp804->pmem, &sp804->irq_id, - sp804_handle_irq, sp804 - ); - if (error) { - ZF_LOGE("Simple fdt alloc helper failed"); - return error; - } - - sp804_timer_reset(sp804); - return 0; -} diff --git a/libplatsupport/src/plat/hikey/dmt.c b/libplatsupport/src/plat/hikey/dmt.c deleted file mode 100644 index 26f83ca6a..000000000 --- a/libplatsupport/src/plat/hikey/dmt.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include "../../ltimer.h" - -/* Driver for the HiSilison hi6220 hikey Dual-timer devices. - * - * There are 9 timer devices, each implementing two downcounters for a total - * of 18 downcounters. These downcounters run at 19.2MHz. - * - * The 9 timer devices each have their own physical frame address, but the - * 2 downcounters for each device reside in the same 4K frame. - * - * We have numbered the downcounters from 0-17 as distinct logical devices. - */ - -#define TCLR_ONESHOT BIT(0) -#define TCLR_VALUE_32 BIT(1) -#define TCLR_INTENABLE BIT(5) -#define TCLR_AUTORELOAD BIT(6) -#define TCLR_STARTTIMER BIT(7) -#define TICKS_PER_SECOND 19200000 -#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) - -#define HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET (0x20) - -static void dmt_timer_reset(dmt_t *dmt) -{ - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = 0; - - dmt->time_h = 0; -} - -int dmt_stop(dmt_t *dmt) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = dmt_regs->control & ~TCLR_STARTTIMER; - return 0; -} - -int dmt_start(dmt_t *dmt) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = dmt_regs->control | TCLR_STARTTIMER; - return 0; -} - -uint64_t dmt_ticks_to_ns(uint64_t ticks) -{ - return ticks / TICKS_PER_MS * NS_IN_MS; -} - -bool dmt_is_irq_pending(dmt_t *dmt) -{ - if (dmt) { - assert(dmt != NULL && dmt->dmt_map != NULL); - return !!dmt->dmt_map->ris; - } - return false; -} - -int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs) -{ - uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; - if (ticks64 > UINT32_MAX) { - return ETIME; - } - return dmt_set_timeout_ticks(dmt, ticks64, periodic, irqs); -} - -int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - - int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; - flags |= irqs ? TCLR_INTENABLE : 0; - - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = 0; - - /* No need to check for ticks == 0, because 0 is a valid value: - * - * Hikey Application Processor Function Description, section 2.3, "TIMERN_LOAD": - * "The minimum valid value of TIMERN_LOAD is 1. If 0 is written to TIMERN_LOAD, a - * timing interrupt is generated immediately." - * - * If the user supplies 0 as the argument, they'll just get an IRQ - * immediately. - */ - if (flags & TCLR_AUTORELOAD) { - /* Hikey Application Processor Function Description, section 2.3, "TIMERN_BGLOAD": - * "TIMERN_BGLOAD is an initial count value register in periodic mode. - * - * In periodic mode, when the value of TIMERN_BGLOAD is updated, the - * value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However, - * the timer counter does not restart counting. After the counter - * decreases to 0, the value of TIMERN_LOAD (that is, - * the value of TIMERN_BGLOAD) is reloaded to the counter. - * dmt->regs->bgload = ticks; - * - * In other words, for periodic mode, load BGLOAD first, then write to - * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0 - * to BGLOAD. - */ - dmt_regs->bgload = ticks; - } else { - dmt_regs->bgload = 0; - } - dmt_regs->load = ticks; - - /* The TIMERN_VALUE register is read-only. */ - dmt_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 - | flags; - - return 0; -} - -static void dmt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) -{ - assert(data != NULL); - dmt_t *dmt = data; - - /* if we are being used for timestamps */ - if (dmt->user_cb_event == LTIMER_OVERFLOW_EVENT) { - dmt->time_h++; - } - - assert(dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->intclr = 0x1; - - ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); - if (dmt->user_cb_fn) { - dmt->user_cb_fn(dmt->user_cb_token, dmt->user_cb_event); - } -} - -uint64_t dmt_get_ticks(dmt_t *dmt) -{ - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - return dmt_regs->value; -} - -uint64_t dmt_get_time(dmt_t *dmt) -{ - uint32_t high, low; - - /* timer must be being used for timekeeping */ - assert(dmt->user_cb_event == LTIMER_OVERFLOW_EVENT); - - /* dmt is a down counter, invert the result */ - high = dmt->time_h; - low = UINT32_MAX - dmt_get_ticks(dmt); - - /* check after fetching low to see if we've missed a high bit */ - if (dmt_is_irq_pending(dmt)) { - high += 1; - assert(high != 0); - } - - uint64_t ticks = (((uint64_t) high << 32llu) | low); - return dmt_ticks_to_ns(ticks); -} - -void dmt_destroy(dmt_t *dmt) -{ - int error; - if (dmt->irq_id != PS_INVALID_IRQ_ID) { - error = ps_irq_unregister(&dmt->ops.irq_ops, dmt->irq_id); - ZF_LOGF_IF(error, "Failed to unregister IRQ"); - } - if (dmt->dmt_map != NULL) { - dmt_stop(dmt); - } - if (dmt->dmt_map_base != NULL) { - /* use base because dmt_map is adjusted based on whether secondary */ - ps_pmem_unmap(&dmt->ops, dmt->pmem, (void *) dmt->dmt_map_base); - } -} - -int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config) -{ - int error; - - if (dmt == NULL) { - ZF_LOGE("dmt cannot be null"); - return EINVAL; - } - - dmt->ops = ops; - dmt->user_cb_fn = config.user_cb_fn; - dmt->user_cb_token = config.user_cb_token; - dmt->user_cb_event = config.user_cb_event; - - error = helper_fdt_alloc_simple( - &ops, config.fdt_path, - DMT_REG_CHOICE, DMT_IRQ_CHOICE, - &dmt->dmt_map_base, &dmt->pmem, &dmt->irq_id, - dmt_handle_irq, dmt - ); - if (error) { - ZF_LOGE("Simple fdt alloc helper failed"); - return error; - } - - dmt->dmt_map = dmt->dmt_map_base; - - dmt_timer_reset(dmt); - return 0; -} - -/* initialise dmt using the base address of dmtp, so that we do not attempt to map - * that base address again but instead re-use it. */ -int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config) -{ - int error; - - if (dmt == NULL || dmtp == NULL) { - ZF_LOGE("dmt or dmtp cannot be null"); - return EINVAL; - } - - dmt->ops = ops; - dmt->user_cb_fn = config.user_cb_fn; - dmt->user_cb_token = config.user_cb_token; - dmt->user_cb_event = config.user_cb_event; - - /* so that destroy does not try to unmap twice */ - dmt->dmt_map_base = NULL; - /* First sub-device is at offset 0, second sub-device is - * at offset 0x20 within the same page. */ - dmt->dmt_map = (void *)((uintptr_t) dmtp->dmt_map_base) + HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET; - dmt->irq_id = PS_INVALID_IRQ_ID; - - /* Gather FDT info */ - ps_fdt_cookie_t *cookie = NULL; - error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, config.fdt_path, &cookie); - if (error) { - ZF_LOGE("Failed to read path (%d, %s)", error, config.fdt_path); - return error; - } - - /* choose irq 1 because secondary */ - irq_id_t irq_id = ps_fdt_index_register_irq(&ops, cookie, 1, dmt_handle_irq, dmt); - if (irq_id <= PS_INVALID_IRQ_ID) { - ZF_LOGE("Failed to register irqs (%d)", irq_id); - return irq_id; - } - - error = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); - if (error) { - ZF_LOGE("Failed to clean up cookie (%d)", error); - return error; - } - - dmt->irq_id = irq_id; - - dmt_timer_reset(dmt); - return 0; -} diff --git a/libplatsupport/src/plat/hikey/ltimer.c b/libplatsupport/src/plat/hikey/ltimer.c index 355d2647e..4ad13acbc 100644 --- a/libplatsupport/src/plat/hikey/ltimer.c +++ b/libplatsupport/src/plat/hikey/ltimer.c @@ -3,147 +3,21 @@ * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include - -#include -#include +#include #include -#include -#include -#include +#include #include "../../ltimer.h" -/* - * We use two dm timers: one to keep track of an absolute time, the other for timeouts. - */ -/* hikey dualtimers have 2 timers per frame, we just use one from each */ -typedef struct { - dmt_t dmt_timeout; - dmt_t dmt_timestamp; - ps_io_ops_t ops; -} hikey_ltimer_t; - -static int get_time(void *data, uint64_t *time) -{ - hikey_ltimer_t *hikey_ltimer = data; - assert(data != NULL); - assert(time != NULL); - - *time = dmt_get_time(&hikey_ltimer->dmt_timestamp); - return 0; -} - -int set_timeout(void *data, uint64_t ns, timeout_type_t type) -{ - if (type == TIMEOUT_ABSOLUTE) { - uint64_t time; - int error = get_time(data, &time); - if (error) { - return error; - } - if (time > ns) { - return ETIME; - } - ns -= time; - } - - hikey_ltimer_t *hikey_ltimer = data; - return dmt_set_timeout(&hikey_ltimer->dmt_timeout, ns, type == TIMEOUT_PERIODIC, true); -} - -static int reset(void *data) -{ - hikey_ltimer_t *hikey_ltimer = data; - /* restart the rtc */ - dmt_stop(&hikey_ltimer->dmt_timeout); - dmt_start(&hikey_ltimer->dmt_timeout); - return 0; -} - -static void destroy(void *data) -{ - assert(data != NULL); - hikey_ltimer_t *hikey_ltimer = data; - /* NOTE: Note that timeout is primary, timestamp is secondary. - * this means that timeout holds the region mapping. - * We must first destroy timestamp before destroying timeout - * so that the region is freed last. */ - dmt_destroy(&hikey_ltimer->dmt_timestamp); - dmt_destroy(&hikey_ltimer->dmt_timeout); - ps_free(&hikey_ltimer->ops.malloc_ops, sizeof(hikey_ltimer_t), hikey_ltimer); -} +#define SP804_LTIMER_PATH "/soc/timer@f8008000" +#define SP804_LTIMER_FREQ 19200000 -int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) +int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) { - int error; - - if (ltimer == NULL) { - ZF_LOGE("ltimer cannot be NULL"); - return EINVAL; - } - - error = create_ltimer_simple( - ltimer, ops, sizeof(hikey_ltimer_t), - get_time, set_timeout, reset, destroy - ); - if (error) { - ZF_LOGE("Failed to create ltimer for hikey"); - return error; - } - - hikey_ltimer_t *hikey_ltimer = ltimer->data; - hikey_ltimer->ops = ops; - - /* set up a DMT for timeouts */ - dmt_config_t dmt_config = { - .fdt_path = DMT_PATH, - .user_cb_fn = callback, - .user_cb_token = callback_token, - .user_cb_event = LTIMER_TIMEOUT_EVENT - }; - - error = dmt_init(&hikey_ltimer->dmt_timeout, ops, dmt_config); - if (error) { - ZF_LOGE("Failed to init dmt timeout timer"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_start(&hikey_ltimer->dmt_timeout); - if (error) { - ZF_LOGE("Failed to start dmt timeout timer"); - destroy(hikey_ltimer); - return error; - } - - /* set up a DMT for timestamps */ - dmt_config.user_cb_event = LTIMER_OVERFLOW_EVENT; - - error = dmt_init_secondary(&hikey_ltimer->dmt_timestamp, &hikey_ltimer->dmt_timeout, ops, dmt_config); - if (error) { - ZF_LOGE("Failed to init dmt secondary for timestamps"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_start(&hikey_ltimer->dmt_timestamp); - if (error) { - ZF_LOGE("Failed to start dmt timestamp timer"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_set_timeout_ticks(&hikey_ltimer->dmt_timestamp, UINT32_MAX, true, true); - if (error) { - ZF_LOGE("Failed to set ticks for dmt timestamp timer"); - destroy(hikey_ltimer); - return error; - } - - return 0; + return ltimer_sp804_init(ltimer, SP804_LTIMER_PATH, SP804_LTIMER_FREQ, ops, + callback, callback_token); } /* This function is intended to be deleted,