Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

138
drivers/firmware/Kconfig Normal file
View file

@ -0,0 +1,138 @@
#
# For a description of the syntax of this configuration file,
# see Documentation/kbuild/kconfig-language.txt.
#
menu "Firmware Drivers"
config EDD
tristate "BIOS Enhanced Disk Drive calls determine boot disk"
depends on X86
help
Say Y or M here if you want to enable BIOS Enhanced Disk Drive
Services real mode BIOS calls to determine which disk
BIOS tries boot from. This information is then exported via sysfs.
This option is experimental and is known to fail to boot on some
obscure configurations. Most disk controller BIOS vendors do
not yet implement this feature.
config EDD_OFF
bool "Sets default behavior for EDD detection to off"
depends on EDD
default n
help
Say Y if you want EDD disabled by default, even though it is compiled into the
kernel. Say N if you want EDD enabled by default. EDD can be dynamically set
using the kernel parameter 'edd={on|skipmbr|off}'.
config FIRMWARE_MEMMAP
bool "Add firmware-provided memory map to sysfs" if EXPERT
default X86
help
Add the firmware-provided (unmodified) memory map to /sys/firmware/memmap.
That memory map is used for example by kexec to set up parameter area
for the next kernel, but can also be used for debugging purposes.
See also Documentation/ABI/testing/sysfs-firmware-memmap.
config EFI_PCDP
bool "Console device selection via EFI PCDP or HCDP table"
depends on ACPI && EFI && IA64
default y if IA64
help
If your firmware supplies the PCDP table, and you want to
automatically use the primary console device it describes
as the Linux console, say Y here.
If your firmware supplies the HCDP table, and you want to
use the first serial port it describes as the Linux console,
say Y here. If your EFI ConOut path contains only a UART
device, it will become the console automatically. Otherwise,
you must specify the "console=hcdp" kernel boot argument.
Neither the PCDP nor the HCDP affects naming of serial devices,
so a serial console may be /dev/ttyS0, /dev/ttyS1, etc, depending
on how the driver discovers devices.
You must also enable the appropriate drivers (serial, VGA, etc.)
See DIG64_HCDPv20_042804.pdf available from
<http://www.dig64.org/specifications/>
config DELL_RBU
tristate "BIOS update support for DELL systems via sysfs"
depends on X86
select FW_LOADER
select FW_LOADER_USER_HELPER
help
Say m if you want to have the option of updating the BIOS for your
DELL system. Note you need a Dell OpenManage or Dell Update package (DUP)
supporting application to communicate with the BIOS regarding the new
image for the image update to take effect.
See <file:Documentation/dell_rbu.txt> for more details on the driver.
config DCDBAS
tristate "Dell Systems Management Base Driver"
depends on X86
help
The Dell Systems Management Base Driver provides a sysfs interface
for systems management software to perform System Management
Interrupts (SMIs) and Host Control Actions (system power cycle or
power off after OS shutdown) on certain Dell systems.
See <file:Documentation/dcdbas.txt> for more details on the driver
and the Dell systems on which Dell systems management software makes
use of this driver.
Say Y or M here to enable the driver for use by Dell systems
management software such as Dell OpenManage.
config DMIID
bool "Export DMI identification via sysfs to userspace"
depends on DMI
default y
help
Say Y here if you want to query SMBIOS/DMI system identification
information from userspace through /sys/class/dmi/id/ or if you want
DMI-based module auto-loading.
config DMI_SYSFS
tristate "DMI table support in sysfs"
depends on SYSFS && DMI
default n
help
Say Y or M here to enable the exporting of the raw DMI table
data via sysfs. This is useful for consuming the data without
requiring any access to /dev/mem at all. Tables are found
under /sys/firmware/dmi when this option is enabled and
loaded.
config DMI_SCAN_MACHINE_NON_EFI_FALLBACK
bool
config ISCSI_IBFT_FIND
bool "iSCSI Boot Firmware Table Attributes"
depends on X86 && ACPI
default n
help
This option enables the kernel to find the region of memory
in which the ISCSI Boot Firmware Table (iBFT) resides. This
is necessary for iSCSI Boot Firmware Table Attributes module to work
properly.
config ISCSI_IBFT
tristate "iSCSI Boot Firmware Table Attributes module"
select ISCSI_BOOT_SYSFS
depends on ISCSI_IBFT_FIND && SCSI && SCSI_LOWLEVEL
default n
help
This option enables support for detection and exposing of iSCSI
Boot Firmware Table (iBFT) via sysfs to userspace. If you wish to
detect iSCSI boot parameters dynamically during system boot, say Y.
Otherwise, say N.
source "drivers/firmware/google/Kconfig"
source "drivers/firmware/efi/Kconfig"
endmenu

17
drivers/firmware/Makefile Normal file
View file

@ -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_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_GOOGLE_FIRMWARE) += google/
obj-$(CONFIG_EFI) += efi/
obj-$(CONFIG_UEFI_CPER) += efi/

650
drivers/firmware/dcdbas.c Normal file
View file

@ -0,0 +1,650 @@
/*
* 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,
.bin_attrs = dcdbas_bin_attrs,
};
static int dcdbas_probe(struct platform_device *dev)
{
int error;
host_control_action = HC_ACTION_NONE;
host_control_smi_type = HC_SMITYPE_NONE;
dcdbas_pdev = dev;
/*
* BIOS SMI calls require buffer addresses be in 32-bit address space.
* This is done by setting the DMA mask below.
*/
error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32));
if (error)
return error;
error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group);
if (error)
return error;
register_reboot_notifier(&dcdbas_reboot_nb);
dev_info(&dev->dev, "%s (version %s)\n",
DRIVER_DESCRIPTION, DRIVER_VERSION);
return 0;
}
static int dcdbas_remove(struct platform_device *dev)
{
unregister_reboot_notifier(&dcdbas_reboot_nb);
sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
return 0;
}
static struct platform_driver dcdbas_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.probe = dcdbas_probe,
.remove = dcdbas_remove,
};
static const struct platform_device_info dcdbas_dev_info __initconst = {
.name = DRIVER_NAME,
.id = -1,
.dma_mask = DMA_BIT_MASK(32),
};
static struct platform_device *dcdbas_pdev_reg;
/**
* dcdbas_init: initialize driver
*/
static int __init dcdbas_init(void)
{
int error;
error = platform_driver_register(&dcdbas_driver);
if (error)
return error;
dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info);
if (IS_ERR(dcdbas_pdev_reg)) {
error = PTR_ERR(dcdbas_pdev_reg);
goto err_unregister_driver;
}
return 0;
err_unregister_driver:
platform_driver_unregister(&dcdbas_driver);
return error;
}
/**
* dcdbas_exit: perform driver cleanup
*/
static void __exit dcdbas_exit(void)
{
/*
* make sure functions that use dcdbas_pdev are called
* before platform_device_unregister
*/
unregister_reboot_notifier(&dcdbas_reboot_nb);
/*
* We have to free the buffer here instead of dcdbas_remove
* because only in module exit function we can be sure that
* all sysfs attributes belonging to this module have been
* released.
*/
if (dcdbas_pdev)
smi_data_buf_free();
platform_device_unregister(dcdbas_pdev_reg);
platform_driver_unregister(&dcdbas_driver);
}
module_init(dcdbas_init);
module_exit(dcdbas_exit);
MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
MODULE_VERSION(DRIVER_VERSION);
MODULE_AUTHOR("Dell Inc.");
MODULE_LICENSE("GPL");
/* Any System or BIOS claiming to be by Dell */
MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");

107
drivers/firmware/dcdbas.h Normal file
View file

@ -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_ */

745
drivers/firmware/dell_rbu.c Normal file
View file

@ -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
*/

245
drivers/firmware/dmi-id.c Normal file
View file

@ -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);

View file

@ -0,0 +1,697 @@
/*
* dmi-sysfs.c
*
* This module exports the DMI tables read-only to userspace through the
* sysfs file system.
*
* Data is currently found below
* /sys/firmware/dmi/...
*
* DMI attributes are presented in attribute files with names
* formatted using %d-%d, so that the first integer indicates the
* structure type (0-255), and the second field is the instance of that
* entry.
*
* Copyright 2011 Google, Inc.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/dmi.h>
#include <linux/capability.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/io.h>
#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
the top entry type is only 8 bits */
struct dmi_sysfs_entry {
struct dmi_header dh;
struct kobject kobj;
int instance;
int position;
struct list_head list;
struct kobject *child;
};
/*
* Global list of dmi_sysfs_entry. Even though this should only be
* manipulated at setup and teardown, the lazy nature of the kobject
* system means we get lazy removes.
*/
static LIST_HEAD(entry_list);
static DEFINE_SPINLOCK(entry_list_lock);
/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
struct dmi_sysfs_attribute {
struct attribute attr;
ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
};
#define DMI_SYSFS_ATTR(_entry, _name) \
struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
.attr = {.name = __stringify(_name), .mode = 0400}, \
.show = dmi_sysfs_##_entry##_##_name, \
}
/*
* dmi_sysfs_mapped_attribute - Attribute where we require the entry be
* mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops.
*/
struct dmi_sysfs_mapped_attribute {
struct attribute attr;
ssize_t (*show)(struct dmi_sysfs_entry *entry,
const struct dmi_header *dh,
char *buf);
};
#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
.attr = {.name = __stringify(_name), .mode = 0400}, \
.show = dmi_sysfs_##_entry##_##_name, \
}
/*************************************************
* Generic DMI entry support.
*************************************************/
static void dmi_entry_free(struct kobject *kobj)
{
kfree(kobj);
}
static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
{
return container_of(kobj, struct dmi_sysfs_entry, kobj);
}
static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
{
return container_of(attr, struct dmi_sysfs_attribute, attr);
}
static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
struct attribute *_attr, char *buf)
{
struct dmi_sysfs_entry *entry = to_entry(kobj);
struct dmi_sysfs_attribute *attr = to_attr(_attr);
/* DMI stuff is only ever admin visible */
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
return attr->show(entry, buf);
}
static const struct sysfs_ops dmi_sysfs_attr_ops = {
.show = dmi_sysfs_attr_show,
};
typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
const struct dmi_header *dh, void *);
struct find_dmi_data {
struct dmi_sysfs_entry *entry;
dmi_callback callback;
void *private;
int instance_countdown;
ssize_t ret;
};
static void find_dmi_entry_helper(const struct dmi_header *dh,
void *_data)
{
struct find_dmi_data *data = _data;
struct dmi_sysfs_entry *entry = data->entry;
/* Is this the entry we want? */
if (dh->type != entry->dh.type)
return;
if (data->instance_countdown != 0) {
/* try the next instance? */
data->instance_countdown--;
return;
}
/*
* Don't ever revisit the instance. Short circuit later
* instances by letting the instance_countdown run negative
*/
data->instance_countdown--;
/* Found the entry */
data->ret = data->callback(entry, dh, data->private);
}
/* State for passing the read parameters through dmi_find_entry() */
struct dmi_read_state {
char *buf;
loff_t pos;
size_t count;
};
static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
dmi_callback callback, void *private)
{
struct find_dmi_data data = {
.entry = entry,
.callback = callback,
.private = private,
.instance_countdown = entry->instance,
.ret = -EIO, /* To signal the entry disappeared */
};
int ret;
ret = dmi_walk(find_dmi_entry_helper, &data);
/* This shouldn't happen, but just in case. */
if (ret)
return -EINVAL;
return data.ret;
}
/*
* Calculate and return the byte length of the dmi entry identified by
* dh. This includes both the formatted portion as well as the
* unformatted string space, including the two trailing nul characters.
*/
static size_t dmi_entry_length(const struct dmi_header *dh)
{
const char *p = (const char *)dh;
p += dh->length;
while (p[0] || p[1])
p++;
return 2 + p - (const char *)dh;
}
/*************************************************
* Support bits for specialized DMI entry support
*************************************************/
struct dmi_entry_attr_show_data {
struct attribute *attr;
char *buf;
};
static ssize_t dmi_entry_attr_show_helper(struct dmi_sysfs_entry *entry,
const struct dmi_header *dh,
void *_data)
{
struct dmi_entry_attr_show_data *data = _data;
struct dmi_sysfs_mapped_attribute *attr;
attr = container_of(data->attr,
struct dmi_sysfs_mapped_attribute, attr);
return attr->show(entry, dh, data->buf);
}
static ssize_t dmi_entry_attr_show(struct kobject *kobj,
struct attribute *attr,
char *buf)
{
struct dmi_entry_attr_show_data data = {
.attr = attr,
.buf = buf,
};
/* Find the entry according to our parent and call the
* normalized show method hanging off of the attribute */
return find_dmi_entry(to_entry(kobj->parent),
dmi_entry_attr_show_helper, &data);
}
static const struct sysfs_ops dmi_sysfs_specialize_attr_ops = {
.show = dmi_entry_attr_show,
};
/*************************************************
* Specialized DMI entry support.
*************************************************/
/*** Type 15 - System Event Table ***/
#define DMI_SEL_ACCESS_METHOD_IO8 0x00
#define DMI_SEL_ACCESS_METHOD_IO2x8 0x01
#define DMI_SEL_ACCESS_METHOD_IO16 0x02
#define DMI_SEL_ACCESS_METHOD_PHYS32 0x03
#define DMI_SEL_ACCESS_METHOD_GPNV 0x04
struct dmi_system_event_log {
struct dmi_header header;
u16 area_length;
u16 header_start_offset;
u16 data_start_offset;
u8 access_method;
u8 status;
u32 change_token;
union {
struct {
u16 index_addr;
u16 data_addr;
} io;
u32 phys_addr32;
u16 gpnv_handle;
u32 access_method_address;
};
u8 header_format;
u8 type_descriptors_supported_count;
u8 per_log_type_descriptor_length;
u8 supported_log_type_descriptos[0];
} __packed;
#define DMI_SYSFS_SEL_FIELD(_field) \
static ssize_t dmi_sysfs_sel_##_field(struct dmi_sysfs_entry *entry, \
const struct dmi_header *dh, \
char *buf) \
{ \
struct dmi_system_event_log sel; \
if (sizeof(sel) > dmi_entry_length(dh)) \
return -EIO; \
memcpy(&sel, dh, sizeof(sel)); \
return sprintf(buf, "%u\n", sel._field); \
} \
static DMI_SYSFS_MAPPED_ATTR(sel, _field)
DMI_SYSFS_SEL_FIELD(area_length);
DMI_SYSFS_SEL_FIELD(header_start_offset);
DMI_SYSFS_SEL_FIELD(data_start_offset);
DMI_SYSFS_SEL_FIELD(access_method);
DMI_SYSFS_SEL_FIELD(status);
DMI_SYSFS_SEL_FIELD(change_token);
DMI_SYSFS_SEL_FIELD(access_method_address);
DMI_SYSFS_SEL_FIELD(header_format);
DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count);
DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length);
static struct attribute *dmi_sysfs_sel_attrs[] = {
&dmi_sysfs_attr_sel_area_length.attr,
&dmi_sysfs_attr_sel_header_start_offset.attr,
&dmi_sysfs_attr_sel_data_start_offset.attr,
&dmi_sysfs_attr_sel_access_method.attr,
&dmi_sysfs_attr_sel_status.attr,
&dmi_sysfs_attr_sel_change_token.attr,
&dmi_sysfs_attr_sel_access_method_address.attr,
&dmi_sysfs_attr_sel_header_format.attr,
&dmi_sysfs_attr_sel_type_descriptors_supported_count.attr,
&dmi_sysfs_attr_sel_per_log_type_descriptor_length.attr,
NULL,
};
static struct kobj_type dmi_system_event_log_ktype = {
.release = dmi_entry_free,
.sysfs_ops = &dmi_sysfs_specialize_attr_ops,
.default_attrs = dmi_sysfs_sel_attrs,
};
typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel,
loff_t offset);
static DEFINE_MUTEX(io_port_lock);
static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel,
loff_t offset)
{
u8 ret;
mutex_lock(&io_port_lock);
outb((u8)offset, sel->io.index_addr);
ret = inb(sel->io.data_addr);
mutex_unlock(&io_port_lock);
return ret;
}
static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel,
loff_t offset)
{
u8 ret;
mutex_lock(&io_port_lock);
outb((u8)offset, sel->io.index_addr);
outb((u8)(offset >> 8), sel->io.index_addr + 1);
ret = inb(sel->io.data_addr);
mutex_unlock(&io_port_lock);
return ret;
}
static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel,
loff_t offset)
{
u8 ret;
mutex_lock(&io_port_lock);
outw((u16)offset, sel->io.index_addr);
ret = inb(sel->io.data_addr);
mutex_unlock(&io_port_lock);
return ret;
}
static sel_io_reader sel_io_readers[] = {
[DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io,
[DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io,
[DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io,
};
static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry,
const struct dmi_system_event_log *sel,
char *buf, loff_t pos, size_t count)
{
ssize_t wrote = 0;
sel_io_reader io_reader = sel_io_readers[sel->access_method];
while (count && pos < sel->area_length) {
count--;
*(buf++) = io_reader(sel, pos++);
wrote++;
}
return wrote;
}
static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry,
const struct dmi_system_event_log *sel,
char *buf, loff_t pos, size_t count)
{
u8 __iomem *mapped;
ssize_t wrote = 0;
mapped = ioremap(sel->access_method_address, sel->area_length);
if (!mapped)
return -EIO;
while (count && pos < sel->area_length) {
count--;
*(buf++) = readb(mapped + pos++);
wrote++;
}
iounmap(mapped);
return wrote;
}
static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry,
const struct dmi_header *dh,
void *_state)
{
struct dmi_read_state *state = _state;
struct dmi_system_event_log sel;
if (sizeof(sel) > dmi_entry_length(dh))
return -EIO;
memcpy(&sel, dh, sizeof(sel));
switch (sel.access_method) {
case DMI_SEL_ACCESS_METHOD_IO8:
case DMI_SEL_ACCESS_METHOD_IO2x8:
case DMI_SEL_ACCESS_METHOD_IO16:
return dmi_sel_raw_read_io(entry, &sel, state->buf,
state->pos, state->count);
case DMI_SEL_ACCESS_METHOD_PHYS32:
return dmi_sel_raw_read_phys32(entry, &sel, state->buf,
state->pos, state->count);
case DMI_SEL_ACCESS_METHOD_GPNV:
pr_info("dmi-sysfs: GPNV support missing.\n");
return -EIO;
default:
pr_info("dmi-sysfs: Unknown access method %02x\n",
sel.access_method);
return -EIO;
}
}
static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct dmi_sysfs_entry *entry = to_entry(kobj->parent);
struct dmi_read_state state = {
.buf = buf,
.pos = pos,
.count = count,
};
return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state);
}
static struct bin_attribute dmi_sel_raw_attr = {
.attr = {.name = "raw_event_log", .mode = 0400},
.read = dmi_sel_raw_read,
};
static int dmi_system_event_log(struct dmi_sysfs_entry *entry)
{
int ret;
entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL);
if (!entry->child)
return -ENOMEM;
ret = kobject_init_and_add(entry->child,
&dmi_system_event_log_ktype,
&entry->kobj,
"system_event_log");
if (ret)
goto out_free;
ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr);
if (ret)
goto out_del;
return 0;
out_del:
kobject_del(entry->child);
out_free:
kfree(entry->child);
return ret;
}
/*************************************************
* Generic DMI entry support.
*************************************************/
static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
{
return sprintf(buf, "%d\n", entry->dh.length);
}
static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
{
return sprintf(buf, "%d\n", entry->dh.handle);
}
static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf)
{
return sprintf(buf, "%d\n", entry->dh.type);
}
static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry,
char *buf)
{
return sprintf(buf, "%d\n", entry->instance);
}
static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry,
char *buf)
{
return sprintf(buf, "%d\n", entry->position);
}
static DMI_SYSFS_ATTR(entry, length);
static DMI_SYSFS_ATTR(entry, handle);
static DMI_SYSFS_ATTR(entry, type);
static DMI_SYSFS_ATTR(entry, instance);
static DMI_SYSFS_ATTR(entry, position);
static struct attribute *dmi_sysfs_entry_attrs[] = {
&dmi_sysfs_attr_entry_length.attr,
&dmi_sysfs_attr_entry_handle.attr,
&dmi_sysfs_attr_entry_type.attr,
&dmi_sysfs_attr_entry_instance.attr,
&dmi_sysfs_attr_entry_position.attr,
NULL,
};
static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
const struct dmi_header *dh,
void *_state)
{
struct dmi_read_state *state = _state;
size_t entry_length;
entry_length = dmi_entry_length(dh);
return memory_read_from_buffer(state->buf, state->count,
&state->pos, dh, entry_length);
}
static ssize_t dmi_entry_raw_read(struct file *filp,
struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct dmi_sysfs_entry *entry = to_entry(kobj);
struct dmi_read_state state = {
.buf = buf,
.pos = pos,
.count = count,
};
return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
}
static const struct bin_attribute dmi_entry_raw_attr = {
.attr = {.name = "raw", .mode = 0400},
.read = dmi_entry_raw_read,
};
static void dmi_sysfs_entry_release(struct kobject *kobj)
{
struct dmi_sysfs_entry *entry = to_entry(kobj);
spin_lock(&entry_list_lock);
list_del(&entry->list);
spin_unlock(&entry_list_lock);
kfree(entry);
}
static struct kobj_type dmi_sysfs_entry_ktype = {
.release = dmi_sysfs_entry_release,
.sysfs_ops = &dmi_sysfs_attr_ops,
.default_attrs = dmi_sysfs_entry_attrs,
};
static struct kobject *dmi_kobj;
static struct kset *dmi_kset;
/* Global count of all instances seen. Only for setup */
static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
/* Global positional count of all entries seen. Only for setup */
static int __initdata position_count;
static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
void *_ret)
{
struct dmi_sysfs_entry *entry;
int *ret = _ret;
/* If a previous entry saw an error, short circuit */
if (*ret)
return;
/* Allocate and register a new entry into the entries set */
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
*ret = -ENOMEM;
return;
}
/* Set the key */
memcpy(&entry->dh, dh, sizeof(*dh));
entry->instance = instance_counts[dh->type]++;
entry->position = position_count++;
entry->kobj.kset = dmi_kset;
*ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
"%d-%d", dh->type, entry->instance);
if (*ret) {
kfree(entry);
return;
}
/* Thread on the global list for cleanup */
spin_lock(&entry_list_lock);
list_add_tail(&entry->list, &entry_list);
spin_unlock(&entry_list_lock);
/* Handle specializations by type */
switch (dh->type) {
case DMI_ENTRY_SYSTEM_EVENT_LOG:
*ret = dmi_system_event_log(entry);
break;
default:
/* No specialization */
break;
}
if (*ret)
goto out_err;
/* Create the raw binary file to access the entry */
*ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
if (*ret)
goto out_err;
return;
out_err:
kobject_put(entry->child);
kobject_put(&entry->kobj);
return;
}
static void cleanup_entry_list(void)
{
struct dmi_sysfs_entry *entry, *next;
/* No locks, we are on our way out */
list_for_each_entry_safe(entry, next, &entry_list, list) {
kobject_put(entry->child);
kobject_put(&entry->kobj);
}
}
static int __init dmi_sysfs_init(void)
{
int error = -ENOMEM;
int val;
/* Set up our directory */
dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
if (!dmi_kobj)
goto err;
dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
if (!dmi_kset)
goto err;
val = 0;
error = dmi_walk(dmi_sysfs_register_handle, &val);
if (error)
goto err;
if (val) {
error = val;
goto err;
}
pr_debug("dmi-sysfs: loaded.\n");
return 0;
err:
cleanup_entry_list();
kset_unregister(dmi_kset);
kobject_put(dmi_kobj);
return error;
}
/* clean up everything. */
static void __exit dmi_sysfs_exit(void)
{
pr_debug("dmi-sysfs: unloading.\n");
cleanup_entry_list();
kset_unregister(dmi_kset);
kobject_del(dmi_kobj);
kobject_put(dmi_kobj);
}
module_init(dmi_sysfs_init);
module_exit(dmi_sysfs_exit);
MODULE_AUTHOR("Mike Waychison <mikew@google.com>");
MODULE_DESCRIPTION("DMI sysfs support");
MODULE_LICENSE("GPL");

878
drivers/firmware/dmi_scan.c Normal file
View file

