1 /*
2  * Copyright (c) 2022 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/modem/backend/tty.h>
8 
9 #include <zephyr/logging/log.h>
10 LOG_MODULE_REGISTER(modem_backend_tty, CONFIG_MODEM_MODULES_LOG_LEVEL);
11 
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <poll.h>
15 #include <string.h>
16 
17 #define MODEM_BACKEND_TTY_THREAD_PRIO          (10)
18 #define MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS (1000)
19 #define MODEM_BACKEND_TTY_THREAD_POLL_DELAY    (100)
20 
21 #define MODEM_BACKEND_TTY_STATE_RUN_BIT (1)
22 
modem_backend_tty_routine(void * p1,void * p2,void * p3)23 static void modem_backend_tty_routine(void *p1, void *p2, void *p3)
24 {
25 	struct modem_backend_tty *backend = (struct modem_backend_tty *)p1;
26 	struct pollfd pd;
27 
28 	ARG_UNUSED(p2);
29 	ARG_UNUSED(p3);
30 
31 	pd.fd = backend->tty_fd;
32 	pd.events = POLLIN;
33 
34 	/* Run until run flag is cleared. Check every MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS */
35 	while (atomic_test_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
36 		/* Clear events */
37 		pd.revents = 0;
38 
39 		if (poll(&pd, 1, MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS) < 0) {
40 			LOG_ERR("Poll operation failed");
41 			break;
42 		}
43 
44 		if (pd.revents & POLLIN) {
45 			modem_pipe_notify_receive_ready(&backend->pipe);
46 		}
47 
48 		k_sleep(K_MSEC(MODEM_BACKEND_TTY_THREAD_POLL_DELAY));
49 	}
50 }
51 
modem_backend_tty_open(void * data)52 static int modem_backend_tty_open(void *data)
53 {
54 	struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
55 
56 	if (atomic_test_and_set_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
57 		return -EALREADY;
58 	}
59 
60 	backend->tty_fd = open(backend->tty_path, (O_RDWR | O_NONBLOCK), 0644);
61 	if (backend->tty_fd < 0) {
62 		return -EPERM;
63 	}
64 
65 	k_thread_create(&backend->thread, backend->stack, backend->stack_size,
66 			modem_backend_tty_routine, backend, NULL, NULL,
67 			MODEM_BACKEND_TTY_THREAD_PRIO, 0, K_NO_WAIT);
68 
69 	modem_pipe_notify_opened(&backend->pipe);
70 	return 0;
71 }
72 
modem_backend_tty_transmit(void * data,const uint8_t * buf,size_t size)73 static int modem_backend_tty_transmit(void *data, const uint8_t *buf, size_t size)
74 {
75 	struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
76 	int ret;
77 
78 	ret = write(backend->tty_fd, buf, size);
79 	modem_pipe_notify_transmit_idle(&backend->pipe);
80 	return ret;
81 }
82 
modem_backend_tty_receive(void * data,uint8_t * buf,size_t size)83 static int modem_backend_tty_receive(void *data, uint8_t *buf, size_t size)
84 {
85 	int ret;
86 	struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
87 
88 	ret = read(backend->tty_fd, buf, size);
89 	return (ret < 0) ? 0 : ret;
90 }
91 
modem_backend_tty_close(void * data)92 static int modem_backend_tty_close(void *data)
93 {
94 	struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
95 
96 	if (!atomic_test_and_clear_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
97 		return -EALREADY;
98 	}
99 
100 	k_thread_join(&backend->thread, K_MSEC(MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS * 2));
101 	close(backend->tty_fd);
102 	modem_pipe_notify_closed(&backend->pipe);
103 	return 0;
104 }
105 
106 static const struct modem_pipe_api modem_backend_tty_api = {
107 	.open = modem_backend_tty_open,
108 	.transmit = modem_backend_tty_transmit,
109 	.receive = modem_backend_tty_receive,
110 	.close = modem_backend_tty_close,
111 };
112 
modem_backend_tty_init(struct modem_backend_tty * backend,const struct modem_backend_tty_config * config)113 struct modem_pipe *modem_backend_tty_init(struct modem_backend_tty *backend,
114 					  const struct modem_backend_tty_config *config)
115 {
116 	__ASSERT_NO_MSG(backend != NULL);
117 	__ASSERT_NO_MSG(config != NULL);
118 	__ASSERT_NO_MSG(config->tty_path != NULL);
119 
120 	memset(backend, 0x00, sizeof(*backend));
121 	backend->tty_path = config->tty_path;
122 	backend->stack = config->stack;
123 	backend->stack_size = config->stack_size;
124 	atomic_set(&backend->state, 0);
125 	modem_pipe_init(&backend->pipe, backend, &modem_backend_tty_api);
126 	return &backend->pipe;
127 }
128