You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by GitBox <gi...@apache.org> on 2018/09/21 23:30:51 UTC

[GitHub] ccollins476ad closed pull request #1380: kernel/os: Registration of time-change listeners

ccollins476ad closed pull request #1380: kernel/os: Registration of time-change listeners
URL: https://github.com/apache/mynewt-core/pull/1380
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/kernel/os/include/os/os_time.h b/kernel/os/include/os/os_time.h
index 6763de3557..6975ad2cba 100644
--- a/kernel/os/include/os/os_time.h
+++ b/kernel/os/include/os/os_time.h
@@ -63,6 +63,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include "os/os_arch.h"
+#include "os/queue.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -143,6 +144,44 @@ struct os_timezone {
     int16_t tz_dsttime;
 };
 
+/**
+ * Represents a time change.  Passed to time change listeners when the current
+ * time-of-day is set.
+ */
+struct os_time_change_info {
+    /** UTC time prior to change. */
+    const struct os_timeval *tci_prev_tv;
+    /** Time zone prior to change. */
+    const struct os_timezone *tci_prev_tz;
+    /** UTC time after change. */
+    const struct os_timeval *tci_cur_tv;
+    /** Time zone after change. */
+    const struct os_timezone *tci_cur_tz;
+    /** True if the time was not set prior to change. */
+    bool tci_newly_synced;
+};
+
+/**
+ * Callback that is executed when the time-of-day is set.
+ *
+ * @param info                  Describes the time change that just occurred.
+ * @param arg                   Optional argument correponding to listener.
+ */
+typedef void os_time_change_fn(const struct os_time_change_info *info,
+                               void *arg);
+
+/**
+ * Time change listener.  Notified when the time-of-day is set.
+ */
+struct os_time_change_listener {
+    /*** Public. */
+    os_time_change_fn *tcl_fn;
+    void *tcl_arg;
+
+    /*** Internal. */
+    STAILQ_ENTRY(os_time_change_listener) tcl_next;
+};
+
 /**
  * Add first two timeval arguments and place results in third timeval
  * argument.
@@ -175,7 +214,8 @@ struct os_timezone {
 
 /**
  * Set the time of day.  This does not modify os time, but rather just modifies
- * the offset by which we are tracking real time against os time.
+ * the offset by which we are tracking real time against os time.  This
+ * function notifies all registered time change listeners.
  *
  * @param utctime A timeval representing the UTC time we are setting
  * @param tz The time-zone to apply against the utctime being set.
@@ -196,6 +236,13 @@ int os_settimeofday(struct os_timeval *utctime, struct os_timezone *tz);
  */
 int os_gettimeofday(struct os_timeval *utctime, struct os_timezone *tz);
 
+/**
+ * Indicates whether the time has been set.
+ *
+ * @return                      true if time is set; false otherwise.
+ */
+bool os_time_is_set(void);
+
 /**
  * Get time since boot in microseconds.
  *
@@ -273,6 +320,35 @@ os_time_ticks_to_ms32(os_time_t ticks)
 #endif
 }
 
+/**
+ * Registers a time change listener.  Whenever the time is set, all registered
+ * listeners are notified.  The provided pointer is added to an internal list,
+ * so the listener's lifetime must extend indefinitely (or until the listener
+ * is removed).
+ *
+ * NOTE: This function is not thread safe.  The following operations must be
+ * kept exclusive:
+ *     o Addition of listener
+ *     o Removal of listener
+ *     o Setting time
+ *
+ * @param listener              The listener to register.
+ */
+void os_time_change_listen(struct os_time_change_listener *listener);
+
+/**
+ * Unregisters a time change listener.
+ *
+ * NOTE: This function is not thread safe.  The following operations must be
+ * kept exclusive:
+ *     o Addition of listener
+ *     o Removal of listener
+ *     o Setting time
+ *
+ * @param listener              The listener to unregister.
+ */
+int os_time_change_remove(const struct os_time_change_listener *listener);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/kernel/os/src/os_time.c b/kernel/os/src/os_time.c
index 51ae7d6a11..7022694762 100644
--- a/kernel/os/src/os_time.c
+++ b/kernel/os/src/os_time.c
@@ -26,6 +26,9 @@ CTASSERT(sizeof(os_time_t) == 4);
 
 os_time_t g_os_time;
 
