Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
87
drivers/firmware/Kconfig
Normal file
87
drivers/firmware/Kconfig
Normal file
@@ -0,0 +1,87 @@
|
||||
#
|
||||
# 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 !IA64
|
||||
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 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 <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
|
||||
|
||||
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.
|
||||
|
||||
endmenu
|
||||
9
drivers/firmware/Makefile
Normal file
9
drivers/firmware/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# Makefile for the linux kernel.
|
||||
#
|
||||
obj-$(CONFIG_DMI) += dmi_scan.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
|
||||
659
drivers/firmware/dcdbas.c
Normal file
659
drivers/firmware/dcdbas.c
Normal 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/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 <asm/semaphore.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",
|
||||
__FUNCTION__, 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",
|
||||
__FUNCTION__, 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",
|
||||
__FUNCTION__, 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 kobject *kobj, char *buf, loff_t pos,
|
||||
size_t count)
|
||||
{
|
||||
size_t max_read;
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&smi_data_lock);
|
||||
|
||||
if (pos >= smi_data_buf_size) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
max_read = smi_data_buf_size - pos;
|
||||
ret = min(max_read, count);
|
||||
memcpy(buf, smi_data_buf + pos, ret);
|
||||
out:
|
||||
mutex_unlock(&smi_data_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t smi_data_write(struct kobject *kobj, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* smi_request: generate SMI request
|
||||
*
|
||||
* Called with smi_data_lock.
|
||||
*/
|
||||
static int smi_request(struct smi_cmd *smi_cmd)
|
||||
{
|
||||
cpumask_t old_mask;
|
||||
int ret = 0;
|
||||
|
||||
if (smi_cmd->magic != SMI_CMD_MAGIC) {
|
||||
dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
|
||||
__FUNCTION__);
|
||||
return -EBADR;
|
||||
}
|
||||
|
||||
/* SMI requires CPU 0 */
|
||||
old_mask = current->cpus_allowed;
|
||||
set_cpus_allowed(current, cpumask_of_cpu(0));
|
||||
if (smp_processor_id() != 0) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
|
||||
__FUNCTION__);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* generate SMI */
|
||||
asm volatile (
|
||||
"outb %b0,%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(current, 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 = 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 = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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",
|
||||
__FUNCTION__, 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", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (smi_data_buf_size < sizeof(struct apm_cmd)) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n",
|
||||
__FUNCTION__);
|
||||
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_32BIT_MASK;
|
||||
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);
|
||||
smi_data_buf_free();
|
||||
platform_device_unregister(dcdbas_pdev);
|
||||
platform_driver_unregister(&dcdbas_driver);
|
||||
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
107
drivers/firmware/dcdbas.h
Normal file
107
drivers/firmware/dcdbas.h
Normal 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/input.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, \
|
||||
.owner = THIS_MODULE }, \
|
||||
.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));
|
||||
|
||||
#endif /* _DCDBAS_H_ */
|
||||
|
||||
756
drivers/firmware/dell_rbu.c
Normal file
756
drivers/firmware/dell_rbu.c
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* 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/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", __FUNCTION__);
|
||||
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",
|
||||
__FUNCTION__);
|
||||
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", __FUNCTION__);
|
||||
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(void *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", __FUNCTION__);
|
||||
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)
|
||||
{
|
||||
unsigned char *ptemp = NULL;
|
||||
size_t bytes_left = 0;
|
||||
size_t data_length = 0;
|
||||
ssize_t ret_count = 0;
|
||||
|
||||
/* 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);
|
||||
ret_count = -ENOMEM;
|
||||
goto read_rbu_data_exit;
|
||||
}
|
||||
|
||||
if (pos > rbu_data.bios_image_size) {
|
||||
ret_count = 0;
|
||||
goto read_rbu_data_exit;
|
||||
}
|
||||
|
||||
bytes_left = rbu_data.bios_image_size - pos;
|
||||
data_length = min(bytes_left, count);
|
||||
|
||||
ptemp = rbu_data.image_update_buffer;
|
||||
memcpy(buffer, (ptemp + pos), data_length);
|
||||
|
||||
if ((pos + count) > rbu_data.bios_image_size)
|
||||
/* this was the last copy */
|
||||
ret_count = bytes_left;
|
||||
else
|
||||
ret_count = count;
|
||||
read_rbu_data_exit:
|
||||
return ret_count;
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_data(struct kobject *kobj, 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 || !fw->size)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_image_type(struct kobject *kobj, char *buffer,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
int size = 0;
|
||||
if (!pos)
|
||||
size = sprintf(buffer, "%s\n", image_type);
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t write_rbu_image_type(struct kobject *kobj, 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, &context,
|
||||
callbackfn_rbu);
|
||||
if (req_firm_rc) {
|
||||
printk(KERN_ERR
|
||||
"dell_rbu:%s request_firmware_nowait"
|
||||
" failed %d\n", __FUNCTION__, 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 kobject *kobj, char *buffer,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
int size = 0;
|
||||
if (!pos) {
|
||||
spin_lock(&rbu_data.lock);
|
||||
size = sprintf(buffer, "%lu\n", rbu_data.packetsize);
|
||||
spin_unlock(&rbu_data.lock);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t write_rbu_packet_size(struct kobject *kobj, 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",.owner = THIS_MODULE,.mode = 0444},
|
||||
.read = read_rbu_data,
|
||||
};
|
||||
|
||||
static struct bin_attribute rbu_image_type_attr = {
|
||||
.attr = {.name = "image_type",.owner = THIS_MODULE,.mode = 0644},
|
||||
.read = read_rbu_image_type,
|
||||
.write = write_rbu_image_type,
|
||||
};
|
||||
|
||||
static struct bin_attribute rbu_packet_size_attr = {
|
||||
.attr = {.name = "packet_size",.owner = THIS_MODULE,.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", __FUNCTION__);
|
||||
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
|
||||
*/
|
||||
406
drivers/firmware/dmi_scan.c
Normal file
406
drivers/firmware/dmi_scan.c
Normal file
@@ -0,0 +1,406 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/dmi.h>
|
||||
|
||||
static char * __init dmi_string(struct dmi_header *dm, u8 s)
|
||||
{
|
||||
u8 *bp = ((u8 *) dm) + dm->length;
|
||||
char *str = "";
|
||||
|
||||
if (s) {
|
||||
s--;
|
||||
while (s > 0 && *bp) {
|
||||
bp += strlen(bp) + 1;
|
||||
s--;
|
||||
}
|
||||
|
||||
if (*bp != 0) {
|
||||
str = dmi_alloc(strlen(bp) + 1);
|
||||
if (str != NULL)
|
||||
strcpy(str, bp);
|
||||
else
|
||||
printk(KERN_ERR "dmi_string: out of memory.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to be cautious here. We have seen BIOSes with DMI pointers
|
||||
* pointing to completely the wrong place for example
|
||||
*/
|
||||
static int __init dmi_table(u32 base, int len, int num,
|
||||
void (*decode)(struct dmi_header *))
|
||||
{
|
||||
u8 *buf, *data;
|
||||
int i = 0;
|
||||
|
||||
buf = dmi_ioremap(base, len);
|
||||
if (buf == NULL)
|
||||
return -1;
|
||||
|
||||
data = buf;
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
struct dmi_header *dm = (struct dmi_header *)data;
|
||||
/*
|
||||
* We want to know the total length (formated 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);
|
||||
data += 2;
|
||||
i++;
|
||||
}
|
||||
dmi_iounmap(buf, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init dmi_checksum(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);
|
||||
|
||||
/*
|
||||
* Save a DMI string
|
||||
*/
|
||||
static void __init dmi_save_ident(struct dmi_header *dm, int slot, int string)
|
||||
{
|
||||
char *p, *d = (char*) dm;
|
||||
|
||||
if (dmi_ident[slot])
|
||||
return;
|
||||
|
||||
p = dmi_string(dm, d[string]);
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
dmi_ident[slot] = p;
|
||||
}
|
||||
|
||||
static void __init dmi_save_devices(struct dmi_header *dm)
|
||||
{
|
||||
int i, count = (dm->length - sizeof(struct dmi_header)) / 2;
|
||||
struct dmi_device *dev;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
char *d = (char *)(dm + 1) + (i * 2);
|
||||
|
||||
/* Skip disabled device */
|
||||
if ((*d & 0x80) == 0)
|
||||
continue;
|
||||
|
||||
dev = dmi_alloc(sizeof(*dev));
|
||||
if (!dev) {
|
||||
printk(KERN_ERR "dmi_save_devices: out of memory.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
dev->type = *d++ & 0x7f;
|
||||
dev->name = dmi_string(dm, *d);
|
||||
dev->device_data = NULL;
|
||||
list_add(&dev->list, &dmi_devices);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init dmi_save_oem_strings_devices(struct dmi_header *dm)
|
||||
{
|
||||
int i, count = *(u8 *)(dm + 1);
|
||||
struct dmi_device *dev;
|
||||
|
||||
for (i = 1; i <= count; i++) {
|
||||
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 = dmi_string(dm, i);
|
||||
dev->device_data = NULL;
|
||||
|
||||
list_add(&dev->list, &dmi_devices);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init dmi_save_ipmi_device(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(&dev->list, &dmi_devices);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a DMI table entry. Right now all we care about are the BIOS
|
||||
* and machine entries. For 2.5 we should pull the smbus controller info
|
||||
* out of here.
|
||||
*/
|
||||
static void __init dmi_decode(struct dmi_header *dm)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init dmi_present(char __iomem *p)
|
||||
{
|
||||
u8 buf[15];
|
||||
memcpy_fromio(buf, p, 15);
|
||||
if ((memcmp(buf, "_DMI_", 5) == 0) && dmi_checksum(buf)) {
|
||||
u16 num = (buf[13] << 8) | buf[12];
|
||||
u16 len = (buf[7] << 8) | buf[6];
|
||||
u32 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_table(base,len, num, dmi_decode) == 0)
|
||||
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 out;
|
||||
|
||||
/* 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 out;
|
||||
|
||||
rc = dmi_present(p + 0x10); /* offset of _DMI_ string */
|
||||
dmi_iounmap(p, 32);
|
||||
if (!rc)
|
||||
return;
|
||||
}
|
||||
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 out;
|
||||
|
||||
for (q = p; q < p + 0x10000; q += 16) {
|
||||
rc = dmi_present(q);
|
||||
if (!rc)
|
||||
return;
|
||||
}
|
||||
}
|
||||
out: printk(KERN_INFO "DMI not present or invalid.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(struct dmi_system_id *list)
|
||||
{
|
||||
int i, count = 0;
|
||||
struct dmi_system_id *d = list;
|
||||
|
||||
while (d->ident) {
|
||||
for (i = 0; i < ARRAY_SIZE(d->matches); i++) {
|
||||
int s = d->matches[i].slot;
|
||||
if (s == DMI_NONE)
|
||||
continue;
|
||||
if (dmi_ident[s] && strstr(dmi_ident[s], d->matches[i].substr))
|
||||
continue;
|
||||
/* No match */
|
||||
goto fail;
|
||||
}
|
||||
count++;
|
||||
if (d->callback && d->callback(d))
|
||||
break;
|
||||
fail: d++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_check_system);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
char *dmi_get_system_info(int field)
|
||||
{
|
||||
return dmi_ident[field];
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_get_system_info);
|
||||
|
||||
|
||||
/**
|
||||
* dmi_name_in_vendors - Check if string is anywhere in the DMI vendor information.
|
||||
* @str: Case sensitive Name
|
||||
*/
|
||||
int dmi_name_in_vendors(char *str)
|
||||
{
|
||||
static int fields[] = { DMI_BIOS_VENDOR, DMI_BIOS_VERSION, DMI_SYS_VENDOR,
|
||||
DMI_PRODUCT_NAME, DMI_PRODUCT_VERSION, DMI_BOARD_VENDOR,
|
||||
DMI_BOARD_NAME, DMI_BOARD_VERSION, DMI_NONE };
|
||||
int i;
|
||||
for (i = 0; fields[i] != DMI_NONE; i++) {
|
||||
int f = fields[i];
|
||||
if (dmi_ident[f] && strstr(dmi_ident[f], str))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_name_in_vendors);
|
||||
|
||||
/**
|
||||
* dmi_find_device - find onboard device by type/name
|
||||
* @type: device type or %DMI_DEV_TYPE_ANY to match all device types
|
||||
* @name: device name string or %NULL to match all
|
||||
* @from: previous device found in search, or %NULL for new search.
|
||||
*
|
||||
* Iterates through the list of known onboard devices. If a device is
|
||||
* found with a matching @vendor and @device, a pointer to its device
|
||||
* structure is returned. Otherwise, %NULL is returned.
|
||||
* A new search is initiated by passing %NULL as the @from argument.
|
||||
* If @from is not %NULL, searches continue from next device.
|
||||
*/
|
||||
struct dmi_device * dmi_find_device(int type, const char *name,
|
||||
struct dmi_device *from)
|
||||
{
|
||||
struct list_head *d, *head = from ? &from->list : &dmi_devices;
|
||||
|
||||
for(d = head->next; d != &dmi_devices; d = d->next) {
|
||||
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_year - Return year of a DMI date
|
||||
* @field: data index (like dmi_get_system_info)
|
||||
*
|
||||
* Returns -1 when the field doesn't exist. 0 when it is broken.
|
||||
*/
|
||||
int dmi_get_year(int field)
|
||||
{
|
||||
int year;
|
||||
char *s = dmi_get_system_info(field);
|
||||
|
||||
if (!s)
|
||||
return -1;
|
||||
if (*s == '\0')
|
||||
return 0;
|
||||
s = strrchr(s, '/');
|
||||
if (!s)
|
||||
return 0;
|
||||
|
||||
s += 1;
|
||||
year = simple_strtoul(s, NULL, 0);
|
||||
if (year && year < 100) { /* 2-digit year */
|
||||
year += 1900;
|
||||
if (year < 1996) /* no dates < spec 1.0 */
|
||||
year += 100;
|
||||
}
|
||||
|
||||
return year;
|
||||
}
|
||||
791
drivers/firmware/edd.c
Normal file
791
drivers/firmware/edd.c
Normal file
@@ -0,0 +1,791 @@
|
||||
/*
|
||||
* 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 Parametes 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/edd30/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, .owner = THIS_MODULE }, \
|
||||
.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 struct sysfs_ops edd_attr_ops = {
|
||||
.show = edd_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
edd_show_host_bus(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
int i;
|
||||
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (isprint(info->params.host_bus_type[i])) {
|
||||
p += scnprintf(p, left, "%c", info->params.host_bus_type[i]);
|
||||
} else {
|
||||
p += scnprintf(p, left, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (!strncmp(info->params.host_bus_type, "ISA", 3)) {
|
||||
p += scnprintf(p, left, "\tbase_address: %x\n",
|
||||
info->params.interface_path.isa.base_address);
|
||||
} else if (!strncmp(info->params.host_bus_type, "PCIX", 4) ||
|
||||
!strncmp(info->params.host_bus_type, "PCI", 3)) {
|
||||
p += scnprintf(p, left,
|
||||
"\t%02x:%02x.%d channel: %u\n",
|
||||
info->params.interface_path.pci.bus,
|
||||
info->params.interface_path.pci.slot,
|
||||
info->params.interface_path.pci.function,
|
||||
info->params.interface_path.pci.channel);
|
||||
} else if (!strncmp(info->params.host_bus_type, "IBND", 4) ||
|
||||
!strncmp(info->params.host_bus_type, "XPRS", 4) ||
|
||||
!strncmp(info->params.host_bus_type, "HTPT", 4)) {
|
||||
p += scnprintf(p, left,
|
||||
"\tTBD: %llx\n",
|
||||
info->params.interface_path.ibnd.reserved);
|
||||
|
||||
} else {
|
||||
p += scnprintf(p, left, "\tunknown: %llx\n",
|
||||
info->params.interface_path.unknown.reserved);
|
||||
}
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_interface(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
int i;
|
||||
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (isprint(info->params.interface_type[i])) {
|
||||
p += scnprintf(p, left, "%c", info->params.interface_type[i]);
|
||||
} else {
|
||||
p += scnprintf(p, left, " ");
|
||||
}
|
||||
}
|
||||
if (!strncmp(info->params.interface_type, "ATAPI", 5)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u lun: %u\n",
|
||||
info->params.device_path.atapi.device,
|
||||
info->params.device_path.atapi.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "ATA", 3)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u\n",
|
||||
info->params.device_path.ata.device);
|
||||
} else if (!strncmp(info->params.interface_type, "SCSI", 4)) {
|
||||
p += scnprintf(p, left, "\tid: %u lun: %llu\n",
|
||||
info->params.device_path.scsi.id,
|
||||
info->params.device_path.scsi.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "USB", 3)) {
|
||||
p += scnprintf(p, left, "\tserial_number: %llx\n",
|
||||
info->params.device_path.usb.serial_number);
|
||||
} else if (!strncmp(info->params.interface_type, "1394", 4)) {
|
||||
p += scnprintf(p, left, "\teui: %llx\n",
|
||||
info->params.device_path.i1394.eui);
|
||||
} else if (!strncmp(info->params.interface_type, "FIBRE", 5)) {
|
||||
p += scnprintf(p, left, "\twwid: %llx lun: %llx\n",
|
||||
info->params.device_path.fibre.wwid,
|
||||
info->params.device_path.fibre.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "I2O", 3)) {
|
||||
p += scnprintf(p, left, "\tidentity_tag: %llx\n",
|
||||
info->params.device_path.i2o.identity_tag);
|
||||
} else if (!strncmp(info->params.interface_type, "RAID", 4)) {
|
||||
p += scnprintf(p, left, "\tidentity_tag: %x\n",
|
||||
info->params.device_path.raid.array_number);
|
||||
} else if (!strncmp(info->params.interface_type, "SATA", 4)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u\n",
|
||||
info->params.device_path.sata.device);
|
||||
} else {
|
||||
p += scnprintf(p, left, "\tunknown: %llx %llx\n",
|
||||
info->params.device_path.unknown.reserved1,
|
||||
info->params.device_path.unknown.reserved2);
|
||||
}
|
||||
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* edd_show_raw_data() - copies raw data to buffer for userspace to parse
|
||||
* @edev: target edd_device
|
||||
* @buf: output buffer
|
||||
*
|
||||
* Returns: number of bytes written, or -EINVAL on failure
|
||||
*/
|
||||
static ssize_t
|
||||
edd_show_raw_data(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
ssize_t len = sizeof (info->params);
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE))
|
||||
len = info->params.length;
|
||||
|
||||
/* In case of buggy BIOSs */
|
||||
if (len > (sizeof(info->params)))
|
||||
len = sizeof(info->params);
|
||||
|
||||
memcpy(buf, &info->params, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_version(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "0x%02x\n", info->version);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_mbr_signature(struct edd_device *edev, char *buf)
|
||||
{
|
||||
char *p = buf;
|
||||
p += scnprintf(p, left, "0x%08x\n", edev->mbr_signature);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_extensions(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) {
|
||||
p += scnprintf(p, left, "Fixed disk access\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) {
|
||||
p += scnprintf(p, left, "Device locking and ejecting\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) {
|
||||
p += scnprintf(p, left, "Enhanced Disk Drive support\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) {
|
||||
p += scnprintf(p, left, "64-bit extensions\n");
|
||||
}
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_info_flags(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (info->params.info_flags & EDD_INFO_DMA_BOUNDARY_ERROR_TRANSPARENT)
|
||||
p += scnprintf(p, left, "DMA boundary error transparent\n");
|
||||
if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID)
|
||||
p += scnprintf(p, left, "geometry valid\n");
|
||||
if (info->params.info_flags & EDD_INFO_REMOVABLE)
|
||||
p += scnprintf(p, left, "removable\n");
|
||||
if (info->params.info_flags & EDD_INFO_WRITE_VERIFY)
|
||||
p += scnprintf(p, left, "write verify\n");
|
||||
if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION)
|
||||
p += scnprintf(p, left, "media change notification\n");
|
||||
if (info->params.info_flags & EDD_INFO_LOCKABLE)
|
||||
p += scnprintf(p, left, "lockable\n");
|
||||
if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT)
|
||||
p += scnprintf(p, left, "no media present\n");
|
||||
if (info->params.info_flags & EDD_INFO_USE_INT13_FN50)
|
||||
p += scnprintf(p, left, "use int13 fn50\n");
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_max_cylinder(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_max_cylinder);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_max_head(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_max_head);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_sectors_per_track(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_sectors_per_track);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_cylinders(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.num_default_cylinders);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_heads(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.num_default_heads);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_sectors_per_track(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.sectors_per_track);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_sectors(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%llu\n", info->params.number_of_sectors);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Some device instances may not have all the above attributes,
|
||||
* or the attribute values may be meaningless (i.e. if
|
||||
* the device is < EDD 3.0, it won't have host_bus and interface
|
||||
* information), so don't bother making files for them. Likewise
|
||||
* if the default_{cylinders,heads,sectors_per_track} values
|
||||
* are zero, the BIOS doesn't provide sane values, don't bother
|
||||
* creating files for them either.
|
||||
*/
|
||||
|
||||
static int
|
||||
edd_has_legacy_max_cylinder(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_max_cylinder > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_legacy_max_head(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_max_head > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_legacy_sectors_per_track(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_sectors_per_track > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_cylinders(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.num_default_cylinders > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_heads(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.num_default_heads > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_sectors_per_track(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.sectors_per_track > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_edd30(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
int i, nonzero_path = 0;
|
||||
char c;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (i = 30; i <= 73; i++) {
|
||||
c = *(((uint8_t *) info) + i + 4);
|
||||
if (c) {
|
||||
nonzero_path++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nonzero_path) {
|
||||
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 ktype_edd = {
|
||||
.release = edd_release,
|
||||
.sysfs_ops = &edd_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
static decl_subsys(edd,&ktype_edd,NULL);
|
||||
|
||||
|
||||
/**
|
||||
* edd_dev_is_type() - is this EDD device a 'type' device?
|
||||
* @edev: target edd_device
|
||||
* @type: a host bus or interface identifier string per the EDD spec
|
||||
*
|
||||
* Returns 1 (TRUE) if it is a 'type' device, 0 otherwise.
|
||||
*/
|
||||
static int
|
||||
edd_dev_is_type(struct edd_device *edev, const char *type)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
|
||||
if (type && info) {
|
||||
if (!strncmp(info->params.host_bus_type, type, strlen(type)) ||
|
||||
!strncmp(info->params.interface_type, type, strlen(type)))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* edd_get_pci_dev() - finds pci_dev that matches edev
|
||||
* @edev: edd_device
|
||||
*
|
||||
* Returns pci_dev if found, or NULL
|
||||
*/
|
||||
static struct pci_dev *
|
||||
edd_get_pci_dev(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info = edd_dev_get_info(edev);
|
||||
|
||||
if (edd_dev_is_type(edev, "PCI")) {
|
||||
return pci_find_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);
|
||||
if (!pci_dev)
|
||||
return 1;
|
||||
return sysfs_create_link(&edev->kobj,&pci_dev->dev.kobj,"pci_dev");
|
||||
}
|
||||
|
||||
static inline void
|
||||
edd_device_unregister(struct edd_device *edev)
|
||||
{
|
||||
kobject_unregister(&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);
|
||||
kobject_set_name(&edev->kobj, "int13_dev%02x",
|
||||
0x80 + i);
|
||||
kobj_set_kset_s(edev,edd_subsys);
|
||||
error = kobject_register(&edev->kobj);
|
||||
if (!error)
|
||||
edd_populate_dir(edev);
|
||||
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)
|
||||
{
|
||||
unsigned 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 1;
|
||||
}
|
||||
|
||||
rc = firmware_register(&edd_subsys);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (i = 0; i < edd_num_devices() && !rc; i++) {
|
||||
edev = kzalloc(sizeof (*edev), GFP_KERNEL);
|
||||
if (!edev)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = edd_device_register(edev, i);
|
||||
if (rc) {
|
||||
kfree(edev);
|
||||
break;
|
||||
}
|
||||
edd_devices[i] = edev;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
firmware_unregister(&edd_subsys);
|
||||
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);
|
||||
}
|
||||
firmware_unregister(&edd_subsys);
|
||||
}
|
||||
|
||||
late_initcall(edd_init);
|
||||
module_exit(edd_exit);
|
||||
777
drivers/firmware/efivars.c
Normal file
777
drivers/firmware/efivars.c
Normal file
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* EFI Variables - efivars.c
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
*
|
||||
* This code takes all variables accessible from EFI runtime and
|
||||
* exports them via sysfs
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 17 May 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* remove check for efi_enabled in exit
|
||||
* add MODULE_VERSION
|
||||
*
|
||||
* 26 Apr 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* minor bug fixes
|
||||
*
|
||||
* 21 Apr 2004 - Matt Tolentino <matthew.e.tolentino@intel.com)
|
||||
* converted driver to export variable information via sysfs
|
||||
* and moved to drivers/firmware directory
|
||||
* bumped revision number to v0.07 to reflect conversion & move
|
||||
*
|
||||
* 10 Dec 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* fix locking per Peter Chubb's findings
|
||||
*
|
||||
* 25 Mar 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* move uuid_unparse() to include/asm-ia64/efi.h:efi_guid_unparse()
|
||||
*
|
||||
* 12 Feb 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* use list_for_each_safe when deleting vars.
|
||||
* remove ifdef CONFIG_SMP around include <linux/smp.h>
|
||||
* v0.04 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 20 April 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Moved vars from /proc/efi to /proc/efi/vars, and made
|
||||
* efi.c own the /proc/efi directory.
|
||||
* v0.03 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* At the request of Stephane, moved ownership of /proc/efi
|
||||
* to efi.c, and now efivars lives under /proc/efi/vars.
|
||||
*
|
||||
* 12 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Feedback received from Stephane Eranian incorporated.
|
||||
* efivar_write() checks copy_from_user() return value.
|
||||
* efivar_read/write() returns proper errno.
|
||||
* v0.02 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 February 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* v0.01 release to linux-ia64@linuxia64.org
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#define EFIVARS_VERSION "0.08"
|
||||
#define EFIVARS_DATE "2004-May-17"
|
||||
|
||||
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
|
||||
MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EFIVARS_VERSION);
|
||||
|
||||
/*
|
||||
* efivars_lock protects two things:
|
||||
* 1) efivar_list - adds, removals, reads, writes
|
||||
* 2) efi.[gs]et_variable() calls.
|
||||
* It must not be held when creating sysfs entries or calling kmalloc.
|
||||
* efi.get_next_variable() is only called from efivars_init(),
|
||||
* which is protected by the BKL, so that path is safe.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(efivars_lock);
|
||||
static LIST_HEAD(efivar_list);
|
||||
|
||||
/*
|
||||
* The maximum size of VariableName + Data = 1024
|
||||
* Therefore, it's reasonable to save that much
|
||||
* space in each part of the structure,
|
||||
* and we use a page for reading/writing.
|
||||
*/
|
||||
|
||||
struct efi_variable {
|
||||
efi_char16_t VariableName[1024/sizeof(efi_char16_t)];
|
||||
efi_guid_t VendorGuid;
|
||||
unsigned long DataSize;
|
||||
__u8 Data[1024];
|
||||
efi_status_t Status;
|
||||
__u32 Attributes;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct efivar_entry {
|
||||
struct efi_variable var;
|
||||
struct list_head list;
|
||||
struct kobject kobj;
|
||||
};
|
||||
|
||||
struct efivar_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show) (struct efivar_entry *entry, char *buf);
|
||||
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
|
||||
#define EFI_ATTR(_name, _mode, _show, _store) \
|
||||
struct subsys_attribute efi_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
||||
struct efivar_attribute efivar_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define VAR_SUBSYS_ATTR(_name, _mode, _show, _store) \
|
||||
struct subsys_attribute var_subsys_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
|
||||
#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
|
||||
|
||||
/*
|
||||
* Prototype for sysfs creation function
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(unsigned long variable_name_size,
|
||||
efi_char16_t *variable_name,
|
||||
efi_guid_t *vendor_guid);
|
||||
|
||||
/* Return the number of unicode characters in data */
|
||||
static unsigned long
|
||||
utf8_strlen(efi_char16_t *data, unsigned long maxlength)
|
||||
{
|
||||
unsigned long length = 0;
|
||||
|
||||
while (*data++ != 0 && length < maxlength)
|
||||
length++;
|
||||
return length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of bytes is the length of this string
|
||||
* Note: this is NOT the same as the number of unicode characters
|
||||
*/
|
||||
static inline unsigned long
|
||||
utf8_strsize(efi_char16_t *data, unsigned long maxlength)
|
||||
{
|
||||
return utf8_strlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t);
|
||||
}
|
||||
|
||||
static efi_status_t
|
||||
get_var_data(struct efi_variable *var)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efivars_lock);
|
||||
var->DataSize = 1024;
|
||||
status = efi.get_variable(var->VariableName,
|
||||
&var->VendorGuid,
|
||||
&var->Attributes,
|
||||
&var->DataSize,
|
||||
var->Data);
|
||||
spin_unlock(&efivars_lock);
|
||||
if (status != EFI_SUCCESS) {
|
||||
printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
|
||||
status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_guid_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
efi_guid_unparse(&var->VendorGuid, str);
|
||||
str += strlen(str);
|
||||
str += sprintf(str, "\n");
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_attr_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
efi_status_t status;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
status = get_var_data(var);
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EIO;
|
||||
|
||||
if (var->Attributes & 0x1)
|
||||
str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
|
||||
if (var->Attributes & 0x2)
|
||||
str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
|
||||
if (var->Attributes & 0x4)
|
||||
str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_size_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
efi_status_t status;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
status = get_var_data(var);
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EIO;
|
||||
|
||||
str += sprintf(str, "0x%lx\n", var->DataSize);
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_data_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
efi_status_t status;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
status = get_var_data(var);
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, var->Data, var->DataSize);
|
||||
return var->DataSize;
|
||||
}
|
||||
/*
|
||||
* We allow each variable to be edited via rewriting the
|
||||
* entire efi variable structure.
|
||||
*/
|
||||
static ssize_t
|
||||
efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
|
||||
{
|
||||
struct efi_variable *new_var, *var = &entry->var;
|
||||
efi_status_t status = EFI_NOT_FOUND;
|
||||
|
||||
if (count != sizeof(struct efi_variable))
|
||||
return -EINVAL;
|
||||
|
||||
new_var = (struct efi_variable *)buf;
|
||||
/*
|
||||
* If only updating the variable data, then the name
|
||||
* and guid should remain the same
|
||||
*/
|
||||
if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) ||
|
||||
efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) {
|
||||
printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){
|
||||
printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock(&efivars_lock);
|
||||
status = efi.set_variable(new_var->VariableName,
|
||||
&new_var->VendorGuid,
|
||||
new_var->Attributes,
|
||||
new_var->DataSize,
|
||||
new_var->Data);
|
||||
|
||||
spin_unlock(&efivars_lock);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
|
||||
status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
memcpy(&entry->var, new_var, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_show_raw(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
efi_status_t status;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
status = get_var_data(var);
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, var, sizeof(*var));
|
||||
return sizeof(*var);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic read/write functions that call the specific functions of
|
||||
* the atttributes...
|
||||
*/
|
||||
static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->show) {
|
||||
ret = efivar_attr->show(var, buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->store)
|
||||
ret = efivar_attr->store(var, buf, count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sysfs_ops efivar_attr_ops = {
|
||||
.show = efivar_attr_show,
|
||||
.store = efivar_attr_store,
|
||||
};
|
||||
|
||||
static void efivar_release(struct kobject *kobj)
|
||||
{
|
||||
struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj);
|
||||
kfree(var);
|
||||
}
|
||||
|
||||
static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
|
||||
static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
|
||||
static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
|
||||
static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
|
||||
static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
|
||||
|
||||
static struct attribute *def_attrs[] = {
|
||||
&efivar_attr_guid.attr,
|
||||
&efivar_attr_size.attr,
|
||||
&efivar_attr_attributes.attr,
|
||||
&efivar_attr_data.attr,
|
||||
&efivar_attr_raw_var.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct kobj_type ktype_efivar = {
|
||||
.release = efivar_release,
|
||||
.sysfs_ops = &efivar_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
dummy(struct subsystem *sub, char *buf)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline void
|
||||
efivar_unregister(struct efivar_entry *var)
|
||||
{
|
||||
kobject_unregister(&var->kobj);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
efivar_create(struct subsystem *sub, const char *buf, size_t count)
|
||||
{
|
||||
struct efi_variable *new_var = (struct efi_variable *)buf;
|
||||
struct efivar_entry *search_efivar, *n;
|
||||
unsigned long strsize1, strsize2;
|
||||
efi_status_t status = EFI_NOT_FOUND;
|
||||
int found = 0;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
spin_lock(&efivars_lock);
|
||||
|
||||
/*
|
||||
* Does this variable already exist?
|
||||
*/
|
||||
list_for_each_entry_safe(search_efivar, n, &efivar_list, list) {
|
||||
strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf8_strsize(new_var->VariableName, 1024);
|
||||
if (strsize1 == strsize2 &&
|
||||
!memcmp(&(search_efivar->var.VariableName),
|
||||
new_var->VariableName, strsize1) &&
|
||||
!efi_guidcmp(search_efivar->var.VendorGuid,
|
||||
new_var->VendorGuid)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
spin_unlock(&efivars_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* now *really* create the variable via EFI */
|
||||
status = efi.set_variable(new_var->VariableName,
|
||||
&new_var->VendorGuid,
|
||||
new_var->Attributes,
|
||||
new_var->DataSize,
|
||||
new_var->Data);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
|
||||
status);
|
||||
spin_unlock(&efivars_lock);
|
||||
return -EIO;
|
||||
}
|
||||
spin_unlock(&efivars_lock);
|
||||
|
||||
/* Create the entry in sysfs. Locking is not required here */
|
||||
status = efivar_create_sysfs_entry(utf8_strsize(new_var->VariableName,
|
||||
1024), new_var->VariableName, &new_var->VendorGuid);
|
||||
if (status) {
|
||||
printk(KERN_WARNING "efivars: variable created, but sysfs entry wasn't.\n");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_delete(struct subsystem *sub, const char *buf, size_t count)
|
||||
{
|
||||
struct efi_variable *del_var = (struct efi_variable *)buf;
|
||||
struct efivar_entry *search_efivar, *n;
|
||||
unsigned long strsize1, strsize2;
|
||||
efi_status_t status = EFI_NOT_FOUND;
|
||||
int found = 0;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
spin_lock(&efivars_lock);
|
||||
|
||||
/*
|
||||
* Does this variable already exist?
|
||||
*/
|
||||
list_for_each_entry_safe(search_efivar, n, &efivar_list, list) {
|
||||
strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf8_strsize(del_var->VariableName, 1024);
|
||||
if (strsize1 == strsize2 &&
|
||||
!memcmp(&(search_efivar->var.VariableName),
|
||||
del_var->VariableName, strsize1) &&
|
||||
!efi_guidcmp(search_efivar->var.VendorGuid,
|
||||
del_var->VendorGuid)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
spin_unlock(&efivars_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* force the Attributes/DataSize to 0 to ensure deletion */
|
||||
del_var->Attributes = 0;
|
||||
del_var->DataSize = 0;
|
||||
|
||||
status = efi.set_variable(del_var->VariableName,
|
||||
&del_var->VendorGuid,
|
||||
del_var->Attributes,
|
||||
del_var->DataSize,
|
||||
del_var->Data);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
|
||||
status);
|
||||
spin_unlock(&efivars_lock);
|
||||
return -EIO;
|
||||
}
|
||||
list_del(&search_efivar->list);
|
||||
/* We need to release this lock before unregistering. */
|
||||
spin_unlock(&efivars_lock);
|
||||
efivar_unregister(search_efivar);
|
||||
|
||||
/* It's dead Jim.... */
|
||||
return count;
|
||||
}
|
||||
|
||||
static VAR_SUBSYS_ATTR(new_var, 0200, dummy, efivar_create);
|
||||
static VAR_SUBSYS_ATTR(del_var, 0200, dummy, efivar_delete);
|
||||
|
||||
static struct subsys_attribute *var_subsys_attrs[] = {
|
||||
&var_subsys_attr_new_var,
|
||||
&var_subsys_attr_del_var,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Let's not leave out systab information that snuck into
|
||||
* the efivars driver
|
||||
*/
|
||||
static ssize_t
|
||||
systab_read(struct subsystem *entry, char *buf)
|
||||
{
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (efi.mps != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "MPS=0x%lx\n", efi.mps);
|
||||
if (efi.acpi20 != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20);
|
||||
if (efi.acpi != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI=0x%lx\n", efi.acpi);
|
||||
if (efi.smbios != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios);
|
||||
if (efi.hcdp != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp);
|
||||
if (efi.boot_info != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info);
|
||||
if (efi.uga != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "UGA=0x%lx\n", efi.uga);
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static EFI_ATTR(systab, 0400, systab_read, NULL);
|
||||
|
||||
static struct subsys_attribute *efi_subsys_attrs[] = {
|
||||
&efi_attr_systab,
|
||||
NULL, /* maybe more in the future? */
|
||||
};
|
||||
|
||||
static decl_subsys(vars, &ktype_efivar, NULL);
|
||||
static decl_subsys(efi, NULL, NULL);
|
||||
|
||||
/*
|
||||
* efivar_create_sysfs_entry()
|
||||
* Requires:
|
||||
* variable_name_size = number of bytes required to hold
|
||||
* variable_name (not counting the NULL
|
||||
* character at the end.
|
||||
* efivars_lock is not held on entry or exit.
|
||||
* Returns 1 on failure, 0 on success
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(unsigned long variable_name_size,
|
||||
efi_char16_t *variable_name,
|
||||
efi_guid_t *vendor_guid)
|
||||
{
|
||||
int i, short_name_size = variable_name_size / sizeof(efi_char16_t) + 38;
|
||||
char *short_name;
|
||||
struct efivar_entry *new_efivar;
|
||||
|
||||
short_name = kzalloc(short_name_size + 1, GFP_KERNEL);
|
||||
new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
|
||||
|
||||
if (!short_name || !new_efivar) {
|
||||
kfree(short_name);
|
||||
kfree(new_efivar);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(new_efivar->var.VariableName, variable_name,
|
||||
variable_name_size);
|
||||
memcpy(&(new_efivar->var.VendorGuid), vendor_guid, sizeof(efi_guid_t));
|
||||
|
||||
/* Convert Unicode to normal chars (assume top bits are 0),
|
||||
ala UTF-8 */
|
||||
for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
|
||||
short_name[i] = variable_name[i] & 0xFF;
|
||||
}
|
||||
/* This is ugly, but necessary to separate one vendor's
|
||||
private variables from another's. */
|
||||
|
||||
*(short_name + strlen(short_name)) = '-';
|
||||
efi_guid_unparse(vendor_guid, short_name + strlen(short_name));
|
||||
|
||||
kobject_set_name(&new_efivar->kobj, "%s", short_name);
|
||||
kobj_set_kset_s(new_efivar, vars_subsys);
|
||||
i = kobject_register(&new_efivar->kobj);
|
||||
if (i) {
|
||||
kfree(short_name);
|
||||
kfree(new_efivar);
|
||||
return 1;
|
||||
}
|
||||
|
||||
kfree(short_name);
|
||||
short_name = NULL;
|
||||
|
||||
spin_lock(&efivars_lock);
|
||||
list_add(&new_efivar->list, &efivar_list);
|
||||
spin_unlock(&efivars_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* For now we register the efi subsystem with the firmware subsystem
|
||||
* and the vars subsystem with the efi subsystem. In the future, it
|
||||
* might make sense to split off the efi subsystem into its own
|
||||
* driver, but for now only efivars will register with it, so just
|
||||
* include it here.
|
||||
*/
|
||||
|
||||
static int __init
|
||||
efivars_init(void)
|
||||
{
|
||||
efi_status_t status = EFI_NOT_FOUND;
|
||||
efi_guid_t vendor_guid;
|
||||
efi_char16_t *variable_name;
|
||||
struct subsys_attribute *attr;
|
||||
unsigned long variable_name_size = 1024;
|
||||
int i, error = 0;
|
||||
|
||||
if (!efi_enabled)
|
||||
return -ENODEV;
|
||||
|
||||
variable_name = kzalloc(variable_name_size, GFP_KERNEL);
|
||||
if (!variable_name) {
|
||||
printk(KERN_ERR "efivars: Memory allocation failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
|
||||
EFIVARS_DATE);
|
||||
|
||||
/*
|
||||
* For now we'll register the efi subsys within this driver
|
||||
*/
|
||||
|
||||
error = firmware_register(&efi_subsys);
|
||||
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: Firmware registration failed with error %d.\n", error);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
kset_set_kset_s(&vars_subsys, efi_subsys);
|
||||
|
||||
error = subsystem_register(&vars_subsys);
|
||||
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: Subsystem registration failed with error %d.\n", error);
|
||||
goto out_firmware_unregister;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per EFI spec, the maximum storage allocated for both
|
||||
* the variable name and variable data is 1024 bytes.
|
||||
*/
|
||||
|
||||
do {
|
||||
variable_name_size = 1024;
|
||||
|
||||
status = efi.get_next_variable(&variable_name_size,
|
||||
variable_name,
|
||||
&vendor_guid);
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
efivar_create_sysfs_entry(variable_name_size,
|
||||
variable_name,
|
||||
&vendor_guid);
|
||||
break;
|
||||
case EFI_NOT_FOUND:
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n",
|
||||
status);
|
||||
status = EFI_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
} while (status != EFI_NOT_FOUND);
|
||||
|
||||
/*
|
||||
* Now add attributes to allow creation of new vars
|
||||
* and deletion of existing ones...
|
||||
*/
|
||||
|
||||
for (i = 0; (attr = var_subsys_attrs[i]) && !error; i++) {
|
||||
if (attr->show && attr->store)
|
||||
error = subsys_create_file(&vars_subsys, attr);
|
||||
}
|
||||
|
||||
/* Don't forget the systab entry */
|
||||
|
||||
for (i = 0; (attr = efi_subsys_attrs[i]) && !error; i++) {
|
||||
if (attr->show)
|
||||
error = subsys_create_file(&efi_subsys, attr);
|
||||
}
|
||||
|
||||
if (error)
|
||||
printk(KERN_ERR "efivars: Sysfs attribute export failed with error %d.\n", error);
|
||||
else
|
||||
goto out_free;
|
||||
|
||||
subsystem_unregister(&vars_subsys);
|
||||
|
||||
out_firmware_unregister:
|
||||
firmware_unregister(&efi_subsys);
|
||||
|
||||
out_free:
|
||||
kfree(variable_name);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit
|
||||
efivars_exit(void)
|
||||
{
|
||||
struct efivar_entry *entry, *n;
|
||||
|
||||
list_for_each_entry_safe(entry, n, &efivar_list, list) {
|
||||
spin_lock(&efivars_lock);
|
||||
list_del(&entry->list);
|
||||
spin_unlock(&efivars_lock);
|
||||
efivar_unregister(entry);
|
||||
}
|
||||
|
||||
subsystem_unregister(&vars_subsys);
|
||||
firmware_unregister(&efi_subsys);
|
||||
}
|
||||
|
||||
module_init(efivars_init);
|
||||
module_exit(efivars_exit);
|
||||
|
||||
134
drivers/firmware/pcdp.c
Normal file
134
drivers/firmware/pcdp.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 <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, "console=uart,%s,0x%lx",
|
||||
mmio ? "mmio" : "io", uart->addr.address);
|
||||
if (uart->baud) {
|
||||
p += sprintf(p, ",%lu", 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);
|
||||
}
|
||||
}
|
||||
|
||||
return early_serial_console_init(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;
|
||||
}
|
||||
111
drivers/firmware/pcdp.h
Normal file
111
drivers/firmware/pcdp.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Definitions for PCDP-defined console devices
|
||||
*
|
||||
* v1.0a: http://www.dig64.org/specifications/DIG64_HCDPv10a_01.pdf
|
||||
* v2.0: http://www.dig64.org/specifications/DIG64_PCDPv20.pdf
|
||||
*
|
||||
* (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));
|
||||
Reference in New Issue
Block a user