From aeb85db62708db2a496f88cf9342ea9e4e90c88d Mon Sep 17 00:00:00 2001 From: Nick Kraus Date: Sun, 15 Oct 2023 14:42:16 -0400 Subject: [PATCH] drivers: uart_emul: Add IRQ Based TX Added an interrupt based transmit routine and interrupt based uart_emul tests. Signed-off-by: Nick Kraus --- drivers/serial/uart_emul.c | 87 +++++++++++++++++++++++-- tests/drivers/uart/uart_emul/prj.conf | 1 + tests/drivers/uart/uart_emul/src/main.c | 62 ++++++++++++++++++ 3 files changed, 146 insertions(+), 4 deletions(-) diff --git a/drivers/serial/uart_emul.c b/drivers/serial/uart_emul.c index 55c726f96b0..7b3c7ca1c39 100644 --- a/drivers/serial/uart_emul.c +++ b/drivers/serial/uart_emul.c @@ -41,6 +41,7 @@ struct uart_emul_data { #ifdef CONFIG_UART_INTERRUPT_DRIVEN bool rx_irq_en; + bool tx_irq_en; struct uart_emul_work irq_work; uart_irq_callback_user_data_t irq_cb; void *irq_cb_udata; @@ -114,6 +115,26 @@ static int uart_emul_config_get(const struct device *dev, struct uart_config *cf #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ #ifdef CONFIG_UART_INTERRUPT_DRIVEN +static int uart_emul_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) +{ + int ret; + struct uart_emul_data *data = dev->data; + const struct uart_emul_config *config = dev->config; + + K_SPINLOCK(&data->tx_lock) { + ret = ring_buf_put(data->tx_rb, tx_data, size); + } + + if (config->loopback) { + uart_emul_put_rx_data(dev, (uint8_t *)tx_data, ret); + } + if (data->tx_data_ready_cb) { + data->tx_data_ready_cb(dev, ring_buf_size_get(data->tx_rb), data->user_data); + } + + return ret; +} + static int uart_emul_fifo_read(const struct device *dev, uint8_t *rx_data, int size) { int ret; @@ -131,6 +152,22 @@ static int uart_emul_fifo_read(const struct device *dev, uint8_t *rx_data, int s return ret; } +static int uart_emul_irq_tx_ready(const struct device *dev) +{ + bool ready = false; + struct uart_emul_data *data = dev->data; + + K_SPINLOCK(&data->tx_lock) { + if (!data->tx_irq_en) { + K_SPINLOCK_BREAK; + } + + ready = ring_buf_space_get(data->tx_rb) > 0; + } + + return ready; +} + static int uart_emul_irq_rx_ready(const struct device *dev) { bool ready = false; @@ -163,6 +200,14 @@ static void uart_emul_irq_handler(struct k_work *work) while (true) { bool have_work = false; + K_SPINLOCK(&data->tx_lock) { + if (!data->tx_irq_en) { + K_SPINLOCK_BREAK; + } + + have_work = have_work || ring_buf_space_get(data->tx_rb) > 0; + } + K_SPINLOCK(&data->rx_lock) { if (!data->rx_irq_en) { K_SPINLOCK_BREAK; @@ -181,14 +226,22 @@ static void uart_emul_irq_handler(struct k_work *work) static int uart_emul_irq_is_pending(const struct device *dev) { - bool rx_pending; + return uart_emul_irq_tx_ready(dev) || uart_emul_irq_rx_ready(dev); +} + +static void uart_emul_irq_tx_enable(const struct device *dev) +{ + bool submit_irq_work; struct uart_emul_data *const data = dev->data; - K_SPINLOCK(&data->rx_lock) { - rx_pending = !ring_buf_is_empty(data->rx_rb); + K_SPINLOCK(&data->tx_lock) { + data->tx_irq_en = true; + submit_irq_work = ring_buf_space_get(data->tx_rb) > 0; } - return rx_pending; + if (submit_irq_work) { + (void)k_work_submit(&data->irq_work.work); + } } static void uart_emul_irq_rx_enable(const struct device *dev) @@ -206,6 +259,15 @@ static void uart_emul_irq_rx_enable(const struct device *dev) } } +static void uart_emul_irq_tx_disable(const struct device *dev) +{ + struct uart_emul_data *const data = dev->data; + + K_SPINLOCK(&data->tx_lock) { + data->tx_irq_en = false; + } +} + static void uart_emul_irq_rx_disable(const struct device *dev) { struct uart_emul_data *const data = dev->data; @@ -215,6 +277,18 @@ static void uart_emul_irq_rx_disable(const struct device *dev) } } +static int uart_emul_irq_tx_complete(const struct device *dev) +{ + bool tx_complete = false; + struct uart_emul_data *const data = dev->data; + + K_SPINLOCK(&data->tx_lock) { + tx_complete = ring_buf_is_empty(data->tx_rb); + } + + return tx_complete; +} + static void uart_emul_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *user_data) { @@ -239,10 +313,15 @@ static const struct uart_driver_api uart_emul_api = { #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ .err_check = uart_emul_err_check, #ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = uart_emul_fifo_fill, .fifo_read = uart_emul_fifo_read, + .irq_tx_enable = uart_emul_irq_tx_enable, .irq_rx_enable = uart_emul_irq_rx_enable, + .irq_tx_disable = uart_emul_irq_tx_disable, .irq_rx_disable = uart_emul_irq_rx_disable, + .irq_tx_ready = uart_emul_irq_tx_ready, .irq_rx_ready = uart_emul_irq_rx_ready, + .irq_tx_complete = uart_emul_irq_tx_complete, .irq_callback_set = uart_emul_irq_callback_set, .irq_update = uart_emul_irq_update, .irq_is_pending = uart_emul_irq_is_pending, diff --git a/tests/drivers/uart/uart_emul/prj.conf b/tests/drivers/uart/uart_emul/prj.conf index 79043ef148e..0fe0e70acee 100644 --- a/tests/drivers/uart/uart_emul/prj.conf +++ b/tests/drivers/uart/uart_emul/prj.conf @@ -1,2 +1,3 @@ CONFIG_ZTEST=y CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y diff --git a/tests/drivers/uart/uart_emul/src/main.c b/tests/drivers/uart/uart_emul/src/main.c index 5e698d0b1e9..af04058171f 100644 --- a/tests/drivers/uart/uart_emul/src/main.c +++ b/tests/drivers/uart/uart_emul/src/main.c @@ -35,6 +35,9 @@ static void uart_emul_before(void *f) { struct uart_emul_fixture *fixture = f; + uart_irq_tx_disable(fixture->dev); + uart_irq_rx_disable(fixture->dev); + uart_emul_flush_rx_data(fixture->dev); uart_emul_flush_tx_data(fixture->dev); } @@ -75,4 +78,63 @@ ZTEST_F(uart_emul, test_polling_in) zassert_equal(rc, -1, "RX buffer should be empty"); } +static void uart_emul_isr(const struct device *dev, void *user_data) +{ + /* always of size SAMPLE_DATA_SIZE */ + uint8_t *buf = user_data; + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_tx_ready(dev)) { + uart_fifo_fill(dev, buf, SAMPLE_DATA_SIZE); + uart_irq_tx_disable(dev); + } + + if (uart_irq_rx_ready(dev)) { + uart_fifo_read(dev, buf, SAMPLE_DATA_SIZE); + } + } +} + +ZTEST_F(uart_emul, test_irq_tx) +{ + uint8_t tx_content[SAMPLE_DATA_SIZE] = {0}; + size_t tx_len; + + uart_irq_callback_user_data_set(fixture->dev, uart_emul_isr, fixture->sample_data); + /* enabling the tx irq will call the callback, if set */ + uart_irq_tx_enable(fixture->dev); + /* allow space for work queue to run */ + k_yield(); + + tx_len = uart_emul_get_tx_data(fixture->dev, tx_content, SAMPLE_DATA_SIZE); + zassert_equal(tx_len, SAMPLE_DATA_SIZE, "TX buffer length does not match"); + zassert_mem_equal(tx_content, fixture->sample_data, SAMPLE_DATA_SIZE); + + /* No more data in TX buffer */ + tx_len = uart_emul_get_tx_data(fixture->dev, tx_content, sizeof(tx_content)); + zassert_equal(tx_len, 0, "TX buffer should be empty"); +} + +ZTEST_F(uart_emul, test_irq_rx) +{ + uint8_t rx_content[SAMPLE_DATA_SIZE] = {0}; + int rc; + + uart_irq_callback_user_data_set(fixture->dev, uart_emul_isr, rx_content); + uart_irq_rx_enable(fixture->dev); + + /* putting rx data will call the irq callback, if enabled */ + uart_emul_put_rx_data(fixture->dev, fixture->sample_data, SAMPLE_DATA_SIZE); + /* allow space for work queue to run */ + k_yield(); + + zassert_mem_equal(rx_content, fixture->sample_data, SAMPLE_DATA_SIZE); + + /* No more data in RX buffer */ + rc = uart_poll_in(fixture->dev, &rx_content[0]); + zassert_equal(rc, -1, "RX buffer should be empty"); + + uart_irq_rx_disable(fixture->dev); +} + ZTEST_SUITE(uart_emul, NULL, uart_emul_setup, uart_emul_before, NULL, NULL);