/** @file avm_reboot_status.c * * ######################################################################## * * This program is free software; you can distribute it and/or modify it * under the terms of the GNU General Public License (Version 2) as * published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. * * ######################################################################## * mbahr: * 1.) hold and get reboot-status after Soft/NMI-Reboot * 2.) handle die-notifier * 3.) handle panic-notifier */ #define pr_fmt(fmt) KBUILD_MODNAME ": reboot_status: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_AVM_FASTIRQ) #if defined (CONFIG_AVM_FASTIRQ_ARCH_ARM_COMMON) #include #else #include #endif #endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/ #include "avm_sammel.h" #if defined(CONFIG_SOC_GRX500) #include "arch_avm_reboot_status_grx.h" #endif/*--- #if defined(CONFIG_SOC_GRX500) ---*/ #if defined(CONFIG_VR9) || defined(CONFIG_AR10) #include "arch_avm_reboot_status_ifx.h" #endif/*--- #if defined(CONFIG_VR9) || defined(CONFIG_AR10) ---*/ #if defined(CONFIG_MACH_PUMA6) #include "arch_avm_reboot_status_puma6a.h" #elif defined(CONFIG_X86) && !defined(CONFIG_X86_PUMA7) #include "arch_avm_reboot_status_puma6x.h" #elif defined(CONFIG_MACH_PUMA7) #if defined(CONFIG_X86_PUMA7) #include "arch_avm_reboot_status_puma7x.h" #else #include "arch_avm_reboot_status_puma7.h" #endif #endif #if defined(CONFIG_MACH_BCM963138) #include "arch_avm_reboot_status_brcma.h" #endif/*--- #if defined(CONFIG_MACH_BCM963138) ---*/ #if defined(CONFIG_MACH_BCM963138) #include "arch_avm_reboot_status_brcma.h" #endif/*--- #if defined(CONFIG_MACH_BCM963138) ---*/ #if defined(CONFIG_ARCH_IPQ40XX) #include "arch_avm_reboot_status_ipq40xx.h" #endif/*--- #if defined(CONFIG_ARCH_IPQ40XX) ---*/ #if defined(CONFIG_SOC_AR724X) || defined(CONFIG_SOC_AR934X) || defined(CONFIG_SOC_QCA955X) || defined(CONFIG_SOC_QCA953X) || defined(CONFIG_SOC_QCA956X) #include "arch_avm_reboot_status_scrpn.h" #endif/*--- #if defined(CONFIG_SOC_AR724X) || defined(CONFIG_SOC_AR934X) || defined(CONFIG_SOC_QCA955X) || defined(CONFIG_SOC_QCA953X) || defined(CONFIG_SOC_QCA956X) ---*/ static enum _avm_reset_status avm_reboot_status; /**--------------------------------------------------------------------------------**\ \brief Deliver last reboot-status \**--------------------------------------------------------------------------------**/ enum _avm_reset_status avm_reset_status(void){ enum _avm_reset_status status; switch(avm_reboot_status) { case RS_PANIC: case RS_OOM: case RS_OOPS: status = RS_REBOOT; break; default: status = avm_reboot_status; } return status; } EXPORT_SYMBOL(avm_reset_status); struct _reboot_info { const enum _avm_reset_status status; const char *matchtext; int matchlen; const char *printouttext; const char *shortprintouttext; unsigned int reboot_count; }; static struct _reboot_info reboot_info[] = { { status: RS_SOFTWATCHDOG, matchtext: SOFTWATCHDOG_REBOOT_STATUS_TEXT, matchlen: sizeof(SOFTWATCHDOG_REBOOT_STATUS_TEXT), printouttext: "Softwatchdog-Reboot", shortprintouttext: "WD" }, { status: RS_NMIWATCHDOG, matchtext: NMI_REBOOT_STATUS_TEXT, matchlen: sizeof(NMI_REBOOT_STATUS_TEXT), printouttext: "NMI-Watchdog-Reset", shortprintouttext: "NMI" }, { status: RS_FIRMWAREUPDATE, matchtext: UPDATE_REBOOT_STATUS_TEXT, matchlen: sizeof(UPDATE_REBOOT_STATUS_TEXT), printouttext: "Fw-Update", shortprintouttext: NULL }, { status: RS_SHORTREBOOT, matchtext: POWERON_REBOOT_STATUS_TEXT, matchlen: sizeof(POWERON_REBOOT_STATUS_TEXT), printouttext: "Short-PowerOff-Reboot", shortprintouttext: "SHORTPOWERCUT"}, { status: RS_TEMP_REBOOT, matchtext: TEMP_REBOOT_STATUS_TEXT, matchlen: sizeof(TEMP_REBOOT_STATUS_TEXT), printouttext: "Temperature-Reboot", shortprintouttext: "TEMPERATURE" }, { status: RS_REBOOT_FOR_UPDATE, matchtext: SOFT_REBOOT_STATUS_TEXT_UPDATE, matchlen: sizeof(SOFT_REBOOT_STATUS_TEXT_UPDATE), printouttext: "Update-Reboot", shortprintouttext: "UPDATE" }, { status: RS_PANIC, matchtext: SOFT_REBOOT_STATUS_TEXT_PANIC, matchlen: sizeof(SOFT_REBOOT_STATUS_TEXT_PANIC), printouttext: "Soft-Reboot", shortprintouttext: "PANIC" }, { status: RS_OOM, matchtext: SOFT_REBOOT_STATUS_TEXT_OOM, matchlen: sizeof(SOFT_REBOOT_STATUS_TEXT_OOM), printouttext: "Soft-Reboot", shortprintouttext: "OOM" }, { status: RS_OOPS, matchtext: SOFT_REBOOT_STATUS_TEXT_OOPS, matchlen: sizeof(SOFT_REBOOT_STATUS_TEXT_OOPS), printouttext: "Soft-Reboot", shortprintouttext: "KCRASH" }, /*--- als vorletzter Eintrag, da dieser Untermenge von RS_PANIC/RS_OOM/RS_OOPS ---*/ { status: RS_REBOOT, matchtext: SOFT_REBOOT_STATUS_TEXT, matchlen: sizeof(SOFT_REBOOT_STATUS_TEXT), printouttext: "Soft-Reboot", shortprintouttext: NULL }, /*--- definition: have to be last entry: ---*/ { status: RS_POWERON, matchtext: NULL, matchlen: 0, printouttext: "Power-On", shortprintouttext: NULL }, }; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void read_reboot_counters(char *txt) { char *p; unsigned int i, control, sum = 0; unsigned int counter[ARRAY_SIZE(reboot_info)]; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { counter[i] = 0; if (reboot_info[i].shortprintouttext && (p = strstr(txt, reboot_info[i].shortprintouttext))) { p += strlen(reboot_info[i].shortprintouttext) + 1; /*--- inklusive ( ---*/ sscanf(p, "%u", &counter[i]); sum += counter[i]; } } if ((p = strstr(txt, "SUM"))) { p += sizeof("SUM"); /*--- inklusive ( ---*/ sscanf(p, "%u", &control); if (control == sum) { for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { reboot_info[i].reboot_count = counter[i]; } } } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void write_reboot_counters(char *txt, int maxlen) { unsigned int i, sum = 0, len; txt[0] = 0; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { if (reboot_info[i].shortprintouttext && reboot_info[i].reboot_count) { sum += reboot_info[i].reboot_count; len = snprintf(txt, maxlen, " %s(%u) ", reboot_info[i].shortprintouttext, reboot_info[i].reboot_count); if (len >= (unsigned int)maxlen) { return; } maxlen -= len, txt += len; } } if (sum) { snprintf(txt, maxlen, "SUM(%u)", sum); } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void clear_reboot_counters(void) { unsigned int i; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { reboot_info[i].reboot_count = 0; } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static unsigned int sum_reboot_counters(void) { unsigned int i, sum = 0; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { if (reboot_info[i].shortprintouttext) { sum += reboot_info[i].reboot_count; } } return sum; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int get_fieldindex_by_status(enum _avm_reset_status status) { unsigned int i; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { if (reboot_info[i].status == status) { return i; } } return -1; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void avm_rebootcounter_reset(void){ clear_reboot_counters(); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ char *avm_rebootcounter_string(char *txt, unsigned int maxlen){ char *start = txt; unsigned int i, len; start[0] = 0; if (sum_reboot_counters() == 0) { return start; } len = snprintf(txt, maxlen, "Irregular Reboots: SUM(%u) - ", sum_reboot_counters()); if (len >= maxlen) { return start; } maxlen -= len, txt += len; for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { if (reboot_info[i].shortprintouttext && reboot_info[i].reboot_count) { len = snprintf(txt, maxlen, "%s(%u) ", reboot_info[i].shortprintouttext, reboot_info[i].reboot_count); if (len >= maxlen) { return start; } maxlen -= len, txt += len; } } return start; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void set_reboot_status(int nofix, enum _avm_reset_status status) { int idx; const char *text = "-----"; unsigned int len = strlen(text) + 1; static const char *reboot_cause_written = NULL; char *mailbox = (char *)arch_get_mailbox(); if (mailbox == NULL) { return; } /*--- only if not valid set before: ---*/ if (reboot_cause_written != NULL) { return; } if ((status == RS_REBOOT) && oops_in_progress) { status = RS_OOPS; } idx = get_fieldindex_by_status(status); if (idx < 0) { return; } if (reboot_info[idx].matchtext) { text = reboot_info[idx].matchtext; len = reboot_info[idx].matchlen; } if (nofix == 0){ reboot_cause_written = text; /*--------------------------------------------------------------------------------*\ * Reboot-Counter updaten/loeschen \*--------------------------------------------------------------------------------*/ switch(status) { case RS_REBOOT: clear_reboot_counters(); break; case RS_FIRMWAREUPDATE: clear_reboot_counters(); break; default: if (reboot_info[idx].shortprintouttext) { reboot_info[idx].reboot_count++; } break; } } #if !defined(CONFIG_X86_PUMA7) memcpy(mailbox, text, min(511U, len)); #else arch_set_mailbox_msg(text); #endif write_reboot_counters(&mailbox[len + 1], 511 - len); /*--- dazwischen nullterminiert ---*/ arch_flush_mailbox(mailbox, 512U - 1U); if (nofix == 0){ printk(KERN_ERR"%s: %s%s%s%s%s%s\n", __func__, reboot_info[idx].printouttext, reboot_info[idx].shortprintouttext ? "(": "", reboot_info[idx].shortprintouttext ? reboot_info[idx].shortprintouttext : "", reboot_info[idx].shortprintouttext ? ") ": " ", mailbox[len + 1] ? " - " : "", &mailbox[len + 1]); } } /**--------------------------------------------------------------------------------**\ \brief set actual reboot-status once - following settings will be ignored. special case: status = RS_POWERON - it happens as a cold-start (PCMCR-workarround for GRX) \param status reboot-status \**--------------------------------------------------------------------------------**/ void avm_set_reset_status(enum _avm_reset_status status){ if (status == RS_POWERON) { set_reboot_status(1, RS_POWERON); return; } set_reboot_status(0, status); } EXPORT_SYMBOL(avm_set_reset_status); /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int avm_reboot_handler(struct notifier_block *nb, unsigned long event, void *buf); static struct notifier_block avm_reboot_notifier = { .notifier_call = avm_reboot_handler, /* callback */ .priority = INT_MAX }; /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int avm_reboot_handler(struct notifier_block *nb __maybe_unused, unsigned long event __maybe_unused, void *buf __maybe_unused){ printk_avm_console_bend(0); /* force serial-output */ avm_set_reset_status(RS_REBOOT); avm_stack_check(NULL); return NOTIFY_DONE; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ #if defined(CONFIG_AVM_FASTIRQ) #ifdef CONFIG_ARCH_QCOM void avm_set_crashed_cpu(int cpu) { SET_CRASHED_CPU(cpu); } int avm_get_crashed_cpu(void) { return(GET_CRASHED_CPU()); } #endif/*--- #ifdef CONFIG_ARCH_QCOM ---*/ #endif/*--- #ifdef CONFIG_AVM_FASTIRQ ---*/ static int panic_handler(struct notifier_block *notifier_block, unsigned long event, void *cause_string); static struct notifier_block panic_notifier = { .notifier_call = panic_handler, .next = NULL, .priority = 0 }; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int panic_handler(struct notifier_block *notifier_block __maybe_unused, unsigned long event __maybe_unused, void *cause_string __maybe_unused) { console_verbose(); avm_set_reset_status(RS_PANIC); avm_stack_check(NULL); avm_oom_show_memstat(0x1); /* show memory depend on situation */ printk_avm_console_bend(0); /*--- force serial output ---*/ #if defined(arch_panic_notifier) return arch_panic_notifier(notifier_block, event, cause_string); #else/*--- #if #defined(arch_panic_notifier) ---*/ return NOTIFY_DONE; #endif/*--- #else ---*//*--- #if #defined(arch_panic_notifier) ---*/ } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int die_notifier(struct notifier_block *self, unsigned long cmd, void *ptr); static struct notifier_block die_nb = { .notifier_call = die_notifier, .priority = 0, }; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int die_notifier(struct notifier_block *self __maybe_unused, unsigned long cmd __maybe_unused, void *ptr __maybe_unused) { console_verbose(); avm_set_reset_status(RS_OOPS); avm_stack_check(NULL); #if defined(arch_die_notifier) return arch_die_notifier(self, cmd, ptr); #else return NOTIFY_OK; #endif/*--- #if #defined(arch_die_notifier) ---*/ } /**--------------------------------------------------------------------------------**\ * \brief initial function called over arch_initcall-chain of linux-os * get the last reboot-status from architecture-depend reboot-safe * further functions: * - print out last reboot-status * - initialize actual reboot-status to overwritable short-power-on * - register die-notifier * - register panic-notifier \**--------------------------------------------------------------------------------**/ static __init int get_reboot_status(void) { unsigned int i, len; char Buffer[512]; char *mailbox = (char *) arch_get_mailbox(); if (mailbox == NULL) { return 0; } memcpy(Buffer, mailbox, 512); Buffer[511] = '\0'; /*--- printk(KERN_ERR"Raw-Reboot Status: '%s'\n", Buffer); ---*/ for(i = 0; i < ARRAY_SIZE(reboot_info); i++) { if(reboot_info[i].matchtext == NULL) { break; } len = reboot_info[i].matchlen; if (memcmp(Buffer, reboot_info[i].matchtext, len) == 0) { read_reboot_counters(&Buffer[len]); #if defined(ATH_NO_NMI) /*--- workarround - should be a nmi/freezer ---*/ if (reboot_info[i].status == RS_SHORTREBOOT) { int idx = get_fieldindex_by_status(RS_NMIWATCHDOG); if(idx >= 0) { char txt[128]; unsigned long error_epc = read_c0_errorepc(); unsigned int sticky_reg = ath_reg_rd(ATH_RESET_BASE + ATH_RESET_REG_STICKY); i = idx; reboot_info[i].reboot_count++; snprintf(txt, sizeof(txt), "Maybe watchdog without log\nerror-epc=%pS\nsticky-reg=0x%08x\n", (void *)error_epc, sticky_reg); TFFS3_panic_dummy_log(txt); } } #endif/*--- #if defined(ATH_NO_NMI) ---*/ if (reboot_info[i].status == RS_SHORTREBOOT) { reboot_info[i].reboot_count++; } break; } } avm_reboot_status = reboot_info[i].status; printk(KERN_ERR"Reboot Status is: %s%s%s%s%s\n", reboot_info[i].printouttext, reboot_info[i].shortprintouttext ? "(" : "", reboot_info[i].shortprintouttext ? reboot_info[i].shortprintouttext : "", reboot_info[i].shortprintouttext ? ") " : " ", avm_rebootcounter_string(Buffer, sizeof(Buffer))); #if defined(ATH_NO_NMI) write_c0_errorepc(0x0); #endif set_reboot_status(1, RS_SHORTREBOOT); register_die_notifier(&die_nb); atomic_notifier_chain_register(&panic_notifier_list, &panic_notifier); register_reboot_notifier(&avm_reboot_notifier); return 0; } #if defined(CONFIG_X86_PUMA7) fs_initcall(get_reboot_status); #else arch_initcall(get_reboot_status); #endif static struct proc_dir_entry *rebootprocdir; #define PROC_REBOOTDIR "avm/reboot" /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void proc_reboot_info(struct seq_file *seq, void *priv __maybe_unused){ char Buffer[512]; int idx = get_fieldindex_by_status(avm_reboot_status); if (idx < 0) { return; } seq_printf(seq, "Reboot Status is: %s%s%s%s%s\n", reboot_info[idx].printouttext, reboot_info[idx].shortprintouttext ? "(" : "", reboot_info[idx].shortprintouttext ? reboot_info[idx].shortprintouttext : "", reboot_info[idx].shortprintouttext ? ") " : " ", avm_rebootcounter_string(Buffer, sizeof(Buffer))); } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static __init int proc_reboot_status(void) { rebootprocdir = proc_mkdir(PROC_REBOOTDIR, NULL); if (rebootprocdir == NULL) { return 0; } return add_simple_proc_file( "avm/reboot/info", NULL, proc_reboot_info, NULL); } late_initcall(proc_reboot_status);