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