1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! SocketCAN 硬件时间戳验证程序
//!
//! 此程序用于验证 Linux SocketCAN 是否支持硬件时间戳(SO_TIMESTAMPING)。
//! 在集成到 SocketCanAdapter 之前,先使用此程序验证环境。
//!
//! 使用方法:
//! ```bash
//! # 在另一个终端发送帧
//! cansend vcan0 123#DEADBEEF
//!
//! # 运行验证程序
//! cargo run --example timestamp_verification --target x86_64-unknown-linux-gnu
//! ```
#[cfg(not(target_os = "linux"))]
fn main() {
// 初始化日志
piper_sdk::init_logger!();
eprintln!("该示例仅支持 Linux(SocketCAN)。");
}
#[cfg(target_os = "linux")]
use nix::sys::socket::{ControlMessageOwned, MsgFlags, SockaddrStorage, recvmsg};
#[cfg(target_os = "linux")]
use socketcan::{CanSocket, Socket};
#[cfg(target_os = "linux")]
use std::io::IoSliceMut;
#[cfg(target_os = "linux")]
use std::mem;
#[cfg(target_os = "linux")]
use std::os::unix::io::AsRawFd;
#[cfg(target_os = "linux")]
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
// 初始化日志
piper_sdk::init_logger!();
println!("SocketCAN 硬件时间戳验证程序");
println!("正在打开 vcan0 接口...");
// 1. 打开 CAN Socket
let socket = CanSocket::open("vcan0").map_err(|e| format!("Failed to open vcan0: {}", e))?;
let fd = socket.as_raw_fd();
println!("✓ vcan0 已打开 (fd: {})", fd);
// 2. 启用 SO_TIMESTAMPING
let flags = libc::SOF_TIMESTAMPING_RX_HARDWARE
| libc::SOF_TIMESTAMPING_RAW_HARDWARE
| libc::SOF_TIMESTAMPING_RX_SOFTWARE
| libc::SOF_TIMESTAMPING_SOFTWARE;
let result = unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_TIMESTAMPING,
&flags as *const _ as *const libc::c_void,
mem::size_of::<u32>() as libc::socklen_t,
)
};
if result < 0 {
return Err(format!(
"Failed to set SO_TIMESTAMPING: {}",
std::io::Error::last_os_error()
)
.into());
}
println!("✓ SO_TIMESTAMPING 已启用");
// 3. 准备缓冲区
// CAN 2.0 帧最大 16 字节(使用 size_of 确保跨平台正确性)
const CAN_FRAME_LEN: usize = std::mem::size_of::<libc::can_frame>();
let mut frame_buf = [0u8; CAN_FRAME_LEN];
let mut cmsg_buf = [0u8; 1024]; // CMSG 缓冲区
println!("\n等待接收 CAN 帧(按 Ctrl+C 退出)...");
println!("提示:在另一个终端运行: cansend vcan0 123#DEADBEEF\n");
// 4. 接收帧(阻塞)
loop {
// 构建 IO 向量(必须在循环内,因为需要可变引用)
let mut iov = [IoSliceMut::new(&mut frame_buf)];
let msg = recvmsg::<SockaddrStorage>(fd, &mut iov, Some(&mut cmsg_buf), MsgFlags::empty())
.map_err(|e| format!("recvmsg failed: {}", e))?;
println!("=== 接收到 CAN 帧 ===");
println!("数据长度: {} 字节", msg.bytes);
// 5. 打印时间戳(验证是否能正确提取)
// 注意:nix 0.30 中使用 ScmTimestampsns,Timestamps 结构体有 system/hw_trans/hw_raw 字段
// msg.cmsgs() 返回 Result<CmsgIterator>,需要先处理错误
let mut found_timestamp = false;
match msg.cmsgs() {
Ok(cmsgs) => {
for cmsg in cmsgs {
if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg {
found_timestamp = true;
println!("\n时间戳信息 (SCM_TIMESTAMPING):");
println!(" Timestamps 结构体字段: system, hw_trans, hw_raw");
// timestamps.system - Software (System Time) - 对应 timestamps[0]
let sw_ts = timestamps.system;
let sw_us =
sw_ts.tv_sec() as u64 * 1_000_000 + sw_ts.tv_nsec() as u64 / 1000;
println!(
" system (Software): sec={}, nsec={} ({} us)",
sw_ts.tv_sec(),
sw_ts.tv_nsec(),
sw_us
);
// timestamps.hw_trans - Hardware-Transformed (System Time) - 对应 timestamps[1]
let hw_trans_ts = timestamps.hw_trans;
let hw_trans_us = if hw_trans_ts.tv_sec() != 0 || hw_trans_ts.tv_nsec() != 0
{
hw_trans_ts.tv_sec() as u64 * 1_000_000
+ hw_trans_ts.tv_nsec() as u64 / 1000
} else {
0
};
println!(
" hw_trans (HW-Trans): sec={}, nsec={} ({} us)",
hw_trans_ts.tv_sec(),
hw_trans_ts.tv_nsec(),
hw_trans_us
);
// timestamps.hw_raw - Hardware-Raw (Device Clock) - 对应 timestamps[2]
let hw_raw_ts = timestamps.hw_raw;
let hw_raw_us = if hw_raw_ts.tv_sec() != 0 || hw_raw_ts.tv_nsec() != 0 {
hw_raw_ts.tv_sec() as u64 * 1_000_000
+ hw_raw_ts.tv_nsec() as u64 / 1000
} else {
0
};
println!(
" hw_raw (HW-Raw): sec={}, nsec={} ({} us)",
hw_raw_ts.tv_sec(),
hw_raw_ts.tv_nsec(),
hw_raw_us
);
// 验证结果
if sw_us > 0 {
println!("\n✓ 软件时间戳可用 (system)");
}
if hw_trans_us > 0 {
println!("✓ 硬件时间戳可用 (hw_trans - Transformed)");
} else {
println!(" (硬件时间戳不可用,这是正常的,因为 vcan0 是虚拟接口)");
}
if hw_raw_us > 0 {
println!("✓ 原始硬件时间戳可用 (hw_raw - Raw)");
}
}
}
},
Err(e) => {
println!("⚠ 警告:CMSG 迭代失败: {}", e);
},
}
if !found_timestamp {
println!("⚠ 警告:未找到 SCM_TIMESTAMPING 控制消息");
}
println!("====================\n");
}
}