[PATCH 0/4] Cadence UART driver

41 views
Skip to first unread message

Stewart Hildebrand

unread,
Mar 11, 2021, 12:15:01 PM3/11/21
to OSv Development, Stewart Hildebrand, Waldek Kozaczuk
This work is part of an effort to port OSv to LynxSecure hypervisor. I've
tested this running on LynxSecure with the UART passed through, both on real
hardware (ZCU102) and the Xilinx qemu model of the ZCU102 with emulated Cadence
UART. I also did a test on qemu-system-aarch64 (without Cadence UART) to ensure
it doesn't break backward compatibility.

Stewart Hildebrand (4):
Introduce Cadence UART driver
Cadence: add device tree parsing function
Cadence: initialize from device tree
Cadence: add barrier

Makefile | 1 +
arch/aarch64/arch-dtb.cc | 40 +++++++++
arch/aarch64/arch-dtb.hh | 8 ++
arch/aarch64/arch-setup.cc | 17 ++++
arch/aarch64/early-console.hh | 2 +
drivers/cadence-uart.cc | 148 ++++++++++++++++++++++++++++++++++
drivers/cadence-uart.hh | 45 +++++++++++
7 files changed, 261 insertions(+)
create mode 100644 drivers/cadence-uart.cc
create mode 100644 drivers/cadence-uart.hh

--
2.30.2

Stewart Hildebrand

unread,
Mar 11, 2021, 12:15:05 PM3/11/21
to OSv Development, Stewart Hildebrand, Waldek Kozaczuk
The irq bits need to be cleared after reading the fifo register, so the fifo
register needs to be read from interrupt context. Therefore, I introduced
the variables _input_ready and _uart_fifo to store the RX character during the
interrupt handler to be retrieved by the service thread later.

The driver will be initialized from device tree in a follow-up patch.

I modeled the structure of the driver roughly after the OSv pl011 driver.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
---
Makefile | 1 +
drivers/cadence-uart.cc | 145 ++++++++++++++++++++++++++++++++++++++++
drivers/cadence-uart.hh | 45 +++++++++++++
3 files changed, 191 insertions(+)
create mode 100644 drivers/cadence-uart.cc
create mode 100644 drivers/cadence-uart.hh

