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 #include <zephyr/modem/stats.h>
14 
15 #ifndef ZEPHYR_MODEM_CHAT_
16 #define ZEPHYR_MODEM_CHAT_
17 
18 #ifdef __cplusplus
19 extern "C" {
20 #endif
21 
22 /**
23  * @brief Modem Chat
24  * @defgroup modem_chat Modem Chat
25  * @since 3.5
26  * @version 1.0.0
27  * @ingroup modem
28  * @{
29  */
30 
31 struct modem_chat;
32 
33 /**
34  * @brief Callback called when matching chat is received
35  *
36  * @param chat Pointer to chat instance instance
37  * @param argv Pointer to array of parsed arguments
38  * @param argc Number of parsed arguments, arg 0 holds the exact match
39  * @param user_data Free to use user data set during modem_chat_init()
40  */
41 typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc,
42 					  void *user_data);
43 
44 /**
45  * @brief Modem chat match
46  */
47 struct modem_chat_match {
48 	/** Match array */
49 	const uint8_t *match;
50 	/** Size of match */
51 	uint8_t match_size;
52 	/** Separators array */
53 	const uint8_t *separators;
54 	/** Size of separators array */
55 	uint8_t separators_size;
56 	/** Set if modem chat instance shall use wildcards when matching */
57 	bool wildcards;
58 	/** Set if script shall not continue to next step in case of match */
59 	bool partial;
60 	/** Type of modem chat instance */
61 	modem_chat_match_callback callback;
62 };
63 
64 #define MODEM_CHAT_MATCH(_match, _separators, _callback)                                           \
65 	{                                                                                          \
66 		.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1),         \
67 		.separators = (uint8_t *)(_separators),                                            \
68 		.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false,         \
69 		.callback = _callback,                                                             \
70 	}
71 
72 #define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback)                                  \
73 	{                                                                                          \
74 		.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1),         \
75 		.separators = (uint8_t *)(_separators),                                            \
76 		.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true,          \
77 		.callback = _callback,                                                             \
78 	}
79 
80 #define MODEM_CHAT_MATCH_INITIALIZER(_match, _separators, _callback, _wildcards, _partial)         \
81 	{                                                                                          \
82 		.match = (uint8_t *)(_match),                                                      \
83 		.match_size = (uint8_t)(sizeof(_match) - 1),                                       \
84 		.separators = (uint8_t *)(_separators),                                            \
85 		.separators_size = (uint8_t)(sizeof(_separators) - 1),                             \
86 		.wildcards = _wildcards,                                                           \
87 		.partial = _partial,                                                               \
88 		.callback = _callback,                                                             \
89 	}
90 
91 #define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback)                              \
92 	const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback)
93 
94 #define MODEM_CHAT_MATCH_WILDCARD_DEFINE(_sym, _match, _separators, _callback)                     \
95 	const static struct modem_chat_match _sym =                                                \
96 		MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback)
97 
98 /* Helper struct to match any response without callback. */
99 extern const struct modem_chat_match modem_chat_any_match;
100 
101 #define MODEM_CHAT_MATCHES_DEFINE(_sym, ...)                                                       \
102 	const static struct modem_chat_match _sym[] = {__VA_ARGS__}
103 
104 /* Helper struct to match nothing. */
105 extern const struct modem_chat_match modem_chat_empty_matches[0];
106 
107 /**
108  * @brief Modem chat script chat
109  */
110 struct modem_chat_script_chat {
111 	/** Request to send to modem */
112 	const uint8_t *request;
113 	/** Size of request */
114 	uint16_t request_size;
115 	/** Expected responses to request */
116 	const struct modem_chat_match *response_matches;
117 	/** Number of elements in expected responses */
118 	uint16_t response_matches_size;
119 	/** Timeout before chat script may continue to next step in milliseconds */
120 	uint16_t timeout;
121 };
122 
123 #define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match)                                      \
124 	{                                                                                          \
125 		.request = (uint8_t *)(_request),                                                  \
126 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
127 		.response_matches = &_response_match,                                              \
128 		.response_matches_size = 1,                                                        \
129 		.timeout = 0,                                                                      \
130 	}
131 
132 #define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches)                               \
133 	{                                                                                          \
134 		.request = (uint8_t *)(_request),                                                  \
135 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
136 		.response_matches = _response_matches,                                             \
137 		.response_matches_size = ARRAY_SIZE(_response_matches),                            \
138 		.timeout = 0,                                                                      \
139 	}
140 
141 #define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout_ms)                                     \
142 	{                                                                                          \
143 		.request = (uint8_t *)(_request),                                                  \
144 		.request_size = (uint16_t)(sizeof(_request) - 1),                                  \
145 		.response_matches = NULL,                                                          \
146 		.response_matches_size = 0,                                                        \
147 		.timeout = _timeout_ms,                                                            \
148 	}
149 
150 #define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...)                                                   \
151 	const static struct modem_chat_script_chat _sym[] = {__VA_ARGS__}
152 
153 /* Helper struct to have no chat script command. */
154 extern const struct modem_chat_script_chat modem_chat_empty_script_chats[0];
155 
156 enum modem_chat_script_result {
157 	MODEM_CHAT_SCRIPT_RESULT_SUCCESS,
158 	MODEM_CHAT_SCRIPT_RESULT_ABORT,
159 	MODEM_CHAT_SCRIPT_RESULT_TIMEOUT
160 };
161 
162 /**
163  * @brief Callback called when script chat is received
164  *
165  * @param chat Pointer to chat instance instance
166  * @param result Result of script execution
167  * @param user_data Free to use user data set during modem_chat_init()
168  */
169 typedef void (*modem_chat_script_callback)(struct modem_chat *chat,
170 					   enum modem_chat_script_result result, void *user_data);
171 
172 /**
173  * @brief Modem chat script
174  */
175 struct modem_chat_script {
176 	/** Name of script */
177 	const char *name;
178 	/** Array of script chats */
179 	const struct modem_chat_script_chat *script_chats;
180 	/** Elements in array of script chats */
181 	uint16_t script_chats_size;
182 	/** Array of abort matches */
183 	const struct modem_chat_match *abort_matches;
184 	/** Number of elements in array of abort matches */
185 	uint16_t abort_matches_size;
186 	/** Callback called when script execution terminates */
187 	modem_chat_script_callback callback;
188 	/** Timeout in seconds within which the script execution must terminate */
189 	uint32_t timeout;
190 };
191 
192 #define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout_s)       \
193 	const static struct modem_chat_script _sym = {                                             \
194 		.name = #_sym,                                                                     \
195 		.script_chats = _script_chats,                                                     \
196 		.script_chats_size = ARRAY_SIZE(_script_chats),                                    \
197 		.abort_matches = _abort_matches,                                                   \
198 		.abort_matches_size = ARRAY_SIZE(_abort_matches),                                  \
199 		.callback = _callback,                                                             \
200 		.timeout = _timeout_s,                                                             \
201 	}
202 
203 #define MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, _script_chats, _callback, _timeout_s)              \
204 	MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, modem_chat_empty_matches,                    \
205 				 _callback, _timeout_s)
206 
207 #define MODEM_CHAT_SCRIPT_EMPTY_DEFINE(_sym)                                                       \
208 	MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(_sym, modem_chat_empty_script_chats, NULL, 0)
209 
210 enum modem_chat_script_send_state {
211 	/* No data to send */
212 	MODEM_CHAT_SCRIPT_SEND_STATE_IDLE,
213 	/* Sending request */
214 	MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST,
215 	/* Sending delimiter */
216 	MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER,
217 };
218 
219 /**
220  * @brief Chat instance internal context
221  * @warning Do not modify any members of this struct directly
222  */
223 struct modem_chat {
224 	/* Pipe used to send and receive data */
225 	struct modem_pipe *pipe;
226 
227 	/* User data passed with match callbacks */
228 	void *user_data;
229 
230 	/* Receive buffer */
231 	uint8_t *receive_buf;
232 	uint16_t receive_buf_size;
233 	uint16_t receive_buf_len;
234 
235 	/* Work buffer */
236 	uint8_t work_buf[32];
237 	uint16_t work_buf_len;
238 
239 	/* Chat delimiter */
240 	uint8_t *delimiter;
241 	uint16_t delimiter_size;
242 	uint16_t delimiter_match_len;
243 
244 	/* Array of bytes which are discarded out by parser */
245 	uint8_t *filter;
246 	uint16_t filter_size;
247 
248 	/* Parsed arguments */
249 	uint8_t **argv;
250 	uint16_t argv_size;
251 	uint16_t argc;
252 
253 	/* Matches
254 	 * Index 0 -> Response matches
255 	 * Index 1 -> Abort matches
256 	 * Index 2 -> Unsolicited matches
257 	 */
258 	const struct modem_chat_match *matches[3];
259 	uint16_t matches_size[3];
260 
261 	/* Script execution */
262 	const struct modem_chat_script *script;
263 	const struct modem_chat_script *pending_script;
264 	struct k_work script_run_work;
265 	struct k_work_delayable script_timeout_work;
266 	struct k_work script_abort_work;
267 	uint16_t script_chat_it;
268 	atomic_t script_state;
269 	enum modem_chat_script_result script_result;
270 	struct k_sem script_stopped_sem;
271 
272 	/* Script sending */
273 	enum modem_chat_script_send_state script_send_state;
274 	uint16_t script_send_pos;
275 	struct k_work script_send_work;
276 	struct k_work_delayable script_send_timeout_work;
277 
278 	/* Match parsing */
279 	const struct modem_chat_match *parse_match;
280 	uint16_t parse_match_len;
281 	uint16_t parse_arg_len;
282 	uint16_t parse_match_type;
283 
284 	/* Process received data */
285 	struct k_work receive_work;
286 
287 	/* Statistics */
288 #if CONFIG_MODEM_STATS
289 	struct modem_stats_buffer receive_buf_stats;
290 	struct modem_stats_buffer work_buf_stats;
291 #endif
292 };
293 
294 /**
295  * @brief Chat configuration
296  */
297 struct modem_chat_config {
298 	/** Free to use user data passed with modem match callbacks */
299 	void *user_data;
300 	/** Receive buffer used to store parsed arguments */
301 	uint8_t *receive_buf;
302 	/** Size of receive buffer should be longest line + longest match */
303 	uint16_t receive_buf_size;
304 	/** Delimiter */
305 	uint8_t *delimiter;
306 	/** Size of delimiter */
307 	uint8_t delimiter_size;
308 	/** Bytes which are discarded by parser */
309 	uint8_t *filter;
310 	/** Size of filter */
311 	uint8_t filter_size;
312 	/** Array of pointers used to point to parsed arguments */
313 	uint8_t **argv;
314 	/** Elements in array of pointers */
315 	uint16_t argv_size;
316 	/** Array of unsolicited matches */
317 	const struct modem_chat_match *unsol_matches;
318 	/** Elements in array of unsolicited matches */
319 	uint16_t unsol_matches_size;
320 };
321 
322 /**
323  * @brief Initialize modem pipe chat instance
324  * @param chat Chat instance
325  * @param config Configuration which shall be applied to Chat instance
326  * @note Chat instance must be attached to pipe
327  */
328 int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config);
329 
330 /**
331  * @brief Attach modem chat instance to pipe
332  * @param chat Chat instance
333  * @param pipe Pipe instance to attach Chat instance to
334  * @returns 0 if successful
335  * @returns negative errno code if failure
336  * @note Chat instance is enabled if successful
337  */
338 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
339 
340 /**
341  * @brief Check if a script is running
342  * @param chat Chat instance
343  * @returns true if a script is currently running
344  * @returns false if a script is not currently running
345  */
346 bool modem_chat_is_running(struct modem_chat *chat);
347 
348 /**
349  * @brief Run script asynchronously
350  * @param chat Chat instance
351  * @param script Script to run
352  * @returns 0 if script successfully started
353  * @returns -EBUSY if a script is currently running
354  * @returns -EPERM if modem pipe is not attached
355  * @returns -EINVAL if arguments or script is invalid
356  * @note Script runs asynchronously until complete or aborted.
357  */
358 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script);
359 
360 /**
361  * @brief Run script
362  * @param chat Chat instance
363  * @param script Script to run
364  * @returns 0 if successful
365  * @returns -EBUSY if a script is currently running
366  * @returns -EPERM if modem pipe is not attached
367  * @returns -EINVAL if arguments or script is invalid
368  * @note Script runs until complete or aborted.
369  */
370 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script);
371 
372 /**
373  * @brief Run script asynchronously
374  * @note Function exists for backwards compatibility and should be deprecated
375  * @param chat Chat instance
376  * @param script Script to run
377  * @returns 0 if script successfully started
378  * @returns -EBUSY if a script is currently running
379  * @returns -EPERM if modem pipe is not attached
380  * @returns -EINVAL if arguments or script is invalid
381  */
modem_chat_script_run(struct modem_chat * chat,const struct modem_chat_script * script)382 static inline int modem_chat_script_run(struct modem_chat *chat,
383 					const struct modem_chat_script *script)
384 {
385 	return modem_chat_run_script_async(chat, script);
386 }
387 
388 /**
389  * @brief Abort script
390  * @param chat Chat instance
391  */
392 void modem_chat_script_abort(struct modem_chat *chat);
393 
394 /**
395  * @brief Release pipe from chat instance
396  * @param chat Chat instance
397  */
398 void modem_chat_release(struct modem_chat *chat);
399 
400 /**
401  * @brief Initialize modem chat match
402  * @param chat_match Modem chat match instance
403  */
404 void modem_chat_match_init(struct modem_chat_match *chat_match);
405 
406 /**
407  * @brief Set match of modem chat match instance
408  * @param chat_match Modem chat match instance
409  * @param match Match to set
410  * @note The lifetime of match must match or exceed the lifetime of chat_match
411  * @warning Always call this API after match is modified
412  *
413  * @retval 0 if successful, negative errno code otherwise
414  */
415 int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match);
416 
417 /**
418  * @brief Set separators of modem chat match instance
419  * @param chat_match Modem chat match instance
420  * @param separators Separators to set
421  * @note The lifetime of separators must match or exceed the lifetime of chat_match
422  * @warning Always call this API after separators are modified
423  *
424  * @retval 0 if successful, negative errno code otherwise
425  */
426 int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators);
427 
428 /**
429  * @brief Set modem chat match callback
430  * @param chat_match Modem chat match instance
431  * @param callback Callback to set
432  */
433 void modem_chat_match_set_callback(struct modem_chat_match *chat_match,
434 				   modem_chat_match_callback callback);
435 
436 /**
437  * @brief Set modem chat match partial flag
438  * @param chat_match Modem chat match instance
439  * @param partial Partial flag to set
440  */
441 void modem_chat_match_set_partial(struct modem_chat_match *chat_match, bool partial);
442 
443 /**
444  * @brief Set modem chat match wildcards flag
445  * @param chat_match Modem chat match instance
446  * @param enable Enable/disable Wildcards
447  */
448 void modem_chat_match_enable_wildcards(struct modem_chat_match *chat_match, bool enable);
449 
450 /**
451  * @brief Initialize modem chat script chat
452  * @param script_chat Modem chat script chat instance
453  */
454 void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat);
455 
456 /**
457  * @brief Set request of modem chat script chat instance
458  * @param script_chat Modem chat script chat instance
459  * @param request Request to set
460  * @note The lifetime of request must match or exceed the lifetime of script_chat
461  * @warning Always call this API after request is modified
462  *
463  * @retval 0 if successful, negative errno code otherwise
464  */
465 int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat,
466 				       const char *request);
467 
468 /**
469  * @brief Set modem chat script chat matches
470  * @param script_chat Modem chat script chat instance
471  * @param response_matches Response match array to set
472  * @param response_matches_size Size of response match array
473  * @note The lifetime of response_matches must match or exceed the lifetime of script_chat
474  *
475  * @retval 0 if successful, negative errno code otherwise
476  */
477 int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat,
478 						const struct modem_chat_match *response_matches,
479 						uint16_t response_matches_size);
480 
481 /**
482  * @brief Set modem chat script chat timeout
483  * @param script_chat Modem chat script chat instance
484  * @param timeout_ms Timeout in milliseconds
485  */
486 void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat,
487 					uint16_t timeout_ms);
488 
489 /**
490  * @brief Initialize modem chat script
491  * @param script Modem chat script instance
492  */
493 void modem_chat_script_init(struct modem_chat_script *script);
494 
495 /**
496  * @brief Set modem chat script name
497  * @param script Modem chat script instance
498  * @param name Name to set
499  * @note The lifetime of name must match or exceed the lifetime of script
500  */
501 void modem_chat_script_set_name(struct modem_chat_script *script, const char *name);
502 
503 /**
504  * @brief Set modem chat script chats
505  * @param script Modem chat script instance
506  * @param script_chats Chat script array to set
507  * @param script_chats_size Size of chat script array
508  * @note The lifetime of script_chats must match or exceed the lifetime of script
509  *
510  * @retval 0 if successful, negative errno code otherwise
511  */
512 int modem_chat_script_set_script_chats(struct modem_chat_script *script,
513 				       const struct modem_chat_script_chat *script_chats,
514 				       uint16_t script_chats_size);
515 
516 /**
517  * @brief Set modem chat script abort matches
518  * @param script Modem chat script instance
519  * @param abort_matches Abort match array to set
520  * @param abort_matches_size Size of abort match array
521  * @note The lifetime of abort_matches must match or exceed the lifetime of script
522  *
523  * @retval 0 if successful, negative errno code otherwise
524  */
525 int modem_chat_script_set_abort_matches(struct modem_chat_script *script,
526 					const struct modem_chat_match *abort_matches,
527 					uint16_t abort_matches_size);
528 
529 /**
530  * @brief Set modem chat script callback
531  * @param script Modem chat script instance
532  * @param callback Callback to set
533  */
534 void modem_chat_script_set_callback(struct modem_chat_script *script,
535 				    modem_chat_script_callback callback);
536 
537 /**
538  * @brief Set modem chat script timeout
539  * @param script Modem chat script instance
540  * @param timeout_s Timeout in seconds
541  */
542 void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s);
543 
544 /**
545  * @}
546  */
547 
548 #ifdef __cplusplus
549 }
550 #endif
551 
552 #endif /* ZEPHYR_MODEM_CHAT_ */
553