@ -0,0 +1,878 @@
#include <linux/types.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/dmi.h>
#include <linux/efi.h>
#include <linux/bootmem.h>
#include <linux/random.h>
#include <asm/dmi.h>
#include <asm/unaligned.h>
/*
* DMI stands for "Desktop Management Interface". It is part
* of and an antecedent to, SMBIOS, which stands for System
* Management BIOS. See further: http://www.dmtf.org/standards
*/
static const char dmi_empty_string[] = " ";
static u16 __initdata dmi_ver;
/*
* Catch too early calls to dmi_check_system():
*/
static int dmi_initialized;
/* DMI system identification string used during boot */
static char dmi_ids_string[128] __initdata;
static struct dmi_memdev_info {
const char *device;
const char *bank;
u16 handle;
} *dmi_memdev;
static int dmi_memdev_nr;
static const char * __init dmi_string_nosave(const struct dmi_header *dm, u8 s)
{
const u8 *bp = ((u8 *) dm) + dm->length;
if (s) {
s--;
while (s > 0 && *bp) {
bp += strlen(bp) + 1;
s--;
}
if (*bp != 0) {
size_t len = strlen(bp)+1;
size_t cmp_len = len > 8 ? 8 : len;
if (!memcmp(bp, dmi_empty_string, cmp_len))
return dmi_empty_string;
return bp;
}
}
return "";
}
static const char * __init dmi_string(const struct dmi_header *dm, u8 s)
{
const char *bp = dmi_string_nosave(dm, s);
char *str;
size_t len;
if (bp == dmi_empty_string)
return dmi_empty_string;
len = strlen(bp) + 1;
str = dmi_alloc(len);
if (str != NULL)
strcpy(str, bp);
return str;
}
/*
* We have to be cautious here. We have seen BIOSes with DMI pointers
* pointing to completely the wrong place for example
*/
static void dmi_table(u8 *buf, 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_early_remap(dmi_base, dmi_len);
if (buf == NULL)
return -1;
dmi_table(buf, dmi_len, dmi_num, decode, NULL);
add_device_randomness(buf, dmi_len);
dmi_early_unmap(buf, dmi_len);
return 0;
}
static int __init dmi_checksum(const u8 *buf, u8 len)
{
u8 sum = 0;
int a;
for (a = 0; a < len; a++)
sum += buf[a];
return sum == 0;
}
static const char *dmi_ident[DMI_STRING_MAX];
static LIST_HEAD(dmi_devices);
int dmi_available;
/*
* Save a DMI string
*/
static void __init dmi_save_ident(const struct dmi_header *dm, int slot,
int string)
{
const char *d = (const char *) dm;
const char *p;
if (dmi_ident[slot])
return;
p = dmi_string(dm, d[string]);
if (p == NULL)
return;
dmi_ident[slot] = p;
}
static void __init dmi_save_uuid(const struct dmi_header *dm, int slot,
int index)
{
const u8 *d = (u8 *) dm + index;
char *s;
int is_ff = 1, is_00 = 1, i;
if (dmi_ident[slot])
return;
for (i = 0; i < 16 && (is_ff || is_00); i++) {
if (d[i] != 0x00)
is_00 = 0;
if (d[i] != 0xFF)
is_ff = 0;
}
if (is_ff || is_00)
return;
s = dmi_alloc(16*2+4+1);
if (!s)
return;
/*
* As of version 2.6 of the SMBIOS specification, the first 3 fields of
* the UUID are supposed to be little-endian encoded. The specification
* says that this is the defacto standard.
*/
if (dmi_ver >= 0x0206)
sprintf(s, "%pUL", d);
else
sprintf(s, "%pUB", d);
dmi_ident[slot] = s;
}
static void __init dmi_save_type(const struct dmi_header *dm, int slot,
int index)
{
const u8 *d = (u8 *) dm + index;
char *s;
if (dmi_ident[slot])
return;
s = dmi_alloc(4);
if (!s)
return;
sprintf(s, "%u", *d & 0x7F);
dmi_ident[slot] = s;
}
static void __init dmi_save_one_device(int type, const char *name)
{
struct dmi_device *dev;
/* No duplicate device */
if (dmi_find_device(type, name, NULL))
return;
dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1);
if (!dev)
return;
dev->type = type;
strcpy((char *)(dev + 1), name);
dev->name = (char *)(dev + 1);
dev->device_data = NULL;
list_add(&dev->list, &dmi_devices);
}
static void __init dmi_save_devices(const struct dmi_header *dm)
{
int i, count = (dm->length - sizeof(struct dmi_header)) / 2;
for (i = 0; i < count; i++) {
const char *d = (char *)(dm + 1) + (i * 2);
/* Skip disabled device */
if ((*d & 0x80) == 0)
continue;
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1)));
}
}
static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm)
{
int i, count = *(u8 *)(dm + 1);
struct dmi_device *dev;
for (i = 1; i <= count; i++) {
const char *devname = dmi_string(dm, i);
if (devname == dmi_empty_string)
continue;
dev = dmi_alloc(sizeof(*dev));
if (!dev)
break;
dev->type = DMI_DEV_TYPE_OEM_STRING;
dev->name = devname;
dev->device_data = NULL;
list_add(&dev->list, &dmi_devices);
}
}
static void __init dmi_save_ipmi_device(const struct dmi_header *dm)
{
struct dmi_device *dev;
void *data;
data = dmi_alloc(dm->length);
if (data == NULL)
return;
memcpy(data, dm, dm->length);
dev = dmi_alloc(sizeof(*dev));
if (!dev)
return;
dev->type = DMI_DEV_TYPE_IPMI;
dev->name = "IPMI controller";
dev->device_data = data;
list_add_tail(&dev->list, &dmi_devices);
}
static void __init dmi_save_dev_onboard(int instance, int segment, int bus,
int devfn, const char *name)
{
struct dmi_dev_onboard *onboard_dev;
onboard_dev = dmi_alloc(sizeof(*onboard_dev) + strlen(name) + 1);
if (!onboard_dev)
return;
onboard_dev->instance = instance;
onboard_dev->segment = segment;
onboard_dev->bus = bus;
onboard_dev->devfn = devfn;
strcpy((char *)&onboard_dev[1], name);
onboard_dev->dev.type = DMI_DEV_TYPE_DEV_ONBOARD;
onboard_dev->dev.name = (char *)&onboard_dev[1];
onboard_dev->dev.device_data = onboard_dev;
list_add(&onboard_dev->dev.list, &dmi_devices);
}
static void __init dmi_save_extended_devices(const struct dmi_header *dm)
{
const u8 *d = (u8 *) dm + 5;
/* Skip disabled device */
if ((*d & 0x80) == 0)
return;
dmi_save_dev_onboard(*(d+1), *(u16 *)(d+2), *(d+4), *(d+5),
dmi_string_nosave(dm, *(d-1)));
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d - 1)));
}
static void __init count_mem_devices(const struct dmi_header *dm, void *v)
{
if (dm->type != DMI_ENTRY_MEM_DEVICE)
return;
dmi_memdev_nr++;
}
static void __init save_mem_devices(const struct dmi_header *dm, void *v)
{
const char *d = (const char *)dm;
static int nr;
if (dm->type != DMI_ENTRY_MEM_DEVICE)
return;
if (nr >= dmi_memdev_nr) {
pr_warn(FW_BUG "Too many DIMM entries in SMBIOS table\n");
return;
}
dmi_memdev[nr].handle = get_unaligned(&dm->handle);
dmi_memdev[nr].device = dmi_string(dm, d[0x10]);
dmi_memdev[nr].bank = dmi_string(dm, d[0x11]);
nr++;
}
void __init dmi_memdev_walk(void)
{
if (!dmi_available)
return;
if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) {
dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr);
if (dmi_memdev)
dmi_walk_early(save_mem_devices);
}
}
/*
* Process a DMI table entry. Right now all we care about are the BIOS
* and machine entries. For 2.5 we should pull the smbus controller info
* out of here.
*/
static void __init dmi_decode(const struct dmi_header *dm, void *dummy)
{
switch (dm->type) {
case 0: /* BIOS Information */
dmi_save_ident(dm, DMI_BIOS_VENDOR, 4);
dmi_save_ident(dm, DMI_BIOS_VERSION, 5);
dmi_save_ident(dm, DMI_BIOS_DATE, 8);
break;
case 1: /* System Information */
dmi_save_ident(dm, DMI_SYS_VENDOR, 4);
dmi_save_ident(dm, DMI_PRODUCT_NAME, 5);
dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6);
dmi_save_ident(dm, DMI_PRODUCT_SERIAL, 7);
dmi_save_uuid(dm, DMI_PRODUCT_UUID, 8);
break;
case 2: /* Base Board Information */
dmi_save_ident(dm, DMI_BOARD_VENDOR, 4);
dmi_save_ident(dm, DMI_BOARD_NAME, 5);
dmi_save_ident(dm, DMI_BOARD_VERSION, 6);
dmi_save_ident(dm, DMI_BOARD_SERIAL, 7);
dmi_save_ident(dm, DMI_BOARD_ASSET_TAG, 8);
break;
case 3: /* Chassis Information */
dmi_save_ident(dm, DMI_CHASSIS_VENDOR, 4);
dmi_save_type(dm, DMI_CHASSIS_TYPE, 5);
dmi_save_ident(dm, DMI_CHASSIS_VERSION, 6);
dmi_save_ident(dm, DMI_CHASSIS_SERIAL, 7);
dmi_save_ident(dm, DMI_CHASSIS_ASSET_TAG, 8);
break;
case 10: /* Onboard Devices Information */
dmi_save_devices(dm);
break;
case 11: /* OEM Strings */
dmi_save_oem_strings_devices(dm);
break;
case 38: /* IPMI Device Information */
dmi_save_ipmi_device(dm);
break;
case 41: /* Onboard Devices Extended Information */
dmi_save_extended_devices(dm);
}
}
static int __init print_filtered(char *buf, size_t len, const char *info)
{
int c = 0;
const char *p;
if (!info)
return c;
for (p = info; *p; p++)
if (isprint(*p))
c += scnprintf(buf + c, len - c, "%c", *p);
else
c += scnprintf(buf + c, len - c, "\\x%02x", *p & 0xff);
return c;
}
static void __init dmi_format_ids(char *buf, size_t len)
{
int c = 0;
const char *board; /* Board Name is optional */
c += print_filtered(buf + c, len - c,
dmi_get_system_info(DMI_SYS_VENDOR));
c += scnprintf(buf + c, len - c, " ");
c += print_filtered(buf + c, len - c,
dmi_get_system_info(DMI_PRODUCT_NAME));
board = dmi_get_system_info(DMI_BOARD_NAME);
if (board) {
c += scnprintf(buf + c, len - c, "/");
c += print_filtered(buf + c, len - c, board);
}
c += scnprintf(buf + c, len - c, ", BIOS ");
c += print_filtered(buf + c, len - c,
dmi_get_system_info(DMI_BIOS_VERSION));
c += scnprintf(buf + c, len - c, " ");
c += print_filtered(buf + c, len - c,
dmi_get_system_info(DMI_BIOS_DATE));
}
/*
* Check for DMI/SMBIOS headers in the system firmware image. Any
* SMBIOS header must start 16 bytes before the DMI header, so take a
* 32 byte buffer and check for DMI at offset 16 and SMBIOS at offset
* 0. If the DMI header is present, set dmi_ver accordingly (SMBIOS
* takes precedence) and return 0. Otherwise return 1.
*/
static int __init dmi_present(const u8 *buf)
{
int smbios_ver;
if (memcmp(buf, "_SM_", 4) == 0 &&
buf[5] < 32 && dmi_checksum(buf, buf[5])) {
smbios_ver = (buf[6] << 8) + buf[7];
/* Some BIOS report weird SMBIOS version, fix that up */
switch (smbios_ver) {
case 0x021F:
case 0x0221:
pr_debug("SMBIOS version fixup(2.%d->2.%d)\n",
smbios_ver & 0xFF, 3);
smbios_ver = 0x0203;
break;
case 0x0233:
pr_debug("SMBIOS version fixup(2.%d->2.%d)\n", 51, 6);
smbios_ver = 0x0206;
break;
}
} else {
smbios_ver = 0;
}
buf += 16;
if (memcmp(buf, "_DMI_", 5) == 0 && dmi_checksum(buf, 15)) {
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];
if (dmi_walk_early(dmi_decode) == 0) {
if (smbios_ver) {
dmi_ver = smbios_ver;
pr_info("SMBIOS %d.%d present.\n",
dmi_ver >> 8, dmi_ver & 0xFF);
} else {
dmi_ver = (buf[14] & 0xF0) << 4 |
(buf[14] & 0x0F);
pr_info("Legacy DMI %d.%d present.\n",
dmi_ver >> 8, dmi_ver & 0xFF);
}
dmi_format_ids(dmi_ids_string, sizeof(dmi_ids_string));
printk(KERN_DEBUG "DMI: %s\n", dmi_ids_string);
return 0;
}
}
return 1;
}
void __init dmi_scan_machine(void)
{
char __iomem *p, *q;
char buf[32];
if (efi_enabled(EFI_CONFIG_TABLES)) {
if (efi.smbios == EFI_INVALID_TABLE_ADDR)
goto error;
/* This is called as a core_initcall() because it isn't
* needed during early boot. This also means we can
* iounmap the space when we're done with it.
*/
p = dmi_early_remap(efi.smbios, 32);
if (p == NULL)
goto error;
memcpy_fromio(buf, p, 32);
dmi_early_unmap(p, 32);
if (!dmi_present(buf)) {
dmi_available = 1;
goto out;
}
} else if (IS_ENABLED(CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK)) {
p = dmi_early_remap(0xF0000, 0x10000);
if (p == NULL)
goto error;
/*
* Iterate over all possible DMI header addresses q.
* Maintain the 32 bytes around q in buf. On the
* first iteration, substitute zero for the
* out-of-range bytes so there is no chance of falsely
* detecting an SMBIOS header.
*/
memset(buf, 0, 16);
for (q = p; q < p + 0x10000; q += 16) {
memcpy_fromio(buf + 16, q, 16);
if (!dmi_present(buf)) {
dmi_available = 1;
dmi_early_unmap(p, 0x10000);
goto out;
}
memcpy(buf, buf + 16, 16);
}
dmi_early_unmap(p, 0x10000);
}
error:
pr_info("DMI not present or invalid.\n");
out:
dmi_initialized = 1;
}
/**
* dmi_set_dump_stack_arch_desc - set arch description for dump_stack()
*
* Invoke dump_stack_set_arch_desc() with DMI system information so that
* DMI identifiers are printed out on task dumps. Arch boot code should
* call this function after dmi_scan_machine() if it wants to print out DMI
* identifiers on task dumps.
*/
void __init dmi_set_dump_stack_arch_desc(void)
{
dump_stack_set_arch_desc("%s", dmi_ids_string);
}
/**
* dmi_matches - check if dmi_system_id structure matches system DMI data
* @dmi: pointer to the dmi_system_id structure to check
*/
static bool dmi_matches(const struct dmi_system_id *dmi)
{
int i;
WARN(!dmi_initialized, KERN_ERR "dmi check: not initialized yet.\n");
for (i = 0; i < ARRAY_SIZE(dmi->matches); i++) {
int s = dmi->matches[i].slot;
if (s == DMI_NONE)
break;
if (dmi_ident[s]) {
if (!dmi->matches[i].exact_match &&
strstr(dmi_ident[s], dmi->matches[i].substr))
continue;
else if (dmi->matches[i].exact_match &&
!strcmp(dmi_ident[s], dmi->matches[i].substr))
continue;
}
/* No match */
return false;
}
return true;
}
/**
* dmi_is_end_of_table - check for end-of-table marker
* @dmi: pointer to the dmi_system_id structure to check
*/
static bool dmi_is_end_of_table(const struct dmi_system_id *dmi)
{
return dmi->matches[0].slot == DMI_NONE;
}
/**
* dmi_check_system - check system DMI data
* @list: array of dmi_system_id structures to match against
* All non-null elements of the list must match
* their slot's (field index's) data (i.e., each
* list string must be a substring of the specified
* DMI slot's string data) to be considered a
* successful match.
*
* Walk the blacklist table running matching functions until someone
* returns non zero or we hit the end. Callback function is called for
* each successful match. Returns the number of matches.
*/
int dmi_check_system(const struct dmi_system_id *list)
{
int count = 0;
const struct dmi_system_id *d;
for (d = list; !dmi_is_end_of_table(d); d++)
if (dmi_matches(d)) {
count++;
if (d->callback && d->callback(d))
break;
}
return count;
}
EXPORT_SYMBOL(dmi_check_system);
/**
* dmi_first_match - find dmi_system_id structure matching system DMI data
* @list: array of dmi_system_id structures to match against
* All non-null elements of the list must match
* their slot's (field index's) data (i.e., each
* list string must be a substring of the specified
* DMI slot's string data) to be considered a
* successful match.
*
* Walk the blacklist table until the first match is found. Return the
* pointer to the matching entry or NULL if there's no match.
*/
const struct dmi_system_id *dmi_first_match(const struct dmi_system_id *list)
{
const struct dmi_system_id *d;
for (d = list; !dmi_is_end_of_table(d); d++)
if (dmi_matches(d))
return d;
return NULL;
}
EXPORT_SYMBOL(dmi_first_match);
/**
* dmi_get_system_info - return DMI data value
* @field: data index (see enum dmi_field)
*
* Returns one DMI data value, can be used to perform
* complex DMI data checks.
*/
const char *dmi_get_system_info(int field)
{
return dmi_ident[field];
}
EXPORT_SYMBOL(dmi_get_system_info);
/**
* dmi_name_in_serial - Check if string is in the DMI product serial information
* @str: string to check for
*/
int dmi_name_in_serial(const char *str)
{
int f = DMI_PRODUCT_SERIAL;
if (dmi_ident[f] && strstr(dmi_ident[f], str))
return 1;
return 0;
}
/**
* dmi_name_in_vendors - Check if string is in the DMI system or board vendor name
* @str: Case sensitive Name
*/
int dmi_name_in_vendors(const char *str)
{
static int fields[] = { DMI_SYS_VENDOR, DMI_BOARD_VENDOR, DMI_NONE };
int i;
for (i = 0; fields[i] != DMI_NONE; i++) {
int f = fields[i];
if (dmi_ident[f] && strstr(dmi_ident[f], str))
return 1;
}
return 0;
}
EXPORT_SYMBOL(dmi_name_in_vendors);
/**
* dmi_find_device - find onboard device by type/name
* @type: device type or %DMI_DEV_TYPE_ANY to match all device types
* @name: device name string or %NULL to match all
* @from: previous device found in search, or %NULL for new search.
*
* Iterates through the list of known onboard devices. If a device is
* found with a matching @vendor and @device, a pointer to its device
* structure is returned. Otherwise, %NULL is returned.
* A new search is initiated by passing %NULL as the @from argument.
* If @from is not %NULL, searches continue from next device.
*/
const struct dmi_device *dmi_find_device(int type, const char *name,
const struct dmi_device *from)
{
const struct list_head *head = from ? &from->list : &dmi_devices;
struct list_head *d;
for (d = head->next; d != &dmi_devices; d = d->next) {
const struct dmi_device *dev =
list_entry(d, struct dmi_device, list);
if (((type == DMI_DEV_TYPE_ANY) || (dev->type == type)) &&
((name == NULL) || (strcmp(dev->name, name) == 0)))
return dev;
}
return NULL;
}
EXPORT_SYMBOL(dmi_find_device);
/**
* dmi_get_date - parse a DMI date
* @field: data index (see enum dmi_field)
* @yearp: optional out parameter for the year
* @monthp: optional out parameter for the month
* @dayp: optional out parameter for the day
*
* The date field is assumed to be in the form resembling
* [mm[/dd]]/yy[yy] and the result is stored in the out
* parameters any or all of which can be omitted.
*
* If the field doesn't exist, all out parameters are set to zero
* and false is returned. Otherwise, true is returned with any
* invalid part of date set to zero.
*
* On return, year, month and day are guaranteed to be in the
* range of [0,9999], [0,12] and [0,31] respectively.
*/
bool dmi_get_date(int field, int *yearp, int *monthp, int *dayp)
{
int year = 0, month = 0, day = 0;
bool exists;
const char *s, *y;
char *e;
s = dmi_get_system_info(field);
exists = s;
if (!exists)
goto out;
/*
* Determine year first. We assume the date string resembles
* mm/dd/yy[yy] but the original code extracted only the year
* from the end. Keep the behavior in the spirit of no
* surprises.
*/
y = strrchr(s, '/');
if (!y)
goto out;
y++;
year = simple_strtoul(y, &e, 10);
if (y != e && year < 100) { /* 2-digit year */
year += 1900;
if (year < 1996) /* no dates < spec 1.0 */
year += 100;
}
if (year > 9999) /* year should fit in %04d */
year = 0;
/* parse the mm and dd */
month = simple_strtoul(s, &e, 10);
if (s == e || *e != '/' || !month || month > 12) {
month = 0;
goto out;
}
s = e + 1;
day = simple_strtoul(s, &e, 10);
if (s == y || s == e || *e != '/' || day > 31)
day = 0;
out:
if (yearp)
*yearp = year;
if (monthp)
*monthp = month;
if (dayp)
*dayp = day;
return exists;
}
EXPORT_SYMBOL(dmi_get_date);
/**
* dmi_walk - Walk the DMI table and get called back for every record
* @decode: Callback function
* @private_data: Private data to be passed to the callback function
*
* Returns -1 when the DMI table can't be reached, 0 on success.
*/
int dmi_walk(void (*decode)(const struct dmi_header *, void *),
void *private_data)
{
u8 *buf;
if (!dmi_available)
return -1;
buf = dmi_remap(dmi_base, dmi_len);
if (buf == NULL)
return -1;
dmi_table(buf, dmi_len, dmi_num, decode, private_data);
dmi_unmap(buf);
return 0;
}
EXPORT_SYMBOL_GPL(dmi_walk);
/**
* dmi_match - compare a string to the dmi field (if exists)
* @f: DMI field identifier
* @str: string to compare the DMI field to
*
* Returns true if the requested field equals to the str (including NULL).
*/
bool dmi_match(enum dmi_field f, const char *str)
{
const char *info = dmi_get_system_info(f);
if (info == NULL || str == NULL)
return info == str;
return !strcmp(info, str);
}
EXPORT_SYMBOL_GPL(dmi_match);
void dmi_memdev_name(u16 handle, const char **bank, const char **device)
{
int n;
if (dmi_memdev == NULL)
return;
for (n = 0; n < dmi_memdev_nr; n++) {
if (handle == dmi_memdev[n].handle) {
*bank = dmi_memdev[n].bank;
*device = dmi_memdev[n].device;
break;
}
}
}
EXPORT_SYMBOL_GPL(dmi_memdev_name);

801
drivers/firmware/edd.c Normal file
View file

