Initial commit of Version 2.0.0, with CAN FD support
parent
9fde92fa40
commit
f2719ce16f
|
|
@ -1,6 +1,6 @@
|
||||||
*.a
|
build/
|
||||||
*.o
|
build*/
|
||||||
icsneo_sample
|
.DS_Store
|
||||||
*.log
|
Thumbs.db
|
||||||
icsscand
|
.vscode
|
||||||
.vscode/
|
*.bak
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "third-party/libicsneo"]
|
||||||
|
path = third-party/libicsneo
|
||||||
|
url = https://github.com/intrepidcs/libicsneo.git
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
project(libicsneo-socketcan-daemon VERSION 2.0.0)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# Enable Warnings
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-switch -Wno-nested-anon-types -Wno-gnu-anonymous-struct -Wno-unknown-pragmas -Wno-zero-length-array")
|
||||||
|
|
||||||
|
# Generate build info header
|
||||||
|
execute_process(
|
||||||
|
COMMAND git rev-parse --abbrev-ref HEAD
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE GIT_BRANCH
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND git describe --abbrev=6 --dirty --always --tags
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE GIT_DESCRIBE
|
||||||
|
ERROR_VARIABLE GIT_DESCRIBE
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
configure_file(src/buildinfo.h.template ${CMAKE_CURRENT_BINARY_DIR}/generated/buildinfo.h)
|
||||||
|
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
add_subdirectory("third-party/libicsneo")
|
||||||
|
|
||||||
|
add_executable(libicsneo-socketcan-daemon src/main.cpp)
|
||||||
|
target_link_libraries(libicsneo-socketcan-daemon icsneocpp)
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2016 Intrepid Control Systems, Inc.
|
Copyright (c) 2016-2019 Intrepid Control Systems, Inc.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
16
Makefile
16
Makefile
|
|
@ -1,16 +0,0 @@
|
||||||
CXX=g++
|
|
||||||
CC=gcc
|
|
||||||
CFLAGS=-g -c -O2
|
|
||||||
LDFLAGS=
|
|
||||||
AR=ar
|
|
||||||
|
|
||||||
all: icsscand
|
|
||||||
|
|
||||||
icsscand: icsscand.o
|
|
||||||
$(CC) $(LDFLAGS) icsscand.o -o icsscand -lpthread -licsneoapi
|
|
||||||
|
|
||||||
icsscand.o: icsscand.c
|
|
||||||
$(CC) $(CFLAGS) icsscand.c
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf *.o icsscand
|
|
||||||
36
README.md
36
README.md
|
|
@ -1,21 +1,29 @@
|
||||||
This is the user mode daemon for Intrepid Control Systems' SocketCAN support. This daemon requires that ```intrepid.ko``` is loaded on your system. For instructions on building and loading the kernel object, see [intrepid-socketcan-kernel-module](https://github.com/intrepidcs/intrepid-socketcan-kernel-module). In addition, you will need a version of ```libicsneoapi```. For the purposes of SocketCAN support the open source version will suffice; see [libicsneoapi](https://github.com/intrepidcs/icsneoapi).
|
Version 2.0.0
|
||||||
|
|
||||||
To build, simply run ```make```.
|
This is the usermode daemon for the Intrepid Control Systems SocketCAN support. This daemon requires that ```intrepid.ko``` is loaded on your system.
|
||||||
|
|
||||||
```
|
1. Build and load the kernel module follwowing the instructions in [intrepid-socketcan-kernel-module](https://github.com/intrepidcs/intrepid-socketcan-kernel-module).
|
||||||
$ make
|
|
||||||
```
|
|
||||||
|
|
||||||
To enable SocketCAN for your attached Intrepid devices, run the daemon. Use ```-D``` to run as a daemon, or pass no arguments to run in the foreground.
|
2. Install the dependencies needed. These are CMake 3.2+, GCC 4.8+, git, and libusb-1.0-0-dev.
|
||||||
|
|
||||||
```
|
On Ubuntu or other Debian-based systems, run `sudo apt install git cmake gcc libusb-1.0-0-dev build-essential`.
|
||||||
$ sudo ./icsscand -D
|
|
||||||
```
|
|
||||||
|
|
||||||
Most Intrepid devices support more than one CAN channel. Your interfaces will be named ```icsXcan_typeY``` where ```X``` is an incrementing identifier for different devices, ```can_type``` identifies the type of CAN channel, and ```Y``` is an incrementing identifier for each device. For example, the first neoVI FIRE you plug into your system will create the interfaces ```ics0can0```, ```ics0can1```, ```ics0can2```, ```ics0can3```, ```ics0lsftcan0```, ```ics0swcan0```.
|
3. Clone this repository recursively by running `git clone --recursive https://github.com/intrepidcs/icsscand.git`
|
||||||
|
|
||||||
Before you can read or write messages on an interface you'll need to bring it up
|
4. Switch into the cloned directory, `cd icsscand`
|
||||||
|
|
||||||
```
|
5. Make a build directory, `mkdir build`
|
||||||
$ sudo ifconfig ics0can0 up
|
|
||||||
```
|
6. Invoke CMake, `cmake .. -DCMAKE_BUILD_TYPE=Release`
|
||||||
|
|
||||||
|
7. Build the daemon, `make`
|
||||||
|
|
||||||
|
8. The daemon is now available as `libicsneo-socketcan-daemon`
|
||||||
|
|
||||||
|
9. Start the daemon up to begin connecting to devices. Use `sudo ./libicsneo-socketcan-daemon`. If you're happy with the results and would like to run in the background, run `sudo ./libicsneo-socketcan-daemon -d` to run in daemon mode.
|
||||||
|
|
||||||
|
10. CAN interfaces will have been created, but are "down" or, in other words, not enabled for transmit and receive yet. You can see them with `ip link`. They will be labelled `can0`, `can1`, and etc. They will have an alias listed which corresponds to the serial number of the device and network on that device.
|
||||||
|
|
||||||
|
11. Enable the CAN interface with `sudo ip link set up can0`, replacing `can0` with whichever interface you'd like to enable
|
||||||
|
|
||||||
|
12. You can now use any SocketCAN application with this interface. A good package for testing is the `can-utils` package. You can get this package with `sudo apt install can-utils`. A good testing tool which comes with this package is `candump`. Running `candump can0` will print a line for every incoming frame.
|
||||||
723
icsscand.c
723
icsscand.c
|
|
@ -1,723 +0,0 @@
|
||||||
/*
|
|
||||||
* icsscd.c - Userspace daemon for Intrepid SocketCAN support
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Intrepid Control Systems, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* 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 2 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/gpl.html
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
* DAMAGE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <syslog.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <linux/if.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <ics/icsneo40API.h>
|
|
||||||
|
|
||||||
#define DAEMON_NAME "icsscand"
|
|
||||||
#define RUN_AS_USER "root"
|
|
||||||
#define NETDEVICE_PATH "/dev/intrepid_netdevice"
|
|
||||||
#define SIOCSADDIF 0x3001
|
|
||||||
#define SIOCSREMOVEIF 0x3002
|
|
||||||
#define SIOCGSHAREDMEMSIZE 0x3003
|
|
||||||
#define SIOCSMSGSWRITTEN 0x3004
|
|
||||||
#define SIOCGMAXIFACES 0x3005
|
|
||||||
#define MSG_BUFFER_NUM_MSGS 20000
|
|
||||||
|
|
||||||
struct interface_t {
|
|
||||||
int kernel_handle;
|
|
||||||
unsigned char *to_kernel;
|
|
||||||
char name[IFNAMSIZ];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct connected_device_t {
|
|
||||||
/* handle returned by icsneoOpenNeoDevice */
|
|
||||||
void *handle;
|
|
||||||
|
|
||||||
/* info on interfaces created by the kernel driver */
|
|
||||||
struct interface_t *interfaces;
|
|
||||||
|
|
||||||
/* the number of interfaces for this device */
|
|
||||||
int num_interfaces;
|
|
||||||
|
|
||||||
/* translation from the icsSpyMessage NetworkID to interface handle */
|
|
||||||
int netid_to_interface[NETID_MAX];
|
|
||||||
|
|
||||||
/* while this is true the rx thread will keep running */
|
|
||||||
int keep_running;
|
|
||||||
|
|
||||||
pthread_t rx_thread;
|
|
||||||
NeoDevice device;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct intrepid_pending_tx_info {
|
|
||||||
int tx_box_index;
|
|
||||||
int count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct netid_lookup_t {
|
|
||||||
int device_index;
|
|
||||||
int netid;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int run_as_daemon = 0; /* if running in daemon mode */
|
|
||||||
static int icsscand_running = 0; /* if we're running */
|
|
||||||
static int netdevice = 0; /* fd of /dev/intrepid_netdevice */
|
|
||||||
static int shared_mem_size = 0; /* size of shared kernel mem */
|
|
||||||
static int max_num_ifaces = 0; /* max ifaces the kernel supports */
|
|
||||||
static unsigned char *shared_mem = NULL; /* shared kernel mem */
|
|
||||||
static int exit_code;
|
|
||||||
|
|
||||||
static struct connected_device_t *connected = NULL; /* connected devices */
|
|
||||||
static struct netid_lookup_t *netid_lookup = NULL; /* iface index -> device/netid */
|
|
||||||
static pthread_mutex_t devices_mutex ; /* mutex for adding/removing devices */
|
|
||||||
|
|
||||||
#define RX_BOX_SIZE (shared_mem_size / (max_num_ifaces * 2))
|
|
||||||
#define TX_BOX_SIZE (shared_mem_size / 4)
|
|
||||||
#define GET_RX_BOX(DEVICE_INDEX) (shared_mem + (RX_BOX_SIZE * DEVICE_INDEX))
|
|
||||||
#define GET_TX_BOX(INDEX) (shared_mem + (shared_mem_size / 2) + (INDEX * TX_BOX_SIZE))
|
|
||||||
#define MAX_NUM_RX_MSGS (RX_BOX_SIZE / sizeof(icsSpyMessage))
|
|
||||||
#define MAX_NUM_TX_MSGS (TX_BOX_SIZE / sizeof(icsSpyMessage))
|
|
||||||
|
|
||||||
#define PROBE_INTERVAL_MS 1000UL
|
|
||||||
#define TX_TIMEOUT_MS 100UL
|
|
||||||
|
|
||||||
#define LOG(LVL, MSG, ...) do{if(run_as_daemon) syslog(LVL, MSG, __VA_ARGS__); \
|
|
||||||
else fprintf(stderr, MSG, __VA_ARGS__);}while(0)
|
|
||||||
#define LOG_NOARGS(LVL, MSG) do{if(run_as_daemon) syslog(LVL, MSG); \
|
|
||||||
else fprintf(stderr, MSG);}while(0)
|
|
||||||
|
|
||||||
static void print_usage(char *prg)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "\nUsage: %s\n\n", prg);
|
|
||||||
fprintf(stderr, " -D (run as a daemon)\n");
|
|
||||||
fprintf(stderr, " -h (show this help page)\n");
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signal_handler(int signum)
|
|
||||||
{
|
|
||||||
switch (signum) {
|
|
||||||
|
|
||||||
case SIGUSR1:
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
break;
|
|
||||||
case SIGALRM:
|
|
||||||
case SIGCHLD:
|
|
||||||
LOG(LOG_NOTICE, "received signal %i\n", signum);
|
|
||||||
exit_code = EXIT_FAILURE;
|
|
||||||
icsscand_running = 0;
|
|
||||||
break;
|
|
||||||
case SIGINT:
|
|
||||||
case SIGTERM:
|
|
||||||
LOG(LOG_NOTICE, "received signal %i\n", signum);
|
|
||||||
exit_code = EXIT_SUCCESS;
|
|
||||||
icsscand_running = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_connected_device(struct connected_device_t* device, int join_thread)
|
|
||||||
{
|
|
||||||
if (join_thread)
|
|
||||||
{
|
|
||||||
device->keep_running = 0;
|
|
||||||
pthread_join(device->rx_thread, NULL);
|
|
||||||
}
|
|
||||||
if (device->handle)
|
|
||||||
{
|
|
||||||
int num_errors;
|
|
||||||
icsneoClosePort(device->handle, &num_errors);
|
|
||||||
}
|
|
||||||
if (device->interfaces)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
for (i = 0 ; i < device->num_interfaces ; ++i)
|
|
||||||
{
|
|
||||||
ioctl(netdevice, SIOCSREMOVEIF, device->interfaces[i].kernel_handle);
|
|
||||||
netid_lookup[device->interfaces[i].kernel_handle].device_index = -1;
|
|
||||||
}
|
|
||||||
free(device->interfaces);
|
|
||||||
}
|
|
||||||
memset(device, 0, sizeof(*device));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setup_interface_info(int device_index, int net_id,
|
|
||||||
int iface_index, const char* name)
|
|
||||||
{
|
|
||||||
struct connected_device_t *device = &connected[device_index];
|
|
||||||
struct interface_t *iface = &device->interfaces[iface_index];
|
|
||||||
|
|
||||||
device->netid_to_interface[net_id] = iface_index;
|
|
||||||
|
|
||||||
netid_lookup[iface->kernel_handle].device_index = device_index;
|
|
||||||
netid_lookup[iface->kernel_handle].netid = net_id;
|
|
||||||
|
|
||||||
/* this is apparently how you change a netdevice's name */
|
|
||||||
if (name)
|
|
||||||
{
|
|
||||||
char new_name[IFNAMSIZ];
|
|
||||||
struct ifreq ifr;
|
|
||||||
int s = socket(PF_INET, SOCK_DGRAM, 0);
|
|
||||||
|
|
||||||
sprintf(new_name, "ics%d%s", device_index, name);
|
|
||||||
|
|
||||||
if (s >= 0)
|
|
||||||
{
|
|
||||||
strncpy(ifr.ifr_name, iface->name, IFNAMSIZ);
|
|
||||||
strncpy(ifr.ifr_newname, new_name, IFNAMSIZ);
|
|
||||||
|
|
||||||
if (ioctl(s, SIOCSIFNAME, &ifr) < 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_NOTICE, "could not rename %s to %s\n",
|
|
||||||
iface->name, new_name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
strncpy(iface->name, new_name, IFNAMSIZ);
|
|
||||||
|
|
||||||
close(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void* rx(void* arg)
|
|
||||||
{
|
|
||||||
int num_errors, num_msgs, ret, i;
|
|
||||||
|
|
||||||
struct connected_device_t* dev = (struct connected_device_t*)arg;
|
|
||||||
icsSpyMessage* msgs = (icsSpyMessage*)malloc(
|
|
||||||
sizeof(icsSpyMessage) * MSG_BUFFER_NUM_MSGS);
|
|
||||||
int *box_count = (int*)malloc(sizeof(int) * dev->num_interfaces);
|
|
||||||
int max_num_msgs = MAX_NUM_RX_MSGS;
|
|
||||||
|
|
||||||
/* we pass the num of written messages as a short, so cap at this amount */
|
|
||||||
if (max_num_msgs >= 1 << 16)
|
|
||||||
max_num_msgs = (1 << 16) - 1;
|
|
||||||
|
|
||||||
if (!msgs || !box_count)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "could not allocate a %d message buffer\n",
|
|
||||||
MSG_BUFFER_NUM_MSGS);
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (dev->keep_running)
|
|
||||||
{
|
|
||||||
/* block the thread waiting for more messages.
|
|
||||||
* this is a pthread_cond_timedwait underneath, so our thread
|
|
||||||
* will sleep if there's nothing there */
|
|
||||||
int ret = icsneoWaitForRxMessagesWithTimeOut(dev->handle, 100);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error waiting for messages on device %d\n",
|
|
||||||
dev->device.SerialNumber);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if(ret == 0)
|
|
||||||
continue; /* timeout */
|
|
||||||
|
|
||||||
/* read out the pending messages */
|
|
||||||
ret = icsneoGetMessages(dev->handle, msgs, &num_msgs, &num_errors);
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error reading messages on device %d\n",
|
|
||||||
dev->device.SerialNumber);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset the counts of all boxes */
|
|
||||||
memset(box_count, 0, sizeof(int) * dev->num_interfaces);
|
|
||||||
|
|
||||||
for(i = 0 ; i < num_msgs ; ++i)
|
|
||||||
{
|
|
||||||
icsSpyMessage* box;
|
|
||||||
struct interface_t *iface;
|
|
||||||
|
|
||||||
/* get the message to copy */
|
|
||||||
const icsSpyMessage* msg = &msgs[i];
|
|
||||||
|
|
||||||
/* lookup which interface this network maps to */
|
|
||||||
int iface_index = dev->netid_to_interface[msg->NetworkID];
|
|
||||||
if (iface_index == -1)
|
|
||||||
continue; /* unknown network */
|
|
||||||
iface = &dev->interfaces[iface_index];
|
|
||||||
|
|
||||||
/* find the appropriate box for writing to this iface */
|
|
||||||
box = (icsSpyMessage*)iface->to_kernel;
|
|
||||||
|
|
||||||
/* copy to kernel memory */
|
|
||||||
memcpy(box + box_count[iface_index], msg, sizeof(icsSpyMessage));
|
|
||||||
|
|
||||||
/* check to see if the box is full -- if so, notify the kernel */
|
|
||||||
if (++box_count[iface_index] == max_num_msgs)
|
|
||||||
{
|
|
||||||
unsigned int ioctl_arg =
|
|
||||||
(iface->kernel_handle << 16) | max_num_msgs;
|
|
||||||
|
|
||||||
ret = ioctl(netdevice, SIOCSMSGSWRITTEN, ioctl_arg);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error transferring to kernel: %s\n",
|
|
||||||
strerror(ret));
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
box_count[iface_index] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* notify the kernel of any boxes that didn't fill up */
|
|
||||||
for(i = 0 ; i < dev->num_interfaces ; ++i)
|
|
||||||
{
|
|
||||||
struct interface_t *iface = &dev->interfaces[i];
|
|
||||||
if(box_count[i] > 0)
|
|
||||||
{
|
|
||||||
unsigned int ioctl_arg =
|
|
||||||
(iface->kernel_handle << 16) | box_count[i];
|
|
||||||
|
|
||||||
ret = ioctl(netdevice, SIOCSMSGSWRITTEN, ioctl_arg);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error transferring to kernel: %s\n",
|
|
||||||
strerror(ret));
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto exit;
|
|
||||||
|
|
||||||
error:
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
|
||||||
free_connected_device(dev, 0); /* don't cancel our thread quite yet */
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
|
||||||
|
|
||||||
exit:
|
|
||||||
if (msgs)
|
|
||||||
free(msgs);
|
|
||||||
if (box_count)
|
|
||||||
free(box_count);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void probe_new_devices()
|
|
||||||
{
|
|
||||||
NeoDevice *detected = (NeoDevice*)malloc(sizeof(NeoDevice) * max_num_ifaces);
|
|
||||||
int num_devices = max_num_ifaces, i;
|
|
||||||
const unsigned long device_types = NEODEVICE_VCAN41 | NEODEVICE_VCAN42 | NEODEVICE_VCAN3
|
|
||||||
| NEODEVICE_FIRE | NEODEVICE_ANY_ION | NEODEVICE_ANY_PLASMA | NEODEVICE_FIRE2;
|
|
||||||
/* find all the connected devices */
|
|
||||||
int ret = icsneoFindNeoDevices(device_types, detected, &num_devices);
|
|
||||||
if (ret == 0 || num_devices == 0)
|
|
||||||
{
|
|
||||||
free(detected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
|
||||||
for (i = 0 ; i < num_devices ; ++i)
|
|
||||||
{
|
|
||||||
int j, num_nets = 0, already_connected = 0, device_index;
|
|
||||||
struct connected_device_t *device = NULL;
|
|
||||||
|
|
||||||
/* see if we're already connected to this device, and if we aren't,
|
|
||||||
* find a spot for this device in our array */
|
|
||||||
for(j = 0 ; j < max_num_ifaces ; ++j)
|
|
||||||
{
|
|
||||||
if (connected[j].handle != NULL &&
|
|
||||||
connected[j].device.DeviceType == detected[i].DeviceType &&
|
|
||||||
connected[j].device.SerialNumber == detected[i].SerialNumber )
|
|
||||||
{
|
|
||||||
already_connected = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (device == NULL && connected[j].handle == NULL)
|
|
||||||
device = &connected[device_index = j];
|
|
||||||
}
|
|
||||||
if (already_connected)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (device == NULL)
|
|
||||||
break; /* no more room at the inn */
|
|
||||||
|
|
||||||
/* actually open the device */
|
|
||||||
ret = icsneoOpenNeoDevice(&detected[i], &device->handle, NULL, 1, 0);
|
|
||||||
if (ret != 1)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "Unable to open device with serial %i\n",
|
|
||||||
device->device.SerialNumber);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
device->device = detected[i];
|
|
||||||
|
|
||||||
/* figure out how many interfaces we need to make */
|
|
||||||
switch(device->device.DeviceType)
|
|
||||||
{
|
|
||||||
case NEODEVICE_VCAN41:
|
|
||||||
case NEODEVICE_VCAN42:
|
|
||||||
case NEODEVICE_VCAN3:
|
|
||||||
num_nets = 2;
|
|
||||||
break;
|
|
||||||
case NEODEVICE_FIRE:
|
|
||||||
case NEODEVICE_ANY_ION:
|
|
||||||
case NEODEVICE_ANY_PLASMA:
|
|
||||||
/* some ions and plasmas actually have 8,
|
|
||||||
* but we can't tell from the PID */
|
|
||||||
num_nets = 6;
|
|
||||||
break;
|
|
||||||
case NEODEVICE_FIRE2:
|
|
||||||
/* todo: software selectable networks */
|
|
||||||
num_nets = 6;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(num_nets <= 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "Unknown device with serial %d\n",
|
|
||||||
device->device.SerialNumber);
|
|
||||||
free_connected_device(device, 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
device->interfaces = (struct interface_t*)malloc(
|
|
||||||
sizeof(struct interface_t) * num_nets
|
|
||||||
);
|
|
||||||
|
|
||||||
for(j = 0 ; j < num_nets ; ++j)
|
|
||||||
{
|
|
||||||
struct interface_t *iface = &device->interfaces[device->num_interfaces++];
|
|
||||||
ret = ioctl(netdevice, SIOCSADDIF, iface->name);
|
|
||||||
if (ret >= 0)
|
|
||||||
{
|
|
||||||
iface->kernel_handle = ret;
|
|
||||||
iface->to_kernel = GET_RX_BOX(ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* make sure we got all the interfaces we wanted */
|
|
||||||
if (device->num_interfaces != num_nets)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "Could not create all interfaces for %d\n",
|
|
||||||
device->device.SerialNumber);
|
|
||||||
free_connected_device(device, 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build a mapping from network id -> interface id */
|
|
||||||
for(j = 0 ; j < NETID_MAX ; ++j)
|
|
||||||
device->netid_to_interface[j] = -1;
|
|
||||||
switch(device->device.DeviceType)
|
|
||||||
{
|
|
||||||
case NEODEVICE_VCAN41:
|
|
||||||
case NEODEVICE_VCAN42:
|
|
||||||
case NEODEVICE_VCAN3:
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN, 0, "can0");
|
|
||||||
setup_interface_info(device_index, NETID_MSCAN, 1, "can1");
|
|
||||||
break;
|
|
||||||
case NEODEVICE_FIRE:
|
|
||||||
case NEODEVICE_ANY_PLASMA:
|
|
||||||
case NEODEVICE_ANY_ION:
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN, 0, "can0");
|
|
||||||
setup_interface_info(device_index, NETID_MSCAN, 1, "can1");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN2, 2, "can2");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN3, 3, "can3");
|
|
||||||
setup_interface_info(device_index, NETID_SWCAN, 4, "swcan0");
|
|
||||||
setup_interface_info(device_index, NETID_LSFTCAN, 5, "lsftcan0");
|
|
||||||
/* TODO: IONs w/ 8 CAN */
|
|
||||||
break;
|
|
||||||
case NEODEVICE_FIRE2:
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN, 0, "can0");
|
|
||||||
setup_interface_info(device_index, NETID_MSCAN, 1, "can1");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN2, 2, "can2");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN3, 3, "can3");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN4, 4, "can4");
|
|
||||||
setup_interface_info(device_index, NETID_HSCAN5, 5, "can5");
|
|
||||||
/* TODO: software selectable networks */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
device->keep_running = 1;
|
|
||||||
ret = pthread_create(&device->rx_thread, NULL, rx, device);
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "Error creating thread for %d\n",
|
|
||||||
device->device.SerialNumber);
|
|
||||||
free_connected_device(device, 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
|
||||||
|
|
||||||
free(detected);
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long get_ms_tick_count()
|
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
|
|
||||||
return (tv.tv_sec * 1000UL) + (tv.tv_usec / 1000UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* kernel wants us to transmit some messages */
|
|
||||||
static void tx(int index, int count)
|
|
||||||
{
|
|
||||||
struct netid_lookup_t *lookup;
|
|
||||||
struct connected_device_t *dev;
|
|
||||||
icsSpyMessage *msg = (icsSpyMessage*)GET_TX_BOX(index);
|
|
||||||
int i, ret;
|
|
||||||
|
|
||||||
for(i = 0 ; i < count ; ++i, ++msg)
|
|
||||||
{
|
|
||||||
/* NetworkID is actually the device index. When we went online we stored
|
|
||||||
* the real NetworkID for this device index in netid. Do the reverse
|
|
||||||
* lookup and set the correct NetworkID */
|
|
||||||
if (msg->NetworkID < 0 || msg->NetworkID >= max_num_ifaces)
|
|
||||||
continue;
|
|
||||||
lookup = &netid_lookup[msg->NetworkID];
|
|
||||||
|
|
||||||
if (lookup->device_index < 0 || lookup->device_index > max_num_ifaces)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
msg->NetworkID = lookup->netid;
|
|
||||||
dev = &connected[lookup->device_index];
|
|
||||||
|
|
||||||
ret = icsneoTxMessages(dev->handle, msg, lookup->netid, 1);
|
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error transmitting on device %d\n",
|
|
||||||
dev->device.SerialNumber);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
|
||||||
free_connected_device(dev, 1);
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void* probe_device_thread(void* arg)
|
|
||||||
{
|
|
||||||
while (icsscand_running)
|
|
||||||
{
|
|
||||||
usleep(PROBE_INTERVAL_MS * 1000);
|
|
||||||
|
|
||||||
if (!icsscand_running)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
probe_new_devices(netdevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int opt, ret, i, opened_devices = 0;
|
|
||||||
unsigned long last_probe = 0;
|
|
||||||
fd_set fds;
|
|
||||||
struct timeval timeout;
|
|
||||||
struct timespec clock;
|
|
||||||
pthread_t probe_thread;
|
|
||||||
|
|
||||||
/* process command line switches */
|
|
||||||
while ((opt = getopt(argc, argv, "hD")) != -1)
|
|
||||||
{
|
|
||||||
switch(opt)
|
|
||||||
{
|
|
||||||
case 'D':
|
|
||||||
run_as_daemon = 1;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
case '?':
|
|
||||||
default:
|
|
||||||
print_usage(argv[0]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* setup logging, signals, and daemonize if requested */
|
|
||||||
openlog(DAEMON_NAME, LOG_PID, LOG_LOCAL5);
|
|
||||||
if (run_as_daemon)
|
|
||||||
{
|
|
||||||
if (daemon(0, 0))
|
|
||||||
{
|
|
||||||
LOG_NOARGS(LOG_ERR, "failed to daemonize");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
signal(SIGINT, signal_handler);
|
|
||||||
signal(SIGTERM, signal_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we're now "running" -- incoming signals tell us to stop */
|
|
||||||
icsscand_running = 1;
|
|
||||||
|
|
||||||
/* this is the lock for adding/removing devices from the connected array */
|
|
||||||
pthread_mutex_init(&devices_mutex, NULL);
|
|
||||||
|
|
||||||
icsneoInitializeAPI();
|
|
||||||
|
|
||||||
/* open /dev/intrepid_netdevice -- this has ioctls for adding can interfaces */
|
|
||||||
netdevice = open(NETDEVICE_PATH, O_RDWR | O_NONBLOCK);
|
|
||||||
if (netdevice < 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "failed to open %s: %s\n", NETDEVICE_PATH, strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read out some constants from the driver (these are #defines that can change) */
|
|
||||||
max_num_ifaces = ioctl(netdevice, SIOCGMAXIFACES);
|
|
||||||
if (max_num_ifaces <= 0)
|
|
||||||
{
|
|
||||||
LOG_NOARGS(LOG_ERR, "could not get maximum number of interfaces\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
shared_mem_size = ioctl(netdevice, SIOCGSHAREDMEMSIZE);
|
|
||||||
if (shared_mem_size <= 0)
|
|
||||||
{
|
|
||||||
LOG_NOARGS(LOG_ERR, "could not read shared memory size\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the shared memory is what we use to transfer messages between the kernel and
|
|
||||||
* user mode. the only blocking kernel switch is the ioctl once we're done copying
|
|
||||||
* (and the inital page faults) */
|
|
||||||
shared_mem = mmap(NULL, shared_mem_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
||||||
netdevice, 0);
|
|
||||||
if (shared_mem == MAP_FAILED)
|
|
||||||
{
|
|
||||||
LOG_NOARGS(LOG_ERR, "could not create shared memory mapping\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* allocate the global structure of the devices we're conntected to */
|
|
||||||
connected = (struct connected_device_t*)
|
|
||||||
malloc(sizeof(struct connected_device_t) * max_num_ifaces);
|
|
||||||
memset(connected, 0, sizeof(struct connected_device_t) * max_num_ifaces);
|
|
||||||
|
|
||||||
/* the NetworkID of transmit requests is the interface index, we need to
|
|
||||||
* convert it to the device/netid pair to transmit on -- allocate this map */
|
|
||||||
netid_lookup = (struct netid_lookup_t*)
|
|
||||||
malloc(sizeof(struct netid_lookup_t) * max_num_ifaces);
|
|
||||||
for(i = 0 ; i < max_num_ifaces ; ++i)
|
|
||||||
netid_lookup[i].device_index = -1;
|
|
||||||
|
|
||||||
/* probe once for new devices, then start a thread to do it in the background
|
|
||||||
* periodically */
|
|
||||||
probe_new_devices(netdevice);
|
|
||||||
pthread_create(&probe_thread, NULL, probe_device_thread, NULL);
|
|
||||||
|
|
||||||
/* main loop. icsscand_running can be set to 0 from SIGINT, SIGTERM, etc. */
|
|
||||||
while (icsscand_running)
|
|
||||||
{
|
|
||||||
/* wait for some new transmit messages */
|
|
||||||
timeout.tv_sec = 0;
|
|
||||||
timeout.tv_usec = TX_TIMEOUT_MS * 1000;
|
|
||||||
|
|
||||||
FD_ZERO(&fds);
|
|
||||||
FD_SET(netdevice, &fds);
|
|
||||||
|
|
||||||
ret = select(netdevice + 1, &fds, NULL, NULL, &timeout);
|
|
||||||
|
|
||||||
if (ret == -1)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error waiting for tx messages: %s\n", strerror(errno));
|
|
||||||
icsscand_running = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if(ret)
|
|
||||||
{
|
|
||||||
/* kernel says there're some new transmit messages waiting to go
|
|
||||||
* out. call read() to find out which box they're in and how many */
|
|
||||||
struct intrepid_pending_tx_info info;
|
|
||||||
ssize_t r;
|
|
||||||
|
|
||||||
r = read(netdevice, &info, sizeof(info));
|
|
||||||
if (r == -1)
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR, "error reading tx messages: %s\n",
|
|
||||||
strerror(errno));
|
|
||||||
icsscand_running = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if(r != sizeof(info))
|
|
||||||
{
|
|
||||||
LOG(LOG_ERR,
|
|
||||||
"unexpected number of bytes read, expected %d got %d\n",
|
|
||||||
(int)sizeof(info), (int)r);
|
|
||||||
icsscand_running = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* send the messages! */
|
|
||||||
tx(info.tx_box_index, info.count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* timeout */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cleanup */
|
|
||||||
|
|
||||||
pthread_join(probe_thread, NULL);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
|
||||||
for(i = 0 ; i < max_num_ifaces ; ++i)
|
|
||||||
free_connected_device(&connected[i], 1);
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
|
||||||
|
|
||||||
free(connected);
|
|
||||||
free(netid_lookup);
|
|
||||||
munmap(shared_mem, shared_mem_size);
|
|
||||||
close(netdevice);
|
|
||||||
|
|
||||||
pthread_exit(NULL);
|
|
||||||
return exit_code;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef __ICSNEO_SOCKETCAN_USERMODE_BUILDINFO_H_
|
||||||
|
#define __ICSNEO_SOCKETCAN_USERMODE_BUILDINFO_H_
|
||||||
|
|
||||||
|
const char* ICSNEO_SOCKETCAN_GIT_BRANCH = "@GIT_BRANCH@";
|
||||||
|
const char* ICSNEO_SOCKETCAN_GIT_DESCRIBE = "@GIT_DESCRIBE@";
|
||||||
|
const uint8_t ICSNEO_SOCKETCAN_BUILD_MAJOR = (@PROJECT_VERSION_MAJOR@);
|
||||||
|
const uint8_t ICSNEO_SOCKETCAN_BUILD_MINOR = (@PROJECT_VERSION_MINOR@);
|
||||||
|
const uint8_t ICSNEO_SOCKETCAN_BUILD_PATCH = (@PROJECT_VERSION_PATCH@);
|
||||||
|
const uint32_t ICSNEO_SOCKETCAN_BUILD_VERINT = (((@PROJECT_VERSION_MAJOR@) << 16) | ((@PROJECT_VERSION_MINOR@) << 8) | (@PROJECT_VERSION_PATCH@));
|
||||||
|
const char* ICSNEO_SOCKETCAN_BUILD_METADATA = "@BUILD_METADATA@";
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,511 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <sysexits.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <linux/if.h>
|
||||||
|
|
||||||
|
#include <icsneo/icsneocpp.h>
|
||||||
|
#include <icsneo/communication/message/neomessage.h>
|
||||||
|
#include <icsneo/communication/message/callback/canmessagecallback.h>
|
||||||
|
#include <generated/buildinfo.h>
|
||||||
|
|
||||||
|
#define LOG(LVL, MSG) do{if(runningAsDaemon) syslog(LVL, MSG); \
|
||||||
|
else fprintf(stderr, MSG);}while(0)
|
||||||
|
#define LOGF(LVL, MSG, ...) do{if(runningAsDaemon) syslog(LVL, MSG, __VA_ARGS__); \
|
||||||
|
else fprintf(stderr, MSG, __VA_ARGS__);}while(0)
|
||||||
|
|
||||||
|
#define SIOCSADDIF 0x3001
|
||||||
|
#define SIOCSREMOVEIF 0x3002
|
||||||
|
#define SIOCGSHAREDMEMSIZE 0x3003
|
||||||
|
#define SIOCSMSGSWRITTEN 0x3004
|
||||||
|
#define SIOCGMAXIFACES 0x3005
|
||||||
|
#define SIOCGVERSION 0x3006
|
||||||
|
#define SIOCGCLIENTVEROK 0x3007
|
||||||
|
|
||||||
|
#define RX_BOX_SIZE (sharedMemSize / (maxInterfaces * 2))
|
||||||
|
#define TX_BOX_SIZE (sharedMemSize / 4)
|
||||||
|
#define GET_RX_BOX(DEVICE_INDEX) (reinterpret_cast<uint8_t*>(sharedMemory) + (RX_BOX_SIZE * DEVICE_INDEX))
|
||||||
|
#define GET_TX_BOX(INDEX) (reinterpret_cast<uint8_t*>(sharedMemory) + (sharedMemSize / 2) + (INDEX * TX_BOX_SIZE))
|
||||||
|
|
||||||
|
bool runningAsDaemon = false;
|
||||||
|
int driver = 0; // /dev/intrepid_netdevice
|
||||||
|
int driverMajor = 0;
|
||||||
|
int driverMinor = 0;
|
||||||
|
int driverPatch = 0;
|
||||||
|
int maxInterfaces = 0; // From driver
|
||||||
|
int sharedMemSize = 0; // From driver
|
||||||
|
void* sharedMemory = nullptr;
|
||||||
|
|
||||||
|
std::atomic<bool> stopRunning(false);
|
||||||
|
|
||||||
|
struct intrepid_pending_tx_info {
|
||||||
|
int tx_box_index;
|
||||||
|
int count;
|
||||||
|
size_t bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkInterface {
|
||||||
|
public:
|
||||||
|
NetworkInterface(const std::string& desiredName) : name(desiredName) {
|
||||||
|
char ifname[IFALIASZ + 1] = {0};
|
||||||
|
strncpy(ifname, name.c_str(), IFALIASZ);
|
||||||
|
kernelHandle = ioctl(driver, SIOCSADDIF, ifname);
|
||||||
|
if(openedSuccessfully()) {
|
||||||
|
rxBox = GET_RX_BOX(kernelHandle);
|
||||||
|
rxBoxCurrentPosition = rxBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~NetworkInterface() {
|
||||||
|
if(openedSuccessfully()) {
|
||||||
|
LOGF(LOG_DEBUG, "Removing device %s with handle %d\n", name.c_str(), kernelHandle);
|
||||||
|
int res = ioctl(driver, SIOCSREMOVEIF, kernelHandle);
|
||||||
|
LOGF(LOG_DEBUG, "Removed device %s with handle %d, result %d\n", name.c_str(), kernelHandle, res);
|
||||||
|
} else
|
||||||
|
LOG(LOG_DEBUG, "Removing interface which was not opened successfully\n");
|
||||||
|
}
|
||||||
|
NetworkInterface(const NetworkInterface&) = delete;
|
||||||
|
NetworkInterface& operator =(const NetworkInterface&) = delete;
|
||||||
|
|
||||||
|
bool openedSuccessfully() const { return kernelHandle >= 0; }
|
||||||
|
int getKernelHandle() const { return kernelHandle; }
|
||||||
|
const std::string& getName() const { return name; }
|
||||||
|
uint8_t* getRxBox() { return rxBox; }
|
||||||
|
const uint8_t* getRxBox() const { return rxBox; }
|
||||||
|
void addReceivedMessageToQueue(const std::shared_ptr<icsneo::Message>& msg) {
|
||||||
|
auto neomessage = icsneo::CreateNeoMessage(msg);
|
||||||
|
size_t bytesNeeded = sizeof(neomessage) + neomessage.length;
|
||||||
|
std::lock_guard<std::mutex> lg(rxBoxLock);
|
||||||
|
if(ssize_t((rxBoxCurrentPosition - rxBox) + bytesNeeded) > RX_BOX_SIZE) {
|
||||||
|
// fail, too big!
|
||||||
|
LOG(LOG_DEBUG, "box too small\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(rxBoxCurrentPosition, &neomessage, sizeof(neomessage));
|
||||||
|
rxBoxCurrentPosition += sizeof(neomessage);
|
||||||
|
memcpy(rxBoxCurrentPosition, neomessage.data, neomessage.length);
|
||||||
|
rxBoxCurrentPosition += neomessage.length;
|
||||||
|
rxBoxMessageCount++;
|
||||||
|
if(ioctl(driver, SIOCSMSGSWRITTEN, (kernelHandle << 16) | rxBoxMessageCount) < 0) {
|
||||||
|
LOGF(LOG_DEBUG, "send ioctl failed %d %zu\n", kernelHandle, rxBoxMessageCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rxBoxCurrentPosition = rxBox;
|
||||||
|
rxBoxMessageCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
int kernelHandle = -1;
|
||||||
|
std::mutex rxBoxLock;
|
||||||
|
uint8_t* rxBox = nullptr;
|
||||||
|
uint8_t* rxBoxCurrentPosition = nullptr;
|
||||||
|
size_t rxBoxMessageCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenDevice {
|
||||||
|
public:
|
||||||
|
OpenDevice(const std::shared_ptr<icsneo::Device>& openDevice) : device(openDevice) {}
|
||||||
|
std::shared_ptr<icsneo::Device> device;
|
||||||
|
std::map<icsneo::Network::NetID, std::shared_ptr<NetworkInterface>> interfaces;
|
||||||
|
|
||||||
|
bool operator ==(const std::shared_ptr<icsneo::Device>& other) const {
|
||||||
|
return device->getSerial() == other->getSerial();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Lazy {
|
||||||
|
public:
|
||||||
|
Lazy(std::function<T()> f) : fn(f) {}
|
||||||
|
operator T() {
|
||||||
|
if(!valid)
|
||||||
|
evaluate();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
void invalidate() { valid = false; }
|
||||||
|
void evaluate() {
|
||||||
|
result = fn();
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
T result;
|
||||||
|
bool valid = false;
|
||||||
|
std::function<T()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<OpenDevice> openDevices;
|
||||||
|
std::vector<std::string /* serial */> failedToOpen;
|
||||||
|
std::mutex openDevicesMutex;
|
||||||
|
|
||||||
|
std::string& replaceInPlace(std::string& str, char o, const std::string& n) {
|
||||||
|
size_t start_pos = 0;
|
||||||
|
const size_t new_len = n.length();
|
||||||
|
while((start_pos = str.find(o, start_pos)) != std::string::npos) {
|
||||||
|
str.replace(start_pos, 1, n);
|
||||||
|
start_pos += new_len;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sanitizeInterfaceName(std::string str) {
|
||||||
|
static const std::string nullString = "";
|
||||||
|
replaceInPlace(str, ' ', nullString);
|
||||||
|
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void header() {
|
||||||
|
std::cout << "The libicsneo SocketCAN Usermode Daemon\n";
|
||||||
|
std::cout << "Copyright Intrepid Control Systems, Inc. 2019\n\n";
|
||||||
|
std::cout << "Daemon v";
|
||||||
|
std::cout << (int)ICSNEO_SOCKETCAN_BUILD_MAJOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_MINOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_PATCH;
|
||||||
|
if(ICSNEO_SOCKETCAN_BUILD_METADATA[0] != '\0')
|
||||||
|
std::cout << '+' << ICSNEO_SOCKETCAN_BUILD_METADATA;
|
||||||
|
std::string describe(ICSNEO_SOCKETCAN_GIT_DESCRIBE);
|
||||||
|
if(describe.find("fatal") != 0) {
|
||||||
|
if(std::string(ICSNEO_SOCKETCAN_GIT_BRANCH) != "master")
|
||||||
|
std::cout << ' ' << ICSNEO_SOCKETCAN_GIT_BRANCH;
|
||||||
|
if(describe[0] != 'v')
|
||||||
|
std::cout << " @ " << describe;
|
||||||
|
}
|
||||||
|
std::cout << "\nlibicsneo " << icsneo::GetVersion() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void usage(std::string executableName) {
|
||||||
|
std::cerr << "The libicsneo SocketCAN Usermode Daemon\n";
|
||||||
|
std::cerr << "Copyright Intrepid Control Systems, Inc. 2019\n\n";
|
||||||
|
std::cerr << "Usage: " << executableName << " [option]\n\n";
|
||||||
|
std::cerr << "Options:\n";
|
||||||
|
std::cerr << "\t-d, --daemon\t\tRun as a daemon in the background\n";
|
||||||
|
std::cerr << "\t-h, -?, --help, --usage\t\tShow this help page\n";
|
||||||
|
std::cerr << "\t --devices\t\tList supported devices\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void terminateSignal(int signal) {
|
||||||
|
stopRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void searchForDevices() {
|
||||||
|
auto found = icsneo::FindAllDevices();
|
||||||
|
std::lock_guard<std::mutex> lg(openDevicesMutex);
|
||||||
|
|
||||||
|
// Open devices we have not seen before
|
||||||
|
for(auto& dev : found) {
|
||||||
|
bool alreadyOpen = false;
|
||||||
|
for(const auto& openDev : openDevices) {
|
||||||
|
if(openDev == dev) {
|
||||||
|
alreadyOpen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(alreadyOpen)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Now open the device
|
||||||
|
OpenDevice newDevice(dev);
|
||||||
|
const std::string serial = newDevice.device->getSerial();
|
||||||
|
Lazy<bool> firstTimeFailedToOpen([&serial]() {
|
||||||
|
return std::find(failedToOpen.begin(), failedToOpen.end(), serial) == failedToOpen.end();
|
||||||
|
});
|
||||||
|
if(!newDevice.device->open() || !newDevice.device->goOnline()) {
|
||||||
|
if(firstTimeFailedToOpen) {
|
||||||
|
icsneo::APIError err;
|
||||||
|
icsneo::GetLastError(err);
|
||||||
|
LOGF(LOG_INFO, "%s failed to connect. Will keep trying...\n%s\n", newDevice.device->describe().c_str(), err.describe().c_str());
|
||||||
|
failedToOpen.push_back(serial);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the supported CAN networks
|
||||||
|
auto supportedNetworks = newDevice.device->getSupportedRXNetworks();
|
||||||
|
supportedNetworks.erase(std::remove_if(supportedNetworks.begin(), supportedNetworks.end(), [](const icsneo::Network& net) -> bool {
|
||||||
|
return net.getType() != icsneo::Network::Type::CAN;// Only want CAN networks
|
||||||
|
}), supportedNetworks.end());
|
||||||
|
if(supportedNetworks.empty()) {
|
||||||
|
if(firstTimeFailedToOpen) {
|
||||||
|
LOGF(LOG_INFO, "%s has no supported CAN networks\n", newDevice.device->describe().c_str());
|
||||||
|
failedToOpen.push_back(serial);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a network interface for each CAN network
|
||||||
|
for(const auto& net : supportedNetworks) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << sanitizeInterfaceName(icsneo::Network::GetNetIDString(net.getNetID())) << "_" << serial;
|
||||||
|
std::string interfaceName(ss.str());
|
||||||
|
if(firstTimeFailedToOpen)
|
||||||
|
LOGF(LOG_INFO, "Creating network interface %s\n", interfaceName.c_str());
|
||||||
|
newDevice.interfaces[net.getNetID()] = std::make_shared<NetworkInterface>(interfaceName);
|
||||||
|
LOGF(LOG_INFO, "Created network interface %s\n", interfaceName.c_str());
|
||||||
|
}
|
||||||
|
bool failedToCreateNetworkInterfaces = false;
|
||||||
|
for(const auto& iface : newDevice.interfaces) {
|
||||||
|
if(!iface.second->openedSuccessfully()) {
|
||||||
|
failedToCreateNetworkInterfaces = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(failedToCreateNetworkInterfaces) {
|
||||||
|
if(firstTimeFailedToOpen) {
|
||||||
|
LOGF(LOG_INFO, "%s failed to create network interfaces. Will keep trying...\n", newDevice.device->describe().c_str());
|
||||||
|
failedToOpen.push_back(serial);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create rx listener
|
||||||
|
newDevice.device->addMessageCallback(icsneo::CANMessageCallback([serial](std::shared_ptr<icsneo::Message> message) {
|
||||||
|
if(message->transmitted)
|
||||||
|
return;
|
||||||
|
auto canMessage = std::static_pointer_cast<icsneo::CANMessage>(message);
|
||||||
|
const OpenDevice* openDevice = nullptr;
|
||||||
|
for(const auto& dev : openDevices) {
|
||||||
|
if(dev.device->getSerial() == serial) {
|
||||||
|
openDevice = &dev;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!openDevice) {
|
||||||
|
LOG(LOG_ERR, "Dropping message, no open device\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo might throw
|
||||||
|
openDevice->interfaces.at(canMessage->network.getNetID())->addReceivedMessageToQueue(canMessage);
|
||||||
|
}));
|
||||||
|
|
||||||
|
LOGF(LOG_INFO, "%s connected\n", newDevice.device->describe().c_str());
|
||||||
|
failedToOpen.erase(std::remove_if(failedToOpen.begin(), failedToOpen.end(), [&serial](const std::string& s) -> bool {
|
||||||
|
return serial == s;
|
||||||
|
}), failedToOpen.end());
|
||||||
|
openDevices.push_back(std::move(newDevice));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close devices we don't see anymore
|
||||||
|
openDevices.erase(
|
||||||
|
std::remove_if(
|
||||||
|
openDevices.begin(),
|
||||||
|
openDevices.end(),
|
||||||
|
[&found](OpenDevice& openDev) -> bool {
|
||||||
|
bool stillHere = false;
|
||||||
|
for(const auto& dev : found) {
|
||||||
|
if(openDev == dev) {
|
||||||
|
stillHere = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(stillHere)
|
||||||
|
return false;
|
||||||
|
// The device is closed and the networks are removed by virtue of removing it from the array
|
||||||
|
LOGF(LOG_INFO, "%s disconnected\n", openDev.device->describe().c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
openDevices.end()
|
||||||
|
);
|
||||||
|
|
||||||
|
for(const auto& err : icsneo::GetErrors()) {
|
||||||
|
bool forErrorDevice = false;
|
||||||
|
for(const auto& dev : failedToOpen) {
|
||||||
|
if(err.isForDevice(dev)) {
|
||||||
|
forErrorDevice = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(forErrorDevice)
|
||||||
|
continue;
|
||||||
|
std::string description = err.describe();
|
||||||
|
description += "\n";
|
||||||
|
LOGF(LOG_INFO, "%s", description.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deviceSearchThread() {
|
||||||
|
while(!stopRunning) {
|
||||||
|
searchForDevices();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if(argc > 2) {
|
||||||
|
usage(argv[0]);
|
||||||
|
return EX_USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argc == 2) {
|
||||||
|
const std::string arg = argv[1];
|
||||||
|
if(arg == "-d" || arg == "--daemon") {
|
||||||
|
runningAsDaemon = true;
|
||||||
|
} else if(arg == "-h" || arg == "--help" || arg == "-?" || arg == "--usage") {
|
||||||
|
usage(argv[0]);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
} else if(arg == "--devices") {
|
||||||
|
header();
|
||||||
|
std::cout<< "\nSupported devices:" << std::endl;
|
||||||
|
for(auto& dev : icsneo::GetSupportedDevices())
|
||||||
|
std::cout << '\t' << dev << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
} else {
|
||||||
|
usage(argv[0]);
|
||||||
|
return EX_USAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header();
|
||||||
|
|
||||||
|
// Open the /dev/intrepid_netdevice kernel driver
|
||||||
|
if((driver = open("/dev/intrepid_netdevice", O_RDWR | O_NONBLOCK)) <= 0) {
|
||||||
|
std::cout << '\n'; // Still printing versions
|
||||||
|
LOGF(LOG_ERR, "Could not open the kernel driver\nError %d: %s\n", errno, strerror(errno));
|
||||||
|
switch(errno) {
|
||||||
|
case 2: // No such file or directory
|
||||||
|
LOG(LOG_ERR, "\nThis usually happens if the driver has not been loaded with insmod\n");
|
||||||
|
break;
|
||||||
|
case 5: // Input/output error
|
||||||
|
LOG(LOG_ERR, "\nThis usually happens if there is already a daemon running\n");
|
||||||
|
break;
|
||||||
|
case 13: // Permission denied
|
||||||
|
LOG(LOG_ERR, "\nThis usually happens if the daemon is not being run as root (use sudo)\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read out version from the driver
|
||||||
|
int version = 0;
|
||||||
|
if((version = ioctl(driver, SIOCGVERSION)) <= 0) {
|
||||||
|
std::cout << '\n'; // Still printing versions
|
||||||
|
LOGF(LOG_ERR, "Error reading the version from the kernel driver\nError %d: %s\n\nThis can happen if the driver is too old",
|
||||||
|
errno, strerror(errno));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
driverMajor = (version >> 16) & 0xFF;
|
||||||
|
driverMinor = (version >> 8) & 0xFF;
|
||||||
|
driverPatch = version & 0xFF;
|
||||||
|
if(driverMajor < 2) {
|
||||||
|
std::cout << '\n'; // Still printing versions
|
||||||
|
LOGF(LOG_ERR, "Error reading the version from the kernel driver\nError version %d.%d.%d\n", driverMajor, driverMinor, driverPatch);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::cout << "Driver v" << driverMajor << '.' << driverMinor << '.' << driverPatch << "\n\n";
|
||||||
|
if(driverMajor > 2) {
|
||||||
|
LOG(LOG_ERR, "This version of the usermode daemon is too old to work with this driver\nPlease ensure that both the usermode daemon and kernel driver are up to date\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ioctl(driver, SIOCGCLIENTVEROK, ICSNEO_SOCKETCAN_BUILD_VERINT) != 0) {
|
||||||
|
LOG(LOG_ERR, "The kernel driver reports an incompatibility with this version of the usermode daemon\nPlease ensure that both the usermode daemon and kernel driver are up to date\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read out other constants from the driver
|
||||||
|
if((maxInterfaces = ioctl(driver, SIOCGMAXIFACES)) <= 0) {
|
||||||
|
LOGF(LOG_ERR, "Error reading the maximum number of interfaces from the kernel driver\nError %d: %s\n", errno, strerror(errno));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if((sharedMemSize = ioctl(driver, SIOCGSHAREDMEMSIZE)) <= 0) {
|
||||||
|
LOGF(LOG_ERR, "Error reading the shared memory size from the kernel driver\nError %d: %s\n", errno, strerror(errno));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up shared memory
|
||||||
|
if((sharedMemory = mmap(nullptr, sharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, driver, 0)) == MAP_FAILED || sharedMemory == nullptr) {
|
||||||
|
LOG(LOG_ERR, "Error setting up shared memory with the kernel driver\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daemonize if necessary
|
||||||
|
if(runningAsDaemon) {
|
||||||
|
LOG(LOG_INFO, "The daemon will now continue to run in the background\n");
|
||||||
|
openlog("icsneo-socketcan", LOG_PID, LOG_LOCAL5);
|
||||||
|
if(daemon(0 /* change pwd to root */, 0 /* no stdout or stderr anymore */)) {
|
||||||
|
std::cerr << "Failed to spawn the daemon. Exiting...\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signal(SIGINT, terminateSignal);
|
||||||
|
LOG(LOG_INFO, "Waiting for connections...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread searchThread(deviceSearchThread);
|
||||||
|
|
||||||
|
while(!stopRunning) {
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(driver, &fds);
|
||||||
|
|
||||||
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = 1000 * 1000; // 1s
|
||||||
|
|
||||||
|
auto ret = select(driver + 1, &fds, NULL, NULL, &timeout);
|
||||||
|
if(ret == -1) {
|
||||||
|
// Fatal error
|
||||||
|
LOGF(LOG_ERR, "Error waiting for tx messages: %s\n", strerror(errno));
|
||||||
|
stopRunning = true;
|
||||||
|
break;
|
||||||
|
} else if(ret != 0) {
|
||||||
|
// Kernel says there are some new transmit messages waiting to go out.
|
||||||
|
// Call read() to find out which box they're in and how many
|
||||||
|
struct intrepid_pending_tx_info info;
|
||||||
|
ssize_t r = read(driver, &info, sizeof(info));
|
||||||
|
if (r == -1) {
|
||||||
|
LOGF(LOG_ERR, "Error waiting for tx messages: %s\n", strerror(errno));
|
||||||
|
stopRunning = true;
|
||||||
|
break;
|
||||||
|
} else if(r != sizeof(info)) {
|
||||||
|
LOGF(LOG_ERR, "Unexpected number of bytes read, expected %d got %d\n", (int)sizeof(info), (int)r);
|
||||||
|
stopRunning = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Send!
|
||||||
|
uint8_t* currentPosition = GET_TX_BOX(info.tx_box_index);
|
||||||
|
while(info.count--) {
|
||||||
|
neomessage_t* msg = reinterpret_cast<neomessage_t*>(currentPosition);
|
||||||
|
currentPosition += sizeof(neomessage_t);
|
||||||
|
msg->data = currentPosition;
|
||||||
|
currentPosition += msg->length;
|
||||||
|
bool sent = false;
|
||||||
|
for(auto& dev : openDevices) {
|
||||||
|
for(auto& netifPair : dev.interfaces) {
|
||||||
|
if(netifPair.second->getKernelHandle() != msg->netid)
|
||||||
|
continue;
|
||||||
|
msg->netid = static_cast<uint16_t>(netifPair.first);
|
||||||
|
auto tx = icsneo::CreateMessageFromNeoMessage(msg);
|
||||||
|
if(!dev.device->transmit(tx))
|
||||||
|
break;
|
||||||
|
sent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(sent)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!sent)
|
||||||
|
LOG(LOG_ERR, "Message dropped, could not find the device the kernel referenced\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchThread.join();
|
||||||
|
|
||||||
|
LOG(LOG_INFO, "\nExiting...\n");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 0b4ffdbaad66904926623a6ed3800effd1d9ff42
|
||||||
Loading…
Reference in New Issue