From 10ce4bd6624c10362d93ca860d68b85f98c15ee9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 00:21:44 +0200 Subject: [PATCH] Adds SocketCan support to the openmrn hub. This enables running the hub on a BeagleBone or a Raspberry Pi with a CAN HAT. All other options are still usable, including repeating traffic between a SocketCAN port and a TCP client, or a SocketCAN port and a USB port via GridConnect. --- applications/hub/main.cxx | 28 +++++++++- src/openlcb/SimpleStack.cxx | 42 +++------------ src/utils/SocketCan.cxx | 101 ++++++++++++++++++++++++++++++++++++ src/utils/SocketCan.hxx | 49 +++++++++++++++++ src/utils/sources | 1 + 5 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 src/utils/SocketCan.cxx create mode 100644 src/utils/SocketCan.hxx diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 599710baf..7da8bee03 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -42,6 +42,8 @@ #include "os/os.h" #include "utils/constants.hxx" #include "utils/Hub.hxx" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" #include "utils/GcTcpHub.hxx" #include "utils/ClientConnection.hxx" #include "executor/Executor.hxx" @@ -64,6 +66,7 @@ bool timestamped = false; bool export_mdns = false; const char* mdns_name = "openmrn_hub"; bool printpackets = false; +const char* socketcan_port = nullptr; void usage(const char *e) { @@ -100,7 +103,7 @@ void usage(const char *e) void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:")) >= 0) + while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:s:")) >= 0) { switch (opt) { @@ -132,6 +135,11 @@ void parse_args(int argc, char *argv[]) case 'l': printpackets = true; break; +#if defined(__linux__) + case 's': + socketcan_port = optarg; + break; +#endif default: fprintf(stderr, "Unknown option %c\n", opt); usage(argv[0]); @@ -160,11 +168,27 @@ int appl_main(int argc, char *argv[]) void mdns_client_start(); void mdns_publish(const char *name, uint16_t port); - if (export_mdns) { + if (export_mdns) + { mdns_client_start(); mdns_publish(mdns_name, port); } #endif +#if defined(__linux__) + if (socketcan_port) + { + int s = socketcan_open(socketcan_port, 1); + if (s >= 0) + { + new HubDeviceSelect(&can_hub0, s); + fprintf(stderr, "Opened SocketCan %s: fd %d\n", socketcan_port, s); + } + else + { + fprintf(stderr, "Failed to open SocketCan %s.\n", socketcan_port); + } + } +#endif if (upstream_host) { diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index f13728e56..7dbe9fed4 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -41,11 +41,6 @@ #include #include /* tc* functions */ #endif -#if defined(__linux__) -#include "utils/HubDeviceSelect.hxx" -#include -#include -#endif #include #include @@ -57,6 +52,8 @@ #include "openlcb/EventHandler.hxx" #include "openlcb/NodeInitializeFlow.hxx" #include "openlcb/SimpleNodeInfo.hxx" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" namespace openlcb { @@ -419,35 +416,12 @@ void SimpleCanStackBase::add_gridconnect_tty( void SimpleCanStackBase::add_socketcan_port_select( const char *device, int loopback) { - int s; - struct sockaddr_can addr; - struct ifreq ifr; - - s = socket(PF_CAN, SOCK_RAW, CAN_RAW); - - // Set the blocking limit to the minimum allowed, typically 1024 in Linux - int sndbuf = 0; - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); - - // turn on/off loopback - setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); - - // setup error notifications - can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | - CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | - CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; - setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); - strcpy(ifr.ifr_name, device); - - ::ioctl(s, SIOCGIFINDEX, &ifr); - - addr.can_family = AF_CAN; - addr.can_ifindex = ifr.ifr_ifindex; - - bind(s, (struct sockaddr *)&addr, sizeof(addr)); - - auto *port = new HubDeviceSelect(can_hub(), s); - additionalComponents_.emplace_back(port); + int s = socketcan_open(device, loopback); + if (s >= 0) + { + auto *port = new HubDeviceSelect(can_hub(), s); + additionalComponents_.emplace_back(port); + } } #endif extern Pool *const __attribute__((__weak__)) g_incoming_datagram_allocator = diff --git a/src/utils/SocketCan.cxx b/src/utils/SocketCan.cxx new file mode 100644 index 000000000..a3df5d487 --- /dev/null +++ b/src/utils/SocketCan.cxx @@ -0,0 +1,101 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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. + * + * \file SocketCan.cxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#include "utils/SocketCan.hxx" +#include "can_frame.h" +#include + +#if defined(__linux__) + +#include +#include +#include +#include +#include + +/// This macro executes an OS call, and if it returns negative result, then +/// prints the errno to stderr, and terminates the current function with -1 +/// return value. +/// @param where textual description of what function was called +/// (e.g. "socket") +/// @param x... the function call. +#define ERRNOLOG(where, x...) \ + do \ + { \ + if ((x) < 0) \ + { \ + perror(where); \ + return -1; \ + } \ + } while (0) + +int socketcan_open(const char *device, int loopback) +{ + int s; + struct sockaddr_can addr; + struct ifreq ifr; + + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + ERRNOLOG("socket", s); + + // Set the blocking limit to the minimum allowed, typically 1024 in Linux + int sndbuf = 0; + ERRNOLOG("setsockopt(sndbuf)", + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))); + + // turn on/off loopback + ERRNOLOG("setsockopt(loopback)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback))); + + // setup error notifications + can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | + CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | + CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; + ERRNOLOG("setsockopt(filter)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask))); + strcpy(ifr.ifr_name, device); + + ERRNOLOG("interface set", ::ioctl(s, SIOCGIFINDEX, &ifr)); + + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + + ERRNOLOG("bind", bind(s, (struct sockaddr *)&addr, sizeof(addr))); + + return s; +} + +#endif diff --git a/src/utils/SocketCan.hxx b/src/utils/SocketCan.hxx new file mode 100644 index 000000000..23a724141 --- /dev/null +++ b/src/utils/SocketCan.hxx @@ -0,0 +1,49 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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. + * + * \file SocketCan.hxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#ifndef _UTILS_SOCKETCAN_HXX_ +#define _UTILS_SOCKETCAN_HXX_ + +#if defined(__linux__) + +/// Opens a SocketCan socket. +/// @param device the name of the CAN device, e.g. can0 +/// @param loopback 1 to enable loopback locally to other open references, +/// 0 to disable loopback locally to other open references. +/// @return an open socket file descriptor, or -1 if there was an error. +int socketcan_open(const char* device, int loopback); + +#endif + +#endif // _UTILS_SOCKETCAN_HXX_ diff --git a/src/utils/sources b/src/utils/sources index 4dc7e26c7..7140a788a 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -22,6 +22,7 @@ CXXSRCS += \ Queue.cxx \ JSHubPort.cxx \ ReflashBootloader.cxx \ + SocketCan.cxx \ constants.cxx \ gc_format.cxx \ logging.cxx \