Author: James G. Smith <jsmith@ecoscentric.com>
Date:   Thu Sep 27 16:14:20 BST 2012

    Timeouts: Provide an alternative "absolute end-time" based timeout model to
    avoid the bug with the previous delta based implementation where timeout
    handlers could drift so badly they affect correct operation (e.g. short
    lease DHCP renewal being missed).

    The changes implemented by this patch do NOT affect the source if the
    relevant manifests are not defined in the system specific "lwipopts.h"
    header file.

    To use the new support the following (example) "lwipopts.h" settings could
    be used:

	/* ---------- Timeout options ---------- */

	/* SYS_TIMEOUT_ENDTIME: Force use of the ENDTIME implementation. */
	#define SYS_TIMEOUT_ENDTIME     1
	#define SYS_TIMEOUT_DELTA       0

	/* SYS_TIME_TYPE is defined if we want to over-ride the default lwIP concept of
	   "absolute time", e.g. as used in the timeout "time" field definition. */
	#define SYS_TIME_TYPE u64_t

	/* STT_F is defined if we want to over-ride the default lwIP diagnostic output
	   printf formatter used for time. */
	#define STT_F U64_F

	/* SYS_TIMEOUT_SANITY_CHECK: Control whether a linked list sanity check is
	   performed after each timeout operation.. */
	#define SYS_TIMEOUT_SANITY_CHECK 0

	/* ------------------------------------- */

    If SYS_TIMEOUT_ENDTIME is NOT defined then the previous delta based timeout
    code is used, and similarly if SYS_TIME_TYPE is not defined then the
    previous u32_t definition is used.

diff --git a/src/core/timers.c b/src/core/timers.c
index c8ead4e..baa0b39 100644
--- a/src/core/timers.c
+++ b/src/core/timers.c
@@ -62,6 +62,12 @@
 #include "lwip/sys.h"
 #include "lwip/pbuf.h"
 
+/* Only one of the sys_timeout implementations should be configured for a
+   build. */
+#if SYS_TIMEOUT_ENDTIME && SYS_TIMEOUT_DELTA
+#error "Incorrect configuration with both SYS_TIMEOUT_ENDTIME and SYS_TIMEOUT_DELTA enabled"
+#endif
+
 /** The one and only timeout list */
 static struct sys_timeo *next_timeout;
 #if NO_SYS
@@ -272,6 +278,13 @@ mld6_timer(void *arg)
 /** Initialize this module */
 void sys_timeouts_init(void)
 {
+#ifdef LWIP_HOOK_TIMEOUT_INIT
+  /* Some architectures may need a start-up hook function to be called to
+     initialise state, especially if complex types are used for
+     SYS_TIME_TYPE. */
+  LWIP_HOOK_TIMEOUT_INIT();
+#endif /* LWIP_HOOK_TIMEOUT_INIT */
+
 #if IP_REASSEMBLY
   sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
 #endif /* IP_REASSEMBLY */
@@ -307,6 +320,41 @@ void sys_timeouts_init(void)
 #endif
 }
 
