/****************************************************************************
 * sched/misc/assert.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <nuttx/arch.h>
#include <nuttx/board.h>
#include <nuttx/cache.h>
#include <nuttx/coredump.h>
#include <nuttx/compiler.h>
#include <nuttx/fs/fs.h>
#include <nuttx/irq.h>
#include <nuttx/init.h>
#include <nuttx/fs/fs.h>
#include <nuttx/tls.h>
#include <nuttx/signal.h>
#include <nuttx/sched.h>
#ifdef CONFIG_ARCH_LEDS
#  include <arch/board/board.h>
#endif
#include <nuttx/panic_notifier.h>
#include <nuttx/reboot_notifier.h>
#include <nuttx/syslog/syslog.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/mm/kasan.h>
#include <nuttx/trace.h>

#include <assert.h>
#include <debug.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/utsname.h>

#include "irq/irq.h"
#include "sched/sched.h"
#include "group/group.h"

#ifdef CONFIG_COREDUMP
#  include "coredump.h"
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifndef CONFIG_BOARD_RESET_ON_ASSERT
#  define CONFIG_BOARD_RESET_ON_ASSERT 0
#endif

/* Check if an interrupt stack size is configured */

#ifndef CONFIG_ARCH_INTERRUPTSTACK
#  define CONFIG_ARCH_INTERRUPTSTACK 0
#endif

/* USB trace dumping */

#ifndef CONFIG_USBDEV_TRACE
#  undef CONFIG_ARCH_USBDUMP
#endif

#define DUMP_PTR(p, x) ((uintptr_t)(&(p)[(x)]) < stack_top ? (p)[(x)] : 0u)
#define DUMP_STRIDE    (sizeof(FAR void *) * 8u)

#if UINTPTR_MAX <= UINT32_MAX
#  define DUMP_FORMAT " %08" PRIxPTR
#elif UINTPTR_MAX <= UINT64_MAX
#  define DUMP_FORMAT " %016" PRIxPTR
#endif

/* Architecture can overwrite the default XCPTCONTEXT alignment */

#ifndef XCPTCONTEXT_ALIGN
#  define XCPTCONTEXT_ALIGN 16
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

#ifdef CONFIG_SMP
static noreturn_function int pause_cpu_handler(FAR void *arg);
#endif
typedef uintptr_t last_regs_t[XCPTCONTEXT_REGS];

/****************************************************************************
 * Private Data
 ****************************************************************************/

#ifdef CONFIG_SMP
static DEFINE_PER_CPU_BSS_SMP(bool, g_cpu_paused);
static struct smp_call_data_s g_call_data =
SMP_CALL_INITIALIZER(pause_cpu_handler, NULL);
#endif

static DEFINE_PER_CPU_BSS(last_regs_t, g_last_regs)
       aligned_data(XCPTCONTEXT_ALIGN);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: assert_tracecallback
 ****************************************************************************/

#ifdef CONFIG_ARCH_USBDUMP
static int usbtrace_syslog(FAR const char *fmt, ...)
{
  va_list ap;

  /* Let vsyslog do the real work */

  va_start(ap, fmt);
  vsyslog(LOG_EMERG, fmt, ap);
  va_end(ap);
  return OK;
}

static int assert_tracecallback(FAR struct usbtrace_s *trace, FAR void *arg)
{
  usbtrace_trprintf(usbtrace_syslog, trace->event, trace->value);
  return 0;
}
#endif

#ifdef CONFIG_ARCH_STACKDUMP

/****************************************************************************
 * Name: stack_dump
 ****************************************************************************/

static void stack_dump(uintptr_t sp, uintptr_t stack_top)
{
  uintptr_t stack;

  for (stack = sp; stack <= stack_top; stack += DUMP_STRIDE)
    {
      FAR uintptr_t *ptr = (FAR uintptr_t *)stack;

      _alert(DUMP_FORMAT ":" DUMP_FORMAT DUMP_FORMAT DUMP_FORMAT DUMP_FORMAT
             DUMP_FORMAT DUMP_FORMAT DUMP_FORMAT DUMP_FORMAT "\n",
             stack, DUMP_PTR(ptr, 0), DUMP_PTR(ptr, 1),
             DUMP_PTR(ptr, 2), DUMP_PTR(ptr, 3), DUMP_PTR(ptr, 4),
             DUMP_PTR(ptr, 5), DUMP_PTR(ptr, 6), DUMP_PTR(ptr, 7));
    }
}

