Skip to content

Commit 2f05778

Browse files
Merge pull request #21 from TomDeRybel/master
socketcan - added loopback and kernel time-stamping
2 parents ebc8dd7 + f04fb8a commit 2f05778

File tree

3 files changed

+156
-41
lines changed

3 files changed

+156
-41
lines changed

socketcan/libcanard/src/socketcan.c

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// This software is distributed under the terms of the MIT License.
22
/// Copyright (c) 2020 UAVCAN Development Team.
3-
/// Author: Pavel Kirienko <[email protected]>
3+
/// Authors: Pavel Kirienko <[email protected]>, Tom De Rybel <[email protected]>
44

55
// This is needed to enable the necessary declarations in sys/
66
#ifndef _GNU_SOURCE
@@ -14,17 +14,18 @@
1414
# include <linux/can/raw.h>
1515
# include <net/if.h>
1616
# include <sys/ioctl.h>
17+
# include <sys/socket.h>
1718
#else
1819
# error "Unsupported OS -- feel free to add support for your OS here. " \
1920
"Zephyr and NuttX are known to support the SocketCAN API."
2021
#endif
2122

2223
#include <assert.h>
2324
#include <errno.h>
24-
#include <unistd.h>
2525
#include <poll.h>
2626
#include <string.h>
2727
#include <time.h>
28+
#include <unistd.h>
2829

2930
#define KILO 1000L
3031
#define MEGA (KILO * KILO)
@@ -101,12 +102,27 @@ SocketCANFD socketcanOpen(const char* const iface_name, const bool can_fd)
101102
}
102103
}
103104

105+
// Enable CAN FD if required.
104106
if (ok && can_fd)
105107
{
106108
const int en = 1;
107109
ok = 0 == setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &en, sizeof(en));
108110
}
109111

