2013-08-29 02:34:34 -03:00
|
|
|
/*
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2012-12-12 18:04:27 -04:00
|
|
|
//
|
|
|
|
// Copyright (c) 2010 Michael Smith. All rights reserved.
|
|
|
|
//
|
2015-08-11 03:28:43 -03:00
|
|
|
#include <AP_HAL/AP_HAL.h>
|
2015-05-04 03:15:12 -03:00
|
|
|
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
|
2012-12-12 18:04:27 -04:00
|
|
|
|
|
|
|
#include <limits.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdarg.h>
|
2015-08-11 03:28:43 -03:00
|
|
|
#include <AP_Math/AP_Math.h>
|
2012-12-12 18:04:27 -04:00
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/ioctl.h>
|
2015-05-04 21:59:07 -03:00
|
|
|
#include <sys/types.h>
|
2012-12-12 18:04:27 -04:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/tcp.h>
|
2015-05-01 06:52:07 -03:00
|
|
|
#include <sys/select.h>
|
2015-11-03 16:59:43 -04:00
|
|
|
#include <termios.h>
|
2017-11-22 18:21:50 -04:00
|
|
|
#include <sys/time.h>
|
2012-12-12 18:04:27 -04:00
|
|
|
|
|
|
|
#include "UARTDriver.h"
|
2012-12-17 22:34:13 -04:00
|
|
|
#include "SITL_State.h"
|
2018-12-05 01:58:37 -04:00
|
|
|
#include <AP_HAL/utility/packetise.h>
|
2012-12-17 22:34:13 -04:00
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
|
2015-05-04 03:15:12 -03:00
|
|
|
using namespace HALSITL;
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
bool UARTDriver::_console;
|
2012-12-13 18:57:01 -04:00
|
|
|
|
2012-12-12 18:04:27 -04:00
|
|
|
/* UARTDriver method implementations */
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::begin(uint32_t baud, uint16_t rxSpace, uint16_t txSpace)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2019-04-08 19:56:15 -03:00
|
|
|
if (_portNumber >= ARRAY_SIZE(_sitlState->_uart_path)) {
|
2018-06-29 04:51:14 -03:00
|
|
|
AP_HAL::panic("port number out of range; you may need to extend _sitlState->_uart_path");
|
|
|
|
}
|
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
const char *path = _sitlState->_uart_path[_portNumber];
|
|
|
|
|
2019-01-24 20:26:38 -04:00
|
|
|
if (baud != 0) {
|
|
|
|
_uart_baudrate = baud;
|
|
|
|
}
|
2018-05-15 21:52:08 -03:00
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
if (strcmp(path, "GPS1") == 0) {
|
2012-12-12 18:04:27 -04:00
|
|
|
/* gps */
|
|
|
|
_connected = true;
|
2020-01-26 00:17:20 -04:00
|
|
|
_fd = _sitlState->gps_pipe(0);
|
2015-11-03 16:59:43 -04:00
|
|
|
} else if (strcmp(path, "GPS2") == 0) {
|
|
|
|
/* 2nd gps */
|
2013-12-21 07:26:30 -04:00
|
|
|
_connected = true;
|
2020-01-26 00:17:20 -04:00
|
|
|
_fd = _sitlState->gps_pipe(1);
|
2015-11-03 16:59:43 -04:00
|
|
|
} else {
|
|
|
|
/* parse type:args:flags string for path.
|
|
|
|
For example:
|
|
|
|
tcp:5760:wait // tcp listen on port 5760
|
|
|
|
tcp:0:wait // tcp listen on use base_port + 0
|
|
|
|
tcpclient:192.168.2.15:5762
|
2018-12-05 03:32:43 -04:00
|
|
|
udpclient:127.0.0.1
|
|
|
|
udpclient:127.0.0.1:14550
|
|
|
|
mcast:
|
|
|
|
mcast:239.255.145.50:14550
|
2015-11-03 16:59:43 -04:00
|
|
|
uart:/dev/ttyUSB0:57600
|
2017-08-10 07:23:11 -03:00
|
|
|
sim:ParticleSensor_SDS021:
|
2015-11-03 16:59:43 -04:00
|
|
|
*/
|
2016-10-30 02:24:21 -03:00
|
|
|
char *saveptr = nullptr;
|
2015-11-03 16:59:43 -04:00
|
|
|
char *s = strdup(path);
|
|
|
|
char *devtype = strtok_r(s, ":", &saveptr);
|
2016-10-30 02:24:21 -03:00
|
|
|
char *args1 = strtok_r(nullptr, ":", &saveptr);
|
|
|
|
char *args2 = strtok_r(nullptr, ":", &saveptr);
|
2015-11-03 16:59:43 -04:00
|
|
|
if (strcmp(devtype, "tcp") == 0) {
|
|
|
|
uint16_t port = atoi(args1);
|
|
|
|
bool wait = (args2 && strcmp(args2, "wait") == 0);
|
|
|
|
_tcp_start_connection(port, wait);
|
|
|
|
} else if (strcmp(devtype, "tcpclient") == 0) {
|
2016-10-30 02:24:21 -03:00
|
|
|
if (args2 == nullptr) {
|
2015-11-19 23:11:17 -04:00
|
|
|
AP_HAL::panic("Invalid tcp client path: %s", path);
|
2015-11-03 16:59:43 -04:00
|
|
|
}
|
|
|
|
uint16_t port = atoi(args2);
|
|
|
|
_tcp_start_client(args1, port);
|
|
|
|
} else if (strcmp(devtype, "uart") == 0) {
|
2016-02-09 18:37:37 -04:00
|
|
|
uint32_t baudrate = args2? atoi(args2) : baud;
|
2015-11-03 16:59:43 -04:00
|
|
|
::printf("UART connection %s:%u\n", args1, baudrate);
|
2016-08-22 04:11:48 -03:00
|
|
|
_uart_path = strdup(args1);
|
|
|
|
_uart_baudrate = baudrate;
|
|
|
|
_uart_start_connection();
|
2017-08-10 07:23:11 -03:00
|
|
|
} else if (strcmp(devtype, "sim") == 0) {
|
|
|
|
if (!_connected) {
|
2019-10-28 23:52:19 -03:00
|
|
|
::printf("SIM connection %s:%s on port %u\n", args1, args2, _portNumber);
|
2017-08-10 07:23:11 -03:00
|
|
|
_connected = true;
|
|
|
|
_fd = _sitlState->sim_fd(args1, args2);
|
2019-10-28 23:52:19 -03:00
|
|
|
_fd_write = _sitlState->sim_fd_write(args1);
|
2017-08-10 07:23:11 -03:00
|
|
|
}
|
2018-12-05 01:32:49 -04:00
|
|
|
} else if (strcmp(devtype, "udpclient") == 0) {
|
2018-12-05 03:32:43 -04:00
|
|
|
// udp client connection
|
2018-12-05 01:32:49 -04:00
|
|
|
const char *ip = args1;
|
|
|
|
uint16_t port = args2?atoi(args2):14550;
|
2018-12-05 03:32:43 -04:00
|
|
|
if (!_connected) {
|
|
|
|
::printf("UDP connection %s:%u\n", ip, port);
|
|
|
|
_udp_start_client(ip, port);
|
|
|
|
}
|
|
|
|
} else if (strcmp(devtype, "mcast") == 0) {
|
|
|
|
// udp multicast connection
|
|
|
|
const char *ip = args1 && *args1?args1:mcast_ip_default;
|
|
|
|
uint16_t port = args2?atoi(args2):mcast_port_default;
|
|
|
|
if (!_connected) {
|
|
|
|
::printf("UDP multicast connection %s:%u\n", ip, port);
|
|
|
|
_udp_start_multicast(ip, port);
|
|
|
|
}
|
2015-11-03 16:59:43 -04:00
|
|
|
} else {
|
2015-11-19 23:11:17 -04:00
|
|
|
AP_HAL::panic("Invalid device path: %s", path);
|
2015-11-03 16:59:43 -04:00
|
|
|
}
|
|
|
|
free(s);
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
|
2018-06-20 06:01:08 -03:00
|
|
|
if (hal.console != this) { // don't clear USB buffers (allows early startup messages to escape)
|
|
|
|
_readbuffer.clear();
|
|
|
|
_writebuffer.clear();
|
|
|
|
}
|
|
|
|
|
2016-01-10 02:17:32 -04:00
|
|
|
_set_nonblocking(_fd);
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::end()
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-08-02 10:42:50 -03:00
|
|
|
uint32_t UARTDriver::available(void)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
|
|
|
_check_connection();
|
|
|
|
|
|
|
|
if (!_connected) {
|
|
|
|
return 0;
|
|
|
|
}
|
2015-05-04 21:59:07 -03:00
|
|
|
|
2016-01-10 02:17:32 -04:00
|
|
|
return _readbuffer.available();
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2016-08-02 10:42:50 -03:00
|
|
|
uint32_t UARTDriver::txspace(void)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2016-01-10 02:17:32 -04:00
|
|
|
_check_connection();
|
|
|
|
if (!_connected) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return _writebuffer.space();
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
int16_t UARTDriver::read(void)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
|
|
|
if (available() <= 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
uint8_t c;
|
|
|
|
_readbuffer.read(&c, 1);
|
|
|
|
return c;
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2020-05-22 21:23:04 -03:00
|
|
|
bool UARTDriver::discard_input(void)
|
|
|
|
{
|
2020-06-03 01:35:01 -03:00
|
|
|
_readbuffer.clear();
|
2020-05-22 21:23:04 -03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::flush(void)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-10-28 23:52:19 -03:00
|
|
|
// size_t UARTDriver::write(uint8_t c)
|
|
|
|
// {
|
|
|
|
// if (txspace() <= 0) {
|
|
|
|
// return 0;
|
|
|
|
// }
|
|
|
|
// _writebuffer.write(&c, 1);
|
|
|
|
// return 1;
|
|
|
|
// }
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
size_t UARTDriver::write(uint8_t c)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2019-10-28 23:52:19 -03:00
|
|
|
return write(&c, 1);
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
2016-01-10 02:23:32 -04:00
|
|
|
size_t UARTDriver::write(const uint8_t *buffer, size_t size)
|
2013-10-02 04:36:54 -03:00
|
|
|
{
|
2018-04-03 00:21:59 -03:00
|
|
|
if (txspace() <= size) {
|
2016-01-10 02:17:32 -04:00
|
|
|
size = txspace();
|
2013-10-02 04:36:54 -03:00
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
if (size <= 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2017-11-22 18:21:50 -04:00
|
|
|
if (_unbuffered_writes) {
|
|
|
|
// write buffer straight to the file descriptor
|
2019-10-28 23:52:19 -03:00
|
|
|
int fd = _fd_write;
|
|
|
|
if (fd == -1) {
|
|
|
|
fd = _fd;
|
|
|
|
}
|
|
|
|
const ssize_t nwritten = ::write(fd, buffer, size);
|
2017-11-30 10:12:57 -04:00
|
|
|
if (nwritten == -1 && errno != EAGAIN && _uart_path) {
|
2019-10-28 23:52:19 -03:00
|
|
|
if (_fd_write != -1) {
|
|
|
|
close(_fd_write);
|
2019-12-16 22:21:13 -04:00
|
|
|
_fd_write = -1;
|
2019-10-28 23:52:19 -03:00
|
|
|
}
|
2017-11-30 10:12:57 -04:00
|
|
|
close(_fd);
|
|
|
|
_fd = -1;
|
|
|
|
_connected = false;
|
|
|
|
}
|
2017-11-22 18:21:50 -04:00
|
|
|
// these have no effect
|
|
|
|
tcdrain(_fd);
|
|
|
|
} else {
|
|
|
|
_writebuffer.write(buffer, size);
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
return size;
|
2013-10-02 04:36:54 -03:00
|
|
|
}
|
|
|
|
|
2016-01-10 02:17:32 -04:00
|
|
|
|
2012-12-12 18:04:27 -04:00
|
|
|
/*
|
|
|
|
start a TCP connection for the serial port. If wait_for_connection
|
|
|
|
is true then block until a client connects
|
|
|
|
*/
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::_tcp_start_connection(uint16_t port, bool wait_for_connection)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2015-05-04 21:59:07 -03:00
|
|
|
int one=1;
|
|
|
|
struct sockaddr_in sockaddr;
|
|
|
|
int ret;
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_connected) {
|
|
|
|
return;
|
|
|
|
}
|
2012-12-17 22:34:13 -04:00
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
_use_send_recv = true;
|
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_console) {
|
|
|
|
// hack for console access
|
|
|
|
_connected = true;
|
2015-11-03 16:59:43 -04:00
|
|
|
_use_send_recv = false;
|
2015-05-04 21:59:07 -03:00
|
|
|
_listen_fd = -1;
|
|
|
|
_fd = 1;
|
|
|
|
return;
|
|
|
|
}
|
2012-12-13 18:57:01 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_fd != -1) {
|
|
|
|
close(_fd);
|
|
|
|
}
|
2012-12-17 22:34:13 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_listen_fd == -1) {
|
|
|
|
memset(&sockaddr,0,sizeof(sockaddr));
|
2012-12-12 18:04:27 -04:00
|
|
|
|
|
|
|
#ifdef HAVE_SOCK_SIN_LEN
|
2015-05-04 21:59:07 -03:00
|
|
|
sockaddr.sin_len = sizeof(sockaddr);
|
2012-12-12 18:04:27 -04:00
|
|
|
#endif
|
2015-11-03 16:59:43 -04:00
|
|
|
if (port > 1000) {
|
|
|
|
sockaddr.sin_port = htons(port);
|
|
|
|
} else {
|
|
|
|
sockaddr.sin_port = htons(_sitlState->base_port() + port);
|
|
|
|
}
|
2015-05-04 21:59:07 -03:00
|
|
|
sockaddr.sin_family = AF_INET;
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2018-06-07 08:09:42 -03:00
|
|
|
_listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_listen_fd == -1) {
|
|
|
|
fprintf(stderr, "socket failed - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2018-06-07 08:09:42 -03:00
|
|
|
ret = fcntl(_listen_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "fcntl failed on setting FD_CLOEXEC - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2012-12-17 22:34:13 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
/* we want to be able to re-use ports quickly */
|
2018-03-31 06:03:47 -03:00
|
|
|
if (setsockopt(_listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
|
|
|
|
fprintf(stderr, "setsockopt failed: %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
fprintf(stderr, "bind port %u for %u\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
2015-05-10 02:36:18 -03:00
|
|
|
(unsigned)_portNumber);
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-10 02:36:18 -03:00
|
|
|
ret = bind(_listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
|
2015-05-04 21:59:07 -03:00
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "bind failed on port %u - %s\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
|
|
|
strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
ret = listen(_listen_fd, 5);
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "listen failed - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
2012-12-17 22:34:13 -04:00
|
|
|
}
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
fprintf(stderr, "Serial port %u on TCP port %u\n", _portNumber,
|
2020-01-08 08:29:42 -04:00
|
|
|
(unsigned)ntohs(sockaddr.sin_port));
|
2015-05-04 21:59:07 -03:00
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wait_for_connection) {
|
|
|
|
fprintf(stdout, "Waiting for connection ....\n");
|
|
|
|
fflush(stdout);
|
2016-10-30 02:24:21 -03:00
|
|
|
_fd = accept(_listen_fd, nullptr, nullptr);
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_fd == -1) {
|
|
|
|
fprintf(stderr, "accept() error - %s", strerror(errno));
|
|
|
|
exit(1);
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
2015-05-04 21:59:07 -03:00
|
|
|
setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
|
|
setsockopt(_fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
2018-06-08 00:37:24 -03:00
|
|
|
fcntl(_fd, F_SETFD, FD_CLOEXEC);
|
2015-05-04 21:59:07 -03:00
|
|
|
_connected = true;
|
2020-01-08 08:29:42 -04:00
|
|
|
fprintf(stdout, "Connection on serial port %u\n", (unsigned)ntohs(sockaddr.sin_port));
|
2015-05-04 21:59:07 -03:00
|
|
|
}
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2015-05-10 02:36:18 -03:00
|
|
|
|
|
|
|
/*
|
|
|
|
start a TCP client connection for the serial port.
|
|
|
|
*/
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::_tcp_start_client(const char *address, uint16_t port)
|
2015-05-10 02:36:18 -03:00
|
|
|
{
|
|
|
|
int one=1;
|
|
|
|
struct sockaddr_in sockaddr;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (_connected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
_use_send_recv = true;
|
|
|
|
|
2015-05-10 02:36:18 -03:00
|
|
|
if (_fd != -1) {
|
|
|
|
close(_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&sockaddr,0,sizeof(sockaddr));
|
|
|
|
|
|
|
|
#ifdef HAVE_SOCK_SIN_LEN
|
|
|
|
sockaddr.sin_len = sizeof(sockaddr);
|
|
|
|
#endif
|
2016-05-30 03:42:28 -03:00
|
|
|
sockaddr.sin_port = htons(port);
|
2015-05-10 02:36:18 -03:00
|
|
|
sockaddr.sin_family = AF_INET;
|
2015-11-03 16:59:43 -04:00
|
|
|
sockaddr.sin_addr.s_addr = inet_addr(address);
|
2015-05-10 02:36:18 -03:00
|
|
|
|
2018-06-07 08:09:42 -03:00
|
|
|
_fd = socket(AF_INET, SOCK_STREAM, 0);
|
2015-05-10 02:36:18 -03:00
|
|
|
if (_fd == -1) {
|
|
|
|
fprintf(stderr, "socket failed - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2018-06-07 08:09:42 -03:00
|
|
|
ret = fcntl(_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "fcntl failed on setting FD_CLOEXEC - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
2015-05-10 02:36:18 -03:00
|
|
|
|
|
|
|
/* we want to be able to re-use ports quickly */
|
|
|
|
setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
|
|
|
|
|
|
ret = connect(_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "connect failed on port %u - %s\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
|
|
|
strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
|
|
setsockopt(_fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
2019-03-12 23:54:51 -03:00
|
|
|
fcntl(_fd, F_SETFD, FD_CLOEXEC);
|
2015-05-10 02:36:18 -03:00
|
|
|
_connected = true;
|
|
|
|
}
|
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
|
2018-12-05 01:32:49 -04:00
|
|
|
/*
|
|
|
|
start a UDP client connection for the serial port.
|
|
|
|
*/
|
|
|
|
void UARTDriver::_udp_start_client(const char *address, uint16_t port)
|
|
|
|
{
|
|
|
|
struct sockaddr_in sockaddr;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (_connected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_use_send_recv = true;
|
|
|
|
|
|
|
|
if (_fd != -1) {
|
|
|
|
close(_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&sockaddr,0,sizeof(sockaddr));
|
|
|
|
|
|
|
|
#ifdef HAVE_SOCK_SIN_LEN
|
|
|
|
sockaddr.sin_len = sizeof(sockaddr);
|
|
|
|
#endif
|
|
|
|
sockaddr.sin_port = htons(port);
|
|
|
|
sockaddr.sin_family = AF_INET;
|
|
|
|
sockaddr.sin_addr.s_addr = inet_addr(address);
|
|
|
|
|
|
|
|
_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
if (_fd == -1) {
|
|
|
|
fprintf(stderr, "socket failed - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
ret = fcntl(_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "fcntl failed on setting FD_CLOEXEC - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to setup for broadcast, this may fail if insufficient privileges
|
|
|
|
int one = 1;
|
|
|
|
setsockopt(_fd,SOL_SOCKET,SO_BROADCAST,(char *)&one,sizeof(one));
|
|
|
|
|
|
|
|
ret = connect(_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "udp connect failed on port %u - %s\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
|
|
|
strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
_is_udp = true;
|
2018-12-05 01:58:37 -04:00
|
|
|
_packetise = true;
|
2018-12-05 01:32:49 -04:00
|
|
|
_connected = true;
|
|
|
|
}
|
|
|
|
|
2018-12-05 03:32:43 -04:00
|
|
|
/*
|
|
|
|
start a UDP multicast connection
|
|
|
|
*/
|
|
|
|
void UARTDriver::_udp_start_multicast(const char *address, uint16_t port)
|
|
|
|
{
|
|
|
|
if (_connected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// establish the listening port
|
|
|
|
struct sockaddr_in sockaddr;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
memset(&sockaddr,0,sizeof(sockaddr));
|
|
|
|
|
|
|
|
#ifdef HAVE_SOCK_SIN_LEN
|
|
|
|
sockaddr.sin_len = sizeof(sockaddr);
|
|
|
|
#endif
|
|
|
|
sockaddr.sin_port = htons(port);
|
|
|
|
sockaddr.sin_family = AF_INET;
|
|
|
|
sockaddr.sin_addr.s_addr = inet_addr(address);
|
|
|
|
|
|
|
|
_mc_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
if (_mc_fd == -1) {
|
|
|
|
fprintf(stderr, "socket failed - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
ret = fcntl(_mc_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "fcntl failed on setting FD_CLOEXEC - %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
int one = 1;
|
|
|
|
if (setsockopt(_mc_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
|
|
|
|
fprintf(stderr, "setsockopt failed: %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// close on exec, to allow reboot
|
|
|
|
fcntl(_mc_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
|
|
|
|
ret = bind(_mc_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "multicast bind failed on port %u - %s\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
|
|
|
strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ip_mreq mreq {};
|
|
|
|
mreq.imr_multiaddr.s_addr = inet_addr(address);
|
|
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
|
|
|
|
|
|
ret = setsockopt(_mc_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
|
|
|
if (ret == -1) {
|
|
|
|
fprintf(stderr, "multicast membership add failed on port %u - %s\n",
|
|
|
|
(unsigned)ntohs(sockaddr.sin_port),
|
|
|
|
strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now start the outgoing connection as an ordinary UDP connection
|
|
|
|
_udp_start_client(address, port);
|
|
|
|
}
|
|
|
|
|
2018-12-05 01:32:49 -04:00
|
|
|
|
2015-11-03 16:59:43 -04:00
|
|
|
/*
|
|
|
|
start a UART connection for the serial port
|
|
|
|
*/
|
2016-08-22 04:11:48 -03:00
|
|
|
void UARTDriver::_uart_start_connection(void)
|
2015-11-03 16:59:43 -04:00
|
|
|
{
|
|
|
|
struct termios t {};
|
|
|
|
if (!_connected) {
|
2016-08-22 04:11:48 -03:00
|
|
|
_fd = ::open(_uart_path, O_RDWR | O_CLOEXEC);
|
|
|
|
if (_fd == -1) {
|
|
|
|
return;
|
|
|
|
}
|
2016-05-23 10:31:23 -03:00
|
|
|
// use much smaller buffer sizes on real UARTs
|
|
|
|
_writebuffer.set_size(1024);
|
|
|
|
_readbuffer.set_size(512);
|
2016-08-22 04:11:48 -03:00
|
|
|
::printf("Opened %s\n", _uart_path);
|
2015-11-03 16:59:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (_fd == -1) {
|
2016-08-22 04:11:48 -03:00
|
|
|
AP_HAL::panic("Unable to open UART %s", _uart_path);
|
2015-11-03 16:59:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// set non-blocking
|
|
|
|
int flags = fcntl(_fd, F_GETFL, 0);
|
|
|
|
flags = flags | O_NONBLOCK;
|
|
|
|
fcntl(_fd, F_SETFL, flags);
|
|
|
|
|
|
|
|
// disable LF -> CR/LF
|
|
|
|
tcgetattr(_fd, &t);
|
|
|
|
t.c_iflag &= ~(BRKINT | ICRNL | IMAXBEL | IXON | IXOFF);
|
|
|
|
t.c_oflag &= ~(OPOST | ONLCR);
|
|
|
|
t.c_lflag &= ~(ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE);
|
|
|
|
t.c_cc[VMIN] = 0;
|
2016-05-16 03:51:09 -03:00
|
|
|
if (_sitlState->use_rtscts()) {
|
|
|
|
t.c_cflag |= CRTSCTS;
|
|
|
|
}
|
2015-11-03 16:59:43 -04:00
|
|
|
tcsetattr(_fd, TCSANOW, &t);
|
|
|
|
|
|
|
|
// set baudrate
|
2017-11-22 18:21:50 -04:00
|
|
|
set_speed(_uart_baudrate);
|
2015-11-03 16:59:43 -04:00
|
|
|
|
|
|
|
_connected = true;
|
|
|
|
_use_send_recv = false;
|
|
|
|
}
|
|
|
|
|
2012-12-12 18:04:27 -04:00
|
|
|
/*
|
|
|
|
see if a new connection is coming in
|
|
|
|
*/
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::_check_connection(void)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_connected) {
|
|
|
|
// we only want 1 connection at a time
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_select_check(_listen_fd)) {
|
2016-10-30 02:24:21 -03:00
|
|
|
_fd = accept(_listen_fd, nullptr, nullptr);
|
2015-05-04 21:59:07 -03:00
|
|
|
if (_fd != -1) {
|
|
|
|
int one = 1;
|
|
|
|
_connected = true;
|
|
|
|
setsockopt(_fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
|
|
|
setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
2019-03-12 23:54:51 -03:00
|
|
|
fcntl(_fd, F_SETFD, FD_CLOEXEC);
|
2015-05-04 21:59:07 -03:00
|
|
|
fprintf(stdout, "New connection on serial port %u\n", _portNumber);
|
|
|
|
}
|
|
|
|
}
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
use select() to see if something is pending
|
|
|
|
*/
|
2016-01-10 02:23:32 -04:00
|
|
|
bool UARTDriver::_select_check(int fd)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2016-06-30 07:45:03 -03:00
|
|
|
if (fd == -1) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-05-04 21:59:07 -03:00
|
|
|
fd_set fds;
|
|
|
|
struct timeval tv;
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(fd, &fds);
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2015-05-04 21:59:07 -03:00
|
|
|
// zero time means immediate return from select()
|
|
|
|
tv.tv_sec = 0;
|
|
|
|
tv.tv_usec = 0;
|
2012-12-12 18:04:27 -04:00
|
|
|
|
2016-10-30 02:24:21 -03:00
|
|
|
if (select(fd+1, &fds, nullptr, nullptr, &tv) == 1) {
|
2015-05-04 21:59:07 -03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::_set_nonblocking(int fd)
|
2012-12-12 18:04:27 -04:00
|
|
|
{
|
2015-05-04 21:59:07 -03:00
|
|
|
unsigned v = fcntl(fd, F_GETFL, 0);
|
|
|
|
fcntl(fd, F_SETFL, v | O_NONBLOCK);
|
2012-12-12 18:04:27 -04:00
|
|
|
}
|
|
|
|
|
2017-11-22 18:21:50 -04:00
|
|
|
bool UARTDriver::set_unbuffered_writes(bool on) {
|
|
|
|
if (_fd == -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
_unbuffered_writes = on;
|
|
|
|
|
|
|
|
// this has no effect
|
|
|
|
unsigned v = fcntl(_fd, F_GETFL, 0);
|
|
|
|
v &= ~O_NONBLOCK;
|
2018-01-19 16:50:19 -04:00
|
|
|
#if defined(__APPLE__) && defined(__MACH__)
|
|
|
|
fcntl(_fd, F_SETFL | F_NOCACHE, v | O_SYNC);
|
|
|
|
#else
|
2017-11-22 18:21:50 -04:00
|
|
|
fcntl(_fd, F_SETFL, v | O_DIRECT | O_SYNC);
|
2018-01-19 16:50:19 -04:00
|
|
|
#endif
|
2017-11-22 18:21:50 -04:00
|
|
|
return _unbuffered_writes;
|
|
|
|
}
|
|
|
|
|
2016-08-22 04:11:48 -03:00
|
|
|
void UARTDriver::_check_reconnect(void)
|
|
|
|
{
|
|
|
|
if (!_uart_path) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_uart_start_connection();
|
|
|
|
}
|
|
|
|
|
2016-01-10 02:23:32 -04:00
|
|
|
void UARTDriver::_timer_tick(void)
|
2016-01-10 02:17:32 -04:00
|
|
|
{
|
2016-01-10 06:26:35 -04:00
|
|
|
if (!_connected) {
|
2016-08-22 04:11:48 -03:00
|
|
|
_check_reconnect();
|
2016-01-10 06:26:35 -04:00
|
|
|
return;
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
ssize_t nwritten;
|
2019-01-24 20:26:38 -04:00
|
|
|
uint32_t max_bytes = 10000;
|
|
|
|
SITL::SITL *_sitl = AP::sitl();
|
|
|
|
if (_sitl && _sitl->telem_baudlimit_enable) {
|
|
|
|
// limit byte rate to configured baudrate
|
|
|
|
uint32_t now = AP_HAL::micros();
|
|
|
|
float dt = 1.0e-6 * (now - last_tick_us);
|
|
|
|
max_bytes = _uart_baudrate * dt / 10;
|
|
|
|
if (max_bytes == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last_tick_us = now;
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
|
2018-12-05 01:58:37 -04:00
|
|
|
if (_packetise) {
|
|
|
|
uint16_t n = _writebuffer.available();
|
2019-01-24 20:26:38 -04:00
|
|
|
n = MIN(n, max_bytes);
|
2018-12-05 01:58:37 -04:00
|
|
|
if (n > 0) {
|
|
|
|
n = mavlink_packetise(_writebuffer, n);
|
|
|
|
}
|
|
|
|
if (n > 0) {
|
|
|
|
// keep as a single UDP packet
|
|
|
|
uint8_t tmpbuf[n];
|
|
|
|
_writebuffer.peekbytes(tmpbuf, n);
|
|
|
|
ssize_t ret = send(_fd, tmpbuf, n, MSG_DONTWAIT);
|
|
|
|
if (ret > 0) {
|
|
|
|
_writebuffer.advance(ret);
|
2016-08-22 04:11:48 -03:00
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
}
|
2018-12-05 01:58:37 -04:00
|
|
|
} else {
|
|
|
|
uint32_t navail;
|
|
|
|
const uint8_t *readptr = _writebuffer.readptr(navail);
|
|
|
|
if (readptr && navail > 0) {
|
2019-01-24 20:26:38 -04:00
|
|
|
navail = MIN(navail, max_bytes);
|
2018-12-05 01:58:37 -04:00
|
|
|
if (!_use_send_recv) {
|
2019-10-28 23:52:19 -03:00
|
|
|
int fd = _fd_write;
|
|
|
|
if (fd == -1) {
|
|
|
|
fd = _fd;
|
|
|
|
}
|
|
|
|
nwritten = ::write(fd, readptr, navail);
|
2018-12-05 01:58:37 -04:00
|
|
|
if (nwritten == -1 && errno != EAGAIN && _uart_path) {
|
2019-10-28 23:52:19 -03:00
|
|
|
if (_fd_write != -1){
|
|
|
|
close(_fd_write);
|
|
|
|
_fd_write = -1;
|
|
|
|
}
|
2018-12-05 01:58:37 -04:00
|
|
|
close(_fd);
|
|
|
|
_fd = -1;
|
|
|
|
_connected = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
nwritten = send(_fd, readptr, navail, MSG_DONTWAIT);
|
|
|
|
}
|
|
|
|
if (nwritten > 0) {
|
|
|
|
_writebuffer.advance(nwritten);
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-10 02:31:50 -04:00
|
|
|
uint32_t space = _readbuffer.space();
|
|
|
|
if (space == 0) {
|
2016-01-10 02:17:32 -04:00
|
|
|
return;
|
|
|
|
}
|
2019-01-24 20:26:38 -04:00
|
|
|
space = MIN(space, max_bytes);
|
2016-01-10 02:17:32 -04:00
|
|
|
|
2016-01-10 02:31:50 -04:00
|
|
|
char buf[space];
|
|
|
|
ssize_t nread = 0;
|
2018-12-05 03:32:43 -04:00
|
|
|
if (_mc_fd >= 0) {
|
|
|
|
if (_select_check(_mc_fd)) {
|
|
|
|
struct sockaddr_in from;
|
|
|
|
socklen_t fromlen = sizeof(from);
|
|
|
|
nread = recvfrom(_mc_fd, buf, space, MSG_DONTWAIT, (struct sockaddr *)&from, &fromlen);
|
|
|
|
uint16_t port = ntohs(from.sin_port);
|
|
|
|
if (_mc_myport == 0) {
|
|
|
|
// get our own address, so we can recognise packets from ourself
|
|
|
|
struct sockaddr_in myaddr;
|
|
|
|
socklen_t myaddrlen;
|
|
|
|
if (getsockname(_fd, (struct sockaddr *)&myaddr, &myaddrlen) == 0) {
|
|
|
|
_mc_myport = ntohs(myaddr.sin_port);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_mc_myport == port) {
|
|
|
|
// assume this is a packet from ourselves. This is not
|
|
|
|
// entirely accurate, as it could be a packet from
|
|
|
|
// another machine that has assigned the same port,
|
|
|
|
// unfortunately we don't have a better way to detect
|
|
|
|
// packets from ourselves
|
|
|
|
nread = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!_use_send_recv) {
|
|
|
|
if (!_select_check(_fd)) {
|
|
|
|
return;
|
|
|
|
}
|
2016-01-10 02:17:32 -04:00
|
|
|
int fd = _console?0:_fd;
|
2016-01-10 02:31:50 -04:00
|
|
|
nread = ::read(fd, buf, space);
|
2016-08-22 04:11:48 -03:00
|
|
|
if (nread == -1 && errno != EAGAIN && _uart_path) {
|
|
|
|
close(_fd);
|
|
|
|
_fd = -1;
|
|
|
|
_connected = false;
|
|
|
|
}
|
2018-12-05 03:32:43 -04:00
|
|
|
} else if (_select_check(_fd)) {
|
|
|
|
nread = recv(_fd, buf, space, MSG_DONTWAIT);
|
|
|
|
if (nread <= 0 && !_is_udp) {
|
|
|
|
// the socket has reached EOF
|
|
|
|
close(_fd);
|
|
|
|
_fd = -1;
|
|
|
|
_connected = false;
|
|
|
|
fprintf(stdout, "Closed connection on serial port %u\n", _portNumber);
|
|
|
|
fflush(stdout);
|
|
|
|
return;
|
2016-01-10 02:17:32 -04:00
|
|
|
}
|
|
|
|
}
|
2016-01-10 02:31:50 -04:00
|
|
|
if (nread > 0) {
|
|
|
|
_readbuffer.write((uint8_t *)buf, nread);
|
2018-05-15 21:52:08 -03:00
|
|
|
_receive_timestamp = AP_HAL::micros64();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
return timestamp estimate in microseconds for when the start of
|
|
|
|
a nbytes packet arrived on the uart. This should be treated as a
|
|
|
|
time constraint, not an exact time. It is guaranteed that the
|
|
|
|
packet did not start being received after this time, but it
|
|
|
|
could have been in a system buffer before the returned time.
|
|
|
|
|
|
|
|
This takes account of the baudrate of the link. For transports
|
|
|
|
that have no baudrate (such as USB) the time estimate may be
|
|
|
|
less accurate.
|
|
|
|
|
|
|
|
A return value of zero means the HAL does not support this API
|
|
|
|
*/
|
2018-05-16 18:01:14 -03:00
|
|
|
uint64_t UARTDriver::receive_time_constraint_us(uint16_t nbytes)
|
2018-05-15 21:52:08 -03:00
|
|
|
{
|
|
|
|
uint64_t last_receive_us = _receive_timestamp;
|
|
|
|
if (_uart_baudrate > 0) {
|
|
|
|
// assume 10 bits per byte.
|
2018-05-16 18:01:14 -03:00
|
|
|
uint32_t transport_time_us = (1000000UL * 10UL / _uart_baudrate) * (nbytes+available());
|
2018-05-15 21:52:08 -03:00
|
|
|
last_receive_us -= transport_time_us;
|
2016-01-10 02:17:32 -04:00
|
|
|
}
|
2018-05-15 21:52:08 -03:00
|
|
|
return last_receive_us;
|
2016-01-10 02:17:32 -04:00
|
|
|
}
|
|
|
|
|
2020-04-02 21:47:06 -03:00
|
|
|
ssize_t UARTDriver::get_system_outqueue_length() const
|
|
|
|
{
|
|
|
|
if (!_connected) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(__CYGWIN__) || defined(__CYGWIN64__) || defined(CYGWIN_BUILD)
|
|
|
|
return 0;
|
|
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
int size;
|
|
|
|
if (ioctl(_fd, TIOCOUTQ, &size) == -1) {
|
|
|
|
// ::fprintf(stderr, "ioctl TIOCOUTQ failed: %m\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2016-01-10 02:17:32 -04:00
|
|
|
#endif // CONFIG_HAL_BOARD
|
|
|
|
|