From f2719ce16fd75ef25ee61247e96c48b36f5c3177 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Thu, 2 May 2019 19:23:12 -0400 Subject: [PATCH] Initial commit of Version 2.0.0, with CAN FD support --- .gitignore | 12 +- .gitmodules | 3 + CMakeLists.txt | 31 ++ LICENSE | 2 +- Makefile | 16 - README.md | 36 +- icsscand.c | 723 --------------------------------------- src/buildinfo.h.template | 12 + src/main.cpp | 511 +++++++++++++++++++++++++++ third-party/libicsneo | 1 + 10 files changed, 587 insertions(+), 760 deletions(-) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt delete mode 100644 Makefile delete mode 100644 icsscand.c create mode 100644 src/buildinfo.h.template create mode 100644 src/main.cpp create mode 160000 third-party/libicsneo diff --git a/.gitignore b/.gitignore index f494626..8586cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -*.a -*.o -icsneo_sample -*.log -icsscand -.vscode/ +build/ +build*/ +.DS_Store +Thumbs.db +.vscode +*.bak diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b951b5f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third-party/libicsneo"] + path = third-party/libicsneo + url = https://github.com/intrepidcs/libicsneo.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a1c8f1d --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/LICENSE b/LICENSE index c3d2377..70ab820 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 Intrepid Control Systems, Inc. +Copyright (c) 2016-2019 Intrepid Control Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile deleted file mode 100644 index 852032a..0000000 --- a/Makefile +++ /dev/null @@ -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 diff --git a/README.md b/README.md index 398c517..bb23a62 100644 --- a/README.md +++ b/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. -``` -$ make -``` +1. Build and load the kernel module follwowing the instructions in [intrepid-socketcan-kernel-module](https://github.com/intrepidcs/intrepid-socketcan-kernel-module). -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. -``` -$ sudo ./icsscand -D -``` +On Ubuntu or other Debian-based systems, run `sudo apt install git cmake gcc libusb-1.0-0-dev build-essential`. -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` -``` -$ sudo ifconfig ics0can0 up -``` +5. Make a build directory, `mkdir build` + +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. \ No newline at end of file diff --git a/icsscand.c b/icsscand.c deleted file mode 100644 index df924d4..0000000 --- a/icsscand.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -} diff --git a/src/buildinfo.h.template b/src/buildinfo.h.template new file mode 100644 index 0000000..8703ed4 --- /dev/null +++ b/src/buildinfo.h.template @@ -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 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8b62624 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,511 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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(sharedMemory) + (RX_BOX_SIZE * DEVICE_INDEX)) +#define GET_TX_BOX(INDEX) (reinterpret_cast(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 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& msg) { + auto neomessage = icsneo::CreateNeoMessage(msg); + size_t bytesNeeded = sizeof(neomessage) + neomessage.length; + std::lock_guard 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& openDevice) : device(openDevice) {} + std::shared_ptr device; + std::map> interfaces; + + bool operator ==(const std::shared_ptr& other) const { + return device->getSerial() == other->getSerial(); + } +}; + +template +class Lazy { +public: + Lazy(std::function 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 fn; +}; + +std::vector openDevices; +std::vector 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 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 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(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 message) { + if(message->transmitted) + return; + auto canMessage = std::static_pointer_cast(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(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(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; +} \ No newline at end of file diff --git a/third-party/libicsneo b/third-party/libicsneo new file mode 160000 index 0000000..0b4ffdb --- /dev/null +++ b/third-party/libicsneo @@ -0,0 +1 @@ +Subproject commit 0b4ffdbaad66904926623a6ed3800effd1d9ff42