]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - arch/x86_64/kernel/mce.c
x86_64: O_EXCL on /dev/mcelog
[linux-2.6-omap-h63xx.git] / arch / x86_64 / kernel / mce.c
index 8011a8e1c7d41d8a5baf2876c87697b37eec39ca..77fee481be4fbcdb456fb83c6ea9aeaae58fb84b 100644 (file)
 #include <linux/percpu.h>
 #include <linux/ctype.h>
 #include <linux/kmod.h>
+#include <linux/kdebug.h>
 #include <asm/processor.h> 
 #include <asm/msr.h>
 #include <asm/mce.h>
-#include <asm/kdebug.h>
 #include <asm/uaccess.h>
 #include <asm/smp.h>
 
@@ -174,7 +174,7 @@ static void do_mce_trigger(void)
        if (events != atomic_read(&mce_logged) && trigger[0]) {
                /* Small race window, but should be harmless.  */
                atomic_set(&mce_logged, events);
-               call_usermodehelper(trigger, trigger_argv, NULL, -1);
+               call_usermodehelper(trigger, trigger_argv, NULL, UMH_NO_WAIT);
        }
 }
 
@@ -323,10 +323,13 @@ void mce_log_therm_throt_event(unsigned int cpu, __u64 status)
 #endif /* CONFIG_X86_MCE_INTEL */
 
 /*
- * Periodic polling timer for "silent" machine check errors.
+ * Periodic polling timer for "silent" machine check errors.  If the
+ * poller finds an MCE, poll 2x faster.  When the poller finds no more
+ * errors, poll 2x slower (up to check_interval seconds).
  */
 
 static int check_interval = 5 * 60; /* 5 minutes */
+static int next_interval; /* in jiffies */
 static void mcheck_timer(struct work_struct *work);
 static DECLARE_DELAYED_WORK(mcheck_work, mcheck_timer);
 
@@ -339,7 +342,6 @@ static void mcheck_check_cpu(void *info)
 static void mcheck_timer(struct work_struct *work)
 {
        on_each_cpu(mcheck_check_cpu, NULL, 1, 1);
-       schedule_delayed_work(&mcheck_work, check_interval * HZ);
 
        /*
         * It's ok to read stale data here for notify_user and
@@ -349,17 +351,30 @@ static void mcheck_timer(struct work_struct *work)
         * writes.
         */
        if (notify_user && console_logged) {
+               static unsigned long last_print;
+               unsigned long now = jiffies;
+
+               /* if we logged an MCE, reduce the polling interval */
+               next_interval = max(next_interval/2, HZ/100);
                notify_user = 0;
                clear_bit(0, &console_logged);
-               printk(KERN_INFO "Machine check events logged\n");
+               if (time_after_eq(now, last_print + (check_interval*HZ))) {
+                       last_print = now;
+                       printk(KERN_INFO "Machine check events logged\n");
+               }
+       } else {
+               next_interval = min(next_interval*2, check_interval*HZ);
        }
+
+       schedule_delayed_work(&mcheck_work, next_interval);
 }
 
 
 static __init int periodic_mcheck_init(void)
 { 
-       if (check_interval)
-               schedule_delayed_work(&mcheck_work, check_interval*HZ);
+       next_interval = check_interval * HZ;
+       if (next_interval)
+               schedule_delayed_work(&mcheck_work, next_interval);
        return 0;
 } 
 __initcall(periodic_mcheck_init);
@@ -450,6 +465,40 @@ void __cpuinit mcheck_init(struct cpuinfo_x86 *c)
  * Character device to read and clear the MCE log.
  */
 
+static DEFINE_SPINLOCK(mce_state_lock);
+static int open_count; /* #times opened */
+static int open_exclu; /* already open exclusive? */
+
+static int mce_open(struct inode *inode, struct file *file)
+{
+       spin_lock(&mce_state_lock);
+
+       if (open_exclu || (open_count && (file->f_flags & O_EXCL))) {
+               spin_unlock(&mce_state_lock);
+               return -EBUSY;
+       }
+
+       if (file->f_flags & O_EXCL)
+               open_exclu = 1;
+       open_count++;
+
+       spin_unlock(&mce_state_lock);
+
+       return 0;
+}
+
+static int mce_release(struct inode *inode, struct file *file)
+{
+       spin_lock(&mce_state_lock);
+
+       open_count--;
+       open_exclu = 0;
+
+       spin_unlock(&mce_state_lock);
+
+       return 0;
+}
+
 static void collect_tscs(void *data) 
 { 
        unsigned long *cpu_tsc = (unsigned long *)data;
@@ -482,15 +531,17 @@ static ssize_t mce_read(struct file *filp, char __user *ubuf, size_t usize, loff
        for (i = 0; i < next; i++) {            
                unsigned long start = jiffies;
                while (!mcelog.entry[i].finished) {
-                       if (!time_before(jiffies, start + 2)) {
+                       if (time_after_eq(jiffies, start + 2)) {
                                memset(mcelog.entry + i,0, sizeof(struct mce));
-                               continue;
+                               goto timeout;
                        }
                        cpu_relax();
                }
                smp_rmb();
                err |= copy_to_user(buf, mcelog.entry + i, sizeof(struct mce));
                buf += sizeof(struct mce); 
+ timeout:
+               ;
        } 
 
        memset(mcelog.entry, 0, next * sizeof(struct mce));
@@ -538,6 +589,8 @@ static int mce_ioctl(struct inode *i, struct file *f,unsigned int cmd, unsigned
 }
 
 static const struct file_operations mce_chrdev_ops = {
+       .open = mce_open,
+       .release = mce_release,
        .read = mce_read,
        .ioctl = mce_ioctl,
 };
@@ -597,12 +650,13 @@ static int mce_resume(struct sys_device *dev)
 /* Reinit MCEs after user configuration changes */
 static void mce_restart(void) 
 { 
-       if (check_interval)
+       if (next_interval)
                cancel_delayed_work(&mcheck_work);
        /* Timer race is harmless here */
        on_each_cpu(mce_init, NULL, 1, 1);       
-       if (check_interval)
-               schedule_delayed_work(&mcheck_work, check_interval*HZ);
+       next_interval = check_interval * HZ;
+       if (next_interval)
+               schedule_delayed_work(&mcheck_work, next_interval);
 }
 
 static struct sysdev_class mce_sysclass = {
@@ -704,9 +758,11 @@ mce_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu)
 
        switch (action) {
        case CPU_ONLINE:
+       case CPU_ONLINE_FROZEN:
                mce_create_device(cpu);
                break;
        case CPU_DEAD:
+       case CPU_DEAD_FROZEN:
                mce_remove_device(cpu);
                break;
        }