blob: ef0b7402715eeb6f1f88b5ab40a13da4589ce92f [file] [log] [blame]
/*
* Copyright (c) 2013 The Chromium OS Authors.
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <errno.h>
#include <fdtdec.h>
#include <fthread.h>
#include <malloc.h>
#include <asm/global_data.h>
#include "fthread_priv.h"
DECLARE_GLOBAL_DATA_PTR;
/* thread library should only be initialized once */
static bool fthread_initialized;
/* Thread state printed information */
static const char * const fthread_state_names[] = {
[FTHREAD_STATE_SCHEDULER] = "SCHEDULER",
[FTHREAD_STATE_NEW] = "NEW",
[FTHREAD_STATE_READY] = "READY",
[FTHREAD_STATE_RUNNING] = "RUNNING",
[FTHREAD_STATE_WAITING] = "WAITING",
[FTHREAD_STATE_DEAD] = "DEAD"
};
int fthread_init(void)
{
int err;
if (fthread_initialized)
return -EBUSY;
debug("%s: enter\n", __func__);
/* initilize the scheduler */
fthread_scheduler_init();
/* spawn the scheduler thread */
err = fthread_spawn(fthread_scheduler, NULL, NULL, NULL, NULL,
FTHREAD_PRIO_MAX, "**SCHEDULER**",
FTHREAD_DEFAULT_STACKSIZE, &fthread_sched);
if (err) {
fthread_scheduler_kill();
return err;
}
/* spawn the main thread, which should have a stack size of 0 */
err = fthread_spawn(FTHREAD_MAIN_FUNC, NULL, NULL, NULL, NULL,
FTHREAD_PRIO_STD, "**main**", 0, &fthread_main);
if (err) {
fthread_scheduler_kill();
return err;
}
fthread_initialized = true;
/*
* We need to manually switch to the scheduler thread the first time to
* start threading. Since the main thread is currently the only thread
* in the scheduler, we should come back immediately. We also need to
* initialize fthread_current here so that the scheduler function is
* spawned correctly.
*/
fthread_current = fthread_sched;
fthread_mctx_switch(fthread_main->mctx, fthread_sched->mctx);
/* we will come right back to here after the scheduler initializes */
debug("%s: exit\n", __func__);
return 0;
}
int fthread_shutdown(void)
{
if (!fthread_initialized)
return -EPERM;
if (fthread_current != fthread_main)
return -EPERM;
debug("%s: enter\n", __func__);
#ifdef CONFIG_FTHREAD_REPORT
fthread_report();
#endif
fthread_scheduler_kill();
fthread_initialized = false;
fthread_tcb_free(fthread_sched);
fthread_tcb_free(fthread_main);
debug("%s: exit\n", __func__);
return 0;
}
void fthread_print_stats(struct fthread *t)
{
/* Spawned time */
print_grouped_ull(t->spawned_us, FTHREAD_REPORT_DIGITS);
/* State */
printf("%*s", FTHREAD_REPORT_DIGITS, fthread_state_names[t->state]);
/* Running time */
print_grouped_ull(t->running_us, FTHREAD_REPORT_DIGITS);
/* Number of dispatches */
print_grouped_ull(t->dispatches, FTHREAD_REPORT_DIGITS);
/* Last time the thread ran */
print_grouped_ull(t->lastran_us, FTHREAD_REPORT_DIGITS);
/* Total sleep error */
print_grouped_ull(t->err_us, FTHREAD_REPORT_DIGITS);
/* Average sleep error */
print_grouped_ull(t->num_sleeps == 0 ? 0 : t->err_us / t->num_sleeps,
FTHREAD_REPORT_DIGITS);
/* Biggest sleep error */
print_grouped_ull(t->maxerr_us, FTHREAD_REPORT_DIGITS);
/* Name */
printf(" %s\n", t->name);
}
void fthread_print_pqueue_stats(struct fthread_pqueue *q)
{
struct fthread *t;
for (t = fthread_pqueue_head(q); t != NULL;
t = fthread_pqueue_walk(q, t, FTHREAD_WALK_NEXT)) {
fthread_print_stats(t);
}
}
int fthread_report(void)
{
unsigned long gdflags = gd->flags;
if (!fthread_initialized)
return -EPERM;
/* Unsilence the console if necessary */
if (fdtdec_get_config_int(gd->fdt_blob, "force-fthread-report", 0))
gd->flags &= ~GD_FLG_SILENT;
puts("Thread summary in microseconds:\n");
printf("%*s%*s%*s%*s%*s%*s%*s%*s %s\n",
FTHREAD_REPORT_NAME, "Spawned",
FTHREAD_REPORT_DIGITS, "State",
FTHREAD_REPORT_NAME, "Running",
FTHREAD_REPORT_NAME, "Dispatches",
FTHREAD_REPORT_NAME, "Lastran",
FTHREAD_REPORT_NAME, "Wait Err(us)",
FTHREAD_REPORT_NAME, "Avg Err(us)",
FTHREAD_REPORT_NAME, "Max Err(us)",
"Name");
/* Print runtime information for each queue */
fthread_print_pqueue_stats(&fthread_nq);
fthread_print_pqueue_stats(&fthread_rq);
fthread_print_pqueue_stats(&fthread_wq);
fthread_print_pqueue_stats(&fthread_dq);
/*
* The current thread and the scheduler will not be in any queue so we
* must explicitly print their runtime information
*/
fthread_print_stats(fthread_current);
fthread_print_stats(fthread_sched);
/* Restore global data flags */
gd->flags = gdflags;
return 0;
}
int fthread_tcb_alloc(unsigned int stacksize, struct fthread **threadp)
{
struct fthread *t = malloc(sizeof(struct fthread));
if (t == NULL)
return -ENOMEM;
t->stacksize = stacksize;
t->stack = NULL;
if (stacksize > 0) { /* stacksize == 0 => main thread */
t->stack = malloc(stacksize);
if (t->stack == NULL) {
free(t);
return -ENOMEM;
}
}
t->mctx = fthread_mctx_alloc();
if (t->mctx == NULL) {
free(t->stack);
free(t);
return -ENOMEM;
}
*threadp = t;
return 0;
}
void fthread_tcb_free(struct fthread *t)
{
fthread_mctx_free(t->mctx);
free(t->stack);
free(t);
}
/**
* fthread_spawn_helper() - Jump start a new thread of execution.
*/
static void fthread_spawn_helper(void)
{
void *data;
data = (*fthread_current->start_func)(fthread_current->start_arg);
/* do an implicit exit with the return value */
fthread_exit(data);
}
int fthread_spawn(void *(*func)(void *), void *arg, void (*pre_start)(void *),
void (*post_stop)(void *), void *context, int prio,
const char *name, size_t stacksize, struct fthread **threadp)
{
unsigned long time;
char *sk_addr_hi;
struct fthread *t;
int err;
debug("%s: spawning thread \"%s\"\n", __func__, name);
if (func == NULL)
return -EINVAL;
/* special case of the main thread */
if (func == FTHREAD_MAIN_FUNC)
func = NULL;
if (fthread_tcb_alloc(stacksize, &t))
return -ENOMEM;
t->prio = prio;
strncpy(t->name, name, FTHREAD_TCB_NAMELEN);
/* initialize time points */
time = fthread_get_current_time_us();
t->spawned_us = time;
t->lastran_us = time;
t->running_us = 0;
t->dispatches = 0;
t->err_us = 0;
t->maxerr_us = 0;
t->num_sleeps = 0;
/* initialize global state preserving functions */
t->pre_start = pre_start;
t->post_stop = post_stop;
t->context = context;
/* initialize starting and ending values */
t->start_func = func;
t->start_arg = arg;
t->join_arg = NULL;
/* initialize machine state */
if (stacksize > 0) { /* The main thread has stacksize == 0 */
sk_addr_hi = t->stack + t->stacksize;
err = fthread_mctx_set(t->mctx, fthread_spawn_helper, t->stack,
sk_addr_hi);
if (err) {
fthread_tcb_free(t);
return err;
}
}
/* Add the thread to the new queue so the scheduler will pick it up */
if (func != fthread_scheduler) {
t->state = FTHREAD_STATE_NEW;
fthread_pqueue_insert(&fthread_nq, t->prio, t);
}
*threadp = t;
debug("%s: exit\n", __func__);
return 0;
}
inline void fthread_yield(void)
{
debug("%s: thread \"%s\" giving up control to scheduler\n",
__func__, fthread_current->name);
fthread_mctx_switch(fthread_current->mctx, fthread_sched->mctx);
debug("%s: returning to thread \"%s\"\n", __func__,
fthread_current->name);
}
unsigned long fthread_usleep(unsigned long waittime)
{
unsigned long actualtime = waittime;
unsigned long err;
/*
* If U-Boot has not relocated or if fthread isn't initialized then
* don't do anything and just sleep
*/
if ((gd->flags & GD_FLG_RELOC) == 0 || !fthread_initialized) {
__udelay(waittime);
} else if (waittime != 0) {
fthread_current->state = FTHREAD_STATE_WAITING;
fthread_current->waitevent = FTHREAD_EVENT_SLEEP;
fthread_current->ev_time = waittime;
fthread_current->ev_tid = NULL;
fthread_current->ev_func = NULL;
fthread_yield();
/* track the actual sleeping time and error */
actualtime = fthread_sched->lastran_us -
fthread_current->lastran_us;
err = actualtime - waittime;
fthread_current->num_sleeps++;
fthread_current->err_us += err;
if (err > fthread_current->maxerr_us)
fthread_current->maxerr_us = err;
}
return actualtime;
}
int fthread_join(struct fthread *tid, void **value)
{
debug("%s: joining thread \"%s\"\n", __func__, tid->name);
if (tid == NULL)
return -EINVAL;
if (tid == fthread_current)
return -EDEADLK;
if (tid->state != FTHREAD_STATE_DEAD) {
fthread_current->state = FTHREAD_STATE_WAITING;
fthread_current->waitevent = FTHREAD_EVENT_JOIN;
fthread_current->ev_tid = tid;
fthread_current->ev_time = 0;
fthread_current->ev_func = NULL;
fthread_yield();
}
if (value != NULL)
*value = tid->join_arg;
#ifndef CONFIG_FTHREAD_REPORT
/* Only delete joined threads if we aren't reporting stats */
fthread_pqueue_delete(&fthread_dq, tid);
fthread_tcb_free(tid);
#endif
return 0;
}
/**
* fthread_exit_main_cb() - Callback to check when main thread should exit
*
* Special predicate function to wait until @a fthread_main is the only thread
* left in the system. This function must be called from _within_ the scheduler
* so that fthread_scheduler_eventmanager() can figure out whether or not to
* wake up the main thread, which will then terminate the application.
*
* @return true if there is only one thread left in the system, false otherwise
*/
static int fthread_exit_main_cb(void)
{
int count = 0;
count += fthread_pqueue_length(&fthread_nq);
count += fthread_pqueue_length(&fthread_rq);
count += fthread_pqueue_length(&fthread_wq);
if (count == 1) /* only the main thread is left */
return true;
else
return false;
}
void fthread_exit(void *value)
{
debug("%s: thread \"%s\" has terminated\n", __func__,
fthread_current->name);
/*
* The main thread is special, because its termination would terminate
* the whole program, so we use a special predicate function event to
* wait until the main thread is the only thread left in the program,
* then cleanup and exit.
*/
if (fthread_current != fthread_main) {
/*
* Mark the current thread as dead and switch to the scheduler,
* which will clean up after us. Control should never return
*/
fthread_current->join_arg = value;
fthread_current->state = FTHREAD_STATE_DEAD;
debug("%s: switching from \"%s\" to scheduler\n",
__func__, fthread_current->name);
fthread_yield();
} else {
if (!fthread_exit_main_cb()) {
fthread_current->state = FTHREAD_STATE_WAITING;
fthread_current->waitevent = FTHREAD_EVENT_FUNC;
fthread_current->ev_func = fthread_exit_main_cb;
fthread_current->ev_time = 0;
fthread_current->ev_tid = NULL;
fthread_yield();
}
fthread_shutdown();
}
}