diff --git a/agent/cobs.c b/agent/cobs.c index ce87cf7..d086fce 100644 --- a/agent/cobs.c +++ b/agent/cobs.c @@ -3,7 +3,6 @@ */ #include "cobs.h" -#include "protocol.h" uint32_t cobs_encode(const uint8_t *input, uint32_t len, uint8_t *output) { uint32_t out_idx = 0; diff --git a/agent/protocol.c b/agent/protocol.c index a059564..8fad51a 100644 --- a/agent/protocol.c +++ b/agent/protocol.c @@ -1,5 +1,9 @@ /* * Flash agent protocol — COBS framing + CRC32. + * + * RX data comes from the IRQ-driven ring buffer in uart.c. + * No software FIFO polling needed — uart_getc_safe() reads from + * the ring buffer which is filled by the UART RX interrupt. */ #include "protocol.h" @@ -42,9 +46,6 @@ static const uint32_t crc32_table[256] = { 0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, }; -/* Forward declaration — defined below */ -void soft_rx_drain(void); - uint32_t crc32(uint32_t crc, const uint8_t *buf, uint32_t len) { crc = crc ^ 0xFFFFFFFF; for (uint32_t i = 0; i < len; i++) { @@ -54,123 +55,60 @@ uint32_t crc32(uint32_t crc, const uint8_t *buf, uint32_t len) { } /* Internal send/recv buffers */ -static uint8_t tx_raw[MAX_PAYLOAD + 16]; /* cmd + data + crc */ -static uint8_t tx_cobs[MAX_PAYLOAD + 32]; /* COBS encoded */ +static uint8_t tx_raw[MAX_PAYLOAD + 16]; +static uint8_t tx_cobs[MAX_PAYLOAD + 32]; static uint8_t rx_cobs[MAX_PAYLOAD + 32]; static uint8_t rx_raw[MAX_PAYLOAD + 16]; -/* Software RX FIFO — drains PL011 hardware FIFO (16 bytes) to prevent - * overflow during COBS decode + CRC32 processing on uncached DDR. */ -static uint8_t soft_rx[4096]; -static uint32_t soft_rx_head = 0; -static uint32_t soft_rx_tail = 0; - -void soft_rx_drain(void) { - while (uart_readable()) { - int b = uart_getc_safe(); - if (b >= 0) { - uint32_t next = (soft_rx_head + 1) % sizeof(soft_rx); - if (next != soft_rx_tail) { - soft_rx[soft_rx_head] = (uint8_t)b; - soft_rx_head = next; - } - } else if (b == -2) { - uart_clear_errors(); - } - } -} - -static int soft_rx_get(void) { - soft_rx_drain(); - if (soft_rx_head != soft_rx_tail) { - uint8_t b = soft_rx[soft_rx_tail]; - soft_rx_tail = (soft_rx_tail + 1) % sizeof(soft_rx); - return b; - } - return -1; -} - void proto_send(uint8_t cmd, const uint8_t *data, uint32_t len) { - /* Build raw packet: cmd + data + crc32 */ tx_raw[0] = cmd; for (uint32_t i = 0; i < len; i++) tx_raw[1 + i] = data[i]; uint32_t raw_len = 1 + len; - uint32_t crc = crc32(0, tx_raw, raw_len); - /* Write CRC bytes individually — tx_raw+raw_len may not be word-aligned, - * and ARM32 STR to unaligned address causes data abort. */ + uint32_t c = crc32(0, tx_raw, raw_len); volatile uint8_t *p = &tx_raw[raw_len]; - p[0] = (crc >> 0) & 0xFF; - p[1] = (crc >> 8) & 0xFF; - p[2] = (crc >> 16) & 0xFF; - p[3] = (crc >> 24) & 0xFF; + p[0] = (c >> 0) & 0xFF; + p[1] = (c >> 8) & 0xFF; + p[2] = (c >> 16) & 0xFF; + p[3] = (c >> 24) & 0xFF; raw_len += 4; - /* COBS encode */ uint32_t cobs_len = cobs_encode(tx_raw, raw_len, tx_cobs); - - /* Send COBS data + 0x00 delimiter */ uart_write(tx_cobs, cobs_len); uart_putc(0x00); } uint8_t proto_recv(uint8_t *data, uint32_t *len, uint32_t timeout_ms) { - /* Read until 0x00 delimiter. - * Uses soft_rx buffer: reads from software FIFO first (leftovers - * from previous drain), then from hardware FIFO. */ + /* Read from IRQ-driven ring buffer until 0x00 delimiter */ uint32_t cobs_len = 0; uint32_t deadline = timeout_ms * 100; while (cobs_len < sizeof(rx_cobs)) { - /* Try software buffer first, then hardware */ - int b; - if (soft_rx_head != soft_rx_tail) { - b = soft_rx[soft_rx_tail]; - soft_rx_tail = (soft_rx_tail + 1) % sizeof(soft_rx); - } else { - b = uart_getc_safe(); - } - + int b = uart_getc_safe(); if (b >= 0) { if (b == 0x00) { - if (cobs_len == 0) continue; /* Skip empty frames */ + if (cobs_len == 0) continue; break; } rx_cobs[cobs_len++] = (uint8_t)b; deadline = timeout_ms * 100; - } else if (b == -2) { - cobs_len = 0; - uart_clear_errors(); } else { - if (deadline == 0) return 0; /* Timeout */ + if (deadline == 0) return 0; deadline--; for (volatile int d = 25; d > 0; d--) {} } } - /* Drain hardware FIFO into software buffer BEFORE heavy processing. - * COBS decode + CRC32 on uncached DDR takes ~1-2ms, enough for - * the 16-byte PL011 FIFO to overflow if data keeps arriving. */ - soft_rx_drain(); - /* COBS decode */ uint32_t raw_len = cobs_decode(rx_cobs, cobs_len, rx_raw); + if (raw_len < 5) return 0; - /* Drain again after decode */ - soft_rx_drain(); - - if (raw_len < 5) return 0; /* Too short: need cmd + crc32 */ - - /* Verify CRC32 — read bytes individually to avoid unaligned access */ + /* Verify CRC32 */ uint32_t payload_len = raw_len - 4; volatile uint8_t *cp = &rx_raw[payload_len]; uint32_t expected_crc = cp[0] | (cp[1] << 8) | (cp[2] << 16) | (cp[3] << 24); uint32_t actual_crc = crc32(0, rx_raw, payload_len); - - /* Drain after CRC */ - soft_rx_drain(); - - if (actual_crc != expected_crc) return 0; /* CRC error */ + if (actual_crc != expected_crc) return 0; /* Extract command and data */ uint8_t cmd = rx_raw[0]; @@ -189,5 +127,5 @@ void proto_send_ack(uint8_t status) { } void proto_drain_fifo(void) { - soft_rx_drain(); + /* No-op: IRQ handler drains the hardware FIFO automatically */ } diff --git a/agent/startup.S b/agent/startup.S index 94d0e8d..d472899 100644 --- a/agent/startup.S +++ b/agent/startup.S @@ -3,7 +3,8 @@ * * Uploaded via HiSilicon boot protocol to DDR. * DDR and clocks are already initialized by the bootrom + DDR step. - * We just need to set up the stack and jump to main(). + * Sets up MMU with identity mapping + D-cache for fast COBS+CRC + * processing, then jumps to main(). */ .section .text.start @@ -13,8 +14,6 @@ /* * ARM exception vector table. * Must be at the entry point so VBAR points here. - * All exceptions loop forever instead of resetting the SoC - * (the SPL's handlers call reset_cpu which kills us). */ _start: _vectors: @@ -24,7 +23,7 @@ _vectors: b _exc_hang /* Prefetch abort */ b _data_abort /* Data abort */ b _exc_hang /* Reserved */ - b _exc_hang /* IRQ */ + b _irq_handler /* IRQ */ b _exc_hang /* FIQ */ _exc_hang: @@ -32,42 +31,56 @@ _exc_hang: /* * Data abort handler — prints fault info over UART before hanging. - * On entry (ABT mode): LR_abt = faulting instruction + 8. - * We must NOT use the stack (SP_abt is uninitialized). - * Instead, read CP15 regs and call handler using SVC stack. */ .global _data_abort _data_abort: - sub lr, lr, #8 /* LR = faulting PC */ - - /* Read fault info into r0-r2 (caller-saved, safe to clobber) */ - mrc p15, 0, r0, c6, c0, 0 /* r0 = DFAR (fault address) */ - mrc p15, 0, r1, c5, c0, 0 /* r1 = DFSR (fault status) */ + sub lr, lr, #8 + mrc p15, 0, r0, c6, c0, 0 /* r0 = DFAR */ + mrc p15, 0, r1, c5, c0, 0 /* r1 = DFSR */ mov r2, lr /* r2 = faulting PC */ - - /* Switch to SVC mode (which has a valid stack) with IRQ/FIQ disabled */ - cps #0x13 /* SVC mode */ - + cps #0x13 /* SVC mode (valid stack) */ push {r0-r3, lr} bl data_abort_handler pop {r0-r3, lr} - b _exc_hang +/* + * IRQ handler — saves context, calls uart_irq_handler, acknowledges GIC. + */ + .global _irq_handler +_irq_handler: + sub lr, lr, #4 /* Return address */ + push {r0-r3, r12, lr} + + /* Read GIC IAR to acknowledge interrupt */ + ldr r0, =0x10302000 /* GIC_CPU_BASE */ + ldr r1, [r0, #0x0C] /* GICC_IAR */ + + /* Call UART IRQ handler */ + push {r0, r1} + bl uart_irq_handler + pop {r0, r1} + + /* Write EOIR to signal end of interrupt */ + str r1, [r0, #0x10] /* GICC_EOIR */ + + pop {r0-r3, r12, lr} + movs pc, lr /* Return from IRQ, restore CPSR */ + _start_code: - /* Set VBAR to our vector table (use ADR for PC-relative) */ + /* Set VBAR to our vector table */ adr r0, _start - mcr p15, 0, r0, c12, c0, 0 /* Write VBAR */ + mcr p15, 0, r0, c12, c0, 0 isb /* Disable interrupts */ mrs r0, cpsr - orr r0, r0, #0xC0 /* Disable IRQ and FIQ */ + orr r0, r0, #0xC0 msr cpsr_c, r0 - /* Invalidate TLBs, caches, then disable MMU */ + /* Disable MMU/caches first (clean state for setup) */ mov r0, #0 - mcr p15, 0, r0, c8, c7, 0 /* Invalidate entire TLB */ + mcr p15, 0, r0, c8, c7, 0 /* Invalidate TLB */ mcr p15, 0, r0, c7, c5, 0 /* Invalidate I-cache */ mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor */ dsb @@ -77,15 +90,15 @@ _start_code: bic r0, r0, #0x1 /* Disable MMU */ bic r0, r0, #0x4 /* Disable D-cache */ bic r0, r0, #0x1000 /* Disable I-cache */ - mcr p15, 0, r0, c1, c0, 0 /* Write SCTLR */ + mcr p15, 0, r0, c1, c0, 0 dsb isb /* Set up stack pointer (16KB below _start) */ ldr sp, =_start - sub sp, sp, #0x4000 /* 16KB stack below code */ + sub sp, sp, #0x4000 - /* Clear BSS */ + /* Clear BSS (includes page table) */ ldr r0, =__bss_start ldr r1, =__bss_end mov r2, #0 @@ -94,10 +107,111 @@ bss_loop: strlt r2, [r0], #4 blt bss_loop + /* ===== MMU PAGE TABLE SETUP ===== + * + * ARMv7 short-descriptor, 1MB sections. + * Identity mapping: virtual == physical. + * + * Section descriptor bits: + * [1:0] = 0b10 (section) + * [2] = B (bufferable) + * [3] = C (cacheable) + * [4] = XN (execute never) + * [5:8] = Domain 0 + * [11:10]= AP = 0b11 (full access) + * [14:12]= TEX + * [31:20]= Physical base (section number) + * + * DEVICE (uncached, strongly-ordered): + * AP=11, Domain=0, XN=1, B=0, C=0, TEX=000, section + * = (3<<10) | (1<<4) | (0b10) = 0x00000C12 + * + * CACHED (write-back, write-allocate): + * AP=11, Domain=0, XN=0, B=1, C=1, TEX=001, section + * = (3<<10) | (1<<12) | (1<<3) | (1<<2) | (0b10) = 0x00001C0E + */ + + /* r4 = page table base, r5 = DEVICE descriptor template, + * r6 = CACHED descriptor template, r7 = loop counter */ + ldr r4, =_page_table + + /* Fill all 4096 entries as DEVICE (uncached) */ + ldr r5, =0x00000C12 /* DEVICE descriptor */ + mov r0, r4 /* r0 = current entry pointer */ + mov r1, r5 /* r1 = descriptor for section 0 */ + mov r7, #4096 +pt_fill_device: + str r1, [r0], #4 + add r1, r1, #0x100000 /* Next 1MB section */ + subs r7, r7, #1 + bne pt_fill_device + + /* Mark DDR sections as CACHED. + * RAM_BASE is set at compile time (0x40000000 or 0x80000000). + * We cache 128MB = 128 sections starting at RAM_BASE >> 20. */ + ldr r6, =0x00001C0E /* CACHED descriptor */ + ldr r1, =RAM_BASE + lsr r2, r1, #20 /* r2 = first DDR section number */ + add r0, r4, r2, lsl #2 /* r0 = &page_table[first_section] */ + orr r1, r6, r2, lsl #20 /* r1 = descriptor for first DDR section */ + mov r7, #128 /* 128 sections = 128MB */ +pt_fill_ddr: + str r1, [r0], #4 + add r1, r1, #0x100000 + subs r7, r7, #1 + bne pt_fill_ddr + + /* ===== ENABLE MMU + CACHES ===== */ + + /* Invalidate TLB again (page table just written) */ + mov r0, #0 + mcr p15, 0, r0, c8, c7, 0 + dsb + isb + + /* Set TTBR0 = page_table | inner WB/WA | outer WB/WA */ + ldr r0, =_page_table + orr r0, r0, #(1 << 0) /* IRGN bit 0 (inner WB) */ + orr r0, r0, #(1 << 6) /* IRGN bit 6 */ + orr r0, r0, #(1 << 3) /* RGN = outer WB */ + mcr p15, 0, r0, c2, c0, 0 + isb + + /* DACR = all domains manager (no permission checks) */ + mvn r0, #0 + mcr p15, 0, r0, c3, c0, 0 + isb + + /* Enable MMU + D-cache + I-cache via SCTLR */ + mrc p15, 0, r0, c1, c0, 0 + orr r0, r0, #(1 << 0) /* M = MMU enable */ + orr r0, r0, #(1 << 2) /* C = D-cache enable */ + orr r0, r0, #(1 << 12) /* I = I-cache enable */ + mcr p15, 0, r0, c1, c0, 0 + dsb + isb + + /* Set up IRQ mode stack (needed for IRQ handler context save) */ + cps #0x12 /* IRQ mode */ + ldr sp, =_start + sub sp, sp, #0x5000 /* 4KB below SVC stack */ + cps #0x13 /* Back to SVC mode */ + + /* Enable IRQ (clear I bit in CPSR), keep FIQ disabled */ + mrs r0, cpsr + bic r0, r0, #0x80 /* Clear IRQ disable bit */ + msr cpsr_c, r0 + /* Jump to C main */ bl main - /* If main returns, loop forever */ hang: wfi b hang + +/* ===== PAGE TABLE (in BSS, 16KB aligned) ===== */ + .section .bss + .align 14 + .global _page_table +_page_table: + .space 16384 diff --git a/agent/uart.c b/agent/uart.c index c095116..6ea3cd3 100644 --- a/agent/uart.c +++ b/agent/uart.c @@ -1,26 +1,130 @@ /* - * PL011 UART driver — bare-metal, no OS. + * PL011 UART driver — bare-metal, IRQ-driven RX. * - * UART is already configured by the bootrom at 115200 8N1. - * We just ensure it's enabled and use it. + * RX bytes are moved from the PL011 hardware FIFO into a software ring + * buffer by the UART RX interrupt handler. This prevents FIFO overflow + * during COBS+CRC processing, even at high baud rates. + * + * TX remains polled (blocking putc). */ #include "uart.h" -/* Simple busy-wait delay (~1ms at typical ARM clock) */ +/* ---- GIC (Generic Interrupt Controller) ---- */ + +/* GIC base addresses for hi3516ev200/ev300 */ +#ifndef GIC_DIST_BASE +#define GIC_DIST_BASE 0x10301000 +#endif +#ifndef GIC_CPU_BASE +#define GIC_CPU_BASE 0x10302000 +#endif + +/* UART0 SPI IRQ number (from qemu-hisilicon) */ +#ifndef UART_IRQ +#define UART_IRQ 7 /* SPI 7 for ev200/ev300 */ +#endif + +#define GICD_REG(off) (*(volatile uint32_t *)(GIC_DIST_BASE + (off))) +#define GICC_REG(off) (*(volatile uint32_t *)(GIC_CPU_BASE + (off))) + +/* GIC Distributor registers */ +#define GICD_CTLR 0x000 +#define GICD_ISENABLER 0x100 /* Set-enable (32 IRQs per register) */ +#define GICD_IPRIORITYR 0x400 /* Priority (4 IRQs per register) */ +#define GICD_ITARGETSR 0x800 /* Target (4 IRQs per register) */ + +/* GIC CPU Interface registers */ +#define GICC_CTLR 0x000 +#define GICC_PMR 0x004 /* Priority mask */ +#define GICC_IAR 0x00C /* Interrupt acknowledge */ +#define GICC_EOIR 0x010 /* End of interrupt */ + +/* ---- RX ring buffer (filled by IRQ handler) ---- */ + +#define RX_BUF_SIZE 4096 /* Must be power of 2 */ +#define RX_BUF_MASK (RX_BUF_SIZE - 1) + +static volatile uint8_t rx_buf[RX_BUF_SIZE]; +static volatile uint32_t rx_head; /* Written by IRQ handler */ +static volatile uint32_t rx_tail; /* Read by main code */ + +/* ---- IRQ handler (called from startup.S) ---- */ + +void uart_irq_handler(void) { + /* Drain PL011 RX FIFO into ring buffer */ + while (!(uart_reg(UART_FR) & UART_FR_RXFE)) { + uint32_t dr = uart_reg(UART_DR); + if (dr & UART_DR_ERR) { + uart_reg(UART_RSR) = 0; + /* Skip BREAK/framing errors */ + if (dr & (UART_DR_BE | UART_DR_FE)) continue; + } + uint32_t next = (rx_head + 1) & RX_BUF_MASK; + if (next != rx_tail) { /* Don't overwrite unread data */ + rx_buf[rx_head] = (uint8_t)(dr & 0xFF); + rx_head = next; + } + } + + /* Clear RX + RT interrupts */ + uart_reg(UART_ICR) = UART_INT_RX | UART_INT_RT; +} + +/* ---- GIC + UART interrupt setup ---- */ + +static void gic_init_uart(void) { + /* GIC SPI numbers start at 32 in the hardware. + * UART_IRQ is the SPI number (e.g., 7). + * Hardware IRQ ID = 32 + UART_IRQ. */ + uint32_t irq_id = 32 + UART_IRQ; + + /* Enable GIC distributor */ + GICD_REG(GICD_CTLR) = 1; + + /* Set priority for UART IRQ (lower = higher priority, 0 = max) */ + uint32_t pri_reg = GICD_IPRIORITYR + (irq_id / 4) * 4; + uint32_t pri_shift = (irq_id % 4) * 8; + uint32_t pri_val = GICD_REG(pri_reg); + pri_val &= ~(0xFF << pri_shift); + pri_val |= (0x80 << pri_shift); /* Priority 128 (mid) */ + GICD_REG(pri_reg) = pri_val; + + /* Target CPU 0 */ + uint32_t tgt_reg = GICD_ITARGETSR + (irq_id / 4) * 4; + uint32_t tgt_shift = (irq_id % 4) * 8; + uint32_t tgt_val = GICD_REG(tgt_reg); + tgt_val &= ~(0xFF << tgt_shift); + tgt_val |= (0x01 << tgt_shift); /* CPU 0 */ + GICD_REG(tgt_reg) = tgt_val; + + /* Enable the UART IRQ in distributor */ + uint32_t en_reg = GICD_ISENABLER + (irq_id / 32) * 4; + GICD_REG(en_reg) = (1 << (irq_id % 32)); + + /* Enable GIC CPU interface, set priority mask to allow all */ + GICC_REG(GICC_PMR) = 0xFF; /* Allow all priorities */ + GICC_REG(GICC_CTLR) = 1; /* Enable CPU interface */ +} + +/* ---- Simple busy-wait delay ---- */ + static void delay_us(uint32_t us) { volatile uint32_t count = us * 10; while (count--) {} } +/* ---- Public API ---- */ + void uart_init(void) { - /* Always reconfigure UART to known-good state. - * Bootrom/SPL may have left loopback or other flags set. */ + /* Reset ring buffer */ + rx_head = 0; + rx_tail = 0; - /* Disable UART first (required before changing config) */ + /* Disable UART first */ uart_reg(UART_CR) = 0; - /* Set baud rate: divisor = clock / (16 * baud) */ + /* Set baud rate */ uint32_t divisor = UART_CLOCK / (16 * UART_BAUD); uint32_t frac = ((UART_CLOCK % (16 * UART_BAUD)) * 64 + (16 * UART_BAUD) / 2) / (16 * UART_BAUD); uart_reg(UART_IBRD) = divisor; @@ -29,47 +133,58 @@ void uart_init(void) { /* 8N1, FIFO enabled */ uart_reg(UART_LCR_H) = UART_LCR_WLEN8 | UART_LCR_FEN; - /* Enable UART, TX, RX — explicitly NO loopback (bit 7 = 0) */ + /* Set FIFO trigger level: RX interrupt at 1/8 full (2 bytes). + * This gives the IRQ handler maximum time to drain. */ + uart_reg(UART_IFLS) = 0; /* RX 1/8, TX 1/8 */ + + /* Enable UART, TX, RX */ uart_reg(UART_CR) = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE; - /* Clear any pending interrupts */ + /* Clear all pending interrupts */ uart_reg(UART_ICR) = 0x7FF; - /* Mask all interrupts (we poll) */ - uart_reg(UART_IMSC) = 0; + + /* Enable RX + RX timeout interrupts */ + uart_reg(UART_IMSC) = UART_INT_RX | UART_INT_RT; + + /* Set up GIC for UART IRQ */ + gic_init_uart(); } void uart_set_baud(uint32_t baud) { - /* Wait for TX FIFO to drain */ + /* Disable RX interrupt during reconfiguration */ + uart_reg(UART_IMSC) = 0; + while (!(uart_reg(UART_FR) & UART_FR_TXFE)) {} while (uart_reg(UART_FR) & UART_FR_BUSY) {} - /* Disable UART before changing baud */ uart_reg(UART_CR) = 0; - /* Recompute divisors: IBRD = clock / (16 * baud), FBRD = frac * 64 */ uint32_t divisor = UART_CLOCK / (16 * baud); uint32_t remainder = UART_CLOCK % (16 * baud); uint32_t frac = (remainder * 64 + (16 * baud) / 2) / (16 * baud); uart_reg(UART_IBRD) = divisor; uart_reg(UART_FBRD) = frac & 0x3F; - /* Must write LCR_H after baud rate to latch the divisors */ uart_reg(UART_LCR_H) = UART_LCR_WLEN8 | UART_LCR_FEN; - - /* Re-enable */ + uart_reg(UART_IFLS) = 0; uart_reg(UART_CR) = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE; uart_reg(UART_ICR) = 0x7FF; + + /* Re-enable RX interrupts */ + uart_reg(UART_IMSC) = UART_INT_RX | UART_INT_RT; + + /* Reset ring buffer (stale data from old baud rate) */ + rx_head = 0; + rx_tail = 0; } void uart_putc(uint8_t ch) { - /* Wait until TX FIFO has space, with timeout */ volatile uint32_t timeout = 100000; while ((uart_reg(UART_FR) & UART_FR_TXFF) && timeout > 0) { timeout--; } uart_reg(UART_DR) = ch; } int uart_putc_safe(uint8_t ch) { - /* Wait until TX FIFO has space, with timeout. Returns 0 on success, -1 on timeout. */ volatile uint32_t timeout = 100000; while ((uart_reg(UART_FR) & UART_FR_TXFF) && timeout > 0) { timeout--; } if (timeout == 0) return -1; @@ -78,26 +193,23 @@ int uart_putc_safe(uint8_t ch) { } uint8_t uart_getc(void) { - /* Wait until RX FIFO has data */ - while (uart_reg(UART_FR) & UART_FR_RXFE) {} - uint32_t dr = uart_reg(UART_DR); - if (dr & UART_DR_ERR) uart_reg(UART_RSR) = 0; /* Clear errors */ - return (uint8_t)(dr & 0xFF); + /* Wait for data in ring buffer (filled by IRQ) */ + while (rx_head == rx_tail) {} + uint8_t b = rx_buf[rx_tail]; + rx_tail = (rx_tail + 1) & RX_BUF_MASK; + return b; } int uart_getc_safe(void) { - /* Non-blocking read. Returns byte (0-255) or -1 if empty, -2 if error (BREAK/framing). */ - if (uart_reg(UART_FR) & UART_FR_RXFE) return -1; - uint32_t dr = uart_reg(UART_DR); - if (dr & UART_DR_ERR) { - uart_reg(UART_RSR) = 0; /* Clear errors */ - if (dr & (UART_DR_BE | UART_DR_FE)) return -2; /* BREAK or framing error */ - } - return (int)(dr & 0xFF); + /* Non-blocking read from ring buffer */ + if (rx_head == rx_tail) return -1; + uint8_t b = rx_buf[rx_tail]; + rx_tail = (rx_tail + 1) & RX_BUF_MASK; + return (int)b; } int uart_readable(void) { - return !(uart_reg(UART_FR) & UART_FR_RXFE); + return rx_head != rx_tail; } void uart_clear_errors(void) { @@ -105,10 +217,14 @@ void uart_clear_errors(void) { } void uart_drain_rx(void) { - while (uart_readable()) { + /* Drain hardware FIFO */ + while (!(uart_reg(UART_FR) & UART_FR_RXFE)) { (void)uart_reg(UART_DR); } uart_reg(UART_RSR) = 0; + /* Reset ring buffer */ + rx_head = 0; + rx_tail = 0; } void uart_puts(const char *s) { @@ -125,12 +241,12 @@ void uart_write(const uint8_t *buf, uint32_t len) { uint32_t uart_read(uint8_t *buf, uint32_t max_len, uint32_t timeout_ms) { uint32_t count = 0; - uint32_t deadline = timeout_ms * 100; /* Rough timer */ + uint32_t deadline = timeout_ms * 100; while (count < max_len) { if (uart_readable()) { buf[count++] = uart_getc(); - deadline = timeout_ms * 100; /* Reset timeout on data */ + deadline = timeout_ms * 100; } else { if (deadline == 0 && count > 0) break; if (deadline > 0) deadline--; diff --git a/agent/uart.h b/agent/uart.h index 8b0962a..6047222 100644 --- a/agent/uart.h +++ b/agent/uart.h @@ -18,9 +18,16 @@ #define UART_FBRD 0x28 /* Fractional baud rate divisor */ #define UART_LCR_H 0x2C /* Line control */ #define UART_CR 0x30 /* Control register */ -#define UART_IMSC 0x38 /* Interrupt mask */ +#define UART_IFLS 0x34 /* Interrupt FIFO level select */ +#define UART_IMSC 0x38 /* Interrupt mask set/clear */ +#define UART_RIS 0x3C /* Raw interrupt status */ +#define UART_MIS 0x40 /* Masked interrupt status */ #define UART_ICR 0x44 /* Interrupt clear */ +/* Interrupt bits */ +#define UART_INT_RX (1 << 4) /* RX interrupt */ +#define UART_INT_RT (1 << 6) /* RX timeout interrupt */ + /* Data register error bits (read) */ #define UART_DR_FE (1 << 8) /* Framing error */ #define UART_DR_PE (1 << 9) /* Parity error */