You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1061 lines
27 KiB
1061 lines
27 KiB
/* |
|
* Copyright (c) 2023 Fabian Blatz |
|
* Copyright (c) 2024 grandcentrix GmbH |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT zephyr_uart_emul |
|
|
|
#include <errno.h> |
|
|
|
#include <zephyr/drivers/emul.h> |
|
#include <zephyr/drivers/uart.h> |
|
#include <zephyr/drivers/serial/uart_emul.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/sys/ring_buffer.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
LOG_MODULE_REGISTER(uart_emul, CONFIG_UART_LOG_LEVEL); |
|
|
|
struct uart_emul_config { |
|
/* emul_list has to be the first member */ |
|
struct emul_list_for_bus emul_list; |
|
|
|
bool loopback; |
|
size_t latch_buffer_size; |
|
}; |
|
|
|
BUILD_ASSERT(offsetof(struct uart_emul_config, emul_list) == 0); |
|
|
|
/* Device run time data */ |
|
struct uart_emul_data { |
|
/* List of struct uart_emul associated with the device */ |
|
sys_slist_t emuls; |
|
|
|
const struct device *dev; |
|
|
|
struct uart_config cfg; |
|
int errors; |
|
|
|
struct ring_buf *rx_rb; |
|
struct k_spinlock rx_lock; |
|
|
|
uart_emul_callback_tx_data_ready_t tx_data_ready_cb; |
|
void *user_data; |
|
|
|
struct ring_buf *tx_rb; |
|
struct k_spinlock tx_lock; |
|
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN |
|
bool rx_irq_en; |
|
bool tx_irq_en; |
|
struct k_work irq_work; |
|
|
|
uart_irq_callback_user_data_t irq_cb; |
|
void *irq_cb_udata; |
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
|
|
|
#ifdef CONFIG_UART_ASYNC_API |
|
bool rx_async_en; |
|
bool rx_stopping; |
|
bool rx_release_on_timeout; |
|
|
|
struct k_work tx_work; |
|
struct k_work rx_work; |
|
struct k_work rx_disable_work; |
|
struct k_work_delayable rx_timeout_work; |
|
|
|
uart_callback_t uart_callback; |
|
void *callback_user_data; |
|
|
|
const uint8_t *tx_buf; |
|
size_t tx_buf_len; |
|
size_t tx_buf_offset; |
|
|
|
uint8_t *rx_buf; |
|
size_t rx_buf_len; |
|
size_t rx_buf_offset; |
|
size_t rx_buf_data_len; |
|
int32_t rx_buf_timeout; |
|
|
|
uint8_t *rx_buf_next; |
|
size_t rx_buf_next_len; |
|
#endif /* CONFIG_UART_ASYNC_API */ |
|
}; |
|
|
|
/* |
|
* Define local thread to emulate different thread priorities. |
|
* |
|
* A UART driver may call back from within a thread with higher or lower priority |
|
* than the thread calling the UART API. This can hide potential concurrency issues, |
|
* especially if the thread priorities are the same, or even using the same thread |
|
* in case the system work queue. |
|
*/ |
|
K_THREAD_STACK_DEFINE(uart_emul_stack_area, CONFIG_UART_EMUL_WORK_Q_STACK_SIZE); |
|
struct k_work_q uart_emul_work_q; |
|
|
|
int uart_emul_init_work_q(void) |
|
{ |
|
struct k_work_queue_config cfg = { |
|
.name = "uart_emul_workq", |
|
.no_yield = false, |
|
}; |
|
|
|
k_work_queue_init(&uart_emul_work_q); |
|
k_work_queue_start(&uart_emul_work_q, uart_emul_stack_area, |
|
K_THREAD_STACK_SIZEOF(uart_emul_stack_area), |
|
CONFIG_UART_EMUL_WORK_Q_PRIORITY, &cfg); |
|
return 0; |
|
} |
|
|
|
SYS_INIT(uart_emul_init_work_q, POST_KERNEL, 0); |
|
|
|
static void uart_emul_tx_data_ready(const struct device *dev) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
sys_snode_t *node; |
|
|
|
if (data->tx_data_ready_cb) { |
|
(data->tx_data_ready_cb)(dev, ring_buf_size_get(data->tx_rb), data->user_data); |
|
} |
|
SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) { |
|
struct uart_emul *emul = CONTAINER_OF(node, struct uart_emul, node); |
|
|
|
__ASSERT_NO_MSG(emul->api != NULL); |
|
__ASSERT_NO_MSG(emul->api->tx_data_ready != NULL); |
|
|
|
emul->api->tx_data_ready(dev, ring_buf_size_get(data->tx_rb), emul->target); |
|
} |
|
} |
|
|
|
static int uart_emul_poll_in(const struct device *dev, unsigned char *p_char) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t read; |
|
|
|
key = k_spin_lock(&drv_data->rx_lock); |
|
read = ring_buf_get(drv_data->rx_rb, p_char, 1); |
|
k_spin_unlock(&drv_data->rx_lock, key); |
|
|
|
if (!read) { |
|
LOG_DBG("Rx buffer is empty"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void uart_emul_poll_out(const struct device *dev, unsigned char out_char) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
const struct uart_emul_config *drv_cfg = dev->config; |
|
k_spinlock_key_t key; |
|
uint32_t written; |
|
|
|
key = k_spin_lock(&drv_data->tx_lock); |
|
written = ring_buf_put(drv_data->tx_rb, &out_char, 1); |
|
k_spin_unlock(&drv_data->tx_lock, key); |
|
|
|
if (!written) { |
|
LOG_DBG("Tx buffer is full"); |
|
return; |
|
} |
|
|
|
if (drv_cfg->loopback) { |
|
uart_emul_put_rx_data(dev, &out_char, 1); |
|
} |
|
|
|
uart_emul_tx_data_ready(dev); |
|
} |
|
|
|
static int uart_emul_err_check(const struct device *dev) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
int errors = drv_data->errors; |
|
|
|
drv_data->errors = 0; |
|
return errors; |
|
} |
|
|
|
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
|
static int uart_emul_configure(const struct device *dev, const struct uart_config *cfg) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
|
|
memcpy(&drv_data->cfg, cfg, sizeof(struct uart_config)); |
|
return 0; |
|
} |
|
|
|
static int uart_emul_config_get(const struct device *dev, struct uart_config *cfg) |
|
{ |
|
const struct uart_emul_data *drv_data = dev->data; |
|
|
|
memcpy(cfg, &drv_data->cfg, sizeof(struct uart_config)); |
|
return 0; |
|
} |
|
#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; |
|
uint32_t put_size = MIN(config->latch_buffer_size, size); |
|
|
|
K_SPINLOCK(&data->tx_lock) { |
|
ret = ring_buf_put(data->tx_rb, tx_data, put_size); |
|
} |
|
|
|
if (config->loopback) { |
|
uart_emul_put_rx_data(dev, (uint8_t *)tx_data, put_size); |
|
} |
|
|
|
uart_emul_tx_data_ready(dev); |
|
|
|
return ret; |
|
} |
|
|
|
static int uart_emul_fifo_read(const struct device *dev, uint8_t *rx_data, int size) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
const struct uart_emul_config *config = dev->config; |
|
uint32_t bytes_to_read; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
bytes_to_read = MIN(config->latch_buffer_size, ring_buf_size_get(data->rx_rb)); |
|
bytes_to_read = MIN(bytes_to_read, size); |
|
ring_buf_get(data->rx_rb, rx_data, bytes_to_read); |
|
} |
|
|
|
return bytes_to_read; |
|
} |
|
|
|
static int uart_emul_irq_tx_ready(const struct device *dev) |
|
{ |
|
int available = 0; |
|
struct uart_emul_data *data = dev->data; |
|
|
|
K_SPINLOCK(&data->tx_lock) { |
|
if (!data->tx_irq_en) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
available = ring_buf_space_get(data->tx_rb); |
|
} |
|
|
|
return available; |
|
} |
|
|
|
static int uart_emul_irq_rx_ready(const struct device *dev) |
|
{ |
|
bool ready = false; |
|
struct uart_emul_data *data = dev->data; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
if (!data->rx_irq_en) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
ready = !ring_buf_is_empty(data->rx_rb); |
|
} |
|
|
|
return ready; |
|
} |
|
|
|
static void uart_emul_irq_handler(struct k_work *work) |
|
{ |
|
struct uart_emul_data *data = CONTAINER_OF(work, struct uart_emul_data, irq_work); |
|
const struct device *dev = data->dev; |
|
uart_irq_callback_user_data_t cb = data->irq_cb; |
|
void *udata = data->irq_cb_udata; |
|
|
|
if (cb == NULL) { |
|
LOG_DBG("No IRQ callback configured for uart_emul device %p", dev); |
|
return; |
|
} |
|
|
|
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; |
|
} |
|
|
|
have_work = have_work || !ring_buf_is_empty(data->rx_rb); |
|
} |
|
|
|
if (!have_work) { |
|
break; |
|
} |
|
|
|
cb(dev, udata); |
|
} |
|
} |
|
|
|
static int uart_emul_irq_is_pending(const struct device *dev) |
|
{ |
|
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->tx_lock) { |
|
data->tx_irq_en = true; |
|
submit_irq_work = ring_buf_space_get(data->tx_rb) > 0; |
|
} |
|
|
|
if (submit_irq_work) { |
|
(void)k_work_submit_to_queue(&uart_emul_work_q, &data->irq_work); |
|
} |
|
} |
|
|
|
static void uart_emul_irq_rx_enable(const struct device *dev) |
|
{ |
|
bool submit_irq_work; |
|
struct uart_emul_data *const data = dev->data; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
data->rx_irq_en = true; |
|
submit_irq_work = !ring_buf_is_empty(data->rx_rb); |
|
} |
|
|
|
if (submit_irq_work) { |
|
(void)k_work_submit_to_queue(&uart_emul_work_q, &data->irq_work); |
|
} |
|
} |
|
|
|
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; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
data->rx_irq_en = false; |
|
} |
|
} |
|
|
|
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) |
|
{ |
|
struct uart_emul_data *const data = dev->data; |
|
|
|
data->irq_cb = cb; |
|
data->irq_cb_udata = user_data; |
|
} |
|
|
|
static int uart_emul_irq_update(const struct device *dev) |
|
{ |
|
return 1; |
|
} |
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
|
|
|
#ifdef CONFIG_UART_ASYNC_API |
|
static void uart_emul_post_event(const struct device *dev, struct uart_event *evt) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
|
|
if (!data->uart_callback) { |
|
LOG_DBG("No async callback configured for uart_emul device %p", dev); |
|
} |
|
|
|
data->uart_callback(dev, evt, data->callback_user_data); |
|
} |
|
|
|
static void uart_emul_simple_event(const struct device *dev, enum uart_event_type type) |
|
{ |
|
uart_emul_post_event(dev, &(struct uart_event){.type = type}); |
|
} |
|
|
|
static void uart_emul_async_switch_buf_nolock(struct uart_emul_data *data) |
|
{ |
|
data->rx_buf = data->rx_buf_next; |
|
data->rx_buf_len = data->rx_buf_next_len; |
|
data->rx_buf_offset = 0; |
|
data->rx_buf_data_len = 0; |
|
data->rx_buf_next = NULL; |
|
data->rx_buf_next_len = 0; |
|
} |
|
|
|
static void uart_emul_async_rx_timeout_handler(struct k_work *_work) |
|
{ |
|
struct k_work_delayable *work = k_work_delayable_from_work(_work); |
|
struct uart_emul_data *data = CONTAINER_OF(work, struct uart_emul_data, rx_timeout_work); |
|
const struct device *dev = data->dev; |
|
|
|
uint8_t *rx_buf; |
|
size_t rx_buf_len; |
|
size_t rx_buf_offset; |
|
size_t rx_buf_data_len; |
|
bool rx_en; |
|
bool rx_buf_released = false; |
|
bool rx_stopped = false; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
rx_en = data->rx_async_en; |
|
rx_buf = data->rx_buf; |
|
rx_buf_len = data->rx_buf_len; |
|
rx_buf_offset = data->rx_buf_offset; |
|
rx_buf_data_len = data->rx_buf_data_len; |
|
|
|
data->rx_buf_offset += rx_buf_data_len; |
|
data->rx_buf_data_len = 0; |
|
|
|
if (data->rx_buf_offset >= rx_buf_len || |
|
(rx_buf_data_len > 0 && data->rx_release_on_timeout)) { |
|
rx_buf_released = true; |
|
uart_emul_async_switch_buf_nolock(data); |
|
if (data->rx_buf == NULL) { |
|
/* There was no second buffer scheduled, so stop receiving */ |
|
rx_stopped = true; |
|
data->rx_async_en = false; |
|
} |
|
} |
|
} |
|
|
|
if (!rx_en || rx_buf == NULL || rx_buf_data_len == 0) { |
|
return; |
|
} |
|
|
|
struct uart_event rx_rdy_event = { |
|
.type = UART_RX_RDY, |
|
.data.rx = { |
|
.buf = rx_buf, |
|
.offset = rx_buf_offset, |
|
.len = rx_buf_data_len, |
|
}, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_rdy_event); |
|
|
|
if (rx_buf_released) { |
|
struct uart_event rx_buf_released_event = { |
|
.type = UART_RX_BUF_RELEASED, |
|
.data.rx_buf.buf = rx_buf, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_buf_released_event); |
|
} |
|
if (rx_stopped) { |
|
uart_emul_simple_event(dev, UART_RX_DISABLED); |
|
} |
|
} |
|
|
|
static void uart_emul_async_rx_handler(struct k_work *work) |
|
{ |
|
struct uart_emul_data *data = CONTAINER_OF(work, struct uart_emul_data, rx_work); |
|
const struct device *dev = data->dev; |
|
|
|
bool rx_en = false; |
|
bool empty = true; |
|
|
|
do { |
|
bool rx_rdy = false; |
|
bool buf_request = false; |
|
|
|
uint8_t *rx_buf = NULL; |
|
size_t buf_len; |
|
size_t offset; |
|
size_t data_len; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
rx_en = data->rx_async_en; |
|
rx_buf = data->rx_buf; |
|
buf_len = data->rx_buf_len; |
|
offset = data->rx_buf_offset; |
|
data_len = data->rx_buf_data_len; |
|
empty = ring_buf_is_empty(data->rx_rb); |
|
|
|
if (!rx_en) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (rx_buf == NULL) { |
|
uart_emul_async_switch_buf_nolock(data); |
|
rx_buf = data->rx_buf; |
|
buf_len = data->rx_buf_len; |
|
offset = data->rx_buf_offset; |
|
data_len = data->rx_buf_data_len; |
|
} |
|
|
|
if (rx_buf == NULL) { |
|
/* During the last iteration the buffer was released but the |
|
* application did not provide a new buffer. Stop RX and quit now. |
|
*/ |
|
data->rx_async_en = false; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (empty) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
buf_request = data_len == 0 && data->rx_buf_next == NULL; |
|
|
|
uint32_t read = ring_buf_get(data->rx_rb, &rx_buf[offset + data_len], |
|
buf_len - (offset + data_len)); |
|
data_len += read; |
|
data->rx_buf_data_len = data_len; |
|
|
|
if (offset + data_len >= data->rx_buf_len) { |
|
rx_rdy = true; |
|
data->rx_buf = NULL; |
|
data->rx_buf_len = 0; |
|
data->rx_buf_offset = 0; |
|
data->rx_buf_data_len = 0; |
|
} |
|
} |
|
|
|
if (!rx_en) { |
|
break; |
|
} |
|
|
|
if (rx_buf == NULL) { |
|
uart_emul_simple_event(dev, UART_RX_DISABLED); |
|
break; |
|
} |
|
|
|
if (empty && data->rx_buf_timeout != SYS_FOREVER_US) { |
|
(void)k_work_reschedule_for_queue(&uart_emul_work_q, &data->rx_timeout_work, |
|
K_USEC(data->rx_buf_timeout)); |
|
} |
|
|
|
if (buf_request) { |
|
uart_emul_simple_event(dev, UART_RX_BUF_REQUEST); |
|
} |
|
|
|
if (rx_rdy) { |
|
struct uart_event rx_rdy_event = { |
|
.type = UART_RX_RDY, |
|
.data.rx = { |
|
.buf = rx_buf, |
|
.offset = offset, |
|
.len = data_len, |
|
}, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_rdy_event); |
|
|
|
struct uart_event rx_buf_released_event = { |
|
.type = UART_RX_BUF_RELEASED, |
|
.data.rx_buf.buf = rx_buf, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_buf_released_event); |
|
} |
|
} while (rx_en && !empty); |
|
} |
|
|
|
static void uart_emul_async_tx_handler(struct k_work *work) |
|
{ |
|
struct uart_emul_data *data = CONTAINER_OF(work, struct uart_emul_data, tx_work); |
|
const struct device *dev = data->dev; |
|
const struct uart_emul_config *config = dev->config; |
|
|
|
uint32_t written; |
|
|
|
const uint8_t *tx_buf = NULL; |
|
size_t tx_buf_len = 0; |
|
size_t tx_buf_offset = 0; |
|
bool tx_done = true; |
|
|
|
K_SPINLOCK(&data->tx_lock) { |
|
tx_buf = data->tx_buf; |
|
tx_buf_len = data->tx_buf_len; |
|
tx_buf_offset = data->tx_buf_offset; |
|
|
|
if (!tx_buf) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
written = ring_buf_put(data->tx_rb, &data->tx_buf[tx_buf_offset], |
|
tx_buf_len - tx_buf_offset); |
|
tx_done = written == (tx_buf_len - tx_buf_offset); |
|
if (!tx_done) { |
|
data->tx_buf_offset += written; |
|
K_SPINLOCK_BREAK; |
|
} |
|
data->tx_buf = NULL; |
|
data->tx_buf_len = 0; |
|
data->tx_buf_offset = 0; |
|
} |
|
|
|
if (!tx_buf) { |
|
return; |
|
} |
|
|
|
if (config->loopback && written) { |
|
uint32_t loop_written = uart_emul_put_rx_data(dev, &tx_buf[tx_buf_offset], written); |
|
|
|
if (loop_written < written) { |
|
LOG_WRN("Lost %" PRIu32 " bytes on loopback", written - loop_written); |
|
} |
|
} |
|
|
|
uart_emul_tx_data_ready(dev); |
|
|
|
if ((config->loopback && written) || !written) { |
|
/* When using the loopback fixture, just allow to drop all bytes in the ring buffer |
|
* not consumed by tx_data_ready_cb(). |
|
*/ |
|
|
|
uint32_t flushed = uart_emul_flush_tx_data(dev); |
|
|
|
if (flushed) { |
|
if (written) { |
|
LOG_DBG("Flushed %" PRIu32 " unused bytes from tx buffer", flushed); |
|
} else { |
|
LOG_WRN("Flushed %" PRIu32 |
|
" unused bytes from tx buffer to break out of infinite " |
|
"loop! Consume or flush the bytes from the tx ring buffer " |
|
"in your test case to prevent this!", |
|
flushed); |
|
} |
|
} |
|
} |
|
|
|
if (!tx_done) { |
|
/* We are not done yet, yield back into workqueue. |
|
* |
|
* This would basically be an infinite loop when tx_data_ready_cb() does not consume |
|
* the bytes in the tx ring buffer. |
|
*/ |
|
k_work_submit_to_queue(&uart_emul_work_q, &data->tx_work); |
|
return; |
|
} |
|
|
|
struct uart_event tx_done_event = { |
|
.type = UART_TX_DONE, |
|
.data.tx = { |
|
.buf = tx_buf, |
|
.len = tx_buf_len, |
|
}, |
|
}; |
|
|
|
uart_emul_post_event(dev, &tx_done_event); |
|
} |
|
|
|
static void uart_emul_rx_stop(const struct device *dev, struct uart_emul_data *data) |
|
{ |
|
uint8_t *rx_buf = NULL; |
|
size_t rx_buf_offset = 0; |
|
size_t rx_buf_data_len = 0; |
|
|
|
k_work_cancel_delayable(&data->rx_timeout_work); |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
if (!data->rx_async_en) { |
|
K_SPINLOCK_BREAK; |
|
} |
|
rx_buf = data->rx_buf; |
|
rx_buf_offset = data->rx_buf_offset; |
|
rx_buf_data_len = data->rx_buf_data_len; |
|
|
|
data->rx_buf = NULL; |
|
data->rx_buf_len = 0; |
|
data->rx_buf_offset = 0; |
|
data->rx_buf_data_len = 0; |
|
data->rx_buf_next = NULL; |
|
data->rx_buf_next_len = 0; |
|
data->rx_async_en = false; |
|
data->rx_stopping = false; |
|
} |
|
|
|
if (rx_buf == NULL) { |
|
return; |
|
} |
|
|
|
if (rx_buf_data_len > 0) { |
|
struct uart_event rx_rdy_event = { |
|
.type = UART_RX_RDY, |
|
.data.rx = { |
|
.buf = rx_buf, |
|
.offset = rx_buf_offset, |
|
.len = rx_buf_data_len, |
|
}, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_rdy_event); |
|
} |
|
|
|
struct uart_event rx_buf_released_event = { |
|
.type = UART_RX_BUF_RELEASED, |
|
.data.rx_buf.buf = rx_buf, |
|
}; |
|
|
|
uart_emul_post_event(dev, &rx_buf_released_event); |
|
uart_emul_simple_event(dev, UART_RX_DISABLED); |
|
} |
|
|
|
static void uart_emul_async_rx_disable_handler(struct k_work *work) |
|
{ |
|
struct uart_emul_data *data = CONTAINER_OF(work, struct uart_emul_data, rx_disable_work); |
|
const struct device *dev = data->dev; |
|
|
|
uart_emul_rx_stop(dev, data); |
|
} |
|
|
|
static int uart_emul_callback_set(const struct device *dev, uart_callback_t callback, |
|
void *user_data) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
|
|
data->uart_callback = callback; |
|
data->callback_user_data = user_data; |
|
|
|
return 0; |
|
} |
|
|
|
static int uart_emul_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&data->tx_lock) { |
|
if (data->tx_buf) { |
|
ret = -EBUSY; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
data->tx_buf = buf; |
|
data->tx_buf_len = len; |
|
data->tx_buf_offset = 0; |
|
|
|
k_work_submit_to_queue(&uart_emul_work_q, &data->tx_work); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int uart_emul_tx_abort(const struct device *dev) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
const uint8_t *tx_buf = NULL; |
|
size_t tx_buf_sent; |
|
|
|
K_SPINLOCK(&data->tx_lock) { |
|
tx_buf = data->tx_buf; |
|
tx_buf_sent = data->tx_buf_offset; |
|
|
|
data->tx_buf = NULL; |
|
data->tx_buf_len = 0; |
|
data->tx_buf_offset = 0; |
|
|
|
k_work_cancel(&data->tx_work); |
|
} |
|
|
|
if (!tx_buf) { |
|
return -EFAULT; |
|
} |
|
|
|
struct uart_event tx_aborted_event = { |
|
.type = UART_TX_ABORTED, |
|
.data.tx = { |
|
.buf = tx_buf, |
|
.len = tx_buf_sent, |
|
}, |
|
}; |
|
|
|
uart_emul_post_event(dev, &tx_aborted_event); |
|
|
|
return 0; |
|
} |
|
|
|
static int uart_emul_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
if (!data->rx_async_en) { |
|
ret = -EACCES; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (data->rx_buf_next != NULL) { |
|
ret = -EBUSY; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
data->rx_buf_next = buf; |
|
data->rx_buf_next_len = len; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int uart_emul_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
int ret = 0; |
|
bool rx_stopping; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
rx_stopping = data->rx_stopping; |
|
k_work_cancel(&data->rx_disable_work); |
|
} |
|
|
|
if (rx_stopping) { |
|
uart_emul_rx_stop(dev, data); |
|
} |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
if (data->rx_async_en) { |
|
ret = -EBUSY; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
data->rx_async_en = true; |
|
data->rx_buf = buf; |
|
data->rx_buf_len = len; |
|
data->rx_buf_timeout = timeout; |
|
data->rx_buf_offset = 0; |
|
data->rx_buf_data_len = 0; |
|
data->rx_buf_next = NULL; |
|
data->rx_buf_next_len = 0; |
|
|
|
if (!ring_buf_is_empty(data->rx_rb)) { |
|
(void)k_work_submit_to_queue(&uart_emul_work_q, &data->rx_work); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int uart_emul_rx_disable(const struct device *dev) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&data->rx_lock) { |
|
if (!data->rx_async_en) { |
|
ret = -EFAULT; |
|
K_SPINLOCK_BREAK; |
|
} |
|
data->rx_stopping = true; |
|
k_work_submit_to_queue(&uart_emul_work_q, &data->rx_disable_work); |
|
} |
|
|
|
return ret; |
|
} |
|
#endif /* CONFIG_UART_ASYNC_API */ |
|
|
|
static DEVICE_API(uart, uart_emul_api) = { |
|
.poll_in = uart_emul_poll_in, |
|
.poll_out = uart_emul_poll_out, |
|
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
|
.config_get = uart_emul_config_get, |
|
.configure = uart_emul_configure, |
|
#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, |
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
|
#ifdef CONFIG_UART_ASYNC_API |
|
.callback_set = uart_emul_callback_set, |
|
.tx = uart_emul_tx, |
|
.tx_abort = uart_emul_tx_abort, |
|
.rx_enable = uart_emul_rx_enable, |
|
.rx_buf_rsp = uart_emul_rx_buf_rsp, |
|
.rx_disable = uart_emul_rx_disable, |
|
#endif /* CONFIG_UART_ASYNC_API */ |
|
}; |
|
|
|
void uart_emul_callback_tx_data_ready_set(const struct device *dev, |
|
uart_emul_callback_tx_data_ready_t cb, void *user_data) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
|
|
drv_data->tx_data_ready_cb = cb; |
|
drv_data->user_data = user_data; |
|
} |
|
|
|
uint32_t uart_emul_put_rx_data(const struct device *dev, const uint8_t *data, size_t size) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
uint32_t count; |
|
__unused bool empty; |
|
__unused bool irq_en; |
|
__unused bool rx_en; |
|
|
|
K_SPINLOCK(&drv_data->rx_lock) { |
|
count = ring_buf_put(drv_data->rx_rb, data, size); |
|
empty = ring_buf_is_empty(drv_data->rx_rb); |
|
IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, (irq_en = drv_data->rx_irq_en;)); |
|
IF_ENABLED(CONFIG_UART_ASYNC_API, (rx_en = drv_data->rx_async_en;)); |
|
} |
|
|
|
if (count < size) { |
|
uart_emul_set_errors(dev, UART_ERROR_OVERRUN); |
|
} |
|
|
|
IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, ( |
|
if (count > 0 && irq_en && !empty) { |
|
(void)k_work_submit_to_queue(&uart_emul_work_q, &drv_data->irq_work); |
|
} |
|
)) |
|
IF_ENABLED(CONFIG_UART_ASYNC_API, ( |
|
if (count > 0 && rx_en && !empty) { |
|
(void)k_work_submit_to_queue(&uart_emul_work_q, &drv_data->rx_work); |
|
} |
|
)) |
|
|
|
return count; |
|
} |
|
|
|
uint32_t uart_emul_get_tx_data(const struct device *dev, uint8_t *data, size_t size) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t count; |
|
|
|
key = k_spin_lock(&drv_data->tx_lock); |
|
count = ring_buf_get(drv_data->tx_rb, data, size); |
|
k_spin_unlock(&drv_data->tx_lock, key); |
|
return count; |
|
} |
|
|
|
uint32_t uart_emul_flush_rx_data(const struct device *dev) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t count; |
|
|
|
key = k_spin_lock(&drv_data->rx_lock); |
|
count = ring_buf_size_get(drv_data->rx_rb); |
|
ring_buf_reset(drv_data->rx_rb); |
|
k_spin_unlock(&drv_data->rx_lock, key); |
|
return count; |
|
} |
|
|
|
uint32_t uart_emul_flush_tx_data(const struct device *dev) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t count; |
|
|
|
key = k_spin_lock(&drv_data->tx_lock); |
|
count = ring_buf_size_get(drv_data->tx_rb); |
|
ring_buf_reset(drv_data->tx_rb); |
|
k_spin_unlock(&drv_data->tx_lock, key); |
|
return count; |
|
} |
|
|
|
void uart_emul_set_errors(const struct device *dev, int errors) |
|
{ |
|
struct uart_emul_data *drv_data = dev->data; |
|
|
|
drv_data->errors |= errors; |
|
} |
|
|
|
void uart_emul_set_release_buffer_on_timeout(const struct device *dev, bool release_on_timeout) |
|
{ |
|
__unused struct uart_emul_data *drv_data = dev->data; |
|
|
|
IF_ENABLED(CONFIG_UART_ASYNC_API, (drv_data->rx_release_on_timeout = release_on_timeout;)); |
|
} |
|
|
|
int uart_emul_register(const struct device *dev, struct uart_emul *emul) |
|
{ |
|
struct uart_emul_data *data = dev->data; |
|
|
|
sys_slist_append(&data->emuls, &emul->node); |
|
|
|
return 0; |
|
} |
|
|
|
#define UART_EMUL_RX_FIFO_SIZE(inst) (DT_INST_PROP(inst, rx_fifo_size)) |
|
#define UART_EMUL_TX_FIFO_SIZE(inst) (DT_INST_PROP(inst, tx_fifo_size)) |
|
|
|
#define EMUL_LINK_AND_COMMA(node_id) \ |
|
{ \ |
|
.dev = DEVICE_DT_GET(node_id), \ |
|
}, |
|
|
|
#define DEFINE_UART_EMUL(inst) \ |
|
static const struct emul_link_for_bus emuls_##inst[] = { \ |
|
DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(inst), EMUL_LINK_AND_COMMA)}; \ |
|
\ |
|
RING_BUF_DECLARE(uart_emul_##inst##_rx_rb, UART_EMUL_RX_FIFO_SIZE(inst)); \ |
|
RING_BUF_DECLARE(uart_emul_##inst##_tx_rb, UART_EMUL_TX_FIFO_SIZE(inst)); \ |
|
\ |
|
static const struct uart_emul_config uart_emul_cfg_##inst = { \ |
|
.loopback = DT_INST_PROP(inst, loopback), \ |
|
.latch_buffer_size = DT_INST_PROP(inst, latch_buffer_size), \ |
|
.emul_list = { \ |
|
.children = emuls_##inst, \ |
|
.num_children = ARRAY_SIZE(emuls_##inst), \ |
|
}, \ |
|
}; \ |
|
static struct uart_emul_data uart_emul_data_##inst = { \ |
|
.emuls = SYS_SLIST_STATIC_INIT(&_CONCAT(uart_emul_data_, inst).emuls), \ |
|
.dev = DEVICE_DT_INST_GET(inst), \ |
|
.rx_rb = &uart_emul_##inst##_rx_rb, \ |
|
.tx_rb = &uart_emul_##inst##_tx_rb, \ |
|
IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, \ |
|
(.irq_work = Z_WORK_INITIALIZER(uart_emul_irq_handler),)) \ |
|
IF_ENABLED(CONFIG_UART_ASYNC_API, \ |
|
(.tx_work = Z_WORK_INITIALIZER(uart_emul_async_tx_handler), \ |
|
.rx_timeout_work = Z_WORK_DELAYABLE_INITIALIZER( \ |
|
uart_emul_async_rx_timeout_handler), \ |
|
.rx_work = Z_WORK_INITIALIZER(uart_emul_async_rx_handler), \ |
|
.rx_disable_work = Z_WORK_INITIALIZER( \ |
|
uart_emul_async_rx_disable_handler),)) \ |
|
}; \ |
|
\ |
|
static int uart_emul_post_init_##inst(void) \ |
|
{ \ |
|
return emul_init_for_bus(DEVICE_DT_INST_GET(inst)); \ |
|
} \ |
|
SYS_INIT(uart_emul_post_init_##inst, POST_KERNEL, CONFIG_UART_EMUL_DEVICE_INIT_PRIORITY); \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &uart_emul_data_##inst, &uart_emul_cfg_##inst, \ |
|
PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &uart_emul_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DEFINE_UART_EMUL)
|
|
|