blob: fb1f35feecc395829c9f4bc602096f189711cdbd [file] [log] [blame]
// Copyright 2023 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
#ifndef ZIRCON_KERNEL_INCLUDE_KERNEL_IDLE_POWER_THREAD_H_
#define ZIRCON_KERNEL_INCLUDE_KERNEL_IDLE_POWER_THREAD_H_
#include <stdint.h>
#include <zircon/time.h>
#include <arch/mp_unplug_event.h>
#include <kernel/mutex.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <ktl/atomic.h>
//
// Platform Independent Per-CPU Idle/Power Thread
//
// Manages the idle, suspend, and offline functions of a CPU. This thread is scheduled whenever
// there is no eligible work in the run queues of a CPU or a power state management operation is
// requested for the CPU. The idle/power thread provides the platform-independent run loop for
// handling these functions and delegates to the platform-specific subsystems for the relevant
// operations.
//
class IdlePowerThread final {
public:
IdlePowerThread() = default;
~IdlePowerThread() = default;
IdlePowerThread(const IdlePowerThread&) = delete;
IdlePowerThread& operator=(const IdlePowerThread&) = delete;
IdlePowerThread(IdlePowerThread&&) = delete;
IdlePowerThread& operator=(IdlePowerThread&&) = delete;
// The current or target high-level operational state of the CPU. The depth and type of
// idle/suspend states depends inputs from other subsystems, such as timer and run queues and
// system power targets.
// TODO(https://fxbug.dev/42086705): Rationalize this state with mp active / online states.
enum class State : uint8_t {
Active,
Wakeup, // Similar to Active with respect to scheduling but signals a wakeup occurred.
Offline,
Suspend,
};
// Returns true if the power thread should be scheduled instead of eligible work because it needs
// to perform a transition or power management function.
bool pending_power_work() const {
const StateMachine state = state_.load(ktl::memory_order_acquire);
return (state.current != State::Active && state.current != State::Wakeup) ||
state.current != state.target;
}
// The result of a transition request, including the status code of the request and the current
// state when the request was made.
struct TransitionResult {
const zx_status_t status;
const State starting_state;
};
// Requests to transition from Active to Offline.
//
// Returns:
// - {ZX_OK, Active} if the transition was successful.
// - {ZX_OK, Offline} if the current state was already Offline.
// - {ZX_ERR_BAD_STATE, current state} if the current state was not Active or Offline.
// - {ZX_ERR_TIMED_OUT, Active} if the current state was Active and transition timeout expired.
TransitionResult TransitionActiveToOffline(zx_time_t timeout_at = ZX_TIME_INFINITE)
TA_EXCL(TransitionLock::Get()) {
Guard<Mutex> guard{TransitionLock::Get()};
return TransitionFromTo(State::Active, State::Offline, timeout_at);
}
// Requests to transition from Offline to Active.
//
// Returns:
// - {ZX_OK, Offline} if the transition was successful.
// - {ZX_OK, Active} if the current state was already Active.
// - {ZX_ERR_BAD_STATE, current state} if the current state was not Active or Offline.
// - {ZX_ERR_TIMED_OUT, Offline} if the current state was Offline and transition timeout
// expired.
TransitionResult TransitionOfflineToActive(zx_time_t timeout_at = ZX_TIME_INFINITE)
TA_EXCL(TransitionLock::Get()) {
Guard<Mutex> guard{TransitionLock::Get()};
return TransitionFromTo(State::Offline, State::Active, timeout_at);
}
// Attempts to transition all active CPUs from Active to Suspend until the specified resume time.
// CPUs that are Offline before suspend will remain Offline after resume until explicitly
// transitioned to Active.
//
// Returns:
// - ZX_OK if active CPU suspend succeeded and then resumed.
// - ZX_ERR_TIMED_OUT when resume_at is in the past before transitioning to suspend.
// - ZX_ERR_BAD_STATE if there is a pending wake event when attempting to transition to suspend.
static zx_status_t TransitionAllActiveToSuspend(zx_time_t resume_at = ZX_TIME_INFINITE)
TA_EXCL(TransitionLock::Get());
// The result of a request to wake up the system.
enum class WakeResult : uint8_t {
// The system was not suspended at the time of the event.
Active,
// The system was suspended at the time of the wake event.
Resumed,
// The system was in the process of suspending at the time of the wake event.
SuspendAborted,
// An already pending wake event was triggered again.
BadState,
};
// Manages the lifecycle of a wake event.
//
// A wake event is triggered in response to an interrupt, exception, timer, or other wake source
// that should resume the system from a suspended state. When a wake event is triggered, it enters
// the pending state and will prevent the system from entering suspend until it has been
// acknowledged. A pending wake event is automatically acknowledged when the WakeEvent instance is
// destroyed to prevent missing an acknowledgement that would render the system unable to suspend.
//
class WakeEvent {
public:
WakeEvent() = default;
~WakeEvent() { Acknowledge(); }
// Triggers a wakeup that resumes the system, or aborts an incomplete suspend sequence, and
// prevents the system from starting a new suspend sequence.
//
// Must be called with interrupts and preempt disabled.
//
// Returns:
// - WakeResult::Active if this wake trigger occurred when the system was active.
// - WakeResult::Resumed if this or another wake trigger resumed the system.
// - WakeResult::SuspendAborted if this wake trigger occurred before suspend completed.
// - WakeResult::BadState if this wake event is already pending.
//
WakeResult Trigger() {
DEBUG_ASSERT(arch_ints_disabled());
DEBUG_ASSERT(Thread::Current::Get()->preemption_state().PreemptIsEnabled() == false);
if (!pending_) {
pending_ = true;
return TriggerSystemWakeEvent();
}
return WakeResult::BadState;
}
// Acknowledges a pending wake event, allowing the system to enter suspend when all other
// suspend conditions are met.
void Acknowledge() {
if (pending_) {
pending_ = false;
AcknowledgeSystemWakeEvent();
}
}
private:
bool pending_{false};
};
// Implements the run loop executed by the CPU's idle/power thread.
static int Run(void* arg);
// Called by mp_unplug_current_cpu() to complete last steps of taking the CPU offline.
void FlushAndHalt();
// Accessors to the underlying Thread instance.
Thread& thread() { return thread_; }
const Thread& thread() const { return thread_; }
// Accessor to the global system suspend state.
static bool system_suspended() {
return system_suspend_state_.load(ktl::memory_order_acquire) & SystemSuspendStateSuspendedBit;
}
static uint64_t pending_wake_events() {
return system_suspend_state_.load(ktl::memory_order_acquire) &
SystemSuspendStateWakeEventPendingMask;
}
private:
// Allow percpu to initialize this_cpu_;
friend struct percpu;
struct StateMachine {
State current{State::Active};
State target{State::Active};
constexpr bool operator==(const StateMachine& other) const {
return current == other.current && target == other.target;
}
};
// Shorthand expressions for various comparisons and asserts.
static constexpr StateMachine kActive{State::Active, State::Active};
static constexpr StateMachine kActiveToSuspend{State::Active, State::Suspend};
static constexpr StateMachine kWakeup{State::Wakeup, State::Wakeup};
static constexpr StateMachine kSuspend{State::Suspend, State::Suspend};
static constexpr StateMachine kSuspendToWakeup{State::Suspend, State::Wakeup};
bool CompareExchangeState(StateMachine& expected, StateMachine desired) {
return state_.compare_exchange_strong(expected, desired, ktl::memory_order_acq_rel,
ktl::memory_order_acquire);
}
TransitionResult TransitionFromTo(State expected_state, State target_state, zx_time_t timeout_at)
TA_REQ(TransitionLock::Get());
static WakeResult WakeBootCpu();
static WakeResult TriggerSystemWakeEvent();
static void AcknowledgeSystemWakeEvent();
ktl::atomic<StateMachine> state_{};
AutounsignalMpUnplugEvent complete_;
Thread thread_;
cpu_num_t this_cpu_{INVALID_CPU};
DECLARE_SINGLETON_MUTEX(TransitionLock);
enum SystemSuspendState : uint64_t {
SystemSuspendStateActive = 0,
SystemSuspendStateSuspendedBit = uint64_t{1} << 63,
SystemSuspendStateWakeEventPendingMask = SystemSuspendStateSuspendedBit - 1,
};
inline static ktl::atomic<uint64_t> system_suspend_state_{SystemSuspendStateActive};
inline static Timer resume_timer_ TA_GUARDED(TransitionLock::Get()){};
};
#endif // ZIRCON_KERNEL_INCLUDE_KERNEL_IDLE_POWER_THREAD_H_