Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / firmware / google / gsmi.c
diff --git a/kernel/drivers/firmware/google/gsmi.c b/kernel/drivers/firmware/google/gsmi.c
new file mode 100644 (file)
index 0000000..f1ab05e
--- /dev/null
@@ -0,0 +1,942 @@
+/*
+ * Copyright 2010 Google Inc. All Rights Reserved.
+ * Author: dlaurie@google.com (Duncan Laurie)
+ *
+ * Re-worked to expose sysfs APIs by mikew@google.com (Mike Waychison)
+ *
+ * EFI SMI interface for Google platforms
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/ioctl.h>
+#include <linux/acpi.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/dmi.h>
+#include <linux/kdebug.h>
+#include <linux/reboot.h>
+#include <linux/efi.h>
+#include <linux/module.h>
+#include <linux/ucs2_string.h>
+
+#define GSMI_SHUTDOWN_CLEAN    0       /* Clean Shutdown */
+/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
+#define GSMI_SHUTDOWN_NMIWDT   1       /* NMI Watchdog */
+#define GSMI_SHUTDOWN_PANIC    2       /* Panic */
+#define GSMI_SHUTDOWN_OOPS     3       /* Oops */
+#define GSMI_SHUTDOWN_DIE      4       /* Die -- No longer meaningful */
+#define GSMI_SHUTDOWN_MCE      5       /* Machine Check */
+#define GSMI_SHUTDOWN_SOFTWDT  6       /* Software Watchdog */
+#define GSMI_SHUTDOWN_MBE      7       /* Uncorrected ECC */
+#define GSMI_SHUTDOWN_TRIPLE   8       /* Triple Fault */
+
+#define DRIVER_VERSION         "1.0"
+#define GSMI_GUID_SIZE         16
+#define GSMI_BUF_SIZE          1024
+#define GSMI_BUF_ALIGN         sizeof(u64)
+#define GSMI_CALLBACK          0xef
+
+/* SMI return codes */
+#define GSMI_SUCCESS           0x00
+#define GSMI_UNSUPPORTED2      0x03
+#define GSMI_LOG_FULL          0x0b
+#define GSMI_VAR_NOT_FOUND     0x0e
+#define GSMI_HANDSHAKE_SPIN    0x7d
+#define GSMI_HANDSHAKE_CF      0x7e
+#define GSMI_HANDSHAKE_NONE    0x7f
+#define GSMI_INVALID_PARAMETER 0x82
+#define GSMI_UNSUPPORTED       0x83
+#define GSMI_BUFFER_TOO_SMALL  0x85
+#define GSMI_NOT_READY         0x86
+#define GSMI_DEVICE_ERROR      0x87
+#define GSMI_NOT_FOUND         0x8e
+
+#define QUIRKY_BOARD_HASH 0x78a30a50
+
+/* Internally used commands passed to the firmware */
+#define GSMI_CMD_GET_NVRAM_VAR         0x01
+#define GSMI_CMD_GET_NEXT_VAR          0x02
+#define GSMI_CMD_SET_NVRAM_VAR         0x03
+#define GSMI_CMD_SET_EVENT_LOG         0x08
+#define GSMI_CMD_CLEAR_EVENT_LOG       0x09
+#define GSMI_CMD_CLEAR_CONFIG          0x20
+#define GSMI_CMD_HANDSHAKE_TYPE                0xC1
+
+/* Magic entry type for kernel events */
+#define GSMI_LOG_ENTRY_TYPE_KERNEL     0xDEAD
+
+/* SMI buffers must be in 32bit physical address space */
+struct gsmi_buf {
+       u8 *start;                      /* start of buffer */
+       size_t length;                  /* length of buffer */
+       dma_addr_t handle;              /* dma allocation handle */
+       u32 address;                    /* physical address of buffer */
+};
+
+struct gsmi_device {
+       struct platform_device *pdev;   /* platform device */
+       struct gsmi_buf *name_buf;      /* variable name buffer */
+       struct gsmi_buf *data_buf;      /* generic data buffer */
+       struct gsmi_buf *param_buf;     /* parameter buffer */
+       spinlock_t lock;                /* serialize access to SMIs */
+       u16 smi_cmd;                    /* SMI command port */
+       int handshake_type;             /* firmware handler interlock type */
+       struct dma_pool *dma_pool;      /* DMA buffer pool */
+} gsmi_dev;
+
+/* Packed structures for communicating with the firmware */
+struct gsmi_nvram_var_param {
+       efi_guid_t      guid;
+       u32             name_ptr;
+       u32             attributes;
+       u32             data_len;
+       u32             data_ptr;
+} __packed;
+
+struct gsmi_get_next_var_param {
+       u8      guid[GSMI_GUID_SIZE];
+       u32     name_ptr;
+       u32     name_len;
+} __packed;
+
+struct gsmi_set_eventlog_param {
+       u32     data_ptr;
+       u32     data_len;
+       u32     type;
+} __packed;
+
+/* Event log formats */
+struct gsmi_log_entry_type_1 {
+       u16     type;
+       u32     instance;
+} __packed;
+
+
+/*
+ * Some platforms don't have explicit SMI handshake
+ * and need to wait for SMI to complete.
+ */
+#define GSMI_DEFAULT_SPINCOUNT 0x10000
+static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT;
+module_param(spincount, uint, 0600);
+MODULE_PARM_DESC(spincount,
+       "The number of loop iterations to use when using the spin handshake.");
+
+static struct gsmi_buf *gsmi_buf_alloc(void)
+{
+       struct gsmi_buf *smibuf;
+
+       smibuf = kzalloc(sizeof(*smibuf), GFP_KERNEL);
+       if (!smibuf) {
+               printk(KERN_ERR "gsmi: out of memory\n");
+               return NULL;
+       }
+
+       /* allocate buffer in 32bit address space */
+       smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL,
+                                      &smibuf->handle);
+       if (!smibuf->start) {
+               printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
+               kfree(smibuf);
+               return NULL;
+       }
+
+       /* fill in the buffer handle */
+       smibuf->length = GSMI_BUF_SIZE;
+       smibuf->address = (u32)virt_to_phys(smibuf->start);
+
+       return smibuf;
+}
+
+static void gsmi_buf_free(struct gsmi_buf *smibuf)
+{
+       if (smibuf) {
+               if (smibuf->start)
+                       dma_pool_free(gsmi_dev.dma_pool, smibuf->start,
+                                     smibuf->handle);
+               kfree(smibuf);
+       }
+}
+
+/*
+ * Make a call to gsmi func(sub).  GSMI error codes are translated to
+ * in-kernel errnos (0 on success, -ERRNO on error).
+ */
+static int gsmi_exec(u8 func, u8 sub)
+{
+       u16 cmd = (sub << 8) | func;
+       u16 result = 0;
+       int rc = 0;
+
+       /*
+        * AH  : Subfunction number
+        * AL  : Function number
+        * EBX : Parameter block address
+        * DX  : SMI command port
+        *
+        * Three protocols here. See also the comment in gsmi_init().
+        */
+       if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) {
+               /*
+                * If handshake_type == HANDSHAKE_CF then set CF on the
+                * way in and wait for the handler to clear it; this avoids
+                * corrupting register state on those chipsets which have
+                * a delay between writing the SMI trigger register and
+                * entering SMM.
+                */
+               asm volatile (
+                       "stc\n"
+                       "outb %%al, %%dx\n"
+               "1:      jc 1b\n"
+                       : "=a" (result)
+                       : "0" (cmd),
+                         "d" (gsmi_dev.smi_cmd),
+                         "b" (gsmi_dev.param_buf->address)
+                       : "memory", "cc"
+               );
+       } else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) {
+               /*
+                * If handshake_type == HANDSHAKE_SPIN we spin a
+                * hundred-ish usecs to ensure the SMI has triggered.
+                */
+               asm volatile (
+                       "outb %%al, %%dx\n"
+               "1:      loop 1b\n"
+                       : "=a" (result)
+                       : "0" (cmd),
+                         "d" (gsmi_dev.smi_cmd),
+                         "b" (gsmi_dev.param_buf->address),
+                         "c" (spincount)
+                       : "memory", "cc"
+               );
+       } else {
+               /*
+                * If handshake_type == HANDSHAKE_NONE we do nothing;
+                * either we don't need to or it's legacy firmware that
+                * doesn't understand the CF protocol.
+                */
+               asm volatile (
+                       "outb %%al, %%dx\n\t"
+                       : "=a" (result)
+                       : "0" (cmd),
+                         "d" (gsmi_dev.smi_cmd),
+                         "b" (gsmi_dev.param_buf->address)
+                       : "memory", "cc"
+               );
+       }
+
+       /* check return code from SMI handler */
+       switch (result) {
+       case GSMI_SUCCESS:
+               break;
+       case GSMI_VAR_NOT_FOUND:
+               /* not really an error, but let the caller know */
+               rc = 1;
+               break;
+       case GSMI_INVALID_PARAMETER:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd);
+               rc = -EINVAL;
+               break;
+       case GSMI_BUFFER_TOO_SMALL:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd);
+               rc = -ENOMEM;
+               break;
+       case GSMI_UNSUPPORTED:
+       case GSMI_UNSUPPORTED2:
+               if (sub != GSMI_CMD_HANDSHAKE_TYPE)
+                       printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n",
+                              cmd);
+               rc = -ENOSYS;
+               break;
+       case GSMI_NOT_READY:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd);
+               rc = -EBUSY;
+               break;
+       case GSMI_DEVICE_ERROR:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd);
+               rc = -EFAULT;
+               break;
+       case GSMI_NOT_FOUND:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd);
+               rc = -ENOENT;
+               break;
+       case GSMI_LOG_FULL:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd);
+               rc = -ENOSPC;
+               break;
+       case GSMI_HANDSHAKE_CF:
+       case GSMI_HANDSHAKE_SPIN:
+       case GSMI_HANDSHAKE_NONE:
+               rc = result;
+               break;
+       default:
+               printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n",
+                      cmd, result);
+               rc = -ENXIO;
+       }
+
+       return rc;
+}
+
+static efi_status_t gsmi_get_variable(efi_char16_t *name,
+                                     efi_guid_t *vendor, u32 *attr,
+                                     unsigned long *data_size,
+                                     void *data)
+{
+       struct gsmi_nvram_var_param param = {
+               .name_ptr = gsmi_dev.name_buf->address,
+               .data_ptr = gsmi_dev.data_buf->address,
+               .data_len = (u32)*data_size,
+       };
+       efi_status_t ret = EFI_SUCCESS;
+       unsigned long flags;
+       size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
+       int rc;
+
+       if (name_len >= GSMI_BUF_SIZE / 2)
+               return EFI_BAD_BUFFER_SIZE;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* Vendor guid */
+       memcpy(&param.guid, vendor, sizeof(param.guid));
+
+       /* variable name, already in UTF-16 */
+       memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
+       memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
+
+       /* data pointer */
+       memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+
+       /* parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NVRAM_VAR);
+       if (rc < 0) {
+               printk(KERN_ERR "gsmi: Get Variable failed\n");
+               ret = EFI_LOAD_ERROR;
+       } else if (rc == 1) {
+               /* variable was not found */
+               ret = EFI_NOT_FOUND;
+       } else {
+               /* Get the arguments back */
+               memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
+
+               /* The size reported is the min of all of our buffers */
+               *data_size = min_t(unsigned long, *data_size,
+                                               gsmi_dev.data_buf->length);
+               *data_size = min_t(unsigned long, *data_size, param.data_len);
+
+               /* Copy data back to return buffer. */
+               memcpy(data, gsmi_dev.data_buf->start, *data_size);
+
+               /* All variables are have the following attributes */
+               *attr = EFI_VARIABLE_NON_VOLATILE |
+                       EFI_VARIABLE_BOOTSERVICE_ACCESS |
+                       EFI_VARIABLE_RUNTIME_ACCESS;
+       }
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       return ret;
+}
+
+static efi_status_t gsmi_get_next_variable(unsigned long *name_size,
+                                          efi_char16_t *name,
+                                          efi_guid_t *vendor)
+{
+       struct gsmi_get_next_var_param param = {
+               .name_ptr = gsmi_dev.name_buf->address,
+               .name_len = gsmi_dev.name_buf->length,
+       };
+       efi_status_t ret = EFI_SUCCESS;
+       int rc;
+       unsigned long flags;
+
+       /* For the moment, only support buffers that exactly match in size */
+       if (*name_size != GSMI_BUF_SIZE)
+               return EFI_BAD_BUFFER_SIZE;
+
+       /* Let's make sure the thing is at least null-terminated */
+       if (ucs2_strnlen(name, GSMI_BUF_SIZE / 2) == GSMI_BUF_SIZE / 2)
+               return EFI_INVALID_PARAMETER;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* guid */
+       memcpy(&param.guid, vendor, sizeof(param.guid));
+
+       /* variable name, already in UTF-16 */
+       memcpy(gsmi_dev.name_buf->start, name, *name_size);
+
+       /* parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NEXT_VAR);
+       if (rc < 0) {
+               printk(KERN_ERR "gsmi: Get Next Variable Name failed\n");
+               ret = EFI_LOAD_ERROR;
+       } else if (rc == 1) {
+               /* variable not found -- end of list */
+               ret = EFI_NOT_FOUND;
+       } else {
+               /* copy variable data back to return buffer */
+               memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
+
+               /* Copy the name back */
+               memcpy(name, gsmi_dev.name_buf->start, GSMI_BUF_SIZE);
+               *name_size = ucs2_strnlen(name, GSMI_BUF_SIZE / 2) * 2;
+
+               /* copy guid to return buffer */
+               memcpy(vendor, &param.guid, sizeof(param.guid));
+               ret = EFI_SUCCESS;
+       }
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       return ret;
+}
+
+static efi_status_t gsmi_set_variable(efi_char16_t *name,
+                                     efi_guid_t *vendor,
+                                     u32 attr,
+                                     unsigned long data_size,
+                                     void *data)
+{
+       struct gsmi_nvram_var_param param = {
+               .name_ptr = gsmi_dev.name_buf->address,
+               .data_ptr = gsmi_dev.data_buf->address,
+               .data_len = (u32)data_size,
+               .attributes = EFI_VARIABLE_NON_VOLATILE |
+                             EFI_VARIABLE_BOOTSERVICE_ACCESS |
+                             EFI_VARIABLE_RUNTIME_ACCESS,
+       };
+       size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
+       efi_status_t ret = EFI_SUCCESS;
+       int rc;
+       unsigned long flags;
+
+       if (name_len >= GSMI_BUF_SIZE / 2)
+               return EFI_BAD_BUFFER_SIZE;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* guid */
+       memcpy(&param.guid, vendor, sizeof(param.guid));
+
+       /* variable name, already in UTF-16 */
+       memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
+       memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
+
+       /* data pointer */
+       memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+       memcpy(gsmi_dev.data_buf->start, data, data_size);
+
+       /* parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_NVRAM_VAR);
+       if (rc < 0) {
+               printk(KERN_ERR "gsmi: Set Variable failed\n");
+               ret = EFI_INVALID_PARAMETER;
+       }
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       return ret;
+}
+
+static const struct efivar_operations efivar_ops = {
+       .get_variable = gsmi_get_variable,
+       .set_variable = gsmi_set_variable,
+       .get_next_variable = gsmi_get_next_variable,
+};
+
+static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
+                              struct bin_attribute *bin_attr,
+                              char *buf, loff_t pos, size_t count)
+{
+       struct gsmi_set_eventlog_param param = {
+               .data_ptr = gsmi_dev.data_buf->address,
+       };
+       int rc = 0;
+       unsigned long flags;
+
+       /* Pull the type out */
+       if (count < sizeof(u32))
+               return -EINVAL;
+       param.type = *(u32 *)buf;
+       count -= sizeof(u32);
+       buf += sizeof(u32);
+
+       /* The remaining buffer is the data payload */
+       if (count > gsmi_dev.data_buf->length)
+               return -EINVAL;
+       param.data_len = count - sizeof(u32);
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* data pointer */
+       memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+       memcpy(gsmi_dev.data_buf->start, buf, param.data_len);
+
+       /* parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
+       if (rc < 0)
+               printk(KERN_ERR "gsmi: Set Event Log failed\n");
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       return rc;
+
+}
+
+static struct bin_attribute eventlog_bin_attr = {
+       .attr = {.name = "append_to_eventlog", .mode = 0200},
+       .write = eventlog_write,
+};
+
+static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj,
+                                        struct kobj_attribute *attr,
+                                        const char *buf, size_t count)
+{
+       int rc;
+       unsigned long flags;
+       unsigned long val;
+       struct {
+               u32 percentage;
+               u32 data_type;
+       } param;
+
+       rc = kstrtoul(buf, 0, &val);
+       if (rc)
+               return rc;
+
+       /*
+        * Value entered is a percentage, 0 through 100, anything else
+        * is invalid.
+        */
+       if (val > 100)
+               return -EINVAL;
+
+       /* data_type here selects the smbios event log. */
+       param.percentage = val;
+       param.data_type = 0;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_EVENT_LOG);
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       if (rc)
+               return rc;
+       return count;
+}
+
+static struct kobj_attribute gsmi_clear_eventlog_attr = {
+       .attr = {.name = "clear_eventlog", .mode = 0200},
+       .store = gsmi_clear_eventlog_store,
+};
+
+static ssize_t gsmi_clear_config_store(struct kobject *kobj,
+                                      struct kobj_attribute *attr,
+                                      const char *buf, size_t count)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       /* clear parameter buffer */
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_CONFIG);
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       if (rc)
+               return rc;
+       return count;
+}
+
+static struct kobj_attribute gsmi_clear_config_attr = {
+       .attr = {.name = "clear_config", .mode = 0200},
+       .store = gsmi_clear_config_store,
+};
+
+static const struct attribute *gsmi_attrs[] = {
+       &gsmi_clear_config_attr.attr,
+       &gsmi_clear_eventlog_attr.attr,
+       NULL,
+};
+
+static int gsmi_shutdown_reason(int reason)
+{
+       struct gsmi_log_entry_type_1 entry = {
+               .type     = GSMI_LOG_ENTRY_TYPE_KERNEL,
+               .instance = reason,
+       };
+       struct gsmi_set_eventlog_param param = {
+               .data_len = sizeof(entry),
+               .type     = 1,
+       };
+       static int saved_reason;
+       int rc = 0;
+       unsigned long flags;
+
+       /* avoid duplicate entries in the log */
+       if (saved_reason & (1 << reason))
+               return 0;
+
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+       saved_reason |= (1 << reason);
+
+       /* data pointer */
+       memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+       memcpy(gsmi_dev.data_buf->start, &entry, sizeof(entry));
+
+       /* parameter buffer */
+       param.data_ptr = gsmi_dev.data_buf->address;
+       memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+       memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+       rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
+
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       if (rc < 0)
+               printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n");
+       else
+               printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n",
+                      reason);
+
+       return rc;
+}
+
+static int gsmi_reboot_callback(struct notifier_block *nb,
+                               unsigned long reason, void *arg)
+{
+       gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN);
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_reboot_notifier = {
+       .notifier_call = gsmi_reboot_callback
+};
+
+static int gsmi_die_callback(struct notifier_block *nb,
+                            unsigned long reason, void *arg)
+{
+       if (reason == DIE_OOPS)
+               gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS);
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_die_notifier = {
+       .notifier_call = gsmi_die_callback
+};
+
+static int gsmi_panic_callback(struct notifier_block *nb,
+                              unsigned long reason, void *arg)
+{
+       gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC);
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_panic_notifier = {
+       .notifier_call = gsmi_panic_callback,
+};
+
+/*
+ * This hash function was blatantly copied from include/linux/hash.h.
+ * It is used by this driver to obfuscate a board name that requires a
+ * quirk within this driver.
+ *
+ * Please do not remove this copy of the function as any changes to the
+ * global utility hash_64() function would break this driver's ability
+ * to identify a board and provide the appropriate quirk -- mikew@google.com
+ */
+static u64 __init local_hash_64(u64 val, unsigned bits)
+{
+       u64 hash = val;
+
+       /*  Sigh, gcc can't optimise this alone like it does for 32 bits. */
+       u64 n = hash;
+       n <<= 18;
+       hash -= n;
+       n <<= 33;
+       hash -= n;
+       n <<= 3;
+       hash += n;
+       n <<= 3;
+       hash -= n;
+       n <<= 4;
+       hash += n;
+       n <<= 2;
+       hash += n;
+
+       /* High bits are more random, so use them. */
+       return hash >> (64 - bits);
+}
+
+static u32 __init hash_oem_table_id(char s[8])
+{
+       u64 input;
+       memcpy(&input, s, 8);
+       return local_hash_64(input, 32);
+}
+
+static struct dmi_system_id gsmi_dmi_table[] __initdata = {
+       {
+               .ident = "Google Board",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
+               },
+       },
+       {}
+};
+MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
+
+static __init int gsmi_system_valid(void)
+{
+       u32 hash;
+
+       if (!dmi_check_system(gsmi_dmi_table))
+               return -ENODEV;
+
+       /*
+        * Only newer firmware supports the gsmi interface.  All older
+        * firmware that didn't support this interface used to plug the
+        * table name in the first four bytes of the oem_table_id field.
+        * Newer firmware doesn't do that though, so use that as the
+        * discriminant factor.  We have to do this in order to
+        * whitewash our board names out of the public driver.
+        */
+       if (!strncmp(acpi_gbl_FADT.header.oem_table_id, "FACP", 4)) {
+               printk(KERN_INFO "gsmi: Board is too old\n");
+               return -ENODEV;
+       }
+
+       /* Disable on board with 1.0 BIOS due to Google bug 2602657 */
+       hash = hash_oem_table_id(acpi_gbl_FADT.header.oem_table_id);
+       if (hash == QUIRKY_BOARD_HASH) {
+               const char *bios_ver = dmi_get_system_info(DMI_BIOS_VERSION);
+               if (strncmp(bios_ver, "1.0", 3) == 0) {
+                       pr_info("gsmi: disabled on this board's BIOS %s\n",
+                               bios_ver);
+                       return -ENODEV;
+               }
+       }
+
+       /* check for valid SMI command port in ACPI FADT */
+       if (acpi_gbl_FADT.smi_command == 0) {
+               pr_info("gsmi: missing smi_command\n");
+               return -ENODEV;
+       }
+
+       /* Found */
+       return 0;
+}
+
+static struct kobject *gsmi_kobj;
+static struct efivars efivars;
+
+static const struct platform_device_info gsmi_dev_info = {
+       .name           = "gsmi",
+       .id             = -1,
+       /* SMI callbacks require 32bit addresses */
+       .dma_mask       = DMA_BIT_MASK(32),
+};
+
+static __init int gsmi_init(void)
+{
+       unsigned long flags;
+       int ret;
+
+       ret = gsmi_system_valid();
+       if (ret)
+               return ret;
+
+       gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
+
+       /* register device */
+       gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info);
+       if (IS_ERR(gsmi_dev.pdev)) {
+               printk(KERN_ERR "gsmi: unable to register platform device\n");
+               return PTR_ERR(gsmi_dev.pdev);
+       }
+
+       /* SMI access needs to be serialized */
+       spin_lock_init(&gsmi_dev.lock);
+
+       ret = -ENOMEM;
+       gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev,
+                                            GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0);
+       if (!gsmi_dev.dma_pool)
+               goto out_err;
+
+       /*
+        * pre-allocate buffers because sometimes we are called when
+        * this is not feasible: oops, panic, die, mce, etc
+        */
+       gsmi_dev.name_buf = gsmi_buf_alloc();
+       if (!gsmi_dev.name_buf) {
+               printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
+               goto out_err;
+       }
+
+       gsmi_dev.data_buf = gsmi_buf_alloc();
+       if (!gsmi_dev.data_buf) {
+               printk(KERN_ERR "gsmi: failed to allocate data buffer\n");
+               goto out_err;
+       }
+
+       gsmi_dev.param_buf = gsmi_buf_alloc();
+       if (!gsmi_dev.param_buf) {
+               printk(KERN_ERR "gsmi: failed to allocate param buffer\n");
+               goto out_err;
+       }
+
+       /*
+        * Determine type of handshake used to serialize the SMI
+        * entry. See also gsmi_exec().
+        *
+        * There's a "behavior" present on some chipsets where writing the
+        * SMI trigger register in the southbridge doesn't result in an
+        * immediate SMI. Rather, the processor can execute "a few" more
+        * instructions before the SMI takes effect. To ensure synchronous
+        * behavior, implement a handshake between the kernel driver and the
+        * firmware handler to spin until released. This ioctl determines
+        * the type of handshake.
+        *
+        * NONE: The firmware handler does not implement any
+        * handshake. Either it doesn't need to, or it's legacy firmware
+        * that doesn't know it needs to and never will.
+        *
+        * CF: The firmware handler will clear the CF in the saved
+        * state before returning. The driver may set the CF and test for
+        * it to clear before proceeding.
+        *
+        * SPIN: The firmware handler does not implement any handshake
+        * but the driver should spin for a hundred or so microseconds
+        * to ensure the SMI has triggered.
+        *
+        * Finally, the handler will return -ENOSYS if
+        * GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies
+        * HANDSHAKE_NONE.
+        */
+       spin_lock_irqsave(&gsmi_dev.lock, flags);
+       gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN;
+       gsmi_dev.handshake_type =
+           gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE);
+       if (gsmi_dev.handshake_type == -ENOSYS)
+               gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE;
+       spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+       /* Remove and clean up gsmi if the handshake could not complete. */
+       if (gsmi_dev.handshake_type == -ENXIO) {
+               printk(KERN_INFO "gsmi version " DRIVER_VERSION
+                      " failed to load\n");
+               ret = -ENODEV;
+               goto out_err;
+       }
+
+       /* Register in the firmware directory */
+       ret = -ENOMEM;
+       gsmi_kobj = kobject_create_and_add("gsmi", firmware_kobj);
+       if (!gsmi_kobj) {
+               printk(KERN_INFO "gsmi: Failed to create firmware kobj\n");
+               goto out_err;
+       }
+
+       /* Setup eventlog access */
+       ret = sysfs_create_bin_file(gsmi_kobj, &eventlog_bin_attr);
+       if (ret) {
+               printk(KERN_INFO "gsmi: Failed to setup eventlog");
+               goto out_err;
+       }
+
+       /* Other attributes */
+       ret = sysfs_create_files(gsmi_kobj, gsmi_attrs);
+       if (ret) {
+               printk(KERN_INFO "gsmi: Failed to add attrs");
+               goto out_remove_bin_file;
+       }
+
+       ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj);
+       if (ret) {
+               printk(KERN_INFO "gsmi: Failed to register efivars\n");
+               goto out_remove_sysfs_files;
+       }
+
+       register_reboot_notifier(&gsmi_reboot_notifier);
+       register_die_notifier(&gsmi_die_notifier);
+       atomic_notifier_chain_register(&panic_notifier_list,
+                                      &gsmi_panic_notifier);
+
+       printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n");
+
+       return 0;
+
+out_remove_sysfs_files:
+       sysfs_remove_files(gsmi_kobj, gsmi_attrs);
+out_remove_bin_file:
+       sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
+out_err:
+       kobject_put(gsmi_kobj);
+       gsmi_buf_free(gsmi_dev.param_buf);
+       gsmi_buf_free(gsmi_dev.data_buf);
+       gsmi_buf_free(gsmi_dev.name_buf);
+       if (gsmi_dev.dma_pool)
+               dma_pool_destroy(gsmi_dev.dma_pool);
+       platform_device_unregister(gsmi_dev.pdev);
+       pr_info("gsmi: failed to load: %d\n", ret);
+       return ret;
+}
+
+static void __exit gsmi_exit(void)
+{
+       unregister_reboot_notifier(&gsmi_reboot_notifier);
+       unregister_die_notifier(&gsmi_die_notifier);
+       atomic_notifier_chain_unregister(&panic_notifier_list,
+                                        &gsmi_panic_notifier);
+       efivars_unregister(&efivars);
+
+       sysfs_remove_files(gsmi_kobj, gsmi_attrs);
+       sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
+       kobject_put(gsmi_kobj);
+       gsmi_buf_free(gsmi_dev.param_buf);
+       gsmi_buf_free(gsmi_dev.data_buf);
+       gsmi_buf_free(gsmi_dev.name_buf);
+       dma_pool_destroy(gsmi_dev.dma_pool);
+       platform_device_unregister(gsmi_dev.pdev);
+}
+
+module_init(gsmi_init);
+module_exit(gsmi_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");