blob: ff75d7551210853ebf25509dbebded6777adc9cd [file] [log] [blame]
// Copyright 2021 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 <ctype.h>
#include <lib/console.h>
#include <lib/lazy_init/lazy_init.h>
#include <stdio.h>
#include <ktl/algorithm.h>
#include <ktl/bit.h>
#include <ktl/iterator.h>
#include <ktl/limits.h>
#include <ktl/type_traits.h>
#include "persistent-debuglog-internal.h"
#include <ktl/enforce.h>
namespace {
template <size_t kStorageSize>
class PersistentDebuglogGlobals {
public:
void init_early() {
auto foo = sizeof(storage_);
static_assert(ktl::is_same_v<decltype(foo), size_t>);
static_assert(static_cast<size_t>(sizeof(storage_)) <= ktl::numeric_limits<uint32_t>::max());
static_assert(sizeof(storage_) <= static_cast<size_t>(ktl::numeric_limits<uint32_t>::max()));
static_assert(static_cast<size_t>(sizeof(storage_)) <=
static_cast<size_t>(ktl::numeric_limits<uint32_t>::max()));
instance_.Initialize(storage_, static_cast<uint32_t>(sizeof(storage_)));
}
void set_location(void* vaddr, size_t len) { instance_->SetLocation(vaddr, len); }
void write(const ktl::string_view str) { instance_->Write(str); }
void invalidate() { instance_->Invalidate(); }
ktl::string_view get_recovered_log() const { return instance_->GetRecoveredLog(); }
int cmd(int argc, const cmd_args* argv, uint32_t flags) {
auto usage = [&]() -> int {
printf("usage:\n");
printf("%s dump : dump the recovered persistent debug log\n", argv[0].str);
return ZX_ERR_INTERNAL;
};
if (argc < 2) {
printf("not enough arguments\n");
return usage();
}
if (!strcmp(argv[1].str, "dump")) {
ktl::string_view recovered = instance_->GetRecoveredLog();
if (recovered.size() > 0) {
printf("Recovered %zu bytes from the persistent debug log.\n", recovered.size());
printf("---- BEGIN ----\n");
stdout->Write(recovered);
printf("---- END ----\n");
} else {
printf("There was no persistent debug log recovered!\n");
}
} else {
printf("unknown command\n");
return usage();
}
return ZX_OK;
}
private:
lazy_init::LazyInit<PersistentDebugLog, lazy_init::CheckType::None,
lazy_init::Destructor::Disabled>
instance_;
char storage_[kStorageSize];
};
template <>
class PersistentDebuglogGlobals<0> {
public:
void init_early() {}
void set_location(void* vaddr, size_t len) {}
void write(const ktl::string_view str) {}
void invalidate() {}
ktl::string_view get_recovered_log() { return ktl::string_view{nullptr, 0}; }
int cmd(int argc, const cmd_args* argv, uint32_t flags) { return -1; }
};
PersistentDebuglogGlobals<kTargetPersistentDebugLogSize> gLog;
} // namespace
void PersistentDebugLog::SetLocation(void* virt, size_t len) {
// The location of the persistent dlog must be compatible with the header's
// alignment, and the amount of space for the log payload must be positive,
// otherwise we cannot effectively use the memory.
static_assert(ktl::has_single_bit(alignof(LogHeader)),
"Persistent dlog header's alignment must be a power of 2 in size");
if ((reinterpret_cast<uintptr_t>(virt) & (alignof(LogHeader) - 1)) ||
(len <= sizeof(LogHeader))) {
return;
}
// If we already have a persistent dlog location, then this function has been
// (improperly) called twice. It is tempting to assert here, but we are
// currently very early in boot, which would make debugging the assert
// extremely difficult. Instead, just ignore this request.
//
// TODO(johngro): come back here and try to put a warning/OOPS in to the dlog
// buffer?
Guard<SpinLock, IrqSave> guard{&persistent_log_lock_};
if (plog.hdr != nullptr) {
return;
}
// Check our log header to see if we have a recovered persistent log. If we
// do, recover the log into our recovery buffer so that it can be picked up
// later on and sent up to usermode in the crashlog.
//
// Note: we are at a point in boot where the heap has not been brought up yet,
// which is why the recovery buffer needs to be statically allocated. If this
// becomes an issue some day, we can shift to a strategy where we remember
// where the persistent log is, but don't actually start to use it until we
// get a chance to save it off into a dynamically allocated buffer.
LogHeader* hdr = reinterpret_cast<LogHeader*>(virt);
const uint32_t payload_size = static_cast<uint32_t>(
ktl::min<size_t>(ktl::numeric_limits<uint32_t>::max(), len - sizeof(LogHeader)));
if (hdr->IsMagicValid() && (hdr->rd_ptr < payload_size)) {
// This looks as good as it is going to. Our magic number is valid, and our
// read pointer lies within the available payload size. Save as much as we
// can in our static buffer, discarding the oldest data first if we cannot
// fit it all.
uint32_t todo, rd;
auto& rpl = recovered_persistent_log_;
if (payload_size <= rpl.capacity) {
todo = payload_size;
rd = hdr->rd_ptr;
} else {
DEBUG_ASSERT(rpl.capacity <= ktl::numeric_limits<decltype(todo)>::max());
todo = rpl.capacity;
rd = (hdr->rd_ptr + (payload_size - todo)) % payload_size;
}
char* src = hdr->payload();
for (rpl.size = 0; todo > 0; --todo) {
char c = src[rd++];
if (rd >= payload_size) {
rd = 0;
}
// Skip all zeros.
if (c == 0) {
continue;
}
rpl.data[rpl.size++] = (isprint(c) || (c == '\n')) ? c : '?';
}
}
// Reset the log, then install it, and we are done.
hdr->ValidateMagic();
hdr->rd_ptr = 0;
::memset(hdr->payload(), 0, payload_size);
CleanCacheRange(hdr, len);
plog.hdr = hdr;
plog.payload_size = payload_size;
}
void PersistentDebugLog::Write(ktl::string_view str) {
Guard<SpinLock, IrqSave> guard{&persistent_log_lock_};
// If we have no persistent log, just get out.
if (plog.hdr == nullptr) {
return;
}
// Copy the data into our log
char* payload = plog.hdr->payload();
uint32_t todo;
size_t offset;
if (str.size() > plog.payload_size) {
todo = plog.payload_size;
offset = str.size() - plog.payload_size;
} else {
todo = static_cast<uint32_t>(str.size());
offset = 0;
}
uint32_t space = plog.payload_size - plog.hdr->rd_ptr;
if (space > todo) {
str.copy(payload + plog.hdr->rd_ptr, todo, offset);
CleanCacheRange(payload + plog.hdr->rd_ptr, todo);
plog.hdr->rd_ptr += todo;
CleanCacheRange(plog.hdr, sizeof(*plog.hdr));
} else {
str.copy(payload + plog.hdr->rd_ptr, space, offset);
str.copy(payload, todo - space, offset + space);
CleanCacheRange(payload + plog.hdr->rd_ptr, space);
CleanCacheRange(payload, todo - space);
plog.hdr->rd_ptr = todo - space;
CleanCacheRange(plog.hdr, sizeof(*plog.hdr));
}
}
void persistent_dlog_init_early() { gLog.init_early(); }
void persistent_dlog_set_location(void* vaddr, size_t len) { gLog.set_location(vaddr, len); }
void persistent_dlog_write(const ktl::string_view str) { gLog.write(str); }
void persistent_dlog_invalidate() { gLog.invalidate(); }
ktl::string_view persistent_dlog_get_recovered_log() { return gLog.get_recovered_log(); }
#if TARGET_PERSISTENT_DEBUGLOG_SIZE > 0
namespace {
int cmd_pdlog(int argc, const cmd_args* argv, uint32_t flags) {
return gLog.cmd(argc, argv, flags);
}
STATIC_COMMAND_START
STATIC_COMMAND_MASKED("pdlog", "pdlog", &cmd_pdlog, CMD_AVAIL_ALWAYS)
STATIC_COMMAND_END(pdlog)
} // namespace
#endif