plptools
Loading...
Searching...
No Matches
datalink.cc
Go to the documentation of this file.
1/*
2 * This file is part of plptools.
3 *
4 * Copyright (C) 1999 Philip Proudman <philip.proudman@btinternet.com>
5 * Copyright (C) 1999-2001 Fritz Elfert <felfert@to.com>
6 * Copyright (C) 2026 Jason Morley <hello@jbmorley.co.uk>
7 *
8 * This program 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 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program 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 along
19 * along with this program; if not, see <https://www.gnu.org/licenses/>.
20 *
21 */
22#include "bufferarray.h"
23#include "config.h"
24
25#include <mutex>
26#include <pthread.h>
27#include <string>
28#include <cstring>
29#include <fstream>
30#include <iomanip>
31#include <iowatch.h>
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <sys/param.h>
36#include <unistd.h>
37#include <sys/time.h>
38#include <sys/types.h>
39#include <errno.h>
40#include <sys/ioctl.h>
41#include <termios.h>
42#include <signal.h>
43#include <fcntl.h>
44
45#include "link.h"
46#include "mp_serial.h"
47#include "ncp_log.h"
48#include "datalink.h"
49
50#define BUFLEN 4096 // Must be a power of 2
51#define BUFMASK (BUFLEN-1)
52#define hasSpace(dir) (((dir##Write + 1) & BUFMASK) != dir##Read)
53#define hasData(dir) (dir##Write != dir##Read)
54#define inca(idx,amount) do { \
55 idx = (idx + amount) & BUFMASK; \
56} while (0)
57#define inc1(idx) inca(idx, 1)
58#define normalize(idx) do { idx &= BUFMASK; } while (0)
59
60extern "C" {
66static void usr1handler(int sig) {
67 signal(SIGUSR1, usr1handler);
69
70void log_data(unsigned short options,
71 unsigned short category,
72 std::string description,
73 unsigned char *buffer, int length) {
74 if (!(options & category)) {
75 return;
76 }
77 printf("pump: %s %d bytes: (", description.c_str(), length);
78 for (int i = 0; i<length; i++) {
79 printf("%02x ", buffer[i]);
80 }
81 printf(")\n");
82}
83
84static void *data_pump_thread(void *arg) {
85 DataLink *dataLink = (DataLink *)arg;
86 while (1) {
87
88 // Get the serial port file descriptor.
89 int serialFd = -1;
90 {
91 std::lock_guard<std::mutex> lock(dataLink->serialMutex_);
92 serialFd = dataLink->fd;
93 }
94
95 if (serialFd == -1) {
96 IOWatch cancellationWatch;
97 cancellationWatch.addIO(dataLink->cancellationFd_);
98 if (cancellationWatch.watch(1, 0)) {
99 // If the watch returned true, cancellationFd_ is readable and we need to shut down.
100 dataLink->shutdown();
101 return nullptr;
102 }
103 } else {
104 fd_set r_set;
105 fd_set w_set;
106
107 // Conditionally watch to to see if we can read and write from the serial port, depending on whether we have
108 // space in the input buffer, and data in the output buffer.
109 FD_ZERO(&r_set);
110 w_set = r_set;
111 FD_SET(dataLink->cancellationFd_, &r_set);
112 {
113 std::lock_guard<std::mutex> inputLock(dataLink->inputMutex_);
114 if (hasSpace(dataLink->in)) {
115 FD_SET(serialFd, &r_set);
116 }
117 }
118 {
119 std::lock_guard<std::mutex> outputLock(dataLink->outputMutex_);
120 if (hasData(dataLink->out)) {
121 FD_SET(serialFd, &w_set);
122 }
123 }
124
125 struct timeval tv = {1, 0};
126 int res = select(MAX(serialFd, dataLink->cancellationFd_) + 1, &r_set, &w_set, NULL, &tv);
127 if (res <= 0) {
128 // Ignore interrupts and timeouts.
129 continue;
130 }
131
132 // Check to see if we were cancelled and, if we were, unblock the writers and exit.
133 if (FD_ISSET(dataLink->cancellationFd_, &r_set)) {
134 dataLink->shutdown();
135 return nullptr;
136 }
137
138 // We can write to the transport; write as much as we can.
139 if (FD_ISSET(serialFd, &w_set)) {
140 std::lock_guard<std::mutex> serialLock(dataLink->serialMutex_);
141 std::lock_guard<std::mutex> outputLock(dataLink->outputMutex_);
142
143 // Work out how much contiguous data there is to write in the out buffer.
144 int count = dataLink->outWrite - dataLink->outRead;
145 if (count < 0) {
146 count = (BUFLEN - dataLink->outRead);
147 }
148
149 // Write as much data as possible.
150 res = write(serialFd, &dataLink->outBuffer[dataLink->outRead], count);
151 if (res > 0) {
152 log_data(dataLink->verbose_, PKT_DEBUG_DUMP, "wrote", dataLink->outBuffer + dataLink->outRead, res);
153 inca(dataLink->outRead, res);
154 dataLink->outputCondition_.notify_all();
155 }
156 }
157
158 // We can read from the transport; read as much as we can.
159 if (FD_ISSET(serialFd, &r_set)) {
160 std::lock_guard<std::mutex> serialLock(dataLink->serialMutex_);
161 std::lock_guard<std::mutex> inputLock(dataLink->inputMutex_);
162
163 // Work out how much contiguous space there is in the buffer.
164 int count = dataLink->inRead - dataLink->inWrite;
165 if (count <= 0) {
166 count = (BUFLEN - dataLink->inWrite);
167 }
168
169 // Read as much data as possible.
170 res = read(serialFd, &dataLink->inBuffer[dataLink->inWrite], count);
171 if (res > 0) {
172 log_data(dataLink->verbose_, PKT_DEBUG_DUMP, "read", dataLink->inBuffer + dataLink->inWrite, res);
173 inca(dataLink->inWrite, res);
174 }
175 }
176
177 // Process any available data.
178 std::vector<BufferStore> receivedData;
179 bool isLinkStable = true;
180 {
181 bool hasInputData = false;
182 {
183 std::lock_guard<std::mutex> inputLock(dataLink->inputMutex_);
184 hasInputData = hasData(dataLink->in);
185 }
186 if (hasInputData) {
187 isLinkStable = dataLink->processInputData(receivedData);
188 }
189 }
190
191 // Reset if we were unable to establish a stable link.
192 if (!isLinkStable) {
193 dataLink->internalReset(false);
194 }
195
196 // Dispatch received data to @ref link_.
197 // Since receivedData is only ever accessed on this thread, we can safely perform this operation without
198 // holding any locks meaning our target can't deadlock against us by calling any of our public APIs that
199 // require locks.
200 dataLink->sendReceivedData(receivedData);
201 }
202 }
203}
204
205};
206
207static const int kBaudRatesTable[] = {
208 115200,
209 57600,
210 38400,
211 19200,
212 9600,
213};
214#define BAUD_RATES_TABLE_SIZE (sizeof(kBaudRatesTable) / sizeof(int))
215
216using namespace std;
217
218DataLink::DataLink(const char *fname,
219 int baud,
220 Link *link,
221 unsigned short verbose,
222 const int cancellationFd)
223: devname(fname)
224, requestedBaudRate_(baud)
225, link_(link)
226, verbose_(verbose)
227, cancellationFd_(cancellationFd) {
228
229 // Initialize CRC table
230 crc_table[0] = 0;
231 for (int i = 0; i < 128; i++) {
232 unsigned int carry = crc_table[i] & 0x8000;
233 unsigned int tmp = (crc_table[i] << 1) & 0xffff;
234 crc_table[i * 2 + (carry ? 0 : 1)] = tmp ^ 0x1021;
235 crc_table[i * 2 + (carry ? 1 : 0)] = tmp;
236 }
237
238 inBuffer = new unsigned char[BUFLEN + 1];
239 outBuffer = new unsigned char[BUFLEN + 1];
240
242 if (requestedBaudRate_ < 0) {
245 }
246 fd = init_serial(devname.c_str(), baudRate_, 0);
247 if (verbose_ & PKT_DEBUG_LOG) {
248 lout << "serial connection set to " << dec << baudRate_
249 << " baud, fd=" << fd << endl;
250 }
251 if (fd == -1) {
252 fcntl(fd, F_SETFL, O_NONBLOCK);
253 lastFatal = true;
254 } else {
255 signal(SIGUSR1, usr1handler);
256 pthread_create(&dataPumpThreadId_, NULL, data_pump_thread, this);
257 }
258}
259
261
262 // Ensure there are no lingering readers.
263 {
264 std::lock_guard<std::mutex> outputLock(outputMutex_);
265 isCancelled_ = true;
266 }
267 outputCondition_.notify_all();
268
269 // Stop the data pump thread and close the serial port.
270 if (fd != -1) {
271 pthread_join(dataPumpThreadId_, NULL);
272 ser_exit(fd);
273 }
274 fd = -1;
275
276 delete []inBuffer;
277 delete []outBuffer;
278}
279
281 internalReset(true);
282}
283
285 std::lock_guard<std::mutex> outputLock(outputMutex_);
286 isCancelled_ = true;
287 outputCondition_.notify_all();
288
289}
290
291void DataLink::internalReset(bool resetBaudRateIndex) {
292 std::lock_guard<std::mutex> serialLock(serialMutex_);
293 std::lock_guard<std::mutex> inputLock(inputMutex_);
294 std::lock_guard<std::mutex> outputLock(outputMutex_);
295
297 lout << "resetting serial connection" << endl;
298 if (fd != -1) {
299 ser_exit(fd);
300 fd = -1;
301 }
302 usleep(100000);
303 outRead = outWrite = 0;
304 inRead = inWrite = 0;
305 esc = false;
306 lastFatal = false;
307 serialStatus = -1;
308 lastSYN = startPkt = -1;
309 crcIn = 0;
311 if (resetBaudRateIndex) {
312 baudRateIndex_ = 0;
313 }
314 justStarted = true;
315 if (requestedBaudRate_ < 0) {
319 baudRateIndex_ = 0;
320 }
321 }
322 fd = init_serial(devname.c_str(), baudRate_, 0);
323 if (verbose_ & PKT_DEBUG_LOG) {
324 lout << "serial connection set to " << dec << baudRate_
325 << " baud, fd=" << fd << endl;
326 }
327 if (fd != -1) {
328 fcntl(fd, F_SETFL, O_NONBLOCK);
329 lastFatal = false;
330 }
331}
332
334 std::lock_guard<std::mutex> serialLock(serialMutex_);
335 return baudRate_;
336}
337
338void DataLink::send(BufferStore &b, bool isEPOC) {
339
340 // Assemble the message.
341 BufferStore message;
342 message.addByte(0x16);
343 message.addByte(0x10);
344 message.addByte(0x02);
345
346 long len = b.getLen();
347
348 if (verbose_ & PKT_DEBUG_LOG) {
349 lout << "packet: >> ";
351 lout << b;
352 else
353 lout << " len=" << dec << len;
354 lout << endl;
355 }
356
357 unsigned short crcOut = 0;
358 for (int i = 0; i < len; i++) {
359 unsigned char c = b.getByte(i);
360 switch (c) {
361 case 0x03:
362 if (isEPOC) {
363 // Stuff ETX as DLE EOT
364 message.addByte(0x10);
365 message.addByte(0x04);
366 } else {
367 message.addByte(c);
368 }
369 break;
370 case 0x10:
371 // Stuff DLE as DLE DLE
372 message.addByte(0x10);
373 message.addByte(0x10);
374 break;
375 default:
376 message.addByte(c);
377 }
378 addToCrc(c, &crcOut);
379 }
380 message.addByte(0x10);
381 message.addByte(0x03);
382 message.addByte(crcOut >> 8);
383 message.addByte(crcOut & 0xff);
384
385 // Signal the data pump thread to write some data and wait on a condition variable for enough space.
386 pthread_kill(dataPumpThreadId_, SIGUSR1);
387 std::unique_lock<std::mutex> outputLock(outputMutex_);
388 outputCondition_.wait(outputLock, [&] {
389 unsigned long free = (outRead - outWrite - 1 + BUFLEN) & BUFMASK;
390 return free >= message.getLen() || isCancelled_;
391 });
392
393 // Exit early if we've been cancelled, dropping the data on the floor.
394 if (isCancelled_) {
395 return;
396 }
397
398 for (unsigned long i = 0; i < message.getLen(); i++) {
399 outBuffer[outWrite] = message.getByte(i);
400 inc1(outWrite);
401 }
402
403 // Signal the data pump thread to tell it there's new data.
404 pthread_kill(dataPumpThreadId_, SIGUSR1);
405}
406
407bool DataLink::processInputData(std::vector<BufferStore> &receivedData) {
408 std::lock_guard<std::mutex> inputLock(inputMutex_);
409
410 int inw = inWrite;
411 int p;
412
413outerLoop:
414 p = (lastSYN >= 0) ? lastSYN : inRead;
415 if (startPkt < 0) {
416 while (p != inw) {
417 normalize(p);
418 if (inBuffer[p++] != 0x16)
419 continue;
420 lastSYN = p - 1;
421 normalize(p);
422 if (p == inw)
423 break;
424 if (inBuffer[p++] != 0x10)
425 continue;
426 normalize(p);
427 if (p == inw)
428 break;
429 if (inBuffer[p++] != 0x02)
430 continue;
431 normalize(p);
432 lastSYN = startPkt = p;
433 crcIn = inCRCstate = 0;
434 rcv.init();
435 esc = false;
436 break;
437 }
438 }
439 if (startPkt >= 0) {
440 justStarted = false;
441 while (p != inw) {
442 unsigned char c = inBuffer[p];
443 switch (inCRCstate) {
444 case 0:
445 if (esc) {
446 esc = false;
447 switch (c) {
448 case 0x03:
449 inCRCstate = 1;
450 break;
451 case 0x04:
452 addToCrc(0x03, &crcIn);
453 rcv.addByte(0x03);
454 break;
455 default:
456 addToCrc(c, &crcIn);
457 rcv.addByte(c);
458 break;
459 }
460 } else {
461 if (c == 0x10)
462 esc = true;
463 else {
464 addToCrc(c, &crcIn);
465 rcv.addByte(c);
466 }
467 }
468 break;
469 case 1:
470 receivedCRC = c;
471 receivedCRC <<= 8;
472 inCRCstate = 2;
473 break;
474 case 2:
475 receivedCRC |= c;
476 inc1(p);
477 inRead = p;
478 startPkt = lastSYN = -1;
479 inCRCstate = 0;
480 if (receivedCRC != crcIn) {
482 lout << "packet: BAD CRC" << endl;
483 } else {
484 if (verbose_ & PKT_DEBUG_LOG) {
485 lout << "packet: << ";
487 lout << rcv;
488 else
489 lout << "len=" << dec << rcv.getLen();
490 lout << endl;
491 }
492 receivedData.push_back(rcv);
493 }
494 rcv.init();
495
496 // Check to see if there's pending data to be sent to the Psion in an effort to avoid starvation.
497 // We should revisit whether this is an unnecessary optimization in the future.
498 bool hasOutputData = false;
499 {
500 std::lock_guard<std::mutex> outputLock(outputMutex_);
501 hasOutputData = hasData(out);
502 }
503 if (hasOutputData) {
504 return true;
505 }
506 goto outerLoop;
507 }
508 inc1(p);
509 }
510 lastSYN = p;
511 } else {
512 // If we get here, no sync was found.
513 // If we are just started and the amount of received data exceeds
514 // 15 bytes, the baudrate is obviously wrong.
515 // (or the connected device is not an EPOC device). Reset the
516 // serial connection and try next baudrate, if auto-baud is set.
517 if (justStarted) {
518 int rx_amount = (inw > inRead) ?
519 inw - inRead : BUFLEN - inRead + inw;
520 if (rx_amount > 15) {
521 return false;
522 }
523 }
524 }
525 return true;
526}
527
528void DataLink::sendReceivedData(std::vector<BufferStore> &receivedData) {
529 for (vector<BufferStore>::iterator i = receivedData.begin(); i != receivedData.end(); i++) {
530 link_->receive(*i);
531 }
532}
533
535 int arg;
536 int res;
537 bool failed = false;
538
539 std::lock_guard<std::mutex> serialLock(serialMutex_);
540
541 if (fd == -1)
542 return false;
543 res = ioctl(fd, TIOCMGET, &arg);
544 if (res < 0)
545 lastFatal = true;
546 if ((serialStatus == -1) || (arg != serialStatus)) {
548 lout << "packet: < DTR:" << ((arg & TIOCM_DTR)?1:0)
549 << " RTS:" << ((arg & TIOCM_RTS)?1:0)
550 << " DCD:" << ((arg & TIOCM_CAR)?1:0)
551 << " DSR:" << ((arg & TIOCM_DSR)?1:0)
552 << " CTS:" << ((arg & TIOCM_CTS)?1:0) << endl;
553 if (!((arg & TIOCM_RTS) && (arg & TIOCM_DTR))) {
554 arg |= (TIOCM_DTR | TIOCM_RTS);
555 res = ioctl(fd, TIOCMSET, &arg);
556 if (res < 0)
557 lastFatal = true;
559 lout << "packet: > DTR:" << ((arg & TIOCM_DTR)?1:0)
560 << " RTS:" << ((arg & TIOCM_RTS)?1:0)
561 << " DCD:" << ((arg & TIOCM_CAR)?1:0)
562 << " DSR:" << ((arg & TIOCM_DSR)?1:0)
563 << " CTS:" << ((arg & TIOCM_CTS)?1:0) << endl;
564 }
565 serialStatus = arg;
566 }
567 // TODO: Check for a solution on Solaris.
568 if ((arg & TIOCM_DSR) == 0) {
569 failed = true;
570 }
572 lout << "packet: linkFATAL\n";
573 if ((verbose_ & PKT_DEBUG_LOG) && failed)
574 lout << "packet: linkFAILED\n";
575 return (lastFatal || failed);
576}
A generic container for an array of bytes.
Definition: bufferstore.h:37
void addByte(unsigned char c)
Appends a byte to the content of this instance.
Definition: bufferstore.cc:157
unsigned long getLen() const
Retrieves the length of a BufferStore.
Definition: bufferstore.cc:92
unsigned char getByte(long pos=0) const
Retrieves the byte at index pos.
Definition: bufferstore.cc:96
void init()
Initializes the BufferStore.
Definition: bufferstore.cc:74
A simple thread-safe wrapper for select()
Definition: iowatch.h:34
void addIO(const int fd)
Adds a file descriptor to the set of descriptors.
Definition: iowatch.cc:42
bool watch(const long secs, const long usecs)
Performs a select() call.
Definition: iowatch.cc:64
void ser_exit(int fd)
Definition: mp_serial.c:172
int init_serial(const char *dev, int speed, int debug)
Definition: mp_serial.c:58
Definition: doctest.h:522
#define PKT_DEBUG_HANDSHAKE
Definition: ncp_log.h:35
std::ostream lout
#define PKT_DEBUG_DUMP
Definition: ncp_log.h:34
#define PKT_DEBUG_LOG
Definition: ncp_log.h:33
int verbose
Definition: plpprintd.cc:58