M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

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

View File

@ -0,0 +1,16 @@
#
# Makefile for the linux kernel.
#
obj-$(CONFIG_DMI) += dmi_scan.o
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o
obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_EFI_PCDP) += pcdp.o
obj-$(CONFIG_DELL_RBU) += dell_rbu.o
obj-$(CONFIG_DCDBAS) += dcdbas.o
obj-$(CONFIG_DMIID) += dmi-id.o
obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o
obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/

View File

@ -0,0 +1,659 @@
/*
* dcdbas.c: Dell Systems Management Base Driver
*
* The Dell Systems Management Base Driver provides a sysfs interface for
* systems management software to perform System Management Interrupts (SMIs)
* and Host Control Actions (power cycle or power off after OS shutdown) on
* Dell systems.
*
* See Documentation/dcdbas.txt for more information.
*
* Copyright (C) 1995-2006 Dell Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mc146818rtc.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <asm/io.h>
#include "dcdbas.h"
#define DRIVER_NAME "dcdbas"
#define DRIVER_VERSION "5.6.0-3.2"
#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver"
static struct platform_device *dcdbas_pdev;
static u8 *smi_data_buf;
static dma_addr_t smi_data_buf_handle;
static unsigned long smi_data_buf_size;
static u32 smi_data_buf_phys_addr;
static DEFINE_MUTEX(smi_data_lock);
static unsigned int host_control_action;
static unsigned int host_control_smi_type;
static unsigned int host_control_on_shutdown;
/**
* smi_data_buf_free: free SMI data buffer
*/
static void smi_data_buf_free(void)
{
if (!smi_data_buf)
return;
dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
__func__, smi_data_buf_phys_addr, smi_data_buf_size);
dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf,
smi_data_buf_handle);
smi_data_buf = NULL;
smi_data_buf_handle = 0;
smi_data_buf_phys_addr = 0;
smi_data_buf_size = 0;
}
/**
* smi_data_buf_realloc: grow SMI data buffer if needed
*/
static int smi_data_buf_realloc(unsigned long size)
{
void *buf;
dma_addr_t handle;
if (smi_data_buf_size >= size)
return 0;
if (size > MAX_SMI_DATA_BUF_SIZE)
return -EINVAL;
/* new buffer is needed */
buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL);
if (!buf) {
dev_dbg(&dcdbas_pdev->dev,
"%s: failed to allocate memory size %lu\n",
__func__, size);
return -ENOMEM;
}
/* memory zeroed by dma_alloc_coherent */
if (smi_data_buf)
memcpy(buf, smi_data_buf, smi_data_buf_size);
/* free any existing buffer */
smi_data_buf_free();
/* set up new buffer for use */
smi_data_buf = buf;
smi_data_buf_handle = handle;
smi_data_buf_phys_addr = (u32) virt_to_phys(buf);
smi_data_buf_size = size;
dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
__func__, smi_data_buf_phys_addr, smi_data_buf_size);
return 0;
}
static ssize_t smi_data_buf_phys_addr_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%x\n", smi_data_buf_phys_addr);
}
static ssize_t smi_data_buf_size_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", smi_data_buf_size);
}
static ssize_t smi_data_buf_size_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long buf_size;
ssize_t ret;
buf_size = simple_strtoul(buf, NULL, 10);
/* make sure SMI data buffer is at least buf_size */
mutex_lock(&smi_data_lock);
ret = smi_data_buf_realloc(buf_size);
mutex_unlock(&smi_data_lock);
if (ret)
return ret;
return count;
}
static ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
ssize_t ret;
mutex_lock(&smi_data_lock);
ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf,
smi_data_buf_size);
mutex_unlock(&smi_data_lock);
return ret;
}
static ssize_t smi_data_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
ssize_t ret;
if ((pos + count) > MAX_SMI_DATA_BUF_SIZE)
return -EINVAL;
mutex_lock(&smi_data_lock);
ret = smi_data_buf_realloc(pos + count);
if (ret)
goto out;
memcpy(smi_data_buf + pos, buf, count);
ret = count;
out:
mutex_unlock(&smi_data_lock);
return ret;
}
static ssize_t host_control_action_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_action);
}
static ssize_t host_control_action_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
ssize_t ret;
/* make sure buffer is available for host control command */
mutex_lock(&smi_data_lock);
ret = smi_data_buf_realloc(sizeof(struct apm_cmd));
mutex_unlock(&smi_data_lock);
if (ret)
return ret;
host_control_action = simple_strtoul(buf, NULL, 10);
return count;
}
static ssize_t host_control_smi_type_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_smi_type);
}
static ssize_t host_control_smi_type_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
host_control_smi_type = simple_strtoul(buf, NULL, 10);
return count;
}
static ssize_t host_control_on_shutdown_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_on_shutdown);
}
static ssize_t host_control_on_shutdown_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
host_control_on_shutdown = simple_strtoul(buf, NULL, 10);
return count;
}
/**
* dcdbas_smi_request: generate SMI request
*
* Called with smi_data_lock.
*/
int dcdbas_smi_request(struct smi_cmd *smi_cmd)
{
cpumask_var_t old_mask;
int ret = 0;
if (smi_cmd->magic != SMI_CMD_MAGIC) {
dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
__func__);
return -EBADR;
}
/* SMI requires CPU 0 */
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
return -ENOMEM;
cpumask_copy(old_mask, &current->cpus_allowed);
set_cpus_allowed_ptr(current, cpumask_of(0));
if (smp_processor_id() != 0) {
dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
__func__);
ret = -EBUSY;
goto out;
}
/* generate SMI */
/* inb to force posted write through and make SMI happen now */
asm volatile (
"outb %b0,%w1\n"
"inb %w1"
: /* no output args */
: "a" (smi_cmd->command_code),
"d" (smi_cmd->command_address),
"b" (smi_cmd->ebx),
"c" (smi_cmd->ecx)
: "memory"
);
out:
set_cpus_allowed_ptr(current, old_mask);
free_cpumask_var(old_mask);
return ret;
}
/**
* smi_request_store:
*
* The valid values are:
* 0: zero SMI data buffer
* 1: generate calling interface SMI
* 2: generate raw SMI
*
* User application writes smi_cmd to smi_data before telling driver
* to generate SMI.
*/
static ssize_t smi_request_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct smi_cmd *smi_cmd;
unsigned long val = simple_strtoul(buf, NULL, 10);
ssize_t ret;
mutex_lock(&smi_data_lock);
if (smi_data_buf_size < sizeof(struct smi_cmd)) {
ret = -ENODEV;
goto out;
}
smi_cmd = (struct smi_cmd *)smi_data_buf;
switch (val) {
case 2:
/* Raw SMI */
ret = dcdbas_smi_request(smi_cmd);
if (!ret)
ret = count;
break;
case 1:
/* Calling Interface SMI */
smi_cmd->ebx = (u32) virt_to_phys(smi_cmd->command_buffer);
ret = dcdbas_smi_request(smi_cmd);
if (!ret)
ret = count;
break;
case 0:
memset(smi_data_buf, 0, smi_data_buf_size);
ret = count;
break;
default:
ret = -EINVAL;
break;
}
out:
mutex_unlock(&smi_data_lock);
return ret;
}
EXPORT_SYMBOL(dcdbas_smi_request);
/**
* host_control_smi: generate host control SMI
*
* Caller must set up the host control command in smi_data_buf.
*/
static int host_control_smi(void)
{
struct apm_cmd *apm_cmd;
u8 *data;
unsigned long flags;
u32 num_ticks;
s8 cmd_status;
u8 index;
apm_cmd = (struct apm_cmd *)smi_data_buf;
apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL;
switch (host_control_smi_type) {
case HC_SMITYPE_TYPE1:
spin_lock_irqsave(&rtc_lock, flags);
/* write SMI data buffer physical address */
data = (u8 *)&smi_data_buf_phys_addr;
for (index = PE1300_CMOS_CMD_STRUCT_PTR;
index < (PE1300_CMOS_CMD_STRUCT_PTR + 4);
index++, data++) {
outb(index,
(CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4));
outb(*data,
(CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4));
}
/* first set status to -1 as called by spec */
cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL;
outb((u8) cmd_status, PCAT_APM_STATUS_PORT);
/* generate SMM call */
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
spin_unlock_irqrestore(&rtc_lock, flags);
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
while ((cmd_status = inb(PCAT_APM_STATUS_PORT))
== ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--;
if (num_ticks == EXPIRED_TIMER)
return -ETIME;
}
break;
case HC_SMITYPE_TYPE2:
case HC_SMITYPE_TYPE3:
spin_lock_irqsave(&rtc_lock, flags);
/* write SMI data buffer physical address */
data = (u8 *)&smi_data_buf_phys_addr;
for (index = PE1400_CMOS_CMD_STRUCT_PTR;
index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
index++, data++) {
outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
}
/* generate SMM call */
if (host_control_smi_type == HC_SMITYPE_TYPE3)
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
else
outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT);
/* restore RTC index pointer since it was written to above */
CMOS_READ(RTC_REG_C);
spin_unlock_irqrestore(&rtc_lock, flags);
/* read control port back to serialize write */
cmd_status = inb(PE1400_APM_CONTROL_PORT);
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--;
if (num_ticks == EXPIRED_TIMER)
return -ETIME;
}
break;
default:
dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n",
__func__, host_control_smi_type);
return -ENOSYS;
}
return 0;
}
/**
* dcdbas_host_control: initiate host control
*
* This function is called by the driver after the system has
* finished shutting down if the user application specified a
* host control action to perform on shutdown. It is safe to
* use smi_data_buf at this point because the system has finished
* shutting down and no userspace apps are running.
*/
static void dcdbas_host_control(void)
{
struct apm_cmd *apm_cmd;
u8 action;
if (host_control_action == HC_ACTION_NONE)
return;
action = host_control_action;
host_control_action = HC_ACTION_NONE;
if (!smi_data_buf) {
dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__);
return;
}
if (smi_data_buf_size < sizeof(struct apm_cmd)) {
dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n",
__func__);
return;
}
apm_cmd = (struct apm_cmd *)smi_data_buf;
/* power off takes precedence */
if (action & HC_ACTION_HOST_CONTROL_POWEROFF) {
apm_cmd->command = ESM_APM_POWER_CYCLE;
apm_cmd->reserved = 0;
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0;
host_control_smi();
} else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) {
apm_cmd->command = ESM_APM_POWER_CYCLE;
apm_cmd->reserved = 0;
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20;
host_control_smi();
}
}
/**
* dcdbas_reboot_notify: handle reboot notification for host control
*/
static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code,
void *unused)
{
switch (code) {
case SYS_DOWN:
case SYS_HALT:
case SYS_POWER_OFF:
if (host_control_on_shutdown) {
/* firmware is going to perform host control action */
printk(KERN_WARNING "Please wait for shutdown "
"action to complete...\n");
dcdbas_host_control();
}
break;
}
return NOTIFY_DONE;
}
static struct notifier_block dcdbas_reboot_nb = {
.notifier_call = dcdbas_reboot_notify,
.next = NULL,
.priority = INT_MIN
};
static DCDBAS_BIN_ATTR_RW(smi_data);
static struct bin_attribute *dcdbas_bin_attrs[] = {
&bin_attr_smi_data,
NULL
};
static DCDBAS_DEV_ATTR_RW(smi_data_buf_size);
static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr);
static DCDBAS_DEV_ATTR_WO(smi_request);
static DCDBAS_DEV_ATTR_RW(host_control_action);
static DCDBAS_DEV_ATTR_RW(host_control_smi_type);
static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown);
static struct attribute *dcdbas_dev_attrs[] = {
&dev_attr_smi_data_buf_size.attr,
&dev_attr_smi_data_buf_phys_addr.attr,
&dev_attr_smi_request.attr,
&dev_attr_host_control_action.attr,
&dev_attr_host_control_smi_type.attr,
&dev_attr_host_control_on_shutdown.attr,
NULL
};
static struct attribute_group dcdbas_attr_group = {
.attrs = dcdbas_dev_attrs,
};
static int __devinit dcdbas_probe(struct platform_device *dev)
{
int i, error;
host_control_action = HC_ACTION_NONE;
host_control_smi_type = HC_SMITYPE_NONE;
/*
* BIOS SMI calls require buffer addresses be in 32-bit address space.
* This is done by setting the DMA mask below.
*/
dcdbas_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
dcdbas_pdev->dev.dma_mask = &dcdbas_pdev->dev.coherent_dma_mask;
error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group);
if (error)
return error;
for (i = 0; dcdbas_bin_attrs[i]; i++) {
error = sysfs_create_bin_file(&dev->dev.kobj,
dcdbas_bin_attrs[i]);
if (error) {
while (--i >= 0)
sysfs_remove_bin_file(&dev->dev.kobj,
dcdbas_bin_attrs[i]);
sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
return error;
}
}
register_reboot_notifier(&dcdbas_reboot_nb);
dev_info(&dev->dev, "%s (version %s)\n",
DRIVER_DESCRIPTION, DRIVER_VERSION);
return 0;
}
static int __devexit dcdbas_remove(struct platform_device *dev)
{
int i;
unregister_reboot_notifier(&dcdbas_reboot_nb);
for (i = 0; dcdbas_bin_attrs[i]; i++)
sysfs_remove_bin_file(&dev->dev.kobj, dcdbas_bin_attrs[i]);
sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
return 0;
}
static struct platform_driver dcdbas_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.probe = dcdbas_probe,
.remove = __devexit_p(dcdbas_remove),
};
/**
* dcdbas_init: initialize driver
*/
static int __init dcdbas_init(void)
{
int error;
error = platform_driver_register(&dcdbas_driver);
if (error)
return error;
dcdbas_pdev = platform_device_alloc(DRIVER_NAME, -1);
if (!dcdbas_pdev) {
error = -ENOMEM;
goto err_unregister_driver;
}
error = platform_device_add(dcdbas_pdev);
if (error)
goto err_free_device;
return 0;
err_free_device:
platform_device_put(dcdbas_pdev);
err_unregister_driver:
platform_driver_unregister(&dcdbas_driver);
return error;
}
/**
* dcdbas_exit: perform driver cleanup
*/
static void __exit dcdbas_exit(void)
{
/*
* make sure functions that use dcdbas_pdev are called
* before platform_device_unregister
*/
unregister_reboot_notifier(&dcdbas_reboot_nb);
/*
* We have to free the buffer here instead of dcdbas_remove
* because only in module exit function we can be sure that
* all sysfs attributes belonging to this module have been
* released.
*/
smi_data_buf_free();
platform_device_unregister(dcdbas_pdev);
platform_driver_unregister(&dcdbas_driver);
}
module_init(dcdbas_init);
module_exit(dcdbas_exit);
MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
MODULE_VERSION(DRIVER_VERSION);
MODULE_AUTHOR("Dell Inc.");
MODULE_LICENSE("GPL");
/* Any System or BIOS claiming to be by Dell */
MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");

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

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

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