+static STAILQ_HEAD(, os_time_change_listener) os_time_change_listeners =
+    STAILQ_HEAD_INITIALIZER(os_time_change_listeners);
+
 /*
  * Time-of-day collateral.
  */
@@ -116,13 +119,125 @@ os_time_delay(os_time_t osticks)
     }
 }
 
+/**
+ * Searches the list of registered time change listeners for the specified
+ * entry.
+ *
+ * @param listener              The listener to find.
+ * @param out_prev              On success, the specified listener's
+ *                                  predecessor gets written here.  Pass NULL
+ *                                  if you do not require this information.
+ *
+ * @return                      0 if the specified listener was found;
+ *                              SYS_ENOENT if the listener is not present.
+ */
+static int
+os_time_change_listener_find(const struct os_time_change_listener *listener,
+                             struct os_time_change_listener **out_prev)
+{
+    struct os_time_change_listener *prev;
+    struct os_time_change_listener *cur;
+
+    prev = NULL;
+    STAILQ_FOREACH(cur, &os_time_change_listeners, tcl_next) {
+        if (cur == listener) {
+            break;
+        }
+
+        prev = cur;
+    }
+
+    if (cur == NULL) {
+        return SYS_ENOENT;
+    }
+
+    if (out_prev != NULL) {
+        *out_prev = prev;
+    }
+
+    return 0;
+}
+
+void
+os_time_change_listen(struct os_time_change_listener *listener)
+{
+#if MYNEWT_VAL(OS_TIME_DEBUG)
+    assert(listener->tcl_fn != NULL);
+    assert(os_time_change_listener_find(listener, NULL) == SYS_ENOENT);
+#endif
+
+    STAILQ_INSERT_TAIL(&os_time_change_listeners, listener, tcl_next);
+}
+
+int
+os_time_change_remove(const struct os_time_change_listener *listener)
+{
+    struct os_time_change_listener *prev;
+    int rc;
+
+    rc = os_time_change_listener_find(listener, &prev);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (prev == NULL) {
+        STAILQ_REMOVE_HEAD(&os_time_change_listeners, tcl_next);
+    } else {
+        STAILQ_NEXT(prev, tcl_next) = STAILQ_NEXT(listener, tcl_next);
+    }
+
+    return 0;
+}
+
+static void
+os_time_change_notify(const struct os_time_change_info *info)
+{
+    struct os_time_change_listener *listener;
+
+    STAILQ_FOREACH(listener, &os_time_change_listeners, tcl_next) {
+        listener->tcl_fn(info, listener->tcl_arg);
+    }
+}
+
+static int
+os_time_populate_info(struct os_time_change_info *info,
+                      const struct os_timeval *new_tv,
+                      const struct os_timezone *new_tz)
+{
+    if (new_tv == NULL && new_tz == NULL) {
+        return SYS_EINVAL;
+    }
+
+    if (new_tv == NULL) {
+        new_tv = &basetod.utctime;
+    }
+    if (new_tz == NULL) {
+        new_tz = &basetod.timezone;
+    }
+
+    info->tci_prev_tv = &basetod.utctime;
+    info->tci_cur_tv = new_tv;
+    info->tci_prev_tz = &basetod.timezone;
+    info->tci_cur_tz = new_tz;
+    info->tci_newly_synced = !os_time_is_set();
+
+    return 0;
+}
+
 int
 os_settimeofday(struct os_timeval *utctime, struct os_timezone *tz)
 {
     os_sr_t sr;
     os_time_t delta;
+    struct os_time_change_info info;
+    bool notify;
+    int rc;
 
     OS_ENTER_CRITICAL(sr);
+
+    rc = os_time_populate_info(&info, utctime, tz);
+    notify = rc == 0;
+
     if (utctime != NULL) {
         /*
          * Update all time-of-day base values.
@@ -136,8 +251,14 @@ os_settimeofday(struct os_timeval *utctime, struct os_timezone *tz)
     if (tz != NULL) {
         basetod.timezone = *tz;
     }
+
     OS_EXIT_CRITICAL(sr);
 
+    /* Notify all listeners of time change. */
+    if (notify) {
+        os_time_change_notify(&info);
+    }
+
     return (0);
 }
 
@@ -161,6 +282,12 @@ os_gettimeofday(struct os_timeval *tv, struct os_timezone *tz)
     return (0);
 }
 