+#if SYS_TIMEOUT_CLEAR_ALL
+/**
+ * Go through timeout list and remove all the entries. This interface is
+ * provided for systems capable of performing soft-restarts, where the ability
+ * to remove all pending timeouts is required.
+ */
+void
+sys_timeouts_clear(void)
+{
+  LWIP_DEBUGF(TIMERS_DEBUG, ("Clearing all timeouts\n"));
+
+  if (next_timeout) {
+    struct sys_timeo *t;
+
+    for (t = next_timeout; (t != NULL); ) {
+      struct sys_timeo *prev_t = t;
+      t = t->next;
+      memp_free(MEMP_SYS_TIMEOUT, prev_t);
+    }
+
+    next_timeout = NULL;
+  }
+
+  return;
+}
+#endif /* SYS_TIMEOUT_CLEAR_ALL */
+
+#if SYS_TIMEOUT_DELTA
+/* NOTE: This implementation has serious flaws especially noticable on
+   lower-throughput systems where the processing of the small delta frequent
+   timeouts can adversely affect the triggering of much longer delta (e.g. DHCP
+   lease renewal timing) events. It also does not take into account the time
+   actually spent processing lwIP operations as part of the delta calculation
+   leading to other "real-time" timeout inaccuracies. */
+
 /**
  * Create a one-shot timer (aka timeout). Timeouts are processed in the
  * following cases:
@@ -458,17 +506,6 @@ sys_check_timeouts(void)
   }
 }
 
-/** Set back the timestamp of the last call to sys_check_timeouts()
- * This is necessary if sys_check_timeouts() hasn't been called for a long
- * time (e.g. while saving energy) to prevent all timer functions of that
- * period being called.
- */
-void
-sys_restart_timeouts(void)
-{
-  timeouts_last_time = sys_now();
-}
-
 #else /* NO_SYS */
 
 /**
@@ -537,6 +574,458 @@ sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
 
 #endif /* NO_SYS */
 
