/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2006 AVM GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that 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 \*------------------------------------------------------------------------------------------*/ #if defined(CONFIG_AVM_WATCHDOG) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (LINUX_VERSION_CODE > KERNEL_VERSION(4,4,0)) #include #else #include #endif #ifdef CONFIG_AVM_FASTIRQ #ifdef CONFIG_AVM_FASTIRQ_ARCH_ARM_COMMON #include #include #include #include #include #include #else #include #include #include #include #include #include #endif #endif #include "avm_sammel.h" /* IO_ADDR */ #include #define SCM_CMD_SET_REGSAVE 0x2 struct qcom_wdt { struct clk *clk; unsigned long rate; unsigned long timeout; unsigned long initialized; struct notifier_block restart_nb; void __iomem *base; void __iomem *wdt_reset; void __iomem *wdt_enable; void __iomem *wdt_bark_time; void __iomem *wdt_bite_time; unsigned int act_wdt_cpu; atomic_t wdt_busy; }; static struct timer_list WDTimer[NR_CPUS]; static struct qcom_wdt *wdt; static atomic_t wdt_active; atomic_t wdt_active_int; static spinlock_t wd_lock; static atomic_t wd_sync; static atomic_t wd_count; static atomic_t wd_handling; #define RETRY_COUNT 1 unsigned int wd_retry_count = RETRY_COUNT; static RAW_NOTIFIER_HEAD(nmi_chain); /**--------------------------------------------------------------------------------**\ * der (pseudo-)nmi-handler ueber fastirq \**--------------------------------------------------------------------------------**/ int register_nmi_notifier(struct notifier_block *nb) { return raw_notifier_chain_register(&nmi_chain, nb); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static long qcom_wdt_configure_bark_dump(void *arg __attribute__((unused))) { long ret = -ENOMEM; struct { unsigned addr; int len; } cmd_buf; /* Area for context dump in secure mode */ void *scm_regsave = (void *)__get_free_page(GFP_KERNEL); if (scm_regsave) { cmd_buf.addr = virt_to_phys(scm_regsave); cmd_buf.len = PAGE_SIZE; #if (LINUX_VERSION_CODE > KERNEL_VERSION(4,4,0)) ret = qcom_scm_regsave(SCM_SVC_UTIL,SCM_CMD_SET_REGSAVE, scm_regsave, PAGE_SIZE); #else ret = scm_call(SCM_SVC_UTIL, SCM_CMD_SET_REGSAVE, &cmd_buf, sizeof(cmd_buf), NULL, 0); #endif } if (ret) pr_err("Setting register save address failed.\n" "Registers won't be dumped on a dog bite\n"); return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int qcom_wdt_start(struct qcom_wdt *wdt) { writel_relaxed(0, wdt->wdt_enable); dmb(); writel_relaxed(1, wdt->wdt_reset); dmb(); writel_relaxed(wdt->timeout * wdt->rate, wdt->wdt_bark_time); writel_relaxed((wdt->timeout * 2) * wdt->rate, wdt->wdt_bite_time); dmb(); writel_relaxed(1, wdt->wdt_enable); dmb(); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ #if 0 static int qcom_wdt_start_nonsecure(struct qcom_wdt *wdt) { writel(0, wdt->wdt_enable); writel(1, wdt->wdt_reset); writel( wdt->rate * wdt->timeout, wdt->wdt_bite_time); writel(1, wdt->wdt_enable); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void dump_wdt(void) { pr_err("{%s} enable 0x%x reset 0x%x bark_time 0x%x bite_time 0x%x", __func__, readl(wdt->wdt_enable), readl(wdt->wdt_reset), readl(wdt->wdt_bark_time), readl(wdt->wdt_bite_time)); } #endif /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int qcom_wdt_restart(struct notifier_block *nb __attribute__((unused)), unsigned long action __attribute__((unused)), void *data __attribute__((unused))) { pr_err("[%s] ...\n", __func__); avm_secure_wdt_config(1, 10000, 5000); while(1){ } return NOTIFY_DONE; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int ar7wdt_hw_is_wdt_running(void) { return atomic_read(&wdt_active); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar7wdt_hw_deinit(void) { /*--- pr_debug( "[qcom:watchdog] stop ...\n"); ---*/ writel(0, wdt->wdt_enable); ar7wdt_hw_secure_wdt_disable(); atomic_set(&wdt_active, 0); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar7wdt_hw_reboot(void) { panic("ar7wdt_hw_reboot: watchdog expired\n"); } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void wd_timer_function(unsigned long context __maybe_unused) { /*--- printk(KERN_INFO"%s\n", __func__); ---*/ writel(1, wdt->wdt_reset); atomic_set(&wd_count, wd_retry_count); atomic_set(&wdt->wdt_busy, 0); } /**------------------------------------------------------------------------------------------*\ * \brief: jede CPU per roundrobin triggern \**------------------------------------------------------------------------------------------*/ void ar7wdt_hw_trigger(void) { unsigned int cpu, this_cpu, i; if(!ar7wdt_hw_is_wdt_running()) { return; } if(atomic_read(&wdt->wdt_busy)) { return; } this_cpu = get_cpu(); /*--- printk(KERN_INFO"[%s] trigger cpu=%u\n", __func__, wdt->act_wdt_cpu); ---*/ cpu = wdt->act_wdt_cpu; for(i = 0; i < NR_CPUS; i++, cpu++) { if(cpu >= NR_CPUS) cpu = 0; if(!cpu_online(cpu)) { continue; } atomic_set(&wdt->wdt_busy, cpu + 1); if(cpu == this_cpu) { wd_timer_function(0); break; } WDTimer[cpu].expires = jiffies + 1; del_timer(&WDTimer[cpu]); add_timer_on(&WDTimer[cpu], cpu); break; } put_cpu(); if(++cpu >= NR_CPUS) cpu = 0; wdt->act_wdt_cpu = cpu; } EXPORT_SYMBOL(ar7wdt_hw_trigger); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ int in_avm_wdt_handling(void) { return( atomic_read(&wd_handling) ); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static irqreturn_t wdt_bark_isr(int irq __attribute__((unused)), void *arg __attribute__((unused))) { struct pt_regs regs, *pregs; unsigned int limit; unsigned int tstart; unsigned int cpu = raw_smp_processor_id(); if(firq_spin_trylock(&wd_lock)) // Master Handling, wd_lock war noch frei { int forced_panic = 0; /* Watchdog reset */ writel_relaxed(1, wdt->wdt_reset); dmb(); printk( KERN_ERR "FIQ watchdog handling, master: CPU#%d, retry counter=%d\n", cpu, atomic_read(&wd_count) ); /* alle Slaves einsammeln */ limit = avm_get_cyclefreq() * 3; /* sec */ tstart = avm_get_cycles(); while(atomic_read(&wd_sync) != (NR_CPUS - 1)) { if((avm_get_cycles() - tstart) > limit){ printk( KERN_ERR "It seems, slave CPU was caught badly while keeping WATCHDOG FIQ masked ...\n"); printk( KERN_ERR "... or didn't get the watchdog interrupt signal ...\n"); printk( KERN_ERR "Sync trigger should come through though, if not, FIQs are erroneously switched off there ...\n"); forced_panic = 1; break; } } /* Jetzt Interrupt-Controller behandeln: spaetes Interrupt-Ack */ (void)get_ICCIAR(IS_ATOMIC); /* Interrupt Pending löschen */ set_ICDICPR(AVM_IRQ_WD, 1, IS_ATOMIC); /* Interrupt beenden */ set_ICCEOIR(AVM_IRQ_WD, IS_ATOMIC); /*Master-Slave Lock zuruecksetzen*/ firq_spin_unlock(&wd_lock); if((forced_panic) || (atomic_read(&wd_count) == 0)) { /* Disable Wdt */ avm_gic_fiq_disable(AVM_IRQ_WD, 0); atomic_set(&wd_handling, 1); /* Wir wollen uns nicht nur auf den Restart-Notifier am Ende der 'die'-Routine verlassen, daher hier schon das Scharfschalten des Secure-WDT Bite (Reset) */ avm_secure_wdt_config(1, 40000, 20000); if(firq_is_linux_context()) { pregs = get_irq_regs(); } else { avm_tick_jiffies_update(); /*--- auch im FASTIRQ-Fall jiffies korrigieren ---*/ pregs = get_fiq_regs(); } prepare_register_for_trap(®s, &pregs); avm_set_reset_status(RS_NMIWATCHDOG); avm_stack_check(NULL); raw_notifier_call_chain(&nmi_chain, 0, pregs); flush_cache_all(); die("HardwareWatchDog - NMI taken" , pregs, 0); // Zur Sicherheit, wir kehren nicht zurueck, Reset wird durch WD gezogen ... while(1) { } } /* Retry-Count erniedrigen */ atomic_dec(&wd_count); /* Sync löschen, dmit Slaves freigeben */ atomic_set(&wd_sync, 0); return IRQ_HANDLED; } else { // Slave Handling, wd_lock war schon von einem Master belegt int wd_counter = 0; printk( KERN_ERR "FIQ watchdog handling, slave CPU#%d caught!\n", cpu ); wd_counter = atomic_read(&wd_count); /* Sync ... */ atomic_inc(&wd_sync); if(wd_counter == 0) { unsigned long flags = 0; unsigned long restore_PMR = 0; printk( KERN_ERR "FIQ watchdog handling, slave: CPU#%d, waiting for backtrace trigger ...\n", cpu ); /* disable target routing of Wdt */ set_ICDIPTR(AVM_IRQ_WD, get_ICDIPTR(AVM_IRQ_WD, IS_ATOMIC) & ~(1 << cpu), IS_ATOMIC); /* Mask all interrupts except Monitor */ avm_mask_all_fiqs_down(FIQ_PRIO_WATCHDOG, &restore_PMR, flags); /* open for Monitor FIQ */ local_fiq_enable(); /* Festhalten, IPI-FIRQ-Trigger wird hier reinschlagen ... */ while(1) { } } /* Festhalten, Slaves werden vom Master koordiniert freigegeben */ while(atomic_read(&wd_sync) != 0) { } return IRQ_HANDLED; } } static irqreturn_t wdt_bite_isr(int irq __attribute__((unused)), void *arg __attribute__((unused))) { unsigned long flags = 0; unsigned long restore_PMR = 0; unsigned int cpu = raw_smp_processor_id(); /* disable target routing of Wdt */ set_ICDIPTR(AVM_IRQ_WD, get_ICDIPTR(AVM_IRQ_WD_BITE, IS_ATOMIC) & ~(1 << cpu), IS_ATOMIC); /* Mask all interrupts except Monitor */ avm_mask_all_fiqs_down(FIQ_PRIO_WATCHDOG, &restore_PMR, flags); pr_info("Non-secure watchdog bite triggered. Waiting for secure watchdog bite for hw reset\n"); return IRQ_HANDLED; } static irqreturn_t wdt_secure_bark_isr(int irq, void *arg __attribute__((unused))) { if(avm_is_avm_tz()) { avm_secure_wdt_pet(); pr_info("[%s] trigger secure wdt!\n", __func__); } return IRQ_HANDLED; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void register_wdt_bark_irq(int irq, struct qcom_wdt *wdt) { uint32_t tz_version, modified; int ret, cpu; #if defined(CONFIG_AVM_FASTIRQ) if( ! avm_get_tz_version(&tz_version, &modified)){ ret = avm_request_fiq_on((1 << NR_CPUS) - 1, irq, wdt_bark_isr, FIQ_CPUMASK, "watchdog_bark", wdt); if( ! ret) { uint32_t hwirq = irq; struct irq_desc *irq_desc = irq_to_desc(irq); if (irq_desc) hwirq = irq_desc->irq_data.hwirq; if (tz_version <= 10148) { // We need to alter the WDOG priority to the highest possible, as the TZ registers // all interrupts with it and there is a high chance that these interrupts prevent the wdog interrupt. // This is neccessary till we decide for a TZ update. avm_gic_fiq_setup(hwirq, (1<< NR_CPUS) - 1, FIQ_PRIO_MONITOR, 0x03, 0); pr_info("[%s] watchdog as fastirq(%u) on all cpus registered with highest prio\n", __func__, hwirq); } else { // Set watchdog to its default priority avm_gic_fiq_setup(hwirq, (1<< NR_CPUS) - 1, FIQ_PRIO_WATCHDOG, 0x03, 0); pr_info("[%s] watchdog as fastirq(%u) on all cpus registered with its default prio\n", __func__, hwirq); } ret = avm_request_fiq_on((1 << NR_CPUS) - 1, AVM_IRQ_WD_BITE, wdt_bite_isr, FIQ_CPUMASK | FIQ_HWIRQ, "watchdog_bite", wdt); if( ! ret) { // Setup of non-secure bite watchdog avm_gic_fiq_setup(AVM_IRQ_WD_BITE, (1<< NR_CPUS) - 1, FIQ_PRIO_MONITOR, 0x03, 0); pr_info("[%s] watchdog as fastirq(%u) on all cpus registered with highest prio\n", __func__, AVM_IRQ_WD_BITE); return; } else { for(cpu = 0; cpu < NR_CPUS; cpu++) { (void)avm_free_fiq_on(cpu, irq, wdt); } } } } #endif ret = request_irq(irq, wdt_bark_isr, IRQF_TRIGGER_HIGH, "watchdog_bark", wdt); if(!ret) { pr_info("[%s] watchdog as irq(%u) registered\n", __func__, irq); return; } pr_err("[%s] ERROR request_irq(irq(%d)) ret:%d\n", __func__, irq, ret); } void register_secure_wdt_bark_irq(int irq, struct qcom_wdt *wdt) { int ret = 0; #if defined(CONFIG_AVM_FASTIRQ) ret = avm_request_fiq_on((1 << NR_CPUS) - 1, irq, wdt_secure_bark_isr, FIQ_CPUMASK | FIQ_HWIRQ, "secure-watchdog", wdt); if(!ret) { uint32_t hwirq = irq; struct irq_desc *irq_desc = irq_to_desc(irq); if (irq_desc) hwirq = irq_desc->irq_data.hwirq; // Setup of secure bark watchdog avm_gic_fiq_setup(hwirq, (1<< NR_CPUS) - 1, FIQ_PRIO_MONITOR, 0x03, 0); pr_info("[%s] secure watchdog as fastirq(%u) on all cpus registered with highest prio\n", __func__, hwirq); return; } #endif } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ void ar7wdt_hw_secure_wdt_disable(void) { #if defined(CONFIG_AVM_FASTIRQ) /* Auch Secure Config Watchdog triggern */ /* Disable Secure Watchdog */ if(avm_is_avm_tz()) { printk(KERN_ERR"[%s] disable secure wdt!\n", __func__); avm_secure_wdt_config(0, 20000, 40000); } #endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/ } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar7wdt_hw_init(void) { struct device_node *np; int ret, cpu; uint32_t val; struct resource irqres; pr_info( "[qcom:watchdog] %s...\n", __func__); if( !wdt->initialized ) { np = of_find_compatible_node(NULL, NULL, "qcom,kpss-wdt-ipq40xx"); if ( ! np) { pr_err("[qcom:%s] ERROR dt-entry\n", __func__); goto err_out; } if ( ! wdt->base) { pr_err("[qcom:%s] ERROR base\n", __func__); goto err_out; } if (of_property_read_u32(np, "wdt_res", &val)) { pr_err("[qcom:%s] ERROR wdt_res not set in dt\n", __func__); goto err_out; } else { wdt->wdt_reset = wdt->base + val; } if (of_property_read_u32(np, "wdt_en", &val)) { pr_err("[qcom:%s] ERROR wdt_en not set in dt\n", __func__); goto err_out; } else { wdt->wdt_enable = wdt->base + val; } if (of_property_read_u32(np, "wdt_bark_time", &val)) { pr_err("[qcom:%s] ERROR wdt_bark_time not set in dt\n", __func__); goto err_out; } else { wdt->wdt_bark_time = wdt->base + val; } if (of_property_read_u32(np, "wdt_bite_time", &val)) { pr_err("[qcom:%s] ERROR wdt_bite_time not set in dt\n", __func__); goto err_out; } else { wdt->wdt_bite_time = wdt->base + val; } wdt->clk = of_clk_get_by_name(np, NULL); if (IS_ERR(wdt->clk)) { pr_err("[qcom:%s] ERROR failed to get input clock\n", __func__); goto err_out; } ret = of_irq_to_resource_table(np, &irqres, 1); if (ret) { register_wdt_bark_irq(irqres.start, wdt); } if (of_property_read_u32(np, "wdt_secure_irq", &val)) { pr_info("[qcom:%s] ERROR wdt_secure_irq not set in dt\n", __func__); } else { register_secure_wdt_bark_irq(val, wdt); } ret = clk_prepare_enable(wdt->clk); if (ret) { pr_err("[qcom:%s] ERROR failed to setup clock\n", __func__); goto err_out; } /* * We use the clock rate to calculate the max timeout, so ensure it's * not zero to avoid a divide-by-zero exception. * * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such * that it would bite before a second elapses it's usefulness is * limited. Bail if this is the case. */ wdt->rate = clk_get_rate(wdt->clk); if (wdt->rate == 0 || wdt->rate > 0x10000000U) { pr_err("[qcom:%s] ERROR invalid clock rate\n", __func__); goto err_clk_unprepare; } /* * If 'timeout-sec' unspecified in devicetree, assume a 30 second * default, unless the max timeout is less than 30 seconds, then use * the max instead. */ if (of_property_read_u32(np, "timeout-sec", &val)) wdt->timeout = val; else wdt->timeout = 10; /* * WDT restart notifier has priority 0 (use as a last resort) */ wdt->restart_nb.notifier_call = qcom_wdt_restart; ret = register_restart_handler(&wdt->restart_nb); if (ret) pr_err("[qcom:%s] ERROR failed to setup restart handler\n", __func__); wdt->initialized = 1; } firq_spin_lock_init(&wd_lock); atomic_set(&wd_sync, 0); atomic_set(&wd_count, wd_retry_count); atomic_set(&wd_handling, 0); /*--- enable watchdog ---*/ work_on_cpu(0, qcom_wdt_configure_bark_dump, NULL); qcom_wdt_start(wdt); pr_info("[qcom:%s] Wdt bark time: %d, bite time: %d\n", __func__, readl_relaxed(wdt->wdt_bark_time), readl_relaxed(wdt->wdt_bite_time)); atomic_set(&wdt_active, 1); atomic_set(&wdt_active_int, 1); #if defined(CONFIG_AVM_FASTIRQ) /* Reconfigure Secure Watchdog */ if(avm_is_avm_tz()) { avm_secure_wdt_config(1, 20000, 40000); pr_info("[qcom:%s] Secure watchdog: bark: %ds, bite time: %ds\n", __func__, 20, 40); } #endif for(cpu = 0; cpu < NR_CPUS; cpu++) { init_timer(&WDTimer[cpu]); WDTimer[cpu].data = (unsigned long)&WDTimer[cpu]; WDTimer[cpu].function = wd_timer_function; } return; err_clk_unprepare: clk_disable_unprepare(wdt->clk); err_out: { struct qcom_wdt *tmp = wdt; wdt = NULL; kfree(tmp); } return; } /* MODULE INFRASTRUCTURE */ static const struct of_device_id wdt_ipq40xx_match_table[] = { { .compatible = "qcom,kpss-wdt-ipq40xx" }, { } }; MODULE_DEVICE_TABLE(of, wdt_ipq40xx_match_table); static int wdt_ipq40xx_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct of_device_id *id; struct resource *mem; pr_info( "[qcom:watchdog] %s ...\n", __func__); if ( ! wdt) { wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if ( ! wdt) { pr_err("[qcom:%s] ERROR no memory\n", __func__); return -ENOMEM; } } id = of_match_device(wdt_ipq40xx_match_table, dev); if (!id) goto err_out; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); #ifdef CONFIG_AVM_FASTIRQ wdt->base = IO_ADDR(mem->start); #else wdt->base = devm_ioremap_resource(dev, mem); #endif if (IS_ERR(wdt->base)) goto err_out; return 0; err_out: { struct qcom_wdt *tmp = wdt; wdt = NULL; kfree(tmp); } return -ENODEV; } static int wdt_ipq40xx_remove(struct platform_device *pdev) { struct qcom_wdt *wdt = platform_get_drvdata(pdev); unregister_restart_handler(&wdt->restart_nb); clk_disable_unprepare(wdt->clk); { struct qcom_wdt *tmp = wdt; wdt = NULL; kfree(tmp); } return 0; } static struct platform_driver wdt_ipq40xx_driver = { .probe = wdt_ipq40xx_probe, .remove = wdt_ipq40xx_remove, .driver = { .name = "qcom,kpss-wdt-ipq40xx", .owner = THIS_MODULE, .of_match_table = wdt_ipq40xx_match_table, }, }; static int __init wdt_ipq40xx_init(void) { return platform_driver_register(&wdt_ipq40xx_driver); } core_initcall(wdt_ipq40xx_init); static void __exit wdt_ipq40xx_exit(void) { platform_driver_unregister(&wdt_ipq40xx_driver); } module_exit(wdt_ipq40xx_exit); #endif