2025-07-19 14:17:31 +02:00

303 lines
10 KiB
C++

/// This software is distributed under the terms of the MIT License.
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
/// Authors: Pavel Kirienko <pavel.kirienko@zubax.com>, Tom De Rybel <tom.derybel@robocow.be>
// This is needed to enable the necessary declarations in sys/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "socketcan.h"
#ifdef __linux__
# include <linux/can.h>
# include <linux/can/raw.h>
# include <net/if.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
#else
# error "Unsupported OS -- feel free to add support for your OS here. " \
"Zephyr and NuttX are known to support the SocketCAN API."
#endif
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define KILO 1000L
#define MEGA (KILO * KILO)
static int16_t getNegatedErrno(void)
{
const int out = -abs(errno);
if (out < 0)
{
if (out >= INT16_MIN)
{
return (int16_t) out;
}
}
else
{
assert(false); // Requested an error when errno is zero?
}
return INT16_MIN;
}
static int16_t doPoll(const SocketCANFD fd, const int16_t mask, const CanardMicrosecond timeout_usec)
{
struct pollfd fds;
memset(&fds, 0, sizeof(fds));
fds.fd = fd;
fds.events = mask;
struct timespec ts;
ts.tv_sec = (long) (timeout_usec / (CanardMicrosecond) MEGA);
ts.tv_nsec = (long) (timeout_usec % (CanardMicrosecond) MEGA) * KILO;
const int poll_result = ppoll(&fds, 1, &ts, NULL);
if (poll_result < 0)
{
return getNegatedErrno();
}
if (poll_result == 0)
{
return 0;
}
if (((uint32_t) fds.revents & (uint32_t) mask) == 0)
{
return -EIO;
}
return 1;
}
SocketCANFD socketcanOpen(const char* const iface_name, const size_t can_mtu)
{
const size_t iface_name_size = strlen(iface_name) + 1;
if (iface_name_size > IFNAMSIZ)
{
return -ENAMETOOLONG;
}
const int fd = socket(PF_CAN, SOCK_RAW | SOCK_NONBLOCK, CAN_RAW); // NOLINT
bool ok = fd >= 0;
if (ok)
{
struct ifreq ifr;
(void) memset(&ifr, 0, sizeof(ifr));
(void) memcpy(ifr.ifr_name, iface_name, iface_name_size);
ok = 0 == ioctl(fd, SIOCGIFINDEX, &ifr);
if (ok)
{
struct sockaddr_can addr;
(void) memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
ok = 0 == bind(fd, (struct sockaddr*) &addr, sizeof(addr));
}
}
// Enable CAN FD if required.
if (ok && (can_mtu > CAN_MAX_DLEN))
{
const int en = 1;
ok = 0 == setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &en, sizeof(en));
}
// Enable timestamping.
if (ok)
{
const int en = 1;
ok = 0 == setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &en, sizeof(en));
}
// Enable outgoing-frame loop-back.
if (ok)
{
const int en = 1;
ok = 0 == setsockopt(fd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &en, sizeof(en));
}
if (ok)
{
return fd;
}
(void) close(fd);
return getNegatedErrno();
}
int16_t socketcanPush(const SocketCANFD fd, const struct CanardFrame* const frame, const CanardMicrosecond timeout_usec)
{
if ((frame == NULL) || (frame->payload.data == NULL) || (frame->payload.size > UINT8_MAX))
{
return -EINVAL;
}
const int16_t poll_result = doPoll(fd, POLLOUT, timeout_usec);
if (poll_result > 0)
{
// We use the CAN FD struct regardless of whether the CAN FD socket option is set.
// Per the user manual, this is acceptable because they are binary compatible.
struct canfd_frame cfd;
(void) memset(&cfd, 0, sizeof(cfd));
cfd.can_id = frame->extended_can_id | CAN_EFF_FLAG;
cfd.len = (uint8_t) frame->payload.size;
// We set the bit rate switch on the assumption that it will be ignored by non-CAN-FD-capable hardware.
cfd.flags = CANFD_BRS;
(void) memcpy(cfd.data, frame->payload.data, frame->payload.size);
// If the payload is small, use the smaller MTU for compatibility with non-FD sockets.
// This way, if the user attempts to transmit a CAN FD frame without having the CAN FD socket option enabled,
// an error will be triggered here. This is convenient -- we can handle both FD and Classic CAN uniformly.
const size_t mtu = (frame->payload.size > CAN_MAX_DLEN) ? CANFD_MTU : CAN_MTU;
if (write(fd, &cfd, mtu) < 0)
{
return getNegatedErrno();
}
}
return poll_result;
}
int16_t socketcanPop(const SocketCANFD fd,
struct CanardFrame* const out_frame,
CanardMicrosecond* const out_timestamp_usec,
const size_t payload_buffer_size,
void* const payload_buffer,
const CanardMicrosecond timeout_usec,
bool* const loopback)
{
if ((out_frame == NULL) || (payload_buffer == NULL))
{
return -EINVAL;
}
const int16_t poll_result = doPoll(fd, POLLIN, timeout_usec);
if (poll_result > 0)
{
// Initialize the message header scatter/gather array. It is to hold a single CAN FD frame struct.
// We use the CAN FD struct regardless of whether the CAN FD socket option is set.
// Per the user manual, this is acceptable because they are binary compatible.
struct canfd_frame sockcan_frame; // CAN FD frame storage.
memset(&sockcan_frame, 0, sizeof sockcan_frame);
struct iovec iov = {
// Scatter/gather array items struct.
.iov_base = &sockcan_frame, // Starting address.
.iov_len = sizeof(sockcan_frame) // Number of bytes to transfer.
};
// Determine the size of the ancillary data and zero-initialize the buffer for it.
// We require space for both the receive message header (implied in CMSG_SPACE) and the time stamp.
// The ancillary data buffer is wrapped in a union to ensure it is suitably aligned.
// See the cmsg(3) man page (release 5.08 dated 2020-06-09, or later) for details.
union
{
uint8_t buf[CMSG_SPACE(sizeof(struct timeval))];
struct cmsghdr align;
} control;
(void) memset(control.buf, 0, sizeof(control.buf));
// Initialize the message header used by recvmsg.
struct msghdr msg; // Message header struct.
memset(&msg, 0, sizeof msg);
msg.msg_iov = &iov; // Scatter/gather array.
msg.msg_iovlen = 1; // Number of elements in the scatter/gather array.
msg.msg_control = control.buf; // Ancillary data.
msg.msg_controllen = sizeof(control.buf); // Ancillary data buffer length.
// Non-blocking receive messages from the socket and validate.
const ssize_t read_size = recvmsg(fd, &msg, MSG_DONTWAIT);
if (read_size < 0)
{
return getNegatedErrno(); // Error occurred -- return the negated error code.
}
if ((read_size != CAN_MTU) && (read_size != CANFD_MTU))
{
return -EIO;
}
if (sockcan_frame.len > payload_buffer_size)
{
return -EFBIG;
}
const bool valid = ((sockcan_frame.can_id & CAN_EFF_FLAG) != 0) && // Extended frame
((sockcan_frame.can_id & CAN_ERR_FLAG) == 0) && // Not RTR frame
((sockcan_frame.can_id & CAN_RTR_FLAG) == 0); // Not error frame
if (!valid)
{
return 0; // Not an extended data frame -- drop silently and return early.
}
// Handle the loopback frame logic.
const bool loopback_frame = ((uint32_t) msg.msg_flags & (uint32_t) MSG_CONFIRM) != 0;
if (loopback == NULL && loopback_frame)
{
return 0; // The loopback pointer is NULL and this is a loopback frame -- drop silently and return early.
}
if (loopback != NULL)
{
*loopback = loopback_frame;
}
// Obtain the CAN frame time stamp from the kernel.
// This time stamp is from the CLOCK_REALTIME kernel source.
if (NULL != out_timestamp_usec)
{
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
struct timeval tv;
memset(&tv, 0, sizeof tv);
assert(cmsg != NULL);
if ((cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SO_TIMESTAMP))
{
(void) memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); // Copy to avoid alignment problems
assert(tv.tv_sec >= 0 && tv.tv_usec >= 0);
}
else
{
assert(0);
return -EIO;
}
(void) memset(out_frame, 0, sizeof(struct CanardFrame));
*out_timestamp_usec = (CanardMicrosecond) (((uint64_t) tv.tv_sec * MEGA) + (uint64_t) tv.tv_usec);
}
out_frame->extended_can_id = sockcan_frame.can_id & CAN_EFF_MASK;
out_frame->payload.size = sockcan_frame.len;
out_frame->payload.data = payload_buffer;
(void) memcpy(payload_buffer, &sockcan_frame.data[0], sockcan_frame.len);
}
return poll_result;
}
int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const struct CanardFilter* const configs)
{
if (configs == NULL)
{
return -EINVAL;
}
if (num_configs > CAN_RAW_FILTER_MAX)
{
return -EFBIG;
}
struct can_filter cfs[CAN_RAW_FILTER_MAX];
for (size_t i = 0; i < num_configs; i++)
{
cfs[i].can_id = (configs[i].extended_can_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
cfs[i].can_mask = (configs[i].extended_mask & CAN_EFF_MASK) | CAN_EFF_FLAG | CAN_RTR_FLAG;
}
const int ret =
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, cfs, (socklen_t) (sizeof(struct can_filter) * num_configs));
return (ret < 0) ? getNegatedErrno() : 0;
}