/* SPDX-License-Identifier: GPL-2.0-only */ /* * canfdtest.c - Full-duplex test program (DUT and host part) * * (C) 2009 by Vladislav Gribov, IXXAT Automation GmbH, * (C) 2009 Wolfgang Grandegger * (C) 2021 Jean Gressmann, IAV GmbH, * * This program is free software; you can redistribute it and/or modify * it under the terms of the version 2 of the GNU General Public License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Send feedback to */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CAN_MSG_ID_PING 0x77 #define CAN_MSG_ID_PONG 0x78 #define CAN_MSG_LEN 8 #define CAN_MSG_COUNT 50 #define CAN_MSG_WAIT 27 static int running = 1; static int verbose; static int sockfd; static int test_loops; static int exit_sig; static int inflight_count = CAN_MSG_COUNT; static canid_t can_id_ping = CAN_MSG_ID_PING; static canid_t can_id_pong = CAN_MSG_ID_PONG; static int has_pong_id = 0; static int is_can_fd = 0; static int bit_rate_switch = 0; static int msg_len = CAN_MSG_LEN; static int is_extended_frame_format = 0; static void print_usage(char *prg) { fprintf(stderr, "%s - Full-duplex test program (DUT and host part).\n" "Usage: %s [options] \n" "\n" "Options:\n" " -b (enable CAN FD Bit Rate Switch)\n" " -d (use CAN FD frames instead of classic CAN)\n" " -e (use 29-bit extended frame format instead of classic 11-bit one)\n" " -f COUNT (number of frames in flight, default: %d)\n" " -g (generate messages)\n" " -i ID (CAN ID to use for frames to DUT (ping), default %x)\n" " -l COUNT (test loop count)\n" " -o ID (CAN ID to use for frames to host (pong), default %x)\n" " -s SIZE (frame payload size in bytes)\n" " -v (low verbosity)\n" " -vv (high verbosity)\n" " -x (ignore other frames on bus)\n" "\n" "With the option '-g' CAN messages are generated and checked\n" "on , otherwise all messages received on the\n" " are sent back incrementing the CAN id and\n" "all data bytes. The program can be aborted with ^C.\n" "\n" "Examples:\n" "\ton DUT:\n" "%s -v can0\n" "\ton Host:\n" "%s -g -v can2\n", prg, prg, CAN_MSG_COUNT, CAN_MSG_ID_PING, CAN_MSG_ID_PONG, prg, prg); exit(1); } static void print_frame(canid_t id, const uint8_t *data, int dlc, int inc_data) { int i; printf("%04x: ", id); if (id & CAN_RTR_FLAG) { printf("remote request"); } else { printf("[%d]", dlc); for (i = 0; i < dlc; i++) printf(" %02x", (uint8_t)(data[i] + inc_data)); } printf("\n"); } static void print_compare(canid_t exp_id, const uint8_t *exp_data, uint8_t exp_dlc, canid_t rec_id, const uint8_t *rec_data, uint8_t rec_dlc, int inc) { printf("expected: "); print_frame(exp_id, exp_data, exp_dlc, inc); printf("received: "); print_frame(rec_id, rec_data, rec_dlc, 0); } static int compare_frame(const struct canfd_frame *exp, const struct canfd_frame *rec, int inc) { int i, err = 0; const canid_t expected_can_id = inc ? can_id_pong : can_id_ping; if (rec->can_id != expected_can_id) { printf("Message ID mismatch!\n"); print_compare(expected_can_id, exp->data, exp->len, rec->can_id, rec->data, rec->len, inc); running = 0; err = -1; } else if (rec->len != exp->len) { printf("Message length mismatch!\n"); print_compare(expected_can_id, exp->data, exp->len, rec->can_id, rec->data, rec->len, inc); running = 0; err = -1; } else { for (i = 0; i < rec->len; i++) { if (rec->data[i] != (uint8_t)(exp->data[i] + inc)) { printf("Databyte %x mismatch!\n", i); print_compare(expected_can_id, exp->data, exp->len, rec->can_id, rec->data, rec->len, inc); running = 0; err = -1; } } } return err; } static void millisleep(int msecs) { struct timespec rqtp, rmtp; int err; /* sleep in ms */ rqtp.tv_sec = msecs / 1000; rqtp.tv_nsec = msecs % 1000 * 1000000; do { err = clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, &rmtp); if (err != 0 && err != EINTR) { printf("t\n"); break; } rqtp = rmtp; } while (err != 0); } static void echo_progress(unsigned char data) { if (data == 0xff) { printf("."); fflush(stdout); } } static void signal_handler(int signo) { close(sockfd); running = 0; exit_sig = signo; } static int recv_frame(struct canfd_frame *frame) { ssize_t ret, len; if (is_can_fd) len = sizeof(struct canfd_frame); else len = sizeof(struct can_frame); ret = recv(sockfd, frame, len, 0); if (ret != len) { if (ret < 0) perror("recv failed"); else fprintf(stderr, "recv returned %zd", ret); return -1; } return 0; } static int send_frame(struct canfd_frame *frame) { ssize_t ret, len; if (is_can_fd) len = sizeof(struct canfd_frame); else len = sizeof(struct can_frame); if (bit_rate_switch) frame->flags |= CANFD_BRS; while ((ret = send(sockfd, frame, len, 0)) != len) { if (ret >= 0) { fprintf(stderr, "send returned %zd", ret); return -1; } if (errno != ENOBUFS) { perror("send failed"); return -1; } if (verbose) { printf("N"); fflush(stdout); } } return 0; } static int check_frame(const struct canfd_frame *frame) { int err = 0; int i; if (frame->can_id != can_id_ping) { printf("Unexpected Message ID 0x%04x!\n", frame->can_id); err = -1; } if (frame->len != msg_len) { printf("Unexpected Message length %d!\n", frame->len); err = -1; } for (i = 1; i < frame->len; i++) { if (frame->data[i] != (uint8_t)(frame->data[i - 1] + 1)) { printf("Frame inconsistent!\n"); print_frame(frame->can_id, frame->data, frame->len, 0); err = -1; goto out; } } out: return err; } static void inc_frame(struct canfd_frame *frame) { int i; if (has_pong_id) frame->can_id = can_id_pong; else frame->can_id++; for (i = 0; i < frame->len; i++) frame->data[i]++; } static int can_echo_dut(void) { unsigned int frame_count = 0; struct canfd_frame frame; int err = 0; while (running) { if (recv_frame(&frame)) return -1; frame_count++; if (verbose == 1) { echo_progress(frame.data[0]); } else if (verbose > 1) { print_frame(frame.can_id, frame.data, frame.len, 0); } err = check_frame(&frame); inc_frame(&frame); if (send_frame(&frame)) return -1; /* * to force a interlacing of the frames send by DUT and PC * test tool a waiting time is injected */ if (frame_count == CAN_MSG_WAIT) { frame_count = 0; millisleep(3); } } return err; } static int can_echo_gen(void) { struct canfd_frame *tx_frames; int *recv_tx; struct canfd_frame rx_frame; unsigned char counter = 0; int send_pos = 0, recv_rx_pos = 0, recv_tx_pos = 0, unprocessed = 0, loops = 0; int err = 0; int i; tx_frames = calloc(inflight_count, sizeof(*tx_frames)); if (!tx_frames) return -1; recv_tx = calloc(inflight_count, sizeof(*recv_tx)); if (!recv_tx) { err = -1; goto out_free_tx_frames; } while (running) { if (unprocessed < inflight_count) { /* still send messages */ tx_frames[send_pos].len = msg_len; tx_frames[send_pos].can_id = can_id_ping; recv_tx[send_pos] = 0; for (i = 0; i < msg_len; i++) tx_frames[send_pos].data[i] = counter + i; if (send_frame(&tx_frames[send_pos])) { err = -1; goto out_free; } send_pos++; if (send_pos == inflight_count) send_pos = 0; unprocessed++; if (verbose == 1) echo_progress(counter); counter++; if ((counter % 33) == 0) millisleep(3); else millisleep(1); } else { if (recv_frame(&rx_frame)) { err = -1; goto out_free; } if (verbose > 1) print_frame(rx_frame.can_id, rx_frame.data, rx_frame.len, 0); /* own frame */ if (rx_frame.can_id == can_id_ping) { err = compare_frame(&tx_frames[recv_tx_pos], &rx_frame, 0); recv_tx[recv_tx_pos] = 1; recv_tx_pos++; if (recv_tx_pos == inflight_count) recv_tx_pos = 0; continue; } if (!recv_tx[recv_rx_pos]) { printf("RX before TX!\n"); print_frame(rx_frame.can_id, rx_frame.data, rx_frame.len, 0); running = 0; } /* compare with expected */ err = compare_frame(&tx_frames[recv_rx_pos], &rx_frame, 1); recv_rx_pos++; if (recv_rx_pos == inflight_count) recv_rx_pos = 0; loops++; if (test_loops && loops >= test_loops) break; unprocessed--; } } printf("\nTest messages sent and received: %d\n", loops); out_free: free(recv_tx); out_free_tx_frames: free(tx_frames); return err; } int main(int argc, char *argv[]) { struct sockaddr_can addr; char *intf_name; int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW; int echo_gen = 0; int opt, err; int enable_socket_option = 1; int filter = 0; signal(SIGTERM, signal_handler); signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); while ((opt = getopt(argc, argv, "bdef:gi:l:o:s:vx?")) != -1) { switch (opt) { case 'b': bit_rate_switch = 1; break; case 'd': is_can_fd = 1; break; case 'e': is_extended_frame_format = 1; break; case 'f': inflight_count = atoi(optarg); break; case 'g': echo_gen = 1; break; case 'i': can_id_ping = strtoul(optarg, NULL, 16); break; case 'l': test_loops = atoi(optarg); break; case 'o': can_id_pong = strtoul(optarg, NULL, 16); has_pong_id = 1; break; case 's': msg_len = atoi(optarg); break; case 'v': verbose++; break; case 'x': filter = 1; break; case '?': default: print_usage(basename(argv[0])); break; } } /* BRS can be enabled only if CAN FD is enabled */ if (bit_rate_switch && !is_can_fd) { printf("Bit rate switch (-b) needs CAN FD (-d) to be enabled\n"); return 1; } /* Make sure the message length is valid */ if (msg_len <= 0) { printf("Message length must > 0\n"); return 1; } if (is_can_fd) { if (msg_len > CANFD_MAX_DLEN) { printf("Message length must be <= %d bytes for CAN FD\n", CANFD_MAX_DLEN); return 1; } } else { if (msg_len > CAN_MAX_DLEN) { printf("Message length must be <= %d bytes for CAN 2.0B\n", CAN_MAX_DLEN); return 1; } } if (is_extended_frame_format) { can_id_ping &= CAN_EFF_MASK; can_id_ping |= CAN_EFF_FLAG; can_id_pong &= CAN_EFF_MASK; can_id_pong |= CAN_EFF_FLAG; } else { can_id_ping &= CAN_SFF_MASK; can_id_pong &= CAN_SFF_MASK; } if ((argc - optind) != 1) print_usage(basename(argv[0])); intf_name = argv[optind]; printf("interface = %s, family = %d, type = %d, proto = %d\n", intf_name, family, type, proto); if ((sockfd = socket(family, type, proto)) < 0) { perror("socket"); return 1; } if (echo_gen) { if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &enable_socket_option, sizeof(enable_socket_option)) == -1) { perror("setsockopt CAN_RAW_RECV_OWN_MSGS"); return 1; } } if (is_can_fd) { if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_socket_option, sizeof(enable_socket_option)) == -1) { perror("setsockopt CAN_RAW_FD_FRAMES"); return 1; } } addr.can_family = family; addr.can_ifindex = if_nametoindex(intf_name); if (!addr.can_ifindex) { perror("if_nametoindex"); close(sockfd); return 1; } if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(sockfd); return 1; } if (!has_pong_id) can_id_pong = can_id_ping + 1; if (filter) { const struct can_filter filters[] = { { .can_id = can_id_ping, .can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_SFF_MASK, }, { .can_id = can_id_pong, .can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_SFF_MASK, }, }; if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, filters, sizeof(struct can_filter) * (1 + echo_gen))) { perror("setsockopt()"); close(sockfd); return 1; } } if (echo_gen) err = can_echo_gen(); else err = can_echo_dut(); if (verbose) printf("Exiting...\n"); close(sockfd); if (exit_sig) return 128 + exit_sig; return err; }