[API-NEXT,6/6] timer: allow timer processing to run on worker cores

Message ID 20170528192302.56363-6-brian.brooks@arm.com
State New
Headers show
Series
  • [API-NEXT,1/6] api: time: add odp_tick_t
Related show

Commit Message

Brian Brooks May 28, 2017, 7:23 p.m.
Use 'worker_timers' option in global initialization to run timer
processing from within the schedule call instead of on background
threads. This option reduces the latency and jitter of the time
when timer pool processing begins. See [1] for details.

[1] https://docs.google.com/document/d/1sY7rOxqCNu-bMqjBiT5_keAIohrX1ZW-eL0oGLAQ4OM/edit?usp=sharing

Signed-off-by: Brian Brooks <brian.brooks@arm.com>

Reviewed-by: Ola Liljedahl <ola.liljedahl@arm.com>

---
 include/odp/api/spec/init.h                        |  8 ++
 .../linux-generic/include/odp_timer_internal.h     |  7 ++
 platform/linux-generic/odp_init.c                  |  9 ++-
 platform/linux-generic/odp_schedule.c              |  5 +-
 platform/linux-generic/odp_schedule_iquery.c       |  4 +
 platform/linux-generic/odp_schedule_sp.c           |  4 +
 platform/linux-generic/odp_timer.c                 | 90 +++++++++++++++++++---
 7 files changed, 115 insertions(+), 12 deletions(-)

-- 
2.13.0

Comments

Bill Fischofer May 28, 2017, 10:27 p.m. | #1
On Sun, May 28, 2017 at 2:23 PM, Brian Brooks <brian.brooks@arm.com> wrote:
> Use 'worker_timers' option in global initialization to run timer

> processing from within the schedule call instead of on background

> threads. This option reduces the latency and jitter of the time

> when timer pool processing begins. See [1] for details.

>

> [1] https://docs.google.com/document/d/1sY7rOxqCNu-bMqjBiT5_keAIohrX1ZW-eL0oGLAQ4OM/edit?usp=sharing

>

> Signed-off-by: Brian Brooks <brian.brooks@arm.com>

> Reviewed-by: Ola Liljedahl <ola.liljedahl@arm.com>

> ---

>  include/odp/api/spec/init.h                        |  8 ++

>  .../linux-generic/include/odp_timer_internal.h     |  7 ++

>  platform/linux-generic/odp_init.c                  |  9 ++-

>  platform/linux-generic/odp_schedule.c              |  5 +-

>  platform/linux-generic/odp_schedule_iquery.c       |  4 +

>  platform/linux-generic/odp_schedule_sp.c           |  4 +

>  platform/linux-generic/odp_timer.c                 | 90 +++++++++++++++++++---

>  7 files changed, 115 insertions(+), 12 deletions(-)

>

> diff --git a/include/odp/api/spec/init.h b/include/odp/api/spec/init.h

> index 154cdf8f..9df99d4a 100644

> --- a/include/odp/api/spec/init.h

> +++ b/include/odp/api/spec/init.h

> @@ -153,6 +153,14 @@ typedef struct odp_init_t {

>         odp_log_func_t log_fn;

>         /** Replacement for the default abort fn */

>         odp_abort_func_t abort_fn;

> +       /** Allow timer maintenance work to run on worker cores.

> +

> +           When enabled, work will run from within odp_schedule().

> +           When disabled, work will run in background threads.

> +

> +           Default: disabled (false)

> +       */

> +       odp_bool_t worker_timers;


How various ODP APIs are implemented is not defined by the ODP API
specification, so this seems strange as there is no implication in the
current timeout definition as to where these originate. Perhaps this
could be recast as an optimization hint that doesn't make reference to
an implementation model? What would this option mean for a platform
where timers are implemented in a separate HW block that runs
independent of any cores/threads?

>  } odp_init_t;

>

>  /**

> diff --git a/platform/linux-generic/include/odp_timer_internal.h b/platform/linux-generic/include/odp_timer_internal.h

> index 91b12c54..4a1bbeb3 100644

> --- a/platform/linux-generic/include/odp_timer_internal.h

> +++ b/platform/linux-generic/include/odp_timer_internal.h

> @@ -20,6 +20,9 @@

>  #include <odp_pool_internal.h>

>  #include <odp/api/timer.h>

>

> +/* Minimum number of nanoseconds between calls to _timer_run(). */