+bool
+os_time_is_set(void)
+{
+    return basetod.utctime.tv_sec > 0;
+}
+
 void
 os_get_uptime(struct os_timeval *tvp)
 {
diff --git a/kernel/os/syscfg.yml b/kernel/os/syscfg.yml
index 6e23f922f2..61ed52472c 100644
--- a/kernel/os/syscfg.yml
+++ b/kernel/os/syscfg.yml
@@ -129,9 +129,14 @@ syscfg.defs:
         description: >
             Maximum duration of tickless idle period in miliseconds.
         value: 600000
+    OS_TIME_DEBUG:
+        description: >
+            Enables debug runtime checks for time-related functionality.
+        value: 0
 
 syscfg.vals.OS_DEBUG_MODE:
     OS_CRASH_STACKTRACE: 1
     OS_CTX_SW_STACK_CHECK: 1
     OS_MEMPOOL_CHECK: 1
     OS_MEMPOOL_POISON: 1
+    OS_TIME_DEBUG: 1
diff --git a/kernel/os/test/include/os_test/os_test.h b/kernel/os/test/include/os_test/os_test.h
index 87fd57d1e0..94341d72d0 100644
--- a/kernel/os/test/include/os_test/os_test.h
+++ b/kernel/os/test/include/os_test/os_test.h
@@ -29,6 +29,10 @@ extern "C" {
 TEST_SUITE_DECL(os_mutex_test_suite);
 TEST_SUITE_DECL(os_sem_test_suite);
 TEST_SUITE_DECL(os_mempool_test_suite);
+
+TEST_SUITE_DECL(os_time_test_suite);
+TEST_CASE_DECL(os_time_test_change);
+
 int os_test_all(void);
 
 #ifdef __cplusplus
diff --git a/kernel/os/test/src/os_test.c b/kernel/os/test/src/os_test.c
index 46bb288f01..d2cfa88fc3 100644
--- a/kernel/os/test/src/os_test.c
+++ b/kernel/os/test/src/os_test.c
@@ -70,6 +70,7 @@ os_test_all(void)
     os_mbuf_test_suite();
     os_eventq_test_suite();
     os_callout_test_suite();
+    os_time_test_suite();
 
     return tu_case_failed;
 }
diff --git a/kernel/os/test/src/os_test_priv.h b/kernel/os/test/src/os_test_priv.h
index 99630af523..5fd5ec7514 100644
--- a/kernel/os/test/src/os_test_priv.h
+++ b/kernel/os/test/src/os_test_priv.h
@@ -22,6 +22,7 @@
 
 #include "os/mynewt.h"
 #include "testutil/testutil.h"
+#include "os_test/os_test.h"
 #include "os_test_priv.h"
 
 #include "callout_test.h"
diff --git a/kernel/os/test/src/testcases/os_time_test_change.c b/kernel/os/test/src/testcases/os_time_test_change.c
new file mode 100644
index 0000000000..16affb6707
--- /dev/null
+++ b/kernel/os/test/src/testcases/os_time_test_change.c
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "os_test_priv.h"
+
+#define OTTC_MAX_ENTRIES    16
+
+struct ottc_entry {
+    struct os_timeval prev_tv;
+    struct os_timeval cur_tv;
+    struct os_timezone prev_tz;
+    struct os_timezone cur_tz;
+    bool newly_synced;
+    void *arg;
+};
+
+/** Holds records of listener callback calls. */
+static struct ottc_entry ottc_entries[OTTC_MAX_ENTRIES];
+static int ottc_num_entries;
+
+static void
+ottc_time_change_cb(const struct os_time_change_info *info, void *arg)
+{
+    struct ottc_entry *entry;
+
+    TEST_ASSERT_FATAL(ottc_num_entries < OTTC_MAX_ENTRIES);
+    entry = &ottc_entries[ottc_num_entries++];
+
+    entry->prev_tv = *info->tci_prev_tv;
+    entry->cur_tv = *info->tci_cur_tv;
+    entry->prev_tz = *info->tci_prev_tz;
+    entry->cur_tz = *info->tci_cur_tz;
+    entry->newly_synced = info->tci_newly_synced;
+    entry->arg = arg;
+}
+
+TEST_CASE(os_time_test_change)
+{
+    struct os_timezone tz1;
+    struct os_timezone tz2;
+    struct os_timeval tv1;
+    struct os_timeval tv2;
+    int rc;
+    int i;
+
+    struct os_time_change_listener listeners[3] = {
+        [0] = {
+            .tcl_fn = ottc_time_change_cb,
+            .tcl_arg = (void *)(uintptr_t)0,
+        },
+        [1] = {
+            .tcl_fn = ottc_time_change_cb,
+            .tcl_arg = (void *)(uintptr_t)1,
+        },
+        [2] = {
+            .tcl_fn = ottc_time_change_cb,
+            .tcl_arg = (void *)(uintptr_t)2,
+        },
+    };
+
+    /* Register one listener. */
+    os_time_change_listen(&listeners[0]);
+
+    /* Set time; ensure single listener called. */
+    tv1.tv_sec = 123;
+    tv1.tv_usec = 456;
+    tz1.tz_minuteswest = 555;
+    tz1.tz_dsttime = 666;
+
+    rc = os_settimeofday(&tv1, &tz1);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    TEST_ASSERT_FATAL(ottc_num_entries == 1);
+    TEST_ASSERT(memcmp(&ottc_entries[0].cur_tv, &tv1, sizeof tv1) == 0);
+    TEST_ASSERT(memcmp(&ottc_entries[0].cur_tz, &tz1, sizeof tz1) == 0);
+    TEST_ASSERT(ottc_entries[0].newly_synced);
+    TEST_ASSERT(ottc_entries[0].arg == (void *)(uintptr_t)0);
+
+    /* Register two more listeners. */
+    os_time_change_listen(&listeners[1]);
+    os_time_change_listen(&listeners[2]);
+
+    /* Set time; ensure all three listeners called. */
+    tv2.tv_sec = 234;
+    tv2.tv_usec = 567;
+    tz2.tz_minuteswest = 777;
+    tz2.tz_dsttime = 888;
+
+    rc = os_settimeofday(&tv2, &tz2);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    TEST_ASSERT_FATAL(ottc_num_entries == 4);
+    for (i = 1; i < 4; i++) {
+        TEST_ASSERT(memcmp(&ottc_entries[i].cur_tv, &tv2, sizeof tv2) == 0);
+        TEST_ASSERT(memcmp(&ottc_entries[i].cur_tz, &tz2, sizeof tz2) == 0);
+        TEST_ASSERT(memcmp(&ottc_entries[i].prev_tv, &tv2, sizeof tv2) == 0);
+        TEST_ASSERT(memcmp(&ottc_entries[i].prev_tz, &tz2, sizeof tz2) == 0);
+        TEST_ASSERT(!ottc_entries[i].newly_synced);
+        TEST_ASSERT(ottc_entries[i].arg == (void *)(uintptr_t)(i - 1));
+    }
+
+    /* Remove all three listeners. */
+    for (i = 0; i < 3; i++) {
+        rc = os_time_change_remove(&listeners[i]);
+        TEST_ASSERT(rc == 0);
+    }
+
+    /* Set time; ensure no listeners called. */
+    tv2.tv_sec = 345;
+    tv2.tv_usec = 678;
+    tz2.tz_minuteswest = 888;
+    tz2.tz_dsttime = 999;
+
+    rc = os_settimeofday(&tv2, &tz2);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    TEST_ASSERT_FATAL(ottc_num_entries == 4);
+}
diff --git a/kernel/os/test/src/time_test.c b/kernel/os/test/src/time_test.c
new file mode 100644
index 0000000000..8d84daf8f8
--- /dev/null
+++ b/kernel/os/test/src/time_test.c
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <string.h>
+#include "os/mynewt.h"
+#include "os_test_priv.h"
+
+TEST_SUITE(os_time_test_suite)
+{
+    os_time_test_change();
+}
diff --git a/kernel/os/test/syscfg.yml b/kernel/os/test/syscfg.yml
new file mode 100644
index 0000000000..66de40e2ff
--- /dev/null
+++ b/kernel/os/test/syscfg.yml
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+syscfg.vals:
+    OS_TIME_DEBUG: 1


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services