1 /*
2  * Copyright (c) 2022 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * This library uses CMUX to create multiple data channels, called DLCIs, on a single serial bus.
9  * Each DLCI has an address from 1 to 63. DLCI address 0 is reserved for control commands.
10  *
11  * Design overview:
12  *
13  *     DLCI1 <-----------+                              +-------> DLCI1
14  *                       v                              v
15  *     DLCI2 <---> CMUX instance <--> Serial bus <--> Client <--> DLCI2
16  *                       ^                              ^
17  *     DLCI3 <-----------+                              +-------> DLCI3
18  *
19  * Writing to and from the CMUX instances is done using the modem_pipe API.
20  */
21 
22 #include <zephyr/kernel.h>
23 #include <zephyr/types.h>
24 #include <zephyr/sys/ring_buffer.h>
25 #include <zephyr/sys/atomic.h>
26 
27 #include <zephyr/modem/pipe.h>
28 #include <zephyr/modem/stats.h>
29 
30 #ifndef ZEPHYR_MODEM_CMUX_
31 #define ZEPHYR_MODEM_CMUX_
32 
33 #ifdef __cplusplus
34 extern "C" {
35 #endif
36 
37 /**
38  * @brief Modem CMUX
39  * @defgroup modem_cmux Modem CMUX
40  * @ingroup modem
41  * @{
42  */
43 
44 struct modem_cmux;
45 
46 enum modem_cmux_event {
47 	MODEM_CMUX_EVENT_CONNECTED = 0,
48 	MODEM_CMUX_EVENT_DISCONNECTED,
49 };
50 
51 typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_event event,
52 				    void *user_data);
53 
54 /**
55  * @cond INTERNAL_HIDDEN
56  */
57 
58 enum modem_cmux_state {
59 	MODEM_CMUX_STATE_DISCONNECTED = 0,
60 	MODEM_CMUX_STATE_CONNECTING,
61 	MODEM_CMUX_STATE_CONNECTED,
62 	MODEM_CMUX_STATE_DISCONNECTING,
63 };
64 
65 enum modem_cmux_receive_state {
66 	MODEM_CMUX_RECEIVE_STATE_SOF = 0,
67 	MODEM_CMUX_RECEIVE_STATE_RESYNC,
68 	MODEM_CMUX_RECEIVE_STATE_ADDRESS,
69 	MODEM_CMUX_RECEIVE_STATE_ADDRESS_CONT,
70 	MODEM_CMUX_RECEIVE_STATE_CONTROL,
71 	MODEM_CMUX_RECEIVE_STATE_LENGTH,
72 	MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT,
73 	MODEM_CMUX_RECEIVE_STATE_DATA,
74 	MODEM_CMUX_RECEIVE_STATE_FCS,
75 	MODEM_CMUX_RECEIVE_STATE_DROP,
76 	MODEM_CMUX_RECEIVE_STATE_EOF,
77 };
78 
79 enum modem_cmux_dlci_state {
80 	MODEM_CMUX_DLCI_STATE_CLOSED,
81 	MODEM_CMUX_DLCI_STATE_OPENING,
82 	MODEM_CMUX_DLCI_STATE_OPEN,
83 	MODEM_CMUX_DLCI_STATE_CLOSING,
84 };
85 
86 struct modem_cmux_dlci {
87 	sys_snode_t node;
88 
89 	/* Pipe */
90 	struct modem_pipe pipe;
91 
92 	/* Context */
93 	uint16_t dlci_address;
94 	struct modem_cmux *cmux;
95 
96 	/* Receive buffer */
97 	struct ring_buf receive_rb;
98 	struct k_mutex receive_rb_lock;
99 
100 	/* Work */
101 	struct k_work_delayable open_work;
102 	struct k_work_delayable close_work;
103 
104 	/* State */
105 	enum modem_cmux_dlci_state state;
106 
107 	/* Statistics */
108 #if CONFIG_MODEM_STATS
109 	struct modem_stats_buffer receive_buf_stats;
110 #endif
111 };
112 
113 struct modem_cmux_frame {
114 	uint8_t dlci_address;
115 	bool cr;
116 	bool pf;
117 	uint8_t type;
118 	const uint8_t *data;
119 	uint16_t data_len;
120 };
121 
122 struct modem_cmux_work {
123 	struct k_work_delayable dwork;
124 	struct modem_cmux *cmux;
125 };
126 
127 struct modem_cmux {
128 	/* Bus pipe */
129 	struct modem_pipe *pipe;
130 
131 	/* Event handler */
132 	modem_cmux_callback callback;
133 	void *user_data;
134 
135 	/* DLCI channel contexts */
136 	sys_slist_t dlcis;
137 
138 	/* State */
139 	enum modem_cmux_state state;
140 	bool flow_control_on;
141 
142 	/* Work lock */
143 	bool attached;
144 	struct k_spinlock work_lock;
145 
146 	/* Receive state*/
147 	enum modem_cmux_receive_state receive_state;
148 
149 	/* Receive buffer */
150 	uint8_t *receive_buf;
151 	uint16_t receive_buf_size;
152 	uint16_t receive_buf_len;
153 
154 	uint8_t work_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE];
155 
156 	/* Transmit buffer */
157 	struct ring_buf transmit_rb;
158 	struct k_mutex transmit_rb_lock;
159 
160 	/* Received frame */
161 	struct modem_cmux_frame frame;
162 	uint8_t frame_header[5];
163 	uint16_t frame_header_len;
164 
165 	/* Work */
166 	struct k_work_delayable receive_work;
167 	struct k_work_delayable transmit_work;
168 	struct k_work_delayable connect_work;
169 	struct k_work_delayable disconnect_work;
170 
171 	/* Synchronize actions */
172 	struct k_event event;
173 
174 	/* Statistics */
175 #if CONFIG_MODEM_STATS
176 	struct modem_stats_buffer receive_buf_stats;
177 	struct modem_stats_buffer transmit_buf_stats;
178 #endif
179 };
180 
181 /**
182  * @endcond
183  */
184 
185 /**
186  * @brief Contains CMUX instance configuration data
187  */
188 struct modem_cmux_config {
189 	/** Invoked when event occurs */
190 	modem_cmux_callback callback;
191 	/** Free to use pointer passed to event handler when invoked */
192 	void *user_data;
193 	/** Receive buffer */
194 	uint8_t *receive_buf;
195 	/** Size of receive buffer in bytes [127, ...] */
196 	uint16_t receive_buf_size;
197 	/** Transmit buffer */
198 	uint8_t *transmit_buf;
199 	/** Size of transmit buffer in bytes [149, ...] */
200 	uint16_t transmit_buf_size;
201 };
202 
203 /**
204  * @brief Initialize CMUX instance
205  * @param cmux CMUX instance
206  * @param config Configuration to apply to CMUX instance
207  */
208 void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *config);
209 
210 /**
211  * @brief CMUX DLCI configuration
212  */
213 struct modem_cmux_dlci_config {
214 	/** DLCI channel address */
215 	uint8_t dlci_address;
216 	/** Receive buffer used by pipe */
217 	uint8_t *receive_buf;
218 	/** Size of receive buffer used by pipe [127, ...] */
219 	uint16_t receive_buf_size;
220 };
221 
222 /**
223  * @brief Initialize DLCI instance and register it with CMUX instance
224  *
225  * @param cmux CMUX instance which the DLCI will be registered to
226  * @param dlci DLCI instance which will be registered and configured
227  * @param config Configuration to apply to DLCI instance
228  */
229 struct modem_pipe *modem_cmux_dlci_init(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci,
230 					const struct modem_cmux_dlci_config *config);
231 
232 /**
233  * @brief Attach CMUX instance to pipe
234  *
235  * @param cmux CMUX instance
236  * @param pipe Pipe instance to attach CMUX instance to
237  */
238 int modem_cmux_attach(struct modem_cmux *cmux, struct modem_pipe *pipe);
239 
240 /**
241  * @brief Connect CMUX instance
242  *
243  * @details This will send a CMUX connect request to target on the serial bus. If successful,
244  * DLCI channels can be now be opened using modem_pipe_open()
245  *
246  * @param cmux CMUX instance
247  *
248  * @note When connected, the bus pipe must not be used directly
249  */
250 int modem_cmux_connect(struct modem_cmux *cmux);
251 
252 /**
253  * @brief Connect CMUX instance asynchronously
254  *
255  * @details This will send a CMUX connect request to target on the serial bus. If successful,
256  * DLCI channels can be now be opened using modem_pipe_open().
257  *
258  * @param cmux CMUX instance
259  *
260  * @note When connected, the bus pipe must not be used directly
261  */
262 int modem_cmux_connect_async(struct modem_cmux *cmux);
263 
264 /**
265  * @brief Close down and disconnect CMUX instance
266  *
267  * @details This will close all open DLCI channels, and close down the CMUX connection.
268  *
269  * @param cmux CMUX instance
270  *
271  * @note The bus pipe must be released using modem_cmux_release() after disconnecting
272  * before being reused.
273  */
274 int modem_cmux_disconnect(struct modem_cmux *cmux);
275 
276 /**
277  * @brief Close down and disconnect CMUX instance asynchronously
278  *
279  * @details This will close all open DLCI channels, and close down the CMUX connection.
280  *
281  * @param cmux CMUX instance
282  *
283  * @note The bus pipe must be released using modem_cmux_release() after disconnecting
284  * before being reused.
285  */
286 int modem_cmux_disconnect_async(struct modem_cmux *cmux);
287 
288 /**
289  * @brief Release CMUX instance from pipe
290  *
291  * @details Releases the pipe and hard resets the CMUX instance internally. CMUX should
292  * be disconnected using modem_cmux_disconnect().
293  *
294  * @param cmux CMUX instance
295  *
296  * @note The bus pipe can be used directly again after CMUX instance is released.
297  */
298 void modem_cmux_release(struct modem_cmux *cmux);
299 
300 /**
301  * @}
302  */
303 
304 #ifdef __cplusplus
305 }
306 #endif
307 
308 #endif /* ZEPHYR_MODEM_CMUX_ */
309