> +#define CONFIG_TIMER_RUN_RATELIMIT_PERIOD 100

> +

>  /**

>   * Internal Timeout header

>   */

> @@ -35,4 +38,8 @@ typedef struct {

>         odp_timer_t timer;

>  } odp_timeout_hdr_t;

>

> +odp_bool_t worker_timers;

> +

> +int timer_run(void);

> +

>  #endif

> diff --git a/platform/linux-generic/odp_init.c b/platform/linux-generic/odp_init.c

> index 685e02fa..f08f221c 100644

> --- a/platform/linux-generic/odp_init.c

> +++ b/platform/linux-generic/odp_init.c

> @@ -4,11 +4,14 @@

>   * SPDX-License-Identifier:     BSD-3-Clause

>   */

>  #include <odp/api/init.h>

> -#include <odp_debug_internal.h>

>  #include <odp/api/debug.h>

> -#include <unistd.h>

> +

>  #include <odp_internal.h>

> +#include <odp_debug_internal.h>

>  #include <odp_schedule_if.h>

> +#include <odp_timer_internal.h>

> +

> +#include <unistd.h>

>  #include <string.h>

>  #include <libconfig.h>

>  #include <stdlib.h>

> @@ -165,6 +168,8 @@ int odp_init_global(odp_instance_t *instance,

>                         odp_global_data.log_fn = params->log_fn;

>                 if (params->abort_fn != NULL)

>                         odp_global_data.abort_fn = params->abort_fn;

> +               if (params->worker_timers)

> +                       worker_timers = true;

>         }

>

>         cleanup_files(_ODP_TMPDIR, odp_global_data.main_pid);

> diff --git a/platform/linux-generic/odp_schedule.c b/platform/linux-generic/odp_schedule.c

> index f680ac47..0c634f62 100644

> --- a/platform/linux-generic/odp_schedule.c

> +++ b/platform/linux-generic/odp_schedule.c

> @@ -22,6 +22,7 @@

>  #include <odp/api/sync.h>

>  #include <odp_ring_internal.h>

>  #include <odp_queue_internal.h>

> +#include <odp_timer_internal.h>

>

>  /* Number of priority levels  */

>  #define NUM_PRIO 8

> @@ -985,7 +986,6 @@ static inline int do_schedule(odp_queue_t *out_queue, odp_event_t out_ev[],

>         return 0;

>  }

>

> -

>  static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,

>                          odp_event_t out_ev[],

>                          unsigned int max_num)

> @@ -995,6 +995,9 @@ static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,

>         int ret;

>