diff --git a/Makefile b/Makefile
index bbc57252..b2332611 100644
--- a/Makefile
+++ b/Makefile
@@ -833,6 +833,7 @@ endif # x64
ifeq ($(arch),aarch64)
drivers += drivers/mmio-isa-serial.o
drivers += drivers/pl011.o
+drivers += drivers/cadence-uart.o
drivers += drivers/xenconsole.o
drivers += drivers/virtio.o
drivers += drivers/virtio-pci-device.o
diff --git a/drivers/cadence-uart.cc b/drivers/cadence-uart.cc
new file mode 100644
index 00000000..aa407008
--- /dev/null
+++ b/drivers/cadence-uart.cc
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 DornerWorks, Ltd
+ * Author: Stewart Hildebrand
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#include "cadence-uart.hh"
+#include <stdint.h>
+
+namespace console {
+
+// Register reference:
+// https://www.xilinx.com/html_docs/registers/ug1087/mod___uart.html
+
+#define BIT(x) (1U << (x))
+
+#define UART_CR_RXRES BIT(0) // RX reset
+#define UART_CR_TXRES BIT(1) // TX reset
+#define UART_CR_RXEN BIT(2) // RX enable
+#define UART_CR_TXEN BIT(4) // TX enable
+
+#define UART_MR_PARITY_NONE BIT(5) // No parity
+
+#define UART_SR_RTRIG BIT(0) // RX trigger
+#define UART_SR_REMPTY BIT(1) // RX empty
+#define UART_SR_TEMPTY BIT(3) // TX empty
+#define UART_SR_TFUL BIT(4) // TX full
+#define UART_SR_TACTIVE BIT(11) // TX active
+
+typedef struct __attribute__ ((aligned (4))) {
+ volatile uint32_t cr; // 0x00 Control register
+ volatile uint32_t mr; // 0x04 Mode register
+ volatile uint32_t ier; // 0x08 Interrupt enable register
+ volatile uint32_t idr; // 0x0C Interrupt disable register
+ volatile uint32_t imr; // 0x10 Interrupt mask register
+ volatile uint32_t cisr; // 0x14 Channel interrupt status register
+ uint32_t pad1[2]; // 0x18 - 0x1C
+ volatile uint32_t rtrig; // 0x20 RX trigger threshold
+ uint32_t pad2[2]; // 0x24 - 0x28
+ volatile uint32_t sr; // 0x2C Status register
+ volatile uint32_t fifo; // 0x30 RX/TX FIFO register
+} cadence_t;
+
+static_assert(sizeof(cadence_t) == 0x34, "Wrong size for cadence_t");
+
+// Default base addr
+cadence_t *uart = (cadence_t *)0xff000000;
+
+bool Cadence_Console::active = false;
+
+void Cadence_Console::set_base_addr(u64 addr)
+{
+ // Intentional bitwise AND inside condition
+ if (addr & 0x3U) {
+ abort("UART base address is not 32-bit aligned");
+ }
+ uart = (cadence_t *)addr;
+}
+
+void Cadence_Console::set_irqid(int irqid) {
+ this->irqid = irqid;
+}
+
+u64 Cadence_Console::get_base_addr() {
+ return (u64)uart;
+}
+
+void Cadence_Console::flush()
+{
+ uint32_t sr;
+ do {
+ sr = uart->sr;
+ asm volatile("nop");
+ // Intentional bitwise AND inside condition
+ } while (!(sr & UART_SR_TEMPTY) || (sr & UART_SR_TACTIVE));
+}
+
+bool Cadence_Console::input_ready() {
+ return _input_ready;
+}
+
+char Cadence_Console::readch()
+{
+ _input_ready = false;
+ return _uart_fifo;
+}
+
+bool Cadence_Console::ack_irq()
+{
+ // Intentional bitwise AND inside condition
+ if (uart->cisr & uart->imr) {
+ return true;
+ }
+ return false;
+}
+
+void Cadence_Console::irq_handler()
+{
+ uint32_t cisr = uart->cisr & uart->imr;
+
+ // Intentional bitwise AND inside condition
+ if ((cisr & UART_SR_RTRIG) && !(uart->sr & UART_SR_REMPTY)) {
+ _uart_fifo = uart->fifo;
+ _input_ready = true;
+ }
+
+ // IRQ must be cleared after character is read from FIFO
+ uart->cisr = cisr;
+
+ _thread->wake();
+}
+
+void Cadence_Console::dev_start() {
+ flush();
+ // Reset and enable the RX and TX paths
+ uart->cr = UART_CR_RXRES | UART_CR_TXRES | UART_CR_RXEN | UART_CR_TXEN;
+
+ uart->mr = UART_MR_PARITY_NONE;
+
+ _irq.reset(new spi_interrupt(gic::irq_type::IRQ_TYPE_LEVEL, this->irqid,
+ [this] { return this->ack_irq(); },
+ [this] { this->irq_handler(); }));
+
+ uart->rtrig = 1U;
+ uart->cisr = ~0U;
+ uart->idr = ~0U;
+
+ uart->ier = UART_SR_RTRIG;
+}
+
+void Cadence_Console::write(const char *str, size_t len) {
+ while (len > 0) {
+ // Intentional bitwise AND inside condition
+ while (uart->sr & UART_SR_TFUL) {
+ // spin
+ asm volatile("nop");
+ }
+ uart->fifo = *str++;
+ len--;
+ }
+}
+
+}
diff --git a/drivers/cadence-uart.hh b/drivers/cadence-uart.hh
new file mode 100644
index 00000000..19f27104
--- /dev/null
+++ b/drivers/cadence-uart.hh
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 DornerWorks, Ltd
+ * Author: Stewart Hildebrand
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#ifndef CADENCE_UART_HH
+#define CADENCE_UART_HH
+
+#include "console-driver.hh"
+#include "exceptions.hh"
+#include <osv/interrupt.hh>
+#include <osv/types.h>
+
+namespace console {
+
+class Cadence_Console : public console_driver {
+public:
+ virtual void write(const char *str, size_t len);
+ virtual void flush();
+ virtual bool input_ready();
+ virtual char readch();
+
+ void set_base_addr(u64 addr);
+ u64 get_base_addr();
+ void set_irqid(int irqid);
+
+ static bool active;
+private:
+ virtual void dev_start();
+ virtual const char *thread_name() { return "cadence-input"; }
+ bool ack_irq();
+ void irq_handler();
+ // Default UART irq = SPI 21 = 32 + 21
+ unsigned int irqid = 53;
+ std::unique_ptr<spi_interrupt> _irq;
+ char _uart_fifo = 0;
+ bool _input_ready = false;
+};
+
+}
+
+#endif /* CADENCE_UART_HH */
--
2.30.2

