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