From 1c835a97387680b778f32eaa6eba86ee9651931e Mon Sep 17 00:00:00 2001 From: Felix Kopp Date: Tue, 5 Jan 2021 13:45:56 +0100 Subject: [PATCH] at91sam3x8e: serial refactor --- arch/at91sam3x8e/Makefile | 3 +- arch/at91sam3x8e/serial.c | 67 +++++++++++++++++++------------ include/arch/at91sam3x8e/serial.h | 21 +++++----- include/arch/serial.h | 11 +++++ include/ardix/serial.h | 1 + 5 files changed, 65 insertions(+), 38 deletions(-) diff --git a/arch/at91sam3x8e/Makefile b/arch/at91sam3x8e/Makefile index 171a58b..81c847f 100644 --- a/arch/at91sam3x8e/Makefile +++ b/arch/at91sam3x8e/Makefile @@ -38,7 +38,8 @@ ARDIX_SOURCES += \ ARDIX_ASM_SOURCES += \ $(ARDIX_ARCH_PWD)/irq_pend_sv.S \ - $(ARDIX_ARCH_PWD)/irq_svc.S + $(ARDIX_ARCH_PWD)/irq_svc.S \ + $(ARDIX_ARCH_PWD)/syscall.S CFLAGS += \ -DARCH_AT91SAM3X8E diff --git a/arch/at91sam3x8e/serial.c b/arch/at91sam3x8e/serial.c index a1f7dc9..7127c74 100644 --- a/arch/at91sam3x8e/serial.c +++ b/arch/at91sam3x8e/serial.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -16,13 +18,14 @@ #include struct arch_serial_interface arch_serial_default_interface = { + .tx_current = NULL, + .tx_next = NULL, .interface = { .tx = NULL, .rx = NULL, .id = 0, .baud = 0, }, - .hw_txrdy = false, }; struct serial_interface *serial_default_interface = &arch_serial_default_interface.interface; @@ -33,8 +36,6 @@ int arch_serial_init(struct serial_interface *interface) if (interface->baud <= 0 || interface->id != 0) return -1; - memset(&arch_iface->txbuf[0], 0, CONFIG_ARCH_SERIAL_BUFSZ); - /* enable peripheral clock for UART (which has peripheral id 8) */ REG_PMC_PCER0 |= REG_PMC_PCER0_PID(8); @@ -57,7 +58,7 @@ int arch_serial_init(struct serial_interface *interface) /* choose the events we want an interrupt on */ REG_UART_IDR = 0xFFFFFFFF; /* make sure all interrupts are disabled first */ REG_UART_IER = REG_UART_IER_RXRDY_MASK - | REG_UART_IER_TXBUFE_MASK + | REG_UART_IER_ENDTX_MASK | REG_UART_IER_OVRE_MASK | REG_UART_IER_FRAME_MASK; @@ -85,24 +86,37 @@ void arch_serial_exit(struct serial_interface *interface) interface->id = -1; } -void io_serial_buf_update(struct serial_interface *interface) +ssize_t arch_serial_write(struct serial_interface *interface, const void *buf, size_t len) { - uint16_t len; + struct arch_serial_buffer *arch_buf = NULL; struct arch_serial_interface *arch_iface = to_arch_serial_interface(interface); - if (arch_iface->hw_txrdy) { - atomic_enter(); - len = (uint16_t)ringbuf_read(&arch_iface->txbuf[0], interface->tx, - CONFIG_ARCH_SERIAL_BUFSZ); - atomic_leave(); + if (arch_iface->tx_next != NULL) + return -EBUSY; - if (len) { - arch_iface->hw_txrdy = false; - REG_UART_IER = REG_UART_IER_TXBUFE_MASK; - REG_UART_PDC_TPR = (uint32_t)&arch_iface->txbuf[0]; - REG_UART_PDC_TCR = len; - } + if (len >= (1 << 16)) /* DMA uses 16-bit counters */ + len = 0xffff; + + arch_buf = malloc(sizeof(*arch_buf) + len); + if (arch_buf == NULL) + return -ENOMEM; + + memcpy(&arch_buf->data[0], buf, len); + arch_buf->len = (uint16_t)len; + + if (arch_iface->tx_current == NULL) { + arch_iface->tx_current = arch_buf; + REG_UART_PDC_TPR = (uint32_t)&arch_buf->data[0]; + REG_UART_PDC_TCR = arch_buf->len; + /* we weren't transmitting, so the interrupt was masked */ + REG_UART_IER = REG_UART_IER_ENDTX_MASK; + } else { + arch_iface->tx_next = arch_buf; + REG_UART_PDC_TNPR = (uint32_t)&arch_buf->data[0]; + REG_UART_PDC_TNCR = arch_buf->len; } + + return (ssize_t)len; } void irq_uart(void) @@ -116,15 +130,16 @@ void irq_uart(void) ringbuf_write(arch_serial_default_interface.interface.rx, &tmp, sizeof(tmp)); } - /* TX buffer has been sent */ - if (state & REG_UART_SR_TXBUFE_MASK) { - /* - * this is picked up by the I/O thread, which will copy the next - * chunk of data from the ring buffer to the hardware buffer and - * resume transmission - */ - arch_serial_default_interface.hw_txrdy = true; - REG_UART_IDR = REG_UART_IDR_TXBUFE_MASK; + /* REG_UART_PDC_TCR has reached zero */ + if (state & REG_UART_SR_ENDTX_MASK) { + free(arch_serial_default_interface.tx_current); + + /* DMA automatically does this to the actual hardware registers */ + arch_serial_default_interface.tx_current = arch_serial_default_interface.tx_next; + arch_serial_default_interface.tx_next = NULL; + + if (arch_serial_default_interface.tx_current == NULL) + REG_UART_IDR = REG_UART_IDR_ENDTX_MASK; } /* check for error conditions */ diff --git a/include/arch/at91sam3x8e/serial.h b/include/arch/at91sam3x8e/serial.h index bc52902..cb96294 100644 --- a/include/arch/at91sam3x8e/serial.h +++ b/include/arch/at91sam3x8e/serial.h @@ -13,20 +13,19 @@ #define CONFIG_ARCH_SERIAL_BUFSZ 32 #endif /* CONFIG_ARCH_SERIAL_BUFSZ */ +struct arch_serial_buffer { + uint16_t len; + uint8_t data[]; +}; + /** Architecture-specific extension of `struct serial_interface` */ struct arch_serial_interface { - struct serial_interface interface; + /** should always match REG_UART_PDC_TPR */ + struct arch_serial_buffer *tx_current; + /** should always match REG_UART_PDC_TNPR */ + struct arch_serial_buffer *tx_next; - /* - * two hardware buffers; one is for being written to while the other one can be read from - * by the hardware w/out interfering with each other. `arch_serial_hwbuf_rotate()` is - * responsible for writing this to the respective hardware register and swapping them out. - * The platform's buffer length registers only allow 16-byte numbers, so we can save some - * memory by not using `size_t` - */ - uint8_t txbuf[CONFIG_ARCH_SERIAL_BUFSZ]; - /** hardware has finished sending the current buffer and ready for a swap */ - bool hw_txrdy; + struct serial_interface interface; }; /** diff --git a/include/arch/serial.h b/include/arch/serial.h index a95165b..f7c848a 100644 --- a/include/arch/serial.h +++ b/include/arch/serial.h @@ -10,6 +10,17 @@ int arch_serial_init(struct serial_interface *interface); void arch_serial_exit(struct serial_interface *interface); +/** + * Copy `buf` to a hardware buffer in the TX queue. + * The transmission is performed asynchronously. + * + * @param interface: serial interface to enqueue the buffer for + * @param buf: raw buffer data + * @param len: length of `buf` + * @returns actual amount of bytes enqueued, or a negative error code on failure + */ +ssize_t arch_serial_write(struct serial_interface *interface, const void *buf, size_t len); + #include ARCH_INCLUDE(serial.h) /* diff --git a/include/ardix/serial.h b/include/ardix/serial.h index 2e0a3bb..7702710 100644 --- a/include/ardix/serial.h +++ b/include/ardix/serial.h @@ -5,6 +5,7 @@ #include #include + #include #ifndef CONFIG_SERIAL_BAUD