Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SocketCAN] Emulate ACK if IFF_ECHO not set #56

Merged
merged 3 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <size>`. 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.
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <size>`. 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.
Expand Down
35 changes: 24 additions & 11 deletions src/socketcan/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Frame>,
}

fn read_iff_echo(if_name: &str) -> Option<bool> {
Expand Down Expand Up @@ -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();
Expand All @@ -65,37 +63,52 @@ 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<crate::can::Frame>) -> Result<()> {
fn send(&mut self, frames: &mut VecDeque<Frame>) -> Result<()> {
while let Some(frame) = frames.pop_front() {
let to_send: socketcan::frame::CanAnyFrame = frame.clone().into();

if self.socket.write_frame(&to_send).is_err() {
// 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<Vec<crate::can::Frame>> {
fn recv(&mut self) -> Result<Vec<Frame>> {
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)
}
}
7 changes: 4 additions & 3 deletions tests/adapter_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -14,7 +15,7 @@ static BULK_TIMEOUT_MS: u64 = 1000;
fn bulk_send_sync<T: CanAdapter>(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());
}

Expand Down Expand Up @@ -48,7 +49,7 @@ fn bulk_send_sync<T: CanAdapter>(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());
}

Expand Down
Loading