Add two shared pieces used by all of the J1939 selftests that follow: - j1939_tests.h: the common C helpers, as static inline functions and macros grouped and documented by purpose, so each test .c keeps to the behaviour it checks rather than the socket and wire plumbing. - j1939_lib.sh: the shared vcan setup/cleanup and test runner; a wrapper just sets J1939_TEST_PROG and sources it.
They remove boilerplate the tests would otherwise repeat: resolving the test interface, opening and connecting CAN_J1939 sockets, opening a CAN_RAW socket, poll()+recv() and send/verify helpers, CAN-ID and PGN/NAME encoding, the raw TP/ETP frame building blocks, and vcan bring-up in every shell wrapper. No tests use them yet; the following patches add the tests on top. Assisted-by: LLM Signed-off-by: Oleksij Rempel <[email protected]> --- tools/testing/selftests/net/can/Makefile | 2 + tools/testing/selftests/net/can/j1939_lib.sh | 74 +++ tools/testing/selftests/net/can/j1939_tests.h | 460 ++++++++++++++++++ 3 files changed, 536 insertions(+) create mode 100644 tools/testing/selftests/net/can/j1939_lib.sh create mode 100644 tools/testing/selftests/net/can/j1939_tests.h diff --git a/tools/testing/selftests/net/can/Makefile b/tools/testing/selftests/net/can/Makefile index 5b82e60a03e7..e2092c289109 100644 --- a/tools/testing/selftests/net/can/Makefile +++ b/tools/testing/selftests/net/can/Makefile @@ -6,6 +6,8 @@ CFLAGS += -Wall -Wl,--no-as-needed -O2 -g -I$(top_srcdir)/usr/include $(KHDR_INC TEST_PROGS := test_raw_filter.sh +TEST_FILES := j1939_lib.sh + TEST_GEN_FILES := test_raw_filter include ../../lib.mk diff --git a/tools/testing/selftests/net/can/j1939_lib.sh b/tools/testing/selftests/net/can/j1939_lib.sh new file mode 100644 index 000000000000..7b7fff228b16 --- /dev/null +++ b/tools/testing/selftests/net/can/j1939_lib.sh @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Shared bring-up for the J1939 selftest wrappers. A wrapper sets +# J1939_TEST_PROG to its test binary and sources this file: +# +# J1939_TEST_PROG=test_j1939_base +# source "$(dirname "$0")/j1939_lib.sh" +# +# It sets up (and tears down) the CAN interface named by CANIF (a vcan by +# default), runs the test binary through the kselftest net lib and reports the +# result. + +ALL_TESTS="j1939_run" + +net_dir=$(dirname "$0")/.. +source "$net_dir/lib.sh" + +export CANIF=${CANIF:-"vcan0"} +# 250 kbit/s: ISO 11783-2:2002 §3.6 (nominal 4 us bit time); same value in SAE +# J1939-11:2006 §3.12. J1939-15:1999 §3.14 Table 1 defers to J1939-11. +# Only the real-CAN path uses this; the default vcan ignores bitrate. +BITRATE=${BITRATE:-250000} + +# Only undo what this script actually did: delete a vcan we created, and bring +# down an interface only if we brought it up. A failed setup() (e.g. CANIF +# already exists) must not disturb a pre-existing interface. +created=0 +brought_up=0 + +setup() +{ + if [[ $CANIF == vcan* ]]; then + ip link add name "$CANIF" type vcan || exit $ksft_skip + created=1 + else + ip link set dev "$CANIF" type can bitrate "$BITRATE" || exit $ksft_skip + fi + ip link set dev "$CANIF" up || exit $ksft_skip + brought_up=1 +} + +cleanup() +{ + if [ "$brought_up" = 1 ]; then + ip link set dev "$CANIF" down 2>/dev/null + fi + if [ "$created" = 1 ]; then + ip link delete "$CANIF" 2>/dev/null + fi +} + +j1939_run() +{ + local rc + + ./"$J1939_TEST_PROG" + rc=$? + # The test binary exits ksft_skip when CANIF is missing or the kernel + # lacks CAN_J1939; keep that a skip instead of letting check_err turn + # any non-zero into a failure. + if ((rc == ksft_skip)); then + ret_set_ksft_status $ksft_skip "$J1939_TEST_PROG" + else + check_err $rc + fi + log_test "$J1939_TEST_PROG" +} + +trap cleanup EXIT INT TERM +setup + +tests_run + +exit $EXIT_STATUS diff --git a/tools/testing/selftests/net/can/j1939_tests.h b/tools/testing/selftests/net/can/j1939_tests.h new file mode 100644 index 000000000000..dd70a7d60ec7 --- /dev/null +++ b/tools/testing/selftests/net/can/j1939_tests.h @@ -0,0 +1,460 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * Shared helpers for the J1939 selftests. + * + * The individual tests are standalone kselftest binaries, so the common + * boilerplate (interface lookup, socket open/connect, timed receive, CAN-ID + * encoding and the CANIF/main() plumbing) lives here as static inline helpers. + * + * The including .c file must include "kselftest_harness.h" before this header + * (the J1939_TEST_MAIN() macro uses KSFT_SKIP and test_harness_run()). + */ +#ifndef __SELFTESTS_NET_CAN_J1939_TESTS_H__ +#define __SELFTESTS_NET_CAN_J1939_TESTS_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <poll.h> +#include <stdint.h> +#include <errno.h> +#include <time.h> + +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <linux/if.h> +#include <linux/can.h> +#include <linux/can/raw.h> +#include <linux/can/j1939.h> + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +/* Harness patience for an expected frame (not a protocol timer). */ +#define J1939_RECV_TIMEOUT_MS 2000 + +/* + * Timeout for a check that expects no frame. Long enough that a real + * local frame would have arrived, so a timeout means "nothing came". + * Much shorter than J1939_RECV_TIMEOUT_MS, to keep the test fast. + */ +#define J1939_NEG_RECV_TIMEOUT_MS 300 + +/* kernel's default J1939 TX priority (net/can/j1939 seeds 6). */ +#define J1939_PRIO_DEFAULT 6 + +/* J1939 PGN helpers: PF >= 0xf0 selects PDU2 (broadcast, no destination). */ +#define J1939_PF(pgn) (((pgn) >> 8) & 0xff) +#define J1939_PS(pgn) ((pgn) & 0xff) +#define J1939_IS_PDU1(pgn) (J1939_PF(pgn) < 0xf0) + +/* Decoders for the fields of a received 29-bit CAN id (mask with CAN_EFF_MASK). */ +#define J1939_ID_PRIO(id) (((id) >> 26) & 0x7) +#define J1939_ID_PF(id) (((id) >> 16) & 0xff) +#define J1939_ID_PS(id) (((id) >> 8) & 0xff) +#define J1939_ID_SA(id) ((id) & 0xff) + +/* Number of 7-byte TP/ETP DT packets needed for len bytes, rounded up. */ +#define J1939_NUM_PKTS(len) (((len) + 6) / 7) + +/* + * Size of a CAN_RAW receive buffer large enough for a full ETP burst. + * A generous round-number headroom, not a computed minimum; applied + * best-effort via SO_RCVBUFFORCE. + */ +#define J1939_RAW_BIG_RCVBUF (8 * 1024 * 1024) + +/* Flags for j1939_open_flags(). */ +#define J1939_OPEN_BCAST (1 << 0) + +/* Set by J1939_TEST_MAIN() from the CANIF environment variable. */ +static char CANIF[IFNAMSIZ]; + +/* Resolve the ifindex of the test interface named by CANIF. */ +static inline int j1939_if_index(void) +{ + struct ifreq ifr = {}; + int s, idx = -1; + + s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939); + if (s < 0) + return -1; + strncpy(ifr.ifr_name, CANIF, sizeof(ifr.ifr_name) - 1); + if (ioctl(s, SIOCGIFINDEX, &ifr) >= 0) + idx = ifr.ifr_ifindex; + close(s); + return idx; +} + +/* + * FIXTURE_SETUP helper: resolve CANIF's ifindex into self->ifindex and abort the + * test if it cannot be found. Used as a statement: J1939_SETUP_IFINDEX(self); + */ +#define J1939_SETUP_IFINDEX(self) \ + do { \ + (self)->ifindex = j1939_if_index(); \ + ASSERT_GE((self)->ifindex, 0) \ + TH_LOG("failed to resolve ifindex for '%s': %d", \ + CANIF, errno); \ + } while (0) + +/* Open a J1939 socket bound to (name, sa, pgn); flags may set J1939_OPEN_BCAST. */ +static inline int j1939_open_flags(int ifindex, name_t name, uint8_t sa, + pgn_t pgn, int flags) +{ + struct sockaddr_can addr = { + .can_family = AF_CAN, + .can_ifindex = ifindex, + }; + int s, one = 1; + + s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939); + if (s < 0) + return -1; + if ((flags & J1939_OPEN_BCAST) && + setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one))) { + close(s); + return -1; + } + addr.can_addr.j1939.name = name; + addr.can_addr.j1939.addr = sa; + addr.can_addr.j1939.pgn = pgn; + if (bind(s, (struct sockaddr *)&addr, sizeof(addr))) { + close(s); + return -1; + } + return s; +} + +/* Open a J1939 socket bound to (sa, pgn) with no NAME and no broadcast. */ +static inline int j1939_open(int ifindex, uint8_t sa, pgn_t pgn) +{ + return j1939_open_flags(ifindex, J1939_NO_NAME, sa, pgn, 0); +} + +/* Connect a J1939 socket to the peer (name, da, pgn). */ +static inline int j1939_connect_name(int s, int ifindex, name_t name, + uint8_t da, pgn_t pgn) +{ + struct sockaddr_can addr = { + .can_family = AF_CAN, + .can_ifindex = ifindex, + }; + + addr.can_addr.j1939.name = name; + addr.can_addr.j1939.addr = da; + addr.can_addr.j1939.pgn = pgn; + return connect(s, (struct sockaddr *)&addr, sizeof(addr)); +} + +static inline int j1939_connect(int s, int ifindex, uint8_t da, pgn_t pgn) +{ + return j1939_connect_name(s, ifindex, J1939_NO_NAME, da, pgn); +} + +/* Fill a sockaddr_can describing a J1939 (name, da, pgn) destination. */ +static inline void j1939_fill_sockaddr(struct sockaddr_can *addr, int ifindex, + name_t name, uint8_t da, pgn_t pgn) +{ + memset(addr, 0, sizeof(*addr)); + addr->can_family = AF_CAN; + addr->can_ifindex = ifindex; + addr->can_addr.j1939.name = name; + addr->can_addr.j1939.addr = da; + addr->can_addr.j1939.pgn = pgn; +} + +/* sendto() len bytes from buf to the J1939 (name, da, pgn) destination. */ +static inline int j1939_sendto(int s, const void *buf, size_t len, int ifindex, + name_t name, uint8_t da, pgn_t pgn) +{ + struct sockaddr_can dst; + + j1939_fill_sockaddr(&dst, ifindex, name, da, pgn); + return sendto(s, buf, len, 0, (struct sockaddr *)&dst, sizeof(dst)); +} + +/* + * Open a CAN_RAW socket on ifindex. rcvbuf > 0 requests that SO_RCVBUFFORCE + * size (best-effort: a failure is logged, not fatal). + */ +static inline int open_raw_rcvbuf(int ifindex, int rcvbuf) +{ + struct sockaddr_can addr = { + .can_family = AF_CAN, + .can_ifindex = ifindex, + }; + int s; + + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (s < 0) + return -1; + if (rcvbuf > 0 && + setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf))) + fprintf(stderr, "%s: SO_RCVBUFFORCE(%d) failed: %d\n", + __func__, rcvbuf, errno); + if (bind(s, (struct sockaddr *)&addr, sizeof(addr))) { + close(s); + return -1; + } + return s; +} + +static inline int open_raw(int ifindex) +{ + return open_raw_rcvbuf(ifindex, 0); +} + +/* + * FIXTURE_SETUP helper: open a CAN_RAW socket on self->ifindex into self->raw + * with the given SO_RCVBUFFORCE size (0 for the default) and abort the test if + * it cannot be opened. Used as a statement: J1939_SETUP_RAW(self, rcvbuf); + */ +#define J1939_SETUP_RAW(self, rcvbuf) \ + do { \ + (self)->raw = open_raw_rcvbuf((self)->ifindex, (rcvbuf)); \ + ASSERT_GE((self)->raw, 0) \ + TH_LOG("failed to open raw socket: %d", errno); \ + } while (0) + +/* poll() + recv(): returns the recv() result, or -1 on timeout/error. */ +static inline int recv_timeout(int s, void *buf, size_t len, int timeout_ms) +{ + struct pollfd pfd = { .fd = s, .events = POLLIN }; + + if (poll(&pfd, 1, timeout_ms) <= 0) + return -1; + return recv(s, buf, len, 0); +} + +/* Send len bytes on tx and verify they arrive byte-for-byte on rx. 0 / -1. */ +static inline int send_recv_verify(int tx, int rx, const void *buf, size_t len, + int timeout_ms) +{ + uint8_t *rx_buf = malloc(len); + int rc = -1; + + if (!rx_buf) + return -1; + if (send(tx, buf, len, 0) == (ssize_t)len && + recv_timeout(rx, rx_buf, len, timeout_ms) == (int)len && + memcmp(buf, rx_buf, len) == 0) + rc = 0; + free(rx_buf); + return rc; +} + +/* Encode a J1939 single frame the way the kernel does on transmit. */ +static inline canid_t j1939_canid(uint8_t prio, pgn_t pgn, uint8_t da, + uint8_t sa) +{ + canid_t id = CAN_EFF_FLAG | ((canid_t)prio << 26) | (pgn << 8) | sa; + + if (J1939_IS_PDU1(pgn)) + id |= (canid_t)da << 8; + return id; +} + +/* Start an 8-byte TP/ETP control frame: unused CM bytes are 0xff, byte 0 = cmd. */ +static inline void ctl_dat_init(uint8_t *dat, uint8_t cmd) +{ + memset(dat, 0xff, 8); + dat[0] = cmd; +} + +/* Embed a 24-bit PGN into a TP/ETP control frame (bytes 5..7, little endian). */ +static inline void put_pgn(uint8_t *dat, pgn_t pgn) +{ + dat[5] = pgn; + dat[6] = pgn >> 8; + dat[7] = pgn >> 16; +} + +/* Serialize a 64-bit NAME little-endian into an 8-byte buffer (host-agnostic). */ +static inline void put_name(uint8_t *dat, name_t name) +{ + int i; + + for (i = 0; i < 8; i++) + dat[i] = (uint8_t)(name >> (8 * i)); +} + +/* Little-endian codecs for the TP/ETP CM multi-byte fields (host-agnostic). */ +static inline void put_le16(uint8_t *p, uint16_t v) +{ + p[0] = v; + p[1] = v >> 8; +} + +static inline void put_le24(uint8_t *p, uint32_t v) +{ + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; +} + +static inline void put_le32(uint8_t *p, uint32_t v) +{ + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; + p[3] = v >> 24; +} + +static inline uint16_t get_le16(const uint8_t *p) +{ + return (uint16_t)(p[0] | (p[1] << 8)); +} + +static inline uint32_t get_le24(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); +} + +static inline uint32_t get_le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +/* Send one 8-byte J1939-encoded frame (any PGN) with the given priority. */ +static inline int send_raw8(int raw, uint8_t prio, pgn_t pgn, uint8_t da, + uint8_t sa, const uint8_t dat[8]) +{ + struct can_frame f = {}; + + f.can_id = j1939_canid(prio, pgn, da, sa); + f.can_dlc = 8; + memcpy(f.data, dat, 8); + return write(raw, &f, sizeof(f)) == (int)sizeof(f) ? 0 : -1; +} + +/* Send a TP/ETP data (DT) frame carrying seq + up to 7 payload bytes. */ +static inline int send_dt(int raw, uint8_t prio, pgn_t dat_pgn, uint8_t da, + uint8_t sa, uint8_t seq, const uint8_t *payload, + size_t off, size_t total) +{ + struct can_frame f = {}; + size_t n; + + /* off >= total would make the size_t subtraction total - off underflow. */ + if (off >= total) + return -1; + n = total - off; + if (n > 7) + n = 7; + f.can_id = j1939_canid(prio, dat_pgn, da, sa); + f.can_dlc = 8; + memset(f.data, 0xff, 8); + f.data[0] = seq; + memcpy(&f.data[1], payload + off, n); + return write(raw, &f, sizeof(f)) == (int)sizeof(f) ? 0 : -1; +} + +/* Elapsed milliseconds between two CLOCK_MONOTONIC samples. */ +static inline long timespec_diff_ms(const struct timespec *a, const struct timespec *b) +{ + return (b->tv_sec - a->tv_sec) * 1000 + (b->tv_nsec - a->tv_nsec) / 1000000; +} + +/* Poll+recv on raw until match() accepts a frame, else timeout. 0/-1. */ +static inline int recv_match(int raw, int timeout_ms, struct can_frame *frame, + bool (*match)(canid_t id, const void *ctx), + const void *ctx) +{ + struct pollfd pfd = { .fd = raw, .events = POLLIN }; + struct timespec now, deadline; + int remaining = timeout_ms; + + /* + * Non-matching frames (other traffic on the bus) must not refresh the + * timeout, or a busy bus would keep the loop alive forever. Poll against + * a fixed deadline and shrink the budget after every frame. + */ + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += timeout_ms / 1000; + deadline.tv_nsec += (long)(timeout_ms % 1000) * 1000000; + if (deadline.tv_nsec >= 1000000000) { + deadline.tv_sec++; + deadline.tv_nsec -= 1000000000; + } + while (remaining > 0 && poll(&pfd, 1, remaining) > 0) { + if (recv(raw, frame, sizeof(*frame), 0) < 0) + return -1; + if (match(frame->can_id & CAN_EFF_MASK, ctx)) + return 0; + clock_gettime(CLOCK_MONOTONIC, &now); + remaining = timespec_diff_ms(&now, &deadline); + } + return -1; +} + +struct j1939_peer_match { uint8_t want_pf, want_da; }; +static inline bool match_peer_dest(canid_t id, const void *ctx) +{ + const struct j1939_peer_match *m = ctx; + + return J1939_ID_PF(id) == m->want_pf && + J1939_ID_PS(id) == m->want_da; /* destination */ +} + +/* Receive the next frame whose CAN-id PF == want_pf addressed to want_da (PS). */ +static inline int recv_for_peer(int raw, uint8_t want_pf, uint8_t want_da, + struct can_frame *frame, int timeout_ms) +{ + struct j1939_peer_match m = { .want_pf = want_pf, .want_da = want_da }; + + return recv_match(raw, timeout_ms, frame, match_peer_dest, &m); +} + +/* recv_for_peer() + check the control command byte; 0 on match, -1 otherwise. */ +static inline int recv_ctl_cmd(int raw, uint8_t want_pf, uint8_t want_da, + struct can_frame *frame, int timeout_ms, + uint8_t want_cmd) +{ + if (recv_for_peer(raw, want_pf, want_da, frame, timeout_ms)) + return -1; + return frame->data[0] == want_cmd ? 0 : -1; +} + +/* Fill buf with a deterministic, position-dependent byte pattern. */ +static inline void fill_pattern(uint8_t *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + buf[i] = (uint8_t)(i ^ (i >> 8) ^ 0x3c); +} + +/* + * Define main(): require CANIF in the environment and a kernel that supports + * CAN_J1939, then run the harness. Skip (rather than fail) when either is + * missing, per the kselftest skip contract. + */ +#define J1939_TEST_MAIN() \ +int main(int argc, char **argv) \ +{ \ + char *ifname = getenv("CANIF"); \ + int probe; \ + \ + if (!ifname) { \ + fprintf(stderr, \ + "CANIF environment variable must contain the test interface\n"); \ + return KSFT_SKIP; \ + } \ + strncpy(CANIF, ifname, sizeof(CANIF) - 1); \ + \ + probe = socket(PF_CAN, SOCK_DGRAM, CAN_J1939); \ + if (probe < 0) { \ + if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) \ + return KSFT_SKIP; \ + } else { \ + close(probe); \ + } \ + return test_harness_run(argc, argv); \ +} + +#endif /* __SELFTESTS_NET_CAN_J1939_TESTS_H__ */ -- 2.47.3

