|  | // 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 |