/****************************************************************************
 * Name: dump_stackinfo
 ****************************************************************************/

static void dump_stackinfo(FAR const char *tag, uintptr_t sp,
                           uintptr_t base, size_t size, size_t used)
{
  uintptr_t top = base + size;

  _alert("%s Stack:\n", tag);
  _alert("  base: 0x%" PRIxPTR "\n", base);
  _alert("  size: %08zu\n", size);

  if (sp)
    {
      _alert("    sp: 0x%" PRIxPTR "\n", sp);

      /* Get more information */

      if (sp - DUMP_STRIDE >= base)
        {
          sp -= DUMP_STRIDE;
        }

#if CONFIG_ARCH_STACKDUMP_MAX_LENGTH > 0
      if (top - sp > CONFIG_ARCH_STACKDUMP_MAX_LENGTH)
        {
          top = sp + CONFIG_ARCH_STACKDUMP_MAX_LENGTH;
        }
#endif

      stack_dump(sp, top);
    }
  else
    {
#ifdef CONFIG_STACK_COLORATION
      size_t remain = size - used;

      base += remain;
      size -= remain;
#endif

#if CONFIG_ARCH_STACKDUMP_MAX_LENGTH > 0
      if (size > CONFIG_ARCH_STACKDUMP_MAX_LENGTH)
        {
          size = CONFIG_ARCH_STACKDUMP_MAX_LENGTH;
        }
#endif

      stack_dump(base, base + size);
    }
}

/****************************************************************************
 * Name: dump_stacks
 ****************************************************************************/

static void dump_stacks(FAR struct tcb_s *rtcb, uintptr_t sp)
{
#ifdef CONFIG_SMP
  int cpu = rtcb->cpu;
#else
  int cpu = this_cpu();
#endif
  UNUSED(cpu);
#if CONFIG_ARCH_INTERRUPTSTACK > 0
  uintptr_t intstack_base = up_get_intstackbase(cpu);
  size_t intstack_size = CONFIG_ARCH_INTERRUPTSTACK;
  uintptr_t intstack_top = intstack_base + intstack_size;
  uintptr_t intstack_sp = 0;
#endif
#ifdef CONFIG_ARCH_KERNEL_STACK
  uintptr_t kernelstack_base = (uintptr_t)rtcb->xcp.kstack;
  size_t kernelstack_size = CONFIG_ARCH_KERNEL_STACKSIZE;
  uintptr_t kernelstack_top = kernelstack_base + kernelstack_size;
  uintptr_t kernelstack_sp = 0;
#endif
  uintptr_t tcbstack_base = (uintptr_t)rtcb->stack_base_ptr;
  size_t tcbstack_size = (size_t)rtcb->adj_stack_size;
  uintptr_t tcbstack_top = tcbstack_base + tcbstack_size;
  uintptr_t tcbstack_sp = 0;
  bool force = false;

#if CONFIG_ARCH_INTERRUPTSTACK > 0
  if (sp >= intstack_base && sp <= intstack_top)
    {
      intstack_sp = sp;
    }
  else
#endif
#ifdef CONFIG_ARCH_KERNEL_STACK
  if (sp >= kernelstack_base && sp < kernelstack_top)
    {
      kernelstack_sp = sp;
    }
  else
#endif
  if (sp >= tcbstack_base && sp <= tcbstack_top)
    {
      tcbstack_sp = sp;
    }
  else
    {
      force = true;
      _alert("ERROR: Stack pointer is not within the stack\n");
    }

#if CONFIG_ARCH_INTERRUPTSTACK > 0
  if (intstack_sp || force)
    {
      dump_stackinfo("IRQ",
                     intstack_sp,
                     intstack_base,
                     intstack_size,
#ifdef CONFIG_STACK_COLORATION
                     up_check_intstack(cpu, 0)
#else
                     0
#endif
                     );

      /* Try to restore SP from current_regs if assert from interrupt. */

      tcbstack_sp = up_interrupt_context() ?
                    up_getusrsp((FAR void *)running_regs()) : 0u;
      if (tcbstack_sp < tcbstack_base || tcbstack_sp >= tcbstack_top)
        {
          tcbstack_sp = 0;
          force = true;
        }
    }
#endif

#ifdef CONFIG_ARCH_KERNEL_STACK
  if (kernelstack_sp || force)
    {
      dump_stackinfo("Kernel",
                     kernelstack_sp,
                     kernelstack_base,
                     kernelstack_size,
                     0);
    }
#endif

  if (tcbstack_sp || force)
    {
      dump_stackinfo("User",
                     tcbstack_sp,
                     tcbstack_base,
                     tcbstack_size,
#ifdef CONFIG_STACK_COLORATION
                     up_check_tcbstack(rtcb, rtcb->adj_stack_size)
#else
                     0
#endif
                     );
    }
}