View File

@ -0,0 +1,749 @@
#include <linux/types.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/dmi.h>
#include <linux/efi.h>
#include <linux/bootmem.h>
#include <asm/dmi.h>
/*
* DMI stands for "Desktop Management Interface". It is part
* of and an antecedent to, SMBIOS, which stands for System
* Management BIOS. See further: http://www.dmtf.org/standards
*/
static char dmi_empty_string[] = " ";
/*
* Catch too early calls to dmi_check_system():
*/
static int dmi_initialized;
static const char * __init dmi_string_nosave(const struct dmi_header *dm, u8 s)
{
const u8 *bp = ((u8 *) dm) + dm->length;
if (s) {
s--;
while (s > 0 && *bp) {
bp += strlen(bp) + 1;
s--;
}
if (*bp != 0) {
size_t len = strlen(bp)+1;
size_t cmp_len = len > 8 ? 8 : len;
if (!memcmp(bp, dmi_empty_string, cmp_len))
return dmi_empty_string;
return bp;
}
}
return "";
}
static char * __init dmi_string(const struct dmi_header *dm, u8 s)
{
const char *bp = dmi_string_nosave(dm, s);
char *str;
size_t len;
if (bp == dmi_empty_string)
return dmi_empty_string;
len = strlen(bp) + 1;
str = dmi_alloc(len);
if (str != NULL)
strcpy(str, bp);
else
printk(KERN_ERR "dmi_string: cannot allocate %Zu bytes.\n", len);
return str;
}
/*
* We have to be cautious here. We have seen BIOSes with DMI pointers
* pointing to completely the wrong place for example
*/
static void dmi_table(u8 *buf, int len, int num,
void (*decode)(const struct dmi_header *, void *),
void *private_data)
{
u8 *data = buf;
int i = 0;
/*
* Stop when we see all the items the table claimed to have
* OR we run off the end of the table (also happens)
*/
while ((i < num) && (data - buf + sizeof(struct dmi_header)) <= len) {
const struct dmi_header *dm = (const struct dmi_header *)data;
/*
* We want to know the total length (formatted area and
* strings) before decoding to make sure we won't run off the
* table in dmi_decode or dmi_string
*/
data += dm->length;
while ((data - buf < len - 1) && (data[0] || data[1]))
data++;
if (data - buf < len - 1)
decode(dm, private_data);
data += 2;
i++;
}
}
static u32 dmi_base;
static u16 dmi_len;
static u16 dmi_num;
static int __init dmi_walk_early(void (*decode)(const struct dmi_header *,
void *))
{
u8 *buf;
buf = dmi_ioremap(dmi_base, dmi_len);
if (buf == NULL)
return -1;
dmi_table(buf, dmi_len, dmi_num, decode, NULL);
dmi_iounmap(buf, dmi_len);
return 0;
}
static int __init dmi_checksum(const u8 *buf)
{
u8 sum = 0;
int a;
for (a = 0; a < 15; a++)
sum += buf[a];
return sum == 0;
}
static char *dmi_ident[DMI_STRING_MAX];
static LIST_HEAD(dmi_devices);
int dmi_available;
/*
* Save a DMI string
*/
static void __init dmi_save_ident(const struct dmi_header *dm, int slot, int string)
{
const char *d = (const char*) dm;
char *p;
if (dmi_ident[slot])
return;
p = dmi_string(dm, d[string]);
if (p == NULL)
return;
dmi_ident[slot] = p;
}
static void __init dmi_save_uuid(const struct dmi_header *dm, int slot, int index)
{
const u8 *d = (u8*) dm + index;
char *s;
int is_ff = 1, is_00 = 1, i;
if (dmi_ident[slot])
return;
for (i = 0; i < 16 && (is_ff || is_00); i++) {
if(d[i] != 0x00) is_ff = 0;
if(d[i] != 0xFF) is_00 = 0;
}
if (is_ff || is_00)
return;
s = dmi_alloc(16*2+4+1);
if (!s)
return;
sprintf(s, "%pUB", d);
dmi_ident[slot] = s;
}
static void __init dmi_save_type(const struct dmi_header *dm, int slot, int index)
{
const u8 *d = (u8*) dm + index;
char *s;
if (dmi_ident[slot])
return;
s = dmi_alloc(4);
if (!s)
return;
sprintf(s, "%u", *d & 0x7F);
dmi_ident[slot] = s;
}
static void __init dmi_save_one_device(int type, const char *name)
{
struct dmi_device *dev;
/* No duplicate device */
if (dmi_find_device(type, name, NULL))
return;
dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1);
if (!dev) {
printk(KERN_ERR "dmi_save_one_device: out of memory.\n");
return;
}
dev->type = type;
strcpy((char *)(dev + 1), name);
dev->name = (char *)(dev + 1);
dev->device_data = NULL;
list_add(&dev->list, &dmi_devices);
}
static void __init dmi_save_devices(const struct dmi_header *dm)
{
int i, count = (dm->length - sizeof(struct dmi_header)) / 2;
for (i = 0; i < count; i++) {
const char *d = (char *)(dm + 1) + (i * 2);
/* Skip disabled device */
if ((*d & 0x80) == 0)
continue;
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1)));
}
}
static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm)
{
int i, count = *(u8 *)(dm + 1);
struct dmi_device *dev;
for (i = 1; i <= count; i++) {
char *devname = dmi_string(dm, i);
if (devname == dmi_empty_string)
continue;
dev = dmi_alloc(sizeof(*dev));
if (!dev) {
printk(KERN_ERR
"dmi_save_oem_strings_devices: out of memory.\n");
break;
}
dev->type = DMI_DEV_TYPE_OEM_STRING;
dev->name = devname;
dev->device_data = NULL;
list_add(&dev->list, &dmi_devices);
}
}
static void __init dmi_save_ipmi_device(const struct dmi_header *dm)
{
struct dmi_device *dev;
void * data;
data = dmi_alloc(dm->length);
if (data == NULL) {
printk(KERN_ERR "dmi_save_ipmi_device: out of memory.\n");
return;
}
memcpy(data, dm, dm->length);
dev = dmi_alloc(sizeof(*dev));
if (!dev) {
printk(KERN_ERR "dmi_save_ipmi_device: out of memory.\n");
return;
}
dev->type = DMI_DEV_TYPE_IPMI;
dev->name = "IPMI controller";
dev->device_data = data;
list_add_tail(&dev->list, &dmi_devices);
}
static void __init dmi_save_dev_onboard(int instance, int segment, int bus,
int devfn, const char *name)
{
struct dmi_dev_onboard *onboard_dev;
onboard_dev = dmi_alloc(sizeof(*onboard_dev) + strlen(name) + 1);
if (!onboard_dev) {
printk(KERN_ERR "dmi_save_dev_onboard: out of memory.\n");
return;
}
onboard_dev->instance = instance;
onboard_dev->segment = segment;
onboard_dev->bus = bus;
onboard_dev->devfn = devfn;
strcpy((char *)&onboard_dev[1], name);
onboard_dev->dev.type = DMI_DEV_TYPE_DEV_ONBOARD;
onboard_dev->dev.name = (char *)&onboard_dev[1];
onboard_dev->dev.device_data = onboard_dev;
list_add(&onboard_dev->dev.list, &dmi_devices);
}
static void __init dmi_save_extended_devices(const struct dmi_header *dm)
{
const u8 *d = (u8*) dm + 5;
/* Skip disabled device */
if ((*d & 0x80) == 0)
return;
dmi_save_dev_onboard(*(d+1), *(u16 *)(d+2), *(d+4), *(d+5),
dmi_string_nosave(dm, *(d-1)));
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d - 1)));
}
/*
* Process a DMI table entry. Right now all we care about are the BIOS
* and machine entries. For 2.5 we should pull the smbus controller info
* out of here.
*/
static void __init dmi_decode(const struct dmi_header *dm, void *dummy)
{
switch(dm->type) {
case 0: /* BIOS Information */
dmi_save_ident(dm, DMI_BIOS_VENDOR, 4);
dmi_save_ident(dm, DMI_BIOS_VERSION, 5);
dmi_save_ident(dm, DMI_BIOS_DATE, 8);
break;
case 1: /* System Information */
dmi_save_ident(dm, DMI_SYS_VENDOR, 4);
dmi_save_ident(dm, DMI_PRODUCT_NAME, 5);
dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6);
dmi_save_ident(dm, DMI_PRODUCT_SERIAL, 7);
dmi_save_uuid(dm, DMI_PRODUCT_UUID, 8);
break;
case 2: /* Base Board Information */
dmi_save_ident(dm, DMI_BOARD_VENDOR, 4);
dmi_save_ident(dm, DMI_BOARD_NAME, 5);
dmi_save_ident(dm, DMI_BOARD_VERSION, 6);
dmi_save_ident(dm, DMI_BOARD_SERIAL, 7);
dmi_save_ident(dm, DMI_BOARD_ASSET_TAG, 8);
break;
case 3: /* Chassis Information */
dmi_save_ident(dm, DMI_CHASSIS_VENDOR, 4);
dmi_save_type(dm, DMI_CHASSIS_TYPE, 5);
dmi_save_ident(dm, DMI_CHASSIS_VERSION, 6);
dmi_save_ident(dm, DMI_CHASSIS_SERIAL, 7);
dmi_save_ident(dm, DMI_CHASSIS_ASSET_TAG, 8);
break;
case 10: /* Onboard Devices Information */
dmi_save_devices(dm);
break;
case 11: /* OEM Strings */
dmi_save_oem_strings_devices(dm);
break;
case 38: /* IPMI Device Information */
dmi_save_ipmi_device(dm);
break;
case 41: /* Onboard Devices Extended Information */
dmi_save_extended_devices(dm);
}
}
static void __init print_filtered(const char *info)
{
const char *p;
if (!info)
return;
for (p = info; *p; p++)
if (isprint(*p))
printk(KERN_CONT "%c", *p);
else
printk(KERN_CONT "\\x%02x", *p & 0xff);
}
static void __init dmi_dump_ids(void)
{
const char *board; /* Board Name is optional */
printk(KERN_DEBUG "DMI: ");
print_filtered(dmi_get_system_info(DMI_SYS_VENDOR));
printk(KERN_CONT " ");
print_filtered(dmi_get_system_info(DMI_PRODUCT_NAME));
board = dmi_get_system_info(DMI_BOARD_NAME);
if (board) {
printk(KERN_CONT "/");
print_filtered(board);
}
printk(KERN_CONT ", BIOS ");
print_filtered(dmi_get_system_info(DMI_BIOS_VERSION));
printk(KERN_CONT " ");
print_filtered(dmi_get_system_info(DMI_BIOS_DATE));
printk(KERN_CONT "\n");
}
static int __init dmi_present(const char __iomem *p)
{
u8 buf[15];
memcpy_fromio(buf, p, 15);
if ((memcmp(buf, "_DMI_", 5) == 0) && dmi_checksum(buf)) {
dmi_num = (buf[13] << 8) | buf[12];
dmi_len = (buf[7] << 8) | buf[6];
dmi_base = (buf[11] << 24) | (buf[10] << 16) |
(buf[9] << 8) | buf[8];
/*
* DMI version 0.0 means that the real version is taken from
* the SMBIOS version, which we don't know at this point.
*/
if (buf[14] != 0)
printk(KERN_INFO "DMI %d.%d present.\n",
buf[14] >> 4, buf[14] & 0xF);
else
printk(KERN_INFO "DMI present.\n");
if (dmi_walk_early(dmi_decode) == 0) {
dmi_dump_ids();
return 0;
}
}
return 1;
}
void __init dmi_scan_machine(void)
{
char __iomem *p, *q;
int rc;
if (efi_enabled) {
if (efi.smbios == EFI_INVALID_TABLE_ADDR)
goto error;
/* This is called as a core_initcall() because it isn't
* needed during early boot. This also means we can
* iounmap the space when we're done with it.
*/
p = dmi_ioremap(efi.smbios, 32);
if (p == NULL)
goto error;
rc = dmi_present(p + 0x10); /* offset of _DMI_ string */
dmi_iounmap(p, 32);
if (!rc) {
dmi_available = 1;
goto out;
}
}
else {
/*
* no iounmap() for that ioremap(); it would be a no-op, but
* it's so early in setup that sucker gets confused into doing
* what it shouldn't if we actually call it.
*/
p = dmi_ioremap(0xF0000, 0x10000);
if (p == NULL)
goto error;
for (q = p; q < p + 0x10000; q += 16) {
rc = dmi_present(q);
if (!rc) {
dmi_available = 1;
dmi_iounmap(p, 0x10000);
goto out;
}
}
dmi_iounmap(p, 0x10000);
}
error:
printk(KERN_INFO "DMI not present or invalid.\n");
out:
dmi_initialized = 1;
}
/**
* dmi_matches - check if dmi_system_id structure matches system DMI data
* @dmi: pointer to the dmi_system_id structure to check
*/
static bool dmi_matches(const struct dmi_system_id *dmi)
{
int i;
WARN(!dmi_initialized, KERN_ERR "dmi check: not initialized yet.\n");
for (i = 0; i < ARRAY_SIZE(dmi->matches); i++) {
int s = dmi->matches[i].slot;
if (s == DMI_NONE)
break;
if (dmi_ident[s]
&& strstr(dmi_ident[s], dmi->matches[i].substr))
continue;
/* No match */
return false;
}
return true;
}
/**
* dmi_is_end_of_table - check for end-of-table marker
* @dmi: pointer to the dmi_system_id structure to check
*/
static bool dmi_is_end_of_table(const struct dmi_system_id *dmi)
{
return dmi->matches[0].slot == DMI_NONE;
}
/**
* dmi_check_system - check system DMI data
* @list: array of dmi_system_id structures to match against
* All non-null elements of the list must match
* their slot's (field index's) data (i.e., each
* list string must be a substring of the specified
* DMI slot's string data) to be considered a
* successful match.
*
* Walk the blacklist table running matching functions until someone
* returns non zero or we hit the end. Callback function is called for
* each successful match. Returns the number of matches.
*/
int dmi_check_system(const struct dmi_system_id *list)
{
int count = 0;
const struct dmi_system_id *d;
for (d = list; !dmi_is_end_of_table(d); d++)
if (dmi_matches(d)) {
count++;
if (d->callback && d->callback(d))
break;
}
return count;
}
EXPORT_SYMBOL(dmi_check_system);
/**
* dmi_first_match - find dmi_system_id structure matching system DMI data
* @list: array of dmi_system_id structures to match against
* All non-null elements of the list must match
* their slot's (field index's) data (i.e., each
* list string must be a substring of the specified
* DMI slot's string data) to be considered a
* successful match.
*
* Walk the blacklist table until the first match is found. Return the
* pointer to the matching entry or NULL if there's no match.
*/
const struct dmi_system_id *dmi_first_match(const struct dmi_system_id *list)
{
const struct dmi_system_id *d;
for (d = list; !dmi_is_end_of_table(d); d++)
if (dmi_matches(d))
return d;
return NULL;
}
EXPORT_SYMBOL(dmi_first_match);
/**
* dmi_get_system_info - return DMI data value
* @field: data index (see enum dmi_field)
*
* Returns one DMI data value, can be used to perform
* complex DMI data checks.
*/
const char *dmi_get_system_info(int field)
{
return dmi_ident[field];
}
EXPORT_SYMBOL(dmi_get_system_info);
/**
* dmi_name_in_serial - Check if string is in the DMI product serial information
* @str: string to check for
*/
int dmi_name_in_serial(const char *str)
{
int f = DMI_PRODUCT_SERIAL;
if (dmi_ident[f] && strstr(dmi_ident[f], str))
return 1;
return 0;
}
/**
* dmi_name_in_vendors - Check if string is 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 = ioremap(dmi_base, dmi_len);
if (buf == NULL)
return -1;
dmi_table(buf, dmi_len, dmi_num, decode, private_data);
iounmap(buf);
return 0;
}
EXPORT_SYMBOL_GPL(dmi_walk);
/**
* dmi_match - compare a string to the dmi field (if exists)
* @f: DMI field identifier
* @str: string to compare the DMI field to
*
* Returns true if the requested field equals to the str (including NULL).
*/
bool dmi_match(enum dmi_field f, const char *str)
{
const char *info = dmi_get_system_info(f);
if (info == NULL || str == NULL)
return info == str;
return !strcmp(info, str);
}
EXPORT_SYMBOL_GPL(dmi_match);

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,949 @@
/*
* 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>
#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */
/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
#define GSMI_SHUTDOWN_NMIWDT 1 /* NMI Watchdog */
#define GSMI_SHUTDOWN_PANIC 2 /* Panic */
#define GSMI_SHUTDOWN_OOPS 3 /* Oops */
#define GSMI_SHUTDOWN_DIE 4 /* Die -- No longer meaningful */
#define GSMI_SHUTDOWN_MCE 5 /* Machine Check */
#define GSMI_SHUTDOWN_SOFTWDT 6 /* Software Watchdog */
#define GSMI_SHUTDOWN_MBE 7 /* Uncorrected ECC */
#define GSMI_SHUTDOWN_TRIPLE 8 /* Triple Fault */
#define DRIVER_VERSION "1.0"
#define GSMI_GUID_SIZE 16
#define GSMI_BUF_SIZE 1024
#define GSMI_BUF_ALIGN sizeof(u64)
#define GSMI_CALLBACK 0xef
/* SMI return codes */
#define GSMI_SUCCESS 0x00
#define GSMI_UNSUPPORTED2 0x03
#define GSMI_LOG_FULL 0x0b
#define GSMI_VAR_NOT_FOUND 0x0e
#define GSMI_HANDSHAKE_SPIN 0x7d
#define GSMI_HANDSHAKE_CF 0x7e
#define GSMI_HANDSHAKE_NONE 0x7f
#define GSMI_INVALID_PARAMETER 0x82
#define GSMI_UNSUPPORTED 0x83
#define GSMI_BUFFER_TOO_SMALL 0x85
#define GSMI_NOT_READY 0x86
#define GSMI_DEVICE_ERROR 0x87
#define GSMI_NOT_FOUND 0x8e
#define QUIRKY_BOARD_HASH 0x78a30a50
/* Internally used commands passed to the firmware */
#define GSMI_CMD_GET_NVRAM_VAR 0x01
#define GSMI_CMD_GET_NEXT_VAR 0x02
#define GSMI_CMD_SET_NVRAM_VAR 0x03
#define GSMI_CMD_SET_EVENT_LOG 0x08
#define GSMI_CMD_CLEAR_EVENT_LOG 0x09
#define GSMI_CMD_CLEAR_CONFIG 0x20
#define GSMI_CMD_HANDSHAKE_TYPE 0xC1
/* Magic entry type for kernel events */
#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD
/* SMI buffers must be in 32bit physical address space */
struct gsmi_buf {
u8 *start; /* start of buffer */
size_t length; /* length of buffer */
dma_addr_t handle; /* dma allocation handle */
u32 address; /* physical address of buffer */
};
struct gsmi_device {
struct platform_device *pdev; /* platform device */
struct gsmi_buf *name_buf; /* variable name buffer */
struct gsmi_buf *data_buf; /* generic data buffer */
struct gsmi_buf *param_buf; /* parameter buffer */
spinlock_t lock; /* serialize access to SMIs */
u16 smi_cmd; /* SMI command port */
int handshake_type; /* firmware handler interlock type */
struct dma_pool *dma_pool; /* DMA buffer pool */
} gsmi_dev;
/* Packed structures for communicating with the firmware */
struct gsmi_nvram_var_param {
efi_guid_t guid;
u32 name_ptr;
u32 attributes;
u32 data_len;
u32 data_ptr;
} __packed;
struct gsmi_get_next_var_param {
u8 guid[GSMI_GUID_SIZE];
u32 name_ptr;
u32 name_len;
} __packed;
struct gsmi_set_eventlog_param {
u32 data_ptr;
u32 data_len;
u32 type;
} __packed;
/* Event log formats */
struct gsmi_log_entry_type_1 {
u16 type;
u32 instance;
} __packed;
/*
* Some platforms don't have explicit SMI handshake
* and need to wait for SMI to complete.
*/
#define GSMI_DEFAULT_SPINCOUNT 0x10000
static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT;
module_param(spincount, uint, 0600);
MODULE_PARM_DESC(spincount,
"The number of loop iterations to use when using the spin handshake.");
static struct gsmi_buf *gsmi_buf_alloc(void)
{
struct gsmi_buf *smibuf;
smibuf = kzalloc(sizeof(*smibuf), GFP_KERNEL);
if (!smibuf) {
printk(KERN_ERR "gsmi: out of memory\n");
return NULL;
}
/* allocate buffer in 32bit address space */
smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL,
&smibuf->handle);
if (!smibuf->start) {
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
kfree(smibuf);
return NULL;
}
/* fill in the buffer handle */
smibuf->length = GSMI_BUF_SIZE;
smibuf->address = (u32)virt_to_phys(smibuf->start);
return smibuf;
}
static void gsmi_buf_free(struct gsmi_buf *smibuf)
{
if (smibuf) {
if (smibuf->start)
dma_pool_free(gsmi_dev.dma_pool, smibuf->start,
smibuf->handle);
kfree(smibuf);
}
}
/*
* Make a call to gsmi func(sub). GSMI error codes are translated to
* in-kernel errnos (0 on success, -ERRNO on error).
*/
static int gsmi_exec(u8 func, u8 sub)
{
u16 cmd = (sub << 8) | func;
u16 result = 0;
int rc = 0;
/*
* AH : Subfunction number
* AL : Function number
* EBX : Parameter block address
* DX : SMI command port
*
* Three protocols here. See also the comment in gsmi_init().
*/
if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) {
/*
* If handshake_type == HANDSHAKE_CF then set CF on the
* way in and wait for the handler to clear it; this avoids
* corrupting register state on those chipsets which have
* a delay between writing the SMI trigger register and
* entering SMM.
*/
asm volatile (
"stc\n"
"outb %%al, %%dx\n"
"1: jc 1b\n"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address)
: "memory", "cc"
);
} else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) {
/*
* If handshake_type == HANDSHAKE_SPIN we spin a
* hundred-ish usecs to ensure the SMI has triggered.
*/
asm volatile (
"outb %%al, %%dx\n"
"1: loop 1b\n"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address),
"c" (spincount)
: "memory", "cc"
);
} else {
/*
* If handshake_type == HANDSHAKE_NONE we do nothing;
* either we don't need to or it's legacy firmware that
* doesn't understand the CF protocol.
*/
asm volatile (
"outb %%al, %%dx\n\t"
: "=a" (result)
: "0" (cmd),
"d" (gsmi_dev.smi_cmd),
"b" (gsmi_dev.param_buf->address)
: "memory", "cc"
);
}
/* check return code from SMI handler */
switch (result) {
case GSMI_SUCCESS:
break;
case GSMI_VAR_NOT_FOUND:
/* not really an error, but let the caller know */
rc = 1;
break;
case GSMI_INVALID_PARAMETER:
printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd);
rc = -EINVAL;
break;
case GSMI_BUFFER_TOO_SMALL:
printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd);
rc = -ENOMEM;
break;
case GSMI_UNSUPPORTED:
case GSMI_UNSUPPORTED2:
if (sub != GSMI_CMD_HANDSHAKE_TYPE)
printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n",
cmd);
rc = -ENOSYS;
break;
case GSMI_NOT_READY:
printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd);
rc = -EBUSY;
break;
case GSMI_DEVICE_ERROR:
printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd);
rc = -EFAULT;
break;
case GSMI_NOT_FOUND:
printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd);
rc = -ENOENT;
break;
case GSMI_LOG_FULL:
printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd);
rc = -ENOSPC;
break;
case GSMI_HANDSHAKE_CF:
case GSMI_HANDSHAKE_SPIN:
case GSMI_HANDSHAKE_NONE:
rc = result;
break;
default:
printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n",
cmd, result);
rc = -ENXIO;
}
return rc;
}
/* Return the number of unicode characters in data */
static size_t
utf16_strlen(efi_char16_t *data, unsigned long maxlength)
{
unsigned long length = 0;
while (*data++ != 0 && length < maxlength)
length++;
return length;
}
static efi_status_t gsmi_get_variable(efi_char16_t *name,
efi_guid_t *vendor, u32 *attr,
unsigned long *data_size,
void *data)
{
struct gsmi_nvram_var_param param = {
.name_ptr = gsmi_dev.name_buf->address,
.data_ptr = gsmi_dev.data_buf->address,
.data_len = (u32)*data_size,
};
efi_status_t ret = EFI_SUCCESS;
unsigned long flags;
size_t name_len = utf16_strlen(name, GSMI_BUF_SIZE / 2);
int rc;
if (name_len >= GSMI_BUF_SIZE / 2)
return EFI_BAD_BUFFER_SIZE;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* Vendor guid */
memcpy(&param.guid, vendor, sizeof(param.guid));
/* variable name, already in UTF-16 */
memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
/* data pointer */
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NVRAM_VAR);
if (rc < 0) {
printk(KERN_ERR "gsmi: Get Variable failed\n");
ret = EFI_LOAD_ERROR;
} else if (rc == 1) {
/* variable was not found */
ret = EFI_NOT_FOUND;
} else {
/* Get the arguments back */
memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
/* The size reported is the min of all of our buffers */
*data_size = min_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 (utf16_strlen(name, GSMI_BUF_SIZE / 2) == GSMI_BUF_SIZE / 2)
return EFI_INVALID_PARAMETER;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* guid */
memcpy(&param.guid, vendor, sizeof(param.guid));
/* variable name, already in UTF-16 */
memcpy(gsmi_dev.name_buf->start, name, *name_size);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NEXT_VAR);
if (rc < 0) {
printk(KERN_ERR "gsmi: Get Next Variable Name failed\n");
ret = EFI_LOAD_ERROR;
} else if (rc == 1) {
/* variable not found -- end of list */
ret = EFI_NOT_FOUND;
} else {
/* copy variable data back to return buffer */
memcpy(&param, gsmi_dev.param_buf->start, sizeof(param));
/* Copy the name back */
memcpy(name, gsmi_dev.name_buf->start, GSMI_BUF_SIZE);
*name_size = utf16_strlen(name, GSMI_BUF_SIZE / 2) * 2;
/* copy guid to return buffer */
memcpy(vendor, &param.guid, sizeof(param.guid));
ret = EFI_SUCCESS;
}
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
return ret;
}
static efi_status_t gsmi_set_variable(efi_char16_t *name,
efi_guid_t *vendor,
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 = utf16_strlen(name, GSMI_BUF_SIZE / 2);
efi_status_t ret = EFI_SUCCESS;
int rc;
unsigned long flags;
if (name_len >= GSMI_BUF_SIZE / 2)
return EFI_BAD_BUFFER_SIZE;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* guid */
memcpy(&param.guid, vendor, sizeof(param.guid));
/* variable name, already in UTF-16 */
memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
/* data pointer */
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
memcpy(gsmi_dev.data_buf->start, data, data_size);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_NVRAM_VAR);
if (rc < 0) {
printk(KERN_ERR "gsmi: Set Variable failed\n");
ret = EFI_INVALID_PARAMETER;
}
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
return ret;
}
static const struct efivar_operations efivar_ops = {
.get_variable = gsmi_get_variable,
.set_variable = gsmi_set_variable,
.get_next_variable = gsmi_get_next_variable,
};
static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
struct gsmi_set_eventlog_param param = {
.data_ptr = gsmi_dev.data_buf->address,
};
int rc = 0;
unsigned long flags;
/* Pull the type out */
if (count < sizeof(u32))
return -EINVAL;
param.type = *(u32 *)buf;
count -= sizeof(u32);
buf += sizeof(u32);
/* The remaining buffer is the data payload */
if (count > gsmi_dev.data_buf->length)
return -EINVAL;
param.data_len = count - sizeof(u32);
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* data pointer */
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
memcpy(gsmi_dev.data_buf->start, buf, param.data_len);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
if (rc < 0)
printk(KERN_ERR "gsmi: Set Event Log failed\n");
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
return rc;
}
static struct bin_attribute eventlog_bin_attr = {
.attr = {.name = "append_to_eventlog", .mode = 0200},
.write = eventlog_write,
};
static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int rc;
unsigned long flags;
unsigned long val;
struct {
u32 percentage;
u32 data_type;
} param;
rc = strict_strtoul(buf, 0, &val);
if (rc)
return rc;
/*
* Value entered is a percentage, 0 through 100, anything else
* is invalid.
*/
if (val > 100)
return -EINVAL;
/* data_type here selects the smbios event log. */
param.percentage = val;
param.data_type = 0;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_EVENT_LOG);
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
if (rc)
return rc;
return count;
}
static struct kobj_attribute gsmi_clear_eventlog_attr = {
.attr = {.name = "clear_eventlog", .mode = 0200},
.store = gsmi_clear_eventlog_store,
};
static ssize_t gsmi_clear_config_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int rc;
unsigned long flags;
spin_lock_irqsave(&gsmi_dev.lock, flags);
/* clear parameter buffer */
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_CONFIG);
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
if (rc)
return rc;
return count;
}
static struct kobj_attribute gsmi_clear_config_attr = {
.attr = {.name = "clear_config", .mode = 0200},
.store = gsmi_clear_config_store,
};
static const struct attribute *gsmi_attrs[] = {
&gsmi_clear_config_attr.attr,
&gsmi_clear_eventlog_attr.attr,
NULL,
};
static int gsmi_shutdown_reason(int reason)
{
struct gsmi_log_entry_type_1 entry = {
.type = GSMI_LOG_ENTRY_TYPE_KERNEL,
.instance = reason,
};
struct gsmi_set_eventlog_param param = {
.data_len = sizeof(entry),
.type = 1,
};
static int saved_reason;
int rc = 0;
unsigned long flags;
/* avoid duplicate entries in the log */
if (saved_reason & (1 << reason))
return 0;
spin_lock_irqsave(&gsmi_dev.lock, flags);
saved_reason |= (1 << reason);
/* data pointer */
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
memcpy(gsmi_dev.data_buf->start, &entry, sizeof(entry));
/* parameter buffer */
param.data_ptr = gsmi_dev.data_buf->address;
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
memcpy(gsmi_dev.param_buf->start, &param, sizeof(param));
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
if (rc < 0)
printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n");
else
printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n",
reason);
return rc;
}
static int gsmi_reboot_callback(struct notifier_block *nb,
unsigned long reason, void *arg)
{
gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN);
return NOTIFY_DONE;
}
static struct notifier_block gsmi_reboot_notifier = {
.notifier_call = gsmi_reboot_callback
};
static int gsmi_die_callback(struct notifier_block *nb,
unsigned long reason, void *arg)
{
if (reason == DIE_OOPS)
gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS);
return NOTIFY_DONE;
}
static struct notifier_block gsmi_die_notifier = {
.notifier_call = gsmi_die_callback
};
static int gsmi_panic_callback(struct notifier_block *nb,
unsigned long reason, void *arg)
{
gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC);
return NOTIFY_DONE;
}
static struct notifier_block gsmi_panic_notifier = {
.notifier_call = gsmi_panic_callback,
};
/*
* This hash function was blatantly copied from include/linux/hash.h.
* It is used by this driver to obfuscate a board name that requires a
* quirk within this driver.
*
* Please do not remove this copy of the function as any changes to the
* global utility hash_64() function would break this driver's ability
* to identify a board and provide the appropriate quirk -- mikew@google.com
*/
static u64 __init local_hash_64(u64 val, unsigned bits)
{
u64 hash = val;
/* Sigh, gcc can't optimise this alone like it does for 32 bits. */
u64 n = hash;
n <<= 18;
hash -= n;
n <<= 33;
hash -= n;
n <<= 3;
hash += n;
n <<= 3;
hash -= n;
n <<= 4;
hash += n;
n <<= 2;
hash += n;
/* High bits are more random, so use them. */
return hash >> (64 - bits);
}
static u32 __init hash_oem_table_id(char s[8])
{
u64 input;
memcpy(&input, s, 8);
return local_hash_64(input, 32);
}
static struct dmi_system_id gsmi_dmi_table[] __initdata = {
{
.ident = "Google Board",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
static __init int gsmi_system_valid(void)
{
u32 hash;
if (!dmi_check_system(gsmi_dmi_table))
return -ENODEV;
/*
* Only newer firmware supports the gsmi interface. All older
* firmware that didn't support this interface used to plug the
* table name in the first four bytes of the oem_table_id field.
* Newer firmware doesn't do that though, so use that as the
* discriminant factor. We have to do this in order to
* whitewash our board names out of the public driver.
*/
if (!strncmp(acpi_gbl_FADT.header.oem_table_id, "FACP", 4)) {
printk(KERN_INFO "gsmi: Board is too old\n");
return -ENODEV;
}
/* Disable on board with 1.0 BIOS due to Google bug 2602657 */
hash = hash_oem_table_id(acpi_gbl_FADT.header.oem_table_id);
if (hash == QUIRKY_BOARD_HASH) {
const char *bios_ver = dmi_get_system_info(DMI_BIOS_VERSION);
if (strncmp(bios_ver, "1.0", 3) == 0) {
pr_info("gsmi: disabled on this board's BIOS %s\n",
bios_ver);
return -ENODEV;
}
}
/* check for valid SMI command port in ACPI FADT */
if (acpi_gbl_FADT.smi_command == 0) {
pr_info("gsmi: missing smi_command\n");
return -ENODEV;
}
/* Found */
return 0;
}
static struct kobject *gsmi_kobj;
static struct efivars efivars;
static __init int gsmi_init(void)
{
unsigned long flags;
int ret;
ret = gsmi_system_valid();
if (ret)
return ret;
gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
/* register device */
gsmi_dev.pdev = platform_device_register_simple("gsmi", -1, NULL, 0);
if (IS_ERR(gsmi_dev.pdev)) {
printk(KERN_ERR "gsmi: unable to register platform device\n");
return PTR_ERR(gsmi_dev.pdev);
}
/* SMI access needs to be serialized */
spin_lock_init(&gsmi_dev.lock);
/* SMI callbacks require 32bit addresses */
gsmi_dev.pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
gsmi_dev.pdev->dev.dma_mask =
&gsmi_dev.pdev->dev.coherent_dma_mask;
ret = -ENOMEM;
gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev,
GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0);
if (!gsmi_dev.dma_pool)
goto out_err;
/*
* pre-allocate buffers because sometimes we are called when
* this is not feasible: oops, panic, die, mce, etc
*/
gsmi_dev.name_buf = gsmi_buf_alloc();
if (!gsmi_dev.name_buf) {
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
goto out_err;
}
gsmi_dev.data_buf = gsmi_buf_alloc();
if (!gsmi_dev.data_buf) {
printk(KERN_ERR "gsmi: failed to allocate data buffer\n");
goto out_err;
}
gsmi_dev.param_buf = gsmi_buf_alloc();
if (!gsmi_dev.param_buf) {
printk(KERN_ERR "gsmi: failed to allocate param buffer\n");
goto out_err;
}
/*
* Determine type of handshake used to serialize the SMI
* entry. See also gsmi_exec().
*
* There's a "behavior" present on some chipsets where writing the
* SMI trigger register in the southbridge doesn't result in an
* immediate SMI. Rather, the processor can execute "a few" more
* instructions before the SMI takes effect. To ensure synchronous
* behavior, implement a handshake between the kernel driver and the
* firmware handler to spin until released. This ioctl determines
* the type of handshake.
*
* NONE: The firmware handler does not implement any
* handshake. Either it doesn't need to, or it's legacy firmware
* that doesn't know it needs to and never will.
*
* CF: The firmware handler will clear the CF in the saved
* state before returning. The driver may set the CF and test for
* it to clear before proceeding.
*
* SPIN: The firmware handler does not implement any handshake
* but the driver should spin for a hundred or so microseconds
* to ensure the SMI has triggered.
*
* Finally, the handler will return -ENOSYS if
* GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies
* HANDSHAKE_NONE.
*/
spin_lock_irqsave(&gsmi_dev.lock, flags);
gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN;
gsmi_dev.handshake_type =
gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE);
if (gsmi_dev.handshake_type == -ENOSYS)
gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE;
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
/* Remove and clean up gsmi if the handshake could not complete. */
if (gsmi_dev.handshake_type == -ENXIO) {
printk(KERN_INFO "gsmi version " DRIVER_VERSION
" failed to load\n");
ret = -ENODEV;
goto out_err;
}
/* 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 = register_efivars(&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);
unregister_efivars(&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,166 @@
/*
* memconsole.c
*
* Infrastructure for importing the BIOS memory based console
* into the kernel log ringbuffer.
*
* Copyright 2010 Google Inc. All rights reserved.
*/
#include <linux/ctype.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/dmi.h>
#include <asm/bios_ebda.h>
#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
struct biosmemcon_ebda {
u32 signature;
union {
struct {
u8 enabled;
u32 buffer_addr;
u16 start;
u16 end;
u16 num_chars;
u8 wrapped;
} __packed v1;
struct {
u32 buffer_addr;
/* Misdocumented as number of pages! */
u16 num_bytes;
u16 start;
u16 end;
} __packed v2;
};
} __packed;
static char *memconsole_baseaddr;
static size_t memconsole_length;
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
memconsole_length);
}
static struct bin_attribute memconsole_bin_attr = {
.attr = {.name = "log", .mode = 0444},
.read = memconsole_read,
};
static void found_v1_header(struct biosmemcon_ebda *hdr)
{
printk(KERN_INFO "BIOS console v1 EBDA structure found at %p\n", hdr);
printk(KERN_INFO "BIOS console buffer at 0x%.8x, "
"start = %d, end = %d, num = %d\n",
hdr->v1.buffer_addr, hdr->v1.start,
hdr->v1.end, hdr->v1.num_chars);
memconsole_length = hdr->v1.num_chars;
memconsole_baseaddr = phys_to_virt(hdr->v1.buffer_addr);
}
static void found_v2_header(struct biosmemcon_ebda *hdr)
{
printk(KERN_INFO "BIOS console v2 EBDA structure found at %p\n", hdr);
printk(KERN_INFO "BIOS console buffer at 0x%.8x, "
"start = %d, end = %d, num_bytes = %d\n",
hdr->v2.buffer_addr, hdr->v2.start,
hdr->v2.end, hdr->v2.num_bytes);
memconsole_length = hdr->v2.end - hdr->v2.start;
memconsole_baseaddr = phys_to_virt(hdr->v2.buffer_addr
+ hdr->v2.start);
}
/*
* Search through the EBDA for the BIOS Memory Console, and
* set the global variables to point to it. Return true if found.
*/
static bool found_memconsole(void)
{
unsigned int address;
size_t length, cur;
address = get_bios_ebda();
if (!address) {
printk(KERN_INFO "BIOS EBDA non-existent.\n");
return false;
}
/* EBDA length is byte 0 of EBDA (in KB) */
length = *(u8 *)phys_to_virt(address);
length <<= 10; /* convert to bytes */
/*
* Search through EBDA for BIOS memory console structure
* note: signature is not necessarily dword-aligned
*/
for (cur = 0; cur < length; cur++) {
struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
/* memconsole v1 */
if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
found_v1_header(hdr);
return true;
}
/* memconsole v2 */
if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
found_v2_header(hdr);
return true;
}
}
printk(KERN_INFO "BIOS console EBDA structure not found!\n");
return false;
}
static struct dmi_system_id memconsole_dmi_table[] __initdata = {
{
.ident = "Google Board",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
static int __init memconsole_init(void)
{
int ret;
if (!dmi_check_system(memconsole_dmi_table))
return -ENODEV;
if (!found_memconsole())
return -ENODEV;
memconsole_bin_attr.size = memconsole_length;
ret = sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
return ret;
}
static void __exit memconsole_exit(void)
{
sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
}
module_init(memconsole_init);
module_exit(memconsole_exit);
MODULE_AUTHOR("Google, Inc.");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,821 @@
/*
* 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" },
};
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)
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;
}

View File

@ -0,0 +1,250 @@
/*
* linux/drivers/firmware/memmap.c
* Copyright (C) 2008 SUSE LINUX Products GmbH
* by Bernhard Walle <bernhard.walle@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/string.h>
#include <linux/firmware-map.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/bootmem.h>
#include <linux/slab.h>
/*
* Data types ------------------------------------------------------------------
*/
/*
* Firmware map entry. Because firmware memory maps are flat and not
* hierarchical, it's ok to organise them in a linked list. No parent
* information is necessary as for the resource tree.
*/
struct firmware_map_entry {
/*
* start and end must be u64 rather than resource_size_t, because e820
* resources can lie at addresses above 4G.
*/
u64 start; /* start of the memory range */
u64 end; /* end of the memory range (incl.) */
const char *type; /* type of the memory range */
struct list_head list; /* entry for the linked list */
struct kobject kobj; /* kobject for each entry */
};
/*
* Forward declarations --------------------------------------------------------
*/
static ssize_t memmap_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf);
static ssize_t start_show(struct firmware_map_entry *entry, char *buf);
static ssize_t end_show(struct firmware_map_entry *entry, char *buf);
static ssize_t type_show(struct firmware_map_entry *entry, char *buf);
/*
* Static data -----------------------------------------------------------------
*/
struct memmap_attribute {
struct attribute attr;
ssize_t (*show)(struct firmware_map_entry *entry, char *buf);
};
static struct memmap_attribute memmap_start_attr = __ATTR_RO(start);
static struct memmap_attribute memmap_end_attr = __ATTR_RO(end);
static struct memmap_attribute memmap_type_attr = __ATTR_RO(type);
/*
* These are default attributes that are added for every memmap entry.
*/
static struct attribute *def_attrs[] = {
&memmap_start_attr.attr,
&memmap_end_attr.attr,
&memmap_type_attr.attr,
NULL
};
static const struct sysfs_ops memmap_attr_ops = {
.show = memmap_attr_show,
};
static struct kobj_type memmap_ktype = {
.sysfs_ops = &memmap_attr_ops,
.default_attrs = def_attrs,
};
/*
* Registration functions ------------------------------------------------------
*/
/*
* Firmware memory map entries. No locking is needed because the
* firmware_map_add() and firmware_map_add_early() functions are called
* in firmware initialisation code in one single thread of execution.
*/
static LIST_HEAD(map_entries);
/**
* firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
* @start: Start of the memory range.
* @end: End of the memory range (inclusive).
* @type: Type of the memory range.
* @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised
* entry.
*
* Common implementation of firmware_map_add() and firmware_map_add_early()
* which expects a pre-allocated struct firmware_map_entry.
**/
static int firmware_map_add_entry(u64 start, u64 end,
const char *type,
struct firmware_map_entry *entry)
{
BUG_ON(start > end);
entry->start = start;
entry->end = end;
entry->type = type;
INIT_LIST_HEAD(&entry->list);
kobject_init(&entry->kobj, &memmap_ktype);
list_add_tail(&entry->list, &map_entries);
return 0;
}
/*
* Add memmap entry on sysfs
*/
static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
{
static int map_entries_nr;
static struct kset *mmap_kset;
if (!mmap_kset) {
mmap_kset = kset_create_and_add("memmap", NULL, firmware_kobj);
if (!mmap_kset)
return -ENOMEM;
}
entry->kobj.kset = mmap_kset;
if (kobject_add(&entry->kobj, NULL, "%d", map_entries_nr++))
kobject_put(&entry->kobj);
return 0;
}
/**
* firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
* memory hotplug.
* @start: Start of the memory range.
* @end: End of the memory range (inclusive).
* @type: Type of the memory range.
*
* Adds a firmware mapping entry. This function is for memory hotplug, it is
* similar to function firmware_map_add_early(). The only difference is that
* it will create the syfs entry dynamically.
*
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
**/
int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
{
struct firmware_map_entry *entry;
entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC);
if (!entry)
return -ENOMEM;
firmware_map_add_entry(start, end, type, entry);
/* create the memmap entry */
add_sysfs_fw_map_entry(entry);
return 0;
}
/**
* firmware_map_add_early() - Adds a firmware mapping entry.
* @start: Start of the memory range.
* @end: End of the memory range (inclusive).
* @type: Type of the memory range.
*
* Adds a firmware mapping entry. This function uses the bootmem allocator
* for memory allocation.
*
* That function must be called before late_initcall.
*
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
**/
int __init firmware_map_add_early(u64 start, u64 end, const char *type)
{
struct firmware_map_entry *entry;
entry = alloc_bootmem(sizeof(struct firmware_map_entry));
if (WARN_ON(!entry))
return -ENOMEM;
return firmware_map_add_entry(start, end, type, entry);
}
/*
* Sysfs functions -------------------------------------------------------------
*/
static ssize_t start_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
(unsigned long long)entry->start);
}
static ssize_t end_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
(unsigned long long)entry->end);
}
static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", entry->type);
}
#define to_memmap_attr(_attr) container_of(_attr, struct memmap_attribute, attr)
#define to_memmap_entry(obj) container_of(obj, struct firmware_map_entry, kobj)
static ssize_t memmap_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct firmware_map_entry *entry = to_memmap_entry(kobj);
struct memmap_attribute *memmap_attr = to_memmap_attr(attr);
return memmap_attr->show(entry, buf);
}
/*
* Initialises stuff and adds the entries in the map_entries list to
* sysfs. Important is that firmware_map_add() and firmware_map_add_early()
* must be called before late_initcall. That's just because that function
* is called as late_initcall() function, which means that if you call
* firmware_map_add() or firmware_map_add_early() afterwards, the entries
* are not added to sysfs.
*/
static int __init memmap_init(void)
{
struct firmware_map_entry *entry;
list_for_each_entry(entry, &map_entries, list)
add_sysfs_fw_map_entry(entry);
return 0;
}
late_initcall(memmap_init);

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

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