50863aac7e8fde2c932163aa927ffc48eaaa8f89
[revag-nm.git] / vw-nm.c
1 /*
2  * Copyright 2015-2016 Max Staudt
3  *
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.
7  */
8
9 #include <assert.h>
10
11 #include <stdio.h>
12 #include <stdint.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <linux/can.h>
19 #include <linux/can/raw.h>
20 #include <net/if.h>
21 #include <sys/ioctl.h>
22 #include <endian.h>
23 #include <sys/time.h>
24
25
26 #include "vw-nm.h"
27 #include "vw-nm-tools.h"
28
29
30
31 static int nm_is_rx_frame_valid(struct NM_Main *nm, struct can_frame *frame)
32 {
33         if (frame->can_dlc < 2) {
34                 printf("Skipping short frame from CAN ID %03x\n", frame->can_id);
35                 return 0;
36         }
37
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);
40                 return 0;
41         }
42
43         return 1;
44 }
45
46
47
48
49 static void nm_update_my_next_id(struct NM_Main *nm) {
50         unsigned id = nm->my_id;
51
52         do {
53                 NM_State state;
54
55                 id++;
56                 if (id >= nm->max_nodes) {
57                         id = 0;
58                 }
59
60                 state = nm->nodes[id].state & NM_MAIN_MASK;
61
62                 if (state == NM_MAIN_ON || state == NM_MAIN_LOGIN) {
63                         /* TODO: Check for limp home nodes? */
64                         nm->nodes[nm->my_id].next = id;
65                         break;
66                 }
67         } while (id != nm->my_id);
68 }
69
70
71
72 static void nm_handle_can_frame(struct NM_Main *nm, struct can_frame *frame)
73 {
74         NM_ID sender, next;
75         NM_State state;
76
77         /* Is this a valid frame within our logical network? */
78         if (!nm_is_rx_frame_valid(nm, frame)) {
79                 return;
80         }
81
82         printf("Received NM frame from CAN ID %03x\n", frame->can_id);
83
84
85         /* Parse sender, its perceived successor, and its state */
86         sender = frame->can_id & (nm->max_nodes - 1);
87         next = frame->data[0];
88         state = frame->data[1];
89
90         /* TODO: Validate state, it needs to be within the enum */
91
92         /* Skip our own frames */
93         if (sender == nm->my_id) {
94                 return;
95         }
96
97         nm->nodes[sender].next = next;
98         nm->nodes[sender].state = state;
99
100         switch (state & NM_MAIN_MASK) {
101                 case NM_MAIN_ON:
102                         if (next == nm->nodes[nm->my_id].next
103                                 && nm->nodes[nm->my_id].next != nm->my_id) {
104                                 /* sender doesn't know we exist */
105
106                                 nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
107
108                                 nm->tv.tv_sec = 0;
109                                 nm->tv.tv_usec = 0;
110                                 /* IMPORTANT: The caller needs to check for
111                                  * timeouts first, so no other NM frames are
112                                  * received until our correcting login has
113                                  * been sent.
114                                  */
115                         } else if (next == nm->nodes[nm->my_id].next) {
116                                 /* where nm->nodes[nm->my_id].next == nm->my_id */
117
118                                 /* TODO: Is this a case we need to handle? */
119
120                                 /* It can happen when:
121                                  *  - our sent frames don't go anywhere
122                                  *  - we just logged in and immediately
123                                  *    afterwards another ECU sent a regular
124                                  *    NM frame without knowing that we exist.
125                                  */
126                         } else if (next == nm->my_id) {
127                                 /* It's our turn.
128                                  * Reset the timeout so anyone we missed
129                                  * can send its login frame to correct us.
130                                  */
131                                 nm->tv.tv_sec = 0;
132                                 nm->tv.tv_usec = NM_USECS_MY_TURN;
133                         } else {
134                                 /* We just got some random ON message.
135                                  * Reset the timer looking out for broken
136                                  * connectivity.
137                                  */
138                                 nm->tv.tv_sec = 0;
139                                 nm->tv.tv_usec = NM_USECS_LIMP_HOME;
140                         }
141                         break;
142                 case NM_MAIN_LOGIN:
143                         /* Note: sender != nm->my_id */
144
145                         printf("Handling LOGIN\n");
146
147                         nm_update_my_next_id(nm);
148
149                         /* We're not alone anymore, so let's change state. */
150                         nm->nodes[nm->my_id].state = NM_MAIN_ON;
151
152                         /* We don't reset the timeout when somebody logs in.
153                          * Instead, we'll simply include them in the next
154                          * round. */
155
156                         /* Actually, when a login is done as a correction,
157                          * we do reset the timeout.
158                          *
159                          * TODO.
160                          */
161                         break;
162         }
163
164         nm_dump_all(nm);
165 }
166
167
168
169
170
171
172 static void nm_buildframe(struct NM_Main *nm, struct can_frame *frame)
173 {
174         frame->can_id = nm->can_base + nm->my_id;
175         frame->can_dlc = 2;
176         frame->data[0] = nm->nodes[nm->my_id].next;
177         frame->data[1] = nm->nodes[nm->my_id].state;
178 }
179
180
181
182
183 static void nm_timeout_callback(struct NM_Main *nm, struct can_frame *frame)
184 {
185         nm->tv.tv_sec = 0;
186         nm->tv.tv_usec = NM_USECS_LIMP_HOME;
187
188         nm_buildframe(nm, frame);
189 }
190
191
192
193
194 static void nm_start(struct NM_Main *nm, struct can_frame *frame)
195 {
196         nm->tv.tv_sec = 0;
197         nm->tv.tv_usec = 50000;
198
199
200
201         nm->nodes[nm->my_id].next = nm->my_id;
202         nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
203
204         nm_buildframe(nm, frame);
205 }
206
207
208
209
210 static int net_init(char *ifname)
211 {
212         int s;
213         int recv_own_msgs;
214         struct sockaddr_can addr;
215         struct ifreq ifr;
216         struct can_filter fi;
217
218         s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
219         if (s < 0) {
220                 perror("socket");
221                 exit(1);
222         }
223
224         /* Convert interface name to index */
225         memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
226         strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
227         if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
228                 perror("SIOCGIFINDEX");
229                 exit(1);
230         }
231
232         /* Open the CAN interface */
233         memset(&addr, 0, sizeof(addr));
234         addr.can_family = AF_CAN;
235         addr.can_ifindex = ifr.ifr_ifindex;
236         if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
237                 perror("bind");
238                 return 0;
239         }
240
241         recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */
242         setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
243                         &recv_own_msgs, sizeof(recv_own_msgs));
244
245         /* Handle only 32 NM IDs at CAN base ID 0x420 */
246         fi.can_id   = 0x420;
247         fi.can_mask = 0x7E0;
248
249         setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &fi, sizeof(struct can_filter));
250
251         return s;
252 }
253
254
255 int main(int argc, char **argv)
256 {
257         struct NM_Main *nm;
258         fd_set rdfs;
259         int s;
260
261         if (argc != 2) {
262                 printf("syntax: %s IFNAME\n", argv[0]);
263                 return 1;
264         }
265
266         nm = nm_alloc(5, 0x0b, 0x420);
267         if (!nm) {
268                 printf("Out of memory allocating NM struct.\n");
269                 return 1;
270         }
271
272         s = net_init(argv[1]);
273
274         /* Stir up the hornet's nest */
275         if (1) {
276                 struct can_frame frame;
277
278                 nm_start(nm, &frame);
279                 can_tx(s, &frame);
280         }
281
282         while (1) {
283                 int retval;
284
285                 FD_ZERO(&rdfs);
286                 FD_SET(s, &rdfs);
287
288                 retval = select(s+1, &rdfs, NULL, NULL, &nm->tv);
289                 /* We currently rely on Linux timeout behavior here,
290                  * i.e. the timeout now reflects the remaining time */
291                 if (retval < 0) {
292                         perror("select");
293                         return 1;
294                 } else if (!retval) {
295                         /* Timeout, we NEED to check this first */
296                         struct can_frame frame;
297
298                         nm_timeout_callback(nm, &frame);
299                         can_tx(s, &frame);
300                 } else if (FD_ISSET(s, &rdfs)) {
301                         struct can_frame frame;
302                         ssize_t ret;
303
304                         ret = read(s, &frame, sizeof(frame));
305                         if (ret < 0) {
306                                 perror("recvfrom CAN socket");
307                                 return 1;
308                         }
309
310                         nm_handle_can_frame(nm, &frame);
311                         continue;
312                 }
313         }
314
315         nm_free(nm);
316
317         return 0;
318 }