#endif

#ifdef CONFIG_DEBUG_ALERT
/****************************************************************************
 * Name: dump_task
 ****************************************************************************/

static void dump_task(FAR struct tcb_s *tcb, FAR void *arg)
{
  char args[64] = "";
  char state[32];
  FAR char *s;
#ifdef CONFIG_STACK_COLORATION
  size_t stack_filled = 0;
  size_t stack_used;
#endif
  static FAR const char * const g_policy[4] =
  {
    "FIFO", "RR", "SPORADIC"
  };

  static FAR const char * const g_ttypenames[4] =
  {
    "Task",
    "pthread",
    "Kthread",
    "Invalid"
  };

#ifndef CONFIG_SCHED_CPULOAD_NONE
  struct cpuload_s cpuload;
  size_t fracpart = 0;
  size_t intpart = 0;
  size_t tmp;

  if (clock_cpuload(tcb->pid, &cpuload) == OK &&
      cpuload.total > 0u)
    {
      tmp      = (size_t)((1000u * cpuload.active) / cpuload.total);
      intpart  = tmp / 10u;
      fracpart = tmp - 10u * intpart;
    }
#endif

#ifdef CONFIG_STACK_COLORATION
  stack_used = up_check_tcbstack(tcb, tcb->adj_stack_size);
  if (tcb->adj_stack_size > 0u && stack_used > 0u)
    {
      /* Use fixed-point math with one decimal place */

      stack_filled = 10u * 100u * stack_used / tcb->adj_stack_size;
    }
#endif

  /* Stringify the argument vector */

  nxtask_argvstr(tcb, args, sizeof(args));

  /* get the task_state */

  nxsched_get_stateinfo(tcb, state, sizeof(state));
  if ((s = strchr(state, (int)',')) != NULL)
    {
      *s = ' ';
    }

  /* Dump interesting properties of this task */

  _alert("   %4d %5d"
#ifndef CONFIG_UP
         "  %4d"
#endif
         " %3d %-8s %-7s %-3c"
         " %-18s"
         " " SIGSET_FMT
         " %p"
         "   %7zu"
#ifdef CONFIG_STACK_COLORATION
         "   %7zu   %3zu.%1zu%%%c"
#endif
#ifndef CONFIG_SCHED_CPULOAD_NONE
         "   %3zu.%01zu%%"
#endif
         "   %s%s\n"
         , tcb->pid
         , tcb->group ? tcb->group->tg_pid : -1
#ifdef CONFIG_SMP
         , tcb->cpu
#elif !defined(CONFIG_UP)
         , this_cpu()
#endif
         , tcb->sched_priority
         , g_policy[(atomic_read(&tcb->flags) & TCB_FLAG_POLICY_MASK) >>
                    TCB_FLAG_POLICY_SHIFT]
         , g_ttypenames[(atomic_read(&tcb->flags) & TCB_FLAG_TTYPE_MASK)
                        >> TCB_FLAG_TTYPE_SHIFT]
         , atomic_read(&tcb->flags) & TCB_FLAG_EXIT_PROCESSING ? 'P' : '-'
         , state
         , SIGSET_ELEM(&tcb->sigprocmask)
         , tcb->stack_base_ptr
         , tcb->adj_stack_size
#ifdef CONFIG_STACK_COLORATION
         , up_check_tcbstack(tcb, tcb->adj_stack_size)
         , stack_filled / 10u, stack_filled % 10u
         , (stack_filled >= 10u * 80u ? '!' : ' ')
#endif
#ifndef CONFIG_SCHED_CPULOAD_NONE
         , intpart, fracpart
#endif
         , get_task_name(tcb)
         , args
        );
}
#endif

