📄 pcwd.c
字号:
/* * Berkshire PC Watchdog Card Driver * * Copyright (c) 1998-2002 Holger Eiboeck <holger@eiboeck.de>, * All Rights Reserved. * * http://www.pcwd.de/ * * Idea and parts of the code by Kenji Hollis <khollis@bitgate.com> * * 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. * * $Id: pcwd.c,v 1.42 2002/07/26 08:19:00 holgi Exp root $ * * $Log: pcwd.c,v $ * Revision 1.42 2002/07/26 08:19:00 holgi * Added an extra spinlock variable for io operations only (David Chambers) * to try to prevent deadlocks. Changed io operations to use this lock (David Chambers) * Explicitly initialize spinlock_t pcwd_lock (David Chambers) * indeted propperly for kernel submission * * Revision 1.41 2002/06/11 12:47:47 holgi * fixed dumb compile error when used direct in kernel found by Martin Josefson * made CONFIG_WATCHDOG_NOWAYOUT a module parameter * fixed a bug in case enabling/disabling the board failed * full IOCTL support to be more compatible with other watchdog drivers * reorganized some spinlocks * separated possible /dev/watchdog and /dev/temperature operations * * Revision 1.40 2001/12/18 10:28:17 holgi * drop kernel < 2.4 support and all wrappers * support pci hotplug * * Revision 1.39 2001/12/18 10:22:09 holgi * code reformat * fixed bug in ISA proc output; now more reliable * fixed Makefile for better 2.4 compatibility * fixed isa card timeouts * * Revision 1.38 2001/09/05 12:38:07 holgi * corrected bug in /proc buffer calculation * new command 'tempoffset' (Olaf Christians) * * Revision 1.37 2001/03/19 12:03:03 holgi * forgot to initialize pcwd_lock (Martin Josefsson) * more spin_locks (Friedrich Lobenstock) * restore capability to compile directly into kernel * tested compatibility with kernel 2.2.17 and 2.4.2 * * Revision 1.36 2001/01/23 18:30:06 holgi * Friedrich Lobenstock <fl@fl.priv.at>: * code reformated to only use tabs to indent * new command 'debug', separate ISA & PCI timout * reset command has a 5 s delay * new commands 'clear', 'relay 1noinvert', 'relay 1nvnoinvert' */#include <linux/version.h>#include <linux/module.h>#include <linux/config.h>#include <linux/errno.h>#include <linux/sched.h>#include <linux/tty.h>#include <linux/timer.h>#include <linux/kernel.h>#include <linux/wait.h>#include <linux/string.h>#include <linux/slab.h>#include <linux/ioport.h>#include <linux/delay.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/stat.h>#include <linux/smp_lock.h>#include <linux/spinlock.h>#include <linux/types.h>#include <linux/pci.h>#include <linux/reboot.h>#include <linux/notifier.h>#include <linux/init.h>#include <linux/watchdog.h>#ifdef CONFIG_PROC_FS#include <linux/proc_fs.h>#endif#include <asm/io.h>#include <asm/uaccess.h>#include <asm/system.h>#ifndef PCI_VENDOR_ID_QUICKLOGIC#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3#endif#ifndef PCI_DEVICE_ID_WATCHDOG_PCWDPCI#define PCI_DEVICE_ID_WATCHDOG_PCWDPCI 0x5030#endifstatic char rcsid[] = "$Revision: 1.42 $$Date: 2002/07/26 08:19:00 $";/* revision */#define PCWD_REVISION_A 1#define PCWD_REVISION_C 2#define PCWD_PCI 3#define PCWD_USB 4/* pcwd_temp_mode */#define CELSIUS 0#define FAHRENHEIT 1/* pcwd_verbose - level of verbosity */#define QUIET 0#define VERBOSE 1 /* normal */#define DEBUG 2 /* print fancy stuff too *//* card_status, pcwd_diag_mode */#define DISABLED 0#define ENABLED 1/* mode of operation for relay 2 */#define TEMPERATURE_TRIP 0 /* activate relay 2 during a temperature trip */#define NO_TEMPERATURE_TRIP 1 /* don't do it */#define WD_TIMEOUT 3 /* 1 1/2 seconds for a timeout *//* max. time we give an ISA watchdog card to process a command *//* 500ms for each 4 bit response */#define ISA_COMMAND_TIMEOUT 1000/* according to documentaion max. time to process a command for the pci watchdog card is 100 ms, so we give it 150 ms to do it's job */#define PCI_COMMAND_TIMEOUT 150MODULE_PARM(nowayout, "i");MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");#ifdef CONFIG_WATCHDOG_NOWAYOUTstatic int nowayout = 1;#elsestatic int nowayout = 0;#endifstatic int revision = PCWD_REVISION_A;static int supports_temp = 0;static int in_use = 0;static int card_status = DISABLED;static int pcwd_verbose = VERBOSE;// static int pcwd_verbose = DEBUG;static int pcwd_temp_mode = CELSIUS;static unsigned long card_ioport = 0x000;static char pcwd_driver_version[40];static char pcwd_fw_string[7]; /* firmware version xx.xxx */static int pcwd_fw_rev_major, pcwd_fw_rev_minor;static int pcwd_diag_mode = DISABLED;static unsigned int ping_counter = 0;static int initial_status, initial_status_set;static int temp_mode_relay2 = TEMPERATURE_TRIP;static int pcwd_temp_panic = DISABLED;static spinlock_t pcwd_io_lock = SPIN_LOCK_UNLOCKED;static spinlock_t pcwd_lock = SPIN_LOCK_UNLOCKED;static voidpcwd_get_version(void){ char *rcsvers, *rcsdate, *tmp; rcsvers = strchr(rcsid, ' '); rcsvers++; tmp = strchr(rcsvers, ' '); *tmp++ = '\0'; rcsdate = strchr(tmp, ' '); rcsdate++; tmp = strrchr(rcsdate, ' '); *tmp = '\0'; sprintf(pcwd_driver_version, "%s (%s)", rcsvers, rcsdate);}static int __initpcwd_find_card(void){ /* Available I/O port addresses for the PC Watchdog cards. */ static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0 }; int card_dat, prev_card_dat, count = 0, i; int rc = 0; int num_cards = 0; /* check for ISA cards */ for (i = 0; pcwd_ioports[i] != 0; i++) { if (rc == 0) { card_dat = 0x00; if (check_region(pcwd_ioports[i], 4)) { printk(KERN_ERR "pcwd: Port 0x%x unavailable.\n", pcwd_ioports[i]); return 0; } spin_lock(&pcwd_io_lock); prev_card_dat = inb_p(pcwd_ioports[i]); count = 0; if (prev_card_dat != 0xFF) { while (count < WD_TIMEOUT) { card_dat = inb_p(pcwd_ioports[i] + 1); card_dat &= 0x02; mdelay(500); if (((card_dat == 0x02) && (prev_card_dat == 0x00)) || ((card_dat == 0x00) && (prev_card_dat == 0x02))) { /* heartbeat found */ card_ioport = pcwd_ioports[i]; num_cards++; rc = 1; break; } prev_card_dat = card_dat; count++; } } spin_unlock(&pcwd_io_lock); } } /* check for PCI cards */ if (pci_present()) { struct pci_dev *dev = (0); while ((dev = pci_find_device(PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCWDPCI, dev)) != (0)) { num_cards++; card_ioport = pci_resource_start(dev, 0); if (pci_enable_device(dev)) { printk(KERN_ERR "pcwd: failed to enable watchdog card!\n"); rc = 0; } else { revision = PCWD_PCI; rc = 1; } } } if (num_cards > 1) { printk(KERN_ERR "pcwd: more than 1 watchdog card found!\n"); rc = 0; } return rc;}static intpcwd_set_diag(void){ if (revision == PCWD_PCI) { /* PCI card has no diag mode */ pcwd_diag_mode = ENABLED; } else { int i, found = 0, count = 0; while ((count < 3) && (!found)) { outb_p(0x80, card_ioport + 2); udelay(ISA_COMMAND_TIMEOUT); i = inb(card_ioport); if (i == 0x00) found = 1; else if (i == 0xF3) outb_p(0x00, card_ioport + 2); udelay(ISA_COMMAND_TIMEOUT); count++; } if (found) { pcwd_diag_mode = ENABLED; } else { pcwd_diag_mode = DISABLED; } } return pcwd_diag_mode;}static voidpcwd_diag_set_idle(void){ if (revision != PCWD_PCI) { /* PCI card has no diag mode -> no idle */ outb_p(0x00, card_ioport + 2); udelay(ISA_COMMAND_TIMEOUT); (void) inb(card_ioport); (void) inb(card_ioport); }}/* send a command to watchdog card and give a 16 bit return value */static intsend_command_ret16(int cmd, int msb, int lsb){ static int response, count; /* send command to card */ outb_p(lsb, card_ioport + 4); outb_p(msb, card_ioport + 5); outb_p(cmd, card_ioport + 6); if (revision == PCWD_PCI) { /* wait till the pci card processed the command, signaled by the WRSP bit in port 2 and give it a max. timeout of PCI_COMMAND_TIMEOUT to process */ for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!(inb(card_ioport + 2) & 0x40)); count++) mdelay(1); if (pcwd_verbose >= DEBUG) printk(KERN_INFO "pcwd: time to process command was: %d ms\n", count); /* read back response */ response = inb(card_ioport + 4) + inb(card_ioport + 5) * 256; /* clear WRSP bit */ inb(card_ioport + 6); } else { /* there is no status bit like the WRSP bit, so */ /* wait max. timeout */ mdelay(ISA_COMMAND_TIMEOUT); /* read back response */ response = inb(card_ioport + 4) + inb(card_ioport + 5) * 256; } return response;}/* send a command to watchdog card and give a 8 bit return value */static intsend_command_ret8(int cmd, int msb, int lsb){ return (send_command_ret16(cmd, msb, lsb) & 0x00ff);}/* send a command to watchdog card and don't care for the result */static inline voidsend_command(int cmd, int msb, int lsb){ send_command_ret16(cmd, msb, lsb);}static intsend_diag_command(int cmd){ int i; if (revision == PCWD_PCI) { i = send_command_ret16(cmd, 0, 0); } else { if ((pcwd_diag_mode != ENABLED) && (pcwd_set_diag() != ENABLED)) { printk(KERN_ERR "pcwd: send_diag_command diag mode failed"); return (0); } outb_p(cmd, card_ioport + 2); udelay(ISA_COMMAND_TIMEOUT); i = inb(card_ioport); i = inb(card_ioport); } return i;}static intpcwd_clear_status(void){ if ((revision == PCWD_PCI) || (revision == PCWD_REVISION_C)) { int cdat; if (pcwd_verbose >= VERBOSE) printk(KERN_INFO "pcwd: clearing trip status\n"); cdat = inb_p(card_ioport + 1); if (pcwd_verbose >= DEBUG) { printk(KERN_DEBUG "pcwd: status was: 0x%02x\n", cdat); printk(KERN_DEBUG "pcwd: sending: 0x%02x\n", (cdat & 0x40) | 0x01); } /* clear trip status & LED and keep mode of relay 2 */ outb_p((cdat & 0x40) | 0x01, card_ioport + 1); /* clear reset counter */ cdat = send_command_ret8(0x84, 0, 0xff); if (pcwd_verbose >= DEBUG) { printk(KERN_DEBUG "pcwd: reset count was: 0x%02x\n", cdat); } /* set clear boot status */ initial_status |= 0x02; } else { /* FIXME: does the ISA card revision A support this? */ /* for now we suppose not */ printk(KERN_ERR "pcwd: clear does not work on revision A\n"); return 1; } return 0;}static intpcwd_init_status(void){ if ((revision == PCWD_PCI) || (revision == PCWD_REVISION_C)) { if (pcwd_verbose >= DEBUG) printk(KERN_INFO "pcwd: inititializing status\n"); /* init status clear trip status & LED */ outb_p((temp_mode_relay2) ? 0x40 : 0x00, card_ioport + 1); } else { /* FIXME: does a ISA card revision A support this? */ /* for now we suppose not */ return 1; } return 0;}static intpcwd_reset_pc(void){ if (revision == PCWD_PCI) { int rc; printk(KERN_INFO "pcwd: reseting PC!\n"); /* reset with a 5 second delay - * user might want to umount, call sync, ... :) */ rc = send_command_ret8(0x80, 0xa5, 5); /* FIXME: check ack from card, but it works... */ card_status = DISABLED; return rc; } else { if (pcwd_set_diag() == ENABLED) { printk(KERN_INFO "pcwd: reseting PC!\n"); send_diag_command(0x86); if (!(inb_p(card_ioport) == 0xAA)) { printk(KERN_ERR "pcwd: Card did not acknowledge reset attempt.\n"); return 0; } pcwd_diag_set_idle(); printk(KERN_INFO "pcwd: reseting PC ACK!\n"); card_status = DISABLED; return 1; } } return 0;}intpcwd_disable_card(void){ int stat_reg; spin_lock(&pcwd_io_lock); outb_p(0xA5, card_ioport + 3); udelay(ISA_COMMAND_TIMEOUT); outb_p(0xA5, card_ioport + 3); udelay(ISA_COMMAND_TIMEOUT); stat_reg = inb_p(card_ioport + 2); spin_unlock(&pcwd_io_lock); if (!(stat_reg & 0x10)) { printk(KERN_ERR "pcwd: Card did not acknowledge disable attempt. Try again.\n"); return 0; } card_status = DISABLED; return 1;}static intpcwd_enable_card(void){ int stat_reg; spin_lock(&pcwd_io_lock); outb_p(0x00, card_ioport + 3); udelay(ISA_COMMAND_TIMEOUT); stat_reg = inb_p(card_ioport + 2); spin_unlock(&pcwd_io_lock); if (stat_reg & 0x10) { printk(KERN_ERR "pcwd: Card timer not enabled.\n"); return 0; } card_status = ENABLED; return 1;}static intpcwd_get_temperature(void){ int dat = 0; pcwd_diag_set_idle(); if (supports_temp) { spin_lock(&pcwd_io_lock); dat = inb(card_ioport); dat = inb(card_ioport); spin_unlock(&pcwd_io_lock); } else { dat = 0; } if (pcwd_temp_mode == FAHRENHEIT) dat = (dat * 9 / 5) + 32; return dat;}static voidpcwd_get_relay2_mode(void){ if (inb_p(card_ioport + 1) & 0x40) temp_mode_relay2 = NO_TEMPERATURE_TRIP; else temp_mode_relay2 = TEMPERATURE_TRIP; if (pcwd_verbose >= DEBUG) printk(KERN_INFO "pcwd: mode of relay 2 is: %s\n", (temp_mode_relay2 == TEMPERATURE_TRIP) ? "temp" : "notemp");}static intpcwd_get_trip_status(void){ int cdat; if (revision == PCWD_REVISION_A) { cdat = inb_p(card_ioport); } else { cdat = inb_p(card_ioport + 1); } if (initial_status_set == 0) { initial_status_set = 1; /* don't store HRBT bit - we need it to store the clear status */ initial_status = cdat & (0xff - 0x02); if (pcwd_verbose >= DEBUG) printk(KERN_INFO "pcwd: initial status: 0x%02x\n", initial_status); } return (cdat & 0x01);}static intpcwd_get_switches(void){ int i, j; if (revision == PCWD_PCI) { i = inb_p(card_ioport + 3); return i; } else { if (pcwd_set_diag() == ENABLED) { j = send_diag_command(0x85); udelay(ISA_COMMAND_TIMEOUT); i = send_diag_command(0x85); pcwd_diag_set_idle(); return i; } else { return 0; } }}static inline voidget_support(void){ if (inb(card_ioport) != 0xF0) supports_temp = 1; else supports_temp = 0;}static inline voidget_revision(void){ if (revision != PCWD_PCI) { if ((inb(card_ioport + 2) == 0xFF) || (inb(card_ioport + 3) == 0xFF)) revision = PCWD_REVISION_A; else revision = PCWD_REVISION_C; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -