v4.19.13 snapshot.
diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig
new file mode 100644
index 0000000..a456a00
--- /dev/null
+++ b/drivers/firmware/google/Kconfig
@@ -0,0 +1,82 @@
+menuconfig GOOGLE_FIRMWARE
+	bool "Google Firmware Drivers"
+	default n
+	help
+	  These firmware drivers are used by Google's servers.  They are
+	  only useful if you are working directly on one of their
+	  proprietary servers.  If in doubt, say "N".
+
+if GOOGLE_FIRMWARE
+
+config GOOGLE_SMI
+	tristate "SMI interface for Google platforms"
+	depends on X86 && ACPI && DMI && EFI
+	select EFI_VARS
+	help
+	  Say Y here if you want to enable SMI callbacks for Google
+	  platforms.  This provides an interface for writing to and
+	  clearing the EFI event log and reading and writing NVRAM
+	  variables.
+
+config GOOGLE_COREBOOT_TABLE
+	tristate
+	depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF
+
+config GOOGLE_COREBOOT_TABLE_ACPI
+	tristate "Coreboot Table Access - ACPI"
+	depends on ACPI
+	select GOOGLE_COREBOOT_TABLE
+	help
+	  This option enables the coreboot_table module, which provides other
+	  firmware modules to access to the coreboot table. The coreboot table
+	  pointer is accessed through the ACPI "GOOGCB00" object.
+	  If unsure say N.
+
+config GOOGLE_COREBOOT_TABLE_OF
+	tristate "Coreboot Table Access - Device Tree"
+	depends on OF
+	select GOOGLE_COREBOOT_TABLE
+	help
+	  This option enable the coreboot_table module, which provide other
+	  firmware modules to access coreboot table. The coreboot table pointer
+	  is accessed through the device tree node /firmware/coreboot.
+	  If unsure say N.
+
+config GOOGLE_MEMCONSOLE
+	tristate
+	depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT
+
+config GOOGLE_MEMCONSOLE_X86_LEGACY
+	tristate "Firmware Memory Console - X86 Legacy support"
+	depends on X86 && ACPI && DMI
+	select GOOGLE_MEMCONSOLE
+	help
+	  This option enables the kernel to search for a firmware log in
+	  the EBDA on Google servers.  If found, this log is exported to
+	  userland in the file /sys/firmware/log.
+
+config GOOGLE_FRAMEBUFFER_COREBOOT
+	tristate "Coreboot Framebuffer"
+	depends on FB_SIMPLE
+	depends on GOOGLE_COREBOOT_TABLE
+	help
+	  This option enables the kernel to search for a framebuffer in
+	  the coreboot table.  If found, it is registered with simplefb.
+
+config GOOGLE_MEMCONSOLE_COREBOOT
+	tristate "Firmware Memory Console"
+	depends on GOOGLE_COREBOOT_TABLE
+	select GOOGLE_MEMCONSOLE
+	help
+	  This option enables the kernel to search for a firmware log in
+	  the coreboot table.  If found, this log is exported to userland
+	  in the file /sys/firmware/log.
+
+config GOOGLE_VPD
+	tristate "Vital Product Data"
+	depends on GOOGLE_COREBOOT_TABLE
+	help
+	  This option enables the kernel to expose the content of Google VPD
+	  under /sys/firmware/vpd.
+
+endif # GOOGLE_FIRMWARE
diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile
new file mode 100644
index 0000000..d0b3fba
--- /dev/null
+++ b/drivers/firmware/google/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_GOOGLE_SMI)		+= gsmi.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE)        += coreboot_table.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI)   += coreboot_table-acpi.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF)     += coreboot_table-of.o
+obj-$(CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT)  += framebuffer-coreboot.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE)            += memconsole.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT)   += memconsole-coreboot.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o
+
+vpd-sysfs-y := vpd.o vpd_decode.o
+obj-$(CONFIG_GOOGLE_VPD)		+= vpd-sysfs.o
diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c
new file mode 100644
index 0000000..77197fe
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table-acpi.c
@@ -0,0 +1,88 @@
+/*
+ * coreboot_table-acpi.c
+ *
+ * Using ACPI to locate Coreboot table and provide coreboot table access.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "coreboot_table.h"
+
+static int coreboot_table_acpi_probe(struct platform_device *pdev)
+{
+	phys_addr_t phyaddr;
+	resource_size_t len;
+	struct coreboot_table_header __iomem *header = NULL;
+	struct resource *res;
+	void __iomem *ptr = NULL;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	len = resource_size(res);
+	if (!res->start || !len)
+		return -EINVAL;
+
+	phyaddr = res->start;
+	header = ioremap_cache(phyaddr, sizeof(*header));
+	if (header == NULL)
+		return -ENOMEM;
+
+	ptr = ioremap_cache(phyaddr,
+			    header->header_bytes + header->table_bytes);
+	iounmap(header);
+	if (!ptr)
+		return -ENOMEM;
+
+	return coreboot_table_init(&pdev->dev, ptr);
+}
+
+static int coreboot_table_acpi_remove(struct platform_device *pdev)
+{
+	return coreboot_table_exit();
+}
+
+static const struct acpi_device_id cros_coreboot_acpi_match[] = {
+	{ "GOOGCB00", 0 },
+	{ "BOOT0000", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
+
+static struct platform_driver coreboot_table_acpi_driver = {
+	.probe = coreboot_table_acpi_probe,
+	.remove = coreboot_table_acpi_remove,
+	.driver = {
+		.name = "coreboot_table_acpi",
+		.acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
+	},
+};
+
+static int __init coreboot_table_acpi_init(void)
+{
+	return platform_driver_register(&coreboot_table_acpi_driver);
+}
+
+module_init(coreboot_table_acpi_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c
new file mode 100644
index 0000000..f15bf40
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table-of.c
@@ -0,0 +1,82 @@
+/*
+ * coreboot_table-of.c
+ *
+ * Coreboot table access through open firmware.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include "coreboot_table.h"
+
+static int coreboot_table_of_probe(struct platform_device *pdev)
+{
+	struct device_node *fw_dn = pdev->dev.of_node;
+	void __iomem *ptr;
+
+	ptr = of_iomap(fw_dn, 0);
+	of_node_put(fw_dn);
+	if (!ptr)
+		return -ENOMEM;
+
+	return coreboot_table_init(&pdev->dev, ptr);
+}
+
+static int coreboot_table_of_remove(struct platform_device *pdev)
+{
+	return coreboot_table_exit();
+}
+
+static const struct of_device_id coreboot_of_match[] = {
+	{ .compatible = "coreboot" },
+	{},
+};
+
+static struct platform_driver coreboot_table_of_driver = {
+	.probe = coreboot_table_of_probe,
+	.remove = coreboot_table_of_remove,
+	.driver = {
+		.name = "coreboot_table_of",
+		.of_match_table = coreboot_of_match,
+	},
+};
+
+static int __init platform_coreboot_table_of_init(void)
+{
+	struct platform_device *pdev;
+	struct device_node *of_node;
+
+	/* Limit device creation to the presence of /firmware/coreboot node */
+	of_node = of_find_node_by_path("/firmware/coreboot");
+	if (!of_node)
+		return -ENODEV;
+
+	if (!of_match_node(coreboot_of_match, of_node))
+		return -ENODEV;
+
+	pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL);
+	if (!pdev)
+		return -ENODEV;
+
+	return platform_driver_register(&coreboot_table_of_driver);
+}
+
+module_init(platform_coreboot_table_of_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c
new file mode 100644
index 0000000..898bb9a
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table.c
@@ -0,0 +1,159 @@
+/*
+ * coreboot_table.c
+ *
+ * Module providing coreboot table access.
+ *
+ * Copyright 2017 Google Inc.
+ * Copyright 2017 Samuel Holland <samuel@sholland.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "coreboot_table.h"
+
+#define CB_DEV(d) container_of(d, struct coreboot_device, dev)
+#define CB_DRV(d) container_of(d, struct coreboot_driver, drv)
+
+static struct coreboot_table_header __iomem *ptr_header;
+
+static int coreboot_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct coreboot_device *device = CB_DEV(dev);
+	struct coreboot_driver *driver = CB_DRV(drv);
+
+	return device->entry.tag == driver->tag;
+}
+
+static int coreboot_bus_probe(struct device *dev)
+{
+	int ret = -ENODEV;
+	struct coreboot_device *device = CB_DEV(dev);
+	struct coreboot_driver *driver = CB_DRV(dev->driver);
+
+	if (driver->probe)
+		ret = driver->probe(device);
+
+	return ret;
+}
+
+static int coreboot_bus_remove(struct device *dev)
+{
+	int ret = 0;
+	struct coreboot_device *device = CB_DEV(dev);
+	struct coreboot_driver *driver = CB_DRV(dev->driver);
+
+	if (driver->remove)
+		ret = driver->remove(device);
+
+	return ret;
+}
+
+static struct bus_type coreboot_bus_type = {
+	.name		= "coreboot",
+	.match		= coreboot_bus_match,
+	.probe		= coreboot_bus_probe,
+	.remove		= coreboot_bus_remove,
+};
+
+static int __init coreboot_bus_init(void)
+{
+	return bus_register(&coreboot_bus_type);
+}
+module_init(coreboot_bus_init);
+
+static void coreboot_device_release(struct device *dev)
+{
+	struct coreboot_device *device = CB_DEV(dev);
+
+	kfree(device);
+}
+
+int coreboot_driver_register(struct coreboot_driver *driver)
+{
+	driver->drv.bus = &coreboot_bus_type;
+
+	return driver_register(&driver->drv);
+}
+EXPORT_SYMBOL(coreboot_driver_register);
+
+void coreboot_driver_unregister(struct coreboot_driver *driver)
+{
+	driver_unregister(&driver->drv);
+}
+EXPORT_SYMBOL(coreboot_driver_unregister);
+
+int coreboot_table_init(struct device *dev, void __iomem *ptr)
+{
+	int i, ret;
+	void *ptr_entry;
+	struct coreboot_device *device;
+	struct coreboot_table_entry entry;
+	struct coreboot_table_header header;
+
+	ptr_header = ptr;
+	memcpy_fromio(&header, ptr_header, sizeof(header));
+
+	if (strncmp(header.signature, "LBIO", sizeof(header.signature))) {
+		pr_warn("coreboot_table: coreboot table missing or corrupt!\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ptr_entry = (void *)ptr_header + header.header_bytes;
+	for (i = 0; i < header.table_entries; i++) {
+		memcpy_fromio(&entry, ptr_entry, sizeof(entry));
+
+		device = kzalloc(sizeof(struct device) + entry.size, GFP_KERNEL);
+		if (!device) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		dev_set_name(&device->dev, "coreboot%d", i);
+		device->dev.parent = dev;
+		device->dev.bus = &coreboot_bus_type;
+		device->dev.release = coreboot_device_release;
+		memcpy_fromio(&device->entry, ptr_entry, entry.size);
+
+		ret = device_register(&device->dev);
+		if (ret) {
+			put_device(&device->dev);
+			break;
+		}
+
+		ptr_entry += entry.size;
+	}
+out:
+	iounmap(ptr);
+	return ret;
+}
+EXPORT_SYMBOL(coreboot_table_init);
+
+int coreboot_table_exit(void)
+{
+	if (ptr_header) {
+		bus_unregister(&coreboot_bus_type);
+		ptr_header = NULL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(coreboot_table_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h
new file mode 100644
index 0000000..8ad95a9
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table.h
@@ -0,0 +1,100 @@
+/*
+ * coreboot_table.h
+ *
+ * Internal header for coreboot table access.
+ *
+ * Copyright 2014 Gerd Hoffmann <kraxel@redhat.com>
+ * Copyright 2017 Google Inc.
+ * Copyright 2017 Samuel Holland <samuel@sholland.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __COREBOOT_TABLE_H
+#define __COREBOOT_TABLE_H
+
+#include <linux/io.h>
+
+/* Coreboot table header structure */
+struct coreboot_table_header {
+	char signature[4];
+	u32 header_bytes;
+	u32 header_checksum;
+	u32 table_bytes;
+	u32 table_checksum;
+	u32 table_entries;
+};
+
+/* List of coreboot entry structures that is used */
+/* Generic */
+struct coreboot_table_entry {
+	u32 tag;
+	u32 size;
+};
+
+/* Points to a CBMEM entry */
+struct lb_cbmem_ref {
+	u32 tag;
+	u32 size;
+
+	u64 cbmem_addr;
+};
+
+/* Describes framebuffer setup by coreboot */
+struct lb_framebuffer {
+	u32 tag;
+	u32 size;
+
+	u64 physical_address;
+	u32 x_resolution;
+	u32 y_resolution;
+	u32 bytes_per_line;
+	u8  bits_per_pixel;
+	u8  red_mask_pos;
+	u8  red_mask_size;
+	u8  green_mask_pos;
+	u8  green_mask_size;
+	u8  blue_mask_pos;
+	u8  blue_mask_size;
+	u8  reserved_mask_pos;
+	u8  reserved_mask_size;
+};
+
+/* A device, additionally with information from coreboot. */
+struct coreboot_device {
+	struct device dev;
+	union {
+		struct coreboot_table_entry entry;
+		struct lb_cbmem_ref cbmem_ref;
+		struct lb_framebuffer framebuffer;
+	};
+};
+
+/* A driver for handling devices described in coreboot tables. */
+struct coreboot_driver {
+	int (*probe)(struct coreboot_device *);
+	int (*remove)(struct coreboot_device *);
+	struct device_driver drv;
+	u32 tag;
+};
+
+/* Register a driver that uses the data from a coreboot table. */
+int coreboot_driver_register(struct coreboot_driver *driver);
+
+/* Unregister a driver that uses the data from a coreboot table. */
+void coreboot_driver_unregister(struct coreboot_driver *driver);
+
+/* Initialize coreboot table module given a pointer to iomem */
+int coreboot_table_init(struct device *dev, void __iomem *ptr);
+
+/* Cleanup coreboot table module */
+int coreboot_table_exit(void);
+
+#endif /* __COREBOOT_TABLE_H */
diff --git a/drivers/firmware/google/framebuffer-coreboot.c b/drivers/firmware/google/framebuffer-coreboot.c
new file mode 100644
index 0000000..b8b49c0
--- /dev/null
+++ b/drivers/firmware/google/framebuffer-coreboot.c
@@ -0,0 +1,115 @@
+/*
+ * framebuffer-coreboot.c
+ *
+ * Memory based framebuffer accessed through coreboot table.
+ *
+ * Copyright 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Copyright 2017 Google Inc.
+ * Copyright 2017 Samuel Holland <samuel@sholland.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/platform_device.h>
+
+#include "coreboot_table.h"
+
+#define CB_TAG_FRAMEBUFFER 0x12
+
+static const struct simplefb_format formats[] = SIMPLEFB_FORMATS;
+
+static int framebuffer_probe(struct coreboot_device *dev)
+{
+	int i;
+	u32 length;
+	struct lb_framebuffer *fb = &dev->framebuffer;
+	struct platform_device *pdev;
+	struct resource res;
+	struct simplefb_platform_data pdata = {
+		.width = fb->x_resolution,
+		.height = fb->y_resolution,
+		.stride = fb->bytes_per_line,
+		.format = NULL,
+	};
+
+	for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+		if (fb->bits_per_pixel     == formats[i].bits_per_pixel &&
+		    fb->red_mask_pos       == formats[i].red.offset &&
+		    fb->red_mask_size      == formats[i].red.length &&
+		    fb->green_mask_pos     == formats[i].green.offset &&
+		    fb->green_mask_size    == formats[i].green.length &&
+		    fb->blue_mask_pos      == formats[i].blue.offset &&
+		    fb->blue_mask_size     == formats[i].blue.length &&
+		    fb->reserved_mask_pos  == formats[i].transp.offset &&
+		    fb->reserved_mask_size == formats[i].transp.length)
+			pdata.format = formats[i].name;
+	}
+	if (!pdata.format)
+		return -ENODEV;
+
+	memset(&res, 0, sizeof(res));
+	res.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+	res.name = "Coreboot Framebuffer";
+	res.start = fb->physical_address;
+	length = PAGE_ALIGN(fb->y_resolution * fb->bytes_per_line);
+	res.end = res.start + length - 1;
+	if (res.end <= res.start)
+		return -EINVAL;
+
+	pdev = platform_device_register_resndata(&dev->dev,
+						 "simple-framebuffer", 0,
+						 &res, 1, &pdata,
+						 sizeof(pdata));
+	if (IS_ERR(pdev))
+		pr_warn("coreboot: could not register framebuffer\n");
+	else
+		dev_set_drvdata(&dev->dev, pdev);
+
+	return PTR_ERR_OR_ZERO(pdev);
+}
+
+static int framebuffer_remove(struct coreboot_device *dev)
+{
+	struct platform_device *pdev = dev_get_drvdata(&dev->dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static struct coreboot_driver framebuffer_driver = {
+	.probe = framebuffer_probe,
+	.remove = framebuffer_remove,
+	.drv = {
+		.name = "framebuffer",
+	},
+	.tag = CB_TAG_FRAMEBUFFER,
+};
+
+static int __init coreboot_framebuffer_init(void)
+{
+	return coreboot_driver_register(&framebuffer_driver);
+}
+
+static void coreboot_framebuffer_exit(void)
+{
+	coreboot_driver_unregister(&framebuffer_driver);
+}
+
+module_init(coreboot_framebuffer_init);
+module_exit(coreboot_framebuffer_exit);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c
new file mode 100644
index 0000000..c8f169b
--- /dev/null
+++ b/drivers/firmware/google/gsmi.c
@@ -0,0 +1,941 @@
+/*
+ * 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 const struct dmi_system_id gsmi_dmi_table[] __initconst = {
+	{
+		.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);
+	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");
diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c
new file mode 100644
index 0000000..b29e107
--- /dev/null
+++ b/drivers/firmware/google/memconsole-coreboot.c
@@ -0,0 +1,134 @@
+/*
+ * memconsole-coreboot.c
+ *
+ * Memory based BIOS console accessed through coreboot table.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "memconsole.h"
+#include "coreboot_table.h"
+
+#define CB_TAG_CBMEM_CONSOLE	0x17
+
+/* CBMEM firmware console log descriptor. */
+struct cbmem_cons {
+	u32 size_dont_access_after_boot;
+	u32 cursor;
+	u8  body[0];
+} __packed;
+
+#define CURSOR_MASK ((1 << 28) - 1)
+#define OVERFLOW (1 << 31)
+
+static struct cbmem_cons __iomem *cbmem_console;
+static u32 cbmem_console_size;
+
+/*
+ * The cbmem_console structure is read again on every access because it may
+ * change at any time if runtime firmware logs new messages. This may rarely
+ * lead to race conditions where the firmware overwrites the beginning of the
+ * ring buffer with more lines after we have already read |cursor|. It should be
+ * rare and harmless enough that we don't spend extra effort working around it.
+ */
+static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count)
+{
+	u32 cursor = cbmem_console->cursor & CURSOR_MASK;
+	u32 flags = cbmem_console->cursor & ~CURSOR_MASK;
+	u32 size = cbmem_console_size;
+	struct seg {	/* describes ring buffer segments in logical order */
+		u32 phys;	/* physical offset from start of mem buffer */
+		u32 len;	/* length of segment */
+	} seg[2] = { {0}, {0} };
+	size_t done = 0;
+	int i;
+
+	if (flags & OVERFLOW) {
+		if (cursor > size)	/* Shouldn't really happen, but... */
+			cursor = 0;
+		seg[0] = (struct seg){.phys = cursor, .len = size - cursor};
+		seg[1] = (struct seg){.phys = 0, .len = cursor};
+	} else {
+		seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)};
+	}
+
+	for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) {
+		done += memory_read_from_buffer(buf + done, count - done, &pos,
+			cbmem_console->body + seg[i].phys, seg[i].len);
+		pos -= seg[i].len;
+	}
+	return done;
+}
+
+static int memconsole_probe(struct coreboot_device *dev)
+{
+	struct cbmem_cons __iomem *tmp_cbmc;
+
+	tmp_cbmc = memremap(dev->cbmem_ref.cbmem_addr,
+			    sizeof(*tmp_cbmc), MEMREMAP_WB);
+
+	if (!tmp_cbmc)
+		return -ENOMEM;
+
+	/* Read size only once to prevent overrun attack through /dev/mem. */
+	cbmem_console_size = tmp_cbmc->size_dont_access_after_boot;
+	cbmem_console = memremap(dev->cbmem_ref.cbmem_addr,
+				 cbmem_console_size + sizeof(*cbmem_console),
+				 MEMREMAP_WB);
+	memunmap(tmp_cbmc);
+
+	if (!cbmem_console)
+		return -ENOMEM;
+
+	memconsole_setup(memconsole_coreboot_read);
+
+	return memconsole_sysfs_init();
+}
+
+static int memconsole_remove(struct coreboot_device *dev)
+{
+	memconsole_exit();
+
+	if (cbmem_console)
+		memunmap(cbmem_console);
+
+	return 0;
+}
+
+static struct coreboot_driver memconsole_driver = {
+	.probe = memconsole_probe,
+	.remove = memconsole_remove,
+	.drv = {
+		.name = "memconsole",
+	},
+	.tag = CB_TAG_CBMEM_CONSOLE,
+};
+
+static void coreboot_memconsole_exit(void)
+{
+	coreboot_driver_unregister(&memconsole_driver);
+}
+
+static int __init coreboot_memconsole_init(void)
+{
+	return coreboot_driver_register(&memconsole_driver);
+}
+
+module_exit(coreboot_memconsole_exit);
+module_init(coreboot_memconsole_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c
new file mode 100644
index 0000000..19bcbd1
--- /dev/null
+++ b/drivers/firmware/google/memconsole-x86-legacy.c
@@ -0,0 +1,165 @@
+/*
+ * memconsole-x86-legacy.c
+ *
+ * EBDA specific parts of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/mm.h>
+#include <asm/bios_ebda.h>
+#include <linux/acpi.h>
+
+#include "memconsole.h"
+
+#define BIOS_MEMCONSOLE_V1_MAGIC	0xDEADBABE
+#define BIOS_MEMCONSOLE_V2_MAGIC	(('M')|('C'<<8)|('O'<<16)|('N'<<24))
+
+struct biosmemcon_ebda {
+	u32 signature;
+	union {
+		struct {
+			u8  enabled;
+			u32 buffer_addr;
+			u16 start;
+			u16 end;
+			u16 num_chars;
+			u8  wrapped;
+		} __packed v1;
+		struct {
+			u32 buffer_addr;
+			/* Misdocumented as number of pages! */
+			u16 num_bytes;
+			u16 start;
+			u16 end;
+		} __packed v2;
+	};
+} __packed;
+
+static char *memconsole_baseaddr;
+static size_t memconsole_length;
+
+static ssize_t memconsole_read(char *buf, loff_t pos, size_t count)
+{
+	return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
+				       memconsole_length);
+}
+
+static void found_v1_header(struct biosmemcon_ebda *hdr)
+{
+	pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n",
+		hdr);
+	pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n",
+		hdr->v1.buffer_addr, hdr->v1.start,
+		hdr->v1.end, hdr->v1.num_chars);
+
+	memconsole_baseaddr = phys_to_virt(hdr->v1.buffer_addr);
+	memconsole_length = hdr->v1.num_chars;
+	memconsole_setup(memconsole_read);
+}
+
+static void found_v2_header(struct biosmemcon_ebda *hdr)
+{
+	pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n",
+		hdr);
+	pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n",
+		hdr->v2.buffer_addr, hdr->v2.start,
+		hdr->v2.end, hdr->v2.num_bytes);
+
+	memconsole_baseaddr = phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start);
+	memconsole_length = hdr->v2.end - hdr->v2.start;
+	memconsole_setup(memconsole_read);
+}
+
+/*
+ * Search through the EBDA for the BIOS Memory Console, and
+ * set the global variables to point to it.  Return true if found.
+ */
+static bool memconsole_ebda_init(void)
+{
+	unsigned int address;
+	size_t length, cur;
+
+	address = get_bios_ebda();
+	if (!address) {
+		pr_info("memconsole: BIOS EBDA non-existent.\n");
+		return false;
+	}
+
+	/* EBDA length is byte 0 of EBDA (in KB) */
+	length = *(u8 *)phys_to_virt(address);
+	length <<= 10; /* convert to bytes */
+
+	/*
+	 * Search through EBDA for BIOS memory console structure
+	 * note: signature is not necessarily dword-aligned
+	 */
+	for (cur = 0; cur < length; cur++) {
+		struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
+
+		/* memconsole v1 */
+		if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
+			found_v1_header(hdr);
+			return true;
+		}
+
+		/* memconsole v2 */
+		if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
+			found_v2_header(hdr);
+			return true;
+		}
+	}
+
+	pr_info("memconsole: BIOS console EBDA structure not found!\n");
+	return false;
+}
+
+static const struct dmi_system_id memconsole_dmi_table[] __initconst = {
+	{
+		.ident = "Google Board",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
+		},
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
+
+static bool __init memconsole_find(void)
+{
+	if (!dmi_check_system(memconsole_dmi_table))
+		return false;
+
+	return memconsole_ebda_init();
+}
+
+static int __init memconsole_x86_init(void)
+{
+	if (!memconsole_find())
+		return -ENODEV;
+
+	return memconsole_sysfs_init();
+}
+
+static void __exit memconsole_x86_exit(void)
+{
+	memconsole_exit();
+}
+
+module_init(memconsole_x86_init);
+module_exit(memconsole_x86_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c
new file mode 100644
index 0000000..166f07c
--- /dev/null
+++ b/drivers/firmware/google/memconsole.c
@@ -0,0 +1,60 @@
+/*
+ * memconsole.c
+ *
+ * Architecture-independent parts of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+
+#include "memconsole.h"
+
+static ssize_t (*memconsole_read_func)(char *, loff_t, size_t);
+
+static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
+			       struct bin_attribute *bin_attr, char *buf,
+			       loff_t pos, size_t count)
+{
+	if (WARN_ON_ONCE(!memconsole_read_func))
+		return -EIO;
+	return memconsole_read_func(buf, pos, count);
+}
+
+static struct bin_attribute memconsole_bin_attr = {
+	.attr = {.name = "log", .mode = 0444},
+	.read = memconsole_read,
+};
+
+void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t))
+{
+	memconsole_read_func = read_func;
+}
+EXPORT_SYMBOL(memconsole_setup);
+
+int memconsole_sysfs_init(void)
+{
+	return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
+}
+EXPORT_SYMBOL(memconsole_sysfs_init);
+
+void memconsole_exit(void)
+{
+	sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
+}
+EXPORT_SYMBOL(memconsole_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h
new file mode 100644
index 0000000..ff1592d
--- /dev/null
+++ b/drivers/firmware/google/memconsole.h
@@ -0,0 +1,44 @@
+/*
+ * memconsole.h
+ *
+ * Internal headers of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H
+#define __FIRMWARE_GOOGLE_MEMCONSOLE_H
+
+#include <linux/types.h>
+
+/*
+ * memconsole_setup
+ *
+ * Initialize the memory console, passing the function to handle read accesses.
+ */
+void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t));
+
+/*
+ * memconsole_sysfs_init
+ *
+ * Update memory console length and create binary file
+ * for firmware object.
+ */
+int memconsole_sysfs_init(void);
+
+/* memconsole_exit
+ *
+ * Unmap the console buffer.
+ */
+void memconsole_exit(void);
+
+#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */
diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c
new file mode 100644
index 0000000..1aa67bb
--- /dev/null
+++ b/drivers/firmware/google/vpd.c
@@ -0,0 +1,342 @@
+/*
+ * vpd.c
+ *
+ * Driver for exporting VPD content to sysfs.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include "coreboot_table.h"
+#include "vpd_decode.h"
+
+#define CB_TAG_VPD      0x2c
+#define VPD_CBMEM_MAGIC 0x43524f53
+
+static struct kobject *vpd_kobj;
+
+struct vpd_cbmem {
+	u32 magic;
+	u32 version;
+	u32 ro_size;
+	u32 rw_size;
+	u8  blob[0];
+};
+
+struct vpd_section {
+	bool enabled;
+	const char *name;
+	char *raw_name;                /* the string name_raw */
+	struct kobject *kobj;          /* vpd/name directory */
+	char *baseaddr;
+	struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
+	struct list_head attribs;      /* key/value in vpd_attrib_info list */
+};
+
+struct vpd_attrib_info {
+	char *key;
+	const char *value;
+	struct bin_attribute bin_attr;
+	struct list_head list;
+};
+
+static struct vpd_section ro_vpd;
+static struct vpd_section rw_vpd;
+
+static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
+			       struct bin_attribute *bin_attr, char *buf,
+			       loff_t pos, size_t count)
+{
+	struct vpd_attrib_info *info = bin_attr->private;
+
+	return memory_read_from_buffer(buf, count, &pos, info->value,
+				       info->bin_attr.size);
+}
+
+/*
+ * vpd_section_check_key_name()
+ *
+ * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
+ * old firmware versions may have entries like "S/N" which are problematic when
+ * exporting them as sysfs attributes. These keys present in old firmwares are
+ * ignored.
+ *
+ * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
+ *
+ * @key: The key name to check
+ * @key_len: key name length
+ */
+static int vpd_section_check_key_name(const u8 *key, s32 key_len)
+{
+	int c;
+
+	while (key_len-- > 0) {
+		c = *key++;
+
+		if (!isalnum(c) && c != '_')
+			return VPD_FAIL;
+	}
+
+	return VPD_OK;
+}
+
+static int vpd_section_attrib_add(const u8 *key, s32 key_len,
+				  const u8 *value, s32 value_len,
+				  void *arg)
+{
+	int ret;
+	struct vpd_section *sec = arg;
+	struct vpd_attrib_info *info;
+
+	/*
+	 * Return VPD_OK immediately to decode next entry if the current key
+	 * name contains invalid characters.
+	 */
+	if (vpd_section_check_key_name(key, key_len) != VPD_OK)
+		return VPD_OK;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->key = kstrndup(key, key_len, GFP_KERNEL);
+	if (!info->key) {
+		ret = -ENOMEM;
+		goto free_info;
+	}
+
+	sysfs_bin_attr_init(&info->bin_attr);
+	info->bin_attr.attr.name = info->key;
+	info->bin_attr.attr.mode = 0444;
+	info->bin_attr.size = value_len;
+	info->bin_attr.read = vpd_attrib_read;
+	info->bin_attr.private = info;
+
+	info->value = value;
+
+	INIT_LIST_HEAD(&info->list);
+
+	ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
+	if (ret)
+		goto free_info_key;
+
+	list_add_tail(&info->list, &sec->attribs);
+	return 0;
+
+free_info_key:
+	kfree(info->key);
+free_info:
+	kfree(info);
+
+	return ret;
+}
+
+static void vpd_section_attrib_destroy(struct vpd_section *sec)
+{
+	struct vpd_attrib_info *info;
+	struct vpd_attrib_info *temp;
+
+	list_for_each_entry_safe(info, temp, &sec->attribs, list) {
+		sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
+		kfree(info->key);
+		kfree(info);
+	}
+}
+
+static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
+				struct bin_attribute *bin_attr, char *buf,
+				loff_t pos, size_t count)
+{
+	struct vpd_section *sec = bin_attr->private;
+
+	return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
+				       sec->bin_attr.size);
+}
+
+static int vpd_section_create_attribs(struct vpd_section *sec)
+{
+	s32 consumed;
+	int ret;
+
+	consumed = 0;
+	do {
+		ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
+					&consumed, vpd_section_attrib_add, sec);
+	} while (ret == VPD_OK);
+
+	return 0;
+}
+
+static int vpd_section_init(const char *name, struct vpd_section *sec,
+			    phys_addr_t physaddr, size_t size)
+{
+	int err;
+
+	sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
+	if (!sec->baseaddr)
+		return -ENOMEM;
+
+	sec->name = name;
+
+	/* We want to export the raw partion with name ${name}_raw */
+	sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
+	if (!sec->raw_name) {
+		err = -ENOMEM;
+		goto err_memunmap;
+	}
+
+	sysfs_bin_attr_init(&sec->bin_attr);
+	sec->bin_attr.attr.name = sec->raw_name;
+	sec->bin_attr.attr.mode = 0444;
+	sec->bin_attr.size = size;
+	sec->bin_attr.read = vpd_section_read;
+	sec->bin_attr.private = sec;
+
+	err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
+	if (err)
+		goto err_free_raw_name;
+
+	sec->kobj = kobject_create_and_add(name, vpd_kobj);
+	if (!sec->kobj) {
+		err = -EINVAL;
+		goto err_sysfs_remove;
+	}
+
+	INIT_LIST_HEAD(&sec->attribs);
+	vpd_section_create_attribs(sec);
+
+	sec->enabled = true;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+err_free_raw_name:
+	kfree(sec->raw_name);
+err_memunmap:
+	memunmap(sec->baseaddr);
+	return err;
+}
+
+static int vpd_section_destroy(struct vpd_section *sec)
+{
+	if (sec->enabled) {
+		vpd_section_attrib_destroy(sec);
+		kobject_put(sec->kobj);
+		sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+		kfree(sec->raw_name);
+		memunmap(sec->baseaddr);
+		sec->enabled = false;
+	}
+
+	return 0;
+}
+
+static int vpd_sections_init(phys_addr_t physaddr)
+{
+	struct vpd_cbmem __iomem *temp;
+	struct vpd_cbmem header;
+	int ret = 0;
+
+	temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
+	if (!temp)
+		return -ENOMEM;
+
+	memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
+	memunmap(temp);
+
+	if (header.magic != VPD_CBMEM_MAGIC)
+		return -ENODEV;
+
+	if (header.ro_size) {
+		ret = vpd_section_init("ro", &ro_vpd,
+				       physaddr + sizeof(struct vpd_cbmem),
+				       header.ro_size);
+		if (ret)
+			return ret;
+	}
+
+	if (header.rw_size) {
+		ret = vpd_section_init("rw", &rw_vpd,
+				       physaddr + sizeof(struct vpd_cbmem) +
+				       header.ro_size, header.rw_size);
+		if (ret) {
+			vpd_section_destroy(&ro_vpd);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int vpd_probe(struct coreboot_device *dev)
+{
+	int ret;
+
+	vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
+	if (!vpd_kobj)
+		return -ENOMEM;
+
+	ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);
+	if (ret) {
+		kobject_put(vpd_kobj);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int vpd_remove(struct coreboot_device *dev)
+{
+	vpd_section_destroy(&ro_vpd);
+	vpd_section_destroy(&rw_vpd);
+
+	kobject_put(vpd_kobj);
+
+	return 0;
+}
+
+static struct coreboot_driver vpd_driver = {
+	.probe = vpd_probe,
+	.remove = vpd_remove,
+	.drv = {
+		.name = "vpd",
+	},
+	.tag = CB_TAG_VPD,
+};
+
+static int __init coreboot_vpd_init(void)
+{
+	return coreboot_driver_register(&vpd_driver);
+}
+
+static void __exit coreboot_vpd_exit(void)
+{
+	coreboot_driver_unregister(&vpd_driver);
+}
+
+module_init(coreboot_vpd_init);
+module_exit(coreboot_vpd_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c
new file mode 100644
index 0000000..943acaa
--- /dev/null
+++ b/drivers/firmware/google/vpd_decode.c
@@ -0,0 +1,99 @@
+/*
+ * vpd_decode.c
+ *
+ * Google VPD decoding routines.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/export.h>
+
+#include "vpd_decode.h"
+
+static int vpd_decode_len(const s32 max_len, const u8 *in,
+			  s32 *length, s32 *decoded_len)
+{
+	u8 more;
+	int i = 0;
+
+	if (!length || !decoded_len)
+		return VPD_FAIL;
+
+	*length = 0;
+	do {
+		if (i >= max_len)
+			return VPD_FAIL;
+
+		more = in[i] & 0x80;
+		*length <<= 7;
+		*length |= in[i] & 0x7f;
+		++i;
+	} while (more);
+
+	*decoded_len = i;
+
+	return VPD_OK;
+}
+
+int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
+		      vpd_decode_callback callback, void *callback_arg)
+{
+	int type;
+	int res;
+	s32 key_len;
+	s32 value_len;
+	s32 decoded_len;
+	const u8 *key;
+	const u8 *value;
+
+	/* type */
+	if (*consumed >= max_len)
+		return VPD_FAIL;
+
+	type = input_buf[*consumed];
+
+	switch (type) {
+	case VPD_TYPE_INFO:
+	case VPD_TYPE_STRING:
+		(*consumed)++;
+
+		/* key */
+		res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
+				     &key_len, &decoded_len);
+		if (res != VPD_OK || *consumed + decoded_len >= max_len)
+			return VPD_FAIL;
+
+		*consumed += decoded_len;
+		key = &input_buf[*consumed];
+		*consumed += key_len;
+
+		/* value */
+		res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
+				     &value_len, &decoded_len);
+		if (res != VPD_OK || *consumed + decoded_len > max_len)
+			return VPD_FAIL;
+
+		*consumed += decoded_len;
+		value = &input_buf[*consumed];
+		*consumed += value_len;
+
+		if (type == VPD_TYPE_STRING)
+			return callback(key, key_len, value, value_len,
+					callback_arg);
+		break;
+
+	default:
+		return VPD_FAIL;
+	}
+
+	return VPD_OK;
+}
diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h
new file mode 100644
index 0000000..be3d62c
--- /dev/null
+++ b/drivers/firmware/google/vpd_decode.h
@@ -0,0 +1,58 @@
+/*
+ * vpd_decode.h
+ *
+ * Google VPD decoding routines.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __VPD_DECODE_H
+#define __VPD_DECODE_H
+
+#include <linux/types.h>
+
+enum {
+	VPD_OK = 0,
+	VPD_FAIL,
+};
+
+enum {
+	VPD_TYPE_TERMINATOR = 0,
+	VPD_TYPE_STRING,
+	VPD_TYPE_INFO                = 0xfe,
+	VPD_TYPE_IMPLICIT_TERMINATOR = 0xff,
+};
+
+/* Callback for vpd_decode_string to invoke. */
+typedef int vpd_decode_callback(const u8 *key, s32 key_len,
+				const u8 *value, s32 value_len,
+				void *arg);
+
+/*
+ * vpd_decode_string
+ *
+ * Given the encoded string, this function invokes callback with extracted
+ * (key, value). The *consumed will be plused the number of bytes consumed in
+ * this function.
+ *
+ * The input_buf points to the first byte of the input buffer.
+ *
+ * The *consumed starts from 0, which is actually the next byte to be decoded.
+ * It can be non-zero to be used in multiple calls.
+ *
+ * If one entry is successfully decoded, sends it to callback and returns the
+ * result.
+ */
+int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
+		      vpd_decode_callback callback, void *callback_arg);
+
+#endif  /* __VPD_DECODE_H */