Skip to content

Commit

Permalink
Introduce Cadence UART driver
Browse files Browse the repository at this point in the history
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 <[email protected]>
Message-Id: <[email protected]>
  • Loading branch information
stewdk authored and wkozaczuk committed Mar 14, 2021
1 parent be49501 commit 3a7d6cc
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
145 changes: 145 additions & 0 deletions drivers/cadence-uart.cc
Original file line number Diff line number Diff line change
@@ -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--;
}
}

}
45 changes: 45 additions & 0 deletions drivers/cadence-uart.hh
Original file line number Diff line number Diff line change
@@ -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 */

0 comments on commit 3a7d6cc

Please sign in to comment.