📄 cpwatchdog.c
字号:
/* cpwatchdog.c - driver implementation for hardware watchdog * timers found on Sun Microsystems CP1400 and CP1500 boards. * * This device supports both the generic Linux watchdog * interface and Solaris-compatible ioctls as best it is * able. * * NOTE: CP1400 systems appear to have a defective intr_mask * register on the PLD, preventing the disabling of * timer interrupts. We use a timer to periodically * reset 'stopped' watchdogs on affected platforms. * * TODO: DevFS support (/dev/watchdogs/0 ... /dev/watchdogs/2) * * Copyright (c) 2000 Eric Brower (ebrower@usa.net) */#include <linux/kernel.h>#include <linux/module.h>#include <linux/version.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/major.h>#include <linux/init.h>#include <linux/miscdevice.h>#include <linux/sched.h>#include <linux/interrupt.h>#include <linux/ioport.h>#include <linux/timer.h>#include <asm/irq.h>#include <asm/ebus.h>#include <asm/oplib.h>#include <asm/uaccess.h>#include <asm/watchdog.h>#define WD_OBPNAME "watchdog"#define WD_BADMODEL "SUNW,501-5336"#define WD_BTIMEOUT (jiffies + (HZ * 1000))#define WD_BLIMIT 0xFFFF#define WD0_DEVNAME "watchdog0"#define WD1_DEVNAME "watchdog1"#define WD2_DEVNAME "watchdog2"#define WD0_MINOR 212#define WD1_MINOR 213 #define WD2_MINOR 214 /* Internal driver definitions */#define WD0_ID 0 /* Watchdog0 */#define WD1_ID 1 /* Watchdog1 */#define WD2_ID 2 /* Watchdog2 */#define WD_NUMDEVS 3 /* Device contains 3 timers */#define WD_INTR_OFF 0 /* Interrupt disable value */#define WD_INTR_ON 1 /* Interrupt enable value */#define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */#define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */#define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred *//* Register value definitions */#define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */#define WD1_INTR_MASK 0x02#define WD2_INTR_MASK 0x04#define WD_S_RUNNING 0x01 /* Watchdog device status running */#define WD_S_EXPIRED 0x02 /* Watchdog device status expired *//* Sun uses Altera PLD EPF8820ATC144-4 * providing three hardware watchdogs: * * 1) RIC - sends an interrupt when triggered * 2) XIR - asserts XIR_B_RESET when triggered, resets CPU * 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board * *** Timer register block definition (struct wd_timer_regblk) * * dcntr and limit registers (halfword access): * ------------------- * | 15 | ...| 1 | 0 | * ------------------- * |- counter val -| * ------------------- * dcntr - Current 16-bit downcounter value. * When downcounter reaches '0' watchdog expires. * Reading this register resets downcounter with 'limit' value. * limit - 16-bit countdown value in 1/10th second increments. * Writing this register begins countdown with input value. * Reading from this register does not affect counter. * NOTES: After watchdog reset, dcntr and limit contain '1' * * status register (byte access): * --------------------------- * | 7 | ... | 2 | 1 | 0 | * --------------+------------ * |- UNUSED -| EXP | RUN | * --------------------------- * status- Bit 0 - Watchdog is running * Bit 1 - Watchdog has expired * *** PLD register block definition (struct wd_pld_regblk) * * intr_mask register (byte access): * --------------------------------- * | 7 | ... | 3 | 2 | 1 | 0 | * +-------------+------------------ * |- UNUSED -| WD3 | WD2 | WD1 | * --------------------------------- * WD3 - 1 == Interrupt disabled for watchdog 3 * WD2 - 1 == Interrupt disabled for watchdog 2 * WD1 - 1 == Interrupt disabled for watchdog 1 * * pld_status register (byte access): * UNKNOWN, MAGICAL MYSTERY REGISTER * */struct wd_timer_regblk { volatile __u16 dcntr; /* down counter - hw */ volatile __u16 dcntr_pad; volatile __u16 limit; /* limit register - hw */ volatile __u16 limit_pad; volatile __u8 status; /* status register - b */ volatile __u8 status_pad; volatile __u16 status_pad2; volatile __u32 pad32; /* yet more padding */};struct wd_pld_regblk { volatile __u8 intr_mask; /* interrupt mask - b */ volatile __u8 intr_mask_pad; volatile __u16 intr_mask_pad2; volatile __u8 status; /* device status - b */ volatile __u8 status_pad; volatile __u16 status_pad2;};struct wd_regblk { volatile struct wd_timer_regblk wd0_regs; volatile struct wd_timer_regblk wd1_regs; volatile struct wd_timer_regblk wd2_regs; volatile struct wd_pld_regblk pld_regs;};/* Individual timer structure */struct wd_timer { __u16 timeout; __u8 intr_mask; unsigned char runstatus; volatile struct wd_timer_regblk* regs;};/* Device structure */struct wd_device { int irq; spinlock_t lock; unsigned char isbaddoggie; /* defective PLD */ unsigned char opt_enable; unsigned char opt_reboot; unsigned short opt_timeout; unsigned char initialized; struct wd_timer watchdog[WD_NUMDEVS]; volatile struct wd_regblk* regs;};static struct wd_device wd_dev = { 0, SPIN_LOCK_UNLOCKED, 0, 0, 0, 0,};static struct timer_list wd_timer;static int wd0_timeout = 0;static int wd1_timeout = 0;static int wd2_timeout = 0;#ifdef MODULEEXPORT_NO_SYMBOLS;MODULE_PARM (wd0_timeout, "i");MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs");MODULE_PARM (wd1_timeout, "i");MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs");MODULE_PARM (wd2_timeout, "i");MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs");MODULE_AUTHOR ("Eric Brower <ebrower@usa.net>");MODULE_DESCRIPTION ("Hardware watchdog driver for Sun Microsystems CP1400/1500");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE ("watchdog");#endif /* ifdef MODULE *//* Forward declarations of internal methods */#ifdef WD_DEBUGstatic void wd_dumpregs(void);#endifstatic void wd_interrupt(int irq, void *dev_id, struct pt_regs *regs);static void wd_toggleintr(struct wd_timer* pTimer, int enable);static void wd_pingtimer(struct wd_timer* pTimer);static void wd_starttimer(struct wd_timer* pTimer);static void wd_resetbrokentimer(struct wd_timer* pTimer);static void wd_stoptimer(struct wd_timer* pTimer);static void wd_brokentimer(unsigned long data);static int wd_getstatus(struct wd_timer* pTimer);/* PLD expects words to be written in LSB format, * so we must flip all words prior to writing them to regs */static inline unsigned short flip_word(unsigned short word){ return ((word & 0xff) << 8) | ((word >> 8) & 0xff);}#define wd_writew(val, addr) (writew(flip_word(val), addr))#define wd_readw(addr) (flip_word(readw(addr)))#define wd_writeb(val, addr) (writeb(val, addr))#define wd_readb(addr) (readb(addr))/* CP1400s seem to have broken PLD implementations-- * the interrupt_mask register cannot be written, so * no timer interrupts can be masked within the PLD. */static inline int wd_isbroken(void){ /* we could test this by read/write/read/restore * on the interrupt mask register only if OBP * 'watchdog-enable?' == FALSE, but it seems * ubiquitous on CP1400s */ char val[32]; prom_getproperty(prom_root_node, "model", val, sizeof(val)); return((!strcmp(val, WD_BADMODEL)) ? 1 : 0);} /* Retrieve watchdog-enable? option from OBP * Returns 0 if false, 1 if true */static inline int wd_opt_enable(void){ int opt_node; opt_node = prom_getchild(prom_root_node); opt_node = prom_searchsiblings(opt_node, "options"); return((-1 == prom_getint(opt_node, "watchdog-enable?")) ? 0 : 1);}/* Retrieve watchdog-reboot? option from OBP * Returns 0 if false, 1 if true */static inline int wd_opt_reboot(void){ int opt_node; opt_node = prom_getchild(prom_root_node); opt_node = prom_searchsiblings(opt_node, "options"); return((-1 == prom_getint(opt_node, "watchdog-reboot?")) ? 0 : 1);}/* Retrieve watchdog-timeout option from OBP * Returns OBP value, or 0 if not located */static inline int wd_opt_timeout(void){ int opt_node; char value[32]; char *p = value; opt_node = prom_getchild(prom_root_node); opt_node = prom_searchsiblings(opt_node, "options"); opt_node = prom_getproperty(opt_node, "watchdog-timeout", value, sizeof(value)); if(-1 != opt_node) { /* atoi implementation */ for(opt_node = 0; /* nop */; p++) { if(*p >= '0' && *p <= '9') { opt_node = (10*opt_node)+(*p-'0'); } else { break; } } } return((-1 == opt_node) ? (0) : (opt_node)); }static int wd_open(struct inode *inode, struct file *f){ switch(MINOR(inode->i_rdev)) { case WD0_MINOR: f->private_data = &wd_dev.watchdog[WD0_ID]; break; case WD1_MINOR: f->private_data = &wd_dev.watchdog[WD1_ID]; break; case WD2_MINOR: f->private_data = &wd_dev.watchdog[WD2_ID]; break; default: return(-ENODEV); } /* Register IRQ on first open of device */ if(0 == wd_dev.initialized) { if (request_irq(wd_dev.irq, &wd_interrupt, SA_SHIRQ, WD_OBPNAME, (void *)wd_dev.regs)) { printk("%s: Cannot register IRQ %s\n", WD_OBPNAME, __irq_itoa(wd_dev.irq)); return(-EBUSY); } wd_dev.initialized = 1; } MOD_INC_USE_COUNT; return(0);}static int wd_release(struct inode *inode, struct file *file){ MOD_DEC_USE_COUNT; return 0;}static int wd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ int setopt = 0; struct wd_timer* pTimer = (struct wd_timer*)file->private_data; struct watchdog_info info = { 0, 0, "Altera EPF8820ATC144-4" }; if(NULL == pTimer) { return(-EINVAL); } switch(cmd) { /* Generic Linux IOCTLs */ case WDIOC_GETSUPPORT: if(copy_to_user((struct watchdog_info *)arg, (struct watchdog_info *)&info, sizeof(struct watchdog_info))) { return(-EFAULT); } break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: if (put_user(0, (int *) arg)) return -EFAULT; break; case WDIOC_KEEPALIVE: wd_pingtimer(pTimer); break; case WDIOC_SETOPTIONS: if(copy_from_user(&setopt, (void*) arg, sizeof(unsigned int))) { return -EFAULT; } if(setopt & WDIOS_DISABLECARD) { if(wd_dev.opt_enable) { printk( "%s: cannot disable watchdog in ENABLED mode\n", WD_OBPNAME); return(-EINVAL); } wd_stoptimer(pTimer); } else if(setopt & WDIOS_ENABLECARD) { wd_starttimer(pTimer); } else { return(-EINVAL); } break; /* Solaris-compatible IOCTLs */ case WIOCGSTAT: setopt = wd_getstatus(pTimer); if(copy_to_user((void*)arg, &setopt, sizeof(unsigned int))) { return(-EFAULT); } break; case WIOCSTART: wd_starttimer(pTimer); break; case WIOCSTOP: if(wd_dev.opt_enable) { printk("%s: cannot disable watchdog in ENABLED mode\n", WD_OBPNAME); return(-EINVAL); } wd_stoptimer(pTimer); break; default: return(-EINVAL); } return(0);}static ssize_t wd_write( struct file *file, const char *buf, size_t count, loff_t *ppos){ struct wd_timer* pTimer = (struct wd_timer*)file->private_data; if(NULL == pTimer) { return(-EINVAL); } if (ppos != &file->f_pos)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -