Author SHA1 Message Date
Paul Hollinsky f7e4af48c2 Use libicsneo with checksum failure logging code
Checksum failures will log to stderr
2019-11-06 10:25:18 -05:00
8 changed files with 107 additions and 695 deletions
-1
View File
@@ -1,4 +1,3 @@
[submodule "third-party/libicsneo"] [submodule "third-party/libicsneo"]
path = third-party/libicsneo path = third-party/libicsneo
url = https://github.com/intrepidcs/libicsneo.git url = https://github.com/intrepidcs/libicsneo.git
branch = master
-26
View File
@@ -1,29 +1,3 @@
v3.1.2
Update libicsneo
Update copyright
Add scan-interval-ms parameter (Jorge Alejandro <jorge.a.alejandro@gmail.com>)
v3.1.1
Update libicsneo
v3.1.0
Update libicsneo
Update copyright
v3.0.0
Added Functionality for Ethernet
v2.1.0
Update copyright date
Update to libicsneo v0.3.0
Report transmits down to the kernel for echo
Allow filtering devices by serial number
v2.0.3
Update copyright date
Update to libicsneo v0.2.0
Ensure lock is held when accessing open devices
v2.0.2 v2.0.2
Use libicsneo v0.1.2 to resolve LEDs not indicating device status Use libicsneo v0.1.2 to resolve LEDs not indicating device status
+2 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.2) cmake_minimum_required(VERSION 3.2)
project(libicsneo-socketcan-daemon VERSION 3.2.0) project(libicsneo-socketcan-daemon VERSION 2.0.2)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 11)
include(GNUInstallDirs) include(GNUInstallDirs)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2016-2025 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
+5 -5
View File
@@ -1,18 +1,18 @@
Version 3.1.2 Version 2.0.2
This is the usermode daemon for the Intrepid Control Systems SocketCAN support. This daemon requires that ```intrepid.ko``` is loaded on your system. 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). 1. Build and load the kernel module follwowing the instructions in [intrepid-socketcan-kernel-module](https://github.com/intrepidcs/intrepid-socketcan-kernel-module).
2. Install the dependencies needed. These are CMake 3.2+, GCC 4.8+, git, libusb-1.0-0-dev, and libpcap. 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 libpcap-dev build-essential`. On Ubuntu or other Debian-based systems, run `sudo apt install git cmake gcc libusb-1.0-0-dev build-essential`.
3. Clone this repository recursively by running `git clone --recursive https://github.com/intrepidcs/icsscand.git` 3. Clone this repository recursively by running `git clone --recursive https://github.com/intrepidcs/icsscand.git`
4. Switch into the cloned directory, `cd icsscand` 4. Switch into the cloned directory, `cd icsscand`
5. Make a build directory and switch into it, `mkdir build && cd build` 5. Make a build directory and switch into it, `mkdir -p build && cd build`
6. Invoke CMake, `cmake .. -DCMAKE_BUILD_TYPE=Release` 6. Invoke CMake, `cmake .. -DCMAKE_BUILD_TYPE=Release`
@@ -24,6 +24,6 @@ On Ubuntu or other Debian-based systems, run `sudo apt install git cmake gcc lib
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. 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 can0 up`, replacing `can0` with whichever interface you'd like to enable 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. 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.
-95
View File
@@ -1,95 +0,0 @@
# RPC
icsscand contains an RPC endpoint that can be used to control the libicsneo
instance within the daemon. To enable the RPC channel, add `--fifo-path <path>`
during launching. The provided path will be used to open a named FIFO, the path
should not exist prior to launching icsscand.
## lock_networks
Invokes `icsneo::Device::lockNetworks()` with the provided space separated
arguments and writes the results of the call to the provided client FIFO. This
call in non-blocking, with the return type indicating that the request was sent
to the device (it does not indicate that the network was successfully locked).
Use `get_network_mutex_status` to check the status of the lock request.
### Usage
- Request: `<api version> lock_networks <device serial> <netid integers, comma separated> <priority> <ttl, ms> <lock type> <client fifo path>`
- Response: `<0 or 1, with 0 indicating an error and 1 indicating success>`
- On error, check the icsscand logs for more detailed information
### Example
- API version: 1
- Service serial: ON0123
- Networks: `ETHERNET_01` & `ETHERNET_02`
- See `icsneo::Network::NetID` for values
- Priority: 2
- TTL (in ms): 1s
- Opened client FIFO path: `/tmp/tmp.AYkIg0b3np`
- This FIFO must be opened prior to invoking the RPC
`1 lock_networks ON0123 93,520 2 1000 /tmp/tmp.AYkIg0b3np`
## unlock_networks
Invokes `icsneo::Device::unlockNetworks()` with the provided space separated
arguments and writes the results of the call to the provided client FIFO.
### Usage
- Request: `<api version> unlock_networks <device serial> <netid integers, comma separated> <client fifo path>`
- Response: `<0 or 1, with 0 indicating an error and 1 indicating success>`
- On error, check the icsscand logs for more detailed information
### Example
- API version: 1
- Service serial: ON0123
- Networks: `ETHERNET_01` & `ETHERNET_02`
- See `icsneo::Network::NetID` for values
- Opened client FIFO path: `/tmp/tmp.KXY8SCXtux`
- This FIFO must be opened prior to invoking the RPC
`1 lock_networks ON0123 93,520 /tmp/tmp.KXY8SCXtux`
## get_network_mutex_status
Invokes `icsneo::Device::getNetworkMutexStatus()` with the provided space separated
arguments and writes the results of the call to the provided client FIFO.
### Usage
- Request: `<api version> get_network_mutex_status <device serial> <netid integer> <client fifo path>`
- Response (one of):
- `0`, error, check the icsscand logs for more detailed information
- `1 <owner client id> <type> <priority> <ttl, ms> <netid integers, comma separated> <event>`
### Example
- API version: 1
- Service serial: ON0123
- Network: `ETHERNET_01`
- See `icsneo::Network::NetID` for values
- Opened client FIFO path: `/tmp/tmp.4huaosZjhA`
- This FIFO must be opened prior to invoking the RPC
`1 get_network_mutex_status ON0123 93 /tmp/tmp.4huaosZjhA`
## get_serials
Returns a space separated list of devices serial numbers that isscand has open.
### Usage
- Request: `<api version> get_serials <client fifo path>`
- Response: `<0 or 1, with 0 indicating an error and 1 indicating success> [serial]...`
### Example
- API version: 1
- Opened client FIFO path: `/tmp/tmp.bBcUh5obRK`
- This FIFO must be opened prior to invoking the RPC
`1 get_serials /tmp/tmp.bBcUh5obRK`
+64 -530
View File
@@ -17,13 +17,9 @@
#include <fcntl.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <linux/if.h> #include <linux/if.h>
#include <sys/eventfd.h>
#include <poll.h>
#include <icsneo/icsneocpp.h> #include <icsneo/icsneocpp.h>
#include <icsneo/communication/message/neomessage.h> #include <icsneo/communication/message/neomessage.h>
#include <icsneo/communication/message/message.h>
#include <icsneo/communication/network.h>
#include <icsneo/communication/message/callback/canmessagecallback.h> #include <icsneo/communication/message/callback/canmessagecallback.h>
#include <generated/buildinfo.h> #include <generated/buildinfo.h>
@@ -32,24 +28,19 @@
#define LOGF(LVL, MSG, ...) do{if(runningAsDaemon) syslog(LVL, MSG, __VA_ARGS__); \ #define LOGF(LVL, MSG, ...) do{if(runningAsDaemon) syslog(LVL, MSG, __VA_ARGS__); \
else fprintf(stderr, MSG, __VA_ARGS__);}while(0) else fprintf(stderr, MSG, __VA_ARGS__);}while(0)
#define SIOCSADDCANIF 0x3001 #define SIOCSADDIF 0x3001
#define SIOCSADDETHIF 0x3002 #define SIOCSREMOVEIF 0x3002
#define SIOCSREMOVECANIF 0x3003 #define SIOCGSHAREDMEMSIZE 0x3003
#define SIOCSREMOVEETHIF 0x3004 #define SIOCSMSGSWRITTEN 0x3004
#define SIOCGSHAREDMEMSIZE 0x3005 #define SIOCGMAXIFACES 0x3005
#define SIOCSMSGSWRITTEN 0x3006 #define SIOCGVERSION 0x3006
#define SIOCGMAXIFACES 0x3007 #define SIOCGCLIENTVEROK 0x3007
#define SIOCGVERSION 0x3008
#define SIOCGCLIENTVEROK 0x3009
#define SIOCSBAUDRATE 0x300A
#define RX_BOX_SIZE (sharedMemSize / (maxInterfaces * 2)) #define RX_BOX_SIZE (sharedMemSize / (maxInterfaces * 2))
#define TX_BOX_SIZE (sharedMemSize / 4) #define TX_BOX_SIZE (sharedMemSize / 4)
#define GET_RX_BOX(DEVICE_INDEX) (reinterpret_cast<uint8_t*>(sharedMemory) + (RX_BOX_SIZE * DEVICE_INDEX)) #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)) #define GET_TX_BOX(INDEX) (reinterpret_cast<uint8_t*>(sharedMemory) + (sharedMemSize / 2) + (INDEX * TX_BOX_SIZE))
#define DEFAULT_SCAN_INTERVAL_MS 1000
bool runningAsDaemon = false; bool runningAsDaemon = false;
int driver = 0; // /dev/intrepid_netdevice int driver = 0; // /dev/intrepid_netdevice
int driverMajor = 0; int driverMajor = 0;
@@ -58,9 +49,6 @@ int driverPatch = 0;
int maxInterfaces = 0; // From driver int maxInterfaces = 0; // From driver
int sharedMemSize = 0; // From driver int sharedMemSize = 0; // From driver
void* sharedMemory = nullptr; void* sharedMemory = nullptr;
std::string serialFilter;
int scanIntervalMs = DEFAULT_SCAN_INTERVAL_MS;
std::string fifoPath;
std::atomic<bool> stopRunning(false); std::atomic<bool> stopRunning(false);
@@ -72,16 +60,10 @@ struct intrepid_pending_tx_info {
class NetworkInterface { class NetworkInterface {
public: public:
NetworkInterface(const std::string& desiredName, icsneo::Network::Type device) : type(device), name(desiredName) { NetworkInterface(const std::string& desiredName) : name(desiredName) {
char ifname[IFALIASZ + 1] = {0}; char ifname[IFALIASZ + 1] = {0};
strncpy(ifname, name.c_str(), IFALIASZ); strncpy(ifname, name.c_str(), IFALIASZ);
kernelHandle = ioctl(driver, SIOCSADDIF, ifname);
if(device == icsneo::Network::Type::CAN) {
kernelHandle = ioctl(driver, SIOCSADDCANIF, ifname); // this will call the intrepid_dev_ioctl()
} else if(device == icsneo::Network::Type::Ethernet) {
kernelHandle = ioctl(driver, SIOCSADDETHIF, ifname); // this will call the intrepid_dev_ioctl()
}
if(openedSuccessfully()) { if(openedSuccessfully()) {
rxBox = GET_RX_BOX(kernelHandle); rxBox = GET_RX_BOX(kernelHandle);
rxBoxCurrentPosition = rxBox; rxBoxCurrentPosition = rxBox;
@@ -89,36 +71,12 @@ public:
} }
~NetworkInterface() { ~NetworkInterface() {
if(openedSuccessfully()) { if(openedSuccessfully()) {
int res = 0;
LOGF(LOG_DEBUG, "Removing device %s with handle %d\n", name.c_str(), kernelHandle); LOGF(LOG_DEBUG, "Removing device %s with handle %d\n", name.c_str(), kernelHandle);
if(type == icsneo::Network::Type::CAN) { int res = ioctl(driver, SIOCSREMOVEIF, kernelHandle);
res = ioctl(driver, SIOCSREMOVECANIF, kernelHandle);
} else if(type == icsneo::Network::Type::Ethernet) {
res = ioctl(driver, SIOCSREMOVEETHIF, kernelHandle);
}
LOGF(LOG_DEBUG, "Removed device %s with handle %d, result %d\n", name.c_str(), kernelHandle, res); LOGF(LOG_DEBUG, "Removed device %s with handle %d, result %d\n", name.c_str(), kernelHandle, res);
} else } else
LOG(LOG_DEBUG, "Removing interface which was not opened successfully\n"); LOG(LOG_DEBUG, "Removing interface which was not opened successfully\n");
} }
bool reportBaudrates(int64_t baudrate, int64_t fd_baudrate) {
struct baudrate_info {
int handle;
int64_t baudrates[2];
} info;
info.handle = kernelHandle;
info.baudrates[0] = baudrate;
/* set fd baudrate to zero if equal to baudrate
* this will disable fd mode in kernel */
info.baudrates[1] = (fd_baudrate==baudrate)?0:fd_baudrate;
if (ioctl(driver, SIOCSBAUDRATE, &info) != 0) {
LOGF(LOG_INFO, "Unable to set baudrate for device %s\n", name.c_str());
return false;
}
return true;
}
NetworkInterface(const NetworkInterface&) = delete; NetworkInterface(const NetworkInterface&) = delete;
NetworkInterface& operator =(const NetworkInterface&) = delete; NetworkInterface& operator =(const NetworkInterface&) = delete;
@@ -127,19 +85,8 @@ public:
const std::string& getName() const { return name; } const std::string& getName() const { return name; }
uint8_t* getRxBox() { return rxBox; } uint8_t* getRxBox() { return rxBox; }
const uint8_t* getRxBox() const { return rxBox; } const uint8_t* getRxBox() const { return rxBox; }
void addReceivedMessageToQueue(const std::shared_ptr<icsneo::Message>& msg) {
template<typename T> auto neomessage = icsneo::CreateNeoMessage(msg);
void addReceivedMessageToQueue(const std::shared_ptr<icsneo::Frame>& msg) {
const auto neomessageGeneric = icsneo::CreateNeoMessage(msg);
if(neomessageGeneric.messageType != neomessagetype_t(icsneo::Message::Type::Frame)) {
LOG(LOG_DEBUG, "could not create a neomessage_can_t\n");
return;
}
if(msg->network.getType() == icsneo::Network::Type::CAN || msg->network.getType() == icsneo::Network::Type::Ethernet) {
const auto& neomessage = *reinterpret_cast<const T*>(&neomessageGeneric);
size_t bytesNeeded = sizeof(neomessage) + neomessage.length; size_t bytesNeeded = sizeof(neomessage) + neomessage.length;
std::lock_guard<std::mutex> lg(rxBoxLock); std::lock_guard<std::mutex> lg(rxBoxLock);
if(ssize_t((rxBoxCurrentPosition - rxBox) + bytesNeeded) > RX_BOX_SIZE) { if(ssize_t((rxBoxCurrentPosition - rxBox) + bytesNeeded) > RX_BOX_SIZE) {
@@ -159,10 +106,8 @@ public:
rxBoxCurrentPosition = rxBox; rxBoxCurrentPosition = rxBox;
rxBoxMessageCount = 0; rxBoxMessageCount = 0;
} }
}
private: private:
icsneo::Network::Type type;
std::string name; std::string name;
int kernelHandle = -1; int kernelHandle = -1;
std::mutex rxBoxLock; std::mutex rxBoxLock;
@@ -206,327 +151,6 @@ std::vector<OpenDevice> openDevices;
std::vector<std::string /* serial */> failedToOpen; std::vector<std::string /* serial */> failedToOpen;
std::mutex openDevicesMutex; std::mutex openDevicesMutex;
class RPC {
public:
RPC() {
if(::mkfifo(fifoPath.c_str(), 0777) == -1) {
throw std::runtime_error("Error creating RPC FIFO: " + std::string(strerror(errno)));
return;
}
::chmod(fifoPath.c_str(), 0777);
fifo = ::open(fifoPath.c_str(), O_RDWR);
if(fifo == -1) {
throw std::runtime_error("Error opening RPC FIFO: " + std::string(strerror(errno)));
return;
}
interrupt = ::eventfd(0, 0);
if(interrupt == -1) {
throw std::runtime_error("Error opening eventfd: " + std::string(strerror(errno)));
return;
}
thread = std::thread(&RPC::loop, this);
}
~RPC() {
uint64_t u = 1;
::write(interrupt, &u, sizeof(uint64_t));
thread.join();
::close(fifo);
::close(interrupt);
::unlink(fifoPath.c_str());
}
private:
int fifo;
int interrupt;
std::thread thread;
void loop() {
static char buffer[2048];
struct pollfd fds[2] = {};
fds[0].fd = fifo;
fds[0].events = POLLIN;
fds[1].fd = interrupt;
fds[1].events = POLLIN;
while (!stopRunning) {
if(::poll(fds, 2, -1) == -1) {
LOGF(LOG_WARNING, "Error polling for RPC: %s\n", strerror(errno));
break;
}
if(fds[1].revents & POLLIN) {
break;
}
const auto size = ::read(fifo, buffer, sizeof(buffer));
if (size == -1) {
LOGF(LOG_WARNING, "Error reading from RPC FIFO: %s\n", strerror(errno));
break;
}
const auto args = split(std::string(buffer, size));
if(args.size() < 2) {
continue;
}
const auto& apiVersion = args[0];
const auto& command = args[1];
if(apiVersion != "1") {
LOGF(LOG_WARNING, "Invalid API version, expected '1' got '%s'\n", apiVersion.c_str());
continue;
}
if(command == "lock_networks") {
lockNetworks(args);
} else if(command == "unlock_networks") {
unlockNetworks(args);
} else if(command == "get_network_mutex_status") {
getNetworkMutexStatus(args);
} else if(command == "get_serials") {
getSerials(args);
} else {
LOGF(LOG_WARNING, "Unknown command '%s'\n", command.c_str());
}
}
}
bool parseNetid(const std::string& arg, icsneo::Network::NetID& netid) {
try {
netid = static_cast<icsneo::Network::NetID>(std::stoi(arg));
return true;
} catch (const std::exception& e) {
LOGF(LOG_WARNING, "Invalid netid '%s': %s\n", arg.c_str(), e.what());
return false;
}
}
bool parseNetids(const std::string& arg, std::set<icsneo::Network::NetID>& netids) {
for(auto&& str : split(arg, ',')) {
icsneo::Network::NetID nid;
if(!parseNetid(str, nid)) {
return false;
}
netids.emplace(nid);
}
return true;
}
template<typename T>
std::string optionalArg(const std::optional<T>& opt) {
return opt ? std::to_string((uint64_t)*opt) : "-1";
}
bool fifoWrite(const std::string& message, const std::string& fifoPath) {
int fifo = ::open(fifoPath.c_str(), O_WRONLY);
if(fifo == -1) {
return false;
}
if(::write(fifo, message.c_str(), message.size()) == -1) {
::close(fifo);
return false;
}
::close(fifo);
return true;
}
std::vector<std::string> split(const std::string& str, char delim = ' ') {
if(str.empty())
return {};
std::vector<std::string> ret;
size_t tail = 0;
size_t head = 0;
while(head < str.size()) {
if(str[head] == delim) {
ret.emplace_back(&str[tail], head - tail);
tail = head + 1;
}
++head;
}
ret.emplace_back(&str[tail], head - tail);
return ret;
}
std::string join(const std::vector<std::string>& parts, char delim = ' ') {
if(parts.empty())
return "";
std::string ret = parts[0];
for(size_t i = 1; i < parts.size(); i++) {
ret += delim + parts[i];
}
return ret;
}
void lockNetworks(const std::vector<std::string>& args) {
if(args.size() != 8) {
LOGF(LOG_WARNING, "lock_networks requires 8 arguments, got %zu\n", args.size());
return;
}
const auto& serial = args[2];
const auto& clientFifoPath = args[7];
std::set<icsneo::Network::NetID> netids;
if(!parseNetids(args[3], netids)) {
fifoWrite("0", clientFifoPath);
return;
}
uint32_t priority;
try {
priority = std::stoul(args[4]);
} catch (const std::exception& e) {
LOGF(LOG_WARNING, "Invalid priority '%s': %s\n", args[4].c_str(), e.what());
fifoWrite("0", clientFifoPath);
return;
}
uint32_t ttl;
try {
ttl = std::stoul(args[5]);
} catch (const std::exception& e) {
LOGF(LOG_WARNING, "Invalid TTL '%s': %s\n", args[5].c_str(), e.what());
fifoWrite("0", clientFifoPath);
return;
}
icsneo::NetworkMutexType type;
try {
type = static_cast<icsneo::NetworkMutexType>(std::stoi(args[6]));
} catch (const std::exception& e) {
LOGF(LOG_WARNING, "Invalid mutex type '%s': %s\n", args[6].c_str(), e.what());
fifoWrite("0", clientFifoPath);
return;
}
std::lock_guard<std::mutex> lg(openDevicesMutex);
std::shared_ptr<icsneo::Device> device;
for(const auto& dev : openDevices) {
if(dev.device->getSerial() == serial) {
device = dev.device;
break;
}
}
if(!device) {
LOGF(LOG_WARNING, "Device with serial '%s' not found\n", serial.c_str());
fifoWrite("0", clientFifoPath);
return;
}
const auto locked = device->lockNetworks(netids, priority, ttl, type, [serial](std::shared_ptr<icsneo::Message> msg) -> void {
auto mutexMsg = std::dynamic_pointer_cast<icsneo::NetworkMutexMessage>(msg);
if(!mutexMsg) {
LOG(LOG_WARNING, "Received a message for the network mutex callback which was not a NetworkMutexMessage\n");
return;
}
LOGF(LOG_INFO, "Received network mutex event for device %s\n", serial.c_str());
});
if(!locked) {
LOGF(LOG_WARNING, "Failed to lock networks for device '%s'\n", icsneo::GetLastError().describe().c_str());
fifoWrite("0", clientFifoPath);
return;
}
device->removeMessageCallback(*locked); // client will explicitly poll status
fifoWrite("1", clientFifoPath);
}
void unlockNetworks(const std::vector<std::string>& args) {
if(args.size() != 5) {
LOGF(LOG_WARNING, "unlock_networks requires 5 arguments, got %zu\n", args.size());
return;
}
const auto& serial = args[2];
const auto& clientFifoPath = args[4];
std::set<icsneo::Network::NetID> netids;
if(!parseNetids(args[3], netids)) {
fifoWrite("0", clientFifoPath);
return;
}
std::lock_guard<std::mutex> lg(openDevicesMutex);
std::shared_ptr<icsneo::Device> device;
for(const auto& dev : openDevices) {
if(dev.device->getSerial() == serial) {
device = dev.device;
break;
}
}
if(!device) {
LOGF(LOG_WARNING, "Device with serial '%s' not found\n", serial.c_str());
fifoWrite("0", clientFifoPath);
return;
}
const auto success = device->unlockNetworks(netids);
if(!success) {
LOGF(LOG_WARNING, "Failed to unlock networks for device '%s': %s\n", serial.c_str(), icsneo::GetLastError().describe().c_str());
fifoWrite("0", clientFifoPath);
return;
}
fifoWrite("1", clientFifoPath);
}
void getNetworkMutexStatus(const std::vector<std::string>& args) {
if(args.size() != 5) {
LOGF(LOG_WARNING, "get_network_mutex_status requires 5 arguments, got %zu\n", args.size());
return;
}
const auto& serial = args[2];
const auto& clientFifoPath = args[4];
icsneo::Network::NetID netid;
if(!parseNetid(args[3], netid)) {
fifoWrite("0", clientFifoPath);
return;
}
std::lock_guard<std::mutex> lg(openDevicesMutex);
std::shared_ptr<icsneo::Device> device;
for(const auto& dev : openDevices) {
if(dev.device->getSerial() == serial) {
device = dev.device;
break;
}
}
if(!device) {
LOGF(LOG_WARNING, "Device with serial '%s' not found\n", serial.c_str());
fifoWrite("0", clientFifoPath);
return;
}
const auto status = device->getNetworkMutexStatus(netid);
if(!status) {
LOGF(LOG_WARNING, "Failed to get network mutex status for device '%s': %s\n", serial.c_str(), icsneo::GetLastError().describe().c_str());
fifoWrite("0", clientFifoPath);
return;
}
const std::string id = optionalArg(status->owner_id);
const std::string type = optionalArg(status->type);
const std::string priority = optionalArg(status->priority);
const std::string ttl = optionalArg(status->ttlMs);
std::vector<std::string> networkStrs;
for(const auto& net : status->networks) {
networkStrs.push_back(std::to_string((neonetid_t)net));
}
const std::string networks = join(networkStrs, ',');
const std::string event = optionalArg(status->event);
const std::string response = join({"1", id, type, priority, ttl, networks, event});
fifoWrite(response, clientFifoPath);
}
void getSerials(const std::vector<std::string>& args) {
if(args.size() != 3) {
LOGF(LOG_WARNING, "get_serials requires 3 arguments, got %zu\n", args.size());
return;
}
const auto& clientFifoPath = args[2];
std::string response = "1";
std::lock_guard<std::mutex> lg(openDevicesMutex);
for(const auto& dev : openDevices) {
response += ' ' + dev.device->getSerial();
}
fifoWrite(response, clientFifoPath);
}
};
std::string& replaceInPlace(std::string& str, char o, const std::string& n) { std::string& replaceInPlace(std::string& str, char o, const std::string& n) {
size_t start_pos = 0; size_t start_pos = 0;
const size_t new_len = n.length(); const size_t new_len = n.length();
@@ -546,7 +170,7 @@ std::string sanitizeInterfaceName(std::string str) {
void header() { void header() {
std::cout << "The libicsneo SocketCAN Usermode Daemon\n"; std::cout << "The libicsneo SocketCAN Usermode Daemon\n";
std::cout << "Copyright Intrepid Control Systems, Inc. 2025\n\n"; std::cout << "Copyright Intrepid Control Systems, Inc. 2019\n\n";
std::cout << "Daemon v"; std::cout << "Daemon v";
std::cout << (int)ICSNEO_SOCKETCAN_BUILD_MAJOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_MINOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_PATCH; std::cout << (int)ICSNEO_SOCKETCAN_BUILD_MAJOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_MINOR << '.' << (int)ICSNEO_SOCKETCAN_BUILD_PATCH;
if(ICSNEO_SOCKETCAN_BUILD_METADATA[0] != '\0') if(ICSNEO_SOCKETCAN_BUILD_METADATA[0] != '\0')
@@ -563,15 +187,12 @@ void header() {
void usage(std::string executableName) { void usage(std::string executableName) {
std::cerr << "The libicsneo SocketCAN Usermode Daemon\n"; std::cerr << "The libicsneo SocketCAN Usermode Daemon\n";
std::cerr << "Copyright 2019-2025 Intrepid Control Systems, Inc.\n\n"; std::cerr << "Copyright Intrepid Control Systems, Inc. 2019\n\n";
std::cerr << "Usage: " << executableName << " [option]\n\n"; std::cerr << "Usage: " << executableName << " [option]\n\n";
std::cerr << "Options:\n"; std::cerr << "Options:\n";
std::cerr << "\t-d, --daemon\t\t\tRun as a daemon in the background\n"; std::cerr << "\t-d, --daemon\t\tRun as a daemon in the background\n";
std::cerr << "\t-h, -?, --help, --usage\t\t\tShow this help page\n"; std::cerr << "\t-h, -?, --help, --usage\t\tShow this help page\n";
std::cerr << "\t --devices\t\t\tList supported devices\n"; std::cerr << "\t --devices\t\tList supported devices\n";
std::cerr << "\t --filter <serial>\t\tOnly connect to devices with serial\n\t\t\t\t\t\tnumbers starting with this filter\n";
std::cerr << "\t --scan-interval-ms <interval>\tDevice scan interval in ms\n\t\t\t\t\t\tIf 0, only a single scan is performed\n";
std::cerr << "\t --fifo-path <path>\t\tPath to RPC FIFO for libicsneo control\n";
} }
void terminateSignal(int signal) { void terminateSignal(int signal) {
@@ -579,8 +200,8 @@ void terminateSignal(int signal) {
} }
void searchForDevices() { void searchForDevices() {
std::lock_guard<std::mutex> lg(openDevicesMutex);
auto found = icsneo::FindAllDevices(); auto found = icsneo::FindAllDevices();
std::lock_guard<std::mutex> lg(openDevicesMutex);
// Open devices we have not seen before // Open devices we have not seen before
for(auto& dev : found) { for(auto& dev : found) {
@@ -594,48 +215,42 @@ void searchForDevices() {
if(alreadyOpen) if(alreadyOpen)
continue; continue;
const std::string serial = dev->getSerial();
// If we have a serial filter, make sure our serial starts with the given filter
if(!serialFilter.empty() && serial.rfind(serialFilter, 0) != 0)
continue;
// Now open the device // Now open the device
OpenDevice newDevice(dev); OpenDevice newDevice(dev);
const std::string serial = newDevice.device->getSerial();
Lazy<bool> firstTimeFailedToOpen([&serial]() { Lazy<bool> firstTimeFailedToOpen([&serial]() {
return std::find(failedToOpen.begin(), failedToOpen.end(), serial) == failedToOpen.end(); return std::find(failedToOpen.begin(), failedToOpen.end(), serial) == failedToOpen.end();
}); });
if(!newDevice.device->open() || !newDevice.device->goOnline()) { if(!newDevice.device->open() || !newDevice.device->goOnline()) {
if(firstTimeFailedToOpen) { if(firstTimeFailedToOpen) {
const std::string err = icsneo::GetLastError().describe(); icsneo::APIEvent err = icsneo::GetLastError();
LOGF(LOG_INFO, "%s failed to connect. Will keep trying...\n%s\n", newDevice.device->describe().c_str(), err.c_str()); 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); failedToOpen.push_back(serial);
} }
continue; continue;
} }
// Get the supported networks // Get the supported CAN networks
auto supportedNetworks = newDevice.device->getSupportedRXNetworks(); auto supportedNetworks = newDevice.device->getSupportedRXNetworks();
supportedNetworks.erase(std::remove_if(supportedNetworks.begin(), supportedNetworks.end(), [](const icsneo::Network& net) -> bool { supportedNetworks.erase(std::remove_if(supportedNetworks.begin(), supportedNetworks.end(), [](const icsneo::Network& net) -> bool {
return net.getType() != icsneo::Network::Type::CAN && net.getType() != icsneo::Network::Type::Ethernet; return net.getType() != icsneo::Network::Type::CAN;// Only want CAN networks
}), supportedNetworks.end()); }), supportedNetworks.end());
if(supportedNetworks.empty()) { if(supportedNetworks.empty()) {
if(firstTimeFailedToOpen) { if(firstTimeFailedToOpen) {
LOGF(LOG_INFO, "%s has no supported networks\n", newDevice.device->describe().c_str()); LOGF(LOG_INFO, "%s has no supported CAN networks\n", newDevice.device->describe().c_str());
failedToOpen.push_back(serial); failedToOpen.push_back(serial);
} }
continue; continue;
} }
// Create a network interface for each network // Create a network interface for each CAN network
for(const auto& net : supportedNetworks) { for(const auto& net : supportedNetworks) {
std::stringstream ss; std::stringstream ss;
ss << sanitizeInterfaceName(icsneo::Network::GetNetIDString(net.getNetID())) << "_" << serial; ss << sanitizeInterfaceName(icsneo::Network::GetNetIDString(net.getNetID())) << "_" << serial;
std::string interfaceName(ss.str()); std::string interfaceName(ss.str());
if(firstTimeFailedToOpen) if(firstTimeFailedToOpen)
LOGF(LOG_INFO, "Creating network interface %s\n", interfaceName.c_str()); LOGF(LOG_INFO, "Creating network interface %s\n", interfaceName.c_str());
newDevice.interfaces[net.getNetID()] = std::make_shared<NetworkInterface>(interfaceName);
newDevice.interfaces[net.getNetID()] = std::make_shared<NetworkInterface>(interfaceName, net.getType());
LOGF(LOG_INFO, "Created network interface %s\n", interfaceName.c_str()); LOGF(LOG_INFO, "Created network interface %s\n", interfaceName.c_str());
} }
bool failedToCreateNetworkInterfaces = false; bool failedToCreateNetworkInterfaces = false;
@@ -652,35 +267,27 @@ void searchForDevices() {
} }
continue; continue;
} }
if (driverMinor > 0) {
for(const auto& net : supportedNetworks) {
if (net.getType() != icsneo::Network::Type::CAN)
continue;
newDevice.interfaces[net.getNetID()]->reportBaudrates(
newDevice.device->settings->getBaudrateFor(net.getNetID()),
newDevice.device->settings->getFDBaudrateFor(net.getNetID())
);
}
}
// Create rx listener // Create rx listener
for(auto&& interface : newDevice.interfaces) { newDevice.device->addMessageCallback(icsneo::CANMessageCallback([serial](std::shared_ptr<icsneo::Message> message) {
newDevice.device->addMessageCallback(std::make_shared<icsneo::MessageCallback>([interface = interface.second](std::shared_ptr<icsneo::Message> message) { if(message->transmitted)
const auto frame = std::static_pointer_cast<icsneo::Frame>(message); return;
const auto messageType = frame->network.getType(); auto canMessage = std::static_pointer_cast<icsneo::CANMessage>(message);
if(frame->type != icsneo::Message::Type::Frame) { const OpenDevice* openDevice = nullptr;
LOG(LOG_ERR, "Dropping message: received invalid message type, expected RawMessage\n"); 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; return;
} }
if(messageType == icsneo::Network::Type::CAN) {
interface->addReceivedMessageToQueue<neomessage_can_t>(frame); // todo might throw
} else if(messageType == icsneo::Network::Type::Ethernet) { openDevice->interfaces.at(canMessage->network.getNetID())->addReceivedMessageToQueue(canMessage);
interface->addReceivedMessageToQueue<neomessage_eth_t>(frame); }));
} else {
LOG(LOG_ERR, "Dropping message, only CAN and Ethernet are currently supported\n");
}
}, std::make_shared<icsneo::MessageFilter>(interface.first)));
}
LOGF(LOG_INFO, "%s connected\n", newDevice.device->describe().c_str()); 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 { failedToOpen.erase(std::remove_if(failedToOpen.begin(), failedToOpen.end(), [&serial](const std::string& s) -> bool {
@@ -731,16 +338,18 @@ void searchForDevices() {
void deviceSearchThread() { void deviceSearchThread() {
while(!stopRunning) { while(!stopRunning) {
searchForDevices(); searchForDevices();
if(scanIntervalMs == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(scanIntervalMs));
} }
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
for(int i = 1; i != argc; i++) { if(argc > 2) {
const std::string arg = argv[i]; usage(argv[0]);
return EX_USAGE;
}
if(argc == 2) {
const std::string arg = argv[1];
if(arg == "-d" || arg == "--daemon") { if(arg == "-d" || arg == "--daemon") {
runningAsDaemon = true; runningAsDaemon = true;
} else if(arg == "-h" || arg == "--help" || arg == "-?" || arg == "--usage") { } else if(arg == "-h" || arg == "--help" || arg == "-?" || arg == "--usage") {
@@ -752,26 +361,6 @@ int main(int argc, char** argv) {
for(auto& dev : icsneo::GetSupportedDevices()) for(auto& dev : icsneo::GetSupportedDevices())
std::cout << '\t' << dev << std::endl; std::cout << '\t' << dev << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} else if(arg == "--filter" && i + 1 <= argc) {
serialFilter = argv[++i];
transform(serialFilter.begin(), serialFilter.end(), serialFilter.begin(), ::toupper);
} else if(arg == "--scan-interval-ms" && i + 1 <= argc) {
try {
scanIntervalMs = std::stoi(argv[++i]);
} catch (const std::invalid_argument& e) {
std::cerr << "Invalid input for scan-interval-ms\n";
return EX_USAGE;
} catch (const std::out_of_range& e) {
std::cerr << "Out of range input for scan-interval-ms\n";
return EX_USAGE;
}
if(scanIntervalMs < 0) {
std::cerr << "Invalid input for scan-interval-ms\n";
return EX_USAGE;
}
} else if(arg == "--fifo-path" && i + 1 <= argc) {
fifoPath = argv[++i];
} else { } else {
usage(argv[0]); usage(argv[0]);
return EX_USAGE; return EX_USAGE;
@@ -815,7 +404,7 @@ int main(int argc, char** argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::cout << "Driver v" << driverMajor << '.' << driverMinor << '.' << driverPatch << "\n\n"; std::cout << "Driver v" << driverMajor << '.' << driverMinor << '.' << driverPatch << "\n\n";
if(driverMajor > 3) { 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"); 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; return EXIT_FAILURE;
} }
@@ -854,16 +443,6 @@ int main(int argc, char** argv) {
LOG(LOG_INFO, "Waiting for connections...\n"); LOG(LOG_INFO, "Waiting for connections...\n");
} }
std::unique_ptr<RPC> rpc;
if(!fifoPath.empty()) {
try {
rpc = std::make_unique<RPC>();
} catch (const std::exception& e) {
LOGF(LOG_ERR, "Failed to set up RPC: %s\n", e.what());
return EXIT_FAILURE;
}
}
std::thread searchThread(deviceSearchThread); std::thread searchThread(deviceSearchThread);
while(!stopRunning) { while(!stopRunning) {
@@ -893,76 +472,31 @@ int main(int argc, char** argv) {
LOGF(LOG_ERR, "Unexpected number of bytes read, expected %d got %d\n", (int)sizeof(info), (int)r); LOGF(LOG_ERR, "Unexpected number of bytes read, expected %d got %d\n", (int)sizeof(info), (int)r);
stopRunning = true; stopRunning = true;
break; break;
} else if (info.tx_box_index < 0) {
// Baudrate changed in kernel
int dev_idx = -(info.tx_box_index + 1);
LOGF(LOG_INFO, "Baudrate change, device %d, baudrate %d fd_baudrate %ld\n",
dev_idx, info.count, info.bytes);
/* fd baudrate is zero if fd mode is disabled in kernel
* set fd baudrate equal to baudrate */
if (info.bytes == 0) {
info.bytes = info.count;
}
for(auto& dev : openDevices) {
for(auto& netifPair : dev.interfaces) {
auto netid = netifPair.first;
if(netifPair.second->getKernelHandle() != dev_idx)
continue;
if (! dev.device->settings->setBaudrateFor(netid, info.count) ) {
LOGF(LOG_ERR, "Unable to set baudrate for device %s\n",
netifPair.second->getName().c_str());
} else if (! dev.device->settings->setFDBaudrateFor(netid, info.bytes)) {
LOGF(LOG_ERR, "Unable to set fd baudrate for device %s\n",
netifPair.second->getName().c_str());
} else if (! dev.device->settings->apply()) {
LOGF(LOG_ERR, "Unable to apply settings for device %s\n",
netifPair.second->getName().c_str());
}
}
}
} else { } else {
// Send! // Send!
uint8_t* currentPosition = GET_TX_BOX(info.tx_box_index); uint8_t* currentPosition = GET_TX_BOX(info.tx_box_index);
while(info.count--) { while(info.count--) {
neomessage_frame_t* msg = reinterpret_cast<neomessage_frame_t*>(currentPosition); neomessage_t* msg = reinterpret_cast<neomessage_t*>(currentPosition);
currentPosition += sizeof(neomessage_frame_t); currentPosition += sizeof(neomessage_t);
msg->data = currentPosition; msg->data = currentPosition;
currentPosition += msg->length; currentPosition += msg->length;
bool sent = false;
if(msg->type != neonettype_t(icsneo::Network::Type::CAN) && msg->type != neonettype_t(icsneo::Network::Type::Ethernet)) { for(auto& dev : openDevices) {
LOG(LOG_ERR, "Message dropped, kernel sent a non-CAN/Ethernet message\n");
continue;
}
const auto transmit = [&] {
std::lock_guard<std::mutex> lg(openDevicesMutex);
for(auto it = openDevices.begin(); it != openDevices.end(); ++it) {
auto& dev = *it;
for(auto& netifPair : dev.interfaces) { for(auto& netifPair : dev.interfaces) {
if(netifPair.second->getKernelHandle() != msg->netid) if(netifPair.second->getKernelHandle() != msg->netid)
continue; continue;
if(!dev.device->isOpen() || !dev.device->isOnline() || dev.device->isDisconnected()) {
LOGF(LOG_ERR, "Message dropped, %s is not open and online\n", dev.device->getSerial().c_str());
openDevices.erase(it);
return;
}
msg->netid = static_cast<uint16_t>(netifPair.first); msg->netid = static_cast<uint16_t>(netifPair.first);
auto txMsg = icsneo::CreateMessageFromNeoMessage(reinterpret_cast<neomessage_t*>(msg)); auto tx = icsneo::CreateMessageFromNeoMessage(msg);
auto tx = std::dynamic_pointer_cast<icsneo::Frame>(txMsg); if(!dev.device->transmit(tx))
if(!tx) { break;
LOG(LOG_ERR, "Message dropped, invalid transmit message\n"); sent = true;
return; break;
}
if(!dev.device->transmit(tx)) {
LOGF(LOG_ERR, "Message dropped, unable to transmit: %s\n", icsneo::GetLastError().describe().c_str());
return;
}
return;
} }
if(sent)
break;
} }
if(!sent)
LOG(LOG_ERR, "Message dropped, could not find the device the kernel referenced\n"); LOG(LOG_ERR, "Message dropped, could not find the device the kernel referenced\n");
};
transmit();
} }
} }
} }