diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/firmware | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/firmware')
-rw-r--r-- | drivers/firmware/Kconfig | 162 | ||||
-rw-r--r-- | drivers/firmware/Makefile | 17 | ||||
-rw-r--r-- | drivers/firmware/dcdbas.c | 659 | ||||
-rw-r--r-- | drivers/firmware/dcdbas.h | 107 | ||||
-rw-r--r-- | drivers/firmware/dell_rbu.c | 745 | ||||
-rw-r--r-- | drivers/firmware/dmi-id.c | 245 | ||||
-rw-r--r-- | drivers/firmware/dmi-sysfs.c | 696 | ||||
-rw-r--r-- | drivers/firmware/dmi_scan.c | 751 | ||||
-rw-r--r-- | drivers/firmware/edd.c | 801 | ||||
-rw-r--r-- | drivers/firmware/efivars.c | 1040 | ||||
-rw-r--r-- | drivers/firmware/google/Kconfig | 32 | ||||
-rw-r--r-- | drivers/firmware/google/Makefile | 3 | ||||
-rw-r--r-- | drivers/firmware/google/gsmi.c | 940 | ||||
-rw-r--r-- | drivers/firmware/google/memconsole.c | 166 | ||||
-rw-r--r-- | drivers/firmware/iscsi_ibft.c | 813 | ||||
-rw-r--r-- | drivers/firmware/iscsi_ibft_find.c | 112 | ||||
-rw-r--r-- | drivers/firmware/memmap.c | 250 | ||||
-rw-r--r-- | drivers/firmware/pcdp.c | 136 | ||||
-rw-r--r-- | drivers/firmware/pcdp.h | 111 | ||||
-rw-r--r-- | drivers/firmware/sigma.c | 153 |
20 files changed, 7939 insertions, 0 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig new file mode 100644 index 00000000..efba1635 --- /dev/null +++ b/drivers/firmware/Kconfig @@ -0,0 +1,162 @@ +# +# 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_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 Matt Domsch's website located at: + <http://linux.dell.com/efibootmgr/testing/efibootmgr-0.5.0-test3.tar.gz> + + Subsequent efibootmgr releases may be found at: + <http://linux.dell.com/efibootmgr> + +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 + 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 ISCSI_IBFT_FIND + bool "iSCSI Boot Firmware Table Attributes" + depends on X86 + 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 SIGMA + tristate "SigmaStudio firmware loader" + depends on I2C + select CRC32 + default n + help + Enable helper functions for working with Analog Devices SigmaDSP + parts and binary firmwares produced by Analog Devices SigmaStudio. + + If unsure, say N here. Drivers that need these helpers will select + this option automatically. + +source "drivers/firmware/google/Kconfig" + +endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile new file mode 100644 index 00000000..47338c97 --- /dev/null +++ b/drivers/firmware/Makefile @@ -0,0 +1,17 @@ +# +# 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_VARS) += efivars.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_SIGMA) += sigma.o + +obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ diff --git a/drivers/firmware/dcdbas.c b/drivers/firmware/dcdbas.c new file mode 100644 index 00000000..ea5ac2dc --- /dev/null +++ b/drivers/firmware/dcdbas.c @@ -0,0 +1,659 @@ +/* + * 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, +}; + +static int __devinit dcdbas_probe(struct platform_device *dev) +{ + int i, error; + + host_control_action = HC_ACTION_NONE; + host_control_smi_type = HC_SMITYPE_NONE; + + /* + * BIOS SMI calls require buffer addresses be in 32-bit address space. + * This is done by setting the DMA mask below. + */ + dcdbas_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + dcdbas_pdev->dev.dma_mask = &dcdbas_pdev->dev.coherent_dma_mask; + + error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); + if (error) + return error; + + for (i = 0; dcdbas_bin_attrs[i]; i++) { + error = sysfs_create_bin_file(&dev->dev.kobj, + dcdbas_bin_attrs[i]); + if (error) { + while (--i >= 0) + sysfs_remove_bin_file(&dev->dev.kobj, + dcdbas_bin_attrs[i]); + sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); + return error; + } + } + + register_reboot_notifier(&dcdbas_reboot_nb); + + dev_info(&dev->dev, "%s (version %s)\n", + DRIVER_DESCRIPTION, DRIVER_VERSION); + + return 0; +} + +static int __devexit dcdbas_remove(struct platform_device *dev) +{ + int i; + + unregister_reboot_notifier(&dcdbas_reboot_nb); + for (i = 0; dcdbas_bin_attrs[i]; i++) + sysfs_remove_bin_file(&dev->dev.kobj, dcdbas_bin_attrs[i]); + sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); + + return 0; +} + +static struct platform_driver dcdbas_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = dcdbas_probe, + .remove = __devexit_p(dcdbas_remove), +}; + +/** + * dcdbas_init: initialize driver + */ +static int __init dcdbas_init(void) +{ + int error; + + error = platform_driver_register(&dcdbas_driver); + if (error) + return error; + + dcdbas_pdev = platform_device_alloc(DRIVER_NAME, -1); + if (!dcdbas_pdev) { + error = -ENOMEM; + goto err_unregister_driver; + } + + error = platform_device_add(dcdbas_pdev); + if (error) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(dcdbas_pdev); + 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. + */ + smi_data_buf_free(); + platform_device_unregister(dcdbas_pdev); + 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 00000000..ca3cb0a5 --- /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 00000000..2f452f1f --- /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 00000000..94a58a08 --- /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 00000000..eb26d62e --- /dev/null +++ b/drivers/firmware/dmi-sysfs.c @@ -0,0 +1,696 @@ +/* + * 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); + sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr); + 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_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 00000000..bcb1126e --- /dev/null +++ b/drivers/firmware/dmi_scan.c @@ -0,0 +1,751 @@ +#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 <asm/dmi.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 char dmi_empty_string[] = " "; + +/* + * Catch too early calls to dmi_check_system(): + */ +static int dmi_initialized; + +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 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); + else + printk(KERN_ERR "dmi_string: cannot allocate %Zu bytes.\n", len); + + 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, int len, int num, + void (*decode)(const struct dmi_header *, void *), + void *private_data) +{ + u8 *data = buf; + int i = 0; + + /* + * Stop when we see all the items the table claimed to have + * OR we run off the end of the table (also happens) + */ + while ((i < num) && (data - buf + sizeof(struct dmi_header)) <= 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 < len - 1) && (data[0] || data[1])) + data++; + if (data - buf < len - 1) + decode(dm, private_data); + data += 2; + i++; + } +} + +static u32 dmi_base; +static u16 dmi_len; +static u16 dmi_num; + +static int __init dmi_walk_early(void (*decode)(const struct dmi_header *, + void *)) +{ + u8 *buf; + + buf = dmi_ioremap(dmi_base, dmi_len); + if (buf == NULL) + return -1; + + dmi_table(buf, dmi_len, dmi_num, decode, NULL); + + dmi_iounmap(buf, dmi_len); + return 0; +} + +static int __init dmi_checksum(const u8 *buf) +{ + u8 sum = 0; + int a; + + for (a = 0; a < 15; a++) + sum += buf[a]; + + return sum == 0; +} + +static 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; + 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_ff = 0; + if(d[i] != 0xFF) is_00 = 0; + } + + if (is_ff || is_00) + return; + + s = dmi_alloc(16*2+4+1); + if (!s) + return; + + 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) { + printk(KERN_ERR "dmi_save_one_device: out of memory.\n"); + 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++) { + char *devname = dmi_string(dm, i); + + if (devname == dmi_empty_string) + continue; + + dev = dmi_alloc(sizeof(*dev)); + if (!dev) { + printk(KERN_ERR + "dmi_save_oem_strings_devices: out of memory.\n"); + 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) { + printk(KERN_ERR "dmi_save_ipmi_device: out of memory.\n"); + return; + } + + memcpy(data, dm, dm->length); + + dev = dmi_alloc(sizeof(*dev)); + if (!dev) { + printk(KERN_ERR "dmi_save_ipmi_device: out of memory.\n"); + 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) { + printk(KERN_ERR "dmi_save_dev_onboard: out of memory.\n"); + 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))); +} + +/* + * 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 void __init print_filtered(const char *info) +{ + const char *p; + + if (!info) + return; + + for (p = info; *p; p++) + if (isprint(*p)) + printk(KERN_CONT "%c", *p); + else + printk(KERN_CONT "\\x%02x", *p & 0xff); +} + +static void __init dmi_dump_ids(void) +{ + const char *board; /* Board Name is optional */ + + printk(KERN_DEBUG "DMI: "); + print_filtered(dmi_get_system_info(DMI_SYS_VENDOR)); + printk(KERN_CONT " "); + print_filtered(dmi_get_system_info(DMI_PRODUCT_NAME)); + board = dmi_get_system_info(DMI_BOARD_NAME); + if (board) { + printk(KERN_CONT "/"); + print_filtered(board); + } + printk(KERN_CONT ", BIOS "); + print_filtered(dmi_get_system_info(DMI_BIOS_VERSION)); + printk(KERN_CONT " "); + print_filtered(dmi_get_system_info(DMI_BIOS_DATE)); + printk(KERN_CONT "\n"); +} + +static int __init dmi_present(const char __iomem *p) +{ + u8 buf[15]; + + memcpy_fromio(buf, p, 15); + if ((memcmp(buf, "_DMI_", 5) == 0) && dmi_checksum(buf)) { + dmi_num = (buf[13] << 8) | buf[12]; + dmi_len = (buf[7] << 8) | buf[6]; + dmi_base = (buf[11] << 24) | (buf[10] << 16) | + (buf[9] << 8) | buf[8]; + + /* + * DMI version 0.0 means that the real version is taken from + * the SMBIOS version, which we don't know at this point. + */ + if (buf[14] != 0) + printk(KERN_INFO "DMI %d.%d present.\n", + buf[14] >> 4, buf[14] & 0xF); + else + printk(KERN_INFO "DMI present.\n"); + if (dmi_walk_early(dmi_decode) == 0) { + dmi_dump_ids(); + return 0; + } + } + return 1; +} + +void __init dmi_scan_machine(void) +{ + char __iomem *p, *q; + int rc; + + if (efi_enabled) { + 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_ioremap(efi.smbios, 32); + if (p == NULL) + goto error; + + rc = dmi_present(p + 0x10); /* offset of _DMI_ string */ + dmi_iounmap(p, 32); + if (!rc) { + dmi_available = 1; + goto out; + } + } + else { + /* + * no iounmap() for that ioremap(); it would be a no-op, but + * it's so early in setup that sucker gets confused into doing + * what it shouldn't if we actually call it. + */ + p = dmi_ioremap(0xF0000, 0x10000); + if (p == NULL) + goto error; + + for (q = p; q < p + 0x10000; q += 16) { + rc = dmi_present(q); + if (!rc) { + dmi_available = 1; + dmi_iounmap(p, 0x10000); + goto out; + } + } + dmi_iounmap(p, 0x10000); + } + error: + printk(KERN_INFO "DMI not present or invalid.\n"); + out: + dmi_initialized = 1; +} + +/** + * 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] + && strstr(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 anywhere in the DMI vendor information. + * @str: Case sensitive Name + */ +int dmi_name_in_vendors(const char *str) +{ + static int fields[] = { DMI_BIOS_VENDOR, DMI_BIOS_VERSION, DMI_SYS_VENDOR, + DMI_PRODUCT_NAME, DMI_PRODUCT_VERSION, DMI_BOARD_VENDOR, + DMI_BOARD_NAME, DMI_BOARD_VERSION, 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 = ioremap(dmi_base, dmi_len); + if (buf == NULL) + return -1; + + dmi_table(buf, dmi_len, dmi_num, decode, private_data); + + iounmap(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); diff --git a/drivers/firmware/edd.c b/drivers/firmware/edd.c new file mode 100644 index 00000000..f1b7f659 --- /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)) { + 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, "XPRS", 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")) { + 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/efivars.c b/drivers/firmware/efivars.c new file mode 100644 index 00000000..e27d56c7 --- /dev/null +++ b/drivers/firmware/efivars.c @@ -0,0 +1,1040 @@ +/* + * EFI Variables - 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_unparse() + * + * 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/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/kobject.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <asm/uaccess.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); + +/* + * The maximum size of VariableName + Data = 1024 + * Therefore, it's reasonable to save that much + * space in each part of the structure, + * and we use a page for reading/writing. + */ + +struct efi_variable { + efi_char16_t VariableName[1024/sizeof(efi_char16_t)]; + efi_guid_t VendorGuid; + unsigned long DataSize; + __u8 Data[1024]; + efi_status_t Status; + __u32 Attributes; +} __attribute__((packed)); + + +struct efivar_entry { + struct efivars *efivars; + struct efi_variable var; + struct list_head list; + struct kobject kobj; +}; + +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 efivars *efivars, + unsigned long variable_name_size, + efi_char16_t *variable_name, + efi_guid_t *vendor_guid); + +/* Return the number of unicode characters in data */ +static unsigned long +utf16_strnlen(efi_char16_t *s, size_t maxlength) +{ + unsigned long length = 0; + + while (*s++ != 0 && length < maxlength) + length++; + return length; +} + +static inline unsigned long +utf16_strlen(efi_char16_t *s) +{ + return utf16_strnlen(s, ~0UL); +} + +/* + * Return the number of bytes is the length of this string + * Note: this is NOT the same as the number of unicode characters + */ +static inline unsigned long +utf16_strsize(efi_char16_t *data, unsigned long maxlength) +{ + return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); +} + +static bool +validate_device_path(struct efi_variable *var, 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(struct efi_variable *var, 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(struct efi_variable *var, int match, u8 *buffer, + unsigned long len) +{ + u16 filepathlength; + int i, desclength = 0, namelen; + + namelen = utf16_strnlen(var->VariableName, sizeof(var->VariableName)); + + /* Either "Boot" or "Driver" followed by four digits of hex */ + for (i = match; i < match+4; i++) { + if (var->VariableName[i] > 127 || + hex_to_bin(var->VariableName[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 = utf16_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, match, buffer + desclength + 6, + filepathlength); +} + +static bool +validate_uint16(struct efi_variable *var, int match, u8 *buffer, + unsigned long len) +{ + /* A single 16-bit integer */ + if (len != 2) + return false; + + return true; +} + +static bool +validate_ascii_string(struct efi_variable *var, 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)(struct efi_variable *var, 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 }, +}; + +static bool +validate_var(struct efi_variable *var, u8 *data, unsigned long len) +{ + int i; + u16 *unicode_name = var->VariableName; + + 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, + 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, + match, data, len); + } + } + + return true; +} + +static efi_status_t +get_var_data(struct efivars *efivars, struct efi_variable *var) +{ + efi_status_t status; + + spin_lock(&efivars->lock); + var->DataSize = 1024; + status = efivars->ops->get_variable(var->VariableName, + &var->VendorGuid, + &var->Attributes, + &var->DataSize, + var->Data); + spin_unlock(&efivars->lock); + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", + status); + } + return status; +} + +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_unparse(&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; + efi_status_t status; + + if (!entry || !buf) + return -EINVAL; + + status = get_var_data(entry->efivars, var); + if (status != EFI_SUCCESS) + return -EIO; + + if (var->Attributes & 0x1) + str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); + if (var->Attributes & 0x2) + str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); + if (var->Attributes & 0x4) + str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\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; + efi_status_t status; + + if (!entry || !buf) + return -EINVAL; + + status = get_var_data(entry->efivars, var); + if (status != EFI_SUCCESS) + 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; + efi_status_t status; + + if (!entry || !buf) + return -EINVAL; + + status = get_var_data(entry->efivars, var); + if (status != EFI_SUCCESS) + return -EIO; + + memcpy(buf, var->Data, var->DataSize); + return var->DataSize; +} +/* + * 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; + struct efivars *efivars = entry->efivars; + efi_status_t status = EFI_NOT_FOUND; + + if (count != sizeof(struct efi_variable)) + return -EINVAL; + + new_var = (struct efi_variable *)buf; + /* + * If only updating the variable data, then the name + * and guid should remain the same + */ + if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) || + efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) { + printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n"); + return -EINVAL; + } + + if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){ + printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n"); + return -EINVAL; + } + + if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || + validate_var(new_var, new_var->Data, new_var->DataSize) == false) { + printk(KERN_ERR "efivars: Malformed variable content\n"); + return -EINVAL; + } + + spin_lock(&efivars->lock); + status = efivars->ops->set_variable(new_var->VariableName, + &new_var->VendorGuid, + new_var->Attributes, + new_var->DataSize, + new_var->Data); + + spin_unlock(&efivars->lock); + + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", + status); + return -EIO; + } + + memcpy(&entry->var, new_var, count); + return count; +} + +static ssize_t +efivar_show_raw(struct efivar_entry *entry, char *buf) +{ + struct efi_variable *var = &entry->var; + efi_status_t status; + + if (!entry || !buf) + return 0; + + status = get_var_data(entry->efivars, var); + if (status != EFI_SUCCESS) + return -EIO; + + memcpy(buf, var, sizeof(*var)); + return sizeof(*var); +} + +/* + * 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 inline void +efivar_unregister(struct efivar_entry *var) +{ + kobject_put(&var->kobj); +} + + +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 efi_variable *new_var = (struct efi_variable *)buf; + struct efivars *efivars = bin_attr->private; + struct efivar_entry *search_efivar, *n; + unsigned long strsize1, strsize2; + efi_status_t status = EFI_NOT_FOUND; + int found = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || + validate_var(new_var, new_var->Data, new_var->DataSize) == false) { + printk(KERN_ERR "efivars: Malformed variable content\n"); + return -EINVAL; + } + + spin_lock(&efivars->lock); + + /* + * Does this variable already exist? + */ + list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { + strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); + strsize2 = utf16_strsize(new_var->VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(&(search_efivar->var.VariableName), + new_var->VariableName, strsize1) && + !efi_guidcmp(search_efivar->var.VendorGuid, + new_var->VendorGuid)) { + found = 1; + break; + } + } + if (found) { + spin_unlock(&efivars->lock); + return -EINVAL; + } + + /* now *really* create the variable via EFI */ + status = efivars->ops->set_variable(new_var->VariableName, + &new_var->VendorGuid, + new_var->Attributes, + new_var->DataSize, + new_var->Data); + + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", + status); + spin_unlock(&efivars->lock); + return -EIO; + } + spin_unlock(&efivars->lock); + + /* Create the entry in sysfs. Locking is not required here */ + status = efivar_create_sysfs_entry(efivars, + utf16_strsize(new_var->VariableName, + 1024), + new_var->VariableName, + &new_var->VendorGuid); + if (status) { + printk(KERN_WARNING "efivars: variable created, but sysfs entry wasn't.\n"); + } + return count; +} + +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 efivars *efivars = bin_attr->private; + struct efivar_entry *search_efivar, *n; + unsigned long strsize1, strsize2; + efi_status_t status = EFI_NOT_FOUND; + int found = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + spin_lock(&efivars->lock); + + /* + * Does this variable already exist? + */ + list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { + strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); + strsize2 = utf16_strsize(del_var->VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(&(search_efivar->var.VariableName), + del_var->VariableName, strsize1) && + !efi_guidcmp(search_efivar->var.VendorGuid, + del_var->VendorGuid)) { + found = 1; + break; + } + } + if (!found) { + spin_unlock(&efivars->lock); + return -EINVAL; + } + /* force the Attributes/DataSize to 0 to ensure deletion */ + del_var->Attributes = 0; + del_var->DataSize = 0; + + status = efivars->ops->set_variable(del_var->VariableName, + &del_var->VendorGuid, + del_var->Attributes, + del_var->DataSize, + del_var->Data); + + if (status != EFI_SUCCESS) { + printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", + status); + spin_unlock(&efivars->lock); + return -EIO; + } + list_del(&search_efivar->list); + /* We need to release this lock before unregistering. */ + spin_unlock(&efivars->lock); + efivar_unregister(search_efivar); + + /* It's dead Jim.... */ + return count; +} + +/* + * 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.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); + +static struct attribute *efi_subsys_attrs[] = { + &efi_attr_systab.attr, + NULL, /* maybe more in the future? */ +}; + +static struct attribute_group efi_subsys_attr_group = { + .attrs = efi_subsys_attrs, +}; + +static struct kobject *efi_kobj; + +/* + * efivar_create_sysfs_entry() + * Requires: + * variable_name_size = number of bytes required to hold + * variable_name (not counting the NULL + * character at the end. + * efivars->lock is not held on entry or exit. + * Returns 1 on failure, 0 on success + */ +static int +efivar_create_sysfs_entry(struct efivars *efivars, + unsigned long variable_name_size, + efi_char16_t *variable_name, + efi_guid_t *vendor_guid) +{ + int i, short_name_size = variable_name_size / sizeof(efi_char16_t) + 38; + char *short_name; + struct efivar_entry *new_efivar; + + short_name = kzalloc(short_name_size + 1, GFP_KERNEL); + new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); + + if (!short_name || !new_efivar) { + kfree(short_name); + kfree(new_efivar); + return 1; + } + + new_efivar->efivars = efivars; + memcpy(new_efivar->var.VariableName, variable_name, + variable_name_size); + memcpy(&(new_efivar->var.VendorGuid), vendor_guid, sizeof(efi_guid_t)); + + /* 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_unparse(vendor_guid, short_name + strlen(short_name)); + + new_efivar->kobj.kset = efivars->kset; + i = kobject_init_and_add(&new_efivar->kobj, &efivar_ktype, NULL, + "%s", short_name); + if (i) { + kfree(short_name); + kfree(new_efivar); + return 1; + } + + kobject_uevent(&new_efivar->kobj, KOBJ_ADD); + kfree(short_name); + short_name = NULL; + + spin_lock(&efivars->lock); + list_add(&new_efivar->list, &efivars->list); + spin_unlock(&efivars->lock); + + return 0; +} + +static int +create_efivars_bin_attributes(struct efivars *efivars) +{ + 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; + attr->private = efivars; + 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; + attr->private = efivars; + 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; +} + +void unregister_efivars(struct efivars *efivars) +{ + struct efivar_entry *entry, *n; + + list_for_each_entry_safe(entry, n, &efivars->list, list) { + spin_lock(&efivars->lock); + list_del(&entry->list); + spin_unlock(&efivars->lock); + efivar_unregister(entry); + } + 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); +} +EXPORT_SYMBOL_GPL(unregister_efivars); + +int register_efivars(struct efivars *efivars, + const struct efivar_operations *ops, + struct kobject *parent_kobj) +{ + efi_status_t status = EFI_NOT_FOUND; + efi_guid_t vendor_guid; + efi_char16_t *variable_name; + unsigned long variable_name_size = 1024; + int error = 0; + + variable_name = kzalloc(variable_name_size, GFP_KERNEL); + if (!variable_name) { + printk(KERN_ERR "efivars: Memory allocation failed.\n"); + return -ENOMEM; + } + + spin_lock_init(&efivars->lock); + INIT_LIST_HEAD(&efivars->list); + efivars->ops = ops; + + efivars->kset = kset_create_and_add("vars", NULL, parent_kobj); + if (!efivars->kset) { + printk(KERN_ERR "efivars: Subsystem registration failed.\n"); + error = -ENOMEM; + goto out; + } + + /* + * 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: + efivar_create_sysfs_entry(efivars, + variable_name_size, + variable_name, + &vendor_guid); + 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); + + error = create_efivars_bin_attributes(efivars); + if (error) + unregister_efivars(efivars); + +out: + kfree(variable_name); + + return error; +} +EXPORT_SYMBOL_GPL(register_efivars); + +static struct efivars __efivars; +static struct efivar_operations ops; + +/* + * For now we register the efi subsystem with the firmware subsystem + * and the vars subsystem with the efi subsystem. In the future, it + * might make sense to split off the efi subsystem into its own + * driver, but for now only efivars will register with it, so just + * include it here. + */ + +static int __init +efivars_init(void) +{ + int error = 0; + + printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION, + EFIVARS_DATE); + + if (!efi_enabled) + return 0; + + /* For now we'll register the efi directory at /sys/firmware/efi */ + efi_kobj = kobject_create_and_add("efi", firmware_kobj); + if (!efi_kobj) { + printk(KERN_ERR "efivars: Firmware registration failed.\n"); + return -ENOMEM; + } + + ops.get_variable = efi.get_variable; + ops.set_variable = efi.set_variable; + ops.get_next_variable = efi.get_next_variable; + error = register_efivars(&__efivars, &ops, efi_kobj); + if (error) + goto err_put; + + /* Don't forget the systab entry */ + error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); + if (error) { + printk(KERN_ERR + "efivars: Sysfs attribute export failed with error %d.\n", + error); + goto err_unregister; + } + + return 0; + +err_unregister: + unregister_efivars(&__efivars); +err_put: + kobject_put(efi_kobj); + return error; +} + +static void __exit +efivars_exit(void) +{ + if (efi_enabled) { + unregister_efivars(&__efivars); + kobject_put(efi_kobj); + } +} + +module_init(efivars_init); +module_exit(efivars_exit); + diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig new file mode 100644 index 00000000..2f21b0bf --- /dev/null +++ b/drivers/firmware/google/Kconfig @@ -0,0 +1,32 @@ +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 + select 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 00000000..54a294e3 --- /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 00000000..fa7f0b3e --- /dev/null +++ b/drivers/firmware/google/gsmi.c @@ -0,0 +1,940 @@ +/* + * 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> + +#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; +} + +/* Return the number of unicode characters in data */ +static size_t +utf16_strlen(efi_char16_t *data, unsigned long maxlength) +{ + unsigned long length = 0; + + while (*data++ != 0 && length < maxlength) + length++; + return length; +} + +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 = utf16_strlen(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(*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 (utf16_strlen(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 = utf16_strlen(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, + unsigned long 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 = utf16_strlen(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 = strict_strtoul(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 __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_simple("gsmi", -1, NULL, 0); + 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); + + /* SMI callbacks require 32bit addresses */ + gsmi_dev.pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + gsmi_dev.pdev->dev.dma_mask = + &gsmi_dev.pdev->dev.coherent_dma_mask; + ret = -ENOMEM; + gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev, + GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0); + if (!gsmi_dev.dma_pool) + 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; + } + + printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n"); + + /* 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_err; + } + + if (register_efivars(&efivars, &efivar_ops, gsmi_kobj)) { + printk(KERN_INFO "gsmi: Failed to register efivars\n"); + goto out_err; + } + + register_reboot_notifier(&gsmi_reboot_notifier); + register_die_notifier(&gsmi_die_notifier); + atomic_notifier_chain_register(&panic_notifier_list, + &gsmi_panic_notifier); + + return 0; + + 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); + unregister_efivars(&efivars); + + 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 00000000..2a90ba61 --- /dev/null +++ b/drivers/firmware/google/memconsole.c @@ -0,0 +1,166 @@ +/* + * 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 <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 char *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) +{ + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); +} + +static struct bin_attribute memconsole_bin_attr = { + .attr = {.name = "log", .mode = 0444}, + .read = memconsole_read, +}; + + +static void found_v1_header(struct biosmemcon_ebda *hdr) +{ + printk(KERN_INFO "BIOS console v1 EBDA structure found at %p\n", hdr); + printk(KERN_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 = phys_to_virt(hdr->v1.buffer_addr); +} + +static void found_v2_header(struct biosmemcon_ebda *hdr) +{ + printk(KERN_INFO "BIOS console v2 EBDA structure found at %p\n", hdr); + printk(KERN_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 = phys_to_virt(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 found_memconsole(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + printk(KERN_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; + } + } + + printk(KERN_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) +{ + int ret; + + if (!dmi_check_system(memconsole_dmi_table)) + return -ENODEV; + + if (!found_memconsole()) + return -ENODEV; + + memconsole_bin_attr.size = memconsole_length; + + ret = sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); + + return ret; +} + +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 00000000..27636430 --- /dev/null +++ b/drivers/firmware/iscsi_ibft.c @@ -0,0 +1,813 @@ +/* + * 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; + +static const char 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 mode_t ibft_check_nic_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_nic *nic = entry->nic; + mode_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 (memcmp(nic->ip_addr, nulls, sizeof(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 (memcmp(nic->gateway, nulls, sizeof(nic->gateway))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_PRIMARY_DNS: + if (memcmp(nic->primary_dns, nulls, + sizeof(nic->primary_dns))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_SECONDARY_DNS: + if (memcmp(nic->secondary_dns, nulls, + sizeof(nic->secondary_dns))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_ETH_DHCP: + if (memcmp(nic->dhcp, nulls, sizeof(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 mode_t __init ibft_check_tgt_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_tgt *tgt = entry->tgt; + mode_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 mode_t __init ibft_check_initiator_for(void *data, int type) +{ + struct ibft_kobject *entry = data; + struct ibft_initiator *init = entry->initiator; + mode_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 (memcmp(init->isns_server, nulls, + sizeof(init->isns_server))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_SLP_SERVER: + if (memcmp(init->slp_server, nulls, + sizeof(init->slp_server))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_PRI_RADIUS_SERVER: + if (memcmp(init->pri_radius_server, nulls, + sizeof(init->pri_radius_server))) + rc = S_IRUGO; + break; + case ISCSI_BOOT_INI_SEC_RADIUS_SERVER: + if (memcmp(init->sec_radius_server, nulls, + sizeof(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; +} + +/* + * 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); + 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); + 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); + 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" }, +}; + +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 00000000..4da4eb9a --- /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) + 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 00000000..adc07102 --- /dev/null +++ b/drivers/firmware/memmap.c @@ -0,0 +1,250 @@ +/* + * 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> + +/* + * 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 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, +}; + +static struct kobj_type memmap_ktype = { + .sysfs_ops = &memmap_attr_ops, + .default_attrs = def_attrs, +}; + +/* + * Registration functions ------------------------------------------------------ + */ + +/* + * Firmware memory map entries. No locking is needed because the + * firmware_map_add() and firmware_map_add_early() functions are called + * in firmware initialisation code in one single thread of execution. + */ +static LIST_HEAD(map_entries); + +/** + * 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 (inclusive). + * @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; + entry->type = type; + INIT_LIST_HEAD(&entry->list); + kobject_init(&entry->kobj, &memmap_ktype); + + list_add_tail(&entry->list, &map_entries); + + return 0; +} + +/* + * 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 (!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; +} + +/** + * 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 (inclusive). + * @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 = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC); + if (!entry) + return -ENOMEM; + + 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 (inclusive). + * @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 = alloc_bootmem(sizeof(struct firmware_map_entry)); + if (WARN_ON(!entry)) + return -ENOMEM; + + return firmware_map_add_entry(start, end, type, entry); +} + +/* + * 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); +} + +#define to_memmap_attr(_attr) container_of(_attr, struct memmap_attribute, attr) +#define to_memmap_entry(obj) container_of(obj, struct firmware_map_entry, kobj) + +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 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(memmap_init); + diff --git a/drivers/firmware/pcdp.c b/drivers/firmware/pcdp.c new file mode 100644 index 00000000..51e0e2d8 --- /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_8250.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_early_serial8250_console(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 = 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: + iounmap(pcdp); + return rc; +} diff --git a/drivers/firmware/pcdp.h b/drivers/firmware/pcdp.h new file mode 100644 index 00000000..e5530608 --- /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/sigma.c b/drivers/firmware/sigma.c new file mode 100644 index 00000000..1eedb6f7 --- /dev/null +++ b/drivers/firmware/sigma.c @@ -0,0 +1,153 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/sigma.h> + +static size_t sigma_action_size(struct sigma_action *sa) +{ + size_t payload = 0; + + switch (sa->instr) { + case SIGMA_ACTION_WRITEXBYTES: + case SIGMA_ACTION_WRITESINGLE: + case SIGMA_ACTION_WRITESAFELOAD: + payload = sigma_action_len(sa); + break; + default: + break; + } + + payload = ALIGN(payload, 2); + + return payload + sizeof(struct sigma_action); +} + +/* + * Returns a negative error value in case of an error, 0 if processing of + * the firmware should be stopped after this action, 1 otherwise. + */ +static int +process_sigma_action(struct i2c_client *client, struct sigma_action *sa) +{ + size_t len = sigma_action_len(sa); + int ret; + + pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__, + sa->instr, sa->addr, len); + + switch (sa->instr) { + case SIGMA_ACTION_WRITEXBYTES: + case SIGMA_ACTION_WRITESINGLE: + case SIGMA_ACTION_WRITESAFELOAD: + ret = i2c_master_send(client, (void *)&sa->addr, len); + if (ret < 0) + return -EINVAL; + break; + case SIGMA_ACTION_DELAY: + udelay(len); + len = 0; + break; + case SIGMA_ACTION_END: + return 0; + default: + return -EINVAL; + } + + return 1; +} + +static int +process_sigma_actions(struct i2c_client *client, struct sigma_firmware *ssfw) +{ + struct sigma_action *sa; + size_t size; + int ret; + + while (ssfw->pos + sizeof(*sa) <= ssfw->fw->size) { + sa = (struct sigma_action *)(ssfw->fw->data + ssfw->pos); + + size = sigma_action_size(sa); + ssfw->pos += size; + if (ssfw->pos > ssfw->fw->size || size == 0) + break; + + ret = process_sigma_action(client, sa); + + pr_debug("%s: action returned %i\n", __func__, ret); + + if (ret <= 0) + return ret; + } + + if (ssfw->pos != ssfw->fw->size) + return -EINVAL; + + return 0; +} + +int process_sigma_firmware(struct i2c_client *client, const char *name) +{ + int ret; + struct sigma_firmware_header *ssfw_head; + struct sigma_firmware ssfw; + const struct firmware *fw; + u32 crc; + + pr_debug("%s: loading firmware %s\n", __func__, name); + + /* first load the blob */ + ret = request_firmware(&fw, name, &client->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); + return ret; + } + ssfw.fw = fw; + + /* then verify the header */ + ret = -EINVAL; + + /* + * Reject too small or unreasonable large files. The upper limit has been + * chosen a bit arbitrarily, but it should be enough for all practical + * purposes and having the limit makes it easier to avoid integer + * overflows later in the loading process. + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) + goto done; + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) + goto done; + + crc = crc32(0, fw->data + sizeof(*ssfw_head), + fw->size - sizeof(*ssfw_head)); + pr_debug("%s: crc=%x\n", __func__, crc); + if (crc != le32_to_cpu(ssfw_head->crc)) + goto done; + + ssfw.pos = sizeof(*ssfw_head); + + /* finally process all of the actions */ + ret = process_sigma_actions(client, &ssfw); + + done: + release_firmware(fw); + + pr_debug("%s: loaded %s\n", __func__, name); + + return ret; +} +EXPORT_SYMBOL(process_sigma_firmware); + +MODULE_LICENSE("GPL"); |