>         while (1) {

> +               if (worker_timers)

> +                       (void)timer_run();

> +

>                 ret = do_schedule(out_queue, out_ev, max_num);

>

>                 if (ret)

> diff --git a/platform/linux-generic/odp_schedule_iquery.c b/platform/linux-generic/odp_schedule_iquery.c

> index b8a40011..67457d8f 100644

> --- a/platform/linux-generic/odp_schedule_iquery.c

> +++ b/platform/linux-generic/odp_schedule_iquery.c

> @@ -22,6 +22,7 @@

>  #include <odp/api/cpu.h>

>  #include <odp/api/thrmask.h>

>  #include <odp_config_internal.h>

> +#include <odp_timer_internal.h>

>

>  /* Number of priority levels */

>  #define NUM_SCHED_PRIO 8

> @@ -718,6 +719,9 @@ static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,

>         odp_time_t next, wtime;

>

>         while (1) {

> +               if (worker_timers)

> +                       (void)timer_run();

> +

>                 count = do_schedule(out_queue, out_ev, max_num);

>

>                 if (count)

> diff --git a/platform/linux-generic/odp_schedule_sp.c b/platform/linux-generic/odp_schedule_sp.c

> index 0fd4d87d..bef9ec01 100644

> --- a/platform/linux-generic/odp_schedule_sp.c

> +++ b/platform/linux-generic/odp_schedule_sp.c

> @@ -15,6 +15,7 @@

>  #include <odp_align_internal.h>

>  #include <odp_config_internal.h>

>  #include <odp_ring_internal.h>

> +#include <odp_timer_internal.h>

>

>  #define NUM_THREAD        ODP_THREAD_COUNT_MAX

>  #define NUM_QUEUE         ODP_CONFIG_QUEUES

> @@ -501,6 +502,9 @@ static int schedule_multi(odp_queue_t *from, uint64_t wait,

>         odp_time_t t1;

>         int update_t1 = 1;

>

> +       if (worker_timers)

> +               (void)timer_run();

> +

>         if (sched_local.cmd) {

>                 /* Continue scheduling if queue is not empty */

>                 if (sched_cb_queue_empty(sched_local.cmd->s.index) == 0)

> diff --git a/platform/linux-generic/odp_timer.c b/platform/linux-generic/odp_timer.c

> index 80dce873..9c48c4a0 100644

> --- a/platform/linux-generic/odp_timer.c

> +++ b/platform/linux-generic/odp_timer.c

> @@ -61,6 +61,8 @@

>   * for checking the freshness of received timeouts */

>  #define TMO_INACTIVE ((uint64_t)0x8000000000000000)

>

> +odp_bool_t worker_timers = false;

> +

>  /******************************************************************************

>   * Mutual exclusion in the absence of CAS16

>   *****************************************************************************/

> @@ -166,7 +168,8 @@ static inline void set_next_free(odp_timer *tim, uint32_t nf)

>   *****************************************************************************/

>

>  typedef struct odp_timer_pool_s {

> -/* Put frequently accessed fields in the first cache line */

> +       uint64_t cpu_tick; /* CPU tick of beginning of last scan */

> +       uint64_t cpu_ticks_per_tp_tick;

>         odp_atomic_u64_t cur_tick;/* Current tick value */

>         uint64_t min_rel_tck;

>         uint64_t max_rel_tck;

> @@ -220,7 +223,6 @@ static inline odp_timer_t tp_idx_to_handle(struct odp_timer_pool_s *tp,

>         return _odp_cast_scalar(odp_timer_t, (tp->tp_idx << INDEX_BITS) | idx);

>  }

>

> -/* Forward declarations */

>  static void itimer_init(odp_timer_pool *tp);

>  static void itimer_fini(odp_timer_pool *tp);

>

> @@ -243,6 +245,11 @@ static odp_timer_pool_t odp_timer_pool_new(const char *name,

>                 ODP_ABORT("%s: timer pool shm-alloc(%zuKB) failed\n",

>                           name, (sz0 + sz1 + sz2) / 1024);

>         odp_timer_pool *tp = (odp_timer_pool *)odp_shm_addr(shm);

> +

> +       if (worker_timers) {

> +               tp->cpu_tick = odp_tick_now();

> +               tp->cpu_ticks_per_tp_tick = odp_ticks_from_ns(param->res_ns);

> +       }

>         odp_atomic_init_u64(&tp->cur_tick, 0);

>

>         if (name == NULL) {

> @@ -277,8 +284,10 @@ static odp_timer_pool_t odp_timer_pool_new(const char *name,

>         tp->tp_idx = tp_idx;

>         odp_spinlock_init(&tp->lock);

>         timer_pool[tp_idx] = tp;

> -       if (tp->param.clk_src == ODP_CLOCK_CPU)

> -               itimer_init(tp);

> +       if (!worker_timers) {

> +               if (tp->param.clk_src == ODP_CLOCK_CPU)

> +                       itimer_init(tp);

> +       }

>         return tp;

>  }

>

> @@ -307,11 +316,13 @@ static void odp_timer_pool_del(odp_timer_pool *tp)

>         odp_spinlock_lock(&tp->lock);

>         timer_pool[tp->tp_idx] = NULL;

>

> -       /* Stop timer triggering */

> -       if (tp->param.clk_src == ODP_CLOCK_CPU)

> -               itimer_fini(tp);

> +       if (!worker_timers) {

> +               /* Stop timer triggering */

> +               if (tp->param.clk_src == ODP_CLOCK_CPU)

> +                       itimer_fini(tp);

>

> -       stop_timer_thread(tp);

> +               stop_timer_thread(tp);

> +       }

>

>         if (tp->num_alloc != 0) {

>                 /* It's a programming error to attempt to destroy a */

> @@ -671,6 +682,63 @@ static unsigned odp_timer_pool_expire(odp_timer_pool_t tpid, uint64_t tick)

>         return nexp;

>  }

>

> +static int _timer_run(void)

> +{

> +       uint64_t last_expired, now, nticks;

> +       odp_timer_pool *tp;

> +       size_t i;

> +       int nexp = 0;

> +

> +       for (i = 0; i < MAX_TIMER_POOLS; i++) {

> +               tp = timer_pool[i];

> +

> +               if (tp == NULL)

> +                       break;

> +

> +               /* Check the last time this timer pool was expired. If one

> +                * or more periods have passed, attempt to expire it. */

> +               last_expired = tp->cpu_tick;

> +               now = odp_tick_now();

> +               nticks = (now - last_expired) / tp->cpu_ticks_per_tp_tick;

> +

> +               if (nticks < 1)

> +                       continue;

> +

> +               if (__atomic_compare_exchange_n(

> +                           &tp->cpu_tick, &last_expired,

> +                           last_expired + (tp->cpu_ticks_per_tp_tick * nticks),

> +                           false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {

> +                       uint64_t tp_tick = _odp_atomic_u64_fetch_add_mm(

> +                               &tp->cur_tick, nticks, _ODP_MEMMODEL_RLX);

> +

> +                       if (tp->notify_overrun && nticks > 1) {

> +                               ODP_ERR("\n\t%d ticks overrun on timer pool "

> +                                       "\"%s\", timer resolution too high\n",

> +                                       nticks, tp->name);

> +                               tp->notify_overrun = 0;

> +                       }

> +                       nexp += odp_timer_pool_expire(tp, tp_tick + nticks);

> +               }

> +       }

> +       return nexp;

> +}

> +

> +static uint64_t cpu_ticks_per_ratelimit;

> +

> +int timer_run(void)

> +{

> +       static __thread uint64_t last_timerscan_run;

> +       uint64_t now = odp_tick_now();

> +

> +       /* Rate limit how often this thread checks the timer pools. */

> +       if ((now - last_timerscan_run) < cpu_ticks_per_ratelimit)

> +               return 0;

> +

> +       last_timerscan_run = now;

> +

> +       return _timer_run();

> +}

> +

>  /******************************************************************************

>   * POSIX timer support

>   * Functions that use Linux/POSIX per-process timers and related facilities

> @@ -1006,7 +1074,11 @@ int odp_timer_init_global(void)

>  #endif

>         odp_atomic_init_u32(&num_timer_pools, 0);

>

> -       block_sigalarm();

> +       cpu_ticks_per_ratelimit =

> +               odp_ticks_from_ns(CONFIG_TIMER_RUN_RATELIMIT_PERIOD);

> +

> +       if (!worker_timers)

> +               block_sigalarm();

>

>         return 0;

>  }

> --

> 2.13.0

>
Savolainen, Petri (Nokia - FI/Espoo) May 29, 2017, 7:57 a.m. | #2
> -----Original Message-----

> From: lng-odp [mailto:lng-odp-bounces@lists.linaro.org] On Behalf Of Bill

> Fischofer

> Sent: Monday, May 29, 2017 1:28 AM

> To: Brian Brooks <brian.brooks@arm.com>

> Cc: lng-odp-forward <lng-odp@lists.linaro.org>

> Subject: Re: [lng-odp] [API-NEXT PATCH 6/6] timer: allow timer processing

> to run on worker cores

> 

> On Sun, May 28, 2017 at 2:23 PM, Brian Brooks <brian.brooks@arm.com>

> wrote:

> > Use 'worker_timers' option in global initialization to run timer

> > processing from within the schedule call instead of on background

> > threads. This option reduces the latency and jitter of the time

> > when timer pool processing begins. See [1] for details.

> >

> > [1] https://docs.google.com/document/d/1sY7rOxqCNu-bMqjBiT5_keAIohrX1ZW-

> eL0oGLAQ4OM/edit?usp=sharing

> >

> > Signed-off-by: Brian Brooks <brian.brooks@arm.com>

> > Reviewed-by: Ola Liljedahl <ola.liljedahl@arm.com>

> > ---

> >  include/odp/api/spec/init.h                        |  8 ++

> >  .../linux-generic/include/odp_timer_internal.h     |  7 ++

> >  platform/linux-generic/odp_init.c                  |  9 ++-

> >  platform/linux-generic/odp_schedule.c              |  5 +-

> >  platform/linux-generic/odp_schedule_iquery.c       |  4 +

> >  platform/linux-generic/odp_schedule_sp.c           |  4 +

> >  platform/linux-generic/odp_timer.c                 | 90

> +++++++++++++++++++---

> >  7 files changed, 115 insertions(+), 12 deletions(-)

> >

> > diff --git a/include/odp/api/spec/init.h b/include/odp/api/spec/init.h

> > index 154cdf8f..9df99d4a 100644

> > --- a/include/odp/api/spec/init.h

> > +++ b/include/odp/api/spec/init.h

> > @@ -153,6 +153,14 @@ typedef struct odp_init_t {

> >         odp_log_func_t log_fn;

> >         /** Replacement for the default abort fn */

> >         odp_abort_func_t abort_fn;

> > +       /** Allow timer maintenance work to run on worker cores.

> > +

> > +           When enabled, work will run from within odp_schedule().

> > +           When disabled, work will run in background threads.

> > +

> > +           Default: disabled (false)

> > +       */

> > +       odp_bool_t worker_timers;

> 

> How various ODP APIs are implemented is not defined by the ODP API

> specification, so this seems strange as there is no implication in the

> current timeout definition as to where these originate. Perhaps this

> could be recast as an optimization hint that doesn't make reference to

> an implementation model? What would this option mean for a platform

> where timers are implemented in a separate HW block that runs

> independent of any cores/threads?


Also timer events can be polled from non-scheduled queues, application might not call scheduler at all, or may call scheduler / handle timer events only on ctrl threads...

These init time configuration options should be pretty high level. The first thing to add would be a bit field to select which features are used / not used. E.g. is application going to use timer/TM/scheduler/etc at all. By default, all features would be enabled but resource usage/performance could be optimized when application indicates that it uses only part of the features (as it usually does).


-Petri

Patch hide | download patch | download mbox

diff --git a/include/odp/api/spec/init.h b/include/odp/api/spec/init.h
index 154cdf8f..9df99d4a 100644
--- a/include/odp/api/spec/init.h
+++ b/include/odp/api/spec/init.h
@@ -153,6 +153,14 @@  typedef struct odp_init_t {
 	odp_log_func_t log_fn;
 	/** Replacement for the default abort fn */
 	odp_abort_func_t abort_fn;
+	/** Allow timer maintenance work to run on worker cores.
+
+	    When enabled, work will run from within odp_schedule().
+	    When disabled, work will run in background threads.
+
+	    Default: disabled (false)
+	*/
+	odp_bool_t worker_timers;
 } odp_init_t;
 
 /**
diff --git a/platform/linux-generic/include/odp_timer_internal.h b/platform/linux-generic/include/odp_timer_internal.h
index 91b12c54..4a1bbeb3 100644
--- a/platform/linux-generic/include/odp_timer_internal.h
+++ b/platform/linux-generic/include/odp_timer_internal.h
@@ -20,6 +20,9 @@ 
 #include <odp_pool_internal.h>
 #include <odp/api/timer.h>
 
+/* Minimum number of nanoseconds between calls to _timer_run(). */
+#define CONFIG_TIMER_RUN_RATELIMIT_PERIOD 100
+
 /**
  * Internal Timeout header
  */
@@ -35,4 +38,8 @@  typedef struct {
 	odp_timer_t timer;
 } odp_timeout_hdr_t;
 
+odp_bool_t worker_timers;
+
+int timer_run(void);
+
 #endif
diff --git a/platform/linux-generic/odp_init.c b/platform/linux-generic/odp_init.c
index 685e02fa..f08f221c 100644
--- a/platform/linux-generic/odp_init.c
+++ b/platform/linux-generic/odp_init.c
@@ -4,11 +4,14 @@ 
  * SPDX-License-Identifier:     BSD-3-Clause
  */
 #include <odp/api/init.h>
-#include <odp_debug_internal.h>
 #include <odp/api/debug.h>
-#include <unistd.h>
+
 #include <odp_internal.h>
+#include <odp_debug_internal.h>
 #include <odp_schedule_if.h>
+#include <odp_timer_internal.h>
+
+#include <unistd.h>
 #include <string.h>
 #include <libconfig.h>
 #include <stdlib.h>
@@ -165,6 +168,8 @@  int odp_init_global(odp_instance_t *instance,
 			odp_global_data.log_fn = params->log_fn;
 		if (params->abort_fn != NULL)
 			odp_global_data.abort_fn = params->abort_fn;
+		if (params->worker_timers)
+			worker_timers = true;
 	}
 
 	cleanup_files(_ODP_TMPDIR, odp_global_data.main_pid);
diff --git a/platform/linux-generic/odp_schedule.c b/platform/linux-generic/odp_schedule.c
index f680ac47..0c634f62 100644
--- a/platform/linux-generic/odp_schedule.c
+++ b/platform/linux-generic/odp_schedule.c
@@ -22,6 +22,7 @@ 
 #include <odp/api/sync.h>
 #include <odp_ring_internal.h>
 #include <odp_queue_internal.h>
+#include <odp_timer_internal.h>
 
 /* Number of priority levels  */
 #define NUM_PRIO 8
@@ -985,7 +986,6 @@  static inline int do_schedule(odp_queue_t *out_queue, odp_event_t out_ev[],
 	return 0;
 }
 
-
 static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,
 			 odp_event_t out_ev[],
 			 unsigned int max_num)
@@ -995,6 +995,9 @@  static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,
 	int ret;
 
 	while (1) {
+		if (worker_timers)
+			(void)timer_run();
+
 		ret = do_schedule(out_queue, out_ev, max_num);
 
 		if (ret)
diff --git a/platform/linux-generic/odp_schedule_iquery.c b/platform/linux-generic/odp_schedule_iquery.c
index b8a40011..67457d8f 100644
--- a/platform/linux-generic/odp_schedule_iquery.c
+++ b/platform/linux-generic/odp_schedule_iquery.c
@@ -22,6 +22,7 @@ 
 #include <odp/api/cpu.h>
 #include <odp/api/thrmask.h>
 #include <odp_config_internal.h>
+#include <odp_timer_internal.h>
 
 /* Number of priority levels */
 #define NUM_SCHED_PRIO 8
@@ -718,6 +719,9 @@  static int schedule_loop(odp_queue_t *out_queue, uint64_t wait,
 	odp_time_t next, wtime;
 
 	while (1) {
+		if (worker_timers)
+			(void)timer_run();
+
 		count = do_schedule(out_queue, out_ev, max_num);
 
 		if (count)
diff --git a/platform/linux-generic/odp_schedule_sp.c b/platform/linux-generic/odp_schedule_sp.c
index 0fd4d87d..bef9ec01 100644
--- a/platform/linux-generic/odp_schedule_sp.c
+++ b/platform/linux-generic/odp_schedule_sp.c
@@ -15,6 +15,7 @@ 
 #include <odp_align_internal.h>
 #include <odp_config_internal.h>
 #include <odp_ring_internal.h>
+#include <odp_timer_internal.h>
 
 #define NUM_THREAD        ODP_THREAD_COUNT_MAX
 #define NUM_QUEUE         ODP_CONFIG_QUEUES
@@ -501,6 +502,9 @@  static int schedule_multi(odp_queue_t *from, uint64_t wait,
 	odp_time_t t1;
 	int update_t1 = 1;
 
+	if (worker_timers)
+		(void)timer_run();
+
 	if (sched_local.cmd) {
 		/* Continue scheduling if queue is not empty */
 		if (sched_cb_queue_empty(sched_local.cmd->s.index) == 0)
diff --git a/platform/linux-generic/odp_timer.c b/platform/linux-generic/odp_timer.c
index 80dce873..9c48c4a0 100644
--- a/platform/linux-generic/odp_timer.c
+++ b/platform/linux-generic/odp_timer.c
@@ -61,6 +61,8 @@ 
  * for checking the freshness of received timeouts */
 #define TMO_INACTIVE ((uint64_t)0x8000000000000000)
 
+odp_bool_t worker_timers = false;
+
 /******************************************************************************
  * Mutual exclusion in the absence of CAS16
  *****************************************************************************/
@@ -166,7 +168,8 @@  static inline void set_next_free(odp_timer *tim, uint32_t nf)
  *****************************************************************************/
 
 typedef struct odp_timer_pool_s {
-/* Put frequently accessed fields in the first cache line */
+	uint64_t cpu_tick; /* CPU tick of beginning of last scan */
+	uint64_t cpu_ticks_per_tp_tick;
 	odp_atomic_u64_t cur_tick;/* Current tick value */
 	uint64_t min_rel_tck;
 	uint64_t max_rel_tck;
@@ -220,7 +223,6 @@  static inline odp_timer_t tp_idx_to_handle(struct odp_timer_pool_s *tp,
 	return _odp_cast_scalar(odp_timer_t, (tp->tp_idx << INDEX_BITS) | idx);
 }
 
-/* Forward declarations */
 static void itimer_init(odp_timer_pool *tp);
 static void itimer_fini(odp_timer_pool *tp);
 
@@ -243,6 +245,11 @@  static odp_timer_pool_t odp_timer_pool_new(const char *name,
 		ODP_ABORT("%s: timer pool shm-alloc(%zuKB) failed\n",
 			  name, (sz0 + sz1 + sz2) / 1024);
 	odp_timer_pool *tp = (odp_timer_pool *)odp_shm_addr(shm);
+
+	if (worker_timers) {
+		tp->cpu_tick = odp_tick_now();
+		tp->cpu_ticks_per_tp_tick = odp_ticks_from_ns(param->res_ns);
+	}
 	odp_atomic_init_u64(&tp->cur_tick, 0);
 
 	if (name == NULL) {
@@ -277,8 +284,10 @@  static odp_timer_pool_t odp_timer_pool_new(const char *name,
 	tp->tp_idx = tp_idx;
 	odp_spinlock_init(&tp->lock);
 	timer_pool[tp_idx] = tp;
-	if (tp->param.clk_src == ODP_CLOCK_CPU)
-		itimer_init(tp);
+	if (!worker_timers) {
+		if (tp->param.clk_src == ODP_CLOCK_CPU)
+			itimer_init(tp);
+	}
 	return tp;
 }
 
@@ -307,11 +316,13 @@  static void odp_timer_pool_del(odp_timer_pool *tp)
 	odp_spinlock_lock(&tp->lock);
 	timer_pool[tp->tp_idx] = NULL;
 
-	/* Stop timer triggering */
-	if (tp->param.clk_src == ODP_CLOCK_CPU)
-		itimer_fini(tp);
+	if (!worker_timers) {
+		/* Stop timer triggering */
+		if (tp->param.clk_src == ODP_CLOCK_CPU)
+			itimer_fini(tp);
 
-	stop_timer_thread(tp);
+		stop_timer_thread(tp);
+	}
 
 	if (tp->num_alloc != 0) {
 		/* It's a programming error to attempt to destroy a */
@@ -671,6 +682,63 @@  static unsigned odp_timer_pool_expire(odp_timer_pool_t tpid, uint64_t tick)
 	return nexp;
 }
 
+static int _timer_run(void)
+{
+	uint64_t last_expired, now, nticks;
+	odp_timer_pool *tp;
+	size_t i;
+	int nexp = 0;
+
+	for (i = 0; i < MAX_TIMER_POOLS; i++) {
+		tp = timer_pool[i];
+
+		if (tp == NULL)
+			break;
+
+		/* Check the last time this timer pool was expired. If one
+		 * or more periods have passed, attempt to expire it. */
+		last_expired = tp->cpu_tick;
+		now = odp_tick_now();
+		nticks = (now - last_expired) / tp->cpu_ticks_per_tp_tick;
+
+		if (nticks < 1)
+			continue;
+
+		if (__atomic_compare_exchange_n(
+			    &tp->cpu_tick, &last_expired,
+			    last_expired + (tp->cpu_ticks_per_tp_tick * nticks),
+			    false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
+			uint64_t tp_tick = _odp_atomic_u64_fetch_add_mm(
+				&tp->cur_tick, nticks, _ODP_MEMMODEL_RLX);
+
+			if (tp->notify_overrun && nticks > 1) {
+				ODP_ERR("\n\t%d ticks overrun on timer pool "
+					"\"%s\", timer resolution too high\n",
+					nticks, tp->name);
+				tp->notify_overrun = 0;
+			}
+			nexp += odp_timer_pool_expire(tp, tp_tick + nticks);
+		}
+	}
+	return nexp;
+}
+
+static uint64_t cpu_ticks_per_ratelimit;
+
+int timer_run(void)
+{
+	static __thread uint64_t last_timerscan_run;
+	uint64_t now = odp_tick_now();
+
+	/* Rate limit how often this thread checks the timer pools. */
+	if ((now - last_timerscan_run) < cpu_ticks_per_ratelimit)
+		return 0;
+
+	last_timerscan_run = now;
+
+	return _timer_run();
+}
+
 /******************************************************************************
  * POSIX timer support
  * Functions that use Linux/POSIX per-process timers and related facilities
@@ -1006,7 +1074,11 @@  int odp_timer_init_global(void)
 #endif
 	odp_atomic_init_u32(&num_timer_pools, 0);
 
-	block_sigalarm();
+	cpu_ticks_per_ratelimit =
+		odp_ticks_from_ns(CONFIG_TIMER_RUN_RATELIMIT_PERIOD);
+
+	if (!worker_timers)
+		block_sigalarm();
 
 	return 0;
 }