@ -0,0 +1,801 @@
/*
* linux/drivers/firmware/edd.c
* Copyright (C) 2002, 2003, 2004 Dell Inc.
* by Matt Domsch <Matt_Domsch@dell.com>
* disk signature by Matt Domsch, Andrew Wilks, and Sandeep K. Shandilya
* legacy CHS by Patrick J. LoPresti <patl@users.sourceforge.net>
*
* BIOS Enhanced Disk Drive Services (EDD)
* conformant to T13 Committee www.t13.org
* projects 1572D, 1484D, 1386D, 1226DT
*
* This code takes information provided by BIOS EDD calls
* fn41 - Check Extensions Present and
* fn48 - Get Device Parameters with EDD extensions
* made in setup.S, copied to safe structures in setup.c,
* and presents it in sysfs.
*
* Please see http://linux.dell.com/edd/results.html for
* the list of BIOSs which have been reported to implement EDD.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/stat.h>
#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/limits.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/blkdev.h>
#include <linux/edd.h>
#define EDD_VERSION "0.16"
#define EDD_DATE "2004-Jun-25"
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
MODULE_DESCRIPTION("sysfs interface to BIOS EDD information");
MODULE_LICENSE("GPL");
MODULE_VERSION(EDD_VERSION);
#define left (PAGE_SIZE - (p - buf) - 1)
struct edd_device {
unsigned int index;
unsigned int mbr_signature;
struct edd_info *info;
struct kobject kobj;
};
struct edd_attribute {
struct attribute attr;
ssize_t(*show) (struct edd_device * edev, char *buf);
int (*test) (struct edd_device * edev);
};
/* forward declarations */
static int edd_dev_is_type(struct edd_device *edev, const char *type);
static struct pci_dev *edd_get_pci_dev(struct edd_device *edev);
static struct edd_device *edd_devices[EDD_MBR_SIG_MAX];
#define EDD_DEVICE_ATTR(_name,_mode,_show,_test) \
struct edd_attribute edd_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.test = _test, \
};
static int
edd_has_mbr_signature(struct edd_device *edev)
{
return edev->index < min_t(unsigned char, edd.mbr_signature_nr, EDD_MBR_SIG_MAX);
}
static int
edd_has_edd_info(struct edd_device *edev)
{
return edev->index < min_t(unsigned char, edd.edd_info_nr, EDDMAXNR);
}
static inline struct edd_info *
edd_dev_get_info(struct edd_device *edev)
{
return edev->info;
}
static inline void
edd_dev_set_info(struct edd_device *edev, int i)
{
edev->index = i;
if (edd_has_mbr_signature(edev))
edev->mbr_signature = edd.mbr_signature[i];
if (edd_has_edd_info(edev))
edev->info = &edd.edd_info[i];
}
#define to_edd_attr(_attr) container_of(_attr,struct edd_attribute,attr)
#define to_edd_device(obj) container_of(obj,struct edd_device,kobj)
static ssize_t
edd_attr_show(struct kobject * kobj, struct attribute *attr, char *buf)
{
struct edd_device *dev = to_edd_device(kobj);
struct edd_attribute *edd_attr = to_edd_attr(attr);
ssize_t ret = -EIO;
if (edd_attr->show)
ret = edd_attr->show(dev, buf);
return ret;
}
static const struct sysfs_ops edd_attr_ops = {
.show = edd_attr_show,
};
static ssize_t
edd_show_host_bus(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
int i;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
for (i = 0; i < 4; i++) {
if (isprint(info->params.host_bus_type[i])) {
p += scnprintf(p, left, "%c", info->params.host_bus_type[i]);
} else {
p += scnprintf(p, left, " ");
}
}
if (!strncmp(info->params.host_bus_type, "ISA", 3)) {
p += scnprintf(p, left, "\tbase_address: %x\n",
info->params.interface_path.isa.base_address);
} else if (!strncmp(info->params.host_bus_type, "PCIX", 4) ||
!strncmp(info->params.host_bus_type, "PCI", 3) ||
!strncmp(info->params.host_bus_type, "XPRS", 4)) {
p += scnprintf(p, left,
"\t%02x:%02x.%d channel: %u\n",
info->params.interface_path.pci.bus,
info->params.interface_path.pci.slot,
info->params.interface_path.pci.function,
info->params.interface_path.pci.channel);
} else if (!strncmp(info->params.host_bus_type, "IBND", 4) ||
!strncmp(info->params.host_bus_type, "HTPT", 4)) {
p += scnprintf(p, left,
"\tTBD: %llx\n",
info->params.interface_path.ibnd.reserved);
} else {
p += scnprintf(p, left, "\tunknown: %llx\n",
info->params.interface_path.unknown.reserved);
}
return (p - buf);
}
static ssize_t
edd_show_interface(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
int i;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
for (i = 0; i < 8; i++) {
if (isprint(info->params.interface_type[i])) {
p += scnprintf(p, left, "%c", info->params.interface_type[i]);
} else {
p += scnprintf(p, left, " ");
}
}
if (!strncmp(info->params.interface_type, "ATAPI", 5)) {
p += scnprintf(p, left, "\tdevice: %u lun: %u\n",
info->params.device_path.atapi.device,
info->params.device_path.atapi.lun);
} else if (!strncmp(info->params.interface_type, "ATA", 3)) {
p += scnprintf(p, left, "\tdevice: %u\n",
info->params.device_path.ata.device);
} else if (!strncmp(info->params.interface_type, "SCSI", 4)) {
p += scnprintf(p, left, "\tid: %u lun: %llu\n",
info->params.device_path.scsi.id,
info->params.device_path.scsi.lun);
} else if (!strncmp(info->params.interface_type, "USB", 3)) {
p += scnprintf(p, left, "\tserial_number: %llx\n",
info->params.device_path.usb.serial_number);
} else if (!strncmp(info->params.interface_type, "1394", 4)) {
p += scnprintf(p, left, "\teui: %llx\n",
info->params.device_path.i1394.eui);
} else if (!strncmp(info->params.interface_type, "FIBRE", 5)) {
p += scnprintf(p, left, "\twwid: %llx lun: %llx\n",
info->params.device_path.fibre.wwid,
info->params.device_path.fibre.lun);
} else if (!strncmp(info->params.interface_type, "I2O", 3)) {
p += scnprintf(p, left, "\tidentity_tag: %llx\n",
info->params.device_path.i2o.identity_tag);
} else if (!strncmp(info->params.interface_type, "RAID", 4)) {
p += scnprintf(p, left, "\tidentity_tag: %x\n",
info->params.device_path.raid.array_number);
} else if (!strncmp(info->params.interface_type, "SATA", 4)) {
p += scnprintf(p, left, "\tdevice: %u\n",
info->params.device_path.sata.device);
} else {
p += scnprintf(p, left, "\tunknown: %llx %llx\n",
info->params.device_path.unknown.reserved1,
info->params.device_path.unknown.reserved2);
}
return (p - buf);
}
/**
* edd_show_raw_data() - copies raw data to buffer for userspace to parse
* @edev: target edd_device
* @buf: output buffer
*
* Returns: number of bytes written, or -EINVAL on failure
*/
static ssize_t
edd_show_raw_data(struct edd_device *edev, char *buf)
{
struct edd_info *info;
ssize_t len = sizeof (info->params);
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE))
len = info->params.length;
/* In case of buggy BIOSs */
if (len > (sizeof(info->params)))
len = sizeof(info->params);
memcpy(buf, &info->params, len);
return len;
}
static ssize_t
edd_show_version(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += scnprintf(p, left, "0x%02x\n", info->version);
return (p - buf);
}
static ssize_t
edd_show_mbr_signature(struct edd_device *edev, char *buf)
{
char *p = buf;
p += scnprintf(p, left, "0x%08x\n", edev->mbr_signature);
return (p - buf);
}
static ssize_t
edd_show_extensions(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) {
p += scnprintf(p, left, "Fixed disk access\n");
}
if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) {
p += scnprintf(p, left, "Device locking and ejecting\n");
}
if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) {
p += scnprintf(p, left, "Enhanced Disk Drive support\n");
}
if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) {
p += scnprintf(p, left, "64-bit extensions\n");
}
return (p - buf);
}
static ssize_t
edd_show_info_flags(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
if (info->params.info_flags & EDD_INFO_DMA_BOUNDARY_ERROR_TRANSPARENT)
p += scnprintf(p, left, "DMA boundary error transparent\n");
if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID)
p += scnprintf(p, left, "geometry valid\n");
if (info->params.info_flags & EDD_INFO_REMOVABLE)
p += scnprintf(p, left, "removable\n");
if (info->params.info_flags & EDD_INFO_WRITE_VERIFY)
p += scnprintf(p, left, "write verify\n");
if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION)
p += scnprintf(p, left, "media change notification\n");
if (info->params.info_flags & EDD_INFO_LOCKABLE)
p += scnprintf(p, left, "lockable\n");
if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT)
p += scnprintf(p, left, "no media present\n");
if (info->params.info_flags & EDD_INFO_USE_INT13_FN50)
p += scnprintf(p, left, "use int13 fn50\n");
return (p - buf);
}
static ssize_t
edd_show_legacy_max_cylinder(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += snprintf(p, left, "%u\n", info->legacy_max_cylinder);
return (p - buf);
}
static ssize_t
edd_show_legacy_max_head(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += snprintf(p, left, "%u\n", info->legacy_max_head);
return (p - buf);
}
static ssize_t
edd_show_legacy_sectors_per_track(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += snprintf(p, left, "%u\n", info->legacy_sectors_per_track);
return (p - buf);
}
static ssize_t
edd_show_default_cylinders(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += scnprintf(p, left, "%u\n", info->params.num_default_cylinders);
return (p - buf);
}
static ssize_t
edd_show_default_heads(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += scnprintf(p, left, "%u\n", info->params.num_default_heads);
return (p - buf);
}
static ssize_t
edd_show_default_sectors_per_track(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += scnprintf(p, left, "%u\n", info->params.sectors_per_track);
return (p - buf);
}
static ssize_t
edd_show_sectors(struct edd_device *edev, char *buf)
{
struct edd_info *info;
char *p = buf;
if (!edev)
return -EINVAL;
info = edd_dev_get_info(edev);
if (!info || !buf)
return -EINVAL;
p += scnprintf(p, left, "%llu\n", info->params.number_of_sectors);
return (p - buf);
}
/*
* Some device instances may not have all the above attributes,
* or the attribute values may be meaningless (i.e. if
* the device is < EDD 3.0, it won't have host_bus and interface
* information), so don't bother making files for them. Likewise
* if the default_{cylinders,heads,sectors_per_track} values
* are zero, the BIOS doesn't provide sane values, don't bother
* creating files for them either.
*/
static int
edd_has_legacy_max_cylinder(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->legacy_max_cylinder > 0;
}
static int
edd_has_legacy_max_head(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->legacy_max_head > 0;
}
static int
edd_has_legacy_sectors_per_track(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->legacy_sectors_per_track > 0;
}
static int
edd_has_default_cylinders(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->params.num_default_cylinders > 0;
}
static int
edd_has_default_heads(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->params.num_default_heads > 0;
}
static int
edd_has_default_sectors_per_track(struct edd_device *edev)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
return info->params.sectors_per_track > 0;
}
static int
edd_has_edd30(struct edd_device *edev)
{
struct edd_info *info;
int i;
u8 csum = 0;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (!info)
return 0;
if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) {
return 0;
}
/* We support only T13 spec */
if (info->params.device_path_info_length != 44)
return 0;
for (i = 30; i < info->params.device_path_info_length + 30; i++)
csum += *(((u8 *)&info->params) + i);
if (csum)
return 0;
return 1;
}
static EDD_DEVICE_ATTR(raw_data, 0444, edd_show_raw_data, edd_has_edd_info);
static EDD_DEVICE_ATTR(version, 0444, edd_show_version, edd_has_edd_info);
static EDD_DEVICE_ATTR(extensions, 0444, edd_show_extensions, edd_has_edd_info);
static EDD_DEVICE_ATTR(info_flags, 0444, edd_show_info_flags, edd_has_edd_info);
static EDD_DEVICE_ATTR(sectors, 0444, edd_show_sectors, edd_has_edd_info);
static EDD_DEVICE_ATTR(legacy_max_cylinder, 0444,
edd_show_legacy_max_cylinder,
edd_has_legacy_max_cylinder);
static EDD_DEVICE_ATTR(legacy_max_head, 0444, edd_show_legacy_max_head,
edd_has_legacy_max_head);
static EDD_DEVICE_ATTR(legacy_sectors_per_track, 0444,
edd_show_legacy_sectors_per_track,
edd_has_legacy_sectors_per_track);
static EDD_DEVICE_ATTR(default_cylinders, 0444, edd_show_default_cylinders,
edd_has_default_cylinders);
static EDD_DEVICE_ATTR(default_heads, 0444, edd_show_default_heads,
edd_has_default_heads);
static EDD_DEVICE_ATTR(default_sectors_per_track, 0444,
edd_show_default_sectors_per_track,
edd_has_default_sectors_per_track);
static EDD_DEVICE_ATTR(interface, 0444, edd_show_interface, edd_has_edd30);
static EDD_DEVICE_ATTR(host_bus, 0444, edd_show_host_bus, edd_has_edd30);
static EDD_DEVICE_ATTR(mbr_signature, 0444, edd_show_mbr_signature, edd_has_mbr_signature);
/* These are default attributes that are added for every edd
* device discovered. There are none.
*/
static struct attribute * def_attrs[] = {
NULL,
};
/* These attributes are conditional and only added for some devices. */
static struct edd_attribute * edd_attrs[] = {
&edd_attr_raw_data,
&edd_attr_version,
&edd_attr_extensions,
&edd_attr_info_flags,
&edd_attr_sectors,
&edd_attr_legacy_max_cylinder,
&edd_attr_legacy_max_head,
&edd_attr_legacy_sectors_per_track,
&edd_attr_default_cylinders,
&edd_attr_default_heads,
&edd_attr_default_sectors_per_track,
&edd_attr_interface,
&edd_attr_host_bus,
&edd_attr_mbr_signature,
NULL,
};
/**
* edd_release - free edd structure
* @kobj: kobject of edd structure
*
* This is called when the refcount of the edd structure
* reaches 0. This should happen right after we unregister,
* but just in case, we use the release callback anyway.
*/
static void edd_release(struct kobject * kobj)
{
struct edd_device * dev = to_edd_device(kobj);
kfree(dev);
}
static struct kobj_type edd_ktype = {
.release = edd_release,
.sysfs_ops = &edd_attr_ops,
.default_attrs = def_attrs,
};
static struct kset *edd_kset;
/**
* edd_dev_is_type() - is this EDD device a 'type' device?
* @edev: target edd_device
* @type: a host bus or interface identifier string per the EDD spec
*
* Returns 1 (TRUE) if it is a 'type' device, 0 otherwise.
*/
static int
edd_dev_is_type(struct edd_device *edev, const char *type)
{
struct edd_info *info;
if (!edev)
return 0;
info = edd_dev_get_info(edev);
if (type && info) {
if (!strncmp(info->params.host_bus_type, type, strlen(type)) ||
!strncmp(info->params.interface_type, type, strlen(type)))
return 1;
}
return 0;
}
/**
* edd_get_pci_dev() - finds pci_dev that matches edev
* @edev: edd_device
*
* Returns pci_dev if found, or NULL
*/
static struct pci_dev *
edd_get_pci_dev(struct edd_device *edev)
{
struct edd_info *info = edd_dev_get_info(edev);
if (edd_dev_is_type(edev, "PCI") || edd_dev_is_type(edev, "XPRS")) {
return pci_get_bus_and_slot(info->params.interface_path.pci.bus,
PCI_DEVFN(info->params.interface_path.pci.slot,
info->params.interface_path.pci.
function));
}
return NULL;
}
static int
edd_create_symlink_to_pcidev(struct edd_device *edev)
{
struct pci_dev *pci_dev = edd_get_pci_dev(edev);
int ret;
if (!pci_dev)
return 1;
ret = sysfs_create_link(&edev->kobj,&pci_dev->dev.kobj,"pci_dev");
pci_dev_put(pci_dev);
return ret;
}
static inline void
edd_device_unregister(struct edd_device *edev)
{
kobject_put(&edev->kobj);
}
static void edd_populate_dir(struct edd_device * edev)
{
struct edd_attribute * attr;
int error = 0;
int i;
for (i = 0; (attr = edd_attrs[i]) && !error; i++) {
if (!attr->test ||
(attr->test && attr->test(edev)))
error = sysfs_create_file(&edev->kobj,&attr->attr);
}
if (!error) {
edd_create_symlink_to_pcidev(edev);
}
}
static int
edd_device_register(struct edd_device *edev, int i)
{
int error;
if (!edev)
return 1;
edd_dev_set_info(edev, i);
edev->kobj.kset = edd_kset;
error = kobject_init_and_add(&edev->kobj, &edd_ktype, NULL,
"int13_dev%02x", 0x80 + i);
if (!error) {
edd_populate_dir(edev);
kobject_uevent(&edev->kobj, KOBJ_ADD);
}
return error;
}
static inline int edd_num_devices(void)
{
return max_t(unsigned char,
min_t(unsigned char, EDD_MBR_SIG_MAX, edd.mbr_signature_nr),
min_t(unsigned char, EDDMAXNR, edd.edd_info_nr));
}
/**
* edd_init() - creates sysfs tree of EDD data
*/
static int __init
edd_init(void)
{
int i;
int rc=0;
struct edd_device *edev;
printk(KERN_INFO "BIOS EDD facility v%s %s, %d devices found\n",
EDD_VERSION, EDD_DATE, edd_num_devices());
if (!edd_num_devices()) {
printk(KERN_INFO "EDD information not available.\n");
return -ENODEV;
}
edd_kset = kset_create_and_add("edd", NULL, firmware_kobj);
if (!edd_kset)
return -ENOMEM;
for (i = 0; i < edd_num_devices(); i++) {
edev = kzalloc(sizeof (*edev), GFP_KERNEL);
if (!edev) {
rc = -ENOMEM;
goto out;
}
rc = edd_device_register(edev, i);
if (rc) {
kfree(edev);
goto out;
}
edd_devices[i] = edev;
}
return 0;
out:
while (--i >= 0)
edd_device_unregister(edd_devices[i]);
kset_unregister(edd_kset);
return rc;
}
static void __exit
edd_exit(void)
{
int i;
struct edd_device *edev;
for (i = 0; i < edd_num_devices(); i++) {
if ((edev = edd_devices[i]))
edd_device_unregister(edev);
}
kset_unregister(edd_kset);
}
late_initcall(edd_init);
module_exit(edd_exit);

View file

@ -0,0 +1,66 @@
menu "EFI (Extensible Firmware Interface) Support"
depends on EFI
config EFI_VARS
tristate "EFI Variable Support via sysfs"
depends on EFI
default n
help
If you say Y here, you are able to get EFI (Extensible Firmware
Interface) variable information via sysfs. You may read,
write, create, and destroy EFI variables through this interface.
Note that using this driver in concert with efibootmgr requires
at least test release version 0.5.0-test3 or later, which is
available from 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_VARS_PSTORE
tristate "Register efivars backend for pstore"
depends on EFI_VARS && PSTORE
default y
help
Say Y here to enable use efivars as a backend to pstore. This
will allow writing console messages, crash dumps, or anything
else supported by pstore to EFI variables.
config EFI_VARS_PSTORE_DEFAULT_DISABLE
bool "Disable using efivars as a pstore backend by default"
depends on EFI_VARS_PSTORE
default n
help
Saying Y here will disable the use of efivars as a storage
backend for pstore by default. This setting can be overridden
using the efivars module's pstore_disable parameter.
config EFI_RUNTIME_MAP
bool "Export efi runtime maps to sysfs"
depends on X86 && EFI && KEXEC
default y
help
Export efi runtime memory maps to /sys/firmware/efi/runtime-map.
That memory map is used for example by kexec to set up efi virtual
mapping the 2nd kernel, but can also be used for debugging purposes.
See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map.
config EFI_PARAMS_FROM_FDT
bool
help
Select this config option from the architecture Kconfig if
the EFI runtime support gets system table address, memory
map address, and other parameters from the device tree.
config EFI_RUNTIME_WRAPPERS
bool
config EFI_ARMSTUB
bool
endmenu
config UEFI_CPER
bool

View file

@ -0,0 +1,10 @@
#
# Makefile for linux kernel
#
obj-$(CONFIG_EFI) += efi.o vars.o reboot.o
obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
obj-$(CONFIG_UEFI_CPER) += cper.o
obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o
obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
obj-$(CONFIG_EFI_ARM_STUB) += libstub/

492
drivers/firmware/efi/cper.c Normal file
View file

@ -0,0 +1,492 @@
/*
* UEFI Common Platform Error Record (CPER) support
*
* Copyright (C) 2010, Intel Corp.
* Author: Huang Ying <ying.huang@intel.com>
*
* CPER is the format used to describe platform hardware error by
* various tables, such as ERST, BERT and HEST etc.
*
* For more information about CPER, please refer to Appendix N of UEFI
* Specification version 2.4.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/cper.h>
#include <linux/dmi.h>
#include <linux/acpi.h>
#include <linux/pci.h>
#include <linux/aer.h>
#define INDENT_SP " "
static char rcd_decode_str[CPER_REC_LEN];
/*
* CPER record ID need to be unique even after reboot, because record
* ID is used as index for ERST storage, while CPER records from
* multiple boot may co-exist in ERST.
*/
u64 cper_next_record_id(void)
{
static atomic64_t seq;
if (!atomic64_read(&seq))
atomic64_set(&seq, ((u64)get_seconds()) << 32);
return atomic64_inc_return(&seq);
}
EXPORT_SYMBOL_GPL(cper_next_record_id);
static const char * const severity_strs[] = {
"recoverable",
"fatal",
"corrected",
"info",
};
const char *cper_severity_str(unsigned int severity)
{
return severity < ARRAY_SIZE(severity_strs) ?
severity_strs[severity] : "unknown";
}
EXPORT_SYMBOL_GPL(cper_severity_str);
/*
* cper_print_bits - print strings for set bits
* @pfx: prefix for each line, including log level and prefix string
* @bits: bit mask
* @strs: string array, indexed by bit position
* @strs_size: size of the string array: @strs
*
* For each set bit in @bits, print the corresponding string in @strs.
* If the output length is longer than 80, multiple line will be
* printed, with @pfx is printed at the beginning of each line.
*/
void cper_print_bits(const char *pfx, unsigned int bits,
const char * const strs[], unsigned int strs_size)
{
int i, len = 0;
const char *str;
char buf[84];
for (i = 0; i < strs_size; i++) {
if (!(bits & (1U << i)))
continue;
str = strs[i];
if (!str)
continue;
if (len && len + strlen(str) + 2 > 80) {
printk("%s\n", buf);
len = 0;
}
if (!len)
len = snprintf(buf, sizeof(buf), "%s%s", pfx, str);
else
len += snprintf(buf+len, sizeof(buf)-len, ", %s", str);
}
if (len)
printk("%s\n", buf);
}
static const char * const proc_type_strs[] = {
"IA32/X64",
"IA64",
};
static const char * const proc_isa_strs[] = {
"IA32",
"IA64",
"X64",
};
static const char * const proc_error_type_strs[] = {
"cache error",
"TLB error",
"bus error",
"micro-architectural error",
};
static const char * const proc_op_strs[] = {
"unknown or generic",
"data read",
"data write",
"instruction execution",
};
static const char * const proc_flag_strs[] = {
"restartable",
"precise IP",
"overflow",
"corrected",
};
static void cper_print_proc_generic(const char *pfx,
const struct cper_sec_proc_generic *proc)
{
if (proc->validation_bits & CPER_PROC_VALID_TYPE)
printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type,
proc->proc_type < ARRAY_SIZE(proc_type_strs) ?
proc_type_strs[proc->proc_type] : "unknown");
if (proc->validation_bits & CPER_PROC_VALID_ISA)
printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa,
proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ?
proc_isa_strs[proc->proc_isa] : "unknown");
if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) {
printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type);
cper_print_bits(pfx, proc->proc_error_type,
proc_error_type_strs,
ARRAY_SIZE(proc_error_type_strs));
}
if (proc->validation_bits & CPER_PROC_VALID_OPERATION)
printk("%s""operation: %d, %s\n", pfx, proc->operation,
proc->operation < ARRAY_SIZE(proc_op_strs) ?
proc_op_strs[proc->operation] : "unknown");
if (proc->validation_bits & CPER_PROC_VALID_FLAGS) {
printk("%s""flags: 0x%02x\n", pfx, proc->flags);
cper_print_bits(pfx, proc->flags, proc_flag_strs,
ARRAY_SIZE(proc_flag_strs));
}
if (proc->validation_bits & CPER_PROC_VALID_LEVEL)
printk("%s""level: %d\n", pfx, proc->level);
if (proc->validation_bits & CPER_PROC_VALID_VERSION)
printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version);
if (proc->validation_bits & CPER_PROC_VALID_ID)
printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id);
if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS)
printk("%s""target_address: 0x%016llx\n",
pfx, proc->target_addr);
if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID)
printk("%s""requestor_id: 0x%016llx\n",
pfx, proc->requestor_id);
if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID)
printk("%s""responder_id: 0x%016llx\n",
pfx, proc->responder_id);
if (proc->validation_bits & CPER_PROC_VALID_IP)
printk("%s""IP: 0x%016llx\n", pfx, proc->ip);
}
static const char * const mem_err_type_strs[] = {
"unknown",
"no error",
"single-bit ECC",
"multi-bit ECC",
"single-symbol chipkill ECC",
"multi-symbol chipkill ECC",
"master abort",
"target abort",
"parity error",
"watchdog timeout",
"invalid address",
"mirror Broken",
"memory sparing",
"scrub corrected error",
"scrub uncorrected error",
"physical memory map-out event",
};
const char *cper_mem_err_type_str(unsigned int etype)
{
return etype < ARRAY_SIZE(mem_err_type_strs) ?
mem_err_type_strs[etype] : "unknown";
}
EXPORT_SYMBOL_GPL(cper_mem_err_type_str);
static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
{
u32 len, n;
if (!msg)
return 0;
n = 0;
len = CPER_REC_LEN - 1;
if (mem->validation_bits & CPER_MEM_VALID_NODE)
n += scnprintf(msg + n, len - n, "node: %d ", mem->node);
if (mem->validation_bits & CPER_MEM_VALID_CARD)
n += scnprintf(msg + n, len - n, "card: %d ", mem->card);
if (mem->validation_bits & CPER_MEM_VALID_MODULE)
n += scnprintf(msg + n, len - n, "module: %d ", mem->module);
if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank);
if (mem->validation_bits & CPER_MEM_VALID_BANK)
n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank);
if (mem->validation_bits & CPER_MEM_VALID_DEVICE)
n += scnprintf(msg + n, len - n, "device: %d ", mem->device);
if (mem->validation_bits & CPER_MEM_VALID_ROW)
n += scnprintf(msg + n, len - n, "row: %d ", mem->row);
if (mem->validation_bits & CPER_MEM_VALID_COLUMN)
n += scnprintf(msg + n, len - n, "column: %d ", mem->column);
if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION)
n += scnprintf(msg + n, len - n, "bit_position: %d ",
mem->bit_pos);
if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ",
mem->requestor_id);
if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ",
mem->responder_id);
if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID)
scnprintf(msg + n, len - n, "target_id: 0x%016llx ",
mem->target_id);
msg[n] = '\0';
return n;
}
static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
{
u32 len, n;
const char *bank = NULL, *device = NULL;
if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE))
return 0;
n = 0;
len = CPER_REC_LEN - 1;
dmi_memdev_name(mem->mem_dev_handle, &bank, &device);
if (bank && device)
n = snprintf(msg, len, "DIMM location: %s %s ", bank, device);
else
n = snprintf(msg, len,
"DIMM location: not present. DMI handle: 0x%.4x ",
mem->mem_dev_handle);
msg[n] = '\0';
return n;
}
void cper_mem_err_pack(const struct cper_sec_mem_err *mem,
struct cper_mem_err_compact *cmem)
{
cmem->validation_bits = mem->validation_bits;
cmem->node = mem->node;
cmem->card = mem->card;
cmem->module = mem->module;
cmem->bank = mem->bank;
cmem->device = mem->device;
cmem->row = mem->row;
cmem->column = mem->column;
cmem->bit_pos = mem->bit_pos;
cmem->requestor_id = mem->requestor_id;
cmem->responder_id = mem->responder_id;
cmem->target_id = mem->target_id;
cmem->rank = mem->rank;
cmem->mem_array_handle = mem->mem_array_handle;
cmem->mem_dev_handle = mem->mem_dev_handle;
}
const char *cper_mem_err_unpack(struct trace_seq *p,
struct cper_mem_err_compact *cmem)
{
const char *ret = p->buffer + p->len;
if (cper_mem_err_location(cmem, rcd_decode_str))
trace_seq_printf(p, "%s", rcd_decode_str);
if (cper_dimm_err_location(cmem, rcd_decode_str))
trace_seq_printf(p, "%s", rcd_decode_str);
trace_seq_putc(p, '\0');
return ret;
}
static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem)
{
struct cper_mem_err_compact cmem;
if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS)
printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status);
if (mem->validation_bits & CPER_MEM_VALID_PA)
printk("%s""physical_address: 0x%016llx\n",
pfx, mem->physical_addr);
if (mem->validation_bits & CPER_MEM_VALID_PA_MASK)
printk("%s""physical_address_mask: 0x%016llx\n",
pfx, mem->physical_addr_mask);
cper_mem_err_pack(mem, &cmem);
if (cper_mem_err_location(&cmem, rcd_decode_str))
printk("%s%s\n", pfx, rcd_decode_str);
if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) {
u8 etype = mem->error_type;
printk("%s""error_type: %d, %s\n", pfx, etype,
cper_mem_err_type_str(etype));
}
if (cper_dimm_err_location(&cmem, rcd_decode_str))
printk("%s%s\n", pfx, rcd_decode_str);
}
static const char * const pcie_port_type_strs[] = {
"PCIe end point",
"legacy PCI end point",
"unknown",
"unknown",
"root port",
"upstream switch port",
"downstream switch port",
"PCIe to PCI/PCI-X bridge",
"PCI/PCI-X to PCIe bridge",
"root complex integrated endpoint device",
"root complex event collector",
};
static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie,
const struct acpi_hest_generic_data *gdata)
{
if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE)
printk("%s""port_type: %d, %s\n", pfx, pcie->port_type,
pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ?
pcie_port_type_strs[pcie->port_type] : "unknown");
if (pcie->validation_bits & CPER_PCIE_VALID_VERSION)
printk("%s""version: %d.%d\n", pfx,
pcie->version.major, pcie->version.minor);
if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS)
printk("%s""command: 0x%04x, status: 0x%04x\n", pfx,
pcie->command, pcie->status);
if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) {
const __u8 *p;
printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx,
pcie->device_id.segment, pcie->device_id.bus,
pcie->device_id.device, pcie->device_id.function);
printk("%s""slot: %d\n", pfx,
pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT);
printk("%s""secondary_bus: 0x%02x\n", pfx,
pcie->device_id.secondary_bus);
printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx,
pcie->device_id.vendor_id, pcie->device_id.device_id);
p = pcie->device_id.class_code;
printk("%s""class_code: %02x%02x%02x\n", pfx, p[0], p[1], p[2]);
}
if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER)
printk("%s""serial number: 0x%04x, 0x%04x\n", pfx,
pcie->serial_number.lower, pcie->serial_number.upper);
if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS)
printk(
"%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n",
pfx, pcie->bridge.secondary_status, pcie->bridge.control);
}
static void cper_estatus_print_section(
const char *pfx, const struct acpi_hest_generic_data *gdata, int sec_no)
{
uuid_le *sec_type = (uuid_le *)gdata->section_type;
__u16 severity;
char newpfx[64];
severity = gdata->error_severity;
printk("%s""Error %d, type: %s\n", pfx, sec_no,
cper_severity_str(severity));
if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
printk("%s""fru_id: %pUl\n", pfx, (uuid_le *)gdata->fru_id);
if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text);
snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP);
if (!uuid_le_cmp(*sec_type, CPER_SEC_PROC_GENERIC)) {
struct cper_sec_proc_generic *proc_err = (void *)(gdata + 1);
printk("%s""section_type: general processor error\n", newpfx);
if (gdata->error_data_length >= sizeof(*proc_err))
cper_print_proc_generic(newpfx, proc_err);
else
goto err_section_too_small;
} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PLATFORM_MEM)) {
struct cper_sec_mem_err *mem_err = (void *)(gdata + 1);
printk("%s""section_type: memory error\n", newpfx);
if (gdata->error_data_length >= sizeof(*mem_err))
cper_print_mem(newpfx, mem_err);
else
goto err_section_too_small;
} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PCIE)) {
struct cper_sec_pcie *pcie = (void *)(gdata + 1);
printk("%s""section_type: PCIe error\n", newpfx);
if (gdata->error_data_length >= sizeof(*pcie))
cper_print_pcie(newpfx, pcie, gdata);
else
goto err_section_too_small;
} else
printk("%s""section type: unknown, %pUl\n", newpfx, sec_type);
return;
err_section_too_small:
pr_err(FW_WARN "error section length is too small\n");
}
void cper_estatus_print(const char *pfx,
const struct acpi_hest_generic_status *estatus)
{
struct acpi_hest_generic_data *gdata;
unsigned int data_len, gedata_len;
int sec_no = 0;
char newpfx[64];
__u16 severity;
severity = estatus->error_severity;
if (severity == CPER_SEV_CORRECTED)
printk("%s%s\n", pfx,
"It has been corrected by h/w "
"and requires no further action");
printk("%s""event severity: %s\n", pfx, cper_severity_str(severity));
data_len = estatus->data_length;
gdata = (struct acpi_hest_generic_data *)(estatus + 1);
snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP);
while (data_len >= sizeof(*gdata)) {
gedata_len = gdata->error_data_length;
cper_estatus_print_section(newpfx, gdata, sec_no);
data_len -= gedata_len + sizeof(*gdata);
gdata = (void *)(gdata + 1) + gedata_len;
sec_no++;
}
}
EXPORT_SYMBOL_GPL(cper_estatus_print);
int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus)
{
if (estatus->data_length &&
estatus->data_length < sizeof(struct acpi_hest_generic_data))
return -EINVAL;
if (estatus->raw_data_length &&
estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(cper_estatus_check_header);
int cper_estatus_check(const struct acpi_hest_generic_status *estatus)
{
struct acpi_hest_generic_data *gdata;
unsigned int data_len, gedata_len;
int rc;
rc = cper_estatus_check_header(estatus);
if (rc)
return rc;
data_len = estatus->data_length;
gdata = (struct acpi_hest_generic_data *)(estatus + 1);
while (data_len >= sizeof(*gdata)) {
gedata_len = gdata->error_data_length;
if (gedata_len > data_len - sizeof(*gdata))
return -EINVAL;
data_len -= gedata_len + sizeof(*gdata);
gdata = (void *)(gdata + 1) + gedata_len;
}
if (data_len)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(cper_estatus_check);

View file

@ -0,0 +1,402 @@
#include <linux/efi.h>
#include <linux/module.h>
#include <linux/pstore.h>
#include <linux/slab.h>
#include <linux/ucs2_string.h>
#define DUMP_NAME_LEN 52
static bool efivars_pstore_disable =
IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
#define PSTORE_EFI_ATTRIBUTES \
(EFI_VARIABLE_NON_VOLATILE | \
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
static int efi_pstore_open(struct pstore_info *psi)
{
psi->data = NULL;
return 0;
}
static int efi_pstore_close(struct pstore_info *psi)
{
psi->data = NULL;
return 0;
}
struct pstore_read_data {
u64 *id;
enum pstore_type_id *type;
int *count;
struct timespec *timespec;
bool *compressed;
char **buf;
};
static inline u64 generic_id(unsigned long timestamp,
unsigned int part, int count)
{
return ((u64) timestamp * 100 + part) * 1000 + count;
}
static int efi_pstore_read_func(struct efivar_entry *entry, void *data)
{
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
struct pstore_read_data *cb_data = data;
char name[DUMP_NAME_LEN], data_type;
int i;
int cnt;
unsigned int part;
unsigned long time, size;
if (efi_guidcmp(entry->var.VendorGuid, vendor))
return 0;
for (i = 0; i < DUMP_NAME_LEN; i++)
name[i] = entry->var.VariableName[i];
if (sscanf(name, "dump-type%u-%u-%d-%lu-%c",
cb_data->type, &part, &cnt, &time, &data_type) == 5) {
*cb_data->id = generic_id(time, part, cnt);
*cb_data->count = cnt;
cb_data->timespec->tv_sec = time;
cb_data->timespec->tv_nsec = 0;
if (data_type == 'C')
*cb_data->compressed = true;
else
*cb_data->compressed = false;
} else if (sscanf(name, "dump-type%u-%u-%d-%lu",
cb_data->type, &part, &cnt, &time) == 4) {
*cb_data->id = generic_id(time, part, cnt);
*cb_data->count = cnt;
cb_data->timespec->tv_sec = time;
cb_data->timespec->tv_nsec = 0;
*cb_data->compressed = false;
} else if (sscanf(name, "dump-type%u-%u-%lu",
cb_data->type, &part, &time) == 3) {
/*
* Check if an old format,
* which doesn't support holding
* multiple logs, remains.
*/
*cb_data->id = generic_id(time, part, 0);
*cb_data->count = 0;
cb_data->timespec->tv_sec = time;
cb_data->timespec->tv_nsec = 0;
*cb_data->compressed = false;
} else
return 0;
entry->var.DataSize = 1024;
__efivar_entry_get(entry, &entry->var.Attributes,
&entry->var.DataSize, entry->var.Data);
size = entry->var.DataSize;
memcpy(*cb_data->buf, entry->var.Data,
(size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
return size;
}
/**
* efi_pstore_scan_sysfs_enter
* @entry: scanning entry
* @next: next entry
* @head: list head
*/
static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
struct efivar_entry *next,
struct list_head *head)
{
pos->scanning = true;
if (&next->list != head)
next->scanning = true;
}
/**
* __efi_pstore_scan_sysfs_exit
* @entry: deleting entry
* @turn_off_scanning: Check if a scanning flag should be turned off
*/
static inline void __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
bool turn_off_scanning)
{
if (entry->deleting) {
list_del(&entry->list);
efivar_entry_iter_end();
efivar_unregister(entry);
efivar_entry_iter_begin();
} else if (turn_off_scanning)
entry->scanning = false;
}
/**
* efi_pstore_scan_sysfs_exit
* @pos: scanning entry
* @next: next entry
* @head: list head
* @stop: a flag checking if scanning will stop
*/
static void efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
struct efivar_entry *next,
struct list_head *head, bool stop)
{
__efi_pstore_scan_sysfs_exit(pos, true);
if (stop)
__efi_pstore_scan_sysfs_exit(next, &next->list != head);
}
/**
* efi_pstore_sysfs_entry_iter
*
* @data: function-specific data to pass to callback
* @pos: entry to begin iterating from
*
* You MUST call efivar_enter_iter_begin() before this function, and
* efivar_entry_iter_end() afterwards.
*
* It is possible to begin iteration from an arbitrary entry within
* the list by passing @pos. @pos is updated on return to point to
* the next entry of the last one passed to efi_pstore_read_func().
* To begin iterating from the beginning of the list @pos must be %NULL.
*/
static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos)
{
struct efivar_entry *entry, *n;
struct list_head *head = &efivar_sysfs_list;
int size = 0;
if (!*pos) {
list_for_each_entry_safe(entry, n, head, list) {
efi_pstore_scan_sysfs_enter(entry, n, head);
size = efi_pstore_read_func(entry, data);
efi_pstore_scan_sysfs_exit(entry, n, head, size < 0);
if (size)
break;
}
*pos = n;
return size;
}
list_for_each_entry_safe_from((*pos), n, head, list) {
efi_pstore_scan_sysfs_enter((*pos), n, head);
size = efi_pstore_read_func((*pos), data);
efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
if (size)
break;
}
*pos = n;
return size;
}
/**
* efi_pstore_read
*
* This function returns a size of NVRAM entry logged via efi_pstore_write().
* The meaning and behavior of efi_pstore/pstore are as below.
*
* size > 0: Got data of an entry logged via efi_pstore_write() successfully,
* and pstore filesystem will continue reading subsequent entries.
* size == 0: Entry was not logged via efi_pstore_write(),
* and efi_pstore driver will continue reading subsequent entries.
* size < 0: Failed to get data of entry logging via efi_pstore_write(),
* and pstore will stop reading entry.
*/
static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
int *count, struct timespec *timespec,
char **buf, bool *compressed,
struct pstore_info *psi)
{
struct pstore_read_data data;
ssize_t size;
data.id = id;
data.type = type;
data.count = count;
data.timespec = timespec;
data.compressed = compressed;
data.buf = buf;
*data.buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
if (!*data.buf)
return -ENOMEM;
efivar_entry_iter_begin();
size = efi_pstore_sysfs_entry_iter(&data,
(struct efivar_entry **)&psi->data);
efivar_entry_iter_end();
if (size <= 0)
kfree(*data.buf);
return size;
}
static int efi_pstore_write(enum pstore_type_id type,
enum kmsg_dump_reason reason, u64 *id,
unsigned int part, int count, bool compressed, size_t size,
struct pstore_info *psi)
{
char name[DUMP_NAME_LEN];
efi_char16_t efi_name[DUMP_NAME_LEN];
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
int i, ret = 0;
sprintf(name, "dump-type%u-%u-%d-%lu-%c", type, part, count,
get_seconds(), compressed ? 'C' : 'D');
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
!pstore_cannot_block_path(reason),
size, psi->buf);
if (reason == KMSG_DUMP_OOPS)
efivar_run_worker();
*id = part;
return ret;
};
struct pstore_erase_data {
u64 id;
enum pstore_type_id type;
int count;
struct timespec time;
efi_char16_t *name;
};
/*
* Clean up an entry with the same name
*/
static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
{
struct pstore_erase_data *ed = data;
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
efi_char16_t efi_name_old[DUMP_NAME_LEN];
efi_char16_t *efi_name = ed->name;
unsigned long ucs2_len = ucs2_strlen(ed->name);
char name_old[DUMP_NAME_LEN];
int i;
if (efi_guidcmp(entry->var.VendorGuid, vendor))
return 0;
if (ucs2_strncmp(entry->var.VariableName,
efi_name, (size_t)ucs2_len)) {
/*
* Check if an old format, which doesn't support
* holding multiple logs, remains.
*/
sprintf(name_old, "dump-type%u-%u-%lu", ed->type,
(unsigned int)ed->id, ed->time.tv_sec);
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name_old[i] = name_old[i];
if (ucs2_strncmp(entry->var.VariableName, efi_name_old,
ucs2_strlen(efi_name_old)))
return 0;
}
if (entry->scanning) {
/*
* Skip deletion because this entry will be deleted
* after scanning is completed.
*/
entry->deleting = true;
} else
list_del(&entry->list);
/* found */
__efivar_entry_delete(entry);
return 1;
}
static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
struct timespec time, struct pstore_info *psi)
{
struct pstore_erase_data edata;
struct efivar_entry *entry = NULL;
char name[DUMP_NAME_LEN];
efi_char16_t efi_name[DUMP_NAME_LEN];
int found, i;
unsigned int part;
do_div(id, 1000);
part = do_div(id, 100);
sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, time.tv_sec);
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
edata.id = part;
edata.type = type;
edata.count = count;
edata.time = time;
edata.name = efi_name;
efivar_entry_iter_begin();
found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry);
if (found && !entry->scanning) {
efivar_entry_iter_end();
efivar_unregister(entry);
} else
efivar_entry_iter_end();
return 0;
}
static struct pstore_info efi_pstore_info = {
.owner = THIS_MODULE,
.name = "efi",
.flags = PSTORE_FLAGS_FRAGILE,
.open = efi_pstore_open,
.close = efi_pstore_close,
.read = efi_pstore_read,
.write = efi_pstore_write,
.erase = efi_pstore_erase,
};
static __init int efivars_pstore_init(void)
{
if (!efi_enabled(EFI_RUNTIME_SERVICES))
return 0;
if (!efivars_kobject())
return 0;
if (efivars_pstore_disable)
return 0;
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
if (!efi_pstore_info.buf)
return -ENOMEM;
efi_pstore_info.bufsize = 1024;
spin_lock_init(&efi_pstore_info.buf_lock);
if (pstore_register(&efi_pstore_info)) {
kfree(efi_pstore_info.buf);
efi_pstore_info.buf = NULL;
efi_pstore_info.bufsize = 0;
}
return 0;
}
static __exit void efivars_pstore_exit(void)
{
}
module_init(efivars_pstore_init);
module_exit(efivars_pstore_exit);
MODULE_DESCRIPTION("EFI variable backend for pstore");
MODULE_LICENSE("GPL");

504
drivers/firmware/efi/efi.c Normal file
View file