112+
// Enable timestamping.
113+
if (ok)
114+
{
115+
const int en = 1;
116+
ok = 0 == setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &en, sizeof(en));
117+
}
118+
119+
// Enable outgoing-frame loop-back.
120+
if (ok)
121+
{
122+
const int en = 1;
123+
ok = 0 == setsockopt(fd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &en, sizeof(en));
124+
}
125+
110126
if (ok)
111127
{
112128
return fd;
@@ -152,7 +168,8 @@ int16_t socketcanPop(const SocketCANFD fd,
152168
CanardFrame* const out_frame,
153169
const size_t payload_buffer_size,
154170
void* const payload_buffer,
155-
const CanardMicrosecond timeout_usec)
171+
const CanardMicrosecond timeout_usec,
172+
bool* const loopback)
156173
{
157174
if ((out_frame == NULL) || (payload_buffer == NULL))
158175
{
@@ -162,43 +179,91 @@ int16_t socketcanPop(const SocketCANFD fd,
162179
const int16_t poll_result = doPoll(fd, POLLIN, timeout_usec);
163180
if (poll_result > 0)
164181
{
165-
struct timespec ts;
166-
if (clock_gettime(CLOCK_TAI, &ts) < 0) // TODO: request the timestamp from the kernel.
167-
{
168-
return getNegatedErrno();
169-
}
170-
182+
// Initialize the message header scatter/gather array. It is to hold a single CAN FD frame struct.
171183
// We use the CAN FD struct regardless of whether the CAN FD socket option is set.
172184
// Per the user manual, this is acceptable because they are binary compatible.
173-
struct canfd_frame cfd;
174-
const ssize_t read_size = read(fd, &cfd, sizeof(cfd));
185+
struct canfd_frame sockcan_frame = {0}; // CAN FD frame storage.
186+
struct iovec iov = {
187+
// Scatter/gather array items struct.
188+
.iov_base = &sockcan_frame, // Starting address.
189+
.iov_len = sizeof(sockcan_frame) // Number of bytes to transfer.
190+
191+
};
192+
193+
// Determine the size of the ancillary data and zero-initialize the buffer for it.
194+
// We require space for both the receive message header (implied in CMSG_SPACE) and the time stamp.
195+
// The ancillary data buffer is wrapped in a union to ensure it is suitably aligned.
196+
// See the cmsg(3) man page (release 5.08 dated 2020-06-09, or later) for details.
197+
union
198+
{
199+
uint8_t buf[CMSG_SPACE(sizeof(struct timeval))];
200+
struct cmsghdr align;
201+
} control;
202+
(void) memset(control.buf, 0, sizeof(control.buf));
203+
204+
// Initialize the message header used by recvmsg.
205+
struct msghdr msg = {0}; // Message header struct.
206+
msg.msg_iov = &iov; // Scatter/gather array.
207+
msg.msg_iovlen = 1; // Number of elements in the scatter/gather array.
208+
msg.msg_control = control.buf; // Ancillary data.
209+
msg.msg_controllen = sizeof(control.buf); // Ancillary data buffer length.
210+
211+
// Non-blocking receive messages from the socket and validate.
212+
const ssize_t read_size = recvmsg(fd, &msg, MSG_DONTWAIT);
175213
if (read_size < 0)
176214
{
177-
return getNegatedErrno();
215+
return getNegatedErrno(); // Error occurred -- return the negated error code.
178216
}
179217
if ((read_size != CAN_MTU) && (read_size != CANFD_MTU))
180218
{
181219
return -EIO;
182220
}
183-
if (cfd.len > payload_buffer_size)
221+
if (sockcan_frame.len > payload_buffer_size)
184222
{
185223
return -EFBIG;
186224
}
187225

188-
const bool valid = ((cfd.can_id & CAN_EFF_FLAG) != 0) && // Extended frame
189-
((cfd.can_id & CAN_RTR_FLAG) == 0) && // Not RTR frame
190-
((cfd.can_id & CAN_ERR_FLAG) == 0); // Not error frame
226+
const bool valid = ((sockcan_frame.can_id & CAN_EFF_FLAG) != 0) && // Extended frame
227+
((sockcan_frame.can_id & CAN_ERR_FLAG) == 0) && // Not RTR frame
228+
((sockcan_frame.can_id & CAN_RTR_FLAG) == 0); // Not error frame
191229
if (!valid)
192230
{
193231
return 0; // Not an extended data frame -- drop silently and return early.
194232
}
195233

234+
// Handle the loopback frame logic.
235+
const bool loopback_frame = ((uint32_t) msg.msg_flags & (uint32_t) MSG_CONFIRM) != 0;
236+
if (loopback == NULL && loopback_frame)
237+
{
238+
return 0; // The loopback pointer is NULL and this is a loopback frame -- drop silently and return early.
239+
}
240+
if (loopback != NULL)
241+
{
242+
*loopback = loopback_frame;
243+
}
244+
245+
// Obtain the CAN frame time stamp from the kernel.
246+
// This time stamp is from the CLOCK_REALTIME kernel source.
247+
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
248+
struct timeval tv = {0};
249+
assert(cmsg != NULL);
250+
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP)
251+
{
252+
(void) memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); // Copy to avoid alignment problems
253+
assert(tv.tv_sec >= 0 && tv.tv_usec >= 0);
254+
}
255+
else
256+
{
257+
assert(0);
258+
return -EIO;
259+
}
260+
196261
(void) memset(out_frame, 0, sizeof(CanardFrame));
197-
out_frame->timestamp_usec = (CanardMicrosecond)((ts.tv_sec * MEGA) + (ts.tv_nsec / KILO));
198-
out_frame->extended_can_id = cfd.can_id & CAN_EFF_MASK;
199-
out_frame->payload_size = cfd.len;
262+
out_frame->timestamp_usec = (CanardMicrosecond)(((uint64_t) tv.tv_sec * MEGA) + (uint64_t) tv.tv_usec);
263+
out_frame->extended_can_id = sockcan_frame.can_id & CAN_EFF_MASK;
264+
out_frame->payload_size = sockcan_frame.len;
200265
out_frame->payload = payload_buffer;
201-
(void) memcpy(payload_buffer, &cfd.data[0], cfd.len);
266+
(void) memcpy(payload_buffer, &sockcan_frame.data[0], sockcan_frame.len);
202267
}
203268
return poll_result;
204269
}

