blob: 4048fba7eafcf66a1c602ce7391ad527a9269d92 [file] [log] [blame]
/*
* Samsung Exynos SoC series NPU driver
*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* 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.
*/
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <asm/barrier.h>
#include "npu-log.h"
#include "npu-util-autosleepthr.h"
#include "npu-util-common.h"
const char *DEFAULT_THREAD_PRINT_NAME = "Anon. auto_sleep_thread";
const int DEFAULT_NO_ACTIVITY_THRESHOLD = 4;
const int AUTO_SLEEP_THREAD_TIMEOUT = 1000;
static const u8 auto_sleep_thread_state_transition[][THREAD_STATE_INVALID+1] = {
/* From - To NOT_INITIALIZED INITIALIZED RUNNING SLEEPING TERMINATED INVALID */
/* NOT_INITIALIZED */ { 0, 1, 0, 0, 0, 0 },
/* INITIALIZED */ { 0, 0, 1, 0, 1, 0 },
/* RUNNING */ { 0, 0, 0, 1, 1, 0 },
/* SLEEPING */ { 0, 0, 1, 0, 1, 0 },
/* TERMINATED */ { 0, 0, 1, 0, 0, 0 },
/* INVALID */ { 0, 0, 0, 0, 0, 0 }
};
static inline auto_sleep_thread_state_e auto_sleep_thread_set_state(struct auto_sleep_thread *thrctx, auto_sleep_thread_state_e new_state)
{
int old_state;
BUG_ON(new_state < 0);
BUG_ON(new_state >= THREAD_STATE_INVALID);
old_state = atomic_xchg(&(thrctx->thr_state), new_state);
/* Check after transition is made - To ensure atomicity */
if (!auto_sleep_thread_state_transition[old_state][new_state]) {
npu_err("NPU: auto sleep thread: invalid transition (%d) -> (%d)\n",
old_state, new_state);
BUG_ON(1);
}
return old_state;
}
static void dump_auto_sleep_thread(const struct auto_sleep_thread *ctx)
{
BUG_ON(!ctx);
BUG_ON(!ctx->thread_ref);
npu_dbg("--Auto sleep thread at %pK --\n"
"thread_ref = %pK {\n"
"\tstate = %ld\n"
"\tpid = %u\n"
"\ttgid = %u\n"
"\tset_child_tid = %pK\n"
"name = %s\n"
"do_task = %pK\n"
"check_work = %pK\n"
"task_param = {.data = %pK}\n"
"no_activity_threshold = %d\n"
"thr_state = %d\n"
"----------------------------\n",
ctx, ctx->thread_ref,
ctx->thread_ref->state, ctx->thread_ref->pid, ctx->thread_ref->tgid,
ctx->thread_ref->set_child_tid,
ctx->name, ctx->do_task, ctx->check_work,
ctx->task_param.data, ctx->no_activity_threshold,
atomic_read(&(ctx->thr_state)));
}
static inline int wakeup_check(struct auto_sleep_thread *thrctx)
{
smp_mb(); /* Ensure the atomic veriable is properly synchronized */
/* If there is a work to do (check_work) or terminating thread is requested(kthread_should_stop())
-> Wake-up the thread */
if (kthread_should_stop()) {
npu_dbg("Stop flag asserted.\n");
return 1;
}
if (thrctx->check_work(&thrctx->task_param)) {
npu_dbg("check_work() returns true. resuming thread.\n");
return 1;
}
/* Nothing to do */
return 0;
}
static int auto_sleep_thread_thrfunc(void *data)
{
int num_activity;
s64 idle_duration_ns;
int no_activity_cnt = 0;
struct auto_sleep_thread *thrctx = (struct auto_sleep_thread *)data;
npu_info("ASThread[%s] thrfunc is initiated. ctx = %pK\n"
, thrctx->name, thrctx);
/* Execute thread task */
BUG_ON(!thrctx);
BUG_ON(!(thrctx->do_task));
while (!kthread_should_stop()) {
/* Execute thread task */
if (!(thrctx->do_task)) {
npu_trace("no do_task defined. terminating AST. thrctx(%pK)\n", thrctx);
return 0;
}
num_activity = thrctx->do_task(&(thrctx->task_param));
if (num_activity == 0) {
/* No activity */
no_activity_cnt++;
// No activity more than threshold -> Go to sleep
if (no_activity_cnt >= thrctx->no_activity_threshold) {
if (thrctx->idle_start_ns) {
idle_duration_ns = npu_get_time_ns() - thrctx->idle_start_ns;
} else {
/* First idle state */
idle_duration_ns = 0;
thrctx->idle_start_ns = npu_get_time_ns();
}
npu_trace("ASThread[%s] goes into sleep. no_activity_cnt = %d, idle duration = %lld\n",
thrctx->name, no_activity_cnt, idle_duration_ns);
/* Invoke idle callback if available */
if (thrctx->on_idle)
thrctx->on_idle(&(thrctx->task_param), idle_duration_ns);
auto_sleep_thread_set_state(thrctx, THREAD_STATE_SLEEPING);
wait_event_interruptible_timeout(thrctx->wq,
wakeup_check(thrctx),
AUTO_SLEEP_THREAD_TIMEOUT);
auto_sleep_thread_set_state(thrctx, THREAD_STATE_RUNNING);
no_activity_cnt = 0;
npu_trace("ASThread[%s] wakeup.", thrctx->name);
}
} else {
no_activity_cnt = 0;
thrctx->idle_start_ns = 0;
}
}
npu_info("ASThread(%s) terminated\n", thrctx->name);
return 0;
}
int auto_sleep_thread_create(struct auto_sleep_thread *newthr, const char *print_name,
int (*do_task)(struct auto_sleep_thread_param *data),
int (*check_work)(struct auto_sleep_thread_param *data),
void (*on_idle)(struct auto_sleep_thread_param *data, s64 idle_duration_ns))
{
BUG_ON(!newthr);
BUG_ON(!do_task);
BUG_ON(!check_work);
atomic_set(&(newthr->thr_state), THREAD_STATE_NOT_INITIALIZED);
npu_info("start in creating auto sleep thread\n");
/* Setting print name */
if (print_name == NULL) {
// Use default name
strncpy(newthr->name, DEFAULT_THREAD_PRINT_NAME, PRINT_NAME_LEN);
} else {
strncpy(newthr->name, print_name, PRINT_NAME_LEN);
}
newthr->name[PRINT_NAME_LEN] = '\0';
/* Initialize other fields */
newthr->thread_ref = NULL;
newthr->do_task = (do_task);
newthr->check_work = (check_work);
newthr->on_idle = (on_idle);
newthr->no_activity_threshold = DEFAULT_NO_ACTIVITY_THRESHOLD;
auto_sleep_thread_set_state(newthr, THREAD_STATE_INITIALIZED);
npu_info("Creating Auto Sleep Thread(%s): Completed - newthr(%pK), do_task(%pK)\n",
newthr->name, newthr, newthr->do_task);
return 0;
}
int auto_sleep_thread_start(struct auto_sleep_thread *thrctx, struct auto_sleep_thread_param param)
{
BUG_ON(!thrctx);
BUG_ON(thrctx->thread_ref); /* Should not have thread object */
npu_info("Starting Autho Sleep Thread[%s] : Starting - newthr = %pK, do_task = %pK\n",
thrctx->name, thrctx, thrctx->do_task);
/* Initialize companion objects */
init_waitqueue_head(&(thrctx->wq));
thrctx->task_param = param;
auto_sleep_thread_set_state(thrctx, THREAD_STATE_RUNNING);
npu_info("calling kthread_run for (%s)...\n", thrctx->name);
thrctx->thread_ref = kthread_run(auto_sleep_thread_thrfunc, thrctx, thrctx->name);
if (IS_ERR(thrctx->thread_ref)) {
npu_err("NPU: kthread_run failed(%pK) [%s]\n", thrctx->thread_ref, thrctx->name);
return -EFAULT;
}
dump_auto_sleep_thread(thrctx);
npu_info("Starting Auto Sleep Thread[%s] : Completed - newthr = %pK, do_task = %pK\n",
thrctx->name, thrctx, thrctx->do_task);
return 0;
}
/*
* Terminate the auto_sleep_thread and returns return value of
* thread function, or returns -EINTR if wake_up_process was neve called
* (Please refer the description of 'kthread_stop (..) for EINTR error)
*/
int auto_sleep_thread_terminate(struct auto_sleep_thread *thrctx)
{
int ret;
BUG_ON(!thrctx);
BUG_ON(!thrctx->thread_ref);
npu_info("terminating auto sleep thread(%s) : starting - newthr(%pK), do_task(%pK)\n",
thrctx->name, thrctx, thrctx->do_task);
dump_auto_sleep_thread(thrctx);
/* Wake-up the thread because sleeping thread would not check the stop flag */
wake_up_all(&(thrctx->wq));
npu_info("wait for thread termination of (%s)...\n", thrctx->name);
/* Now terminating the thread */
ret = kthread_stop(thrctx->thread_ref);
auto_sleep_thread_set_state(thrctx, THREAD_STATE_TERMINATED);
npu_info("terminating auto sleep thread(%s) : Completed\n",
thrctx->name);
/* Prepare for re-start */
thrctx->thread_ref = NULL;
return ret;
}
void auto_sleep_thread_signal(struct auto_sleep_thread *thrctx)
{
if (!thrctx) {
npu_warn("invalid thrctx\n");
return;
} else if (!thrctx->thread_ref) {
npu_warn("invalid in thrctx->thread_ref\n");
return;
}
npu_dbg("sending wakeup signal on ASThread (%s)...\n", thrctx->name);
// Wake up tasks on the wait_queue
wake_up_all(&(thrctx->wq));
return;
}
/* Unit test */
#ifdef CONFIG_VISION_UNITTEST
#define IDIOT_TESTCASE_IMPL "../npu/asthread.idiot"
#include "idiot-def.h"
#endif