x86/mce/dev-mcelog: Dynamically allocate space for machine check records
authorTony Luck <tony.luck@intel.com>
Tue, 18 Feb 2020 18:44:08 +0000 (10:44 -0800)
committerBorislav Petkov <bp@suse.de>
Tue, 10 Mar 2020 09:25:14 +0000 (10:25 +0100)
We have had a hard coded limit of 32 machine check records since the
dawn of time.  But as numbers of cores increase, it is possible for
more than 32 errors to be reported before a user process reads from
/dev/mcelog. In this case the additional errors are lost.

Keep 32 as the minimum. But tune the maximum value up based on the
number of processors.

Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lkml.kernel.org/r/20200218184408.GA23048@agluck-desk2.amr.corp.intel.com
arch/x86/include/asm/mce.h
arch/x86/kernel/cpu/mce/dev-mcelog.c

index 4359b955e0b7ae5e389323aa5d76c1b7a3d509de..9d5b09913ef326b149985cf186673d5bc3993f92 100644 (file)
 
 #define MCE_OVERFLOW 0         /* bit 0 in flags means overflow */
 
-#define MCE_LOG_LEN 32
+#define MCE_LOG_MIN_LEN 32U
 #define MCE_LOG_SIGNATURE      "MACHINECHECK"
 
 /* AMD Scalable MCA */
  */
 struct mce_log_buffer {
        char signature[12]; /* "MACHINECHECK" */
-       unsigned len;       /* = MCE_LOG_LEN */
+       unsigned len;       /* = elements in .mce_entry[] */
        unsigned next;
        unsigned flags;
        unsigned recordlen;     /* length of struct mce */
-       struct mce entry[MCE_LOG_LEN];
+       struct mce entry[];
 };
 
 enum mce_notifier_prios {
index 7c8958dee10357960aee868eb315b1af1272aed5..d089567a9ce82228cff67cb9ea151460651a31a9 100644 (file)
@@ -29,11 +29,7 @@ static char *mce_helper_argv[2] = { mce_helper, NULL };
  * separate MCEs from kernel messages to avoid bogus bug reports.
  */
 
-static struct mce_log_buffer mcelog = {
-       .signature      = MCE_LOG_SIGNATURE,
-       .len            = MCE_LOG_LEN,
-       .recordlen      = sizeof(struct mce),
-};
+static struct mce_log_buffer *mcelog;
 
 static DECLARE_WAIT_QUEUE_HEAD(mce_chrdev_wait);
 
@@ -45,21 +41,21 @@ static int dev_mce_log(struct notifier_block *nb, unsigned long val,
 
        mutex_lock(&mce_chrdev_read_mutex);
 
-       entry = mcelog.next;
+       entry = mcelog->next;
 
        /*
         * When the buffer fills up discard new entries. Assume that the
         * earlier errors are the more interesting ones:
         */
-       if (entry >= MCE_LOG_LEN) {
-               set_bit(MCE_OVERFLOW, (unsigned long *)&mcelog.flags);
+       if (entry >= mcelog->len) {
+               set_bit(MCE_OVERFLOW, (unsigned long *)&mcelog->flags);
                goto unlock;
        }
 
-       mcelog.next = entry + 1;
+       mcelog->next = entry + 1;
 
-       memcpy(mcelog.entry + entry, mce, sizeof(struct mce));
-       mcelog.entry[entry].finished = 1;
+       memcpy(mcelog->entry + entry, mce, sizeof(struct mce));
+       mcelog->entry[entry].finished = 1;
 
        /* wake processes polling /dev/mcelog */
        wake_up_interruptible(&mce_chrdev_wait);
@@ -214,21 +210,21 @@ static ssize_t mce_chrdev_read(struct file *filp, char __user *ubuf,
 
        /* Only supports full reads right now */
        err = -EINVAL;
-       if (*off != 0 || usize < MCE_LOG_LEN*sizeof(struct mce))
+       if (*off != 0 || usize < mcelog->len * sizeof(struct mce))
                goto out;
 
-       next = mcelog.next;
+       next = mcelog->next;
        err = 0;
 
        for (i = 0; i < next; i++) {
-               struct mce *m = &mcelog.entry[i];
+               struct mce *m = &mcelog->entry[i];
 
                err |= copy_to_user(buf, m, sizeof(*m));
                buf += sizeof(*m);
        }
 
-       memset(mcelog.entry, 0, next * sizeof(struct mce));
-       mcelog.next = 0;
+       memset(mcelog->entry, 0, next * sizeof(struct mce));
+       mcelog->next = 0;
 
        if (err)
                err = -EFAULT;
@@ -242,7 +238,7 @@ out:
 static __poll_t mce_chrdev_poll(struct file *file, poll_table *wait)
 {
        poll_wait(file, &mce_chrdev_wait, wait);
-       if (READ_ONCE(mcelog.next))
+       if (READ_ONCE(mcelog->next))
                return EPOLLIN | EPOLLRDNORM;
        if (!mce_apei_read_done && apei_check_mce())
                return EPOLLIN | EPOLLRDNORM;
@@ -261,13 +257,13 @@ static long mce_chrdev_ioctl(struct file *f, unsigned int cmd,
        case MCE_GET_RECORD_LEN:
                return put_user(sizeof(struct mce), p);
        case MCE_GET_LOG_LEN:
-               return put_user(MCE_LOG_LEN, p);
+               return put_user(mcelog->len, p);
        case MCE_GETCLEAR_FLAGS: {
                unsigned flags;
 
                do {
-                       flags = mcelog.flags;
-               } while (cmpxchg(&mcelog.flags, flags, 0) != flags);
+                       flags = mcelog->flags;
+               } while (cmpxchg(&mcelog->flags, flags, 0) != flags);
 
                return put_user(flags, p);
        }
@@ -339,8 +335,18 @@ static struct miscdevice mce_chrdev_device = {
 
 static __init int dev_mcelog_init_device(void)
 {
+       int mce_log_len;
        int err;
 
+       mce_log_len = max(MCE_LOG_MIN_LEN, num_online_cpus());
+       mcelog = kzalloc(sizeof(*mcelog) + mce_log_len * sizeof(struct mce), GFP_KERNEL);
+       if (!mcelog)
+               return -ENOMEM;
+
+       strncpy(mcelog->signature, MCE_LOG_SIGNATURE, sizeof(mcelog->signature));
+       mcelog->len = mce_log_len;
+       mcelog->recordlen = sizeof(struct mce);
+
        /* register character device /dev/mcelog */
        err = misc_register(&mce_chrdev_device);
        if (err) {
@@ -350,6 +356,7 @@ static __init int dev_mcelog_init_device(void)
                else
                        pr_err("Unable to init device /dev/mcelog (rc: %d)\n", err);
 
+               kfree(mcelog);
                return err;
        }