1 /** @file
2 * @brief interface for modem context
3 *
4 * UART-based modem interface implementation for modem context driver.
5 */
6
7 /*
8 * Copyright (c) 2019 Foundries.io
9 *
10 * SPDX-License-Identifier: Apache-2.0
11 */
12
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL);
15
16 #include <zephyr/kernel.h>
17 #include <zephyr/drivers/uart.h>
18
19 #include "modem_context.h"
20 #include "modem_iface_uart.h"
21
22 /**
23 * @brief Drains UART.
24 *
25 * @note Discards remaining data.
26 *
27 * @param iface: modem interface.
28 *
29 * @retval None.
30 */
modem_iface_uart_flush(struct modem_iface * iface)31 static void modem_iface_uart_flush(struct modem_iface *iface)
32 {
33 uint8_t c;
34
35 while (uart_fifo_read(iface->dev, &c, 1) > 0) {
36 continue;
37 }
38 }
39
40 /**
41 * @brief Modem interface interrupt handler.
42 *
43 * @note Fills interfaces ring buffer with received data.
44 * When ring buffer is full the data is discarded.
45 *
46 * @param uart_dev: uart device.
47 *
48 * @retval None.
49 */
modem_iface_uart_isr(const struct device * uart_dev,void * user_data)50 static void modem_iface_uart_isr(const struct device *uart_dev,
51 void *user_data)
52 {
53 struct modem_context *ctx;
54 struct modem_iface_uart_data *data;
55 int rx = 0, ret;
56 uint8_t *dst;
57 uint32_t partial_size = 0;
58 uint32_t total_size = 0;
59
60 ARG_UNUSED(user_data);
61
62 /* lookup the modem context */
63 ctx = modem_context_from_iface_dev(uart_dev);
64 if (!ctx || !ctx->iface.iface_data) {
65 return;
66 }
67
68 data = (struct modem_iface_uart_data *)(ctx->iface.iface_data);
69 /* get all of the data off UART as fast as we can */
70 while (uart_irq_update(ctx->iface.dev) &&
71 uart_irq_rx_ready(ctx->iface.dev)) {
72 if (!partial_size) {
73 partial_size = ring_buf_put_claim(&data->rx_rb, &dst,
74 UINT32_MAX);
75 }
76 if (!partial_size) {
77 if (data->hw_flow_control) {
78 uart_irq_rx_disable(ctx->iface.dev);
79 } else {
80 LOG_ERR("Rx buffer doesn't have enough space");
81 modem_iface_uart_flush(&ctx->iface);
82 }
83 break;
84 }
85
86 rx = uart_fifo_read(ctx->iface.dev, dst, partial_size);
87 if (rx <= 0) {
88 continue;
89 }
90
91 dst += rx;
92 total_size += rx;
93 partial_size -= rx;
94 }
95
96 ret = ring_buf_put_finish(&data->rx_rb, total_size);
97 __ASSERT_NO_MSG(ret == 0);
98
99 if (total_size > 0) {
100 k_sem_give(&data->rx_sem);
101 }
102 }
103
modem_iface_uart_read(struct modem_iface * iface,uint8_t * buf,size_t size,size_t * bytes_read)104 static int modem_iface_uart_read(struct modem_iface *iface,
105 uint8_t *buf, size_t size, size_t *bytes_read)
106 {
107 struct modem_iface_uart_data *data;
108
109 if (!iface || !iface->iface_data) {
110 return -EINVAL;
111 }
112
113 if (size == 0) {
114 *bytes_read = 0;
115 return 0;
116 }
117
118 data = (struct modem_iface_uart_data *)(iface->iface_data);
119 *bytes_read = ring_buf_get(&data->rx_rb, buf, size);
120
121 if (data->hw_flow_control && *bytes_read == 0) {
122 uart_irq_rx_enable(iface->dev);
123 }
124
125 return 0;
126 }
127
mux_is_active(struct modem_iface * iface)128 static bool mux_is_active(struct modem_iface *iface)
129 {
130 bool active = false;
131
132 #if defined(CONFIG_UART_MUX_DEVICE_NAME)
133 active = strncmp(CONFIG_UART_MUX_DEVICE_NAME, iface->dev->name,
134 sizeof(CONFIG_UART_MUX_DEVICE_NAME) - 1) == 0;
135 #endif /* CONFIG_UART_MUX_DEVICE_NAME */
136
137 return active;
138 }
139
modem_iface_uart_write(struct modem_iface * iface,const uint8_t * buf,size_t size)140 static int modem_iface_uart_write(struct modem_iface *iface,
141 const uint8_t *buf, size_t size)
142 {
143 if (!iface || !iface->iface_data) {
144 return -EINVAL;
145 }
146
147 if (size == 0) {
148 return 0;
149 }
150
151 /* If we're using gsm_mux, We don't want to use poll_out because sending
152 * one byte at a time causes each byte to get wrapped in muxing headers.
153 * But we can safely call uart_fifo_fill outside of ISR context when
154 * muxing because uart_mux implements it in software.
155 */
156 if (mux_is_active(iface)) {
157 uart_fifo_fill(iface->dev, buf, size);
158 } else {
159 do {
160 uart_poll_out(iface->dev, *buf++);
161 } while (--size);
162 }
163
164 return 0;
165 }
166
modem_iface_uart_init_dev(struct modem_iface * iface,const struct device * dev)167 int modem_iface_uart_init_dev(struct modem_iface *iface,
168 const struct device *dev)
169 {
170 /* get UART device */
171 const struct device *prev = iface->dev;
172
173 if (!device_is_ready(dev)) {
174 return -ENODEV;
175 }
176
177 /* Check if there's already a device inited to this iface. If so,
178 * interrupts needs to be disabled on that too before switching to avoid
179 * race conditions with modem_iface_uart_isr.
180 */
181 if (prev) {
182 uart_irq_tx_disable(prev);
183 uart_irq_rx_disable(prev);
184 }
185
186 uart_irq_rx_disable(dev);
187 uart_irq_tx_disable(dev);
188 iface->dev = dev;
189
190 modem_iface_uart_flush(iface);
191 uart_irq_callback_set(iface->dev, modem_iface_uart_isr);
192 uart_irq_rx_enable(iface->dev);
193
194 if (prev) {
195 uart_irq_rx_enable(prev);
196 }
197
198 return 0;
199 }
200
modem_iface_uart_init(struct modem_iface * iface,struct modem_iface_uart_data * data,const struct modem_iface_uart_config * config)201 int modem_iface_uart_init(struct modem_iface *iface, struct modem_iface_uart_data *data,
202 const struct modem_iface_uart_config *config)
203 {
204 int ret;
205
206 if (iface == NULL || data == NULL || config == NULL) {
207 return -EINVAL;
208 }
209
210 iface->iface_data = data;
211 iface->read = modem_iface_uart_read;
212 iface->write = modem_iface_uart_write;
213
214 ring_buf_init(&data->rx_rb, config->rx_rb_buf_len, config->rx_rb_buf);
215 k_sem_init(&data->rx_sem, 0, 1);
216
217 /* Configure hardware flow control */
218 data->hw_flow_control = config->hw_flow_control;
219
220 /* Get UART device */
221 ret = modem_iface_uart_init_dev(iface, config->dev);
222 if (ret < 0) {
223 iface->iface_data = NULL;
224 iface->read = NULL;
225 iface->write = NULL;
226
227 return ret;
228 }
229
230 return 0;
231 }
232