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