/** @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 LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL); #include #include #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) active = strncmp(CONFIG_UART_MUX_DEVICE_NAME, iface->dev->name, sizeof(CONFIG_UART_MUX_DEVICE_NAME) - 1) == 0; #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; }