@ -0,0 +1,504 @@
/*
* efi.c - EFI subsystem
*
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
* Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
*
* This code registers /sys/firmware/efi{,/efivars} when EFI is supported,
* allowing the efivarfs to be mounted or the efivars module to be loaded.
* The existance of /sys/firmware/efi may also be used by userspace to
* determine that the system supports EFI.
*
* This file is released under the GPLv2.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
struct efi __read_mostly efi = {
.mps = EFI_INVALID_TABLE_ADDR,
.acpi = EFI_INVALID_TABLE_ADDR,
.acpi20 = EFI_INVALID_TABLE_ADDR,
.smbios = EFI_INVALID_TABLE_ADDR,
.sal_systab = EFI_INVALID_TABLE_ADDR,
.boot_info = EFI_INVALID_TABLE_ADDR,
.hcdp = EFI_INVALID_TABLE_ADDR,
.uga = EFI_INVALID_TABLE_ADDR,
.uv_systab = EFI_INVALID_TABLE_ADDR,
.fw_vendor = EFI_INVALID_TABLE_ADDR,
.runtime = EFI_INVALID_TABLE_ADDR,
.config_table = EFI_INVALID_TABLE_ADDR,
};
EXPORT_SYMBOL(efi);
static bool disable_runtime;
static int __init setup_noefi(char *arg)
{
disable_runtime = true;
return 0;
}
early_param("noefi", setup_noefi);
bool efi_runtime_disabled(void)
{
return disable_runtime;
}
static int __init parse_efi_cmdline(char *str)
{
if (parse_option_str(str, "noruntime"))
disable_runtime = true;
return 0;
}
early_param("efi", parse_efi_cmdline);
static struct kobject *efi_kobj;
static struct kobject *efivars_kobj;
/*
* Let's not leave out systab information that snuck into
* the efivars driver
*/
static ssize_t systab_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
char *str = buf;
if (!kobj || !buf)
return -EINVAL;
if (efi.mps != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "MPS=0x%lx\n", efi.mps);
if (efi.acpi20 != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20);
if (efi.acpi != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "ACPI=0x%lx\n", efi.acpi);
if (efi.smbios != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios);
if (efi.hcdp != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp);
if (efi.boot_info != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info);
if (efi.uga != EFI_INVALID_TABLE_ADDR)
str += sprintf(str, "UGA=0x%lx\n", efi.uga);
return str - buf;
}
static struct kobj_attribute efi_attr_systab =
__ATTR(systab, 0400, systab_show, NULL);
#define EFI_FIELD(var) efi.var
#define EFI_ATTR_SHOW(name) \
static ssize_t name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \
}
EFI_ATTR_SHOW(fw_vendor);
EFI_ATTR_SHOW(runtime);
EFI_ATTR_SHOW(config_table);
static struct kobj_attribute efi_attr_fw_vendor = __ATTR_RO(fw_vendor);
static struct kobj_attribute efi_attr_runtime = __ATTR_RO(runtime);
static struct kobj_attribute efi_attr_config_table = __ATTR_RO(config_table);
static struct attribute *efi_subsys_attrs[] = {
&efi_attr_systab.attr,
&efi_attr_fw_vendor.attr,
&efi_attr_runtime.attr,
&efi_attr_config_table.attr,
NULL,
};
static umode_t efi_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
if (attr == &efi_attr_fw_vendor.attr) {
if (efi_enabled(EFI_PARAVIRT) ||
efi.fw_vendor == EFI_INVALID_TABLE_ADDR)
return 0;
} else if (attr == &efi_attr_runtime.attr) {
if (efi.runtime == EFI_INVALID_TABLE_ADDR)
return 0;
} else if (attr == &efi_attr_config_table.attr) {
if (efi.config_table == EFI_INVALID_TABLE_ADDR)
return 0;
}
return attr->mode;
}
static struct attribute_group efi_subsys_attr_group = {
.attrs = efi_subsys_attrs,
.is_visible = efi_attr_is_visible,
};
static struct efivars generic_efivars;
static struct efivar_operations generic_ops;
static int generic_ops_register(void)
{
generic_ops.get_variable = efi.get_variable;
generic_ops.set_variable = efi.set_variable;
generic_ops.get_next_variable = efi.get_next_variable;
generic_ops.query_variable_store = efi_query_variable_store;
return efivars_register(&generic_efivars, &generic_ops, efi_kobj);
}
static void generic_ops_unregister(void)
{
efivars_unregister(&generic_efivars);
}
/*
* We register the efi subsystem with the firmware subsystem and the
* efivars subsystem with the efi subsystem, if the system was booted with
* EFI.
*/
static int __init efisubsys_init(void)
{
int error;
if (!efi_enabled(EFI_BOOT))
return 0;
/* We register the efi directory at /sys/firmware/efi */
efi_kobj = kobject_create_and_add("efi", firmware_kobj);
if (!efi_kobj) {
pr_err("efi: Firmware registration failed.\n");
return -ENOMEM;
}
error = generic_ops_register();
if (error)
goto err_put;
error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group);
if (error) {
pr_err("efi: Sysfs attribute export failed with error %d.\n",
error);
goto err_unregister;
}
error = efi_runtime_map_init(efi_kobj);
if (error)
goto err_remove_group;
/* and the standard mountpoint for efivarfs */
efivars_kobj = kobject_create_and_add("efivars", efi_kobj);
if (!efivars_kobj) {
pr_err("efivars: Subsystem registration failed.\n");
error = -ENOMEM;
goto err_remove_group;
}
return 0;
err_remove_group:
sysfs_remove_group(efi_kobj, &efi_subsys_attr_group);
err_unregister:
generic_ops_unregister();
err_put:
kobject_put(efi_kobj);
return error;
}
subsys_initcall(efisubsys_init);
/*
* We can't ioremap data in EFI boot services RAM, because we've already mapped
* it as RAM. So, look it up in the existing EFI memory map instead. Only
* callable after efi_enter_virtual_mode and before efi_free_boot_services.
*/
void __iomem *efi_lookup_mapped_addr(u64 phys_addr)
{
struct efi_memory_map *map;
void *p;
map = efi.memmap;
if (!map)
return NULL;
if (WARN_ON(!map->map))
return NULL;
for (p = map->map; p < map->map_end; p += map->desc_size) {
efi_memory_desc_t *md = p;
u64 size = md->num_pages << EFI_PAGE_SHIFT;
u64 end = md->phys_addr + size;
if (!(md->attribute & EFI_MEMORY_RUNTIME) &&
md->type != EFI_BOOT_SERVICES_CODE &&
md->type != EFI_BOOT_SERVICES_DATA)
continue;
if (!md->virt_addr)
continue;
if (phys_addr >= md->phys_addr && phys_addr < end) {
phys_addr += md->virt_addr - md->phys_addr;
return (__force void __iomem *)(unsigned long)phys_addr;
}
}
return NULL;
}
static __initdata efi_config_table_type_t common_tables[] = {
{ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20},
{ACPI_TABLE_GUID, "ACPI", &efi.acpi},
{HCDP_TABLE_GUID, "HCDP", &efi.hcdp},
{MPS_TABLE_GUID, "MPS", &efi.mps},
{SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab},
{SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios},
{UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga},
{NULL_GUID, NULL, NULL},
};
static __init int match_config_table(efi_guid_t *guid,
unsigned long table,
efi_config_table_type_t *table_types)
{
u8 str[EFI_VARIABLE_GUID_LEN + 1];
int i;
if (table_types) {
efi_guid_unparse(guid, str);
for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) {
efi_guid_unparse(&table_types[i].guid, str);
if (!efi_guidcmp(*guid, table_types[i].guid)) {
*(table_types[i].ptr) = table;
pr_cont(" %s=0x%lx ",
table_types[i].name, table);
return 1;
}
}
}
return 0;
}
int __init efi_config_init(efi_config_table_type_t *arch_tables)
{
void *config_tables, *tablep;
int i, sz;
if (efi_enabled(EFI_64BIT))
sz = sizeof(efi_config_table_64_t);
else
sz = sizeof(efi_config_table_32_t);
/*
* Let's see what config tables the firmware passed to us.
*/
config_tables = early_memremap(efi.systab->tables,
efi.systab->nr_tables * sz);
if (config_tables == NULL) {
pr_err("Could not map Configuration table!\n");
return -ENOMEM;
}
tablep = config_tables;
pr_info("");
for (i = 0; i < efi.systab->nr_tables; i++) {
efi_guid_t guid;
unsigned long table;
if (efi_enabled(EFI_64BIT)) {
u64 table64;
guid = ((efi_config_table_64_t *)tablep)->guid;
table64 = ((efi_config_table_64_t *)tablep)->table;
table = table64;
#ifndef CONFIG_64BIT
if (table64 >> 32) {
pr_cont("\n");
pr_err("Table located above 4GB, disabling EFI.\n");
early_memunmap(config_tables,
efi.systab->nr_tables * sz);
return -EINVAL;
}
#endif
} else {
guid = ((efi_config_table_32_t *)tablep)->guid;
table = ((efi_config_table_32_t *)tablep)->table;
}
if (!match_config_table(&guid, table, common_tables))
match_config_table(&guid, table, arch_tables);
tablep += sz;
}
pr_cont("\n");
early_memunmap(config_tables, efi.systab->nr_tables * sz);
set_bit(EFI_CONFIG_TABLES, &efi.flags);
return 0;
}
#ifdef CONFIG_EFI_VARS_MODULE
static int __init efi_load_efivars(void)
{
struct platform_device *pdev;
if (!efi_enabled(EFI_RUNTIME_SERVICES))
return 0;
pdev = platform_device_register_simple("efivars", 0, NULL, 0);
return IS_ERR(pdev) ? PTR_ERR(pdev) : 0;
}
device_initcall(efi_load_efivars);
#endif
#ifdef CONFIG_EFI_PARAMS_FROM_FDT
#define UEFI_PARAM(name, prop, field) \
{ \
{ name }, \
{ prop }, \
offsetof(struct efi_fdt_params, field), \
FIELD_SIZEOF(struct efi_fdt_params, field) \
}
static __initdata struct {
const char name[32];
const char propname[32];
int offset;
int size;
} dt_params[] = {
UEFI_PARAM("System Table", "linux,uefi-system-table", system_table),
UEFI_PARAM("MemMap Address", "linux,uefi-mmap-start", mmap),
UEFI_PARAM("MemMap Size", "linux,uefi-mmap-size", mmap_size),
UEFI_PARAM("MemMap Desc. Size", "linux,uefi-mmap-desc-size", desc_size),
UEFI_PARAM("MemMap Desc. Version", "linux,uefi-mmap-desc-ver", desc_ver)
};
struct param_info {
int verbose;
int found;
void *params;
};
static int __init fdt_find_uefi_params(unsigned long node, const char *uname,
int depth, void *data)
{
struct param_info *info = data;
const void *prop;
void *dest;
u64 val;
int i, len;
if (depth != 1 ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
for (i = 0; i < ARRAY_SIZE(dt_params); i++) {
prop = of_get_flat_dt_prop(node, dt_params[i].propname, &len);
if (!prop)
return 0;
dest = info->params + dt_params[i].offset;
info->found++;
val = of_read_number(prop, len / sizeof(u32));
if (dt_params[i].size == sizeof(u32))
*(u32 *)dest = val;
else
*(u64 *)dest = val;
if (info->verbose)
pr_info(" %s: 0x%0*llx\n", dt_params[i].name,
dt_params[i].size * 2, val);
}
return 1;
}
int __init efi_get_fdt_params(struct efi_fdt_params *params, int verbose)
{
struct param_info info;
int ret;
pr_info("Getting EFI parameters from FDT:\n");
info.verbose = verbose;
info.found = 0;
info.params = params;
ret = of_scan_flat_dt(fdt_find_uefi_params, &info);
if (!info.found)
pr_info("UEFI not found.\n");
else if (!ret)
pr_err("Can't find '%s' in device tree!\n",
dt_params[info.found].name);
return ret;
}
#endif /* CONFIG_EFI_PARAMS_FROM_FDT */
static __initdata char memory_type_name[][20] = {
"Reserved",
"Loader Code",
"Loader Data",
"Boot Code",
"Boot Data",
"Runtime Code",
"Runtime Data",
"Conventional Memory",
"Unusable Memory",
"ACPI Reclaim Memory",
"ACPI Memory NVS",
"Memory Mapped I/O",
"MMIO Port Space",
"PAL Code"
};
char * __init efi_md_typeattr_format(char *buf, size_t size,
const efi_memory_desc_t *md)
{
char *pos;
int type_len;
u64 attr;
pos = buf;
if (md->type >= ARRAY_SIZE(memory_type_name))
type_len = snprintf(pos, size, "[type=%u", md->type);
else
type_len = snprintf(pos, size, "[%-*s",
(int)(sizeof(memory_type_name[0]) - 1),
memory_type_name[md->type]);
if (type_len >= size)
return buf;
pos += type_len;
size -= type_len;
attr = md->attribute;
if (attr & ~(EFI_MEMORY_UC | EFI_MEMORY_WC | EFI_MEMORY_WT |
EFI_MEMORY_WB | EFI_MEMORY_UCE | EFI_MEMORY_WP |
EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RUNTIME))
snprintf(pos, size, "|attr=0x%016llx]",
(unsigned long long)attr);
else
snprintf(pos, size, "|%3s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]",
attr & EFI_MEMORY_RUNTIME ? "RUN" : "",
attr & EFI_MEMORY_XP ? "XP" : "",
attr & EFI_MEMORY_RP ? "RP" : "",
attr & EFI_MEMORY_WP ? "WP" : "",
attr & EFI_MEMORY_UCE ? "UCE" : "",
attr & EFI_MEMORY_WB ? "WB" : "",
attr & EFI_MEMORY_WT ? "WT" : "",
attr & EFI_MEMORY_WC ? "WC" : "",
attr & EFI_MEMORY_UC ? "UC" : "");
return buf;
}

View file

@ -0,0 +1,755 @@
/*
* Originally from efivars.c,
*
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
*
* This code takes all variables accessible from EFI runtime and
* exports them via sysfs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Changelog:
*
* 17 May 2004 - Matt Domsch <Matt_Domsch@dell.com>
* remove check for efi_enabled in exit
* add MODULE_VERSION
*
* 26 Apr 2004 - Matt Domsch <Matt_Domsch@dell.com>
* minor bug fixes
*
* 21 Apr 2004 - Matt Tolentino <matthew.e.tolentino@intel.com)
* converted driver to export variable information via sysfs
* and moved to drivers/firmware directory
* bumped revision number to v0.07 to reflect conversion & move
*
* 10 Dec 2002 - Matt Domsch <Matt_Domsch@dell.com>
* fix locking per Peter Chubb's findings
*
* 25 Mar 2002 - Matt Domsch <Matt_Domsch@dell.com>
* move uuid_unparse() to include/asm-ia64/efi.h:efi_guid_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/efi.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ucs2_string.h>
#include <linux/compat.h>
#define EFIVARS_VERSION "0.08"
#define EFIVARS_DATE "2004-May-17"
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
MODULE_DESCRIPTION("sysfs interface to EFI Variables");
MODULE_LICENSE("GPL");
MODULE_VERSION(EFIVARS_VERSION);
MODULE_ALIAS("platform:efivars");
LIST_HEAD(efivar_sysfs_list);
EXPORT_SYMBOL_GPL(efivar_sysfs_list);
static struct kset *efivars_kset;
static struct bin_attribute *efivars_new_var;
static struct bin_attribute *efivars_del_var;
struct compat_efi_variable {
efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
efi_guid_t VendorGuid;
__u32 DataSize;
__u8 Data[1024];
__u32 Status;
__u32 Attributes;
} __packed;
struct efivar_attribute {
struct attribute attr;
ssize_t (*show) (struct efivar_entry *entry, char *buf);
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
};
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
struct efivar_attribute efivar_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode}, \
.show = _show, \
.store = _store, \
};
#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
/*
* Prototype for sysfs creation function
*/
static int
efivar_create_sysfs_entry(struct efivar_entry *new_var);
static ssize_t
efivar_guid_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
char *str = buf;
if (!entry || !buf)
return 0;
efi_guid_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;
if (!entry || !buf)
return -EINVAL;
var->DataSize = 1024;
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
str += sprintf(str,
"EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
if (var->Attributes &
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
str += sprintf(str,
"EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
return str - buf;
}
static ssize_t
efivar_size_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
char *str = buf;
if (!entry || !buf)
return -EINVAL;
var->DataSize = 1024;
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
str += sprintf(str, "0x%lx\n", var->DataSize);
return str - buf;
}
static ssize_t
efivar_data_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
if (!entry || !buf)
return -EINVAL;
var->DataSize = 1024;
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
memcpy(buf, var->Data, var->DataSize);
return var->DataSize;
}
static inline int
sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
unsigned long size, u32 attributes, u8 *data)
{
/*
* If only updating the variable data, then the name
* and guid should remain the same
*/
if (memcmp(name, var->VariableName, sizeof(var->VariableName)) ||
efi_guidcmp(vendor, var->VendorGuid)) {
printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
return -EINVAL;
}
if ((size <= 0) || (attributes == 0)){
printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
return -EINVAL;
}
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
efivar_validate(name, data, size) == false) {
printk(KERN_ERR "efivars: Malformed variable content\n");
return -EINVAL;
}
return 0;
}
static inline bool is_compat(void)
{
if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task())
return true;
return false;
}
static void
copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src)
{
memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN);
memcpy(dst->Data, src->Data, sizeof(src->Data));
dst->VendorGuid = src->VendorGuid;
dst->DataSize = src->DataSize;
dst->Attributes = src->Attributes;
}
/*
* We allow each variable to be edited via rewriting the
* entire efi variable structure.
*/
static ssize_t
efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
{
struct efi_variable *new_var, *var = &entry->var;
efi_char16_t *name;
unsigned long size;
efi_guid_t vendor;
u32 attributes;
u8 *data;
int err;
if (is_compat()) {
struct compat_efi_variable *compat;
if (count != sizeof(*compat))
return -EINVAL;
compat = (struct compat_efi_variable *)buf;
attributes = compat->Attributes;
vendor = compat->VendorGuid;
name = compat->VariableName;
size = compat->DataSize;
data = compat->Data;
err = sanity_check(var, name, vendor, size, attributes, data);
if (err)
return err;
copy_out_compat(&entry->var, compat);
} else {
if (count != sizeof(struct efi_variable))
return -EINVAL;
new_var = (struct efi_variable *)buf;
attributes = new_var->Attributes;
vendor = new_var->VendorGuid;
name = new_var->VariableName;
size = new_var->DataSize;
data = new_var->Data;
err = sanity_check(var, name, vendor, size, attributes, data);
if (err)
return err;
memcpy(&entry->var, new_var, count);
}
err = efivar_entry_set(entry, attributes, size, data, NULL);
if (err) {
printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
return -EIO;
}
return count;
}
static ssize_t
efivar_show_raw(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
struct compat_efi_variable *compat;
size_t size;
if (!entry || !buf)
return 0;
var->DataSize = 1024;
if (efivar_entry_get(entry, &entry->var.Attributes,
&entry->var.DataSize, entry->var.Data))
return -EIO;
if (is_compat()) {
compat = (struct compat_efi_variable *)buf;
size = sizeof(*compat);
memcpy(compat->VariableName, var->VariableName,
EFI_VAR_NAME_LEN);
memcpy(compat->Data, var->Data, sizeof(compat->Data));
compat->VendorGuid = var->VendorGuid;
compat->DataSize = var->DataSize;
compat->Attributes = var->Attributes;
} else {
size = sizeof(*var);
memcpy(buf, var, size);
}
return size;
}
/*
* Generic read/write functions that call the specific functions of
* the attributes...
*/
static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct efivar_entry *var = to_efivar_entry(kobj);
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
ssize_t ret = -EIO;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (efivar_attr->show) {
ret = efivar_attr->show(var, buf);
}
return ret;
}
static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct efivar_entry *var = to_efivar_entry(kobj);
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
ssize_t ret = -EIO;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (efivar_attr->store)
ret = efivar_attr->store(var, buf, count);
return ret;
}
static const struct sysfs_ops efivar_attr_ops = {
.show = efivar_attr_show,
.store = efivar_attr_store,
};
static void efivar_release(struct kobject *kobj)
{
struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj);
kfree(var);
}
static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
static struct attribute *def_attrs[] = {
&efivar_attr_guid.attr,
&efivar_attr_size.attr,
&efivar_attr_attributes.attr,
&efivar_attr_data.attr,
&efivar_attr_raw_var.attr,
NULL,
};
static struct kobj_type efivar_ktype = {
.release = efivar_release,
.sysfs_ops = &efivar_attr_ops,
.default_attrs = def_attrs,
};
static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;
struct efi_variable *new_var = (struct efi_variable *)buf;
struct efivar_entry *new_entry;
bool need_compat = is_compat();
efi_char16_t *name;
unsigned long size;
u32 attributes;
u8 *data;
int err;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (need_compat) {
if (count != sizeof(*compat))
return -EINVAL;
attributes = compat->Attributes;
name = compat->VariableName;
size = compat->DataSize;
data = compat->Data;
} else {
if (count != sizeof(*new_var))
return -EINVAL;
attributes = new_var->Attributes;
name = new_var->VariableName;
size = new_var->DataSize;
data = new_var->Data;
}
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
efivar_validate(name, data, size) == false) {
printk(KERN_ERR "efivars: Malformed variable content\n");
return -EINVAL;
}
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
if (!new_entry)
return -ENOMEM;
if (need_compat)
copy_out_compat(&new_entry->var, compat);
else
memcpy(&new_entry->var, new_var, sizeof(*new_var));
err = efivar_entry_set(new_entry, attributes, size,
data, &efivar_sysfs_list);
if (err) {
if (err == -EEXIST)
err = -EINVAL;
goto out;
}
if (efivar_create_sysfs_entry(new_entry)) {
printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
kfree(new_entry);
}
return count;
out:
kfree(new_entry);
return err;
}
static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct efi_variable *del_var = (struct efi_variable *)buf;
struct compat_efi_variable *compat;
struct efivar_entry *entry;
efi_char16_t *name;
efi_guid_t vendor;
int err = 0;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (is_compat()) {
if (count != sizeof(*compat))
return -EINVAL;
compat = (struct compat_efi_variable *)buf;
name = compat->VariableName;
vendor = compat->VendorGuid;
} else {
if (count != sizeof(*del_var))
return -EINVAL;
name = del_var->VariableName;
vendor = del_var->VendorGuid;
}
efivar_entry_iter_begin();
entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
if (!entry)
err = -EINVAL;
else if (__efivar_entry_delete(entry))
err = -EIO;
if (err) {
efivar_entry_iter_end();
return err;
}
if (!entry->scanning) {
efivar_entry_iter_end();
efivar_unregister(entry);
} else
efivar_entry_iter_end();
/* It's dead Jim.... */
return count;
}
/**
* efivar_create_sysfs_entry - create a new entry in sysfs
* @new_var: efivar entry to create
*
* Returns 1 on failure, 0 on success
*/
static int
efivar_create_sysfs_entry(struct efivar_entry *new_var)
{
int i, short_name_size;
char *short_name;
unsigned long variable_name_size;
efi_char16_t *variable_name;
variable_name = new_var->var.VariableName;
variable_name_size = ucs2_strlen(variable_name) * sizeof(efi_char16_t);
/*
* Length of the variable bytes in ASCII, plus the '-' separator,
* plus the GUID, plus trailing NUL
*/
short_name_size = variable_name_size / sizeof(efi_char16_t)
+ 1 + EFI_VARIABLE_GUID_LEN + 1;
short_name = kzalloc(short_name_size, GFP_KERNEL);
if (!short_name)
return 1;
/* Convert Unicode to normal chars (assume top bits are 0),
ala UTF-8 */
for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
short_name[i] = variable_name[i] & 0xFF;
}
/* This is ugly, but necessary to separate one vendor's
private variables from another's. */
*(short_name + strlen(short_name)) = '-';
efi_guid_unparse(&new_var->var.VendorGuid,
short_name + strlen(short_name));
new_var->kobj.kset = efivars_kset;
i = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
NULL, "%s", short_name);
kfree(short_name);
if (i)
return 1;
kobject_uevent(&new_var->kobj, KOBJ_ADD);
efivar_entry_add(new_var, &efivar_sysfs_list);
return 0;
}
static int
create_efivars_bin_attributes(void)
{
struct bin_attribute *attr;
int error;
/* new_var */
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr)
return -ENOMEM;
attr->attr.name = "new_var";
attr->attr.mode = 0200;
attr->write = efivar_create;
efivars_new_var = attr;
/* del_var */
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr) {
error = -ENOMEM;
goto out_free;
}
attr->attr.name = "del_var";
attr->attr.mode = 0200;
attr->write = efivar_delete;
efivars_del_var = attr;
sysfs_bin_attr_init(efivars_new_var);
sysfs_bin_attr_init(efivars_del_var);
/* Register */
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
if (error) {
printk(KERN_ERR "efivars: unable to create new_var sysfs file"
" due to error %d\n", error);
goto out_free;
}
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
if (error) {
printk(KERN_ERR "efivars: unable to create del_var sysfs file"
" due to error %d\n", error);
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
goto out_free;
}
return 0;
out_free:
kfree(efivars_del_var);
efivars_del_var = NULL;
kfree(efivars_new_var);
efivars_new_var = NULL;
return error;
}
static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor,
unsigned long name_size, void *data)
{
struct efivar_entry *entry = data;
if (efivar_entry_find(name, vendor, &efivar_sysfs_list, false))
return 0;
memcpy(entry->var.VariableName, name, name_size);
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
return 1;
}
static void efivar_update_sysfs_entries(struct work_struct *work)
{
struct efivar_entry *entry;
int err;
/* Add new sysfs entries */
while (1) {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return;
err = efivar_init(efivar_update_sysfs_entry, entry,
true, false, &efivar_sysfs_list);
if (!err)
break;
efivar_create_sysfs_entry(entry);
}
kfree(entry);
}
static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
unsigned long name_size, void *data)
{
struct efivar_entry *entry;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
memcpy(entry->var.VariableName, name, name_size);
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
efivar_create_sysfs_entry(entry);
return 0;
}
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
{
efivar_entry_remove(entry);
efivar_unregister(entry);
return 0;
}
static void efivars_sysfs_exit(void)
{
/* Remove all entries and destroy */
__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);
if (efivars_new_var)
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
if (efivars_del_var)
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
kfree(efivars_new_var);
kfree(efivars_del_var);
kset_unregister(efivars_kset);
}
int efivars_sysfs_init(void)
{
struct kobject *parent_kobj = efivars_kobject();
int error = 0;
if (!efi_enabled(EFI_RUNTIME_SERVICES))
return -ENODEV;
/* No efivars has been registered yet */
if (!parent_kobj)
return 0;
printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
EFIVARS_DATE);
efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
if (!efivars_kset) {
printk(KERN_ERR "efivars: Subsystem registration failed.\n");
return -ENOMEM;
}
efivar_init(efivars_sysfs_callback, NULL, false,
true, &efivar_sysfs_list);
error = create_efivars_bin_attributes();
if (error) {
efivars_sysfs_exit();
return error;
}
INIT_WORK(&efivar_work, efivar_update_sysfs_entries);
return 0;
}
EXPORT_SYMBOL_GPL(efivars_sysfs_init);
module_init(efivars_sysfs_init);
module_exit(efivars_sysfs_exit);

View file

@ -0,0 +1,26 @@
#
# The stub may be linked into the kernel proper or into a separate boot binary,
# but in either case, it executes before the kernel does (with MMU disabled) so
# things like ftrace and stack-protector are likely to cause trouble if left
# enabled, even if doing so doesn't break the build.
#
cflags-$(CONFIG_X86_32) := -march=i386
cflags-$(CONFIG_X86_64) := -mcmodel=small
cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2 \
-fPIC -fno-strict-aliasing -mno-red-zone \
-mno-mmx -mno-sse -DDISABLE_BRANCH_PROFILING
cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS))
cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \
-fno-builtin -fpic -mno-single-pic-base
KBUILD_CFLAGS := $(cflags-y) \
$(call cc-option,-ffreestanding) \
$(call cc-option,-fno-stack-protector)
GCOV_PROFILE := n
lib-y := efi-stub-helper.o
lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o
CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/

View file

@ -0,0 +1,288 @@
/*
* EFI stub implementation that is shared by arm and arm64 architectures.
* This should be #included by the EFI stub implementation files.
*
* Copyright (C) 2013,2014 Linaro Limited
* Roy Franz <roy.franz@linaro.org
* Copyright (C) 2013 Red Hat, Inc.
* Mark Salter <msalter@redhat.com>
*
* This file is part of the Linux kernel, and is made available under the
* terms of the GNU General Public License version 2.
*
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg)
{
static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID;
static efi_char16_t const var_name[] __initconst = {
'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 };
efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable;
unsigned long size = sizeof(u8);
efi_status_t status;
u8 val;
status = f_getvar((efi_char16_t *)var_name, (efi_guid_t *)&var_guid,
NULL, &size, &val);
switch (status) {
case EFI_SUCCESS:
return val;
case EFI_NOT_FOUND:
return 0;
default:
return 1;
}
}
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg,
void *__image, void **__fh)
{
efi_file_io_interface_t *io;
efi_loaded_image_t *image = __image;
efi_file_handle_t *fh;
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
efi_status_t status;
void *handle = (void *)(unsigned long)image->device_handle;
status = sys_table_arg->boottime->handle_protocol(handle,
&fs_proto, (void **)&io);
if (status != EFI_SUCCESS) {
efi_printk(sys_table_arg, "Failed to handle fs_proto\n");
return status;
}
status = io->open_volume(io, &fh);
if (status != EFI_SUCCESS)
efi_printk(sys_table_arg, "Failed to open volume\n");
*__fh = fh;
return status;
}
efi_status_t efi_file_close(void *handle)
{
efi_file_handle_t *fh = handle;
return fh->close(handle);
}
efi_status_t
efi_file_read(void *handle, unsigned long *size, void *addr)
{
efi_file_handle_t *fh = handle;
return fh->read(handle, size, addr);
}
efi_status_t
efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
efi_char16_t *filename_16, void **handle, u64 *file_sz)
{
efi_file_handle_t *h, *fh = __fh;
efi_file_info_t *info;
efi_status_t status;
efi_guid_t info_guid = EFI_FILE_INFO_ID;
unsigned long info_sz;
status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0);
if (status != EFI_SUCCESS) {
efi_printk(sys_table_arg, "Failed to open file: ");
efi_char16_printk(sys_table_arg, filename_16);
efi_printk(sys_table_arg, "\n");
return status;
}
*handle = h;
info_sz = 0;
status = h->get_info(h, &info_guid, &info_sz, NULL);
if (status != EFI_BUFFER_TOO_SMALL) {
efi_printk(sys_table_arg, "Failed to get file info size\n");
return status;
}
grow:
status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA,
info_sz, (void **)&info);
if (status != EFI_SUCCESS) {
efi_printk(sys_table_arg, "Failed to alloc mem for file info\n");
return status;
}
status = h->get_info(h, &info_guid, &info_sz,
info);
if (status == EFI_BUFFER_TOO_SMALL) {
sys_table_arg->boottime->free_pool(info);
goto grow;
}
*file_sz = info->file_size;
sys_table_arg->boottime->free_pool(info);
if (status != EFI_SUCCESS)
efi_printk(sys_table_arg, "Failed to get initrd info\n");
return status;
}
void efi_char16_printk(efi_system_table_t *sys_table_arg,
efi_char16_t *str)
{
struct efi_simple_text_output_protocol *out;
out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out;
out->output_string(out, str);
}
/*
* This function handles the architcture specific differences between arm and
* arm64 regarding where the kernel image must be loaded and any memory that
* must be reserved. On failure it is required to free all
* all allocations it has made.
*/
efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
unsigned long dram_base,
efi_loaded_image_t *image);
/*
* EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint
* that is described in the PE/COFF header. Most of the code is the same
* for both archictectures, with the arch-specific code provided in the
* handle_kernel_image() function.
*/
unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table,
unsigned long *image_addr)
{
efi_loaded_image_t *image;
efi_status_t status;
unsigned long image_size = 0;
unsigned long dram_base;
/* addr/point and size pairs for memory management*/
unsigned long initrd_addr;
u64 initrd_size = 0;
unsigned long fdt_addr = 0; /* Original DTB */
u64 fdt_size = 0; /* We don't get size from configuration table */
char *cmdline_ptr = NULL;
int cmdline_size = 0;
unsigned long new_fdt_addr;
efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
unsigned long reserve_addr = 0;
unsigned long reserve_size = 0;
/* Check if we were booted by the EFI firmware */
if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
goto fail;
pr_efi(sys_table, "Booting Linux Kernel...\n");
/*
* Get a handle to the loaded image protocol. This is used to get
* information about the running image, such as size and the command
* line.
*/
status = sys_table->boottime->handle_protocol(handle,
&loaded_image_proto, (void *)&image);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Failed to get loaded image protocol\n");
goto fail;
}
dram_base = get_dram_base(sys_table);
if (dram_base == EFI_ERROR) {
pr_efi_err(sys_table, "Failed to find DRAM base\n");
goto fail;
}
status = handle_kernel_image(sys_table, image_addr, &image_size,
&reserve_addr,
&reserve_size,
dram_base, image);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Failed to relocate kernel\n");
goto fail;
}
/*
* Get the command line from EFI, using the LOADED_IMAGE
* protocol. We are going to copy the command line into the
* device tree, so this can be allocated anywhere.
*/
cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size);
if (!cmdline_ptr) {
pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n");
goto fail_free_image;
}
status = efi_parse_options(cmdline_ptr);
if (status != EFI_SUCCESS)
pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n");
/*
* Unauthenticated device tree data is a security hazard, so
* ignore 'dtb=' unless UEFI Secure Boot is disabled.
*/
if (efi_secureboot_enabled(sys_table)) {
pr_efi(sys_table, "UEFI Secure Boot is enabled.\n");
} else {
status = handle_cmdline_files(sys_table, image, cmdline_ptr,
"dtb=",
~0UL, (unsigned long *)&fdt_addr,
(unsigned long *)&fdt_size);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Failed to load device tree!\n");
goto fail_free_cmdline;
}
}
if (!fdt_addr)
/* Look for a device tree configuration table entry. */
fdt_addr = (uintptr_t)get_fdt(sys_table);
status = handle_cmdline_files(sys_table, image, cmdline_ptr,
"initrd=", dram_base + SZ_512M,
(unsigned long *)&initrd_addr,
(unsigned long *)&initrd_size);
if (status != EFI_SUCCESS)
pr_efi_err(sys_table, "Failed initrd from command line!\n");
new_fdt_addr = fdt_addr;
status = allocate_new_fdt_and_exit_boot(sys_table, handle,
&new_fdt_addr, dram_base + MAX_FDT_OFFSET,
initrd_addr, initrd_size, cmdline_ptr,
fdt_addr, fdt_size);
/*
* If all went well, we need to return the FDT address to the
* calling function so it can be passed to kernel as part of
* the kernel boot protocol.
*/
if (status == EFI_SUCCESS)
return new_fdt_addr;
pr_efi_err(sys_table, "Failed to update FDT and exit boot services\n");
efi_free(sys_table, initrd_size, initrd_addr);
efi_free(sys_table, fdt_size, fdt_addr);
fail_free_cmdline:
efi_free(sys_table, cmdline_size, (unsigned long)cmdline_ptr);
fail_free_image:
efi_free(sys_table, image_size, *image_addr);
efi_free(sys_table, reserve_size, reserve_addr);
fail:
return EFI_ERROR;
}

View file