Stewart Hildebrand

unread,
Mar 11, 2021, 12:15:09 PM3/11/21
to OSv Development, Stewart Hildebrand, Waldek Kozaczuk
Parses the Cadence UART base address and IRQ.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
---
arch/aarch64/arch-dtb.cc | 40 ++++++++++++++++++++++++++++++++++++++++
arch/aarch64/arch-dtb.hh | 8 ++++++++
2 files changed, 48 insertions(+)

diff --git a/arch/aarch64/arch-dtb.cc b/arch/aarch64/arch-dtb.cc
index a80b5d22..24ac9267 100644
--- a/arch/aarch64/arch-dtb.cc
+++ b/arch/aarch64/arch-dtb.cc
@@ -220,6 +220,46 @@ u64 dtb_get_mmio_serial_console(int *irqid)
return address;
}

+u64 dtb_get_cadence_uart(int *irqid)
+{
+ const char *compatible[] = {
+ "cdns,uart-r1p8",
+ "cdns,uart-r1p12",
+ "xlnx,xuartps",
+ };
+ unsigned int i;
+ int node;
+ struct dtb_int_spec int_spec[1];
+ u64 addr;
+
+ if (!dtb) {
+ return 0;
+ }
+
+ for (i = 0; i < sizeof(compatible)/sizeof(compatible[0]); i++) {
+ node = fdt_node_offset_by_compatible(dtb, -1, compatible[i]);
+ if (node >= 0)
+ break;
+ }
+
+ if (node < 0) {
+ return 0;
+ }
+
+ if (!dtb_get_reg(node, &addr)) {
+ return 0;
+ }
+
+ if (!dtb_get_int_spec(node, int_spec, 1)) {
+ return 0;
+ }
+
+ if (irqid) {
+ *irqid = int_spec[0].irq_id;
+ }
+ return addr;
+}
+
#define VIRTIO_MMIO_DEV_COMPAT "virtio,mmio"
#define DTB_MAX_VIRTIO_MMIO_DEV_COUNT 8
static virtio::mmio_device_info dtb_dtb_virtio_mmio_devices_infos[DTB_MAX_VIRTIO_MMIO_DEV_COUNT];
diff --git a/arch/aarch64/arch-dtb.hh b/arch/aarch64/arch-dtb.hh
index e967d754..0aebb4b3 100644
--- a/arch/aarch64/arch-dtb.hh
+++ b/arch/aarch64/arch-dtb.hh
@@ -57,6 +57,14 @@ u64 dtb_get_uart(int *irqid);
*/
u64 dtb_get_mmio_serial_console(int *irqid);

