diff options
Diffstat (limited to 'drivers/firmware')
34 files changed, 12996 insertions, 0 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig new file mode 100644 index 000000000..6517132e5 --- /dev/null +++ b/drivers/firmware/Kconfig @@ -0,0 +1,142 @@ +# +# For a description of the syntax of this configuration file, +# see Documentation/kbuild/kconfig-language.txt. +# + +menu "Firmware Drivers" + +config EDD + tristate "BIOS Enhanced Disk Drive calls determine boot disk" + depends on X86 + help + Say Y or M here if you want to enable BIOS Enhanced Disk Drive + Services real mode BIOS calls to determine which disk + BIOS tries boot from. This information is then exported via sysfs. + + This option is experimental and is known to fail to boot on some + obscure configurations. Most disk controller BIOS vendors do + not yet implement this feature. + +config EDD_OFF + bool "Sets default behavior for EDD detection to off" + depends on EDD + default n + help + Say Y if you want EDD disabled by default, even though it is compiled into the + kernel. Say N if you want EDD enabled by default. EDD can be dynamically set + using the kernel parameter 'edd={on|skipmbr|off}'. + +config FIRMWARE_MEMMAP + bool "Add firmware-provided memory map to sysfs" if EXPERT + default X86 + help + Add the firmware-provided (unmodified) memory map to /sys/firmware/memmap. + That memory map is used for example by kexec to set up parameter area + for the next kernel, but can also be used for debugging purposes. + + See also Documentation/ABI/testing/sysfs-firmware-memmap. + +config EFI_PCDP + bool "Console device selection via EFI PCDP or HCDP table" + depends on ACPI && EFI && IA64 + default y if IA64 + help + If your firmware supplies the PCDP table, and you want to + automatically use the primary console device it describes + as the Linux console, say Y here. + + If your firmware supplies the HCDP table, and you want to + use the first serial port it describes as the Linux console, + say Y here. If your EFI ConOut path contains only a UART + device, it will become the console automatically. Otherwise, + you must specify the "console=hcdp" kernel boot argument. + + Neither the PCDP nor the HCDP affects naming of serial devices, + so a serial console may be /dev/ttyS0, /dev/ttyS1, etc, depending + on how the driver discovers devices. + + You must also enable the appropriate drivers (serial, VGA, etc.) + + See DIG64_HCDPv20_042804.pdf available from + <http://www.dig64.org/specifications/> + +config DELL_RBU + tristate "BIOS update support for DELL systems via sysfs" + depends on X86 + select FW_LOADER + select FW_LOADER_USER_HELPER + help + Say m if you want to have the option of updating the BIOS for your + DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) + supporting application to communicate with the BIOS regarding the new + image for the image update to take effect. + See <file:Documentation/dell_rbu.txt> for more details on the driver. + +config DCDBAS + tristate "Dell Systems Management Base Driver" + depends on X86 + help + The Dell Systems Management Base Driver provides a sysfs interface + for systems management software to perform System Management + Interrupts (SMIs) and Host Control Actions (system power cycle or + power off after OS shutdown) on certain Dell systems. + + See <file:Documentation/dcdbas.txt> for more details on the driver + and the Dell systems on which Dell systems management software makes + use of this driver. + + Say Y or M here to enable the driver for use by Dell systems + management software such as Dell OpenManage. + +config DMIID + bool "Export DMI identification via sysfs to userspace" + depends on DMI + default y + help + Say Y here if you want to query SMBIOS/DMI system identification + information from userspace through /sys/class/dmi/id/ or if you want + DMI-based module auto-loading. + +config DMI_SYSFS + tristate "DMI table support in sysfs" + depends on SYSFS && DMI + default n + help + Say Y or M here to enable the exporting of the raw DMI table + data via sysfs. This is useful for consuming the data without + requiring any access to /dev/mem at all. Tables are found + under /sys/firmware/dmi when this option is enabled and + loaded. + +config DMI_SCAN_MACHINE_NON_EFI_FALLBACK + bool + +config ISCSI_IBFT_FIND + bool "iSCSI Boot Firmware Table Attributes" + depends on X86 && ACPI + default n + help + This option enables the kernel to find the region of memory + in which the ISCSI Boot Firmware Table (iBFT) resides. This + is necessary for iSCSI Boot Firmware Table Attributes module to work + properly. + +config ISCSI_IBFT + tristate "iSCSI Boot Firmware Table Attributes module" + select ISCSI_BOOT_SYSFS + depends on ISCSI_IBFT_FIND && SCSI && SCSI_LOWLEVEL + default n + help + This option enables support for detection and exposing of iSCSI + Boot Firmware Table (iBFT) via sysfs to userspace. If you wish to + detect iSCSI boot parameters dynamically during system boot, say Y. + Otherwise, say N. + +config QCOM_SCM + bool + depends on ARM || ARM64 + +source "drivers/firmware/google/Kconfig" +source "drivers/firmware/efi/Kconfig" + +endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile new file mode 100644 index 000000000..3fdd39127 --- /dev/null +++ b/drivers/firmware/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the linux kernel. +# +obj-$(CONFIG_DMI) += dmi_scan.o +obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o +obj-$(CONFIG_EDD) += edd.o +obj-$(CONFIG_EFI_PCDP) += pcdp.o +obj-$(CONFIG_DELL_RBU) += dell_rbu.o +obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_DMIID) += dmi-id.o +obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o +obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o +obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o +obj-$(CONFIG_QCOM_SCM) += qcom_scm.o +CFLAGS_qcom_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) + +obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ +obj-$(CONFIG_EFI) += efi/ +obj-$(CONFIG_UEFI_CPER) += efi/ diff --git a/drivers/firmware/dcdbas.c b/drivers/firmware/dcdbas.c new file mode 100644 index 000000000..829eec895 --- /dev/null +++ b/drivers/firmware/dcdbas.c @@ -0,0 +1,649 @@ +/* + * dcdbas.c: Dell Systems Management Base Driver + * + * The Dell Systems Management Base Driver provides a sysfs interface for + * systems management software to perform System Management Interrupts (SMIs) + * and Host Control Actions (power cycle or power off after OS shutdown) on + * Dell systems. + * + * See Documentation/dcdbas.txt for more information. + * + * Copyright (C) 1995-2006 Dell 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/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mc146818rtc.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <asm/io.h> + +#include "dcdbas.h" + +#define DRIVER_NAME "dcdbas" +#define DRIVER_VERSION "5.6.0-3.2" +#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" + +static struct platform_device *dcdbas_pdev; + +static u8 *smi_data_buf; +static dma_addr_t smi_data_buf_handle; +static unsigned long smi_data_buf_size; +static u32 smi_data_buf_phys_addr; +static DEFINE_MUTEX(smi_data_lock); + +static unsigned int host_control_action; +static unsigned int host_control_smi_type; +static unsigned int host_control_on_shutdown; + +/** + * smi_data_buf_free: free SMI data buffer + */ +static void smi_data_buf_free(void) +{ + if (!smi_data_buf) + return; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, + smi_data_buf_handle); + smi_data_buf = NULL; + smi_data_buf_handle = 0; + smi_data_buf_phys_addr = 0; + smi_data_buf_size = 0; +} + +/** + * smi_data_buf_realloc: grow SMI data buffer if needed + */ +static int smi_data_buf_realloc(unsigned long size) +{ + void *buf; + dma_addr_t handle; + + if (smi_data_buf_size >= size) + return 0; + + if (size > MAX_SMI_DATA_BUF_SIZE) + return -EINVAL; + + /* new buffer is needed */ + buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); + if (!buf) { + dev_dbg(&dcdbas_pdev->dev, + "%s: failed to allocate memory size %lu\n", + __func__, size); + return -ENOMEM; + } + /* memory zeroed by dma_alloc_coherent */ + + if (smi_data_buf) + memcpy(buf, smi_data_buf, smi_data_buf_size); + + /* free any existing buffer */ + smi_data_buf_free(); + + /* set up new buffer for use */ + smi_data_buf = buf; + smi_data_buf_handle = handle; + smi_data_buf_phys_addr = (u32) virt_to_phys(buf); + smi_data_buf_size = size; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + return 0; +} + +static ssize_t smi_data_buf_phys_addr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%x\n", smi_data_buf_phys_addr); +} + +static ssize_t smi_data_buf_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", smi_data_buf_size); +} + +static ssize_t smi_data_buf_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long buf_size; + ssize_t ret; + + buf_size = simple_strtoul(buf, NULL, 10); + + /* make sure SMI data buffer is at least buf_size */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(buf_size); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + return count; +} + +static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + mutex_lock(&smi_data_lock); + ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, + smi_data_buf_size); + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + if ((pos + count) > MAX_SMI_DATA_BUF_SIZE) + return -EINVAL; + + mutex_lock(&smi_data_lock); + + ret = smi_data_buf_realloc(pos + count); + if (ret) + goto out; + + memcpy(smi_data_buf + pos, buf, count); + ret = count; +out: + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t host_control_action_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_action); +} + +static ssize_t host_control_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t ret; + + /* make sure buffer is available for host control command */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + host_control_action = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_smi_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_smi_type); +} + +static ssize_t host_control_smi_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_smi_type = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_on_shutdown_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_on_shutdown); +} + +static ssize_t host_control_on_shutdown_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_on_shutdown = simple_strtoul(buf, NULL, 10); + return count; +} + +/** + * dcdbas_smi_request: generate SMI request + * + * Called with smi_data_lock. + */ +int dcdbas_smi_request(struct smi_cmd *smi_cmd) +{ + cpumask_var_t old_mask; + int ret = 0; + + if (smi_cmd->magic != SMI_CMD_MAGIC) { + dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", + __func__); + return -EBADR; + } + + /* SMI requires CPU 0 */ + if (!alloc_cpumask_var(&old_mask, GFP_KERNEL)) + return -ENOMEM; + + cpumask_copy(old_mask, ¤t->cpus_allowed); + set_cpus_allowed_ptr(current, cpumask_of(0)); + if (smp_processor_id() != 0) { + dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", + __func__); + ret = -EBUSY; + goto out; + } + + /* generate SMI */ + /* inb to force posted write through and make SMI happen now */ + asm volatile ( + "outb %b0,%w1\n" + "inb %w1" + : /* no output args */ + : "a" (smi_cmd->command_code), + "d" (smi_cmd->command_address), + "b" (smi_cmd->ebx), + "c" (smi_cmd->ecx) + : "memory" + ); + +out: + set_cpus_allowed_ptr(current, old_mask); + free_cpumask_var(old_mask); + return ret; +} + +/** + * smi_request_store: + * + * The valid values are: + * 0: zero SMI data buffer + * 1: generate calling interface SMI + * 2: generate raw SMI + * + * User application writes smi_cmd to smi_data before telling driver + * to generate SMI. + */ +static ssize_t smi_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct smi_cmd *smi_cmd; + unsigned long val = simple_strtoul(buf, NULL, 10); + ssize_t ret; + + mutex_lock(&smi_data_lock); + + if (smi_data_buf_size < sizeof(struct smi_cmd)) { + ret = -ENODEV; + goto out; + } + smi_cmd = (struct smi_cmd *)smi_data_buf; + + switch (val) { + case 2: + /* Raw SMI */ + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 1: + /* Calling Interface SMI */ + smi_cmd->ebx = (u32) virt_to_phys(smi_cmd->command_buffer); + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 0: + memset(smi_data_buf, 0, smi_data_buf_size); + ret = count; + break; + default: + ret = -EINVAL; + break; + } + +out: + mutex_unlock(&smi_data_lock); + return ret; +} +EXPORT_SYMBOL(dcdbas_smi_request); + +/** + * host_control_smi: generate host control SMI + * + * Caller must set up the host control command in smi_data_buf. + */ +static int host_control_smi(void) +{ + struct apm_cmd *apm_cmd; + u8 *data; + unsigned long flags; + u32 num_ticks; + s8 cmd_status; + u8 index; + + apm_cmd = (struct apm_cmd *)smi_data_buf; + apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; + + switch (host_control_smi_type) { + case HC_SMITYPE_TYPE1: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1300_CMOS_CMD_STRUCT_PTR; + index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, + (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); + outb(*data, + (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); + } + + /* first set status to -1 as called by spec */ + cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; + outb((u8) cmd_status, PCAT_APM_STATUS_PORT); + + /* generate SMM call */ + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) + == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + case HC_SMITYPE_TYPE2: + case HC_SMITYPE_TYPE3: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1400_CMOS_CMD_STRUCT_PTR; + index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); + outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); + } + + /* generate SMM call */ + if (host_control_smi_type == HC_SMITYPE_TYPE3) + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + else + outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); + + /* restore RTC index pointer since it was written to above */ + CMOS_READ(RTC_REG_C); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* read control port back to serialize write */ + cmd_status = inb(PE1400_APM_CONTROL_PORT); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + default: + dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", + __func__, host_control_smi_type); + return -ENOSYS; + } + + return 0; +} + +/** + * dcdbas_host_control: initiate host control + * + * This function is called by the driver after the system has + * finished shutting down if the user application specified a + * host control action to perform on shutdown. It is safe to + * use smi_data_buf at this point because the system has finished + * shutting down and no userspace apps are running. + */ +static void dcdbas_host_control(void) +{ + struct apm_cmd *apm_cmd; + u8 action; + + if (host_control_action == HC_ACTION_NONE) + return; + + action = host_control_action; + host_control_action = HC_ACTION_NONE; + + if (!smi_data_buf) { + dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); + return; + } + + if (smi_data_buf_size < sizeof(struct apm_cmd)) { + dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", + __func__); + return; + } + + apm_cmd = (struct apm_cmd *)smi_data_buf; + + /* power off takes precedence */ + if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; + host_control_smi(); + } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; + host_control_smi(); + } +} + +/** + * dcdbas_reboot_notify: handle reboot notification for host control + */ +static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, + void *unused) +{ + switch (code) { + case SYS_DOWN: + case SYS_HALT: + case SYS_POWER_OFF: + if (host_control_on_shutdown) { + /* firmware is going to perform host control action */ + printk(KERN_WARNING "Please wait for shutdown " + "action to complete...\n"); + dcdbas_host_control(); + } + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block dcdbas_reboot_nb = { + .notifier_call = dcdbas_reboot_notify, + .next = NULL, + .priority = INT_MIN +}; + +static DCDBAS_BIN_ATTR_RW(smi_data); + +static struct bin_attribute *dcdbas_bin_attrs[] = { + &bin_attr_smi_data, + NULL +}; + +static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); +static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); +static DCDBAS_DEV_ATTR_WO(smi_request); +static DCDBAS_DEV_ATTR_RW(host_control_action); +static DCDBAS_DEV_ATTR_RW(host_control_smi_type); +static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); + +static struct attribute *dcdbas_dev_attrs[] = { + &dev_attr_smi_data_buf_size.attr, + &dev_attr_smi_data_buf_phys_addr.attr, + &dev_attr_smi_request.attr, + &dev_attr_host_control_action.attr, + &dev_attr_host_control_smi_type.attr, + &dev_attr_host_control_on_shutdown.attr, + NULL +}; + +static struct attribute_group dcdbas_attr_group = { + .attrs = dcdbas_dev_attrs, + .bin_attrs = dcdbas_bin_attrs, +}; + +static int dcdbas_probe(struct platform_device *dev) +{ + int error; + + host_control_action = HC_ACTION_NONE; + host_control_smi_type = HC_SMITYPE_NONE; + + dcdbas_pdev = dev; + + /* + * BIOS SMI calls require buffer addresses be in 32-bit address space. + * This is done by setting the DMA mask below. + */ + error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); + if (error) + return error; + + error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); + if (error) + return error; + + register_reboot_notifier(&dcdbas_reboot_nb); + + dev_info(&dev->dev, "%s (version %s)\n", + DRIVER_DESCRIPTION, DRIVER_VERSION); + + return 0; +} + +static int dcdbas_remove(struct platform_device *dev) +{ + unregister_reboot_notifier(&dcdbas_reboot_nb); + sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); + + return 0; +} + +static struct platform_driver dcdbas_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = dcdbas_probe, + .remove = dcdbas_remove, +}; + +static const struct platform_device_info dcdbas_dev_info __initconst = { + .name = DRIVER_NAME, + .id = -1, + .dma_mask = DMA_BIT_MASK(32), +}; + +static struct platform_device *dcdbas_pdev_reg; + +/** + * dcdbas_init: initialize driver + */ +static int __init dcdbas_init(void) +{ + int error; + + error = platform_driver_register(&dcdbas_driver); + if (error) + return error; + + dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); + if (IS_ERR(dcdbas_pdev_reg)) { + error = PTR_ERR(dcdbas_pdev_reg); + goto err_unregister_driver; + } + + return 0; + + err_unregister_driver: + platform_driver_unregister(&dcdbas_driver); + return error; +} + +/** + * dcdbas_exit: perform driver cleanup + */ +static void __exit dcdbas_exit(void) +{ + /* + * make sure functions that use dcdbas_pdev are called + * before platform_device_unregister + */ + unregister_reboot_notifier(&dcdbas_reboot_nb); + + /* + * We have to free the buffer here instead of dcdbas_remove + * because only in module exit function we can be sure that + * all sysfs attributes belonging to this module have been + * released. + */ + if (dcdbas_pdev) + smi_data_buf_free(); + platform_device_unregister(dcdbas_pdev_reg); + platform_driver_unregister(&dcdbas_driver); +} + +module_init(dcdbas_init); +module_exit(dcdbas_exit); + +MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Dell Inc."); +MODULE_LICENSE("GPL"); +/* Any System or BIOS claiming to be by Dell */ +MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); diff --git a/drivers/firmware/dcdbas.h b/drivers/firmware/dcdbas.h new file mode 100644 index 000000000..ca3cb0a54 --- /dev/null +++ b/drivers/firmware/dcdbas.h @@ -0,0 +1,107 @@ +/* + * dcdbas.h: Definitions for Dell Systems Management Base driver + * + * Copyright (C) 1995-2005 Dell 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 _DCDBAS_H_ +#define _DCDBAS_H_ + +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#define MAX_SMI_DATA_BUF_SIZE (256 * 1024) + +#define HC_ACTION_NONE (0) +#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1) +#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2) + +#define HC_SMITYPE_NONE (0) +#define HC_SMITYPE_TYPE1 (1) +#define HC_SMITYPE_TYPE2 (2) +#define HC_SMITYPE_TYPE3 (3) + +#define ESM_APM_CMD (0x0A0) +#define ESM_APM_POWER_CYCLE (0x10) +#define ESM_STATUS_CMD_UNSUCCESSFUL (-1) + +#define CMOS_BASE_PORT (0x070) +#define CMOS_PAGE1_INDEX_PORT (0) +#define CMOS_PAGE1_DATA_PORT (1) +#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2) +#define CMOS_PAGE2_DATA_PORT_PIIX4 (3) +#define PE1400_APM_CONTROL_PORT (0x0B0) +#define PCAT_APM_CONTROL_PORT (0x0B2) +#define PCAT_APM_STATUS_PORT (0x0B3) +#define PE1300_CMOS_CMD_STRUCT_PTR (0x38) +#define PE1400_CMOS_CMD_STRUCT_PTR (0x70) + +#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14) +#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16) + +#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000) +#define EXPIRED_TIMER (0) + +#define SMI_CMD_MAGIC (0x534D4931) + +#define DCDBAS_DEV_ATTR_RW(_name) \ + DEVICE_ATTR(_name,0600,_name##_show,_name##_store); + +#define DCDBAS_DEV_ATTR_RO(_name) \ + DEVICE_ATTR(_name,0400,_name##_show,NULL); + +#define DCDBAS_DEV_ATTR_WO(_name) \ + DEVICE_ATTR(_name,0200,NULL,_name##_store); + +#define DCDBAS_BIN_ATTR_RW(_name) \ +struct bin_attribute bin_attr_##_name = { \ + .attr = { .name = __stringify(_name), \ + .mode = 0600 }, \ + .read = _name##_read, \ + .write = _name##_write, \ +} + +struct smi_cmd { + __u32 magic; + __u32 ebx; + __u32 ecx; + __u16 command_address; + __u8 command_code; + __u8 reserved; + __u8 command_buffer[1]; +} __attribute__ ((packed)); + +struct apm_cmd { + __u8 command; + __s8 status; + __u16 reserved; + union { + struct { + __u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN]; + } __attribute__ ((packed)) shortreq; + + struct { + __u16 num_sg_entries; + struct { + __u32 size; + __u64 addr; + } __attribute__ ((packed)) + sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM]; + } __attribute__ ((packed)) longreq; + } __attribute__ ((packed)) parameters; +} __attribute__ ((packed)); + +int dcdbas_smi_request(struct smi_cmd *smi_cmd); + +#endif /* _DCDBAS_H_ */ + diff --git a/drivers/firmware/dell_rbu.c b/drivers/firmware/dell_rbu.c new file mode 100644 index 000000000..2f452f1f7 --- /dev/null +++ b/drivers/firmware/dell_rbu.c @@ -0,0 +1,745 @@ +/* + * dell_rbu.c + * Bios Update driver for Dell systems + * Author: Dell Inc + * Abhay Salunke <abhay_salunke@dell.com> + * + * Copyright (C) 2005 Dell Inc. + * + * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by + * creating entries in the /sys file systems on Linux 2.6 and higher + * kernels. The driver supports two mechanism to update the BIOS namely + * contiguous and packetized. Both these methods still require having some + * application to set the CMOS bit indicating the BIOS to update itself + * after a reboot. + * + * Contiguous method: + * This driver writes the incoming data in a monolithic image by allocating + * contiguous physical pages large enough to accommodate the incoming BIOS + * image size. + * + * Packetized method: + * The driver writes the incoming packet image by allocating a new packet + * on every time the packet data is written. This driver requires an + * application to break the BIOS image in to fixed sized packet chunks. + * + * See Documentation/dell_rbu.txt for more info. + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/blkdev.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> + +MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>"); +MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("3.2"); + +#define BIOS_SCAN_LIMIT 0xffffffff +#define MAX_IMAGE_LENGTH 16 +static struct _rbu_data { + void *image_update_buffer; + unsigned long image_update_buffer_size; + unsigned long bios_image_size; + int image_update_ordernum; + int dma_alloc; + spinlock_t lock; + unsigned long packet_read_count; + unsigned long num_packets; + unsigned long packetsize; + unsigned long imagesize; + int entry_created; +} rbu_data; + +static char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; +module_param_string(image_type, image_type, sizeof (image_type), 0); +MODULE_PARM_DESC(image_type, + "BIOS image type. choose- mono or packet or init"); + +static unsigned long allocation_floor = 0x100000; +module_param(allocation_floor, ulong, 0644); +MODULE_PARM_DESC(allocation_floor, + "Minimum address for allocations when using Packet mode"); + +struct packet_data { + struct list_head list; + size_t length; + void *data; + int ordernum; +}; + +static struct packet_data packet_data_head; + +static struct platform_device *rbu_device; +static int context; +static dma_addr_t dell_rbu_dmaaddr; + +static void init_packet_head(void) +{ + INIT_LIST_HEAD(&packet_data_head.list); + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.packetsize = 0; + rbu_data.imagesize = 0; +} + +static int create_packet(void *data, size_t length) +{ + struct packet_data *newpacket; + int ordernum = 0; + int retval = 0; + unsigned int packet_array_size = 0; + void **invalid_addr_packet_array = NULL; + void *packet_data_temp_buf = NULL; + unsigned int idx = 0; + + pr_debug("create_packet: entry \n"); + + if (!rbu_data.packetsize) { + pr_debug("create_packet: packetsize not specified\n"); + retval = -EINVAL; + goto out_noalloc; + } + + spin_unlock(&rbu_data.lock); + + newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); + + if (!newpacket) { + printk(KERN_WARNING + "dell_rbu:%s: failed to allocate new " + "packet\n", __func__); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_noalloc; + } + + ordernum = get_order(length); + + /* + * BIOS errata mean we cannot allocate packets below 1MB or they will + * be overwritten by BIOS. + * + * array to temporarily hold packets + * that are below the allocation floor + * + * NOTE: very simplistic because we only need the floor to be at 1MB + * due to BIOS errata. This shouldn't be used for higher floors + * or you will run out of mem trying to allocate the array. + */ + packet_array_size = max( + (unsigned int)(allocation_floor / rbu_data.packetsize), + (unsigned int)1); + invalid_addr_packet_array = kzalloc(packet_array_size * sizeof(void*), + GFP_KERNEL); + + if (!invalid_addr_packet_array) { + printk(KERN_WARNING + "dell_rbu:%s: failed to allocate " + "invalid_addr_packet_array \n", + __func__); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet; + } + + while (!packet_data_temp_buf) { + packet_data_temp_buf = (unsigned char *) + __get_free_pages(GFP_KERNEL, ordernum); + if (!packet_data_temp_buf) { + printk(KERN_WARNING + "dell_rbu:%s: failed to allocate new " + "packet\n", __func__); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet_array; + } + + if ((unsigned long)virt_to_phys(packet_data_temp_buf) + < allocation_floor) { + pr_debug("packet 0x%lx below floor at 0x%lx.\n", + (unsigned long)virt_to_phys( + packet_data_temp_buf), + allocation_floor); + invalid_addr_packet_array[idx++] = packet_data_temp_buf; + packet_data_temp_buf = NULL; + } + } + spin_lock(&rbu_data.lock); + + newpacket->data = packet_data_temp_buf; + + pr_debug("create_packet: newpacket at physical addr %lx\n", + (unsigned long)virt_to_phys(newpacket->data)); + + /* packets may not have fixed size */ + newpacket->length = length; + newpacket->ordernum = ordernum; + ++rbu_data.num_packets; + + /* initialize the newly created packet headers */ + INIT_LIST_HEAD(&newpacket->list); + list_add_tail(&newpacket->list, &packet_data_head.list); + + memcpy(newpacket->data, data, length); + + pr_debug("create_packet: exit \n"); + +out_alloc_packet_array: + /* always free packet array */ + for (;idx>0;idx--) { + pr_debug("freeing unused packet below floor 0x%lx.\n", + (unsigned long)virt_to_phys( + invalid_addr_packet_array[idx-1])); + free_pages((unsigned long)invalid_addr_packet_array[idx-1], + ordernum); + } + kfree(invalid_addr_packet_array); + +out_alloc_packet: + /* if error, free data */ + if (retval) + kfree(newpacket); + +out_noalloc: + return retval; +} + +static int packetize_data(const u8 *data, size_t length) +{ + int rc = 0; + int done = 0; + int packet_length; + u8 *temp; + u8 *end = (u8 *) data + length; + pr_debug("packetize_data: data length %zd\n", length); + if (!rbu_data.packetsize) { + printk(KERN_WARNING + "dell_rbu: packetsize not specified\n"); + return -EIO; + } + + temp = (u8 *) data; + + /* packetize the hunk */ + while (!done) { + if ((temp + rbu_data.packetsize) < end) + packet_length = rbu_data.packetsize; + else { + /* this is the last packet */ + packet_length = end - temp; + done = 1; + } + + if ((rc = create_packet(temp, packet_length))) + return rc; + + pr_debug("%p:%td\n", temp, (end - temp)); + temp += packet_length; + } + + rbu_data.imagesize = length; + + return rc; +} + +static int do_packet_read(char *data, struct list_head *ptemp_list, + int length, int bytes_read, int *list_read_count) +{ + void *ptemp_buf; + struct packet_data *newpacket = NULL; + int bytes_copied = 0; + int j = 0; + + newpacket = list_entry(ptemp_list, struct packet_data, list); + *list_read_count += newpacket->length; + + if (*list_read_count > bytes_read) { + /* point to the start of unread data */ + j = newpacket->length - (*list_read_count - bytes_read); + /* point to the offset in the packet buffer */ + ptemp_buf = (u8 *) newpacket->data + j; + /* + * check if there is enough room in + * * the incoming buffer + */ + if (length > (*list_read_count - bytes_read)) + /* + * copy what ever is there in this + * packet and move on + */ + bytes_copied = (*list_read_count - bytes_read); + else + /* copy the remaining */ + bytes_copied = length; + memcpy(data, ptemp_buf, bytes_copied); + } + return bytes_copied; +} + +static int packet_read_list(char *data, size_t * pread_length) +{ + struct list_head *ptemp_list; + int temp_count = 0; + int bytes_copied = 0; + int bytes_read = 0; + int remaining_bytes = 0; + char *pdest = data; + + /* check if we have any packets */ + if (0 == rbu_data.num_packets) + return -ENOMEM; + + remaining_bytes = *pread_length; + bytes_read = rbu_data.packet_read_count; + + ptemp_list = (&packet_data_head.list)->next; + while (!list_empty(ptemp_list)) { + bytes_copied = do_packet_read(pdest, ptemp_list, + remaining_bytes, bytes_read, &temp_count); + remaining_bytes -= bytes_copied; + bytes_read += bytes_copied; + pdest += bytes_copied; + /* + * check if we reached end of buffer before reaching the + * last packet + */ + if (remaining_bytes == 0) + break; + + ptemp_list = ptemp_list->next; + } + /*finally set the bytes read */ + *pread_length = bytes_read - rbu_data.packet_read_count; + rbu_data.packet_read_count = bytes_read; + return 0; +} + +static void packet_empty_list(void) +{ + struct list_head *ptemp_list; + struct list_head *pnext_list; + struct packet_data *newpacket; + + ptemp_list = (&packet_data_head.list)->next; + while (!list_empty(ptemp_list)) { + newpacket = + list_entry(ptemp_list, struct packet_data, list); + pnext_list = ptemp_list->next; + list_del(ptemp_list); + ptemp_list = pnext_list; + /* + * zero out the RBU packet memory before freeing + * to make sure there are no stale RBU packets left in memory + */ + memset(newpacket->data, 0, rbu_data.packetsize); + free_pages((unsigned long) newpacket->data, + newpacket->ordernum); + kfree(newpacket); + } + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.imagesize = 0; +} + +/* + * img_update_free: Frees the buffer allocated for storing BIOS image + * Always called with lock held and returned with lock held + */ +static void img_update_free(void) +{ + if (!rbu_data.image_update_buffer) + return; + /* + * zero out this buffer before freeing it to get rid of any stale + * BIOS image copied in memory. + */ + memset(rbu_data.image_update_buffer, 0, + rbu_data.image_update_buffer_size); + if (rbu_data.dma_alloc == 1) + dma_free_coherent(NULL, rbu_data.bios_image_size, + rbu_data.image_update_buffer, dell_rbu_dmaaddr); + else + free_pages((unsigned long) rbu_data.image_update_buffer, + rbu_data.image_update_ordernum); + + /* + * Re-initialize the rbu_data variables after a free + */ + rbu_data.image_update_ordernum = -1; + rbu_data.image_update_buffer = NULL; + rbu_data.image_update_buffer_size = 0; + rbu_data.bios_image_size = 0; + rbu_data.dma_alloc = 0; +} + +/* + * img_update_realloc: This function allocates the contiguous pages to + * accommodate the requested size of data. The memory address and size + * values are stored globally and on every call to this function the new + * size is checked to see if more data is required than the existing size. + * If true the previous memory is freed and new allocation is done to + * accommodate the new size. If the incoming size is less then than the + * already allocated size, then that memory is reused. This function is + * called with lock held and returns with lock held. + */ +static int img_update_realloc(unsigned long size) +{ + unsigned char *image_update_buffer = NULL; + unsigned long rc; + unsigned long img_buf_phys_addr; + int ordernum; + int dma_alloc = 0; + + /* + * check if the buffer of sufficient size has been + * already allocated + */ + if (rbu_data.image_update_buffer_size >= size) { + /* + * check for corruption + */ + if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { + printk(KERN_ERR "dell_rbu:%s: corruption " + "check failed\n", __func__); + return -EINVAL; + } + /* + * we have a valid pre-allocated buffer with + * sufficient size + */ + return 0; + } + + /* + * free any previously allocated buffer + */ + img_update_free(); + + spin_unlock(&rbu_data.lock); + + ordernum = get_order(size); + image_update_buffer = + (unsigned char *) __get_free_pages(GFP_KERNEL, ordernum); + + img_buf_phys_addr = + (unsigned long) virt_to_phys(image_update_buffer); + + if (img_buf_phys_addr > BIOS_SCAN_LIMIT) { + free_pages((unsigned long) image_update_buffer, ordernum); + ordernum = -1; + image_update_buffer = dma_alloc_coherent(NULL, size, + &dell_rbu_dmaaddr, GFP_KERNEL); + dma_alloc = 1; + } + + spin_lock(&rbu_data.lock); + + if (image_update_buffer != NULL) { + rbu_data.image_update_buffer = image_update_buffer; + rbu_data.image_update_buffer_size = size; + rbu_data.bios_image_size = + rbu_data.image_update_buffer_size; + rbu_data.image_update_ordernum = ordernum; + rbu_data.dma_alloc = dma_alloc; + rc = 0; + } else { + pr_debug("Not enough memory for image update:" + "size = %ld\n", size); + rc = -ENOMEM; + } + + return rc; +} + +static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) +{ + int retval; + size_t bytes_left; + size_t data_length; + char *ptempBuf = buffer; + + /* check to see if we have something to return */ + if (rbu_data.num_packets == 0) { + pr_debug("read_packet_data: no packets written\n"); + retval = -ENOMEM; + goto read_rbu_data_exit; + } + + if (pos > rbu_data.imagesize) { + retval = 0; + printk(KERN_WARNING "dell_rbu:read_packet_data: " + "data underrun\n"); + goto read_rbu_data_exit; + } + + bytes_left = rbu_data.imagesize - pos; + data_length = min(bytes_left, count); + + if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) + goto read_rbu_data_exit; + + if ((pos + count) > rbu_data.imagesize) { + rbu_data.packet_read_count = 0; + /* this was the last copy */ + retval = bytes_left; + } else + retval = count; + + read_rbu_data_exit: + return retval; +} + +static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) +{ + /* check to see if we have something to return */ + if ((rbu_data.image_update_buffer == NULL) || + (rbu_data.bios_image_size == 0)) { + pr_debug("read_rbu_data_mono: image_update_buffer %p ," + "bios_image_size %lu\n", + rbu_data.image_update_buffer, + rbu_data.bios_image_size); + return -ENOMEM; + } + + return memory_read_from_buffer(buffer, count, &pos, + rbu_data.image_update_buffer, rbu_data.bios_image_size); +} + +static ssize_t read_rbu_data(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + ssize_t ret_count = 0; + + spin_lock(&rbu_data.lock); + + if (!strcmp(image_type, "mono")) + ret_count = read_rbu_mono_data(buffer, pos, count); + else if (!strcmp(image_type, "packet")) + ret_count = read_packet_data(buffer, pos, count); + else + pr_debug("read_rbu_data: invalid image type specified\n"); + + spin_unlock(&rbu_data.lock); + return ret_count; +} + +static void callbackfn_rbu(const struct firmware *fw, void *context) +{ + rbu_data.entry_created = 0; + + if (!fw) + return; + + if (!fw->size) + goto out; + + spin_lock(&rbu_data.lock); + if (!strcmp(image_type, "mono")) { + if (!img_update_realloc(fw->size)) + memcpy(rbu_data.image_update_buffer, + fw->data, fw->size); + } else if (!strcmp(image_type, "packet")) { + /* + * we need to free previous packets if a + * new hunk of packets needs to be downloaded + */ + packet_empty_list(); + if (packetize_data(fw->data, fw->size)) + /* Incase something goes wrong when we are + * in middle of packetizing the data, we + * need to free up whatever packets might + * have been created before we quit. + */ + packet_empty_list(); + } else + pr_debug("invalid image type specified.\n"); + spin_unlock(&rbu_data.lock); + out: + release_firmware(fw); +} + +static ssize_t read_rbu_image_type(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) + size = scnprintf(buffer, count, "%s\n", image_type); + return size; +} + +static ssize_t write_rbu_image_type(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int rc = count; + int req_firm_rc = 0; + int i; + spin_lock(&rbu_data.lock); + /* + * Find the first newline or space + */ + for (i = 0; i < count; ++i) + if (buffer[i] == '\n' || buffer[i] == ' ') { + buffer[i] = '\0'; + break; + } + if (i == count) + buffer[count] = '\0'; + + if (strstr(buffer, "mono")) + strcpy(image_type, "mono"); + else if (strstr(buffer, "packet")) + strcpy(image_type, "packet"); + else if (strstr(buffer, "init")) { + /* + * If due to the user error the driver gets in a bad + * state where even though it is loaded , the + * /sys/class/firmware/dell_rbu entries are missing. + * to cover this situation the user can recreate entries + * by writing init to image_type. + */ + if (!rbu_data.entry_created) { + spin_unlock(&rbu_data.lock); + req_firm_rc = request_firmware_nowait(THIS_MODULE, + FW_ACTION_NOHOTPLUG, "dell_rbu", + &rbu_device->dev, GFP_KERNEL, &context, + callbackfn_rbu); + if (req_firm_rc) { + printk(KERN_ERR + "dell_rbu:%s request_firmware_nowait" + " failed %d\n", __func__, rc); + rc = -EIO; + } else + rbu_data.entry_created = 1; + + spin_lock(&rbu_data.lock); + } + } else { + printk(KERN_WARNING "dell_rbu: image_type is invalid\n"); + spin_unlock(&rbu_data.lock); + return -EINVAL; + } + + /* we must free all previous allocations */ + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + + return rc; +} + +static ssize_t read_rbu_packet_size(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) { + spin_lock(&rbu_data.lock); + size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); + spin_unlock(&rbu_data.lock); + } + return size; +} + +static ssize_t write_rbu_packet_size(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + unsigned long temp; + spin_lock(&rbu_data.lock); + packet_empty_list(); + sscanf(buffer, "%lu", &temp); + if (temp < 0xffffffff) + rbu_data.packetsize = temp; + + spin_unlock(&rbu_data.lock); + return count; +} + +static struct bin_attribute rbu_data_attr = { + .attr = {.name = "data", .mode = 0444}, + .read = read_rbu_data, +}; + +static struct bin_attribute rbu_image_type_attr = { + .attr = {.name = "image_type", .mode = 0644}, + .read = read_rbu_image_type, + .write = write_rbu_image_type, +}; + +static struct bin_attribute rbu_packet_size_attr = { + .attr = {.name = "packet_size", .mode = 0644}, + .read = read_rbu_packet_size, + .write = write_rbu_packet_size, +}; + +static int __init dcdrbu_init(void) +{ + int rc; + spin_lock_init(&rbu_data.lock); + + init_packet_head(); + rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); + if (IS_ERR(rbu_device)) { + printk(KERN_ERR + "dell_rbu:%s:platform_device_register_simple " + "failed\n", __func__); + return PTR_ERR(rbu_device); + } + + rc = sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_data_attr); + if (rc) + goto out_devreg; + rc = sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_image_type_attr); + if (rc) + goto out_data; + rc = sysfs_create_bin_file(&rbu_device->dev.kobj, + &rbu_packet_size_attr); + if (rc) + goto out_imtype; + + rbu_data.entry_created = 0; + return 0; + +out_imtype: + sysfs_remove_bin_file(&rbu_device->dev.kobj, &rbu_image_type_attr); +out_data: + sysfs_remove_bin_file(&rbu_device->dev.kobj, &rbu_data_attr); +out_devreg: + platform_device_unregister(rbu_device); + return rc; +} + +static __exit void dcdrbu_exit(void) +{ + spin_lock(&rbu_data.lock); + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + platform_device_unregister(rbu_device); +} + +module_exit(dcdrbu_exit); +module_init(dcdrbu_init); + +/* vim:noet:ts=8:sw=8 +*/ diff --git a/drivers/firmware/dmi-id.c b/drivers/firmware/dmi-id.c new file mode 100644 index 000000000..94a58a082 --- /dev/null +++ b/drivers/firmware/dmi-id.c @@ -0,0 +1,245 @@ +/* + * Export SMBIOS/DMI info via sysfs to userspace + * + * Copyright 2007, Lennart Poettering + * + * Licensed under GPLv2 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/device.h> +#include <linux/slab.h> + +struct dmi_device_attribute{ + struct device_attribute dev_attr; + int field; +}; +#define to_dmi_dev_attr(_dev_attr) \ + container_of(_dev_attr, struct dmi_device_attribute, dev_attr) + +static ssize_t sys_dmi_field_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + int field = to_dmi_dev_attr(attr)->field; + ssize_t len; + len = scnprintf(page, PAGE_SIZE, "%s\n", dmi_get_system_info(field)); + page[len-1] = '\n'; + return len; +} + +#define DMI_ATTR(_name, _mode, _show, _field) \ + { .dev_attr = __ATTR(_name, _mode, _show, NULL), \ + .field = _field } + +#define DEFINE_DMI_ATTR_WITH_SHOW(_name, _mode, _field) \ +static struct dmi_device_attribute sys_dmi_##_name##_attr = \ + DMI_ATTR(_name, _mode, sys_dmi_field_show, _field); + +DEFINE_DMI_ATTR_WITH_SHOW(bios_vendor, 0444, DMI_BIOS_VENDOR); +DEFINE_DMI_ATTR_WITH_SHOW(bios_version, 0444, DMI_BIOS_VERSION); +DEFINE_DMI_ATTR_WITH_SHOW(bios_date, 0444, DMI_BIOS_DATE); +DEFINE_DMI_ATTR_WITH_SHOW(sys_vendor, 0444, DMI_SYS_VENDOR); +DEFINE_DMI_ATTR_WITH_SHOW(product_name, 0444, DMI_PRODUCT_NAME); +DEFINE_DMI_ATTR_WITH_SHOW(product_version, 0444, DMI_PRODUCT_VERSION); +DEFINE_DMI_ATTR_WITH_SHOW(product_serial, 0400, DMI_PRODUCT_SERIAL); +DEFINE_DMI_ATTR_WITH_SHOW(product_uuid, 0400, DMI_PRODUCT_UUID); +DEFINE_DMI_ATTR_WITH_SHOW(board_vendor, 0444, DMI_BOARD_VENDOR); +DEFINE_DMI_ATTR_WITH_SHOW(board_name, 0444, DMI_BOARD_NAME); +DEFINE_DMI_ATTR_WITH_SHOW(board_version, 0444, DMI_BOARD_VERSION); +DEFINE_DMI_ATTR_WITH_SHOW(board_serial, 0400, DMI_BOARD_SERIAL); +DEFINE_DMI_ATTR_WITH_SHOW(board_asset_tag, 0444, DMI_BOARD_ASSET_TAG); +DEFINE_DMI_ATTR_WITH_SHOW(chassis_vendor, 0444, DMI_CHASSIS_VENDOR); +DEFINE_DMI_ATTR_WITH_SHOW(chassis_type, 0444, DMI_CHASSIS_TYPE); +DEFINE_DMI_ATTR_WITH_SHOW(chassis_version, 0444, DMI_CHASSIS_VERSION); +DEFINE_DMI_ATTR_WITH_SHOW(chassis_serial, 0400, DMI_CHASSIS_SERIAL); +DEFINE_DMI_ATTR_WITH_SHOW(chassis_asset_tag, 0444, DMI_CHASSIS_ASSET_TAG); + +static void ascii_filter(char *d, const char *s) +{ + /* Filter out characters we don't want to see in the modalias string */ + for (; *s; s++) + if (*s > ' ' && *s < 127 && *s != ':') + *(d++) = *s; + + *d = 0; +} + +static ssize_t get_modalias(char *buffer, size_t buffer_size) +{ + static const struct mafield { + const char *prefix; + int field; + } fields[] = { + { "bvn", DMI_BIOS_VENDOR }, + { "bvr", DMI_BIOS_VERSION }, + { "bd", DMI_BIOS_DATE }, + { "svn", DMI_SYS_VENDOR }, + { "pn", DMI_PRODUCT_NAME }, + { "pvr", DMI_PRODUCT_VERSION }, + { "rvn", DMI_BOARD_VENDOR }, + { "rn", DMI_BOARD_NAME }, + { "rvr", DMI_BOARD_VERSION }, + { "cvn", DMI_CHASSIS_VENDOR }, + { "ct", DMI_CHASSIS_TYPE }, + { "cvr", DMI_CHASSIS_VERSION }, + { NULL, DMI_NONE } + }; + + ssize_t l, left; + char *p; + const struct mafield *f; + + strcpy(buffer, "dmi"); + p = buffer + 3; left = buffer_size - 4; + + for (f = fields; f->prefix && left > 0; f++) { + const char *c; + char *t; + + c = dmi_get_system_info(f->field); + if (!c) + continue; + + t = kmalloc(strlen(c) + 1, GFP_KERNEL); + if (!t) + break; + ascii_filter(t, c); + l = scnprintf(p, left, ":%s%s", f->prefix, t); + kfree(t); + + p += l; + left -= l; + } + + p[0] = ':'; + p[1] = 0; + + return p - buffer + 1; +} + +static ssize_t sys_dmi_modalias_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + ssize_t r; + r = get_modalias(page, PAGE_SIZE-1); + page[r] = '\n'; + page[r+1] = 0; + return r+1; +} + +static struct device_attribute sys_dmi_modalias_attr = + __ATTR(modalias, 0444, sys_dmi_modalias_show, NULL); + +static struct attribute *sys_dmi_attributes[DMI_STRING_MAX+2]; + +static struct attribute_group sys_dmi_attribute_group = { + .attrs = sys_dmi_attributes, +}; + +static const struct attribute_group* sys_dmi_attribute_groups[] = { + &sys_dmi_attribute_group, + NULL +}; + +static int dmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + ssize_t len; + + if (add_uevent_var(env, "MODALIAS=")) + return -ENOMEM; + len = get_modalias(&env->buf[env->buflen - 1], + sizeof(env->buf) - env->buflen); + if (len >= (sizeof(env->buf) - env->buflen)) + return -ENOMEM; + env->buflen += len; + return 0; +} + +static struct class dmi_class = { + .name = "dmi", + .dev_release = (void(*)(struct device *)) kfree, + .dev_uevent = dmi_dev_uevent, +}; + +static struct device *dmi_dev; + +/* Initialization */ + +#define ADD_DMI_ATTR(_name, _field) \ + if (dmi_get_system_info(_field)) \ + sys_dmi_attributes[i++] = &sys_dmi_##_name##_attr.dev_attr.attr; + +/* In a separate function to keep gcc 3.2 happy - do NOT merge this in + dmi_id_init! */ +static void __init dmi_id_init_attr_table(void) +{ + int i; + + /* Not necessarily all DMI fields are available on all + * systems, hence let's built an attribute table of just + * what's available */ + i = 0; + ADD_DMI_ATTR(bios_vendor, DMI_BIOS_VENDOR); + ADD_DMI_ATTR(bios_version, DMI_BIOS_VERSION); + ADD_DMI_ATTR(bios_date, DMI_BIOS_DATE); + ADD_DMI_ATTR(sys_vendor, DMI_SYS_VENDOR); + ADD_DMI_ATTR(product_name, DMI_PRODUCT_NAME); + ADD_DMI_ATTR(product_version, DMI_PRODUCT_VERSION); + ADD_DMI_ATTR(product_serial, DMI_PRODUCT_SERIAL); + ADD_DMI_ATTR(product_uuid, DMI_PRODUCT_UUID); + ADD_DMI_ATTR(board_vendor, DMI_BOARD_VENDOR); + ADD_DMI_ATTR(board_name, DMI_BOARD_NAME); + ADD_DMI_ATTR(board_version, DMI_BOARD_VERSION); + ADD_DMI_ATTR(board_serial, DMI_BOARD_SERIAL); + ADD_DMI_ATTR(board_asset_tag, DMI_BOARD_ASSET_TAG); + ADD_DMI_ATTR(chassis_vendor, DMI_CHASSIS_VENDOR); + ADD_DMI_ATTR(chassis_type, DMI_CHASSIS_TYPE); + ADD_DMI_ATTR(chassis_version, DMI_CHASSIS_VERSION); + ADD_DMI_ATTR(chassis_serial, DMI_CHASSIS_SERIAL); + ADD_DMI_ATTR(chassis_asset_tag, DMI_CHASSIS_ASSET_TAG); + sys_dmi_attributes[i++] = &sys_dmi_modalias_attr.attr; +} + +static int __init dmi_id_init(void) +{ + int ret; + + if (!dmi_available) + return -ENODEV; + + dmi_id_init_attr_table(); + + ret = class_register(&dmi_class); + if (ret) + return ret; + + dmi_dev = kzalloc(sizeof(*dmi_dev), GFP_KERNEL); + if (!dmi_dev) { + ret = -ENOMEM; + goto fail_class_unregister; + } + + dmi_dev->class = &dmi_class; + dev_set_name(dmi_dev, "id"); + dmi_dev->groups = sys_dmi_attribute_groups; + + ret = device_register(dmi_dev); + if (ret) + goto fail_free_dmi_dev; + + return 0; + +fail_free_dmi_dev: + kfree(dmi_dev); +fail_class_unregister: + + class_unregister(&dmi_class); + + return ret; +} + +arch_initcall(dmi_id_init); diff --git a/drivers/firmware/dmi-sysfs.c b/drivers/firmware/dmi-sysfs.c new file mode 100644 index 000000000..e0f1cb3d3 --- /dev/null +++ b/drivers/firmware/dmi-sysfs.c @@ -0,0 +1,697 @@ +/* + * dmi-sysfs.c + * + * This module exports the DMI tables read-only to userspace through the + * sysfs file system. + * + * Data is currently found below + * /sys/firmware/dmi/... + * + * DMI attributes are presented in attribute files with names + * formatted using %d-%d, so that the first integer indicates the + * structure type (0-255), and the second field is the instance of that + * entry. + * + * Copyright 2011 Google, Inc. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kobject.h> +#include <linux/dmi.h> +#include <linux/capability.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/io.h> + +#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider + the top entry type is only 8 bits */ + +struct dmi_sysfs_entry { + struct dmi_header dh; + struct kobject kobj; + int instance; + int position; + struct list_head list; + struct kobject *child; +}; + +/* + * Global list of dmi_sysfs_entry. Even though this should only be + * manipulated at setup and teardown, the lazy nature of the kobject + * system means we get lazy removes. + */ +static LIST_HEAD(entry_list); +static DEFINE_SPINLOCK(entry_list_lock); + +/* dmi_sysfs_attribute - Top level attribute. used by all entries. */ +struct dmi_sysfs_attribute { + struct attribute attr; + ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf); +}; + +#define DMI_SYSFS_ATTR(_entry, _name) \ +struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \ + .attr = {.name = __stringify(_name), .mode = 0400}, \ + .show = dmi_sysfs_##_entry##_##_name, \ +} + +/* + * dmi_sysfs_mapped_attribute - Attribute where we require the entry be + * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops. + */ +struct dmi_sysfs_mapped_attribute { + struct attribute attr; + ssize_t (*show)(struct dmi_sysfs_entry *entry, + const struct dmi_header *dh, + char *buf); +}; + +#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \ +struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \ + .attr = {.name = __stringify(_name), .mode = 0400}, \ + .show = dmi_sysfs_##_entry##_##_name, \ +} + +/************************************************* + * Generic DMI entry support. + *************************************************/ +static void dmi_entry_free(struct kobject *kobj) +{ + kfree(kobj); +} + +static struct dmi_sysfs_entry *to_entry(struct kobject *kobj) +{ + return container_of(kobj, struct dmi_sysfs_entry, kobj); +} + +static struct dmi_sysfs_attribute *to_attr(struct attribute *attr) +{ + return container_of(attr, struct dmi_sysfs_attribute, attr); +} + +static ssize_t dmi_sysfs_attr_show(struct kobject *kobj, + struct attribute *_attr, char *buf) +{ + struct dmi_sysfs_entry *entry = to_entry(kobj); + struct dmi_sysfs_attribute *attr = to_attr(_attr); + + /* DMI stuff is only ever admin visible */ + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + return attr->show(entry, buf); +} + +static const struct sysfs_ops dmi_sysfs_attr_ops = { + .show = dmi_sysfs_attr_show, +}; + +typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *, + const struct dmi_header *dh, void *); + +struct find_dmi_data { + struct dmi_sysfs_entry *entry; + dmi_callback callback; + void *private; + int instance_countdown; + ssize_t ret; +}; + +static void find_dmi_entry_helper(const struct dmi_header *dh, + void *_data) +{ + struct find_dmi_data *data = _data; + struct dmi_sysfs_entry *entry = data->entry; + + /* Is this the entry we want? */ + if (dh->type != entry->dh.type) + return; + + if (data->instance_countdown != 0) { + /* try the next instance? */ + data->instance_countdown--; + return; + } + + /* + * Don't ever revisit the instance. Short circuit later + * instances by letting the instance_countdown run negative + */ + data->instance_countdown--; + + /* Found the entry */ + data->ret = data->callback(entry, dh, data->private); +} + +/* State for passing the read parameters through dmi_find_entry() */ +struct dmi_read_state { + char *buf; + loff_t pos; + size_t count; +}; + +static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry, + dmi_callback callback, void *private) +{ + struct find_dmi_data data = { + .entry = entry, + .callback = callback, + .private = private, + .instance_countdown = entry->instance, + .ret = -EIO, /* To signal the entry disappeared */ + }; + int ret; + + ret = dmi_walk(find_dmi_entry_helper, &data); + /* This shouldn't happen, but just in case. */ + if (ret) + return -EINVAL; + return data.ret; +} + +/* + * Calculate and return the byte length of the dmi entry identified by + * dh. This includes both the formatted portion as well as the + * unformatted string space, including the two trailing nul characters. + */ +static size_t dmi_entry_length(const struct dmi_header *dh) +{ + const char *p = (const char *)dh; + + p += dh->length; + + while (p[0] || p[1]) + p++; + + return 2 + p - (const char *)dh; +} + +/************************************************* + * Support bits for specialized DMI entry support + *************************************************/ +struct dmi_entry_attr_show_data { + struct attribute *attr; + char *buf; +}; + +static ssize_t dmi_entry_attr_show_helper(struct dmi_sysfs_entry *entry, + const struct dmi_header *dh, + void *_data) +{ + struct dmi_entry_attr_show_data *data = _data; + struct dmi_sysfs_mapped_attribute *attr; + + attr = container_of(data->attr, + struct dmi_sysfs_mapped_attribute, attr); + return attr->show(entry, dh, data->buf); +} + +static ssize_t dmi_entry_attr_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct dmi_entry_attr_show_data data = { + .attr = attr, + .buf = buf, + }; + /* Find the entry according to our parent and call the + * normalized show method hanging off of the attribute */ + return find_dmi_entry(to_entry(kobj->parent), + dmi_entry_attr_show_helper, &data); +} + +static const struct sysfs_ops dmi_sysfs_specialize_attr_ops = { + .show = dmi_entry_attr_show, +}; + +/************************************************* + * Specialized DMI entry support. + *************************************************/ + +/*** Type 15 - System Event Table ***/ + +#define DMI_SEL_ACCESS_METHOD_IO8 0x00 +#define DMI_SEL_ACCESS_METHOD_IO2x8 0x01 +#define DMI_SEL_ACCESS_METHOD_IO16 0x02 +#define DMI_SEL_ACCESS_METHOD_PHYS32 0x03 +#define DMI_SEL_ACCESS_METHOD_GPNV 0x04 + +struct dmi_system_event_log { + struct dmi_header header; + u16 area_length; + u16 header_start_offset; + u16 data_start_offset; + u8 access_method; + u8 status; + u32 change_token; + union { + struct { + u16 index_addr; + u16 data_addr; + } io; + u32 phys_addr32; + u16 gpnv_handle; + u32 access_method_address; + }; + u8 header_format; + u8 type_descriptors_supported_count; + u8 per_log_type_descriptor_length; + u8 supported_log_type_descriptos[0]; +} __packed; + +#define DMI_SYSFS_SEL_FIELD(_field) \ +static ssize_t dmi_sysfs_sel_##_field(struct dmi_sysfs_entry *entry, \ + const struct dmi_header *dh, \ + char *buf) \ +{ \ + struct dmi_system_event_log sel; \ + if (sizeof(sel) > dmi_entry_length(dh)) \ + return -EIO; \ + memcpy(&sel, dh, sizeof(sel)); \ + return sprintf(buf, "%u\n", sel._field); \ +} \ +static DMI_SYSFS_MAPPED_ATTR(sel, _field) + +DMI_SYSFS_SEL_FIELD(area_length); +DMI_SYSFS_SEL_FIELD(header_start_offset); +DMI_SYSFS_SEL_FIELD(data_start_offset); +DMI_SYSFS_SEL_FIELD(access_method); +DMI_SYSFS_SEL_FIELD(status); +DMI_SYSFS_SEL_FIELD(change_token); +DMI_SYSFS_SEL_FIELD(access_method_address); +DMI_SYSFS_SEL_FIELD(header_format); +DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count); +DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length); + +static struct attribute *dmi_sysfs_sel_attrs[] = { + &dmi_sysfs_attr_sel_area_length.attr, + &dmi_sysfs_attr_sel_header_start_offset.attr, + &dmi_sysfs_attr_sel_data_start_offset.attr, + &dmi_sysfs_attr_sel_access_method.attr, + &dmi_sysfs_attr_sel_status.attr, + &dmi_sysfs_attr_sel_change_token.attr, + &dmi_sysfs_attr_sel_access_method_address.attr, + &dmi_sysfs_attr_sel_header_format.attr, + &dmi_sysfs_attr_sel_type_descriptors_supported_count.attr, + &dmi_sysfs_attr_sel_per_log_type_descriptor_length.attr, + NULL, +}; + + +static struct kobj_type dmi_system_event_log_ktype = { + .release = dmi_entry_free, + .sysfs_ops = &dmi_sysfs_specialize_attr_ops, + .default_attrs = dmi_sysfs_sel_attrs, +}; + +typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel, + loff_t offset); + +static DEFINE_MUTEX(io_port_lock); + +static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel, + loff_t offset) +{ + u8 ret; + + mutex_lock(&io_port_lock); + outb((u8)offset, sel->io.index_addr); + ret = inb(sel->io.data_addr); + mutex_unlock(&io_port_lock); + return ret; +} + +static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel, + loff_t offset) +{ + u8 ret; + + mutex_lock(&io_port_lock); + outb((u8)offset, sel->io.index_addr); + outb((u8)(offset >> 8), sel->io.index_addr + 1); + ret = inb(sel->io.data_addr); + mutex_unlock(&io_port_lock); + return ret; +} + +static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel, + loff_t offset) +{ + u8 ret; + + mutex_lock(&io_port_lock); + outw((u16)offset, sel->io.index_addr); + ret = inb(sel->io.data_addr); + mutex_unlock(&io_port_lock); + return ret; +} + +static sel_io_reader sel_io_readers[] = { + [DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io, + [DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io, + [DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io, +}; + +static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry, + const struct dmi_system_event_log *sel, + char *buf, loff_t pos, size_t count) +{ + ssize_t wrote = 0; + + sel_io_reader io_reader = sel_io_readers[sel->access_method]; + + while (count && pos < sel->area_length) { + count--; + *(buf++) = io_reader(sel, pos++); + wrote++; + } + + return wrote; +} + +static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry, + const struct dmi_system_event_log *sel, + char *buf, loff_t pos, size_t count) +{ + u8 __iomem *mapped; + ssize_t wrote = 0; + + mapped = ioremap(sel->access_method_address, sel->area_length); + if (!mapped) + return -EIO; + + while (count && pos < sel->area_length) { + count--; + *(buf++) = readb(mapped + pos++); + wrote++; + } + + iounmap(mapped); + return wrote; +} + +static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry, + const struct dmi_header *dh, + void *_state) +{ + struct dmi_read_state *state = _state; + struct dmi_system_event_log sel; + + if (sizeof(sel) > dmi_entry_length(dh)) + return -EIO; + + memcpy(&sel, dh, sizeof(sel)); + + switch (sel.access_method) { + case DMI_SEL_ACCESS_METHOD_IO8: + case DMI_SEL_ACCESS_METHOD_IO2x8: + case DMI_SEL_ACCESS_METHOD_IO16: + return dmi_sel_raw_read_io(entry, &sel, state->buf, + state->pos, state->count); + case DMI_SEL_ACCESS_METHOD_PHYS32: + return dmi_sel_raw_read_phys32(entry, &sel, state->buf, + state->pos, state->count); + case DMI_SEL_ACCESS_METHOD_GPNV: + pr_info("dmi-sysfs: GPNV support missing.\n"); + return -EIO; + default: + pr_info("dmi-sysfs: Unknown access method %02x\n", + sel.access_method); + return -EIO; + } +} + +static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + struct dmi_sysfs_entry *entry = to_entry(kobj->parent); + struct dmi_read_state state = { + .buf = buf, + .pos = pos, + .count = count, + }; + + return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state); +} + +static struct bin_attribute dmi_sel_raw_attr = { + .attr = {.name = "raw_event_log", .mode = 0400}, + .read = dmi_sel_raw_read, +}; + +static int dmi_system_event_log(struct dmi_sysfs_entry *entry) +{ + int ret; + + entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL); + if (!entry->child) + return -ENOMEM; + ret = kobject_init_and_add(entry->child, + &dmi_system_event_log_ktype, + &entry->kobj, + "system_event_log"); + if (ret) + goto out_free; + + ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr); + if (ret) + goto out_del; + + return 0; + +out_del: + kobject_del(entry->child); +out_free: + kfree(entry->child); + return ret; +} + +/************************************************* + * Generic DMI entry support. + *************************************************/ + +static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) +{ + return sprintf(buf, "%d\n", entry->dh.length); +} + +static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) +{ + return sprintf(buf, "%d\n", entry->dh.handle); +} + +static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) +{ + return sprintf(buf, "%d\n", entry->dh.type); +} + +static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, + char *buf) +{ + return sprintf(buf, "%d\n", entry->instance); +} + +static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, + char *buf) +{ + return sprintf(buf, "%d\n", entry->position); +} + +static DMI_SYSFS_ATTR(entry, length); +static DMI_SYSFS_ATTR(entry, handle); +static DMI_SYSFS_ATTR(entry, type); +static DMI_SYSFS_ATTR(entry, instance); +static DMI_SYSFS_ATTR(entry, position); + +static struct attribute *dmi_sysfs_entry_attrs[] = { + &dmi_sysfs_attr_entry_length.attr, + &dmi_sysfs_attr_entry_handle.attr, + &dmi_sysfs_attr_entry_type.attr, + &dmi_sysfs_attr_entry_instance.attr, + &dmi_sysfs_attr_entry_position.attr, + NULL, +}; + +static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry, + const struct dmi_header *dh, + void *_state) +{ + struct dmi_read_state *state = _state; + size_t entry_length; + + entry_length = dmi_entry_length(dh); + + return memory_read_from_buffer(state->buf, state->count, + &state->pos, dh, entry_length); +} + +static ssize_t dmi_entry_raw_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + struct dmi_sysfs_entry *entry = to_entry(kobj); + struct dmi_read_state state = { + .buf = buf, + .pos = pos, + .count = count, + }; + + return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state); +} + +static const struct bin_attribute dmi_entry_raw_attr = { + .attr = {.name = "raw", .mode = 0400}, + .read = dmi_entry_raw_read, +}; + +static void dmi_sysfs_entry_release(struct kobject *kobj) +{ + struct dmi_sysfs_entry *entry = to_entry(kobj); + + spin_lock(&entry_list_lock); + list_del(&entry->list); + spin_unlock(&entry_list_lock); + kfree(entry); +} + +static struct kobj_type dmi_sysfs_entry_ktype = { + .release = dmi_sysfs_entry_release, + .sysfs_ops = &dmi_sysfs_attr_ops, + .default_attrs = dmi_sysfs_entry_attrs, +}; + +static struct kobject *dmi_kobj; +static struct kset *dmi_kset; + +/* Global count of all instances seen. Only for setup */ +static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; + +/* Global positional count of all entries seen. Only for setup */ +static int __initdata position_count; + +static void __init dmi_sysfs_register_handle(const struct dmi_header *dh, + void *_ret) +{ + struct dmi_sysfs_entry *entry; + int *ret = _ret; + + /* If a previous entry saw an error, short circuit */ + if (*ret) + return; + + /* Allocate and register a new entry into the entries set */ + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + *ret = -ENOMEM; + return; + } + + /* Set the key */ + memcpy(&entry->dh, dh, sizeof(*dh)); + entry->instance = instance_counts[dh->type]++; + entry->position = position_count++; + + entry->kobj.kset = dmi_kset; + *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL, + "%d-%d", dh->type, entry->instance); + + if (*ret) { + kfree(entry); + return; + } + + /* Thread on the global list for cleanup */ + spin_lock(&entry_list_lock); + list_add_tail(&entry->list, &entry_list); + spin_unlock(&entry_list_lock); + + /* Handle specializations by type */ + switch (dh->type) { + case DMI_ENTRY_SYSTEM_EVENT_LOG: + *ret = dmi_system_event_log(entry); + break; + default: + /* No specialization */ + break; + } + if (*ret) + goto out_err; + + /* Create the raw binary file to access the entry */ + *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr); + if (*ret) + goto out_err; + + return; +out_err: + kobject_put(entry->child); + kobject_put(&entry->kobj); + return; +} + +static void cleanup_entry_list(void) +{ + struct dmi_sysfs_entry *entry, *next; + + /* No locks, we are on our way out */ + list_for_each_entry_safe(entry, next, &entry_list, list) { + kobject_put(entry->child); + kobject_put(&entry->kobj); + } +} + +static int __init dmi_sysfs_init(void) +{ + int error = -ENOMEM; + int val; + + /* Set up our directory */ + dmi_kobj = kobject_create_and_add("dmi", firmware_kobj); + if (!dmi_kobj) + goto err; + + dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj); + if (!dmi_kset) + goto err; + + val = 0; + error = dmi_walk(dmi_sysfs_register_handle, &val); + if (error) + goto err; + if (val) { + error = val; + goto err; + } + + pr_debug("dmi-sysfs: loaded.\n"); + + return 0; +err: + cleanup_entry_list(); + kset_unregister(dmi_kset); + kobject_put(dmi_kobj); + return error; +} + +/* clean up everything. */ +static void __exit dmi_sysfs_exit(void) +{ + pr_debug("dmi-sysfs: unloading.\n"); + cleanup_entry_list(); + kset_unregister(dmi_kset); + kobject_del(dmi_kobj); + kobject_put(dmi_kobj); +} + +module_init(dmi_sysfs_init); +module_exit(dmi_sysfs_exit); + +MODULE_AUTHOR("Mike Waychison <mikew@google.com>"); +MODULE_DESCRIPTION("DMI sysfs support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/dmi_scan.c b/drivers/firmware/dmi_scan.c new file mode 100644 index 000000000..bba843c2b --- /dev/null +++ b/drivers/firmware/dmi_scan.c @@ -0,0 +1,945 @@ +#include <linux/types.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/dmi.h> +#include <linux/efi.h> +#include <linux/bootmem.h> +#include <linux/random.h> +#include <asm/dmi.h> +#include <asm/unaligned.h> + +/* + * DMI stands for "Desktop Management Interface". It is part + * of and an antecedent to, SMBIOS, which stands for System + * Management BIOS. See further: http://www.dmtf.org/standards + */ +static const char dmi_empty_string[] = " "; + +static u32 dmi_ver __initdata; +static u32 dmi_len; +static u16 dmi_num; +/* + * Catch too early calls to dmi_check_system(): + */ +static int dmi_initialized; + +/* DMI system identification string used during boot */ +static char dmi_ids_string[128] __initdata; + +static struct dmi_memdev_info { + const char *device; + const char *bank; + u16 handle; +} *dmi_memdev; +static int dmi_memdev_nr; + +static const char * __init dmi_string_nosave(const struct dmi_header *dm, u8 s) +{ + const u8 *bp = ((u8 *) dm) + dm->length; + + if (s) { + s--; + while (s > 0 && *bp) { + bp += strlen(bp) + 1; + s--; + } + + if (*bp != 0) { + size_t len = strlen(bp)+1; + size_t cmp_len = len > 8 ? 8 : len; + + if (!memcmp(bp, dmi_empty_string, cmp_len)) + return dmi_empty_string; + return bp; + } + } + + return ""; +} + +static const char * __init dmi_string(const struct dmi_header *dm, u8 s) +{ + const char *bp = dmi_string_nosave(dm, s); + char *str; + size_t len; + + if (bp == dmi_empty_string) + return dmi_empty_string; + + len = strlen(bp) + 1; + str = dmi_alloc(len); + if (str != NULL) + strcpy(str, bp); + + return str; +} + +/* + * We have to be cautious here. We have seen BIOSes with DMI pointers + * pointing to completely the wrong place for example + */ +static void dmi_table(u8 *buf, + void (*decode)(const struct dmi_header *, void *), + void *private_data) +{ + u8 *data = buf; + int i = 0; + + /* + * Stop when we have seen all the items the table claimed to have + * (SMBIOS < 3.0 only) OR we reach an end-of-table marker (SMBIOS + * >= 3.0 only) OR we run off the end of the table (should never + * happen but sometimes does on bogus implementations.) + */ + while ((!dmi_num || i < dmi_num) && + (data - buf + sizeof(struct dmi_header)) <= dmi_len) { + const struct dmi_header *dm = (const struct dmi_header *)data; + + /* + * We want to know the total length (formatted area and + * strings) before decoding to make sure we won't run off the + * table in dmi_decode or dmi_string + */ + data += dm->length; + while ((data - buf < dmi_len - 1) && (data[0] || data[1])) + data++; + if (data - buf < dmi_len - 1) + decode(dm, private_data); + + /* + * 7.45 End-of-Table (Type 127) [SMBIOS reference spec v3.0.0] + * For tables behind a 64-bit entry point, we have no item + * count and no exact table length, so stop on end-of-table + * marker. For tables behind a 32-bit entry point, we have + * seen OEM structures behind the end-of-table marker on + * some systems, so don't trust it. + */ + if (!dmi_num && dm->type == DMI_ENTRY_END_OF_TABLE) + break; + + data += 2; + i++; + } +} + +static phys_addr_t dmi_base; + +static int __init dmi_walk_early(void (*decode)(const struct dmi_header *, + void *)) +{ + u8 *buf; + + buf = dmi_early_remap(dmi_base, dmi_len); + if (buf == NULL) + return -1; + + dmi_table(buf, decode, NULL); + + add_device_randomness(buf, dmi_len); + + dmi_early_unmap(buf, dmi_len); + return 0; +} + +static int __init dmi_checksum(const u8 *buf, u8 len) +{ + u8 sum = 0; + int a; + + for (a = 0; a < len; a++) + sum += buf[a]; + + return sum == 0; +} + +static const char *dmi_ident[DMI_STRING_MAX]; +static LIST_HEAD(dmi_devices); +int dmi_available; + +/* + * Save a DMI string + */ +static void __init dmi_save_ident(const struct dmi_header *dm, int slot, + int string) +{ + const char *d = (const char *) dm; + const char *p; + + if (dmi_ident[slot]) + return; + + p = dmi_string(dm, d[string]); + if (p == NULL) + return; + + dmi_ident[slot] = p; +} + +static void __init dmi_save_uuid(const struct dmi_header *dm, int slot, + int index) +{ + const u8 *d = (u8 *) dm + index; + char *s; + int is_ff = 1, is_00 = 1, i; + + if (dmi_ident[slot]) + return; + + for (i = 0; i < 16 && (is_ff || is_00); i++) { + if (d[i] != 0x00) + is_00 = 0; + if (d[i] != 0xFF) + is_ff = 0; + } + + if (is_ff || is_00) + return; + + s = dmi_alloc(16*2+4+1); + if (!s) + return; + + /* + * As of version 2.6 of the SMBIOS specification, the first 3 fields of + * the UUID are supposed to be little-endian encoded. The specification + * says that this is the defacto standard. + */ + if (dmi_ver >= 0x020600) + sprintf(s, "%pUL", d); + else + sprintf(s, "%pUB", d); + + dmi_ident[slot] = s; +} + +static void __init dmi_save_type(const struct dmi_header *dm, int slot, + int index) +{ + const u8 *d = (u8 *) dm + index; + char *s; + + if (dmi_ident[slot]) + return; + + s = dmi_alloc(4); + if (!s) + return; + + sprintf(s, "%u", *d & 0x7F); + dmi_ident[slot] = s; +} + +static void __init dmi_save_one_device(int type, const char *name) +{ + struct dmi_device *dev; + + /* No duplicate device */ + if (dmi_find_device(type, name, NULL)) + return; + + dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1); + if (!dev) + return; + + dev->type = type; + strcpy((char *)(dev + 1), name); + dev->name = (char *)(dev + 1); + dev->device_data = NULL; + list_add(&dev->list, &dmi_devices); +} + +static void __init dmi_save_devices(const struct dmi_header *dm) +{ + int i, count = (dm->length - sizeof(struct dmi_header)) / 2; + + for (i = 0; i < count; i++) { + const char *d = (char *)(dm + 1) + (i * 2); + + /* Skip disabled device */ + if ((*d & 0x80) == 0) + continue; + + dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1))); + } +} + +static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm) +{ + int i, count = *(u8 *)(dm + 1); + struct dmi_device *dev; + + for (i = 1; i <= count; i++) { + const char *devname = dmi_string(dm, i); + + if (devname == dmi_empty_string) + continue; + + dev = dmi_alloc(sizeof(*dev)); + if (!dev) + break; + + dev->type = DMI_DEV_TYPE_OEM_STRING; + dev->name = devname; + dev->device_data = NULL; + + list_add(&dev->list, &dmi_devices); + } +} + +static void __init dmi_save_ipmi_device(const struct dmi_header *dm) +{ + struct dmi_device *dev; + void *data; + + data = dmi_alloc(dm->length); + if (data == NULL) + return; + + memcpy(data, dm, dm->length); + + dev = dmi_alloc(sizeof(*dev)); + if (!dev) + return; + + dev->type = DMI_DEV_TYPE_IPMI; + dev->name = "IPMI controller"; + dev->device_data = data; + + list_add_tail(&dev->list, &dmi_devices); +} + +static void __init dmi_save_dev_onboard(int instance, int segment, int bus, + int devfn, const char *name) +{ + struct dmi_dev_onboard *onboard_dev; + + onboard_dev = dmi_alloc(sizeof(*onboard_dev) + strlen(name) + 1); + if (!onboard_dev) + return; + + onboard_dev->instance = instance; + onboard_dev->segment = segment; + onboard_dev->bus = bus; + onboard_dev->devfn = devfn; + + strcpy((char *)&onboard_dev[1], name); + onboard_dev->dev.type = DMI_DEV_TYPE_DEV_ONBOARD; + onboard_dev->dev.name = (char *)&onboard_dev[1]; + onboard_dev->dev.device_data = onboard_dev; + + list_add(&onboard_dev->dev.list, &dmi_devices); +} + +static void __init dmi_save_extended_devices(const struct dmi_header *dm) +{ + const u8 *d = (u8 *) dm + 5; + + /* Skip disabled device */ + if ((*d & 0x80) == 0) + return; + + dmi_save_dev_onboard(*(d+1), *(u16 *)(d+2), *(d+4), *(d+5), + dmi_string_nosave(dm, *(d-1))); + dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d - 1))); +} + +static void __init count_mem_devices(const struct dmi_header *dm, void *v) +{ + if (dm->type != DMI_ENTRY_MEM_DEVICE) + return; + dmi_memdev_nr++; +} + +static void __init save_mem_devices(const struct dmi_header *dm, void *v) +{ + const char *d = (const char *)dm; + static int nr; + + if (dm->type != DMI_ENTRY_MEM_DEVICE) + return; + if (nr >= dmi_memdev_nr) { + pr_warn(FW_BUG "Too many DIMM entries in SMBIOS table\n"); + return; + } + dmi_memdev[nr].handle = get_unaligned(&dm->handle); + dmi_memdev[nr].device = dmi_string(dm, d[0x10]); + dmi_memdev[nr].bank = dmi_string(dm, d[0x11]); + nr++; +} + +void __init dmi_memdev_walk(void) +{ + if (!dmi_available) + return; + + if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) { + dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr); + if (dmi_memdev) + dmi_walk_early(save_mem_devices); + } +} + +/* + * Process a DMI table entry. Right now all we care about are the BIOS + * and machine entries. For 2.5 we should pull the smbus controller info + * out of here. + */ +static void __init dmi_decode(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0: /* BIOS Information */ + dmi_save_ident(dm, DMI_BIOS_VENDOR, 4); + dmi_save_ident(dm, DMI_BIOS_VERSION, 5); + dmi_save_ident(dm, DMI_BIOS_DATE, 8); + break; + case 1: /* System Information */ + dmi_save_ident(dm, DMI_SYS_VENDOR, 4); + dmi_save_ident(dm, DMI_PRODUCT_NAME, 5); + dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6); + dmi_save_ident(dm, DMI_PRODUCT_SERIAL, 7); + dmi_save_uuid(dm, DMI_PRODUCT_UUID, 8); + break; + case 2: /* Base Board Information */ + dmi_save_ident(dm, DMI_BOARD_VENDOR, 4); + dmi_save_ident(dm, DMI_BOARD_NAME, 5); + dmi_save_ident(dm, DMI_BOARD_VERSION, 6); + dmi_save_ident(dm, DMI_BOARD_SERIAL, 7); + dmi_save_ident(dm, DMI_BOARD_ASSET_TAG, 8); + break; + case 3: /* Chassis Information */ + dmi_save_ident(dm, DMI_CHASSIS_VENDOR, 4); + dmi_save_type(dm, DMI_CHASSIS_TYPE, 5); + dmi_save_ident(dm, DMI_CHASSIS_VERSION, 6); + dmi_save_ident(dm, DMI_CHASSIS_SERIAL, 7); + dmi_save_ident(dm, DMI_CHASSIS_ASSET_TAG, 8); + break; + case 10: /* Onboard Devices Information */ + dmi_save_devices(dm); + break; + case 11: /* OEM Strings */ + dmi_save_oem_strings_devices(dm); + break; + case 38: /* IPMI Device Information */ + dmi_save_ipmi_device(dm); + break; + case 41: /* Onboard Devices Extended Information */ + dmi_save_extended_devices(dm); + } +} + +static int __init print_filtered(char *buf, size_t len, const char *info) +{ + int c = 0; + const char *p; + + if (!info) + return c; + + for (p = info; *p; p++) + if (isprint(*p)) + c += scnprintf(buf + c, len - c, "%c", *p); + else + c += scnprintf(buf + c, len - c, "\\x%02x", *p & 0xff); + return c; +} + +static void __init dmi_format_ids(char *buf, size_t len) +{ + int c = 0; + const char *board; /* Board Name is optional */ + + c += print_filtered(buf + c, len - c, + dmi_get_system_info(DMI_SYS_VENDOR)); + c += scnprintf(buf + c, len - c, " "); + c += print_filtered(buf + c, len - c, + dmi_get_system_info(DMI_PRODUCT_NAME)); + + board = dmi_get_system_info(DMI_BOARD_NAME); + if (board) { + c += scnprintf(buf + c, len - c, "/"); + c += print_filtered(buf + c, len - c, board); + } + c += scnprintf(buf + c, len - c, ", BIOS "); + c += print_filtered(buf + c, len - c, + dmi_get_system_info(DMI_BIOS_VERSION)); + c += scnprintf(buf + c, len - c, " "); + c += print_filtered(buf + c, len - c, + dmi_get_system_info(DMI_BIOS_DATE)); +} + +/* + * Check for DMI/SMBIOS headers in the system firmware image. Any + * SMBIOS header must start 16 bytes before the DMI header, so take a + * 32 byte buffer and check for DMI at offset 16 and SMBIOS at offset + * 0. If the DMI header is present, set dmi_ver accordingly (SMBIOS + * takes precedence) and return 0. Otherwise return 1. + */ +static int __init dmi_present(const u8 *buf) +{ + u32 smbios_ver; + + if (memcmp(buf, "_SM_", 4) == 0 && + buf[5] < 32 && dmi_checksum(buf, buf[5])) { + smbios_ver = get_unaligned_be16(buf + 6); + + /* Some BIOS report weird SMBIOS version, fix that up */ + switch (smbios_ver) { + case 0x021F: + case 0x0221: + pr_debug("SMBIOS version fixup(2.%d->2.%d)\n", + smbios_ver & 0xFF, 3); + smbios_ver = 0x0203; + break; + case 0x0233: + pr_debug("SMBIOS version fixup(2.%d->2.%d)\n", 51, 6); + smbios_ver = 0x0206; + break; + } + } else { + smbios_ver = 0; + } + + buf += 16; + + if (memcmp(buf, "_DMI_", 5) == 0 && dmi_checksum(buf, 15)) { + if (smbios_ver) + dmi_ver = smbios_ver; + else + dmi_ver = (buf[14] & 0xF0) << 4 | (buf[14] & 0x0F); + dmi_num = get_unaligned_le16(buf + 12); + dmi_len = get_unaligned_le16(buf + 6); + dmi_base = get_unaligned_le32(buf + 8); + + if (dmi_walk_early(dmi_decode) == 0) { + if (smbios_ver) { + pr_info("SMBIOS %d.%d present.\n", + dmi_ver >> 8, dmi_ver & 0xFF); + } else { + pr_info("Legacy DMI %d.%d present.\n", + dmi_ver >> 8, dmi_ver & 0xFF); + } + dmi_ver <<= 8; + dmi_format_ids(dmi_ids_string, sizeof(dmi_ids_string)); + printk(KERN_DEBUG "DMI: %s\n", dmi_ids_string); + return 0; + } + } + + return 1; +} + +/* + * Check for the SMBIOS 3.0 64-bit entry point signature. Unlike the legacy + * 32-bit entry point, there is no embedded DMI header (_DMI_) in here. + */ +static int __init dmi_smbios3_present(const u8 *buf) +{ + if (memcmp(buf, "_SM3_", 5) == 0 && + buf[6] < 32 && dmi_checksum(buf, buf[6])) { + dmi_ver = get_unaligned_be32(buf + 6); + dmi_ver &= 0xFFFFFF; + dmi_num = 0; /* No longer specified */ + dmi_len = get_unaligned_le32(buf + 12); + dmi_base = get_unaligned_le64(buf + 16); + + if (dmi_walk_early(dmi_decode) == 0) { + pr_info("SMBIOS %d.%d.%d present.\n", + dmi_ver >> 16, (dmi_ver >> 8) & 0xFF, + dmi_ver & 0xFF); + dmi_format_ids(dmi_ids_string, sizeof(dmi_ids_string)); + pr_debug("DMI: %s\n", dmi_ids_string); + return 0; + } + } + return 1; +} + +void __init dmi_scan_machine(void) +{ + char __iomem *p, *q; + char buf[32]; + + if (efi_enabled(EFI_CONFIG_TABLES)) { + /* + * According to the DMTF SMBIOS reference spec v3.0.0, it is + * allowed to define both the 64-bit entry point (smbios3) and + * the 32-bit entry point (smbios), in which case they should + * either both point to the same SMBIOS structure table, or the + * table pointed to by the 64-bit entry point should contain a + * superset of the table contents pointed to by the 32-bit entry + * point (section 5.2) + * This implies that the 64-bit entry point should have + * precedence if it is defined and supported by the OS. If we + * have the 64-bit entry point, but fail to decode it, fall + * back to the legacy one (if available) + */ + if (efi.smbios3 != EFI_INVALID_TABLE_ADDR) { + p = dmi_early_remap(efi.smbios3, 32); + if (p == NULL) + goto error; + memcpy_fromio(buf, p, 32); + dmi_early_unmap(p, 32); + + if (!dmi_smbios3_present(buf)) { + dmi_available = 1; + goto out; + } + } + if (efi.smbios == EFI_INVALID_TABLE_ADDR) + goto error; + + /* This is called as a core_initcall() because it isn't + * needed during early boot. This also means we can + * iounmap the space when we're done with it. + */ + p = dmi_early_remap(efi.smbios, 32); + if (p == NULL) + goto error; + memcpy_fromio(buf, p, 32); + dmi_early_unmap(p, 32); + + if (!dmi_present(buf)) { + dmi_available = 1; + goto out; + } + } else if (IS_ENABLED(CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK)) { + p = dmi_early_remap(0xF0000, 0x10000); + if (p == NULL) + goto error; + + /* + * Iterate over all possible DMI header addresses q. + * Maintain the 32 bytes around q in buf. On the + * first iteration, substitute zero for the + * out-of-range bytes so there is no chance of falsely + * detecting an SMBIOS header. + */ + memset(buf, 0, 16); + for (q = p; q < p + 0x10000; q += 16) { + memcpy_fromio(buf + 16, q, 16); + if (!dmi_smbios3_present(buf) || !dmi_present(buf)) { + dmi_available = 1; + dmi_early_unmap(p, 0x10000); + goto out; + } + memcpy(buf, buf + 16, 16); + } + dmi_early_unmap(p, 0x10000); + } + error: + pr_info("DMI not present or invalid.\n"); + out: + dmi_initialized = 1; +} + +/** + * dmi_set_dump_stack_arch_desc - set arch description for dump_stack() + * + * Invoke dump_stack_set_arch_desc() with DMI system information so that + * DMI identifiers are printed out on task dumps. Arch boot code should + * call this function after dmi_scan_machine() if it wants to print out DMI + * identifiers on task dumps. + */ +void __init dmi_set_dump_stack_arch_desc(void) +{ + dump_stack_set_arch_desc("%s", dmi_ids_string); +} + +/** + * dmi_matches - check if dmi_system_id structure matches system DMI data + * @dmi: pointer to the dmi_system_id structure to check + */ +static bool dmi_matches(const struct dmi_system_id *dmi) +{ + int i; + + WARN(!dmi_initialized, KERN_ERR "dmi check: not initialized yet.\n"); + + for (i = 0; i < ARRAY_SIZE(dmi->matches); i++) { + int s = dmi->matches[i].slot; + if (s == DMI_NONE) + break; + if (dmi_ident[s]) { + if (!dmi->matches[i].exact_match && + strstr(dmi_ident[s], dmi->matches[i].substr)) + continue; + else if (dmi->matches[i].exact_match && + !strcmp(dmi_ident[s], dmi->matches[i].substr)) + continue; + } + + /* No match */ + return false; + } + return true; +} + +/** + * dmi_is_end_of_table - check for end-of-table marker + * @dmi: pointer to the dmi_system_id structure to check + */ +static bool dmi_is_end_of_table(const struct dmi_system_id *dmi) +{ + return dmi->matches[0].slot == DMI_NONE; +} + +/** + * dmi_check_system - check system DMI data + * @list: array of dmi_system_id structures to match against + * All non-null elements of the list must match + * their slot's (field index's) data (i.e., each + * list string must be a substring of the specified + * DMI slot's string data) to be considered a + * successful match. + * + * Walk the blacklist table running matching functions until someone + * returns non zero or we hit the end. Callback function is called for + * each successful match. Returns the number of matches. + */ +int dmi_check_system(const struct dmi_system_id *list) +{ + int count = 0; + const struct dmi_system_id *d; + + for (d = list; !dmi_is_end_of_table(d); d++) + if (dmi_matches(d)) { + count++; + if (d->callback && d->callback(d)) + break; + } + + return count; +} +EXPORT_SYMBOL(dmi_check_system); + +/** + * dmi_first_match - find dmi_system_id structure matching system DMI data + * @list: array of dmi_system_id structures to match against + * All non-null elements of the list must match + * their slot's (field index's) data (i.e., each + * list string must be a substring of the specified + * DMI slot's string data) to be considered a + * successful match. + * + * Walk the blacklist table until the first match is found. Return the + * pointer to the matching entry or NULL if there's no match. + */ +const struct dmi_system_id *dmi_first_match(const struct dmi_system_id *list) +{ + const struct dmi_system_id *d; + + for (d = list; !dmi_is_end_of_table(d); d++) + if (dmi_matches(d)) + return d; + + return NULL; +} +EXPORT_SYMBOL(dmi_first_match); + +/** + * dmi_get_system_info - return DMI data value + * @field: data index (see enum dmi_field) + * + * Returns one DMI data value, can be used to perform + * complex DMI data checks. + */ +const char *dmi_get_system_info(int field) +{ + return dmi_ident[field]; +} +EXPORT_SYMBOL(dmi_get_system_info); + +/** + * dmi_name_in_serial - Check if string is in the DMI product serial information + * @str: string to check for + */ +int dmi_name_in_serial(const char *str) +{ + int f = DMI_PRODUCT_SERIAL; + if (dmi_ident[f] && strstr(dmi_ident[f], str)) + return 1; + return 0; +} + +/** + * dmi_name_in_vendors - Check if string is in the DMI system or board vendor name + * @str: Case sensitive Name + */ +int dmi_name_in_vendors(const char *str) +{ + static int fields[] = { DMI_SYS_VENDOR, DMI_BOARD_VENDOR, DMI_NONE }; + int i; + for (i = 0; fields[i] != DMI_NONE; i++) { + int f = fields[i]; + if (dmi_ident[f] && strstr(dmi_ident[f], str)) + return 1; + } + return 0; +} +EXPORT_SYMBOL(dmi_name_in_vendors); + +/** + * dmi_find_device - find onboard device by type/name + * @type: device type or %DMI_DEV_TYPE_ANY to match all device types + * @name: device name string or %NULL to match all + * @from: previous device found in search, or %NULL for new search. + * + * Iterates through the list of known onboard devices. If a device is + * found with a matching @vendor and @device, a pointer to its device + * structure is returned. Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. + * If @from is not %NULL, searches continue from next device. + */ +const struct dmi_device *dmi_find_device(int type, const char *name, + const struct dmi_device *from) +{ + const struct list_head *head = from ? &from->list : &dmi_devices; + struct list_head *d; + + for (d = head->next; d != &dmi_devices; d = d->next) { + const struct dmi_device *dev = + list_entry(d, struct dmi_device, list); + + if (((type == DMI_DEV_TYPE_ANY) || (dev->type == type)) && + ((name == NULL) || (strcmp(dev->name, name) == 0))) + return dev; + } + + return NULL; +} +EXPORT_SYMBOL(dmi_find_device); + +/** + * dmi_get_date - parse a DMI date + * @field: data index (see enum dmi_field) + * @yearp: optional out parameter for the year + * @monthp: optional out parameter for the month + * @dayp: optional out parameter for the day + * + * The date field is assumed to be in the form resembling + * [mm[/dd]]/yy[yy] and the result is stored in the out + * parameters any or all of which can be omitted. + * + * If the field doesn't exist, all out parameters are set to zero + * and false is returned. Otherwise, true is returned with any + * invalid part of date set to zero. + * + * On return, year, month and day are guaranteed to be in the + * range of [0,9999], [0,12] and [0,31] respectively. + */ +bool dmi_get_date(int field, int *yearp, int *monthp, int *dayp) +{ + int year = 0, month = 0, day = 0; + bool exists; + const char *s, *y; + char *e; + + s = dmi_get_system_info(field); + exists = s; + if (!exists) + goto out; + + /* + * Determine year first. We assume the date string resembles + * mm/dd/yy[yy] but the original code extracted only the year + * from the end. Keep the behavior in the spirit of no + * surprises. + */ + y = strrchr(s, '/'); + if (!y) + goto out; + + y++; + year = simple_strtoul(y, &e, 10); + if (y != e && year < 100) { /* 2-digit year */ + year += 1900; + if (year < 1996) /* no dates < spec 1.0 */ + year += 100; + } + if (year > 9999) /* year should fit in %04d */ + year = 0; + + /* parse the mm and dd */ + month = simple_strtoul(s, &e, 10); + if (s == e || *e != '/' || !month || month > 12) { + month = 0; + goto out; + } + + s = e + 1; + day = simple_strtoul(s, &e, 10); + if (s == y || s == e || *e != '/' || day > 31) + day = 0; +out: + if (yearp) + *yearp = year; + if (monthp) + *monthp = month; + if (dayp) + *dayp = day; + return exists; +} +EXPORT_SYMBOL(dmi_get_date); + +/** + * dmi_walk - Walk the DMI table and get called back for every record + * @decode: Callback function + * @private_data: Private data to be passed to the callback function + * + * Returns -1 when the DMI table can't be reached, 0 on success. + */ +int dmi_walk(void (*decode)(const struct dmi_header *, void *), + void *private_data) +{ + u8 *buf; + + if (!dmi_available) + return -1; + + buf = dmi_remap(dmi_base, dmi_len); + if (buf == NULL) + return -1; + + dmi_table(buf, decode, private_data); + + dmi_unmap(buf); + return 0; +} +EXPORT_SYMBOL_GPL(dmi_walk); + +/** + * dmi_match - compare a string to the dmi field (if exists) + * @f: DMI field identifier + * @str: string to compare the DMI field to + * + * Returns true if the requested field equals to the str (including NULL). + */ +bool dmi_match(enum dmi_field f, const char *str) +{ + const char *info = dmi_get_system_info(f); + + if (info == NULL || str == NULL) + return info == str; + + return !strcmp(info, str); +} +EXPORT_SYMBOL_GPL(dmi_match); + +void dmi_memdev_name(u16 handle, const char **bank, const char **device) +{ + int n; + + if (dmi_memdev == NULL) + return; + + for (n = 0; n < dmi_memdev_nr; n++) { + if (handle == dmi_memdev[n].handle) { + *bank = dmi_memdev[n].bank; + *device = dmi_memdev[n].device; + break; + } + } +} +EXPORT_SYMBOL_GPL(dmi_memdev_name); diff --git a/drivers/firmware/edd.c b/drivers/firmware/edd.c new file mode 100644 index 000000000..e22957665 --- /dev/null +++ b/drivers/firmware/edd.c @@ -0,0 +1,801 @@ +/* + * linux/drivers/firmware/edd.c + * Copyright (C) 2002, 2003, 2004 Dell Inc. + * by Matt Domsch <Matt_Domsch@dell.com> + * disk signature by Matt Domsch, Andrew Wilks, and Sandeep K. Shandilya + * legacy CHS by Patrick J. LoPresti <patl@users.sourceforge.net> + * + * BIOS Enhanced Disk Drive Services (EDD) + * conformant to T13 Committee www.t13.org + * projects 1572D, 1484D, 1386D, 1226DT + * + * This code takes information provided by BIOS EDD calls + * fn41 - Check Extensions Present and + * fn48 - Get Device Parameters with EDD extensions + * made in setup.S, copied to safe structures in setup.c, + * and presents it in sysfs. + * + * Please see http://linux.dell.com/edd/results.html for + * the list of BIOSs which have been reported to implement EDD. + * + * 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/module.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/err.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/limits.h> +#include <linux/device.h> +#include <linux/pci.h> +#include <linux/blkdev.h> +#include <linux/edd.h> + +#define EDD_VERSION "0.16" +#define EDD_DATE "2004-Jun-25" + +MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>"); +MODULE_DESCRIPTION("sysfs interface to BIOS EDD information"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(EDD_VERSION); + +#define left (PAGE_SIZE - (p - buf) - 1) + +struct edd_device { + unsigned int index; + unsigned int mbr_signature; + struct edd_info *info; + struct kobject kobj; +}; + +struct edd_attribute { + struct attribute attr; + ssize_t(*show) (struct edd_device * edev, char *buf); + int (*test) (struct edd_device * edev); +}; + +/* forward declarations */ +static int edd_dev_is_type(struct edd_device *edev, const char *type); +static struct pci_dev *edd_get_pci_dev(struct edd_device *edev); + +static struct edd_device *edd_devices[EDD_MBR_SIG_MAX]; + +#define EDD_DEVICE_ATTR(_name,_mode,_show,_test) \ +struct edd_attribute edd_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .test = _test, \ +}; + +static int +edd_has_mbr_signature(struct edd_device *edev) +{ + return edev->index < min_t(unsigned char, edd.mbr_signature_nr, EDD_MBR_SIG_MAX); +} + +static int +edd_has_edd_info(struct edd_device *edev) +{ + return edev->index < min_t(unsigned char, edd.edd_info_nr, EDDMAXNR); +} + +static inline struct edd_info * +edd_dev_get_info(struct edd_device *edev) +{ + return edev->info; +} + +static inline void +edd_dev_set_info(struct edd_device *edev, int i) +{ + edev->index = i; + if (edd_has_mbr_signature(edev)) + edev->mbr_signature = edd.mbr_signature[i]; + if (edd_has_edd_info(edev)) + edev->info = &edd.edd_info[i]; +} + +#define to_edd_attr(_attr) container_of(_attr,struct edd_attribute,attr) +#define to_edd_device(obj) container_of(obj,struct edd_device,kobj) + +static ssize_t +edd_attr_show(struct kobject * kobj, struct attribute *attr, char *buf) +{ + struct edd_device *dev = to_edd_device(kobj); + struct edd_attribute *edd_attr = to_edd_attr(attr); + ssize_t ret = -EIO; + + if (edd_attr->show) + ret = edd_attr->show(dev, buf); + return ret; +} + +static const struct sysfs_ops edd_attr_ops = { + .show = edd_attr_show, +}; + +static ssize_t +edd_show_host_bus(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + int i; + + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + for (i = 0; i < 4; i++) { + if (isprint(info->params.host_bus_type[i])) { + p += scnprintf(p, left, "%c", info->params.host_bus_type[i]); + } else { + p += scnprintf(p, left, " "); + } + } + + if (!strncmp(info->params.host_bus_type, "ISA", 3)) { + p += scnprintf(p, left, "\tbase_address: %x\n", + info->params.interface_path.isa.base_address); + } else if (!strncmp(info->params.host_bus_type, "PCIX", 4) || + !strncmp(info->params.host_bus_type, "PCI", 3) || + !strncmp(info->params.host_bus_type, "XPRS", 4)) { + p += scnprintf(p, left, + "\t%02x:%02x.%d channel: %u\n", + info->params.interface_path.pci.bus, + info->params.interface_path.pci.slot, + info->params.interface_path.pci.function, + info->params.interface_path.pci.channel); + } else if (!strncmp(info->params.host_bus_type, "IBND", 4) || + !strncmp(info->params.host_bus_type, "HTPT", 4)) { + p += scnprintf(p, left, + "\tTBD: %llx\n", + info->params.interface_path.ibnd.reserved); + + } else { + p += scnprintf(p, left, "\tunknown: %llx\n", + info->params.interface_path.unknown.reserved); + } + return (p - buf); +} + +static ssize_t +edd_show_interface(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + int i; + + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + for (i = 0; i < 8; i++) { + if (isprint(info->params.interface_type[i])) { + p += scnprintf(p, left, "%c", info->params.interface_type[i]); + } else { + p += scnprintf(p, left, " "); + } + } + if (!strncmp(info->params.interface_type, "ATAPI", 5)) { + p += scnprintf(p, left, "\tdevice: %u lun: %u\n", + info->params.device_path.atapi.device, + info->params.device_path.atapi.lun); + } else if (!strncmp(info->params.interface_type, "ATA", 3)) { + p += scnprintf(p, left, "\tdevice: %u\n", + info->params.device_path.ata.device); + } else if (!strncmp(info->params.interface_type, "SCSI", 4)) { + p += scnprintf(p, left, "\tid: %u lun: %llu\n", + info->params.device_path.scsi.id, + info->params.device_path.scsi.lun); + } else if (!strncmp(info->params.interface_type, "USB", 3)) { + p += scnprintf(p, left, "\tserial_number: %llx\n", + info->params.device_path.usb.serial_number); + } else if (!strncmp(info->params.interface_type, "1394", 4)) { + p += scnprintf(p, left, "\teui: %llx\n", + info->params.device_path.i1394.eui); + } else if (!strncmp(info->params.interface_type, "FIBRE", 5)) { + p += scnprintf(p, left, "\twwid: %llx lun: %llx\n", + info->params.device_path.fibre.wwid, + info->params.device_path.fibre.lun); + } else if (!strncmp(info->params.interface_type, "I2O", 3)) { + p += scnprintf(p, left, "\tidentity_tag: %llx\n", + info->params.device_path.i2o.identity_tag); + } else if (!strncmp(info->params.interface_type, "RAID", 4)) { + p += scnprintf(p, left, "\tidentity_tag: %x\n", + info->params.device_path.raid.array_number); + } else if (!strncmp(info->params.interface_type, "SATA", 4)) { + p += scnprintf(p, left, "\tdevice: %u\n", + info->params.device_path.sata.device); + } else { + p += scnprintf(p, left, "\tunknown: %llx %llx\n", + info->params.device_path.unknown.reserved1, + info->params.device_path.unknown.reserved2); + } + + return (p - buf); +} + +/** + * edd_show_raw_data() - copies raw data to buffer for userspace to parse + * @edev: target edd_device + * @buf: output buffer + * + * Returns: number of bytes written, or -EINVAL on failure + */ +static ssize_t +edd_show_raw_data(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + ssize_t len = sizeof (info->params); + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) + len = info->params.length; + + /* In case of buggy BIOSs */ + if (len > (sizeof(info->params))) + len = sizeof(info->params); + + memcpy(buf, &info->params, len); + return len; +} + +static ssize_t +edd_show_version(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += scnprintf(p, left, "0x%02x\n", info->version); + return (p - buf); +} + +static ssize_t +edd_show_mbr_signature(struct edd_device *edev, char *buf) +{ + char *p = buf; + p += scnprintf(p, left, "0x%08x\n", edev->mbr_signature); + return (p - buf); +} + +static ssize_t +edd_show_extensions(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) { + p += scnprintf(p, left, "Fixed disk access\n"); + } + if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) { + p += scnprintf(p, left, "Device locking and ejecting\n"); + } + if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) { + p += scnprintf(p, left, "Enhanced Disk Drive support\n"); + } + if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) { + p += scnprintf(p, left, "64-bit extensions\n"); + } + return (p - buf); +} + +static ssize_t +edd_show_info_flags(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + if (info->params.info_flags & EDD_INFO_DMA_BOUNDARY_ERROR_TRANSPARENT) + p += scnprintf(p, left, "DMA boundary error transparent\n"); + if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID) + p += scnprintf(p, left, "geometry valid\n"); + if (info->params.info_flags & EDD_INFO_REMOVABLE) + p += scnprintf(p, left, "removable\n"); + if (info->params.info_flags & EDD_INFO_WRITE_VERIFY) + p += scnprintf(p, left, "write verify\n"); + if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION) + p += scnprintf(p, left, "media change notification\n"); + if (info->params.info_flags & EDD_INFO_LOCKABLE) + p += scnprintf(p, left, "lockable\n"); + if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT) + p += scnprintf(p, left, "no media present\n"); + if (info->params.info_flags & EDD_INFO_USE_INT13_FN50) + p += scnprintf(p, left, "use int13 fn50\n"); + return (p - buf); +} + +static ssize_t +edd_show_legacy_max_cylinder(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += snprintf(p, left, "%u\n", info->legacy_max_cylinder); + return (p - buf); +} + +static ssize_t +edd_show_legacy_max_head(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += snprintf(p, left, "%u\n", info->legacy_max_head); + return (p - buf); +} + +static ssize_t +edd_show_legacy_sectors_per_track(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += snprintf(p, left, "%u\n", info->legacy_sectors_per_track); + return (p - buf); +} + +static ssize_t +edd_show_default_cylinders(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += scnprintf(p, left, "%u\n", info->params.num_default_cylinders); + return (p - buf); +} + +static ssize_t +edd_show_default_heads(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += scnprintf(p, left, "%u\n", info->params.num_default_heads); + return (p - buf); +} + +static ssize_t +edd_show_default_sectors_per_track(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += scnprintf(p, left, "%u\n", info->params.sectors_per_track); + return (p - buf); +} + +static ssize_t +edd_show_sectors(struct edd_device *edev, char *buf) +{ + struct edd_info *info; + char *p = buf; + if (!edev) + return -EINVAL; + info = edd_dev_get_info(edev); + if (!info || !buf) + return -EINVAL; + + p += scnprintf(p, left, "%llu\n", info->params.number_of_sectors); + return (p - buf); +} + + +/* + * Some device instances may not have all the above attributes, + * or the attribute values may be meaningless (i.e. if + * the device is < EDD 3.0, it won't have host_bus and interface + * information), so don't bother making files for them. Likewise + * if the default_{cylinders,heads,sectors_per_track} values + * are zero, the BIOS doesn't provide sane values, don't bother + * creating files for them either. + */ + +static int +edd_has_legacy_max_cylinder(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->legacy_max_cylinder > 0; +} + +static int +edd_has_legacy_max_head(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->legacy_max_head > 0; +} + +static int +edd_has_legacy_sectors_per_track(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->legacy_sectors_per_track > 0; +} + +static int +edd_has_default_cylinders(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->params.num_default_cylinders > 0; +} + +static int +edd_has_default_heads(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->params.num_default_heads > 0; +} + +static int +edd_has_default_sectors_per_track(struct edd_device *edev) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + return info->params.sectors_per_track > 0; +} + +static int +edd_has_edd30(struct edd_device *edev) +{ + struct edd_info *info; + int i; + u8 csum = 0; + + if (!edev) + return 0; + info = edd_dev_get_info(edev); + if (!info) + return 0; + + if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) { + return 0; + } + + + /* We support only T13 spec */ + if (info->params.device_path_info_length != 44) + return 0; + + for (i = 30; i < info->params.device_path_info_length + 30; i++) + csum += *(((u8 *)&info->params) + i); + + if (csum) + return 0; + + return 1; +} + + +static EDD_DEVICE_ATTR(raw_data, 0444, edd_show_raw_data, edd_has_edd_info); +static EDD_DEVICE_ATTR(version, 0444, edd_show_version, edd_has_edd_info); +static EDD_DEVICE_ATTR(extensions, 0444, edd_show_extensions, edd_has_edd_info); +static EDD_DEVICE_ATTR(info_flags, 0444, edd_show_info_flags, edd_has_edd_info); +static EDD_DEVICE_ATTR(sectors, 0444, edd_show_sectors, edd_has_edd_info); +static EDD_DEVICE_ATTR(legacy_max_cylinder, 0444, + edd_show_legacy_max_cylinder, + edd_has_legacy_max_cylinder); +static EDD_DEVICE_ATTR(legacy_max_head, 0444, edd_show_legacy_max_head, + edd_has_legacy_max_head); +static EDD_DEVICE_ATTR(legacy_sectors_per_track, 0444, + edd_show_legacy_sectors_per_track, + edd_has_legacy_sectors_per_track); +static EDD_DEVICE_ATTR(default_cylinders, 0444, edd_show_default_cylinders, + edd_has_default_cylinders); +static EDD_DEVICE_ATTR(default_heads, 0444, edd_show_default_heads, + edd_has_default_heads); +static EDD_DEVICE_ATTR(default_sectors_per_track, 0444, + edd_show_default_sectors_per_track, + edd_has_default_sectors_per_track); +static EDD_DEVICE_ATTR(interface, 0444, edd_show_interface, edd_has_edd30); +static EDD_DEVICE_ATTR(host_bus, 0444, edd_show_host_bus, edd_has_edd30); +static EDD_DEVICE_ATTR(mbr_signature, 0444, edd_show_mbr_signature, edd_has_mbr_signature); + + +/* These are default attributes that are added for every edd + * device discovered. There are none. + */ +static struct attribute * def_attrs[] = { + NULL, +}; + +/* These attributes are conditional and only added for some devices. */ +static struct edd_attribute * edd_attrs[] = { + &edd_attr_raw_data, + &edd_attr_version, + &edd_attr_extensions, + &edd_attr_info_flags, + &edd_attr_sectors, + &edd_attr_legacy_max_cylinder, + &edd_attr_legacy_max_head, + &edd_attr_legacy_sectors_per_track, + &edd_attr_default_cylinders, + &edd_attr_default_heads, + &edd_attr_default_sectors_per_track, + &edd_attr_interface, + &edd_attr_host_bus, + &edd_attr_mbr_signature, + NULL, +}; + +/** + * edd_release - free edd structure + * @kobj: kobject of edd structure + * + * This is called when the refcount of the edd structure + * reaches 0. This should happen right after we unregister, + * but just in case, we use the release callback anyway. + */ + +static void edd_release(struct kobject * kobj) +{ + struct edd_device * dev = to_edd_device(kobj); + kfree(dev); +} + +static struct kobj_type edd_ktype = { + .release = edd_release, + .sysfs_ops = &edd_attr_ops, + .default_attrs = def_attrs, +}; + +static struct kset *edd_kset; + + +/** + * edd_dev_is_type() - is this EDD device a 'type' device? + * @edev: target edd_device + * @type: a host bus or interface identifier string per the EDD spec + * + * Returns 1 (TRUE) if it is a 'type' device, 0 otherwise. + */ +static int +edd_dev_is_type(struct edd_device *edev, const char *type) +{ + struct edd_info *info; + if (!edev) + return 0; + info = edd_dev_get_info(edev); + + if (type && info) { + if (!strncmp(info->params.host_bus_type, type, strlen(type)) || + !strncmp(info->params.interface_type, type, strlen(type))) + return 1; + } + return 0; +} + +/** + * edd_get_pci_dev() - finds pci_dev that matches edev + * @edev: edd_device + * + * Returns pci_dev if found, or NULL + */ +static struct pci_dev * +edd_get_pci_dev(struct edd_device *edev) +{ + struct edd_info *info = edd_dev_get_info(edev); + + if (edd_dev_is_type(edev, "PCI") || edd_dev_is_type(edev, "XPRS")) { + return pci_get_bus_and_slot(info->params.interface_path.pci.bus, + PCI_DEVFN(info->params.interface_path.pci.slot, + info->params.interface_path.pci. + function)); + } + return NULL; +} + +static int +edd_create_symlink_to_pcidev(struct edd_device *edev) +{ + + struct pci_dev *pci_dev = edd_get_pci_dev(edev); + int ret; + if (!pci_dev) + return 1; + ret = sysfs_create_link(&edev->kobj,&pci_dev->dev.kobj,"pci_dev"); + pci_dev_put(pci_dev); + return ret; +} + +static inline void +edd_device_unregister(struct edd_device *edev) +{ + kobject_put(&edev->kobj); +} + +static void edd_populate_dir(struct edd_device * edev) +{ + struct edd_attribute * attr; + int error = 0; + int i; + + for (i = 0; (attr = edd_attrs[i]) && !error; i++) { + if (!attr->test || + (attr->test && attr->test(edev))) + error = sysfs_create_file(&edev->kobj,&attr->attr); + } + + if (!error) { + edd_create_symlink_to_pcidev(edev); + } +} + +static int +edd_device_register(struct edd_device *edev, int i) +{ + int error; + + if (!edev) + return 1; + edd_dev_set_info(edev, i); + edev->kobj.kset = edd_kset; + error = kobject_init_and_add(&edev->kobj, &edd_ktype, NULL, + "int13_dev%02x", 0x80 + i); + if (!error) { + edd_populate_dir(edev); + kobject_uevent(&edev->kobj, KOBJ_ADD); + } + return error; +} + +static inline int edd_num_devices(void) +{ + return max_t(unsigned char, + min_t(unsigned char, EDD_MBR_SIG_MAX, edd.mbr_signature_nr), + min_t(unsigned char, EDDMAXNR, edd.edd_info_nr)); +} + +/** + * edd_init() - creates sysfs tree of EDD data + */ +static int __init +edd_init(void) +{ + int i; + int rc=0; + struct edd_device *edev; + + printk(KERN_INFO "BIOS EDD facility v%s %s, %d devices found\n", + EDD_VERSION, EDD_DATE, edd_num_devices()); + + if (!edd_num_devices()) { + printk(KERN_INFO "EDD information not available.\n"); + return -ENODEV; + } + + edd_kset = kset_create_and_add("edd", NULL, firmware_kobj); + if (!edd_kset) + return -ENOMEM; + + for (i = 0; i < edd_num_devices(); i++) { + edev = kzalloc(sizeof (*edev), GFP_KERNEL); + if (!edev) { + rc = -ENOMEM; + goto out; + } + + rc = edd_device_register(edev, i); + if (rc) { + kfree(edev); + goto out; + } + edd_devices[i] = edev; + } + + return 0; + +out: + while (--i >= 0) + edd_device_unregister(edd_devices[i]); + kset_unregister(edd_kset); + return rc; +} + +static void __exit +edd_exit(void) +{ + int i; + struct edd_device *edev; + + for (i = 0; i < edd_num_devices(); i++) { + if ((edev = edd_devices[i])) + edd_device_unregister(edev); + } + kset_unregister(edd_kset); +} + +late_initcall(edd_init); +module_exit(edd_exit); diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig new file mode 100644 index 000000000..8de4da5c9 --- /dev/null +++ b/drivers/firmware/efi/Kconfig @@ -0,0 +1,66 @@ +menu "EFI (Extensible Firmware Interface) Support" + depends on EFI + +config EFI_VARS + tristate "EFI Variable Support via sysfs" + depends on EFI + default n + help + If you say Y here, you are able to get EFI (Extensible Firmware + Interface) variable information via sysfs. You may read, + write, create, and destroy EFI variables through this interface. + + Note that using this driver in concert with efibootmgr requires + at least test release version 0.5.0-test3 or later, which is + available from: + <http://linux.dell.com/efibootmgr/testing/efibootmgr-0.5.0-test3.tar.gz> + + Subsequent efibootmgr releases may be found at: + <http://github.com/vathpela/efibootmgr> + +config EFI_VARS_PSTORE + tristate "Register efivars backend for pstore" + depends on EFI_VARS && PSTORE + default y + help + Say Y here to enable use efivars as a backend to pstore. This + will allow writing console messages, crash dumps, or anything + else supported by pstore to EFI variables. + +config EFI_VARS_PSTORE_DEFAULT_DISABLE + bool "Disable using efivars as a pstore backend by default" + depends on EFI_VARS_PSTORE + default n + help + Saying Y here will disable the use of efivars as a storage + backend for pstore by default. This setting can be overridden + using the efivars module's pstore_disable parameter. + +config EFI_RUNTIME_MAP + bool "Export efi runtime maps to sysfs" + depends on X86 && EFI && KEXEC + default y + help + Export efi runtime memory maps to /sys/firmware/efi/runtime-map. + That memory map is used for example by kexec to set up efi virtual + mapping the 2nd kernel, but can also be used for debugging purposes. + + See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map. + +config EFI_PARAMS_FROM_FDT + bool + help + Select this config option from the architecture Kconfig if + the EFI runtime support gets system table address, memory + map address, and other parameters from the device tree. + +config EFI_RUNTIME_WRAPPERS + bool + +config EFI_ARMSTUB + bool + +endmenu + +config UEFI_CPER + bool diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile new file mode 100644 index 000000000..d8be608a9 --- /dev/null +++ b/drivers/firmware/efi/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for linux kernel +# +obj-$(CONFIG_EFI) += efi.o vars.o reboot.o +obj-$(CONFIG_EFI_VARS) += efivars.o +obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o +obj-$(CONFIG_UEFI_CPER) += cper.o +obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o +obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o +obj-$(CONFIG_EFI_STUB) += libstub/ diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c new file mode 100644 index 000000000..4fd9961d5 --- /dev/null +++ b/drivers/firmware/efi/cper.c @@ -0,0 +1,492 @@ +/* + * UEFI Common Platform Error Record (CPER) support + * + * Copyright (C) 2010, Intel Corp. + * Author: Huang Ying <ying.huang@intel.com> + * + * CPER is the format used to describe platform hardware error by + * various tables, such as ERST, BERT and HEST etc. + * + * For more information about CPER, please refer to Appendix N of UEFI + * Specification version 2.4. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/cper.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/aer.h> + +#define INDENT_SP " " + +static char rcd_decode_str[CPER_REC_LEN]; + +/* + * CPER record ID need to be unique even after reboot, because record + * ID is used as index for ERST storage, while CPER records from + * multiple boot may co-exist in ERST. + */ +u64 cper_next_record_id(void) +{ + static atomic64_t seq; + + if (!atomic64_read(&seq)) + atomic64_set(&seq, ((u64)get_seconds()) << 32); + + return atomic64_inc_return(&seq); +} +EXPORT_SYMBOL_GPL(cper_next_record_id); + +static const char * const severity_strs[] = { + "recoverable", + "fatal", + "corrected", + "info", +}; + +const char *cper_severity_str(unsigned int severity) +{ + return severity < ARRAY_SIZE(severity_strs) ? + severity_strs[severity] : "unknown"; +} +EXPORT_SYMBOL_GPL(cper_severity_str); + +/* + * cper_print_bits - print strings for set bits + * @pfx: prefix for each line, including log level and prefix string + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * For each set bit in @bits, print the corresponding string in @strs. + * If the output length is longer than 80, multiple line will be + * printed, with @pfx is printed at the beginning of each line. + */ +void cper_print_bits(const char *pfx, unsigned int bits, + const char * const strs[], unsigned int strs_size) +{ + int i, len = 0; + const char *str; + char buf[84]; + + for (i = 0; i < strs_size; i++) { + if (!(bits & (1U << i))) + continue; + str = strs[i]; + if (!str) + continue; + if (len && len + strlen(str) + 2 > 80) { + printk("%s\n", buf); + len = 0; + } + if (!len) + len = snprintf(buf, sizeof(buf), "%s%s", pfx, str); + else + len += snprintf(buf+len, sizeof(buf)-len, ", %s", str); + } + if (len) + printk("%s\n", buf); +} + +static const char * const proc_type_strs[] = { + "IA32/X64", + "IA64", +}; + +static const char * const proc_isa_strs[] = { + "IA32", + "IA64", + "X64", +}; + +static const char * const proc_error_type_strs[] = { + "cache error", + "TLB error", + "bus error", + "micro-architectural error", +}; + +static const char * const proc_op_strs[] = { + "unknown or generic", + "data read", + "data write", + "instruction execution", +}; + +static const char * const proc_flag_strs[] = { + "restartable", + "precise IP", + "overflow", + "corrected", +}; + +static void cper_print_proc_generic(const char *pfx, + const struct cper_sec_proc_generic *proc) +{ + if (proc->validation_bits & CPER_PROC_VALID_TYPE) + printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type, + proc->proc_type < ARRAY_SIZE(proc_type_strs) ? + proc_type_strs[proc->proc_type] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_ISA) + printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa, + proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ? + proc_isa_strs[proc->proc_isa] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { + printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type); + cper_print_bits(pfx, proc->proc_error_type, + proc_error_type_strs, + ARRAY_SIZE(proc_error_type_strs)); + } + if (proc->validation_bits & CPER_PROC_VALID_OPERATION) + printk("%s""operation: %d, %s\n", pfx, proc->operation, + proc->operation < ARRAY_SIZE(proc_op_strs) ? + proc_op_strs[proc->operation] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { + printk("%s""flags: 0x%02x\n", pfx, proc->flags); + cper_print_bits(pfx, proc->flags, proc_flag_strs, + ARRAY_SIZE(proc_flag_strs)); + } + if (proc->validation_bits & CPER_PROC_VALID_LEVEL) + printk("%s""level: %d\n", pfx, proc->level); + if (proc->validation_bits & CPER_PROC_VALID_VERSION) + printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version); + if (proc->validation_bits & CPER_PROC_VALID_ID) + printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id); + if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) + printk("%s""target_address: 0x%016llx\n", + pfx, proc->target_addr); + if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) + printk("%s""requestor_id: 0x%016llx\n", + pfx, proc->requestor_id); + if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) + printk("%s""responder_id: 0x%016llx\n", + pfx, proc->responder_id); + if (proc->validation_bits & CPER_PROC_VALID_IP) + printk("%s""IP: 0x%016llx\n", pfx, proc->ip); +} + +static const char * const mem_err_type_strs[] = { + "unknown", + "no error", + "single-bit ECC", + "multi-bit ECC", + "single-symbol chipkill ECC", + "multi-symbol chipkill ECC", + "master abort", + "target abort", + "parity error", + "watchdog timeout", + "invalid address", + "mirror Broken", + "memory sparing", + "scrub corrected error", + "scrub uncorrected error", + "physical memory map-out event", +}; + +const char *cper_mem_err_type_str(unsigned int etype) +{ + return etype < ARRAY_SIZE(mem_err_type_strs) ? + mem_err_type_strs[etype] : "unknown"; +} +EXPORT_SYMBOL_GPL(cper_mem_err_type_str); + +static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg) +{ + u32 len, n; + + if (!msg) + return 0; + + n = 0; + len = CPER_REC_LEN - 1; + if (mem->validation_bits & CPER_MEM_VALID_NODE) + n += scnprintf(msg + n, len - n, "node: %d ", mem->node); + if (mem->validation_bits & CPER_MEM_VALID_CARD) + n += scnprintf(msg + n, len - n, "card: %d ", mem->card); + if (mem->validation_bits & CPER_MEM_VALID_MODULE) + n += scnprintf(msg + n, len - n, "module: %d ", mem->module); + if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) + n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank); + if (mem->validation_bits & CPER_MEM_VALID_BANK) + n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank); + if (mem->validation_bits & CPER_MEM_VALID_DEVICE) + n += scnprintf(msg + n, len - n, "device: %d ", mem->device); + if (mem->validation_bits & CPER_MEM_VALID_ROW) + n += scnprintf(msg + n, len - n, "row: %d ", mem->row); + if (mem->validation_bits & CPER_MEM_VALID_COLUMN) + n += scnprintf(msg + n, len - n, "column: %d ", mem->column); + if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) + n += scnprintf(msg + n, len - n, "bit_position: %d ", + mem->bit_pos); + if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) + n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ", + mem->requestor_id); + if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) + n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ", + mem->responder_id); + if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) + scnprintf(msg + n, len - n, "target_id: 0x%016llx ", + mem->target_id); + + msg[n] = '\0'; + return n; +} + +static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg) +{ + u32 len, n; + const char *bank = NULL, *device = NULL; + + if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE)) + return 0; + + n = 0; + len = CPER_REC_LEN - 1; + dmi_memdev_name(mem->mem_dev_handle, &bank, &device); + if (bank && device) + n = snprintf(msg, len, "DIMM location: %s %s ", bank, device); + else + n = snprintf(msg, len, + "DIMM location: not present. DMI handle: 0x%.4x ", + mem->mem_dev_handle); + + msg[n] = '\0'; + return n; +} + +void cper_mem_err_pack(const struct cper_sec_mem_err *mem, + struct cper_mem_err_compact *cmem) +{ + cmem->validation_bits = mem->validation_bits; + cmem->node = mem->node; + cmem->card = mem->card; + cmem->module = mem->module; + cmem->bank = mem->bank; + cmem->device = mem->device; + cmem->row = mem->row; + cmem->column = mem->column; + cmem->bit_pos = mem->bit_pos; + cmem->requestor_id = mem->requestor_id; + cmem->responder_id = mem->responder_id; + cmem->target_id = mem->target_id; + cmem->rank = mem->rank; + cmem->mem_array_handle = mem->mem_array_handle; + cmem->mem_dev_handle = mem->mem_dev_handle; +} + +const char *cper_mem_err_unpack(struct trace_seq *p, + struct cper_mem_err_compact *cmem) +{ + const char *ret = trace_seq_buffer_ptr(p); + + if (cper_mem_err_location(cmem, rcd_decode_str)) + trace_seq_printf(p, "%s", rcd_decode_str); + if (cper_dimm_err_location(cmem, rcd_decode_str)) + trace_seq_printf(p, "%s", rcd_decode_str); + trace_seq_putc(p, '\0'); + + return ret; +} + +static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem) +{ + struct cper_mem_err_compact cmem; + + if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) + printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status); + if (mem->validation_bits & CPER_MEM_VALID_PA) + printk("%s""physical_address: 0x%016llx\n", + pfx, mem->physical_addr); + if (mem->validation_bits & CPER_MEM_VALID_PA_MASK) + printk("%s""physical_address_mask: 0x%016llx\n", + pfx, mem->physical_addr_mask); + cper_mem_err_pack(mem, &cmem); + if (cper_mem_err_location(&cmem, rcd_decode_str)) + printk("%s%s\n", pfx, rcd_decode_str); + if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { + u8 etype = mem->error_type; + printk("%s""error_type: %d, %s\n", pfx, etype, + cper_mem_err_type_str(etype)); + } + if (cper_dimm_err_location(&cmem, rcd_decode_str)) + printk("%s%s\n", pfx, rcd_decode_str); +} + +static const char * const pcie_port_type_strs[] = { + "PCIe end point", + "legacy PCI end point", + "unknown", + "unknown", + "root port", + "upstream switch port", + "downstream switch port", + "PCIe to PCI/PCI-X bridge", + "PCI/PCI-X to PCIe bridge", + "root complex integrated endpoint device", + "root complex event collector", +}; + +static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, + const struct acpi_hest_generic_data *gdata) +{ + if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) + printk("%s""port_type: %d, %s\n", pfx, pcie->port_type, + pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ? + pcie_port_type_strs[pcie->port_type] : "unknown"); + if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) + printk("%s""version: %d.%d\n", pfx, + pcie->version.major, pcie->version.minor); + if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) + printk("%s""command: 0x%04x, status: 0x%04x\n", pfx, + pcie->command, pcie->status); + if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { + const __u8 *p; + printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx, + pcie->device_id.segment, pcie->device_id.bus, + pcie->device_id.device, pcie->device_id.function); + printk("%s""slot: %d\n", pfx, + pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); + printk("%s""secondary_bus: 0x%02x\n", pfx, + pcie->device_id.secondary_bus); + printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, + pcie->device_id.vendor_id, pcie->device_id.device_id); + p = pcie->device_id.class_code; + printk("%s""class_code: %02x%02x%02x\n", pfx, p[0], p[1], p[2]); + } + if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) + printk("%s""serial number: 0x%04x, 0x%04x\n", pfx, + pcie->serial_number.lower, pcie->serial_number.upper); + if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) + printk( + "%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n", + pfx, pcie->bridge.secondary_status, pcie->bridge.control); +} + +static void cper_estatus_print_section( + const char *pfx, const struct acpi_hest_generic_data *gdata, int sec_no) +{ + uuid_le *sec_type = (uuid_le *)gdata->section_type; + __u16 severity; + char newpfx[64]; + + severity = gdata->error_severity; + printk("%s""Error %d, type: %s\n", pfx, sec_no, + cper_severity_str(severity)); + if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) + printk("%s""fru_id: %pUl\n", pfx, (uuid_le *)gdata->fru_id); + if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) + printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text); + + snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP); + if (!uuid_le_cmp(*sec_type, CPER_SEC_PROC_GENERIC)) { + struct cper_sec_proc_generic *proc_err = (void *)(gdata + 1); + printk("%s""section_type: general processor error\n", newpfx); + if (gdata->error_data_length >= sizeof(*proc_err)) + cper_print_proc_generic(newpfx, proc_err); + else + goto err_section_too_small; + } else if (!uuid_le_cmp(*sec_type, CPER_SEC_PLATFORM_MEM)) { + struct cper_sec_mem_err *mem_err = (void *)(gdata + 1); + printk("%s""section_type: memory error\n", newpfx); + if (gdata->error_data_length >= sizeof(*mem_err)) + cper_print_mem(newpfx, mem_err); + else + goto err_section_too_small; + } else if (!uuid_le_cmp(*sec_type, CPER_SEC_PCIE)) { + struct cper_sec_pcie *pcie = (void *)(gdata + 1); + printk("%s""section_type: PCIe error\n", newpfx); + if (gdata->error_data_length >= sizeof(*pcie)) + cper_print_pcie(newpfx, pcie, gdata); + else + goto err_section_too_small; + } else + printk("%s""section type: unknown, %pUl\n", newpfx, sec_type); + + return; + +err_section_too_small: + pr_err(FW_WARN "error section length is too small\n"); +} + +void cper_estatus_print(const char *pfx, + const struct acpi_hest_generic_status *estatus) +{ + struct acpi_hest_generic_data *gdata; + unsigned int data_len, gedata_len; + int sec_no = 0; + char newpfx[64]; + __u16 severity; + + severity = estatus->error_severity; + if (severity == CPER_SEV_CORRECTED) + printk("%s%s\n", pfx, + "It has been corrected by h/w " + "and requires no further action"); + printk("%s""event severity: %s\n", pfx, cper_severity_str(severity)); + data_len = estatus->data_length; + gdata = (struct acpi_hest_generic_data *)(estatus + 1); + snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP); + while (data_len >= sizeof(*gdata)) { + gedata_len = gdata->error_data_length; + cper_estatus_print_section(newpfx, gdata, sec_no); + data_len -= gedata_len + sizeof(*gdata); + gdata = (void *)(gdata + 1) + gedata_len; + sec_no++; + } +} +EXPORT_SYMBOL_GPL(cper_estatus_print); + +int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) +{ + if (estatus->data_length && + estatus->data_length < sizeof(struct acpi_hest_generic_data)) + return -EINVAL; + if (estatus->raw_data_length && + estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check_header); + +int cper_estatus_check(const struct acpi_hest_generic_status *estatus) +{ + struct acpi_hest_generic_data *gdata; + unsigned int data_len, gedata_len; + int rc; + + rc = cper_estatus_check_header(estatus); + if (rc) + return rc; + data_len = estatus->data_length; + gdata = (struct acpi_hest_generic_data *)(estatus + 1); + while (data_len >= sizeof(*gdata)) { + gedata_len = gdata->error_data_length; + if (gedata_len > data_len - sizeof(*gdata)) + return -EINVAL; + data_len -= gedata_len + sizeof(*gdata); + gdata = (void *)(gdata + 1) + gedata_len; + } + if (data_len) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check); diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c new file mode 100644 index 000000000..e992abc5e --- /dev/null +++ b/drivers/firmware/efi/efi-pstore.c @@ -0,0 +1,402 @@ +#include <linux/efi.h> +#include <linux/module.h> +#include <linux/pstore.h> +#include <linux/slab.h> +#include <linux/ucs2_string.h> + +#define DUMP_NAME_LEN 52 + +static bool efivars_pstore_disable = + IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); + +module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644); + +#define PSTORE_EFI_ATTRIBUTES \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +static int efi_pstore_open(struct pstore_info *psi) +{ + psi->data = NULL; + return 0; +} + +static int efi_pstore_close(struct pstore_info *psi) +{ + psi->data = NULL; + return 0; +} + +struct pstore_read_data { + u64 *id; + enum pstore_type_id *type; + int *count; + struct timespec *timespec; + bool *compressed; + char **buf; +}; + +static inline u64 generic_id(unsigned long timestamp, + unsigned int part, int count) +{ + return ((u64) timestamp * 100 + part) * 1000 + count; +} + +static int efi_pstore_read_func(struct efivar_entry *entry, void *data) +{ + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + struct pstore_read_data *cb_data = data; + char name[DUMP_NAME_LEN], data_type; + int i; + int cnt; + unsigned int part; + unsigned long time, size; + + if (efi_guidcmp(entry->var.VendorGuid, vendor)) + return 0; + + for (i = 0; i < DUMP_NAME_LEN; i++) + name[i] = entry->var.VariableName[i]; + + if (sscanf(name, "dump-type%u-%u-%d-%lu-%c", + cb_data->type, &part, &cnt, &time, &data_type) == 5) { + *cb_data->id = generic_id(time, part, cnt); + *cb_data->count = cnt; + cb_data->timespec->tv_sec = time; + cb_data->timespec->tv_nsec = 0; + if (data_type == 'C') + *cb_data->compressed = true; + else + *cb_data->compressed = false; + } else if (sscanf(name, "dump-type%u-%u-%d-%lu", + cb_data->type, &part, &cnt, &time) == 4) { + *cb_data->id = generic_id(time, part, cnt); + *cb_data->count = cnt; + cb_data->timespec->tv_sec = time; + cb_data->timespec->tv_nsec = 0; + *cb_data->compressed = false; + } else if (sscanf(name, "dump-type%u-%u-%lu", + cb_data->type, &part, &time) == 3) { + /* + * Check if an old format, + * which doesn't support holding + * multiple logs, remains. + */ + *cb_data->id = generic_id(time, part, 0); + *cb_data->count = 0; + cb_data->timespec->tv_sec = time; + cb_data->timespec->tv_nsec = 0; + *cb_data->compressed = false; + } else + return 0; + + entry->var.DataSize = 1024; + __efivar_entry_get(entry, &entry->var.Attributes, + &entry->var.DataSize, entry->var.Data); + size = entry->var.DataSize; + memcpy(*cb_data->buf, entry->var.Data, + (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size)); + + return size; +} + +/** + * efi_pstore_scan_sysfs_enter + * @entry: scanning entry + * @next: next entry + * @head: list head + */ +static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos, + struct efivar_entry *next, + struct list_head *head) +{ + pos->scanning = true; + if (&next->list != head) + next->scanning = true; +} + +/** + * __efi_pstore_scan_sysfs_exit + * @entry: deleting entry + * @turn_off_scanning: Check if a scanning flag should be turned off + */ +static inline void __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry, + bool turn_off_scanning) +{ + if (entry->deleting) { + list_del(&entry->list); + efivar_entry_iter_end(); + efivar_unregister(entry); + efivar_entry_iter_begin(); + } else if (turn_off_scanning) + entry->scanning = false; +} + +/** + * efi_pstore_scan_sysfs_exit + * @pos: scanning entry + * @next: next entry + * @head: list head + * @stop: a flag checking if scanning will stop + */ +static void efi_pstore_scan_sysfs_exit(struct efivar_entry *pos, + struct efivar_entry *next, + struct list_head *head, bool stop) +{ + __efi_pstore_scan_sysfs_exit(pos, true); + if (stop) + __efi_pstore_scan_sysfs_exit(next, &next->list != head); +} + +/** + * efi_pstore_sysfs_entry_iter + * + * @data: function-specific data to pass to callback + * @pos: entry to begin iterating from + * + * You MUST call efivar_enter_iter_begin() before this function, and + * efivar_entry_iter_end() afterwards. + * + * It is possible to begin iteration from an arbitrary entry within + * the list by passing @pos. @pos is updated on return to point to + * the next entry of the last one passed to efi_pstore_read_func(). + * To begin iterating from the beginning of the list @pos must be %NULL. + */ +static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) +{ + struct efivar_entry *entry, *n; + struct list_head *head = &efivar_sysfs_list; + int size = 0; + + if (!*pos) { + list_for_each_entry_safe(entry, n, head, list) { + efi_pstore_scan_sysfs_enter(entry, n, head); + + size = efi_pstore_read_func(entry, data); + efi_pstore_scan_sysfs_exit(entry, n, head, size < 0); + if (size) + break; + } + *pos = n; + return size; + } + + list_for_each_entry_safe_from((*pos), n, head, list) { + efi_pstore_scan_sysfs_enter((*pos), n, head); + + size = efi_pstore_read_func((*pos), data); + efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0); + if (size) + break; + } + *pos = n; + return size; +} + +/** + * efi_pstore_read + * + * This function returns a size of NVRAM entry logged via efi_pstore_write(). + * The meaning and behavior of efi_pstore/pstore are as below. + * + * size > 0: Got data of an entry logged via efi_pstore_write() successfully, + * and pstore filesystem will continue reading subsequent entries. + * size == 0: Entry was not logged via efi_pstore_write(), + * and efi_pstore driver will continue reading subsequent entries. + * size < 0: Failed to get data of entry logging via efi_pstore_write(), + * and pstore will stop reading entry. + */ +static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, + int *count, struct timespec *timespec, + char **buf, bool *compressed, + struct pstore_info *psi) +{ + struct pstore_read_data data; + ssize_t size; + + data.id = id; + data.type = type; + data.count = count; + data.timespec = timespec; + data.compressed = compressed; + data.buf = buf; + + *data.buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL); + if (!*data.buf) + return -ENOMEM; + + efivar_entry_iter_begin(); + size = efi_pstore_sysfs_entry_iter(&data, + (struct efivar_entry **)&psi->data); + efivar_entry_iter_end(); + if (size <= 0) + kfree(*data.buf); + return size; +} + +static int efi_pstore_write(enum pstore_type_id type, + enum kmsg_dump_reason reason, u64 *id, + unsigned int part, int count, bool compressed, size_t size, + struct pstore_info *psi) +{ + char name[DUMP_NAME_LEN]; + efi_char16_t efi_name[DUMP_NAME_LEN]; + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + int i, ret = 0; + + sprintf(name, "dump-type%u-%u-%d-%lu-%c", type, part, count, + get_seconds(), compressed ? 'C' : 'D'); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; + + efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES, + !pstore_cannot_block_path(reason), + size, psi->buf); + + if (reason == KMSG_DUMP_OOPS) + efivar_run_worker(); + + *id = part; + return ret; +}; + +struct pstore_erase_data { + u64 id; + enum pstore_type_id type; + int count; + struct timespec time; + efi_char16_t *name; +}; + +/* + * Clean up an entry with the same name + */ +static int efi_pstore_erase_func(struct efivar_entry *entry, void *data) +{ + struct pstore_erase_data *ed = data; + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + efi_char16_t efi_name_old[DUMP_NAME_LEN]; + efi_char16_t *efi_name = ed->name; + unsigned long ucs2_len = ucs2_strlen(ed->name); + char name_old[DUMP_NAME_LEN]; + int i; + + if (efi_guidcmp(entry->var.VendorGuid, vendor)) + return 0; + + if (ucs2_strncmp(entry->var.VariableName, + efi_name, (size_t)ucs2_len)) { + /* + * Check if an old format, which doesn't support + * holding multiple logs, remains. + */ + sprintf(name_old, "dump-type%u-%u-%lu", ed->type, + (unsigned int)ed->id, ed->time.tv_sec); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name_old[i] = name_old[i]; + + if (ucs2_strncmp(entry->var.VariableName, efi_name_old, + ucs2_strlen(efi_name_old))) + return 0; + } + + if (entry->scanning) { + /* + * Skip deletion because this entry will be deleted + * after scanning is completed. + */ + entry->deleting = true; + } else + list_del(&entry->list); + + /* found */ + __efivar_entry_delete(entry); + + return 1; +} + +static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, + struct timespec time, struct pstore_info *psi) +{ + struct pstore_erase_data edata; + struct efivar_entry *entry = NULL; + char name[DUMP_NAME_LEN]; + efi_char16_t efi_name[DUMP_NAME_LEN]; + int found, i; + unsigned int part; + + do_div(id, 1000); + part = do_div(id, 100); + sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, time.tv_sec); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; + + edata.id = part; + edata.type = type; + edata.count = count; + edata.time = time; + edata.name = efi_name; + + efivar_entry_iter_begin(); + found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry); + + if (found && !entry->scanning) { + efivar_entry_iter_end(); + efivar_unregister(entry); + } else + efivar_entry_iter_end(); + + return 0; +} + +static struct pstore_info efi_pstore_info = { + .owner = THIS_MODULE, + .name = "efi", + .flags = PSTORE_FLAGS_FRAGILE, + .open = efi_pstore_open, + .close = efi_pstore_close, + .read = efi_pstore_read, + .write = efi_pstore_write, + .erase = efi_pstore_erase, +}; + +static __init int efivars_pstore_init(void) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return 0; + + if (!efivars_kobject()) + return 0; + + if (efivars_pstore_disable) + return 0; + + efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); + if (!efi_pstore_info.buf) + return -ENOMEM; + + efi_pstore_info.bufsize = 1024; + spin_lock_init(&efi_pstore_info.buf_lock); + + if (pstore_register(&efi_pstore_info)) { + kfree(efi_pstore_info.buf); + efi_pstore_info.buf = NULL; + efi_pstore_info.bufsize = 0; + } + + return 0; +} + +static __exit void efivars_pstore_exit(void) +{ +} + +module_init(efivars_pstore_init); +module_exit(efivars_pstore_exit); + +MODULE_DESCRIPTION("EFI variable backend for pstore"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c new file mode 100644 index 000000000..e14363d12 --- /dev/null +++ b/drivers/firmware/efi/efi.c @@ -0,0 +1,517 @@ +/* + * efi.c - EFI subsystem + * + * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com> + * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com> + * Copyright (C) 2013 Tom Gundersen <teg@jklm.no> + * + * This code registers /sys/firmware/efi{,/efivars} when EFI is supported, + * allowing the efivarfs to be mounted or the efivars module to be loaded. + * The existance of /sys/firmware/efi may also be used by userspace to + * determine that the system supports EFI. + * + * This file is released under the GPLv2. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/efi.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +struct efi __read_mostly efi = { + .mps = EFI_INVALID_TABLE_ADDR, + .acpi = EFI_INVALID_TABLE_ADDR, + .acpi20 = EFI_INVALID_TABLE_ADDR, + .smbios = EFI_INVALID_TABLE_ADDR, + .smbios3 = EFI_INVALID_TABLE_ADDR, + .sal_systab = EFI_INVALID_TABLE_ADDR, + .boot_info = EFI_INVALID_TABLE_ADDR, + .hcdp = EFI_INVALID_TABLE_ADDR, + .uga = EFI_INVALID_TABLE_ADDR, + .uv_systab = EFI_INVALID_TABLE_ADDR, + .fw_vendor = EFI_INVALID_TABLE_ADDR, + .runtime = EFI_INVALID_TABLE_ADDR, + .config_table = EFI_INVALID_TABLE_ADDR, +}; +EXPORT_SYMBOL(efi); + +static bool disable_runtime; +static int __init setup_noefi(char *arg) +{ + disable_runtime = true; + return 0; +} +early_param("noefi", setup_noefi); + +bool efi_runtime_disabled(void) +{ + return disable_runtime; +} + +static int __init parse_efi_cmdline(char *str) +{ + if (parse_option_str(str, "noruntime")) + disable_runtime = true; + + return 0; +} +early_param("efi", parse_efi_cmdline); + +static struct kobject *efi_kobj; + +/* + * Let's not leave out systab information that snuck into + * the efivars driver + */ +static ssize_t systab_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *str = buf; + + if (!kobj || !buf) + return -EINVAL; + + if (efi.mps != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "MPS=0x%lx\n", efi.mps); + if (efi.acpi20 != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20); + if (efi.acpi != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "ACPI=0x%lx\n", efi.acpi); + if (efi.smbios != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios); + if (efi.smbios3 != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "SMBIOS3=0x%lx\n", efi.smbios3); + if (efi.hcdp != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp); + if (efi.boot_info != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info); + if (efi.uga != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "UGA=0x%lx\n", efi.uga); + + return str - buf; +} + +static struct kobj_attribute efi_attr_systab = + __ATTR(systab, 0400, systab_show, NULL); + +#define EFI_FIELD(var) efi.var + +#define EFI_ATTR_SHOW(name) \ +static ssize_t name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \ +} + +EFI_ATTR_SHOW(fw_vendor); +EFI_ATTR_SHOW(runtime); +EFI_ATTR_SHOW(config_table); + +static ssize_t fw_platform_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", efi_enabled(EFI_64BIT) ? 64 : 32); +} + +static struct kobj_attribute efi_attr_fw_vendor = __ATTR_RO(fw_vendor); +static struct kobj_attribute efi_attr_runtime = __ATTR_RO(runtime); +static struct kobj_attribute efi_attr_config_table = __ATTR_RO(config_table); +static struct kobj_attribute efi_attr_fw_platform_size = + __ATTR_RO(fw_platform_size); + +static struct attribute *efi_subsys_attrs[] = { + &efi_attr_systab.attr, + &efi_attr_fw_vendor.attr, + &efi_attr_runtime.attr, + &efi_attr_config_table.attr, + &efi_attr_fw_platform_size.attr, + NULL, +}; + +static umode_t efi_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + if (attr == &efi_attr_fw_vendor.attr) { + if (efi_enabled(EFI_PARAVIRT) || + efi.fw_vendor == EFI_INVALID_TABLE_ADDR) + return 0; + } else if (attr == &efi_attr_runtime.attr) { + if (efi.runtime == EFI_INVALID_TABLE_ADDR) + return 0; + } else if (attr == &efi_attr_config_table.attr) { + if (efi.config_table == EFI_INVALID_TABLE_ADDR) + return 0; + } + + return attr->mode; +} + +static struct attribute_group efi_subsys_attr_group = { + .attrs = efi_subsys_attrs, + .is_visible = efi_attr_is_visible, +}; + +static struct efivars generic_efivars; +static struct efivar_operations generic_ops; + +static int generic_ops_register(void) +{ + generic_ops.get_variable = efi.get_variable; + generic_ops.set_variable = efi.set_variable; + generic_ops.get_next_variable = efi.get_next_variable; + generic_ops.query_variable_store = efi_query_variable_store; + + return efivars_register(&generic_efivars, &generic_ops, efi_kobj); +} + +static void generic_ops_unregister(void) +{ + efivars_unregister(&generic_efivars); +} + +/* + * We register the efi subsystem with the firmware subsystem and the + * efivars subsystem with the efi subsystem, if the system was booted with + * EFI. + */ +static int __init efisubsys_init(void) +{ + int error; + + if (!efi_enabled(EFI_BOOT)) + return 0; + + /* We register the efi directory at /sys/firmware/efi */ + efi_kobj = kobject_create_and_add("efi", firmware_kobj); + if (!efi_kobj) { + pr_err("efi: Firmware registration failed.\n"); + return -ENOMEM; + } + + error = generic_ops_register(); + if (error) + goto err_put; + + error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); + if (error) { + pr_err("efi: Sysfs attribute export failed with error %d.\n", + error); + goto err_unregister; + } + + error = efi_runtime_map_init(efi_kobj); + if (error) + goto err_remove_group; + + /* and the standard mountpoint for efivarfs */ + error = sysfs_create_mount_point(efi_kobj, "efivars"); + if (error) { + pr_err("efivars: Subsystem registration failed.\n"); + goto err_remove_group; + } + + return 0; + +err_remove_group: + sysfs_remove_group(efi_kobj, &efi_subsys_attr_group); +err_unregister: + generic_ops_unregister(); +err_put: + kobject_put(efi_kobj); + return error; +} + +subsys_initcall(efisubsys_init); + + +/* + * We can't ioremap data in EFI boot services RAM, because we've already mapped + * it as RAM. So, look it up in the existing EFI memory map instead. Only + * callable after efi_enter_virtual_mode and before efi_free_boot_services. + */ +void __iomem *efi_lookup_mapped_addr(u64 phys_addr) +{ + struct efi_memory_map *map; + void *p; + map = efi.memmap; + if (!map) + return NULL; + if (WARN_ON(!map->map)) + return NULL; + for (p = map->map; p < map->map_end; p += map->desc_size) { + efi_memory_desc_t *md = p; + u64 size = md->num_pages << EFI_PAGE_SHIFT; + u64 end = md->phys_addr + size; + if (!(md->attribute & EFI_MEMORY_RUNTIME) && + md->type != EFI_BOOT_SERVICES_CODE && + md->type != EFI_BOOT_SERVICES_DATA) + continue; + if (!md->virt_addr) + continue; + if (phys_addr >= md->phys_addr && phys_addr < end) { + phys_addr += md->virt_addr - md->phys_addr; + return (__force void __iomem *)(unsigned long)phys_addr; + } + } + return NULL; +} + +static __initdata efi_config_table_type_t common_tables[] = { + {ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20}, + {ACPI_TABLE_GUID, "ACPI", &efi.acpi}, + {HCDP_TABLE_GUID, "HCDP", &efi.hcdp}, + {MPS_TABLE_GUID, "MPS", &efi.mps}, + {SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab}, + {SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios}, + {SMBIOS3_TABLE_GUID, "SMBIOS 3.0", &efi.smbios3}, + {UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga}, + {NULL_GUID, NULL, NULL}, +}; + +static __init int match_config_table(efi_guid_t *guid, + unsigned long table, + efi_config_table_type_t *table_types) +{ + int i; + + if (table_types) { + for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) { + if (!efi_guidcmp(*guid, table_types[i].guid)) { + *(table_types[i].ptr) = table; + pr_cont(" %s=0x%lx ", + table_types[i].name, table); + return 1; + } + } + } + + return 0; +} + +int __init efi_config_parse_tables(void *config_tables, int count, int sz, + efi_config_table_type_t *arch_tables) +{ + void *tablep; + int i; + + tablep = config_tables; + pr_info(""); + for (i = 0; i < count; i++) { + efi_guid_t guid; + unsigned long table; + + if (efi_enabled(EFI_64BIT)) { + u64 table64; + guid = ((efi_config_table_64_t *)tablep)->guid; + table64 = ((efi_config_table_64_t *)tablep)->table; + table = table64; +#ifndef CONFIG_64BIT + if (table64 >> 32) { + pr_cont("\n"); + pr_err("Table located above 4GB, disabling EFI.\n"); + return -EINVAL; + } +#endif + } else { + guid = ((efi_config_table_32_t *)tablep)->guid; + table = ((efi_config_table_32_t *)tablep)->table; + } + + if (!match_config_table(&guid, table, common_tables)) + match_config_table(&guid, table, arch_tables); + + tablep += sz; + } + pr_cont("\n"); + set_bit(EFI_CONFIG_TABLES, &efi.flags); + return 0; +} + +int __init efi_config_init(efi_config_table_type_t *arch_tables) +{ + void *config_tables; + int sz, ret; + + if (efi_enabled(EFI_64BIT)) + sz = sizeof(efi_config_table_64_t); + else + sz = sizeof(efi_config_table_32_t); + + /* + * Let's see what config tables the firmware passed to us. + */ + config_tables = early_memremap(efi.systab->tables, + efi.systab->nr_tables * sz); + if (config_tables == NULL) { + pr_err("Could not map Configuration table!\n"); + return -ENOMEM; + } + + ret = efi_config_parse_tables(config_tables, efi.systab->nr_tables, sz, + arch_tables); + + early_memunmap(config_tables, efi.systab->nr_tables * sz); + return ret; +} + +#ifdef CONFIG_EFI_VARS_MODULE +static int __init efi_load_efivars(void) +{ + struct platform_device *pdev; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return 0; + + pdev = platform_device_register_simple("efivars", 0, NULL, 0); + return IS_ERR(pdev) ? PTR_ERR(pdev) : 0; +} +device_initcall(efi_load_efivars); +#endif + +#ifdef CONFIG_EFI_PARAMS_FROM_FDT + +#define UEFI_PARAM(name, prop, field) \ + { \ + { name }, \ + { prop }, \ + offsetof(struct efi_fdt_params, field), \ + FIELD_SIZEOF(struct efi_fdt_params, field) \ + } + +static __initdata struct { + const char name[32]; + const char propname[32]; + int offset; + int size; +} dt_params[] = { + UEFI_PARAM("System Table", "linux,uefi-system-table", system_table), + UEFI_PARAM("MemMap Address", "linux,uefi-mmap-start", mmap), + UEFI_PARAM("MemMap Size", "linux,uefi-mmap-size", mmap_size), + UEFI_PARAM("MemMap Desc. Size", "linux,uefi-mmap-desc-size", desc_size), + UEFI_PARAM("MemMap Desc. Version", "linux,uefi-mmap-desc-ver", desc_ver) +}; + +struct param_info { + int verbose; + int found; + void *params; +}; + +static int __init fdt_find_uefi_params(unsigned long node, const char *uname, + int depth, void *data) +{ + struct param_info *info = data; + const void *prop; + void *dest; + u64 val; + int i, len; + + if (depth != 1 || strcmp(uname, "chosen") != 0) + return 0; + + for (i = 0; i < ARRAY_SIZE(dt_params); i++) { + prop = of_get_flat_dt_prop(node, dt_params[i].propname, &len); + if (!prop) + return 0; + dest = info->params + dt_params[i].offset; + info->found++; + + val = of_read_number(prop, len / sizeof(u32)); + + if (dt_params[i].size == sizeof(u32)) + *(u32 *)dest = val; + else + *(u64 *)dest = val; + + if (info->verbose) + pr_info(" %s: 0x%0*llx\n", dt_params[i].name, + dt_params[i].size * 2, val); + } + return 1; +} + +int __init efi_get_fdt_params(struct efi_fdt_params *params, int verbose) +{ + struct param_info info; + int ret; + + pr_info("Getting EFI parameters from FDT:\n"); + + info.verbose = verbose; + info.found = 0; + info.params = params; + + ret = of_scan_flat_dt(fdt_find_uefi_params, &info); + if (!info.found) + pr_info("UEFI not found.\n"); + else if (!ret) + pr_err("Can't find '%s' in device tree!\n", + dt_params[info.found].name); + + return ret; +} +#endif /* CONFIG_EFI_PARAMS_FROM_FDT */ + +static __initdata char memory_type_name[][20] = { + "Reserved", + "Loader Code", + "Loader Data", + "Boot Code", + "Boot Data", + "Runtime Code", + "Runtime Data", + "Conventional Memory", + "Unusable Memory", + "ACPI Reclaim Memory", + "ACPI Memory NVS", + "Memory Mapped I/O", + "MMIO Port Space", + "PAL Code" +}; + +char * __init efi_md_typeattr_format(char *buf, size_t size, + const efi_memory_desc_t *md) +{ + char *pos; + int type_len; + u64 attr; + + pos = buf; + if (md->type >= ARRAY_SIZE(memory_type_name)) + type_len = snprintf(pos, size, "[type=%u", md->type); + else + type_len = snprintf(pos, size, "[%-*s", + (int)(sizeof(memory_type_name[0]) - 1), + memory_type_name[md->type]); + if (type_len >= size) + return buf; + + pos += type_len; + size -= type_len; + + attr = md->attribute; + if (attr & ~(EFI_MEMORY_UC | EFI_MEMORY_WC | EFI_MEMORY_WT | + EFI_MEMORY_WB | EFI_MEMORY_UCE | EFI_MEMORY_WP | + EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RUNTIME)) + snprintf(pos, size, "|attr=0x%016llx]", + (unsigned long long)attr); + else + snprintf(pos, size, "|%3s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]", + attr & EFI_MEMORY_RUNTIME ? "RUN" : "", + attr & EFI_MEMORY_XP ? "XP" : "", + attr & EFI_MEMORY_RP ? "RP" : "", + attr & EFI_MEMORY_WP ? "WP" : "", + attr & EFI_MEMORY_UCE ? "UCE" : "", + attr & EFI_MEMORY_WB ? "WB" : "", + attr & EFI_MEMORY_WT ? "WT" : "", + attr & EFI_MEMORY_WC ? "WC" : "", + attr & EFI_MEMORY_UC ? "UC" : ""); + return buf; +} diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c new file mode 100644 index 000000000..7b2e0496e --- /dev/null +++ b/drivers/firmware/efi/efivars.c @@ -0,0 +1,755 @@ +/* + * Originally from efivars.c, + * + * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com> + * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com> + * + * This code takes all variables accessible from EFI runtime and + * exports them via sysfs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Changelog: + * + * 17 May 2004 - Matt Domsch <Matt_Domsch@dell.com> + * remove check for efi_enabled in exit + * add MODULE_VERSION + * + * 26 Apr 2004 - Matt Domsch <Matt_Domsch@dell.com> + * minor bug fixes + * + * 21 Apr 2004 - Matt Tolentino <matthew.e.tolentino@intel.com) + * converted driver to export variable information via sysfs + * and moved to drivers/firmware directory + * bumped revision number to v0.07 to reflect conversion & move + * + * 10 Dec 2002 - Matt Domsch <Matt_Domsch@dell.com> + * fix locking per Peter Chubb's findings + * + * 25 Mar 2002 - Matt Domsch <Matt_Domsch@dell.com> + * move uuid_unparse() to include/asm-ia64/efi.h:efi_guid_to_str() + * + * 12 Feb 2002 - Matt Domsch <Matt_Domsch@dell.com> + * use list_for_each_safe when deleting vars. + * remove ifdef CONFIG_SMP around include <linux/smp.h> + * v0.04 release to linux-ia64@linuxia64.org + * + * 20 April 2001 - Matt Domsch <Matt_Domsch@dell.com> + * Moved vars from /proc/efi to /proc/efi/vars, and made + * efi.c own the /proc/efi directory. + * v0.03 release to linux-ia64@linuxia64.org + * + * 26 March 2001 - Matt Domsch <Matt_Domsch@dell.com> + * At the request of Stephane, moved ownership of /proc/efi + * to efi.c, and now efivars lives under /proc/efi/vars. + * + * 12 March 2001 - Matt Domsch <Matt_Domsch@dell.com> + * Feedback received from Stephane Eranian incorporated. + * efivar_write() checks copy_from_user() return value. + * efivar_read/write() returns proper errno. + * v0.02 release to linux-ia64@linuxia64.org + * + * 26 February 2001 - Matt Domsch <Matt_Domsch@dell.com> + * v0.01 release to linux-ia64@linuxia64.org + */ + +#include <linux/efi.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/ucs2_string.h> +#include <linux/compat.h> + +#define EFIVARS_VERSION "0.08" +#define EFIVARS_DATE "2004-May-17" + +MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>"); +MODULE_DESCRIPTION("sysfs interface to EFI Variables"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(EFIVARS_VERSION); +MODULE_ALIAS("platform:efivars"); + +LIST_HEAD(efivar_sysfs_list); +EXPORT_SYMBOL_GPL(efivar_sysfs_list); + +static struct kset *efivars_kset; + +static struct bin_attribute *efivars_new_var; +static struct bin_attribute *efivars_del_var; + +struct compat_efi_variable { + efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; + efi_guid_t VendorGuid; + __u32 DataSize; + __u8 Data[1024]; + __u32 Status; + __u32 Attributes; +} __packed; + +struct efivar_attribute { + struct attribute attr; + ssize_t (*show) (struct efivar_entry *entry, char *buf); + ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count); +}; + +#define EFIVAR_ATTR(_name, _mode, _show, _store) \ +struct efivar_attribute efivar_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode}, \ + .show = _show, \ + .store = _store, \ +}; + +#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr) +#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj) + +/* + * Prototype for sysfs creation function + */ +static int +efivar_create_sysfs_entry(struct efivar_entry *new_var); + +static ssize_t +efivar_guid_read(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + char *str = buf; + + if (!entry || !buf) + return 0; + + efi_guid_to_str(&var->VendorGuid, str); + str += strlen(str); + str += sprintf(str, "\n"); + + return str - buf; +} + +static ssize_t +efivar_attr_read(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + char *str = buf; + + if (!entry || !buf) + return -EINVAL; + + var->DataSize = 1024; + if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) + return -EIO; + + if (var->Attributes & EFI_VARIABLE_NON_VOLATILE) + str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); + if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS) + str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS) + str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) + str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n"); + if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_APPEND_WRITE) + str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n"); + return str - buf; +} + +static ssize_t +efivar_size_read(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + char *str = buf; + + if (!entry || !buf) + return -EINVAL; + + var->DataSize = 1024; + if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) + return -EIO; + + str += sprintf(str, "0x%lx\n", var->DataSize); + return str - buf; +} + +static ssize_t +efivar_data_read(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + + if (!entry || !buf) + return -EINVAL; + + var->DataSize = 1024; + if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) + return -EIO; + + memcpy(buf, var->Data, var->DataSize); + return var->DataSize; +} + +static inline int +sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor, + unsigned long size, u32 attributes, u8 *data) +{ + /* + * If only updating the variable data, then the name + * and guid should remain the same + */ + if (memcmp(name, var->VariableName, sizeof(var->VariableName)) || + efi_guidcmp(vendor, var->VendorGuid)) { + printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n"); + return -EINVAL; + } + + if ((size <= 0) || (attributes == 0)){ + printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n"); + return -EINVAL; + } + + if ((attributes & ~EFI_VARIABLE_MASK) != 0 || + efivar_validate(name, data, size) == false) { + printk(KERN_ERR "efivars: Malformed variable content\n"); + return -EINVAL; + } + + return 0; +} + +static inline bool is_compat(void) +{ + if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task()) + return true; + + return false; +} + +static void +copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src) +{ + memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN); + memcpy(dst->Data, src->Data, sizeof(src->Data)); + + dst->VendorGuid = src->VendorGuid; + dst->DataSize = src->DataSize; + dst->Attributes = src->Attributes; +} + +/* + * We allow each variable to be edited via rewriting the + * entire efi variable structure. + */ +static ssize_t +efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) +{ + struct efi_variable *new_var, *var = &entry->var; + efi_char16_t *name; + unsigned long size; + efi_guid_t vendor; + u32 attributes; + u8 *data; + int err; + + if (is_compat()) { + struct compat_efi_variable *compat; + + if (count != sizeof(*compat)) + return -EINVAL; + + compat = (struct compat_efi_variable *)buf; + attributes = compat->Attributes; + vendor = compat->VendorGuid; + name = compat->VariableName; + size = compat->DataSize; + data = compat->Data; + + err = sanity_check(var, name, vendor, size, attributes, data); + if (err) + return err; + + copy_out_compat(&entry->var, compat); + } else { + if (count != sizeof(struct efi_variable)) + return -EINVAL; + + new_var = (struct efi_variable *)buf; + + attributes = new_var->Attributes; + vendor = new_var->VendorGuid; + name = new_var->VariableName; + size = new_var->DataSize; + data = new_var->Data; + + err = sanity_check(var, name, vendor, size, attributes, data); + if (err) + return err; + + memcpy(&entry->var, new_var, count); + } + + err = efivar_entry_set(entry, attributes, size, data, NULL); + if (err) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err); + return -EIO; + } + + return count; +} + +static ssize_t +efivar_show_raw(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + struct compat_efi_variable *compat; + size_t size; + + if (!entry || !buf) + return 0; + + var->DataSize = 1024; + if (efivar_entry_get(entry, &entry->var.Attributes, + &entry->var.DataSize, entry->var.Data)) + return -EIO; + + if (is_compat()) { + compat = (struct compat_efi_variable *)buf; + + size = sizeof(*compat); + memcpy(compat->VariableName, var->VariableName, + EFI_VAR_NAME_LEN); + memcpy(compat->Data, var->Data, sizeof(compat->Data)); + + compat->VendorGuid = var->VendorGuid; + compat->DataSize = var->DataSize; + compat->Attributes = var->Attributes; + } else { + size = sizeof(*var); + memcpy(buf, var, size); + } + + return size; +} + +/* + * Generic read/write functions that call the specific functions of + * the attributes... + */ +static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct efivar_entry *var = to_efivar_entry(kobj); + struct efivar_attribute *efivar_attr = to_efivar_attr(attr); + ssize_t ret = -EIO; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (efivar_attr->show) { + ret = efivar_attr->show(var, buf); + } + return ret; +} + +static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct efivar_entry *var = to_efivar_entry(kobj); + struct efivar_attribute *efivar_attr = to_efivar_attr(attr); + ssize_t ret = -EIO; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (efivar_attr->store) + ret = efivar_attr->store(var, buf, count); + + return ret; +} + +static const struct sysfs_ops efivar_attr_ops = { + .show = efivar_attr_show, + .store = efivar_attr_store, +}; + +static void efivar_release(struct kobject *kobj) +{ + struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj); + kfree(var); +} + +static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL); +static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL); +static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL); +static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL); +static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw); + +static struct attribute *def_attrs[] = { + &efivar_attr_guid.attr, + &efivar_attr_size.attr, + &efivar_attr_attributes.attr, + &efivar_attr_data.attr, + &efivar_attr_raw_var.attr, + NULL, +}; + +static struct kobj_type efivar_ktype = { + .release = efivar_release, + .sysfs_ops = &efivar_attr_ops, + .default_attrs = def_attrs, +}; + +static ssize_t efivar_create(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + struct compat_efi_variable *compat = (struct compat_efi_variable *)buf; + struct efi_variable *new_var = (struct efi_variable *)buf; + struct efivar_entry *new_entry; + bool need_compat = is_compat(); + efi_char16_t *name; + unsigned long size; + u32 attributes; + u8 *data; + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (need_compat) { + if (count != sizeof(*compat)) + return -EINVAL; + + attributes = compat->Attributes; + name = compat->VariableName; + size = compat->DataSize; + data = compat->Data; + } else { + if (count != sizeof(*new_var)) + return -EINVAL; + + attributes = new_var->Attributes; + name = new_var->VariableName; + size = new_var->DataSize; + data = new_var->Data; + } + + if ((attributes & ~EFI_VARIABLE_MASK) != 0 || + efivar_validate(name, data, size) == false) { + printk(KERN_ERR "efivars: Malformed variable content\n"); + return -EINVAL; + } + + new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) + return -ENOMEM; + + if (need_compat) + copy_out_compat(&new_entry->var, compat); + else + memcpy(&new_entry->var, new_var, sizeof(*new_var)); + + err = efivar_entry_set(new_entry, attributes, size, + data, &efivar_sysfs_list); + if (err) { + if (err == -EEXIST) + err = -EINVAL; + goto out; + } + + if (efivar_create_sysfs_entry(new_entry)) { + printk(KERN_WARNING "efivars: failed to create sysfs entry.\n"); + kfree(new_entry); + } + return count; + +out: + kfree(new_entry); + return err; +} + +static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + struct efi_variable *del_var = (struct efi_variable *)buf; + struct compat_efi_variable *compat; + struct efivar_entry *entry; + efi_char16_t *name; + efi_guid_t vendor; + int err = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (is_compat()) { + if (count != sizeof(*compat)) + return -EINVAL; + + compat = (struct compat_efi_variable *)buf; + name = compat->VariableName; + vendor = compat->VendorGuid; + } else { + if (count != sizeof(*del_var)) + return -EINVAL; + + name = del_var->VariableName; + vendor = del_var->VendorGuid; + } + + efivar_entry_iter_begin(); + entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true); + if (!entry) + err = -EINVAL; + else if (__efivar_entry_delete(entry)) + err = -EIO; + + if (err) { + efivar_entry_iter_end(); + return err; + } + + if (!entry->scanning) { + efivar_entry_iter_end(); + efivar_unregister(entry); + } else + efivar_entry_iter_end(); + + /* It's dead Jim.... */ + return count; +} + +/** + * efivar_create_sysfs_entry - create a new entry in sysfs + * @new_var: efivar entry to create + * + * Returns 1 on failure, 0 on success + */ +static int +efivar_create_sysfs_entry(struct efivar_entry *new_var) +{ + int i, short_name_size; + char *short_name; + unsigned long variable_name_size; + efi_char16_t *variable_name; + + variable_name = new_var->var.VariableName; + variable_name_size = ucs2_strlen(variable_name) * sizeof(efi_char16_t); + + /* + * Length of the variable bytes in ASCII, plus the '-' separator, + * plus the GUID, plus trailing NUL + */ + short_name_size = variable_name_size / sizeof(efi_char16_t) + + 1 + EFI_VARIABLE_GUID_LEN + 1; + + short_name = kzalloc(short_name_size, GFP_KERNEL); + + if (!short_name) + return 1; + + /* Convert Unicode to normal chars (assume top bits are 0), + ala UTF-8 */ + for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) { + short_name[i] = variable_name[i] & 0xFF; + } + /* This is ugly, but necessary to separate one vendor's + private variables from another's. */ + + *(short_name + strlen(short_name)) = '-'; + efi_guid_to_str(&new_var->var.VendorGuid, + short_name + strlen(short_name)); + + new_var->kobj.kset = efivars_kset; + + i = kobject_init_and_add(&new_var->kobj, &efivar_ktype, + NULL, "%s", short_name); + kfree(short_name); + if (i) + return 1; + + kobject_uevent(&new_var->kobj, KOBJ_ADD); + efivar_entry_add(new_var, &efivar_sysfs_list); + + return 0; +} + +static int +create_efivars_bin_attributes(void) +{ + struct bin_attribute *attr; + int error; + + /* new_var */ + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) + return -ENOMEM; + + attr->attr.name = "new_var"; + attr->attr.mode = 0200; + attr->write = efivar_create; + efivars_new_var = attr; + + /* del_var */ + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) { + error = -ENOMEM; + goto out_free; + } + attr->attr.name = "del_var"; + attr->attr.mode = 0200; + attr->write = efivar_delete; + efivars_del_var = attr; + + sysfs_bin_attr_init(efivars_new_var); + sysfs_bin_attr_init(efivars_del_var); + + /* Register */ + error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var); + if (error) { + printk(KERN_ERR "efivars: unable to create new_var sysfs file" + " due to error %d\n", error); + goto out_free; + } + + error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var); + if (error) { + printk(KERN_ERR "efivars: unable to create del_var sysfs file" + " due to error %d\n", error); + sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var); + goto out_free; + } + + return 0; +out_free: + kfree(efivars_del_var); + efivars_del_var = NULL; + kfree(efivars_new_var); + efivars_new_var = NULL; + return error; +} + +static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor, + unsigned long name_size, void *data) +{ + struct efivar_entry *entry = data; + + if (efivar_entry_find(name, vendor, &efivar_sysfs_list, false)) + return 0; + + memcpy(entry->var.VariableName, name, name_size); + memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); + + return 1; +} + +static void efivar_update_sysfs_entries(struct work_struct *work) +{ + struct efivar_entry *entry; + int err; + + /* Add new sysfs entries */ + while (1) { + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + err = efivar_init(efivar_update_sysfs_entry, entry, + true, false, &efivar_sysfs_list); + if (!err) + break; + + efivar_create_sysfs_entry(entry); + } + + kfree(entry); +} + +static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor, + unsigned long name_size, void *data) +{ + struct efivar_entry *entry; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + memcpy(entry->var.VariableName, name, name_size); + memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); + + efivar_create_sysfs_entry(entry); + + return 0; +} + +static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data) +{ + efivar_entry_remove(entry); + efivar_unregister(entry); + return 0; +} + +static void efivars_sysfs_exit(void) +{ + /* Remove all entries and destroy */ + __efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL); + + if (efivars_new_var) + sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var); + if (efivars_del_var) + sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var); + kfree(efivars_new_var); + kfree(efivars_del_var); + kset_unregister(efivars_kset); +} + +int efivars_sysfs_init(void) +{ + struct kobject *parent_kobj = efivars_kobject(); + int error = 0; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return -ENODEV; + + /* No efivars has been registered yet */ + if (!parent_kobj) + return 0; + + printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION, + EFIVARS_DATE); + + efivars_kset = kset_create_and_add("vars", NULL, parent_kobj); + if (!efivars_kset) { + printk(KERN_ERR "efivars: Subsystem registration failed.\n"); + return -ENOMEM; + } + + efivar_init(efivars_sysfs_callback, NULL, false, + true, &efivar_sysfs_list); + + error = create_efivars_bin_attributes(); + if (error) { + efivars_sysfs_exit(); + return error; + } + + INIT_WORK(&efivar_work, efivar_update_sysfs_entries); + + return 0; +} +EXPORT_SYMBOL_GPL(efivars_sysfs_init); + +module_init(efivars_sysfs_init); +module_exit(efivars_sysfs_exit); diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile new file mode 100644 index 000000000..280bc0a63 --- /dev/null +++ b/drivers/firmware/efi/libstub/Makefile @@ -0,0 +1,41 @@ +# +# The stub may be linked into the kernel proper or into a separate boot binary, +# but in either case, it executes before the kernel does (with MMU disabled) so +# things like ftrace and stack-protector are likely to cause trouble if left +# enabled, even if doing so doesn't break the build. +# +cflags-$(CONFIG_X86_32) := -march=i386 +cflags-$(CONFIG_X86_64) := -mcmodel=small +cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2 \ + -fPIC -fno-strict-aliasing -mno-red-zone \ + -mno-mmx -mno-sse -DDISABLE_BRANCH_PROFILING + +cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS)) +cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \ + -fno-builtin -fpic -mno-single-pic-base + +KBUILD_CFLAGS := $(cflags-y) \ + $(call cc-option,-ffreestanding) \ + $(call cc-option,-fno-stack-protector) + +GCOV_PROFILE := n +KASAN_SANITIZE := n + +lib-y := efi-stub-helper.o +lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o + +CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/ + +# +# arm64 puts the stub in the kernel proper, which will unnecessarily retain all +# code indefinitely unless it is annotated as __init/__initdata/__initconst etc. +# So let's apply the __init annotations at the section level, by prefixing +# the section names directly. This will ensure that even all the inline string +# literals are covered. +# +extra-$(CONFIG_ARM64) := $(lib-y) +lib-$(CONFIG_ARM64) := $(patsubst %.o,%.init.o,$(lib-y)) + +OBJCOPYFLAGS := --prefix-alloc-sections=.init +$(obj)/%.init.o: $(obj)/%.o FORCE + $(call if_changed,objcopy) diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c new file mode 100644 index 000000000..e29560e6b --- /dev/null +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -0,0 +1,355 @@ +/* + * EFI stub implementation that is shared by arm and arm64 architectures. + * This should be #included by the EFI stub implementation files. + * + * Copyright (C) 2013,2014 Linaro Limited + * Roy Franz <roy.franz@linaro.org + * Copyright (C) 2013 Red Hat, Inc. + * Mark Salter <msalter@redhat.com> + * + * This file is part of the Linux kernel, and is made available under the + * terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <asm/efi.h> + +#include "efistub.h" + +static int efi_secureboot_enabled(efi_system_table_t *sys_table_arg) +{ + static efi_guid_t const var_guid = EFI_GLOBAL_VARIABLE_GUID; + static efi_char16_t const var_name[] = { + 'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 }; + + efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable; + unsigned long size = sizeof(u8); + efi_status_t status; + u8 val; + + status = f_getvar((efi_char16_t *)var_name, (efi_guid_t *)&var_guid, + NULL, &size, &val); + + switch (status) { + case EFI_SUCCESS: + return val; + case EFI_NOT_FOUND: + return 0; + default: + return 1; + } +} + +efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, + void *__image, void **__fh) +{ + efi_file_io_interface_t *io; + efi_loaded_image_t *image = __image; + efi_file_handle_t *fh; + efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; + efi_status_t status; + void *handle = (void *)(unsigned long)image->device_handle; + + status = sys_table_arg->boottime->handle_protocol(handle, + &fs_proto, (void **)&io); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to handle fs_proto\n"); + return status; + } + + status = io->open_volume(io, &fh); + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to open volume\n"); + + *__fh = fh; + return status; +} + +efi_status_t efi_file_close(void *handle) +{ + efi_file_handle_t *fh = handle; + + return fh->close(handle); +} + +efi_status_t +efi_file_read(void *handle, unsigned long *size, void *addr) +{ + efi_file_handle_t *fh = handle; + + return fh->read(handle, size, addr); +} + + +efi_status_t +efi_file_size(efi_system_table_t *sys_table_arg, void *__fh, + efi_char16_t *filename_16, void **handle, u64 *file_sz) +{ + efi_file_handle_t *h, *fh = __fh; + efi_file_info_t *info; + efi_status_t status; + efi_guid_t info_guid = EFI_FILE_INFO_ID; + unsigned long info_sz; + + status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to open file: "); + efi_char16_printk(sys_table_arg, filename_16); + efi_printk(sys_table_arg, "\n"); + return status; + } + + *handle = h; + + info_sz = 0; + status = h->get_info(h, &info_guid, &info_sz, NULL); + if (status != EFI_BUFFER_TOO_SMALL) { + efi_printk(sys_table_arg, "Failed to get file info size\n"); + return status; + } + +grow: + status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA, + info_sz, (void **)&info); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to alloc mem for file info\n"); + return status; + } + + status = h->get_info(h, &info_guid, &info_sz, + info); + if (status == EFI_BUFFER_TOO_SMALL) { + sys_table_arg->boottime->free_pool(info); + goto grow; + } + + *file_sz = info->file_size; + sys_table_arg->boottime->free_pool(info); + + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to get initrd info\n"); + + return status; +} + + + +void efi_char16_printk(efi_system_table_t *sys_table_arg, + efi_char16_t *str) +{ + struct efi_simple_text_output_protocol *out; + + out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out; + out->output_string(out, str); +} + + +/* + * This function handles the architcture specific differences between arm and + * arm64 regarding where the kernel image must be loaded and any memory that + * must be reserved. On failure it is required to free all + * all allocations it has made. + */ +efi_status_t handle_kernel_image(efi_system_table_t *sys_table, + unsigned long *image_addr, + unsigned long *image_size, + unsigned long *reserve_addr, + unsigned long *reserve_size, + unsigned long dram_base, + efi_loaded_image_t *image); +/* + * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint + * that is described in the PE/COFF header. Most of the code is the same + * for both archictectures, with the arch-specific code provided in the + * handle_kernel_image() function. + */ +unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, + unsigned long *image_addr) +{ + efi_loaded_image_t *image; + efi_status_t status; + unsigned long image_size = 0; + unsigned long dram_base; + /* addr/point and size pairs for memory management*/ + unsigned long initrd_addr; + u64 initrd_size = 0; + unsigned long fdt_addr = 0; /* Original DTB */ + unsigned long fdt_size = 0; + char *cmdline_ptr = NULL; + int cmdline_size = 0; + unsigned long new_fdt_addr; + efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID; + unsigned long reserve_addr = 0; + unsigned long reserve_size = 0; + + /* Check if we were booted by the EFI firmware */ + if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) + goto fail; + + pr_efi(sys_table, "Booting Linux Kernel...\n"); + + /* + * Get a handle to the loaded image protocol. This is used to get + * information about the running image, such as size and the command + * line. + */ + status = sys_table->boottime->handle_protocol(handle, + &loaded_image_proto, (void *)&image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to get loaded image protocol\n"); + goto fail; + } + + dram_base = get_dram_base(sys_table); + if (dram_base == EFI_ERROR) { + pr_efi_err(sys_table, "Failed to find DRAM base\n"); + goto fail; + } + status = handle_kernel_image(sys_table, image_addr, &image_size, + &reserve_addr, + &reserve_size, + dram_base, image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to relocate kernel\n"); + goto fail; + } + + /* + * Get the command line from EFI, using the LOADED_IMAGE + * protocol. We are going to copy the command line into the + * device tree, so this can be allocated anywhere. + */ + cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size); + if (!cmdline_ptr) { + pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n"); + goto fail_free_image; + } + + status = efi_parse_options(cmdline_ptr); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n"); + + /* + * Unauthenticated device tree data is a security hazard, so + * ignore 'dtb=' unless UEFI Secure Boot is disabled. + */ + if (efi_secureboot_enabled(sys_table)) { + pr_efi(sys_table, "UEFI Secure Boot is enabled.\n"); + } else { + status = handle_cmdline_files(sys_table, image, cmdline_ptr, + "dtb=", + ~0UL, &fdt_addr, &fdt_size); + + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to load device tree!\n"); + goto fail_free_cmdline; + } + } + + if (fdt_addr) { + pr_efi(sys_table, "Using DTB from command line\n"); + } else { + /* Look for a device tree configuration table entry. */ + fdt_addr = (uintptr_t)get_fdt(sys_table, &fdt_size); + if (fdt_addr) + pr_efi(sys_table, "Using DTB from configuration table\n"); + } + + if (!fdt_addr) + pr_efi(sys_table, "Generating empty DTB\n"); + + status = handle_cmdline_files(sys_table, image, cmdline_ptr, + "initrd=", dram_base + SZ_512M, + (unsigned long *)&initrd_addr, + (unsigned long *)&initrd_size); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table, "Failed initrd from command line!\n"); + + new_fdt_addr = fdt_addr; + status = allocate_new_fdt_and_exit_boot(sys_table, handle, + &new_fdt_addr, dram_base + MAX_FDT_OFFSET, + initrd_addr, initrd_size, cmdline_ptr, + fdt_addr, fdt_size); + + /* + * If all went well, we need to return the FDT address to the + * calling function so it can be passed to kernel as part of + * the kernel boot protocol. + */ + if (status == EFI_SUCCESS) + return new_fdt_addr; + + pr_efi_err(sys_table, "Failed to update FDT and exit boot services\n"); + + efi_free(sys_table, initrd_size, initrd_addr); + efi_free(sys_table, fdt_size, fdt_addr); + +fail_free_cmdline: + efi_free(sys_table, cmdline_size, (unsigned long)cmdline_ptr); + +fail_free_image: + efi_free(sys_table, image_size, *image_addr); + efi_free(sys_table, reserve_size, reserve_addr); +fail: + return EFI_ERROR; +} + +/* + * This is the base address at which to start allocating virtual memory ranges + * for UEFI Runtime Services. This is in the low TTBR0 range so that we can use + * any allocation we choose, and eliminate the risk of a conflict after kexec. + * The value chosen is the largest non-zero power of 2 suitable for this purpose + * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can + * be mapped efficiently. + */ +#define EFI_RT_VIRTUAL_BASE 0x40000000 + +/* + * efi_get_virtmap() - create a virtual mapping for the EFI memory map + * + * This function populates the virt_addr fields of all memory region descriptors + * in @memory_map whose EFI_MEMORY_RUNTIME attribute is set. Those descriptors + * are also copied to @runtime_map, and their total count is returned in @count. + */ +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, + unsigned long desc_size, efi_memory_desc_t *runtime_map, + int *count) +{ + u64 efi_virt_base = EFI_RT_VIRTUAL_BASE; + efi_memory_desc_t *out = runtime_map; + int l; + + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *in = (void *)memory_map + l; + u64 paddr, size; + + if (!(in->attribute & EFI_MEMORY_RUNTIME)) + continue; + + /* + * Make the mapping compatible with 64k pages: this allows + * a 4k page size kernel to kexec a 64k page size kernel and + * vice versa. + */ + paddr = round_down(in->phys_addr, SZ_64K); + size = round_up(in->num_pages * EFI_PAGE_SIZE + + in->phys_addr - paddr, SZ_64K); + + /* + * Avoid wasting memory on PTEs by choosing a virtual base that + * is compatible with section mappings if this region has the + * appropriate size and physical alignment. (Sections are 2 MB + * on 4k granule kernels) + */ + if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M) + efi_virt_base = round_up(efi_virt_base, SZ_2M); + + in->virt_addr = efi_virt_base + in->phys_addr - paddr; + efi_virt_base += size; + + memcpy(out, in, desc_size); + out = (void *)out + desc_size; + ++*count; + } +} diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c new file mode 100644 index 000000000..f07d4a67f --- /dev/null +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -0,0 +1,699 @@ +/* + * Helper functions used by the EFI stub on multiple + * architectures. This should be #included by the EFI stub + * implementation files. + * + * Copyright 2011 Intel Corporation; author Matt Fleming + * + * This file is part of the Linux kernel, and is made available + * under the terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <asm/efi.h> + +#include "efistub.h" + +/* + * Some firmware implementations have problems reading files in one go. + * A read chunk size of 1MB seems to work for most platforms. + * + * Unfortunately, reading files in chunks triggers *other* bugs on some + * platforms, so we provide a way to disable this workaround, which can + * be done by passing "efi=nochunk" on the EFI boot stub command line. + * + * If you experience issues with initrd images being corrupt it's worth + * trying efi=nochunk, but chunking is enabled by default because there + * are far more machines that require the workaround than those that + * break with it enabled. + */ +#define EFI_READ_CHUNK_SIZE (1024 * 1024) + +static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE; + +/* + * Allow the platform to override the allocation granularity: this allows + * systems that have the capability to run with a larger page size to deal + * with the allocations for initrd and fdt more efficiently. + */ +#ifndef EFI_ALLOC_ALIGN +#define EFI_ALLOC_ALIGN EFI_PAGE_SIZE +#endif + +struct file_info { + efi_file_handle_t *handle; + u64 size; +}; + +void efi_printk(efi_system_table_t *sys_table_arg, char *str) +{ + char *s8; + + for (s8 = str; *s8; s8++) { + efi_char16_t ch[2] = { 0 }; + + ch[0] = *s8; + if (*s8 == '\n') { + efi_char16_t nl[2] = { '\r', 0 }; + efi_char16_printk(sys_table_arg, nl); + } + + efi_char16_printk(sys_table_arg, ch); + } +} + +efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg, + efi_memory_desc_t **map, + unsigned long *map_size, + unsigned long *desc_size, + u32 *desc_ver, + unsigned long *key_ptr) +{ + efi_memory_desc_t *m = NULL; + efi_status_t status; + unsigned long key; + u32 desc_version; + + *map_size = sizeof(*m) * 32; +again: + /* + * Add an additional efi_memory_desc_t because we're doing an + * allocation which may be in a new descriptor region. + */ + *map_size += sizeof(*m); + status = efi_call_early(allocate_pool, EFI_LOADER_DATA, + *map_size, (void **)&m); + if (status != EFI_SUCCESS) + goto fail; + + *desc_size = 0; + key = 0; + status = efi_call_early(get_memory_map, map_size, m, + &key, desc_size, &desc_version); + if (status == EFI_BUFFER_TOO_SMALL) { + efi_call_early(free_pool, m); + goto again; + } + + if (status != EFI_SUCCESS) + efi_call_early(free_pool, m); + + if (key_ptr && status == EFI_SUCCESS) + *key_ptr = key; + if (desc_ver && status == EFI_SUCCESS) + *desc_ver = desc_version; + +fail: + *map = m; + return status; +} + + +unsigned long get_dram_base(efi_system_table_t *sys_table_arg) +{ + efi_status_t status; + unsigned long map_size; + unsigned long membase = EFI_ERROR; + struct efi_memory_map map; + efi_memory_desc_t *md; + + status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map, + &map_size, &map.desc_size, NULL, NULL); + if (status != EFI_SUCCESS) + return membase; + + map.map_end = map.map + map_size; + + for_each_efi_memory_desc(&map, md) + if (md->attribute & EFI_MEMORY_WB) + if (membase > md->phys_addr) + membase = md->phys_addr; + + efi_call_early(free_pool, map.map); + + return membase; +} + +/* + * Allocate at the highest possible address that is not above 'max'. + */ +efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg, + unsigned long size, unsigned long align, + unsigned long *addr, unsigned long max) +{ + unsigned long map_size, desc_size; + efi_memory_desc_t *map; + efi_status_t status; + unsigned long nr_pages; + u64 max_addr = 0; + int i; + + status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, + NULL, NULL); + if (status != EFI_SUCCESS) + goto fail; + + /* + * Enforce minimum alignment that EFI requires when requesting + * a specific address. We are doing page-based allocations, + * so we must be aligned to a page. + */ + if (align < EFI_ALLOC_ALIGN) + align = EFI_ALLOC_ALIGN; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; +again: + for (i = 0; i < map_size / desc_size; i++) { + efi_memory_desc_t *desc; + unsigned long m = (unsigned long)map; + u64 start, end; + + desc = (efi_memory_desc_t *)(m + (i * desc_size)); + if (desc->type != EFI_CONVENTIONAL_MEMORY) + continue; + + if (desc->num_pages < nr_pages) + continue; + + start = desc->phys_addr; + end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT); + + if (end > max) + end = max; + + if ((start + size) > end) + continue; + + if (round_down(end - size, align) < start) + continue; + + start = round_down(end - size, align); + + /* + * Don't allocate at 0x0. It will confuse code that + * checks pointers against NULL. + */ + if (start == 0x0) + continue; + + if (start > max_addr) + max_addr = start; + } + + if (!max_addr) + status = EFI_NOT_FOUND; + else { + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &max_addr); + if (status != EFI_SUCCESS) { + max = max_addr; + max_addr = 0; + goto again; + } + + *addr = max_addr; + } + + efi_call_early(free_pool, map); +fail: + return status; +} + +/* + * Allocate at the lowest possible address. + */ +efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, + unsigned long size, unsigned long align, + unsigned long *addr) +{ + unsigned long map_size, desc_size; + efi_memory_desc_t *map; + efi_status_t status; + unsigned long nr_pages; + int i; + + status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, + NULL, NULL); + if (status != EFI_SUCCESS) + goto fail; + + /* + * Enforce minimum alignment that EFI requires when requesting + * a specific address. We are doing page-based allocations, + * so we must be aligned to a page. + */ + if (align < EFI_ALLOC_ALIGN) + align = EFI_ALLOC_ALIGN; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + for (i = 0; i < map_size / desc_size; i++) { + efi_memory_desc_t *desc; + unsigned long m = (unsigned long)map; + u64 start, end; + + desc = (efi_memory_desc_t *)(m + (i * desc_size)); + + if (desc->type != EFI_CONVENTIONAL_MEMORY) + continue; + + if (desc->num_pages < nr_pages) + continue; + + start = desc->phys_addr; + end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT); + + /* + * Don't allocate at 0x0. It will confuse code that + * checks pointers against NULL. Skip the first 8 + * bytes so we start at a nice even number. + */ + if (start == 0x0) + start += 8; + + start = round_up(start, align); + if ((start + size) > end) + continue; + + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &start); + if (status == EFI_SUCCESS) { + *addr = start; + break; + } + } + + if (i == map_size / desc_size) + status = EFI_NOT_FOUND; + + efi_call_early(free_pool, map); +fail: + return status; +} + +void efi_free(efi_system_table_t *sys_table_arg, unsigned long size, + unsigned long addr) +{ + unsigned long nr_pages; + + if (!size) + return; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + efi_call_early(free_pages, addr, nr_pages); +} + +/* + * Parse the ASCII string 'cmdline' for EFI options, denoted by the efi= + * option, e.g. efi=nochunk. + * + * It should be noted that efi= is parsed in two very different + * environments, first in the early boot environment of the EFI boot + * stub, and subsequently during the kernel boot. + */ +efi_status_t efi_parse_options(char *cmdline) +{ + char *str; + + /* + * If no EFI parameters were specified on the cmdline we've got + * nothing to do. + */ + str = strstr(cmdline, "efi="); + if (!str) + return EFI_SUCCESS; + + /* Skip ahead to first argument */ + str += strlen("efi="); + + /* + * Remember, because efi= is also used by the kernel we need to + * skip over arguments we don't understand. + */ + while (*str) { + if (!strncmp(str, "nochunk", 7)) { + str += strlen("nochunk"); + __chunk_size = -1UL; + } + + /* Group words together, delimited by "," */ + while (*str && *str != ',') + str++; + + if (*str == ',') + str++; + } + + return EFI_SUCCESS; +} + +/* + * Check the cmdline for a LILO-style file= arguments. + * + * We only support loading a file from the same filesystem as + * the kernel image. + */ +efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg, + efi_loaded_image_t *image, + char *cmd_line, char *option_string, + unsigned long max_addr, + unsigned long *load_addr, + unsigned long *load_size) +{ + struct file_info *files; + unsigned long file_addr; + u64 file_size_total; + efi_file_handle_t *fh = NULL; + efi_status_t status; + int nr_files; + char *str; + int i, j, k; + + file_addr = 0; + file_size_total = 0; + + str = cmd_line; + + j = 0; /* See close_handles */ + + if (!load_addr || !load_size) + return EFI_INVALID_PARAMETER; + + *load_addr = 0; + *load_size = 0; + + if (!str || !*str) + return EFI_SUCCESS; + + for (nr_files = 0; *str; nr_files++) { + str = strstr(str, option_string); + if (!str) + break; + + str += strlen(option_string); + + /* Skip any leading slashes */ + while (*str == '/' || *str == '\\') + str++; + + while (*str && *str != ' ' && *str != '\n') + str++; + } + + if (!nr_files) + return EFI_SUCCESS; + + status = efi_call_early(allocate_pool, EFI_LOADER_DATA, + nr_files * sizeof(*files), (void **)&files); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to alloc mem for file handle list\n"); + goto fail; + } + + str = cmd_line; + for (i = 0; i < nr_files; i++) { + struct file_info *file; + efi_char16_t filename_16[256]; + efi_char16_t *p; + + str = strstr(str, option_string); + if (!str) + break; + + str += strlen(option_string); + + file = &files[i]; + p = filename_16; + + /* Skip any leading slashes */ + while (*str == '/' || *str == '\\') + str++; + + while (*str && *str != ' ' && *str != '\n') { + if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16)) + break; + + if (*str == '/') { + *p++ = '\\'; + str++; + } else { + *p++ = *str++; + } + } + + *p = '\0'; + + /* Only open the volume once. */ + if (!i) { + status = efi_open_volume(sys_table_arg, image, + (void **)&fh); + if (status != EFI_SUCCESS) + goto free_files; + } + + status = efi_file_size(sys_table_arg, fh, filename_16, + (void **)&file->handle, &file->size); + if (status != EFI_SUCCESS) + goto close_handles; + + file_size_total += file->size; + } + + if (file_size_total) { + unsigned long addr; + + /* + * Multiple files need to be at consecutive addresses in memory, + * so allocate enough memory for all the files. This is used + * for loading multiple files. + */ + status = efi_high_alloc(sys_table_arg, file_size_total, 0x1000, + &file_addr, max_addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to alloc highmem for files\n"); + goto close_handles; + } + + /* We've run out of free low memory. */ + if (file_addr > max_addr) { + pr_efi_err(sys_table_arg, "We've run out of free low memory\n"); + status = EFI_INVALID_PARAMETER; + goto free_file_total; + } + + addr = file_addr; + for (j = 0; j < nr_files; j++) { + unsigned long size; + + size = files[j].size; + while (size) { + unsigned long chunksize; + if (size > __chunk_size) + chunksize = __chunk_size; + else + chunksize = size; + + status = efi_file_read(files[j].handle, + &chunksize, + (void *)addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to read file\n"); + goto free_file_total; + } + addr += chunksize; + size -= chunksize; + } + + efi_file_close(files[j].handle); + } + + } + + efi_call_early(free_pool, files); + + *load_addr = file_addr; + *load_size = file_size_total; + + return status; + +free_file_total: + efi_free(sys_table_arg, file_size_total, file_addr); + +close_handles: + for (k = j; k < i; k++) + efi_file_close(files[k].handle); +free_files: + efi_call_early(free_pool, files); +fail: + *load_addr = 0; + *load_size = 0; + + return status; +} +/* + * Relocate a kernel image, either compressed or uncompressed. + * In the ARM64 case, all kernel images are currently + * uncompressed, and as such when we relocate it we need to + * allocate additional space for the BSS segment. Any low + * memory that this function should avoid needs to be + * unavailable in the EFI memory map, as if the preferred + * address is not available the lowest available address will + * be used. + */ +efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg, + unsigned long *image_addr, + unsigned long image_size, + unsigned long alloc_size, + unsigned long preferred_addr, + unsigned long alignment) +{ + unsigned long cur_image_addr; + unsigned long new_addr = 0; + efi_status_t status; + unsigned long nr_pages; + efi_physical_addr_t efi_addr = preferred_addr; + + if (!image_addr || !image_size || !alloc_size) + return EFI_INVALID_PARAMETER; + if (alloc_size < image_size) + return EFI_INVALID_PARAMETER; + + cur_image_addr = *image_addr; + + /* + * The EFI firmware loader could have placed the kernel image + * anywhere in memory, but the kernel has restrictions on the + * max physical address it can run at. Some architectures + * also have a prefered address, so first try to relocate + * to the preferred address. If that fails, allocate as low + * as possible while respecting the required alignment. + */ + nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &efi_addr); + new_addr = efi_addr; + /* + * If preferred address allocation failed allocate as low as + * possible. + */ + if (status != EFI_SUCCESS) { + status = efi_low_alloc(sys_table_arg, alloc_size, alignment, + &new_addr); + } + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n"); + return status; + } + + /* + * We know source/dest won't overlap since both memory ranges + * have been allocated by UEFI, so we can safely use memcpy. + */ + memcpy((void *)new_addr, (void *)cur_image_addr, image_size); + + /* Return the new address of the relocated image. */ + *image_addr = new_addr; + + return status; +} + +/* + * Get the number of UTF-8 bytes corresponding to an UTF-16 character. + * This overestimates for surrogates, but that is okay. + */ +static int efi_utf8_bytes(u16 c) +{ + return 1 + (c >= 0x80) + (c >= 0x800); +} + +/* + * Convert an UTF-16 string, not necessarily null terminated, to UTF-8. + */ +static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n) +{ + unsigned int c; + + while (n--) { + c = *src++; + if (n && c >= 0xd800 && c <= 0xdbff && + *src >= 0xdc00 && *src <= 0xdfff) { + c = 0x10000 + ((c & 0x3ff) << 10) + (*src & 0x3ff); + src++; + n--; + } + if (c >= 0xd800 && c <= 0xdfff) + c = 0xfffd; /* Unmatched surrogate */ + if (c < 0x80) { + *dst++ = c; + continue; + } + if (c < 0x800) { + *dst++ = 0xc0 + (c >> 6); + goto t1; + } + if (c < 0x10000) { + *dst++ = 0xe0 + (c >> 12); + goto t2; + } + *dst++ = 0xf0 + (c >> 18); + *dst++ = 0x80 + ((c >> 12) & 0x3f); + t2: + *dst++ = 0x80 + ((c >> 6) & 0x3f); + t1: + *dst++ = 0x80 + (c & 0x3f); + } + + return dst; +} + +/* + * Convert the unicode UEFI command line to ASCII to pass to kernel. + * Size of memory allocated return in *cmd_line_len. + * Returns NULL on error. + */ +char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, + efi_loaded_image_t *image, + int *cmd_line_len) +{ + const u16 *s2; + u8 *s1 = NULL; + unsigned long cmdline_addr = 0; + int load_options_chars = image->load_options_size / 2; /* UTF-16 */ + const u16 *options = image->load_options; + int options_bytes = 0; /* UTF-8 bytes */ + int options_chars = 0; /* UTF-16 chars */ + efi_status_t status; + u16 zero = 0; + + if (options) { + s2 = options; + while (*s2 && *s2 != '\n' + && options_chars < load_options_chars) { + options_bytes += efi_utf8_bytes(*s2++); + options_chars++; + } + } + + if (!options_chars) { + /* No command line options, so return empty string*/ + options = &zero; + } + + options_bytes++; /* NUL termination */ + + status = efi_low_alloc(sys_table_arg, options_bytes, 0, &cmdline_addr); + if (status != EFI_SUCCESS) + return NULL; + + s1 = (u8 *)cmdline_addr; + s2 = (const u16 *)options; + + s1 = efi_utf16_to_utf8(s1, s2, options_chars); + *s1 = '\0'; + + *cmd_line_len = options_bytes; + return (char *)cmdline_addr; +} diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h new file mode 100644 index 000000000..e334a01cf --- /dev/null +++ b/drivers/firmware/efi/libstub/efistub.h @@ -0,0 +1,50 @@ + +#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H +#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H + +/* error code which can't be mistaken for valid address */ +#define EFI_ERROR (~0UL) + +#undef memcpy +#undef memset +#undef memmove + +void efi_char16_printk(efi_system_table_t *, efi_char16_t *); + +efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image, + void **__fh); + +efi_status_t efi_file_size(efi_system_table_t *sys_table_arg, void *__fh, + efi_char16_t *filename_16, void **handle, + u64 *file_sz); + +efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr); + +efi_status_t efi_file_close(void *handle); + +unsigned long get_dram_base(efi_system_table_t *sys_table_arg); + +efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, + unsigned long orig_fdt_size, + void *fdt, int new_fdt_size, char *cmdline_ptr, + u64 initrd_addr, u64 initrd_size, + efi_memory_desc_t *memory_map, + unsigned long map_size, unsigned long desc_size, + u32 desc_ver); + +efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, + void *handle, + unsigned long *new_fdt_addr, + unsigned long max_addr, + u64 initrd_addr, u64 initrd_size, + char *cmdline_ptr, + unsigned long fdt_addr, + unsigned long fdt_size); + +void *get_fdt(efi_system_table_t *sys_table, unsigned long *fdt_size); + +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, + unsigned long desc_size, efi_memory_desc_t *runtime_map, + int *count); + +#endif diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c new file mode 100644 index 000000000..ef5d764e2 --- /dev/null +++ b/drivers/firmware/efi/libstub/fdt.c @@ -0,0 +1,348 @@ +/* + * FDT related Helper functions used by the EFI stub on multiple + * architectures. This should be #included by the EFI stub + * implementation files. + * + * Copyright 2013 Linaro Limited; author Roy Franz + * + * This file is part of the Linux kernel, and is made available + * under the terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <linux/libfdt.h> +#include <asm/efi.h> + +#include "efistub.h" + +efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, + unsigned long orig_fdt_size, + void *fdt, int new_fdt_size, char *cmdline_ptr, + u64 initrd_addr, u64 initrd_size, + efi_memory_desc_t *memory_map, + unsigned long map_size, unsigned long desc_size, + u32 desc_ver) +{ + int node, prev, num_rsv; + int status; + u32 fdt_val32; + u64 fdt_val64; + + /* Do some checks on provided FDT, if it exists*/ + if (orig_fdt) { + if (fdt_check_header(orig_fdt)) { + pr_efi_err(sys_table, "Device Tree header not valid!\n"); + return EFI_LOAD_ERROR; + } + /* + * We don't get the size of the FDT if we get if from a + * configuration table. + */ + if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) { + pr_efi_err(sys_table, "Truncated device tree! foo!\n"); + return EFI_LOAD_ERROR; + } + } + + if (orig_fdt) + status = fdt_open_into(orig_fdt, fdt, new_fdt_size); + else + status = fdt_create_empty_tree(fdt, new_fdt_size); + + if (status != 0) + goto fdt_set_fail; + + /* + * Delete any memory nodes present. We must delete nodes which + * early_init_dt_scan_memory may try to use. + */ + prev = 0; + for (;;) { + const char *type; + int len; + + node = fdt_next_node(fdt, prev, NULL); + if (node < 0) + break; + + type = fdt_getprop(fdt, node, "device_type", &len); + if (type && strncmp(type, "memory", len) == 0) { + fdt_del_node(fdt, node); + continue; + } + + prev = node; + } + + /* + * Delete all memory reserve map entries. When booting via UEFI, + * kernel will use the UEFI memory map to find reserved regions. + */ + num_rsv = fdt_num_mem_rsv(fdt); + while (num_rsv-- > 0) + fdt_del_mem_rsv(fdt, num_rsv); + + node = fdt_subnode_offset(fdt, 0, "chosen"); + if (node < 0) { + node = fdt_add_subnode(fdt, 0, "chosen"); + if (node < 0) { + status = node; /* node is error code when negative */ + goto fdt_set_fail; + } + } + + if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) { + status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr, + strlen(cmdline_ptr) + 1); + if (status) + goto fdt_set_fail; + } + + /* Set initrd address/end in device tree, if present */ + if (initrd_size != 0) { + u64 initrd_image_end; + u64 initrd_image_start = cpu_to_fdt64(initrd_addr); + + status = fdt_setprop(fdt, node, "linux,initrd-start", + &initrd_image_start, sizeof(u64)); + if (status) + goto fdt_set_fail; + initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size); + status = fdt_setprop(fdt, node, "linux,initrd-end", + &initrd_image_end, sizeof(u64)); + if (status) + goto fdt_set_fail; + } + + /* Add FDT entries for EFI runtime services in chosen node. */ + node = fdt_subnode_offset(fdt, 0, "chosen"); + fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table); + status = fdt_setprop(fdt, node, "linux,uefi-system-table", + &fdt_val64, sizeof(fdt_val64)); + if (status) + goto fdt_set_fail; + + fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-start", + &fdt_val64, sizeof(fdt_val64)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(map_size); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-size", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(desc_size); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(desc_ver); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + /* + * Add kernel version banner so stub/kernel match can be + * verified. + */ + status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver", + linux_banner); + if (status) + goto fdt_set_fail; + + return EFI_SUCCESS; + +fdt_set_fail: + if (status == -FDT_ERR_NOSPACE) + return EFI_BUFFER_TOO_SMALL; + + return EFI_LOAD_ERROR; +} + +#ifndef EFI_FDT_ALIGN +#define EFI_FDT_ALIGN EFI_PAGE_SIZE +#endif + +/* + * Allocate memory for a new FDT, then add EFI, commandline, and + * initrd related fields to the FDT. This routine increases the + * FDT allocation size until the allocated memory is large + * enough. EFI allocations are in EFI_PAGE_SIZE granules, + * which are fixed at 4K bytes, so in most cases the first + * allocation should succeed. + * EFI boot services are exited at the end of this function. + * There must be no allocations between the get_memory_map() + * call and the exit_boot_services() call, so the exiting of + * boot services is very tightly tied to the creation of the FDT + * with the final memory map in it. + */ + +efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, + void *handle, + unsigned long *new_fdt_addr, + unsigned long max_addr, + u64 initrd_addr, u64 initrd_size, + char *cmdline_ptr, + unsigned long fdt_addr, + unsigned long fdt_size) +{ + unsigned long map_size, desc_size; + u32 desc_ver; + unsigned long mmap_key; + efi_memory_desc_t *memory_map, *runtime_map; + unsigned long new_fdt_size; + efi_status_t status; + int runtime_entry_count = 0; + + /* + * Get a copy of the current memory map that we will use to prepare + * the input for SetVirtualAddressMap(). We don't have to worry about + * subsequent allocations adding entries, since they could not affect + * the number of EFI_MEMORY_RUNTIME regions. + */ + status = efi_get_memory_map(sys_table, &runtime_map, &map_size, + &desc_size, &desc_ver, &mmap_key); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n"); + return status; + } + + pr_efi(sys_table, + "Exiting boot services and installing virtual address map...\n"); + + /* + * Estimate size of new FDT, and allocate memory for it. We + * will allocate a bigger buffer if this ends up being too + * small, so a rough guess is OK here. + */ + new_fdt_size = fdt_size + EFI_PAGE_SIZE; + while (1) { + status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN, + new_fdt_addr, max_addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n"); + goto fail; + } + + /* + * Now that we have done our final memory allocation (and free) + * we can get the memory map key needed for + * exit_boot_services(). + */ + status = efi_get_memory_map(sys_table, &memory_map, &map_size, + &desc_size, &desc_ver, &mmap_key); + if (status != EFI_SUCCESS) + goto fail_free_new_fdt; + + status = update_fdt(sys_table, + (void *)fdt_addr, fdt_size, + (void *)*new_fdt_addr, new_fdt_size, + cmdline_ptr, initrd_addr, initrd_size, + memory_map, map_size, desc_size, desc_ver); + + /* Succeeding the first time is the expected case. */ + if (status == EFI_SUCCESS) + break; + + if (status == EFI_BUFFER_TOO_SMALL) { + /* + * We need to allocate more space for the new + * device tree, so free existing buffer that is + * too small. Also free memory map, as we will need + * to get new one that reflects the free/alloc we do + * on the device tree buffer. + */ + efi_free(sys_table, new_fdt_size, *new_fdt_addr); + sys_table->boottime->free_pool(memory_map); + new_fdt_size += EFI_PAGE_SIZE; + } else { + pr_efi_err(sys_table, "Unable to constuct new device tree.\n"); + goto fail_free_mmap; + } + } + + /* + * Update the memory map with virtual addresses. The function will also + * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME + * entries so that we can pass it straight into SetVirtualAddressMap() + */ + efi_get_virtmap(memory_map, map_size, desc_size, runtime_map, + &runtime_entry_count); + + /* Now we are ready to exit_boot_services.*/ + status = sys_table->boottime->exit_boot_services(handle, mmap_key); + + if (status == EFI_SUCCESS) { + efi_set_virtual_address_map_t *svam; + + /* Install the new virtual address map */ + svam = sys_table->runtime->set_virtual_address_map; + status = svam(runtime_entry_count * desc_size, desc_size, + desc_ver, runtime_map); + + /* + * We are beyond the point of no return here, so if the call to + * SetVirtualAddressMap() failed, we need to signal that to the + * incoming kernel but proceed normally otherwise. + */ + if (status != EFI_SUCCESS) { + int l; + + /* + * Set the virtual address field of all + * EFI_MEMORY_RUNTIME entries to 0. This will signal + * the incoming kernel that no virtual translation has + * been installed. + */ + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *p = (void *)memory_map + l; + + if (p->attribute & EFI_MEMORY_RUNTIME) + p->virt_addr = 0; + } + } + return EFI_SUCCESS; + } + + pr_efi_err(sys_table, "Exit boot services failed.\n"); + +fail_free_mmap: + sys_table->boottime->free_pool(memory_map); + +fail_free_new_fdt: + efi_free(sys_table, new_fdt_size, *new_fdt_addr); + +fail: + sys_table->boottime->free_pool(runtime_map); + return EFI_LOAD_ERROR; +} + +void *get_fdt(efi_system_table_t *sys_table, unsigned long *fdt_size) +{ + efi_guid_t fdt_guid = DEVICE_TREE_GUID; + efi_config_table_t *tables; + void *fdt; + int i; + + tables = (efi_config_table_t *) sys_table->tables; + fdt = NULL; + + for (i = 0; i < sys_table->nr_tables; i++) + if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) { + fdt = (void *) tables[i].table; + if (fdt_check_header(fdt) != 0) { + pr_efi_err(sys_table, "Invalid header detected on UEFI supplied FDT, ignoring ...\n"); + return NULL; + } + *fdt_size = fdt_totalsize(fdt); + break; + } + + return fdt; +} diff --git a/drivers/firmware/efi/reboot.c b/drivers/firmware/efi/reboot.c new file mode 100644 index 000000000..9c59d1c79 --- /dev/null +++ b/drivers/firmware/efi/reboot.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Intel Corporation; author Matt Fleming + * Copyright (c) 2014 Red Hat, Inc., Mark Salter <msalter@redhat.com> + */ +#include <linux/efi.h> +#include <linux/reboot.h> + +int efi_reboot_quirk_mode = -1; + +void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) +{ + int efi_mode; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return; + + switch (reboot_mode) { + case REBOOT_WARM: + case REBOOT_SOFT: + efi_mode = EFI_RESET_WARM; + break; + default: + efi_mode = EFI_RESET_COLD; + break; + } + + /* + * If a quirk forced an EFI reset mode, always use that. + */ + if (efi_reboot_quirk_mode != -1) + efi_mode = efi_reboot_quirk_mode; + + efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL); +} + +bool __weak efi_poweroff_required(void) +{ + return false; +} + +static void efi_power_off(void) +{ + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL); +} + +static int __init efi_shutdown_init(void) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return -ENODEV; + + if (efi_poweroff_required()) + pm_power_off = efi_power_off; + + return 0; +} +late_initcall(efi_shutdown_init); diff --git a/drivers/firmware/efi/runtime-map.c b/drivers/firmware/efi/runtime-map.c new file mode 100644 index 000000000..5c55227a3 --- /dev/null +++ b/drivers/firmware/efi/runtime-map.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/efi/runtime-map.c + * Copyright (C) 2013 Red Hat, Inc., Dave Young <dyoung@redhat.com> + * + * This file is released under the GPLv2. + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/efi.h> +#include <linux/slab.h> + +#include <asm/setup.h> + +static void *efi_runtime_map; +static int nr_efi_runtime_map; +static u32 efi_memdesc_size; + +struct efi_runtime_map_entry { + efi_memory_desc_t md; + struct kobject kobj; /* kobject for each entry */ +}; + +static struct efi_runtime_map_entry **map_entries; + +struct map_attribute { + struct attribute attr; + ssize_t (*show)(struct efi_runtime_map_entry *entry, char *buf); +}; + +static inline struct map_attribute *to_map_attr(struct attribute *attr) +{ + return container_of(attr, struct map_attribute, attr); +} + +static ssize_t type_show(struct efi_runtime_map_entry *entry, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%x\n", entry->md.type); +} + +#define EFI_RUNTIME_FIELD(var) entry->md.var + +#define EFI_RUNTIME_U64_ATTR_SHOW(name) \ +static ssize_t name##_show(struct efi_runtime_map_entry *entry, char *buf) \ +{ \ + return snprintf(buf, PAGE_SIZE, "0x%llx\n", EFI_RUNTIME_FIELD(name)); \ +} + +EFI_RUNTIME_U64_ATTR_SHOW(phys_addr); +EFI_RUNTIME_U64_ATTR_SHOW(virt_addr); +EFI_RUNTIME_U64_ATTR_SHOW(num_pages); +EFI_RUNTIME_U64_ATTR_SHOW(attribute); + +static inline struct efi_runtime_map_entry *to_map_entry(struct kobject *kobj) +{ + return container_of(kobj, struct efi_runtime_map_entry, kobj); +} + +static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct efi_runtime_map_entry *entry = to_map_entry(kobj); + struct map_attribute *map_attr = to_map_attr(attr); + + return map_attr->show(entry, buf); +} + +static struct map_attribute map_type_attr = __ATTR_RO(type); +static struct map_attribute map_phys_addr_attr = __ATTR_RO(phys_addr); +static struct map_attribute map_virt_addr_attr = __ATTR_RO(virt_addr); +static struct map_attribute map_num_pages_attr = __ATTR_RO(num_pages); +static struct map_attribute map_attribute_attr = __ATTR_RO(attribute); + +/* + * These are default attributes that are added for every memmap entry. + */ +static struct attribute *def_attrs[] = { + &map_type_attr.attr, + &map_phys_addr_attr.attr, + &map_virt_addr_attr.attr, + &map_num_pages_attr.attr, + &map_attribute_attr.attr, + NULL +}; + +static const struct sysfs_ops map_attr_ops = { + .show = map_attr_show, +}; + +static void map_release(struct kobject *kobj) +{ + struct efi_runtime_map_entry *entry; + + entry = to_map_entry(kobj); + kfree(entry); +} + +static struct kobj_type __refdata map_ktype = { + .sysfs_ops = &map_attr_ops, + .default_attrs = def_attrs, + .release = map_release, +}; + +static struct kset *map_kset; + +static struct efi_runtime_map_entry * +add_sysfs_runtime_map_entry(struct kobject *kobj, int nr) +{ + int ret; + struct efi_runtime_map_entry *entry; + + if (!map_kset) { + map_kset = kset_create_and_add("runtime-map", NULL, kobj); + if (!map_kset) + return ERR_PTR(-ENOMEM); + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + kset_unregister(map_kset); + map_kset = NULL; + return ERR_PTR(-ENOMEM); + } + + memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size, + sizeof(efi_memory_desc_t)); + + kobject_init(&entry->kobj, &map_ktype); + entry->kobj.kset = map_kset; + ret = kobject_add(&entry->kobj, NULL, "%d", nr); + if (ret) { + kobject_put(&entry->kobj); + kset_unregister(map_kset); + map_kset = NULL; + return ERR_PTR(ret); + } + + return entry; +} + +int efi_get_runtime_map_size(void) +{ + return nr_efi_runtime_map * efi_memdesc_size; +} + +int efi_get_runtime_map_desc_size(void) +{ + return efi_memdesc_size; +} + +int efi_runtime_map_copy(void *buf, size_t bufsz) +{ + size_t sz = efi_get_runtime_map_size(); + + if (sz > bufsz) + sz = bufsz; + + memcpy(buf, efi_runtime_map, sz); + return 0; +} + +void efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size) +{ + efi_runtime_map = map; + nr_efi_runtime_map = nr_entries; + efi_memdesc_size = desc_size; +} + +int __init efi_runtime_map_init(struct kobject *efi_kobj) +{ + int i, j, ret = 0; + struct efi_runtime_map_entry *entry; + + if (!efi_runtime_map) + return 0; + + map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL); + if (!map_entries) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nr_efi_runtime_map; i++) { + entry = add_sysfs_runtime_map_entry(efi_kobj, i); + if (IS_ERR(entry)) { + ret = PTR_ERR(entry); + goto out_add_entry; + } + *(map_entries + i) = entry; + } + + return 0; +out_add_entry: + for (j = i - 1; j >= 0; j--) { + entry = *(map_entries + j); + kobject_put(&entry->kobj); + } +out: + return ret; +} diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c new file mode 100644 index 000000000..228bbf910 --- /dev/null +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -0,0 +1,305 @@ +/* + * runtime-wrappers.c - Runtime Services function call wrappers + * + * Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org> + * + * Split off from arch/x86/platform/efi/efi.c + * + * Copyright (C) 1999 VA Linux Systems + * Copyright (C) 1999 Walt Drummond <drummond@valinux.com> + * Copyright (C) 1999-2002 Hewlett-Packard Co. + * Copyright (C) 2005-2008 Intel Co. + * Copyright (C) 2013 SuSE Labs + * + * This file is released under the GPLv2. + */ + +#include <linux/bug.h> +#include <linux/efi.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <asm/efi.h> + +/* + * According to section 7.1 of the UEFI spec, Runtime Services are not fully + * reentrant, and there are particular combinations of calls that need to be + * serialized. (source: UEFI Specification v2.4A) + * + * Table 31. Rules for Reentry Into Runtime Services + * +------------------------------------+-------------------------------+ + * | If previous call is busy in | Forbidden to call | + * +------------------------------------+-------------------------------+ + * | Any | SetVirtualAddressMap() | + * +------------------------------------+-------------------------------+ + * | ConvertPointer() | ConvertPointer() | + * +------------------------------------+-------------------------------+ + * | SetVariable() | ResetSystem() | + * | UpdateCapsule() | | + * | SetTime() | | + * | SetWakeupTime() | | + * | GetNextHighMonotonicCount() | | + * +------------------------------------+-------------------------------+ + * | GetVariable() | GetVariable() | + * | GetNextVariableName() | GetNextVariableName() | + * | SetVariable() | SetVariable() | + * | QueryVariableInfo() | QueryVariableInfo() | + * | UpdateCapsule() | UpdateCapsule() | + * | QueryCapsuleCapabilities() | QueryCapsuleCapabilities() | + * | GetNextHighMonotonicCount() | GetNextHighMonotonicCount() | + * +------------------------------------+-------------------------------+ + * | GetTime() | GetTime() | + * | SetTime() | SetTime() | + * | GetWakeupTime() | GetWakeupTime() | + * | SetWakeupTime() | SetWakeupTime() | + * +------------------------------------+-------------------------------+ + * + * Due to the fact that the EFI pstore may write to the variable store in + * interrupt context, we need to use a spinlock for at least the groups that + * contain SetVariable() and QueryVariableInfo(). That leaves little else, as + * none of the remaining functions are actually ever called at runtime. + * So let's just use a single spinlock to serialize all Runtime Services calls. + */ +static DEFINE_SPINLOCK(efi_runtime_lock); + +/* + * Some runtime services calls can be reentrant under NMI, even if the table + * above says they are not. (source: UEFI Specification v2.4A) + * + * Table 32. Functions that may be called after Machine Check, INIT and NMI + * +----------------------------+------------------------------------------+ + * | Function | Called after Machine Check, INIT and NMI | + * +----------------------------+------------------------------------------+ + * | GetTime() | Yes, even if previously busy. | + * | GetVariable() | Yes, even if previously busy | + * | GetNextVariableName() | Yes, even if previously busy | + * | QueryVariableInfo() | Yes, even if previously busy | + * | SetVariable() | Yes, even if previously busy | + * | UpdateCapsule() | Yes, even if previously busy | + * | QueryCapsuleCapabilities() | Yes, even if previously busy | + * | ResetSystem() | Yes, even if previously busy | + * +----------------------------+------------------------------------------+ + * + * In order to prevent deadlocks under NMI, the wrappers for these functions + * may only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi(). + * However, not all of the services listed are reachable through NMI code paths, + * so the the special handling as suggested by the UEFI spec is only implemented + * for QueryVariableInfo() and SetVariable(), as these can be reached in NMI + * context through efi_pstore_write(). + */ + +/* + * As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"), + * the EFI specification requires that callers of the time related runtime + * functions serialize with other CMOS accesses in the kernel, as the EFI time + * functions may choose to also use the legacy CMOS RTC. + */ +__weak DEFINE_SPINLOCK(rtc_lock); + +static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + spin_lock(&efi_runtime_lock); + status = efi_call_virt(get_time, tm, tc); + spin_unlock(&efi_runtime_lock); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_set_time(efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + spin_lock(&efi_runtime_lock); + status = efi_call_virt(set_time, tm); + spin_unlock(&efi_runtime_lock); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled, + efi_bool_t *pending, + efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + spin_lock(&efi_runtime_lock); + status = efi_call_virt(get_wakeup_time, enabled, pending, tm); + spin_unlock(&efi_runtime_lock); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + spin_lock(&efi_runtime_lock); + status = efi_call_virt(set_wakeup_time, enabled, tm); + spin_unlock(&efi_runtime_lock); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_variable(efi_char16_t *name, + efi_guid_t *vendor, + u32 *attr, + unsigned long *data_size, + void *data) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(get_variable, name, vendor, attr, data_size, + data); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, + efi_char16_t *name, + efi_guid_t *vendor) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(get_next_variable, name_size, name, vendor); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static efi_status_t virt_efi_set_variable(efi_char16_t *name, + efi_guid_t *vendor, + u32 attr, + unsigned long data_size, + void *data) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(set_variable, name, vendor, attr, data_size, + data); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static efi_status_t +virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor, + u32 attr, unsigned long data_size, + void *data) +{ + unsigned long flags; + efi_status_t status; + + if (!spin_trylock_irqsave(&efi_runtime_lock, flags)) + return EFI_NOT_READY; + + status = efi_call_virt(set_variable, name, vendor, attr, data_size, + data); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + + +static efi_status_t virt_efi_query_variable_info(u32 attr, + u64 *storage_space, + u64 *remaining_space, + u64 *max_variable_size) +{ + unsigned long flags; + efi_status_t status; + + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(query_variable_info, attr, storage_space, + remaining_space, max_variable_size); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(get_next_high_mono_count, count); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static void virt_efi_reset_system(int reset_type, + efi_status_t status, + unsigned long data_size, + efi_char16_t *data) +{ + unsigned long flags; + + spin_lock_irqsave(&efi_runtime_lock, flags); + __efi_call_virt(reset_system, reset_type, status, data_size, data); + spin_unlock_irqrestore(&efi_runtime_lock, flags); +} + +static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules, + unsigned long count, + unsigned long sg_list) +{ + unsigned long flags; + efi_status_t status; + + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(update_capsule, capsules, count, sg_list); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, + unsigned long count, + u64 *max_size, + int *reset_type) +{ + unsigned long flags; + efi_status_t status; + + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + spin_lock_irqsave(&efi_runtime_lock, flags); + status = efi_call_virt(query_capsule_caps, capsules, count, max_size, + reset_type); + spin_unlock_irqrestore(&efi_runtime_lock, flags); + return status; +} + +void efi_native_runtime_setup(void) +{ + efi.get_time = virt_efi_get_time; + efi.set_time = virt_efi_set_time; + efi.get_wakeup_time = virt_efi_get_wakeup_time; + efi.set_wakeup_time = virt_efi_set_wakeup_time; + efi.get_variable = virt_efi_get_variable; + efi.get_next_variable = virt_efi_get_next_variable; + efi.set_variable = virt_efi_set_variable; + efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking; + efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; + efi.reset_system = virt_efi_reset_system; + efi.query_variable_info = virt_efi_query_variable_info; + efi.update_capsule = virt_efi_update_capsule; + efi.query_capsule_caps = virt_efi_query_capsule_caps; +} diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c new file mode 100644 index 000000000..70a0fb105 --- /dev/null +++ b/drivers/firmware/efi/vars.c @@ -0,0 +1,1096 @@ +/* + * Originally from efivars.c + * + * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com> + * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/capability.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/smp.h> +#include <linux/efi.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/ucs2_string.h> + +/* Private pointer to registered efivars */ +static struct efivars *__efivars; + +static bool efivar_wq_enabled = true; +DECLARE_WORK(efivar_work, NULL); +EXPORT_SYMBOL_GPL(efivar_work); + +static bool +validate_device_path(efi_char16_t *var_name, int match, u8 *buffer, + unsigned long len) +{ + struct efi_generic_dev_path *node; + int offset = 0; + + node = (struct efi_generic_dev_path *)buffer; + + if (len < sizeof(*node)) + return false; + + while (offset <= len - sizeof(*node) && + node->length >= sizeof(*node) && + node->length <= len - offset) { + offset += node->length; + + if ((node->type == EFI_DEV_END_PATH || + node->type == EFI_DEV_END_PATH2) && + node->sub_type == EFI_DEV_END_ENTIRE) + return true; + + node = (struct efi_generic_dev_path *)(buffer + offset); + } + + /* + * If we're here then either node->length pointed past the end + * of the buffer or we reached the end of the buffer without + * finding a device path end node. + */ + return false; +} + +static bool +validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer, + unsigned long len) +{ + /* An array of 16-bit integers */ + if ((len % 2) != 0) + return false; + + return true; +} + +static bool +validate_load_option(efi_char16_t *var_name, int match, u8 *buffer, + unsigned long len) +{ + u16 filepathlength; + int i, desclength = 0, namelen; + + namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN); + + /* Either "Boot" or "Driver" followed by four digits of hex */ + for (i = match; i < match+4; i++) { + if (var_name[i] > 127 || + hex_to_bin(var_name[i] & 0xff) < 0) + return true; + } + + /* Reject it if there's 4 digits of hex and then further content */ + if (namelen > match + 4) + return false; + + /* A valid entry must be at least 8 bytes */ + if (len < 8) + return false; + + filepathlength = buffer[4] | buffer[5] << 8; + + /* + * There's no stored length for the description, so it has to be + * found by hand + */ + desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2; + + /* Each boot entry must have a descriptor */ + if (!desclength) + return false; + + /* + * If the sum of the length of the description, the claimed filepath + * length and the original header are greater than the length of the + * variable, it's malformed + */ + if ((desclength + filepathlength + 6) > len) + return false; + + /* + * And, finally, check the filepath + */ + return validate_device_path(var_name, match, buffer + desclength + 6, + filepathlength); +} + +static bool +validate_uint16(efi_char16_t *var_name, int match, u8 *buffer, + unsigned long len) +{ + /* A single 16-bit integer */ + if (len != 2) + return false; + + return true; +} + +static bool +validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer, + unsigned long len) +{ + int i; + + for (i = 0; i < len; i++) { + if (buffer[i] > 127) + return false; + + if (buffer[i] == 0) + return true; + } + + return false; +} + +struct variable_validate { + char *name; + bool (*validate)(efi_char16_t *var_name, int match, u8 *data, + unsigned long len); +}; + +static const struct variable_validate variable_validate[] = { + { "BootNext", validate_uint16 }, + { "BootOrder", validate_boot_order }, + { "DriverOrder", validate_boot_order }, + { "Boot*", validate_load_option }, + { "Driver*", validate_load_option }, + { "ConIn", validate_device_path }, + { "ConInDev", validate_device_path }, + { "ConOut", validate_device_path }, + { "ConOutDev", validate_device_path }, + { "ErrOut", validate_device_path }, + { "ErrOutDev", validate_device_path }, + { "Timeout", validate_uint16 }, + { "Lang", validate_ascii_string }, + { "PlatformLang", validate_ascii_string }, + { "", NULL }, +}; + +bool +efivar_validate(efi_char16_t *var_name, u8 *data, unsigned long len) +{ + int i; + u16 *unicode_name = var_name; + + for (i = 0; variable_validate[i].validate != NULL; i++) { + const char *name = variable_validate[i].name; + int match; + + for (match = 0; ; match++) { + char c = name[match]; + u16 u = unicode_name[match]; + + /* All special variables are plain ascii */ + if (u > 127) + return true; + + /* Wildcard in the matching name means we've matched */ + if (c == '*') + return variable_validate[i].validate(var_name, + match, data, len); + + /* Case sensitive match */ + if (c != u) + break; + + /* Reached the end of the string while matching */ + if (!c) + return variable_validate[i].validate(var_name, + match, data, len); + } + } + + return true; +} +EXPORT_SYMBOL_GPL(efivar_validate); + +static efi_status_t +check_var_size(u32 attributes, unsigned long size) +{ + const struct efivar_operations *fops = __efivars->ops; + + if (!fops->query_variable_store) + return EFI_UNSUPPORTED; + + return fops->query_variable_store(attributes, size); +} + +static int efi_status_to_err(efi_status_t status) +{ + int err; + + switch (status) { + case EFI_SUCCESS: + err = 0; + break; + case EFI_INVALID_PARAMETER: + err = -EINVAL; + break; + case EFI_OUT_OF_RESOURCES: + err = -ENOSPC; + break; + case EFI_DEVICE_ERROR: + err = -EIO; + break; + case EFI_WRITE_PROTECTED: + err = -EROFS; + break; + case EFI_SECURITY_VIOLATION: + err = -EACCES; + break; + case EFI_NOT_FOUND: + err = -ENOENT; + break; + default: + err = -EINVAL; + } + + return err; +} + +static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, + struct list_head *head) +{ + struct efivar_entry *entry, *n; + unsigned long strsize1, strsize2; + bool found = false; + + strsize1 = ucs2_strsize(variable_name, 1024); + list_for_each_entry_safe(entry, n, head, list) { + strsize2 = ucs2_strsize(entry->var.VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(variable_name, &(entry->var.VariableName), + strsize2) && + !efi_guidcmp(entry->var.VendorGuid, + *vendor)) { + found = true; + break; + } + } + return found; +} + +/* + * Returns the size of variable_name, in bytes, including the + * terminating NULL character, or variable_name_size if no NULL + * character is found among the first variable_name_size bytes. + */ +static unsigned long var_name_strnsize(efi_char16_t *variable_name, + unsigned long variable_name_size) +{ + unsigned long len; + efi_char16_t c; + + /* + * The variable name is, by definition, a NULL-terminated + * string, so make absolutely sure that variable_name_size is + * the value we expect it to be. If not, return the real size. + */ + for (len = 2; len <= variable_name_size; len += sizeof(c)) { + c = variable_name[(len / sizeof(c)) - 1]; + if (!c) + break; + } + + return min(len, variable_name_size); +} + +/* + * Print a warning when duplicate EFI variables are encountered and + * disable the sysfs workqueue since the firmware is buggy. + */ +static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid, + unsigned long len16) +{ + size_t i, len8 = len16 / sizeof(efi_char16_t); + char *str8; + + /* + * Disable the workqueue since the algorithm it uses for + * detecting new variables won't work with this buggy + * implementation of GetNextVariableName(). + */ + efivar_wq_enabled = false; + + str8 = kzalloc(len8, GFP_KERNEL); + if (!str8) + return; + + for (i = 0; i < len8; i++) + str8[i] = str16[i]; + + printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n", + str8, vendor_guid); + kfree(str8); +} + +/** + * efivar_init - build the initial list of EFI variables + * @func: callback function to invoke for every variable + * @data: function-specific data to pass to @func + * @atomic: do we need to execute the @func-loop atomically? + * @duplicates: error if we encounter duplicates on @head? + * @head: initialised head of variable list + * + * Get every EFI variable from the firmware and invoke @func. @func + * should call efivar_entry_add() to build the list of variables. + * + * Returns 0 on success, or a kernel error code on failure. + */ +int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), + void *data, bool atomic, bool duplicates, + struct list_head *head) +{ + const struct efivar_operations *ops = __efivars->ops; + unsigned long variable_name_size = 1024; + efi_char16_t *variable_name; + efi_status_t status; + efi_guid_t vendor_guid; + int err = 0; + + variable_name = kzalloc(variable_name_size, GFP_KERNEL); + if (!variable_name) { + printk(KERN_ERR "efivars: Memory allocation failed.\n"); + return -ENOMEM; + } + + spin_lock_irq(&__efivars->lock); + + /* + * Per EFI spec, the maximum storage allocated for both + * the variable name and variable data is 1024 bytes. + */ + + do { + variable_name_size = 1024; + + status = ops->get_next_variable(&variable_name_size, + variable_name, + &vendor_guid); + switch (status) { + case EFI_SUCCESS: + if (!atomic) + spin_unlock_irq(&__efivars->lock); + + variable_name_size = var_name_strnsize(variable_name, + variable_name_size); + + /* + * Some firmware implementations return the + * same variable name on multiple calls to + * get_next_variable(). Terminate the loop + * immediately as there is no guarantee that + * we'll ever see a different variable name, + * and may end up looping here forever. + */ + if (duplicates && + variable_is_present(variable_name, &vendor_guid, head)) { + dup_variable_bug(variable_name, &vendor_guid, + variable_name_size); + if (!atomic) + spin_lock_irq(&__efivars->lock); + + status = EFI_NOT_FOUND; + break; + } + + err = func(variable_name, vendor_guid, variable_name_size, data); + if (err) + status = EFI_NOT_FOUND; + + if (!atomic) + spin_lock_irq(&__efivars->lock); + + break; + case EFI_NOT_FOUND: + break; + default: + printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n", + status); + status = EFI_NOT_FOUND; + break; + } + + } while (status != EFI_NOT_FOUND); + + spin_unlock_irq(&__efivars->lock); + + kfree(variable_name); + + return err; +} +EXPORT_SYMBOL_GPL(efivar_init); + +/** + * efivar_entry_add - add entry to variable list + * @entry: entry to add to list + * @head: list head + */ +void efivar_entry_add(struct efivar_entry *entry, struct list_head *head) +{ + spin_lock_irq(&__efivars->lock); + list_add(&entry->list, head); + spin_unlock_irq(&__efivars->lock); +} +EXPORT_SYMBOL_GPL(efivar_entry_add); + +/** + * efivar_entry_remove - remove entry from variable list + * @entry: entry to remove from list + */ +void efivar_entry_remove(struct efivar_entry *entry) +{ + spin_lock_irq(&__efivars->lock); + list_del(&entry->list); + spin_unlock_irq(&__efivars->lock); +} +EXPORT_SYMBOL_GPL(efivar_entry_remove); + +/* + * efivar_entry_list_del_unlock - remove entry from variable list + * @entry: entry to remove + * + * Remove @entry from the variable list and release the list lock. + * + * NOTE: slightly weird locking semantics here - we expect to be + * called with the efivars lock already held, and we release it before + * returning. This is because this function is usually called after + * set_variable() while the lock is still held. + */ +static void efivar_entry_list_del_unlock(struct efivar_entry *entry) +{ + lockdep_assert_held(&__efivars->lock); + + list_del(&entry->list); + spin_unlock_irq(&__efivars->lock); +} + +/** + * __efivar_entry_delete - delete an EFI variable + * @entry: entry containing EFI variable to delete + * + * Delete the variable from the firmware but leave @entry on the + * variable list. + * + * This function differs from efivar_entry_delete() because it does + * not remove @entry from the variable list. Also, it is safe to be + * called from within a efivar_entry_iter_begin() and + * efivar_entry_iter_end() region, unlike efivar_entry_delete(). + * + * Returns 0 on success, or a converted EFI status code if + * set_variable() fails. + */ +int __efivar_entry_delete(struct efivar_entry *entry) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + + lockdep_assert_held(&__efivars->lock); + + status = ops->set_variable(entry->var.VariableName, + &entry->var.VendorGuid, + 0, 0, NULL); + + return efi_status_to_err(status); +} +EXPORT_SYMBOL_GPL(__efivar_entry_delete); + +/** + * efivar_entry_delete - delete variable and remove entry from list + * @entry: entry containing variable to delete + * + * Delete the variable from the firmware and remove @entry from the + * variable list. It is the caller's responsibility to free @entry + * once we return. + * + * Returns 0 on success, or a converted EFI status code if + * set_variable() fails. + */ +int efivar_entry_delete(struct efivar_entry *entry) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + + spin_lock_irq(&__efivars->lock); + status = ops->set_variable(entry->var.VariableName, + &entry->var.VendorGuid, + 0, 0, NULL); + if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) { + spin_unlock_irq(&__efivars->lock); + return efi_status_to_err(status); + } + + efivar_entry_list_del_unlock(entry); + return 0; +} +EXPORT_SYMBOL_GPL(efivar_entry_delete); + +/** + * efivar_entry_set - call set_variable() + * @entry: entry containing the EFI variable to write + * @attributes: variable attributes + * @size: size of @data buffer + * @data: buffer containing variable data + * @head: head of variable list + * + * Calls set_variable() for an EFI variable. If creating a new EFI + * variable, this function is usually followed by efivar_entry_add(). + * + * Before writing the variable, the remaining EFI variable storage + * space is checked to ensure there is enough room available. + * + * If @head is not NULL a lookup is performed to determine whether + * the entry is already on the list. + * + * Returns 0 on success, -EEXIST if a lookup is performed and the entry + * already exists on the list, or a converted EFI status code if + * set_variable() fails. + */ +int efivar_entry_set(struct efivar_entry *entry, u32 attributes, + unsigned long size, void *data, struct list_head *head) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + efi_char16_t *name = entry->var.VariableName; + efi_guid_t vendor = entry->var.VendorGuid; + + spin_lock_irq(&__efivars->lock); + + if (head && efivar_entry_find(name, vendor, head, false)) { + spin_unlock_irq(&__efivars->lock); + return -EEXIST; + } + + status = check_var_size(attributes, size + ucs2_strsize(name, 1024)); + if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED) + status = ops->set_variable(name, &vendor, + attributes, size, data); + + spin_unlock_irq(&__efivars->lock); + + return efi_status_to_err(status); + +} +EXPORT_SYMBOL_GPL(efivar_entry_set); + +/* + * efivar_entry_set_nonblocking - call set_variable_nonblocking() + * + * This function is guaranteed to not block and is suitable for calling + * from crash/panic handlers. + * + * Crucially, this function will not block if it cannot acquire + * __efivars->lock. Instead, it returns -EBUSY. + */ +static int +efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor, + u32 attributes, unsigned long size, void *data) +{ + const struct efivar_operations *ops = __efivars->ops; + unsigned long flags; + efi_status_t status; + + if (!spin_trylock_irqsave(&__efivars->lock, flags)) + return -EBUSY; + + status = check_var_size(attributes, size + ucs2_strsize(name, 1024)); + if (status != EFI_SUCCESS) { + spin_unlock_irqrestore(&__efivars->lock, flags); + return -ENOSPC; + } + + status = ops->set_variable_nonblocking(name, &vendor, attributes, + size, data); + + spin_unlock_irqrestore(&__efivars->lock, flags); + return efi_status_to_err(status); +} + +/** + * efivar_entry_set_safe - call set_variable() if enough space in firmware + * @name: buffer containing the variable name + * @vendor: variable vendor guid + * @attributes: variable attributes + * @block: can we block in this context? + * @size: size of @data buffer + * @data: buffer containing variable data + * + * Ensures there is enough free storage in the firmware for this variable, and + * if so, calls set_variable(). If creating a new EFI variable, this function + * is usually followed by efivar_entry_add(). + * + * Returns 0 on success, -ENOSPC if the firmware does not have enough + * space for set_variable() to succeed, or a converted EFI status code + * if set_variable() fails. + */ +int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes, + bool block, unsigned long size, void *data) +{ + const struct efivar_operations *ops = __efivars->ops; + unsigned long flags; + efi_status_t status; + + if (!ops->query_variable_store) + return -ENOSYS; + + /* + * If the EFI variable backend provides a non-blocking + * ->set_variable() operation and we're in a context where we + * cannot block, then we need to use it to avoid live-locks, + * since the implication is that the regular ->set_variable() + * will block. + * + * If no ->set_variable_nonblocking() is provided then + * ->set_variable() is assumed to be non-blocking. + */ + if (!block && ops->set_variable_nonblocking) + return efivar_entry_set_nonblocking(name, vendor, attributes, + size, data); + + if (!block) { + if (!spin_trylock_irqsave(&__efivars->lock, flags)) + return -EBUSY; + } else { + spin_lock_irqsave(&__efivars->lock, flags); + } + + status = check_var_size(attributes, size + ucs2_strsize(name, 1024)); + if (status != EFI_SUCCESS) { + spin_unlock_irqrestore(&__efivars->lock, flags); + return -ENOSPC; + } + + status = ops->set_variable(name, &vendor, attributes, size, data); + + spin_unlock_irqrestore(&__efivars->lock, flags); + + return efi_status_to_err(status); +} +EXPORT_SYMBOL_GPL(efivar_entry_set_safe); + +/** + * efivar_entry_find - search for an entry + * @name: the EFI variable name + * @guid: the EFI variable vendor's guid + * @head: head of the variable list + * @remove: should we remove the entry from the list? + * + * Search for an entry on the variable list that has the EFI variable + * name @name and vendor guid @guid. If an entry is found on the list + * and @remove is true, the entry is removed from the list. + * + * The caller MUST call efivar_entry_iter_begin() and + * efivar_entry_iter_end() before and after the invocation of this + * function, respectively. + * + * Returns the entry if found on the list, %NULL otherwise. + */ +struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid, + struct list_head *head, bool remove) +{ + struct efivar_entry *entry, *n; + int strsize1, strsize2; + bool found = false; + + lockdep_assert_held(&__efivars->lock); + + list_for_each_entry_safe(entry, n, head, list) { + strsize1 = ucs2_strsize(name, 1024); + strsize2 = ucs2_strsize(entry->var.VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(name, &(entry->var.VariableName), strsize1) && + !efi_guidcmp(guid, entry->var.VendorGuid)) { + found = true; + break; + } + } + + if (!found) + return NULL; + + if (remove) { + if (entry->scanning) { + /* + * The entry will be deleted + * after scanning is completed. + */ + entry->deleting = true; + } else + list_del(&entry->list); + } + + return entry; +} +EXPORT_SYMBOL_GPL(efivar_entry_find); + +/** + * efivar_entry_size - obtain the size of a variable + * @entry: entry for this variable + * @size: location to store the variable's size + */ +int efivar_entry_size(struct efivar_entry *entry, unsigned long *size) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + + *size = 0; + + spin_lock_irq(&__efivars->lock); + status = ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, NULL, size, NULL); + spin_unlock_irq(&__efivars->lock); + + if (status != EFI_BUFFER_TOO_SMALL) + return efi_status_to_err(status); + + return 0; +} +EXPORT_SYMBOL_GPL(efivar_entry_size); + +/** + * __efivar_entry_get - call get_variable() + * @entry: read data for this variable + * @attributes: variable attributes + * @size: size of @data buffer + * @data: buffer to store variable data + * + * The caller MUST call efivar_entry_iter_begin() and + * efivar_entry_iter_end() before and after the invocation of this + * function, respectively. + */ +int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes, + unsigned long *size, void *data) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + + lockdep_assert_held(&__efivars->lock); + + status = ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + attributes, size, data); + + return efi_status_to_err(status); +} +EXPORT_SYMBOL_GPL(__efivar_entry_get); + +/** + * efivar_entry_get - call get_variable() + * @entry: read data for this variable + * @attributes: variable attributes + * @size: size of @data buffer + * @data: buffer to store variable data + */ +int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, + unsigned long *size, void *data) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_status_t status; + + spin_lock_irq(&__efivars->lock); + status = ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + attributes, size, data); + spin_unlock_irq(&__efivars->lock); + + return efi_status_to_err(status); +} +EXPORT_SYMBOL_GPL(efivar_entry_get); + +/** + * efivar_entry_set_get_size - call set_variable() and get new size (atomic) + * @entry: entry containing variable to set and get + * @attributes: attributes of variable to be written + * @size: size of data buffer + * @data: buffer containing data to write + * @set: did the set_variable() call succeed? + * + * This is a pretty special (complex) function. See efivarfs_file_write(). + * + * Atomically call set_variable() for @entry and if the call is + * successful, return the new size of the variable from get_variable() + * in @size. The success of set_variable() is indicated by @set. + * + * Returns 0 on success, -EINVAL if the variable data is invalid, + * -ENOSPC if the firmware does not have enough available space, or a + * converted EFI status code if either of set_variable() or + * get_variable() fail. + * + * If the EFI variable does not exist when calling set_variable() + * (EFI_NOT_FOUND), @entry is removed from the variable list. + */ +int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, + unsigned long *size, void *data, bool *set) +{ + const struct efivar_operations *ops = __efivars->ops; + efi_char16_t *name = entry->var.VariableName; + efi_guid_t *vendor = &entry->var.VendorGuid; + efi_status_t status; + int err; + + *set = false; + + if (efivar_validate(name, data, *size) == false) + return -EINVAL; + + /* + * The lock here protects the get_variable call, the conditional + * set_variable call, and removal of the variable from the efivars + * list (in the case of an authenticated delete). + */ + spin_lock_irq(&__efivars->lock); + + /* + * Ensure that the available space hasn't shrunk below the safe level + */ + status = check_var_size(attributes, *size + ucs2_strsize(name, 1024)); + if (status != EFI_SUCCESS) { + if (status != EFI_UNSUPPORTED) { + err = efi_status_to_err(status); + goto out; + } + + if (*size > 65536) { + err = -ENOSPC; + goto out; + } + } + + status = ops->set_variable(name, vendor, attributes, *size, data); + if (status != EFI_SUCCESS) { + err = efi_status_to_err(status); + goto out; + } + + *set = true; + + /* + * Writing to the variable may have caused a change in size (which + * could either be an append or an overwrite), or the variable to be + * deleted. Perform a GetVariable() so we can tell what actually + * happened. + */ + *size = 0; + status = ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + NULL, size, NULL); + + if (status == EFI_NOT_FOUND) + efivar_entry_list_del_unlock(entry); + else + spin_unlock_irq(&__efivars->lock); + + if (status && status != EFI_BUFFER_TOO_SMALL) + return efi_status_to_err(status); + + return 0; + +out: + spin_unlock_irq(&__efivars->lock); + return err; + +} +EXPORT_SYMBOL_GPL(efivar_entry_set_get_size); + +/** + * efivar_entry_iter_begin - begin iterating the variable list + * + * Lock the variable list to prevent entry insertion and removal until + * efivar_entry_iter_end() is called. This function is usually used in + * conjunction with __efivar_entry_iter() or efivar_entry_iter(). + */ +void efivar_entry_iter_begin(void) +{ + spin_lock_irq(&__efivars->lock); +} +EXPORT_SYMBOL_GPL(efivar_entry_iter_begin); + +/** + * efivar_entry_iter_end - finish iterating the variable list + * + * Unlock the variable list and allow modifications to the list again. + */ +void efivar_entry_iter_end(void) +{ + spin_unlock_irq(&__efivars->lock); +} +EXPORT_SYMBOL_GPL(efivar_entry_iter_end); + +/** + * __efivar_entry_iter - iterate over variable list + * @func: callback function + * @head: head of the variable list + * @data: function-specific data to pass to callback + * @prev: entry to begin iterating from + * + * Iterate over the list of EFI variables and call @func with every + * entry on the list. It is safe for @func to remove entries in the + * list via efivar_entry_delete(). + * + * You MUST call efivar_enter_iter_begin() before this function, and + * efivar_entry_iter_end() afterwards. + * + * It is possible to begin iteration from an arbitrary entry within + * the list by passing @prev. @prev is updated on return to point to + * the last entry passed to @func. To begin iterating from the + * beginning of the list @prev must be %NULL. + * + * The restrictions for @func are the same as documented for + * efivar_entry_iter(). + */ +int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *), + struct list_head *head, void *data, + struct efivar_entry **prev) +{ + struct efivar_entry *entry, *n; + int err = 0; + + if (!prev || !*prev) { + list_for_each_entry_safe(entry, n, head, list) { + err = func(entry, data); + if (err) + break; + } + + if (prev) + *prev = entry; + + return err; + } + + + list_for_each_entry_safe_continue((*prev), n, head, list) { + err = func(*prev, data); + if (err) + break; + } + + return err; +} +EXPORT_SYMBOL_GPL(__efivar_entry_iter); + +/** + * efivar_entry_iter - iterate over variable list + * @func: callback function + * @head: head of variable list + * @data: function-specific data to pass to callback + * + * Iterate over the list of EFI variables and call @func with every + * entry on the list. It is safe for @func to remove entries in the + * list via efivar_entry_delete() while iterating. + * + * Some notes for the callback function: + * - a non-zero return value indicates an error and terminates the loop + * - @func is called from atomic context + */ +int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), + struct list_head *head, void *data) +{ + int err = 0; + + efivar_entry_iter_begin(); + err = __efivar_entry_iter(func, head, data, NULL); + efivar_entry_iter_end(); + + return err; +} +EXPORT_SYMBOL_GPL(efivar_entry_iter); + +/** + * efivars_kobject - get the kobject for the registered efivars + * + * If efivars_register() has not been called we return NULL, + * otherwise return the kobject used at registration time. + */ +struct kobject *efivars_kobject(void) +{ + if (!__efivars) + return NULL; + + return __efivars->kobject; +} +EXPORT_SYMBOL_GPL(efivars_kobject); + +/** + * efivar_run_worker - schedule the efivar worker thread + */ +void efivar_run_worker(void) +{ + if (efivar_wq_enabled) + schedule_work(&efivar_work); +} +EXPORT_SYMBOL_GPL(efivar_run_worker); + +/** + * efivars_register - register an efivars + * @efivars: efivars to register + * @ops: efivars operations + * @kobject: @efivars-specific kobject + * + * Only a single efivars can be registered at any time. + */ +int efivars_register(struct efivars *efivars, + const struct efivar_operations *ops, + struct kobject *kobject) +{ + spin_lock_init(&efivars->lock); + efivars->ops = ops; + efivars->kobject = kobject; + + __efivars = efivars; + + return 0; +} +EXPORT_SYMBOL_GPL(efivars_register); + +/** + * efivars_unregister - unregister an efivars + * @efivars: efivars to unregister + * + * The caller must have already removed every entry from the list, + * failure to do so is an error. + */ +int efivars_unregister(struct efivars *efivars) +{ + int rv; + + if (!__efivars) { + printk(KERN_ERR "efivars not registered\n"); + rv = -EINVAL; + goto out; + } + + if (__efivars != efivars) { + rv = -EINVAL; + goto out; + } + + __efivars = NULL; + + rv = 0; +out: + return rv; +} +EXPORT_SYMBOL_GPL(efivars_unregister); diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig new file mode 100644 index 000000000..29c8cdda8 --- /dev/null +++ b/drivers/firmware/google/Kconfig @@ -0,0 +1,31 @@ +config GOOGLE_FIRMWARE + bool "Google Firmware Drivers" + depends on X86 + 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". + +menu "Google Firmware Drivers" + depends on GOOGLE_FIRMWARE + +config GOOGLE_SMI + tristate "SMI interface for Google platforms" + depends on 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_MEMCONSOLE + tristate "Firmware Memory Console" + depends on DMI + 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. + +endmenu diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile new file mode 100644 index 000000000..54a294e3c --- /dev/null +++ b/drivers/firmware/google/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_GOOGLE_SMI) += gsmi.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c new file mode 100644 index 000000000..f1ab05ea5 --- /dev/null +++ b/drivers/firmware/google/gsmi.c @@ -0,0 +1,942 @@ +/* + * Copyright 2010 Google Inc. All Rights Reserved. + * Author: dlaurie@google.com (Duncan Laurie) + * + * Re-worked to expose sysfs APIs by mikew@google.com (Mike Waychison) + * + * EFI SMI interface for Google platforms + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/acpi.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/dmi.h> +#include <linux/kdebug.h> +#include <linux/reboot.h> +#include <linux/efi.h> +#include <linux/module.h> +#include <linux/ucs2_string.h> + +#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */ +/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */ +#define GSMI_SHUTDOWN_NMIWDT 1 /* NMI Watchdog */ +#define GSMI_SHUTDOWN_PANIC 2 /* Panic */ +#define GSMI_SHUTDOWN_OOPS 3 /* Oops */ +#define GSMI_SHUTDOWN_DIE 4 /* Die -- No longer meaningful */ +#define GSMI_SHUTDOWN_MCE 5 /* Machine Check */ +#define GSMI_SHUTDOWN_SOFTWDT 6 /* Software Watchdog */ +#define GSMI_SHUTDOWN_MBE 7 /* Uncorrected ECC */ +#define GSMI_SHUTDOWN_TRIPLE 8 /* Triple Fault */ + +#define DRIVER_VERSION "1.0" +#define GSMI_GUID_SIZE 16 +#define GSMI_BUF_SIZE 1024 +#define GSMI_BUF_ALIGN sizeof(u64) +#define GSMI_CALLBACK 0xef + +/* SMI return codes */ +#define GSMI_SUCCESS 0x00 +#define GSMI_UNSUPPORTED2 0x03 +#define GSMI_LOG_FULL 0x0b +#define GSMI_VAR_NOT_FOUND 0x0e +#define GSMI_HANDSHAKE_SPIN 0x7d +#define GSMI_HANDSHAKE_CF 0x7e +#define GSMI_HANDSHAKE_NONE 0x7f +#define GSMI_INVALID_PARAMETER 0x82 +#define GSMI_UNSUPPORTED 0x83 +#define GSMI_BUFFER_TOO_SMALL 0x85 +#define GSMI_NOT_READY 0x86 +#define GSMI_DEVICE_ERROR 0x87 +#define GSMI_NOT_FOUND 0x8e + +#define QUIRKY_BOARD_HASH 0x78a30a50 + +/* Internally used commands passed to the firmware */ +#define GSMI_CMD_GET_NVRAM_VAR 0x01 +#define GSMI_CMD_GET_NEXT_VAR 0x02 +#define GSMI_CMD_SET_NVRAM_VAR 0x03 +#define GSMI_CMD_SET_EVENT_LOG 0x08 +#define GSMI_CMD_CLEAR_EVENT_LOG 0x09 +#define GSMI_CMD_CLEAR_CONFIG 0x20 +#define GSMI_CMD_HANDSHAKE_TYPE 0xC1 + +/* Magic entry type for kernel events */ +#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD + +/* SMI buffers must be in 32bit physical address space */ +struct gsmi_buf { + u8 *start; /* start of buffer */ + size_t length; /* length of buffer */ + dma_addr_t handle; /* dma allocation handle */ + u32 address; /* physical address of buffer */ +}; + +struct gsmi_device { + struct platform_device *pdev; /* platform device */ + struct gsmi_buf *name_buf; /* variable name buffer */ + struct gsmi_buf *data_buf; /* generic data buffer */ + struct gsmi_buf *param_buf; /* parameter buffer */ + spinlock_t lock; /* serialize access to SMIs */ + u16 smi_cmd; /* SMI command port */ + int handshake_type; /* firmware handler interlock type */ + struct dma_pool *dma_pool; /* DMA buffer pool */ +} gsmi_dev; + +/* Packed structures for communicating with the firmware */ +struct gsmi_nvram_var_param { + efi_guid_t guid; + u32 name_ptr; + u32 attributes; + u32 data_len; + u32 data_ptr; +} __packed; + +struct gsmi_get_next_var_param { + u8 guid[GSMI_GUID_SIZE]; + u32 name_ptr; + u32 name_len; +} __packed; + +struct gsmi_set_eventlog_param { + u32 data_ptr; + u32 data_len; + u32 type; +} __packed; + +/* Event log formats */ +struct gsmi_log_entry_type_1 { + u16 type; + u32 instance; +} __packed; + + +/* + * Some platforms don't have explicit SMI handshake + * and need to wait for SMI to complete. + */ +#define GSMI_DEFAULT_SPINCOUNT 0x10000 +static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT; +module_param(spincount, uint, 0600); +MODULE_PARM_DESC(spincount, + "The number of loop iterations to use when using the spin handshake."); + +static struct gsmi_buf *gsmi_buf_alloc(void) +{ + struct gsmi_buf *smibuf; + + smibuf = kzalloc(sizeof(*smibuf), GFP_KERNEL); + if (!smibuf) { + printk(KERN_ERR "gsmi: out of memory\n"); + return NULL; + } + + /* allocate buffer in 32bit address space */ + smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL, + &smibuf->handle); + if (!smibuf->start) { + printk(KERN_ERR "gsmi: failed to allocate name buffer\n"); + kfree(smibuf); + return NULL; + } + + /* fill in the buffer handle */ + smibuf->length = GSMI_BUF_SIZE; + smibuf->address = (u32)virt_to_phys(smibuf->start); + + return smibuf; +} + +static void gsmi_buf_free(struct gsmi_buf *smibuf) +{ + if (smibuf) { + if (smibuf->start) + dma_pool_free(gsmi_dev.dma_pool, smibuf->start, + smibuf->handle); + kfree(smibuf); + } +} + +/* + * Make a call to gsmi func(sub). GSMI error codes are translated to + * in-kernel errnos (0 on success, -ERRNO on error). + */ +static int gsmi_exec(u8 func, u8 sub) +{ + u16 cmd = (sub << 8) | func; + u16 result = 0; + int rc = 0; + + /* + * AH : Subfunction number + * AL : Function number + * EBX : Parameter block address + * DX : SMI command port + * + * Three protocols here. See also the comment in gsmi_init(). + */ + if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) { + /* + * If handshake_type == HANDSHAKE_CF then set CF on the + * way in and wait for the handler to clear it; this avoids + * corrupting register state on those chipsets which have + * a delay between writing the SMI trigger register and + * entering SMM. + */ + asm volatile ( + "stc\n" + "outb %%al, %%dx\n" + "1: jc 1b\n" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address) + : "memory", "cc" + ); + } else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) { + /* + * If handshake_type == HANDSHAKE_SPIN we spin a + * hundred-ish usecs to ensure the SMI has triggered. + */ + asm volatile ( + "outb %%al, %%dx\n" + "1: loop 1b\n" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address), + "c" (spincount) + : "memory", "cc" + ); + } else { + /* + * If handshake_type == HANDSHAKE_NONE we do nothing; + * either we don't need to or it's legacy firmware that + * doesn't understand the CF protocol. + */ + asm volatile ( + "outb %%al, %%dx\n\t" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address) + : "memory", "cc" + ); + } + + /* check return code from SMI handler */ + switch (result) { + case GSMI_SUCCESS: + break; + case GSMI_VAR_NOT_FOUND: + /* not really an error, but let the caller know */ + rc = 1; + break; + case GSMI_INVALID_PARAMETER: + printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd); + rc = -EINVAL; + break; + case GSMI_BUFFER_TOO_SMALL: + printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd); + rc = -ENOMEM; + break; + case GSMI_UNSUPPORTED: + case GSMI_UNSUPPORTED2: + if (sub != GSMI_CMD_HANDSHAKE_TYPE) + printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n", + cmd); + rc = -ENOSYS; + break; + case GSMI_NOT_READY: + printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd); + rc = -EBUSY; + break; + case GSMI_DEVICE_ERROR: + printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd); + rc = -EFAULT; + break; + case GSMI_NOT_FOUND: + printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd); + rc = -ENOENT; + break; + case GSMI_LOG_FULL: + printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd); + rc = -ENOSPC; + break; + case GSMI_HANDSHAKE_CF: + case GSMI_HANDSHAKE_SPIN: + case GSMI_HANDSHAKE_NONE: + rc = result; + break; + default: + printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n", + cmd, result); + rc = -ENXIO; + } + + return rc; +} + +static efi_status_t gsmi_get_variable(efi_char16_t *name, + efi_guid_t *vendor, u32 *attr, + unsigned long *data_size, + void *data) +{ + struct gsmi_nvram_var_param param = { + .name_ptr = gsmi_dev.name_buf->address, + .data_ptr = gsmi_dev.data_buf->address, + .data_len = (u32)*data_size, + }; + efi_status_t ret = EFI_SUCCESS; + unsigned long flags; + size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2); + int rc; + + if (name_len >= GSMI_BUF_SIZE / 2) + return EFI_BAD_BUFFER_SIZE; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* Vendor guid */ + memcpy(¶m.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, ¶m, 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(¶m, 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(¶m.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, ¶m, 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(¶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; + } + + 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(¶m.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, ¶m, 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, ¶m, 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, ¶m, 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, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + if (rc < 0) + printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n"); + else + printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n", + reason); + + return rc; +} + +static int gsmi_reboot_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_reboot_notifier = { + .notifier_call = gsmi_reboot_callback +}; + +static int gsmi_die_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + if (reason == DIE_OOPS) + gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_die_notifier = { + .notifier_call = gsmi_die_callback +}; + +static int gsmi_panic_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_panic_notifier = { + .notifier_call = gsmi_panic_callback, +}; + +/* + * This hash function was blatantly copied from include/linux/hash.h. + * It is used by this driver to obfuscate a board name that requires a + * quirk within this driver. + * + * Please do not remove this copy of the function as any changes to the + * global utility hash_64() function would break this driver's ability + * to identify a board and provide the appropriate quirk -- mikew@google.com + */ +static u64 __init local_hash_64(u64 val, unsigned bits) +{ + u64 hash = val; + + /* Sigh, gcc can't optimise this alone like it does for 32 bits. */ + u64 n = hash; + n <<= 18; + hash -= n; + n <<= 33; + hash -= n; + n <<= 3; + hash += n; + n <<= 3; + hash -= n; + n <<= 4; + hash += n; + n <<= 2; + hash += n; + + /* High bits are more random, so use them. */ + return hash >> (64 - bits); +} + +static u32 __init hash_oem_table_id(char s[8]) +{ + u64 input; + memcpy(&input, s, 8); + return local_hash_64(input, 32); +} + +static struct dmi_system_id gsmi_dmi_table[] __initdata = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table); + +static __init int gsmi_system_valid(void) +{ + u32 hash; + + if (!dmi_check_system(gsmi_dmi_table)) + return -ENODEV; + + /* + * Only newer firmware supports the gsmi interface. All older + * firmware that didn't support this interface used to plug the + * table name in the first four bytes of the oem_table_id field. + * Newer firmware doesn't do that though, so use that as the + * discriminant factor. We have to do this in order to + * whitewash our board names out of the public driver. + */ + if (!strncmp(acpi_gbl_FADT.header.oem_table_id, "FACP", 4)) { + printk(KERN_INFO "gsmi: Board is too old\n"); + return -ENODEV; + } + + /* Disable on board with 1.0 BIOS due to Google bug 2602657 */ + hash = hash_oem_table_id(acpi_gbl_FADT.header.oem_table_id); + if (hash == QUIRKY_BOARD_HASH) { + const char *bios_ver = dmi_get_system_info(DMI_BIOS_VERSION); + if (strncmp(bios_ver, "1.0", 3) == 0) { + pr_info("gsmi: disabled on this board's BIOS %s\n", + bios_ver); + return -ENODEV; + } + } + + /* check for valid SMI command port in ACPI FADT */ + if (acpi_gbl_FADT.smi_command == 0) { + pr_info("gsmi: missing smi_command\n"); + return -ENODEV; + } + + /* Found */ + return 0; +} + +static struct kobject *gsmi_kobj; +static struct efivars efivars; + +static const struct platform_device_info gsmi_dev_info = { + .name = "gsmi", + .id = -1, + /* SMI callbacks require 32bit addresses */ + .dma_mask = DMA_BIT_MASK(32), +}; + +static __init int gsmi_init(void) +{ + unsigned long flags; + int ret; + + ret = gsmi_system_valid(); + if (ret) + return ret; + + gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command; + + /* register device */ + gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info); + if (IS_ERR(gsmi_dev.pdev)) { + printk(KERN_ERR "gsmi: unable to register platform device\n"); + return PTR_ERR(gsmi_dev.pdev); + } + + /* SMI access needs to be serialized */ + spin_lock_init(&gsmi_dev.lock); + + ret = -ENOMEM; + gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev, + GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0); + if (!gsmi_dev.dma_pool) + goto out_err; + + /* + * pre-allocate buffers because sometimes we are called when + * this is not feasible: oops, panic, die, mce, etc + */ + gsmi_dev.name_buf = gsmi_buf_alloc(); + if (!gsmi_dev.name_buf) { + printk(KERN_ERR "gsmi: failed to allocate name buffer\n"); + goto out_err; + } + + gsmi_dev.data_buf = gsmi_buf_alloc(); + if (!gsmi_dev.data_buf) { + printk(KERN_ERR "gsmi: failed to allocate data buffer\n"); + goto out_err; + } + + gsmi_dev.param_buf = gsmi_buf_alloc(); + if (!gsmi_dev.param_buf) { + printk(KERN_ERR "gsmi: failed to allocate param buffer\n"); + goto out_err; + } + + /* + * Determine type of handshake used to serialize the SMI + * entry. See also gsmi_exec(). + * + * There's a "behavior" present on some chipsets where writing the + * SMI trigger register in the southbridge doesn't result in an + * immediate SMI. Rather, the processor can execute "a few" more + * instructions before the SMI takes effect. To ensure synchronous + * behavior, implement a handshake between the kernel driver and the + * firmware handler to spin until released. This ioctl determines + * the type of handshake. + * + * NONE: The firmware handler does not implement any + * handshake. Either it doesn't need to, or it's legacy firmware + * that doesn't know it needs to and never will. + * + * CF: The firmware handler will clear the CF in the saved + * state before returning. The driver may set the CF and test for + * it to clear before proceeding. + * + * SPIN: The firmware handler does not implement any handshake + * but the driver should spin for a hundred or so microseconds + * to ensure the SMI has triggered. + * + * Finally, the handler will return -ENOSYS if + * GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies + * HANDSHAKE_NONE. + */ + spin_lock_irqsave(&gsmi_dev.lock, flags); + gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN; + gsmi_dev.handshake_type = + gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE); + if (gsmi_dev.handshake_type == -ENOSYS) + gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE; + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + /* Remove and clean up gsmi if the handshake could not complete. */ + if (gsmi_dev.handshake_type == -ENXIO) { + printk(KERN_INFO "gsmi version " DRIVER_VERSION + " failed to load\n"); + ret = -ENODEV; + goto out_err; + } + + /* Register in the firmware directory */ + ret = -ENOMEM; + gsmi_kobj = kobject_create_and_add("gsmi", firmware_kobj); + if (!gsmi_kobj) { + printk(KERN_INFO "gsmi: Failed to create firmware kobj\n"); + goto out_err; + } + + /* Setup eventlog access */ + ret = sysfs_create_bin_file(gsmi_kobj, &eventlog_bin_attr); + if (ret) { + printk(KERN_INFO "gsmi: Failed to setup eventlog"); + goto out_err; + } + + /* Other attributes */ + ret = sysfs_create_files(gsmi_kobj, gsmi_attrs); + if (ret) { + printk(KERN_INFO "gsmi: Failed to add attrs"); + goto out_remove_bin_file; + } + + ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj); + if (ret) { + printk(KERN_INFO "gsmi: Failed to register efivars\n"); + goto out_remove_sysfs_files; + } + + register_reboot_notifier(&gsmi_reboot_notifier); + register_die_notifier(&gsmi_die_notifier); + atomic_notifier_chain_register(&panic_notifier_list, + &gsmi_panic_notifier); + + printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n"); + + return 0; + +out_remove_sysfs_files: + sysfs_remove_files(gsmi_kobj, gsmi_attrs); +out_remove_bin_file: + sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr); +out_err: + kobject_put(gsmi_kobj); + gsmi_buf_free(gsmi_dev.param_buf); + gsmi_buf_free(gsmi_dev.data_buf); + gsmi_buf_free(gsmi_dev.name_buf); + if (gsmi_dev.dma_pool) + dma_pool_destroy(gsmi_dev.dma_pool); + platform_device_unregister(gsmi_dev.pdev); + pr_info("gsmi: failed to load: %d\n", ret); + return ret; +} + +static void __exit gsmi_exit(void) +{ + unregister_reboot_notifier(&gsmi_reboot_notifier); + unregister_die_notifier(&gsmi_die_notifier); + atomic_notifier_chain_unregister(&panic_notifier_list, + &gsmi_panic_notifier); + efivars_unregister(&efivars); + + sysfs_remove_files(gsmi_kobj, gsmi_attrs); + sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr); + kobject_put(gsmi_kobj); + gsmi_buf_free(gsmi_dev.param_buf); + gsmi_buf_free(gsmi_dev.data_buf); + gsmi_buf_free(gsmi_dev.name_buf); + dma_pool_destroy(gsmi_dev.dma_pool); + platform_device_unregister(gsmi_dev.pdev); +} + +module_init(gsmi_init); +module_exit(gsmi_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c new file mode 100644 index 000000000..2f569aaed --- /dev/null +++ b/drivers/firmware/google/memconsole.c @@ -0,0 +1,171 @@ +/* + * memconsole.c + * + * Infrastructure for importing the BIOS memory based console + * into the kernel log ringbuffer. + * + * Copyright 2010 Google Inc. All rights reserved. + */ + +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/io.h> +#include <asm/bios_ebda.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 u32 memconsole_baseaddr; +static size_t memconsole_length; + +static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + char *memconsole; + ssize_t ret; + + memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length); + if (!memconsole) { + pr_err("memconsole: ioremap_cache failed\n"); + return -ENOMEM; + } + ret = memory_read_from_buffer(buf, count, &pos, memconsole, + memconsole_length); + iounmap(memconsole); + return ret; +} + +static struct bin_attribute memconsole_bin_attr = { + .attr = {.name = "log", .mode = 0444}, + .read = memconsole_read, +}; + + +static void __init found_v1_header(struct biosmemcon_ebda *hdr) +{ + pr_info("BIOS console v1 EBDA structure found at %p\n", hdr); + pr_info("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_length = hdr->v1.num_chars; + memconsole_baseaddr = hdr->v1.buffer_addr; +} + +static void __init found_v2_header(struct biosmemcon_ebda *hdr) +{ + pr_info("BIOS console v2 EBDA structure found at %p\n", hdr); + pr_info("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_length = hdr->v2.end - hdr->v2.start; + memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start; +} + +/* + * Search through the EBDA for the BIOS Memory Console, and + * set the global variables to point to it. Return true if found. + */ +static bool __init found_memconsole(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + pr_info("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("BIOS console EBDA structure not found!\n"); + return false; +} + +static struct dmi_system_id memconsole_dmi_table[] __initdata = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); + +static int __init memconsole_init(void) +{ + if (!dmi_check_system(memconsole_dmi_table)) + return -ENODEV; + + if (!found_memconsole()) + return -ENODEV; + + memconsole_bin_attr.size = memconsole_length; + return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); +} + +static void __exit memconsole_exit(void) +{ + sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr); +} + +module_init(memconsole_init); +module_exit(memconsole_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/iscsi_ibft.c b/drivers/firmware/iscsi_ibft.c new file mode 100644 index 000000000..72791232e --- /dev/null +++ b/drivers/firmware/iscsi_ibft.c @@ -0,0 +1,828 @@ +/* + * Copyright 2007-2010 Red Hat, Inc. + * by Peter Jones <pjones@redhat.com> + * Copyright 2008 IBM, Inc. + * by Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Copyright 2008 + * by Konrad Rzeszutek <ketuzsezr@darnok.org> + * + * This code exposes the iSCSI Boot Format Table to userland via sysfs. + * + * 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. + * + * Changelog: + * + * 06 Jan 2010 - Peter Jones <pjones@redhat.com> + * New changelog entries are in the git log from now on. Not here. + * + * 14 Mar 2008 - Konrad Rzeszutek <ketuzsezr@darnok.org> + * Updated comments and copyrights. (v0.4.9) + * + * 11 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Converted to using ibft_addr. (v0.4.8) + * + * 8 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Combined two functions in one: reserve_ibft_region. (v0.4.7) + * + * 30 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added logic to handle IPv6 addresses. (v0.4.6) + * + * 25 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added logic to handle badly not-to-spec iBFT. (v0.4.5) + * + * 4 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added __init to function declarations. (v0.4.4) + * + * 21 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Updated kobject registration, combined unregister functions in one + * and code and style cleanup. (v0.4.3) + * + * 5 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added end-markers to enums and re-organized kobject registration. (v0.4.2) + * + * 4 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Created 'device' sysfs link to the NIC and style cleanup. (v0.4.1) + * + * 28 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added sysfs-ibft documentation, moved 'find_ibft' function to + * in its own file and added text attributes for every struct field. (v0.4) + * + * 21 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added text attributes emulating OpenFirmware /proc/device-tree naming. + * Removed binary /sysfs interface (v0.3) + * + * 29 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Added functionality in setup.c to reserve iBFT region. (v0.2) + * + * 27 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * First version exposing iBFT data via a binary /sysfs. (v0.1) + * + */ + + +#include <linux/blkdev.h> +#include <linux/capability.h> +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/iscsi_ibft.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/iscsi_boot_sysfs.h> + +#define IBFT_ISCSI_VERSION "0.5.0" +#define IBFT_ISCSI_DATE "2010-Feb-25" + +MODULE_AUTHOR("Peter Jones <pjones@redhat.com> and " + "Konrad Rzeszutek <ketuzsezr@darnok.org>"); +MODULE_DESCRIPTION("sysfs interface to BIOS iBFT information"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(IBFT_ISCSI_VERSION); + +struct ibft_hdr { + u8 id; + u8 version; + u16 length; + u8 index; + u8 flags; +} __attribute__((__packed__)); + +struct ibft_control { + struct ibft_hdr hdr; + u16 extensions; + u16 initiator_off; + u16 nic0_off; + u16 tgt0_off; + u16 nic1_off; + u16 tgt1_off; +} __attribute__((__packed__)); + +struct ibft_initiator { + struct ibft_hdr hdr; + char isns_server[16]; + char slp_server[16]; + char pri_radius_server[16]; + char sec_radius_server[16]; + u16 initiator_name_len; + u16 initiator_name_off; +} __attribute__((__packed__)); + +struct ibft_nic { + struct ibft_hdr hdr; + char ip_addr[16]; + u8 subnet_mask_prefix; + u8 origin; + char gateway[16]; + char primary_dns[16]; + char secondary_dns[16]; + char dhcp[16]; + u16 vlan; + char mac[6]; + u16 pci_bdf; + u16 hostname_len; + u16 hostname_off; +} __attribute__((__packed__)); + +struct ibft_tgt { + struct ibft_hdr hdr; + char ip_addr[16]; + u16 port; + char lun[8]; + u8 chap_type; + u8 nic_assoc; + u16 tgt_name_len; + u16 tgt_name_off; + u16 chap_name_len; + u16 chap_name_off; + u16 chap_secret_len; + u16 chap_secret_off; + u16 rev_chap_name_len; + u16 rev_chap_name_off; + u16 rev_chap_secret_len; + u16 rev_chap_secret_off; +} __attribute__((__packed__)); + +/* + * The kobject different types and its names. + * +*/ +enum ibft_id { + id_reserved = 0, /* We don't support. */ + id_control = 1, /* Should show up only once and is not exported. */ + id_initiator = 2, + id_nic = 3, + id_target = 4, + id_extensions = 5, /* We don't support. */ + id_end_marker, +}; + +/* + * The kobject and attribute structures. + */ + +struct ibft_kobject { + struct acpi_table_ibft *header; + union { + struct ibft_initiator *initiator; + struct ibft_nic *nic; + struct ibft_tgt *tgt; + struct ibft_hdr *hdr; + }; +}; + +static struct iscsi_boot_kset *boot_kset; + +/* fully null address */ +static const char nulls[16]; + +/* IPv4-mapped IPv6 ::ffff:0.0.0.0 */ +static const char mapped_nulls[16] = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00 }; + +static int address_not_null(u8 *ip) +{ + return (memcmp(ip, nulls, 16) && memcmp(ip, mapped_nulls, 16)); +} + +/* + * Helper functions to parse data properly. + */ +static ssize_t sprintf_ipaddr(char *buf, u8 *ip) +{ + char *str = buf; + + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && + ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 && + ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff) { + /* + * IPV4 + */ + str += sprintf(buf, "%pI4", ip + 12); + } else { + /* + * IPv6 + */ + str += sprintf(str, "%pI6", ip); + } + str += sprintf(str, "\n"); + return str - buf; +} + +static ssize_t sprintf_string(char *str, int len, char *buf) +{ + return sprintf(str, "%.*s\n", len, buf); +} + +/* + * Helper function to verify the IBFT header. + */ +static int ibft_verify_hdr(char *t, struct ibft_hdr *hdr, int id, int length) +{ + if (hdr->id != id) { + printk(KERN_ERR "iBFT error: We expected the %s " \ + "field header.id to have %d but " \ + "found %d instead!\n", t, id, hdr->id); + return -ENODEV; + } + if (hdr->length != length) { + printk(KERN_ERR "iBFT error: We expected the %s " \ + "field header.length to have %d but " \ + "found %d instead!\n", t, length, hdr->length); + return -ENODEV; + } + + return 0; +} + +/* + * Routines for parsing the iBFT data to be human readable. + */ +static ssize_t ibft_attr_show_initiator(void *data, int type, char *buf) +{ + struct ibft_kobject *entry = data; + struct ibft_initiator *initiator = entry->initiator; + void *ibft_loc = entry->header; + char *str = buf; + + if (!initiator) + return 0; + + switch (type) { + case ISCSI_BOOT_INI_INDEX: + str += sprintf(str, "%d\n", initiator->hdr.index); + break; + case ISCSI_BOOT_INI_FLAGS: + str += sprintf(str, "%d\n", initiator->hdr.flags); + break; + case ISCSI_BOOT_INI_ISNS_SERVER: + str += sprintf_ipaddr(str, initiator->isns_server); + break; + case ISCSI_BOOT_INI_SLP_SERVER: + str += sprintf_ipaddr(str, initiator->slp_server); + break; + case ISCSI_BOOT_INI_PRI_RADIUS_SERVER: + str += sprintf_ipaddr(str, initiator->pri_radius_server); + break; + case ISCSI_BOOT_INI_SEC_RADIUS_SERVER: + str += sprintf_ipaddr(str, initiator->sec_radius_server); + break; + case ISCSI_BOOT_INI_INITIATOR_NAME: + str += sprintf_string(str, initiator->initiator_name_len, + (char *)ibft_loc + + initiator->initiator_name_off); + break; + default: + break; + } + + return str - buf; +} + +static ssize_t ibft_attr_show_nic(void *data, int type, char *buf) +{ + struct ibft_kobject *entry = data; + struct ibft_nic *nic = entry->nic; + void *ibft_loc = entry->header; + char *str = buf; + __be32 val; + + if (!nic) + return 0; + + switch (type) { + case ISCSI_BOOT_ETH_INDEX: + str += sprintf(str, "%d\n", nic->hdr.index); + break; + case ISCSI_BOOT_ETH_FLAGS: + str += sprintf(str, "%d\n", nic->hdr.flags); + break; + case ISCSI_BOOT_ETH_IP_ADDR: + str += sprintf_ipaddr(str, nic->ip_addr); + break; + case ISCSI_BOOT_ETH_SUBNET_MASK: + val = cpu_to_be32(~((1 << (32-nic->subnet_mask_prefix))-1)); + str += sprintf(str, "%pI4", &val); + break; + case ISCSI_BOOT_ETH_ORIGIN: + str += sprintf(str, "%d\n", nic->origin); + break; + case ISCSI_BOOT_ETH_GATEWAY: + str += sprintf_ipaddr(str, nic->gateway); + break; + case ISCSI_BOOT_ETH_PRIMARY_DNS: + str += sprintf_ipaddr(str, nic->primary_dns); + break; + case ISCSI_BOOT_ETH_SECONDARY_DNS: + str += sprintf_ipaddr(str, nic->secondary_dns); + break; + case ISCSI_BOOT_ETH_DHCP: + str += sprintf_ipaddr(str, nic->dhcp); + break; + case ISCSI_BOOT_ETH_VLAN: + str += sprintf(str, "%d\n", nic->vlan); + break; + case ISCSI_BOOT_ETH_MAC: + str += sprintf(str, "%pM\n", nic->mac); + break; + case ISCSI_BOOT_ETH_HOSTNAME: + str += sprintf_string(str, nic->hostname_len, + (char *)ibft_loc + nic->hostname_off); + break; + default: + break; + } + + return str - buf; +}; + +static ssize_t ibft_attr_show_target(void *data, int type, char *buf) +{ + struct ibft_kobject *entry = data; + struct ibft_tgt *tgt = entry->tgt; + void *ibft_loc = entry->header; + char *str = buf; + int i; + + if (!tgt) + return 0; + + switch (type) { + case ISCSI_BOOT_TGT_INDEX: + str += sprintf(str, "%d\n", tgt->hdr.index); + break; + case ISCSI_BOOT_TGT_FLAGS: + str += sprintf(str, "%d\n", tgt->hdr.flags); + break; + case ISCSI_BOOT_TGT_IP_ADDR: + str += sprintf_ipaddr(str, tgt->ip_addr); + break; + case ISCSI_BOOT_TGT_PORT: + str += sprintf(str, "%d\n", tgt->port); + break; + case ISCSI_BOOT_TGT_LUN: + for (i = 0; i < 8; i++) + str += sprintf(str, "%x", (u8)tgt->lun[i]); + str += sprintf(str, "\n"); + break; + case ISCSI_BOOT_TGT_NIC_ASSOC: + str += sprintf(str, "%d\n", tgt->nic_assoc); + break; + case ISCSI_BOOT_TGT_CHAP_TYPE: + str += sprintf(str, "%d\n", tgt->chap_type); + break; + case ISCSI_BOOT_TGT_NAME: + str += sprintf_string(str, tgt->tgt_name_len, + (char *)ibft_loc + tgt->tgt_name_off); + break; + case ISCSI_BOOT_TGT_CHAP_NAME: + str += sprintf_string(str, tgt->chap_name_len, + (char *)ibft_loc + tgt->chap_name_off); + break; + case ISCSI_BOOT_TGT_CHAP_SECRET: + str += sprintf_string(str, tgt->chap_secret_len, + (char *)ibft_loc + tgt->chap_secret_off); + break; + case ISCSI_BOOT_TGT_REV_CHAP_NAME: + str += sprintf_string(str, tgt->rev_chap_name_len, + (char *)ibft_loc + + tgt->rev_chap_name_off); + break; + case ISCSI_BOOT_TGT_REV_CHAP_SECRET: + str += sprintf_string(str, tgt->rev_chap_secret_len, + (char *)ibft_loc + + tgt->rev_chap_secret_off); + break; + default: + break; + } + + return str - buf; +} + +static int __init ibft_check_device(void) +{ + int len; + u8 *pos; + u8 csum = 0; + + len = ibft_addr->header.length; + + /* Sanity checking of iBFT. */ + if (ibft_addr->header.revision != 1) { + printk(KERN_ERR "iBFT module supports only revision 1, " \ + "while this is %d.\n", + ibft_addr->header.revision); + return -ENOENT; + } + for (pos = (u8 *)ibft_addr; pos < (u8 *)ibft_addr + len; pos++) + csum += *pos; + + if (csum) { + printk(KERN_ERR "iBFT has incorrect checksum (0x%x)!\n", csum); + return -ENOENT; + } + + return 0; +} + +/* + * Helper routiners to check to determine if the entry is valid + * in the proper iBFT structure. + */ +static umode_t ibft_check_nic_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_nic *nic = entry->nic; + umode_t rc = 0; + + switch (type) { + case ISCSI_BOOT_ETH_INDEX: + case ISCSI_BOOT_ETH_FLAGS: + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_IP_ADDR: + if (address_not_null(nic->ip_addr)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_SUBNET_MASK: + if (nic->subnet_mask_prefix) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_ORIGIN: + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_GATEWAY: + if (address_not_null(nic->gateway)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_PRIMARY_DNS: + if (address_not_null(nic->primary_dns)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_SECONDARY_DNS: + if (address_not_null(nic->secondary_dns)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_DHCP: + if (address_not_null(nic->dhcp)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_VLAN: + case ISCSI_BOOT_ETH_MAC: + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_HOSTNAME: + if (nic->hostname_off) + rc = S_IRUGO; + break; + default: + break; + } + + return rc; +} + +static umode_t __init ibft_check_tgt_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_tgt *tgt = entry->tgt; + umode_t rc = 0; + + switch (type) { + case ISCSI_BOOT_TGT_INDEX: + case ISCSI_BOOT_TGT_FLAGS: + case ISCSI_BOOT_TGT_IP_ADDR: + case ISCSI_BOOT_TGT_PORT: + case ISCSI_BOOT_TGT_LUN: + case ISCSI_BOOT_TGT_NIC_ASSOC: + case ISCSI_BOOT_TGT_CHAP_TYPE: + rc = S_IRUGO; + case ISCSI_BOOT_TGT_NAME: + if (tgt->tgt_name_len) + rc = S_IRUGO; + break; + case ISCSI_BOOT_TGT_CHAP_NAME: + case ISCSI_BOOT_TGT_CHAP_SECRET: + if (tgt->chap_name_len) + rc = S_IRUGO; + break; + case ISCSI_BOOT_TGT_REV_CHAP_NAME: + case ISCSI_BOOT_TGT_REV_CHAP_SECRET: + if (tgt->rev_chap_name_len) + rc = S_IRUGO; + break; + default: + break; + } + + return rc; +} + +static umode_t __init ibft_check_initiator_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_initiator *init = entry->initiator; + umode_t rc = 0; + + switch (type) { + case ISCSI_BOOT_INI_INDEX: + case ISCSI_BOOT_INI_FLAGS: + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_ISNS_SERVER: + if (address_not_null(init->isns_server)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_SLP_SERVER: + if (address_not_null(init->slp_server)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_PRI_RADIUS_SERVER: + if (address_not_null(init->pri_radius_server)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_SEC_RADIUS_SERVER: + if (address_not_null(init->sec_radius_server)) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_INITIATOR_NAME: + if (init->initiator_name_len) + rc = S_IRUGO; + break; + default: + break; + } + + return rc; +} + +static void ibft_kobj_release(void *data) +{ + kfree(data); +} + +/* + * Helper function for ibft_register_kobjects. + */ +static int __init ibft_create_kobject(struct acpi_table_ibft *header, + struct ibft_hdr *hdr) +{ + struct iscsi_boot_kobj *boot_kobj = NULL; + struct ibft_kobject *ibft_kobj = NULL; + struct ibft_nic *nic = (struct ibft_nic *)hdr; + struct pci_dev *pci_dev; + int rc = 0; + + ibft_kobj = kzalloc(sizeof(*ibft_kobj), GFP_KERNEL); + if (!ibft_kobj) + return -ENOMEM; + + ibft_kobj->header = header; + ibft_kobj->hdr = hdr; + + switch (hdr->id) { + case id_initiator: + rc = ibft_verify_hdr("initiator", hdr, id_initiator, + sizeof(*ibft_kobj->initiator)); + if (rc) + break; + + boot_kobj = iscsi_boot_create_initiator(boot_kset, hdr->index, + ibft_kobj, + ibft_attr_show_initiator, + ibft_check_initiator_for, + ibft_kobj_release); + if (!boot_kobj) { + rc = -ENOMEM; + goto free_ibft_obj; + } + break; + case id_nic: + rc = ibft_verify_hdr("ethernet", hdr, id_nic, + sizeof(*ibft_kobj->nic)); + if (rc) + break; + + boot_kobj = iscsi_boot_create_ethernet(boot_kset, hdr->index, + ibft_kobj, + ibft_attr_show_nic, + ibft_check_nic_for, + ibft_kobj_release); + if (!boot_kobj) { + rc = -ENOMEM; + goto free_ibft_obj; + } + break; + case id_target: + rc = ibft_verify_hdr("target", hdr, id_target, + sizeof(*ibft_kobj->tgt)); + if (rc) + break; + + boot_kobj = iscsi_boot_create_target(boot_kset, hdr->index, + ibft_kobj, + ibft_attr_show_target, + ibft_check_tgt_for, + ibft_kobj_release); + if (!boot_kobj) { + rc = -ENOMEM; + goto free_ibft_obj; + } + break; + case id_reserved: + case id_control: + case id_extensions: + /* Fields which we don't support. Ignore them */ + rc = 1; + break; + default: + printk(KERN_ERR "iBFT has unknown structure type (%d). " \ + "Report this bug to %.6s!\n", hdr->id, + header->header.oem_id); + rc = 1; + break; + } + + if (rc) { + /* Skip adding this kobject, but exit with non-fatal error. */ + rc = 0; + goto free_ibft_obj; + } + + if (hdr->id == id_nic) { + /* + * We don't search for the device in other domains than + * zero. This is because on x86 platforms the BIOS + * executes only devices which are in domain 0. Furthermore, the + * iBFT spec doesn't have a domain id field :-( + */ + pci_dev = pci_get_bus_and_slot((nic->pci_bdf & 0xff00) >> 8, + (nic->pci_bdf & 0xff)); + if (pci_dev) { + rc = sysfs_create_link(&boot_kobj->kobj, + &pci_dev->dev.kobj, "device"); + pci_dev_put(pci_dev); + } + } + return 0; + +free_ibft_obj: + kfree(ibft_kobj); + return rc; +} + +/* + * Scan the IBFT table structure for the NIC and Target fields. When + * found add them on the passed-in list. We do not support the other + * fields at this point, so they are skipped. + */ +static int __init ibft_register_kobjects(struct acpi_table_ibft *header) +{ + struct ibft_control *control = NULL; + void *ptr, *end; + int rc = 0; + u16 offset; + u16 eot_offset; + + control = (void *)header + sizeof(*header); + end = (void *)control + control->hdr.length; + eot_offset = (void *)header + header->header.length - (void *)control; + rc = ibft_verify_hdr("control", (struct ibft_hdr *)control, id_control, + sizeof(*control)); + + /* iBFT table safety checking */ + rc |= ((control->hdr.index) ? -ENODEV : 0); + if (rc) { + printk(KERN_ERR "iBFT error: Control header is invalid!\n"); + return rc; + } + for (ptr = &control->initiator_off; ptr < end; ptr += sizeof(u16)) { + offset = *(u16 *)ptr; + if (offset && offset < header->header.length && + offset < eot_offset) { + rc = ibft_create_kobject(header, + (void *)header + offset); + if (rc) + break; + } + } + + return rc; +} + +static void ibft_unregister(void) +{ + struct iscsi_boot_kobj *boot_kobj, *tmp_kobj; + struct ibft_kobject *ibft_kobj; + + list_for_each_entry_safe(boot_kobj, tmp_kobj, + &boot_kset->kobj_list, list) { + ibft_kobj = boot_kobj->data; + if (ibft_kobj->hdr->id == id_nic) + sysfs_remove_link(&boot_kobj->kobj, "device"); + }; +} + +static void ibft_cleanup(void) +{ + if (boot_kset) { + ibft_unregister(); + iscsi_boot_destroy_kset(boot_kset); + } +} + +static void __exit ibft_exit(void) +{ + ibft_cleanup(); +} + +#ifdef CONFIG_ACPI +static const struct { + char *sign; +} ibft_signs[] = { + /* + * One spec says "IBFT", the other says "iBFT". We have to check + * for both. + */ + { ACPI_SIG_IBFT }, + { "iBFT" }, + { "BIFT" }, /* Broadcom iSCSI Offload */ +}; + +static void __init acpi_find_ibft_region(void) +{ + int i; + struct acpi_table_header *table = NULL; + + if (acpi_disabled) + return; + + for (i = 0; i < ARRAY_SIZE(ibft_signs) && !ibft_addr; i++) { + acpi_get_table(ibft_signs[i].sign, 0, &table); + ibft_addr = (struct acpi_table_ibft *)table; + } +} +#else +static void __init acpi_find_ibft_region(void) +{ +} +#endif + +/* + * ibft_init() - creates sysfs tree entries for the iBFT data. + */ +static int __init ibft_init(void) +{ + int rc = 0; + + /* + As on UEFI systems the setup_arch()/find_ibft_region() + is called before ACPI tables are parsed and it only does + legacy finding. + */ + if (!ibft_addr) + acpi_find_ibft_region(); + + if (ibft_addr) { + pr_info("iBFT detected.\n"); + + rc = ibft_check_device(); + if (rc) + return rc; + + boot_kset = iscsi_boot_create_kset("ibft"); + if (!boot_kset) + return -ENOMEM; + + /* Scan the IBFT for data and register the kobjects. */ + rc = ibft_register_kobjects(ibft_addr); + if (rc) + goto out_free; + } else + printk(KERN_INFO "No iBFT detected.\n"); + + return 0; + +out_free: + ibft_cleanup(); + return rc; +} + +module_init(ibft_init); +module_exit(ibft_exit); diff --git a/drivers/firmware/iscsi_ibft_find.c b/drivers/firmware/iscsi_ibft_find.c new file mode 100644 index 000000000..2224f1dc0 --- /dev/null +++ b/drivers/firmware/iscsi_ibft_find.c @@ -0,0 +1,112 @@ +/* + * Copyright 2007-2010 Red Hat, Inc. + * by Peter Jones <pjones@redhat.com> + * Copyright 2007 IBM, Inc. + * by Konrad Rzeszutek <konradr@linux.vnet.ibm.com> + * Copyright 2008 + * by Konrad Rzeszutek <ketuzsezr@darnok.org> + * + * This code finds the iSCSI Boot Format Table. + * + * 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/bootmem.h> +#include <linux/blkdev.h> +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/efi.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/iscsi_ibft.h> + +#include <asm/mmzone.h> + +/* + * Physical location of iSCSI Boot Format Table. + */ +struct acpi_table_ibft *ibft_addr; +EXPORT_SYMBOL_GPL(ibft_addr); + +static const struct { + char *sign; +} ibft_signs[] = { + { "iBFT" }, + { "BIFT" }, /* Broadcom iSCSI Offload */ +}; + +#define IBFT_SIGN_LEN 4 +#define IBFT_START 0x80000 /* 512kB */ +#define IBFT_END 0x100000 /* 1MB */ +#define VGA_MEM 0xA0000 /* VGA buffer */ +#define VGA_SIZE 0x20000 /* 128kB */ + +static int __init find_ibft_in_mem(void) +{ + unsigned long pos; + unsigned int len = 0; + void *virt; + int i; + + for (pos = IBFT_START; pos < IBFT_END; pos += 16) { + /* The table can't be inside the VGA BIOS reserved space, + * so skip that area */ + if (pos == VGA_MEM) + pos += VGA_SIZE; + virt = isa_bus_to_virt(pos); + + for (i = 0; i < ARRAY_SIZE(ibft_signs); i++) { + if (memcmp(virt, ibft_signs[i].sign, IBFT_SIGN_LEN) == + 0) { + unsigned long *addr = + (unsigned long *)isa_bus_to_virt(pos + 4); + len = *addr; + /* if the length of the table extends past 1M, + * the table cannot be valid. */ + if (pos + len <= (IBFT_END-1)) { + ibft_addr = (struct acpi_table_ibft *)virt; + pr_info("iBFT found at 0x%lx.\n", pos); + goto done; + } + } + } + } +done: + return len; +} +/* + * Routine used to find the iSCSI Boot Format Table. The logical + * kernel address is set in the ibft_addr global variable. + */ +unsigned long __init find_ibft_region(unsigned long *sizep) +{ + ibft_addr = NULL; + + /* iBFT 1.03 section 1.4.3.1 mandates that UEFI machines will + * only use ACPI for this */ + + if (!efi_enabled(EFI_BOOT)) + find_ibft_in_mem(); + + if (ibft_addr) { + *sizep = PAGE_ALIGN(ibft_addr->header.length); + return (u64)isa_virt_to_bus(ibft_addr); + } + + *sizep = 0; + return 0; +} diff --git a/drivers/firmware/memmap.c b/drivers/firmware/memmap.c new file mode 100644 index 000000000..cc016c615 --- /dev/null +++ b/drivers/firmware/memmap.c @@ -0,0 +1,424 @@ +/* + * linux/drivers/firmware/memmap.c + * Copyright (C) 2008 SUSE LINUX Products GmbH + * by Bernhard Walle <bernhard.walle@gmx.de> + * + * 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/string.h> +#include <linux/firmware-map.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/bootmem.h> +#include <linux/slab.h> +#include <linux/mm.h> + +/* + * Data types ------------------------------------------------------------------ + */ + +/* + * Firmware map entry. Because firmware memory maps are flat and not + * hierarchical, it's ok to organise them in a linked list. No parent + * information is necessary as for the resource tree. + */ +struct firmware_map_entry { + /* + * start and end must be u64 rather than resource_size_t, because e820 + * resources can lie at addresses above 4G. + */ + u64 start; /* start of the memory range */ + u64 end; /* end of the memory range (incl.) */ + const char *type; /* type of the memory range */ + struct list_head list; /* entry for the linked list */ + struct kobject kobj; /* kobject for each entry */ +}; + +/* + * Forward declarations -------------------------------------------------------- + */ +static ssize_t memmap_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf); +static ssize_t start_show(struct firmware_map_entry *entry, char *buf); +static ssize_t end_show(struct firmware_map_entry *entry, char *buf); +static ssize_t type_show(struct firmware_map_entry *entry, char *buf); + +static struct firmware_map_entry * __meminit +firmware_map_find_entry(u64 start, u64 end, const char *type); + +/* + * Static data ----------------------------------------------------------------- + */ + +struct memmap_attribute { + struct attribute attr; + ssize_t (*show)(struct firmware_map_entry *entry, char *buf); +}; + +static struct memmap_attribute memmap_start_attr = __ATTR_RO(start); +static struct memmap_attribute memmap_end_attr = __ATTR_RO(end); +static struct memmap_attribute memmap_type_attr = __ATTR_RO(type); + +/* + * These are default attributes that are added for every memmap entry. + */ +static struct attribute *def_attrs[] = { + &memmap_start_attr.attr, + &memmap_end_attr.attr, + &memmap_type_attr.attr, + NULL +}; + +static const struct sysfs_ops memmap_attr_ops = { + .show = memmap_attr_show, +}; + +/* Firmware memory map entries. */ +static LIST_HEAD(map_entries); +static DEFINE_SPINLOCK(map_entries_lock); + +/* + * For memory hotplug, there is no way to free memory map entries allocated + * by boot mem after the system is up. So when we hot-remove memory whose + * map entry is allocated by bootmem, we need to remember the storage and + * reuse it when the memory is hot-added again. + */ +static LIST_HEAD(map_entries_bootmem); +static DEFINE_SPINLOCK(map_entries_bootmem_lock); + + +static inline struct firmware_map_entry * +to_memmap_entry(struct kobject *kobj) +{ + return container_of(kobj, struct firmware_map_entry, kobj); +} + +static void __meminit release_firmware_map_entry(struct kobject *kobj) +{ + struct firmware_map_entry *entry = to_memmap_entry(kobj); + + if (PageReserved(virt_to_page(entry))) { + /* + * Remember the storage allocated by bootmem, and reuse it when + * the memory is hot-added again. The entry will be added to + * map_entries_bootmem here, and deleted from &map_entries in + * firmware_map_remove_entry(). + */ + spin_lock(&map_entries_bootmem_lock); + list_add(&entry->list, &map_entries_bootmem); + spin_unlock(&map_entries_bootmem_lock); + + return; + } + + kfree(entry); +} + +static struct kobj_type __refdata memmap_ktype = { + .release = release_firmware_map_entry, + .sysfs_ops = &memmap_attr_ops, + .default_attrs = def_attrs, +}; + +/* + * Registration functions ------------------------------------------------------ + */ + +/** + * firmware_map_add_entry() - Does the real work to add a firmware memmap entry. + * @start: Start of the memory range. + * @end: End of the memory range (exclusive). + * @type: Type of the memory range. + * @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised + * entry. + * + * Common implementation of firmware_map_add() and firmware_map_add_early() + * which expects a pre-allocated struct firmware_map_entry. + **/ +static int firmware_map_add_entry(u64 start, u64 end, + const char *type, + struct firmware_map_entry *entry) +{ + BUG_ON(start > end); + + entry->start = start; + entry->end = end - 1; + entry->type = type; + INIT_LIST_HEAD(&entry->list); + kobject_init(&entry->kobj, &memmap_ktype); + + spin_lock(&map_entries_lock); + list_add_tail(&entry->list, &map_entries); + spin_unlock(&map_entries_lock); + + return 0; +} + +/** + * firmware_map_remove_entry() - Does the real work to remove a firmware + * memmap entry. + * @entry: removed entry. + * + * The caller must hold map_entries_lock, and release it properly. + **/ +static inline void firmware_map_remove_entry(struct firmware_map_entry *entry) +{ + list_del(&entry->list); +} + +/* + * Add memmap entry on sysfs + */ +static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry) +{ + static int map_entries_nr; + static struct kset *mmap_kset; + + if (entry->kobj.state_in_sysfs) + return -EEXIST; + + if (!mmap_kset) { + mmap_kset = kset_create_and_add("memmap", NULL, firmware_kobj); + if (!mmap_kset) + return -ENOMEM; + } + + entry->kobj.kset = mmap_kset; + if (kobject_add(&entry->kobj, NULL, "%d", map_entries_nr++)) + kobject_put(&entry->kobj); + + return 0; +} + +/* + * Remove memmap entry on sysfs + */ +static inline void remove_sysfs_fw_map_entry(struct firmware_map_entry *entry) +{ + kobject_put(&entry->kobj); +} + +/* + * firmware_map_find_entry_in_list() - Search memmap entry in a given list. + * @start: Start of the memory range. + * @end: End of the memory range (exclusive). + * @type: Type of the memory range. + * @list: In which to find the entry. + * + * This function is to find the memmap entey of a given memory range in a + * given list. The caller must hold map_entries_lock, and must not release + * the lock until the processing of the returned entry has completed. + * + * Return: Pointer to the entry to be found on success, or NULL on failure. + */ +static struct firmware_map_entry * __meminit +firmware_map_find_entry_in_list(u64 start, u64 end, const char *type, + struct list_head *list) +{ + struct firmware_map_entry *entry; + + list_for_each_entry(entry, list, list) + if ((entry->start == start) && (entry->end == end) && + (!strcmp(entry->type, type))) { + return entry; + } + + return NULL; +} + +/* + * firmware_map_find_entry() - Search memmap entry in map_entries. + * @start: Start of the memory range. + * @end: End of the memory range (exclusive). + * @type: Type of the memory range. + * + * This function is to find the memmap entey of a given memory range. + * The caller must hold map_entries_lock, and must not release the lock + * until the processing of the returned entry has completed. + * + * Return: Pointer to the entry to be found on success, or NULL on failure. + */ +static struct firmware_map_entry * __meminit +firmware_map_find_entry(u64 start, u64 end, const char *type) +{ + return firmware_map_find_entry_in_list(start, end, type, &map_entries); +} + +/* + * firmware_map_find_entry_bootmem() - Search memmap entry in map_entries_bootmem. + * @start: Start of the memory range. + * @end: End of the memory range (exclusive). + * @type: Type of the memory range. + * + * This function is similar to firmware_map_find_entry except that it find the + * given entry in map_entries_bootmem. + * + * Return: Pointer to the entry to be found on success, or NULL on failure. + */ +static struct firmware_map_entry * __meminit +firmware_map_find_entry_bootmem(u64 start, u64 end, const char *type) +{ + return firmware_map_find_entry_in_list(start, end, type, + &map_entries_bootmem); +} + +/** + * firmware_map_add_hotplug() - Adds a firmware mapping entry when we do + * memory hotplug. + * @start: Start of the memory range. + * @end: End of the memory range (exclusive) + * @type: Type of the memory range. + * + * Adds a firmware mapping entry. This function is for memory hotplug, it is + * similar to function firmware_map_add_early(). The only difference is that + * it will create the syfs entry dynamically. + * + * Returns 0 on success, or -ENOMEM if no memory could be allocated. + **/ +int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type) +{ + struct firmware_map_entry *entry; + + entry = firmware_map_find_entry(start, end - 1, type); + if (entry) + return 0; + + entry = firmware_map_find_entry_bootmem(start, end - 1, type); + if (!entry) { + entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC); + if (!entry) + return -ENOMEM; + } else { + /* Reuse storage allocated by bootmem. */ + spin_lock(&map_entries_bootmem_lock); + list_del(&entry->list); + spin_unlock(&map_entries_bootmem_lock); + + memset(entry, 0, sizeof(*entry)); + } + + firmware_map_add_entry(start, end, type, entry); + /* create the memmap entry */ + add_sysfs_fw_map_entry(entry); + + return 0; +} + +/** + * firmware_map_add_early() - Adds a firmware mapping entry. + * @start: Start of the memory range. + * @end: End of the memory range. + * @type: Type of the memory range. + * + * Adds a firmware mapping entry. This function uses the bootmem allocator + * for memory allocation. + * + * That function must be called before late_initcall. + * + * Returns 0 on success, or -ENOMEM if no memory could be allocated. + **/ +int __init firmware_map_add_early(u64 start, u64 end, const char *type) +{ + struct firmware_map_entry *entry; + + entry = memblock_virt_alloc(sizeof(struct firmware_map_entry), 0); + if (WARN_ON(!entry)) + return -ENOMEM; + + return firmware_map_add_entry(start, end, type, entry); +} + +/** + * firmware_map_remove() - remove a firmware mapping entry + * @start: Start of the memory range. + * @end: End of the memory range. + * @type: Type of the memory range. + * + * removes a firmware mapping entry. + * + * Returns 0 on success, or -EINVAL if no entry. + **/ +int __meminit firmware_map_remove(u64 start, u64 end, const char *type) +{ + struct firmware_map_entry *entry; + + spin_lock(&map_entries_lock); + entry = firmware_map_find_entry(start, end - 1, type); + if (!entry) { + spin_unlock(&map_entries_lock); + return -EINVAL; + } + + firmware_map_remove_entry(entry); + spin_unlock(&map_entries_lock); + + /* remove the memmap entry */ + remove_sysfs_fw_map_entry(entry); + + return 0; +} + +/* + * Sysfs functions ------------------------------------------------------------- + */ + +static ssize_t start_show(struct firmware_map_entry *entry, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%llx\n", + (unsigned long long)entry->start); +} + +static ssize_t end_show(struct firmware_map_entry *entry, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%llx\n", + (unsigned long long)entry->end); +} + +static ssize_t type_show(struct firmware_map_entry *entry, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", entry->type); +} + +static inline struct memmap_attribute *to_memmap_attr(struct attribute *attr) +{ + return container_of(attr, struct memmap_attribute, attr); +} + +static ssize_t memmap_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct firmware_map_entry *entry = to_memmap_entry(kobj); + struct memmap_attribute *memmap_attr = to_memmap_attr(attr); + + return memmap_attr->show(entry, buf); +} + +/* + * Initialises stuff and adds the entries in the map_entries list to + * sysfs. Important is that firmware_map_add() and firmware_map_add_early() + * must be called before late_initcall. That's just because that function + * is called as late_initcall() function, which means that if you call + * firmware_map_add() or firmware_map_add_early() afterwards, the entries + * are not added to sysfs. + */ +static int __init firmware_memmap_init(void) +{ + struct firmware_map_entry *entry; + + list_for_each_entry(entry, &map_entries, list) + add_sysfs_fw_map_entry(entry); + + return 0; +} +late_initcall(firmware_memmap_init); + diff --git a/drivers/firmware/pcdp.c b/drivers/firmware/pcdp.c new file mode 100644 index 000000000..75273a251 --- /dev/null +++ b/drivers/firmware/pcdp.c @@ -0,0 +1,136 @@ +/* + * Parse the EFI PCDP table to locate the console device. + * + * (c) Copyright 2002, 2003, 2004 Hewlett-Packard Development Company, L.P. + * Khalid Aziz <khalid.aziz@hp.com> + * Alex Williamson <alex.williamson@hp.com> + * Bjorn Helgaas <bjorn.helgaas@hp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/console.h> +#include <linux/efi.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <asm/vga.h> +#include "pcdp.h" + +static int __init +setup_serial_console(struct pcdp_uart *uart) +{ +#ifdef CONFIG_SERIAL_8250_CONSOLE + int mmio; + static char options[64], *p = options; + char parity; + + mmio = (uart->addr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY); + p += sprintf(p, "uart8250,%s,0x%llx", + mmio ? "mmio" : "io", uart->addr.address); + if (uart->baud) { + p += sprintf(p, ",%llu", uart->baud); + if (uart->bits) { + switch (uart->parity) { + case 0x2: parity = 'e'; break; + case 0x3: parity = 'o'; break; + default: parity = 'n'; + } + p += sprintf(p, "%c%d", parity, uart->bits); + } + } + + add_preferred_console("uart", 8250, &options[9]); + return setup_earlycon(options); +#else + return -ENODEV; +#endif +} + +static int __init +setup_vga_console(struct pcdp_device *dev) +{ +#if defined(CONFIG_VT) && defined(CONFIG_VGA_CONSOLE) + u8 *if_ptr; + + if_ptr = ((u8 *)dev + sizeof(struct pcdp_device)); + if (if_ptr[0] == PCDP_IF_PCI) { + struct pcdp_if_pci if_pci; + + /* struct copy since ifptr might not be correctly aligned */ + + memcpy(&if_pci, if_ptr, sizeof(if_pci)); + + if (if_pci.trans & PCDP_PCI_TRANS_IOPORT) + vga_console_iobase = if_pci.ioport_tra; + + if (if_pci.trans & PCDP_PCI_TRANS_MMIO) + vga_console_membase = if_pci.mmio_tra; + } + + if (efi_mem_type(vga_console_membase + 0xA0000) == EFI_CONVENTIONAL_MEMORY) { + printk(KERN_ERR "PCDP: VGA selected, but frame buffer is not MMIO!\n"); + return -ENODEV; + } + + conswitchp = &vga_con; + printk(KERN_INFO "PCDP: VGA console\n"); + return 0; +#else + return -ENODEV; +#endif +} + +int __init +efi_setup_pcdp_console(char *cmdline) +{ + struct pcdp *pcdp; + struct pcdp_uart *uart; + struct pcdp_device *dev, *end; + int i, serial = 0; + int rc = -ENODEV; + + if (efi.hcdp == EFI_INVALID_TABLE_ADDR) + return -ENODEV; + + pcdp = early_ioremap(efi.hcdp, 4096); + printk(KERN_INFO "PCDP: v%d at 0x%lx\n", pcdp->rev, efi.hcdp); + + if (strstr(cmdline, "console=hcdp")) { + if (pcdp->rev < 3) + serial = 1; + } else if (strstr(cmdline, "console=")) { + printk(KERN_INFO "Explicit \"console=\"; ignoring PCDP\n"); + goto out; + } + + if (pcdp->rev < 3 && efi_uart_console_only()) + serial = 1; + + for (i = 0, uart = pcdp->uart; i < pcdp->num_uarts; i++, uart++) { + if (uart->flags & PCDP_UART_PRIMARY_CONSOLE || serial) { + if (uart->type == PCDP_CONSOLE_UART) { + rc = setup_serial_console(uart); + goto out; + } + } + } + + end = (struct pcdp_device *) ((u8 *) pcdp + pcdp->length); + for (dev = (struct pcdp_device *) (pcdp->uart + pcdp->num_uarts); + dev < end; + dev = (struct pcdp_device *) ((u8 *) dev + dev->length)) { + if (dev->flags & PCDP_PRIMARY_CONSOLE) { + if (dev->type == PCDP_CONSOLE_VGA) { + rc = setup_vga_console(dev); + goto out; + } + } + } + +out: + early_iounmap(pcdp, 4096); + return rc; +} diff --git a/drivers/firmware/pcdp.h b/drivers/firmware/pcdp.h new file mode 100644 index 000000000..e5530608e --- /dev/null +++ b/drivers/firmware/pcdp.h @@ -0,0 +1,111 @@ +/* + * Definitions for PCDP-defined console devices + * + * For DIG64_HCDPv10a_01.pdf and DIG64_PCDPv20.pdf (v1.0a and v2.0 resp.), + * please see <http://www.dig64.org/specifications/> + * + * (c) Copyright 2002, 2004 Hewlett-Packard Development Company, L.P. + * Khalid Aziz <khalid.aziz@hp.com> + * Bjorn Helgaas <bjorn.helgaas@hp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define PCDP_CONSOLE 0 +#define PCDP_DEBUG 1 +#define PCDP_CONSOLE_OUTPUT 2 +#define PCDP_CONSOLE_INPUT 3 + +#define PCDP_UART (0 << 3) +#define PCDP_VGA (1 << 3) +#define PCDP_USB (2 << 3) + +/* pcdp_uart.type and pcdp_device.type */ +#define PCDP_CONSOLE_UART (PCDP_UART | PCDP_CONSOLE) +#define PCDP_DEBUG_UART (PCDP_UART | PCDP_DEBUG) +#define PCDP_CONSOLE_VGA (PCDP_VGA | PCDP_CONSOLE_OUTPUT) +#define PCDP_CONSOLE_USB (PCDP_USB | PCDP_CONSOLE_INPUT) + +/* pcdp_uart.flags */ +#define PCDP_UART_EDGE_SENSITIVE (1 << 0) +#define PCDP_UART_ACTIVE_LOW (1 << 1) +#define PCDP_UART_PRIMARY_CONSOLE (1 << 2) +#define PCDP_UART_IRQ (1 << 6) /* in pci_func for rev < 3 */ +#define PCDP_UART_PCI (1 << 7) /* in pci_func for rev < 3 */ + +struct pcdp_uart { + u8 type; + u8 bits; + u8 parity; + u8 stop_bits; + u8 pci_seg; + u8 pci_bus; + u8 pci_dev; + u8 pci_func; + u64 baud; + struct acpi_generic_address addr; + u16 pci_dev_id; + u16 pci_vendor_id; + u32 gsi; + u32 clock_rate; + u8 pci_prog_intfc; + u8 flags; + u16 conout_index; + u32 reserved; +} __attribute__((packed)); + +#define PCDP_IF_PCI 1 + +/* pcdp_if_pci.trans */ +#define PCDP_PCI_TRANS_IOPORT 0x02 +#define PCDP_PCI_TRANS_MMIO 0x01 + +struct pcdp_if_pci { + u8 interconnect; + u8 reserved; + u16 length; + u8 segment; + u8 bus; + u8 dev; + u8 fun; + u16 dev_id; + u16 vendor_id; + u32 acpi_interrupt; + u64 mmio_tra; + u64 ioport_tra; + u8 flags; + u8 trans; +} __attribute__((packed)); + +struct pcdp_vga { + u8 count; /* address space descriptors */ +} __attribute__((packed)); + +/* pcdp_device.flags */ +#define PCDP_PRIMARY_CONSOLE 1 + +struct pcdp_device { + u8 type; + u8 flags; + u16 length; + u16 efi_index; + /* next data is pcdp_if_pci or pcdp_if_acpi (not yet supported) */ + /* next data is device specific type (currently only pcdp_vga) */ +} __attribute__((packed)); + +struct pcdp { + u8 signature[4]; + u32 length; + u8 rev; /* PCDP v2.0 is rev 3 */ + u8 chksum; + u8 oemid[6]; + u8 oem_tabid[8]; + u32 oem_rev; + u8 creator_id[4]; + u32 creator_rev; + u32 num_uarts; + struct pcdp_uart uart[0]; /* actual size is num_uarts */ + /* remainder of table is pcdp_device structures */ +} __attribute__((packed)); diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c new file mode 100644 index 000000000..994b50fd9 --- /dev/null +++ b/drivers/firmware/qcom_scm.c @@ -0,0 +1,494 @@ +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (C) 2015 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/qcom_scm.h> + +#include <asm/outercache.h> +#include <asm/cacheflush.h> + + +#define QCOM_SCM_ENOMEM -5 +#define QCOM_SCM_EOPNOTSUPP -4 +#define QCOM_SCM_EINVAL_ADDR -3 +#define QCOM_SCM_EINVAL_ARG -2 +#define QCOM_SCM_ERROR -1 +#define QCOM_SCM_INTERRUPTED 1 + +#define QCOM_SCM_FLAG_COLDBOOT_CPU0 0x00 +#define QCOM_SCM_FLAG_COLDBOOT_CPU1 0x01 +#define QCOM_SCM_FLAG_COLDBOOT_CPU2 0x08 +#define QCOM_SCM_FLAG_COLDBOOT_CPU3 0x20 + +#define QCOM_SCM_FLAG_WARMBOOT_CPU0 0x04 +#define QCOM_SCM_FLAG_WARMBOOT_CPU1 0x02 +#define QCOM_SCM_FLAG_WARMBOOT_CPU2 0x10 +#define QCOM_SCM_FLAG_WARMBOOT_CPU3 0x40 + +struct qcom_scm_entry { + int flag; + void *entry; +}; + +static struct qcom_scm_entry qcom_scm_wb[] = { + { .flag = QCOM_SCM_FLAG_WARMBOOT_CPU0 }, + { .flag = QCOM_SCM_FLAG_WARMBOOT_CPU1 }, + { .flag = QCOM_SCM_FLAG_WARMBOOT_CPU2 }, + { .flag = QCOM_SCM_FLAG_WARMBOOT_CPU3 }, +}; + +static DEFINE_MUTEX(qcom_scm_lock); + +/** + * struct qcom_scm_command - one SCM command buffer + * @len: total available memory for command and response + * @buf_offset: start of command buffer + * @resp_hdr_offset: start of response buffer + * @id: command to be executed + * @buf: buffer returned from qcom_scm_get_command_buffer() + * + * An SCM command is laid out in memory as follows: + * + * ------------------- <--- struct qcom_scm_command + * | command header | + * ------------------- <--- qcom_scm_get_command_buffer() + * | command buffer | + * ------------------- <--- struct qcom_scm_response and + * | response header | qcom_scm_command_to_response() + * ------------------- <--- qcom_scm_get_response_buffer() + * | response buffer | + * ------------------- + * + * There can be arbitrary padding between the headers and buffers so + * you should always use the appropriate qcom_scm_get_*_buffer() routines + * to access the buffers in a safe manner. + */ +struct qcom_scm_command { + __le32 len; + __le32 buf_offset; + __le32 resp_hdr_offset; + __le32 id; + __le32 buf[0]; +}; + +/** + * struct qcom_scm_response - one SCM response buffer + * @len: total available memory for response + * @buf_offset: start of response data relative to start of qcom_scm_response + * @is_complete: indicates if the command has finished processing + */ +struct qcom_scm_response { + __le32 len; + __le32 buf_offset; + __le32 is_complete; +}; + +/** + * alloc_qcom_scm_command() - Allocate an SCM command + * @cmd_size: size of the command buffer + * @resp_size: size of the response buffer + * + * Allocate an SCM command, including enough room for the command + * and response headers as well as the command and response buffers. + * + * Returns a valid &qcom_scm_command on success or %NULL if the allocation fails. + */ +static struct qcom_scm_command *alloc_qcom_scm_command(size_t cmd_size, size_t resp_size) +{ + struct qcom_scm_command *cmd; + size_t len = sizeof(*cmd) + sizeof(struct qcom_scm_response) + cmd_size + + resp_size; + u32 offset; + + cmd = kzalloc(PAGE_ALIGN(len), GFP_KERNEL); + if (cmd) { + cmd->len = cpu_to_le32(len); + offset = offsetof(struct qcom_scm_command, buf); + cmd->buf_offset = cpu_to_le32(offset); + cmd->resp_hdr_offset = cpu_to_le32(offset + cmd_size); + } + return cmd; +} + +/** + * free_qcom_scm_command() - Free an SCM command + * @cmd: command to free + * + * Free an SCM command. + */ +static inline void free_qcom_scm_command(struct qcom_scm_command *cmd) +{ + kfree(cmd); +} + +/** + * qcom_scm_command_to_response() - Get a pointer to a qcom_scm_response + * @cmd: command + * + * Returns a pointer to a response for a command. + */ +static inline struct qcom_scm_response *qcom_scm_command_to_response( + const struct qcom_scm_command *cmd) +{ + return (void *)cmd + le32_to_cpu(cmd->resp_hdr_offset); +} + +/** + * qcom_scm_get_command_buffer() - Get a pointer to a command buffer + * @cmd: command + * + * Returns a pointer to the command buffer of a command. + */ +static inline void *qcom_scm_get_command_buffer(const struct qcom_scm_command *cmd) +{ + return (void *)cmd->buf; +} + +/** + * qcom_scm_get_response_buffer() - Get a pointer to a response buffer + * @rsp: response + * + * Returns a pointer to a response buffer of a response. + */ +static inline void *qcom_scm_get_response_buffer(const struct qcom_scm_response *rsp) +{ + return (void *)rsp + le32_to_cpu(rsp->buf_offset); +} + +static int qcom_scm_remap_error(int err) +{ + pr_err("qcom_scm_call failed with error code %d\n", err); + switch (err) { + case QCOM_SCM_ERROR: + return -EIO; + case QCOM_SCM_EINVAL_ADDR: + case QCOM_SCM_EINVAL_ARG: + return -EINVAL; + case QCOM_SCM_EOPNOTSUPP: + return -EOPNOTSUPP; + case QCOM_SCM_ENOMEM: + return -ENOMEM; + } + return -EINVAL; +} + +static u32 smc(u32 cmd_addr) +{ + int context_id; + register u32 r0 asm("r0") = 1; + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = cmd_addr; + do { + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r0") + __asmeq("%2", "r1") + __asmeq("%3", "r2") +#ifdef REQUIRES_SEC + ".arch_extension sec\n" +#endif + "smc #0 @ switch to secure world\n" + : "=r" (r0) + : "r" (r0), "r" (r1), "r" (r2) + : "r3"); + } while (r0 == QCOM_SCM_INTERRUPTED); + + return r0; +} + +static int __qcom_scm_call(const struct qcom_scm_command *cmd) +{ + int ret; + u32 cmd_addr = virt_to_phys(cmd); + + /* + * Flush the command buffer so that the secure world sees + * the correct data. + */ + __cpuc_flush_dcache_area((void *)cmd, cmd->len); + outer_flush_range(cmd_addr, cmd_addr + cmd->len); + + ret = smc(cmd_addr); + if (ret < 0) + ret = qcom_scm_remap_error(ret); + + return ret; +} + +static void qcom_scm_inv_range(unsigned long start, unsigned long end) +{ + u32 cacheline_size, ctr; + + asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr)); + cacheline_size = 4 << ((ctr >> 16) & 0xf); + + start = round_down(start, cacheline_size); + end = round_up(end, cacheline_size); + outer_inv_range(start, end); + while (start < end) { + asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start) + : "memory"); + start += cacheline_size; + } + dsb(); + isb(); +} + +/** + * qcom_scm_call() - Send an SCM command + * @svc_id: service identifier + * @cmd_id: command identifier + * @cmd_buf: command buffer + * @cmd_len: length of the command buffer + * @resp_buf: response buffer + * @resp_len: length of the response buffer + * + * Sends a command to the SCM and waits for the command to finish processing. + * + * A note on cache maintenance: + * Note that any buffers that are expected to be accessed by the secure world + * must be flushed before invoking qcom_scm_call and invalidated in the cache + * immediately after qcom_scm_call returns. Cache maintenance on the command + * and response buffers is taken care of by qcom_scm_call; however, callers are + * responsible for any other cached buffers passed over to the secure world. + */ +static int qcom_scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, + size_t cmd_len, void *resp_buf, size_t resp_len) +{ + int ret; + struct qcom_scm_command *cmd; + struct qcom_scm_response *rsp; + unsigned long start, end; + + cmd = alloc_qcom_scm_command(cmd_len, resp_len); + if (!cmd) + return -ENOMEM; + + cmd->id = cpu_to_le32((svc_id << 10) | cmd_id); + if (cmd_buf) + memcpy(qcom_scm_get_command_buffer(cmd), cmd_buf, cmd_len); + + mutex_lock(&qcom_scm_lock); + ret = __qcom_scm_call(cmd); + mutex_unlock(&qcom_scm_lock); + if (ret) + goto out; + + rsp = qcom_scm_command_to_response(cmd); + start = (unsigned long)rsp; + + do { + qcom_scm_inv_range(start, start + sizeof(*rsp)); + } while (!rsp->is_complete); + + end = (unsigned long)qcom_scm_get_response_buffer(rsp) + resp_len; + qcom_scm_inv_range(start, end); + + if (resp_buf) + memcpy(resp_buf, qcom_scm_get_response_buffer(rsp), resp_len); +out: + free_qcom_scm_command(cmd); + return ret; +} + +#define SCM_CLASS_REGISTER (0x2 << 8) +#define SCM_MASK_IRQS BIT(5) +#define SCM_ATOMIC(svc, cmd, n) (((((svc) << 10)|((cmd) & 0x3ff)) << 12) | \ + SCM_CLASS_REGISTER | \ + SCM_MASK_IRQS | \ + (n & 0xf)) + +/** + * qcom_scm_call_atomic1() - Send an atomic SCM command with one argument + * @svc_id: service identifier + * @cmd_id: command identifier + * @arg1: first argument + * + * This shall only be used with commands that are guaranteed to be + * uninterruptable, atomic and SMP safe. + */ +static s32 qcom_scm_call_atomic1(u32 svc, u32 cmd, u32 arg1) +{ + int context_id; + + register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 1); + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = arg1; + + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r0") + __asmeq("%2", "r1") + __asmeq("%3", "r2") +#ifdef REQUIRES_SEC + ".arch_extension sec\n" +#endif + "smc #0 @ switch to secure world\n" + : "=r" (r0) + : "r" (r0), "r" (r1), "r" (r2) + : "r3"); + return r0; +} + +u32 qcom_scm_get_version(void) +{ + int context_id; + static u32 version = -1; + register u32 r0 asm("r0"); + register u32 r1 asm("r1"); + + if (version != -1) + return version; + + mutex_lock(&qcom_scm_lock); + + r0 = 0x1 << 8; + r1 = (u32)&context_id; + do { + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r1") + __asmeq("%2", "r0") + __asmeq("%3", "r1") +#ifdef REQUIRES_SEC + ".arch_extension sec\n" +#endif + "smc #0 @ switch to secure world\n" + : "=r" (r0), "=r" (r1) + : "r" (r0), "r" (r1) + : "r2", "r3"); + } while (r0 == QCOM_SCM_INTERRUPTED); + + version = r1; + mutex_unlock(&qcom_scm_lock); + + return version; +} +EXPORT_SYMBOL(qcom_scm_get_version); + +#define QCOM_SCM_SVC_BOOT 0x1 +#define QCOM_SCM_BOOT_ADDR 0x1 +/* + * Set the cold/warm boot address for one of the CPU cores. + */ +static int qcom_scm_set_boot_addr(u32 addr, int flags) +{ + struct { + __le32 flags; + __le32 addr; + } cmd; + + cmd.addr = cpu_to_le32(addr); + cmd.flags = cpu_to_le32(flags); + return qcom_scm_call(QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR, + &cmd, sizeof(cmd), NULL, 0); +} + +/** + * qcom_scm_set_cold_boot_addr() - Set the cold boot address for cpus + * @entry: Entry point function for the cpus + * @cpus: The cpumask of cpus that will use the entry point + * + * Set the cold boot address of the cpus. Any cpu outside the supported + * range would be removed from the cpu present mask. + */ +int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus) +{ + int flags = 0; + int cpu; + int scm_cb_flags[] = { + QCOM_SCM_FLAG_COLDBOOT_CPU0, + QCOM_SCM_FLAG_COLDBOOT_CPU1, + QCOM_SCM_FLAG_COLDBOOT_CPU2, + QCOM_SCM_FLAG_COLDBOOT_CPU3, + }; + + if (!cpus || (cpus && cpumask_empty(cpus))) + return -EINVAL; + + for_each_cpu(cpu, cpus) { + if (cpu < ARRAY_SIZE(scm_cb_flags)) + flags |= scm_cb_flags[cpu]; + else + set_cpu_present(cpu, false); + } + + return qcom_scm_set_boot_addr(virt_to_phys(entry), flags); +} +EXPORT_SYMBOL(qcom_scm_set_cold_boot_addr); + +/** + * qcom_scm_set_warm_boot_addr() - Set the warm boot address for cpus + * @entry: Entry point function for the cpus + * @cpus: The cpumask of cpus that will use the entry point + * + * Set the Linux entry point for the SCM to transfer control to when coming + * out of a power down. CPU power down may be executed on cpuidle or hotplug. + */ +int qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus) +{ + int ret; + int flags = 0; + int cpu; + + /* + * Reassign only if we are switching from hotplug entry point + * to cpuidle entry point or vice versa. + */ + for_each_cpu(cpu, cpus) { + if (entry == qcom_scm_wb[cpu].entry) + continue; + flags |= qcom_scm_wb[cpu].flag; + } + + /* No change in entry function */ + if (!flags) + return 0; + + ret = qcom_scm_set_boot_addr(virt_to_phys(entry), flags); + if (!ret) { + for_each_cpu(cpu, cpus) + qcom_scm_wb[cpu].entry = entry; + } + + return ret; +} +EXPORT_SYMBOL(qcom_scm_set_warm_boot_addr); + +#define QCOM_SCM_CMD_TERMINATE_PC 0x2 +#define QCOM_SCM_FLUSH_FLAG_MASK 0x3 + +/** + * qcom_scm_cpu_power_down() - Power down the cpu + * @flags - Flags to flush cache + * + * This is an end point to power down cpu. If there was a pending interrupt, + * the control would return from this function, otherwise, the cpu jumps to the + * warm boot entry point set for this cpu upon reset. + */ +void qcom_scm_cpu_power_down(u32 flags) +{ + qcom_scm_call_atomic1(QCOM_SCM_SVC_BOOT, QCOM_SCM_CMD_TERMINATE_PC, + flags & QCOM_SCM_FLUSH_FLAG_MASK); +} +EXPORT_SYMBOL(qcom_scm_cpu_power_down); |