| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <bits.h> |
| #include <lib/cbuf.h> |
| #include <lib/debuglog.h> |
| #include <lib/zircon-internal/macros.h> |
| #include <platform.h> |
| #include <reg.h> |
| #include <stdio.h> |
| #include <trace.h> |
| #include <zircon/boot/driver-config.h> |
| |
| #include <arch/arm64/periphmap.h> |
| #include <dev/interrupt.h> |
| #include <dev/uart.h> |
| #include <dev/uart/motmot/init.h> |
| #include <kernel/auto_lock.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/thread.h> |
| #include <kernel/timer.h> |
| #include <ktl/algorithm.h> |
| #include <pdev/uart.h> |
| #include <platform/debug.h> |
| |
| #include <ktl/enforce.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| // Motmot implementation |
| |
| // clang-format off |
| #define UART_ULCON (0x00) |
| #define UART_UCON (0x04) |
| #define UART_UFCON (0x08) |
| #define UART_UMCON (0x0c) |
| #define UART_UTRSTAT (0x10) |
| #define UART_UERSTAT (0x14) |
| #define UART_UFSTAT (0x18) |
| #define UART_UMSTAT (0x1c) |
| #define UART_UTXH (0x20) |
| #define UART_URXH (0x24) |
| #define UART_UBRDIV (0x28) |
| #define UART_UFRACVAL (0x2c) |
| #define UART_UINTP (0x30) |
| #define UART_UINTS (0x34) |
| #define UART_UINTM (0x38) // interrupt mask register, protect with uart_spinlock |
| #define UART_UFLT_CONF (0x40) |
| #define UART_FIFO_DEPTH (0xdc) |
| // clang-format on |
| |
| #define UARTREG(base, reg) (*REG32((base) + (reg))) |
| #define UARTREG_RMW(base, reg, mask, val) \ |
| do { \ |
| UARTREG(base, reg) = (UARTREG(base, reg) & ~(mask)) | (val); \ |
| } while (0) |
| |
| const size_t RXBUF_SIZE = 256; |
| |
| namespace { |
| |
| // values read from zbi |
| vaddr_t uart_base = 0; |
| uint32_t uart_irq = 0; |
| |
| Cbuf uart_rx_buf; |
| |
| // Tx driven irq: |
| // NOTE: For the motmot, txim is the "ready to transmit" interrupt. So we must |
| // mask it when we no longer care about it and unmask it when we start txing. |
| bool uart_tx_irq_enabled = false; |
| uint32_t uart_tx_fifo_size = 0; |
| uint32_t uart_rx_fifo_size = 0; |
| AutounsignalEvent uart_dputc_event{true}; |
| |
| // It's important to ensure that no other locks are acquired while holding this lock. This lock |
| // is needed for the printf and panic code paths, and printing and panicking must be safe while |
| // holding (almost) any lock. |
| DECLARE_SINGLETON_SPINLOCK_WITH_TYPE(uart_spinlock, MonitoredSpinLock); |
| } // namespace |
| |
| static inline void uartreg_and_eq(uintptr_t base, ptrdiff_t reg, uint32_t flags) { |
| volatile uint32_t* ptr = reinterpret_cast<volatile uint32_t*>(base + reg); |
| *ptr = *ptr & flags; |
| } |
| |
| static inline void uartreg_or_eq(uintptr_t base, ptrdiff_t reg, uint32_t flags) { |
| volatile uint32_t* ptr = reinterpret_cast<volatile uint32_t*>(base + reg); |
| *ptr = *ptr | flags; |
| } |
| |
| // The UINTM register is contended from both IRQ and threaded mode, so protect |
| // accesses via the uart_spinlock. |
| static inline void motmot_uart_mask_tx() TA_REQ(uart_spinlock::Get()) { |
| uartreg_or_eq(uart_base, UART_UINTM, (1 << 2)); // txd |
| } |
| static inline void motmot_uart_unmask_tx() TA_REQ(uart_spinlock::Get()) { |
| uartreg_and_eq(uart_base, UART_UINTM, ~(1 << 2)); // txd |
| } |
| static inline void motmot_uart_mask_rx() TA_REQ(uart_spinlock::Get()) { |
| uartreg_or_eq(uart_base, UART_UINTM, (1 << 0)); // rxd |
| } |
| static inline void motmot_uart_unmask_rx() TA_REQ(uart_spinlock::Get()) { |
| uartreg_and_eq(uart_base, UART_UINTM, ~(1 << 0)); // rxd |
| } |
| |
| static interrupt_eoi motmot_uart_irq(void* arg) { |
| // read interrupt status |
| uint32_t isr = UARTREG(uart_base, UART_UINTP); |
| |
| LTRACEF("irq UINTP %#x UINTS %#x ", isr, UARTREG(uart_base, UART_UINTS)); |
| LTRACEF("UTRSTAT %#x\n", UARTREG(uart_base, UART_UTRSTAT)); |
| |
| uint32_t pending_ack = 0; // accumulate pending writes to UINTP at the end |
| |
| if (isr & (1 << 0)) { // rxd |
| // while fifo is not empty, read chars out of it |
| while ((UARTREG(uart_base, UART_UFSTAT) & (0x1ff)) != 0) { // uart fifo level |
| LTRACEF("fstat %#x\n", UARTREG(uart_base, UART_UFSTAT)); |
| // if we're out of rx buffer, mask the irq instead of handling it |
| { |
| // This critical section is paired with the one in |motmot_uart_getc| |
| // where RX is unmasked. This is necessary to avoid the following race |
| // condition: |
| // |
| // Assume we have two threads, a reader R and a writer W, and the |
| // buffer is full. For simplicity, let us assume the buffer size is 1; |
| // the same process applies with a larger buffer and more readers. |
| // |
| // W: Observes the buffer is full. |
| // R: Reads a character. The buffer is now empty. |
| // R: Unmasks RX. |
| // W: Masks RX. |
| // |
| // At this point, we have an empty buffer and RX interrupts are masked - |
| // we're stuck! Thus, to avoid this, we acquire the spinlock before |
| // checking if the buffer is full, and release after (conditionally) |
| // masking RX interrupts. By pairing this with the acquisition of the |
| // same lock around unmasking RX interrupts, we prevent the writer above |
| // from being interrupted by a read-and-unmask. |
| Guard<MonitoredSpinLock, NoIrqSave> guard{uart_spinlock::Get(), SOURCE_TAG}; |
| if (uart_rx_buf.Full()) { |
| LTRACEF("out of buf, masking rx\n"); |
| motmot_uart_mask_rx(); |
| break; |
| } |
| } |
| |
| // See if there are any pending errors queued up in the error stack. |
| // The hardware keeps a parallel stack of pending errors next to the RX fifo |
| // with the idea that the error only rises to the surface when the character |
| // that it was triggered on is the current top of the rx stack. Thusly we |
| // need to read the error status register on every char and make sure we're |
| // not actually looking at a fouled read. |
| // |
| // It's a bit unclear, but it seems that overrun and break detects are somewhat |
| // independent of the character in the fifo itself, but are really triggered |
| // at the boundary between it and the next character, so only discard fifo reads |
| // for framing and parity errors. |
| bool discard_char = false; |
| uint32_t err = UARTREG(uart_base, UART_UERSTAT); |
| if (unlikely(err & 0b1111)) { |
| if (err & (1 << 0)) { // overrun error |
| // not much we can do except log and move on |
| printf("UART: rx overrun\n"); |
| } |
| if (err & (1 << 1)) { // parity error |
| printf("UART: rx parity\n"); |
| // discard the next char |
| discard_char = true; |
| } |
| if (err & (1 << 2)) { // framing error |
| printf("UART: rx frame err\n"); |
| // discard the next char |
| discard_char = true; |
| } |
| if (err & (1 << 3)) { // break detect |
| printf("UART: brk\n"); |
| } |
| } |
| |
| char c = static_cast<char>(UARTREG(uart_base, UART_URXH)); |
| if (!discard_char) { |
| LTRACEF("%#hhx in cbuf\n", c); |
| uart_rx_buf.WriteChar(c); |
| } |
| } |
| pending_ack |= (1 << 0); // clear rxd |
| } |
| |
| if (uart_tx_irq_enabled) { |
| if (isr & (1 << 2)) { // txd |
| pending_ack |= (1 << 2); // clear txd |
| |
| // Wake up any waiters in uart_dputs. |
| uart_dputc_event.Signal(); |
| |
| // Mask the TX irq, uart_dputs will unmask if necessary. |
| { |
| Guard<MonitoredSpinLock, NoIrqSave> guard{uart_spinlock::Get(), SOURCE_TAG}; |
| motmot_uart_mask_tx(); |
| } |
| } |
| } |
| |
| // ack any pending irqs |
| if (pending_ack) { |
| UARTREG(uart_base, UART_UINTP) = pending_ack; |
| } |
| |
| return IRQ_EOI_DEACTIVATE; |
| } |
| |
| static int motmot_uart_getc(bool wait) { |
| // RX irq based |
| zx::status<char> result = uart_rx_buf.ReadChar(wait); |
| if (result.is_ok()) { |
| { |
| // See the comment on the critical section in |motmot_uart_irq|. |
| Guard<MonitoredSpinLock, IrqSave> guard{uart_spinlock::Get(), SOURCE_TAG}; |
| motmot_uart_unmask_rx(); |
| } |
| return result.value(); |
| } |
| |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // panic-time getc/putc |
| static void motmot_uart_pputc(char c) { |
| if (c == '\n') { |
| motmot_uart_pputc('\r'); |
| } |
| |
| // spin while fifo is full |
| while (UARTREG(uart_base, UART_UFSTAT) & (1 << 24)) // tx fifo full |
| ; |
| UARTREG(uart_base, UART_UTXH) = c; |
| } |
| |
| static int motmot_uart_pgetc() { |
| for (;;) { |
| if ((UARTREG(uart_base, UART_UFSTAT) & (0x1ff)) != 0) { // rx fifo count |
| // read and discard character if framing or parity error is queued |
| uint32_t err = UARTREG(uart_base, UART_UERSTAT); |
| if (err & 0b0110) { // framing and parity error |
| volatile uint32_t discard __UNUSED = UARTREG(uart_base, UART_URXH); |
| continue; |
| } |
| |
| return UARTREG(uart_base, UART_URXH); |
| } else { |
| return -1; |
| } |
| } |
| } |
| |
| static void motmot_uart_dputs(const char* str, size_t len, bool block, bool map_NL) { |
| bool copied_CR = false; |
| |
| if (!uart_tx_irq_enabled) { |
| block = false; |
| } |
| |
| while (len > 0) { |
| bool wait = false; |
| size_t to_write = 0; |
| |
| // Acquire the main uart spinlock once every iteration to try to cap the worst |
| // case time holding it. If a large string is passed, for example, this routine |
| // will write up to 64 bytes at a time into the fifo per iteration, dropping and |
| // reacquiring the spinlock every cycle. |
| Guard<MonitoredSpinLock, IrqSave> guard{uart_spinlock::Get(), SOURCE_TAG}; |
| |
| uint32_t ufstat = UARTREG(uart_base, UART_UFSTAT); |
| if (ufstat & (1 << 24)) { // tx fifo full |
| // Is the FIFO completely full? if so, block or spin at the end of the loop |
| wait = true; |
| } else { |
| // Compute how much space we have in the fifo and put up to that many |
| // characters in. |
| uint32_t used_fifo = BITS_SHIFT(ufstat, 23, 16); // tx fifo count |
| size_t remaining_fifo = uart_tx_fifo_size - used_fifo; |
| |
| to_write = ktl::min(len, remaining_fifo); |
| } |
| |
| // stuff up to to_write number of chars into the fifo |
| for (size_t i = 0; i < to_write; i++) { |
| if (!copied_CR && map_NL && *str == '\n') { |
| copied_CR = true; |
| UARTREG(uart_base, UART_UTXH) = '\r'; |
| } else { |
| copied_CR = false; |
| UARTREG(uart_base, UART_UTXH) = *str++; |
| len--; |
| } |
| } |
| |
| // If at the end of the loop we've decided to wait, block or spin. Otherwise loop |
| // around. |
| if (wait) { |
| if (block) { |
| // Unmask Tx interrupts before we block on the event. The TX irq handler |
| // will signal the event when the fifo falls below a threshold. |
| motmot_uart_unmask_tx(); |
| |
| // drop the spinlock before waiting |
| guard.Release(); |
| uart_dputc_event.Wait(); |
| } else { |
| // drop the spinlock before yielding |
| guard.Release(); |
| arch::Yield(); |
| } |
| } |
| |
| // Note spinlock will be dropped and reaquired around this loop |
| } |
| } |
| |
| static void motmot_uart_start_panic() { uart_tx_irq_enabled = false; } |
| |
| static const struct pdev_uart_ops uart_ops = { |
| .getc = motmot_uart_getc, |
| .pputc = motmot_uart_pputc, |
| .pgetc = motmot_uart_pgetc, |
| .start_panic = motmot_uart_start_panic, |
| .dputs = motmot_uart_dputs, |
| }; |
| |
| void MotmotUartInitEarly(const dcfg_simple_t& config) { |
| ASSERT(config.mmio_phys != 0); |
| ASSERT(config.irq != 0); |
| |
| uart_base = periph_paddr_to_vaddr(config.mmio_phys); |
| ASSERT(uart_base != 0); |
| uart_irq = config.irq; |
| |
| UARTREG(uart_base, UART_ULCON) = (3 << 0); // no parity, one stop bit, 8 bit |
| UARTREG(uart_base, UART_UMCON) = 0; // no auto flow control |
| |
| // read the tx and rx fifo size, useful later |
| uint32_t fifo_depth = UARTREG(uart_base, UART_FIFO_DEPTH); |
| uart_tx_fifo_size = BITS_SHIFT(fifo_depth, 24, 16); |
| uart_rx_fifo_size = BITS(fifo_depth, 8, 0); |
| |
| // sanity check the sizes in case hardware returns something bogus |
| if (uart_tx_fifo_size == 0 || uart_tx_fifo_size > 256) { |
| uart_tx_fifo_size = 1; |
| } |
| if (uart_rx_fifo_size == 0 || uart_rx_fifo_size > 256) { |
| uart_rx_fifo_size = 1; |
| } |
| |
| pdev_register_uart(&uart_ops); |
| } |
| |
| void MotmotUartInitLate() { |
| // create circular buffer to hold received data |
| uart_rx_buf.Initialize(RXBUF_SIZE, malloc(RXBUF_SIZE)); |
| |
| // register IRQ handler |
| zx_status_t status = register_int_handler(uart_irq, &motmot_uart_irq, nullptr); |
| DEBUG_ASSERT(status == ZX_OK); |
| |
| // mask all irqs |
| UARTREG(uart_base, UART_UINTM) = 0xf; // mask CTS, TX, error, RX |
| |
| // clear all irqs |
| UARTREG(uart_base, UART_UINTP) = 0xf; // clear CTX, TX, error, RX |
| |
| // disable fifos, set tx/rx threshold to minimum |
| UARTREG(uart_base, UART_UFCON) = 0; |
| |
| // reset rx fifo |
| uartreg_or_eq(uart_base, UART_UFCON, (1 << 1)); |
| |
| // wait for it to clear |
| while (UARTREG(uart_base, UART_UFCON) & (1 << 1)) |
| ; |
| |
| // enable fifos |
| uartreg_or_eq(uart_base, UART_UFCON, (1 << 0)); |
| |
| // enable receive |
| // clang-format off |
| UARTREG_RMW(uart_base, UART_UCON, |
| (0xf << 12) | (1 << 11) | (3 << 0), |
| (3 << 12) // default rx timeout interval |
| | (0 << 11) // disable rx timeout when rx fifo empty |
| | (1 << 7) // rx timeout enable |
| | (1 << 6) // rx interrupt enable |
| | (1 << 0)); // rx enable interrupt mode |
| // clang-format on |
| |
| LTRACEF("UART: FIFO_DEPTH %#x\n", UARTREG(uart_base, UART_FIFO_DEPTH)); |
| LTRACEF("UCON %#x\n", UARTREG(uart_base, UART_UCON)); |
| LTRACEF("UFCON %#x\n", UARTREG(uart_base, UART_UFCON)); |
| LTRACEF("UMCON %#x\n", UARTREG(uart_base, UART_UMCON)); |
| LTRACEF("UERSTAT %#x\n", UARTREG(uart_base, UART_UERSTAT)); |
| |
| // unmask rx interrupt |
| { |
| Guard<MonitoredSpinLock, IrqSave> guard{uart_spinlock::Get(), SOURCE_TAG}; |
| motmot_uart_unmask_rx(); |
| } |
| |
| // level triggered irq |
| configure_interrupt(uart_irq, IRQ_TRIGGER_MODE_LEVEL, IRQ_POLARITY_ACTIVE_HIGH); |
| |
| // enable interrupt |
| unmask_interrupt(uart_irq); |
| |
| // use PIO driven TX if bypassing debuglog |
| if (dlog_bypass() == true) { |
| uart_tx_irq_enabled = false; |
| } else { |
| // start up tx driven output |
| printf("UART: started IRQ driven TX\n"); |
| uart_tx_irq_enabled = true; |
| } |
| |
| printf("UART: rx fifo len %u tx fifo len %u\n", uart_rx_fifo_size, uart_tx_fifo_size); |
| } |