socketcan/libcanard/src/socketcan.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
/// To integrate the library into your application, just copy-paste the c/h files into your project tree.
1313
///
1414
/// --------------------------------------------------------------------------------------------------------------------
15+
/// Changelog
16+
///
17+
/// v2.0 - Added loop-back functionality.
18+
/// API change in socketcanPop(): loopback flag added.
19+
/// - Changed to kernel-based time-stamping for received frames for improved accuracy.
20+
/// API change in socketcanPop(): time stamp clock source is now CLOCK_REALTIME, vs CLOCK_TAI before.
21+
///
22+
/// v1.0 - Initial release
23+
/// --------------------------------------------------------------------------------------------------------------------
1524
///
1625
/// This software is distributed under the terms of the MIT License.
1726
/// Copyright (c) 2020 UAVCAN Development Team.
@@ -36,13 +45,6 @@ typedef int SocketCANFD;
3645
/// On failure, a negated errno is returned.
3746
/// To discard the socket just call close() on it; no additional de-initialization activities are required.
3847
/// The argument can_fd enables support for CAN FD frames.
39-
///
40-
/// If you need the outgoing frames to be looped back, set option CAN_RAW_RECV_OWN_MSGS on the returned socket handle:
41-
/// const int on = 1;
42-
/// int err = setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &on, sizeof(on));
43-
/// It is currently not possible to distinguish loopback frames from true RX frames. If you need that,
44-
/// consider using recvmsg(..) instead of this wrapper as shown in the following example:
45-
/// https://github.com/UAVCAN/platform_specific_components/blob/4745ef59f57b7e1c34705b127ea8c7a35e3874c1/linux/libuavcan/include/uavcan_linux/socketcan.hpp#L210-L270
4648
SocketCANFD socketcanOpen(const char* const iface_name, const bool can_fd);
4749

4850
/// Enqueue a new extended CAN data frame for transmission.
@@ -55,15 +57,19 @@ int16_t socketcanPush(const SocketCANFD fd, const CanardFrame* const frame, cons
5557
/// If the received frame is not an extended-ID data frame, it will be dropped and the function will return early.
5658
/// The payload pointer of the returned frame will point to the payload_buffer. It can be a stack-allocated array.
5759
/// The payload_buffer_size shall be large enough (64 bytes is enough for CAN FD), otherwise an error is returned.
58-
/// The timestamp of the received frame will be set to the CLOCK_TAI sampled near the moment of its arrival.
60+
/// The received frame timestamp will be set to CLOCK_REALTIME by the kernel, sampled near the moment of its arrival.
61+
/// The loopback flag pointer is used to both indicate and control behavior when a looped-back message is received.
62+
/// If the flag pointer is NULL, loopback frames are silently dropped; if not NULL, they are accepted and indicated
63+
/// using this flag.
5964
/// The function will block until a frame is received or until the timeout is expired. It may return early.
6065
/// Zero timeout makes the operation non-blocking.
6166
/// Returns 1 on success, 0 on timeout, negated errno on error.
6267
int16_t socketcanPop(const SocketCANFD fd,
6368
CanardFrame* const out_frame,
6469
const size_t payload_buffer_size,
6570
void* const payload_buffer,
66-
const CanardMicrosecond timeout_usec);
71+
const CanardMicrosecond timeout_usec,
72+
bool* const loopback);
6773

6874
/// The configuration of a single extended 29-bit data frame acceptance filter.
6975
/// Bits above the 29-th shall be cleared.

socketcan/libcanard/test/test_io.cpp

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,26 @@ TEST_CASE("IO-Classic") // Catch2 does not support parametrized tests yet.
2929

3030
char buf[255]{};
3131
fr = {};
32-
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000));
32+
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, nullptr));
3333
REQUIRE(fr.timestamp_usec > 0);
3434
REQUIRE(fr.extended_can_id == 0x1234U);
3535
REQUIRE(fr.payload_size == 6);
3636
REQUIRE(0 == std::memcmp(fr.payload, "Hello", 6));
3737
auto old_ts = fr.timestamp_usec;
3838