@ -0,0 +1,690 @@
/*
* Helper functions used by the EFI stub on multiple
* architectures. This should be #included by the EFI stub
* implementation files.
*
* Copyright 2011 Intel Corporation; author Matt Fleming
*
* This file is part of the Linux kernel, and is made available
* under the terms of the GNU General Public License version 2.
*
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
/*
* Some firmware implementations have problems reading files in one go.
* A read chunk size of 1MB seems to work for most platforms.
*
* Unfortunately, reading files in chunks triggers *other* bugs on some
* platforms, so we provide a way to disable this workaround, which can
* be done by passing "efi=nochunk" on the EFI boot stub command line.
*
* If you experience issues with initrd images being corrupt it's worth
* trying efi=nochunk, but chunking is enabled by default because there
* are far more machines that require the workaround than those that
* break with it enabled.
*/
#define EFI_READ_CHUNK_SIZE (1024 * 1024)
static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
struct file_info {
efi_file_handle_t *handle;
u64 size;
};
void efi_printk(efi_system_table_t *sys_table_arg, char *str)
{
char *s8;
for (s8 = str; *s8; s8++) {
efi_char16_t ch[2] = { 0 };
ch[0] = *s8;
if (*s8 == '\n') {
efi_char16_t nl[2] = { '\r', 0 };
efi_char16_printk(sys_table_arg, nl);
}
efi_char16_printk(sys_table_arg, ch);
}
}
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
efi_memory_desc_t **map,
unsigned long *map_size,
unsigned long *desc_size,
u32 *desc_ver,
unsigned long *key_ptr)
{
efi_memory_desc_t *m = NULL;
efi_status_t status;
unsigned long key;
u32 desc_version;
*map_size = sizeof(*m) * 32;
again:
/*
* Add an additional efi_memory_desc_t because we're doing an
* allocation which may be in a new descriptor region.
*/
*map_size += sizeof(*m);
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
*map_size, (void **)&m);
if (status != EFI_SUCCESS)
goto fail;
*desc_size = 0;
key = 0;
status = efi_call_early(get_memory_map, map_size, m,
&key, desc_size, &desc_version);
if (status == EFI_BUFFER_TOO_SMALL) {
efi_call_early(free_pool, m);
goto again;
}
if (status != EFI_SUCCESS)
efi_call_early(free_pool, m);
if (key_ptr && status == EFI_SUCCESS)
*key_ptr = key;
if (desc_ver && status == EFI_SUCCESS)
*desc_ver = desc_version;
fail:
*map = m;
return status;
}
unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg)
{
efi_status_t status;
unsigned long map_size;
unsigned long membase = EFI_ERROR;
struct efi_memory_map map;
efi_memory_desc_t *md;
status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map,
&map_size, &map.desc_size, NULL, NULL);
if (status != EFI_SUCCESS)
return membase;
map.map_end = map.map + map_size;
for_each_efi_memory_desc(&map, md)
if (md->attribute & EFI_MEMORY_WB)
if (membase > md->phys_addr)
membase = md->phys_addr;
efi_call_early(free_pool, map.map);
return membase;
}
/*
* Allocate at the highest possible address that is not above 'max'.
*/
efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align,
unsigned long *addr, unsigned long max)
{
unsigned long map_size, desc_size;
efi_memory_desc_t *map;
efi_status_t status;
unsigned long nr_pages;
u64 max_addr = 0;
int i;
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
NULL, NULL);
if (status != EFI_SUCCESS)
goto fail;
/*
* Enforce minimum alignment that EFI requires when requesting
* a specific address. We are doing page-based allocations,
* so we must be aligned to a page.
*/
if (align < EFI_PAGE_SIZE)
align = EFI_PAGE_SIZE;
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
again:
for (i = 0; i < map_size / desc_size; i++) {
efi_memory_desc_t *desc;
unsigned long m = (unsigned long)map;
u64 start, end;
desc = (efi_memory_desc_t *)(m + (i * desc_size));
if (desc->type != EFI_CONVENTIONAL_MEMORY)
continue;
if (desc->num_pages < nr_pages)
continue;
start = desc->phys_addr;
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
if (end > max)
end = max;
if ((start + size) > end)
continue;
if (round_down(end - size, align) < start)
continue;
start = round_down(end - size, align);
/*
* Don't allocate at 0x0. It will confuse code that
* checks pointers against NULL.
*/
if (start == 0x0)
continue;
if (start > max_addr)
max_addr = start;
}
if (!max_addr)
status = EFI_NOT_FOUND;
else {
status = efi_call_early(allocate_pages,
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
nr_pages, &max_addr);
if (status != EFI_SUCCESS) {
max = max_addr;
max_addr = 0;
goto again;
}
*addr = max_addr;
}
efi_call_early(free_pool, map);
fail:
return status;
}
/*
* Allocate at the lowest possible address.
*/
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align,
unsigned long *addr)
{
unsigned long map_size, desc_size;
efi_memory_desc_t *map;
efi_status_t status;
unsigned long nr_pages;
int i;
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
NULL, NULL);
if (status != EFI_SUCCESS)
goto fail;
/*
* Enforce minimum alignment that EFI requires when requesting
* a specific address. We are doing page-based allocations,
* so we must be aligned to a page.
*/
if (align < EFI_PAGE_SIZE)
align = EFI_PAGE_SIZE;
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
for (i = 0; i < map_size / desc_size; i++) {
efi_memory_desc_t *desc;
unsigned long m = (unsigned long)map;
u64 start, end;
desc = (efi_memory_desc_t *)(m + (i * desc_size));
if (desc->type != EFI_CONVENTIONAL_MEMORY)
continue;
if (desc->num_pages < nr_pages)
continue;
start = desc->phys_addr;
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
/*
* Don't allocate at 0x0. It will confuse code that
* checks pointers against NULL. Skip the first 8
* bytes so we start at a nice even number.
*/
if (start == 0x0)
start += 8;
start = round_up(start, align);
if ((start + size) > end)
continue;
status = efi_call_early(allocate_pages,
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
nr_pages, &start);
if (status == EFI_SUCCESS) {
*addr = start;
break;
}
}
if (i == map_size / desc_size)
status = EFI_NOT_FOUND;
efi_call_early(free_pool, map);
fail:
return status;
}
void efi_free(efi_system_table_t *sys_table_arg, unsigned long size,
unsigned long addr)
{
unsigned long nr_pages;
if (!size)
return;
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
efi_call_early(free_pages, addr, nr_pages);
}
/*
* Parse the ASCII string 'cmdline' for EFI options, denoted by the efi=
* option, e.g. efi=nochunk.
*
* It should be noted that efi= is parsed in two very different
* environments, first in the early boot environment of the EFI boot
* stub, and subsequently during the kernel boot.
*/
efi_status_t efi_parse_options(char *cmdline)
{
char *str;
/*
* If no EFI parameters were specified on the cmdline we've got
* nothing to do.
*/
str = strstr(cmdline, "efi=");
if (!str)
return EFI_SUCCESS;
/* Skip ahead to first argument */
str += strlen("efi=");
/*
* Remember, because efi= is also used by the kernel we need to
* skip over arguments we don't understand.
*/
while (*str) {
if (!strncmp(str, "nochunk", 7)) {
str += strlen("nochunk");
__chunk_size = -1UL;
}
/* Group words together, delimited by "," */
while (*str && *str != ',')
str++;
if (*str == ',')
str++;
}
return EFI_SUCCESS;
}
/*
* Check the cmdline for a LILO-style file= arguments.
*
* We only support loading a file from the same filesystem as
* the kernel image.
*/
efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
efi_loaded_image_t *image,
char *cmd_line, char *option_string,
unsigned long max_addr,
unsigned long *load_addr,
unsigned long *load_size)
{
struct file_info *files;
unsigned long file_addr;
u64 file_size_total;
efi_file_handle_t *fh = NULL;
efi_status_t status;
int nr_files;
char *str;
int i, j, k;
file_addr = 0;
file_size_total = 0;
str = cmd_line;
j = 0; /* See close_handles */
if (!load_addr || !load_size)
return EFI_INVALID_PARAMETER;
*load_addr = 0;
*load_size = 0;
if (!str || !*str)
return EFI_SUCCESS;
for (nr_files = 0; *str; nr_files++) {
str = strstr(str, option_string);
if (!str)
break;
str += strlen(option_string);
/* Skip any leading slashes */
while (*str == '/' || *str == '\\')
str++;
while (*str && *str != ' ' && *str != '\n')
str++;
}
if (!nr_files)
return EFI_SUCCESS;
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
nr_files * sizeof(*files), (void **)&files);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to alloc mem for file handle list\n");
goto fail;
}
str = cmd_line;
for (i = 0; i < nr_files; i++) {
struct file_info *file;
efi_char16_t filename_16[256];
efi_char16_t *p;
str = strstr(str, option_string);
if (!str)
break;
str += strlen(option_string);
file = &files[i];
p = filename_16;
/* Skip any leading slashes */
while (*str == '/' || *str == '\\')
str++;
while (*str && *str != ' ' && *str != '\n') {
if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
break;
if (*str == '/') {
*p++ = '\\';
str++;
} else {
*p++ = *str++;
}
}
*p = '\0';
/* Only open the volume once. */
if (!i) {
status = efi_open_volume(sys_table_arg, image,
(void **)&fh);
if (status != EFI_SUCCESS)
goto free_files;
}
status = efi_file_size(sys_table_arg, fh, filename_16,
(void **)&file->handle, &file->size);
if (status != EFI_SUCCESS)
goto close_handles;
file_size_total += file->size;
}
if (file_size_total) {
unsigned long addr;
/*
* Multiple files need to be at consecutive addresses in memory,
* so allocate enough memory for all the files. This is used
* for loading multiple files.
*/
status = efi_high_alloc(sys_table_arg, file_size_total, 0x1000,
&file_addr, max_addr);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to alloc highmem for files\n");
goto close_handles;
}
/* We've run out of free low memory. */
if (file_addr > max_addr) {
pr_efi_err(sys_table_arg, "We've run out of free low memory\n");
status = EFI_INVALID_PARAMETER;
goto free_file_total;
}
addr = file_addr;
for (j = 0; j < nr_files; j++) {
unsigned long size;
size = files[j].size;
while (size) {
unsigned long chunksize;
if (size > __chunk_size)
chunksize = __chunk_size;
else
chunksize = size;
status = efi_file_read(files[j].handle,
&chunksize,
(void *)addr);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to read file\n");
goto free_file_total;
}
addr += chunksize;
size -= chunksize;
}
efi_file_close(files[j].handle);
}
}
efi_call_early(free_pool, files);
*load_addr = file_addr;
*load_size = file_size_total;
return status;
free_file_total:
efi_free(sys_table_arg, file_size_total, file_addr);
close_handles:
for (k = j; k < i; k++)
efi_file_close(files[k].handle);
free_files:
efi_call_early(free_pool, files);
fail:
*load_addr = 0;
*load_size = 0;
return status;
}
/*
* Relocate a kernel image, either compressed or uncompressed.
* In the ARM64 case, all kernel images are currently
* uncompressed, and as such when we relocate it we need to
* allocate additional space for the BSS segment. Any low
* memory that this function should avoid needs to be
* unavailable in the EFI memory map, as if the preferred
* address is not available the lowest available address will
* be used.
*/
efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
unsigned long *image_addr,
unsigned long image_size,
unsigned long alloc_size,
unsigned long preferred_addr,
unsigned long alignment)
{
unsigned long cur_image_addr;
unsigned long new_addr = 0;
efi_status_t status;
unsigned long nr_pages;
efi_physical_addr_t efi_addr = preferred_addr;
if (!image_addr || !image_size || !alloc_size)
return EFI_INVALID_PARAMETER;
if (alloc_size < image_size)
return EFI_INVALID_PARAMETER;
cur_image_addr = *image_addr;
/*
* The EFI firmware loader could have placed the kernel image
* anywhere in memory, but the kernel has restrictions on the
* max physical address it can run at. Some architectures
* also have a prefered address, so first try to relocate
* to the preferred address. If that fails, allocate as low
* as possible while respecting the required alignment.
*/
nr_pages = round_up(alloc_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
status = efi_call_early(allocate_pages,
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
nr_pages, &efi_addr);
new_addr = efi_addr;
/*
* If preferred address allocation failed allocate as low as
* possible.
*/
if (status != EFI_SUCCESS) {
status = efi_low_alloc(sys_table_arg, alloc_size, alignment,
&new_addr);
}
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n");
return status;
}
/*
* We know source/dest won't overlap since both memory ranges
* have been allocated by UEFI, so we can safely use memcpy.
*/
memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
/* Return the new address of the relocated image. */
*image_addr = new_addr;
return status;
}
/*
* Get the number of UTF-8 bytes corresponding to an UTF-16 character.
* This overestimates for surrogates, but that is okay.
*/
static int efi_utf8_bytes(u16 c)
{
return 1 + (c >= 0x80) + (c >= 0x800);
}
/*
* Convert an UTF-16 string, not necessarily null terminated, to UTF-8.
*/
static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n)
{
unsigned int c;
while (n--) {
c = *src++;
if (n && c >= 0xd800 && c <= 0xdbff &&
*src >= 0xdc00 && *src <= 0xdfff) {
c = 0x10000 + ((c & 0x3ff) << 10) + (*src & 0x3ff);
src++;
n--;
}
if (c >= 0xd800 && c <= 0xdfff)
c = 0xfffd; /* Unmatched surrogate */
if (c < 0x80) {
*dst++ = c;
continue;
}
if (c < 0x800) {
*dst++ = 0xc0 + (c >> 6);
goto t1;
}
if (c < 0x10000) {
*dst++ = 0xe0 + (c >> 12);
goto t2;
}
*dst++ = 0xf0 + (c >> 18);
*dst++ = 0x80 + ((c >> 12) & 0x3f);
t2:
*dst++ = 0x80 + ((c >> 6) & 0x3f);
t1:
*dst++ = 0x80 + (c & 0x3f);
}
return dst;
}
/*
* Convert the unicode UEFI command line to ASCII to pass to kernel.
* Size of memory allocated return in *cmd_line_len.
* Returns NULL on error.
*/
char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
efi_loaded_image_t *image,
int *cmd_line_len)
{
const u16 *s2;
u8 *s1 = NULL;
unsigned long cmdline_addr = 0;
int load_options_chars = image->load_options_size / 2; /* UTF-16 */
const u16 *options = image->load_options;
int options_bytes = 0; /* UTF-8 bytes */
int options_chars = 0; /* UTF-16 chars */
efi_status_t status;
u16 zero = 0;
if (options) {
s2 = options;
while (*s2 && *s2 != '\n'
&& options_chars < load_options_chars) {
options_bytes += efi_utf8_bytes(*s2++);
options_chars++;
}
}
if (!options_chars) {
/* No command line options, so return empty string*/
options = &zero;
}
options_bytes++; /* NUL termination */
status = efi_low_alloc(sys_table_arg, options_bytes, 0, &cmdline_addr);
if (status != EFI_SUCCESS)
return NULL;
s1 = (u8 *)cmdline_addr;
s2 = (const u16 *)options;
s1 = efi_utf16_to_utf8(s1, s2, options_chars);
*s1 = '\0';
*cmd_line_len = options_bytes;
return (char *)cmdline_addr;
}

View file

@ -0,0 +1,42 @@
#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H
#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H
/* error code which can't be mistaken for valid address */
#define EFI_ERROR (~0UL)
void efi_char16_printk(efi_system_table_t *, efi_char16_t *);
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image,
void **__fh);
efi_status_t efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
efi_char16_t *filename_16, void **handle,
u64 *file_sz);
efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr);
efi_status_t efi_file_close(void *handle);
unsigned long get_dram_base(efi_system_table_t *sys_table_arg);
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
unsigned long orig_fdt_size,
void *fdt, int new_fdt_size, char *cmdline_ptr,
u64 initrd_addr, u64 initrd_size,
efi_memory_desc_t *memory_map,
unsigned long map_size, unsigned long desc_size,
u32 desc_ver);
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
void *handle,
unsigned long *new_fdt_addr,
unsigned long max_addr,
u64 initrd_addr, u64 initrd_size,
char *cmdline_ptr,
unsigned long fdt_addr,
unsigned long fdt_size);
void *get_fdt(efi_system_table_t *sys_table);
#endif

View file

@ -0,0 +1,287 @@
/*
* FDT related Helper functions used by the EFI stub on multiple
* architectures. This should be #included by the EFI stub
* implementation files.
*
* Copyright 2013 Linaro Limited; author Roy Franz
*
* This file is part of the Linux kernel, and is made available
* under the terms of the GNU General Public License version 2.
*
*/
#include <linux/efi.h>
#include <linux/libfdt.h>
#include <asm/efi.h>
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
unsigned long orig_fdt_size,
void *fdt, int new_fdt_size, char *cmdline_ptr,
u64 initrd_addr, u64 initrd_size,
efi_memory_desc_t *memory_map,
unsigned long map_size, unsigned long desc_size,
u32 desc_ver)
{
int node, prev, num_rsv;
int status;
u32 fdt_val32;
u64 fdt_val64;
/* Do some checks on provided FDT, if it exists*/
if (orig_fdt) {
if (fdt_check_header(orig_fdt)) {
pr_efi_err(sys_table, "Device Tree header not valid!\n");
return EFI_LOAD_ERROR;
}
/*
* We don't get the size of the FDT if we get if from a
* configuration table.
*/
if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) {
pr_efi_err(sys_table, "Truncated device tree! foo!\n");
return EFI_LOAD_ERROR;
}
}
if (orig_fdt)
status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
else
status = fdt_create_empty_tree(fdt, new_fdt_size);
if (status != 0)
goto fdt_set_fail;
/*
* Delete any memory nodes present. We must delete nodes which
* early_init_dt_scan_memory may try to use.
*/
prev = 0;
for (;;) {
const char *type;
int len;
node = fdt_next_node(fdt, prev, NULL);
if (node < 0)
break;
type = fdt_getprop(fdt, node, "device_type", &len);
if (type && strncmp(type, "memory", len) == 0) {
fdt_del_node(fdt, node);
continue;
}
prev = node;
}
/*
* Delete all memory reserve map entries. When booting via UEFI,
* kernel will use the UEFI memory map to find reserved regions.
*/
num_rsv = fdt_num_mem_rsv(fdt);
while (num_rsv-- > 0)
fdt_del_mem_rsv(fdt, num_rsv);
node = fdt_subnode_offset(fdt, 0, "chosen");
if (node < 0) {
node = fdt_add_subnode(fdt, 0, "chosen");
if (node < 0) {
status = node; /* node is error code when negative */
goto fdt_set_fail;
}
}
if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
strlen(cmdline_ptr) + 1);
if (status)
goto fdt_set_fail;
}
/* Set initrd address/end in device tree, if present */
if (initrd_size != 0) {
u64 initrd_image_end;
u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
status = fdt_setprop(fdt, node, "linux,initrd-start",
&initrd_image_start, sizeof(u64));
if (status)
goto fdt_set_fail;
initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
status = fdt_setprop(fdt, node, "linux,initrd-end",
&initrd_image_end, sizeof(u64));
if (status)
goto fdt_set_fail;
}
/* Add FDT entries for EFI runtime services in chosen node. */
node = fdt_subnode_offset(fdt, 0, "chosen");
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table);
status = fdt_setprop(fdt, node, "linux,uefi-system-table",
&fdt_val64, sizeof(fdt_val64));
if (status)
goto fdt_set_fail;
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map);
status = fdt_setprop(fdt, node, "linux,uefi-mmap-start",
&fdt_val64, sizeof(fdt_val64));
if (status)
goto fdt_set_fail;
fdt_val32 = cpu_to_fdt32(map_size);
status = fdt_setprop(fdt, node, "linux,uefi-mmap-size",
&fdt_val32, sizeof(fdt_val32));
if (status)
goto fdt_set_fail;
fdt_val32 = cpu_to_fdt32(desc_size);
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size",
&fdt_val32, sizeof(fdt_val32));
if (status)
goto fdt_set_fail;
fdt_val32 = cpu_to_fdt32(desc_ver);
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver",
&fdt_val32, sizeof(fdt_val32));
if (status)
goto fdt_set_fail;
/*
* Add kernel version banner so stub/kernel match can be
* verified.
*/
status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver",
linux_banner);
if (status)
goto fdt_set_fail;
return EFI_SUCCESS;
fdt_set_fail:
if (status == -FDT_ERR_NOSPACE)
return EFI_BUFFER_TOO_SMALL;
return EFI_LOAD_ERROR;
}
#ifndef EFI_FDT_ALIGN
#define EFI_FDT_ALIGN EFI_PAGE_SIZE
#endif
/*
* Allocate memory for a new FDT, then add EFI, commandline, and
* initrd related fields to the FDT. This routine increases the
* FDT allocation size until the allocated memory is large
* enough. EFI allocations are in EFI_PAGE_SIZE granules,
* which are fixed at 4K bytes, so in most cases the first
* allocation should succeed.
* EFI boot services are exited at the end of this function.
* There must be no allocations between the get_memory_map()
* call and the exit_boot_services() call, so the exiting of
* boot services is very tightly tied to the creation of the FDT
* with the final memory map in it.
*/
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
void *handle,
unsigned long *new_fdt_addr,
unsigned long max_addr,
u64 initrd_addr, u64 initrd_size,
char *cmdline_ptr,
unsigned long fdt_addr,
unsigned long fdt_size)
{
unsigned long map_size, desc_size;
u32 desc_ver;
unsigned long mmap_key;
efi_memory_desc_t *memory_map;
unsigned long new_fdt_size;
efi_status_t status;
/*
* Estimate size of new FDT, and allocate memory for it. We
* will allocate a bigger buffer if this ends up being too
* small, so a rough guess is OK here.
*/
new_fdt_size = fdt_size + EFI_PAGE_SIZE;
while (1) {
status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
new_fdt_addr, max_addr);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
goto fail;
}
/*
* Now that we have done our final memory allocation (and free)
* we can get the memory map key needed for
* exit_boot_services().
*/
status = efi_get_memory_map(sys_table, &memory_map, &map_size,
&desc_size, &desc_ver, &mmap_key);
if (status != EFI_SUCCESS)
goto fail_free_new_fdt;
status = update_fdt(sys_table,
(void *)fdt_addr, fdt_size,
(void *)*new_fdt_addr, new_fdt_size,
cmdline_ptr, initrd_addr, initrd_size,
memory_map, map_size, desc_size, desc_ver);
/* Succeeding the first time is the expected case. */
if (status == EFI_SUCCESS)
break;
if (status == EFI_BUFFER_TOO_SMALL) {
/*
* We need to allocate more space for the new
* device tree, so free existing buffer that is
* too small. Also free memory map, as we will need
* to get new one that reflects the free/alloc we do
* on the device tree buffer.
*/
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
sys_table->boottime->free_pool(memory_map);
new_fdt_size += EFI_PAGE_SIZE;
} else {
pr_efi_err(sys_table, "Unable to constuct new device tree.\n");
goto fail_free_mmap;
}
}
/* Now we are ready to exit_boot_services.*/
status = sys_table->boottime->exit_boot_services(handle, mmap_key);
if (status == EFI_SUCCESS)
return status;
pr_efi_err(sys_table, "Exit boot services failed.\n");
fail_free_mmap:
sys_table->boottime->free_pool(memory_map);
fail_free_new_fdt:
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
fail:
return EFI_LOAD_ERROR;
}
void *get_fdt(efi_system_table_t *sys_table)
{
efi_guid_t fdt_guid = DEVICE_TREE_GUID;
efi_config_table_t *tables;
void *fdt;
int i;
tables = (efi_config_table_t *) sys_table->tables;
fdt = NULL;
for (i = 0; i < sys_table->nr_tables; i++)
if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) {
fdt = (void *) tables[i].table;
break;
}
return fdt;
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 Intel Corporation; author Matt Fleming
* Copyright (c) 2014 Red Hat, Inc., Mark Salter <msalter@redhat.com>
*/
#include <linux/efi.h>
#include <linux/reboot.h>
int efi_reboot_quirk_mode = -1;
void efi_reboot(enum reboot_mode reboot_mode, const char *__unused)
{
int efi_mode;
if (!efi_enabled(EFI_RUNTIME_SERVICES))
return;
switch (reboot_mode) {
case REBOOT_WARM:
case REBOOT_SOFT:
efi_mode = EFI_RESET_WARM;
break;
default:
efi_mode = EFI_RESET_COLD;
break;
}
/*
* If a quirk forced an EFI reset mode, always use that.
*/
if (efi_reboot_quirk_mode != -1)
efi_mode = efi_reboot_quirk_mode;
efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL);
}
bool __weak efi_poweroff_required(void)
{
return false;
}
static void efi_power_off(void)
{
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
}
static int __init efi_shutdown_init(void)
{
if (!efi_enabled(EFI_RUNTIME_SERVICES))
return -ENODEV;
if (efi_poweroff_required())
pm_power_off = efi_power_off;
return 0;
}
late_initcall(efi_shutdown_init);

View file

@ -0,0 +1,202 @@
/*
* linux/drivers/efi/runtime-map.c
* Copyright (C) 2013 Red Hat, Inc., Dave Young <dyoung@redhat.com>
*
* This file is released under the GPLv2.
*/
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/efi.h>
#include <linux/slab.h>
#include <asm/setup.h>
static void *efi_runtime_map;
static int nr_efi_runtime_map;
static u32 efi_memdesc_size;
struct efi_runtime_map_entry {
efi_memory_desc_t md;
struct kobject kobj; /* kobject for each entry */
};
static struct efi_runtime_map_entry **map_entries;
struct map_attribute {
struct attribute attr;
ssize_t (*show)(struct efi_runtime_map_entry *entry, char *buf);
};
static inline struct map_attribute *to_map_attr(struct attribute *attr)
{
return container_of(attr, struct map_attribute, attr);
}
static ssize_t type_show(struct efi_runtime_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%x\n", entry->md.type);
}
#define EFI_RUNTIME_FIELD(var) entry->md.var
#define EFI_RUNTIME_U64_ATTR_SHOW(name) \
static ssize_t name##_show(struct efi_runtime_map_entry *entry, char *buf) \
{ \
return snprintf(buf, PAGE_SIZE, "0x%llx\n", EFI_RUNTIME_FIELD(name)); \
}
EFI_RUNTIME_U64_ATTR_SHOW(phys_addr);
EFI_RUNTIME_U64_ATTR_SHOW(virt_addr);
EFI_RUNTIME_U64_ATTR_SHOW(num_pages);
EFI_RUNTIME_U64_ATTR_SHOW(attribute);
static inline struct efi_runtime_map_entry *to_map_entry(struct kobject *kobj)
{
return container_of(kobj, struct efi_runtime_map_entry, kobj);
}
static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct efi_runtime_map_entry *entry = to_map_entry(kobj);
struct map_attribute *map_attr = to_map_attr(attr);
return map_attr->show(entry, buf);
}
static struct map_attribute map_type_attr = __ATTR_RO(type);
static struct map_attribute map_phys_addr_attr = __ATTR_RO(phys_addr);
static struct map_attribute map_virt_addr_attr = __ATTR_RO(virt_addr);
static struct map_attribute map_num_pages_attr = __ATTR_RO(num_pages);
static struct map_attribute map_attribute_attr = __ATTR_RO(attribute);
/*
* These are default attributes that are added for every memmap entry.
*/
static struct attribute *def_attrs[] = {
&map_type_attr.attr,
&map_phys_addr_attr.attr,
&map_virt_addr_attr.attr,
&map_num_pages_attr.attr,
&map_attribute_attr.attr,
NULL
};
static const struct sysfs_ops map_attr_ops = {
.show = map_attr_show,
};
static void map_release(struct kobject *kobj)
{
struct efi_runtime_map_entry *entry;
entry = to_map_entry(kobj);
kfree(entry);
}
static struct kobj_type __refdata map_ktype = {
.sysfs_ops = &map_attr_ops,
.default_attrs = def_attrs,
.release = map_release,
};
static struct kset *map_kset;
static struct efi_runtime_map_entry *
add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
{
int ret;
struct efi_runtime_map_entry *entry;
if (!map_kset) {
map_kset = kset_create_and_add("runtime-map", NULL, kobj);
if (!map_kset)
return ERR_PTR(-ENOMEM);
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
kset_unregister(map_kset);
return entry;
}
memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size,
sizeof(efi_memory_desc_t));
kobject_init(&entry->kobj, &map_ktype);
entry->kobj.kset = map_kset;
ret = kobject_add(&entry->kobj, NULL, "%d", nr);
if (ret) {
kobject_put(&entry->kobj);
kset_unregister(map_kset);
return ERR_PTR(ret);
}
return entry;
}
int efi_get_runtime_map_size(void)
{
return nr_efi_runtime_map * efi_memdesc_size;
}
int efi_get_runtime_map_desc_size(void)
{
return efi_memdesc_size;
}
int efi_runtime_map_copy(void *buf, size_t bufsz)
{
size_t sz = efi_get_runtime_map_size();
if (sz > bufsz)
sz = bufsz;
memcpy(buf, efi_runtime_map, sz);
return 0;
}
void efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size)
{
efi_runtime_map = map;
nr_efi_runtime_map = nr_entries;
efi_memdesc_size = desc_size;
}
int __init efi_runtime_map_init(struct kobject *efi_kobj)
{
int i, j, ret = 0;
struct efi_runtime_map_entry *entry;
if (!efi_runtime_map)
return 0;
map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL);
if (!map_entries) {
ret = -ENOMEM;
goto out;
}
for (i = 0; i < nr_efi_runtime_map; i++) {
entry = add_sysfs_runtime_map_entry(efi_kobj, i);
if (IS_ERR(entry)) {
ret = PTR_ERR(entry);
goto out_add_entry;
}
*(map_entries + i) = entry;
}
return 0;
out_add_entry:
for (j = i - 1; j >= 0; j--) {
entry = *(map_entries + j);
kobject_put(&entry->kobj);
}
if (map_kset)
kset_unregister(map_kset);
out:
return ret;
}

