Timers: Simplify reset and reset out of limp home
[revag-nm.git] / vw-nm.c
diff --git a/vw-nm.c b/vw-nm.c
index 6bdf20e29bebfc399b6a1c63040105fa0cf563a4..a181fce00866871671c1bd9f3dca7cdac0c15b08 100644 (file)
--- a/vw-nm.c
+++ b/vw-nm.c
 
 
 
+static void nm_update_my_next_id(struct NM_Main *nm) {
+       unsigned id = nm->my_id;
 
+       do {
+               NM_State state;
 
-static int nm_handle_can_frame(struct NM_Main *nm, struct can_frame *frame)
-{
-       NM_ID id;
-       NM_ID next;
-       NM_State state;
-       int its_my_turn = 0;
+               id++;
+               if (id >= nm->max_nodes) {
+                       id = 0;
+               }
 
-       if (frame->can_dlc < 2) {
-               printf("Skipping short frame from CAN ID %03x\n", frame->can_id);
-               return 0;
-       }
+               state = nm->nodes[id].state & NM_MAIN_MASK;
+
+               if (state == NM_MAIN_ON || state == NM_MAIN_LOGIN) {
+                       /* We skip limp home nodes */
+                       nm->nodes[nm->my_id].next = id;
+                       break;
+               }
+       } while (id != nm->my_id);
+}
 
 
-       if ((frame->can_id & ~(nm->max_nodes - 1)) != nm->can_base) {
-               printf("Skipping non-NM from CAN ID %03x\n", frame->can_id);
-               return 0;
+
+static void nm_handle_can_frame(struct NM_Main *nm, struct can_frame *frame)
+{
+       NM_ID sender, next;
+       NM_State state;
+
+       /* Is this a valid frame within our logical network? */
+       if (!nm_is_rx_frame_valid(nm, frame)) {
+               return;
        }
 
        printf("Received NM frame from CAN ID %03x\n", frame->can_id);
 
-       id = frame->can_id & (nm->max_nodes - 1);
+
+       /* Parse sender, its perceived successor, and its state */
+       sender = frame->can_id & (nm->max_nodes - 1);
        next = frame->data[0];
        state = frame->data[1];
 
-       nm->nodes[id].next = (state & NM_MAIN_MASK) == NM_MAIN_ON
-                               ? next
-                               : 0xff;
-       nm->nodes[id].state = state;
+       /* TODO: Validate state, it needs to be within the enum */
+
+       /* Skip our own frames */
+       if (sender == nm->my_id) {
+               return;
+       }
+
+       /* If we're currently stuck in Limp Home mode, and we can see
+        * someone else's messages, reset and re-login.
+        */
+       if (nm->nodes[nm->my_id].state == NM_MAIN_LIMPHOME) {
+               nm_reset(nm);
+               return;
+       }
+
+       nm->nodes[sender].next = next;
+       nm->nodes[sender].state = state;
+
+       /* Update our view of the world */
+       nm_update_my_next_id(nm);
 
        switch (state & NM_MAIN_MASK) {
                case NM_MAIN_ON:
-                       if (next == nm->my_id) {
-                               its_my_turn = 1;
+                       /* We're not alone, so let's transition to ON for now.
+                        */
+                       nm->nodes[nm->my_id].state = NM_MAIN_ON;
+
+                       /* The AWOL timeout is ONLY reset on
+                        * NM_MAIN_ON messages.
+                        */
+                       nm_set_timer_awol(nm);
+
+                       if (next == nm->nodes[nm->my_id].next
+                               && nm->nodes[nm->my_id].next != nm->my_id) {
+                               /* Sender doesn't know we exist */
+
+                               nm->nodes[nm->my_id].state = NM_MAIN_LOGIN;
+
+                               nm_set_timer_now(nm);
+
+                               /* IMPORTANT: The caller needs to check for
+                                * timeouts first, i.e. no other NM frames
+                                * are received until our correcting login
+                                * has been sent.
+                                */
+                       } else if (next == nm->nodes[nm->my_id].next) {
+                               /* where (nm->nodes[nm->my_id].next == nm->my_id) */
+
+                               /* It can happen when:
+                                *  - our sent frames don't go anywhere
+                                *  - we just logged in and immediately
+                                *    afterwards another ECU sent a regular
+                                *    NM frame.
+                                */
+
+                               /* Nothing to do. */
+                       } else if (next == nm->my_id) {
+                               /* It's our turn, do a normal timeout.
+                                * This is a period in which anyone we missed
+                                * can send its re-login frame to correct us.
+                                */
+
+                               nm_set_timer_normal(nm);
+                       } else {
+                               /* We just received a random ON message. */
+
+                               /* Nothing to do. */
                        }
                        break;
                case NM_MAIN_LOGIN:
-                       if (id == nm->my_id) {
-                               break;
-                       }
-                       printf("Handling LOGIN\n");
-                       printf("Testing %x < %x\n", id, nm->nodes[nm->my_id].next);
-                       if (id < nm->nodes[nm->my_id].next) {
-                               nm->nodes[nm->my_id].next = id;
-                       }
+                       /* Note: sender != nm->my_id */
+
+                       /* We're not alone anymore, so let's change state. */
+                       nm->nodes[nm->my_id].state = NM_MAIN_ON;
+
+                       /* We don't reset the timeout when somebody logs in.
+                        * Instead, we'll simply include them in the next
+                        * round.
+                        */
+
+                       /* Nothing else to do. */
+                       break;
+               case NM_MAIN_LIMPHOME:
+                       /* Nothing we can do. Poor guy. */
                        break;
        }
 
        nm_dump_all(nm);
-
-       return its_my_turn;
 }
 
 
 
 
-static NM_ID nm_my_next_id(struct NM_Main *nm) {
-       unsigned id;
-
-       if (nm->max_nodes < 2
-               || (nm->nodes[nm->my_id].state & NM_MAIN_MASK) != NM_MAIN_ON) {
-               assert(0);
-       }
-
-       id = nm->my_id;
-       do {
-               struct NM_Node *node;
-
-               id++;
-               if (id >= nm->max_nodes) {
-                       id = 0;
-               }
-               node = &nm->nodes[id];
 
-               if ((node->state & NM_MAIN_MASK) == NM_MAIN_ON) {
-                       return id;
-               }
-       } while (id != nm->my_id);
 
-       /* This is never reached */
-       assert(0);
-       return -1;
+static void nm_buildframe(struct NM_Main *nm, struct can_frame *frame)
+{
+       frame->can_id = nm->can_base + nm->my_id;
+       frame->can_dlc = 2;
+       frame->data[0] = nm->nodes[nm->my_id].next;
+       frame->data[1] = nm->nodes[nm->my_id].state;
 }
 
 
 
 
-static void nm_timeout_callback(struct NM_Main *nm, struct can_frame *frame) {
-       nm->nodes[nm->my_id].next = nm_my_next_id(nm);
+static void nm_timeout_callback(struct NM_Main *nm, struct can_frame *frame)
+{
+       nm_buildframe(nm, frame);
 
-       frame->can_id = nm->can_base + nm->my_id;
-       frame->can_dlc = 2;
-       frame->data[0] = nm->nodes[nm->my_id].next;
-       frame->data[1] = NM_MAIN_ON;
+       switch(nm->timer_reason) {
+               case NM_TIMER_NOW:
+                       /* We're due to log in */
+
+                       /* We're going to be ready, let's
+                        * change state (RCD 310 behavior)
+                        */
+                       nm->nodes[nm->my_id].state = NM_MAIN_ON;
+                       nm_set_timer_normal(nm);
+                       break;
+               case NM_TIMER_NORMAL:
+                       /* We're due to send our own ring message */
+                       switch(nm->nodes[nm->my_id].state & NM_MAIN_MASK) {
+                               case NM_MAIN_ON:
+                                       break;
+                               default:
+                                       printf("BUG: TIMER_NORMAL expired in non-ON state\n");
+                                       break;
+                       }
+                       nm_set_timer_awol(nm);
+                       break;
+               case NM_TIMER_AWOL:
+                       /* The network is silent because a node disappeared
+                        * or something bad happened.
+                        * Reset everything and start over.
+                        */
+                       nm_reset(nm);
+                       break;
+               case NM_TIMER_LIMPHOME:
+                       nm_set_timer_limphome(nm);
+                       break;
+       }
 }
 
 
@@ -178,16 +266,18 @@ static int net_init(char *ifname)
 int main(int argc, char **argv)
 {
        struct NM_Main *nm;
-       struct timeval tv, *next_tv = NULL;
        fd_set rdfs;
        int s;
+       NM_ID my_id;
 
-       if (argc != 2) {
-               printf("syntax: %s IFNAME\n", argv[0]);
+       if (argc != 3) {
+               printf("syntax: %s IFNAME MY_ID\n", argv[0]);
                return 1;
        }
 
-       nm = nm_alloc(5, 0x0b, 0x420);
+       my_id = strtoul(argv[2], NULL, 0);
+
+       nm = nm_alloc(5, my_id, 0x420);
        if (!nm) {
                printf("Out of memory allocating NM struct.\n");
                return 1;
@@ -201,20 +291,18 @@ int main(int argc, char **argv)
                FD_ZERO(&rdfs);
                FD_SET(s, &rdfs);
 
-               retval = select(s+1, &rdfs, NULL, NULL, next_tv);
+               retval = select(s+1, &rdfs, NULL, NULL, &nm->tv);
                /* We currently rely on Linux timeout behavior here,
                 * i.e. the timeout now reflects the remaining time */
                if (retval < 0) {
                        perror("select");
                        return 1;
                } else if (!retval) {
-                       /* Timeout */
+                       /* Timeout, we NEED to check this first */
                        struct can_frame frame;
 
                        nm_timeout_callback(nm, &frame);
                        can_tx(s, &frame);
-
-                       next_tv = NULL;
                } else if (FD_ISSET(s, &rdfs)) {
                        struct can_frame frame;
                        ssize_t ret;
@@ -225,16 +313,13 @@ int main(int argc, char **argv)
                                return 1;
                        }
 
-                       if (nm_handle_can_frame(nm, &frame)) {
-                               tv.tv_sec = 0;
-                               tv.tv_usec = 400000;
-                               next_tv = &tv;
-                       }
+                       nm_handle_can_frame(nm, &frame);
                        continue;
                }
        }
 
        nm_free(nm);
+       close(s);
 
        return 0;
 }