import sample program & help page

pull/66/head
Kurt Van Dijck 2013-11-18 23:30:46 +01:00
parent fe748b1c2c
commit 198c5801ec
5 changed files with 627 additions and 0 deletions

11
Makefile 100644
View File

@ -0,0 +1,11 @@
OUTPUT = j1939.html testj1939
default: $(OUTPUT)
%.html: %.page page.theme
theme -f -o $@ $< -p "$*"
CFLAGS = -Wall -g3 -O0
clean:
rm -f $(wildcard *.o) $(OUTPUT)

327
j1939.page 100644
View File

@ -0,0 +1,327 @@
# CAN-J1939 on linux
## 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
CAN has no protocol id field.
Enabling protocols must be done manually
### netlink
ip link set can0 j1939 on
### 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
### Static addressing
ip addr add j1939 0x80 dev can0
### Dynamic addressing
ip addr add j1939 name 0x012345678abcdef dev can0
# First steps with j1939
Use [testj1939](testj1939.c)
Load modules, when vcan & can-j1939 are not in-kernel
modprobe vcan
modprobe can-j1939
### create virtual CAN bus
Make sure *can0* is available, or replace *can0* with *vcan0*
ip link add can0 type vcan
### enable CAN
ip link set can0 up
### enable j1939
modprobe can-j1939
### 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.
### Use source address
./testj1939 can0:0x80
will say
./testj1939: bind(): Cannot assign requested address
Since J1939 maintains addressing, **0x80** has not yet been assigned
as an address on **can0** . This behaviour is very similar to IP
addressing: you cannot bind to an address that is not your own.
Now tell the kernel that we *own* address 0x80.
It will be available from now on.
ip addr add j1939 0x80 dev can0
./testj1939 can0:0x80
now succeeds.
### 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
This produces **1BFFFF80#0123456789ABCDEF** on CAN.
### Multiple source addresses on 1 CAN device
ip addr add j1939 0x90 dev can0
./testj1939 -s can0:0x90
produces **1BFFFF90#0123456789ABCDEF** ,
./testj1939 -s can0:
still produces **1BFFFF80#0123456789ABCDEF** , since **0x80**
is the default _source address_.
Check
ip addr show can0
emits
X: can0: <NOARP,UP,LOWER_UP> mtu 16 qdisc noqueue state UNKNOWN
link/can
can-j1939 0x80 scope link
can-j1939 0x90 scope link
0x80 is the first address on can0.
### Use specific PGN
./testj1939 -s can0:,0x12345
emits **1923FF80#0123456789ABCDEF** .
Note that the real PGN is **0x12300**, and destination address is **0xff**.
### Emit destination specific packets
The destination field may be set during sendto().
*testj1939* implements that like this
./testj1939 -s can0:,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.
./testj1939 -s can0:,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
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.
./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
## dynamic addressing

21
page.theme 100644
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>page: <?theme title?></title>
<link rel="stylesheet" type="text/css" href="style.css">
<style>
div#toc li {
list-style : none;
}
</style>
</head>
<body>
<div id='toc'>
<?theme toc?>
</div>
<?theme body?>
</div>
</body>
</html>

68
style.css 100644
View File

@ -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;
}

200
testj1939.c 100644
View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 2013 EIA Electronics
*
* Authors:
* Kurt Van Dijck <kurt.van.dijck@eia.be>
*
* 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 <signal.h>
#include <time.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <error.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/j1939.h>
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"
" -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"
"\n"
"Example:\n"
"testj1939 can1 20\n"
"\n"
;
static const char optstring[] = "?vs::rep:cn";
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);
}
/* main */
int main(int argc, char *argv[])
{
int ret, sock, opt, j;
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 todo_send = 0, todo_recv = 0, todo_echo = 0, todo_prio = -1;
int todo_connect = 0, todo_names = 0;
/* argument parsing */
while ((opt = getopt(argc, argv, optstring)) != -1)
switch (opt) {
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;
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);
++optind;
}
/* open socket */
sock = ret = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
if (ret < 0)
error(1, errno, "socket(j1939)");
if (todo_prio >= 0) {
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);
}
ret = bind(sock, (void *)&sockname, sizeof(sockname));
if (ret < 0)
error(1, errno, "bind()");
if (todo_connect) {
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 */
ret = sendto(sock, dat, todo_send, 0,
(void *)&peername, sizeof(peername));
if (ret < 0)
error(1, errno, "sendto");
}
/* main loop */
while (todo_echo || todo_recv) {
/*
* re-use peername for storing the sender's peername of
* received packets
*/
peernamelen = sizeof(peername);
ret = recvfrom(sock, dat, sizeof(dat), 0,
(void *)&peername, &peernamelen);
if (ret < 0) {
if (EINTR == errno)
continue;
error(1, errno, "recvfrom()");
}
if (todo_echo) {
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");
}
}
return 0;
}