// SPDX-License-Identifier: GPL-2.0-only /* * 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
*/
/* * Some platforms don't have explicit SMI handshake * and need to wait for SMI to complete.
*/ #define GSMI_DEFAULT_SPINCOUNT 0x10000 staticunsignedint 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.");
/* * 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.
*/ staticbool s0ix_logging_enable = true;
module_param(s0ix_logging_enable, bool, 0600);
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 = kmem_cache_alloc(gsmi_dev.mem_pool, GFP_KERNEL); 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;
}
staticvoid gsmi_buf_free(struct gsmi_buf *smibuf)
{ if (smibuf) { if (smibuf->start)
kmem_cache_free(gsmi_dev.mem_pool, smibuf->start);
kfree(smibuf);
}
}
/* * Make a call to gsmi func(sub). GSMI error codes are translated to * in-kernel errnos (0 on success, -ERRNO on error).
*/ staticint 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.
*/ asmvolatile ( "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"
);
} elseif (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) { /* * If handshake_type == HANDSHAKE_SPIN we spin a * hundred-ish usecs to ensure the SMI has triggered.
*/ asmvolatile ( "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.
*/ asmvolatile ( "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;
}
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;
} elseif (rc == 1) { /* variable was not found */
ret = EFI_NOT_FOUND;
} else { /* Get the arguments back */
memcpy(¶m, gsmi_dev.param_buf->start, sizeof(param));
/* The size reported is the min of all of our buffers */
*data_size = min_t(unsignedlong, *data_size,
gsmi_dev.data_buf->length);
*data_size = min_t(unsignedlong, *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 */ if (attr)
*attr = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS;
}
/* 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;
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;
} elseif (rc == 1) { /* variable not found -- end of list */
ret = EFI_NOT_FOUND;
} else { /* copy variable data back to return buffer */
memcpy(¶m, 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, ¶m.guid, sizeof(param.guid));
ret = EFI_SUCCESS;
}
/* Pull the type out */ if (count < sizeof(u32)) return -EINVAL;
param.type = *(u32 *)buf;
buf += sizeof(u32);
/* The remaining buffer is the data payload */ if ((count - sizeof(u32)) > 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);
/* * 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;
/* * 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 __init int gsmi_system_valid(void)
{
u32 hash;
u16 cmd, result;
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) { constchar *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;
}
/* 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; asmvolatile ( "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;
}
staticint 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;
}
staticint 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 __init int gsmi_init(void)
{ unsignedlong flags; int ret;
ret = gsmi_system_valid(); if (ret) return ret;
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_full(&gsmi_dev_info); if (IS_ERR(gsmi_dev.pdev)) {
printk(KERN_ERR "gsmi: unable to register platform device\n");
ret = PTR_ERR(gsmi_dev.pdev); goto out_unregister;
}
/* SMI access needs to be serialized */
spin_lock_init(&gsmi_dev.lock);
ret = -ENOMEM;
/* * 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;
/* * 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;
}
#ifdef CONFIG_EFI
ret = efivars_register(&efivars, &efivar_ops); if (ret) {
printk(KERN_INFO "gsmi: Failed to register efivars\n");
sysfs_remove_files(gsmi_kobj, gsmi_attrs); goto out_remove_bin_file;
} #endif
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.