* 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
#ifndef CONFIG_ARCH_INTERRUPTSTACK
# define CONFIG_ARCH_INTERRUPTSTACK 0
#endif
#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
#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;
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);
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
);
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)
{
stack_filled = 10u * 100u * stack_used / tcb->adj_stack_size;
}
#endif
nxtask_argvstr(tcb, args, sizeof(args));
nxsched_get_stateinfo(tcb, state, sizeof(state));
if ((s = strchr(state, (int)',')) != NULL)
{
*s = ' ';
}
_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
_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)
{
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;
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)
{
up_dump_register(regs);
#ifdef CONFIG_ARCH_STACKDUMP
dump_stacks(rtcb, up_getusrsp(regs));
#endif
#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_running_task(rtcb, regs);
syslog_flush();
}
#endif
* 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;
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_tasks();
#ifdef CONFIG_ARCH_DEADLOCKDUMP
dump_deadlock();
#endif
#ifdef CONFIG_ARCH_USBDUMP
usbtrace_enumerate(assert_tracecallback, NULL);
#endif
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
# ifdef CONFIG_BOARD_COREDUMP_FULL
coredump_dump(INVALID_PROCESS_ID);
# else
coredump_dump(rtcb->pid);
# endif
#endif
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 *)®s[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
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;
static DEFINE_PER_CPU_BMP(spinlock_t, g_assert_lock) = SP_UNLOCKED;
#define g_assert_lock this_cpu_var_bmp(g_assert_lock)
up_saveusercontext(this_cpu_var(g_last_regs));
regs = this_cpu_var(g_last_regs);
if (OSINIT_IS_PANIC())
{
#ifdef CONFIG_ARCH_LOWPUTC
dump_mini_info(regs);
#endif
reset_board();
}
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
{
kasan_stop();
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,
¬ifier_data);
#ifdef CONFIG_ARCH_LEDS
board_autoled_on(LED_ASSERTION);
#endif
syslog_flush();
#ifdef CONFIG_DEBUG_ALERT
dump_assert_info(rtcb, filename, linenum, msg, regs);
#endif
if (OSINIT_IS_PANIC())
{
dump_fatal_info(rtcb, filename, linenum, msg, regs);
up_flush_dcache_all();
panic_notifier_call_chain(PANIC_KERNEL_FINAL, ¬ifier_data);
dump_core_info(rtcb, filename, linenum, msg, regs);
reboot_notifier_call_chain(SYS_HALT, NULL);
reset_board();
}
if (os_ready)
{
spin_unlock_irqrestore_nopreempt(&g_assert_lock, flags);
}
}