3939
fr = {};
40-
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000));
40+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
41+
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
4142
REQUIRE(fr.timestamp_usec > 0);
4243
REQUIRE(fr.timestamp_usec >= old_ts);
4344
REQUIRE(fr.extended_can_id == 0x4321U);
4445
REQUIRE(fr.payload_size == 7);
4546
REQUIRE(0 == std::memcmp(fr.payload, "World!", 7));
4647

47-
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0));
48-
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000));
49-
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000));
50-
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000));
48+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr));
49+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr));
50+
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000, nullptr));
51+
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000, nullptr));
5152

5253
REQUIRE(-EINVAL == socketcanPush(sa, nullptr, 1'000'000));
5354

@@ -85,28 +86,71 @@ TEST_CASE("IO-FD")
8586

8687
char buf[255]{};
8788
fr = {};
88-
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000));
89+
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, nullptr));
8990
REQUIRE(fr.timestamp_usec > 0);
9091
REQUIRE(fr.extended_can_id == 0x1234U);
9192
REQUIRE(fr.payload_size == 13);
9293
REQUIRE(0 == std::memcmp(fr.payload, "Hello world!", 13));
9394
auto old_ts = fr.timestamp_usec;
9495

9596
fr = {};
96-
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000));
97+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
98+
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
9799
REQUIRE(fr.timestamp_usec > 0);
98100
REQUIRE(fr.timestamp_usec >= old_ts);
99101
REQUIRE(fr.extended_can_id == 0x4321U);
100102
REQUIRE(fr.payload_size == 10);
101103
REQUIRE(0 == std::memcmp(fr.payload, "0123456789", 10));
102104

103-
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0));
104-
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000));
105-
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000));
106-
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000));
105+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr));
106+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr));
107+
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000, nullptr));
108+
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000, nullptr));
107109

108110
REQUIRE(-EINVAL == socketcanPush(sa, nullptr, 1'000'000));
109111

110112
::close(sa);
111113
::close(sb);
112114
}
115+
116+
TEST_CASE("IO-FD-Loopback")
117+
{
118+
const char* const iface_name = "vcan0";
119+
120+
const auto sa = socketcanOpen(iface_name, true);
121+
const auto sb = socketcanOpen(iface_name, true);
122+
REQUIRE(sa >= 0);
123+
REQUIRE(sb >= 0);
124+
125+
CanardFrame fr{};
126+
fr.extended_can_id = 0x1234U;
127+
fr.payload_size = 13;
128+
fr.payload = "Hello World!";
129+
REQUIRE(1 == socketcanPush(sa, &fr, 1'000'000)); // Send frame on sa.
130+
131+
bool loopback = true;
132+
char buf[255]{};
133+
fr = {};
134+
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, &loopback)); // Receive actual frame on sb.
135+
REQUIRE(loopback == false);
136+
REQUIRE(fr.timestamp_usec > 0);
137+
REQUIRE(fr.extended_can_id == 0x1234U);
138+
REQUIRE(fr.payload_size == 13);
139+
REQUIRE(0 == std::memcmp(fr.payload, "Hello World!", 13));
140+
auto old_ts = fr.timestamp_usec;
141+
142+
fr = {};
143+
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, &loopback)); // Receive loopback frame on sa.
144+
REQUIRE(loopback == true);
145+
REQUIRE(fr.timestamp_usec > 0);
146+
REQUIRE(fr.timestamp_usec >= old_ts);
147+
REQUIRE(fr.extended_can_id == 0x1234U);
148+
REQUIRE(fr.payload_size == 13);
149+
REQUIRE(0 == std::memcmp(fr.payload, "Hello World!", 13));
150+
151+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr)); // No more frames.
152+
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // No more frames.
153+
154+
::close(sa);
155+
::close(sb);
156+
}

0 commit comments

Comments
 (0)