klipper/src/sched.c

322 lines
7.9 KiB
C

// Basic scheduling functions and startup/shutdown code.
//
// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <setjmp.h> // setjmp
#include "autoconf.h" // CONFIG_*
#include "board/irq.h" // irq_save
#include "board/misc.h" // timer_from_us
#include "command.h" // shutdown
#include "sched.h" // sched_from_us
#include "stepper.h" // stepper_event
/****************************************************************
* Timers
****************************************************************/
// Return the number of clock ticks for a given number of microseconds
uint32_t
sched_from_us(uint32_t us)
{
return timer_from_us(us);
}
// Return the current time (in clock ticks)
uint32_t
sched_read_time(void)
{
return timer_read_time();
}
// Return true if time1 is before time2. Always use this function to
// compare times as regular C comparisons can fail if the counter
// rolls over.
uint8_t
sched_is_before(uint32_t time1, uint32_t time2)
{
return (int32_t)(time1 - time2) < 0;
}
static uint16_t millis;
// Check if ready for a recurring periodic event
uint8_t
sched_check_periodic(uint16_t time, uint16_t *pnext)
{
uint16_t next = *pnext, cur;
irqstatus_t flag = irq_save();
cur = millis;
irq_restore(flag);
if ((int16_t)(cur - next) < 0)
return 0;
*pnext = cur + time;
return 1;
}
static struct timer ms_timer, sentinel_timer, *timer_list = &ms_timer;
// Default millisecond timer. This timer counts milliseconds. It
// also simplifies the timer code by ensuring there is always a timer
// on the timer list and that there is always a timer not more than
// 1ms in the future.
static uint_fast8_t
ms_event(struct timer *t)
{
millis++;
timer_periodic();
ms_timer.waketime += sched_from_us(1000);
sentinel_timer.waketime = ms_timer.waketime + 0x80000000;
return SF_RESCHEDULE;
}
static struct timer ms_timer = {
.func = ms_event,
.next = &sentinel_timer,
};
// The sentinel timer is always the last timer on timer_list - its
// presence allows the code to avoid checking for NULL while
// traversing timer_list. Since sentinel_timer.waketime is always
// equal to (ms_timer.waketime + 0x80000000) any added timer must
// always have a waketime less than one of these two timers.
static uint_fast8_t
sentinel_event(struct timer *t)
{
shutdown("sentinel timer called");
}
static struct timer sentinel_timer = {
.func = sentinel_event,
.waketime = 0x80000000,
};
// Schedule a function call at a supplied time.
void
sched_add_timer(struct timer *add)
{
uint32_t waketime = add->waketime;
irqstatus_t flag = irq_save();
if (sched_is_before(waketime, timer_list->waketime))
// Timer in past (or very near future)
shutdown("Timer too close");
// Find position in list and insert
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);
}
// 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.
void
sched_del_timer(struct timer *del)
{
irqstatus_t flag = irq_save();
if (timer_list == del) {
// Deleting the next active timer - replace with deleted_timer
deleted_timer.waketime = del->waketime;
deleted_timer.next = del->next;
timer_list = &deleted_timer;
} else {
// Find and remove from timer list (if present)
struct timer *pos;
for (pos = timer_list; pos->next; pos = pos->next) {
if (pos->next == del) {
pos->next = del->next;
break;
}
}
}
irq_restore(flag);
}
// Move a rescheduled timer to its new location in the list. Returns
// the next timer to run.
static struct timer *
reschedule_timer(struct timer *t)
{
struct timer *pos = t->next;
uint32_t minwaketime = t->waketime + 1;
if (!sched_is_before(pos->waketime, minwaketime))
// Timer is still the first - no insertion needed
return t;
// Find new timer position and update list
timer_list = pos;
struct timer *prev;
for (;;) {
prev = pos;
if (CONFIG_MACH_AVR)
// micro optimization for AVR - reduces register pressure
asm("" : "+r"(prev) : : "memory");
pos = pos->next;
if (!sched_is_before(pos->waketime, minwaketime))
break;
}
t->next = pos;
prev->next = t;
return timer_list;
}
// Invoke timers - called from board timer irq code.
void
sched_timer_kick(void)
{
struct timer *t = timer_list;
for (;;) {
// Invoke timer callback
uint_fast8_t res;
if (CONFIG_INLINE_STEPPER_HACK && likely(!t->func))
res = stepper_event(t);
else
res = t->func(t);
// Update timer_list (rescheduling current timer if necessary)
if (unlikely(res == SF_DONE))
t = timer_list = t->next;
else
t = reschedule_timer(t);
// Schedule next timer event (or run next timer if it's ready)
res = timer_try_set_next(t->waketime);
if (res)
break;
}
}
// Shutdown all user timers on an emergency stop.
static void
sched_timer_shutdown(void)
{
timer_list = &deleted_timer;
deleted_timer.waketime = ms_timer.waketime;
deleted_timer.next = &ms_timer;
ms_timer.next = &sentinel_timer;
}
DECL_SHUTDOWN(sched_timer_shutdown);
/****************************************************************
* Shutdown processing
****************************************************************/
static uint16_t shutdown_reason;
static uint8_t shutdown_status;
// Return true if the machine is in an emergency stop state
uint8_t
sched_is_shutdown(void)
{
return !!shutdown_status;
}
uint16_t
sched_shutdown_reason(void)
{
return shutdown_reason;
}
// Transition out of shutdown state
void
sched_clear_shutdown(void)
{
if (!shutdown_status)
shutdown("Shutdown cleared when not shutdown");
if (shutdown_status == 2)
// Ignore attempt to clear shutdown if still processing shutdown
return;
shutdown_status = 0;
}
// Invoke all shutdown functions (as declared by DECL_SHUTDOWN)
static void
run_shutdown(void)
{
shutdown_status = 2;
struct callback_handler *p;
foreachdecl(p, shutdownfuncs) {
void (*func)(void) = READP(p->func);
func();
}
shutdown_status = 1;
irq_enable();
sendf("shutdown static_string_id=%hu", shutdown_reason);
}
// Shutdown the machine if not already in the process of shutting down
void
sched_try_shutdown(unsigned int reason)
{
if (shutdown_status != 2)
sched_shutdown(reason);
}
static jmp_buf shutdown_jmp;
// Force the machine to immediately run the shutdown handlers
void
sched_shutdown(unsigned int reason)
{
irq_disable();
if (!shutdown_status)
shutdown_reason = reason;
longjmp(shutdown_jmp, 1);
}
/****************************************************************
* Startup and background task processing
****************************************************************/
// Invoke all init functions (as declared by DECL_INIT)
static void
run_init(void)
{
struct callback_handler *p;
foreachdecl(p, initfuncs) {
void (*func)(void) = READP(p->func);
func();
}
}
// Invoke all background task functions (as declared by DECL_TASK)
static void
run_task(void)
{
struct callback_handler *p;
foreachdecl(p, taskfuncs) {
void (*func)(void) = READP(p->func);
func();
}
}
// Main loop of program
void
sched_main(void)
{
run_init();
int ret = setjmp(shutdown_jmp);
if (ret)
run_shutdown();
for (;;)
run_task();
}