/****************************************************************************
 * Name: dump_backtrace
 ****************************************************************************/

#ifdef CONFIG_SCHED_BACKTRACE
static void dump_backtrace(FAR struct tcb_s *tcb, FAR void *arg)
{
  sched_dumpstack(tcb->pid);
}
#endif

/****************************************************************************
 * Name: dump_filelist
 ****************************************************************************/

#ifdef CONFIG_SCHED_DUMP_ON_EXIT
static void dump_fdlist(FAR struct tcb_s *tcb, FAR void *arg)
{
  FAR struct fdlist *list = &tcb->group->tg_fdlist;
  fdlist_dump(list);
}
#endif

/****************************************************************************
 * Name: dump_tasks
 ****************************************************************************/

static void dump_tasks(void)
{
#if CONFIG_ARCH_INTERRUPTSTACK > 0
  int cpu = this_cpu();
#endif

  /* Dump interesting properties of each task in the crash environment */

  _alert("   PID GROUP"
#ifndef CONFIG_UP
         "   CPU"
#endif
         " PRI POLICY   TYPE    NPX"
         " STATE   EVENT"
         "      SIGMASK        "
         "  STACKBASE"
         "  STACKSIZE"
#ifdef CONFIG_STACK_COLORATION
         "      USED   FILLED "
#endif
#ifndef CONFIG_SCHED_CPULOAD_NONE
         "      CPU"
#endif
         "   COMMAND\n");

#if CONFIG_ARCH_INTERRUPTSTACK > 0
#  ifdef CONFIG_SMP
  for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++)
    {
#  endif
#  ifdef CONFIG_STACK_COLORATION
      size_t stack_used = up_check_intstack(cpu, 0u);
      size_t stack_filled = 0u;

      if (stack_used > 0u)
        {
          /* Use fixed-point math with one decimal place */

          stack_filled = 10u * 100u * stack_used /
                         (size_t)CONFIG_ARCH_INTERRUPTSTACK;
        }
#  endif

      _alert("  ----   ---"
#  ifndef CONFIG_UP
             "  %4d"
#  endif
             " --- --------"
             " ------- ---"
             " ------- ----------"
             " ----------------"
             " 0x%" PRIxPTR
             "   %7u"
#  ifdef CONFIG_STACK_COLORATION
             "   %7zu   %3zu.%1zu%%%c"
#  endif
#  ifndef CONFIG_SCHED_CPULOAD_NONE
             "     ----"
#  endif
             "   irq\n"
#  ifndef CONFIG_UP
             , cpu
#endif
             , up_get_intstackbase(cpu)
             , CONFIG_ARCH_INTERRUPTSTACK
#  ifdef CONFIG_STACK_COLORATION
             , stack_used
             , stack_filled / 10u
             , stack_filled % 10u
             , (stack_filled >= 10u * 80u ? '!' : ' ')
#  endif
            );
#  ifdef CONFIG_SMP
    }
#  endif
#endif

#ifdef CONFIG_DEBUG_ALERT
  nxsched_foreach(dump_task, NULL);
#endif

#ifdef CONFIG_SCHED_BACKTRACE
  nxsched_foreach(dump_backtrace, NULL);
#endif

#ifdef CONFIG_SCHED_DUMP_ON_EXIT
  nxsched_foreach(dump_fdlist, NULL);
