mirror of
				https://github.com/bytecodealliance/wasm-micro-runtime.git
				synced 2025-10-31 05:11:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			470 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2019 Intel Corporation.  All rights reserved.
 | |
|  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
|  */
 | |
| 
 | |
| #include "runtime_timer.h"
 | |
| 
 | |
| #if 1
 | |
| #define PRINT(...) (void)0
 | |
| #else
 | |
| #define PRINT printf
 | |
| #endif
 | |
| 
 | |
| typedef struct _app_timer {
 | |
|     struct _app_timer *next;
 | |
|     uint32 id;
 | |
|     uint32 interval;
 | |
|     uint64 expiry;
 | |
|     bool is_periodic;
 | |
| } app_timer_t;
 | |
| 
 | |
| struct _timer_ctx {
 | |
|     app_timer_t *app_timers;
 | |
|     app_timer_t *idle_timers;
 | |
|     app_timer_t *free_timers;
 | |
|     uint32 max_timer_id;
 | |
|     int pre_allocated;
 | |
|     uint32 owner;
 | |
| 
 | |
|     /* mutex and condition */
 | |
|     korp_cond cond;
 | |
|     korp_mutex mutex;
 | |
| 
 | |
|     timer_callback_f timer_callback;
 | |
|     check_timer_expiry_f refresh_checker;
 | |
| };
 | |
| 
 | |
| uint64
 | |
| bh_get_tick_ms()
 | |
| {
 | |
|     return os_time_get_boot_microsecond() / 1000;
 | |
| }
 | |
| 
 | |
| uint32
 | |
| bh_get_elpased_ms(uint32 *last_system_clock)
 | |
| {
 | |
|     uint32 elpased_ms;
 | |
|     /* attention: the bh_get_tick_ms() return 64 bits integer, but
 | |
|        the bh_get_elpased_ms() is designed to use 32 bits clock count */
 | |
|     uint32 now = (uint32)bh_get_tick_ms();
 | |
| 
 | |
|     /* system clock overrun */
 | |
|     if (now < *last_system_clock) {
 | |
|         PRINT("system clock overrun!\n");
 | |
|         elpased_ms = now + (UINT32_MAX - *last_system_clock) + 1;
 | |
|     }
 | |
|     else {
 | |
|         elpased_ms = now - *last_system_clock;
 | |
|     }
 | |
| 
 | |
|     *last_system_clock = now;
 | |
|     return elpased_ms;
 | |
| }
 | |
| 
 | |
| static app_timer_t *
 | |
| remove_timer_from(timer_ctx_t ctx, uint32 timer_id, bool active_list)
 | |
