blob: 44dea6cb4843f2f7f23e52e8436d04d64c8b00f6 [file] [log] [blame] [edit]
// Copyright 2024 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 <lib/affine/ratio.h>
#include <lib/wake-vector.h>
#include <zircon/syscalls/system.h>
#include <zircon/types.h>
#include <kernel/idle_power_thread.h>
#include <kernel/thread.h>
namespace wake_vector {
namespace {
template <typename CountType>
CountType LimitSizeT(size_t size) {
return static_cast<CountType>(std::min<size_t>(size, ktl::numeric_limits<CountType>::max()));
}
} // namespace
WakeEvent::GlobalList WakeEvent::global_list_;
WakeEvent::PendingList WakeEvent::pending_list_;
void WakeEvent::Initialize() {
// Stash our WakeVector constants in our report_info. These will never change over the lifetime
// of the WakeEvent, so we might as well just stash it all now.
WakeVector::Diagnostics diagnostic_info;
wake_vector_.GetDiagnostics(diagnostic_info);
Guard<Mutex> guard(GlobalListLock::Get());
DEBUG_ASSERT(!in_global_list());
{
Guard<SpinLock, IrqSave> pending_guard{PendingListLock::Get()};
DEBUG_ASSERT(!in_pending_list());
report_info_ = zx_wake_source_report_entry_t{0};
report_info_.koid = diagnostic_info.koid;
static_assert(sizeof(report_info_.name) == diagnostic_info.extra.size());
::memcpy(report_info_.name, diagnostic_info.extra.data(), sizeof(report_info_.name));
// Initially flag our report entry as having been reported. This sets up
// our bookkeeping properly so the first time it becomes signaled, it will
// initialize all of the fields instead of simply bumping the signal count
// and updating the last signal time.
AssignHasBeenReported(true);
}
global_list_.push_back(this);
}
void WakeEvent::Destroy() {
Guard<Mutex> guard(GlobalListLock::Get());
DEBUG_ASSERT(in_global_list());
{
// If we are in pending list right now, we need to remove ourselves, making
// certain to ack at the IdlePowerThread if needed.
Guard<SpinLock, IrqSave> pending_guard{PendingListLock::Get()};
if (in_pending_list()) {
if (is_signaled()) {
IdlePowerThread::AcknowledgeSystemWakeEvent();
AssignSignaled(false);
}
pending_list_.erase(*this);
} else {
// It should not be possible to have a pending ack, but not be on the
// pending list.
DEBUG_ASSERT(!is_signaled());
}
}
global_list_.erase(*this);
}
WakeResult WakeEvent::TriggerLocked(zx_instant_boot_t trigger_time) {
// There are three cases to consider here:
//
// 1) We are already signaled. This is an unusual state to be in as we are
// being triggered again, even though we have never received an ack.
// Regardless we should just bump our signal count, and record a new last
// triggered time.
// 2) We are not signaled, but we have not been reported yet. We must have
// have been acknowledged previously, and are becoming triggered once
// again. Just like case #1, bump the signal count and record a new last
// triggered time.
// 3) We are not signaled, and have been reported (note; this is also the
// initial state of a WakeEvent). It is time to re-initialize our report
// entry state to record the first trigger event.
if (!is_signaled() && has_been_reported()) {
report_info_.initial_signal_time = trigger_time;
report_info_.last_ack_time = ZX_TIME_INFINITE;
report_info_.signal_count = 1;
AssignHasBeenReported(false);
} else {
++report_info_.signal_count;
}
// Regardless of whether this was the first trigger, or a subsequent trigger,
// record a new last trigger time.
report_info_.last_signal_time = trigger_time;
// If we are not in the pending list, add ourself now. Now, we add ourself to
// the front of the list instead of the back in order to prevent this one wake
// source from being double-reported in the case that it has been added to a
// report, and becomes re-triggered before report generation has finished.
// See the extensive comment near the end of the WakeEvent class in
// "wake-vector.h" for more details.
if (!in_pending_list()) {
pending_list_.push_front(this);
}
// If we are triggered multiple times without being ack'ed, make sure to
// report only one trigger to the IdlePowerThread level.
if (!is_signaled()) {
AssignSignaled(true);
return IdlePowerThread::TriggerSystemWakeEvent();
}
return WakeResult::BadState;
}
void WakeEvent::AcknowledgeLocked(zx_instant_boot_t trigger_time, AckBehavior ack_behavior) {
// In order for an event to be waiting to be acknowledged, it must be in the
// pending list, and it needs to be signaled (we should not be getting spurious
// double acks)
DEBUG_ASSERT(in_pending_list());
DEBUG_ASSERT(is_signaled());
// Always record the last ack time.
report_info_.last_ack_time = trigger_time;
if (ack_behavior == AckBehavior::ClearSignaled) {
// If our AckBehavior tells us to clear our signaled, then ack at the
// IdlePowerThread level, and clear the signaled flag.
IdlePowerThread::AcknowledgeSystemWakeEvent();
AssignSignaled(false);
} else {
// We have been told that we should remain signaled. Clear our "has been
// reported" flag. We are in a situation where the interrupt was triggered
// at least once more after the first time. The first trigger may have been
// reported, but the second and subsequent triggers have not been reported
// yet.
AssignHasBeenReported(false);
}
// Do NOT remove this event from the pending list, even if it has been
// reported already. If we remove the event from the list, we run the risk of
// invalidating the iterator for a report which is currently being generated.
// Instead, just leave the event it place, and let report generation handle
// removing the event from the list when the time comes.
//
// See the extensive comment in `wake-vector.h` for a more comprehensive
// explanation of the pending list and the various ways that operations
// interact with it, and the things they need to do in order to remain safe.
}
void WakeEvent::Dump(FILE* f, zx_instant_boot_t log_triggered_after_boot_time) {
Guard<Mutex> guard(GlobalListLock::Get());
Guard<SpinLock, IrqSave> pending_guard{PendingListLock::Get()};
for (auto iter = global_list_.cbegin(); iter.IsValid(); ++iter) {
if (iter->is_signaled() ||
iter->report_info_.last_signal_time >= log_triggered_after_boot_time) {
const zx_wake_source_report_entry_t entry = iter->report_info_;
pending_guard.CallUnlocked([f, &entry]() {
// clang-format off
fprintf(f, " koid : %" PRIu64 "\n"
" pending : %s\n"
" prev reported : %s\n"
" signal count : %u\n"
" initial trigger : %" PRIi64 "\n"
" last trigger : %" PRIi64 "\n"
" last ack : %" PRIi64 "\n"
" extra : %.*s\n\n",
entry.koid,
entry.flags & ZX_SYSTEM_WAKE_REPORT_ENTRY_FLAG_SIGNALED ? "yes" : "no",
entry.flags & ZX_SYSTEM_WAKE_REPORT_ENTRY_FLAG_PREVIOUSLY_REPORTED ? "yes" : "no",
entry.signal_count,
entry.initial_signal_time,
entry.last_signal_time,
entry.last_ack_time,
static_cast<int>(ktl::size(entry.name)), entry.name);
// clang-format on
});
}
}
}
zx_status_t WakeEvent::GenerateWakeEventReport(
zx_instant_boot_t suspend_start_time, user_out_ptr<zx_wake_source_report_header_t> out_header,
user_out_ptr<zx_wake_source_report_entry_t> out_entries, uint32_t num_entries,
user_out_ptr<uint32_t> actual_entries) {
// These should have been verified for us already at the syscall layer.
DEBUG_ASSERT(static_cast<bool>(out_header));
const bool do_report_entries = static_cast<bool>(out_entries);
DEBUG_ASSERT(do_report_entries == (num_entries != 0));
DEBUG_ASSERT(do_report_entries == static_cast<bool>(actual_entries));
zx_wake_source_report_header_t hdr{0};
uint32_t reported_entries{0};
hdr.suspend_start_time = suspend_start_time;
// We are going to need to heap allocate a side buffer to copy our results into. We cannot copy
// our results back out to user mode while holding any of our locks, and we cannot allocate an
// arbitrary amount of entries on our stack.
ktl::unique_ptr<zx_wake_source_report_entry_t[]> entry_side_buffer;
size_t entry_side_buffer_len{0};
{
// Hold the global list lock for the entire O(n) time it takes to create the
// report. This prevents any wake sources from being created or destroyed
// while we iterate the pending list. This guarantees that our total wake
// source count cannot change while we generate the report, and also means
// that wake source destruction cannot invalidate our iterator out from
// under us when we drop the pending lock in order to copy data out to
// user-mode.
//
// See the extensive comment in `wake-vector.h` for a more comprehensive
// explanation of the pending list and the various ways that operations
// interact with it, and the things they need to do in order to remain safe.
//
Guard<Mutex> guard(GlobalListLock::Get());
{
Guard<SpinLock, IrqSave> pending_guard{PendingListLock::Get()};
// Record the number of entries which are waiting to be reported at the
// start of report generation. We'll adjust this number later on to
// reflect the actual number of entries which were waiting at the start of
// the report which didn't actually fit into the user's buffer.
hdr.unreported_wake_report_entries =
LimitSizeT<decltype(hdr.unreported_wake_report_entries)>(pending_list_.size());
if (do_report_entries) {
PendingList::iterator next = pending_list_.begin();
entry_side_buffer_len = ktl::min<size_t>(num_entries, pending_list_.size());
// Now that we have captured our initial iterator, and computed the
// maximum number of entries we might attempt to report, allocate a side
// buffer we can copy the information into. We cannot simply copy into
// a stack allocated single entry which we then copy to user mode after
// dropping the pending lock. We are not allowed to hold _any_ locks
// when we copy into user mode, so we will need to be able to buffer
// _all_ of our results before starting to do so.
//
// TODO(johngro): We don't necessarily have to do this every time we
// generate a report. We could also keep the allocation around as a
// static member of WakeEvent protected by the global lock, and growing
// it only when we needed to. At the end of the op (when we drop the
// global lock to copy out to user mode), we could move the buffer out,
// perform the copy, then lock and move the buffer back in again. We'd
// never have a second buffer sitting around unless there was a
// concurrent attempt to generate a report, something which is
// technically possible but should never happen in practice.
zx_status_t alloc_result{ZX_OK};
pending_guard.CallUnlocked([&]() {
if (entry_side_buffer_len > 0) {
fbl::AllocChecker ac;
entry_side_buffer = fbl::make_unique_checked<zx_wake_source_report_entry_t[]>(
ac, entry_side_buffer_len);
if (!ac.check()) {
alloc_result = ZX_ERR_NO_MEMORY;
}
}
});
// Bail out if we could not allocate a side buffer.
if (alloc_result != ZX_OK) {
return alloc_result;
}
while (next.IsValid() && (reported_entries < entry_side_buffer_len)) {
PendingList::iterator iter = next++;
bool skip_reporting = false;
if (!iter->is_signaled()) {
// The wake source is not currently signaled. If it has been
// reported already, then skip adding it to the user's report.
// Either way, remove it from the pending list.
if (iter->has_been_reported()) {
skip_reporting = true;
}
pending_list_.erase(iter);
}
if (!skip_reporting) {
// Copy our result into our side buffer and mark it as having been
// reported. Then, then drop our lock briefly allowing pending IRQs
// to run, and wake sources to become signaled/acked.
entry_side_buffer[reported_entries++] = iter->report_info_;
iter->AssignHasBeenReported(true);
pending_guard.CallUnlocked([]() { arch::Yield(); });
}
// Whether we added the entry to the user's report, or skipped it
// because the entry was just waiting around to be cleaned up by us,
// we should drop the unreported count.
DEBUG_ASSERT(hdr.unreported_wake_report_entries > 0);
hdr.unreported_wake_report_entries--;
}
}
}
// Record the total number of wake sources in the system as well as our
// report time before dropping the global lock.
hdr.total_wake_sources = LimitSizeT<decltype(hdr.total_wake_sources)>(global_list_.size());
hdr.report_time = current_boot_time();
}
// We're done. Finish up by trying to copy everything back out to user-mode.
if (do_report_entries) {
DEBUG_ASSERT(reported_entries <= entry_side_buffer_len);
if (const zx_status_t status =
out_entries.copy_array_to_user(entry_side_buffer.get(), reported_entries, 0);
status != ZX_OK) {
return status;
}
if (const zx_status_t status = actual_entries.copy_to_user(reported_entries); status != ZX_OK) {
return status;
}
}
return out_header.copy_to_user(hdr);
}
void WakeEvent::DiscardWakeEventReport() {
Guard<Mutex> guard(GlobalListLock::Get());
Guard<SpinLock, IrqSave> pending_guard{PendingListLock::Get()};
PendingList::iterator next = pending_list_.begin();
while (next.IsValid()) {
PendingList::iterator iter = next++;
// Remove the event from the pending list iff it has been acknowledged.
// Make sure the reported flag is set when we remove it from the list.
if (!iter->is_signaled()) {
iter->AssignHasBeenReported(true);
pending_list_.erase(iter);
}
// Drop the lock and arch::Yield to give any pending triggers or acks a chance to run.
pending_guard.CallUnlocked([]() { arch::Yield(); });
}
}
} // namespace wake_vector