/// This software is distributed under the terms of the MIT License. /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT /// Authors: Pavel Kirienko , Tom De Rybel // This is needed to enable the necessary declarations in sys/ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include "socketcan.h" #ifdef __linux__ # include # include # include # include # include #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 #include #include #include #include #include #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; }