+/* u64 dtb_get_cadence_uart(int *irqid)
+ *
+ * return the base address of the uart and writes the
+ * irqid of the uart interrupt to irqid,
+ * or returns zero on failure.
+ */
+u64 dtb_get_cadence_uart(int *irqid);
+
/* dtb_collect_parsed_mmio_virtio_devices()
*
* collect and add any parsed mmio devices
--
2.30.2

Stewart Hildebrand

unread,
Mar 11, 2021, 12:15:10 PM3/11/21
to OSv Development, Stewart Hildebrand, Waldek Kozaczuk
Add runtime map for Cadence UART

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
---
arch/aarch64/arch-setup.cc | 17 +++++++++++++++++
arch/aarch64/early-console.hh | 2 ++
2 files changed, 19 insertions(+)

diff --git a/arch/aarch64/arch-setup.cc b/arch/aarch64/arch-setup.cc
index e22b4ea4..fd2103b7 100644
--- a/arch/aarch64/arch-setup.cc
+++ b/arch/aarch64/arch-setup.cc
@@ -100,6 +100,13 @@ void arch_setup_free_memory()
mmu::mattr::dev);
}

+ if (console::Cadence_Console::active) {
+ // linear_map [TTBR0 - UART]
+ addr = (mmu::phys)console::aarch64_console.cadence.get_base_addr();
+ mmu::linear_map((void *)addr, addr, 0x1000, mmu::page_size,
+ mmu::mattr::dev);
+ }
+
/* linear_map [TTBR0 - GIC DIST and GIC CPU] */
u64 dist, cpu;
size_t dist_len, cpu_len;
@@ -211,6 +218,16 @@ void arch_init_early_console()
return;
}

+ mmio_serial_address = dtb_get_cadence_uart(&irqid);
+ if (mmio_serial_address) {
+ new (&console::aarch64_console.cadence) console::Cadence_Console();
+ console::arch_early_console = console::aarch64_console.cadence;
+ console::aarch64_console.cadence.set_base_addr(mmio_serial_address);
+ console::aarch64_console.cadence.set_irqid(irqid);
+ console::Cadence_Console::active = true;
+ return;
+ }
+
new (&console::aarch64_console.pl011) console::PL011_Console();
console::arch_early_console = console::aarch64_console.pl011;
console::PL011_Console::active = true;
diff --git a/arch/aarch64/early-console.hh b/arch/aarch64/early-console.hh
index 6ecedc0e..74cf36a7 100644
--- a/arch/aarch64/early-console.hh
+++ b/arch/aarch64/early-console.hh
@@ -10,6 +10,7 @@

#include <drivers/console-driver.hh>
#include <drivers/pl011.hh>
+#include <drivers/cadence-uart.hh>
#include <drivers/xenconsole.hh>
#include <drivers/mmio-isa-serial.hh>

@@ -18,6 +19,7 @@ namespace console {

union AARCH64_Console {
PL011_Console pl011;
+ Cadence_Console cadence;
XEN_Console xen;
mmio_isa_serial_console isa_serial;

--
2.30.2

Stewart Hildebrand

unread,
Mar 11, 2021, 12:15:11 PM3/11/21
to OSv Development, Stewart Hildebrand, Waldek Kozaczuk
During early boot, we don't have a good way to map the UART page as
Device-nGnRnE memory. I'm testing this on a hypervisor that maps the UART page
as Device-GRE memory in the 2nd stage translation table. The OSv early page
tables map everything as normal memory in stage 1. As a result, there will be
missed characters during early boot, and the OSv version message will appear
garbled.

After the memory barrier is introduced, OSv prints the proper early version
message.

The UART page does eventually get mapped as Device-nGnRnE memory, but not until
later in the boot process when we switch over to the runtime page tables.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
---
drivers/cadence-uart.cc | 3 +++
1 file changed, 3 insertions(+)

diff --git a/drivers/cadence-uart.cc b/drivers/cadence-uart.cc
index aa407008..52fc84ff 100644
--- a/drivers/cadence-uart.cc
+++ b/drivers/cadence-uart.cc
@@ -138,6 +138,9 @@ void Cadence_Console::write(const char *str, size_t len) {
asm volatile("nop");
}
uart->fifo = *str++;
+#ifdef __aarch64__
+ asm volatile("dmb nshst");
+#endif
len--;
}
}
--
2.30.2

