Showing error 854

User: Jiri Slaby
Error type: Resource Leak
Error type description: The code omits to put the resource to the system for reuse
File location: drivers/watchdog/alim7101_wdt.c
Line in file: 357
Project: Linux Kernel
Project version: 2.6.28
Tools: Stanse (1.2)
Entered: 2011-11-07 22:40:13 UTC


Source:

  1/*
  2 *        ALi M7101 PMU Computer Watchdog Timer driver
  3 *
  4 *        Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net>
  5 *        and the Cobalt kernel WDT timer driver by Tim Hockin
  6 *                                              <thockin@cobaltnet.com>
  7 *
  8 *        (c)2002 Steve Hill <steve@navaho.co.uk>
  9 *
 10 *  This WDT driver is different from most other Linux WDT
 11 *  drivers in that the driver will ping the watchdog by itself,
 12 *  because this particular WDT has a very short timeout (1.6
 13 *  seconds) and it would be insane to count on any userspace
 14 *  daemon always getting scheduled within that time frame.
 15 *
 16 *  Additions:
 17 *   Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
 18 *                  found on very old cobalt hardware.
 19 *                  -- Mike Waychison <michael.waychison@sun.com>
 20 */
 21
 22#include <linux/module.h>
 23#include <linux/moduleparam.h>
 24#include <linux/types.h>
 25#include <linux/timer.h>
 26#include <linux/miscdevice.h>
 27#include <linux/watchdog.h>
 28#include <linux/ioport.h>
 29#include <linux/notifier.h>
 30#include <linux/reboot.h>
 31#include <linux/init.h>
 32#include <linux/fs.h>
 33#include <linux/pci.h>
 34#include <linux/io.h>
 35#include <linux/uaccess.h>
 36
 37#include <asm/system.h>
 38
 39#define OUR_NAME "alim7101_wdt"
 40#define PFX OUR_NAME ": "
 41
 42#define WDT_ENABLE 0x9C
 43#define WDT_DISABLE 0x8C
 44
 45#define ALI_7101_WDT    0x92
 46#define ALI_7101_GPIO   0x7D
 47#define ALI_7101_GPIO_O 0x7E
 48#define ALI_WDT_ARM     0x01
 49
 50/*
 51 * We're going to use a 1 second timeout.
 52 * If we reset the watchdog every ~250ms we should be safe.  */
 53
 54#define WDT_INTERVAL (HZ/4+1)
 55
 56/*
 57 * We must not require too good response from the userspace daemon.
 58 * Here we require the userspace daemon to send us a heartbeat
 59 * char to /dev/watchdog every 30 seconds.
 60 */
 61
 62#define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
 63/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
 64static int timeout = WATCHDOG_TIMEOUT;
 65module_param(timeout, int, 0);
 66MODULE_PARM_DESC(timeout,
 67                "Watchdog timeout in seconds. (1<=timeout<=3600, default="
 68                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
 69
 70static int use_gpio; /* Use the pic (for a1d revision alim7101) */
 71module_param(use_gpio, int, 0);
 72MODULE_PARM_DESC(use_gpio,
 73                "Use the gpio watchdog (required by old cobalt boards).");
 74
 75static void wdt_timer_ping(unsigned long);
 76static DEFINE_TIMER(timer, wdt_timer_ping, 0, 1);
 77static unsigned long next_heartbeat;
 78static unsigned long wdt_is_open;
 79static char wdt_expect_close;
 80static struct pci_dev *alim7101_pmu;
 81
 82static int nowayout = WATCHDOG_NOWAYOUT;
 83module_param(nowayout, int, 0);
 84MODULE_PARM_DESC(nowayout,
 85                "Watchdog cannot be stopped once started (default="
 86                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 87
 88/*
 89 *        Whack the dog
 90 */
 91
 92static void wdt_timer_ping(unsigned long data)
 93{
 94        /* If we got a heartbeat pulse within the WDT_US_INTERVAL
 95         * we agree to ping the WDT
 96         */
 97        char tmp;
 98
 99        if (time_before(jiffies, next_heartbeat)) {
100                /* Ping the WDT (this is actually a disarm/arm sequence) */
101                pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
102                pci_write_config_byte(alim7101_pmu,
103                                        ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
104                pci_write_config_byte(alim7101_pmu,
105                                        ALI_7101_WDT, (tmp | ALI_WDT_ARM));
106                if (use_gpio) {
107                        pci_read_config_byte(alim7101_pmu,
108                                        ALI_7101_GPIO_O, &tmp);
109                        pci_write_config_byte(alim7101_pmu,
110                                        ALI_7101_GPIO_O, tmp | 0x20);
111                        pci_write_config_byte(alim7101_pmu,
112                                        ALI_7101_GPIO_O, tmp & ~0x20);
113                }
114        } else {
115                printk(KERN_WARNING PFX
116                        "Heartbeat lost! Will not ping the watchdog\n");
117        }
118        /* Re-set the timer interval */
119        mod_timer(&timer, jiffies + WDT_INTERVAL);
120}
121
122/*
123 * Utility routines
124 */
125
126static void wdt_change(int writeval)
127{
128        char tmp;
129
130        pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
131        if (writeval == WDT_ENABLE) {
132                pci_write_config_byte(alim7101_pmu,
133                                        ALI_7101_WDT, (tmp | ALI_WDT_ARM));
134                if (use_gpio) {
135                        pci_read_config_byte(alim7101_pmu,
136                                        ALI_7101_GPIO_O, &tmp);
137                        pci_write_config_byte(alim7101_pmu,
138                                        ALI_7101_GPIO_O, tmp & ~0x20);
139                }
140
141        } else {
142                pci_write_config_byte(alim7101_pmu,
143                                        ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
144                if (use_gpio) {
145                        pci_read_config_byte(alim7101_pmu,
146                                        ALI_7101_GPIO_O, &tmp);
147                        pci_write_config_byte(alim7101_pmu,
148                                        ALI_7101_GPIO_O, tmp | 0x20);
149                }
150        }
151}
152
153static void wdt_startup(void)
154{
155        next_heartbeat = jiffies + (timeout * HZ);
156
157        /* We must enable before we kick off the timer in case the timer
158           occurs as we ping it */
159
160        wdt_change(WDT_ENABLE);
161
162        /* Start the timer */
163        mod_timer(&timer, jiffies + WDT_INTERVAL);
164
165        printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
166}
167
168static void wdt_turnoff(void)
169{
170        /* Stop the timer */
171        del_timer_sync(&timer);
172        wdt_change(WDT_DISABLE);
173        printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
174}
175
176static void wdt_keepalive(void)
177{
178        /* user land ping */
179        next_heartbeat = jiffies + (timeout * HZ);
180}
181
182/*
183 * /dev/watchdog handling
184 */
185
186static ssize_t fop_write(struct file *file, const char __user *buf,
187                                                size_t count, loff_t *ppos)
188{
189        /* See if we got the magic character 'V' and reload the timer */
190        if (count) {
191                if (!nowayout) {
192                        size_t ofs;
193
194                        /* note: just in case someone wrote the magic character
195                         * five months ago... */
196                        wdt_expect_close = 0;
197
198                        /* now scan */
199                        for (ofs = 0; ofs != count; ofs++) {
200                                char c;
201                                if (get_user(c, buf + ofs))
202                                        return -EFAULT;
203                                if (c == 'V')
204                                        wdt_expect_close = 42;
205                        }
206                }
207                /* someone wrote to us, we should restart timer */
208                wdt_keepalive();
209        }
210        return count;
211}
212
213static int fop_open(struct inode *inode, struct file *file)
214{
215        /* Just in case we're already talking to someone... */
216        if (test_and_set_bit(0, &wdt_is_open))
217                return -EBUSY;
218        /* Good, fire up the show */
219        wdt_startup();
220        return nonseekable_open(inode, file);
221}
222
223static int fop_close(struct inode *inode, struct file *file)
224{
225        if (wdt_expect_close == 42)
226                wdt_turnoff();
227        else {
228                /* wim: shouldn't there be a: del_timer(&timer); */
229                printk(KERN_CRIT PFX
230                  "device file closed unexpectedly. Will not stop the WDT!\n");
231        }
232        clear_bit(0, &wdt_is_open);
233        wdt_expect_close = 0;
234        return 0;
235}
236
237static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
238{
239        void __user *argp = (void __user *)arg;
240        int __user *p = argp;
241        static struct watchdog_info ident = {
242                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
243                                                        | WDIOF_MAGICCLOSE,
244                .firmware_version = 1,
245                .identity = "ALiM7101",
246        };
247
248        switch (cmd) {
249        case WDIOC_GETSUPPORT:
250                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
251        case WDIOC_GETSTATUS:
252        case WDIOC_GETBOOTSTATUS:
253                return put_user(0, p);
254        case WDIOC_SETOPTIONS:
255        {
256                int new_options, retval = -EINVAL;
257
258                if (get_user(new_options, p))
259                        return -EFAULT;
260                if (new_options & WDIOS_DISABLECARD) {
261                        wdt_turnoff();
262                        retval = 0;
263                }
264                if (new_options & WDIOS_ENABLECARD) {
265                        wdt_startup();
266                        retval = 0;
267                }
268                return retval;
269        }
270        case WDIOC_KEEPALIVE:
271                wdt_keepalive();
272                return 0;
273        case WDIOC_SETTIMEOUT:
274        {
275                int new_timeout;
276
277                if (get_user(new_timeout, p))
278                        return -EFAULT;
279                /* arbitrary upper limit */
280                if (new_timeout < 1 || new_timeout > 3600)
281                        return -EINVAL;
282                timeout = new_timeout;
283                wdt_keepalive();
284                /* Fall through */
285        }
286        case WDIOC_GETTIMEOUT:
287                return put_user(timeout, p);
288        default:
289                return -ENOTTY;
290        }
291}
292
293static const struct file_operations wdt_fops = {
294        .owner                =        THIS_MODULE,
295        .llseek                =        no_llseek,
296        .write                =        fop_write,
297        .open                =        fop_open,
298        .release        =        fop_close,
299        .unlocked_ioctl        =        fop_ioctl,
300};
301
302static struct miscdevice wdt_miscdev = {
303        .minor        =        WATCHDOG_MINOR,
304        .name        =        "watchdog",
305        .fops        =        &wdt_fops,
306};
307
308/*
309 *        Notifier for system down
310 */
311
312static int wdt_notify_sys(struct notifier_block *this,
313                                        unsigned long code, void *unused)
314{
315        if (code == SYS_DOWN || code == SYS_HALT)
316                wdt_turnoff();
317
318        if (code == SYS_RESTART) {
319                /*
320                 * Cobalt devices have no way of rebooting themselves other
321                 * than getting the watchdog to pull reset, so we restart the
322                 * watchdog on reboot with no heartbeat
323                 */
324                wdt_change(WDT_ENABLE);
325                printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n");
326        }
327        return NOTIFY_DONE;
328}
329
330/*
331 *        The WDT needs to learn about soft shutdowns in order to
332 *        turn the timebomb registers off.
333 */
334
335static struct notifier_block wdt_notifier = {
336        .notifier_call = wdt_notify_sys,
337};
338
339static void __exit alim7101_wdt_unload(void)
340{
341        wdt_turnoff();
342        /* Deregister */
343        misc_deregister(&wdt_miscdev);
344        unregister_reboot_notifier(&wdt_notifier);
345        pci_dev_put(alim7101_pmu);
346}
347
348static int __init alim7101_wdt_init(void)
349{
350        int rc = -EBUSY;
351        struct pci_dev *ali1543_south;
352        char tmp;
353
354        printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n");
355        alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,
356                NULL);
357        if (!alim7101_pmu) {
358                printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n");
359                return -EBUSY;
360        }
361
362        /* Set the WDT in the PMU to 1 second */
363        pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
364
365        ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533,
366                NULL);
367        if (!ali1543_south) {
368                printk(KERN_INFO PFX
369                        "ALi 1543 South-Bridge not present - WDT not set\n");
370                goto err_out;
371        }
372        pci_read_config_byte(ali1543_south, 0x5e, &tmp);
373        pci_dev_put(ali1543_south);
374        if ((tmp & 0x1e) == 0x00) {
375                if (!use_gpio) {
376                        printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'.  If this is a cobalt board, set the 'use_gpio' module parameter.\n");
377                        goto err_out;
378                }
379                nowayout = 1;
380        } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
381                printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
382                goto err_out;
383        }
384
385        if (timeout < 1 || timeout > 3600) {
386                /* arbitrary upper limit */
387                timeout = WATCHDOG_TIMEOUT;
388                printk(KERN_INFO PFX
389                        "timeout value must be 1 <= x <= 3600, using %d\n",
390                                                                timeout);
391        }
392
393        rc = register_reboot_notifier(&wdt_notifier);
394        if (rc) {
395                printk(KERN_ERR PFX
396                        "cannot register reboot notifier (err=%d)\n", rc);
397                goto err_out;
398        }
399
400        rc = misc_register(&wdt_miscdev);
401        if (rc) {
402                printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
403                        wdt_miscdev.minor, rc);
404                goto err_out_reboot;
405        }
406
407        if (nowayout)
408                __module_get(THIS_MODULE);
409
410        printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
411                timeout, nowayout);
412        return 0;
413
414err_out_reboot:
415        unregister_reboot_notifier(&wdt_notifier);
416err_out:
417        pci_dev_put(alim7101_pmu);
418        return rc;
419}
420
421module_init(alim7101_wdt_init);
422module_exit(alim7101_wdt_unload);
423
424static struct pci_device_id alim7101_pci_tbl[] __devinitdata = {
425        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) },
426        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) },
427        { }
428};
429
430MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl);
431
432MODULE_AUTHOR("Steve Hill");
433MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
434MODULE_LICENSE("GPL");
435MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);