2 * Copyright 2015-2016 Max Staudt
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License 2 as published
6 * by the Free Software Foundation.
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <linux/can.h>
19 #include <linux/can/raw.h>
21 #include <sys/ioctl.h>
27 #include "vw-nm-tools.h"
31 static int nm_is_rx_frame_valid(struct NM_Main *nm, struct can_frame *frame)
33 if (frame->can_dlc < 2) {
34 printf("Skipping short frame from CAN ID %03x\n", frame->can_id);
38 if ((frame->can_id & ~(nm->max_nodes - 1)) != nm->can_base) {
39 printf("Skipping non-NM from CAN ID %03x\n", frame->can_id);
48 static void nm_set_timer_now(struct NM_Main *nm) {
51 nm->timer_reason = NM_TIMER_NOW;
54 static void nm_set_timer_normal(struct NM_Main *nm) {
56 nm->tv.tv_usec = NM_USECS_NORMAL_TURN;
57 nm->timer_reason = NM_TIMER_NORMAL;
60 static void nm_set_timer_awol(struct NM_Main *nm) {
62 nm->tv.tv_usec = NM_USECS_NODE_AWOL;
63 nm->timer_reason = NM_TIMER_AWOL;
67 static void nm_set_timer_limphome(struct NM_Main *nm) {
69 nm->tv.tv_usec = NM_USECS_LIMPHOME;
70 nm->timer_reason = NM_TIMER_LIMPHOME;
75 static void nm_update_my_next_id(struct NM_Main *nm) {
76 unsigned id = nm->my_id;
82 if (id >= nm->max_nodes) {
86 state = nm->nodes[id].state & NM_MAIN_MASK;
88 if (state == NM_MAIN_ON || state == NM_MAIN_LOGIN) {
89 /* We skip limp home nodes */
90 nm->nodes[nm->my_id].next = id;
93 } while (id != nm->my_id);
95 if (nm->nodes[nm->my_id].next == nm->my_id) {
96 /* Uh oh, we're the only one left. */
99 nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
101 /* TODO: Timeout 140ms (RCD 310) */
107 static void nm_handle_can_frame(struct NM_Main *nm, struct can_frame *frame)
112 /* Is this a valid frame within our logical network? */
113 if (!nm_is_rx_frame_valid(nm, frame)) {
117 printf("Received NM frame from CAN ID %03x\n", frame->can_id);
120 /* Parse sender, its perceived successor, and its state */
121 sender = frame->can_id & (nm->max_nodes - 1);
122 next = frame->data[0];
123 state = frame->data[1];
125 /* TODO: Validate state, it needs to be within the enum */
127 /* Skip our own frames */
128 if (sender == nm->my_id) {
132 nm->nodes[sender].next = next;
133 nm->nodes[sender].state = state;
135 switch (state & NM_MAIN_MASK) {
137 if (next == nm->nodes[nm->my_id].next
138 && nm->nodes[nm->my_id].next != nm->my_id) {
139 /* sender doesn't know we exist */
141 nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
143 nm_set_timer_now(nm);
145 /* IMPORTANT: The caller needs to check for
146 * timeouts first, i.e. no other NM frames
147 * are received until our correcting login
150 } else if (next == nm->nodes[nm->my_id].next) {
151 /* where (nm->nodes[nm->my_id].next == nm->my_id) */
153 /* It can happen when:
154 * - our sent frames don't go anywhere
155 * - we just logged in and immediately
156 * afterwards another ECU sent a regular
160 /* Let's handle this just like a LOGIN, since
161 * we're learning about a new device.
162 * See case NM_MAIN_LOGIN below for details.
165 nm_update_my_next_id(nm);
166 nm->nodes[nm->my_id].state = NM_MAIN_ON;
167 } else if (next == nm->my_id) {
169 * Reset the timeout so anyone we missed
170 * can send its login frame to correct us.
172 nm_set_timer_normal(nm);
174 /* We just got some random ON message.
175 * Reset the timer looking out for broken
178 nm_set_timer_awol(nm);
182 /* Note: sender != nm->my_id */
184 nm_update_my_next_id(nm);
186 /* We're not alone anymore, so let's change state. */
187 nm->nodes[nm->my_id].state = NM_MAIN_ON;
189 /* We don't reset the timeout when somebody logs in,
190 * i.e. (next == sender).
191 * Instead, we'll simply include them in the next
194 /* Actually, when a login is done as a correction,
195 * we do reset the timeout.
197 if (next != sender) {
198 nm_set_timer_awol(nm);
201 case NM_MAIN_LIMPHOME:
202 nm_update_my_next_id(nm);
213 static void nm_buildframe(struct NM_Main *nm, struct can_frame *frame)
215 frame->can_id = nm->can_base + nm->my_id;
217 frame->data[0] = nm->nodes[nm->my_id].next;
218 frame->data[1] = nm->nodes[nm->my_id].state;
224 static void nm_timeout_callback(struct NM_Main *nm, struct can_frame *frame)
226 nm_set_timer_awol(nm);
228 nm_buildframe(nm, frame);
234 static void nm_start(struct NM_Main *nm, struct can_frame *frame)
237 nm->tv.tv_usec = NM_USECS_NODE_AWOL;
241 nm->nodes[nm->my_id].next = nm->my_id;
242 nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
244 nm_buildframe(nm, frame);
250 static int net_init(char *ifname)
254 struct sockaddr_can addr;
256 struct can_filter fi;
258 s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
264 /* Convert interface name to index */
265 memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
266 strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
267 if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
268 perror("SIOCGIFINDEX");
272 /* Open the CAN interface */
273 memset(&addr, 0, sizeof(addr));
274 addr.can_family = AF_CAN;
275 addr.can_ifindex = ifr.ifr_ifindex;
276 if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
281 recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */
282 setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
283 &recv_own_msgs, sizeof(recv_own_msgs));
285 /* Handle only 32 NM IDs at CAN base ID 0x420 */
289 setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &fi, sizeof(struct can_filter));
295 int main(int argc, char **argv)
303 printf("syntax: %s IFNAME MY_ID\n", argv[0]);
307 my_id = strtoul(argv[2], NULL, 0);
309 nm = nm_alloc(5, my_id, 0x420);
311 printf("Out of memory allocating NM struct.\n");
315 s = net_init(argv[1]);
317 /* Stir up the hornet's nest */
319 struct can_frame frame;
321 nm_start(nm, &frame);
331 retval = select(s+1, &rdfs, NULL, NULL, &nm->tv);
332 /* We currently rely on Linux timeout behavior here,
333 * i.e. the timeout now reflects the remaining time */
337 } else if (!retval) {
338 /* Timeout, we NEED to check this first */
339 struct can_frame frame;
341 nm_timeout_callback(nm, &frame);
343 } else if (FD_ISSET(s, &rdfs)) {
344 struct can_frame frame;
347 ret = read(s, &frame, sizeof(frame));
349 perror("recvfrom CAN socket");
353 nm_handle_can_frame(nm, &frame);