#endif
}

/****************************************************************************
 * Name: dump_lockholder
 ****************************************************************************/

#ifdef CONFIG_ARCH_DEADLOCKDUMP
static void dump_lockholder(pid_t tid)
{
  char buf[BACKTRACE_BUFFER_SIZE(CONFIG_LIBC_BACKTRACE_DEPTH)];
  FAR struct tcb_s *tcb;
  FAR void **stack = NULL;
  int depth = 0;
  pid_t holder = INVALID_PROCESS_ID;

  tcb = nxsched_get_tcb(tid);
  DEBUGASSERT(tcb != NULL);

  if (tcb->task_state == TSTATE_WAIT_SEM)
    {
      FAR mutex_t *mutex = tcb->waitobj;

      holder = nxmutex_get_holder(mutex);
#  ifdef CONFIG_LIBC_MUTEX_BACKTRACE
      stack = backtrace_get(mutex->stack, &depth);
#  endif
    }
#  ifdef CONFIG_SPINLOCK_DEBUG
  else
    {
      FAR spinlock_debug_t *info = tcb->wait_spinlock;

      holder = spinlock_get_holder(info);
#    ifdef CONFIG_SPINLOCK_BACKTRACE
      stack = backtrace_get(info->stack, &depth);
#    endif
    }
#  endif

  nxsched_put_tcb(tcb);
  backtrace_format(buf, sizeof(buf), stack, depth);

  _alert("Deadlock(%d) holder(%d) backtrace: %s\n", tid, holder, buf);
}

/****************************************************************************
 * Name: dump_deadlock
 ****************************************************************************/

static void dump_deadlock(void)
{
  pid_t deadlock[CONFIG_ARCH_DEADLOCKDUMP_MAX];
  size_t i = nxsched_collect_deadlock(deadlock,
                                      CONFIG_ARCH_DEADLOCKDUMP_MAX);

  while (i-- > 0)
    {
      dump_lockholder(deadlock[i]);
#  ifdef CONFIG_SCHED_BACKTRACE
      sched_dumpstack(deadlock[i]);
#  endif
    }
}
#endif

#ifdef CONFIG_SMP

/****************************************************************************
 * Name: pause_cpu_handler
 ****************************************************************************/

static noreturn_function int pause_cpu_handler(FAR void *arg)
{
  up_copyusercontext(this_cpu_var(g_last_regs), running_regs(),
                     sizeof(last_regs_t));
  this_cpu_var_smp(g_cpu_paused) = true;
  up_flush_dcache_all();
  while (1);
}

/****************************************************************************
 * Name: pause_all_cpu
 ****************************************************************************/

static void pause_all_cpu(void)
{
  cpu_set_t cpuset = (1 << CONFIG_SMP_NCPUS) - 1;
  int delay = CONFIG_ASSERT_PAUSE_CPU_TIMEOUT;
  int ncpus = 0;

  CPU_CLR(this_cpu(), &cpuset);
  nxsched_smp_call_async(cpuset, &g_call_data);
  this_cpu_var_smp(g_cpu_paused) = true;

  /* Check if all CPUs paused with timeout */

  while (delay-- > 0 && ncpus < CONFIG_SMP_NCPUS)
    {
      if (per_cpu_var_smp(g_cpu_paused, ncpus))
        {
          ncpus++;
        }
      else
        {
          up_mdelay(1);
        }
    }
}
#endif

#ifdef CONFIG_DEBUG_ALERT
/****************************************************************************
 * Name: dump_running_task
 ****************************************************************************/

static void dump_running_task(FAR struct tcb_s *rtcb, FAR void *regs)
{
  /* Register dump */

  up_dump_register(regs);

#ifdef CONFIG_ARCH_STACKDUMP
  dump_stacks(rtcb, up_getusrsp(regs));
#endif

  /* Show back trace */

#ifdef CONFIG_SCHED_BACKTRACE
  sched_dumpstack(rtcb->pid);
#endif
}

/****************************************************************************
 * Name: dump_assert_info
 *
 * Description:
 *   Dump basic information of assertion
 *
 ****************************************************************************/

