sched: Avoid rescheduling the currently active timer

It's tricky to reschedule the timer irq correctly (due to race
conditions with the irq) and in practice it's very rarely needed.
Handle the special cases in the generic sched.c code so that the board
code doesn't have to handle it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2017-03-10 20:03:56 -05:00
parent cb286ede9d
commit 0a3c23bcf6
5 changed files with 53 additions and 90 deletions

View File

@ -38,13 +38,6 @@ timer_set(uint16_t next)
OCR1A = next; OCR1A = next;
} }
static inline void
timer_set_clear(uint16_t next)
{
OCR1A = next;
TIFR1 = 1<<OCF1A;
}
static inline void static inline void
timer_repeat_set(uint16_t next) timer_repeat_set(uint16_t next)
{ {
@ -78,6 +71,15 @@ timer_init(void)
} }
DECL_INIT(timer_init); DECL_INIT(timer_init);
static void
timer_shutdown(void)
{
// Reenable timer irq
timer_set(timer_get() + 50);
TIFR1 = 1<<OCF1A;
}
DECL_SHUTDOWN(timer_shutdown);
/**************************************************************** /****************************************************************
* 32bit timer wrappers * 32bit timer wrappers
@ -120,35 +122,15 @@ timer_periodic(void)
} }
} }
#define TIMER_MIN_TICKS 100
// Set the next timer wake time (in absolute clock ticks). Caller
// must disable irqs. The caller should not schedule a time more than
// a few milliseconds in the future.
uint8_t
timer_set_next(uint32_t next)
{
uint32_t cur = timer_read_time();
if ((int16_t)(OCR1A - (uint16_t)cur) < 0 && !(TIFR1 & (1<<OCF1A)))
// Already processing timer irqs
try_shutdown("timer_set_next called during timer dispatch");
uint32_t mintime = cur + TIMER_MIN_TICKS;
if (sched_is_before(mintime, next)) {
timer_set_clear(next);
return 0;
}
timer_set_clear(mintime);
return 1;
}
#define TIMER_IDLE_REPEAT_TICKS 8000 #define TIMER_IDLE_REPEAT_TICKS 8000
#define TIMER_REPEAT_TICKS 3000 #define TIMER_REPEAT_TICKS 3000
#define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress #define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
#define TIMER_DEFER_REPEAT_TICKS 200 #define TIMER_DEFER_REPEAT_TICKS 200
// Similar to timer_set_next(), but wait for the given time if it is // Set the next timer wake time (in absolute clock ticks) or return 1
// in the near future. // if the next timer is too close to schedule. Caller must disable
// irqs.
uint8_t uint8_t
timer_try_set_next(uint32_t target) timer_try_set_next(uint32_t target)
{ {

View File

@ -12,7 +12,6 @@ void console_push_output(uint8_t len);
uint32_t timer_from_us(uint32_t us); uint32_t timer_from_us(uint32_t us);
void timer_periodic(void); void timer_periodic(void);
uint32_t timer_read_time(void); uint32_t timer_read_time(void);
uint8_t timer_set_next(uint32_t next);
uint8_t timer_try_set_next(uint32_t next); uint8_t timer_try_set_next(uint32_t next);
size_t alloc_maxsize(size_t reqsize); size_t alloc_maxsize(size_t reqsize);

View File

@ -43,13 +43,6 @@ timer_set(uint32_t value)
TC0->TC_CHANNEL[0].TC_RA = value; TC0->TC_CHANNEL[0].TC_RA = value;
} }
static void
timer_set_clear(uint32_t value)
{
TC0->TC_CHANNEL[0].TC_RA = value;
TC0->TC_CHANNEL[0].TC_SR; // read to clear irq pending
}
static void static void
timer_init(void) timer_init(void)
{ {
@ -69,6 +62,15 @@ timer_init(void)
} }
DECL_INIT(timer_init); DECL_INIT(timer_init);
static void
timer_shutdown(void)
{
// Reenable timer irq
timer_set(timer_read_time() + 50);
TC0->TC_CHANNEL[0].TC_SR; // read to clear irq pending
}
DECL_SHUTDOWN(timer_shutdown);
// Called by main code once every millisecond. (IRQs disabled.) // Called by main code once every millisecond. (IRQs disabled.)
void void
timer_periodic(void) timer_periodic(void)
@ -82,28 +84,6 @@ timer_read_time(void)
return TC0->TC_CHANNEL[0].TC_CV; return TC0->TC_CHANNEL[0].TC_CV;
} }
#define TIMER_MIN_TICKS 100
// Set the next timer wake time (in absolute clock ticks). Caller
// must disable irqs. The caller should not schedule a time more than
// a few milliseconds in the future.
uint8_t
timer_set_next(uint32_t next)
{
uint32_t cur = timer_read_time();
if (sched_is_before(TC0->TC_CHANNEL[0].TC_RA, cur)
&& !(TC0->TC_CHANNEL[0].TC_SR & TC_SR_CPAS))
// Already processing timer irqs
try_shutdown("timer_set_next called during timer dispatch");
uint32_t mintime = cur + TIMER_MIN_TICKS;
if (sched_is_before(mintime, next)) {
timer_set_clear(next);
return 0;
}
timer_set_clear(mintime);
return 1;
}
static uint32_t timer_repeat_until; static uint32_t timer_repeat_until;
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500) #define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
#define TIMER_REPEAT_TICKS timer_from_us(100) #define TIMER_REPEAT_TICKS timer_from_us(100)
@ -111,8 +91,9 @@ static uint32_t timer_repeat_until;
#define TIMER_MIN_TRY_TICKS timer_from_us(1) #define TIMER_MIN_TRY_TICKS timer_from_us(1)
#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5) #define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
// Similar to timer_set_next(), but wait for the given time if it is // Set the next timer wake time (in absolute clock ticks) or return 1
// in the near future. // if the next timer is too close to schedule. Caller must disable
// irqs.
uint8_t uint8_t
timer_try_set_next(uint32_t next) timer_try_set_next(uint32_t next)
{ {

View File

@ -101,33 +101,39 @@ sched_timer(struct timer *add)
{ {
uint32_t waketime = add->waketime; uint32_t waketime = add->waketime;
irqstatus_t flag = irq_save(); irqstatus_t flag = irq_save();
if (sched_is_before(waketime, timer_list->waketime)) { if (sched_is_before(waketime, timer_list->waketime))
// This timer is the next - insert at front of list and reschedule // Timer in past (or very near future)
add->next = timer_list; shutdown("Timer too close");
timer_list = add; // Find position in list and insert
uint8_t ret = timer_set_next(waketime); struct timer *pos = timer_list;
if (ret) while (!sched_is_before(waketime, pos->next->waketime))
shutdown("Timer too close"); pos = pos->next;
} else { add->next = pos->next;
// Find position in list and insert pos->next = add;
struct timer *pos = timer_list;
while (!sched_is_before(waketime, pos->next->waketime))
pos = pos->next;
add->next = pos->next;
pos->next = add;
}
irq_restore(flag); irq_restore(flag);
} }
// The deleted timer is used when deleting an active timer.
static uint_fast8_t
deleted_event(struct timer *t)
{
return SF_DONE;
}
static struct timer deleted_timer = {
.func = deleted_event,
};
// Remove a timer that may be live. // Remove a timer that may be live.
void void
sched_del_timer(struct timer *del) sched_del_timer(struct timer *del)
{ {
irqstatus_t flag = irq_save(); irqstatus_t flag = irq_save();
if (timer_list == del) { if (timer_list == del) {
// Deleting the next active timer - delete and reschedule // Deleting the next active timer - replace with deleted_timer
timer_list = del->next; deleted_timer.waketime = del->waketime;
timer_set_next(timer_list->waketime); deleted_timer.next = del->next;
timer_list = &deleted_timer;
} else { } else {
// Find and remove from timer list (if present) // Find and remove from timer list (if present)
struct timer *pos; struct timer *pos;
@ -197,13 +203,14 @@ sched_timer_kick(void)
// Shutdown all user timers on an emergency stop. // Shutdown all user timers on an emergency stop.
static void static void
timer_shutdown(void) sched_timer_shutdown(void)
{ {
timer_list = &ms_timer; timer_list = &deleted_timer;
deleted_timer.waketime = ms_timer.waketime;
deleted_timer.next = &ms_timer;
ms_timer.next = &sentinel_timer; ms_timer.next = &sentinel_timer;
timer_set_next(timer_list->waketime);
} }
DECL_SHUTDOWN(timer_shutdown); DECL_SHUTDOWN(sched_timer_shutdown);
/**************************************************************** /****************************************************************

View File

@ -69,12 +69,6 @@ timer_read_time(void)
return 0; // XXX return 0; // XXX
} }
uint8_t
timer_set_next(uint32_t next)
{
return 0;
}
uint8_t uint8_t
timer_try_set_next(uint32_t next) timer_try_set_next(uint32_t next)
{ {