1 /** @file
2  * @brief Modem command handler for modem context driver
3  *
4  * Text-based command handler implementation for modem context driver.
5  */
6 
7 /*
8  * Copyright (c) 2019-2020 Foundries.io
9  *
10  * SPDX-License-Identifier: Apache-2.0
11  */
12 
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(modem_cmd_handler, CONFIG_MODEM_LOG_LEVEL);
15 
16 #include <zephyr/kernel.h>
17 #include <stddef.h>
18 #include <zephyr/net_buf.h>
19 
20 #include "modem_context.h"
21 #include "modem_cmd_handler.h"
22 
23 
24 /*
25  * Parsing Functions
26  */
27 
is_crlf(uint8_t c)28 static bool is_crlf(uint8_t c)
29 {
30 	if (c == '\n' || c == '\r') {
31 		return true;
32 	} else {
33 		return false;
34 	}
35 }
36 
skipcrlf(struct modem_cmd_handler_data * data)37 static void skipcrlf(struct modem_cmd_handler_data *data)
38 {
39 	while (data->rx_buf && data->rx_buf->len &&
40 			is_crlf(*data->rx_buf->data)) {
41 		net_buf_pull_u8(data->rx_buf);
42 		if (!data->rx_buf->len) {
43 			data->rx_buf = net_buf_frag_del(NULL, data->rx_buf);
44 		}
45 	}
46 }
47 
findcrlf(struct modem_cmd_handler_data * data,struct net_buf ** frag,uint16_t * offset)48 static uint16_t findcrlf(struct modem_cmd_handler_data *data,
49 		      struct net_buf **frag, uint16_t *offset)
50 {
51 	struct net_buf *buf = data->rx_buf;
52 	uint16_t len = 0U, pos = 0U;
53 
54 	while (buf && buf->len && !is_crlf(*(buf->data + pos))) {
55 		if (pos + 1 >= buf->len) {
56 			len += buf->len;
57 			buf = buf->frags;
58 			pos = 0U;
59 		} else {
60 			pos++;
61 		}
62 	}
63 
64 	if (buf && buf->len && is_crlf(*(buf->data + pos))) {
65 		len += pos;
66 		*offset = pos;
67 		*frag = buf;
68 		return len;
69 	}
70 
71 	return 0;
72 }
73 
starts_with(struct net_buf * buf,const char * str)74 static bool starts_with(struct net_buf *buf, const char *str)
75 {
76 	int pos = 0;
77 
78 	while (buf && buf->len && *str) {
79 		if (*(buf->data + pos) == *str) {
80 			str++;
81 			pos++;
82 			if (pos >= buf->len) {
83 				buf = buf->frags;
84 				pos = 0;
85 			}
86 		} else {
87 			return false;
88 		}
89 	}
90 
91 	if (*str == 0) {
92 		return true;
93 	}
94 
95 	return false;
96 }
97 
98 /*
99  * Cmd Handler Functions
100  */
101 
102 /* return scanned length for params */
parse_params(struct modem_cmd_handler_data * data,size_t match_len,const struct modem_cmd * cmd,uint8_t ** argv,size_t argv_len,uint16_t * argc)103 static int parse_params(struct modem_cmd_handler_data *data,  size_t match_len,
104 			const struct modem_cmd *cmd,
105 			uint8_t **argv, size_t argv_len, uint16_t *argc)
106 {
107 	int count = 0;
108 	size_t delim_len, begin, end, i;
109 	bool quoted = false;
110 
111 	if (!data || !data->match_buf || !match_len || !cmd || !argv || !argc) {
112 		return -EINVAL;
113 	}
114 
115 	begin = cmd->cmd_len;
116 	end = cmd->cmd_len;
117 	delim_len = strlen(cmd->delim);
118 	while (end < match_len) {
119 		/* Don't look for delimiters in the middle of a quoted parameter */
120 		if (data->match_buf[end] == '"') {
121 			quoted = !quoted;
122 		}
123 		if (quoted) {
124 			end++;
125 			continue;
126 		}
127 		/* Look for delimiter characters */
128 		for (i = 0; i < delim_len; i++) {
129 			if (data->match_buf[end] == cmd->delim[i]) {
130 				/* mark a parameter beginning */
131 				argv[*argc] = &data->match_buf[begin];
132 				/* end parameter with NUL char */
133 				data->match_buf[end] = '\0';
134 				/* bump begin */
135 				begin = end + 1;
136 				count += 1;
137 				(*argc)++;
138 				break;
139 			}
140 		}
141 
142 		if (count >= cmd->arg_count_max) {
143 			break;
144 		}
145 
146 		if (*argc == argv_len) {
147 			break;
148 		}
149 
150 		end++;
151 	}
152 
153 	/* consider the ending portion a param if end > begin */
154 	if (end > begin) {
155 		/* mark a parameter beginning */
156 		argv[*argc] = &data->match_buf[begin];
157 		/* end parameter with NUL char
158 		 * NOTE: if this is at the end of match_len will probably
159 		 * be overwriting a NUL that's already there
160 		 */
161 		data->match_buf[end] = '\0';
162 		(*argc)++;
163 	}
164 
165 	/* missing arguments */
166 	if (*argc < cmd->arg_count_min) {
167 		/* Do not return -EAGAIN here as there is no way new argument
168 		 * can be parsed later because match_len is computed to be
169 		 * the minimum of the distance to the first CRLF and the size
170 		 * of the buffer.
171 		 * Therefore, waiting more data on the interface won't change
172 		 * match_len value, which mean there is no point in waiting
173 		 * for more arguments, this will just end in a infinite loop
174 		 * parsing data and finding that some arguments are missing.
175 		 */
176 		return -EINVAL;
177 	}
178 
179 	/*
180 	 * return the beginning of the next unfinished param so we don't
181 	 * "skip" any data that could be parsed later.
182 	 */
183 	return begin - cmd->cmd_len;
184 }
185 
186 /* process a "matched" command */
process_cmd(const struct modem_cmd * cmd,size_t match_len,struct modem_cmd_handler_data * data)187 static int process_cmd(const struct modem_cmd *cmd, size_t match_len,
188 			struct modem_cmd_handler_data *data)
189 {
190 	int parsed_len = 0, ret = 0;
191 	uint8_t *argv[CONFIG_MODEM_CMD_HANDLER_MAX_PARAM_COUNT];
192 	uint16_t argc = 0U;
193 
194 	/* reset params */
195 	memset(argv, 0, sizeof(argv[0]) * ARRAY_SIZE(argv));
196 
197 	/* do we need to parse arguments? */
198 	if (cmd->arg_count_max > 0U) {
199 		/* returns < 0 on error and > 0 for parsed len */
200 		parsed_len = parse_params(data, match_len, cmd,
201 					  argv, ARRAY_SIZE(argv), &argc);
202 		if (parsed_len < 0) {
203 			return parsed_len;
204 		}
205 	}
206 
207 	/* skip cmd_len + parsed len */
208 	data->rx_buf = net_buf_skip(data->rx_buf, cmd->cmd_len + parsed_len);
209 
210 	/* call handler */
211 	if (cmd->func) {
212 		ret = cmd->func(data, match_len - cmd->cmd_len - parsed_len,
213 				argv, argc);
214 		if (ret == -EAGAIN) {
215 			/* wait for more data */
216 			net_buf_push(data->rx_buf, cmd->cmd_len + parsed_len);
217 		}
218 	}
219 
220 	return ret;
221 }
222 
223 /*
224  * check 3 arrays of commands for a match in match_buf:
225  * - response handlers[0]
226  * - unsolicited handlers[1]
227  * - current assigned handlers[2]
228  */
find_cmd_match(struct modem_cmd_handler_data * data)229 static const struct modem_cmd *find_cmd_match(
230 		struct modem_cmd_handler_data *data)
231 {
232 	int j;
233 	size_t i;
234 
235 	for (j = 0; j < ARRAY_SIZE(data->cmds); j++) {
236 		if (!data->cmds[j] || data->cmds_len[j] == 0U) {
237 			continue;
238 		}
239 
240 		for (i = 0; i < data->cmds_len[j]; i++) {
241 			/* match on "empty" cmd */
242 			if (strlen(data->cmds[j][i].cmd) == 0 ||
243 			    strncmp(data->match_buf, data->cmds[j][i].cmd,
244 				    data->cmds[j][i].cmd_len) == 0) {
245 				return &data->cmds[j][i];
246 			}
247 		}
248 	}
249 
250 	return NULL;
251 }
252 
find_cmd_direct_match(struct modem_cmd_handler_data * data)253 static const struct modem_cmd *find_cmd_direct_match(
254 		struct modem_cmd_handler_data *data)
255 {
256 	size_t j, i;
257 
258 	for (j = 0; j < ARRAY_SIZE(data->cmds); j++) {
259 		if (!data->cmds[j] || data->cmds_len[j] == 0U) {
260 			continue;
261 		}
262 
263 		for (i = 0; i < data->cmds_len[j]; i++) {
264 			/* match start of cmd */
265 			if (data->cmds[j][i].direct &&
266 			    (data->cmds[j][i].cmd[0] == '\0' ||
267 			     starts_with(data->rx_buf, data->cmds[j][i].cmd))) {
268 				return &data->cmds[j][i];
269 			}
270 		}
271 	}
272 
273 	return NULL;
274 }
275 
cmd_handler_process_iface_data(struct modem_cmd_handler_data * data,struct modem_iface * iface)276 static int cmd_handler_process_iface_data(struct modem_cmd_handler_data *data,
277 					  struct modem_iface *iface)
278 {
279 	struct net_buf *last;
280 	size_t bytes_read = 0;
281 	int ret;
282 
283 	if (!data->rx_buf) {
284 		data->rx_buf = net_buf_alloc(data->buf_pool,
285 					     data->alloc_timeout);
286 		if (!data->rx_buf) {
287 			/* there is potentially more data waiting */
288 			return -ENOMEM;
289 		}
290 	}
291 
292 	last = net_buf_frag_last(data->rx_buf);
293 
294 	/* read all of the data from modem iface */
295 	while (true) {
296 		struct net_buf *frag = last;
297 		size_t frag_room = net_buf_tailroom(frag);
298 
299 		if (!frag_room) {
300 			frag = net_buf_alloc(data->buf_pool,
301 					    data->alloc_timeout);
302 			if (!frag) {
303 				/* there is potentially more data waiting */
304 				return -ENOMEM;
305 			}
306 
307 			net_buf_frag_insert(last, frag);
308 			last = frag;
309 
310 			frag_room = net_buf_tailroom(frag);
311 		}
312 
313 		ret = iface->read(iface, net_buf_tail(frag), frag_room,
314 				  &bytes_read);
315 		if (ret < 0 || bytes_read == 0) {
316 			/* modem context buffer is empty */
317 			return 0;
318 		}
319 
320 		net_buf_add(frag, bytes_read);
321 	}
322 }
323 
cmd_handler_process_rx_buf(struct modem_cmd_handler_data * data)324 static void cmd_handler_process_rx_buf(struct modem_cmd_handler_data *data)
325 {
326 	const struct modem_cmd *cmd;
327 	struct net_buf *frag = NULL;
328 	size_t match_len;
329 	int ret;
330 	uint16_t offset, len;
331 
332 	/* process all of the data in the net_buf */
333 	while (data->rx_buf && data->rx_buf->len) {
334 		skipcrlf(data);
335 		if (!data->rx_buf || !data->rx_buf->len) {
336 			break;
337 		}
338 
339 		cmd = find_cmd_direct_match(data);
340 		if (cmd && cmd->func) {
341 			ret = cmd->func(data, cmd->cmd_len, NULL, 0);
342 			if (ret == -EAGAIN) {
343 				/* Wait for more data */
344 				break;
345 			} else if (ret > 0) {
346 				LOG_DBG("match direct cmd [%s] (ret:%d)",
347 					cmd->cmd, ret);
348 				data->rx_buf = net_buf_skip(data->rx_buf, ret);
349 			}
350 
351 			continue;
352 		}
353 
354 		frag = NULL;
355 		/* locate next CR/LF */
356 		len = findcrlf(data, &frag, &offset);
357 		if (!frag) {
358 			/*
359 			 * No CR/LF found.  Let's exit and leave any data
360 			 * for next time
361 			 */
362 			break;
363 		}
364 
365 		/* load match_buf with content up to the next CR/LF */
366 		/* NOTE: keep room in match_buf for ending NUL char */
367 		match_len = net_buf_linearize(data->match_buf,
368 					      data->match_buf_len - 1,
369 					      data->rx_buf, 0, len);
370 		if ((data->match_buf_len - 1) < match_len) {
371 			LOG_ERR("Match buffer size (%zu) is too small for "
372 				"incoming command size: %zu!  Truncating!",
373 				data->match_buf_len - 1, match_len);
374 		}
375 
376 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
377 		LOG_HEXDUMP_DBG(data->match_buf, match_len, "RECV");
378 #endif
379 
380 		k_sem_take(&data->sem_parse_lock, K_FOREVER);
381 
382 		cmd = find_cmd_match(data);
383 		if (cmd) {
384 			LOG_DBG("match cmd [%s] (len:%zu)",
385 				cmd->cmd, match_len);
386 
387 			ret = process_cmd(cmd, match_len, data);
388 			if (ret == -EAGAIN) {
389 				k_sem_give(&data->sem_parse_lock);
390 				break;
391 			} else if (ret < 0) {
392 				LOG_ERR("process cmd [%s] (len:%zu, ret:%d)",
393 					cmd->cmd, match_len, ret);
394 			}
395 
396 			/*
397 			 * make sure we didn't run out of data during
398 			 * command processing
399 			 */
400 			if (!data->rx_buf) {
401 				/* we're out of data, exit early */
402 				k_sem_give(&data->sem_parse_lock);
403 				break;
404 			}
405 
406 			frag = NULL;
407 			/*
408 			 * We've handled the current line.
409 			 * Let's skip any "extra" data in that
410 			 * line, and look for the next CR/LF.
411 			 * This leaves us ready for the next
412 			 * handler search.
413 			 * Ignore the length returned.
414 			 */
415 			(void)findcrlf(data, &frag, &offset);
416 		}
417 
418 		k_sem_give(&data->sem_parse_lock);
419 
420 		if (frag && data->rx_buf) {
421 			/* clear out processed line (net_buf's) */
422 			while (frag && data->rx_buf != frag) {
423 				data->rx_buf = net_buf_frag_del(NULL,
424 								data->rx_buf);
425 			}
426 
427 			net_buf_pull(data->rx_buf, offset);
428 		}
429 	}
430 }
431 
cmd_handler_process(struct modem_cmd_handler * cmd_handler,struct modem_iface * iface)432 static void cmd_handler_process(struct modem_cmd_handler *cmd_handler,
433 				struct modem_iface *iface)
434 {
435 	struct modem_cmd_handler_data *data;
436 	int err;
437 
438 	if (!cmd_handler || !cmd_handler->cmd_handler_data ||
439 	    !iface || !iface->read) {
440 		return;
441 	}
442 
443 	data = (struct modem_cmd_handler_data *)(cmd_handler->cmd_handler_data);
444 
445 	do {
446 		err = cmd_handler_process_iface_data(data, iface);
447 		cmd_handler_process_rx_buf(data);
448 	} while (err);
449 }
450 
modem_cmd_handler_get_error(struct modem_cmd_handler_data * data)451 int modem_cmd_handler_get_error(struct modem_cmd_handler_data *data)
452 {
453 	if (!data) {
454 		return -EINVAL;
455 	}
456 
457 	return data->last_error;
458 }
459 
modem_cmd_handler_set_error(struct modem_cmd_handler_data * data,int error_code)460 int modem_cmd_handler_set_error(struct modem_cmd_handler_data *data,
461 				int error_code)
462 {
463 	if (!data) {
464 		return -EINVAL;
465 	}
466 
467 	data->last_error = error_code;
468 	return 0;
469 }
470 
modem_cmd_handler_update_cmds(struct modem_cmd_handler_data * data,const struct modem_cmd * handler_cmds,size_t handler_cmds_len,bool reset_error_flag)471 int modem_cmd_handler_update_cmds(struct modem_cmd_handler_data *data,
472 				  const struct modem_cmd *handler_cmds,
473 				  size_t handler_cmds_len,
474 				  bool reset_error_flag)
475 {
476 	if (!data) {
477 		return -EINVAL;
478 	}
479 
480 	data->cmds[CMD_HANDLER] = handler_cmds;
481 	data->cmds_len[CMD_HANDLER] = handler_cmds_len;
482 	if (reset_error_flag) {
483 		data->last_error = 0;
484 	}
485 
486 	return 0;
487 }
488 
modem_cmd_send_ext(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,int flags)489 int modem_cmd_send_ext(struct modem_iface *iface,
490 		       struct modem_cmd_handler *handler,
491 		       const struct modem_cmd *handler_cmds,
492 		       size_t handler_cmds_len, const uint8_t *buf,
493 		       struct k_sem *sem, k_timeout_t timeout, int flags)
494 {
495 	struct modem_cmd_handler_data *data;
496 	int ret = 0;
497 
498 	if (!iface || !handler || !handler->cmd_handler_data || !buf) {
499 		return -EINVAL;
500 	}
501 
502 	if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
503 		/* semaphore is not needed if there is no timeout */
504 		sem = NULL;
505 	} else if (!sem) {
506 		/* cannot respect timeout without semaphore */
507 		return -EINVAL;
508 	}
509 
510 	data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
511 	if (!(flags & MODEM_NO_TX_LOCK)) {
512 		k_sem_take(&data->sem_tx_lock, K_FOREVER);
513 	}
514 
515 	if (!(flags & MODEM_NO_SET_CMDS)) {
516 		ret = modem_cmd_handler_update_cmds(data, handler_cmds,
517 						    handler_cmds_len, true);
518 		if (ret < 0) {
519 			goto unlock_tx_lock;
520 		}
521 	}
522 
523 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
524 	LOG_HEXDUMP_DBG(buf, strlen(buf), "SENT DATA");
525 
526 	if (data->eol_len > 0) {
527 		if (data->eol[0] != '\r') {
528 			/* Print the EOL only if it is not \r, otherwise there
529 			 * is just too much printing.
530 			 */
531 			LOG_HEXDUMP_DBG(data->eol, data->eol_len, "SENT EOL");
532 		}
533 	} else {
534 		LOG_DBG("EOL not set!!!");
535 	}
536 #endif
537 	if (sem) {
538 		k_sem_reset(sem);
539 	}
540 
541 	iface->write(iface, buf, strlen(buf));
542 	iface->write(iface, data->eol, data->eol_len);
543 
544 	if (sem) {
545 		ret = k_sem_take(sem, timeout);
546 
547 		if (ret == 0) {
548 			ret = data->last_error;
549 		} else if (ret == -EAGAIN) {
550 			ret = -ETIMEDOUT;
551 		}
552 	}
553 
554 	if (!(flags & MODEM_NO_UNSET_CMDS)) {
555 		/* unset handlers and ignore any errors */
556 		(void)modem_cmd_handler_update_cmds(data, NULL, 0U, false);
557 	}
558 
559 unlock_tx_lock:
560 	if (!(flags & MODEM_NO_TX_LOCK)) {
561 		k_sem_give(&data->sem_tx_lock);
562 	}
563 
564 	return ret;
565 }
566 
567 /* run a set of AT commands */
modem_cmd_handler_setup_cmds(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct setup_cmd * cmds,size_t cmds_len,struct k_sem * sem,k_timeout_t timeout)568 int modem_cmd_handler_setup_cmds(struct modem_iface *iface,
569 				 struct modem_cmd_handler *handler,
570 				 const struct setup_cmd *cmds, size_t cmds_len,
571 				 struct k_sem *sem, k_timeout_t timeout)
572 {
573 	int ret = 0;
574 	size_t i;
575 
576 	for (i = 0; i < cmds_len; i++) {
577 
578 		if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
579 			ret = modem_cmd_send(iface, handler,
580 					     &cmds[i].handle_cmd, 1U,
581 					     cmds[i].send_cmd,
582 					     sem, timeout);
583 		} else {
584 			ret = modem_cmd_send(iface, handler,
585 					     NULL, 0, cmds[i].send_cmd,
586 					     sem, timeout);
587 		}
588 
589 		k_sleep(K_MSEC(50));
590 
591 		if (ret < 0) {
592 			LOG_ERR("command %s ret:%d",
593 				cmds[i].send_cmd, ret);
594 			break;
595 		}
596 	}
597 
598 	return ret;
599 }
600 
601 /* run a set of AT commands, without lock */
modem_cmd_handler_setup_cmds_nolock(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct setup_cmd * cmds,size_t cmds_len,struct k_sem * sem,k_timeout_t timeout)602 int modem_cmd_handler_setup_cmds_nolock(struct modem_iface *iface,
603 					struct modem_cmd_handler *handler,
604 					const struct setup_cmd *cmds,
605 					size_t cmds_len, struct k_sem *sem,
606 					k_timeout_t timeout)
607 {
608 	int ret = 0;
609 	size_t i;
610 
611 	for (i = 0; i < cmds_len; i++) {
612 
613 		if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
614 			ret = modem_cmd_send_nolock(iface, handler,
615 						    &cmds[i].handle_cmd, 1U,
616 						    cmds[i].send_cmd,
617 						    sem, timeout);
618 		} else {
619 			ret = modem_cmd_send_nolock(iface, handler,
620 						    NULL, 0, cmds[i].send_cmd,
621 						    sem, timeout);
622 		}
623 
624 		k_sleep(K_MSEC(50));
625 
626 		if (ret < 0) {
627 			LOG_ERR("command %s ret:%d",
628 				cmds[i].send_cmd, ret);
629 			break;
630 		}
631 	}
632 
633 	return ret;
634 }
635 
modem_cmd_handler_tx_lock(struct modem_cmd_handler * handler,k_timeout_t timeout)636 int modem_cmd_handler_tx_lock(struct modem_cmd_handler *handler,
637 			      k_timeout_t timeout)
638 {
639 	struct modem_cmd_handler_data *data;
640 	data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
641 
642 	return k_sem_take(&data->sem_tx_lock, timeout);
643 }
644 
modem_cmd_handler_tx_unlock(struct modem_cmd_handler * handler)645 void modem_cmd_handler_tx_unlock(struct modem_cmd_handler *handler)
646 {
647 	struct modem_cmd_handler_data *data;
648 	data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
649 
650 	k_sem_give(&data->sem_tx_lock);
651 }
652 
modem_cmd_handler_init(struct modem_cmd_handler * handler,struct modem_cmd_handler_data * data,const struct modem_cmd_handler_config * config)653 int modem_cmd_handler_init(struct modem_cmd_handler *handler,
654 			   struct modem_cmd_handler_data *data,
655 			   const struct modem_cmd_handler_config *config)
656 {
657 	/* Verify arguments */
658 	if (handler == NULL || data == NULL || config == NULL) {
659 		return -EINVAL;
660 	}
661 
662 	/* Verify config */
663 	if ((config->match_buf == NULL) ||
664 	    (config->match_buf_len == 0) ||
665 	    (config->buf_pool == NULL) ||
666 	    (NULL != config->response_cmds && 0 == config->response_cmds_len) ||
667 	    (NULL != config->unsol_cmds && 0 == config->unsol_cmds_len)) {
668 		return -EINVAL;
669 	}
670 
671 	/* Assign data to command handler */
672 	handler->cmd_handler_data = data;
673 
674 	/* Assign command process implementation to command handler */
675 	handler->process = cmd_handler_process;
676 
677 	/* Store arguments */
678 	data->match_buf = config->match_buf;
679 	data->match_buf_len = config->match_buf_len;
680 	data->buf_pool = config->buf_pool;
681 	data->alloc_timeout = config->alloc_timeout;
682 	data->eol = config->eol;
683 	data->cmds[CMD_RESP] = config->response_cmds;
684 	data->cmds_len[CMD_RESP] = config->response_cmds_len;
685 	data->cmds[CMD_UNSOL] = config->unsol_cmds;
686 	data->cmds_len[CMD_UNSOL] = config->unsol_cmds_len;
687 
688 	/* Process end of line */
689 	data->eol_len = data->eol == NULL ? 0 : strlen(data->eol);
690 
691 	/* Store optional user data */
692 	data->user_data = config->user_data;
693 
694 	/* Initialize command handler data members */
695 	k_sem_init(&data->sem_tx_lock, 1, 1);
696 	k_sem_init(&data->sem_parse_lock, 1, 1);
697 
698 	return 0;
699 }
700