zephyr/kernel/timer.c
Andy Ross 987c0e5fc1 kernel: New timeout implementation
Now that the API has been fixed up, replace the existing timeout queue
with a much smaller version.  The basic algorithm is unchanged:
timeouts are stored in a sorted dlist with each node nolding a delta
time from the previous node in the list; the announce call just walks
this list pulling off the heads as needed.  Advantages:

* Properly spinlocked and SMP-aware.  The earlier timer implementation
  relied on only CPU 0 doing timeout work, and on an irq_lock() being
  taken before entry (something that was violated in a few spots).
  Now any CPU can wake up for an event (or all of them) and everything
  works correctly.

* The *_thread_timeout() API is now expressible as a clean wrapping
  (just one liners) around the lower-level interface based on function
  pointer callbacks.  As a result the timeout objects no longer need
  to store backpointers to the thread and wait_q and have shrunk by
  33%.

* MUCH smaller, to the tune of hundreds of lines of code removed.

* Future proof, in that all operations on the queue are now fronted by
  just two entry points (_add_timeout() and z_clock_announce()) which
  can easily be augmented with fancier data structures.

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2018-10-16 15:03:10 -04:00

240 lines
5.2 KiB
C

/*
* Copyright (c) 1997-2016 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <debug/object_tracing_common.h>
#include <init.h>
#include <wait_q.h>
#include <syscall_handler.h>
extern struct k_timer _k_timer_list_start[];
extern struct k_timer _k_timer_list_end[];
#ifdef CONFIG_OBJECT_TRACING
struct k_timer *_trace_list_k_timer;
/*
* Complete initialization of statically defined timers.
*/
static int init_timer_module(struct device *dev)
{
ARG_UNUSED(dev);
struct k_timer *timer;
for (timer = _k_timer_list_start; timer < _k_timer_list_end; timer++) {
SYS_TRACING_OBJ_INIT(k_timer, timer);
}
return 0;
}
SYS_INIT(init_timer_module, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
#endif /* CONFIG_OBJECT_TRACING */
/**
* @brief Handle expiration of a kernel timer object.
*
* @param t Timeout used by the timer.
*
* @return N/A
*/
void _timer_expiration_handler(struct _timeout *t)
{
struct k_timer *timer = CONTAINER_OF(t, struct k_timer, timeout);
struct k_thread *thread;
unsigned int key;
/*
* if the timer is periodic, start it again; don't add _TICK_ALIGN
* since we're already aligned to a tick boundary
*/
if (timer->period > 0) {
key = irq_lock();
_add_timeout(&timer->timeout, _timer_expiration_handler,
timer->period);
irq_unlock(key);
}
/* update timer's status */
timer->status += 1;
/* invoke timer expiry function */
if (timer->expiry_fn) {
timer->expiry_fn(timer);
}
thread = _waitq_head(&timer->wait_q);
if (thread == NULL) {
return;
}
/*
* Interrupts _DO NOT_ have to be locked in this specific
* instance of thread unpending because a) this is the only
* place a thread can be taken off this pend queue, and b) the
* only place a thread can be put on the pend queue is at
* thread level, which of course cannot interrupt the current
* context.
*/
_unpend_thread_no_timeout(thread);
key = irq_lock();
_ready_thread(thread);
irq_unlock(key);
_set_thread_return_value(thread, 0);
}
void k_timer_init(struct k_timer *timer,
void (*expiry_fn)(struct k_timer *),
void (*stop_fn)(struct k_timer *))
{
timer->expiry_fn = expiry_fn;
timer->stop_fn = stop_fn;
timer->status = 0;
_waitq_init(&timer->wait_q);
_init_timeout(&timer->timeout, _timer_expiration_handler);
SYS_TRACING_OBJ_INIT(k_timer, timer);
timer->user_data = NULL;
_k_object_init(timer);
}
void _impl_k_timer_start(struct k_timer *timer, s32_t duration, s32_t period)
{
__ASSERT(duration >= 0 && period >= 0 &&
(duration != 0 || period != 0), "invalid parameters\n");
volatile s32_t period_in_ticks, duration_in_ticks;
period_in_ticks = _ms_to_ticks(period);
duration_in_ticks = _ms_to_ticks(duration);
unsigned int key = irq_lock();
(void)_abort_timeout(&timer->timeout);
timer->period = period_in_ticks;
timer->status = 0;
_add_timeout(&timer->timeout, _timer_expiration_handler,
duration_in_ticks);
irq_unlock(key);
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(k_timer_start, timer, duration_p, period_p)
{
s32_t duration, period;
duration = (s32_t)duration_p;
period = (s32_t)period_p;
Z_OOPS(Z_SYSCALL_VERIFY(duration >= 0 && period >= 0 &&
(duration != 0 || period != 0)));
Z_OOPS(Z_SYSCALL_OBJ(timer, K_OBJ_TIMER));
_impl_k_timer_start((struct k_timer *)timer, duration, period);
return 0;
}
#endif
void _impl_k_timer_stop(struct k_timer *timer)
{
unsigned int key = irq_lock();
int inactive = (_abort_timeout(&timer->timeout) == _INACTIVE);
irq_unlock(key);
if (inactive) {
return;
}
if (timer->stop_fn) {
timer->stop_fn(timer);
}
key = irq_lock();
struct k_thread *pending_thread = _unpend1_no_timeout(&timer->wait_q);
if (pending_thread != NULL) {
_ready_thread(pending_thread);
}
if (_is_in_isr()) {
irq_unlock(key);
} else {
_reschedule(key);
}
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER1_SIMPLE_VOID(k_timer_stop, K_OBJ_TIMER, struct k_timer *);
#endif
u32_t _impl_k_timer_status_get(struct k_timer *timer)
{
unsigned int key = irq_lock();
u32_t result = timer->status;
timer->status = 0;
irq_unlock(key);
return result;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER1_SIMPLE(k_timer_status_get, K_OBJ_TIMER, struct k_timer *);
#endif
u32_t _impl_k_timer_status_sync(struct k_timer *timer)
{
__ASSERT(!_is_in_isr(), "");
unsigned int key = irq_lock();
u32_t result = timer->status;
if (result == 0) {
if (timer->timeout.dticks != _INACTIVE) {
/* wait for timer to expire or stop */
(void)_pend_current_thread(key, &timer->wait_q, K_FOREVER);
/* get updated timer status */
key = irq_lock();
result = timer->status;
} else {
/* timer is already stopped */
}
} else {
/* timer has already expired at least once */
}
timer->status = 0;
irq_unlock(key);
return result;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER1_SIMPLE(k_timer_status_sync, K_OBJ_TIMER, struct k_timer *);
#endif
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER1_SIMPLE(k_timer_remaining_get, K_OBJ_TIMER, struct k_timer *);
Z_SYSCALL_HANDLER1_SIMPLE(k_timer_user_data_get, K_OBJ_TIMER, struct k_timer *);
Z_SYSCALL_HANDLER(k_timer_user_data_set, timer, user_data)
{
Z_OOPS(Z_SYSCALL_OBJ(timer, K_OBJ_TIMER));
_impl_k_timer_user_data_set((struct k_timer *)timer, (void *)user_data);
return 0;
}
#endif