It is possible that schedule(), and hence cyclic_run(), gets called very early, perhaps even from assembly code. With commit9c1b13b3fd("cyclic: reduce get_timer_us() calls inside hlist_for_each_entry_safe()"), there is now an unconditional get_timer_us(0) done outside the loop, and depending on the platform, the timer infrastructure may not be set up yet. In at least one case, that has caused a divide-by-0 and hence a failure to boot. Platforms should really ensure their timers are ready ASAP, and in the concrete case reported, that was indeed possible to fix that way. However, it doesn't hurt to also insert an early return here, and that could prevent other such hard-to-debug boot failures. Reported-by: Emanuele Ghidoli <ghidoliemanuele@gmail.com> Link: https://marc.info/?l=u-boot&m=178481834846283&w=2 Fixes:9c1b13b3fd("cyclic: reduce get_timer_us() calls inside hlist_for_each_entry_safe()") Signed-off-by: Rasmus Villemoes <rv@rasmusvillemoes.dk> Reviewed-by: Emanuele Ghidoli <emanuele.ghidoli@toradex.com> Reviewed-by: Stefan Roese <stefan.roese@mailbox.org> [sr: fix Fixes: tag SHA length and return statement indentation]
144 lines
3.5 KiB
C
144 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* A general-purpose cyclic execution infrastructure, to allow "small"
|
|
* (run-time wise) functions to be executed at a specified frequency.
|
|
* Things like LED blinking or watchdog triggering are examples for such
|
|
* tasks.
|
|
*
|
|
* Copyright (C) 2022 Stefan Roese <sr@denx.de>
|
|
*/
|
|
|
|
#include <cyclic.h>
|
|
#include <log.h>
|
|
#include <malloc.h>
|
|
#include <time.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/list.h>
|
|
#include <asm/global_data.h>
|
|
#include <u-boot/schedule.h>
|
|
#include <uthread.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
void hw_watchdog_reset(void);
|
|
|
|
static bool cyclic_is_registered(const struct cyclic_info *cyclic)
|
|
{
|
|
const struct cyclic_info *c;
|
|
|
|
hlist_for_each_entry(c, &gd->cyclic_list, list) {
|
|
if (c == cyclic)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void cyclic_register(struct cyclic_info *cyclic, cyclic_func_t func,
|
|
u64 delay_us, const char *name)
|
|
{
|
|
cyclic_unregister(cyclic);
|
|
|
|
memset(cyclic, 0, sizeof(*cyclic));
|
|
|
|
/* Store values in struct */
|
|
cyclic->func = func;
|
|
cyclic->name = name;
|
|
cyclic->delay_us = delay_us;
|
|
cyclic->start_time_us = get_timer_us(0);
|
|
hlist_add_head(&cyclic->list, &gd->cyclic_list);
|
|
}
|
|
|
|
void cyclic_unregister(struct cyclic_info *cyclic)
|
|
{
|
|
if (!cyclic_is_registered(cyclic))
|
|
return;
|
|
|
|
hlist_del(&cyclic->list);
|
|
}
|
|
|
|
static void cyclic_run(void)
|
|
{
|
|
struct cyclic_info *cyclic;
|
|
struct hlist_node *tmp;
|
|
u64 now, after, cpu_time;
|
|
|
|
/*
|
|
* Nothing to do if the list is empty. Also, schedule() can be
|
|
* called before timer infrastructure is ready, in which case
|
|
* calling get_timer_us() before the (empty) loop could cause
|
|
* a divide-by-0 or otherwise crash the system. No clients
|
|
* should be registered before the timer infrastructure is up,
|
|
* so the check for the list being empty should be
|
|
* ok. Otherwise, we would need a new GD_FLG_TIMERS_READY
|
|
* flag.
|
|
*/
|
|
if (hlist_empty(&gd->cyclic_list))
|
|
return;
|
|
|
|
/* Prevent recursion */
|
|
if (gd->flags & GD_FLG_CYCLIC_RUNNING)
|
|
return;
|
|
|
|
gd->flags |= GD_FLG_CYCLIC_RUNNING;
|
|
now = get_timer_us(0);
|
|
hlist_for_each_entry_safe(cyclic, tmp, &gd->cyclic_list, list) {
|
|
/*
|
|
* Check if this cyclic function needs to get called, e.g.
|
|
* do not call the cyclic func too often
|
|
*/
|
|
if (time_after_eq64(now, cyclic->next_call)) {
|
|
/* Call cyclic function and account it's cpu-time */
|
|
cyclic->next_call = now + cyclic->delay_us;
|
|
cyclic->func(cyclic);
|
|
after = get_timer_us(0);
|
|
cyclic->run_cnt++;
|
|
cpu_time = after - now;
|
|
cyclic->cpu_time_us += cpu_time;
|
|
now = after;
|
|
|
|
/* Check if cpu-time exceeds max allowed time */
|
|
if ((cpu_time > CONFIG_CYCLIC_MAX_CPU_TIME_US) &&
|
|
(!cyclic->already_warned)) {
|
|
pr_err("cyclic function %s took too long: %lldus vs %dus max\n",
|
|
cyclic->name, cpu_time,
|
|
CONFIG_CYCLIC_MAX_CPU_TIME_US);
|
|
|
|
/*
|
|
* Don't disable this function, just warn once
|
|
* about this exceeding CPU time usage
|
|
*/
|
|
cyclic->already_warned = true;
|
|
}
|
|
}
|
|
}
|
|
gd->flags &= ~GD_FLG_CYCLIC_RUNNING;
|
|
}
|
|
|
|
void schedule(void)
|
|
{
|
|
/* The HW watchdog is not integrated into the cyclic IF (yet) */
|
|
if (IS_ENABLED(CONFIG_HW_WATCHDOG))
|
|
hw_watchdog_reset();
|
|
|
|
/*
|
|
* schedule() might get called very early before the cyclic IF is
|
|
* ready. Make sure to only call cyclic_run() when it's initalized.
|
|
*/
|
|
if (gd)
|
|
cyclic_run();
|
|
|
|
uthread_schedule();
|
|
}
|
|
|
|
int cyclic_unregister_all(void)
|
|
{
|
|
struct cyclic_info *cyclic;
|
|
struct hlist_node *tmp;
|
|
|
|
hlist_for_each_entry_safe(cyclic, tmp, &gd->cyclic_list, list)
|
|
cyclic_unregister(cyclic);
|
|
|
|
return 0;
|
|
}
|