| {
 | |
|     app_timer_t **head, *prev, *t;
 | |
| 
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
| 
 | |
|     if (active_list)
 | |
|         head = &ctx->app_timers;
 | |
|     else
 | |
|         head = &ctx->idle_timers;
 | |
| 
 | |
|     t = *head;
 | |
|     prev = NULL;
 | |
| 
 | |
|     while (t) {
 | |
|         if (t->id == timer_id) {
 | |
|             if (prev == NULL) {
 | |
|                 *head = t->next;
 | |
|                 PRINT("removed timer [%d] at head from list %d\n", t->id,
 | |
|                       active_list);
 | |
|             }
 | |
|             else {
 | |
|                 prev->next = t->next;
 | |
|                 PRINT("removed timer [%d] after [%d] from list %d\n", t->id,
 | |
|                       prev->id, active_list);
 | |
|             }
 | |
|             os_mutex_unlock(&ctx->mutex);
 | |
| 
 | |
|             if (active_list && prev == NULL && ctx->refresh_checker)
 | |
|                 ctx->refresh_checker(ctx);
 | |
|             return t;
 | |
|         }
 | |
|         else {
 | |
|             prev = t;
 | |
|             t = t->next;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| static app_timer_t *
 | |
| remove_timer(timer_ctx_t ctx, uint32 timer_id, bool *active)
 | |
| {
 | |
|     app_timer_t *t = remove_timer_from(ctx, timer_id, true);
 | |
| 
 | |
|     if (t) {
 | |
|         if (active)
 | |
|             *active = true;
 | |
|         return t;
 | |
|     }
 | |
| 
 | |
|     if (active)
 | |
|         *active = false;
 | |
|     return remove_timer_from(ctx, timer_id, false);
 | |
| }
 | |
| 
 | |
| static void
 | |
| reschedule_timer(timer_ctx_t ctx, app_timer_t *timer)
 | |
| {
 | |
|     app_timer_t *t;
 | |
|     app_timer_t *prev = NULL;
 | |
| 
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
| 
 | |
|     t = ctx->app_timers;
 | |
|     timer->next = NULL;
 | |
|     timer->expiry = bh_get_tick_ms() + timer->interval;
 | |
| 
 | |
|     while (t) {
 | |
|         if (timer->expiry < t->expiry) {
 | |
|             if (prev == NULL) {
 | |
|                 timer->next = ctx->app_timers;
 | |
|                 ctx->app_timers = timer;
 | |
|                 PRINT("rescheduled timer [%d] at head\n", timer->id);
 | |
|             }
 | |
|             else {
 | |
|                 timer->next = t;
 | |
|                 prev->next = timer;
 | |
|                 PRINT("rescheduled timer [%d] after [%d]\n", timer->id,
 | |
|                       prev->id);
 | |
|             }
 | |
| 
 | |
|             goto out;
 | |
|         }
 | |
|         else {
 | |
|             prev = t;
 | |
|             t = t->next;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (prev) {
 | |
|         /* insert to the list end */
 | |
|         prev->next = timer;
 | |
|         PRINT("rescheduled timer [%d] at end, after [%d]\n", timer->id,
 | |
|               prev->id);
 | |
|     }
 | |
|     else {
 | |
|         /* insert at the begin */
 | |
|         bh_assert(ctx->app_timers == NULL);
 | |
|         ctx->app_timers = timer;
 | |
|         PRINT("rescheduled timer [%d] as first\n", timer->id);
 | |
|     }
 | |
| 
 | |
| out:
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
| 
 | |
|     /* ensure the refresh_checker() is called out of the lock */
 | |
|     if (prev == NULL && ctx->refresh_checker)
 | |
|         ctx->refresh_checker(ctx);
 | |
| }
 | |
| 
 | |
| static void
 | |
| release_timer(timer_ctx_t ctx, app_timer_t *t)
 | |
| {
 | |
|     if (ctx->pre_allocated) {
 | |
|         os_mutex_lock(&ctx->mutex);
 | |
|         t->next = ctx->free_timers;
 | |
|         ctx->free_timers = t;
 | |
|         PRINT("recycle timer :%d\n", t->id);
 | |
|         os_mutex_unlock(&ctx->mutex);
 | |
|     }
 | |
|     else {
 | |
|         PRINT("destroy timer :%d\n", t->id);
 | |
|         BH_FREE(t);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| release_timer_list(app_timer_t **p_list)
 | |
| {
 | |
|     app_timer_t *t = *p_list;
 | |
| 
 | |
|     while (t) {
 | |
|         app_timer_t *next = t->next;
 | |
|         PRINT("destroy timer list:%d\n", t->id);
 | |
|         BH_FREE(t);
 | |
|         t = next;
 | |
|     }
 | |
| 
 | |
|     *p_list = NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * API exposed
 | |
|  */
 | |
| 
 | |
| timer_ctx_t
 | |
| create_timer_ctx(timer_callback_f timer_handler,
 | |
|                  check_timer_expiry_f expiery_checker, int prealloc_num,
 | |
|                  unsigned int owner)
 | |
| {
 | |
|     timer_ctx_t ctx = (timer_ctx_t)BH_MALLOC(sizeof(struct _timer_ctx));
 | |
| 
 | |
|     if (ctx == NULL)
 | |
|         return NULL;
 | |
| 
 | |
|     memset(ctx, 0, sizeof(struct _timer_ctx));
 | |
| 
 | |
|     ctx->timer_callback = timer_handler;
 | |
|     ctx->pre_allocated = prealloc_num;
 | |
|     ctx->refresh_checker = expiery_checker;
 | |
|     ctx->owner = owner;
 | |
| 
 | |
|     while (prealloc_num > 0) {
 | |
|         app_timer_t *timer = (app_timer_t *)BH_MALLOC(sizeof(app_timer_t));
 | |
| 
 | |
|         if (timer == NULL)
 | |
|             goto cleanup;
 | |
| 
 | |
|         memset(timer, 0, sizeof(*timer));
 | |
|         timer->next = ctx->free_timers;
 | |
|         ctx->free_timers = timer;
 | |
|         prealloc_num--;
 | |
|     }
 | |
| 
 | |
|     if (os_cond_init(&ctx->cond) != 0)
 | |
|         goto cleanup;
 | |
| 
 | |
|     if (os_mutex_init(&ctx->mutex) != 0) {
 | |
|         os_cond_destroy(&ctx->cond);
 | |
|         goto cleanup;
 | |
|     }
 | |
| 
 | |
|     PRINT("timer ctx created. pre-alloc: %d\n", ctx->pre_allocated);
 | |
|     return ctx;
 | |
| 
 | |
| cleanup:
 | |
|     if (ctx) {
 | |
|         release_timer_list(&ctx->free_timers);
 | |
|         BH_FREE(ctx);
 | |
|     }
 | |
|     PRINT("timer ctx create failed\n");
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| void
 | |
| destroy_timer_ctx(timer_ctx_t ctx)
 | |
| {
 | |
|     while (ctx->free_timers) {
 | |
|         void *tmp = ctx->free_timers;
 | |
|         ctx->free_timers = ctx->free_timers->next;
 | |
|         BH_FREE(tmp);
 | |
|     }
 | |
| 
 | |
|     cleanup_app_timers(ctx);
 | |
| 
 | |
|     os_cond_destroy(&ctx->cond);
 | |
|     os_mutex_destroy(&ctx->mutex);
 | |
|     BH_FREE(ctx);
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| timer_ctx_get_owner(timer_ctx_t ctx)
 | |
| {
 | |
|     return ctx->owner;
 | |
| }
 | |
| 
 | |
| void
 | |
| add_idle_timer(timer_ctx_t ctx, app_timer_t *timer)
 | |
| {
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
|     timer->next = ctx->idle_timers;
 | |
|     ctx->idle_timers = timer;
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
| }
 | |
| 
 | |
| uint32
 | |
| sys_create_timer(timer_ctx_t ctx, int interval, bool is_period, bool auto_start)
 | |
| {
 | |
|     app_timer_t *timer;
 | |
| 
 | |
|     if (ctx->pre_allocated) {
 | |
|         if (ctx->free_timers == NULL) {
 | |
|             return (uint32)-1;
 | |
|         }
 | |
|         else {
 | |
|             timer = ctx->free_timers;
 | |
|             ctx->free_timers = timer->next;
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         timer = (app_timer_t *)BH_MALLOC(sizeof(app_timer_t));
 | |
|         if (timer == NULL)
 | |
|             return (uint32)-1;
 | |
|     }
 | |
| 
 | |
|     memset(timer, 0, sizeof(*timer));
 | |
| 
 | |
|     ctx->max_timer_id++;
 | |
|     if (ctx->max_timer_id == (uint32)-1)
 | |
|         ctx->max_timer_id++;
 | |
|     timer->id = ctx->max_timer_id;
 | |
|     timer->interval = (uint32)interval;
 | |
|     timer->is_periodic = is_period;
 | |
| 
 | |
|     if (auto_start)
 | |
|         reschedule_timer(ctx, timer);
 | |
|     else
 | |
|         add_idle_timer(ctx, timer);
 | |
| 
 | |
|     return timer->id;
 | |
| }
 | |
| 
 | |
| bool
 | |
| sys_timer_cancel(timer_ctx_t ctx, uint32 timer_id)
 | |
| {
 | |
|     bool from_active;
 | |
|     app_timer_t *t = remove_timer(ctx, timer_id, &from_active);
 | |
| 
 | |
|     if (t == NULL)
 | |
|         return false;
 | |
| 
 | |
|     add_idle_timer(ctx, t);
 | |
| 
 | |
|     PRINT("sys_timer_stop called\n");
 | |
|     return from_active;
 | |
| }
 | |
| 
 | |
| bool
 | |
| sys_timer_destroy(timer_ctx_t ctx, uint32 timer_id)
 | |
| {
 | |
|     bool from_active;
 | |
|     app_timer_t *t = remove_timer(ctx, timer_id, &from_active);
 | |
| 
 | |
|     if (t == NULL)
 | |
|         return false;
 | |
| 
 | |
|     release_timer(ctx, t);
 | |
| 
 | |
|     PRINT("sys_timer_destroy called\n");
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| sys_timer_restart(timer_ctx_t ctx, uint32 timer_id, int interval)
 | |
| {
 | |
|     app_timer_t *t = remove_timer(ctx, timer_id, NULL);
 | |
| 
 | |
|     if (t == NULL)
 | |
|         return false;
 | |
| 
 | |
|     t->interval = (uint32)interval;
 | |
| 
 | |
|     reschedule_timer(ctx, t);
 | |
| 
 | |
|     PRINT("sys_timer_restart called\n");
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * API called by the timer manager from another thread or the kernel timer
 | |
|  * handler
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * lookup the app queue by the module name
 | |
|  * post a timeout message to the app queue
 | |
|  */
 | |
| static void
 | |
| handle_expired_timers(timer_ctx_t ctx, app_timer_t *expired)
 | |
| {
 | |
|     while (expired) {
 | |
|         app_timer_t *t = expired;
 | |
|         ctx->timer_callback(t->id, ctx->owner);
 | |
| 
 | |
|         /* get next expired timer first, since the following
 | |
|            operation may change expired->next */
 | |
|         expired = expired->next;
 | |
|         if (t->is_periodic) {
 | |
|             /* if it is repeating, then reschedule it; */
 | |
|             reschedule_timer(ctx, t);
 | |
|         }
 | |
|         else {
 | |
|             /* else move it to idle list */
 | |
|             add_idle_timer(ctx, t);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| uint32
 | |
| get_expiry_ms(timer_ctx_t ctx)
 | |
| {
 | |
|     uint32 ms_to_next_expiry;
 | |
|     uint64 now = bh_get_tick_ms();
 | |
| 
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
|     if (ctx->app_timers == NULL)
 | |
|         ms_to_next_expiry = (uint32)-1;
 | |
|     else if (ctx->app_timers->expiry >= now)
 | |
|         ms_to_next_expiry = (uint32)(ctx->app_timers->expiry - now);
 | |
|     else
 | |
|         ms_to_next_expiry = 0;
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
| 
 | |
|     return ms_to_next_expiry;
 | |
| }
 | |
| 
 | |
| uint32
 | |
| check_app_timers(timer_ctx_t ctx)
 | |
| {
 | |
|     app_timer_t *t, *expired = NULL, *expired_end = NULL;
 | |
|     uint64 now = bh_get_tick_ms();
 | |
| 
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
| 
 | |
|     t = ctx->app_timers;
 | |
|     while (t) {
 | |
|         if (now >= t->expiry) {
 | |
|             ctx->app_timers = t->next;
 | |
| 
 | |
|             /* append t to the end of expired list */
 | |
|             t->next = NULL;
 | |
|             if (!expired_end) {
 | |
|                 expired = expired_end = t;
 | |
|             }
 | |
|             else {
 | |
|                 expired_end->next = t;
 | |
|                 expired_end = t;
 | |
|             }
 | |
| 
 | |
|             t = ctx->app_timers;
 | |
|         }
 | |
|         else {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
| 
 | |
|     handle_expired_timers(ctx, expired);
 | |
|     return get_expiry_ms(ctx);
 | |
| }
 | |
| 
 | |
| void
 | |
| cleanup_app_timers(timer_ctx_t ctx)
 | |
| {
 | |
|     os_mutex_lock(&ctx->mutex);
 | |
| 
 | |
|     release_timer_list(&ctx->app_timers);
 | |
|     release_timer_list(&ctx->idle_timers);
 | |
| 
 | |
|     os_mutex_unlock(&ctx->mutex);
 | |
| }
 |