View file

@ -0,0 +1,305 @@
/*
* runtime-wrappers.c - Runtime Services function call wrappers
*
* Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org>
*
* Split off from arch/x86/platform/efi/efi.c
*
* Copyright (C) 1999 VA Linux Systems
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
* Copyright (C) 1999-2002 Hewlett-Packard Co.
* Copyright (C) 2005-2008 Intel Co.
* Copyright (C) 2013 SuSE Labs
*
* This file is released under the GPLv2.
*/
#include <linux/bug.h>
#include <linux/efi.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <asm/efi.h>
/*
* According to section 7.1 of the UEFI spec, Runtime Services are not fully
* reentrant, and there are particular combinations of calls that need to be
* serialized. (source: UEFI Specification v2.4A)
*
* Table 31. Rules for Reentry Into Runtime Services
* +------------------------------------+-------------------------------+
* | If previous call is busy in | Forbidden to call |
* +------------------------------------+-------------------------------+
* | Any | SetVirtualAddressMap() |
* +------------------------------------+-------------------------------+
* | ConvertPointer() | ConvertPointer() |
* +------------------------------------+-------------------------------+
* | SetVariable() | ResetSystem() |
* | UpdateCapsule() | |
* | SetTime() | |
* | SetWakeupTime() | |
* | GetNextHighMonotonicCount() | |
* +------------------------------------+-------------------------------+
* | GetVariable() | GetVariable() |
* | GetNextVariableName() | GetNextVariableName() |
* | SetVariable() | SetVariable() |
* | QueryVariableInfo() | QueryVariableInfo() |
* | UpdateCapsule() | UpdateCapsule() |
* | QueryCapsuleCapabilities() | QueryCapsuleCapabilities() |
* | GetNextHighMonotonicCount() | GetNextHighMonotonicCount() |
* +------------------------------------+-------------------------------+
* | GetTime() | GetTime() |
* | SetTime() | SetTime() |
* | GetWakeupTime() | GetWakeupTime() |
* | SetWakeupTime() | SetWakeupTime() |
* +------------------------------------+-------------------------------+
*
* Due to the fact that the EFI pstore may write to the variable store in
* interrupt context, we need to use a spinlock for at least the groups that
* contain SetVariable() and QueryVariableInfo(). That leaves little else, as
* none of the remaining functions are actually ever called at runtime.
* So let's just use a single spinlock to serialize all Runtime Services calls.
*/
static DEFINE_SPINLOCK(efi_runtime_lock);
/*
* Some runtime services calls can be reentrant under NMI, even if the table
* above says they are not. (source: UEFI Specification v2.4A)
*
* Table 32. Functions that may be called after Machine Check, INIT and NMI
* +----------------------------+------------------------------------------+
* | Function | Called after Machine Check, INIT and NMI |
* +----------------------------+------------------------------------------+
* | GetTime() | Yes, even if previously busy. |
* | GetVariable() | Yes, even if previously busy |
* | GetNextVariableName() | Yes, even if previously busy |
* | QueryVariableInfo() | Yes, even if previously busy |
* | SetVariable() | Yes, even if previously busy |
* | UpdateCapsule() | Yes, even if previously busy |
* | QueryCapsuleCapabilities() | Yes, even if previously busy |
* | ResetSystem() | Yes, even if previously busy |
* +----------------------------+------------------------------------------+
*
* In order to prevent deadlocks under NMI, the wrappers for these functions
* may only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi().
* However, not all of the services listed are reachable through NMI code paths,
* so the the special handling as suggested by the UEFI spec is only implemented
* for QueryVariableInfo() and SetVariable(), as these can be reached in NMI
* context through efi_pstore_write().
*/
/*
* As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"),
* the EFI specification requires that callers of the time related runtime
* functions serialize with other CMOS accesses in the kernel, as the EFI time
* functions may choose to also use the legacy CMOS RTC.
*/
__weak DEFINE_SPINLOCK(rtc_lock);
static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&rtc_lock, flags);
spin_lock(&efi_runtime_lock);
status = efi_call_virt(get_time, tm, tc);
spin_unlock(&efi_runtime_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
return status;
}
static efi_status_t virt_efi_set_time(efi_time_t *tm)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&rtc_lock, flags);
spin_lock(&efi_runtime_lock);
status = efi_call_virt(set_time, tm);
spin_unlock(&efi_runtime_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
return status;
}
static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
efi_bool_t *pending,
efi_time_t *tm)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&rtc_lock, flags);
spin_lock(&efi_runtime_lock);
status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
spin_unlock(&efi_runtime_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
return status;
}
static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&rtc_lock, flags);
spin_lock(&efi_runtime_lock);
status = efi_call_virt(set_wakeup_time, enabled, tm);
spin_unlock(&efi_runtime_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
return status;
}
static efi_status_t virt_efi_get_variable(efi_char16_t *name,
efi_guid_t *vendor,
u32 *attr,
unsigned long *data_size,
void *data)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(get_variable, name, vendor, attr, data_size,
data);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(get_next_variable, name_size, name, vendor);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t virt_efi_set_variable(efi_char16_t *name,
efi_guid_t *vendor,
u32 attr,
unsigned long data_size,
void *data)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
data);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t
virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
u32 attr, unsigned long data_size,
void *data)
{
unsigned long flags;
efi_status_t status;
if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
return EFI_NOT_READY;
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
data);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t virt_efi_query_variable_info(u32 attr,
u64 *storage_space,
u64 *remaining_space,
u64 *max_variable_size)
{
unsigned long flags;
efi_status_t status;
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
return EFI_UNSUPPORTED;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(query_variable_info, attr, storage_space,
remaining_space, max_variable_size);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
{
unsigned long flags;
efi_status_t status;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(get_next_high_mono_count, count);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static void virt_efi_reset_system(int reset_type,
efi_status_t status,
unsigned long data_size,
efi_char16_t *data)
{
unsigned long flags;
spin_lock_irqsave(&efi_runtime_lock, flags);
__efi_call_virt(reset_system, reset_type, status, data_size, data);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
}
static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
unsigned long count,
unsigned long sg_list)
{
unsigned long flags;
efi_status_t status;
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
return EFI_UNSUPPORTED;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(update_capsule, capsules, count, sg_list);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
unsigned long count,
u64 *max_size,
int *reset_type)
{
unsigned long flags;
efi_status_t status;
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
return EFI_UNSUPPORTED;
spin_lock_irqsave(&efi_runtime_lock, flags);
status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
reset_type);
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
void efi_native_runtime_setup(void)
{
efi.get_time = virt_efi_get_time;
efi.set_time = virt_efi_set_time;
efi.get_wakeup_time = virt_efi_get_wakeup_time;
efi.set_wakeup_time = virt_efi_set_wakeup_time;
efi.get_variable = virt_efi_get_variable;
efi.get_next_variable = virt_efi_get_next_variable;
efi.set_variable = virt_efi_set_variable;
efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
efi.reset_system = virt_efi_reset_system;
efi.query_variable_info = virt_efi_query_variable_info;
efi.update_capsule = virt_efi_update_capsule;
efi.query_capsule_caps = virt_efi_query_capsule_caps;
}

1096
drivers/firmware/efi/vars.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
config GOOGLE_FIRMWARE
bool "Google Firmware Drivers"
depends on X86
default n
help
These firmware drivers are used by Google's servers. They are
only useful if you are working directly on one of their
proprietary servers. If in doubt, say "N".
menu "Google Firmware Drivers"
depends on GOOGLE_FIRMWARE
config GOOGLE_SMI
tristate "SMI interface for Google platforms"
depends on ACPI && DMI && EFI
select EFI_VARS
help
Say Y here if you want to enable SMI callbacks for Google
platforms. This provides an interface for writing to and
clearing the EFI event log and reading and writing NVRAM
variables.
config GOOGLE_MEMCONSOLE
tristate "Firmware Memory Console"
depends on DMI
help
This option enables the kernel to search for a firmware log in
the EBDA on Google servers. If found, this log is exported to
userland in the file /sys/firmware/log.
endmenu

View file

@ -0,0 +1,3 @@
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o

View file

@ -0,0 +1,942 @@
/*
* Copyright 2010 Google Inc. All Rights Reserved.
* Author: dlaurie@google.com (Duncan Laurie)
*
* Re-worked to expose sysfs APIs by mikew@google.com (Mike Waychison)
*
* EFI SMI interface for Google platforms
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/dmi.h>
#include <linux/kdebug.h>
#include <linux/reboot.h>
#include <linux/efi.h>
#include <linux/module.h>
#include <linux/ucs2_string.h>
#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */
/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
#define GSMI_SHUTDOWN_NMIWDT 1 /* NMI Watchdog */
#define GSMI_SHUTDOWN_PANIC 2 /* Panic */
#define GSMI_SHUTDOWN_OOPS 3 /* Oops */
#define GSMI_SHUTDOWN_DIE 4 /* Die -- No longer meaningful */
#define GSMI_SHUTDOWN_MCE 5 /* Machine Check */
#define GSMI_SHUTDOWN_SOFTWDT 6 /* Software Watchdog */
#define GSMI_SHUTDOWN_MBE 7 /* Uncorrected ECC */
#define GSMI_SHUTDOWN_TRIPLE 8 /* Triple Fault */
#define DRIVER_VERSION "1.0"
#define GSMI_GUID_SIZE 16
#define GSMI_BUF_SIZE 1024
#define GSMI_BUF_ALIGN sizeof(u64)
#define GSMI_CALLBACK 0xef
/* SMI return codes */
#define GSMI_SUCCESS 0x00
#define GSMI_UNSUPPORTED2 0x03
#define GSMI_LOG_FULL 0x0b
#define GSMI_VAR_NOT_FOUND 0x0e
#define GSMI_HANDSHAKE_SPIN 0x7d
#define GSMI_HANDSHAKE_CF 0x7e
#define GSMI_HANDSHAKE_NONE 0x7f
#define GSMI_INVALID_PARAMETER 0x82
#define GSMI_UNSUPPORTED 0x83
#define GSMI_BUFFER_TOO_SMALL 0x85
#define GSMI_NOT_READY 0x86
#define GSMI_DEVICE_ERROR 0x87
#define GSMI_NOT_FOUND 0x8e
#define QUIRKY_BOARD_HASH 0x78a30a50
/* Internally used commands passed to the firmware */
#define GSMI_CMD_GET_NVRAM_VAR 0x01
#define GSMI_CMD_GET_NEXT_VAR 0x02
#define GSMI_CMD_SET_NVRAM_VAR 0x03
#define GSMI_CMD_SET_EVENT_LOG 0x08
#define GSMI_CMD_CLEAR_EVENT_LOG 0x09
#define GSMI_CMD_CLEAR_CONFIG 0x20
#define GSMI_CMD_HANDSHAKE_TYPE 0xC1
/* Magic entry type for kernel events */
#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD
/* SMI buffers must be in 32bit physical address space */
struct gsmi_buf {
u8 *start; /* start of buffer */
size_t length; /* length of buffer */
dma_addr_t handle; /* dma allocation handle */
u32 address; /* physical address of buffer */
};
struct gsmi_device {
struct platform_device *pdev; /* platform device */
struct gsmi_buf *name_buf; /* variable name buffer */
struct gsmi_buf *data_buf; /* generic data buffer */
struct gsmi_buf *param_buf; /* parameter buffer */
spinlock_t lock; /* serialize access to SMIs */
u16 smi_cmd; /* SMI command port */
int handshake_type; /* firmware handler interlock type */
struct dma_pool *dma_pool; /* DMA buffer pool */
} gsmi_dev;
/* Packed structures for communicating with the firmware */
struct gsmi_nvram_var_param {
efi_guid_t guid;
u32 name_ptr;
u32 attributes;
u32 data_len;
u32 data_ptr;
} __packed;
struct gsmi_get_next_var_param {
u8 guid[GSMI_GUID_SIZE];
u32 name_ptr;
u32 name_len;
} __packed;
struct gsmi_set_eventlog_param {
u32 data_ptr;
u32 data_len;
u32 type;
} __packed;
/* Event log formats */
struct gsmi_log_entry_type_1 {
u16 type;
u32 instance;
} __packed;
/*
* Some platforms don't have explicit SMI handshake
* and need to wait for SMI to complete.
*/
#define GSMI_DEFAULT_SPINCOUNT 0x10000
static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT;
module_param(spincount, uint, 0600);
MODULE_PARM_DESC(spincount,
"The number of loop iterations to use when using the spin handshake.");
static struct gsmi_buf *gsmi_buf_alloc(void)
{
struct gsmi_buf *smibuf;
smibuf = kzalloc(sizeof(*smibuf), GFP_KERNEL);
if (!smibuf) {
printk(KERN_ERR "gsmi: out of memory\n");
return NULL;
}
/* allocate buffer in 32bit address space */
smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL,
&smibuf->handle);
if (!smibuf->start) {
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
kfree(smibuf);
return NULL;
}
/* fill in the buffer handle */
smibuf->length = GSMI_BUF_SIZE;
smibuf->address = (u32)virt_to_phys(smibuf->start);
return smibuf;
}
static void gsmi_buf_free(struct gsmi_buf *smibuf)
{
if (smibuf) {
if (smibuf->start)
dma_pool_free(gsmi_dev.dma_pool, smibuf->start,
smibuf->handle);
kfree(smibuf);
}
}
/*
* Make a call to gsmi func(sub). GSMI error codes are translated to
* in-kernel errnos (0 on success, -ERRNO on error).
*/
static int gsmi_exec(u8 func, u8 sub)
{
u16 cmd = (sub << 8) | func;
u16 result = 0;
int rc = 0;
/*
* AH : Subfunction number
* AL : Function number
* EBX : Parameter block address
* DX : SMI command port
*
* Three protocols here. See also the comment in gsmi_init().
*/
if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) {
/*
* If handshake_type == HANDSHAKE_CF then set CF on the
* way in and wait for the handler to clear it; this avoids
* corrupting register state on those chipsets which have
* a delay between writing the SMI trigger register and
* entering SMM.
*/
asm volatile (
"stc\n"
"outb %%al, %%dx\n"
"1: jc 1b\n"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address)
: "memory", "cc"
);
} else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) {
/*
* If handshake_type == HANDSHAKE_SPIN we spin a
* hundred-ish usecs to ensure the SMI has triggered.
*/
asm volatile (
"outb %%al, %%dx\n"
"1: loop 1b\n"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address),
"c" (spincount)
: "memory", "cc"
);
} else {
/*
* If handshake_type == HANDSHAKE_NONE we do nothing;
* either we don't need to or it's legacy firmware that
* doesn't understand the CF protocol.
*/
asm volatile (
"outb %%al, %%dx\n\t"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address)
: "memory", "cc"
);
}
/* check return code from SMI handler */
switch (result) {
case GSMI_SUCCESS:
break;
case GSMI_VAR_NOT_FOUND:
/* not really an error, but let the caller know */
rc = 1;
break;
case GSMI_INVALID_PARAMETER:
printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd);
rc = -EINVAL;
break;
case GSMI_BUFFER_TOO_SMALL:
printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd);
rc = -ENOMEM;
break;
case GSMI_UNSUPPORTED:
case GSMI_UNSUPPORTED2:
if (sub != GSMI_CMD_HANDSHAKE_TYPE)
printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n",
cmd);
rc = -ENOSYS;
break;
case GSMI_NOT_READY:
printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd);
rc = -EBUSY;
break;
case GSMI_DEVICE_ERROR:
printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd);
rc = -EFAULT;
break;
case GSMI_NOT_FOUND:
printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd);
rc = -ENOENT;
break;
case GSMI_LOG_FULL:
printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd);
rc = -ENOSPC;
break;
case GSMI_HANDSHAKE_CF:
case GSMI_HANDSHAKE_SPIN:
case GSMI_HANDSHAKE_NONE:
rc = result;
break;
default:
printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n",
cmd, result);
rc = -ENXIO;
}
return rc;
}
static efi_status_t gsmi_get_variable(efi_char16_t *name,
efi_guid_t *vendor, u32 *attr,
unsigned long *data_size,
void *data)
{
struct gsmi_nvram_var_param param = {
.name_ptr = gsmi_dev.name_buf->address,
.data_ptr = gsmi_dev.data_buf->address,
.data_len = (u32)*data_size,
};
efi_status_t ret = EFI_SUCCESS;
unsigned long flags;
size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
int rc;
if (name_len >= GSMI_BUF_SIZE / 2)
return EFI_BAD_BUFFER_SIZE;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* Vendor guid */
memcpy(&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_t(unsigned long, *data_size,
gsmi_dev.data_buf->length);
*data_size = min_t(unsigned long, *data_size, param.data_len);
/* Copy data back to return buffer. */
memcpy(data, gsmi_dev.data_buf->start, *data_size);
/* All variables are have the following attributes */
*attr = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS;
}
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
return ret;
}
static efi_status_t gsmi_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
struct gsmi_get_next_var_param param = {
.name_ptr = gsmi_dev.name_buf->address,
.name_len = gsmi_dev.name_buf->length,
};
efi_status_t ret = EFI_SUCCESS;
int rc;
unsigned long flags;
/* For the moment, only support buffers that exactly match in size */
if (*name_size != GSMI_BUF_SIZE)
return EFI_BAD_BUFFER_SIZE;
/* Let's make sure the thing is at least null-terminated */
if (ucs2_strnlen(name, GSMI_BUF_SIZE / 2) == GSMI_BUF_SIZE / 2)
return EFI_INVALID_PARAMETER;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* guid */
memcpy(&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 = ucs2_strnlen(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,
u32 attr,
unsigned long data_size,
void *data)
{
struct gsmi_nvram_var_param param = {
.name_ptr = gsmi_dev.name_buf->address,
.data_ptr = gsmi_dev.data_buf->address,
.data_len = (u32)data_size,
.attributes = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
};
size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
efi_status_t ret = EFI_SUCCESS;
int rc;
unsigned long flags;
if (name_len >= GSMI_BUF_SIZE / 2)
return EFI_BAD_BUFFER_SIZE;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* guid */
memcpy(&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 = kstrtoul(buf, 0, &val);
if (rc)
return rc;
/*
* Value entered is a percentage, 0 through 100, anything else
* is invalid.
*/
if (val > 100)
return -EINVAL;
/* data_type here selects the smbios event log. */
param.percentage = val;
param.data_type = 0;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &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 const struct platform_device_info gsmi_dev_info = {
.name = "gsmi",
.id = -1,
/* SMI callbacks require 32bit addresses */
.dma_mask = DMA_BIT_MASK(32),
};
static __init int gsmi_init(void)
{
unsigned long flags;
int ret;
ret = gsmi_system_valid();
if (ret)
return ret;
gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
/* register device */
gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info);
if (IS_ERR(gsmi_dev.pdev)) {
printk(KERN_ERR "gsmi: unable to register platform device\n");
return PTR_ERR(gsmi_dev.pdev);
}
/* SMI access needs to be serialized */
spin_lock_init(&gsmi_dev.lock);
ret = -ENOMEM;
gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev,
GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0);
if (!gsmi_dev.dma_pool)
goto out_err;
/*
* pre-allocate buffers because sometimes we are called when
* this is not feasible: oops, panic, die, mce, etc
*/
gsmi_dev.name_buf = gsmi_buf_alloc();
if (!gsmi_dev.name_buf) {
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
goto out_err;
}
gsmi_dev.data_buf = gsmi_buf_alloc();
if (!gsmi_dev.data_buf) {
printk(KERN_ERR "gsmi: failed to allocate data buffer\n");
goto out_err;
}
gsmi_dev.param_buf = gsmi_buf_alloc();
if (!gsmi_dev.param_buf) {
printk(KERN_ERR "gsmi: failed to allocate param buffer\n");
goto out_err;
}
/*
* Determine type of handshake used to serialize the SMI
* entry. See also gsmi_exec().
*
* There's a "behavior" present on some chipsets where writing the
* SMI trigger register in the southbridge doesn't result in an
* immediate SMI. Rather, the processor can execute "a few" more
* instructions before the SMI takes effect. To ensure synchronous
* behavior, implement a handshake between the kernel driver and the
* firmware handler to spin until released. This ioctl determines
* the type of handshake.
*
* NONE: The firmware handler does not implement any
* handshake. Either it doesn't need to, or it's legacy firmware
* that doesn't know it needs to and never will.
*
* CF: The firmware handler will clear the CF in the saved
* state before returning. The driver may set the CF and test for
* it to clear before proceeding.
*
* SPIN: The firmware handler does not implement any handshake
* but the driver should spin for a hundred or so microseconds
* to ensure the SMI has triggered.
*
* Finally, the handler will return -ENOSYS if
* GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies
* HANDSHAKE_NONE.
*/
spin_lock_irqsave(&gsmi_dev.lock, flags);
gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN;
gsmi_dev.handshake_type =
gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE);
if (gsmi_dev.handshake_type == -ENOSYS)
gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE;
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
/* Remove and clean up gsmi if the handshake could not complete. */
if (gsmi_dev.handshake_type == -ENXIO) {
printk(KERN_INFO "gsmi version " DRIVER_VERSION
" failed to load\n");
ret = -ENODEV;
goto out_err;
}
/* Register in the firmware directory */
ret = -ENOMEM;
gsmi_kobj = kobject_create_and_add("gsmi", firmware_kobj);
if (!gsmi_kobj) {
printk(KERN_INFO "gsmi: Failed to create firmware kobj\n");
goto out_err;
}
/* Setup eventlog access */
ret = sysfs_create_bin_file(gsmi_kobj, &eventlog_bin_attr);
if (ret) {
printk(KERN_INFO "gsmi: Failed to setup eventlog");
goto out_err;
}
/* Other attributes */
ret = sysfs_create_files(gsmi_kobj, gsmi_attrs);
if (ret) {
printk(KERN_INFO "gsmi: Failed to add attrs");
goto out_remove_bin_file;
}
ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj);
if (ret) {
printk(KERN_INFO "gsmi: Failed to register efivars\n");
goto out_remove_sysfs_files;
}
register_reboot_notifier(&gsmi_reboot_notifier);
register_die_notifier(&gsmi_die_notifier);
atomic_notifier_chain_register(&panic_notifier_list,
&gsmi_panic_notifier);
printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n");
return 0;
out_remove_sysfs_files:
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
out_remove_bin_file:
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
out_err:
kobject_put(gsmi_kobj);
gsmi_buf_free(gsmi_dev.param_buf);
gsmi_buf_free(gsmi_dev.data_buf);
gsmi_buf_free(gsmi_dev.name_buf);
if (gsmi_dev.dma_pool)
dma_pool_destroy(gsmi_dev.dma_pool);
platform_device_unregister(gsmi_dev.pdev);
pr_info("gsmi: failed to load: %d\n", ret);
return ret;
}
static void __exit gsmi_exit(void)
{
unregister_reboot_notifier(&gsmi_reboot_notifier);
unregister_die_notifier(&gsmi_die_notifier);
atomic_notifier_chain_unregister(&panic_notifier_list,
&gsmi_panic_notifier);
efivars_unregister(&efivars);
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
kobject_put(gsmi_kobj);
gsmi_buf_free(gsmi_dev.param_buf);
gsmi_buf_free(gsmi_dev.data_buf);
gsmi_buf_free(gsmi_dev.name_buf);
dma_pool_destroy(gsmi_dev.dma_pool);
platform_device_unregister(gsmi_dev.pdev);
}
module_init(gsmi_init);
module_exit(gsmi_exit);
MODULE_AUTHOR("Google, Inc.");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,171 @@
/*
* memconsole.c
*
* Infrastructure for importing the BIOS memory based console
* into the kernel log ringbuffer.
*
* Copyright 2010 Google Inc. All rights reserved.
*/
#include <linux/ctype.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/dmi.h>
#include <linux/io.h>
#include <asm/bios_ebda.h>
#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
struct biosmemcon_ebda {
u32 signature;
union {
struct {
u8 enabled;
u32 buffer_addr;
u16 start;
u16 end;
u16 num_chars;
u8 wrapped;
} __packed v1;
struct {
u32 buffer_addr;
/* Misdocumented as number of pages! */
u16 num_bytes;
u16 start;
u16 end;
} __packed v2;
};
} __packed;
static u32 memconsole_baseaddr;
static size_t memconsole_length;
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
char *memconsole;
ssize_t ret;
memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length);
if (!memconsole) {
pr_err("memconsole: ioremap_cache failed\n");
return -ENOMEM;
}
ret = memory_read_from_buffer(buf, count, &pos, memconsole,
memconsole_length);
iounmap(memconsole);
return ret;
}
static struct bin_attribute memconsole_bin_attr = {
.attr = {.name = "log", .mode = 0444},
.read = memconsole_read,
};
static void __init found_v1_header(struct biosmemcon_ebda *hdr)
{
pr_info("BIOS console v1 EBDA structure found at %p\n", hdr);
pr_info("BIOS console buffer at 0x%.8x, "
"start = %d, end = %d, num = %d\n",
hdr->v1.buffer_addr, hdr->v1.start,
hdr->v1.end, hdr->v1.num_chars);
memconsole_length = hdr->v1.num_chars;
memconsole_baseaddr = hdr->v1.buffer_addr;
}
static void __init found_v2_header(struct biosmemcon_ebda *hdr)
{
pr_info("BIOS console v2 EBDA structure found at %p\n", hdr);
pr_info("BIOS console buffer at 0x%.8x, "
"start = %d, end = %d, num_bytes = %d\n",
hdr->v2.buffer_addr, hdr->v2.start,
hdr->v2.end, hdr->v2.num_bytes);
memconsole_length = hdr->v2.end - hdr->v2.start;
memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start;
}
/*
* Search through the EBDA for the BIOS Memory Console, and
* set the global variables to point to it. Return true if found.
*/
static bool __init found_memconsole(void)
{
unsigned int address;
size_t length, cur;
address = get_bios_ebda();
if (!address) {
pr_info("BIOS EBDA non-existent.\n");
return false;
}
/* EBDA length is byte 0 of EBDA (in KB) */
length = *(u8 *)phys_to_virt(address);
length <<= 10; /* convert to bytes */
/*
* Search through EBDA for BIOS memory console structure
* note: signature is not necessarily dword-aligned
*/
for (cur = 0; cur < length; cur++) {
struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
/* memconsole v1 */
if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
found_v1_header(hdr);
return true;
}
/* memconsole v2 */
if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
found_v2_header(hdr);
return true;
}
}
pr_info("BIOS console EBDA structure not found!\n");
return false;
}
static struct dmi_system_id memconsole_dmi_table[] __initdata = {
{
.ident = "Google Board",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
static int __init memconsole_init(void)
{
if (!dmi_check_system(memconsole_dmi_table))
return -ENODEV;
if (!found_memconsole())
return -ENODEV;
memconsole_bin_attr.size = memconsole_length;
return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
}
static void __exit memconsole_exit(void)
{
sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
}
module_init(memconsole_init);
module_exit(memconsole_exit);
MODULE_AUTHOR("Google, Inc.");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,822 @@
/*
* 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 umode_t ibft_check_nic_for(void *data, int type)
{
struct ibft_kobject *entry = data;
struct ibft_nic *nic = entry->nic;
umode_t rc = 0;
switch (type) {
case ISCSI_BOOT_ETH_INDEX:
case ISCSI_BOOT_ETH_FLAGS:
rc = S_IRUGO;
break;
case ISCSI_BOOT_ETH_IP_ADDR:
if (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 umode_t __init ibft_check_tgt_for(void *data, int type)
{
struct ibft_kobject *entry = data;
struct ibft_tgt *tgt = entry->tgt;
umode_t rc = 0;
switch (type) {
case ISCSI_BOOT_TGT_INDEX:
case ISCSI_BOOT_TGT_FLAGS:
case ISCSI_BOOT_TGT_IP_ADDR:
case ISCSI_BOOT_TGT_PORT:
case ISCSI_BOOT_TGT_LUN:
case ISCSI_BOOT_TGT_NIC_ASSOC:
case ISCSI_BOOT_TGT_CHAP_TYPE:
rc = S_IRUGO;
case ISCSI_BOOT_TGT_NAME:
if (tgt->tgt_name_len)
rc = S_IRUGO;
break;
case ISCSI_BOOT_TGT_CHAP_NAME:
case ISCSI_BOOT_TGT_CHAP_SECRET:
if (tgt->chap_name_len)
rc = S_IRUGO;
break;
case ISCSI_BOOT_TGT_REV_CHAP_NAME:
case ISCSI_BOOT_TGT_REV_CHAP_SECRET:
if (tgt->rev_chap_name_len)
rc = S_IRUGO;
break;
default:
break;
}
return rc;
}
static umode_t __init ibft_check_initiator_for(void *data, int type)
{
struct ibft_kobject *entry = data;
struct ibft_initiator *init = entry->initiator;
umode_t rc = 0;
switch (type) {
case ISCSI_BOOT_INI_INDEX:
case ISCSI_BOOT_INI_FLAGS:
rc = S_IRUGO;
break;
case ISCSI_BOOT_INI_ISNS_SERVER:
if (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;
}
static void ibft_kobj_release(void *data)
{
kfree(data);
}
/*
* Helper function for ibft_register_kobjects.
*/
static int __init ibft_create_kobject(struct acpi_table_ibft *header,
struct ibft_hdr *hdr)
{
struct iscsi_boot_kobj *boot_kobj = NULL;
struct ibft_kobject *ibft_kobj = NULL;
struct ibft_nic *nic = (struct ibft_nic *)hdr;
struct pci_dev *pci_dev;
int rc = 0;
ibft_kobj = kzalloc(sizeof(*ibft_kobj), GFP_KERNEL);
if (!ibft_kobj)
return -ENOMEM;
ibft_kobj->header = header;
ibft_kobj->hdr = hdr;
switch (hdr->id) {
case id_initiator:
rc = ibft_verify_hdr("initiator", hdr, id_initiator,
sizeof(*ibft_kobj->initiator));
if (rc)
break;
boot_kobj = iscsi_boot_create_initiator(boot_kset, hdr->index,
ibft_kobj,
ibft_attr_show_initiator,
ibft_check_initiator_for,
ibft_kobj_release);
if (!boot_kobj) {
rc = -ENOMEM;
goto free_ibft_obj;
}
break;
case id_nic:
rc = ibft_verify_hdr("ethernet", hdr, id_nic,
sizeof(*ibft_kobj->nic));
if (rc)
break;
boot_kobj = iscsi_boot_create_ethernet(boot_kset, hdr->index,
ibft_kobj,
ibft_attr_show_nic,
ibft_check_nic_for,
ibft_kobj_release);
if (!boot_kobj) {
rc = -ENOMEM;
goto free_ibft_obj;
}
break;
case id_target:
rc = ibft_verify_hdr("target", hdr, id_target,
sizeof(*ibft_kobj->tgt));
if (rc)
break;
boot_kobj = iscsi_boot_create_target(boot_kset, hdr->index,
ibft_kobj,
ibft_attr_show_target,
ibft_check_tgt_for,
ibft_kobj_release);
if (!boot_kobj) {
rc = -ENOMEM;
goto free_ibft_obj;
}
break;
case id_reserved:
case id_control:
case id_extensions:
/* Fields which we don't support. Ignore them */
rc = 1;
break;
default:
printk(KERN_ERR "iBFT has unknown structure type (%d). " \
"Report this bug to %.6s!\n", hdr->id,
header->header.oem_id);
rc = 1;
break;
}
if (rc) {
/* Skip adding this kobject, but exit with non-fatal error. */
rc = 0;
goto free_ibft_obj;
}
if (hdr->id == id_nic) {
/*
* We don't search for the device in other domains than
* zero. This is because on x86 platforms the BIOS
* executes only devices which are in domain 0. Furthermore, the
* iBFT spec doesn't have a domain id field :-(
*/
pci_dev = pci_get_bus_and_slot((nic->pci_bdf & 0xff00) >> 8,
(nic->pci_bdf & 0xff));
if (pci_dev) {
rc = sysfs_create_link(&boot_kobj->kobj,
&pci_dev->dev.kobj, "device");
pci_dev_put(pci_dev);
}
}
return 0;
free_ibft_obj:
kfree(ibft_kobj);
return rc;
}
/*
* Scan the IBFT table structure for the NIC and Target fields. When
* found add them on the passed-in list. We do not support the other
* fields at this point, so they are skipped.
*/
static int __init ibft_register_kobjects(struct acpi_table_ibft *header)
{
struct ibft_control *control = NULL;
void *ptr, *end;
int rc = 0;
u16 offset;
u16 eot_offset;
control = (void *)header + sizeof(*header);
end = (void *)control + control->hdr.length;
eot_offset = (void *)header + header->header.length - (void *)control;
rc = ibft_verify_hdr("control", (struct ibft_hdr *)control, id_control,
sizeof(*control));
/* iBFT table safety checking */
rc |= ((control->hdr.index) ? -ENODEV : 0);
if (rc) {
printk(KERN_ERR "iBFT error: Control header is invalid!\n");
return rc;
}
for (ptr = &control->initiator_off; ptr < end; ptr += sizeof(u16)) {
offset = *(u16 *)ptr;
if (offset && offset < header->header.length &&
offset < eot_offset) {
rc = ibft_create_kobject(header,
(void *)header + offset);
if (rc)
break;
}
}
return rc;
}
static void ibft_unregister(void)
{
struct iscsi_boot_kobj *boot_kobj, *tmp_kobj;
struct ibft_kobject *ibft_kobj;
list_for_each_entry_safe(boot_kobj, tmp_kobj,
&boot_kset->kobj_list, list) {
ibft_kobj = boot_kobj->data;
if (ibft_kobj->hdr->id == id_nic)
sysfs_remove_link(&boot_kobj->kobj, "device");
};
}
static void ibft_cleanup(void)
{
if (boot_kset) {
ibft_unregister();
iscsi_boot_destroy_kset(boot_kset);
}
}
static void __exit ibft_exit(void)
{
ibft_cleanup();
}
#ifdef CONFIG_ACPI
static const struct {
char *sign;
} ibft_signs[] = {
/*
* One spec says "IBFT", the other says "iBFT". We have to check
* for both.
*/
{ ACPI_SIG_IBFT },
{ "iBFT" },
{ "BIFT" }, /* Broadcom iSCSI Offload */
};
static void __init acpi_find_ibft_region(void)
{
int i;
struct acpi_table_header *table = NULL;
if (acpi_disabled)
return;
for (i = 0; i < ARRAY_SIZE(ibft_signs) && !ibft_addr; i++) {
acpi_get_table(ibft_signs[i].sign, 0, &table);
ibft_addr = (struct acpi_table_ibft *)table;
}
}
#else
static void __init acpi_find_ibft_region(void)
{
}
#endif
/*
* ibft_init() - creates sysfs tree entries for the iBFT data.
*/
static int __init ibft_init(void)
{
int rc = 0;
/*
As on UEFI systems the setup_arch()/find_ibft_region()
is called before ACPI tables are parsed and it only does
legacy finding.
*/
if (!ibft_addr)
acpi_find_ibft_region();
if (ibft_addr) {
pr_info("iBFT detected.\n");
rc = ibft_check_device();
if (rc)
return rc;
boot_kset = iscsi_boot_create_kset("ibft");
if (!boot_kset)
return -ENOMEM;
/* Scan the IBFT for data and register the kobjects. */
rc = ibft_register_kobjects(ibft_addr);
if (rc)
goto out_free;
} else
printk(KERN_INFO "No iBFT detected.\n");
return 0;
out_free:
ibft_cleanup();
return rc;
}
module_init(ibft_init);
module_exit(ibft_exit);

View file

@ -0,0 +1,112 @@
/*
* Copyright 2007-2010 Red Hat, Inc.
* by Peter Jones <pjones@redhat.com>
* Copyright 2007 IBM, Inc.
* by Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
* Copyright 2008
* by Konrad Rzeszutek <ketuzsezr@darnok.org>
*
* This code finds the iSCSI Boot Format Table.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/bootmem.h>
#include <linux/blkdev.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/iscsi_ibft.h>
#include <asm/mmzone.h>
/*
* Physical location of iSCSI Boot Format Table.
*/
struct acpi_table_ibft *ibft_addr;
EXPORT_SYMBOL_GPL(ibft_addr);
static const struct {
char *sign;
} ibft_signs[] = {
{ "iBFT" },
{ "BIFT" }, /* Broadcom iSCSI Offload */
};
#define IBFT_SIGN_LEN 4
#define IBFT_START 0x80000 /* 512kB */
#define IBFT_END 0x100000 /* 1MB */
#define VGA_MEM 0xA0000 /* VGA buffer */
#define VGA_SIZE 0x20000 /* 128kB */
static int __init find_ibft_in_mem(void)
{
unsigned long pos;
unsigned int len = 0;
void *virt;
int i;
for (pos = IBFT_START; pos < IBFT_END; pos += 16) {
/* The table can't be inside the VGA BIOS reserved space,
* so skip that area */
if (pos == VGA_MEM)
pos += VGA_SIZE;
virt = isa_bus_to_virt(pos);
for (i = 0; i < ARRAY_SIZE(ibft_signs); i++) {
if (memcmp(virt, ibft_signs[i].sign, IBFT_SIGN_LEN) ==
0) {
unsigned long *addr =
(unsigned long *)isa_bus_to_virt(pos + 4);
len = *addr;
/* if the length of the table extends past 1M,
* the table cannot be valid. */
if (pos + len <= (IBFT_END-1)) {
ibft_addr = (struct acpi_table_ibft *)virt;
pr_info("iBFT found at 0x%lx.\n", pos);
goto done;
}
}
}
}
done:
return len;
}
/*
* Routine used to find the iSCSI Boot Format Table. The logical
* kernel address is set in the ibft_addr global variable.
*/
unsigned long __init find_ibft_region(unsigned long *sizep)
{
ibft_addr = NULL;
/* iBFT 1.03 section 1.4.3.1 mandates that UEFI machines will
* only use ACPI for this */
if (!efi_enabled(EFI_BOOT))
find_ibft_in_mem();
if (ibft_addr) {
*sizep = PAGE_ALIGN(ibft_addr->header.length);
return (u64)isa_virt_to_bus(ibft_addr);
}
*sizep = 0;
return 0;
}

424
drivers/firmware/memmap.c Normal file
View file

@ -0,0 +1,424 @@
/*
* linux/drivers/firmware/memmap.c
* Copyright (C) 2008 SUSE LINUX Products GmbH
* by Bernhard Walle <bernhard.walle@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/string.h>
#include <linux/firmware-map.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/bootmem.h>
#include <linux/slab.h>
#include <linux/mm.h>
/*
* Data types ------------------------------------------------------------------
*/
/*
* Firmware map entry. Because firmware memory maps are flat and not
* hierarchical, it's ok to organise them in a linked list. No parent
* information is necessary as for the resource tree.
*/
struct firmware_map_entry {
/*
* start and end must be u64 rather than resource_size_t, because e820
* resources can lie at addresses above 4G.
*/
u64 start; /* start of the memory range */
u64 end; /* end of the memory range (incl.) */
const char *type; /* type of the memory range */
struct list_head list; /* entry for the linked list */
struct kobject kobj; /* kobject for each entry */
};
/*
* Forward declarations --------------------------------------------------------
*/
static ssize_t memmap_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf);
static ssize_t start_show(struct firmware_map_entry *entry, char *buf);
static ssize_t end_show(struct firmware_map_entry *entry, char *buf);
static ssize_t type_show(struct firmware_map_entry *entry, char *buf);
static struct firmware_map_entry * __meminit
firmware_map_find_entry(u64 start, u64 end, const char *type);
/*
* Static data -----------------------------------------------------------------
*/
struct memmap_attribute {
struct attribute attr;
ssize_t (*show)(struct firmware_map_entry *entry, char *buf);
};
static struct memmap_attribute memmap_start_attr = __ATTR_RO(start);
static struct memmap_attribute memmap_end_attr = __ATTR_RO(end);
static struct memmap_attribute memmap_type_attr = __ATTR_RO(type);
/*
* These are default attributes that are added for every memmap entry.
*/
static struct attribute *def_attrs[] = {
&memmap_start_attr.attr,
&memmap_end_attr.attr,
&memmap_type_attr.attr,
NULL
};
static const struct sysfs_ops memmap_attr_ops = {
.show = memmap_attr_show,
};
/* Firmware memory map entries. */
static LIST_HEAD(map_entries);
static DEFINE_SPINLOCK(map_entries_lock);
/*
* For memory hotplug, there is no way to free memory map entries allocated
* by boot mem after the system is up. So when we hot-remove memory whose
* map entry is allocated by bootmem, we need to remember the storage and
* reuse it when the memory is hot-added again.
*/
static LIST_HEAD(map_entries_bootmem);
static DEFINE_SPINLOCK(map_entries_bootmem_lock);
static inline struct firmware_map_entry *
to_memmap_entry(struct kobject *kobj)
{
return container_of(kobj, struct firmware_map_entry, kobj);
}
static void __meminit release_firmware_map_entry(struct kobject *kobj)
{
struct firmware_map_entry *entry = to_memmap_entry(kobj);
if (PageReserved(virt_to_page(entry))) {
/*
* Remember the storage allocated by bootmem, and reuse it when
* the memory is hot-added again. The entry will be added to
* map_entries_bootmem here, and deleted from &map_entries in
* firmware_map_remove_entry().
*/
spin_lock(&map_entries_bootmem_lock);
list_add(&entry->list, &map_entries_bootmem);
spin_unlock(&map_entries_bootmem_lock);
return;
}
kfree(entry);
}
static struct kobj_type __refdata memmap_ktype = {
.release = release_firmware_map_entry,
.sysfs_ops = &memmap_attr_ops,
.default_attrs = def_attrs,
};
/*
* Registration functions ------------------------------------------------------
*/
/**
* firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
* @start: Start of the memory range.
* @end: End of the memory range (exclusive).
* @type: Type of the memory range.
* @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised
* entry.
*
* Common implementation of firmware_map_add() and firmware_map_add_early()
* which expects a pre-allocated struct firmware_map_entry.
**/
static int firmware_map_add_entry(u64 start, u64 end,
const char *type,
struct firmware_map_entry *entry)
{
BUG_ON(start > end);
entry->start = start;
entry->end = end - 1;
entry->type = type;
INIT_LIST_HEAD(&entry->list);
kobject_init(&entry->kobj, &memmap_ktype);
spin_lock(&map_entries_lock);
list_add_tail(&entry->list, &map_entries);
spin_unlock(&map_entries_lock);
return 0;
}
/**
* firmware_map_remove_entry() - Does the real work to remove a firmware
* memmap entry.
* @entry: removed entry.
*
* The caller must hold map_entries_lock, and release it properly.
**/
static inline void firmware_map_remove_entry(struct firmware_map_entry *entry)
{
list_del(&entry->list);
}
/*
* Add memmap entry on sysfs
*/
static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
{
static int map_entries_nr;
static struct kset *mmap_kset;
if (entry->kobj.state_in_sysfs)
return -EEXIST;
if (!mmap_kset) {
mmap_kset = kset_create_and_add("memmap", NULL, firmware_kobj);
if (!mmap_kset)
return -ENOMEM;
}
entry->kobj.kset = mmap_kset;
if (kobject_add(&entry->kobj, NULL, "%d", map_entries_nr++))
kobject_put(&entry->kobj);
return 0;
}
/*
* Remove memmap entry on sysfs
*/
static inline void remove_sysfs_fw_map_entry(struct firmware_map_entry *entry)
{
kobject_put(&entry->kobj);
}
/*
* firmware_map_find_entry_in_list() - Search memmap entry in a given list.
* @start: Start of the memory range.
* @end: End of the memory range (exclusive).
* @type: Type of the memory range.
* @list: In which to find the entry.
*
* This function is to find the memmap entey of a given memory range in a
* given list. The caller must hold map_entries_lock, and must not release
* the lock until the processing of the returned entry has completed.
*
* Return: Pointer to the entry to be found on success, or NULL on failure.
*/
static struct firmware_map_entry * __meminit
firmware_map_find_entry_in_list(u64 start, u64 end, const char *type,
struct list_head *list)
{
struct firmware_map_entry *entry;
list_for_each_entry(entry, list, list)
if ((entry->start == start) && (entry->end == end) &&
(!strcmp(entry->type, type))) {
return entry;
}
return NULL;
}
/*
* firmware_map_find_entry() - Search memmap entry in map_entries.
* @start: Start of the memory range.
* @end: End of the memory range (exclusive).
* @type: Type of the memory range.
*
* This function is to find the memmap entey of a given memory range.
* The caller must hold map_entries_lock, and must not release the lock
* until the processing of the returned entry has completed.
*
* Return: Pointer to the entry to be found on success, or NULL on failure.
*/
static struct firmware_map_entry * __meminit
firmware_map_find_entry(u64 start, u64 end, const char *type)
{
return firmware_map_find_entry_in_list(start, end, type, &map_entries);
}
/*
* firmware_map_find_entry_bootmem() - Search memmap entry in map_entries_bootmem.
* @start: Start of the memory range.
* @end: End of the memory range (exclusive).
* @type: Type of the memory range.
*
* This function is similar to firmware_map_find_entry except that it find the
* given entry in map_entries_bootmem.
*
* Return: Pointer to the entry to be found on success, or NULL on failure.
*/
static struct firmware_map_entry * __meminit
firmware_map_find_entry_bootmem(u64 start, u64 end, const char *type)
{
return firmware_map_find_entry_in_list(start, end, type,
&map_entries_bootmem);
}
/**
* firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
* memory hotplug.
* @start: Start of the memory range.
* @end: End of the memory range (exclusive)
* @type: Type of the memory range.
*
* Adds a firmware mapping entry. This function is for memory hotplug, it is
* similar to function firmware_map_add_early(). The only difference is that
* it will create the syfs entry dynamically.
*
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
**/
int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
{
struct firmware_map_entry *entry;
entry = firmware_map_find_entry(start, end - 1, type);
if (entry)
return 0;
entry = firmware_map_find_entry_bootmem(start, end - 1, type);
if (!entry) {
entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC);
if (!entry)
return -ENOMEM;
} else {
/* Reuse storage allocated by bootmem. */
spin_lock(&map_entries_bootmem_lock);
list_del(&entry->list);
spin_unlock(&map_entries_bootmem_lock);
memset(entry, 0, sizeof(*entry));
}
firmware_map_add_entry(start, end, type, entry);
/* create the memmap entry */
add_sysfs_fw_map_entry(entry);
return 0;
}
/**
* firmware_map_add_early() - Adds a firmware mapping entry.
* @start: Start of the memory range.
* @end: End of the memory range.
* @type: Type of the memory range.
*
* Adds a firmware mapping entry. This function uses the bootmem allocator
* for memory allocation.
*
* That function must be called before late_initcall.
*
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
**/
int __init firmware_map_add_early(u64 start, u64 end, const char *type)
{
struct firmware_map_entry *entry;
entry = memblock_virt_alloc(sizeof(struct firmware_map_entry), 0);
if (WARN_ON(!entry))
return -ENOMEM;
return firmware_map_add_entry(start, end, type, entry);
}
/**
* firmware_map_remove() - remove a firmware mapping entry
* @start: Start of the memory range.
* @end: End of the memory range.
* @type: Type of the memory range.
*
* removes a firmware mapping entry.
*
* Returns 0 on success, or -EINVAL if no entry.
**/
int __meminit firmware_map_remove(u64 start, u64 end, const char *type)
{
struct firmware_map_entry *entry;
spin_lock(&map_entries_lock);
entry = firmware_map_find_entry(start, end - 1, type);
if (!entry) {
spin_unlock(&map_entries_lock);
return -EINVAL;
}
firmware_map_remove_entry(entry);
spin_unlock(&map_entries_lock);
/* remove the memmap entry */
remove_sysfs_fw_map_entry(entry);
return 0;
}
/*
* Sysfs functions -------------------------------------------------------------
*/
static ssize_t start_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
(unsigned long long)entry->start);
}
static ssize_t end_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
(unsigned long long)entry->end);
}
static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", entry->type);
}
static inline struct memmap_attribute *to_memmap_attr(struct attribute *attr)
{
return container_of(attr, struct memmap_attribute, attr);
}
static ssize_t memmap_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct firmware_map_entry *entry = to_memmap_entry(kobj);
struct memmap_attribute *memmap_attr = to_memmap_attr(attr);
return memmap_attr->show(entry, buf);
}
/*
* Initialises stuff and adds the entries in the map_entries list to
* sysfs. Important is that firmware_map_add() and firmware_map_add_early()
* must be called before late_initcall. That's just because that function
* is called as late_initcall() function, which means that if you call
* firmware_map_add() or firmware_map_add_early() afterwards, the entries
* are not added to sysfs.
*/
static int __init firmware_memmap_init(void)
{
struct firmware_map_entry *entry;
list_for_each_entry(entry, &map_entries, list)
add_sysfs_fw_map_entry(entry);
return 0;
}
late_initcall(firmware_memmap_init);

