/* * Copyright (c) 2021, Thomas Stranger * * SPDX-License-Identifier: Apache-2.0 */ /* * This is not a real serial driver. It is used to instantiate struct * devices for the "vnd,serial" devicetree compatible used in test code. */ #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(mock_serial, CONFIG_LOG_DEFAULT_LEVEL); #define DT_DRV_COMPAT vnd_serial struct serial_vnd_data { #ifdef CONFIG_RING_BUFFER struct ring_buf *written; struct ring_buf *read_queue; #endif serial_vnd_write_cb_t callback; void *callback_data; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_callback_user_data_t irq_isr; bool irq_rx_enabled; bool irq_tx_enabled; #endif #ifdef CONFIG_UART_ASYNC_API uart_callback_t async_cb; void *async_cb_user_data; uint8_t *read_buf; size_t read_size; size_t read_position; #endif }; #ifdef CONFIG_UART_INTERRUPT_DRIVEN static bool is_irq_rx_pending(const struct device *dev) { struct serial_vnd_data *data = dev->data; return !ring_buf_is_empty(data->read_queue); } static bool is_irq_tx_pending(const struct device *dev) { struct serial_vnd_data *data = dev->data; return ring_buf_space_get(data->written) != 0; } static void irq_process(const struct device *dev) { struct serial_vnd_data *data = dev->data; for (;;) { bool rx_rdy = is_irq_rx_pending(dev); bool tx_rdy = is_irq_tx_pending(dev); bool rx_int = rx_rdy && data->irq_rx_enabled; bool tx_int = tx_rdy && data->irq_tx_enabled; LOG_DBG("rx_rdy %d tx_rdy %d", rx_rdy, tx_rdy); LOG_DBG("rx_int %d tx_int %d", rx_int, tx_int); if (!(rx_int || tx_int)) { break; } LOG_DBG("isr"); if (!data->irq_isr) { LOG_ERR("no isr registered"); break; } data->irq_isr(dev, NULL); }; } static void irq_rx_enable(const struct device *dev) { struct serial_vnd_data *data = dev->data; data->irq_rx_enabled = true; LOG_DBG("rx enabled"); irq_process(dev); } static void irq_rx_disable(const struct device *dev) { struct serial_vnd_data *data = dev->data; data->irq_rx_enabled = false; LOG_DBG("rx disabled"); } static int irq_rx_ready(const struct device *dev) { struct serial_vnd_data *data = dev->data; bool ready = !ring_buf_is_empty(data->read_queue); LOG_DBG("rx ready: %d", ready); return ready; } static void irq_tx_enable(const struct device *dev) { struct serial_vnd_data *data = dev->data; LOG_DBG("tx enabled"); data->irq_tx_enabled = true; irq_process(dev); } static void irq_tx_disable(const struct device *dev) { struct serial_vnd_data *data = dev->data; data->irq_tx_enabled = false; LOG_DBG("tx disabled"); } static int irq_tx_ready(const struct device *dev) { struct serial_vnd_data *data = dev->data; bool ready = (ring_buf_space_get(data->written) != 0); LOG_DBG("tx ready: %d", ready); return ready; } static void irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *user_data) { struct serial_vnd_data *data = dev->data; /* Not implemented. Ok because `user_data` is always NULL in the current * implementation of core UART API. */ __ASSERT_NO_MSG(user_data == NULL); #if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS) && defined(CONFIG_UART_ASYNC_API) if (data->read_buf) { LOG_ERR("Setting callback to NULL while asynchronous API is in use."); } data->async_cb = NULL; data->async_cb_user_data = NULL; #endif data->irq_isr = cb; LOG_DBG("callback set"); } static int fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) { struct serial_vnd_data *data = dev->data; uint32_t write_len = ring_buf_put(data->written, tx_data, size); if (data->callback) { data->callback(dev, data->callback_data); } return write_len; } static int fifo_read(const struct device *dev, uint8_t *rx_data, const int size) { struct serial_vnd_data *data = dev->data; int read_len = ring_buf_get(data->read_queue, rx_data, size); LOG_HEXDUMP_DBG(rx_data, read_len, ""); return read_len; } #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ static int serial_vnd_poll_in(const struct device *dev, unsigned char *c) { #ifdef CONFIG_RING_BUFFER struct serial_vnd_data *data = dev->data; uint32_t bytes_read; if (data == NULL || data->read_queue == NULL) { return -ENOTSUP; } bytes_read = ring_buf_get(data->read_queue, c, 1); if (bytes_read == 1) { return 0; } return -1; #else return -ENOTSUP; #endif } static void serial_vnd_poll_out(const struct device *dev, unsigned char c) { struct serial_vnd_data *data = dev->data; #ifdef CONFIG_RING_BUFFER if (data == NULL || data->written == NULL) { return; } ring_buf_put(data->written, &c, 1); #endif if (data->callback) { data->callback(dev, data->callback_data); } } #ifdef CONFIG_UART_ASYNC_API static void async_rx_run(const struct device *dev); #endif #ifdef CONFIG_RING_BUFFER int serial_vnd_queue_in_data(const struct device *dev, const unsigned char *c, uint32_t size) { struct serial_vnd_data *data = dev->data; int write_size; if (data == NULL || data->read_queue == NULL) { return -ENOTSUP; } write_size = ring_buf_put(data->read_queue, c, size); LOG_DBG("size %u write_size %u", size, write_size); LOG_HEXDUMP_DBG(c, write_size, ""); #ifdef CONFIG_UART_INTERRUPT_DRIVEN if (write_size > 0) { irq_process(dev); } #endif #ifdef CONFIG_UART_ASYNC_API async_rx_run(dev); #endif return write_size; } uint32_t serial_vnd_out_data_size_get(const struct device *dev) { struct serial_vnd_data *data = dev->data; if (data == NULL || data->written == NULL) { return -ENOTSUP; } return ring_buf_size_get(data->written); } uint32_t serial_vnd_read_out_data(const struct device *dev, unsigned char *out_data, uint32_t size) { struct serial_vnd_data *data = dev->data; if (data == NULL || data->written == NULL) { return -ENOTSUP; } return ring_buf_get(data->written, out_data, size); } uint32_t serial_vnd_peek_out_data(const struct device *dev, unsigned char *out_data, uint32_t size) { struct serial_vnd_data *data = dev->data; if (data == NULL || data->written == NULL) { return -ENOTSUP; } return ring_buf_peek(data->written, out_data, size); } #endif void serial_vnd_set_callback(const struct device *dev, serial_vnd_write_cb_t callback, void *user_data) { struct serial_vnd_data *data = dev->data; if (data == NULL) { return; } data->callback = callback; data->callback_data = user_data; } static int serial_vnd_err_check(const struct device *dev) { return -ENOTSUP; } #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE static int serial_vnd_configure(const struct device *dev, const struct uart_config *cfg) { return -ENOTSUP; } static int serial_vnd_config_get(const struct device *dev, struct uart_config *cfg) { return -ENOTSUP; } #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ #ifdef CONFIG_UART_ASYNC_API static int serial_vnd_callback_set(const struct device *dev, uart_callback_t callback, void *user_data) { struct serial_vnd_data *data = dev->data; if (data == NULL) { return -ENOTSUP; } #if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS) && defined(CONFIG_UART_INTERRUPT_DRIVEN) data->irq_isr = NULL; #endif if (callback == NULL && data->read_buf) { LOG_ERR("Setting callback to NULL while asynchronous API is in use."); } data->async_cb = callback; data->async_cb_user_data = user_data; return 0; } static int serial_vnd_api_tx(const struct device *dev, const uint8_t *tx_data, size_t len, int32_t timeout) { struct serial_vnd_data *data = dev->data; struct uart_event evt; uint32_t write_len; if (data == NULL) { return -ENOTSUP; } if (data->async_cb == NULL) { return -EINVAL; } write_len = ring_buf_put(data->written, tx_data, len); if (data->callback) { data->callback(dev, data->callback_data); } __ASSERT(write_len == len, "Ring buffer full. Async wait not implemented."); evt = (struct uart_event){ .type = UART_TX_DONE, .data.tx.buf = tx_data, .data.tx.len = len, }; data->async_cb(dev, &evt, data->async_cb_user_data); return 0; } static void async_rx_run(const struct device *dev) { struct serial_vnd_data *data = dev->data; struct uart_event evt; uint32_t read_len; uint32_t read_remaining; if (!data->read_buf) { return; } __ASSERT_NO_MSG(data->async_cb); read_remaining = data->read_size - data->read_position; read_len = ring_buf_get(data->read_queue, &data->read_buf[data->read_position], read_remaining); if (read_len != 0) { evt = (struct uart_event){ .type = UART_RX_RDY, .data.rx.buf = data->read_buf, .data.rx.len = read_len, .data.rx.offset = data->read_position, }; data->async_cb(dev, &evt, data->async_cb_user_data); } data->read_position += read_len; if (data->read_position == data->read_size) { data->read_buf = NULL; evt = (struct uart_event){ .type = UART_RX_DISABLED, }; data->async_cb(dev, &evt, data->async_cb_user_data); } } static int serial_vnd_rx_enable(const struct device *dev, uint8_t *read_buf, size_t read_size, int32_t timeout) { struct serial_vnd_data *data = dev->data; LOG_WRN("read_size %zd", read_size); if (data == NULL) { return -ENOTSUP; } if (data->async_cb == NULL) { return -EINVAL; } __ASSERT(timeout == SYS_FOREVER_MS, "Async timeout not implemented."); data->read_buf = read_buf; data->read_size = read_size; data->read_position = 0; async_rx_run(dev); return 0; } #endif /* CONFIG_UART_ASYNC_API */ static const struct uart_driver_api serial_vnd_api = { .poll_in = serial_vnd_poll_in, .poll_out = serial_vnd_poll_out, .err_check = serial_vnd_err_check, #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE .configure = serial_vnd_configure, .config_get = serial_vnd_config_get, #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ #ifdef CONFIG_UART_INTERRUPT_DRIVEN .irq_callback_set = irq_callback_set, .irq_rx_enable = irq_rx_enable, .irq_rx_disable = irq_rx_disable, .irq_rx_ready = irq_rx_ready, .irq_tx_enable = irq_tx_enable, .irq_tx_disable = irq_tx_disable, .irq_tx_ready = irq_tx_ready, .fifo_read = fifo_read, .fifo_fill = fifo_fill, #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ #ifdef CONFIG_UART_ASYNC_API .callback_set = serial_vnd_callback_set, .tx = serial_vnd_api_tx, .rx_enable = serial_vnd_rx_enable, #endif /* CONFIG_UART_ASYNC_API */ }; #define VND_SERIAL_DATA_BUFFER(n) \ RING_BUF_DECLARE(written_data_##n, DT_INST_PROP(n, buffer_size)); \ RING_BUF_DECLARE(read_queue_##n, DT_INST_PROP(n, buffer_size)); \ static struct serial_vnd_data serial_vnd_data_##n = { \ .written = &written_data_##n, \ .read_queue = &read_queue_##n, \ }; #define VND_SERIAL_DATA(n) static struct serial_vnd_data serial_vnd_data_##n = {}; #define VND_SERIAL_INIT(n) \ COND_CODE_1(DT_INST_NODE_HAS_PROP(n, buffer_size), (VND_SERIAL_DATA_BUFFER(n)), \ (VND_SERIAL_DATA(n))) \ DEVICE_DT_INST_DEFINE(n, NULL, NULL, &serial_vnd_data_##n, NULL, POST_KERNEL, \ CONFIG_SERIAL_INIT_PRIORITY, &serial_vnd_api); DT_INST_FOREACH_STATUS_OKAY(VND_SERIAL_INIT)