static void dump_assert_info(FAR struct tcb_s *rtcb,
                             FAR const char *filename, int linenum,
                             FAR const char *msg, FAR void *regs)
{
  FAR struct tcb_s *ptcb = NULL;
  struct utsname name;

  if (rtcb->group &&
      (atomic_read(&rtcb->flags) & TCB_FLAG_TTYPE_MASK) !=
       TCB_FLAG_TTYPE_KERNEL)
    {
      ptcb = nxsched_get_tcb(rtcb->group->tg_pid);
    }

  uname(&name);
  _alert("Current Version: %s %s %s %s %s\n",
         name.sysname, name.nodename,
         name.release, name.version, name.machine);

  _alert("Assertion failed %s: at file: %s:%d task"
#ifndef CONFIG_UP
         "(CPU%d)"
#endif
         ": "
         "%s "
         "process: %s "
         "%p\n",
         msg ? msg : "",
         filename ? filename : "", linenum,
#ifndef CONFIG_UP
         up_cpu_index(),
#endif
         get_task_name(rtcb),
         ptcb ? get_task_name(ptcb) : "Kernel",
         rtcb->entry.main);

  nxsched_put_tcb(ptcb);

  /* Dump current CPU registers, running task stack and backtrace. */

  dump_running_task(rtcb, regs);

  /* Flush any buffered SYSLOG data */

  syslog_flush();
}
#endif  /* CONFIG_DEBUG_ALERT */

/****************************************************************************
 * Name: dump_fatal_info
 ****************************************************************************/

static void dump_fatal_info(FAR struct tcb_s *rtcb,
                            FAR const char *filename, int linenum,
                            FAR const char *msg, FAR void *regs)
{
#if defined(CONFIG_SMP) && defined(CONFIG_DEBUG_ALERT)
  int cpu;

  /* Dump other CPUs registers, running task stack and backtrace. */

  for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++)
    {
      if (cpu == this_cpu())
        {
          continue;
        }

      _alert("Dump CPU%d: %s\n", cpu,
             per_cpu_var_smp(g_cpu_paused, cpu) ? "PAUSED" : "RUNNING");

      if (per_cpu_var_smp(g_cpu_paused, cpu))
        {
          dump_running_task(per_cpu_var_smp(g_running_tasks, cpu),
                            per_cpu_var_smp(g_last_regs, cpu));
        }
    }
#endif

  /* Dump backtrace of other tasks. */

  dump_tasks();

#ifdef CONFIG_ARCH_DEADLOCKDUMP
  /* Deadlock Dump */

  dump_deadlock();
#endif

#ifdef CONFIG_ARCH_USBDUMP
  /* Dump USB trace data */

  usbtrace_enumerate(assert_tracecallback, NULL);
#endif

  /* Flush previous SYSLOG data before possible long time coredump */

  syslog_flush();
}

/****************************************************************************
 * Name: dump_core_info
 ****************************************************************************/

static void dump_core_info(FAR struct tcb_s *rtcb, FAR const char *filename,
                           int linenum, FAR const char *msg, FAR void *regs)
{
#ifdef CONFIG_BOARD_CRASHDUMP_CUSTOM
  board_crashdump(up_getsp(), rtcb, filename, linenum, msg, regs);
#endif

#ifndef CONFIG_BOARD_CRASHDUMP_NONE

  /* Dump core information */

#  ifdef CONFIG_BOARD_COREDUMP_FULL
  coredump_dump(INVALID_PROCESS_ID);
#  else
  coredump_dump(rtcb->pid);
#  endif
#endif

  /* Flush any buffered SYSLOG data */

  syslog_flush();
}

#ifdef CONFIG_ARCH_LOWPUTC
/****************************************************************************
 * Name: dump_mini_info
 ****************************************************************************/

