blob: e38fbf17b2acce0f9dfbdbe5d03f46d3cdd21fd2 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "kernel/dpc.h"
#include <assert.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/listnode.h>
#include <zircon/types.h>
#include <kernel/event.h>
#include <kernel/lockdep.h>
#include <kernel/percpu.h>
#include <kernel/spinlock.h>
#include <lk/init.h>
#define DPC_THREAD_PRIORITY HIGH_PRIORITY
DECLARE_SINGLETON_SPINLOCK(dpc_lock);
zx_status_t Dpc::Queue(bool reschedule) {
DEBUG_ASSERT(func_);
DpcQueue* dpc_queue;
{
Guard<SpinLock, IrqSave> guard{dpc_lock::Get()};
if (InContainer()) {
return ZX_ERR_ALREADY_EXISTS;
}
dpc_queue = &percpu::GetCurrent().dpc_queue;
// Put this Dpc at the tail of the list. Signal the worker outside the lock.
dpc_queue->Enqueue(this);
}
dpc_queue->Signal(reschedule);
return ZX_OK;
}
zx_status_t Dpc::QueueThreadLocked() {
DEBUG_ASSERT(func_);
// Interrupts are already disabled.
{
Guard<SpinLock, NoIrqSave> guard{dpc_lock::Get()};
if (InContainer()) {
return ZX_ERR_ALREADY_EXISTS;
}
DpcQueue& dpc_queue = percpu::GetCurrent().dpc_queue;
// Put this Dpc at the tail of the list and signal the worker.
dpc_queue.Enqueue(this);
dpc_queue.SignalLocked();
}
return ZX_OK;
}
void Dpc::Invoke() {
if (func_)
func_(this);
}
void DpcQueue::Enqueue(Dpc* dpc) { list_.push_back(dpc); }
void DpcQueue::Signal(bool reschedule) { event_.SignalEtc(reschedule); }
void DpcQueue::SignalLocked() { event_.SignalThreadLocked(); }
zx_status_t DpcQueue::Shutdown(zx_time_t deadline) {
Thread* t;
Event* event;
{
Guard<SpinLock, IrqSave> guard{dpc_lock::Get()};
// Ask the Dpc's thread to terminate.
DEBUG_ASSERT(!stop_);
stop_ = true;
// Remember this Event so we can signal it outside the spinlock.
event = &event_;
// Remember the thread so we can join outside the spinlock.
t = thread_;
thread_ = nullptr;
}
// Wake it.
event->SignalNoResched();
// Wait for it to terminate.
return t->Join(nullptr, deadline);
}
void DpcQueue::TransitionOffCpu(DpcQueue& source) {
Guard<SpinLock, IrqSave> guard{dpc_lock::Get()};
// |source|'s cpu is shutting down. Assert that we are migrating to the current cpu.
DEBUG_ASSERT(cpu_ == arch_curr_cpu_num());
DEBUG_ASSERT(cpu_ != source.cpu_);
// The Dpc's thread must already have been stopped by a call to |Shutdown|.
DEBUG_ASSERT(source.stop_);
DEBUG_ASSERT(source.thread_ == nullptr);
// Move the contents of |source.list_| to the back of our |list_|.
auto back = list_.end();
list_.splice(back, source.list_);
// Reset |source|'s state so we can restart Dpc processing if its cpu comes back online.
source.event_.Unsignal();
DEBUG_ASSERT(source.list_.is_empty());
source.stop_ = false;
source.initialized_ = false;
source.cpu_ = INVALID_CPU;
}
int DpcQueue::WorkerThread(void* unused) { return percpu::GetCurrent().dpc_queue.Work(); }
int DpcQueue::Work() {
for (;;) {
// Wait for a Dpc to fire.
__UNUSED zx_status_t err = event_.Wait();
DEBUG_ASSERT(err == ZX_OK);
Dpc dpc_local;
{
Guard<SpinLock, IrqSave> guard{dpc_lock::Get()};
if (stop_) {
return 0;
}
// Pop a Dpc off our list, and make a local copy.
Dpc* dpc = list_.pop_front();
// If our list is now empty, unsignal the event so we block until it is.
if (!dpc) {
event_.Unsignal();
continue;
}
// Copy the Dpc to the stack.
dpc_local = *dpc;
}
// Call the Dpc.
dpc_local.Invoke();
}
return 0;
}
void DpcQueue::InitForCurrentCpu() {
// This cpu's DpcQueue was initialized on a previous hotplug event.
if (initialized_) {
return;
}
DEBUG_ASSERT(cpu_ == INVALID_CPU);
DEBUG_ASSERT(!stop_);
DEBUG_ASSERT(thread_ == nullptr);
cpu_ = arch_curr_cpu_num();
initialized_ = true;
stop_ = false;
char name[10];
snprintf(name, sizeof(name), "dpc-%u", cpu_);
thread_ = Thread::Create(name, &DpcQueue::WorkerThread, nullptr, DPC_THREAD_PRIORITY);
DEBUG_ASSERT(thread_ != nullptr);
thread_->SetCpuAffinity(cpu_num_to_mask(cpu_));
// The Dpc thread may use up to 150us out of every 300us (i.e. 50% of the CPU)
// in the worst case. DPCs usually take only a small fraction of this and have
// a much lower frequency than 3.333KHz.
// TODO(fxbug.dev/38571): Make this runtime tunable. It may be necessary to change the
// Dpc deadline params later in boot, after configuration is loaded somehow.
thread_->SetDeadline({ZX_USEC(150), ZX_USEC(300), ZX_USEC(300)});
thread_->Resume();
}
static void dpc_init(unsigned int level) {
// Initialize the DpcQueue for the main cpu.
percpu::GetCurrent().dpc_queue.InitForCurrentCpu();
}
LK_INIT_HOOK(dpc, dpc_init, LK_INIT_LEVEL_THREADING)