136
drivers/firmware/pcdp.c Normal file
View file

@ -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 = early_ioremap(efi.hcdp, 4096);
printk(KERN_INFO "PCDP: v%d at 0x%lx\n", pcdp->rev, efi.hcdp);
if (strstr(cmdline, "console=hcdp")) {
if (pcdp->rev < 3)
serial = 1;
} else if (strstr(cmdline, "console=")) {
printk(KERN_INFO "Explicit \"console=\"; ignoring PCDP\n");
goto out;
}
if (pcdp->rev < 3 && efi_uart_console_only())
serial = 1;
for (i = 0, uart = pcdp->uart; i < pcdp->num_uarts; i++, uart++) {
if (uart->flags & PCDP_UART_PRIMARY_CONSOLE || serial) {
if (uart->type == PCDP_CONSOLE_UART) {
rc = setup_serial_console(uart);
goto out;
}
}
}
end = (struct pcdp_device *) ((u8 *) pcdp + pcdp->length);
for (dev = (struct pcdp_device *) (pcdp->uart + pcdp->num_uarts);
dev < end;
dev = (struct pcdp_device *) ((u8 *) dev + dev->length)) {
if (dev->flags & PCDP_PRIMARY_CONSOLE) {
if (dev->type == PCDP_CONSOLE_VGA) {
rc = setup_vga_console(dev);
goto out;
}
}
}
out:
early_iounmap(pcdp, 4096);
return rc;
}

111
drivers/firmware/pcdp.h Normal file
View file

@ -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));