/*
 * Copyright 2013, Paweł Dziepak, pdziepak@quarnos.org.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "scheduler_cpu.h"
 
#include <util/AutoLock.h>
 
#include <algorithm>
 
#include "scheduler_thread.h"
 
 
namespace Scheduler {
 
 
CPUEntry* gCPUEntries;
 
CoreEntry* gCoreEntries;
CoreLoadHeap gCoreLoadHeap;
CoreLoadHeap gCoreHighLoadHeap;
rw_spinlock gCoreHeapsLock = B_RW_SPINLOCK_INITIALIZER;
int32 gCoreCount;
 
PackageEntry* gPackageEntries;
IdlePackageList gIdlePackageList;
rw_spinlock gIdlePackageLock = B_RW_SPINLOCK_INITIALIZER;
int32 gPackageCount;
 
 
}	// namespace Scheduler
 
using namespace Scheduler;
 
 
class Scheduler::DebugDumper {
public:
	static	void		DumpCPURunQueue(CPUEntry* cpu);
	static	void		DumpCoreRunQueue(CoreEntry* core);
	static	void		DumpCoreLoadHeapEntry(CoreEntry* core);
	static	void		DumpIdleCoresInPackage(PackageEntry* package);
 
private:
	struct CoreThreadsData {
			CoreEntry*	fCore;
			int32		fLoad;
	};
 
	static	void		_AnalyzeCoreThreads(Thread* thread, void* data);
};
 
 
static CPUPriorityHeap sDebugCPUHeap;
static CoreLoadHeap sDebugCoreHeap;
 
 
void
ThreadRunQueue::Dump() const
{
	ThreadRunQueue::ConstIterator iterator = GetConstIterator();
	if (!iterator.HasNext())
		kprintf("Run queue is empty.\n");
	else {
		kprintf("thread      id      priority penalty  name\n");
		while (iterator.HasNext()) {
			ThreadData* threadData = iterator.Next();
			Thread* thread = threadData->GetThread();
 
			kprintf("%p  %-7" B_PRId32 " %-8" B_PRId32 " %-8" B_PRId32 " %s\n",
				thread, thread->id, thread->priority,
				thread->priority - threadData->GetEffectivePriority(),
				thread->name);
		}
	}
}
 
 
CPUEntry::CPUEntry()
	:
	fLoad(0),
	fMeasureActiveTime(0),
	fMeasureTime(0),
	fUpdateLoadEvent(false)
{
	B_INITIALIZE_RW_SPINLOCK(&fSchedulerModeLock);
	B_INITIALIZE_SPINLOCK(&fQueueLock);
}
 
 
void
CPUEntry::Init(int32 id, CoreEntry* core)
{
	fCPUNumber = id;
	fCore = core;
}
 
 
void
CPUEntry::Start()
{
	fLoad = 0;
	fCore->AddCPU(this);
}
 
 
void
CPUEntry::Stop()
{
	cpu_ent* entry = &gCPU[fCPUNumber];
 
	// get rid of irqs
	SpinLocker locker(entry->irqs_lock);
	irq_assignment* irq
		= (irq_assignment*)list_get_first_item(&entry->irqs);
	while (irq != NULL) {
		locker.Unlock();
 
		assign_io_interrupt_to_cpu(irq->irq, -1);
 
		locker.Lock();
		irq = (irq_assignment*)list_get_first_item(&entry->irqs);
	}
	locker.Unlock();
}
 
 
void
CPUEntry::PushFront(ThreadData* thread, int32 priority)
{
	SCHEDULER_ENTER_FUNCTION();
	fRunQueue.PushFront(thread, priority);
}
 
 
void
CPUEntry::PushBack(ThreadData* thread, int32 priority)
{
	SCHEDULER_ENTER_FUNCTION();
	fRunQueue.PushBack(thread, priority);
}
 
 
void
CPUEntry::Remove(ThreadData* thread)
{
	SCHEDULER_ENTER_FUNCTION();
	ASSERT(thread->IsEnqueued());
	thread->SetDequeued();
	fRunQueue.Remove(thread);
}
 
 
inline ThreadData*
CoreEntry::PeekThread() const
{
	SCHEDULER_ENTER_FUNCTION();
	return fRunQueue.PeekMaximum();
}
 
 
inline ThreadData*
CPUEntry::PeekThread() const
{
	SCHEDULER_ENTER_FUNCTION();
	return fRunQueue.PeekMaximum();
}
 
 
ThreadData*
CPUEntry::PeekIdleThread() const
{
	SCHEDULER_ENTER_FUNCTION();
	return fRunQueue.GetHead(B_IDLE_PRIORITY);
}
 
 
void
CPUEntry::UpdatePriority(int32 priority)
{
	SCHEDULER_ENTER_FUNCTION();
 
	ASSERT(!gCPU[fCPUNumber].disabled);
 
	int32 oldPriority = CPUPriorityHeap::GetKey(this);
	if (oldPriority == priority)
		return;
	fCore->CPUHeap()->ModifyKey(this, priority);
 
	if (oldPriority == B_IDLE_PRIORITY)
		fCore->CPUWakesUp(this);
	else if (priority == B_IDLE_PRIORITY)
		fCore->CPUGoesIdle(this);
}
 
 
void
CPUEntry::ComputeLoad()
{
	SCHEDULER_ENTER_FUNCTION();
 
	ASSERT(gTrackCPULoad);
	ASSERT(!gCPU[fCPUNumber].disabled);
	ASSERT(fCPUNumber == smp_get_current_cpu());
 
	int oldLoad = compute_load(fMeasureTime, fMeasureActiveTime, fLoad,
			system_time());
	if (oldLoad < 0)
		return;
 
	if (fLoad > kVeryHighLoad)
		gCurrentMode->rebalance_irqs(false);
}
 
 
ThreadData*
CPUEntry::ChooseNextThread(ThreadData* oldThread, bool putAtBack)
{
	SCHEDULER_ENTER_FUNCTION();
 
	int32 oldPriority = -1;
	if (oldThread != NULL)
		oldPriority = oldThread->GetEffectivePriority();
 
	CPURunQueueLocker cpuLocker(this);
 
	ThreadData* pinnedThread = fRunQueue.PeekMaximum();
	int32 pinnedPriority = -1;
	if (pinnedThread != NULL)
		pinnedPriority = pinnedThread->GetEffectivePriority();
 
	CoreRunQueueLocker coreLocker(fCore);
 
	ThreadData* sharedThread = fCore->PeekThread();
	ASSERT(sharedThread != NULL || pinnedThread != NULL || oldThread != NULL);
 
	int32 sharedPriority = -1;
	if (sharedThread != NULL)
		sharedPriority = sharedThread->GetEffectivePriority();
 
	int32 rest = std::max(pinnedPriority, sharedPriority);
	if (oldPriority > rest || (!putAtBack && oldPriority == rest))
		return oldThread;
 
	if (sharedPriority > pinnedPriority) {
		fCore->Remove(sharedThread);
		return sharedThread;
	}
 
	coreLocker.Unlock();
 
	Remove(pinnedThread);
	return pinnedThread;
}
 
 
void
CPUEntry::TrackActivity(ThreadData* oldThreadData, ThreadData* nextThreadData)
{
	SCHEDULER_ENTER_FUNCTION();
 
	cpu_ent* cpuEntry = &gCPU[fCPUNumber];
 
	Thread* oldThread = oldThreadData->GetThread();
	if (!thread_is_idle_thread(oldThread)) {
		bigtime_t active
			= (oldThread->kernel_time - cpuEntry->last_kernel_time)
				+ (oldThread->user_time - cpuEntry->last_user_time);
 
		WriteSequentialLocker locker(cpuEntry->active_time_lock);
		cpuEntry->active_time += active;
		locker.Unlock();
 
		fMeasureActiveTime += active;
		fCore->IncreaseActiveTime(active);
 
		oldThreadData->UpdateActivity(active);
	}
 
	if (gTrackCPULoad) {
		if (!cpuEntry->disabled)
			ComputeLoad();
		_RequestPerformanceLevel(nextThreadData);
	}
 
	Thread* nextThread = nextThreadData->GetThread();
	if (!thread_is_idle_thread(nextThread)) {
		cpuEntry->last_kernel_time = nextThread->kernel_time;
		cpuEntry->last_user_time = nextThread->user_time;
 
		nextThreadData->SetLastInterruptTime(cpuEntry->interrupt_time);
	}
}
 
 
void
CPUEntry::StartQuantumTimer(ThreadData* thread, bool wasPreempted)
{
	cpu_ent* cpu = &gCPU[ID()];
 
	if (!wasPreempted || fUpdateLoadEvent)
		cancel_timer(&cpu->quantum_timer);
	fUpdateLoadEvent = false;
 
	if (!thread->IsIdle()) {
		bigtime_t quantum = thread->GetQuantumLeft();
		add_timer(&cpu->quantum_timer, &CPUEntry::_RescheduleEvent, quantum,
			B_ONE_SHOT_RELATIVE_TIMER);
	} else if (gTrackCoreLoad) {
		add_timer(&cpu->quantum_timer, &CPUEntry::_UpdateLoadEvent,
			kLoadMeasureInterval * 2, B_ONE_SHOT_RELATIVE_TIMER);
		fUpdateLoadEvent = true;
	}
}
 
 
void
CPUEntry::_RequestPerformanceLevel(ThreadData* threadData)
{
	SCHEDULER_ENTER_FUNCTION();
 
	if (gCPU[fCPUNumber].disabled) {
		decrease_cpu_performance(kCPUPerformanceScaleMax);
		return;
	}
 
	int32 load = std::max(threadData->GetLoad(), fCore->GetLoad());
	ASSERT(load >= 0 && load <= kMaxLoad);
 
	if (load < kTargetLoad) {
		int32 delta = kTargetLoad - load;
 
		delta *= kTargetLoad;
		delta /= kCPUPerformanceScaleMax;
 
		decrease_cpu_performance(delta);
	} else {
		int32 delta = load - kTargetLoad;
		delta *= kMaxLoad - kTargetLoad;
		delta /= kCPUPerformanceScaleMax;
 
		increase_cpu_performance(delta);
	}
}
 
 
/* static */ int32
CPUEntry::_RescheduleEvent(timer* /* unused */)
{
	get_cpu_struct()->invoke_scheduler = true;
	get_cpu_struct()->preempted = true;
	return B_HANDLED_INTERRUPT;
}
 
 
/* static */ int32
CPUEntry::_UpdateLoadEvent(timer* /* unused */)
{
	CoreEntry::GetCore(smp_get_current_cpu())->ChangeLoad(0);
	CPUEntry::GetCPU(smp_get_current_cpu())->fUpdateLoadEvent = false;
	return B_HANDLED_INTERRUPT;
}
 
 
CPUPriorityHeap::CPUPriorityHeap(int32 cpuCount)
	:
	Heap<CPUEntry, int32>(cpuCount)
{
}
 
 
void
CPUPriorityHeap::Dump()
{
	kprintf("cpu priority load\n");
	CPUEntry* entry = PeekRoot();
	while (entry) {
		int32 cpu = entry->ID();
		int32 key = GetKey(entry);
		kprintf("%3" B_PRId32 " %8" B_PRId32 " %3" B_PRId32 "%%\n", cpu, key,
			entry->GetLoad() / 10);
 
		RemoveRoot();
		sDebugCPUHeap.Insert(entry, key);
 
		entry = PeekRoot();
	}
 
	entry = sDebugCPUHeap.PeekRoot();
	while (entry) {
		int32 key = GetKey(entry);
		sDebugCPUHeap.RemoveRoot();
		Insert(entry, key);
		entry = sDebugCPUHeap.PeekRoot();
	}
}
 
 
CoreEntry::CoreEntry()
	:
	fCPUCount(0),
	fIdleCPUCount(0),
	fThreadCount(0),
	fActiveTime(0),
	fLoad(0),
	fCurrentLoad(0),
	fLoadMeasurementEpoch(0),
	fHighLoad(false),
	fLastLoadUpdate(0)
{
	B_INITIALIZE_SPINLOCK(&fCPULock);
	B_INITIALIZE_SPINLOCK(&fQueueLock);
	B_INITIALIZE_SEQLOCK(&fActiveTimeLock);
	B_INITIALIZE_RW_SPINLOCK(&fLoadLock);
}
 
 
void
CoreEntry::Init(int32 id, PackageEntry* package)
{
	fCoreID = id;
	fPackage = package;
}
 
 
void
CoreEntry::PushFront(ThreadData* thread, int32 priority)
{
	SCHEDULER_ENTER_FUNCTION();
 
	fRunQueue.PushFront(thread, priority);
	atomic_add(&fThreadCount, 1);
}
 
 
void
CoreEntry::PushBack(ThreadData* thread, int32 priority)
{
	SCHEDULER_ENTER_FUNCTION();
 
	fRunQueue.PushBack(thread, priority);
	atomic_add(&fThreadCount, 1);
}
 
 
void
CoreEntry::Remove(ThreadData* thread)
{
	SCHEDULER_ENTER_FUNCTION();
 
	ASSERT(!thread->IsIdle());
 
	ASSERT(thread->IsEnqueued());
	thread->SetDequeued();
 
	fRunQueue.Remove(thread);
	atomic_add(&fThreadCount, -1);
}
 
 
void
CoreEntry::AddCPU(CPUEntry* cpu)
{
	ASSERT(fCPUCount >= 0);
	ASSERT(fIdleCPUCount >= 0);
 
	fIdleCPUCount++;
	if (fCPUCount++ == 0) {
		// core has been reenabled
		fLoad = 0;
		fCurrentLoad = 0;
		fHighLoad = false;
		gCoreLoadHeap.Insert(this, 0);
 
		fPackage->AddIdleCore(this);
	}
 
	fCPUHeap.Insert(cpu, B_IDLE_PRIORITY);
}
 
 
void
CoreEntry::RemoveCPU(CPUEntry* cpu, ThreadProcessing& threadPostProcessing)
{
	ASSERT(fCPUCount > 0);
	ASSERT(fIdleCPUCount > 0);
 
	fIdleCPUCount--;
	if (--fCPUCount == 0) {
		// unassign threads
		thread_map(CoreEntry::_UnassignThread, this);
 
		// core has been disabled
		if (fHighLoad) {
			gCoreHighLoadHeap.ModifyKey(this, -1);
			ASSERT(gCoreHighLoadHeap.PeekMinimum() == this);
			gCoreHighLoadHeap.RemoveMinimum();
		} else {
			gCoreLoadHeap.ModifyKey(this, -1);
			ASSERT(gCoreLoadHeap.PeekMinimum() == this);
			gCoreLoadHeap.RemoveMinimum();
		}
 
		fPackage->RemoveIdleCore(this);
 
		// get rid of threads
		while (fRunQueue.PeekMaximum() != NULL) {
			ThreadData* threadData = fRunQueue.PeekMaximum();
 
			Remove(threadData);
 
			ASSERT(threadData->Core() == NULL);
			threadPostProcessing(threadData);
		}
 
		fThreadCount = 0;
	}
 
	fCPUHeap.ModifyKey(cpu, -1);
	ASSERT(fCPUHeap.PeekRoot() == cpu);
	fCPUHeap.RemoveRoot();
 
	ASSERT(cpu->GetLoad() >= 0 && cpu->GetLoad() <= kMaxLoad);
	ASSERT(fLoad >= 0);
}
 
 
void
CoreEntry::_UpdateLoad(bool forceUpdate)
{
	SCHEDULER_ENTER_FUNCTION();
 
	if (fCPUCount <= 0)
		return;
 
	bigtime_t now = system_time();
	bool intervalEnded = now >= kLoadMeasureInterval + fLastLoadUpdate;
	bool intervalSkipped = now >= kLoadMeasureInterval * 2 + fLastLoadUpdate;
 
	if (!intervalEnded && !forceUpdate)
		return;
 
	WriteSpinLocker coreLocker(gCoreHeapsLock);
 
	int32 newKey;
	if (intervalEnded) {
		WriteSpinLocker locker(fLoadLock);
 
		newKey = intervalSkipped ? fCurrentLoad : GetLoad();
 
		ASSERT(fCurrentLoad >= 0);
		ASSERT(fLoad >= fCurrentLoad);
 
		fLoad = fCurrentLoad;
		fLoadMeasurementEpoch++;
		fLastLoadUpdate = now;
	} else
		newKey = GetLoad();
 
	int32 oldKey = CoreLoadHeap::GetKey(this);
 
	ASSERT(oldKey >= 0);
	ASSERT(newKey >= 0);
 
	if (oldKey == newKey)
		return;
 
	if (newKey > kHighLoad) {
		if (!fHighLoad) {
			gCoreLoadHeap.ModifyKey(this, -1);
			ASSERT(gCoreLoadHeap.PeekMinimum() == this);
			gCoreLoadHeap.RemoveMinimum();
 
			gCoreHighLoadHeap.Insert(this, newKey);
 
			fHighLoad = true;
		} else
			gCoreHighLoadHeap.ModifyKey(this, newKey);
	} else if (newKey < kMediumLoad) {
		if (fHighLoad) {
			gCoreHighLoadHeap.ModifyKey(this, -1);
			ASSERT(gCoreHighLoadHeap.PeekMinimum() == this);
			gCoreHighLoadHeap.RemoveMinimum();
 
			gCoreLoadHeap.Insert(this, newKey);
 
			fHighLoad = false;
		} else
			gCoreLoadHeap.ModifyKey(this, newKey);
	} else {
		if (fHighLoad)
			gCoreHighLoadHeap.ModifyKey(this, newKey);
		else
			gCoreLoadHeap.ModifyKey(this, newKey);
	}
}
 
 
/* static */ void
CoreEntry::_UnassignThread(Thread* thread, void* data)
{
	CoreEntry* core = static_cast<CoreEntry*>(data);
	ThreadData* threadData = thread->scheduler_data;
 
	if (threadData->Core() == core && thread->pinned_to_cpu == 0)
		threadData->UnassignCore();
}
 
 
CoreLoadHeap::CoreLoadHeap(int32 coreCount)
	:
	MinMaxHeap<CoreEntry, int32>(coreCount)
{
}
 
 
void
CoreLoadHeap::Dump()
{
	CoreEntry* entry = PeekMinimum();
	while (entry) {
		int32 key = GetKey(entry);
 
		DebugDumper::DumpCoreLoadHeapEntry(entry);
 
		RemoveMinimum();
		sDebugCoreHeap.Insert(entry, key);
 
		entry = PeekMinimum();
	}
 
	entry = sDebugCoreHeap.PeekMinimum();
	while (entry) {
		int32 key = GetKey(entry);
		sDebugCoreHeap.RemoveMinimum();
		Insert(entry, key);
		entry = sDebugCoreHeap.PeekMinimum();
	}
}
 
 
PackageEntry::PackageEntry()
	:
	fIdleCoreCount(0),
	fCoreCount(0)
{
	B_INITIALIZE_RW_SPINLOCK(&fCoreLock);
}
 
 
void
PackageEntry::Init(int32 id)
{
	fPackageID = id;
}
 
 
void
PackageEntry::AddIdleCore(CoreEntry* core)
{
	fCoreCount++;
	fIdleCoreCount++;
	fIdleCores.Add(core);
 
	if (fCoreCount == 1)
		gIdlePackageList.Add(this);
}
 
 
void
PackageEntry::RemoveIdleCore(CoreEntry* core)
{
	fIdleCores.Remove(core);
	fIdleCoreCount--;
	fCoreCount--;
 
	if (fCoreCount == 0)
		gIdlePackageList.Remove(this);
}
 
 
/* static */ void
DebugDumper::DumpCPURunQueue(CPUEntry* cpu)
{
	ThreadRunQueue::ConstIterator iterator = cpu->fRunQueue.GetConstIterator();
 
	if (iterator.HasNext()
		&& !thread_is_idle_thread(iterator.Next()->GetThread())) {
		kprintf("\nCPU %" B_PRId32 " run queue:\n", cpu->ID());
		cpu->fRunQueue.Dump();
	}
}
 
 
/* static */ void
DebugDumper::DumpCoreRunQueue(CoreEntry* core)
{
	core->fRunQueue.Dump();
}
 
 
/* static */ void
DebugDumper::DumpCoreLoadHeapEntry(CoreEntry* entry)
{
	CoreThreadsData threadsData;
	threadsData.fCore = entry;
	threadsData.fLoad = 0;
	thread_map(DebugDumper::_AnalyzeCoreThreads, &threadsData);
 
	kprintf("%4" B_PRId32 " %11" B_PRId32 "%% %11" B_PRId32 "%% %11" B_PRId32
		"%% %7" B_PRId32 " %5" B_PRIu32 "\n", entry->ID(), entry->fLoad / 10,
		entry->fCurrentLoad / 10, threadsData.fLoad, entry->ThreadCount(),
		entry->fLoadMeasurementEpoch);
}
 
 
/* static */ void
DebugDumper::DumpIdleCoresInPackage(PackageEntry* package)
{
	kprintf("%-7" B_PRId32 " ", package->fPackageID);
 
	DoublyLinkedList<CoreEntry>::ReverseIterator iterator
		= package->fIdleCores.GetReverseIterator();
	if (iterator.HasNext()) {
		while (iterator.HasNext()) {
			CoreEntry* coreEntry = iterator.Next();
			kprintf("%" B_PRId32 "%s", coreEntry->ID(),
				iterator.HasNext() ? ", " : "");
		}
	} else
		kprintf("-");
	kprintf("\n");
}
 
 
/* static */ void
DebugDumper::_AnalyzeCoreThreads(Thread* thread, void* data)
{
	CoreThreadsData* threadsData = static_cast<CoreThreadsData*>(data);
	if (thread->scheduler_data->Core() == threadsData->fCore)
		threadsData->fLoad += thread->scheduler_data->GetLoad();
}
 
 
static int
dump_run_queue(int /* argc */, char** /* argv */)
{
	int32 cpuCount = smp_get_num_cpus();
	int32 coreCount = gCoreCount;
 
	for (int32 i = 0; i < coreCount; i++) {
		kprintf("%sCore %" B_PRId32 " run queue:\n", i > 0 ? "\n" : "", i);
		DebugDumper::DumpCoreRunQueue(&gCoreEntries[i]);
	}
 
	for (int32 i = 0; i < cpuCount; i++)
		DebugDumper::DumpCPURunQueue(&gCPUEntries[i]);
 
	return 0;
}
 
 
static int
dump_cpu_heap(int /* argc */, char** /* argv */)
{
	kprintf("core average_load current_load threads_load threads epoch\n");
	gCoreLoadHeap.Dump();
	kprintf("\n");
	gCoreHighLoadHeap.Dump();
 
	for (int32 i = 0; i < gCoreCount; i++) {
		if (gCoreEntries[i].CPUCount() < 2)
			continue;
 
		kprintf("\nCore %" B_PRId32 " heap:\n", i);
		gCoreEntries[i].CPUHeap()->Dump();
	}
 
	return 0;
}
 
 
static int
dump_idle_cores(int /* argc */, char** /* argv */)
{
	kprintf("Idle packages:\n");
	IdlePackageList::ReverseIterator idleIterator
		= gIdlePackageList.GetReverseIterator();
 
	if (idleIterator.HasNext()) {
		kprintf("package cores\n");
 
		while (idleIterator.HasNext())
			DebugDumper::DumpIdleCoresInPackage(idleIterator.Next());
	} else
		kprintf("No idle packages.\n");
 
	return 0;
}
 
 
void Scheduler::init_debug_commands()
{
	new(&sDebugCPUHeap) CPUPriorityHeap(smp_get_num_cpus());
	new(&sDebugCoreHeap) CoreLoadHeap(smp_get_num_cpus());
 
	add_debugger_command_etc("run_queue", &dump_run_queue,
		"List threads in run queue", "\nLists threads in run queue", 0);
	if (!gSingleCore) {
		add_debugger_command_etc("cpu_heap", &dump_cpu_heap,
			"List CPUs in CPU priority heap",
			"\nList CPUs in CPU priority heap", 0);
		add_debugger_command_etc("idle_cores", &dump_idle_cores,
			"List idle cores", "\nList idle cores", 0);
	}
}
 

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fCPUNumber, fCore, fSchedulerModeLock, fQueueLock.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fPackageID, fCoreLock.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fCoreID, fPackage, fCPULock, fQueueLock, fActiveTimeLock, fLoadLock.