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 (includes empty string arguments). */
154 if (end >= begin && *argc < argv_len && count < cmd->arg_count_max) {
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_handler_await(struct modem_cmd_handler_data * data,struct k_sem * sem,k_timeout_t timeout)489 int modem_cmd_handler_await(struct modem_cmd_handler_data *data,
490 struct k_sem *sem, k_timeout_t timeout)
491 {
492 int ret = k_sem_take(sem, timeout);
493
494 if (ret == 0) {
495 ret = modem_cmd_handler_get_error(data);
496 } else if (ret == -EAGAIN) {
497 ret = -ETIMEDOUT;
498 }
499
500 return ret;
501 }
502
modem_cmd_send_data_nolock(struct modem_iface * iface,const uint8_t * buf,size_t len)503 int modem_cmd_send_data_nolock(struct modem_iface *iface,
504 const uint8_t *buf, size_t len)
505 {
506 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
507 if (len > 256) {
508 /* Truncate the message, since too long log messages gets dropped somewhere. */
509 LOG_HEXDUMP_DBG(buf, 256, "SENT DIRECT DATA (truncated)");
510 } else {
511 LOG_HEXDUMP_DBG(buf, len, "SENT DIRECT DATA");
512 }
513 #endif
514 return iface->write(iface, buf, len);
515 }
516
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)517 int modem_cmd_send_ext(struct modem_iface *iface,
518 struct modem_cmd_handler *handler,
519 const struct modem_cmd *handler_cmds,
520 size_t handler_cmds_len, const uint8_t *buf,
521 struct k_sem *sem, k_timeout_t timeout, int flags)
522 {
523 struct modem_cmd_handler_data *data;
524 int ret = 0;
525
526 if (!iface || !handler || !handler->cmd_handler_data || !buf) {
527 return -EINVAL;
528 }
529
530 if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
531 /* semaphore is not needed if there is no timeout */
532 sem = NULL;
533 } else if (!sem) {
534 /* cannot respect timeout without semaphore */
535 return -EINVAL;
536 }
537
538 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
539 if (!(flags & MODEM_NO_TX_LOCK)) {
540 k_sem_take(&data->sem_tx_lock, K_FOREVER);
541 }
542
543 if (!(flags & MODEM_NO_SET_CMDS)) {
544 ret = modem_cmd_handler_update_cmds(data, handler_cmds,
545 handler_cmds_len, true);
546 if (ret < 0) {
547 goto unlock_tx_lock;
548 }
549 }
550
551 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
552 LOG_HEXDUMP_DBG(buf, strlen(buf), "SENT DATA");
553
554 if (data->eol_len > 0) {
555 if (data->eol[0] != '\r') {
556 /* Print the EOL only if it is not \r, otherwise there
557 * is just too much printing.
558 */
559 LOG_HEXDUMP_DBG(data->eol, data->eol_len, "SENT EOL");
560 }
561 } else {
562 LOG_DBG("EOL not set!!!");
563 }
564 #endif
565 if (sem) {
566 k_sem_reset(sem);
567 }
568
569 iface->write(iface, buf, strlen(buf));
570 iface->write(iface, data->eol, data->eol_len);
571
572 if (sem) {
573 ret = modem_cmd_handler_await(data, sem, timeout);
574 }
575
576 if (!(flags & MODEM_NO_UNSET_CMDS)) {
577 /* unset handlers and ignore any errors */
578 (void)modem_cmd_handler_update_cmds(data, NULL, 0U, false);
579 }
580
581 unlock_tx_lock:
582 if (!(flags & MODEM_NO_TX_LOCK)) {
583 k_sem_give(&data->sem_tx_lock);
584 }
585
586 return ret;
587 }
588
589 /* 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)590 int modem_cmd_handler_setup_cmds(struct modem_iface *iface,
591 struct modem_cmd_handler *handler,
592 const struct setup_cmd *cmds, size_t cmds_len,
593 struct k_sem *sem, k_timeout_t timeout)
594 {
595 int ret = 0;
596 size_t i;
597
598 for (i = 0; i < cmds_len; i++) {
599
600 if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
601 ret = modem_cmd_send(iface, handler,
602 &cmds[i].handle_cmd, 1U,
603 cmds[i].send_cmd,
604 sem, timeout);
605 } else {
606 ret = modem_cmd_send(iface, handler,
607 NULL, 0, cmds[i].send_cmd,
608 sem, timeout);
609 }
610
611 k_sleep(K_MSEC(50));
612
613 if (ret < 0) {
614 LOG_ERR("command %s ret:%d",
615 cmds[i].send_cmd, ret);
616 break;
617 }
618 }
619
620 return ret;
621 }
622
623 /* 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)624 int modem_cmd_handler_setup_cmds_nolock(struct modem_iface *iface,
625 struct modem_cmd_handler *handler,
626 const struct setup_cmd *cmds,
627 size_t cmds_len, struct k_sem *sem,
628 k_timeout_t timeout)
629 {
630 int ret = 0;
631 size_t i;
632
633 for (i = 0; i < cmds_len; i++) {
634
635 if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
636 ret = modem_cmd_send_nolock(iface, handler,
637 &cmds[i].handle_cmd, 1U,
638 cmds[i].send_cmd,
639 sem, timeout);
640 } else {
641 ret = modem_cmd_send_nolock(iface, handler,
642 NULL, 0, cmds[i].send_cmd,
643 sem, timeout);
644 }
645
646 k_sleep(K_MSEC(50));
647
648 if (ret < 0) {
649 LOG_ERR("command %s ret:%d",
650 cmds[i].send_cmd, ret);
651 break;
652 }
653 }
654
655 return ret;
656 }
657
modem_cmd_handler_tx_lock(struct modem_cmd_handler * handler,k_timeout_t timeout)658 int modem_cmd_handler_tx_lock(struct modem_cmd_handler *handler,
659 k_timeout_t timeout)
660 {
661 struct modem_cmd_handler_data *data;
662 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
663
664 return k_sem_take(&data->sem_tx_lock, timeout);
665 }
666
modem_cmd_handler_tx_unlock(struct modem_cmd_handler * handler)667 void modem_cmd_handler_tx_unlock(struct modem_cmd_handler *handler)
668 {
669 struct modem_cmd_handler_data *data;
670 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
671
672 k_sem_give(&data->sem_tx_lock);
673 }
674
modem_cmd_handler_init(struct modem_cmd_handler * handler,struct modem_cmd_handler_data * data,const struct modem_cmd_handler_config * config)675 int modem_cmd_handler_init(struct modem_cmd_handler *handler,
676 struct modem_cmd_handler_data *data,
677 const struct modem_cmd_handler_config *config)
678 {
679 /* Verify arguments */
680 if (handler == NULL || data == NULL || config == NULL) {
681 return -EINVAL;
682 }
683
684 /* Verify config */
685 if ((config->match_buf == NULL) ||
686 (config->match_buf_len == 0) ||
687 (config->buf_pool == NULL) ||
688 (NULL != config->response_cmds && 0 == config->response_cmds_len) ||
689 (NULL != config->unsol_cmds && 0 == config->unsol_cmds_len)) {
690 return -EINVAL;
691 }
692
693 /* Assign data to command handler */
694 handler->cmd_handler_data = data;
695
696 /* Assign command process implementation to command handler */
697 handler->process = cmd_handler_process;
698
699 /* Store arguments */
700 data->match_buf = config->match_buf;
701 data->match_buf_len = config->match_buf_len;
702 data->buf_pool = config->buf_pool;
703 data->alloc_timeout = config->alloc_timeout;
704 data->eol = config->eol;
705 data->cmds[CMD_RESP] = config->response_cmds;
706 data->cmds_len[CMD_RESP] = config->response_cmds_len;
707 data->cmds[CMD_UNSOL] = config->unsol_cmds;
708 data->cmds_len[CMD_UNSOL] = config->unsol_cmds_len;
709
710 /* Process end of line */
711 data->eol_len = data->eol == NULL ? 0 : strlen(data->eol);
712
713 /* Store optional user data */
714 data->user_data = config->user_data;
715
716 /* Initialize command handler data members */
717 k_sem_init(&data->sem_tx_lock, 1, 1);
718 k_sem_init(&data->sem_parse_lock, 1, 1);
719
720 return 0;
721 }
722