📄 cpwatchdog.c
字号:
return -ESPIPE; if (count) { wd_pingtimer(pTimer); return 1; } return 0;}static ssize_t wd_read(struct file * file, char * buffer, size_t count, loff_t *ppos){#ifdef WD_DEBUG wd_dumpregs(); return(0);#else return(-EINVAL);#endif /* ifdef WD_DEBUG */}static void wd_interrupt(int irq, void *dev_id, struct pt_regs *regs){ /* Only WD0 will interrupt-- others are NMI and we won't * see them here.... */ spin_lock_irq(&wd_dev.lock); if((unsigned long)wd_dev.regs == (unsigned long)dev_id) { wd_stoptimer(&wd_dev.watchdog[WD0_ID]); wd_dev.watchdog[WD0_ID].runstatus |= WD_STAT_SVCD; } spin_unlock_irq(&wd_dev.lock); return;}static struct file_operations wd_fops = { owner: THIS_MODULE, ioctl: wd_ioctl, open: wd_open, write: wd_write, read: wd_read, release: wd_release,};static struct miscdevice wd0_miscdev = { WD0_MINOR, WD0_DEVNAME, &wd_fops };static struct miscdevice wd1_miscdev = { WD1_MINOR, WD1_DEVNAME, &wd_fops };static struct miscdevice wd2_miscdev = { WD2_MINOR, WD2_DEVNAME, &wd_fops };#ifdef WD_DEBUGstatic void wd_dumpregs(void){ /* Reading from downcounters initiates watchdog countdown-- * Example is included below for illustration purposes. */ int i; printk("%s: dumping register values\n", WD_OBPNAME); for(i = WD0_ID; i < WD_NUMDEVS; ++i) { /* printk("\t%s%i: dcntr at 0x%lx: 0x%x\n", * WD_OBPNAME, * i, * (unsigned long)(&wd_dev.watchdog[i].regs->dcntr), * readw(&wd_dev.watchdog[i].regs->dcntr)); */ printk("\t%s%i: limit at 0x%lx: 0x%x\n", WD_OBPNAME, i, (unsigned long)(&wd_dev.watchdog[i].regs->limit), readw(&wd_dev.watchdog[i].regs->limit)); printk("\t%s%i: status at 0x%lx: 0x%x\n", WD_OBPNAME, i, (unsigned long)(&wd_dev.watchdog[i].regs->status), readb(&wd_dev.watchdog[i].regs->status)); printk("\t%s%i: driver status: 0x%x\n", WD_OBPNAME, i, wd_getstatus(&wd_dev.watchdog[i])); } printk("\tintr_mask at 0x%lx: 0x%x\n", (unsigned long)(&wd_dev.regs->pld_regs.intr_mask), readb(&wd_dev.regs->pld_regs.intr_mask)); printk("\tpld_status at 0x%lx: 0x%x\n", (unsigned long)(&wd_dev.regs->pld_regs.status), readb(&wd_dev.regs->pld_regs.status));}#endif/* Enable or disable watchdog interrupts * Because of the CP1400 defect this should only be * called during initialzation or by wd_[start|stop]timer() * * pTimer - pointer to timer device, or NULL to indicate all timers * enable - non-zero to enable interrupts, zero to disable */static void wd_toggleintr(struct wd_timer* pTimer, int enable){ unsigned char curregs = wd_readb(&wd_dev.regs->pld_regs.intr_mask); unsigned char setregs = (NULL == pTimer) ? (WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) : (pTimer->intr_mask); (WD_INTR_ON == enable) ? (curregs &= ~setregs): (curregs |= setregs); wd_writeb(curregs, &wd_dev.regs->pld_regs.intr_mask); return;}/* Reset countdown timer with 'limit' value and continue countdown. * This will not start a stopped timer. * * pTimer - pointer to timer device */static void wd_pingtimer(struct wd_timer* pTimer){ if(wd_readb(&pTimer->regs->status) & WD_S_RUNNING) { wd_readb(&pTimer->regs->dcntr); }}/* Stop a running watchdog timer-- the timer actually keeps * running, but the interrupt is masked so that no action is * taken upon expiration. * * pTimer - pointer to timer device */static void wd_stoptimer(struct wd_timer* pTimer){ if(wd_readb(&pTimer->regs->status) & WD_S_RUNNING) { wd_toggleintr(pTimer, WD_INTR_OFF); if(wd_dev.isbaddoggie) { pTimer->runstatus |= WD_STAT_BSTOP; wd_brokentimer((unsigned long)&wd_dev); } }}/* Start a watchdog timer with the specified limit value * If the watchdog is running, it will be restarted with * the provided limit value. * * This function will enable interrupts on the specified * watchdog. * * pTimer - pointer to timer device * limit - limit (countdown) value in 1/10th seconds */static void wd_starttimer(struct wd_timer* pTimer){ if(wd_dev.isbaddoggie) { pTimer->runstatus &= ~WD_STAT_BSTOP; } pTimer->runstatus &= ~WD_STAT_SVCD; wd_writew(pTimer->timeout, &pTimer->regs->limit); wd_toggleintr(pTimer, WD_INTR_ON);}/* Restarts timer with maximum limit value and * does not unset 'brokenstop' value. */static void wd_resetbrokentimer(struct wd_timer* pTimer){ wd_toggleintr(pTimer, WD_INTR_ON); wd_writew(WD_BLIMIT, &pTimer->regs->limit);}/* Timer device initialization helper. * Returns 0 on success, other on failure */static int wd_inittimer(int whichdog){ struct miscdevice *whichmisc; volatile struct wd_timer_regblk *whichregs; char whichident[8]; int whichmask; __u16 whichlimit; switch(whichdog) { case WD0_ID: whichmisc = &wd0_miscdev; strcpy(whichident, "RIC"); whichregs = &wd_dev.regs->wd0_regs; whichmask = WD0_INTR_MASK; whichlimit= (0 == wd0_timeout) ? (wd_dev.opt_timeout): (wd0_timeout); break; case WD1_ID: whichmisc = &wd1_miscdev; strcpy(whichident, "XIR"); whichregs = &wd_dev.regs->wd1_regs; whichmask = WD1_INTR_MASK; whichlimit= (0 == wd1_timeout) ? (wd_dev.opt_timeout): (wd1_timeout); break; case WD2_ID: whichmisc = &wd2_miscdev; strcpy(whichident, "POR"); whichregs = &wd_dev.regs->wd2_regs; whichmask = WD2_INTR_MASK; whichlimit= (0 == wd2_timeout) ? (wd_dev.opt_timeout): (wd2_timeout); break; default: printk("%s: %s: invalid watchdog id: %i\n", WD_OBPNAME, __FUNCTION__, whichdog); return(1); } if(0 != misc_register(whichmisc)) { return(1); } wd_dev.watchdog[whichdog].regs = whichregs; wd_dev.watchdog[whichdog].timeout = whichlimit; wd_dev.watchdog[whichdog].intr_mask = whichmask; wd_dev.watchdog[whichdog].runstatus &= ~WD_STAT_BSTOP; wd_dev.watchdog[whichdog].runstatus |= WD_STAT_INIT; printk("%s%i: %s hardware watchdog [%01i.%i sec] %s\n", WD_OBPNAME, whichdog, whichident, wd_dev.watchdog[whichdog].timeout / 10, wd_dev.watchdog[whichdog].timeout % 10, (0 != wd_dev.opt_enable) ? "in ENABLED mode" : ""); return(0);}/* Timer method called to reset stopped watchdogs-- * because of the PLD bug on CP1400, we cannot mask * interrupts within the PLD so me must continually * reset the timers ad infinitum. */static void wd_brokentimer(unsigned long data){ struct wd_device* pDev = (struct wd_device*)data; int id, tripped = 0; /* kill a running timer instance, in case we * were called directly instead of by kernel timer */ if(timer_pending(&wd_timer)) { del_timer(&wd_timer); } for(id = WD0_ID; id < WD_NUMDEVS; ++id) { if(pDev->watchdog[id].runstatus & WD_STAT_BSTOP) { ++tripped; wd_resetbrokentimer(&pDev->watchdog[id]); } } if(tripped) { /* there is at least one timer brokenstopped-- reschedule */ wd_timer.expires = WD_BTIMEOUT; add_timer(&wd_timer); }}static int wd_getstatus(struct wd_timer* pTimer){ unsigned char stat = wd_readb(&pTimer->regs->status); unsigned char intr = wd_readb(&wd_dev.regs->pld_regs.intr_mask); unsigned char ret = WD_STOPPED; /* determine STOPPED */ if(0 == stat ) { return(ret); } /* determine EXPIRED vs FREERUN vs RUNNING */ else if(WD_S_EXPIRED & stat) { ret = WD_EXPIRED; } else if(WD_S_RUNNING & stat) { if(intr & pTimer->intr_mask) { ret = WD_FREERUN; } else { /* Fudge WD_EXPIRED status for defective CP1400-- * IF timer is running * AND brokenstop is set * AND an interrupt has been serviced * we are WD_EXPIRED. * * IF timer is running * AND brokenstop is set * AND no interrupt has been serviced * we are WD_FREERUN. */ if(wd_dev.isbaddoggie && (pTimer->runstatus & WD_STAT_BSTOP)) { if(pTimer->runstatus & WD_STAT_SVCD) { ret = WD_EXPIRED; } else { /* we could as well pretend we are expired */ ret = WD_FREERUN; } } else { ret = WD_RUNNING; } } } /* determine SERVICED */ if(pTimer->runstatus & WD_STAT_SVCD) { ret |= WD_SERVICED; } return(ret);}static int __init wd_init(void){ int id; struct linux_ebus *ebus = NULL; struct linux_ebus_device *edev = NULL; for_each_ebus(ebus) { for_each_ebusdev(edev, ebus) { if (!strcmp(edev->prom_name, WD_OBPNAME)) goto ebus_done; } }ebus_done: if(!edev) { printk("%s: unable to locate device\n", WD_OBPNAME); return -ENODEV; } wd_dev.regs = ioremap(edev->resource[0].start, sizeof(struct wd_regblk)); if(NULL == wd_dev.regs) { printk("%s: unable to map registers\n", WD_OBPNAME); return(-ENODEV); } /* initialize device structure from OBP parameters */ wd_dev.irq = edev->irqs[0]; wd_dev.opt_enable = wd_opt_enable(); wd_dev.opt_reboot = wd_opt_reboot(); wd_dev.opt_timeout = wd_opt_timeout(); wd_dev.isbaddoggie = wd_isbroken(); /* disable all interrupts unless watchdog-enabled? == true */ if(! wd_dev.opt_enable) { wd_toggleintr(NULL, WD_INTR_OFF); } /* register miscellaneous devices */ for(id = WD0_ID; id < WD_NUMDEVS; ++id) { if(0 != wd_inittimer(id)) { printk("%s%i: unable to initialize\n", WD_OBPNAME, id); } } /* warn about possible defective PLD */ if(wd_dev.isbaddoggie) { init_timer(&wd_timer); wd_timer.function = wd_brokentimer; wd_timer.data = (unsigned long)&wd_dev; wd_timer.expires = WD_BTIMEOUT; printk("%s: PLD defect workaround enabled for model %s\n", WD_OBPNAME, WD_BADMODEL); } return(0);}static void __exit wd_cleanup(void){ int id; /* if 'watchdog-enable?' == TRUE, timers are not stopped * when module is unloaded. All brokenstopped timers will * also now eventually trip. */ for(id = WD0_ID; id < WD_NUMDEVS; ++id) { if(WD_S_RUNNING == wd_readb(&wd_dev.watchdog[id].regs->status)) { if(wd_dev.opt_enable) { printk(KERN_WARNING "%s%i: timer not stopped at release\n", WD_OBPNAME, id); } else { wd_stoptimer(&wd_dev.watchdog[id]); if(wd_dev.watchdog[id].runstatus & WD_STAT_BSTOP) { wd_resetbrokentimer(&wd_dev.watchdog[id]); printk(KERN_WARNING "%s%i: defect workaround disabled at release, "\ "timer expires in ~%01i sec\n", WD_OBPNAME, id, wd_readw(&wd_dev.watchdog[id].regs->limit) / 10); } } } } if(wd_dev.isbaddoggie && timer_pending(&wd_timer)) { del_timer(&wd_timer); } if(0 != (wd_dev.watchdog[WD0_ID].runstatus & WD_STAT_INIT)) { misc_deregister(&wd0_miscdev); } if(0 != (wd_dev.watchdog[WD1_ID].runstatus & WD_STAT_INIT)) { misc_deregister(&wd1_miscdev); } if(0 != (wd_dev.watchdog[WD2_ID].runstatus & WD_STAT_INIT)) { misc_deregister(&wd2_miscdev); } if(0 != wd_dev.initialized) { free_irq(wd_dev.irq, (void *)wd_dev.regs); } iounmap(wd_dev.regs);}module_init(wd_init);module_exit(wd_cleanup);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -