| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2008-2015 Travis Geiselbrecht |
| // |
| // 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 <ctype.h> |
| #include <debug.h> |
| #include <lib/debuglog.h> |
| #include <lib/io.h> |
| #include <lib/persistent-debuglog.h> |
| #include <lib/zircon-internal/macros.h> |
| #include <platform.h> |
| #include <string.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <arch/ops.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/thread.h> |
| #include <platform/debug.h> |
| #include <vm/vm.h> |
| |
| namespace { |
| |
| enum class SkipPersistedDebuglog { No = 0, Yes }; |
| |
| DECLARE_SINGLETON_SPINLOCK_WITH_TYPE(dputc_spin_lock, MonitoredSpinLock); |
| |
| } // namespace |
| |
| void serial_write(ktl::string_view str) { |
| Guard<MonitoredSpinLock, IrqSave> guard{dputc_spin_lock::Get(), SOURCE_TAG}; |
| |
| // Write out the serial port. |
| platform_dputs_irq(str.data(), str.size()); |
| } |
| |
| static void stdout_write(ktl::string_view str, SkipPersistedDebuglog skip_pdlog) { |
| if (skip_pdlog == SkipPersistedDebuglog::No) { |
| persistent_dlog_write(str); |
| } |
| |
| if (dlog_bypass() == false) { |
| if (dlog_write(DEBUGLOG_INFO, 0, str) == ZX_OK) |
| return; |
| } |
| serial_write(str); |
| } |
| |
| static void stdout_write_buffered(ktl::string_view str, SkipPersistedDebuglog skip_pdlog) { |
| Thread* t = Thread::Current::Get(); |
| |
| if (unlikely(t == nullptr)) { |
| stdout_write(str, skip_pdlog); |
| return; |
| } |
| |
| // Look for corruption and don't continue. |
| Thread::Linebuffer& lb = t->linebuffer(); |
| if (unlikely(!is_kernel_address((uintptr_t)lb.buffer.data()) || lb.pos >= lb.buffer.size())) { |
| stdout_write("<linebuffer corruption>\n"sv, skip_pdlog); |
| return; |
| } |
| |
| while (!str.empty()) { |
| size_t remaining = lb.buffer.size() - lb.pos; |
| auto substring = str.substr(0, remaining); |
| size_t newline_pos = substring.find_first_of('\n'); |
| |
| size_t size; |
| bool inject; |
| bool flush; |
| if (newline_pos != substring.npos) { |
| // A newline that fits in our remaining buffer. |
| size = newline_pos + 1; |
| inject = false; |
| flush = true; |
| } else if (substring.size() == remaining) { |
| // We fill the buffer, injecting a newline. |
| size = remaining - 1; |
| inject = true; |
| flush = true; |
| } else { |
| // We only add to the buffer. |
| size = substring.size(); |
| inject = false; |
| flush = false; |
| } |
| |
| memcpy(&lb.buffer[lb.pos], substring.data(), size); |
| str.remove_prefix(size); |
| lb.pos += size; |
| |
| if (inject) { |
| lb.buffer[lb.pos] = '\n'; |
| lb.pos += 1; |
| } |
| if (flush) { |
| stdout_write({lb.buffer.data(), lb.pos}, skip_pdlog); |
| lb.pos = 0; |
| } |
| } |
| } |
| |
| // This is what printf calls. Really this could and should be const. |
| // But all the stdio function signatures require non-const `FILE*`. |
| FILE FILE::stdout_{[](void*, ktl::string_view str) { |
| stdout_write_buffered(str, SkipPersistedDebuglog::No); |
| return static_cast<int>(str.size()); |
| }, |
| nullptr}; |
| |
| FILE gSerialFile{[](void*, ktl::string_view str) { |
| serial_write(str); |
| return static_cast<int>(str.size()); |
| }, |
| nullptr}; |
| |
| FILE gStdoutUnbuffered{[](void*, ktl::string_view str) { |
| stdout_write(str, SkipPersistedDebuglog::No); |
| return static_cast<int>(str.size()); |
| }, |
| nullptr}; |
| |
| FILE gStdoutNoPersist{[](void*, ktl::string_view str) { |
| stdout_write_buffered(str, SkipPersistedDebuglog::Yes); |
| return static_cast<int>(str.size()); |
| }, |
| nullptr}; |