#ifdef USE_ZEPHYR #include "esphome/core/application.h" #include "esphome/core/log.h" #include "logger.h" #include #include #include #include #ifdef USE_LOGGER_EARLY_MESSAGE #include #endif namespace esphome::zephyr_coredump { __attribute__((weak)) void print_coredump() {} } // namespace esphome::zephyr_coredump namespace esphome::logger { __attribute__((section(".noinit"))) struct { uint32_t magic; uint32_t reason; uint32_t pc; uint32_t lr; #if defined(CONFIG_THREAD_NAME) char thread[CONFIG_THREAD_MAX_NAME_LEN]; #endif } crash_buf; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const char *const TAG = "logger"; #ifdef USE_LOGGER_UART_SELECTION_USB_CDC void Logger::cdc_loop_() { if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } static bool opened = false; uint32_t dtr = 0; uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr); /* Poll if the DTR flag was set, optional */ if (opened == dtr) { return; } if (!opened) { App.schedule_dump_config(); } opened = !opened; } #endif void Logger::pre_setup() { if (this->baud_rate_ > 0) { static const struct device *uart_dev = nullptr; switch (this->uart_) { case UART_SELECTION_UART0: uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart0)); break; case UART_SELECTION_UART1: uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1)); break; #ifdef USE_LOGGER_USB_CDC case UART_SELECTION_USB_CDC: uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0)); if (device_is_ready(uart_dev)) { usb_enable(nullptr); } break; #endif } if (!device_is_ready(uart_dev)) { ESP_LOGE(TAG, "%s is not ready.", LOG_STR_ARG(get_uart_selection_())); } else { this->uart_dev_ = uart_dev; #if defined(USE_LOGGER_WAIT_FOR_CDC) && defined(USE_LOGGER_UART_SELECTION_USB_CDC) uint32_t dtr = 0; uint32_t count = (10 * 100); // wait 10 sec for USB CDC to have early logs while (dtr == 0 && count-- != 0) { uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr); delay(10); arch_feed_wdt(); } #endif } } global_logger = this; ESP_LOGI(TAG, "Log initialized"); #ifdef USE_LOGGER_EARLY_MESSAGE char reason_buffer[zephyr::RESET_REASON_BUFFER_SIZE]; const char *reset_reason = zephyr::get_reset_reason(std::span(reason_buffer)); ESP_LOGI(TAG, "Reset reason: %s", reset_reason); dump_crash_(); zephyr_coredump::print_coredump(); #endif } void HOT Logger::write_msg_(const char *msg, uint16_t len) { // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK // Requires the debug component and an active SWD connection. // It is used for pyocd rtt -t nrf52840 printk("%.*s", static_cast(len), msg); #endif if (this->uart_dev_ == nullptr) { return; } for (uint16_t i = 0; i < len; ++i) { uart_poll_out(this->uart_dev_, msg[i]); } } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { case UART_SELECTION_UART0: return LOG_STR("UART0"); case UART_SELECTION_UART1: return LOG_STR("UART1"); #ifdef USE_LOGGER_USB_CDC case UART_SELECTION_USB_CDC: return LOG_STR("USB_CDC"); #endif default: return LOG_STR("UNKNOWN"); } } static const uint8_t REASON_BUF_SIZE = 32; static const char *reason_to_str(unsigned int reason, char *buf) { switch (reason) { case K_ERR_CPU_EXCEPTION: return "CPU exception"; case K_ERR_SPURIOUS_IRQ: return "Unhandled interrupt"; case K_ERR_STACK_CHK_FAIL: return "Stack overflow"; case K_ERR_KERNEL_OOPS: return "Kernel oops"; case K_ERR_KERNEL_PANIC: return "Kernel panic"; default: snprintf(buf, REASON_BUF_SIZE, "Unknown error (%u)", reason); return buf; } } void Logger::dump_crash_() { ESP_LOGD(TAG, "Crash buffer address %p", &crash_buf); if (crash_buf.magic == App.get_config_hash()) { char reason_buf[REASON_BUF_SIZE]; ESP_LOGE(TAG, "Last crash:"); ESP_LOGE(TAG, "Reason=%s PC=0x%08x LR=0x%08x", reason_to_str(crash_buf.reason, reason_buf), crash_buf.pc, crash_buf.lr); #if defined(CONFIG_THREAD_NAME) ESP_LOGE(TAG, "Thread: %s", crash_buf.thread); #endif } } void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) { crash_buf.magic = App.get_config_hash(); crash_buf.reason = reason; if (esf) { crash_buf.pc = esf->basic.pc; crash_buf.lr = esf->basic.lr; } #if defined(CONFIG_THREAD_NAME) auto thread = k_current_get(); const char *name = k_thread_name_get(thread); if (name) { strncpy(crash_buf.thread, name, sizeof(crash_buf.thread) - 1); crash_buf.thread[sizeof(crash_buf.thread) - 1] = '\0'; } else { crash_buf.thread[0] = '\0'; } #endif arch_restart(); } } // namespace esphome::logger extern "C" { void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) { esphome::logger::k_sys_fatal_error_handler(reason, esf); } } #endif