/* * CDC-ACM <-> UART1 bridge. * * FPGA -> PC: UART1 RX is drained byte by byte by a circular DMA into a * 64 KB ring, so nothing is lost no matter how late the CPU looks at it. * The 8 kHz tick and the IN-complete interrupt move whatever is new to the * CDC IN endpoint. When the host stops reading, the oldest data is dropped. * * PC -> FPGA: OUT packets land in a queue of 512 byte buffers that a second * DMA channel feeds to the UART TX FIFO. When the queue is full the OUT * endpoint is simply not re-armed: the host gets NAKs, nothing is lost. * * All state below is touched only from interrupt context (USB, DMA, timer), * which all run at the same level and therefore never preempt each other. */ #include #include "bflb_uart.h" #include "bflb_gpio.h" #include "bflb_dma.h" #include "bflb_clock.h" #include "bflb_irq.h" #include "bl616_glb.h" #include "hardware/uart_reg.h" #include "hardware/dma_reg.h" #include "usbd_core.h" #include "usbd_cdc_acm.h" #include "usb_desc.h" #include "uart_bridge.h" #include "sys.h" #include "hw.h" #define RX_RING_SIZE (64u * 1024u) /* power of two */ #define RX_RING_MASK (RX_RING_SIZE - 1) #define RX_SEG_SIZE 2048u #define RX_SEG_NUM (RX_RING_SIZE / RX_SEG_SIZE) #define RX_GUARD (8u * 1024u) #define IN_CHUNK_MAX 2048u /* * One buffer per USB packet: usbser.sys writes in multiples of the packet * size without a terminating zero length packet, so a read longer than one * packet could wait forever for the end of a transfer. */ #define TX_BUF_NUM 32u #define TX_BUF_SIZE 512u /* 160 MHz / 8 = 20 Mbaud is the fastest setting accepted */ #define UART_DIV_MIN 8u #define TICK_HZ 8000u /* uart_bridge_tick() rate */ static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(64))) uint8_t rx_ring[RX_RING_SIZE]; static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(16))) struct bflb_dma_channel_lli_pool_s rx_lli[RX_SEG_NUM]; static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(16))) struct bflb_dma_channel_lli_pool_s tx_lli[1]; /* the USB VDMA wants aligned buffers, so IN data goes through a bounce buffer */ static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t in_buf[2][IN_CHUNK_MAX]; static uint8_t in_buf_idx; static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t tx_buf[TX_BUF_NUM][TX_BUF_SIZE]; static struct bflb_device_s *uart; static struct bflb_device_s *rx_dma; static struct bflb_device_s *tx_dma; static uintptr_t uart_base; static uint32_t uart_clk; static volatile uint32_t *rx_dma_dst; /* FPGA -> PC */ static uint32_t rx_rd; /* ring offset of the oldest byte not yet sent */ static uint32_t rx_last_wr; /* DMA write offset seen by the last update */ static uint32_t rx_pending; /* bytes between rx_rd and rx_last_wr */ static bool in_busy; static uint16_t in_mps; /* CDC IN/OUT max packet size of the current connection */ static uint16_t out_mps; /* PC -> FPGA: buffers [tx_rd, tx_wr) are full, tx_wr is the one being filled */ static uint16_t tx_len[TX_BUF_NUM]; static uint32_t tx_wr, tx_rd; static bool tx_dma_busy; static bool out_armed; static bool configured; static bool dtr; static struct cdc_line_coding line_coding = { 115200, 0, 0, 8 }; static struct uart_stats stats; static uint32_t rx_wd_limit; static uint32_t rx_wd_ticks; static uint32_t rx_wd_bytes; static void tx_dma_done(void *arg); static void dma_dummy_isr(void *arg) { (void)arg; } /* ---------------------------------------------------------------- UART */ /* * (Re)configure UART1 from scratch. The BL616 receiver can lock up for good * (URX busy, no more bytes) when its bit period changes in the middle of an * incoming frame; clearing/setting the enable bits, as the SDK does, does not * get it out. A peripheral reset does, so every real change of the line * parameters goes through one. The DMA channels are left running: they just * wait for the next request. */ static void uart_apply_line_coding(const struct cdc_line_coding *lc) { uint32_t baud = lc->dwDTERate ? lc->dwDTERate : 115200; uint32_t div = (uart_clk + baud / 2) / baud; uint32_t bits = lc->bDataBits; uint32_t tx, rx, fifo; if (div < UART_DIV_MIN) { div = UART_DIV_MIN; } if (div > 0x10000) { div = 0x10000; } if (bits < 5 || bits > 8) { bits = 8; } GLB_AHB_MCU_Software_Reset(GLB_AHB_MCU_SW_UART1); putreg32(((div - 1) << 16) | (div - 1), uart_base + UART_BIT_PRD_OFFSET); putreg32(0, uart_base + UART_DATA_CONFIG_OFFSET); putreg32(0xFFFFFFFF, uart_base + UART_INT_MASK_OFFSET); /* DMA requests as soon as there is one byte / one free slot */ fifo = getreg32(uart_base + UART_FIFO_CONFIG_1_OFFSET); fifo &= ~(UART_TX_FIFO_TH_MASK | UART_RX_FIFO_TH_MASK); putreg32(fifo, uart_base + UART_FIFO_CONFIG_1_OFFSET); tx = getreg32(uart_base + UART_UTX_CONFIG_OFFSET); rx = getreg32(uart_base + UART_URX_CONFIG_OFFSET); tx |= UART_CR_UTX_FRM_EN; tx &= ~(UART_CR_UTX_BIT_CNT_D_MASK | UART_CR_UTX_BIT_CNT_P_MASK | UART_CR_UTX_PRT_EN | UART_CR_UTX_PRT_SEL); rx &= ~(UART_CR_URX_BIT_CNT_D_MASK | UART_CR_URX_PRT_EN | UART_CR_URX_PRT_SEL); tx |= (bits - 1) << UART_CR_UTX_BIT_CNT_D_SHIFT; rx |= (bits - 1) << UART_CR_URX_BIT_CNT_D_SHIFT; /* stop bits field counts half bits minus one: 1 -> 1, 1.5 -> 2, 2 -> 3 */ switch (lc->bCharFormat) { case 1: tx |= UART_STOP_BITS_1_5 << UART_CR_UTX_BIT_CNT_P_SHIFT; break; case 2: tx |= UART_STOP_BITS_2 << UART_CR_UTX_BIT_CNT_P_SHIFT; break; default: tx |= UART_STOP_BITS_1 << UART_CR_UTX_BIT_CNT_P_SHIFT; break; } /* mark/space parity is not supported by the hardware: sent as none */ if (lc->bParityType == 1) { tx |= UART_CR_UTX_PRT_EN | UART_CR_UTX_PRT_SEL; rx |= UART_CR_URX_PRT_EN | UART_CR_URX_PRT_SEL; } else if (lc->bParityType == 2) { tx |= UART_CR_UTX_PRT_EN; rx |= UART_CR_URX_PRT_EN; } putreg32(UART_DMA_TX_EN | UART_DMA_RX_EN, uart_base + UART_FIFO_CONFIG_0_OFFSET); putreg32(tx | UART_CR_UTX_EN, uart_base + UART_UTX_CONFIG_OFFSET); putreg32(rx | UART_CR_URX_EN, uart_base + UART_URX_CONFIG_OFFSET); stats.baud_requested = baud; stats.baud_actual = uart_clk / div; /* receiver watchdog: 100 ms plus 30 frame times */ rx_wd_limit = TICK_HZ / 10 + (TICK_HZ * 300u) / baud; rx_wd_ticks = 0; } /* receiver busy, yet no byte for a long time: it is stuck, reset it */ static void rx_watchdog(void) { if ((getreg32(uart_base + UART_STATUS_OFFSET) & UART_STS_URX_BUS_BUSY) && stats.rx_bytes == rx_wd_bytes) { if (++rx_wd_ticks > rx_wd_limit) { uart_apply_line_coding(&line_coding); stats.rx_resets++; } } else { rx_wd_bytes = stats.rx_bytes; rx_wd_ticks = 0; } } void uart_bridge_init(void) { struct bflb_device_s *gpio = bflb_device_get_by_name("gpio"); struct bflb_uart_config_s cfg = { .baudrate = 115200, .direction = UART_DIRECTION_TXRX, .data_bits = UART_DATA_BITS_8, .stop_bits = UART_STOP_BITS_1, .parity = UART_PARITY_NONE, .bit_order = UART_LSB_FIRST, .flow_ctrl = 0, /* DMA requests as soon as there is one byte / one free slot */ .tx_fifo_threshold = 0, .rx_fifo_threshold = 0, }; bflb_gpio_uart_init(gpio, PIN_UART_TX, GPIO_UART_FUNC_UART1_TX); bflb_gpio_uart_init(gpio, PIN_UART_RX, GPIO_UART_FUNC_UART1_RX); uart = bflb_device_get_by_name("uart1"); bflb_uart_init(uart, &cfg); uart_base = uart->reg_base; uart_clk = bflb_clk_get_peripheral_clock(BFLB_DEVICE_TYPE_UART, 1); uart_apply_line_coding(&line_coding); /* RX: endless DMA into the ring */ struct bflb_dma_channel_config_s rx_cfg = { .direction = DMA_PERIPH_TO_MEMORY, .src_req = DMA_REQUEST_UART1_RX, .dst_req = DMA_REQUEST_NONE, .src_addr_inc = DMA_ADDR_INCREMENT_DISABLE, .dst_addr_inc = DMA_ADDR_INCREMENT_ENABLE, .src_burst_count = DMA_BURST_INCR1, .dst_burst_count = DMA_BURST_INCR1, .src_width = DMA_DATA_WIDTH_8BIT, .dst_width = DMA_DATA_WIDTH_8BIT, }; struct bflb_dma_channel_lli_transfer_s seg[RX_SEG_NUM]; rx_dma = bflb_device_get_by_name("dma0_ch0"); bflb_dma_channel_init(rx_dma, &rx_cfg); /* the shared DMA ISR calls every channel's handler without a NULL check */ bflb_dma_channel_irq_attach(rx_dma, dma_dummy_isr, NULL); bflb_dma_channel_tcint_mask(rx_dma, true); for (uint32_t i = 0; i < RX_SEG_NUM; i++) { seg[i].src_addr = DMA_ADDR_UART1_RDR; seg[i].dst_addr = (uint32_t)(uintptr_t)&rx_ring[i * RX_SEG_SIZE]; seg[i].nbytes = RX_SEG_SIZE; } int used = bflb_dma_channel_lli_reload(rx_dma, rx_lli, RX_SEG_NUM, seg, RX_SEG_NUM); bflb_dma_channel_lli_link_head(rx_dma, rx_lli, used); rx_dma_dst = (volatile uint32_t *)(rx_dma->reg_base + DMA_CxDSTADDR_OFFSET); rx_rd = rx_last_wr = 0; /* TX: one DMA transfer per filled buffer */ struct bflb_dma_channel_config_s tx_cfg = { .direction = DMA_MEMORY_TO_PERIPH, .src_req = DMA_REQUEST_NONE, .dst_req = DMA_REQUEST_UART1_TX, .src_addr_inc = DMA_ADDR_INCREMENT_ENABLE, .dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE, .src_burst_count = DMA_BURST_INCR1, .dst_burst_count = DMA_BURST_INCR1, .src_width = DMA_DATA_WIDTH_8BIT, .dst_width = DMA_DATA_WIDTH_8BIT, }; tx_dma = bflb_device_get_by_name("dma0_ch1"); bflb_dma_channel_init(tx_dma, &tx_cfg); bflb_dma_channel_irq_attach(tx_dma, tx_dma_done, NULL); bflb_uart_link_rxdma(uart, true); bflb_uart_link_txdma(uart, true); bflb_dma_channel_start(rx_dma); } /* -------------------------------------------------------- FPGA -> PC */ static inline uint32_t rx_wr_offset(void) { uint32_t off = *rx_dma_dst - (uint32_t)(uintptr_t)rx_ring; /* right at the end of the last segment the address points past the ring */ return (off >= RX_RING_SIZE) ? 0 : off; } static void rx_update(void) { uint32_t wr = rx_wr_offset(); uint32_t delta = (wr - rx_last_wr) & RX_RING_MASK; if (delta == 0) { return; } rx_last_wr = wr; rx_pending += delta; stats.rx_bytes += delta; if (rx_pending > RX_RING_SIZE - RX_GUARD) { /* host is not reading: keep the newest half */ uint32_t drop = rx_pending - RX_RING_SIZE / 2; rx_rd = (rx_rd + drop) & RX_RING_MASK; rx_pending -= drop; stats.rx_dropped += drop; } } static void rx_submit(void) { if (in_busy || !configured || rx_pending == 0) { return; } uint32_t n = (rx_pending < IN_CHUNK_MAX) ? rx_pending : IN_CHUNK_MAX; /* * Never end a transfer on a packet boundary: the host only hands data to * the application after a short packet. The usual cure, a zero length * packet, is not reliable on BL616 (requested while the last data packet * still sits in the FIFO it gets lost), so keep one byte for the next * transfer instead. */ if (in_mps && (n % in_mps) == 0) { n--; } uint32_t first = RX_RING_SIZE - rx_rd; if (first > n) { first = n; } /* alternate buffers: never touch the one the last transfer came from */ uint8_t *buf = in_buf[in_buf_idx]; in_buf_idx ^= 1; memcpy(buf, &rx_ring[rx_rd], first); if (n > first) { memcpy(buf + first, &rx_ring[0], n - first); } rx_rd = (rx_rd + n) & RX_RING_MASK; rx_pending -= n; in_busy = true; usbd_ep_start_write(0, CDC_IN_EP, buf, n); sys_activity(); } static void cdc_in_cb(uint8_t busid, uint8_t ep, uint32_t nbytes) { (void)busid; (void)ep; (void)nbytes; in_busy = false; rx_update(); rx_submit(); } void uart_bridge_tick(void) { rx_update(); rx_watchdog(); rx_submit(); } /* -------------------------------------------------------- PC -> FPGA */ static void out_arm(void) { if (out_armed || !configured) { return; } if ((tx_wr - tx_rd) < TX_BUF_NUM) { out_armed = true; usbd_ep_start_read(0, CDC_OUT_EP, tx_buf[tx_wr % TX_BUF_NUM], out_mps); } } static void tx_kick(void) { if (tx_dma_busy || tx_rd == tx_wr) { return; } uint32_t i = tx_rd % TX_BUF_NUM; struct bflb_dma_channel_lli_transfer_s tr = { .src_addr = (uint32_t)(uintptr_t)tx_buf[i], .dst_addr = DMA_ADDR_UART1_TDR, .nbytes = tx_len[i], }; tx_dma_busy = true; bflb_dma_channel_lli_reload(tx_dma, tx_lli, 1, &tr, 1); bflb_dma_channel_start(tx_dma); } static void tx_dma_done(void *arg) { (void)arg; tx_dma_busy = false; tx_rd++; tx_kick(); out_arm(); } static void cdc_out_cb(uint8_t busid, uint8_t ep, uint32_t nbytes) { (void)busid; (void)ep; if (nbytes == 0) { /* zero length packet: the BL616 driver keeps the read armed */ return; } out_armed = false; tx_len[tx_wr % TX_BUF_NUM] = (uint16_t)nbytes; tx_wr++; stats.tx_bytes += nbytes; tx_kick(); sys_activity(); out_arm(); } /* ------------------------------------------------------- CDC control */ void usbd_cdc_acm_set_line_coding(uint8_t busid, uint8_t intf, struct cdc_line_coding *lc) { (void)busid; (void)intf; if (lc->dwDTERate == UART_MAGIC_ISP_BAUD) { /* "1200 bps touch" style: firmware update without opening the case */ sys_request_isp_reboot(); return; } /* terminals send the same settings again and again: only act on changes */ if (memcmp(lc, &line_coding, sizeof(line_coding)) == 0) { return; } line_coding = *lc; uart_apply_line_coding(&line_coding); } void usbd_cdc_acm_get_line_coding(uint8_t busid, uint8_t intf, struct cdc_line_coding *lc) { (void)busid; (void)intf; *lc = line_coding; } void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool state) { (void)busid; (void)intf; if (state && !dtr) { /* port just opened: drop what piled up while nobody listened */ rx_update(); stats.rx_dropped += rx_pending; rx_rd = rx_last_wr; rx_pending = 0; } dtr = state; } void usbd_cdc_acm_set_rts(uint8_t busid, uint8_t intf, bool state) { (void)busid; (void)intf; (void)state; } /* ---------------------------------------------------------------- USB */ static struct usbd_interface cdc_intf_comm; static struct usbd_interface cdc_intf_data; static struct usbd_endpoint cdc_in_ep = { .ep_addr = CDC_IN_EP, .ep_cb = cdc_in_cb, }; static struct usbd_endpoint cdc_out_ep = { .ep_addr = CDC_OUT_EP, .ep_cb = cdc_out_cb, }; void uart_bridge_usb_init(uint8_t busid) { usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &cdc_intf_comm)); usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &cdc_intf_data)); usbd_add_endpoint(busid, &cdc_in_ep); usbd_add_endpoint(busid, &cdc_out_ep); } void uart_bridge_on_reset(void) { configured = false; in_busy = false; out_armed = false; dtr = false; } void uart_bridge_on_configured(void) { configured = true; in_busy = false; in_mps = usbd_get_ep_mps(0, CDC_IN_EP); out_mps = usbd_get_ep_mps(0, CDC_OUT_EP); out_armed = false; out_arm(); rx_submit(); } void uart_bridge_get_stats(struct uart_stats *st) { uintptr_t flag = bflb_irq_save(); *st = stats; bflb_irq_restore(flag); }