summaryrefslogtreecommitdiff
path: root/drivers/firmware/google/gsmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/google/gsmi.c')
-rw-r--r--drivers/firmware/google/gsmi.c236
1 files changed, 193 insertions, 43 deletions
diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c
index acba0b9f4406..0ceccde5a302 100644
--- a/drivers/firmware/google/gsmi.c
+++ b/drivers/firmware/google/gsmi.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2010 Google Inc. All Rights Reserved.
* Author: dlaurie@google.com (Duncan Laurie)
@@ -16,9 +17,9 @@
#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/panic_notifier.h>
#include <linux/ioctl.h>
#include <linux/acpi.h>
#include <linux/io.h>
@@ -29,6 +30,7 @@
#include <linux/efi.h>
#include <linux/module.h>
#include <linux/ucs2_string.h>
+#include <linux/suspend.h>
#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */
/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
@@ -70,8 +72,11 @@
#define GSMI_CMD_SET_NVRAM_VAR 0x03
#define GSMI_CMD_SET_EVENT_LOG 0x08
#define GSMI_CMD_CLEAR_EVENT_LOG 0x09
+#define GSMI_CMD_LOG_S0IX_SUSPEND 0x0a
+#define GSMI_CMD_LOG_S0IX_RESUME 0x0b
#define GSMI_CMD_CLEAR_CONFIG 0x20
#define GSMI_CMD_HANDSHAKE_TYPE 0xC1
+#define GSMI_CMD_RESERVED 0xff
/* Magic entry type for kernel events */
#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD
@@ -80,11 +85,10 @@
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 {
+static 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 */
@@ -92,7 +96,7 @@ struct gsmi_device {
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 */
+ struct kmem_cache *mem_pool; /* kmem cache for gsmi_buf allocations */
} gsmi_dev;
/* Packed structures for communicating with the firmware */
@@ -122,7 +126,6 @@ struct gsmi_log_entry_type_1 {
u32 instance;
} __packed;
-
/*
* Some platforms don't have explicit SMI handshake
* and need to wait for SMI to complete.
@@ -133,6 +136,19 @@ module_param(spincount, uint, 0600);
MODULE_PARM_DESC(spincount,
"The number of loop iterations to use when using the spin handshake.");
+/*
+ * Some older platforms with Apollo Lake chipsets do not support S0ix logging
+ * in their GSMI handlers, and behaved poorly when resuming via power button
+ * press if the logging was attempted. Updated firmware with proper behavior
+ * has long since shipped, removing the need for this opt-in parameter. It
+ * now exists as an opt-out parameter for folks defiantly running old
+ * firmware, or unforeseen circumstances. After the change from opt-in to
+ * opt-out has baked sufficiently, this parameter should probably be removed
+ * entirely.
+ */
+static bool s0ix_logging_enable = true;
+module_param(s0ix_logging_enable, bool, 0600);
+
static struct gsmi_buf *gsmi_buf_alloc(void)
{
struct gsmi_buf *smibuf;
@@ -144,8 +160,7 @@ static struct gsmi_buf *gsmi_buf_alloc(void)
}
/* allocate buffer in 32bit address space */
- smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL,
- &smibuf->handle);
+ smibuf->start = kmem_cache_alloc(gsmi_dev.mem_pool, GFP_KERNEL);
if (!smibuf->start) {
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
kfree(smibuf);
@@ -163,8 +178,7 @@ 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);
+ kmem_cache_free(gsmi_dev.mem_pool, smibuf->start);
kfree(smibuf);
}
}
@@ -289,6 +303,10 @@ static int gsmi_exec(u8 func, u8 sub)
return rc;
}
+#ifdef CONFIG_EFI
+
+static struct efivars efivars;
+
static efi_status_t gsmi_get_variable(efi_char16_t *name,
efi_guid_t *vendor, u32 *attr,
unsigned long *data_size,
@@ -343,9 +361,10 @@ static efi_status_t gsmi_get_variable(efi_char16_t *name,
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;
+ if (attr)
+ *attr = EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS;
}
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
@@ -466,8 +485,10 @@ static const struct efivar_operations efivar_ops = {
.get_next_variable = gsmi_get_next_variable,
};
+#endif /* CONFIG_EFI */
+
static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
+ const struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct gsmi_set_eventlog_param param = {
@@ -480,11 +501,10 @@ static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
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)
+ if ((count - sizeof(u32)) > gsmi_dev.data_buf->length)
return -EINVAL;
param.data_len = count - sizeof(u32);
@@ -504,11 +524,11 @@ static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
- return rc;
+ return (rc == 0) ? count : rc;
}
-static struct bin_attribute eventlog_bin_attr = {
+static const struct bin_attribute eventlog_bin_attr = {
.attr = {.name = "append_to_eventlog", .mode = 0200},
.write = eventlog_write,
};
@@ -525,7 +545,7 @@ static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj,
u32 data_type;
} param;
- rc = strict_strtoul(buf, 0, &val);
+ rc = kstrtoul(buf, 0, &val);
if (rc)
return rc;
@@ -662,6 +682,15 @@ static struct notifier_block gsmi_die_notifier = {
static int gsmi_panic_callback(struct notifier_block *nb,
unsigned long reason, void *arg)
{
+
+ /*
+ * Panic callbacks are executed with all other CPUs stopped,
+ * so we must not attempt to spin waiting for gsmi_dev.lock
+ * to be released.
+ */
+ if (spin_is_locked(&gsmi_dev.lock))
+ return NOTIFY_DONE;
+
gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC);
return NOTIFY_DONE;
}
@@ -709,13 +738,19 @@ static u32 __init hash_oem_table_id(char s[8])
return local_hash_64(input, 32);
}
-static struct dmi_system_id gsmi_dmi_table[] __initdata = {
+static const struct dmi_system_id gsmi_dmi_table[] __initconst = {
{
.ident = "Google Board",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
},
},
+ {
+ .ident = "Coreboot Firmware",
+ .matches = {
+ DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
+ },
+ },
{}
};
MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
@@ -723,6 +758,7 @@ MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
static __init int gsmi_system_valid(void)
{
u32 hash;
+ u16 cmd, result;
if (!dmi_check_system(gsmi_dmi_table))
return -ENODEV;
@@ -757,12 +793,107 @@ static __init int gsmi_system_valid(void)
return -ENODEV;
}
+ /* Test the smihandler with a bogus command. If it leaves the
+ * calling argument in %ax untouched, there is no handler for
+ * GSMI commands.
+ */
+ cmd = GSMI_CALLBACK | GSMI_CMD_RESERVED << 8;
+ asm volatile (
+ "outb %%al, %%dx\n\t"
+ : "=a" (result)
+ : "0" (cmd),
+ "d" (acpi_gbl_FADT.smi_command)
+ : "memory", "cc"
+ );
+ if (cmd == result) {
+ pr_info("gsmi: no gsmi handler in firmware\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),
+};
+
+#ifdef CONFIG_PM
+static void gsmi_log_s0ix_info(u8 cmd)
+{
+ unsigned long flags;
+
+ /*
+ * If platform has not enabled S0ix logging, then no action is
+ * necessary.
+ */
+ if (!s0ix_logging_enable)
+ return;
+
+ spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+
+ gsmi_exec(GSMI_CALLBACK, cmd);
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+}
+
+static int gsmi_log_s0ix_suspend(struct device *dev)
+{
+ /*
+ * If system is not suspending via firmware using the standard ACPI Sx
+ * types, then make a GSMI call to log the suspend info.
+ */
+ if (!pm_suspend_via_firmware())
+ gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_SUSPEND);
+
+ /*
+ * Always return success, since we do not want suspend
+ * to fail just because of logging failure.
+ */
+ return 0;
+}
+
+static int gsmi_log_s0ix_resume(struct device *dev)
+{
+ /*
+ * If system did not resume via firmware, then make a GSMI call to log
+ * the resume info and wake source.
+ */
+ if (!pm_resume_via_firmware())
+ gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_RESUME);
+
+ /*
+ * Always return success, since we do not want resume
+ * to fail just because of logging failure.
+ */
+ return 0;
+}
+
+static const struct dev_pm_ops gsmi_pm_ops = {
+ .suspend_noirq = gsmi_log_s0ix_suspend,
+ .resume_noirq = gsmi_log_s0ix_resume,
+};
+
+static int gsmi_platform_driver_probe(struct platform_device *dev)
+{
+ return 0;
+}
+
+static struct platform_driver gsmi_driver_info = {
+ .driver = {
+ .name = "gsmi",
+ .pm = &gsmi_pm_ops,
+ },
+ .probe = gsmi_platform_driver_probe,
+};
+#endif
static __init int gsmi_init(void)
{
@@ -775,24 +906,40 @@ static __init int gsmi_init(void)
gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
+#ifdef CONFIG_PM
+ ret = platform_driver_register(&gsmi_driver_info);
+ if (unlikely(ret)) {
+ printk(KERN_ERR "gsmi: unable to register platform driver\n");
+ return ret;
+ }
+#endif
+
/* register device */
- gsmi_dev.pdev = platform_device_register_simple("gsmi", -1, NULL, 0);
+ 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);
+ ret = PTR_ERR(gsmi_dev.pdev);
+ goto out_unregister;
}
/* SMI access needs to be serialized */
spin_lock_init(&gsmi_dev.lock);
- /* SMI callbacks require 32bit addresses */
- gsmi_dev.pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
- gsmi_dev.pdev->dev.dma_mask =
- &gsmi_dev.pdev->dev.coherent_dma_mask;
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)
+
+ /*
+ * SLAB cache is created using SLAB_CACHE_DMA32 to ensure that the
+ * allocations for gsmi_buf come from the DMA32 memory zone. These
+ * buffers have nothing to do with DMA. They are required for
+ * communication with firmware executing in SMI mode which can only
+ * access the bottom 4GiB of physical memory. Since DMA32 memory zone
+ * guarantees allocation under the 4GiB boundary, this driver creates
+ * a SLAB cache with SLAB_CACHE_DMA32 flag.
+ */
+ gsmi_dev.mem_pool = kmem_cache_create("gsmi", GSMI_BUF_SIZE,
+ GSMI_BUF_ALIGN,
+ SLAB_CACHE_DMA32, NULL);
+ if (!gsmi_dev.mem_pool)
goto out_err;
/*
@@ -883,18 +1030,14 @@ static __init int gsmi_init(void)
goto out_remove_bin_file;
}
- ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj);
+#ifdef CONFIG_EFI
+ ret = efivars_register(&efivars, &efivar_ops);
if (ret) {
printk(KERN_INFO "gsmi: Failed to register efivars\n");
- goto out_remove_sysfs_files;
- }
-
- ret = efivars_sysfs_init();
- if (ret) {
- printk(KERN_INFO "gsmi: Failed to create efivars files\n");
- efivars_unregister(&efivars);
- goto out_remove_sysfs_files;
+ sysfs_remove_files(gsmi_kobj, gsmi_attrs);
+ goto out_remove_bin_file;
}
+#endif
register_reboot_notifier(&gsmi_reboot_notifier);
register_die_notifier(&gsmi_die_notifier);
@@ -905,8 +1048,6 @@ static __init int gsmi_init(void)
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:
@@ -914,9 +1055,12 @@ out_err:
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);
+ kmem_cache_destroy(gsmi_dev.mem_pool);
platform_device_unregister(gsmi_dev.pdev);
+out_unregister:
+#ifdef CONFIG_PM
+ platform_driver_unregister(&gsmi_driver_info);
+#endif
pr_info("gsmi: failed to load: %d\n", ret);
return ret;
}
@@ -927,7 +1071,9 @@ static void __exit gsmi_exit(void)
unregister_die_notifier(&gsmi_die_notifier);
atomic_notifier_chain_unregister(&panic_notifier_list,
&gsmi_panic_notifier);
+#ifdef CONFIG_EFI
efivars_unregister(&efivars);
+#endif
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
@@ -935,12 +1081,16 @@ static void __exit gsmi_exit(void)
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);
+ kmem_cache_destroy(gsmi_dev.mem_pool);
platform_device_unregister(gsmi_dev.pdev);
+#ifdef CONFIG_PM
+ platform_driver_unregister(&gsmi_driver_info);
+#endif
}
module_init(gsmi_init);
module_exit(gsmi_exit);
MODULE_AUTHOR("Google, Inc.");
+MODULE_DESCRIPTION("EFI SMI interface for Google platforms");
MODULE_LICENSE("GPL");