1 /** @file
2  * @brief Modem command handler header file.
3  *
4  * Text-based command handler implementation for modem context driver.
5  */
6 
7 /*
8  * Copyright (c) 2019 Foundries.io
9  *
10  * SPDX-License-Identifier: Apache-2.0
11  */
12 
13 #ifndef ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_
14 #define ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_
15 
16 #include <zephyr/kernel.h>
17 
18 #include "modem_context.h"
19 
20 #ifdef __cplusplus
21 extern "C" {
22 #endif
23 
24 #define MODEM_CMD_DEFINE(name_) \
25 static int name_(struct modem_cmd_handler_data *data, uint16_t len, \
26 		 uint8_t **argv, uint16_t argc)
27 
28 #define MODEM_CMD(cmd_, func_cb_, acount_, adelim_) { \
29 	.cmd = cmd_, \
30 	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
31 	.func = func_cb_, \
32 	.arg_count_min = acount_, \
33 	.arg_count_max = acount_, \
34 	.delim = adelim_, \
35 	.direct = false, \
36 }
37 
38 #define MODEM_CMD_ARGS_MAX(cmd_, func_cb_, acount_, acountmax_, adelim_) { \
39 	.cmd = cmd_, \
40 	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
41 	.func = func_cb_, \
42 	.arg_count_min = acount_, \
43 	.arg_count_max = acountmax_, \
44 	.delim = adelim_, \
45 	.direct = false, \
46 }
47 
48 #define MODEM_CMD_DIRECT_DEFINE(name_) MODEM_CMD_DEFINE(name_)
49 
50 #define MODEM_CMD_DIRECT(cmd_, func_cb_) { \
51 	.cmd = cmd_, \
52 	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
53 	.func = func_cb_, \
54 	.arg_count_min = 0, \
55 	.arg_count_max = 0, \
56 	.delim = "", \
57 	.direct = true, \
58 }
59 
60 #define CMD_RESP	0
61 #define CMD_UNSOL	1
62 #define CMD_HANDLER	2
63 #define CMD_MAX		3
64 
65 /*
66  * Flags for modem_send_cmd_ext.
67  */
68 #define MODEM_NO_TX_LOCK	BIT(0)
69 #define MODEM_NO_SET_CMDS	BIT(1)
70 #define MODEM_NO_UNSET_CMDS	BIT(2)
71 
72 struct modem_cmd_handler_data;
73 
74 struct modem_cmd {
75 	int (*func)(struct modem_cmd_handler_data *data, uint16_t len,
76 		    uint8_t **argv, uint16_t argc);
77 	const char *cmd;
78 	const char *delim;
79 	uint16_t cmd_len;
80 	uint16_t arg_count_min;
81 	uint16_t arg_count_max;
82 	bool direct;
83 };
84 
85 #define SETUP_CMD(cmd_send_, match_cmd_, func_cb_, num_param_, delim_) { \
86 	.send_cmd = cmd_send_, \
87 	MODEM_CMD(match_cmd_, func_cb_, num_param_, delim_) \
88 }
89 
90 #define SETUP_CMD_NOHANDLE(send_cmd_) \
91 		SETUP_CMD(send_cmd_, NULL, NULL, 0U, NULL)
92 
93 /* series of modem setup commands to run */
94 struct setup_cmd {
95 	const char *send_cmd;
96 	struct modem_cmd handle_cmd;
97 };
98 
99 struct modem_cmd_handler_data {
100 	const struct modem_cmd *cmds[CMD_MAX];
101 	size_t cmds_len[CMD_MAX];
102 
103 	char *match_buf;
104 	size_t match_buf_len;
105 
106 	int last_error;
107 
108 	const char *eol;
109 	size_t eol_len;
110 
111 	/* rx net buffer */
112 	struct net_buf *rx_buf;
113 
114 	/* allocation info */
115 	struct net_buf_pool *buf_pool;
116 	k_timeout_t alloc_timeout;
117 
118 	/* locks */
119 	struct k_sem sem_tx_lock;
120 	struct k_sem sem_parse_lock;
121 
122 	/* user data */
123 	void *user_data;
124 };
125 
126 /**
127  * @brief  get the last error code
128  *
129  * @param  data: command handler data reference
130  *
131  * @retval last handled error.
132  */
133 int modem_cmd_handler_get_error(struct modem_cmd_handler_data *data);
134 
135 /**
136  * @brief  set the last error code
137  *
138  * @param  data: command handler data reference
139  * @param  error_code: error
140  *
141  * @retval 0 if ok, < 0 if error.
142  */
143 int modem_cmd_handler_set_error(struct modem_cmd_handler_data *data,
144 				int error_code);
145 
146 /**
147  * @brief  update the parser's handler commands
148  *
149  * @param  data: handler data to use
150  * @param  handler_cmds: commands to attach
151  * @param  handler_cmds_len: size of commands array
152  * @param  reset_error_flag: reset last error code
153  *
154  * @retval 0 if ok, < 0 if error.
155  */
156 int modem_cmd_handler_update_cmds(struct modem_cmd_handler_data *data,
157 				  const struct modem_cmd *handler_cmds,
158 				  size_t handler_cmds_len,
159 				  bool reset_error_flag);
160 
161 /**
162  * @brief  Wait until semaphore is given
163  *
164  * This function does the same wait behavior as @ref modem_cmd_send, but can wait without sending
165  * any command first. Useful for waiting for asynchronous responses.
166  *
167  * @param  data: handler data to use
168  * @param  sem: wait for semaphore.
169  * @param  timeout: wait timeout.
170  *
171  * @retval 0 if ok, < 0 if error.
172  */
173 int modem_cmd_handler_await(struct modem_cmd_handler_data *data,
174 			    struct k_sem *sem, k_timeout_t timeout);
175 
176 /**
177  * @brief  send data directly to interface w/o TX lock.
178  *
179  * This function just writes directly to the modem interface.
180  * Recommended to use to get verbose logging for all data sent to the interface.
181  * @param  iface: interface to use
182  * @param  buf: send buffer (not NULL terminated)
183  * @param  len: length of send buffer.
184  */
185 int modem_cmd_send_data_nolock(struct modem_iface *iface,
186 			       const uint8_t *buf, size_t len);
187 
188 /**
189  * @brief  send AT command to interface with behavior defined by flags
190  *
191  * This function is similar to @ref modem_cmd_send, but it allows to choose a
192  * specific behavior regarding acquiring tx_lock, setting and unsetting
193  * @a handler_cmds.
194  *
195  * @param  iface: interface to use
196  * @param  handler: command handler to use
197  * @param  handler_cmds: commands to attach
198  * @param  handler_cmds_len: size of commands array
199  * @param  buf: NULL terminated send buffer
200  * @param  sem: wait for response semaphore
201  * @param  timeout: timeout of command
202  * @param  flags: flags which influence behavior of command sending
203  *
204  * @retval 0 if ok, < 0 if error.
205  */
206 int modem_cmd_send_ext(struct modem_iface *iface,
207 		       struct modem_cmd_handler *handler,
208 		       const struct modem_cmd *handler_cmds,
209 		       size_t handler_cmds_len, const uint8_t *buf,
210 		       struct k_sem *sem, k_timeout_t timeout, int flags);
211 
212 /**
213  * @brief  send AT command to interface w/o locking TX
214  *
215  * @param  iface: interface to use
216  * @param  handler: command handler to use
217  * @param  handler_cmds: commands to attach
218  * @param  handler_cmds_len: size of commands array
219  * @param  buf: NULL terminated send buffer
220  * @param  sem: wait for response semaphore
221  * @param  timeout: timeout of command
222  *
223  * @retval 0 if ok, < 0 if error.
224  */
modem_cmd_send_nolock(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct modem_cmd * handler_cmds,size_t handler_cmds_len,const uint8_t * buf,struct k_sem * sem,k_timeout_t timeout)225 static inline int modem_cmd_send_nolock(struct modem_iface *iface,
226 					struct modem_cmd_handler *handler,
227 					const struct modem_cmd *handler_cmds,
228 					size_t handler_cmds_len,
229 					const uint8_t *buf, struct k_sem *sem,
230 					k_timeout_t timeout)
231 {
232 	return modem_cmd_send_ext(iface, handler, handler_cmds,
233 				  handler_cmds_len, buf, sem, timeout,
234 				  MODEM_NO_TX_LOCK);
235 }
236 
237 /**
238  * @brief  send AT command to interface w/ a TX lock
239  *
240  * @param  iface: interface to use
241  * @param  handler: command handler to use
242  * @param  handler_cmds: commands to attach
243  * @param  handler_cmds_len: size of commands array
244  * @param  buf: NULL terminated send buffer
245  * @param  sem: wait for response semaphore
246  * @param  timeout: timeout of command
247  *
248  * @retval 0 if ok, < 0 if error.
249  */
modem_cmd_send(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct modem_cmd * handler_cmds,size_t handler_cmds_len,const uint8_t * buf,struct k_sem * sem,k_timeout_t timeout)250 static inline int modem_cmd_send(struct modem_iface *iface,
251 				 struct modem_cmd_handler *handler,
252 				 const struct modem_cmd *handler_cmds,
253 				 size_t handler_cmds_len, const uint8_t *buf,
254 				 struct k_sem *sem, k_timeout_t timeout)
255 {
256 	return modem_cmd_send_ext(iface, handler, handler_cmds,
257 				  handler_cmds_len, buf, sem, timeout, 0);
258 }
259 
260 /**
261  * @brief  send a series of AT commands w/ a TX lock
262  *
263  * @param  iface: interface to use
264  * @param  handler: command handler to use
265  * @param  cmds: array of setup commands to send
266  * @param  cmds_len: size of the setup command array
267  * @param  sem: wait for response semaphore
268  * @param  timeout: timeout of command
269  *
270  * @retval 0 if ok, < 0 if error.
271  */
272 int modem_cmd_handler_setup_cmds(struct modem_iface *iface,
273 				 struct modem_cmd_handler *handler,
274 				 const struct setup_cmd *cmds, size_t cmds_len,
275 				 struct k_sem *sem, k_timeout_t timeout);
276 
277 /**
278  * @brief  send a series of AT commands w/o locking TX
279  *
280  * @param  iface: interface to use
281  * @param  handler: command handler to use
282  * @param  cmds: array of setup commands to send
283  * @param  cmds_len: size of the setup command array
284  * @param  sem: wait for response semaphore
285  * @param  timeout: timeout of command
286  *
287  * @retval 0 if ok, < 0 if error.
288  */
289 int modem_cmd_handler_setup_cmds_nolock(struct modem_iface *iface,
290 					struct modem_cmd_handler *handler,
291 					const struct setup_cmd *cmds,
292 					size_t cmds_len, struct k_sem *sem,
293 					k_timeout_t timeout);
294 
295 /**
296  * @brief Modem command handler configuration
297  *
298  * @details Contains user configuration which is used to set up
299  * command handler data context. The struct is initialized and then passed
300  * to modem_cmd_handler_init().
301  *
302  * @retval 0 if ok, < 0 if error.
303  * @param match_buf Buffer used for matching commands
304  * @param match_buf_len Length of buffer used for matching commands
305  * @param buf_pool Initialized buffer pool used to store incoming data
306  * @param alloc_timeout Timeout for allocating data in buffer pool
307  * @param eol End of line represented as string
308  * @param user_data Free to use data which can be retrieved from within command handlers
309  * @param response_cmds Array of response command handlers
310  * @param response_cmds_len Length of response command handlers array
311  * @param unsol_cmds Array of unsolicitet command handlers
312  * @param unsol_cmds_len Length of unsolicitet command handlers array
313  */
314 struct modem_cmd_handler_config {
315 	char *match_buf;
316 	size_t match_buf_len;
317 	struct net_buf_pool *buf_pool;
318 	k_timeout_t alloc_timeout;
319 	const char *eol;
320 	void *user_data;
321 	const struct modem_cmd *response_cmds;
322 	size_t response_cmds_len;
323 	const struct modem_cmd *unsol_cmds;
324 	size_t unsol_cmds_len;
325 };
326 
327 /**
328  * @brief Initialize modem command handler
329  *
330  * @details This function is called once for each command handler, before any
331  * incoming data is processed.
332  *
333  * @note All arguments passed to this function, including the referenced data
334  * contained in the setup struct, must persist as long as the command handler itself.
335  *
336  * @param handler Command handler to initialize
337  * @param data Command handler data to use
338  * @param setup Command handler setup
339  *
340  * @return -EINVAL if any argument is invalid
341  * @return 0 if successful
342  */
343 int modem_cmd_handler_init(struct modem_cmd_handler *handler,
344 			   struct modem_cmd_handler_data *data,
345 			   const struct modem_cmd_handler_config *config);
346 
347 /**
348  * @brief  Lock the modem for sending cmds
349  *
350  * This is semaphore-based rather than mutex based, which means there's no
351  * requirements of thread ownership for the user. This function is useful
352  * when one needs to prevent threads from sending UART data to the modem for an
353  * extended period of time (for example during modem reset).
354  *
355  * @param  handler: command handler to lock
356  * @param  timeout: give up after timeout
357  *
358  * @retval 0 if ok, < 0 if error.
359  */
360 int modem_cmd_handler_tx_lock(struct modem_cmd_handler *handler,
361 			      k_timeout_t timeout);
362 
363 /**
364  * @brief  Unlock the modem for sending cmds
365  *
366  * @param  handler: command handler to unlock
367  */
368 void modem_cmd_handler_tx_unlock(struct modem_cmd_handler *handler);
369 
370 /**
371  * @brief Process incoming data
372  *
373  * @details This function will process any data available from the interface
374  * using the command handler. The command handler will invoke any matching modem
375  * command which has been registered using @ref modem_cmd_handler_init_cmds or
376  * @ref modem_cmd_handler_update_cmds. Once handled, the function will return.
377  *
378  * @note This function should be invoked from a dedicated thread, which only handles
379  * commands.
380  *
381  * @param handler The handler wich will handle the command when processed
382  * @param iface The interface which receives incoming data
383  */
modem_cmd_handler_process(struct modem_cmd_handler * handler,struct modem_iface * iface)384 static inline void modem_cmd_handler_process(struct modem_cmd_handler *handler,
385 					     struct modem_iface *iface)
386 {
387 	handler->process(handler, iface);
388 }
389 
390 #ifdef __cplusplus
391 }
392 #endif
393 
394 #endif /* ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_ */
395