+#endif /* SYS_TIMEOUT_DELTA */
+
+#if SYS_TIMEOUT_ENDTIME
+/* This implementation is based on holding an end-time (rather than a delta) and
+   ensuring that list entries are sorted. This is normally quicker than the
+   older DELTA based implementation since it avoids the possibility of having to
+   update every entry in the timeout list as a change is made. We just need to
+   maintain the head for the next (or matching set of next) timeouts to be
+   processed.
+
+   The main reason for using the ENDTIME implementation over the previous DELTA
+   variant is to minimise timeout drift. We cannot, with the current interface,
+   just have "sys_arch_mbox_fetch()" better track the delta since it has to
+   return the catch-all SYS_ARCH_TIMEOUT when timeout processing is required and
+   the actual amount of real-time that has passed is not available to the
+   generic "sys_timeouts_mbox_fetch()" code, plus the fact that the
+   "next_timeout" handle is NOT exported means it cannot directly maintain the
+   timeout deltas in its system specific processing. However, due to the
+   single-threaded nature of the timeout processing (only via the main TCPIP
+   thread) it would be possible to update this source to export "next_timeout"
+   and allow the "sys_arch_mbox_fetch()" implementation to update the timeout
+   list to better track real-time.
+
+   The problem of drift is further compounded in the DELTA implementation by the
+   timeout mechanism not taking into account how long the actual timeout handler
+   processing takes. */
+
+#if SYS_TIMEOUT_SANITY_CHECK
+/**
+ * Provide simple run-time timeout list validity checking.
+ *
+ * @return Boolean true if valid, or false on error detection.
+ */
+static int
+sys_timeout_sanity(void)
+{
+  int success = 1;
+  struct sys_timeo *outer;
+
+  /* This requires single-threaded access to the list (which we assume since
+     there is no locking provided around the list handling in the
+     sys_timeout()/sys_untimeout() calls anyway). */
+
+  for (outer = next_timeout; (success && (outer != NULL)); outer = outer->next) {
+    struct sys_timeo *inner;
+
+    for (inner = outer->next; (inner != NULL); inner = inner->next) {
+      if (outer == inner) {
+        success = 0; /* Entry appears more than once in the list. */
+        break;
+      }
+      if ((outer->h == inner->h) && (outer->arg == inner->arg)) {
+        success = 0; /* Duplicate handler/arg entries in the list. */
+        break;
+      }
+      if (sys_arch_time_before(inner->time, outer->time)) {
+        success = 0; /* Earlier end-time later in list. */
+        break;
+      }
+    }
+  }
+
+  return success;
+}
+#endif /* SYS_TIMEOUT_SANITY_CHECK */
+
+/* TODO:IMPLEMENT: We want the dumping of the complete list to be an extra debug
+   option (possibly dependant on TIMERS_DEBUG being enabled). Consider adding
+   TIMEOUT_DEBUG option that can be enabled for extra timeout specific
+   debugging. The following is currently just like the LWIP_DEBUG_TIMERNAMES
+   support. */
+#ifndef LWIP_DEBUG_TIMEOUTS
+#ifdef LWIP_DEBUG
+#define LWIP_DEBUG_TIMEOUTS SYS_DEBUG
+#else /* !LWIP_DEBUG */
+#define LWIP_DEBUG_TIMEOUTS 0
+#endif /* !LWIP_DEBUG */
+#endif /* !LWIP_DEBUG_TIMEOUTS */
+
+#if defined(LWIP_DEBUG_TIMEOUTS)
+static void dump_timeouts(const char *tag)
+{
+  struct sys_timeo *p_tmp;
+
+  LWIP_DEBUGF(TIMERS_DEBUG, ("dump_timeouts: %s: next_timeout=%p:", tag, next_timeout));
+
+  for (p_tmp = next_timeout; (p_tmp != NULL); p_tmp = p_tmp->next) {
+#if LWIP_DEBUG_TIMERNAMES
+    LWIP_DEBUGF(TIMERS_DEBUG, (" %p{%s(%p)@%"STT_F"}", p_tmp, p_tmp->handler_name, p_tmp->arg, p_tmp->time));
+#else /* !LWIP_DEBUG_TIMERNAMES */
+    LWIP_DEBUGF(TIMERS_DEBUG, (" %p{%p(%p)@%"STT_F"}", p_tmp, p_tmp->h, p_tmp->arg, p_tmp->time));
+#endif /* !LWIP_DEBUG_TIMERNAMES */
+  }
+
+  LWIP_DEBUGF(TIMERS_DEBUG, ("\n"));
+
+  return;
+}
+#else /* !LWIP_DEBUG_TIMEOUTS */
+#define dump_timeouts(t) /* do nothing */
+#endif /* !LWIP_DEBUG_TIMEOUTS */
+
+/**
+ * Create a one-shot timer (aka timeout). Timeouts are processed in the
+ * following cases:
+ * - while waiting for a message using sys_timeouts_mbox_fetch()
+ * - by calling sys_check_timeouts() (NO_SYS==1 only)
+ *
+ * @note: This sys_timeout() implementation explicitly does NOT allow duplicate
+ * handler entries. If a handler/arg pairing exists then the active timeout is
+ * set to the earliest end-time, which is a reasonable limitation for the
+ * internal lwIP timeout users.
+
+ * @param msecs time in milliseconds after which the timer should expire
+ * @param handler callback function to call when msecs have elapsed
+ * @param arg argument to pass to the callback function
+ */
+#if LWIP_DEBUG_TIMERNAMES
+void
+sys_timeout_debug(u32_t msecs, sys_timeout_handler handler, void *arg, const char* handler_name)
+#else /* LWIP_DEBUG_TIMERNAMES */
+void
+sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
+#endif /* LWIP_DEBUG_TIMERNAMES */
+{
+  /* NOTE: Regardless of the low-level sys_arch implementation the internal lwIP
+     timeouts are specified as milliseconds from "now". */
+  struct sys_timeo *p_entry = NULL;
+  SYS_TIME_TYPE endtime;
+
+  LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: msecs=%u handler=%p arg=%p\n", msecs, handler, arg));
+
+  /* NOTE: We check if an entry already exists for a given callback handler and
+     arg pairing to avoid creating multiple entries. We only allow a single
+     entry with the given parameters; keeping the "earliest" end-time as the
+     active timeout. This ensures that the "sys_untimeout()" to remove a timeout
+     will cope with multiple users having set a timeout with different arg
+     values. */
+
+  {
+    SYS_TIME_TYPE now = sys_jiffies();
+    SYS_TIME_TYPE ticks = sys_msec_to_jiffy(msecs);
+    /* We do not allow a timeout of 0: */
+    if (ticks == 0) {
+      ticks = 1;
+    }
+
+    endtime = (now + ticks);
+    LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: msecs %u ticks %"STT_F" now %"STT_F" endtime %"STT_F"\n", msecs, ticks, now, endtime));
+  }
+
+  for (p_entry = next_timeout; (p_entry != NULL); p_entry = p_entry->next) {
+    if ((p_entry->h == handler) && (p_entry->arg == arg)) {
+      break; /* matched */
+    }
+  }
+
+  if (p_entry) { /* we have a match */
+    LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: existing %p: endtime=%"STT_F" : new endtime=%"STT_F" : next=%p\n", p_entry, p_entry->time, endtime, p_entry->next));
+    /* We want the earliest endtime to be the active timeout: */
+    if (sys_arch_time_before(endtime, p_entry->time)) {
+      p_entry->time = endtime;
+    }
+  } else {
+    /* No match so we need to allocate a new entry for this callback/arg
+       pairing: */
+    p_entry = memp_malloc(MEMP_SYS_TIMEOUT);
+    LWIP_ASSERT("sys_timeout: MEMP_SYS_TIMEOUT pool empty", (p_entry != NULL));
+    if (p_entry) {
+      p_entry->next = NULL;
+      p_entry->h = handler;
+      p_entry->arg = arg;
+      p_entry->time = endtime;
+#if LWIP_DEBUG_TIMERNAMES
+      p_entry->handler_name = handler_name;
+      LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: new %p endtime=%"STT_F" handler=%s arg=%p\n", p_entry, endtime, handler_name, arg));
+#else /* !LWIP_DEBUG_TIMERNAMES */
+      LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: new %p endtime=%"STT_F" handler=%p arg=%p\n", p_entry, endtime, handler, arg));
+#endif /* !LWIP_DEBUG_TIMERNAMES */
+
+      /* Quick exit if this is the only timeout entry: */
+      if (next_timeout == NULL) {
+        next_timeout = p_entry;
+        LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: only entry %p\n", p_entry));
+      } else {
+        /* Check if this new entry replaces the current head: */
+        if (sys_arch_time_before(endtime, next_timeout->time)) {
+          p_entry->next = next_timeout;
+          next_timeout = p_entry;
+        } else {
+          struct sys_timeo *p_tmp;
+
+          /* We need to search for where we insert this entry into the sorted
+             list: */
+          for (p_tmp = next_timeout; (p_tmp != NULL); p_tmp = p_tmp->next) {
+            if ((p_tmp->next == NULL) || (sys_arch_time_before(endtime, p_tmp->next->time))) {
+              p_entry->next = p_tmp->next;
+              p_tmp->next = p_entry;
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+#if SYS_TIMEOUT_SANITY_CHECK
+  LWIP_ASSERT("sys_timeout sanity", sys_timeout_sanity());
+#endif /* SYS_TIMEOUT_SANITY_CHECK */
+
+  return;
+}
+
+/**
+ * Go through timeout list (for this task only) and remove the first matching
+ * entry, even though the timeout has not triggered yet.
+ *
+ * @note This function only works as expected if there is only one timeout
+ * calling 'handler' in the list of timeouts.
+ *
+ * @param handler callback function that would be called by the timeout
+ * @param arg callback argument that would be passed to handler
+*/
+void
+sys_untimeout(sys_timeout_handler handler, void *arg)
+{
+  /* NOTE: At the moment some lwIP implementation can have the sys_untimeout()
+     routine called when the supplied handler has NOT previously been attached
+     by a corresponding "sys_timeout()" call. We silently ignore such
+     requests. */
+  LWIP_DEBUGF(TIMERS_DEBUG, ("sys_untimeout: h=%p arg=%p\n", handler, arg));
+
+  if (next_timeout) {
+    struct sys_timeo *prev_t;
+    struct sys_timeo *t;
+
+    for (t = next_timeout, prev_t = NULL; (t != NULL); prev_t = t, t = t->next) {
+      if ((t->h == handler) && (t->arg == arg)) {
+        /* We have a match */
+        /* Unlink from previous in list */
+        if (prev_t == NULL) {
+          next_timeout = t->next;
+        } else {
+          prev_t->next = t->next;
+        }
+        memp_free(MEMP_SYS_TIMEOUT,t);
+        break;
+      }
+    }
+  }
+
+#if SYS_TIMEOUT_SANITY_CHECK
+  LWIP_ASSERT("sys_untimeout sanity", sys_timeout_sanity());
+#endif /* SYS_TIMEOUT_SANITY_CHECK */
+
+  return;
+}
+
+#if NO_SYS
+
+/** Handle timeouts for NO_SYS==1 (i.e. without using
+ * tcpip_thread/sys_timeouts_mbox_fetch(). Uses sys_now() to call timeout
+ * handler functions when timeouts expire.
+ *
+ * Must be called periodically from your main loop.
+ */
+void
+sys_check_timeouts(void)
+{
+  if (next_timeout) {
+    int doloop = 1;
+    SYS_TIME_TYPE now = sys_jiffies();
+
+    LWIP_DEBUGF(TIMERS_DEBUG, ("sct: SYS_ARCH_TIMEOUT: now %"STT_F"\n", now));
+
+    dump_timeouts("in");
+
+    /* Loop removing all entries that have timed out from the head of the
+       list. NOTE: Since a called timeout handler may have added new timeout
+       entries every time we remove an entry we start from the head of the
+       list again. */
+    do {
+      struct sys_timeo *tmptimeout = next_timeout;
+
+#if PBUF_POOL_FREE_OOSEQ
+      PBUF_CHECK_FREE_OOSEQ();
+#endif /* PBUF_POOL_FREE_OOSEQ */
+
+      if (tmptimeout) {
+        LWIP_DEBUGF(TIMERS_DEBUG, ("sct: now %"STT_F" tmptimeout->endtime = %"STT_F"\n", now, tmptimeout->time));
+        if (tmptimeout->time <= now) {
+          sys_timeout_handler handler; /* The callback "handler" routine for the timeout. */
+          void *arg; /* The callback "argument" to be passed to the callback routine. */
+
+          next_timeout = tmptimeout->next;
+
+          handler = tmptimeout->h;
+          arg = tmptimeout->arg;
+#if LWIP_DEBUG_TIMERNAMES
+          if (handler != NULL) {
+            LWIP_DEBUGF(TIMERS_DEBUG, ("sct: calling h=%s arg=%p\n", tmptimeout->handler_name, arg));
+          }
+#else /* !LWIP_DEBUG_TIMERNAMES */
+          LWIP_DEBUGF(TIMERS_DEBUG, ("sct: calling h=%p arg=%p\n", handler, arg));
+#endif /* !LWIP_DEBUG_TIMERNAMES */
+          memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
+          if (handler != NULL) {
+            (handler)(arg);
+          }
+
+          /* Update concept of "now" after the handler has executed: */
+          now = sys_jiffies();
+        } else {
+          doloop = 0;
+        }
+      } else {
+        doloop = 0;
+      }
+    } while (doloop != 0);
+
+    dump_timeouts("out");
+  }
+
+  return;
+}
+
+#else /* !NO_SYS */
+
+/**
+ * Wait (forever) for a message to arrive in an mbox.
+ * While waiting, timeouts are processed.
+ *
+ * @param mbox the mbox to fetch the message from
+ * @param msg the place to store the message
+ */
+void
+sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
+{
+  u32_t time_taken;
+
+  do {
+    SYS_TIME_TYPE now = sys_jiffies();
+    u32_t waitmsecs = 0; /* wait forever if no timeouts pending */
+
+    if (next_timeout == NULL) {
+      LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: no timeouts pending: sys_arch_mbox_fetch(%p, msg, 0)\n", mbox));
+      /* No current timeouts so block indefinetely for a message. This relies on
+         no timeout being added other than as a result of a message being posted
+         to perform an action. */
+      time_taken = sys_arch_mbox_fetch(mbox, msg, 0);
+      /* NOTE: This is currently different from the old code which would (if
+         next_timeout == NULL) just block until a message was received and
+         return it without processing any timeouts. */
+    } else {
+      /* TODO:CONSIDER: Have POLL of sys_arch_time_before() so that we always
+         process a message if one is available, to give them priority over
+         timeout handling. */
+
+      if (sys_arch_time_before(next_timeout->time, now)) {
+        LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: head expired: SYS_ARCH_TIMEOUT\n"));
+        time_taken = SYS_ARCH_TIMEOUT;
+      } else {
+        /* From sys_arch_time_before() check above we know that
+           "next_timeout->time" is at least the same or after "now". */
+        u32_t delta = (next_timeout->time - now);
+        LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: now %"STT_F" next_timeout->time %"STT_F" and delta %u\n", now, next_timeout->time, delta));
+        if (delta > 0) {
+          time_taken = sys_arch_mbox_fetch(mbox, msg, delta);
+        } else {
+          LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: delta 0: SYS_ARCH_TIMEOUT\n"));
+          time_taken = SYS_ARCH_TIMEOUT;
+        }
+      }
+    }
+
+    if (time_taken == SYS_ARCH_TIMEOUT) {
+      /* TODO:OPTIMISE: This block is the same as used in sys_check_timeouts()
+         with the exception of the initial (next_timeout) check and the fact
+         that it has PBUF_POOL_FREE_OOSEQ build conditional check included in
+         the main loop. We should be able to provide a common routine to allow
+         for better target optimisation. */
+      int doloop = 1;
+
+      now = sys_jiffies();
+
+      LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: SYS_ARCH_TIMEOUT: now %"STT_F"\n", now));
+
+      dump_timeouts("in");
+
+      /* Loop removing all entries that have timed out from the head of the
+         list. NOTE: Since a called timeout handler may have added new timeout
+         entries every time we remove an entry we start from the head of the
+         list again. */
+      do {
+        struct sys_timeo *tmptimeout = next_timeout;
+        if (tmptimeout) {
+          LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: now %"STT_F" tmptimeout->endtime = %"STT_F"\n", now, tmptimeout->time));
+          if (tmptimeout->time <= now) {
+            sys_timeout_handler handler; /* The callback "handler" routine for the timeout. */
+            void *arg; /* The callback "argument" to be passed to the callback routine. */
+
+            next_timeout = tmptimeout->next;
+
+            handler = tmptimeout->h;
+            arg = tmptimeout->arg;
+#if LWIP_DEBUG_TIMERNAMES
+            if (handler != NULL) {
+              LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: calling h=%s arg=%p\n", tmptimeout->handler_name, arg));
+            }
+#else /* !LWIP_DEBUG_TIMERNAMES */
+            LWIP_DEBUGF(TIMERS_DEBUG, ("stmf: calling h=%p arg=%p\n", handler, arg));
+#endif /* !LWIP_DEBUG_TIMERNAMES */
+            memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
+            if (handler != NULL) {
+              (handler)(arg);
+            }
+
+            /* Update concept of "now" after the handler has executed: */
+            now = sys_jiffies();
+          } else {
+            doloop = 0;
+          }
+        } else {
+          doloop = 0;
+        }
+      } while (doloop != 0);
+
+      dump_timeouts("out");
+    }
+  } while (time_taken == SYS_ARCH_TIMEOUT);
+
+  return;
+}
+
+#endif /* NO_SYS */
+
+#endif /* SYS_TIMEOUT_ENDTIME */
+
+#if NO_SYS
+/** Set back the timestamp of the last call to sys_check_timeouts()
+ * This is necessary if sys_check_timeouts() hasn't been called for a long
+ * time (e.g. while saving energy) to prevent all timer functions of that
+ * period being called.
+ */
+void
+sys_restart_timeouts(void)
+{
+  timeouts_last_time = sys_now();
+}
+
+#endif /* NO_SYS */
+
 #else /* LWIP_TIMERS */
 /* Satisfy the TCP code which calls this function */
 void
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
index e51f8e5..06a72a1 100644
--- a/src/include/lwip/opt.h
+++ b/src/include/lwip/opt.h
@@ -77,6 +77,47 @@
 #endif
 
 /**
+ * SYS_TIMEOUT_ENDTIME==1: Provide the absolute end-time based timeout
+ * handling. This fixes a major flaw in the old SYS_TIMEOUT_DELTA
+ * implementation, and also (for most targets) provides a simpler, quicker,
+ * implementation. NOTE: For backwards compatibility this feature is disabled by
+ * default, since it requires slight changes to the sys_arch platform support.
+ */
+#ifndef SYS_TIMEOUT_ENDTIME
+#define SYS_TIMEOUT_ENDTIME             0
+#endif
+
+/**
+ * SYS_TIMEOUT_DELTA==1: Provide the original "delta" based timeout handling
+ * implementation. This is enabled as the default (for backwards compatibility)
+ * when the alternative absolute end-time implementation is not enabled.
+ */
+#ifndef SYS_TIMEOUT_DELTA
+#if SYS_TIMEOUT_ENDTIME
+#define SYS_TIMEOUT_DELTA               0
+#else
+#define SYS_TIMEOUT_DELTA               1
+#endif
+#endif
+
+/**
+ * SYS_TIMEOUT_CLEAR_ALL==1: Provide the sys_timeouts_clear() function for
+ * soft-restart capable systems to remove all active timeouts.
+ */
+#ifndef SYS_TIMEOUT_CLEAR_ALL
+#define SYS_TIMEOUT_CLEAR_ALL           0
+#endif
+
+/**
+ * SYS_TIMEOUT_SANITY_CHECK==1: run a sanity check after each sys_timeout() and
+ * sys_untimeout() call to make sure that there are no cycles in the linked
+ * list.
+ */
+#ifndef SYS_TIMEOUT_SANITY_CHECK
+#define SYS_TIMEOUT_SANITY_CHECK        0
+#endif
+
+/**
  * MEMCPY: override this if you have a faster implementation at hand than the
  * one included in your C library
  */
diff --git a/src/include/lwip/sys.h b/src/include/lwip/sys.h
index dc93513..c324413 100644
--- a/src/include/lwip/sys.h
+++ b/src/include/lwip/sys.h
@@ -228,15 +228,48 @@ sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg,
 /* sys_init() must be called before anthing else. */
 void sys_init(void);
 
+/* To allow for platform specific implementations we use an opaque type to hold
+   system "absolute time" information. This could be a complex-type if that is
+   what is required for the architecture specific support, but for most
+   platforms it will simply be an unsigned integer capable of holding the width
+   of information required. */
+#ifndef SYS_TIME_TYPE
+#define SYS_TIME_TYPE u32_t
+/* NOTE: A u32_t millisecond value gives a range of over 1100hours (49days). */
+#endif /* SYS_TIME_TYPE */
+
+/* If not supplied by architecture specific configuration then we define a
+   format suitable for using with the default SYS_TIME_TYPE manifest. */
+#ifndef STT_F
+#define STT_F U32_F
+#endif /* STT_F */
+
 #ifndef sys_jiffies
 /** Ticks/jiffies since power up. */
-u32_t sys_jiffies(void);
+SYS_TIME_TYPE sys_jiffies(void);
+#endif
+
+#ifndef sys_msec_to_jiffy
+/** Convert millisecond value to number of ticks/jiffies. */
+SYS_TIME_TYPE sys_msec_to_jiffy(u32_t msecs);
 #endif
 
 /** Returns the current time in milliseconds,
  * may be the same as sys_jiffies or at least based on it. */
 u32_t sys_now(void);
 
+/* For most system architectures this should can be defined in "arch/sys_arch.h"
+   as a simple comparison manifest, since complex types will not be being used
+   to hold system time. */
+#ifndef sys_arch_time_before
+/**
+ * Check if system time a < system time b.
+ * @param a earlier time
+ * @param b later time
+ * @return boolean true if (a < b), or false if (a >= b) */
+int sys_arch_time_before(SYS_TIME_TYPE a,SYS_TIME_TYPE b);
+#endif
+
 /* Critical Region Protection */
 /* These functions must be implemented in the sys_arch.c file.
    In some implementations they can provide a more light-weight protection
diff --git a/src/include/lwip/timers.h b/src/include/lwip/timers.h
index 04e78e0..18c2d2e 100644
--- a/src/include/lwip/timers.h
+++ b/src/include/lwip/timers.h
@@ -57,6 +57,7 @@ extern "C" {
 #endif /* LWIP_DEBUG*/
 #endif
 
+
 /** Function prototype for a timeout callback function. Register such a function
  * using sys_timeout().
  *
@@ -64,9 +65,19 @@ extern "C" {
  */
 typedef void (* sys_timeout_handler)(void *arg);
 
+/** In reality this structure is internal to the timeout handling code and
+ * should not need to be exported via this header, but we currently need to know
+ * the structure size for the memory pool sub-system.
+ *
+ * Using an opaque type for the actual "time" field allows lwIP to support
+ * systems with larger clock values, where real-time is not available, or where
+ * a very short system timer wrap occurs. The architecture specific
+ * implementation can be coded for correct timeout operation as appropriate for
+ * the run-time in use.
+ */
 struct sys_timeo {
   struct sys_timeo *next;
-  u32_t time;
+  SYS_TIME_TYPE time;
   sys_timeout_handler h;
   void *arg;
 #if LWIP_DEBUG_TIMERNAMES
@@ -91,6 +102,9 @@ void sys_restart_timeouts(void);
 void sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg);
 #endif /* NO_SYS */
 
+#if SYS_TIMEOUT_CLEAR_ALL
+void sys_timeouts_clear(void);
+#endif /* SYS_TIMEOUT_CLEAR_ALL */
 
 #ifdef __cplusplus
 }
diff --git a/src/netif/ppp/ppp.c b/src/netif/ppp/ppp.c
index 2a34657..406a05f 100644
--- a/src/netif/ppp/ppp.c
+++ b/src/netif/ppp/ppp.c
@@ -206,7 +206,7 @@ typedef struct PPPControl_s {
   u16_t mtu;                    /* Peer's mru */
   int  pcomp;                   /* Does peer accept protocol compression? */
   int  accomp;                  /* Does peer accept addr/ctl compression? */
-  u_long lastXMit;              /* Time of last transmission. */
+  SYS_TIME_TYPE lastXMit;       /* Time of last transmission. */
   ext_accm outACCM;             /* Async-Ctl-Char-Map for output. */
 #if PPPOS_SUPPORT && VJ_SUPPORT
   int  vjEnabled;               /* Flag indicating VJ compression enabled. */
diff --git a/src/netif/ppp/randm.c b/src/netif/ppp/randm.c
index b736091..aef5570 100644
--- a/src/netif/ppp/randm.c
+++ b/src/netif/ppp/randm.c
@@ -199,7 +199,7 @@ avRandomInit()
   avRandomSeed += *(u32_t *)clockBuf + *lptr1 + OSIdleCtr
            + ppp_mtime() + ((u32_t)TM1 << 16) + TM1;
 #else
-  avRandomSeed += sys_jiffies(); /* XXX */
+  avRandomSeed += (unsigned)sys_jiffies(); /* XXX */
 #endif
 
   /* Initialize the Borland random number generator. */
@@ -216,7 +216,7 @@ avRandomInit()
 void
 avRandomize(void)
 {
-  static u32_t last_jiffies;
+  static SYS_TIME_TYPE last_jiffies;
 
   if (!avRandomized) {
     avRandomized = !0;
@@ -224,7 +224,7 @@ avRandomize(void)
     /* The initialization function also updates the seed. */
   } else {
     /* avRandomSeed += (avRandomSeed << 16) + TM1; */
-    avRandomSeed += (sys_jiffies() - last_jiffies); /* XXX */
+      avRandomSeed += (unsigned)(sys_jiffies() - last_jiffies); /* XXX */
   }
   last_jiffies = sys_jiffies();
 }
