blob: c883f2ba48dc7b9ec0427e04776c18864c9be6fa [file] [log] [blame]
/*
* thread group band
*
* Copyright (C) 2018 Samsung Electronics Co., Ltd
* Park Bumgyu <bumgyu.park@samsung.com>
*/
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/ems.h>
#include <linux/sched/signal.h>
#include <trace/events/ems.h>
#include "../sched.h"
#include "ems.h"
static struct task_band *lookup_band(struct task_struct *p)
{
struct task_band *band;
rcu_read_lock();
band = rcu_dereference(p->band);
rcu_read_unlock();
if (!band)
return NULL;
return band;
}
int band_play_cpu(struct task_struct *p)
{
struct task_band *band;
int cpu, min_cpu = -1;
unsigned long min_util = ULONG_MAX;
band = lookup_band(p);
if (!band)
return -1;
for_each_cpu(cpu, &band->playable_cpus) {
if (!cpu_rq(cpu)->nr_running)
return cpu;
if (cpu_util(cpu) < min_util) {
min_cpu = cpu;
min_util = cpu_util(cpu);
}
}
return min_cpu;
}
static void pick_playable_cpus(struct task_band *band)
{
cpumask_clear(&band->playable_cpus);
/* pick condition should be fixed */
if (band->util < 442) // LIT up-threshold * 2
cpumask_and(&band->playable_cpus, cpu_online_mask, cpu_coregroup_mask(0));
else if (band->util < 1260) // MED up-threshold * 2
cpumask_and(&band->playable_cpus, cpu_online_mask, cpu_coregroup_mask(4));
else
cpumask_and(&band->playable_cpus, cpu_online_mask, cpu_coregroup_mask(6));
}
static unsigned long out_of_time = 100000000; /* 100ms */
/* This function should be called protected with band->lock */
static void __update_band(struct task_band *band, unsigned long now)
{
struct task_struct *task;
unsigned long util_sum = 0;
list_for_each_entry(task, &band->members, band_members) {
if (now - task->se.avg.last_update_time > out_of_time)
continue;
util_sum += task_util(task);
}
band->util = util_sum;
band->last_update_time = now;
pick_playable_cpus(band);
task = list_first_entry(&band->members, struct task_struct, band_members);
trace_ems_update_band(band->id, band->util, band->member_count,
*(unsigned int *)cpumask_bits(&band->playable_cpus));
}
static int update_interval = 20000000; /* 20ms */
void update_band(struct task_struct *p, long old_util)
{
struct task_band *band;
unsigned long now = cpu_rq(0)->clock_task;
band = lookup_band(p);
if (!band)
return;
/*
* Updates the utilization of the band only when it has been enough time
* to update the utilization of the band, or when the utilization of the
* task changes abruptly.
*/
if (now - band->last_update_time >= update_interval ||
(old_util >= 0 && abs(old_util - task_util(p)) > (SCHED_CAPACITY_SCALE >> 4))) {
raw_spin_lock(&band->lock);
__update_band(band, now);
raw_spin_unlock(&band->lock);
}
}
#define MAX_NUM_BAND_ID 20
static struct task_band *bands[MAX_NUM_BAND_ID];
DEFINE_RWLOCK(band_rwlock);
#define band_playing(band) (band->tgid >= 0)
static void join_band(struct task_struct *p)
{
struct task_band *band;
int pos, empty = -1;
char event[30] = "join band";
if (lookup_band(p))
return;
write_lock(&band_rwlock);
/*
* Find the band assigned to the tasks's thread group in the
* band pool. If there is no band assigend to thread group, it
* indicates that the task is the first one in the thread group
* to join the band. In this case, assign the first empty band
* in the band pool to the thread group.
*/
for (pos = 0; pos < MAX_NUM_BAND_ID; pos++) {
band = bands[pos];
if (!band_playing(band)) {
if (empty < 0)
empty = pos;
continue;
}
if (p->tgid == band->tgid)
break;
}
/* failed to find band, organize the new band */
if (pos == MAX_NUM_BAND_ID)
band = bands[empty];
raw_spin_lock(&band->lock);
if (!band_playing(band))
band->tgid = p->tgid;
list_add(&p->band_members, &band->members);
rcu_assign_pointer(p->band, band);
band->member_count++;
trace_ems_manage_band(p, band->id, event);
__update_band(band, cpu_rq(0)->clock_task);
raw_spin_unlock(&band->lock);
write_unlock(&band_rwlock);
}
static void leave_band(struct task_struct *p)
{
struct task_band *band;
char event[30] = "leave band";
if (!lookup_band(p))
return;
write_lock(&band_rwlock);
band = p->band;
raw_spin_lock(&band->lock);
list_del_init(&p->band_members);
rcu_assign_pointer(p->band, NULL);
band->member_count--;
trace_ems_manage_band(p, band->id, event);
/* last member of band, band split up */
if (list_empty(&band->members)) {
band->tgid = -1;
cpumask_clear(&band->playable_cpus);
}
__update_band(band, cpu_rq(0)->clock_task);
raw_spin_unlock(&band->lock);
write_unlock(&band_rwlock);
}
void sync_band(struct task_struct *p, bool join)
{
if (join)
join_band(p);
else
leave_band(p);
}
void newbie_join_band(struct task_struct *newbie)
{
unsigned long flags;
struct task_band *band;
struct task_struct *leader = newbie->group_leader;
char event[30] = "newbie join band";
if (thread_group_leader(newbie))
return;
write_lock_irqsave(&band_rwlock, flags);
band = lookup_band(leader);
if (!band || newbie->band) {
write_unlock_irqrestore(&band_rwlock, flags);
return;
}
raw_spin_lock(&band->lock);
list_add(&newbie->band_members, &band->members);
rcu_assign_pointer(newbie->band, band);
band->member_count++;
trace_ems_manage_band(newbie, band->id, event);
raw_spin_unlock(&band->lock);
write_unlock_irqrestore(&band_rwlock, flags);
}
int alloc_bands(void)
{
struct task_band *band;
int pos, ret, i;
for (pos = 0; pos < MAX_NUM_BAND_ID; pos++) {
band = kzalloc(sizeof(*band), GFP_KERNEL);
if (!band) {
ret = -ENOMEM;
goto fail;
}
band->id = pos;
band->tgid = -1;
raw_spin_lock_init(&band->lock);
INIT_LIST_HEAD(&band->members);
band->member_count = 0;
cpumask_clear(&band->playable_cpus);
bands[pos] = band;
}
return 0;
fail:
for (i = pos - 1; i >= 0; i--) {
kfree(bands[i]);
bands[i] = NULL;
}
return ret;
}