make uart_pty threadsafe
[simavr] / examples / parts / uart_pty.c
1 /*
2         uart_pty.c
3
4         Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
5
6         This file is part of simavr.
7
8         simavr is free software: you can redistribute it and/or modify
9         it under the terms of the GNU General Public License as published by
10         the Free Software Foundation, either version 3 of the License, or
11         (at your option) any later version.
12
13         simavr is distributed in the hope that it will be useful,
14         but WITHOUT ANY WARRANTY; without even the implied warranty of
15         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16         GNU General Public License for more details.
17
18         You should have received a copy of the GNU General Public License
19         along with simavr.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include "sim_network.h"
23 #include <stdlib.h>
24 #include <pthread.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <signal.h>
30 #ifdef __APPLE__
31 #include <util.h>
32 #else
33 #include <pty.h>
34 #endif
35
36 #include "uart_pty.h"
37 #include "avr_uart.h"
38 #include "sim_hex.h"
39
40 DEFINE_FIFO(uint8_t,uart_pty_fifo);
41
42 //#define TRACE(_w) _w
43 #ifndef TRACE
44 #define TRACE(_w)
45 #endif
46
47 #define LOCK_MUTEX { TRACE(printf("%s lock:  %p\n", __FUNCTION__ , (void*)pthread_self())); pthread_mutex_lock(&p->lock); }
48 #define UNLOCK_MUTEX { pthread_mutex_unlock(&p->lock); TRACE(printf("%s unlock: %p\n", __FUNCTION__ , (void*)pthread_self())); }
49
50 /*
51  * called when a byte is send via the uart on the AVR
52  */
53 static void
54 uart_pty_in_hook(
55                 struct avr_irq_t * irq,
56                 uint32_t value,
57                 void * param)
58 {
59         uart_pty_t * p = (uart_pty_t*)param;
60         LOCK_MUTEX;
61         TRACE(printf("uart_pty_in_hook %02x\n", value);)
62         uart_pty_fifo_write(&p->pty.in, value);
63
64         if (p->tap.s) {
65                 if (p->tap.crlf && value == '\n')
66                         uart_pty_fifo_write(&p->tap.in, '\r');
67                 uart_pty_fifo_write(&p->tap.in, value);
68         }
69         UNLOCK_MUTEX;
70 }
71
72 // try to empty our fifo, the uart_pty_xoff_hook() will be called when
73 // other side is full
74 static void
75 uart_pty_flush_incoming(
76                 uart_pty_t * p)
77 {
78         LOCK_MUTEX;
79         while (p->xon && !uart_pty_fifo_isempty(&p->pty.out)) {
80                 uint8_t byte = uart_pty_fifo_read(&p->pty.out);
81                 TRACE(printf("uart_pty_flush_incoming send %02x\n", byte);)
82                 avr_raise_irq(p->irq + IRQ_UART_PTY_BYTE_OUT, byte);
83
84                 if (p->tap.s) {
85                         if (p->tap.crlf && byte == '\n')
86                                 uart_pty_fifo_write(&p->tap.in, '\r');
87                         uart_pty_fifo_write(&p->tap.in, byte);
88                 }
89         }
90         if (p->tap.s) {
91                 while (p->xon && !uart_pty_fifo_isempty(&p->tap.out)) {
92                         uint8_t byte = uart_pty_fifo_read(&p->tap.out);
93                         if (p->tap.crlf && byte == '\r') {
94                                 uart_pty_fifo_write(&p->tap.in, '\n');
95                         }
96                         if (byte == '\n')
97                                 continue;
98                         uart_pty_fifo_write(&p->tap.in, byte);
99                         avr_raise_irq(p->irq + IRQ_UART_PTY_BYTE_OUT, byte);
100                 }
101         }
102         UNLOCK_MUTEX;
103 }
104
105 /*
106  * Called when the uart has room in it's input buffer. This is called repeateadly
107  * if necessary, while the xoff is called only when the uart fifo is FULL
108  */
109 static void
110 uart_pty_xon_hook(
111                 struct avr_irq_t * irq,
112                 uint32_t value,
113                 void * param)
114 {
115         uart_pty_t * p = (uart_pty_t*)param;
116         TRACE(if (!p->xon) printf("uart_pty_xon_hook\n");)
117         p->xon = 1;
118         uart_pty_flush_incoming(p);
119 }
120
121 /*
122  * Called when the uart ran out of room in it's input buffer
123  */
124 static void
125 uart_pty_xoff_hook(
126                 struct avr_irq_t * irq,
127                 uint32_t value,
128                 void * param)
129 {
130         uart_pty_t * p = (uart_pty_t*)param;
131         TRACE(if (p->xon) printf("uart_pty_xoff_hook\n");)
132         p->xon = 0;
133 }
134
135 static void *
136 uart_pty_thread(
137                 void * param)
138 {
139         uart_pty_t * p = (uart_pty_t*)param;
140
141         while (1) {
142                 fd_set read_set, write_set;
143                 int max = 0;
144                 FD_ZERO(&read_set);
145                 FD_ZERO(&write_set);
146
147                 for (int ti = 0; ti < 2; ti++) if (p->port[ti].s) {
148                         // read more only if buffer was flushed
149                         if (p->port[ti].buffer_len == p->port[ti].buffer_done) {
150                                 FD_SET(p->port[ti].s, &read_set);
151                                 max = p->port[ti].s > max ? p->port[ti].s : max;
152                         }
153                         if (!uart_pty_fifo_isempty(&p->port[ti].in)) {
154                                 FD_SET(p->port[ti].s, &write_set);
155                                 max = p->port[ti].s > max ? p->port[ti].s : max;
156                         }
157                 }
158
159                 struct timeval timo = { 0, 500 };       // short, but not too short interval
160                 int ret = select(max+1, &read_set, &write_set, NULL, &timo);
161
162                 if (!ret)
163                         continue;
164                 if (ret < 0)
165                         break;
166
167                 for (int ti = 0; ti < 2; ti++) if (p->port[ti].s) {
168                         if (FD_ISSET(p->port[ti].s, &read_set)) {
169                                 LOCK_MUTEX;
170                                 ssize_t r = read(p->port[ti].s, p->port[ti].buffer, sizeof(p->port[ti].buffer)-1);
171                                 p->port[ti].buffer_len = r;
172                                 p->port[ti].buffer_done = 0;
173                                 TRACE(hdump("pty recv", p->port[ti].buffer, r);)
174                                 UNLOCK_MUTEX;
175                         }
176                         if (p->port[ti].buffer_done < p->port[ti].buffer_len) {
177                                 LOCK_MUTEX;
178                                 // write them in fifo
179                                 while (p->port[ti].buffer_done < p->port[ti].buffer_len &&
180                                                 !uart_pty_fifo_isfull(&p->port[ti].out))
181                                         uart_pty_fifo_write(&p->port[ti].out,
182                                                         p->port[ti].buffer[p->port[ti].buffer_done++]);
183                                 UNLOCK_MUTEX;
184                         }
185                         if (FD_ISSET(p->port[ti].s, &write_set)) {
186                                 LOCK_MUTEX;
187                                 uint8_t buffer[512];
188                                 // write them in fifo
189                                 uint8_t * dst = buffer;
190                                 while (!uart_pty_fifo_isempty(&p->port[ti].in) &&
191                                                 dst < (buffer + sizeof(buffer)))
192                                         *dst++ = uart_pty_fifo_read(&p->port[ti].in);
193                                 size_t len = dst - buffer;
194                                 TRACE(size_t r =) write(p->port[ti].s, buffer, len);
195                                 TRACE(hdump("pty send", buffer, r);)
196                                 UNLOCK_MUTEX;
197                         }
198                 }
199                 uart_pty_flush_incoming(p);
200         }
201         return NULL;
202 }
203
204 static const char * irq_names[IRQ_UART_PTY_COUNT] = {
205         [IRQ_UART_PTY_BYTE_IN] = "8<uart_pty.in",
206         [IRQ_UART_PTY_BYTE_OUT] = "8>uart_pty.out",
207 };
208
209 void
210 uart_pty_init(
211                 struct avr_t * avr,
212                 uart_pty_t * p)
213 {
214         memset(p, 0, sizeof(*p));
215
216         p->avr = avr;
217         p->irq = avr_alloc_irq(&avr->irq_pool, 0, IRQ_UART_PTY_COUNT, irq_names);
218         avr_irq_register_notify(p->irq + IRQ_UART_PTY_BYTE_IN, uart_pty_in_hook, p);
219
220         int hastap = (getenv("SIMAVR_UART_TAP") && atoi(getenv("SIMAVR_UART_TAP"))) ||
221                         (getenv("SIMAVR_UART_XTERM") && atoi(getenv("SIMAVR_UART_XTERM"))) ;
222
223         for (int ti = 0; ti < 1 + hastap; ti++) {
224                 int m, s;
225
226                 if (openpty(&m, &s, p->port[ti].slavename, NULL, NULL) < 0) {
227                         fprintf(stderr, "%s: Can't create pty: %s", __FUNCTION__, strerror(errno));
228                         return ;
229                 }
230                 struct termios tio;
231                 tcgetattr(m, &tio);
232                 cfmakeraw(&tio);
233                 tcsetattr(m, TCSANOW, &tio);
234                 p->port[ti].s = m;
235                 p->port[ti].tap = ti != 0;
236                 p->port[ti].crlf = ti != 0;
237                 printf("uart_pty_init %s on port *** %s ***\n",
238                                 ti == 0 ? "bridge" : "tap", p->port[ti].slavename);
239         }
240
241         pthread_mutex_init(&p->lock, NULL);
242         pthread_create(&p->thread, NULL, uart_pty_thread, p);
243
244 }
245
246 void
247 uart_pty_stop(
248                 uart_pty_t * p)
249 {
250         puts(__func__);
251         pthread_kill(p->thread, SIGINT);
252         for (int ti = 0; ti < 2; ti++)
253                 if (p->port[ti].s)
254                         close(p->port[ti].s);
255         void * ret;
256         pthread_join(p->thread, &ret);
257 }
258
259 void
260 uart_pty_connect(
261                 uart_pty_t * p,
262                 char uart)
263 {
264         // disable the stdio dump, as we are sending binary there
265         uint32_t f = 0;
266         avr_ioctl(p->avr, AVR_IOCTL_UART_GET_FLAGS(uart), &f);
267         f &= ~AVR_UART_FLAG_STDIO;
268         avr_ioctl(p->avr, AVR_IOCTL_UART_SET_FLAGS(uart), &f);
269
270         avr_irq_t * src = avr_io_getirq(p->avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUTPUT);
271         avr_irq_t * dst = avr_io_getirq(p->avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_INPUT);
272         avr_irq_t * xon = avr_io_getirq(p->avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUT_XON);
273         avr_irq_t * xoff = avr_io_getirq(p->avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUT_XOFF);
274         if (src && dst) {
275                 avr_connect_irq(src, p->irq + IRQ_UART_PTY_BYTE_IN);
276                 avr_connect_irq(p->irq + IRQ_UART_PTY_BYTE_OUT, dst);
277         }
278         if (xon)
279                 avr_irq_register_notify(xon, uart_pty_xon_hook, p);
280         if (xoff)
281                 avr_irq_register_notify(xoff, uart_pty_xoff_hook, p);
282
283         for (int ti = 0; ti < 1; ti++) if (p->port[ti].s) {
284                 char link[128];
285                 sprintf(link, "/tmp/simavr-uart%s%c", ti == 1 ? "tap" : "", uart);
286                 unlink(link);
287                 if (symlink(p->port[ti].slavename, link) != 0) {
288                         fprintf(stderr, "WARN %s: Can't create %s: %s", __func__, link, strerror(errno));
289                 } else {
290                         printf("%s: %s now points to %s\n", __func__, link, p->port[ti].slavename);
291                 }
292         }
293         if (getenv("SIMAVR_UART_XTERM") && atoi(getenv("SIMAVR_UART_XTERM"))) {
294                 char cmd[256];
295                 sprintf(cmd, "xterm -e picocom -b 115200 %s >/dev/null 2>&1 &",
296                                 p->tap.slavename);
297                 system(cmd);
298         } else
299                 printf("note: export SIMAVR_UART_XTERM=1 and install picocom to get a terminal\n");
300 }
301