| /** @file |
| * @brief interface for modem context |
| * |
| * UART-based modem interface implementation for modem context driver. |
| */ |
| |
| /* |
| * Copyright (c) 2019 Foundries.io |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL); |
| |
| #include <kernel.h> |
| #include <drivers/uart.h> |
| |
| #include "modem_context.h" |
| #include "modem_iface_uart.h" |
| |
| /** |
| * @brief Drains UART. |
| * |
| * @note Discards remaining data. |
| * |
| * @param *iface: modem interface. |
| * |
| * @retval None. |
| */ |
| static void modem_iface_uart_flush(struct modem_iface *iface) |
| { |
| uint8_t c; |
| |
| while (uart_fifo_read(iface->dev, &c, 1) > 0) { |
| continue; |
| } |
| } |
| |
| /** |
| * @brief Modem interface interrupt handler. |
| * |
| * @note Fills interfaces ring buffer with received data. |
| * When ring buffer is full the data is discarded. |
| * |
| * @param *uart_dev: uart device. |
| * |
| * @retval None. |
| */ |
| static void modem_iface_uart_isr(const struct device *uart_dev, |
| void *user_data) |
| { |
| struct modem_context *ctx; |
| struct modem_iface_uart_data *data; |
| int rx = 0, ret; |
| uint8_t *dst; |
| uint32_t partial_size = 0; |
| uint32_t total_size = 0; |
| |
| ARG_UNUSED(user_data); |
| |
| /* lookup the modem context */ |
| ctx = modem_context_from_iface_dev(uart_dev); |
| if (!ctx || !ctx->iface.iface_data) { |
| return; |
| } |
| |
| data = (struct modem_iface_uart_data *)(ctx->iface.iface_data); |
| /* get all of the data off UART as fast as we can */ |
| while (uart_irq_update(ctx->iface.dev) && |
| uart_irq_rx_ready(ctx->iface.dev)) { |
| if (!partial_size) { |
| partial_size = ring_buf_put_claim(&data->rx_rb, &dst, |
| UINT32_MAX); |
| } |
| if (!partial_size) { |
| if (data->hw_flow_control) { |
| uart_irq_rx_disable(ctx->iface.dev); |
| } else { |
| LOG_ERR("Rx buffer doesn't have enough space"); |
| modem_iface_uart_flush(&ctx->iface); |
| } |
| break; |
| } |
| |
| rx = uart_fifo_read(ctx->iface.dev, dst, partial_size); |
| if (rx <= 0) { |
| continue; |
| } |
| |
| dst += rx; |
| total_size += rx; |
| partial_size -= rx; |
| } |
| |
| ret = ring_buf_put_finish(&data->rx_rb, total_size); |
| __ASSERT_NO_MSG(ret == 0); |
| |
| if (total_size > 0) { |
| k_sem_give(&data->rx_sem); |
| } |
| } |
| |
| static int modem_iface_uart_read(struct modem_iface *iface, |
| uint8_t *buf, size_t size, size_t *bytes_read) |
| { |
| struct modem_iface_uart_data *data; |
| |
| if (!iface || !iface->iface_data) { |
| return -EINVAL; |
| } |
| |
| if (size == 0) { |
| *bytes_read = 0; |
| return 0; |
| } |
| |
| data = (struct modem_iface_uart_data *)(iface->iface_data); |
| *bytes_read = ring_buf_get(&data->rx_rb, buf, size); |
| |
| if (data->hw_flow_control && *bytes_read == 0) { |
| uart_irq_rx_enable(iface->dev); |
| } |
| |
| return 0; |
| } |
| |
| static bool mux_is_active(struct modem_iface *iface) |
| { |
| bool active = false; |
| |
| #if defined(CONFIG_UART_MUX_DEVICE_NAME) |
| const char *mux_name = CONFIG_UART_MUX_DEVICE_NAME; |
| active = (mux_name == iface->dev->name); |
| #endif /* CONFIG_UART_MUX_DEVICE_NAME */ |
| |
| return active; |
| } |
| |
| static int modem_iface_uart_write(struct modem_iface *iface, |
| const uint8_t *buf, size_t size) |
| { |
| if (!iface || !iface->iface_data) { |
| return -EINVAL; |
| } |
| |
| if (size == 0) { |
| return 0; |
| } |
| |
| /* If we're using gsm_mux, We don't want to use poll_out because sending |
| * one byte at a time causes each byte to get wrapped in muxing headers. |
| * But we can safely call uart_fifo_fill outside of ISR context when |
| * muxing because uart_mux implements it in software. |
| */ |
| if (mux_is_active(iface)) { |
| uart_fifo_fill(iface->dev, buf, size); |
| } else { |
| do { |
| uart_poll_out(iface->dev, *buf++); |
| } while (--size); |
| } |
| |
| return 0; |
| } |
| |
| int modem_iface_uart_init_dev(struct modem_iface *iface, |
| const struct device *dev) |
| { |
| /* get UART device */ |
| const struct device *prev = iface->dev; |
| |
| if (!device_is_ready(dev)) { |
| return -ENODEV; |
| } |
| |
| /* Check if there's already a device inited to this iface. If so, |
| * interrupts needs to be disabled on that too before switching to avoid |
| * race conditions with modem_iface_uart_isr. |
| */ |
| if (prev) { |
| uart_irq_tx_disable(prev); |
| uart_irq_rx_disable(prev); |
| } |
| |
| uart_irq_rx_disable(dev); |
| uart_irq_tx_disable(dev); |
| iface->dev = dev; |
| |
| modem_iface_uart_flush(iface); |
| uart_irq_callback_set(iface->dev, modem_iface_uart_isr); |
| uart_irq_rx_enable(iface->dev); |
| |
| if (prev) { |
| uart_irq_rx_enable(prev); |
| } |
| |
| return 0; |
| } |
| |
| int modem_iface_uart_init(struct modem_iface *iface, |
| struct modem_iface_uart_data *data, |
| const struct device *dev) |
| { |
| int ret; |
| |
| if (!iface || !data) { |
| return -EINVAL; |
| } |
| |
| iface->iface_data = data; |
| iface->read = modem_iface_uart_read; |
| iface->write = modem_iface_uart_write; |
| |
| ring_buf_init(&data->rx_rb, data->rx_rb_buf_len, data->rx_rb_buf); |
| k_sem_init(&data->rx_sem, 0, 1); |
| |
| /* get UART device */ |
| ret = modem_iface_uart_init_dev(iface, dev); |
| if (ret < 0) { |
| iface->iface_data = NULL; |
| iface->read = NULL; |
| iface->write = NULL; |
| |
| return ret; |
| } |
| |
| return 0; |
| } |