From 7423933dc79443ac48538ce6d134c3d742ceee90 Mon Sep 17 00:00:00 2001 From: Mieszko Mierunski Date: Wed, 24 Oct 2018 14:09:31 +0200 Subject: [PATCH 1/3] api: uart: Add new asynchronous UART API. Added new UART API, that allows for longer transmissions, leaves IRQ handling on driver side and allows for DMA usage. Signed-off-by: Mieszko Mierunski --- drivers/serial/Kconfig | 7 + include/uart.h | 384 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 360 insertions(+), 31 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 1d1136a5e400a..704adbf439e37 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -28,12 +28,19 @@ config SERIAL_SUPPORT_INTERRUPT This is an option to be enabled by individual serial driver to signal that the driver and hardware supports interrupts. +config UART_ASYNC_API + bool "Enable new asynchronous UART API [EXPERIMENTAL]" + help + This option enables new asynchronous UART API. + +if UART_ASYNC_API=n config UART_INTERRUPT_DRIVEN bool "Enable UART Interrupt support" depends on SERIAL_SUPPORT_INTERRUPT help This option enables interrupt support for UART allowing console input and other UART based drivers. +endif config UART_LINE_CTRL bool "Enable Serial Line Control API" diff --git a/include/uart.h b/include/uart.h index 806ce019cc245..b5aa802a12713 100644 --- a/include/uart.h +++ b/include/uart.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2018-2019 Nordic Semiconductor ASA * Copyright (c) 2015 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 @@ -29,41 +29,212 @@ extern "C" { #include -#ifdef CONFIG_PCI -#include -#include -#endif +/** @brief Line control signals. */ +enum uart_line_ctrl { + UART_LINE_CTRL_RTS = (1 << 1), + UART_LINE_CTRL_DTR = (1 << 2), + UART_LINE_CTRL_DCD = (1 << 3), + UART_LINE_CTRL_DSR = (1 << 4), +}; + /** - * @brief Options for @a UART initialization. + * @brief Types of events passed to callback in UART_ASYNC_API + * + * Receiving: + * 1. To start receiving, uart_rx_enable has to be called with first buffer + * 2. When receiving starts to current buffer, UART_RX_BUF_REQUEST will be + * generated, in response to that user can either: + * + * - Provide second buffer using uart_rx_buf_rsp, when first buffer is + * filled, receiving will automatically start to second buffer. + * - Ignore the event, this way when current buffer is filled UART_RX_DONE + * event will be generated and receiving will be stopped. + * + * 3. If some data was received and timeout occurred UART_RX_RDY event will be + * generated. It can happen multiples times for the same buffer. RX timeout + * is counted from last byte received i.e. if no data was received, there + * won't be any timeout event. + * 4. After buffer is filled UART_RX_RDY will be generated, immediately + * followed by UART_RX_BUF_RELEASED indicating that current buffer is no + * longer used. + * 5. If there was second buffer provided, it will become current buffer and + * we start again at point 2. + * If no second buffer was specified receiving is stopped and + * UART_RX_DISABLED event is generated. After that whole process can be + * repeated. + * + * Any time during reception UART_RX_STOPPED event can occur. It will be + * followed by UART_RX_BUF_RELEASED event for every buffer currently passed to + * driver and finally by UART_RX_DISABLED event. + * + * Receiving can be disabled using uart_rx_disable, after calling that + * function any data received will be lost, UART_RX_BUF_RELEASED event will be + * generated for every buffer currently passed to driver and UART_RX_DISABLED + * event will occur. + * + * Transmitting: + * 1. Transmitting starts by uart_tx function. + * 2. If whole buffer was transmitted UART_TX_DONE is generated. + * If timeout occurred UART_TX_ABORTED will be generated. + * + * Transmitting can be aborted using uart_tx_abort, after calling that + * function UART_TX_ABORTED event will be generated. + * */ -#define UART_OPTION_AFCE 0x01 +enum uart_event_type { + /** @brief Whole TX buffer was transmitted. */ + UART_TX_DONE, + /** + * @brief Transmitting aborted due to timeout or uart_tx_abort call + * + * When flow control is enabled, there is a possibility that TX transfer + * won't finish in the allotted time. Some data may have been + * transferred, information about it can be found in event data. + */ + UART_TX_ABORTED, + /** + * @brief Received data is ready for processing. + * + * This event is generated in two cases: + * - When RX timeout occurred, and data was stored in provided buffer. + * This can happen multiple times in the same buffer. + * - When provided buffer is full. + */ + UART_RX_RDY, + /** + * @brief Driver requests next buffer for continuous reception. + * + * This event is triggered when receiving has started for a new buffer, + * i.e. it's time to provide a next buffer for a seamless switchover to + * it. For continuous reliable receiving, user should provide another RX + * buffer in response to this event, using uart_rx_buf_rsp function + * + * If uart_rx_buf_rsp is not called before current buffer + * is filled up, receiving will stop. + */ + UART_RX_BUF_REQUEST, + /** + * @brief Buffer is no longer used by UART driver. + */ + UART_RX_BUF_RELEASED, + /** + * @brief RX has been disabled and can be reenabled. + * + * This event is generated whenever receiver has been stopped, disabled + * or finished its operation and can be enabled again using + * uart_rx_enable + */ + UART_RX_DISABLED, + /** + * @brief RX has stopped due to external event. + * + * Reason is one of uart_rx_stop_reason. + */ + UART_RX_STOPPED, +}; -/** Common line controls for UART.*/ -#define LINE_CTRL_BAUD_RATE (1 << 0) -#define LINE_CTRL_RTS (1 << 1) -#define LINE_CTRL_DTR (1 << 2) -#define LINE_CTRL_DCD (1 << 3) -#define LINE_CTRL_DSR (1 << 4) -/* Common communication errors for UART.*/ +/** + * @brief Reception stop reasons. + * + * Values that correspond to events or errors responsible for stopping + * receiving. + * + * We start counting from 1, to not use 0 which is treated as no error. + */ +enum uart_rx_stop_reason { + /** + * @brief Break interrupt + * + * A break interrupt was received. This happens when the serial input + * is held at a logic '0' state for longer than the sum of + * start time + data bits + parity + stop bits. + */ + UART_BREAK = 1, + /** @brief Overrun error */ + UART_ERROR_OVERRUN, + /** @brief Parity error */ + UART_ERROR_PARITY, + /** @brief Framing error */ + UART_ERROR_FRAMING, +}; -/** @brief Overrun error */ -#define UART_ERROR_OVERRUN (1 << 0) +/** @brief Backward compatibility defines, deprecated */ +#define UART_ERROR_BREAK UART_BREAK +#define LINE_CTRL_BAUD_RATE (1 << 0) +#define LINE_CTRL_RTS UART_LINE_CTRL_RTS +#define LINE_CTRL_DTR UART_LINE_CTRL_DTR +#define LINE_CTRL_DCD UART_LINE_CTRL_DCD +#define LINE_CTRL_DSR UART_LINE_CTRL_DSR + + +/** @brief UART TX event data. */ +struct uart_event_tx { + /** @brief Pointer to current buffer. */ + const u8_t *buf; + /** @brief Number of bytes sent. */ + size_t len; +}; + +/** @brief UART RX event data. */ +struct uart_event_rx { + /** @brief Pointer to current buffer. */ + u8_t *buf; + /** @brief Offset from buffer start to currently received data. */ + size_t offset; + /** @brief Number of bytes received. */ + size_t len; +}; -/** @brief Parity error */ -#define UART_ERROR_PARITY (1 << 1) +/** @brief UART RX buffer released event data. */ +struct uart_event_rx_buf { + /* @brief Pointer to buffer that is no longer in use. */ + u8_t *buf; +}; -/** @brief Framing error */ -#define UART_ERROR_FRAMING (1 << 2) +/** @brief UART RX stopped data. */ +struct uart_event_rx_stop { + /** @brief Reason why receiving stopped */ + enum uart_rx_stop_reason reason; + /** @brief Last received data. */ + struct uart_event_rx data; +}; + +/** @brief Structure containing information about current event. */ +struct uart_event { + /** @brief Type of event */ + enum uart_event_type type; + /** @brief Event data */ + union { + /** @brief UART_TX_DONE and UART_TX_ABORTED events data. */ + struct uart_event_tx tx; + /** @brief UART_RX_RDY event data. */ + struct uart_event_rx rx; + /** @brief UART_RX_BUF_RELEASED event data. */ + struct uart_event_rx_buf rx_buf; + /** @brief UART_RX_STOPPED event data. */ + struct uart_event_rx_stop rx_stop; + } data; +}; /** - * @brief Break interrupt error: + * @typedef uart_callback_t + * @brief Define the application callback function signature for + * uart_set_callback() function. * - * A break interrupt was received. This happens when the serial input is - * held at a logic '0' state for longer than the sum of start time + data bits - * + parity + stop bits. + * @param evt Pointer to uart_event structure. + * @param user_data Pointer to data specified by user. */ -#define UART_ERROR_BREAK (1 << 3) +typedef void (*uart_callback_t)(struct uart_event *evt, void *user_data); + +#ifdef CONFIG_PCI +#include +#include +#endif +/** + * @brief Options for @a UART initialization. + */ +#define UART_OPTION_AFCE 0x01 /** * @brief UART controller configuration structure @@ -120,7 +291,6 @@ enum uart_config_flow_control { UART_CFG_FLOW_CTRL_DTR_DSR, }; - /** * @typedef uart_irq_callback_user_data_t * @brief Define the application callback function signature for @@ -175,6 +345,23 @@ struct uart_device_config { /** @brief Driver API structure. */ struct uart_driver_api { + +#ifdef CONFIG_UART_ASYNC_API + + int (*callback_set)(struct device *dev, uart_callback_t callback, + void *user_data); + + int (*tx)(struct device *dev, const u8_t *buf, size_t len, + u32_t timeout); + int (*tx_abort)(struct device *dev); + + int (*rx_enable)(struct device *dev, u8_t *buf, size_t len, + u32_t timeout); + int (*rx_buf_rsp)(struct device *dev, u8_t *buf, size_t len); + int (*rx_disable)(struct device *dev); + +#endif + /** Console I/O function */ int (*poll_in)(struct device *dev, unsigned char *p_char); void (*poll_out)(struct device *dev, unsigned char out_char); @@ -245,16 +432,150 @@ struct uart_driver_api { }; +#ifdef CONFIG_UART_ASYNC_API + +/** + * @brief Set event handler function. + * + * @param dev UART device structure. + * @param callback Event handler. + * @param user_data Data to pass to event handler function. + * + * @retval 0 If successful, negative errno code otherwise. + */ +static inline int uart_callback_set(struct device *dev, + uart_callback_t callback, + void *user_data) +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->callback_set(dev, callback, user_data); +} + +/** + * @brief Send given number of bytes from buffer through UART. + * + * Function returns immediately and event handler, + * set using @ref uart_set_callback, is called after transfer is finished. + * + * @param dev UART device structure. + * @param buf Pointer to transmit buffer. + * @param len Length of transmit buffer. + * @param timeout Timeout in milliseconds. Valid only if flow control is enabled + * + * @retval -EBUSY There is already an ongoing transfer. + * @retval 0 If successful, negative errno code otherwise. + */ +static inline int uart_tx(struct device *dev, + const u8_t *buf, + size_t len, + u32_t timeout) + +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->tx(dev, buf, len, timeout); +} + +/** + * @brief Abort current TX transmission. + * + * UART_TX_DONE event will be generated with amount of data sent. + * + * @param dev UART device structure. + * + * @retval -EFAULT There is no active transmission. + * @retval 0 If successful, negative errno code otherwise. + */ +static inline int uart_tx_abort(struct device *dev) +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->tx_abort(dev); +} + +/** + * @brief Start receiving data through UART. + * + * Function sets given buffer as first buffer for receiving and returns + * immediately. After that event handler, set using @ref uart_set_callback, + * is called with UART_RX_RDY or UART_RX_BUF_REQUEST events. + * + * @param dev UART device structure. + * @param buf Pointer to receive buffer. + * @param len Buffer length. + * @param timeout Timeout in milliseconds. + * + * @retval -EBUSY RX already in progress. + * @retval 0 If successful, negative errno code otherwise. + * + */ +static inline int uart_rx_enable(struct device *dev, u8_t *buf, size_t len, + u32_t timeout) +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->rx_enable(dev, buf, len, timeout); +} + +/** + * @brief Provide receive buffer in response to UART_RX_BUF_REQUEST event. + * + * Provide pointer to RX buffer, which will be used when current buffer is + * filled. + * + * @note Providing buffer that is already in usage by driver leads to + * undefined behavior. Buffer can be reused when it has been released + * by driver. + * + * @param dev UART device structure. + * @param buf Pointer to receive buffer. + * @param len Buffer length. + * + * @retval -EBUSY Next buffer already set. + * @retval 0 If successful, negative errno code otherwise. + * + */ +static inline int uart_rx_buf_rsp(struct device *dev, u8_t *buf, size_t len) +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->rx_buf_rsp(dev, buf, len); +} + +/** + * @brief Disable RX + * + * UART_RX_BUF_RELEASED event will be generated for every buffer scheduled, + * after that UART_RX_DISABLED event will be generated. + * + * @param dev UART device structure. + * + * @retval -EFAULT There is no active reception. + * @retval 0 If successful, negative errno code otherwise. + */ +static inline int uart_rx_disable(struct device *dev) +{ + const struct uart_driver_api *api = + (const struct uart_driver_api *)dev->driver_api; + + return api->rx_disable(dev); +} + +#endif + /** * @brief Check whether an error was detected. * * @param dev UART device structure. * - * @retval UART_ERROR_OVERRUN if an overrun error was detected. - * @retval UART_ERROR_PARITY if a parity error was detected. - * @retval UART_ERROR_FRAMING if a framing error was detected. - * @retval UART_ERROR_BREAK if a break error was detected. - * @retval 0 Otherwise. + * @retval uart_rx_stop_reason If error during receiving occurred. + * @retval 0 Otherwise. */ __syscall int uart_err_check(struct device *dev); @@ -280,6 +601,7 @@ static inline int _impl_uart_err_check(struct device *dev) * @retval -1 If no character was available to read (i.e., the UART * input buffer was empty). * @retval -ENOTSUP If the operation is not supported. + * @retval -EBUSY If reception was enabled using uart_rx_enabled */ __syscall int uart_poll_in(struct device *dev, unsigned char *p_char); From 7566181ef518dc61fc36d980b2080709f5be6eab Mon Sep 17 00:00:00 2001 From: Mieszko Mierunski Date: Fri, 14 Dec 2018 16:13:14 +0100 Subject: [PATCH 2/3] drivers: nrf: Rework UART shim to support async UART API. Add support for async UART API. Signed-off-by: Mieszko Mierunski --- drivers/serial/uart_nrfx_uart.c | 388 +++++++++++++++++++++++++++++++- 1 file changed, 387 insertions(+), 1 deletion(-) diff --git a/drivers/serial/uart_nrfx_uart.c b/drivers/serial/uart_nrfx_uart.c index fb8dbbdfd86e6..ea4b823f83faa 100644 --- a/drivers/serial/uart_nrfx_uart.c +++ b/drivers/serial/uart_nrfx_uart.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 Nordic Semiconductor ASA + * Copyright (c) 2016-2019 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -38,6 +38,33 @@ static inline const struct uart_nrfx_config *get_dev_config(struct device *dev) return dev->config->config_info; } +#ifdef CONFIG_UART_ASYNC_API +static struct { + uart_callback_t callback; + void *user_data; + + u8_t *rx_buffer; + u8_t *rx_secondary_buffer; + size_t rx_buffer_length; + size_t rx_secondary_buffer_length; + volatile size_t rx_counter; + volatile size_t rx_offset; + size_t rx_timeout; + struct k_delayed_work rx_timeout_work; + bool rx_enabled; + + bool tx_abort; + const u8_t *tx_buffer; + size_t tx_buffer_length; + volatile size_t tx_counter; +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + size_t tx_timeout; + struct k_delayed_work tx_timeout_work; +#endif +} uart0_cb; +#endif /* CONFIG_UART_ASYNC_API */ + #ifdef CONFIG_UART_0_INTERRUPT_DRIVEN static uart_irq_callback_user_data_t irq_callback; /**< Callback function pointer */ @@ -291,6 +318,347 @@ static int uart_nrfx_config_get(struct device *dev, struct uart_config *cfg) return 0; } + +#ifdef CONFIG_UART_ASYNC_API + +static void user_callback(struct uart_event *event) +{ + if (uart0_cb.callback) { + uart0_cb.callback(event, uart0_cb.user_data); + } +} + +static int uart_nrfx_callback_set(struct device *dev, uart_callback_t callback, + void *user_data) +{ + uart0_cb.callback = callback; + uart0_cb.user_data = user_data; + + return 0; +} + +static int uart_nrfx_tx(struct device *dev, const u8_t *buf, size_t len, + u32_t timeout) +{ + if (uart0_cb.tx_buffer_length != 0) { + return -EBUSY; + } + + uart0_cb.tx_buffer = buf; + uart0_cb.tx_buffer_length = len; +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + uart0_cb.tx_timeout = timeout; +#endif + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_TXDRDY); + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STARTTX); + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_TXDRDY); + nrf_uart_int_enable(uart0_addr, NRF_UART_INT_MASK_TXDRDY); + + u8_t txd = uart0_cb.tx_buffer[uart0_cb.tx_counter]; + + nrf_uart_txd_set(uart0_addr, txd); + + return 0; +} + +static int uart_nrfx_tx_abort(struct device *dev) +{ + if (uart0_cb.tx_buffer_length == 0) { + return -EINVAL; + } +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + k_delayed_work_cancel(&uart0_cb.tx_timeout_work); +#endif + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STOPTX); + + struct uart_event evt = { + .type = UART_TX_ABORTED, + .data.tx.buf = uart0_cb.tx_buffer, + .data.tx.len = uart0_cb.tx_counter + }; + + uart0_cb.tx_buffer_length = 0; + uart0_cb.tx_counter = 0; + + user_callback(&evt); + + return 0; +} + +static int uart_nrfx_rx_enable(struct device *dev, u8_t *buf, size_t len, + u32_t timeout) +{ + if (uart0_cb.rx_buffer_length != 0) { + return -EBUSY; + } + uart0_cb.rx_enabled = 1; + uart0_cb.rx_buffer = buf; + uart0_cb.rx_buffer_length = len; + uart0_cb.rx_counter = 0; + uart0_cb.rx_secondary_buffer_length = 0; + uart0_cb.rx_timeout = timeout; + + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_ERROR); + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_RXDRDY); + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_RXTO); + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STARTRX); + nrf_uart_int_enable(uart0_addr, NRF_UART_INT_MASK_RXDRDY | + NRF_UART_INT_MASK_ERROR | + NRF_UART_INT_MASK_RXTO); + + return 0; +} + +static int uart_nrfx_rx_buf_rsp(struct device *dev, u8_t *buf, size_t len) +{ + if (!uart0_cb.rx_enabled) { + return -EACCES; + } + if (uart0_cb.rx_secondary_buffer_length != 0) { + return -EBUSY; + } + uart0_cb.rx_secondary_buffer = buf; + uart0_cb.rx_secondary_buffer_length = len; + + return 0; +} + +static int uart_nrfx_rx_disable(struct device *dev) +{ + if (uart0_cb.rx_buffer_length == 0) { + return -EFAULT; + } + + uart0_cb.rx_enabled = 0; + k_delayed_work_cancel(&uart0_cb.rx_timeout_work); + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STOPRX); + + return 0; +} + +static void rx_rdy_evt(void) +{ + struct uart_event event; + size_t rx_cnt = uart0_cb.rx_counter; + + event.type = UART_RX_RDY; + event.data.rx.buf = uart0_cb.rx_buffer; + event.data.rx.len = rx_cnt - uart0_cb.rx_offset; + event.data.rx.offset = uart0_cb.rx_offset; + + uart0_cb.rx_offset = rx_cnt; + + user_callback(&event); +} + +static void buf_released_evt(void) +{ + struct uart_event event = { + .type = UART_RX_BUF_RELEASED, + .data.rx_buf.buf = uart0_cb.rx_buffer + }; + user_callback(&event); +} + +static void rx_disabled_evt(void) +{ + struct uart_event event = { + .type = UART_RX_DISABLED + }; + user_callback(&event); +} + +static void rx_reset_state(void) +{ + nrf_uart_int_disable(uart0_addr, + NRF_UART_INT_MASK_RXDRDY | + NRF_UART_INT_MASK_ERROR | + NRF_UART_INT_MASK_RXTO); + uart0_cb.rx_buffer_length = 0; + uart0_cb.rx_enabled = 0; + uart0_cb.rx_counter = 0; + uart0_cb.rx_offset = 0; + uart0_cb.rx_secondary_buffer_length = 0; +} + +static void rx_isr(struct device *dev) +{ + struct uart_event event; + + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_RXDRDY); + + if (!uart0_cb.rx_buffer_length || !uart0_cb.rx_enabled) { + /* Byte received when receiving is disabled - data lost. */ + nrf_uart_rxd_get(uart0_addr); + } else { + if (uart0_cb.rx_counter == 0) { + event.type = UART_RX_BUF_REQUEST; + user_callback(&event); + } + uart0_cb.rx_buffer[uart0_cb.rx_counter] = + nrf_uart_rxd_get(uart0_addr); + uart0_cb.rx_counter++; + if (uart0_cb.rx_timeout == K_NO_WAIT) { + rx_rdy_evt(); + } else if (uart0_cb.rx_timeout != K_FOREVER) { + k_delayed_work_submit(&uart0_cb.rx_timeout_work, + uart0_cb.rx_timeout); + } + } + + if (uart0_cb.rx_buffer_length == uart0_cb.rx_counter) { + k_delayed_work_cancel(&uart0_cb.rx_timeout_work); + rx_rdy_evt(); + + if (uart0_cb.rx_secondary_buffer_length) { + buf_released_evt(); + /* Switch to secondary buffer. */ + uart0_cb.rx_buffer_length = + uart0_cb.rx_secondary_buffer_length; + uart0_cb.rx_buffer = uart0_cb.rx_secondary_buffer; + uart0_cb.rx_secondary_buffer_length = 0; + uart0_cb.rx_counter = 0; + uart0_cb.rx_offset = 0; + + event.type = UART_RX_BUF_REQUEST; + user_callback(&event); + } else { + uart_nrfx_rx_disable(dev); + } + } +} + +static void tx_isr(void) +{ + uart0_cb.tx_counter++; + if (uart0_cb.tx_counter < uart0_cb.tx_buffer_length && + !uart0_cb.tx_abort) { +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + k_delayed_work_submit(&uart0_cb.tx_timeout_work, + uart0_cb.tx_timeout); +#endif + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_TXDRDY); + + u8_t txd = uart0_cb.tx_buffer[uart0_cb.tx_counter]; + + nrf_uart_txd_set(uart0_addr, txd); + } else { +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + k_delayed_work_cancel(&uart0_cb.tx_timeout_work); +#endif + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_TXDRDY); + uart0_cb.tx_buffer_length = 0; + uart0_cb.tx_counter = 0; + struct uart_event event = { + .type = UART_TX_DONE, + .data.tx.buf = uart0_cb.tx_buffer, + .data.tx.len = uart0_cb.tx_counter + }; + user_callback(&event); + } +} + +#define UART_ERROR_FROM_MASK(mask) \ + (mask & NRF_UART_ERROR_OVERRUN_MASK ? UART_ERROR_OVERRUN \ + : mask & NRF_UART_ERROR_PARITY_MASK ? UART_ERROR_PARITY \ + : mask & NRF_UART_ERROR_FRAMING_MASK ? UART_ERROR_FRAMING \ + : mask & NRF_UART_ERROR_BREAK_MASK ? UART_BREAK \ + : 0) + +static void error_isr(struct device *dev) +{ + k_delayed_work_cancel(&uart0_cb.rx_timeout_work); + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_ERROR); + + if (!uart0_cb.rx_enabled) { + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STOPRX); + } + struct uart_event event = { + .type = UART_RX_STOPPED, + .data.rx_stop.reason = + UART_ERROR_FROM_MASK( + nrf_uart_errorsrc_get_and_clear(uart0_addr)), + .data.rx_stop.data.len = uart0_cb.rx_counter + - uart0_cb.rx_offset, + .data.rx_stop.data.offset = uart0_cb.rx_offset, + .data.rx_stop.data.buf = uart0_cb.rx_buffer + }; + + user_callback(&event); + /* Abort transfer. */ + uart_nrfx_rx_disable(dev); +} + +/* + * In nRF hardware RX timeout can occur only after stopping the peripheral, + * it is used as a sign that peripheral has finished its operation and is + * disabled. + */ +static void rxto_isr(void) +{ + nrf_uart_event_clear(uart0_addr, NRF_UART_EVENT_RXTO); + + buf_released_evt(); + if (uart0_cb.rx_secondary_buffer_length) { + uart0_cb.rx_buffer = uart0_cb.rx_secondary_buffer; + buf_released_evt(); + } + + rx_reset_state(); + rx_disabled_evt(); +} + +void uart_nrfx_isr(void *arg) +{ + struct device *uart = (struct device *) arg; + + if (nrf_uart_int_enable_check(uart0_addr, NRF_UART_INT_MASK_ERROR) && + nrf_uart_event_check(uart0_addr, NRF_UART_EVENT_ERROR)) { + error_isr(uart); + } else if (nrf_uart_int_enable_check(uart0_addr, + NRF_UART_INT_MASK_RXDRDY) && + nrf_uart_event_check(uart0_addr, NRF_UART_EVENT_RXDRDY)) { + rx_isr(uart); + } + + if (nrf_uart_event_check(uart0_addr, NRF_UART_EVENT_TXDRDY)) { + tx_isr(); + } + + if (nrf_uart_event_check(uart0_addr, NRF_UART_EVENT_RXTO)) { + rxto_isr(); + } +} + +static void rx_timeout(struct k_work *work) +{ + rx_rdy_evt(); +} +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) +static void tx_timeout(struct k_work *work) +{ + struct uart_event evt; + + k_delayed_work_cancel(&uart0_cb.tx_timeout_work); + nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STOPTX); + evt.type = UART_TX_ABORTED; + evt.data.tx.buf = uart0_cb.tx_buffer; + evt.data.tx.len = uart0_cb.tx_buffer_length; + uart0_cb.tx_buffer_length = 0; + uart0_cb.tx_counter = 0; + user_callback(&evt); + +} +#endif + +#endif /* CONFIG_UART_ASYNC_API */ + + #ifdef CONFIG_UART_0_INTERRUPT_DRIVEN /** Interrupt driven FIFO fill function */ @@ -525,6 +893,9 @@ static int uart_nrfx_init(struct device *dev) * is indicated correctly. */ uart_sw_event_txdrdy = 1U; +#endif + +#if defined(CONFIG_UART_ASYNC_API) || defined(CONFIG_UART_0_INTERRUPT_DRIVEN) IRQ_CONNECT(DT_NORDIC_NRF_UART_UART_0_IRQ, DT_NORDIC_NRF_UART_UART_0_IRQ_PRIORITY, @@ -534,6 +905,13 @@ static int uart_nrfx_init(struct device *dev) irq_enable(DT_NORDIC_NRF_UART_UART_0_IRQ); #endif +#ifdef CONFIG_UART_ASYNC_API + k_delayed_work_init(&uart0_cb.rx_timeout_work, rx_timeout); +#if defined(DT_NORDIC_NRF_UART_UART_0_RTS_PIN) && \ + defined(DT_NORDIC_NRF_UART_UART_0_CTS_PIN) + k_delayed_work_init(&uart0_cb.tx_timeout_work, tx_timeout); +#endif +#endif return 0; } @@ -541,6 +919,14 @@ static int uart_nrfx_init(struct device *dev) * because Nordic hardware does not distinguish between them. */ static const struct uart_driver_api uart_nrfx_uart_driver_api = { +#ifdef CONFIG_UART_ASYNC_API + .callback_set = uart_nrfx_callback_set, + .tx = uart_nrfx_tx, + .tx_abort = uart_nrfx_tx_abort, + .rx_enable = uart_nrfx_rx_enable, + .rx_buf_rsp = uart_nrfx_rx_buf_rsp, + .rx_disable = uart_nrfx_rx_disable, +#endif /* CONFIG_UART_ASYNC_API */ .poll_in = uart_nrfx_poll_in, .poll_out = uart_nrfx_poll_out, .err_check = uart_nrfx_err_check, From 53e37e07227fbb3b3cf5e79564d95115a44b39f6 Mon Sep 17 00:00:00 2001 From: Mieszko Mierunski Date: Tue, 18 Dec 2018 12:46:41 +0100 Subject: [PATCH 3/3] tests: uart: Add tests for async UART API. Added tests for async UART API and test configuration for nrf52840_pca10056 board. For tests to work, RX and TX pins have to be connected together and second UART is needed to output results to console. Signed-off-by: Mieszko Mierunski --- .../uart/uart_async_api/CMakeLists.txt | 17 + .../boards/nrf52840_pca10056.conf | 1 + .../uart_async_api/nrf52840_pca10056.overlay | 24 ++ tests/drivers/uart/uart_async_api/prj.conf | 3 + tests/drivers/uart/uart_async_api/src/main.c | 26 ++ .../uart/uart_async_api/src/test_uart.h | 34 ++ .../uart/uart_async_api/src/test_uart_async.c | 300 ++++++++++++++++++ .../drivers/uart/uart_async_api/testcase.yaml | 5 + 8 files changed, 410 insertions(+) create mode 100644 tests/drivers/uart/uart_async_api/CMakeLists.txt create mode 100644 tests/drivers/uart/uart_async_api/boards/nrf52840_pca10056.conf create mode 100644 tests/drivers/uart/uart_async_api/nrf52840_pca10056.overlay create mode 100644 tests/drivers/uart/uart_async_api/prj.conf create mode 100644 tests/drivers/uart/uart_async_api/src/main.c create mode 100644 tests/drivers/uart/uart_async_api/src/test_uart.h create mode 100644 tests/drivers/uart/uart_async_api/src/test_uart_async.c create mode 100644 tests/drivers/uart/uart_async_api/testcase.yaml diff --git a/tests/drivers/uart/uart_async_api/CMakeLists.txt b/tests/drivers/uart/uart_async_api/CMakeLists.txt new file mode 100644 index 0000000000000..d7137d095f440 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.8.2) + +macro(set_conf_file) + if(EXISTS ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf) + set(CONF_FILE "prj.conf ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf") + else() + set(CONF_FILE "prj.conf") + endif() +endmacro() + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(uart_high_level_api) + +target_sources(app PRIVATE + src/main.c + src/test_uart_async.c + ) diff --git a/tests/drivers/uart/uart_async_api/boards/nrf52840_pca10056.conf b/tests/drivers/uart/uart_async_api/boards/nrf52840_pca10056.conf new file mode 100644 index 0000000000000..9070e2ff9bed5 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/boards/nrf52840_pca10056.conf @@ -0,0 +1 @@ +CONFIG_UART_1_NRF_UARTE=y diff --git a/tests/drivers/uart/uart_async_api/nrf52840_pca10056.overlay b/tests/drivers/uart/uart_async_api/nrf52840_pca10056.overlay new file mode 100644 index 0000000000000..f87a262bd5a08 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/nrf52840_pca10056.overlay @@ -0,0 +1,24 @@ +/ { + chosen { + zephyr,console = &uart1; + }; +}; + +&uart1 { + current-speed = <115200>; + status = "ok"; + tx-pin = <6>; + rx-pin = <8>; + rts-pin = <0>; + cts-pin = <0>; +}; + +&uart0 { + compatible = "nordic,nrf-uart"; + current-speed = <115200>; + status = "ok"; + tx-pin = <33>; + rx-pin = <34>; + rts-pin = <5>; + cts-pin = <7>; +}; diff --git a/tests/drivers/uart/uart_async_api/prj.conf b/tests/drivers/uart/uart_async_api/prj.conf new file mode 100644 index 0000000000000..e2dae2ee1a365 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/prj.conf @@ -0,0 +1,3 @@ +CONFIG_SERIAL=y +CONFIG_UART_ASYNC_API=y +CONFIG_ZTEST=y diff --git a/tests/drivers/uart/uart_async_api/src/main.c b/tests/drivers/uart/uart_async_api/src/main.c new file mode 100644 index 0000000000000..6a3c486eec596 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/src/main.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @addtogroup t_driver_uart + * @{ + * @defgroup t_uart_async test_uart_async + * @} + */ + +#include "test_uart.h" + +void test_main(void) +{ + ztest_test_suite(uart_async_test, + ztest_unit_test(test_single_read), + ztest_unit_test(test_chained_read), + ztest_unit_test(test_double_buffer), + ztest_unit_test(test_read_abort), + ztest_unit_test(test_write_abort)); + + ztest_run_test_suite(uart_async_test); +} diff --git a/tests/drivers/uart/uart_async_api/src/test_uart.h b/tests/drivers/uart/uart_async_api/src/test_uart.h new file mode 100644 index 0000000000000..1a07162e09d94 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/src/test_uart.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief UART cases header file + * + * Header file for UART cases + */ + +#ifndef __TEST_UART_H__ +#define __TEST_UART_H__ + +#include +#include + +/* RX and TX pins have to be connected together*/ + +#if defined(CONFIG_BOARD_NRF52840_PCA10056) +#define UART_DEVICE_NAME DT_UART_0_NAME +#else +#define UART_DEVICE_NAME CONFIG_UART_CONSOLE_ON_DEV_NAME +#endif + +void test_single_read(void); +void test_chained_read(void); +void test_double_buffer(void); +void test_read_abort(void); +void test_write_abort(void); + +#endif /* __TEST_UART_H__ */ diff --git a/tests/drivers/uart/uart_async_api/src/test_uart_async.c b/tests/drivers/uart/uart_async_api/src/test_uart_async.c new file mode 100644 index 0000000000000..b90d066ff3e1b --- /dev/null +++ b/tests/drivers/uart/uart_async_api/src/test_uart_async.c @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_uart.h" + +K_SEM_DEFINE(tx_done, 0, 1); +K_SEM_DEFINE(tx_aborted, 0, 1); +K_SEM_DEFINE(rx_rdy, 0, 1); +K_SEM_DEFINE(rx_buf_released, 0, 1); +K_SEM_DEFINE(rx_disabled, 0, 1); + +void test_single_read_callback(struct uart_event *evt, void *user_data) +{ + switch (evt->type) { + case UART_TX_DONE: + k_sem_give(&tx_done); + break; + case UART_RX_RDY: + k_sem_give(&rx_rdy); + break; + case UART_RX_BUF_RELEASED: + k_sem_give(&rx_buf_released); + break; + case UART_RX_DISABLED: + k_sem_give(&rx_disabled); + break; + default: + break; + } + +} + +void test_single_read(void) +{ + struct device *uart_dev = device_get_binding(UART_DEVICE_NAME); + + u8_t rx_buf[10] = {0}; + u8_t tx_buf[5] = "test"; + + zassert_not_equal(memcmp(tx_buf, rx_buf, 5), 0, + "Initial buffer check failed"); + + uart_callback_set(uart_dev, test_single_read_callback, NULL); + + uart_rx_enable(uart_dev, rx_buf, 10, 50); + uart_tx(uart_dev, tx_buf, sizeof(tx_buf), 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + + zassert_equal(memcmp(tx_buf, rx_buf, 5), 0, "Buffers not equal"); + zassert_not_equal(memcmp(tx_buf, rx_buf+5, 5), 0, "Buffers not equal"); + + uart_tx(uart_dev, tx_buf, sizeof(tx_buf), 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + zassert_equal(k_sem_take(&rx_buf_released, 100), + 0, + "RX_BUF_RELEASED timeout"); + zassert_equal(k_sem_take(&rx_disabled, 1000), 0, "RX_DISABLED timeout"); + zassert_equal(memcmp(tx_buf, rx_buf+5, 5), 0, "Buffers not equal"); +} + +u8_t chained_read_buf0[10]; +u8_t chained_read_buf1[20]; +u8_t chained_read_buf2[30]; +u8_t buf_num = 1; +u8_t *read_ptr; + +void test_chained_read_callback(struct uart_event *evt, void *user_data) +{ + struct device *uart_dev = (struct device *) user_data; + + switch (evt->type) { + case UART_TX_DONE: + k_sem_give(&tx_done); + break; + case UART_RX_RDY: + read_ptr = evt->data.rx.buf + evt->data.rx.offset; + k_sem_give(&rx_rdy); + break; + case UART_RX_BUF_REQUEST: + if (buf_num == 1) { + uart_rx_buf_rsp(uart_dev, + chained_read_buf1, + sizeof(chained_read_buf1)); + buf_num = 2; + } else if (buf_num == 2) { + uart_rx_buf_rsp(uart_dev, + chained_read_buf2, + sizeof(chained_read_buf2)); + buf_num = 0; + } + break; + case UART_RX_DISABLED: + k_sem_give(&rx_disabled); + break; + default: + break; + } + +} + +void test_chained_read(void) +{ + struct device *uart_dev = device_get_binding(UART_DEVICE_NAME); + + u8_t tx_buf[10]; + + uart_callback_set(uart_dev, test_chained_read_callback, uart_dev); + + uart_rx_enable(uart_dev, chained_read_buf0, 10, 50); + + for (int i = 0; i < 6; i++) { + zassert_not_equal(k_sem_take(&rx_disabled, 10), + 0, + "RX_DISABLED occurred"); + snprintf(tx_buf, sizeof(tx_buf), "Message %d", i); + uart_tx(uart_dev, tx_buf, sizeof(tx_buf), 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 1000), 0, "RX_RDY timeout"); + zassert_equal(memcmp(tx_buf, read_ptr, sizeof(tx_buf)), + 0, + "Buffers not equal"); + } + zassert_equal(k_sem_take(&rx_disabled, 100), 0, "RX_DISABLED timeout"); +} + +u8_t double_buffer[2][12]; +u8_t *next_buf = double_buffer[1]; + +void test_double_buffer_callback(struct uart_event *evt, void *user_data) +{ + struct device *uart_dev = (struct device *) user_data; + + switch (evt->type) { + case UART_TX_DONE: + k_sem_give(&tx_done); + break; + case UART_RX_RDY: + read_ptr = evt->data.rx.buf + evt->data.rx.offset; + k_sem_give(&rx_rdy); + break; + case UART_RX_BUF_REQUEST: + uart_rx_buf_rsp(uart_dev, next_buf, sizeof(double_buffer[0])); + break; + case UART_RX_BUF_RELEASED: + next_buf = evt->data.rx_buf.buf; + k_sem_give(&rx_buf_released); + break; + case UART_RX_DISABLED: + k_sem_give(&rx_disabled); + break; + default: + break; + } + +} + +void test_double_buffer(void) +{ + struct device *uart_dev = device_get_binding(UART_DEVICE_NAME); + + u8_t tx_buf[4]; + + uart_callback_set(uart_dev, test_double_buffer_callback, uart_dev); + + zassert_equal(uart_rx_enable(uart_dev, + double_buffer[0], + sizeof(double_buffer[0]), + 50), + 0, + "Failed to enable receiving"); + + for (int i = 0; i < 100; i++) { + snprintf(tx_buf, sizeof(tx_buf), "%03d", i); + uart_tx(uart_dev, tx_buf, sizeof(tx_buf), 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + zassert_equal(memcmp(tx_buf, read_ptr, sizeof(tx_buf)), + 0, + "Buffers not equal"); + } + uart_rx_disable(uart_dev); + zassert_equal(k_sem_take(&rx_disabled, 100), 0, "RX_DISABLED timeout"); +} + +void test_read_abort_callback(struct uart_event *evt, void *user_data) +{ + switch (evt->type) { + case UART_TX_DONE: + k_sem_give(&tx_done); + break; + case UART_RX_RDY: + k_sem_give(&rx_rdy); + break; + case UART_RX_BUF_RELEASED: + k_sem_give(&rx_buf_released); + break; + case UART_RX_DISABLED: + k_sem_give(&rx_disabled); + break; + default: + break; + } +} + +void test_read_abort(void) +{ + struct device *uart_dev = device_get_binding(UART_DEVICE_NAME); + + u8_t rx_buf[100]; + u8_t tx_buf[100]; + + memset(rx_buf, 0, sizeof(rx_buf)); + memset(tx_buf, 1, sizeof(tx_buf)); + + uart_callback_set(uart_dev, test_read_abort_callback, NULL); + + uart_rx_enable(uart_dev, rx_buf, sizeof(rx_buf), 50); + + uart_tx(uart_dev, tx_buf, 5, 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + zassert_equal(memcmp(tx_buf, rx_buf, 5), 0, "Buffers not equal"); + + + uart_tx(uart_dev, tx_buf, 95, 100); + uart_rx_disable(uart_dev); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_buf_released, 100), + 0, + "RX_BUF_RELEASED timeout"); + zassert_equal(k_sem_take(&rx_disabled, 100), 0, "RX_DISABLED timeout"); + zassert_not_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY occurred"); + zassert_not_equal(memcmp(tx_buf, rx_buf, 100), 0, "Buffers equal"); +} + +volatile size_t sent; +volatile size_t received; + +void test_write_abort_callback(struct uart_event *evt, void *user_data) +{ + switch (evt->type) { + case UART_TX_DONE: + k_sem_give(&tx_done); + break; + case UART_TX_ABORTED: + sent = evt->data.tx.len; + k_sem_give(&tx_aborted); + break; + case UART_RX_RDY: + received = evt->data.rx.len; + k_sem_give(&rx_rdy); + break; + case UART_RX_BUF_RELEASED: + k_sem_give(&rx_buf_released); + break; + case UART_RX_DISABLED: + k_sem_give(&rx_disabled); + break; + default: + break; + } +} + +void test_write_abort(void) +{ + struct device *uart_dev = device_get_binding(UART_DEVICE_NAME); + + u8_t rx_buf[100]; + u8_t tx_buf[100]; + + memset(rx_buf, 0, sizeof(rx_buf)); + memset(tx_buf, 1, sizeof(tx_buf)); + + uart_callback_set(uart_dev, test_write_abort_callback, NULL); + + uart_rx_enable(uart_dev, rx_buf, sizeof(rx_buf), 50); + + uart_tx(uart_dev, tx_buf, 5, 100); + zassert_equal(k_sem_take(&tx_done, 100), 0, "TX_DONE timeout"); + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + zassert_equal(memcmp(tx_buf, rx_buf, 5), 0, "Buffers not equal"); + + uart_tx(uart_dev, tx_buf, 95, 100); + uart_tx_abort(uart_dev); + zassert_equal(k_sem_take(&tx_aborted, 100), 0, "TX_ABORTED timeout"); + if (sent != 0) { + zassert_equal(k_sem_take(&rx_rdy, 100), 0, "RX_RDY timeout"); + zassert_equal(sent, received, "Sent is not equal to received."); + } + uart_rx_disable(uart_dev); + zassert_equal(k_sem_take(&rx_buf_released, 100), + 0, + "RX_BUF_RELEASED timeout"); + zassert_equal(k_sem_take(&rx_disabled, 100), 0, "RX_DISABLED timeout"); +} diff --git a/tests/drivers/uart/uart_async_api/testcase.yaml b/tests/drivers/uart/uart_async_api/testcase.yaml new file mode 100644 index 0000000000000..7ebc1a3f34c55 --- /dev/null +++ b/tests/drivers/uart/uart_async_api/testcase.yaml @@ -0,0 +1,5 @@ +tests: + peripheral.uart_async_api: + tags: drivers + filter: CONFIG_UART_CONSOLE + harness: keyboard