Commit Bot

unread,
Mar 14, 2021, 12:11:11 PM3/14/21
to osv...@googlegroups.com, Stewart Hildebrand
From: Stewart Hildebrand <stewart.h...@dornerworks.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

Introduce Cadence UART driver

The irq bits need to be cleared after reading the fifo register, so the fifo
register needs to be read from interrupt context. Therefore, I introduced
the variables _input_ready and _uart_fifo to store the RX character during the
interrupt handler to be retrieved by the service thread later.

The driver will be initialized from device tree in a follow-up patch.

I modeled the structure of the driver roughly after the OSv pl011 driver.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
Message-Id: <20210311171431.31417...@dornerworks.com>

---
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -833,6 +833,7 @@ endif # x64
ifeq ($(arch),aarch64)
drivers += drivers/mmio-isa-serial.o
drivers += drivers/pl011.o
+drivers += drivers/cadence-uart.o
drivers += drivers/xenconsole.o
drivers += drivers/virtio.o
drivers += drivers/virtio-pci-device.o
diff --git a/drivers/cadence-uart.cc b/drivers/cadence-uart.cc
--- a/drivers/cadence-uart.cc
--- a/drivers/cadence-uart.hh

Commit Bot

unread,
Mar 14, 2021, 12:11:11 PM3/14/21
to osv...@googlegroups.com, Stewart Hildebrand' via OSv Development
From: Stewart Hildebrand' via OSv Development <osv...@googlegroups.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

Cadence: add device tree parsing function

Parses the Cadence UART base address and IRQ.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
Message-Id: <20210311171431.31417...@dornerworks.com>

---
diff --git a/arch/aarch64/arch-dtb.cc b/arch/aarch64/arch-dtb.cc

Commit Bot

unread,
Mar 14, 2021, 12:11:12 PM3/14/21
to osv...@googlegroups.com, Stewart Hildebrand
From: Stewart Hildebrand <stewart.h...@dornerworks.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

Cadence: initialize from device tree

Add runtime map for Cadence UART

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
Message-Id: <20210311171431.31417...@dornerworks.com>

---
diff --git a/arch/aarch64/arch-setup.cc b/arch/aarch64/arch-setup.cc

Commit Bot

unread,
Mar 14, 2021, 12:11:13 PM3/14/21
to osv...@googlegroups.com, Stewart Hildebrand
From: Stewart Hildebrand <stewart.h...@dornerworks.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

Cadence: add barrier

During early boot, we don't have a good way to map the UART page as
Device-nGnRnE memory. I'm testing this on a hypervisor that maps the UART page
as Device-GRE memory in the 2nd stage translation table. The OSv early page
tables map everything as normal memory in stage 1. As a result, there will be
missed characters during early boot, and the OSv version message will appear
garbled.

After the memory barrier is introduced, OSv prints the proper early version
message.

The UART page does eventually get mapped as Device-nGnRnE memory, but not until
later in the boot process when we switch over to the runtime page tables.

Signed-off-by: Stewart Hildebrand <stewart.h...@dornerworks.com>
Message-Id: <20210311171431.31417...@dornerworks.com>

---
diff --git a/drivers/cadence-uart.cc b/drivers/cadence-uart.cc

Waldek Kozaczuk

unread,
Mar 14, 2021, 12:14:09 PM3/14/21
to OSv Development
Hey,

I obviously could not test any of that Xilinx but I have verified these patches do not break other console logic on QEMU with TCG and KVM and Firecracker.

Otherwise, your changes look good, so I have committed it.

Waldek 

Reply all
Reply to author
Forward
0 new messages