aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
authorroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
committerroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
commit849369d6c66d3054688672f97d31fceb8e8230fb (patch)
tree6135abc790ca67dedbe07c39806591e70eda81ce /drivers/firmware
downloadlinux-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/Kconfig162
-rw-r--r--drivers/firmware/Makefile17
-rw-r--r--drivers/firmware/dcdbas.c659
-rw-r--r--drivers/firmware/dcdbas.h107
-rw-r--r--drivers/firmware/dell_rbu.c745
-rw-r--r--drivers/firmware/dmi-id.c245
-rw-r--r--drivers/firmware/dmi-sysfs.c696
-rw-r--r--drivers/firmware/dmi_scan.c751
-rw-r--r--drivers/firmware/edd.c801
-rw-r--r--drivers/firmware/efivars.c1040
-rw-r--r--drivers/firmware/google/Kconfig32
-rw-r--r--drivers/firmware/google/Makefile3
-rw-r--r--drivers/firmware/google/gsmi.c940
-rw-r--r--drivers/firmware/google/memconsole.c166
-rw-r--r--drivers/firmware/iscsi_ibft.c813
-rw-r--r--drivers/firmware/iscsi_ibft_find.c112
-rw-r--r--drivers/firmware/memmap.c250
-rw-r--r--drivers/firmware/pcdp.c136
-rw-r--r--drivers/firmware/pcdp.h111
-rw-r--r--drivers/firmware/sigma.c153
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, &current->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(&param.guid, vendor, sizeof(param.guid));
+
+ /* variable name, already in UTF-16 */
+ memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
+ memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
+
+ /* data pointer */
+ memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+
+ /* parameter buffer */
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+ memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NVRAM_VAR);
+ if (rc < 0) {
+ printk(KERN_ERR "gsmi: Get Variable failed\n");
+ ret = EFI_LOAD_ERROR;
+ } else if (rc == 1) {
+ /* variable was not found */
+ ret = EFI_NOT_FOUND;
+ } else {
+ /* Get the arguments back */
+ memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
+
+ /* The size reported is the min of all of our buffers */
+ *data_size = min(*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(&param.guid, vendor, sizeof(param.guid));
+
+ /* variable name, already in UTF-16 */
+ memcpy(gsmi_dev.name_buf->start, name, *name_size);
+
+ /* parameter buffer */
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+ memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NEXT_VAR);
+ if (rc < 0) {
+ printk(KERN_ERR "gsmi: Get Next Variable Name failed\n");
+ ret = EFI_LOAD_ERROR;
+ } else if (rc == 1) {
+ /* variable not found -- end of list */
+ ret = EFI_NOT_FOUND;
+ } else {
+ /* copy variable data back to return buffer */
+ memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
+
+ /* Copy the name back */
+ memcpy(name, gsmi_dev.name_buf->start, GSMI_BUF_SIZE);
+ *name_size = utf16_strlen(name, GSMI_BUF_SIZE / 2) * 2;
+
+ /* copy guid to return buffer */
+ memcpy(vendor, &param.guid, sizeof(param.guid));
+ ret = EFI_SUCCESS;
+ }
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ return ret;
+}
+
+static efi_status_t gsmi_set_variable(efi_char16_t *name,
+ efi_guid_t *vendor,
+ 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(&param.guid, vendor, sizeof(param.guid));
+
+ /* variable name, already in UTF-16 */
+ memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
+ memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
+
+ /* data pointer */
+ memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+ memcpy(gsmi_dev.data_buf->start, data, data_size);
+
+ /* parameter buffer */
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+ memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_NVRAM_VAR);
+ if (rc < 0) {
+ printk(KERN_ERR "gsmi: Set Variable failed\n");
+ ret = EFI_INVALID_PARAMETER;
+ }
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ return ret;
+}
+
+static const struct efivar_operations efivar_ops = {
+ .get_variable = gsmi_get_variable,
+ .set_variable = gsmi_set_variable,
+ .get_next_variable = gsmi_get_next_variable,
+};
+
+static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t count)
+{
+ struct gsmi_set_eventlog_param param = {
+ .data_ptr = gsmi_dev.data_buf->address,
+ };
+ int rc = 0;
+ unsigned long flags;
+
+ /* Pull the type out */
+ if (count < sizeof(u32))
+ return -EINVAL;
+ param.type = *(u32 *)buf;
+ count -= sizeof(u32);
+ buf += sizeof(u32);
+
+ /* The remaining buffer is the data payload */
+ if (count > gsmi_dev.data_buf->length)
+ return -EINVAL;
+ param.data_len = count - sizeof(u32);
+
+ spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+ /* data pointer */
+ memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+ memcpy(gsmi_dev.data_buf->start, buf, param.data_len);
+
+ /* parameter buffer */
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+ memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
+ if (rc < 0)
+ printk(KERN_ERR "gsmi: Set Event Log failed\n");
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ return rc;
+
+}
+
+static struct bin_attribute eventlog_bin_attr = {
+ .attr = {.name = "append_to_eventlog", .mode = 0200},
+ .write = eventlog_write,
+};
+
+static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+ unsigned long flags;
+ unsigned long val;
+ struct {
+ u32 percentage;
+ u32 data_type;
+ } param;
+
+ rc = 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, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_EVENT_LOG);
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ if (rc)
+ return rc;
+ return count;
+}
+
+static struct kobj_attribute gsmi_clear_eventlog_attr = {
+ .attr = {.name = "clear_eventlog", .mode = 0200},
+ .store = gsmi_clear_eventlog_store,
+};
+
+static ssize_t gsmi_clear_config_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+ /* clear parameter buffer */
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_CONFIG);
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ if (rc)
+ return rc;
+ return count;
+}
+
+static struct kobj_attribute gsmi_clear_config_attr = {
+ .attr = {.name = "clear_config", .mode = 0200},
+ .store = gsmi_clear_config_store,
+};
+
+static const struct attribute *gsmi_attrs[] = {
+ &gsmi_clear_config_attr.attr,
+ &gsmi_clear_eventlog_attr.attr,
+ NULL,
+};
+
+static int gsmi_shutdown_reason(int reason)
+{
+ struct gsmi_log_entry_type_1 entry = {
+ .type = GSMI_LOG_ENTRY_TYPE_KERNEL,
+ .instance = reason,
+ };
+ struct gsmi_set_eventlog_param param = {
+ .data_len = sizeof(entry),
+ .type = 1,
+ };
+ static int saved_reason;
+ int rc = 0;
+ unsigned long flags;
+
+ /* avoid duplicate entries in the log */
+ if (saved_reason & (1 << reason))
+ return 0;
+
+ spin_lock_irqsave(&gsmi_dev.lock, flags);
+
+ saved_reason |= (1 << reason);
+
+ /* data pointer */
+ memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
+ memcpy(gsmi_dev.data_buf->start, &entry, sizeof(entry));
+
+ /* parameter buffer */
+ param.data_ptr = gsmi_dev.data_buf->address;
+ memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
+ memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
+
+ rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
+
+ spin_unlock_irqrestore(&gsmi_dev.lock, flags);
+
+ if (rc < 0)
+ printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n");
+ else
+ printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n",
+ reason);
+
+ return rc;
+}
+
+static int gsmi_reboot_callback(struct notifier_block *nb,
+ unsigned long reason, void *arg)
+{
+ gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_reboot_notifier = {
+ .notifier_call = gsmi_reboot_callback
+};
+
+static int gsmi_die_callback(struct notifier_block *nb,
+ unsigned long reason, void *arg)
+{
+ if (reason == DIE_OOPS)
+ gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_die_notifier = {
+ .notifier_call = gsmi_die_callback
+};
+
+static int gsmi_panic_callback(struct notifier_block *nb,
+ unsigned long reason, void *arg)
+{
+ gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block gsmi_panic_notifier = {
+ .notifier_call = gsmi_panic_callback,
+};
+
+/*
+ * This hash function was blatantly copied from include/linux/hash.h.
+ * It is used by this driver to obfuscate a board name that requires a
+ * quirk within this driver.
+ *
+ * Please do not remove this copy of the function as any changes to the
+ * global utility hash_64() function would break this driver's ability
+ * to identify a board and provide the appropriate quirk -- mikew@google.com
+ */
+static u64 __init local_hash_64(u64 val, unsigned bits)
+{
+ u64 hash = val;
+
+ /* Sigh, gcc can't optimise this alone like it does for 32 bits. */
+ u64 n = hash;
+ n <<= 18;
+ hash -= n;
+ n <<= 33;
+ hash -= n;
+ n <<= 3;
+ hash += n;
+ n <<= 3;
+ hash -= n;
+ n <<= 4;
+ hash += n;
+ n <<= 2;
+ hash += n;
+
+ /* High bits are more random, so use them. */
+ return hash >> (64 - bits);
+}
+
+static u32 __init hash_oem_table_id(char s[8])
+{
+ u64 input;
+ memcpy(&input, s, 8);
+ return local_hash_64(input, 32);
+}
+
+static 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");