PX4 Firmware
PX4 Autopilot Software http://px4.io
protocol_splitter.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (c) 2016 PX4 Development Team. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in
13  * the documentation and/or other materials provided with the
14  * distribution.
15  * 3. Neither the name PX4 nor the names of its contributors may be
16  * used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  ****************************************************************************/
33 
34 /**
35  * @file protocol_splitter.cpp
36  * NuttX Driver to multiplex mavlink and RTPS on a single serial port.
37  * Makes sure the two protocols can be read & written simultanously by 2 processes.
38  * It will create two devices:
39  * /dev/mavlink
40  * /dev/rtps
41  */
42 
43 #include <lib/cdev/CDev.hpp>
44 #include <px4_platform_common/sem.hpp>
45 #include <px4_platform_common/log.h>
46 
47 #include <sys/ioctl.h>
48 #include <unistd.h>
49 #include <cstdint>
50 #include <string.h>
51 
52 class Mavlink2Dev;
53 class RtpsDev;
54 class ReadBuffer;
55 
56 extern "C" __EXPORT int protocol_splitter_main(int argc, char *argv[]);
57 
58 struct StaticData {
61  sem_t r_lock;
62  sem_t w_lock;
63  char device_name[16];
65 };
66 
67 namespace
68 {
69 static StaticData *objects = nullptr;
70 }
71 
73 {
74 public:
75  int read(int fd);
76  void move(void *dest, size_t pos, size_t n);
77 
78  uint8_t buffer[512] = {};
79  size_t buf_size = 0;
80 
81  static const size_t BUFFER_THRESHOLD = sizeof(buffer) * 0.8;
82 };
83 
85 {
86  /* Discard whole buffer if it's filled beyond a threshold,
87  * This should prevent buffer being filled by garbage that
88  * no reader (MAVLink or RTPS) can understand.
89  *
90  * TODO: a better approach would be checking if both reader
91  * start understanding messages beyond a certain buffer size,
92  * meaning that everything before is garbage.
93  */
94  if (buf_size > BUFFER_THRESHOLD) {
95  buf_size = 0;
96  }
97 
98  int r = ::read(fd, buffer + buf_size, sizeof(buffer) - buf_size);
99 
100  if (r < 0) {
101  return r;
102  }
103 
104  buf_size += r;
105 
106  return r;
107 }
108 
109 void ReadBuffer::move(void *dest, size_t pos, size_t n)
110 {
111  ASSERT(pos < buf_size);
112  ASSERT(pos + n <= buf_size);
113 
114  memmove(dest, buffer + pos, n); // send desired data
115  memmove(buffer + pos, buffer + (pos + n), sizeof(buffer) - pos - n);
116  buf_size -= n;
117 }
118 
119 class DevCommon : public cdev::CDev
120 {
121 public:
122  DevCommon(const char *device_path);
123  virtual ~DevCommon();
124 
125  virtual int ioctl(struct file *filp, int cmd, unsigned long arg);
126 
127  virtual int open(file *filp);
128  virtual int close(file *filp);
129 
130  enum Operation {Read, Write};
131 
132 protected:
133 
134  virtual pollevent_t poll_state(struct file *filp);
135 
136 
137  void lock(enum Operation op)
138  {
139  sem_t *this_lock = op == Read ? &objects->r_lock : &objects->w_lock;
140 
141  while (sem_wait(this_lock) != 0) {
142  /* The only case that an error should occur here is if
143  * the wait was awakened by a signal.
144  */
145  ASSERT(get_errno() == EINTR);
146  }
147  }
148 
149  void unlock(enum Operation op)
150  {
151  sem_t *this_lock = op == Read ? &objects->r_lock : &objects->w_lock;
152  sem_post(this_lock);
153  }
154 
155  int _fd = -1;
156 
157  uint16_t _packet_len;
158  enum class ParserState : uint8_t {
159  Idle = 0,
160  GotLength
161  };
162  ParserState _parser_state = ParserState::Idle;
163 
164  bool _had_data = false; ///< whether poll() returned available data
165 
166 private:
167 };
168 
169 DevCommon::DevCommon(const char *device_path)
170  : CDev(device_path)
171 {
172 }
173 
175 {
176  if (_fd >= 0) {
177  ::close(_fd);
178  }
179 }
180 
181 int DevCommon::ioctl(struct file *filp, int cmd, unsigned long arg)
182 {
183  //pretend we have enough space left to write, so mavlink will not drop data and throw off
184  //our parsing state
185  if (cmd == FIONSPACE) {
186  *(int *)arg = 1024;
187  return 0;
188  }
189 
190  return ::ioctl(_fd, cmd, arg);
191 }
192 
194 {
195  _fd = ::open(objects->device_name, O_RDWR | O_NOCTTY);
196  CDev::open(filp);
197  return _fd >= 0 ? 0 : -1;
198 }
199 
201 {
202  //int ret = ::close(_fd); // FIXME: calling this results in a dead-lock, because DevCommon::close()
203  // is called from within another close(), and NuttX seems to hold a semaphore at this point
204  _fd = -1;
205  CDev::close(filp);
206  return 0;
207 }
208 
209 pollevent_t DevCommon::poll_state(struct file *filp)
210 {
211  pollfd fds[1];
212  fds[0].fd = _fd;
213  fds[0].events = POLLIN;
214 
215  /* Here we should just check the poll state (which is called before an actual poll waiting).
216  * Instead we poll on the fd with some timeout, and then pretend that there is data.
217  * This will let the calling poll return immediately (there's still no busy loop since
218  * we do actually poll here).
219  * We do this because there is no simple way with the given interface to poll on
220  * the _fd in here or by overriding some other method.
221  */
222 
223  int ret = ::poll(fds, sizeof(fds) / sizeof(fds[0]), 100);
224  _had_data = ret > 0 && (fds[0].revents & POLLIN);
225 
226  return POLLIN;
227 }
228 
229 class Mavlink2Dev : public DevCommon
230 {
231 public:
232  Mavlink2Dev(ReadBuffer *_read_buffer);
233  virtual ~Mavlink2Dev() {}
234 
235  virtual ssize_t read(struct file *filp, char *buffer, size_t buflen);
236  virtual ssize_t write(struct file *filp, const char *buffer, size_t buflen);
237 
238 protected:
240  size_t _remaining_partial = 0;
241  size_t _partial_start = 0;
242  uint8_t _partial_buffer[512] = {};
243 };
244 
246  : DevCommon("/dev/mavlink")
247  , _read_buffer{read_buffer}
248 {
249 }
250 
251 ssize_t Mavlink2Dev::read(struct file *filp, char *buffer, size_t buflen)
252 {
253  int i, ret;
254  uint16_t packet_len = 0;
255 
256  /* last reading was partial (i.e., buffer didn't fit whole message),
257  * so now we'll just send remaining bytes */
258  if (_remaining_partial > 0) {
259  size_t len = _remaining_partial;
260 
261  if (buflen < len) {
262  len = buflen;
263  }
264 
265  memmove(buffer, _partial_buffer + _partial_start, len);
266  _partial_start += len;
267  _remaining_partial -= len;
268 
269  if (_remaining_partial == 0) {
270  _partial_start = 0;
271  }
272 
273  return len;
274  }
275 
276  if (!_had_data) {
277  return 0;
278  }
279 
280  lock(Read);
281  ret = _read_buffer->read(_fd);
282 
283  if (ret < 0) {
284  goto end;
285  }
286 
287  ret = 0;
288 
289  if (_read_buffer->buf_size < 3) {
290  goto end;
291  }
292 
293  // Search for a mavlink packet on buffer to send it
294  i = 0;
295 
296  while ((unsigned)i < (_read_buffer->buf_size - 3)
297  && _read_buffer->buffer[i] != 253
298  && _read_buffer->buffer[i] != 254) {
299  i++;
300  }
301 
302  // We need at least the first three bytes to get packet len
303  if ((unsigned)i >= _read_buffer->buf_size - 3) {
304  goto end;
305  }
306 
307  if (_read_buffer->buffer[i] == 253) {
308  uint8_t payload_len = _read_buffer->buffer[i + 1];
309  uint8_t incompat_flags = _read_buffer->buffer[i + 2];
310  packet_len = payload_len + 12;
311 
312  if (incompat_flags & 0x1) { //signing
313  packet_len += 13;
314  }
315 
316  } else {
317  packet_len = _read_buffer->buffer[i + 1] + 8;
318  }
319 
320  // packet is bigger than what we've read, better luck next time
321  if ((unsigned)i + packet_len > _read_buffer->buf_size) {
322  goto end;
323  }
324 
325  /* if buffer doesn't fit message, send what's possible and copy remaining
326  * data into a temporary buffer on this class */
327  if (packet_len > buflen) {
328  _read_buffer->move(buffer, i, buflen);
329  _read_buffer->move(_partial_buffer, i, packet_len - buflen);
330  _remaining_partial = packet_len - buflen;
331  ret = buflen;
332  goto end;
333  }
334 
335  _read_buffer->move(buffer, i, packet_len);
336  ret = packet_len;
337 
338 end:
339  unlock(Read);
340  return ret;
341 }
342 
343 ssize_t Mavlink2Dev::write(struct file *filp, const char *buffer, size_t buflen)
344 {
345  /*
346  * we need to look into the data to make sure the output is locked for the duration
347  * of a whole packet.
348  * assumptions:
349  * - packet header is written all at once (or at least it contains the payload length)
350  * - a single write call does not contain multiple (or parts of multiple) packets
351  */
352  ssize_t ret = 0;
353 
354  switch (_parser_state) {
355  case ParserState::Idle:
356  ASSERT(buflen >= 3);
357 
358  if ((unsigned char)buffer[0] == 253) {
359  uint8_t payload_len = buffer[1];
360  uint8_t incompat_flags = buffer[2];
361  _packet_len = payload_len + 12;
362 
363  if (incompat_flags & 0x1) { //signing
364  _packet_len += 13;
365  }
366 
368  lock(Write);
369 
370  } else if ((unsigned char)buffer[0] == 254) { // mavlink 1
371  uint8_t payload_len = buffer[1];
372  _packet_len = payload_len + 8;
373 
375  lock(Write);
376 
377  } else {
378  PX4_ERR("parser error");
379  return 0;
380  }
381 
382  /* FALLTHROUGH */
383 
384  case ParserState::GotLength: {
385  _packet_len -= buflen;
386  int buf_free;
387  ::ioctl(_fd, FIONSPACE, (unsigned long)&buf_free);
388 
389  if (buf_free < (int)buflen) {
390  //let write fail, to let mavlink know the buffer would overflow
391  //(this is because in the ioctl we pretend there is always enough space)
392  ret = -1;
393 
394  } else {
395  ret = ::write(_fd, buffer, buflen);
396  }
397 
398  if (_packet_len == 0) {
399  unlock(Write);
401  }
402  }
403 
404  break;
405  }
406 
407  return ret;
408 }
409 
410 class RtpsDev : public DevCommon
411 {
412 public:
414  virtual ~RtpsDev() {}
415 
416  virtual ssize_t read(struct file *filp, char *buffer, size_t buflen);
417  virtual ssize_t write(struct file *filp, const char *buffer, size_t buflen);
418 
419 protected:
421 
422  static const uint8_t HEADER_SIZE = 9;
423 };
424 
426  : DevCommon("/dev/rtps")
427  , _read_buffer{read_buffer}
428 {
429 }
430 
431 ssize_t RtpsDev::read(struct file *filp, char *buffer, size_t buflen)
432 {
433  int i, ret;
434  uint16_t packet_len, payload_len;
435 
436  if (!_had_data) {
437  return 0;
438  }
439 
440  lock(Read);
441  ret = _read_buffer->read(_fd);
442 
443  if (ret < 0) {
444  goto end;
445  }
446 
447  ret = 0;
448 
450  goto end; // starting ">>>" + topic + seq + lenhigh + lenlow + crchigh + crclow
451  }
452 
453  // Search for a rtps packet on buffer to send it
454  i = 0;
455 
456  while ((unsigned)i < (_read_buffer->buf_size - HEADER_SIZE) && (memcmp(_read_buffer->buffer + i, ">>>", 3) != 0)) {
457  i++;
458  }
459 
460  // We need at least the first six bytes to get packet len
461  if ((unsigned)i >= _read_buffer->buf_size - HEADER_SIZE) {
462  goto end;
463  }
464 
465  payload_len = ((uint16_t)_read_buffer->buffer[i + 5] << 8) | _read_buffer->buffer[i + 6];
466  packet_len = payload_len + HEADER_SIZE;
467 
468  // packet is bigger than what we've read, better luck next time
469  if ((unsigned)i + packet_len > _read_buffer->buf_size) {
470  goto end;
471  }
472 
473  // buffer should be big enough to hold a rtps packet
474  if (packet_len > buflen) {
475  ret = -EMSGSIZE;
476  goto end;
477  }
478 
479  _read_buffer->move(buffer, i, packet_len);
480  ret = packet_len;
481 
482 end:
483  unlock(Read);
484  return ret;
485 }
486 
487 ssize_t RtpsDev::write(struct file *filp, const char *buffer, size_t buflen)
488 {
489  /*
490  * we need to look into the data to make sure the output is locked for the duration
491  * of a whole packet.
492  * assumptions:
493  * - packet header is written all at once (or at least it contains the payload length)
494  * - a single write call does not contain multiple (or parts of multiple) packets
495  */
496  ssize_t ret = 0;
497  uint16_t payload_len;
498 
499  switch (_parser_state) {
500  case ParserState::Idle:
501  ASSERT(buflen >= HEADER_SIZE);
502 
503  if (memcmp(buffer, ">>>", 3) != 0) {
504  PX4_ERR("parser error");
505  return 0;
506  }
507 
508  payload_len = ((uint16_t)buffer[5] << 8) | buffer[6];
509  _packet_len = payload_len + HEADER_SIZE;
511  lock(Write);
512 
513  /* FALLTHROUGH */
514 
515  case ParserState::GotLength: {
516  _packet_len -= buflen;
517  int buf_free;
518  ::ioctl(_fd, FIONSPACE, (unsigned long)&buf_free);
519 
520  // TODO should I care about this for rtps?
521  if ((unsigned)buf_free < buflen) {
522  //let write fail, to let rtps know the buffer would overflow
523  //(this is because in the ioctl we pretend there is always enough space)
524  ret = -1;
525 
526  } else {
527  ret = ::write(_fd, buffer, buflen);
528  }
529 
530  if (_packet_len == 0) {
531  unlock(Write);
533  }
534  }
535 
536  break;
537  }
538 
539  return ret;
540 }
541 
542 int protocol_splitter_main(int argc, char *argv[])
543 {
544  if (argc < 2) {
545  goto out;
546  }
547 
548  /*
549  * Start/load the driver.
550  */
551  if (!strcmp(argv[1], "start")) {
552  if (objects) {
553  PX4_ERR("already running");
554  return 1;
555  }
556 
557  if (argc != 3) {
558  goto out;
559  }
560 
561  objects = new StaticData();
562 
563  if (!objects) {
564  PX4_ERR("alloc failed");
565  return -1;
566  }
567 
568  strncpy(objects->device_name, argv[2], sizeof(objects->device_name));
569  sem_init(&objects->r_lock, 1, 1);
570  sem_init(&objects->w_lock, 1, 1);
571  objects->read_buffer = new ReadBuffer();
572  objects->mavlink2 = new Mavlink2Dev(objects->read_buffer);
573  objects->rtps = new RtpsDev(objects->read_buffer);
574 
575  if (!objects->mavlink2 || !objects->rtps) {
576  delete objects->mavlink2;
577  delete objects->rtps;
578  delete objects->read_buffer;
579  sem_destroy(&objects->r_lock);
580  sem_destroy(&objects->w_lock);
581  delete objects;
582  objects = nullptr;
583  PX4_ERR("alloc failed");
584  return -1;
585 
586  } else {
587  objects->mavlink2->init();
588  objects->rtps->init();
589  }
590  }
591 
592  if (!strcmp(argv[1], "stop")) {
593  if (objects) {
594  delete objects->mavlink2;
595  delete objects->rtps;
596  delete objects->read_buffer;
597  sem_destroy(&objects->r_lock);
598  sem_destroy(&objects->w_lock);
599  delete objects;
600  objects = nullptr;
601  }
602  }
603 
604  /*
605  * Print driver status.
606  */
607  if (!strcmp(argv[1], "status")) {
608  if (objects) {
609  PX4_INFO("running");
610 
611  } else {
612  PX4_INFO("not running");
613  }
614  }
615 
616  return 0;
617 
618 out:
619  PX4_ERR("unrecognized command, try 'start <device>', 'stop', 'status'");
620  return 1;
621 }
char device_name[16]
ReadBuffer * read_buffer
virtual ~Mavlink2Dev()
virtual int open(file *filp)
void unlock(enum Operation op)
void lock()
Take the driver lock.
Definition: CDev.hpp:264
RtpsDev(ReadBuffer *_read_buffer)
Definition: I2C.hpp:51
Mavlink2Dev(ReadBuffer *_read_buffer)
virtual ssize_t read(struct file *filp, char *buffer, size_t buflen)
virtual ssize_t read(struct file *filp, char *buffer, size_t buflen)
ReadBuffer * _read_buffer
Mavlink2Dev * mavlink2
virtual ssize_t write(struct file *filp, const char *buffer, size_t buflen)
virtual int ioctl(struct file *filp, int cmd, unsigned long arg)
virtual ssize_t read(file_t *filep, char *buffer, size_t buflen)
Perform a read from the device.
Definition: CDev.hpp:111
int read(int fd)
static void read(bootloader_app_shared_t *pshared)
virtual int close(file *filp)
bool _had_data
whether poll() returned available data
Abstract class for any character device.
Definition: CDev.hpp:58
void lock(enum Operation op)
ReadBuffer * _read_buffer
static const uint8_t HEADER_SIZE
uint16_t _packet_len
virtual pollevent_t poll_state(struct file *filp)
uint8_t buffer[512]
virtual ssize_t write(file_t *filep, const char *buffer, size_t buflen)
Perform a write to the device.
Definition: CDev.hpp:123
int fd
Definition: dataman.cpp:146
virtual ~DevCommon()
struct @83::@85::@87 file
virtual ssize_t write(struct file *filp, const char *buffer, size_t buflen)
uint8_t _partial_buffer[512]
DevCommon(const char *device_path)
virtual ~RtpsDev()
__EXPORT int protocol_splitter_main(int argc, char *argv[])
void move(void *dest, size_t pos, size_t n)
void unlock()
Release the driver lock.
Definition: CDev.hpp:269
ParserState _parser_state
virtual int poll(file_t *filep, px4_pollfd_struct_t *fds, bool setup)
Perform a poll setup/teardown operation.
Definition: CDev.cpp:213