diff --git a/.gitignore b/.gitignore index b51f1ca..7f41b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ GNUmakefile.in /slcan_attach /slcand /slcanpty +/testj1939 /can-utils-*.tar.bz2 /can-utils-*.tar.gz diff --git a/GNUmakefile.am b/GNUmakefile.am index a4c50a1..29b7570 100644 --- a/GNUmakefile.am +++ b/GNUmakefile.am @@ -63,7 +63,8 @@ bin_PROGRAMS = \ log2long \ slcan_attach \ slcand \ - slcanpty + slcanpty \ + testj1939 jacd_LDADD = libj1939.la jspy_LDADD = libj1939.la @@ -73,7 +74,9 @@ EXTRA_DIST = \ .travis.yml \ Android.mk \ README.md \ - autogen.sh + autogen.sh \ + can-j1939-kickstart.md + can-j1939.md MAINTAINERCLEANFILES = \ configure \ diff --git a/Makefile b/Makefile index b8853df..a63f72a 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ CPPFLAGS += -Iinclude \ PROGRAMS_ISOTP = isotpdump isotprecv isotpsend isotpsniffer isotptun isotpserver isotpperf PROGRAMS_CANGW = cangw PROGRAMS_SLCAN = slcan_attach slcand -PROGRAMS_J1939 = jacd jspy jsr +PROGRAMS_J1939 = jacd jspy jsr testj1939 PROGRAMS = can-calc-bit-timing candump cansniffer cansend canplayer cangen canbusload\ log2long log2asc asc2log\ canlogserver bcmserver\ diff --git a/can-j1939-kickstart.md b/can-j1939-kickstart.md new file mode 100644 index 0000000..7ea75ff --- /dev/null +++ b/can-j1939-kickstart.md @@ -0,0 +1,216 @@ +# Kickstart guide to can-j1939 on linux + +## Prepare using VCAN + +You may skip this step entirely if you have a functional +**can0** bus on your system. + +Load module, when *vcan* is not in-kernel + + modprobe vcan + +Create a virtual can0 device and start the device + + ip link add can0 type vcan + ip link set can0 up + +## First steps with j1939 + +Use [testj1939](testj1939.c) + +When *can-j1939* is compiled as module, opening a socket will load it, +__or__ you can load it manually + + modprobe can-j1939 + +Most of the subsequent examples will use 2 sockets programs (in 2 terminals). +One will use CAN_J1939 sockets using *testj1939*, +and the other will use CAN_RAW sockets using cansend+candump. + +testj1939 can be told to print the used API calls by adding **-v** program argument. + +### receive without source address + +Do in terminal 1 + + ./testj1939 -r can0: + +Send raw CAN in terminal 2 + + cansend can0 1823ff40#0123 + +You should have this output in terminal 1 + + 40 02300: 01 23 + +This means, from NAME 0, SA 40, PGN 02300 was received, +with 2 databytes, *01* & *02*. + +now emit this CAN message: + + cansend can0 18234140#0123 + +In J1939, this means that ECU 0x40 sends directly to ECU 0x41 +Since we did not bind to address 0x41, this traffic +is not meant for us and *testj1939* does not receive it. + +### Use source address + +Binding a can-j1939 socket to a source address will register +allow you to send packets. + + ./testj1939 can0:0x80 + +Your system had, for a small moment, source address 0x80 assigned. + +### receive with source address + +Terminal 1: + + ./testj1939 -r can0:0x80 + +Terminal 2: + + cansend can0 18238040#0123 + +Will emit this output + + 40 02300: 01 23 + +This is because the traffic had destination address __0x80__ . + +### send + +Open in terminal 1: + + candump -L can0 + +And to these test in another terminal + + ./testj1939 -s can0:0x80,0x3ffff + +This produces **1BFFFF80#0123456789ABCDEF** on CAN. + +### Multiple source addresses on 1 CAN device + + ./testj1939 -s can0:0x90,0x3ffff + +produces **1BFFFF90#0123456789ABCDEF** , + +### Use PDU1 PGN + + ./testj1939 -s can0:0x80,0x12345 + +emits **1923FF80#0123456789ABCDEF** . + +Note that the real PGN is **0x12300**, and destination address is **0xff**. + +### Use destination address info + +The destination field may be set during sendto(). +*testj1939* implements that like this + + ./testj1939 -s can0:0x80,0x12345 can0:0x40 + +emits **19234080#0123456789ABCDEF** . + +The destination CAN iface __must__ always match the source CAN iface. +Specifing one during bind is therefore sufficient. + + ./testj1939 -s can0:,0x12300 :0x40 + +emits the very same. + +### Emit different PGNs using the same socket + +The PGN is provided in both __bind( *sockname* )__ and +__sendto( *peername* )__ , and only one is used. +*peername* PGN has highest precedence. + +For broadcasted transmissions + + ./testj1939 -s can0:0x80,0x12300 :,0x32100 + +emits **1B21FF80#0123456789ABCDEF** rather than 1923FF80#012345678ABCDEF + +Desitination specific transmissions + + ./testj1939 -s can0:0x80,0x12300 :0x40,0x32100 + +emits **1B214080#0123456789ABCDEF** . + +It makes sometimes sense to omit the PGN in __bind( *sockname* )__ . + +### Larger packets + +J1939 transparently switches to *Transport Protocol* when packets +do not fit into single CAN packets. + + ./testj1939 -s20 can0:0x80 :,0x12300 + +emits: + + 18ECFF80#20140003FF002301 + +This is the first fragment for broadcasted *Transport Protocol*. +_testj1939_ returns before the subsequent packets can leave, and +as the last socket on the system closes, can-j1939 effectively +cleans up all resources. Real-world applications will run like forever, +and will not encounter this side-effect. + +Try again, and instruct _testj1939_ to keep the socket open for 1 second. + + ./testj1939 -w1.0 -s20 can0:0x80 :,0x12300 + +emits: + + 18ECFF80#20140003FF002301 + 18EBFF80#010123456789ABCD + 18EBFF80#02EF0123456789AB + 18EBFF80#03CDEF01234567 + +The fragments for broadcasted *Transport Protocol* are seperated +__50ms__ from each other. +Destination specific *Transport Protocol* applies flow control +and may emit CAN packets much faster. + +First assign 0x90 to the local system. +This becomes important because the kernel must interact in the +transport protocol sessions before the complete packet is delivered. + + ./testj1939 can0:0x90 -r & + +Now test: + + ./testj1939 -s20 can0:0x80 :0x90,0x12300 + +emits: + + 18EC9080#1014000303002301 + 18EC8090#110301FFFF002301 + 18EB9080#010123456789ABCD + 18EB9080#02EF0123456789AB + 18EB9080#03CDEF01234567 + 18EC8090#13140003FF002301 + +The flow control causes a bit overhead. +This overhead scales very good for larger J1939 packets. + +## Advanced topics with j1939 + +### Change priority of J1939 packets + + ./testj1939 -s can0:0x80,0x0100 + ./testj1939 -s -p3 can0:0x80,0x0200 + +emits + + 1801FF80#0123456789ABCDEF + 0C02FF80#0123456789ABCDEF + +### using connect + +### advanced filtering + +## dynamic addressing + diff --git a/can-j1939.md b/can-j1939.md new file mode 100644 index 0000000..3e8fcfb --- /dev/null +++ b/can-j1939.md @@ -0,0 +1,132 @@ +# CAN-J1939 on linux + +The [Kickstart guide is here](can-j1939-kickstart.md) + +## CAN on linux + +See [Wikipedia:socketcan](http://en.wikipedia.org/wiki/Socketcan) + +## J1939 networking in short + +* Add addressing on top of CAN (destination address & broadcast) + +* Any (max 1780) length packets. + Packets of 9 or more use **Transport Protocol** (fragmentation) + Such packets use different CANid for the same PGN. + +* only **29**bit, non-**RTR** CAN frames + +* CAN id is composed of + * 0..8: SA (source address) + * 9..26: + * PDU1: PGN+DA (destionation address) + * PDU2: PGN + * 27..29: PRIO + +* SA / DA may be dynamically assigned via j1939-81 + Fixed rules of precedence in Specification, no master necessary + +## J1939 on SocketCAN + +J1939 is *just another protocol* that fits +in the Berkely sockets. + + socket(AF_CAN, SOCK_DGRAM, CAN_J1939) + +## differences from CAN_RAW +### addressing + +SA, DA & PGN are used, not CAN id. + +Berkeley socket API is used to communicate these to userspace: + + * SA+PGN is put in sockname ([getsockname](http://man7.org/linux/man-pages/man2/getsockname.2.html)) + * DA+PGN is put in peername ([getpeername](http://man7.org/linux/man-pages/man2/getpeername.2.html)) + PGN is put in both structs + +PRIO is a datalink property, and irrelevant for interpretation +Therefore, PRIO is not in *sockname* or *peername*. + +The *data* that is [recv][recvfrom] or [send][sendto] is the real payload. +Unlike CAN_RAW, where addressing info is data. + +### Packet size + +J1939 handles packets of 8+ bytes with **Transport Protocol** fragmentation transparently. +No fixed data size is necessary. + + send(sock, data, 8, 0); + +will emit a single CAN frame. + + send(sock, data, 9, 0); + +will use fragementation, emitting 1+ CAN frames. + +## Enable j1939 (obsolete!) + +CAN has no protocol id field. +The can-j1939 stack only activates when a socket opens +for a network device. + +The methods described here existed in earlier implementations. + +### netlink + + ip link set can0 j1939 on + +This method is obsoleted in favor of _on socket connect_. + +### procfs for legacy kernel (2.6.25) + +This API is dropped for kernels with netlink support! + + echo can0 > /proc/net/can-j1939/net + +# Using J1939 + +## BSD socket implementation +* socket +* bind / connect +* recvfrom / sendto +* getsockname / getpeername + +## Modified *struct sockaddr_can* + + struct sockaddr_can { + sa_family_t can_family; + int can_ifindex; + union { + struct { + __u64 name; + __u32 pgn; + __u8 addr; + } j1939; + } can_addr; + } + +* *can_addr.j1939.pgn* is PGN + +* *can_addr.j1939.addr* & *can_addr.j1939.name* + determine the ECU + + * receiving address information, + *addr* is always set, + *name* is set when available. + + * When providing address information, + *name* != 0 indicates dynamic addressing + +## iproute2 (obsolete!) + +Older versions of can-j1939 used a modified iproute2 +for manipulating the kernel lists of current addresses. + +### Static addressing + + ip addr add j1939 0x80 dev can0 + +### Dynamic addressing + + ip addr add j1939 name 0x012345678abcdef dev can0 + diff --git a/page.theme b/page.theme new file mode 100644 index 0000000..e850a9b --- /dev/null +++ b/page.theme @@ -0,0 +1,21 @@ + + + + page: <?theme title?> + + + + +
+ +
+ + + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..6be67d2 --- /dev/null +++ b/style.css @@ -0,0 +1,68 @@ +* { + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; +} +body { + max-width: 60em; + margin: 0 auto; + color: #111; +} + +pre, code { + font-family: Monaco, Courier New, monospace; + font-size: 11px; +} + +h1 { + color: rgb(43,105,145); + font-weight: bold; + font-size: 40px; + letter-spacing: -1px; + margin-bottom: -5px; + margin: 0; +} +h1 code { + font-size: 32px; +} + +h2 { + color: rgb(43,105,145); + font-weight: bold; + margin-bottom: -5px; +} +h2 code { + font-size: 22px; +} + +h3 { + margin-bottom: -5px; +} +h3 code { + font-size: 16px; +} + +a { + color: blue; + text-decoration: none; +} +a:visited { + color: navy; +} +a:hover { + text-decoration: underline; +} + +pre { + border-width: 1px; + border-color: #777; + border-style: solid; + padding: 0.5em; + background-color: #ccc; + overflow: auto; + color: #000; + font-weight: bold; +} + +p, li { + font-size: 13px; + line-height: 18px; +} diff --git a/testj1939.c b/testj1939.c new file mode 100644 index 0000000..e602477 --- /dev/null +++ b/testj1939.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2013 EIA Electronics + * + * Authors: + * Kurt Van Dijck + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char help_msg[] = + "testj1939: demonstrate j1939 use\n" + "Usage: testj1939 FROM TO\n" + " FROM / TO - or [IFACE][:[SA][,[PGN][,NAME]]]\n" + "Options:\n" + " -v Print relevant API calls\n" + " -s[=LEN] Initial send of LEN bytes dummy data\n" + " -r Receive (and print) data\n" + " -e Echo incoming packets back\n" + " This atually receives packets\n" + " -c Issue connect()\n" + " -p=PRIO Set priority to PRIO\n" + " -n Emit 64bit NAMEs in output\n" + " -w[TIME] Return after TIME (default 1) seconds\n" + "\n" + "Example:\n" + "testj1939 can1 20\n" + "\n" + ; + +static const char optstring[] = "?vs::rep:cnw::"; + +static void parse_canaddr(char *spec, struct sockaddr_can *paddr) +{ + char *str; + + str = strsep(&spec, ":"); + if (strlen(str)) + paddr->can_ifindex = if_nametoindex(str); + + str = strsep(&spec, ","); + if (str && strlen(str)) + paddr->can_addr.j1939.addr = strtoul(str, NULL, 0); + + str = strsep(&spec, ","); + if (str && strlen(str)) + paddr->can_addr.j1939.pgn = strtoul(str, NULL, 0); + + str = strsep(&spec, ","); + if (str && strlen(str)) + paddr->can_addr.j1939.name = strtoul(str, NULL, 0); +} + +static const char *canaddr2str(const struct sockaddr_can *paddr) +{ + static char buf[128]; + char *str = buf; + char ifname[IF_NAMESIZE]; + + if (paddr->can_ifindex) + str += sprintf(str, "%s", if_indextoname(paddr->can_ifindex, ifname)); + *str++ = ':'; + + if (paddr->can_addr.j1939.addr != J1939_NO_ADDR) + str += sprintf(str, "%02x", paddr->can_addr.j1939.addr); + *str++ = ','; + if (paddr->can_addr.j1939.pgn != J1939_NO_PGN) + str += sprintf(str, "%05x", paddr->can_addr.j1939.pgn); + *str++ = ','; + if (paddr->can_addr.j1939.name != J1939_NO_NAME) + str += sprintf(str, "%016llx", paddr->can_addr.j1939.name); + *str++ = 0; + return buf; +} + +static void onsigalrm(int sig) +{ + error(0, 0, "exit as requested"); + exit(0); +} + +static void schedule_oneshot_itimer(double delay) +{ + struct itimerval it = {}; + + it.it_value.tv_sec = delay; + it.it_value.tv_usec = (long)(delay * 1e6) % 1000000; + if (setitimer(ITIMER_REAL, &it, NULL) < 0) + error(1, errno, "schedule itimer %.3lfs", delay); +} + +/* main */ +int main(int argc, char *argv[]) +{ + int ret, sock, opt, j, verbose; + socklen_t peernamelen; + struct sockaddr_can sockname = { + .can_family = AF_CAN, + .can_addr.j1939 = { + .addr = J1939_NO_ADDR, + .name = J1939_NO_NAME, + .pgn = J1939_NO_PGN, + }, + }, peername = { + .can_family = AF_CAN, + .can_addr.j1939 = { + .addr = J1939_NO_ADDR, + .name = J1939_NO_NAME, + .pgn = J1939_NO_PGN, + }, + }; + uint8_t dat[128]; + int valid_peername = 0; + int todo_send = 0, todo_recv = 0, todo_echo = 0, todo_prio = -1; + int todo_connect = 0, todo_names = 0, todo_wait = 0; + + /* argument parsing */ + while ((opt = getopt(argc, argv, optstring)) != -1) + switch (opt) { + case 'v': + verbose = 1; + break; + case 's': + todo_send = strtoul(optarg ?: "8", NULL, 0); + break; + case 'r': + todo_recv = 1; + break; + case 'e': + todo_echo = 1; + break; + case 'p': + todo_prio = strtoul(optarg, NULL, 0); + break; + case 'c': + todo_connect = 1; + break; + case 'n': + todo_names = 1; + break; + case 'w': + schedule_oneshot_itimer(strtod(optarg ?: "1", NULL)); + signal(SIGALRM, onsigalrm); + todo_wait = 1; + break; + default: + fputs(help_msg, stderr); + exit(1); + break; + } + + if (argv[optind]) { + if (strcmp("-", argv[optind])) + parse_canaddr(argv[optind], &sockname); + ++optind; + } + + if (argv[optind]) { + if (strcmp("-", argv[optind])) { + parse_canaddr(argv[optind], &peername); + valid_peername = 1; + } + ++optind; + } + + /* open socket */ + if (verbose) + fprintf(stderr, "- socket(PF_CAN, SOCK_DGRAM, CAN_J1939);\n"); + sock = ret = socket(PF_CAN, SOCK_DGRAM, CAN_J1939); + if (ret < 0) + error(1, errno, "socket(j1939)"); + + if (todo_prio >= 0) { + if (verbose) + fprintf(stderr, "- setsockopt(, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &%i);\n", todo_prio); + ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO, + &todo_prio, sizeof(todo_prio)); + if (ret < 0) + error(1, errno, "set priority %i", todo_prio); + } + + if (verbose) + fprintf(stderr, "- bind(, %s, %zi);\n", canaddr2str(&sockname), sizeof(sockname)); + ret = bind(sock, (void *)&sockname, sizeof(sockname)); + if (ret < 0) + error(1, errno, "bind()"); + + if (todo_connect) { + if (!valid_peername) + error(1, 0, "no peername supplied"); + if (verbose) + fprintf(stderr, "- connect(, %s, %zi);\n", canaddr2str(&peername), sizeof(peername)); + ret = connect(sock, (void *)&peername, sizeof(peername)); + if (ret < 0) + error(1, errno, "connect()"); + } + + if (todo_send) { + /* initialize test vector */ + for (j = 0; j < sizeof(dat); ++j) + dat[j] = ((2*j) << 4) + ((2*j+1) & 0xf); + + /* send data */ + /* + * when using connect, do not provide additional + * destination information and use send() + */ + if (valid_peername && !todo_connect) { + if (verbose) + fprintf(stderr, "- sendto(, , %i, 0, %s, %zi);\n", todo_send, canaddr2str(&peername), sizeof(peername)); + ret = sendto(sock, dat, todo_send, 0, + (void *)&peername, sizeof(peername)); + } else { + /* + * we may do sendto(sock, dat, todo_send, 0, NULL, 0) + * as well, but using send() demonstrates the API better + */ + if (verbose) + fprintf(stderr, "- send(, , %i, 0);\n", todo_send); + ret = send(sock, dat, todo_send, 0); + } + + if (ret < 0) + error(1, errno, "sendto"); + } + + /* main loop */ + if ((todo_echo || todo_recv) && verbose) + fprintf(stderr, "- while (1)\n"); + while (todo_echo || todo_recv) { + /* + * re-use peername for storing the sender's peername of + * received packets + */ + if (verbose) + fprintf(stderr, "- recvfrom(, , %zi, 0, &, %zi);\n", sizeof(peername), sizeof(peername)); + peernamelen = sizeof(peername); + ret = recvfrom(sock, dat, sizeof(dat), 0, + (void *)&peername, &peernamelen); + if (ret < 0) { + if (EINTR == errno) { + if (verbose) + fprintf(stderr, "-\t\n"); + continue; + } + error(1, errno, "recvfrom()"); + } + + if (todo_echo) { + if (verbose) + fprintf(stderr, "- sendto(, , %i, 0, %s, %i);\n", ret, canaddr2str(&peername), peernamelen); + ret = sendto(sock, dat, ret, 0, + (void *)&peername, peernamelen); + if (ret < 0) + error(1, errno, "sendto"); + } + if (todo_recv) { + if (todo_names && peername.can_addr.j1939.name) + printf("%016llx ", peername.can_addr.j1939.name); + printf("%02x %05x:", peername.can_addr.j1939.addr, + peername.can_addr.j1939.pgn); + for (j = 0; j < ret; ++j) + printf(" %02x", dat[j]); + printf("\n"); + } + } + if (todo_wait) + for (;;) + sleep(1); + return 0; +} +