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