From 8dca6395d0627da7a3f4cc7cf882b016541193b0 Mon Sep 17 00:00:00 2001 From: Tony Cao Date: Thu, 24 Sep 2026 15:34:25 +0800 Subject: [PATCH] feat(rfm): add rfm-ring mode for one-shot PCIe ring-continuity check --- src/main.rs | 14 +++++++++++++- src/rfm.rs | 18 ++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index be420c9..b68150d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; +use std::os::unix::io::AsRawFd; use el6695_rt::{bridge, regs, rt, stats}; use ethercrab::{ @@ -65,6 +66,8 @@ enum Mode { /// RFM bridge: EL6695 RX -> RFM -> remote echo -> RFM -> EL6695 TX. /// End-to-end CX<->x86 latency via reflective memory hop. RfmBridge, + /// One-shot hardware ring-continuity check (prints ring=OK/BROKEN, exits). + RfmRing, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -154,6 +157,7 @@ fn parse_args() -> Result { "rfm-ping" => Mode::RfmPing, "rfm-echo" => Mode::RfmEcho, "rfm-bridge" => Mode::RfmBridge, + "rfm-ring" => Mode::RfmRing, other => return Err(format!("unknown mode '{}'", other)), } } @@ -239,6 +243,7 @@ async fn run(cfg: &Config) -> Result<(), Box> { Mode::RfmPing => "rfm-ping", Mode::RfmEcho => "rfm-echo", Mode::RfmBridge => "rfm-bridge", + Mode::RfmRing => "rfm-ring", }, cfg.duration_sec, cfg.cycle_us, @@ -249,7 +254,14 @@ async fn run(cfg: &Config) -> Result<(), Box> { let pdu_storage = PDU_STORAGE.get_or_init(PduStorage::new); // ---- RFM-only modes: no EtherCAT bring-up needed ---- - if matches!(cfg.mode, Mode::RfmPing | Mode::RfmEcho) { + if matches!(cfg.mode, Mode::RfmPing | Mode::RfmEcho | Mode::RfmRing) { + if cfg.mode == Mode::RfmRing { + let rfm = el6695_rt::rfm::Rfm::open_path(cfg.rfm_dev.as_deref())?; + let fd = rfm.device_fd.as_raw_fd(); + let ok = el6695_rt::rfm::ring_cont(fd)?; + println!("ring={}", if ok { "OK" } else { "BROKEN" }); + std::process::exit(if ok { 0 } else { 1 }); + } return rfm_mode_main(cfg).await; } diff --git a/src/rfm.rs b/src/rfm.rs index c3b38b9..3001c3d 100644 --- a/src/rfm.rs +++ b/src/rfm.rs @@ -65,8 +65,7 @@ fn ioctl(fd: i32, req: u32, arg: usize) -> io::Result { } pub struct Rfm { - #[allow(dead_code)] - device_fd: File, + pub device_fd: File, /// BAR0 (registers) mapping, if present. pub regs: Option<&'static mut [u8]>, /// BAR2 reflective memory mapping. @@ -326,6 +325,21 @@ pub fn now_ns() -> u64 { ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64 } +/// Hardware ring-continuity check: IOCTL_RFM2G_CHECK_RING_CONT sends a packet +/// around the ring (to our own node id) and waits (up to ~500ms in the +/// driver) for it to come back. Returns Ok(true) if the ring is complete +/// (fiber OK + all nodes alive), Ok(false) if broken. NOTE: the driver +/// busy-waits, so call only during a suspected outage, never per-cycle. +pub fn ring_cont(fd: i32) -> io::Result { + // _IOR(RFM2G_MAGIC=0xeb, 52, RFM2G_UINT8) + const IOCTL_RFM2G_CHECK_RING_CONT: u32 = ior(0xeb, 52, 1); + let mut out: u8 = 0; + match ioctl(fd, IOCTL_RFM2G_CHECK_RING_CONT, &mut out as *mut u8 as usize) { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } +} + // ---- device-memory-safe bulk access ---- // // The reflective memory is mapped non-cached (pgprot_noncached /