static void dump_mini_info(FAR uintptr_t *regs)
{
  char out[32];
  int i;

  up_puts("Reset board on recursive assert:");

  for (i = 0; i < XCPTCONTEXT_REGS; i++)
    {
      if ((i & 7) == 0)
        {
          snprintf(out, sizeof(out), "\r\n%p:", (void *)&regs[i]);
          up_puts(out);
        }

      snprintf(out, sizeof(out), DUMP_FORMAT, regs[i]);
      up_puts(out);
    }
}
#endif

/****************************************************************************
 * Name: reset_board
 *
 * Description:
 *   Reset board or stuck here to flash LED. It should never return.
 ****************************************************************************/

static void reset_board(void)
{
#if CONFIG_BOARD_RESET_ON_ASSERT >= 1
  board_reset(CONFIG_BOARD_ASSERT_RESET_VALUE);
#else
  for (; ; )
    {
#ifdef CONFIG_ARCH_LEDS
      /* FLASH LEDs a 2Hz */

      board_autoled_on(LED_PANIC);
      up_mdelay(250);
      board_autoled_off(LED_PANIC);
#endif
      up_mdelay(250);
    }
#endif
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: _assert
 ****************************************************************************/

void _assert(FAR const char *filename, int linenum,
             FAR const char *msg, FAR void *regs, bool irq)
{
  const bool os_ready = OSINIT_OS_READY();
  FAR struct tcb_s *rtcb = running_task();
  struct panic_notifier_s notifier_data;
  irqstate_t flags = 0; /* Suppress GCC warning */

  static DEFINE_PER_CPU_BMP(spinlock_t, g_assert_lock) = SP_UNLOCKED;
#define g_assert_lock this_cpu_var_bmp(g_assert_lock)

  /* Save registers to this point */

  up_saveusercontext(this_cpu_var(g_last_regs));
  regs = this_cpu_var(g_last_regs);

  if (OSINIT_IS_PANIC())
    {
      /* Already in fatal state, reset board directly. */

#ifdef CONFIG_ARCH_LOWPUTC
      dump_mini_info(regs);
#endif
      reset_board(); /* Should not return. */
    }

  if (os_ready)
    {
      flags = spin_lock_irqsave_nopreempt(&g_assert_lock);
    }

#if CONFIG_BOARD_RESET_ON_ASSERT < 2
  if (irq || (atomic_read(&rtcb->flags) & TCB_FLAG_TTYPE_MASK) ==
      TCB_FLAG_TTYPE_KERNEL)
#endif
    {
      /* Disable KASAN to avoid false positive */

      kasan_stop();

      /* Fatal error, enter panic state. */

      g_nx_initstate = OSINIT_PANIC;
      sched_trace_mark("PANIC");

#ifdef CONFIG_SMP
      if (os_ready)
        {
          pause_all_cpu();
        }
#endif
    }

  notifier_data.rtcb = rtcb;
  notifier_data.regs = regs;
  notifier_data.filename = filename;
  notifier_data.linenum = linenum;
  notifier_data.msg = msg;
  panic_notifier_call_chain(OSINIT_IS_PANIC()
                            ? PANIC_KERNEL : PANIC_TASK,
                            &notifier_data);
#ifdef CONFIG_ARCH_LEDS
  board_autoled_on(LED_ASSERTION);
#endif

  /* Flush any buffered SYSLOG data (from prior to the assertion) */

  syslog_flush();

#ifdef CONFIG_DEBUG_ALERT
  /* Dump basic info of assertion. */

  dump_assert_info(rtcb, filename, linenum, msg, regs);
#endif

  if (OSINIT_IS_PANIC())
    {
      /* Dump fatal info of assertion. */

      dump_fatal_info(rtcb, filename, linenum, msg, regs);

      /* Flush dcache before notify other CPU */

      up_flush_dcache_all();

      /* Notify PANIC_KERNEL_FINAL */

      panic_notifier_call_chain(PANIC_KERNEL_FINAL, &notifier_data);

      /* Dump core information */

      dump_core_info(rtcb, filename, linenum, msg, regs);

      reboot_notifier_call_chain(SYS_HALT, NULL);

      reset_board(); /* Should not return. */
    }

  if (os_ready)
    {
      spin_unlock_irqrestore_nopreempt(&g_assert_lock, flags);
    }
}