diff --git a/README.md b/README.md index d2e39f9..4a6b5ca 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This library has some unique features that might expose (performance) issues in This library supports awaiting a sent frame and waiting for the ACK on the CAN bus. This requires receiving these ACKs from the adapter, and matching them to the appropriate sent frame. This requires some level of hardware support that is not offered by all adapters/drivers. If this is not supported by the driver, an ACK will be simulated as soon as the frame is transmitted, but this can cause issues if precise timing is needed. - - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. This is tracked in [automotive#52](https://github.com/I-CAN-hack/automotive/issues/52) with the goal of handling the fake ACKs internally instead of by the Linux Kernel. + - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. To solve this we implement emulated ACKs ourself, instead of relying on the ACKs from the kernel. - comma.ai panda: The panda does not retry frames that are not ACKed, and drops them instead. This can cause panics in some internal parts of the library when frames are dropped. [panda#1922](https://github.com/commaai/panda/issues/1922) tracks this issue. The CAN-FD flag on a frame is also ignored, if the hardware is configured for CAN-FD all frames will be interpreted as FD regardless of the FD frame bit (r0 bit). - PCAN-USB: The Peak CAN adapters have two types of drivers. One built-in to the linux kernel (`peak_usb`), and an out-of-tree one (`pcan`) that can be [downloaded](https://www.peak-system.com/fileadmin/media/linux/index.htm) from Peak System's website. The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be inreased with `ifconfig can0 txqueuelen `. The out-of-tree driver is not recommended as it does not implement `IFF_ECHO`. - neoVI/ValueCAN: Use of Intrepid Control System's devices is not recommended due to issues in their SocketCAN driver. If many frames are transmitted simultaneously it will cause the whole system/kernel to hang. diff --git a/src/lib.rs b/src/lib.rs index a113fda..d07145a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! //! This library supports awaiting a sent frame and waiting for the ACK on the CAN bus. This requires receiving these ACKs from the adapter, and matching them to the appropriate sent frame. This requires some level of hardware support that is not offered by all adapters/drivers. If this is not supported by the driver, an ACK will be simulated as soon as the frame is transmitted, but this can cause issues if precise timing is needed. //! -//! - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. This is tracked in [automotive#52](https://github.com/I-CAN-hack/automotive/issues/52) with the goal of handling the fake ACKs internally instead of by the Linux Kernel. +//! - SocketCAN drivers without `IFF_ECHO`: This class of SocketCAN drivers has no hardware support for notifying the driver when a frame was ACKed. This is instead emulated by the [Linux kernel](https://github.com/torvalds/linux/blob/master/net/can/af_can.c#L256). Due to transmitted frames immediately being received again this can cause the receive queue to fill up if more than 476 (default RX queue size on most systems) are transmitted in one go. To solve this we implement emulated ACKs ourself, instead of relying on the ACKs from the kernel. //! - comma.ai panda: The panda does not retry frames that are not ACKed, and drops them instead. This can cause panics in some internal parts of the library when frames are dropped. [panda#1922](https://github.com/commaai/panda/issues/1922) tracks this issue. The CAN-FD flag on a frame is also ignored, if the hardware is configured for CAN-FD all frames will be interpreted as FD regardless of the FD frame bit (r0 bit). //! - PCAN-USB: The Peak CAN adapters have two types of drivers. One built-in to the linux kernel (`peak_usb`), and an out-of-tree one (`pcan`) that can be [downloaded](https://www.peak-system.com/fileadmin/media/linux/index.htm) from Peak System's website. The kernel driver properly implements `IFF_ECHO`, but has a rather small TX queue. This should not cause any issues, but it can be inreased with `ifconfig can0 txqueuelen `. The out-of-tree driver is not recommended as it does not implement `IFF_ECHO`. //! - neoVI/ValueCAN: Use of Intrepid Control System's devices is not recommended due to issues in their SocketCAN driver. If many frames are transmitted simultaneously it will cause the whole system/kernel to hang. diff --git a/src/socketcan/mod.rs b/src/socketcan/mod.rs index bf315e5..f3de066 100644 --- a/src/socketcan/mod.rs +++ b/src/socketcan/mod.rs @@ -1,6 +1,5 @@ //! This module provides a [`CanAdapter`] implementation for the [`socketcan`] crate. -use crate::can::AsyncCanAdapter; -use crate::can::CanAdapter; +use crate::can::{AsyncCanAdapter, CanAdapter, Frame}; use crate::Result; use socketcan::socket::Socket; @@ -14,10 +13,10 @@ const IFF_ECHO: u64 = 1 << 18; // include/uapi/linux/if.h /// Aadapter for a [`socketcan::CanFdSocket`]. pub struct SocketCan { socket: socketcan::CanFdSocket, - - /// If the IFF_ECHO flag is set on the interface, it will implement proper ACK logic. If the flag is not set, the kernel will emulate this. - #[allow(dead_code)] + /// If the IFF_ECHO flag is set on the interface, it will implement proper ACK logic. iff_echo: bool, + /// Queue used for fake loopback frames if IFF_ECHO is not set. + loopback_queue: VecDeque, } fn read_iff_echo(if_name: &str) -> Option { @@ -47,7 +46,6 @@ impl SocketCan { socket.set_nonblocking(true).unwrap(); socket.set_loopback(true).unwrap(); - socket.set_recv_own_msgs(true).unwrap(); // Attempt to increase the buffer receive size to 1MB socket.as_raw_socket().set_recv_buffer_size(1_000_000).ok(); @@ -65,16 +63,22 @@ impl SocketCan { } }; - if !iff_echo { - tracing::warn!("IFF_ECHO is not set on the interface. ACK support is emulated by the Linux Kernel."); + if iff_echo { + socket.set_recv_own_msgs(true).unwrap(); + } else { + tracing::warn!("IFF_ECHO is not set on the interface. ACK support is emulated."); } - Ok(SocketCan { socket, iff_echo }) + Ok(SocketCan { + socket, + iff_echo, + loopback_queue: VecDeque::new(), + }) } } impl CanAdapter for SocketCan { - fn send(&mut self, frames: &mut VecDeque) -> Result<()> { + fn send(&mut self, frames: &mut VecDeque) -> Result<()> { while let Some(frame) = frames.pop_front() { let to_send: socketcan::frame::CanAnyFrame = frame.clone().into(); @@ -82,20 +86,29 @@ impl CanAdapter for SocketCan { // Failed to send frame, push it back to the front of the queue for next send call frames.push_front(frame); break; + } else if !self.iff_echo { + // If IFF_ECHO is not set, we need to emulate the ACK logic. + let mut frame = frame.clone(); + frame.loopback = true; + self.loopback_queue.push_back(frame); } } Ok(()) } - fn recv(&mut self) -> Result> { + fn recv(&mut self) -> Result> { let mut frames = vec![]; + while let Ok((frame, meta)) = self.socket.read_frame_with_meta() { let mut frame: crate::can::Frame = frame.into(); frame.loopback = meta.loopback; frames.push(frame); } + // Add fake loopback frames to the receive queue + frames.extend(self.loopback_queue.drain(..)); + Ok(frames) } } diff --git a/tests/adapter_tests.rs b/tests/adapter_tests.rs index d0a8e5d..e5a20e9 100644 --- a/tests/adapter_tests.rs +++ b/tests/adapter_tests.rs @@ -5,7 +5,8 @@ use automotive::panda::Panda; use std::collections::VecDeque; use std::time::Duration; -static BULK_NUM_FRAMES: u64 = 0x100; +static BULK_NUM_FRAMES_SYNC: u64 = 0x100; +static BULK_NUM_FRAMES_ASYNC: u64 = 0x1000; static BULK_TIMEOUT_MS: u64 = 1000; /// Sends a large number of frames to a "blocking" adapter, and then reads back all sent messages. @@ -14,7 +15,7 @@ static BULK_TIMEOUT_MS: u64 = 1000; fn bulk_send_sync(adapter: &mut T) { let mut frames = vec![]; - for i in 0..BULK_NUM_FRAMES { + for i in 0..BULK_NUM_FRAMES_SYNC { frames.push(Frame::new(0, 0x123.into(), &i.to_be_bytes()).unwrap()); } @@ -48,7 +49,7 @@ fn bulk_send_sync(adapter: &mut T) { async fn bulk_send(adapter: &AsyncCanAdapter) { let mut frames = vec![]; - for i in 0..BULK_NUM_FRAMES { + for i in 0..BULK_NUM_FRAMES_ASYNC { frames.push(Frame::new(0, 0x123.into(), &i.to_be_bytes()).unwrap()); }