1 /*
2  * Copyright (c) 2022 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/types.h>
9 #include <zephyr/device.h>
10 #include <zephyr/sys/ring_buffer.h>
11 
12 #include <zephyr/modem/pipe.h>
13 
14 #ifndef ZEPHYR_MODEM_CHAT_
15 #define ZEPHYR_MODEM_CHAT_
16 
17 #ifdef __cplusplus
18 extern "C" {
19 #endif
20 
21 struct modem_chat;
22 
23 /**
24  * @brief Callback called when matching chat is received
25  *
26  * @param chat Pointer to chat instance instance
27  * @param argv Pointer to array of parsed arguments
28  * @param argc Number of parsed arguments, arg 0 holds the exact match
29  * @param user_data Free to use user data set during modem_chat_init()
30  */
31 typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc,
32 					  void *user_data);
33 
34 /**
35  * @brief Modem chat match
36  */
37 struct modem_chat_match {
38 	/** Match array */
39 	const uint8_t *match;
40 	/** Size of match */
41 	uint8_t match_size;
42 	/** Separators array */
43 	const uint8_t *separators;
44 	/** Size of separators array */
45 	uint8_t separators_size;
46 	/** Set if modem chat instance shall use wildcards when matching */
47 	uint8_t wildcards : 1;
48 	/** Set if script shall not continue to next step in case of match */
49 	uint8_t partial : 1;
50 	/** Type of modem chat instance */
51 	modem_chat_match_callback callback;
52 };
53 
54 #define MODEM_CHAT_MATCH(_match, _separators, _callback)                                           \
55 	{                                                                                          \
56 		.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1),         \
57 		.separators = (uint8_t *)(_separators),                                            \
58 		.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false,         \
59 		.callback = _callback,                                                             \
60 	}
61 
62 #define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback)                                  \
63 	{                                                                                          \
64 		.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1),         \
65 		.separators = (uint8_t *)(_separators),                                            \
66 		.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true,          \
67 		.callback = _callback,                                                             \
68 	}
69 
70 #define MODEM_CHAT_MATCH_INITIALIZER(_match, _separators, _callback, _wildcards, _partial)         \
71 	{                                                                                          \
72 		.match = (uint8_t *)(_match),                                                      \
73 		.match_size = (uint8_t)(sizeof(_match) - 1),                                       \
74 		.separators = (uint8_t *)(_separators),                                            \
75 		.separators_size = (uint8_t)(sizeof(_separators) - 1),                             \
76 		.wildcards = _wildcards,                                                           \
77 		.partial = _partial,                                                               \
78 		.callback = _callback,                                                             \
79 	}
80 
81 #define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback)                              \
82 	const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback)
83 
84 #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...)                                                       \
85 	const static struct modem_chat_match _sym[] = {__VA_ARGS__}
86 
87 /**
88  * @brief Modem chat script chat
89  */
90 struct modem_chat_script_chat {
91 	/** Request to send to modem */
92 	const uint8_t *request;
93 	/** Size of request */
94 	uint16_t request_size;
95 	/** Expected responses to request */
96 	const struct modem_chat_match *response_matches;
97 	/** Number of elements in expected responses */
98 	uint16_t response_matches_size;
99 	/** Timeout before chat script may continue to next step in milliseconds */
100 	uint16_t timeout;
101 };
102 
103 #define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match)                                      \
104 	{                                                                                          \
105 		.request = (uint8_t *)(_request),                                                  \
106 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
107 		.response_matches = &_response_match,                                              \
108 		.response_matches_size = 1,                                                        \
109 		.timeout = 0,                                                                      \
110 	}
111 
112 #define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches)                               \
113 	{                                                                                          \
114 		.request = (uint8_t *)(_request),                                                  \
115 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
116 		.response_matches = _response_matches,                                             \
117 		.response_matches_size = ARRAY_SIZE(_response_matches),                            \
118 		.timeout = 0,                                                                      \
119 	}
120 
121 #define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout)                                        \
122 	{                                                                                          \
123 		.request = (uint8_t *)(_request),                                                  \
124 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
125 		.response_matches = NULL,                                                          \
126 		.response_matches_size = 0,                                                        \
127 		.timeout = _timeout,                                                               \
128 	}
129 
130 #define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...)                                                   \
131 	const struct modem_chat_script_chat _sym[] = {__VA_ARGS__}
132 
133 enum modem_chat_script_result {
134 	MODEM_CHAT_SCRIPT_RESULT_SUCCESS,
135 	MODEM_CHAT_SCRIPT_RESULT_ABORT,
136 	MODEM_CHAT_SCRIPT_RESULT_TIMEOUT
137 };
138 
139 /**
140  * @brief Callback called when script chat is received
141  *
142  * @param chat Pointer to chat instance instance
143  * @param result Result of script execution
144  * @param user_data Free to use user data set during modem_chat_init()
145  */
146 typedef void (*modem_chat_script_callback)(struct modem_chat *chat,
147 					   enum modem_chat_script_result result, void *user_data);
148 
149 /**
150  * @brief Modem chat script
151  */
152 struct modem_chat_script {
153 	/** Name of script */
154 	const char *name;
155 	/** Array of script chats */
156 	const struct modem_chat_script_chat *script_chats;
157 	/** Elements in array of script chats */
158 	uint16_t script_chats_size;
159 	/** Array of abort matches */
160 	const struct modem_chat_match *abort_matches;
161 	/** Number of elements in array of abort matches */
162 	uint16_t abort_matches_size;
163 	/** Callback called when script execution terminates */
164 	modem_chat_script_callback callback;
165 	/** Timeout in seconds within which the script execution must terminate */
166 	uint32_t timeout;
167 };
168 
169 #define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout)         \
170 	const static struct modem_chat_script _sym = {                                             \
171 		.name = #_sym,                                                                     \
172 		.script_chats = _script_chats,                                                     \
173 		.script_chats_size = ARRAY_SIZE(_script_chats),                                    \
174 		.abort_matches = _abort_matches,                                                   \
175 		.abort_matches_size = ARRAY_SIZE(_abort_matches),                                  \
176 		.callback = _callback,                                                             \
177 		.timeout = _timeout,                                                               \
178 	}
179 
180 enum modem_chat_script_send_state {
181 	/* No data to send */
182 	MODEM_CHAT_SCRIPT_SEND_STATE_IDLE,
183 	/* Sending request */
184 	MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST,
185 	/* Sending delimiter */
186 	MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER,
187 };
188 
189 /**
190  * @brief Chat instance internal context
191  * @warning Do not modify any members of this struct directly
192  */
193 struct modem_chat {
194 	/* Pipe used to send and receive data */
195 	struct modem_pipe *pipe;
196 
197 	/* User data passed with match callbacks */
198 	void *user_data;
199 
200 	/* Receive buffer */
201 	uint8_t *receive_buf;
202 	uint16_t receive_buf_size;
203 	uint16_t receive_buf_len;
204 
205 	/* Work buffer */
206 	uint8_t work_buf[32];
207 	uint16_t work_buf_len;
208 
209 	/* Chat delimiter */
210 	uint8_t *delimiter;
211 	uint16_t delimiter_size;
212 	uint16_t delimiter_match_len;
213 
214 	/* Array of bytes which are discarded out by parser */
215 	uint8_t *filter;
216 	uint16_t filter_size;
217 
218 	/* Parsed arguments */
219 	uint8_t **argv;
220 	uint16_t argv_size;
221 	uint16_t argc;
222 
223 	/* Matches
224 	 * Index 0 -> Response matches
225 	 * Index 1 -> Abort matches
226 	 * Index 2 -> Unsolicited matches
227 	 */
228 	const struct modem_chat_match *matches[3];
229 	uint16_t matches_size[3];
230 
231 	/* Script execution */
232 	const struct modem_chat_script *script;
233 	const struct modem_chat_script *pending_script;
234 	struct k_work script_run_work;
235 	struct k_work_delayable script_timeout_work;
236 	struct k_work script_abort_work;
237 	uint16_t script_chat_it;
238 	atomic_t script_state;
239 	enum modem_chat_script_result script_result;
240 	struct k_sem script_stopped_sem;
241 
242 	/* Script sending */
243 	uint16_t script_send_request_pos;
244 	uint16_t script_send_delimiter_pos;
245 	struct k_work_delayable script_send_work;
246 	struct k_work_delayable script_send_timeout_work;
247 
248 	/* Match parsing */
249 	const struct modem_chat_match *parse_match;
250 	uint16_t parse_match_len;
251 	uint16_t parse_arg_len;
252 	uint16_t parse_match_type;
253 
254 	/* Process received data */
255 	struct k_work_delayable process_work;
256 	k_timeout_t process_timeout;
257 };
258 
259 /**
260  * @brief Chat configuration
261  */
262 struct modem_chat_config {
263 	/** Free to use user data passed with modem match callbacks */
264 	void *user_data;
265 	/** Receive buffer used to store parsed arguments */
266 	uint8_t *receive_buf;
267 	/** Size of receive buffer should be longest line + longest match */
268 	uint16_t receive_buf_size;
269 	/** Delimiter */
270 	uint8_t *delimiter;
271 	/** Size of delimiter */
272 	uint8_t delimiter_size;
273 	/** Bytes which are discarded by parser */
274 	uint8_t *filter;
275 	/** Size of filter */
276 	uint8_t filter_size;
277 	/** Array of pointers used to point to parsed arguments */
278 	uint8_t **argv;
279 	/** Elements in array of pointers */
280 	uint16_t argv_size;
281 	/** Array of unsolicited matches */
282 	const struct modem_chat_match *unsol_matches;
283 	/** Elements in array of unsolicited matches */
284 	uint16_t unsol_matches_size;
285 	/** Delay from receive ready event to pipe receive occurs */
286 	k_timeout_t process_timeout;
287 };
288 
289 /**
290  * @brief Initialize modem pipe chat instance
291  * @param chat Chat instance
292  * @param config Configuration which shall be applied to Chat instance
293  * @note Chat instance must be attached to pipe
294  */
295 int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config);
296 
297 /**
298  * @brief Attach modem chat instance to pipe
299  * @param chat Chat instance
300  * @param pipe Pipe instance to attach Chat instance to
301  * @returns 0 if successful
302  * @returns negative errno code if failure
303  * @note Chat instance is enabled if successful
304  */
305 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
306 
307 /**
308  * @brief Run script asynchronously
309  * @param chat Chat instance
310  * @param script Script to run
311  * @returns 0 if script successfully started
312  * @returns -EBUSY if a script is currently running
313  * @returns -EPERM if modem pipe is not attached
314  * @returns -EINVAL if arguments or script is invalid
315  * @note Script runs asynchronously until complete or aborted.
316  */
317 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script);
318 
319 /**
320  * @brief Run script
321  * @param chat Chat instance
322  * @param script Script to run
323  * @returns 0 if successful
324  * @returns -EBUSY if a script is currently running
325  * @returns -EPERM if modem pipe is not attached
326  * @returns -EINVAL if arguments or script is invalid
327  * @note Script runs until complete or aborted.
328  */
329 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script);
330 
331 /**
332  * @brief Run script asynchronously
333  * @note Function exists for backwards compatibility and should be deprecated
334  * @param chat Chat instance
335  * @param script Script to run
336  * @returns 0 if script successfully started
337  * @returns -EBUSY if a script is currently running
338  * @returns -EPERM if modem pipe is not attached
339  * @returns -EINVAL if arguments or script is invalid
340  */
modem_chat_script_run(struct modem_chat * chat,const struct modem_chat_script * script)341 static inline int modem_chat_script_run(struct modem_chat *chat,
342 					const struct modem_chat_script *script)
343 {
344 	return modem_chat_run_script_async(chat, script);
345 }
346 
347 /**
348  * @brief Abort script
349  * @param chat Chat instance
350  */
351 void modem_chat_script_abort(struct modem_chat *chat);
352 
353 /**
354  * @brief Release pipe from chat instance
355  * @param chat Chat instance
356  */
357 void modem_chat_release(struct modem_chat *chat);
358 
359 #ifdef __cplusplus
360 }
361 #endif
362 
363 #endif /* ZEPHYR_MODEM_CHAT_ */
364