Skip to content

Commit

Permalink
Update libcanard and fix socketcan, supersede 38 (#41)
Browse files Browse the repository at this point in the history
* Update libcanard and fix socketcan
* Fix clang-format and brackets
* socketcan: Use CanardFilter instead of custom type
* Cyphalize

Signed-off-by: delphi <cpp.create@gmail.com>
Co-authored-by: delphi <cpp.create@gmail.com>
  • Loading branch information
pavel-kirienko and asmfreak committed May 5, 2022
1 parent 9f446aa commit de15924
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 107 deletions.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2018 UAVCAN
Copyright (c) 2022 OpenCyphal

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Platform-specific components

[![Build Status](https://travis-ci.org/UAVCAN/platform_specific_components.svg?branch=master)](https://travis-ci.org/UAVCAN/platform_specific_components)
[![Forum](https://img.shields.io/discourse/users.svg?server=https%3A%2F%2Fforum.uavcan.org&color=1700b3)](https://forum.uavcan.org)
[![Main Workflow](https://github.com/OpenCyphal-Garage/platform_specific_components/actions/workflows/main.yml/badge.svg)](https://github.com/OpenCyphal-Garage/platform_specific_components/actions/workflows/main.yml)
[![Forum](https://img.shields.io/discourse/users.svg?server=https%3A%2F%2Fforum.opencyphal.org&color=1700b3)](https://forum.opencyphal.org)

This repository contains various platform-specific components maintained by the UAVCAN Development Team.
This repository contains various platform-specific components maintained by the OpenCyphal team.
The quality and the level of support provided for these components may be substantially lower than
that of the UAVCAN implementation libraries.
that of the official OpenCyphal implementation libraries.

The content is organized into directories following the pattern `/<platform>/<library>/`;
for example, the Libuavcan driver for STM32 can be found under `/stm32/libuavcan/`.
for example, the Libuavcan driver for STM32 can be found under `/stm32/libcanard/`.
Further segregation may be defined on a per-directory basis.

All code is MIT-licensed unless a dedicated LICENSE file is provided.
Expand All @@ -21,5 +21,3 @@ All code is MIT-licensed unless a dedicated LICENSE file is provided.
Users are encouraged to search through this repository for code that can be used with their target platform
and use it as a starting point or as a practical guideline in the development of a customized solution for
the specific application.
If nothing is found, consider checking the `legacy-v0` branch: the code contained there may require heavy modification
but it might still be useful.
58 changes: 31 additions & 27 deletions socketcan/libcanard/src/socketcan.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (c) 2020 UAVCAN Development Team.
/// Copyright (c) 2020 OpenCyphal
/// Authors: Pavel Kirienko <pavel.kirienko@zubax.com>, Tom De Rybel <tom.derybel@robocow.be>

// This is needed to enable the necessary declarations in sys/
Expand Down Expand Up @@ -164,12 +164,13 @@ int16_t socketcanPush(const SocketCANFD fd, const CanardFrame* const frame, cons
return poll_result;
}

int16_t socketcanPop(const SocketCANFD fd,
CanardFrame* const out_frame,
const size_t payload_buffer_size,
void* const payload_buffer,
const CanardMicrosecond timeout_usec,
bool* const loopback)
int16_t socketcanPop(const SocketCANFD fd,
CanardFrame* const out_frame,
CanardMicrosecond* const out_timestamp_usec,
const size_t payload_buffer_size,
void* const payload_buffer,
const CanardMicrosecond timeout_usec,
bool* const loopback)
{
if ((out_frame == NULL) || (payload_buffer == NULL))
{
Expand All @@ -185,8 +186,8 @@ int16_t socketcanPop(const SocketCANFD fd,
struct canfd_frame sockcan_frame = {0}; // CAN FD frame storage.
struct iovec iov = {
// Scatter/gather array items struct.
.iov_base = &sockcan_frame, // Starting address.
.iov_len = sizeof(sockcan_frame) // Number of bytes to transfer.
.iov_base = &sockcan_frame, // Starting address.
.iov_len = sizeof(sockcan_frame) // Number of bytes to transfer.

};

Expand Down Expand Up @@ -244,22 +245,25 @@ int16_t socketcanPop(const SocketCANFD fd,

// Obtain the CAN frame time stamp from the kernel.
// This time stamp is from the CLOCK_REALTIME kernel source.
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
struct timeval tv = {0};
assert(cmsg != NULL);
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP)
if (NULL != out_timestamp_usec)
{
(void) memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); // Copy to avoid alignment problems
assert(tv.tv_sec >= 0 && tv.tv_usec >= 0);
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
struct timeval tv = {0};
assert(cmsg != NULL);
if ((cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SO_TIMESTAMP))
{
(void) memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); // Copy to avoid alignment problems
assert(tv.tv_sec >= 0 && tv.tv_usec >= 0);
}
else
{
assert(0);
return -EIO;
}

(void) memset(out_frame, 0, sizeof(CanardFrame));
*out_timestamp_usec = (CanardMicrosecond) (((uint64_t) tv.tv_sec * MEGA) + (uint64_t) tv.tv_usec);
}
else
{
assert(0);
return -EIO;
}

(void) memset(out_frame, 0, sizeof(CanardFrame));
out_frame->timestamp_usec = (CanardMicrosecond)(((uint64_t) tv.tv_sec * MEGA) + (uint64_t) tv.tv_usec);
out_frame->extended_can_id = sockcan_frame.can_id & CAN_EFF_MASK;
out_frame->payload_size = sockcan_frame.len;
out_frame->payload = payload_buffer;
Expand All @@ -268,7 +272,7 @@ int16_t socketcanPop(const SocketCANFD fd,
return poll_result;
}

int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const SocketCANFilterConfig* const configs)
int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const CanardFilter* const configs)
{
if (configs == NULL)
{
Expand All @@ -282,12 +286,12 @@ int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const So
struct can_filter cfs[CAN_RAW_FILTER_MAX];
for (size_t i = 0; i < num_configs; i++)
{
cfs[i].can_id = (configs[i].extended_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
cfs[i].can_mask = (configs[i].mask & CAN_EFF_MASK) | CAN_EFF_FLAG | CAN_RTR_FLAG;
cfs[i].can_id = (configs[i].extended_can_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
cfs[i].can_mask = (configs[i].extended_mask & CAN_EFF_MASK) | CAN_EFF_FLAG | CAN_RTR_FLAG;
}

const int ret =
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, cfs, (socklen_t)(sizeof(struct can_filter) * num_configs));
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, cfs, (socklen_t) (sizeof(struct can_filter) * num_configs));

return (ret < 0) ? getNegatedErrno() : 0;
}
40 changes: 17 additions & 23 deletions socketcan/libcanard/src/socketcan.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/// __ __ _______ __ __ _______ _______ __ __
/// | | | | / _ ` | | | | / ____| / _ ` | ` | |
/// | | | | | |_| | | | | | | | | |_| | | `| |
/// | |_| | | _ | ` `_/ / | |____ | _ | | |` |
/// `_______/ |__| |__| `_____/ `_______| |__| |__| |__| `__|
/// | | | | | |
/// ----o------o------------o---------o------o---------o-------
/// ____ ______ __ __
/// / __ `____ ___ ____ / ____/_ ______ / /_ ____ / /
/// / / / / __ `/ _ `/ __ `/ / / / / / __ `/ __ `/ __ `/ /
/// / /_/ / /_/ / __/ / / / /___/ /_/ / /_/ / / / / /_/ / /
/// `____/ .___/`___/_/ /_/`____/`__, / .___/_/ /_/`__,_/_/
/// /_/ /____/_/
///
/// This is a basic adapter library that bridges Libcanard with SocketCAN.
/// Read the API documentation for usage information.
Expand All @@ -14,6 +13,8 @@
/// --------------------------------------------------------------------------------------------------------------------
/// Changelog
///
/// v3.0 - Update for compatibility with Libcanard v3.
///
/// v2.0 - Added loop-back functionality.
/// API change in socketcanPop(): loopback flag added.
/// - Changed to kernel-based time-stamping for received frames for improved accuracy.
Expand All @@ -23,7 +24,7 @@
/// --------------------------------------------------------------------------------------------------------------------
///
/// This software is distributed under the terms of the MIT License.
/// Copyright (c) 2020 UAVCAN Development Team.
/// Copyright (c) 2020 OpenCyphal
/// Author: Pavel Kirienko <pavel.kirienko@zubax.com>

#ifndef SOCKETCAN_H_INCLUDED
Expand Down Expand Up @@ -64,26 +65,19 @@ int16_t socketcanPush(const SocketCANFD fd, const CanardFrame* const frame, cons
/// The function will block until a frame is received or until the timeout is expired. It may return early.
/// Zero timeout makes the operation non-blocking.
/// Returns 1 on success, 0 on timeout, negated errno on error.
int16_t socketcanPop(const SocketCANFD fd,
CanardFrame* const out_frame,
const size_t payload_buffer_size,
void* const payload_buffer,
const CanardMicrosecond timeout_usec,
bool* const loopback);

/// The configuration of a single extended 29-bit data frame acceptance filter.
/// Bits above the 29-th shall be cleared.
typedef struct SocketCANFilterConfig
{
uint32_t extended_id;
uint32_t mask;
} SocketCANFilterConfig;
int16_t socketcanPop(const SocketCANFD fd,
CanardFrame* const out_frame,
CanardMicrosecond* const out_timestamp_usec,
const size_t payload_buffer_size,
void* const payload_buffer,
const CanardMicrosecond timeout_usec,
bool* const loopback);

/// Apply the specified acceptance filter configuration.
/// Note that it is only possible to accept extended-format data frames.
/// The default configuration is to accept everything.
/// Returns 0 on success, negated errno on error.
int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const SocketCANFilterConfig* const configs);
int16_t socketcanFilter(const SocketCANFD fd, const size_t num_configs, const CanardFilter* const configs);

#ifdef __cplusplus
}
Expand Down
76 changes: 41 additions & 35 deletions socketcan/libcanard/test/test_io.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (c) 2020 UAVCAN Development Team.
/// Copyright (c) 2020 OpenCyphal
/// Author: Pavel Kirienko <pavel.kirienko@zubax.com>

#include "socketcan.h"
Expand All @@ -16,7 +16,8 @@ TEST_CASE("IO-Classic") // Catch2 does not support parametrized tests yet.
REQUIRE(sa >= 0);
REQUIRE(sb >= 0);

CanardFrame fr{};
CanardFrame fr{};
CanardMicrosecond timestamp_usec{};
fr.extended_can_id = 0x1234U;
fr.payload_size = 6;
fr.payload = "Hello";
Expand All @@ -29,26 +30,26 @@ TEST_CASE("IO-Classic") // Catch2 does not support parametrized tests yet.

char buf[255]{};
fr = {};
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(1 == socketcanPop(sb, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr));
REQUIRE(timestamp_usec > 0);
REQUIRE(fr.extended_can_id == 0x1234U);
REQUIRE(fr.payload_size == 6);
REQUIRE(0 == std::memcmp(fr.payload, "Hello", 6));
auto old_ts = fr.timestamp_usec;
auto old_ts = timestamp_usec;

fr = {};
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(fr.timestamp_usec >= old_ts);
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
REQUIRE(1 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
REQUIRE(timestamp_usec > 0);
REQUIRE(timestamp_usec >= old_ts);
REQUIRE(fr.extended_can_id == 0x4321U);
REQUIRE(fr.payload_size == 7);
REQUIRE(0 == std::memcmp(fr.payload, "World!", 7));

REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 0, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, &fr, &timestamp_usec, 0, nullptr, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, nullptr, sizeof(buf), buf, 1000, nullptr));

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

Expand All @@ -65,15 +66,16 @@ TEST_CASE("IO-FD")
REQUIRE(sa >= 0);
REQUIRE(sb >= 0);

const SocketCANFilterConfig fcs = {
const CanardFilter fcs = {
0x00001234U,
0x1FFFFFFFU,
};
REQUIRE(0 == socketcanFilter(sb, 1, &fcs));
REQUIRE(-EFBIG == socketcanFilter(sa, 1'000'000, &fcs));
REQUIRE(-EINVAL == socketcanFilter(sa, 1, NULL));

CanardFrame fr{};
CanardFrame fr{};
CanardMicrosecond timestamp_usec{};
fr.extended_can_id = 0x1234U;
fr.payload_size = 13;
fr.payload = "Hello world!";
Expand All @@ -86,26 +88,26 @@ TEST_CASE("IO-FD")

char buf[255]{};
fr = {};
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(1 == socketcanPop(sb, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr));
REQUIRE(timestamp_usec > 0);
REQUIRE(fr.extended_can_id == 0x1234U);
REQUIRE(fr.payload_size == 13);
REQUIRE(0 == std::memcmp(fr.payload, "Hello world!", 13));
auto old_ts = fr.timestamp_usec;
auto old_ts = timestamp_usec;

fr = {};
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(fr.timestamp_usec >= old_ts);
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr)); // Loopback frame.
REQUIRE(1 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr)); // Received actual frame.
REQUIRE(timestamp_usec > 0);
REQUIRE(timestamp_usec >= old_ts);
REQUIRE(fr.extended_can_id == 0x4321U);
REQUIRE(fr.payload_size == 10);
REQUIRE(0 == std::memcmp(fr.payload, "0123456789", 10));

REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, &fr, 0, nullptr, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, sizeof(buf), buf, 1000, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 0, nullptr));
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, &fr, &timestamp_usec, 0, nullptr, 1000, nullptr));
REQUIRE(-EINVAL == socketcanPop(sa, nullptr, nullptr, sizeof(buf), buf, 1000, nullptr));

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

Expand All @@ -122,7 +124,8 @@ TEST_CASE("IO-FD-Loopback")
REQUIRE(sa >= 0);
REQUIRE(sb >= 0);

CanardFrame fr{};
CanardFrame fr{};
CanardMicrosecond timestamp_usec{};
fr.extended_can_id = 0x1234U;
fr.payload_size = 13;
fr.payload = "Hello World!";
Expand All @@ -131,25 +134,28 @@ TEST_CASE("IO-FD-Loopback")
bool loopback = true;
char buf[255]{};
fr = {};
REQUIRE(1 == socketcanPop(sb, &fr, sizeof(buf), buf, 1000, &loopback)); // Receive actual frame on sb.
REQUIRE(1 ==
socketcanPop(sb, &fr, &timestamp_usec, sizeof(buf), buf, 1000, &loopback)); // Receive actual frame on sb.
REQUIRE(loopback == false);
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(timestamp_usec > 0);
REQUIRE(fr.extended_can_id == 0x1234U);
REQUIRE(fr.payload_size == 13);
REQUIRE(0 == std::memcmp(fr.payload, "Hello World!", 13));
auto old_ts = fr.timestamp_usec;
auto old_ts = timestamp_usec;

fr = {};
REQUIRE(1 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, &loopback)); // Receive loopback frame on sa.
REQUIRE(
1 ==
socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, &loopback)); // Receive loopback frame on sa.
REQUIRE(loopback == true);
REQUIRE(fr.timestamp_usec > 0);
REQUIRE(fr.timestamp_usec >= old_ts);
REQUIRE(timestamp_usec > 0);
REQUIRE(timestamp_usec >= old_ts);
REQUIRE(fr.extended_can_id == 0x1234U);
REQUIRE(fr.payload_size == 13);
REQUIRE(0 == std::memcmp(fr.payload, "Hello World!", 13));

REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 0, nullptr)); // No more frames.
REQUIRE(0 == socketcanPop(sa, &fr, sizeof(buf), buf, 1000, nullptr)); // No more frames.
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 0, nullptr)); // No more frames.
REQUIRE(0 == socketcanPop(sa, &fr, &timestamp_usec, sizeof(buf), buf, 1000, nullptr)); // No more frames.

::close(sa);
::close(sb);
Expand Down
4 changes: 2 additions & 2 deletions stm32/libcanard/bxcan/src/bxcan.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (c) 2016-2020 UAVCAN Development Team.
/// Copyright (c) 2016-2022 OpenCyphal
/// Authors: Pavel Kirienko <pavel.kirienko@zubax.com>, Tom De Rybel <tom.derybel@robocow.be>

#include "bxcan.h"
Expand Down Expand Up @@ -294,7 +294,7 @@ bool bxCANConfigure(const uint8_t iface_index, //
//
// Filters are alternating between FIFO0 and FIFO1 in order to equalize the load. (Set in FFA1R.)
// This will cause occasional priority inversion and frame reordering on reception,
// but that is acceptable for UAVCAN, and a majority of other protocols will tolerate
// but that is acceptable for Cyphal/CAN, and a majority of other protocols will tolerate
// this too, since there will be no reordering within the same CAN ID.
if (iface_index == 0U)
{
Expand Down
Loading

0 comments on commit de15924

Please sign in to comment.