blob: e20a63071777d1a1df614beac55af4626472921b [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 <assert.h>
#include <err.h>
#include <list.h>
#include <trace.h>
#include <kernel/dpc.h>
#include <kernel/event.h>
#include <kernel/percpu.h>
#include <kernel/spinlock.h>
#include <lk/init.h>
static spin_lock_t dpc_lock = SPIN_LOCK_INITIAL_VALUE;
zx_status_t dpc_queue(dpc_t* dpc, bool reschedule) {
DEBUG_ASSERT(dpc);
DEBUG_ASSERT(dpc->func);
// disable interrupts before finding lock
spin_lock_saved_state_t state;
spin_lock_irqsave(&dpc_lock, state);
if (list_in_list(&dpc->node)) {
spin_unlock_irqrestore(&dpc_lock, state);
return ZX_ERR_ALREADY_EXISTS;
}
struct percpu* cpu = get_local_percpu();
// put the dpc at the tail of the list and signal the worker
list_add_tail(&cpu->dpc_list, &dpc->node);
spin_unlock_irqrestore(&dpc_lock, state);
event_signal(&cpu->dpc_event, reschedule);
return ZX_OK;
}
zx_status_t dpc_queue_thread_locked(dpc_t* dpc) {
DEBUG_ASSERT(dpc);
DEBUG_ASSERT(dpc->func);
// interrupts are already disabled
spin_lock(&dpc_lock);
if (list_in_list(&dpc->node)) {
spin_unlock(&dpc_lock);
return ZX_ERR_ALREADY_EXISTS;
}
struct percpu* cpu = get_local_percpu();
// put the dpc at the tail of the list and signal the worker
list_add_tail(&cpu->dpc_list, &dpc->node);
event_signal_thread_locked(&cpu->dpc_event);
spin_unlock(&dpc_lock);
return ZX_OK;
}
void dpc_shutdown(uint cpu_id) {
DEBUG_ASSERT(cpu_id < SMP_MAX_CPUS);
spin_lock_saved_state_t state;
spin_lock_irqsave(&dpc_lock, state);
auto& percpu = percpu::Get(cpu_id);
DEBUG_ASSERT(!percpu.dpc_stop);
// Ask the DPC thread to terminate.
percpu.dpc_stop = true;
// Take the thread pointer so we can join outside the spinlock.
thread_t* t = percpu.dpc_thread;
percpu.dpc_thread = nullptr;
spin_unlock_irqrestore(&dpc_lock, state);
// Wake it.
event_signal(&percpu.dpc_event, false);
// Wait for it to terminate.
int ret = 0;
zx_status_t status = thread_join(t, &ret, ZX_TIME_INFINITE);
DEBUG_ASSERT(status == ZX_OK);
DEBUG_ASSERT(ret == 0);
}
void dpc_shutdown_transition_off_cpu(uint cpu_id) {
DEBUG_ASSERT(cpu_id < SMP_MAX_CPUS);
spin_lock_saved_state_t state;
spin_lock_irqsave(&dpc_lock, state);
uint cur_cpu = arch_curr_cpu_num();
DEBUG_ASSERT(cpu_id != cur_cpu);
auto& percpu = percpu::Get(cpu_id);
// The DPC thread should already be stopped.
DEBUG_ASSERT(percpu.dpc_stop);
DEBUG_ASSERT(percpu.dpc_thread == nullptr);
list_node_t* src_list = &percpu.dpc_list;
list_node_t* dst_list = &percpu::Get(cur_cpu).dpc_list;
dpc_t* dpc;
while ((dpc = list_remove_head_type(src_list, dpc_t, node))) {
list_add_tail(dst_list, &dpc->node);
}
// Reset the state so we can restart DPC processing if the CPU comes back online.
DEBUG_ASSERT(list_is_empty(&percpu.dpc_list));
percpu.dpc_stop = false;
event_destroy(&percpu.dpc_event);
spin_unlock_irqrestore(&dpc_lock, state);
}
static int dpc_thread(void* arg) {
dpc_t dpc_local;
spin_lock_saved_state_t state;
arch_interrupt_save(&state, SPIN_LOCK_FLAG_INTERRUPTS);
struct percpu* cpu = get_local_percpu();
event_t* event = &cpu->dpc_event;
list_node_t* list = &cpu->dpc_list;
arch_interrupt_restore(state, SPIN_LOCK_FLAG_INTERRUPTS);
for (;;) {
// wait for a dpc to fire
__UNUSED zx_status_t err = event_wait(event);
DEBUG_ASSERT(err == ZX_OK);
spin_lock_irqsave(&dpc_lock, state);
if (cpu->dpc_stop) {
spin_unlock_irqrestore(&dpc_lock, state);
return 0;
}
// pop a dpc off the list, make a local copy.
dpc_t* dpc = list_remove_head_type(list, dpc_t, node);
// if the list is now empty, unsignal the event so we block until it is
if (!dpc) {
event_unsignal(event);
dpc_local.func = NULL;
} else {
dpc_local = *dpc;
}
spin_unlock_irqrestore(&dpc_lock, state);
// call the dpc
if (dpc_local.func) {
dpc_local.func(&dpc_local);
}
}
return 0;
}
void dpc_init_for_cpu(void) {
struct percpu* cpu = get_local_percpu();
uint cpu_num = arch_curr_cpu_num();
// the cpu's dpc state was initialized on a previous hotplug event
if (event_initialized(&cpu->dpc_event)) {
return;
}
list_initialize(&cpu->dpc_list);
event_init(&cpu->dpc_event, false, 0);
cpu->dpc_stop = false;
char name[10];
snprintf(name, sizeof(name), "dpc-%u", cpu_num);
cpu->dpc_thread = thread_create(name, &dpc_thread, NULL, DPC_THREAD_PRIORITY);
DEBUG_ASSERT(cpu->dpc_thread != nullptr);
thread_set_cpu_affinity(cpu->dpc_thread, cpu_num_to_mask(cpu_num));
thread_resume(cpu->dpc_thread);
}
static void dpc_init(unsigned int level) {
// initialize dpc for the main CPU
dpc_init_for_cpu();
}
LK_INIT_HOOK(dpc, dpc_init, LK_INIT_LEVEL_THREADING)