mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
138
drivers/firmware/Kconfig
Normal file
138
drivers/firmware/Kconfig
Normal file
|
@ -0,0 +1,138 @@
|
|||
#
|
||||
# For a description of the syntax of this configuration file,
|
||||
# see Documentation/kbuild/kconfig-language.txt.
|
||||
#
|
||||
|
||||
menu "Firmware Drivers"
|
||||
|
||||
config EDD
|
||||
tristate "BIOS Enhanced Disk Drive calls determine boot disk"
|
||||
depends on X86
|
||||
help
|
||||
Say Y or M here if you want to enable BIOS Enhanced Disk Drive
|
||||
Services real mode BIOS calls to determine which disk
|
||||
BIOS tries boot from. This information is then exported via sysfs.
|
||||
|
||||
This option is experimental and is known to fail to boot on some
|
||||
obscure configurations. Most disk controller BIOS vendors do
|
||||
not yet implement this feature.
|
||||
|
||||
config EDD_OFF
|
||||
bool "Sets default behavior for EDD detection to off"
|
||||
depends on EDD
|
||||
default n
|
||||
help
|
||||
Say Y if you want EDD disabled by default, even though it is compiled into the
|
||||
kernel. Say N if you want EDD enabled by default. EDD can be dynamically set
|
||||
using the kernel parameter 'edd={on|skipmbr|off}'.
|
||||
|
||||
config FIRMWARE_MEMMAP
|
||||
bool "Add firmware-provided memory map to sysfs" if EXPERT
|
||||
default X86
|
||||
help
|
||||
Add the firmware-provided (unmodified) memory map to /sys/firmware/memmap.
|
||||
That memory map is used for example by kexec to set up parameter area
|
||||
for the next kernel, but can also be used for debugging purposes.
|
||||
|
||||
See also Documentation/ABI/testing/sysfs-firmware-memmap.
|
||||
|
||||
config EFI_PCDP
|
||||
bool "Console device selection via EFI PCDP or HCDP table"
|
||||
depends on ACPI && EFI && IA64
|
||||
default y if IA64
|
||||
help
|
||||
If your firmware supplies the PCDP table, and you want to
|
||||
automatically use the primary console device it describes
|
||||
as the Linux console, say Y here.
|
||||
|
||||
If your firmware supplies the HCDP table, and you want to
|
||||
use the first serial port it describes as the Linux console,
|
||||
say Y here. If your EFI ConOut path contains only a UART
|
||||
device, it will become the console automatically. Otherwise,
|
||||
you must specify the "console=hcdp" kernel boot argument.
|
||||
|
||||
Neither the PCDP nor the HCDP affects naming of serial devices,
|
||||
so a serial console may be /dev/ttyS0, /dev/ttyS1, etc, depending
|
||||
on how the driver discovers devices.
|
||||
|
||||
You must also enable the appropriate drivers (serial, VGA, etc.)
|
||||
|
||||
See DIG64_HCDPv20_042804.pdf available from
|
||||
<http://www.dig64.org/specifications/>
|
||||
|
||||
config DELL_RBU
|
||||
tristate "BIOS update support for DELL systems via sysfs"
|
||||
depends on X86
|
||||
select FW_LOADER
|
||||
select FW_LOADER_USER_HELPER
|
||||
help
|
||||
Say m if you want to have the option of updating the BIOS for your
|
||||
DELL system. Note you need a Dell OpenManage or Dell Update package (DUP)
|
||||
supporting application to communicate with the BIOS regarding the new
|
||||
image for the image update to take effect.
|
||||
See <file:Documentation/dell_rbu.txt> for more details on the driver.
|
||||
|
||||
config DCDBAS
|
||||
tristate "Dell Systems Management Base Driver"
|
||||
depends on X86
|
||||
help
|
||||
The Dell Systems Management Base Driver provides a sysfs interface
|
||||
for systems management software to perform System Management
|
||||
Interrupts (SMIs) and Host Control Actions (system power cycle or
|
||||
power off after OS shutdown) on certain Dell systems.
|
||||
|
||||
See <file:Documentation/dcdbas.txt> for more details on the driver
|
||||
and the Dell systems on which Dell systems management software makes
|
||||
use of this driver.
|
||||
|
||||
Say Y or M here to enable the driver for use by Dell systems
|
||||
management software such as Dell OpenManage.
|
||||
|
||||
config DMIID
|
||||
bool "Export DMI identification via sysfs to userspace"
|
||||
depends on DMI
|
||||
default y
|
||||
help
|
||||
Say Y here if you want to query SMBIOS/DMI system identification
|
||||
information from userspace through /sys/class/dmi/id/ or if you want
|
||||
DMI-based module auto-loading.
|
||||
|
||||
config DMI_SYSFS
|
||||
tristate "DMI table support in sysfs"
|
||||
depends on SYSFS && DMI
|
||||
default n
|
||||
help
|
||||
Say Y or M here to enable the exporting of the raw DMI table
|
||||
data via sysfs. This is useful for consuming the data without
|
||||
requiring any access to /dev/mem at all. Tables are found
|
||||
under /sys/firmware/dmi when this option is enabled and
|
||||
loaded.
|
||||
|
||||
config DMI_SCAN_MACHINE_NON_EFI_FALLBACK
|
||||
bool
|
||||
|
||||
config ISCSI_IBFT_FIND
|
||||
bool "iSCSI Boot Firmware Table Attributes"
|
||||
depends on X86 && ACPI
|
||||
default n
|
||||
help
|
||||
This option enables the kernel to find the region of memory
|
||||
in which the ISCSI Boot Firmware Table (iBFT) resides. This
|
||||
is necessary for iSCSI Boot Firmware Table Attributes module to work
|
||||
properly.
|
||||
|
||||
config ISCSI_IBFT
|
||||
tristate "iSCSI Boot Firmware Table Attributes module"
|
||||
select ISCSI_BOOT_SYSFS
|
||||
depends on ISCSI_IBFT_FIND && SCSI && SCSI_LOWLEVEL
|
||||
default n
|
||||
help
|
||||
This option enables support for detection and exposing of iSCSI
|
||||
Boot Firmware Table (iBFT) via sysfs to userspace. If you wish to
|
||||
detect iSCSI boot parameters dynamically during system boot, say Y.
|
||||
Otherwise, say N.
|
||||
|
||||
source "drivers/firmware/google/Kconfig"
|
||||
source "drivers/firmware/efi/Kconfig"
|
||||
|
||||
endmenu
|
17
drivers/firmware/Makefile
Normal file
17
drivers/firmware/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Makefile for the linux kernel.
|
||||
#
|
||||
obj-$(CONFIG_DMI) += dmi_scan.o
|
||||
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
|
||||
obj-$(CONFIG_EDD) += edd.o
|
||||
obj-$(CONFIG_EFI_PCDP) += pcdp.o
|
||||
obj-$(CONFIG_DELL_RBU) += dell_rbu.o
|
||||
obj-$(CONFIG_DCDBAS) += dcdbas.o
|
||||
obj-$(CONFIG_DMIID) += dmi-id.o
|
||||
obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o
|
||||
obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
|
||||
obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
|
||||
|
||||
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
|
||||
obj-$(CONFIG_EFI) += efi/
|
||||
obj-$(CONFIG_UEFI_CPER) += efi/
|
650
drivers/firmware/dcdbas.c
Normal file
650
drivers/firmware/dcdbas.c
Normal file
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
* dcdbas.c: Dell Systems Management Base Driver
|
||||
*
|
||||
* The Dell Systems Management Base Driver provides a sysfs interface for
|
||||
* systems management software to perform System Management Interrupts (SMIs)
|
||||
* and Host Control Actions (power cycle or power off after OS shutdown) on
|
||||
* Dell systems.
|
||||
*
|
||||
* See Documentation/dcdbas.txt for more information.
|
||||
*
|
||||
* Copyright (C) 1995-2006 Dell Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mc146818rtc.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "dcdbas.h"
|
||||
|
||||
#define DRIVER_NAME "dcdbas"
|
||||
#define DRIVER_VERSION "5.6.0-3.2"
|
||||
#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver"
|
||||
|
||||
static struct platform_device *dcdbas_pdev;
|
||||
|
||||
static u8 *smi_data_buf;
|
||||
static dma_addr_t smi_data_buf_handle;
|
||||
static unsigned long smi_data_buf_size;
|
||||
static u32 smi_data_buf_phys_addr;
|
||||
static DEFINE_MUTEX(smi_data_lock);
|
||||
|
||||
static unsigned int host_control_action;
|
||||
static unsigned int host_control_smi_type;
|
||||
static unsigned int host_control_on_shutdown;
|
||||
|
||||
/**
|
||||
* smi_data_buf_free: free SMI data buffer
|
||||
*/
|
||||
static void smi_data_buf_free(void)
|
||||
{
|
||||
if (!smi_data_buf)
|
||||
return;
|
||||
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
|
||||
__func__, smi_data_buf_phys_addr, smi_data_buf_size);
|
||||
|
||||
dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf,
|
||||
smi_data_buf_handle);
|
||||
smi_data_buf = NULL;
|
||||
smi_data_buf_handle = 0;
|
||||
smi_data_buf_phys_addr = 0;
|
||||
smi_data_buf_size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smi_data_buf_realloc: grow SMI data buffer if needed
|
||||
*/
|
||||
static int smi_data_buf_realloc(unsigned long size)
|
||||
{
|
||||
void *buf;
|
||||
dma_addr_t handle;
|
||||
|
||||
if (smi_data_buf_size >= size)
|
||||
return 0;
|
||||
|
||||
if (size > MAX_SMI_DATA_BUF_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
/* new buffer is needed */
|
||||
buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
dev_dbg(&dcdbas_pdev->dev,
|
||||
"%s: failed to allocate memory size %lu\n",
|
||||
__func__, size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* memory zeroed by dma_alloc_coherent */
|
||||
|
||||
if (smi_data_buf)
|
||||
memcpy(buf, smi_data_buf, smi_data_buf_size);
|
||||
|
||||
/* free any existing buffer */
|
||||
smi_data_buf_free();
|
||||
|
||||
/* set up new buffer for use */
|
||||
smi_data_buf = buf;
|
||||
smi_data_buf_handle = handle;
|
||||
smi_data_buf_phys_addr = (u32) virt_to_phys(buf);
|
||||
smi_data_buf_size = size;
|
||||
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
|
||||
__func__, smi_data_buf_phys_addr, smi_data_buf_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t smi_data_buf_phys_addr_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%x\n", smi_data_buf_phys_addr);
|
||||
}
|
||||
|
||||
static ssize_t smi_data_buf_size_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%lu\n", smi_data_buf_size);
|
||||
}
|
||||
|
||||
static ssize_t smi_data_buf_size_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long buf_size;
|
||||
ssize_t ret;
|
||||
|
||||
buf_size = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
/* make sure SMI data buffer is at least buf_size */
|
||||
mutex_lock(&smi_data_lock);
|
||||
ret = smi_data_buf_realloc(buf_size);
|
||||
mutex_unlock(&smi_data_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&smi_data_lock);
|
||||
ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf,
|
||||
smi_data_buf_size);
|
||||
mutex_unlock(&smi_data_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t smi_data_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
if ((pos + count) > MAX_SMI_DATA_BUF_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&smi_data_lock);
|
||||
|
||||
ret = smi_data_buf_realloc(pos + count);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
memcpy(smi_data_buf + pos, buf, count);
|
||||
ret = count;
|
||||
out:
|
||||
mutex_unlock(&smi_data_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t host_control_action_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_action);
|
||||
}
|
||||
|
||||
static ssize_t host_control_action_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
/* make sure buffer is available for host control command */
|
||||
mutex_lock(&smi_data_lock);
|
||||
ret = smi_data_buf_realloc(sizeof(struct apm_cmd));
|
||||
mutex_unlock(&smi_data_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
host_control_action = simple_strtoul(buf, NULL, 10);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t host_control_smi_type_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_smi_type);
|
||||
}
|
||||
|
||||
static ssize_t host_control_smi_type_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
host_control_smi_type = simple_strtoul(buf, NULL, 10);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t host_control_on_shutdown_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_on_shutdown);
|
||||
}
|
||||
|
||||
static ssize_t host_control_on_shutdown_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
host_control_on_shutdown = simple_strtoul(buf, NULL, 10);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* dcdbas_smi_request: generate SMI request
|
||||
*
|
||||
* Called with smi_data_lock.
|
||||
*/
|
||||
int dcdbas_smi_request(struct smi_cmd *smi_cmd)
|
||||
{
|
||||
cpumask_var_t old_mask;
|
||||
int ret = 0;
|
||||
|
||||
if (smi_cmd->magic != SMI_CMD_MAGIC) {
|
||||
dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
|
||||
__func__);
|
||||
return -EBADR;
|
||||
}
|
||||
|
||||
/* SMI requires CPU 0 */
|
||||
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
cpumask_copy(old_mask, ¤t->cpus_allowed);
|
||||
set_cpus_allowed_ptr(current, cpumask_of(0));
|
||||
if (smp_processor_id() != 0) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
|
||||
__func__);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* generate SMI */
|
||||
/* inb to force posted write through and make SMI happen now */
|
||||
asm volatile (
|
||||
"outb %b0,%w1\n"
|
||||
"inb %w1"
|
||||
: /* no output args */
|
||||
: "a" (smi_cmd->command_code),
|
||||
"d" (smi_cmd->command_address),
|
||||
"b" (smi_cmd->ebx),
|
||||
"c" (smi_cmd->ecx)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
out:
|
||||
set_cpus_allowed_ptr(current, old_mask);
|
||||
free_cpumask_var(old_mask);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smi_request_store:
|
||||
*
|
||||
* The valid values are:
|
||||
* 0: zero SMI data buffer
|
||||
* 1: generate calling interface SMI
|
||||
* 2: generate raw SMI
|
||||
*
|
||||
* User application writes smi_cmd to smi_data before telling driver
|
||||
* to generate SMI.
|
||||
*/
|
||||
static ssize_t smi_request_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct smi_cmd *smi_cmd;
|
||||
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&smi_data_lock);
|
||||
|
||||
if (smi_data_buf_size < sizeof(struct smi_cmd)) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
smi_cmd = (struct smi_cmd *)smi_data_buf;
|
||||
|
||||
switch (val) {
|
||||
case 2:
|
||||
/* Raw SMI */
|
||||
ret = dcdbas_smi_request(smi_cmd);
|
||||
if (!ret)
|
||||
ret = count;
|
||||
break;
|
||||
case 1:
|
||||
/* Calling Interface SMI */
|
||||
smi_cmd->ebx = (u32) virt_to_phys(smi_cmd->command_buffer);
|
||||
ret = dcdbas_smi_request(smi_cmd);
|
||||
if (!ret)
|
||||
ret = count;
|
||||
break;
|
||||
case 0:
|
||||
memset(smi_data_buf, 0, smi_data_buf_size);
|
||||
ret = count;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&smi_data_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(dcdbas_smi_request);
|
||||
|
||||
/**
|
||||
* host_control_smi: generate host control SMI
|
||||
*
|
||||
* Caller must set up the host control command in smi_data_buf.
|
||||
*/
|
||||
static int host_control_smi(void)
|
||||
{
|
||||
struct apm_cmd *apm_cmd;
|
||||
u8 *data;
|
||||
unsigned long flags;
|
||||
u32 num_ticks;
|
||||
s8 cmd_status;
|
||||
u8 index;
|
||||
|
||||
apm_cmd = (struct apm_cmd *)smi_data_buf;
|
||||
apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL;
|
||||
|
||||
switch (host_control_smi_type) {
|
||||
case HC_SMITYPE_TYPE1:
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
/* write SMI data buffer physical address */
|
||||
data = (u8 *)&smi_data_buf_phys_addr;
|
||||
for (index = PE1300_CMOS_CMD_STRUCT_PTR;
|
||||
index < (PE1300_CMOS_CMD_STRUCT_PTR + 4);
|
||||
index++, data++) {
|
||||
outb(index,
|
||||
(CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4));
|
||||
outb(*data,
|
||||
(CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4));
|
||||
}
|
||||
|
||||
/* first set status to -1 as called by spec */
|
||||
cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL;
|
||||
outb((u8) cmd_status, PCAT_APM_STATUS_PORT);
|
||||
|
||||
/* generate SMM call */
|
||||
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
|
||||
/* wait a few to see if it executed */
|
||||
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
|
||||
while ((cmd_status = inb(PCAT_APM_STATUS_PORT))
|
||||
== ESM_STATUS_CMD_UNSUCCESSFUL) {
|
||||
num_ticks--;
|
||||
if (num_ticks == EXPIRED_TIMER)
|
||||
return -ETIME;
|
||||
}
|
||||
break;
|
||||
|
||||
case HC_SMITYPE_TYPE2:
|
||||
case HC_SMITYPE_TYPE3:
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
/* write SMI data buffer physical address */
|
||||
data = (u8 *)&smi_data_buf_phys_addr;
|
||||
for (index = PE1400_CMOS_CMD_STRUCT_PTR;
|
||||
index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
|
||||
index++, data++) {
|
||||
outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
|
||||
outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
|
||||
}
|
||||
|
||||
/* generate SMM call */
|
||||
if (host_control_smi_type == HC_SMITYPE_TYPE3)
|
||||
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
|
||||
else
|
||||
outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT);
|
||||
|
||||
/* restore RTC index pointer since it was written to above */
|
||||
CMOS_READ(RTC_REG_C);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
|
||||
/* read control port back to serialize write */
|
||||
cmd_status = inb(PE1400_APM_CONTROL_PORT);
|
||||
|
||||
/* wait a few to see if it executed */
|
||||
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
|
||||
while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
|
||||
num_ticks--;
|
||||
if (num_ticks == EXPIRED_TIMER)
|
||||
return -ETIME;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n",
|
||||
__func__, host_control_smi_type);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dcdbas_host_control: initiate host control
|
||||
*
|
||||
* This function is called by the driver after the system has
|
||||
* finished shutting down if the user application specified a
|
||||
* host control action to perform on shutdown. It is safe to
|
||||
* use smi_data_buf at this point because the system has finished
|
||||
* shutting down and no userspace apps are running.
|
||||
*/
|
||||
static void dcdbas_host_control(void)
|
||||
{
|
||||
struct apm_cmd *apm_cmd;
|
||||
u8 action;
|
||||
|
||||
if (host_control_action == HC_ACTION_NONE)
|
||||
return;
|
||||
|
||||
action = host_control_action;
|
||||
host_control_action = HC_ACTION_NONE;
|
||||
|
||||
if (!smi_data_buf) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (smi_data_buf_size < sizeof(struct apm_cmd)) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
apm_cmd = (struct apm_cmd *)smi_data_buf;
|
||||
|
||||
/* power off takes precedence */
|
||||
if (action & HC_ACTION_HOST_CONTROL_POWEROFF) {
|
||||
apm_cmd->command = ESM_APM_POWER_CYCLE;
|
||||
apm_cmd->reserved = 0;
|
||||
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0;
|
||||
host_control_smi();
|
||||
} else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) {
|
||||
apm_cmd->command = ESM_APM_POWER_CYCLE;
|
||||
apm_cmd->reserved = 0;
|
||||
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20;
|
||||
host_control_smi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dcdbas_reboot_notify: handle reboot notification for host control
|
||||
*/
|
||||
static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code,
|
||||
void *unused)
|
||||
{
|
||||
switch (code) {
|
||||
case SYS_DOWN:
|
||||
case SYS_HALT:
|
||||
case SYS_POWER_OFF:
|
||||
if (host_control_on_shutdown) {
|
||||
/* firmware is going to perform host control action */
|
||||
printk(KERN_WARNING "Please wait for shutdown "
|
||||
"action to complete...\n");
|
||||
dcdbas_host_control();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block dcdbas_reboot_nb = {
|
||||
.notifier_call = dcdbas_reboot_notify,
|
||||
.next = NULL,
|
||||
.priority = INT_MIN
|
||||
};
|
||||
|
||||
static DCDBAS_BIN_ATTR_RW(smi_data);
|
||||
|
||||
static struct bin_attribute *dcdbas_bin_attrs[] = {
|
||||
&bin_attr_smi_data,
|
||||
NULL
|
||||
};
|
||||
|
||||
static DCDBAS_DEV_ATTR_RW(smi_data_buf_size);
|
||||
static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr);
|
||||
static DCDBAS_DEV_ATTR_WO(smi_request);
|
||||
static DCDBAS_DEV_ATTR_RW(host_control_action);
|
||||
static DCDBAS_DEV_ATTR_RW(host_control_smi_type);
|
||||
static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown);
|
||||
|
||||
static struct attribute *dcdbas_dev_attrs[] = {
|
||||
&dev_attr_smi_data_buf_size.attr,
|
||||
&dev_attr_smi_data_buf_phys_addr.attr,
|
||||
&dev_attr_smi_request.attr,
|
||||
&dev_attr_host_control_action.attr,
|
||||
&dev_attr_host_control_smi_type.attr,
|
||||
&dev_attr_host_control_on_shutdown.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group dcdbas_attr_group = {
|
||||
.attrs = dcdbas_dev_attrs,
|
||||
.bin_attrs = dcdbas_bin_attrs,
|
||||
};
|
||||
|
||||
static int dcdbas_probe(struct platform_device *dev)
|
||||
{
|
||||
int error;
|
||||
|
||||
host_control_action = HC_ACTION_NONE;
|
||||
host_control_smi_type = HC_SMITYPE_NONE;
|
||||
|
||||
dcdbas_pdev = dev;
|
||||
|
||||
/*
|
||||
* BIOS SMI calls require buffer addresses be in 32-bit address space.
|
||||
* This is done by setting the DMA mask below.
|
||||
*/
|
||||
error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32));
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
register_reboot_notifier(&dcdbas_reboot_nb);
|
||||
|
||||
dev_info(&dev->dev, "%s (version %s)\n",
|
||||
DRIVER_DESCRIPTION, DRIVER_VERSION);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcdbas_remove(struct platform_device *dev)
|
||||
{
|
||||
unregister_reboot_notifier(&dcdbas_reboot_nb);
|
||||
sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver dcdbas_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = dcdbas_probe,
|
||||
.remove = dcdbas_remove,
|
||||
};
|
||||
|
||||
static const struct platform_device_info dcdbas_dev_info __initconst = {
|
||||
.name = DRIVER_NAME,
|
||||
.id = -1,
|
||||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
static struct platform_device *dcdbas_pdev_reg;
|
||||
|
||||
/**
|
||||
* dcdbas_init: initialize driver
|
||||
*/
|
||||
static int __init dcdbas_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = platform_driver_register(&dcdbas_driver);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info);
|
||||
if (IS_ERR(dcdbas_pdev_reg)) {
|
||||
error = PTR_ERR(dcdbas_pdev_reg);
|
||||
goto err_unregister_driver;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_driver:
|
||||
platform_driver_unregister(&dcdbas_driver);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dcdbas_exit: perform driver cleanup
|
||||
*/
|
||||
static void __exit dcdbas_exit(void)
|
||||
{
|
||||
/*
|
||||
* make sure functions that use dcdbas_pdev are called
|
||||
* before platform_device_unregister
|
||||
*/
|
||||
unregister_reboot_notifier(&dcdbas_reboot_nb);
|
||||
|
||||
/*
|
||||
* We have to free the buffer here instead of dcdbas_remove
|
||||
* because only in module exit function we can be sure that
|
||||
* all sysfs attributes belonging to this module have been
|
||||
* released.
|
||||
*/
|
||||
if (dcdbas_pdev)
|
||||
smi_data_buf_free();
|
||||
platform_device_unregister(dcdbas_pdev_reg);
|
||||
platform_driver_unregister(&dcdbas_driver);
|
||||
}
|
||||
|
||||
module_init(dcdbas_init);
|
||||
module_exit(dcdbas_exit);
|
||||
|
||||
MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_AUTHOR("Dell Inc.");
|
||||
MODULE_LICENSE("GPL");
|
||||
/* Any System or BIOS claiming to be by Dell */
|
||||
MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");
|
107
drivers/firmware/dcdbas.h
Normal file
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/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define MAX_SMI_DATA_BUF_SIZE (256 * 1024)
|
||||
|
||||
#define HC_ACTION_NONE (0)
|
||||
#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1)
|
||||
#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2)
|
||||
|
||||
#define HC_SMITYPE_NONE (0)
|
||||
#define HC_SMITYPE_TYPE1 (1)
|
||||
#define HC_SMITYPE_TYPE2 (2)
|
||||
#define HC_SMITYPE_TYPE3 (3)
|
||||
|
||||
#define ESM_APM_CMD (0x0A0)
|
||||
#define ESM_APM_POWER_CYCLE (0x10)
|
||||
#define ESM_STATUS_CMD_UNSUCCESSFUL (-1)
|
||||
|
||||
#define CMOS_BASE_PORT (0x070)
|
||||
#define CMOS_PAGE1_INDEX_PORT (0)
|
||||
#define CMOS_PAGE1_DATA_PORT (1)
|
||||
#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2)
|
||||
#define CMOS_PAGE2_DATA_PORT_PIIX4 (3)
|
||||
#define PE1400_APM_CONTROL_PORT (0x0B0)
|
||||
#define PCAT_APM_CONTROL_PORT (0x0B2)
|
||||
#define PCAT_APM_STATUS_PORT (0x0B3)
|
||||
#define PE1300_CMOS_CMD_STRUCT_PTR (0x38)
|
||||
#define PE1400_CMOS_CMD_STRUCT_PTR (0x70)
|
||||
|
||||
#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14)
|
||||
#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16)
|
||||
|
||||
#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000)
|
||||
#define EXPIRED_TIMER (0)
|
||||
|
||||
#define SMI_CMD_MAGIC (0x534D4931)
|
||||
|
||||
#define DCDBAS_DEV_ATTR_RW(_name) \
|
||||
DEVICE_ATTR(_name,0600,_name##_show,_name##_store);
|
||||
|
||||
#define DCDBAS_DEV_ATTR_RO(_name) \
|
||||
DEVICE_ATTR(_name,0400,_name##_show,NULL);
|
||||
|
||||
#define DCDBAS_DEV_ATTR_WO(_name) \
|
||||
DEVICE_ATTR(_name,0200,NULL,_name##_store);
|
||||
|
||||
#define DCDBAS_BIN_ATTR_RW(_name) \
|
||||
struct bin_attribute bin_attr_##_name = { \
|
||||
.attr = { .name = __stringify(_name), \
|
||||
.mode = 0600 }, \
|
||||
.read = _name##_read, \
|
||||
.write = _name##_write, \
|
||||
}
|
||||
|
||||
struct smi_cmd {
|
||||
__u32 magic;
|
||||
__u32 ebx;
|
||||
__u32 ecx;
|
||||
__u16 command_address;
|
||||
__u8 command_code;
|
||||
__u8 reserved;
|
||||
__u8 command_buffer[1];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct apm_cmd {
|
||||
__u8 command;
|
||||
__s8 status;
|
||||
__u16 reserved;
|
||||
union {
|
||||
struct {
|
||||
__u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN];
|
||||
} __attribute__ ((packed)) shortreq;
|
||||
|
||||
struct {
|
||||
__u16 num_sg_entries;
|
||||
struct {
|
||||
__u32 size;
|
||||
__u64 addr;
|
||||
} __attribute__ ((packed))
|
||||
sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM];
|
||||
} __attribute__ ((packed)) longreq;
|
||||
} __attribute__ ((packed)) parameters;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
int dcdbas_smi_request(struct smi_cmd *smi_cmd);
|
||||
|
||||
#endif /* _DCDBAS_H_ */
|
||||
|
745
drivers/firmware/dell_rbu.c
Normal file
745
drivers/firmware/dell_rbu.c
Normal file
|
@ -0,0 +1,745 @@
|
|||
/*
|
||||
* dell_rbu.c
|
||||
* Bios Update driver for Dell systems
|
||||
* Author: Dell Inc
|
||||
* Abhay Salunke <abhay_salunke@dell.com>
|
||||
*
|
||||
* Copyright (C) 2005 Dell Inc.
|
||||
*
|
||||
* Remote BIOS Update (rbu) driver is used for updating DELL BIOS by
|
||||
* creating entries in the /sys file systems on Linux 2.6 and higher
|
||||
* kernels. The driver supports two mechanism to update the BIOS namely
|
||||
* contiguous and packetized. Both these methods still require having some
|
||||
* application to set the CMOS bit indicating the BIOS to update itself
|
||||
* after a reboot.
|
||||
*
|
||||
* Contiguous method:
|
||||
* This driver writes the incoming data in a monolithic image by allocating
|
||||
* contiguous physical pages large enough to accommodate the incoming BIOS
|
||||
* image size.
|
||||
*
|
||||
* Packetized method:
|
||||
* The driver writes the incoming packet image by allocating a new packet
|
||||
* on every time the packet data is written. This driver requires an
|
||||
* application to break the BIOS image in to fixed sized packet chunks.
|
||||
*
|
||||
* See Documentation/dell_rbu.txt for more info.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
|
||||
MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("3.2");
|
||||
|
||||
#define BIOS_SCAN_LIMIT 0xffffffff
|
||||
#define MAX_IMAGE_LENGTH 16
|
||||
static struct _rbu_data {
|
||||
void *image_update_buffer;
|
||||
unsigned long image_update_buffer_size;
|
||||
unsigned long bios_image_size;
|
||||
int image_update_ordernum;
|
||||
int dma_alloc;
|
||||
spinlock_t lock;
|
||||
unsigned long packet_read_count;
|
||||
unsigned long num_packets;
|
||||
unsigned long packetsize;
|
||||
unsigned long imagesize;
|
||||
int entry_created;
|
||||
} rbu_data;
|
||||
|
||||
static char image_type[MAX_IMAGE_LENGTH + 1] = "mono";
|
||||
module_param_string(image_type, image_type, sizeof (image_type), 0);
|
||||
MODULE_PARM_DESC(image_type,
|
||||
"BIOS image type. choose- mono or packet or init");
|
||||
|
||||
static unsigned long allocation_floor = 0x100000;
|
||||
module_param(allocation_floor, ulong, 0644);
|
||||
MODULE_PARM_DESC(allocation_floor,
|
||||
"Minimum address for allocations when using Packet mode");
|
||||
|
||||
struct packet_data {
|
||||
struct list_head list;
|
||||
size_t length;
|
||||
void *data;
|
||||
int ordernum;
|
||||
};
|
||||
|
||||
static struct packet_data packet_data_head;
|
||||
|
||||
static struct platform_device *rbu_device;
|
||||
static int context;
|
||||
static dma_addr_t dell_rbu_dmaaddr;
|
||||
|
||||
static void init_packet_head(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&packet_data_head.list);
|
||||
rbu_data.packet_read_count = 0;
|
||||
rbu_data.num_packets = 0;
|
||||
rbu_data.packetsize = 0;
|
||||
rbu_data.imagesize = 0;
|
||||
}
|
||||
|
||||
static int create_packet(void *data, size_t length)
|
||||
{
|
||||
struct packet_data *newpacket;
|
||||
int ordernum = 0;
|
||||
int retval = 0;
|
||||
unsigned int packet_array_size = 0;
|
||||
void **invalid_addr_packet_array = NULL;
|
||||
void *packet_data_temp_buf = NULL;
|
||||
unsigned int idx = 0;
|
||||
|
||||
pr_debug("create_packet: entry \n");
|
||||
|
||||
if (!rbu_data.packetsize) {
|
||||
pr_debug("create_packet: packetsize not specified\n");
|
||||
retval = -EINVAL;
|
||||
goto out_noalloc;
|
||||
}
|
||||
|
||||
spin_unlock(&rbu_data.lock);
|
||||
|
||||
newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL);
|
||||
|
||||
if (!newpacket) {
|
||||
printk(KERN_WARNING
|
||||
"dell_rbu:%s: failed to allocate new "
|
||||
"packet\n", __func__);
|
||||
retval = -ENOMEM;
|
||||
spin_lock(&rbu_data.lock);
|
||||
goto out_noalloc;
|
||||
}
|
||||
|
||||
ordernum = get_order(length);
|
||||
|
||||
/*
|
||||
* BIOS errata mean we cannot allocate packets below 1MB or they will
|
||||
* be overwritten by BIOS.
|
||||
*
|
||||
* array to temporarily hold packets
|
||||
* that are below the allocation floor
|
||||
*
|
||||
* NOTE: very simplistic because we only need the floor to be at 1MB
|
||||
* due to BIOS errata. This shouldn't be used for higher floors
|
||||
* or you will run out of mem trying to allocate the array.
|
||||
*/
|
||||
packet_array_size = max(
|
||||
(unsigned int)(allocation_floor / rbu_data.packetsize),
|
||||
(unsigned int)1);
|
||||
invalid_addr_packet_array = kzalloc(packet_array_size * sizeof(void*),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!invalid_addr_packet_array) {
|
||||
printk(KERN_WARNING
|
||||
"dell_rbu:%s: failed to allocate "
|
||||
"invalid_addr_packet_array \n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
spin_lock(&rbu_data.lock);
|
||||
goto out_alloc_packet;
|
||||
}
|
||||
|
||||
while (!packet_data_temp_buf) {
|
||||
packet_data_temp_buf = (unsigned char *)
|
||||
__get_free_pages(GFP_KERNEL, ordernum);
|
||||
if (!packet_data_temp_buf) {
|
||||
printk(KERN_WARNING
|
||||
"dell_rbu:%s: failed to allocate new "
|
||||
"packet\n", __func__);
|
||||
retval = -ENOMEM;
|
||||
spin_lock(&rbu_data.lock);
|
||||
goto out_alloc_packet_array;
|
||||
}
|
||||
|
||||
if ((unsigned long)virt_to_phys(packet_data_temp_buf)
|
||||
< allocation_floor) {
|
||||
pr_debug("packet 0x%lx below floor at 0x%lx.\n",
|
||||
(unsigned long)virt_to_phys(
|
||||
packet_data_temp_buf),
|
||||
allocation_floor);
|
||||
invalid_addr_packet_array[idx++] = packet_data_temp_buf;
|
||||
packet_data_temp_buf = NULL;
|
||||
}
|
||||
}
|
||||
spin_lock(&rbu_data.lock);
|
||||
|
||||
newpacket->data = packet_data_temp_buf;
|
||||
|
||||
pr_debug("create_packet: newpacket at physical addr %lx\n",
|
||||
(unsigned long)virt_to_phys(newpacket->data));
|
||||
|
||||
/* packets may not have fixed size */
|
||||
newpacket->length = length;
|
||||
newpacket->ordernum = ordernum;
|
||||
++rbu_data.num_packets;
|
||||
|
||||
/* initialize the newly created packet headers */
|
||||
INIT_LIST_HEAD(&newpacket->list);
|
||||
list_add_tail(&newpacket->list, &packet_data_head.list);
|
||||
|
||||
memcpy(newpacket->data, data, length);
|
||||
|
||||
pr_debug("create_packet: exit \n");
|
||||
|
||||
out_alloc_packet_array:
|
||||
/* always free packet array */
|
||||
for (;idx>0;idx--) {
|
||||
pr_debug("freeing unused packet below floor 0x%lx.\n",
|
||||
(unsigned long)virt_to_phys(
|
||||
invalid_addr_packet_array[idx-1]));
|
||||
free_pages((unsigned long)invalid_addr_packet_array[idx-1],
|
||||
ordernum);
|
||||
}
|
||||
kfree(invalid_addr_packet_array);
|
||||
|
||||
out_alloc_packet:
|
||||
/* if error, free data */
|
||||
if (retval)
|
||||
kfree(newpacket);
|
||||
|
||||
out_noalloc:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int packetize_data(const u8 *data, size_t length)
|
||||
{
|
||||
int rc = 0;
|
||||
int done = 0;
|
||||
int packet_length;
|
||||
u8 *temp;
|
||||
u8 *end = (u8 *) data + length;
|
||||
pr_debug("packetize_data: data length %zd\n", length);
|
||||
if (!rbu_data.packetsize) {
|
||||
printk(KERN_WARNING
|
||||
"dell_rbu: packetsize not specified\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
temp = (u8 *) data;
|
||||
|
||||
/* packetize the hunk */
|
||||
while (!done) {
|
||||
if ((temp + rbu_data.packetsize) < end)
|
||||
packet_length = rbu_data.packetsize;
|
||||
else {
|
||||
/* this is the last packet */
|
||||
packet_length = end - temp;
|
||||
done = 1;
|
||||
}
|
||||
|
||||
if ((rc = create_packet(temp, packet_length)))
|
||||
return rc;
|
||||
|
||||
pr_debug("%p:%td\n", temp, (end - temp));
|
||||
temp += packet_length;
|
||||
}
|
||||
|
||||
rbu_data.imagesize = length;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int do_packet_read(char *data, struct list_head *ptemp_list,
|
||||
int length, int bytes_read, int *list_read_count)
|
||||
{
|
||||
void *ptemp_buf;
|
||||
struct packet_data *newpacket = NULL;
|
||||
int bytes_copied = 0;
|
||||
int j = 0;
|
||||
|
||||
newpacket = list_entry(ptemp_list, struct packet_data, list);
|
||||
*list_read_count += newpacket->length;
|
||||
|
||||
if (*list_read_count > bytes_read) {
|
||||
/* point to the start of unread data */
|
||||
j = newpacket->length - (*list_read_count - bytes_read);
|
||||
/* point to the offset in the packet buffer */
|
||||
ptemp_buf = (u8 *) newpacket->data + j;
|
||||
/*
|
||||
* check if there is enough room in
|
||||
* * the incoming buffer
|
||||
*/
|
||||
if (length > (*list_read_count - bytes_read))
|
||||
/*
|
||||
* copy what ever is there in this
|
||||
* packet and move on
|
||||
*/
|
||||
bytes_copied = (*list_read_count - bytes_read);
|
||||
else
|
||||
/* copy the remaining */
|
||||
bytes_copied = length;
|
||||
memcpy(data, ptemp_buf, bytes_copied);
|
||||
}
|
||||
return bytes_copied;
|
||||
}
|
||||
|
||||
static int packet_read_list(char *data, size_t * pread_length)
|
||||
{
|
||||
struct list_head *ptemp_list;
|
||||
int temp_count = 0;
|
||||
int bytes_copied = 0;
|
||||
int bytes_read = 0;
|
||||
int remaining_bytes = 0;
|
||||
char *pdest = data;
|
||||
|
||||
/* check if we have any packets */
|
||||
if (0 == rbu_data.num_packets)
|
||||
return -ENOMEM;
|
||||
|
||||
remaining_bytes = *pread_length;
|
||||
bytes_read = rbu_data.packet_read_count;
|
||||
|
||||
ptemp_list = (&packet_data_head.list)->next;
|
||||
while (!list_empty(ptemp_list)) {
|
||||
bytes_copied = do_packet_read(pdest, ptemp_list,
|
||||
remaining_bytes, bytes_read, &temp_count);
|
||||
remaining_bytes -= bytes_copied;
|
||||
bytes_read += bytes_copied;
|
||||
pdest += bytes_copied;
|
||||
/*
|
||||
* check if we reached end of buffer before reaching the
|
||||
* last packet
|
||||
*/
|
||||
if (remaining_bytes == 0)
|
||||
break;
|
||||
|
||||
ptemp_list = ptemp_list->next;
|
||||
}
|
||||
/*finally set the bytes read */
|
||||
*pread_length = bytes_read - rbu_data.packet_read_count;
|
||||
rbu_data.packet_read_count = bytes_read;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void packet_empty_list(void)
|
||||
{
|
||||
struct list_head *ptemp_list;
|
||||
struct list_head *pnext_list;
|
||||
struct packet_data *newpacket;
|
||||
|
||||
ptemp_list = (&packet_data_head.list)->next;
|
||||
while (!list_empty(ptemp_list)) {
|
||||
newpacket =
|
||||
list_entry(ptemp_list, struct packet_data, list);
|
||||
pnext_list = ptemp_list->next;
|
||||
list_del(ptemp_list);
|
||||
ptemp_list = pnext_list;
|
||||
/*
|
||||
* zero out the RBU packet memory before freeing
|
||||
* to make sure there are no stale RBU packets left in memory
|
||||
*/
|
||||
memset(newpacket->data, 0, rbu_data.packetsize);
|
||||
free_pages((unsigned long) newpacket->data,
|
||||
newpacket->ordernum);
|
||||
kfree(newpacket);
|
||||
}
|
||||
rbu_data.packet_read_count = 0;
|
||||
rbu_data.num_packets = 0;
|
||||
rbu_data.imagesize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* img_update_free: Frees the buffer allocated for storing BIOS image
|
||||
* Always called with lock held and returned with lock held
|
||||
*/
|
||||
static void img_update_free(void)
|
||||
{
|
||||
if (!rbu_data.image_update_buffer)
|
||||
return;
|
||||
/*
|
||||
* zero out this buffer before freeing it to get rid of any stale
|
||||
* BIOS image copied in memory.
|
||||
*/
|
||||
memset(rbu_data.image_update_buffer, 0,
|
||||
rbu_data.image_update_buffer_size);
|
||||
if (rbu_data.dma_alloc == 1)
|
||||
dma_free_coherent(NULL, rbu_data.bios_image_size,
|
||||
rbu_data.image_update_buffer, dell_rbu_dmaaddr);
|
||||
else
|
||||
free_pages((unsigned long) rbu_data.image_update_buffer,
|
||||
rbu_data.image_update_ordernum);
|
||||
|
||||
/*
|
||||
* Re-initialize the rbu_data variables after a free
|
||||
*/
|
||||
rbu_data.image_update_ordernum = -1;
|
||||
rbu_data.image_update_buffer = NULL;
|
||||
rbu_data.image_update_buffer_size = 0;
|
||||
rbu_data.bios_image_size = 0;
|
||||
rbu_data.dma_alloc = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* img_update_realloc: This function allocates the contiguous pages to
|
||||
* accommodate the requested size of data. The memory address and size
|
||||
* values are stored globally and on every call to this function the new
|
||||
* size is checked to see if more data is required than the existing size.
|
||||
* If true the previous memory is freed and new allocation is done to
|
||||
* accommodate the new size. If the incoming size is less then than the
|
||||
* already allocated size, then that memory is reused. This function is
|
||||
* called with lock held and returns with lock held.
|
||||
*/
|
||||
static int img_update_realloc(unsigned long size)
|
||||
{
|
||||
unsigned char *image_update_buffer = NULL;
|
||||
unsigned long rc;
|
||||
unsigned long img_buf_phys_addr;
|
||||
int ordernum;
|
||||
int dma_alloc = 0;
|
||||
|
||||
/*
|
||||
* check if the buffer of sufficient size has been
|
||||
* already allocated
|
||||
*/
|
||||
if (rbu_data.image_update_buffer_size >= size) {
|
||||
/*
|
||||
* check for corruption
|
||||
*/
|
||||
if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
|
||||
printk(KERN_ERR "dell_rbu:%s: corruption "
|
||||
"check failed\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
/*
|
||||
* we have a valid pre-allocated buffer with
|
||||
* sufficient size
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* free any previously allocated buffer
|
||||
*/
|
||||
img_update_free();
|
||||
|
||||
spin_unlock(&rbu_data.lock);
|
||||
|
||||
ordernum = get_order(size);
|
||||
image_update_buffer =
|
||||
(unsigned char *) __get_free_pages(GFP_KERNEL, ordernum);
|
||||
|
||||
img_buf_phys_addr =
|
||||
(unsigned long) virt_to_phys(image_update_buffer);
|
||||
|
||||
if (img_buf_phys_addr > BIOS_SCAN_LIMIT) {
|
||||
free_pages((unsigned long) image_update_buffer, ordernum);
|
||||
ordernum = -1;
|
||||
image_update_buffer = dma_alloc_coherent(NULL, size,
|
||||
&dell_rbu_dmaaddr, GFP_KERNEL);
|
||||
dma_alloc = 1;
|
||||
}
|
||||
|
||||
spin_lock(&rbu_data.lock);
|
||||
|
||||
if (image_update_buffer != NULL) {
|
||||
rbu_data.image_update_buffer = image_update_buffer;
|
||||
rbu_data.image_update_buffer_size = size;
|
||||
rbu_data.bios_image_size =
|
||||
rbu_data.image_update_buffer_size;
|
||||
rbu_data.image_update_ordernum = ordernum;
|
||||
rbu_data.dma_alloc = dma_alloc;
|
||||
rc = 0;
|
||||
} else {
|
||||
pr_debug("Not enough memory for image update:"
|
||||
"size = %ld\n", size);
|
||||
rc = -ENOMEM;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
int retval;
|
||||
size_t bytes_left;
|
||||
size_t data_length;
|
||||
char *ptempBuf = buffer;
|
||||
|
||||
/* check to see if we have something to return */
|
||||
if (rbu_data.num_packets == 0) {
|
||||
pr_debug("read_packet_data: no packets written\n");
|
||||
retval = -ENOMEM;
|
||||
goto read_rbu_data_exit;
|
||||
}
|
||||
|
||||
if (pos > rbu_data.imagesize) {
|
||||
retval = 0;
|
||||
printk(KERN_WARNING "dell_rbu:read_packet_data: "
|
||||
"data underrun\n");
|
||||
goto read_rbu_data_exit;
|
||||
}
|
||||
|
||||
bytes_left = rbu_data.imagesize - pos;
|
||||
data_length = min(bytes_left, count);
|
||||
|
||||
if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
|
||||
goto read_rbu_data_exit;
|
||||
|
||||
if ((pos + count) > rbu_data.imagesize) {
|
||||
rbu_data.packet_read_count = 0;
|
||||
/* this was the last copy */
|
||||
retval = bytes_left;
|
||||
} else
|
||||
retval = count;
|
||||
|
||||
read_rbu_data_exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
/* check to see if we have something to return */
|
||||
if ((rbu_data.image_update_buffer == NULL) ||
|
||||
(rbu_data.bios_image_size == 0)) {
|
||||
pr_debug("read_rbu_data_mono: image_update_buffer %p ,"
|
||||
"bios_image_size %lu\n",
|
||||
rbu_data.image_update_buffer,
|
||||
rbu_data.bios_image_size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return memory_read_from_buffer(buffer, count, &pos,
|
||||
rbu_data.image_update_buffer, rbu_data.bios_image_size);
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_data(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
ssize_t ret_count = 0;
|
||||
|
||||
spin_lock(&rbu_data.lock);
|
||||
|
||||
if (!strcmp(image_type, "mono"))
|
||||
ret_count = read_rbu_mono_data(buffer, pos, count);
|
||||
else if (!strcmp(image_type, "packet"))
|
||||
ret_count = read_packet_data(buffer, pos, count);
|
||||
else
|
||||
pr_debug("read_rbu_data: invalid image type specified\n");
|
||||
|
||||
spin_unlock(&rbu_data.lock);
|
||||
return ret_count;
|
||||
}
|
||||
|
||||
static void callbackfn_rbu(const struct firmware *fw, void *context)
|
||||
{
|
||||
rbu_data.entry_created = 0;
|
||||
|
||||
if (!fw)
|
||||
return;
|
||||
|
||||
if (!fw->size)
|
||||
goto out;
|
||||
|
||||
spin_lock(&rbu_data.lock);
|
||||
if (!strcmp(image_type, "mono")) {
|
||||
if (!img_update_realloc(fw->size))
|
||||
memcpy(rbu_data.image_update_buffer,
|
||||
fw->data, fw->size);
|
||||
} else if (!strcmp(image_type, "packet")) {
|
||||
/*
|
||||
* we need to free previous packets if a
|
||||
* new hunk of packets needs to be downloaded
|
||||
*/
|
||||
packet_empty_list();
|
||||
if (packetize_data(fw->data, fw->size))
|
||||
/* Incase something goes wrong when we are
|
||||
* in middle of packetizing the data, we
|
||||
* need to free up whatever packets might
|
||||
* have been created before we quit.
|
||||
*/
|
||||
packet_empty_list();
|
||||
} else
|
||||
pr_debug("invalid image type specified.\n");
|
||||
spin_unlock(&rbu_data.lock);
|
||||
out:
|
||||
release_firmware(fw);
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_image_type(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
int size = 0;
|
||||
if (!pos)
|
||||
size = scnprintf(buffer, count, "%s\n", image_type);
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t write_rbu_image_type(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
int rc = count;
|
||||
int req_firm_rc = 0;
|
||||
int i;
|
||||
spin_lock(&rbu_data.lock);
|
||||
/*
|
||||
* Find the first newline or space
|
||||
*/
|
||||
for (i = 0; i < count; ++i)
|
||||
if (buffer[i] == '\n' || buffer[i] == ' ') {
|
||||
buffer[i] = '\0';
|
||||
break;
|
||||
}
|
||||
if (i == count)
|
||||
buffer[count] = '\0';
|
||||
|
||||
if (strstr(buffer, "mono"))
|
||||
strcpy(image_type, "mono");
|
||||
else if (strstr(buffer, "packet"))
|
||||
strcpy(image_type, "packet");
|
||||
else if (strstr(buffer, "init")) {
|
||||
/*
|
||||
* If due to the user error the driver gets in a bad
|
||||
* state where even though it is loaded , the
|
||||
* /sys/class/firmware/dell_rbu entries are missing.
|
||||
* to cover this situation the user can recreate entries
|
||||
* by writing init to image_type.
|
||||
*/
|
||||
if (!rbu_data.entry_created) {
|
||||
spin_unlock(&rbu_data.lock);
|
||||
req_firm_rc = request_firmware_nowait(THIS_MODULE,
|
||||
FW_ACTION_NOHOTPLUG, "dell_rbu",
|
||||
&rbu_device->dev, GFP_KERNEL, &context,
|
||||
callbackfn_rbu);
|
||||
if (req_firm_rc) {
|
||||
printk(KERN_ERR
|
||||
"dell_rbu:%s request_firmware_nowait"
|
||||
" failed %d\n", __func__, rc);
|
||||
rc = -EIO;
|
||||
} else
|
||||
rbu_data.entry_created = 1;
|
||||
|
||||
spin_lock(&rbu_data.lock);
|
||||
}
|
||||
} else {
|
||||
printk(KERN_WARNING "dell_rbu: image_type is invalid\n");
|
||||
spin_unlock(&rbu_data.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* we must free all previous allocations */
|
||||
packet_empty_list();
|
||||
img_update_free();
|
||||
spin_unlock(&rbu_data.lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t read_rbu_packet_size(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
int size = 0;
|
||||
if (!pos) {
|
||||
spin_lock(&rbu_data.lock);
|
||||
size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize);
|
||||
spin_unlock(&rbu_data.lock);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t write_rbu_packet_size(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buffer, loff_t pos, size_t count)
|
||||
{
|
||||
unsigned long temp;
|
||||
spin_lock(&rbu_data.lock);
|
||||
packet_empty_list();
|
||||
sscanf(buffer, "%lu", &temp);
|
||||
if (temp < 0xffffffff)
|
||||
rbu_data.packetsize = temp;
|
||||
|
||||
spin_unlock(&rbu_data.lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct bin_attribute rbu_data_attr = {
|
||||
.attr = {.name = "data", .mode = 0444},
|
||||
.read = read_rbu_data,
|
||||
};
|
||||
|
||||
static struct bin_attribute rbu_image_type_attr = {
|
||||
.attr = {.name = "image_type", .mode = 0644},
|
||||
.read = read_rbu_image_type,
|
||||
.write = write_rbu_image_type,
|
||||
};
|
||||
|
||||
static struct bin_attribute rbu_packet_size_attr = {
|
||||
.attr = {.name = "packet_size", .mode = 0644},
|
||||
.read = read_rbu_packet_size,
|
||||
.write = write_rbu_packet_size,
|
||||
};
|
||||
|
||||
static int __init dcdrbu_init(void)
|
||||
{
|
||||
int rc;
|
||||
spin_lock_init(&rbu_data.lock);
|
||||
|
||||
init_packet_head();
|
||||
rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0);
|
||||
if (IS_ERR(rbu_device)) {
|
||||
printk(KERN_ERR
|
||||
"dell_rbu:%s:platform_device_register_simple "
|
||||
"failed\n", __func__);
|
||||
return PTR_ERR(rbu_device);
|
||||
}
|
||||
|
||||
rc = sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_data_attr);
|
||||
if (rc)
|
||||
goto out_devreg;
|
||||
rc = sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_image_type_attr);
|
||||
if (rc)
|
||||
goto out_data;
|
||||
rc = sysfs_create_bin_file(&rbu_device->dev.kobj,
|
||||
&rbu_packet_size_attr);
|
||||
if (rc)
|
||||
goto out_imtype;
|
||||
|
||||
rbu_data.entry_created = 0;
|
||||
return 0;
|
||||
|
||||
out_imtype:
|
||||
sysfs_remove_bin_file(&rbu_device->dev.kobj, &rbu_image_type_attr);
|
||||
out_data:
|
||||
sysfs_remove_bin_file(&rbu_device->dev.kobj, &rbu_data_attr);
|
||||
out_devreg:
|
||||
platform_device_unregister(rbu_device);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static __exit void dcdrbu_exit(void)
|
||||
{
|
||||
spin_lock(&rbu_data.lock);
|
||||
packet_empty_list();
|
||||
img_update_free();
|
||||
spin_unlock(&rbu_data.lock);
|
||||
platform_device_unregister(rbu_device);
|
||||
}
|
||||
|
||||
module_exit(dcdrbu_exit);
|
||||
module_init(dcdrbu_init);
|
||||
|
||||
/* vim:noet:ts=8:sw=8
|
||||
*/
|
245
drivers/firmware/dmi-id.c
Normal file
245
drivers/firmware/dmi-id.c
Normal file
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Export SMBIOS/DMI info via sysfs to userspace
|
||||
*
|
||||
* Copyright 2007, Lennart Poettering
|
||||
*
|
||||
* Licensed under GPLv2
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct dmi_device_attribute{
|
||||
struct device_attribute dev_attr;
|
||||
int field;
|
||||
};
|
||||
#define to_dmi_dev_attr(_dev_attr) \
|
||||
container_of(_dev_attr, struct dmi_device_attribute, dev_attr)
|
||||
|
||||
static ssize_t sys_dmi_field_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
int field = to_dmi_dev_attr(attr)->field;
|
||||
ssize_t len;
|
||||
len = scnprintf(page, PAGE_SIZE, "%s\n", dmi_get_system_info(field));
|
||||
page[len-1] = '\n';
|
||||
return len;
|
||||
}
|
||||
|
||||
#define DMI_ATTR(_name, _mode, _show, _field) \
|
||||
{ .dev_attr = __ATTR(_name, _mode, _show, NULL), \
|
||||
.field = _field }
|
||||
|
||||
#define DEFINE_DMI_ATTR_WITH_SHOW(_name, _mode, _field) \
|
||||
static struct dmi_device_attribute sys_dmi_##_name##_attr = \
|
||||
DMI_ATTR(_name, _mode, sys_dmi_field_show, _field);
|
||||
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(bios_vendor, 0444, DMI_BIOS_VENDOR);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(bios_version, 0444, DMI_BIOS_VERSION);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(bios_date, 0444, DMI_BIOS_DATE);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(sys_vendor, 0444, DMI_SYS_VENDOR);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(product_name, 0444, DMI_PRODUCT_NAME);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(product_version, 0444, DMI_PRODUCT_VERSION);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(product_serial, 0400, DMI_PRODUCT_SERIAL);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(product_uuid, 0400, DMI_PRODUCT_UUID);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(board_vendor, 0444, DMI_BOARD_VENDOR);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(board_name, 0444, DMI_BOARD_NAME);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(board_version, 0444, DMI_BOARD_VERSION);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(board_serial, 0400, DMI_BOARD_SERIAL);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(board_asset_tag, 0444, DMI_BOARD_ASSET_TAG);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(chassis_vendor, 0444, DMI_CHASSIS_VENDOR);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(chassis_type, 0444, DMI_CHASSIS_TYPE);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(chassis_version, 0444, DMI_CHASSIS_VERSION);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(chassis_serial, 0400, DMI_CHASSIS_SERIAL);
|
||||
DEFINE_DMI_ATTR_WITH_SHOW(chassis_asset_tag, 0444, DMI_CHASSIS_ASSET_TAG);
|
||||
|
||||
static void ascii_filter(char *d, const char *s)
|
||||
{
|
||||
/* Filter out characters we don't want to see in the modalias string */
|
||||
for (; *s; s++)
|
||||
if (*s > ' ' && *s < 127 && *s != ':')
|
||||
*(d++) = *s;
|
||||
|
||||
*d = 0;
|
||||
}
|
||||
|
||||
static ssize_t get_modalias(char *buffer, size_t buffer_size)
|
||||
{
|
||||
static const struct mafield {
|
||||
const char *prefix;
|
||||
int field;
|
||||
} fields[] = {
|
||||
{ "bvn", DMI_BIOS_VENDOR },
|
||||
{ "bvr", DMI_BIOS_VERSION },
|
||||
{ "bd", DMI_BIOS_DATE },
|
||||
{ "svn", DMI_SYS_VENDOR },
|
||||
{ "pn", DMI_PRODUCT_NAME },
|
||||
{ "pvr", DMI_PRODUCT_VERSION },
|
||||
{ "rvn", DMI_BOARD_VENDOR },
|
||||
{ "rn", DMI_BOARD_NAME },
|
||||
{ "rvr", DMI_BOARD_VERSION },
|
||||
{ "cvn", DMI_CHASSIS_VENDOR },
|
||||
{ "ct", DMI_CHASSIS_TYPE },
|
||||
{ "cvr", DMI_CHASSIS_VERSION },
|
||||
{ NULL, DMI_NONE }
|
||||
};
|
||||
|
||||
ssize_t l, left;
|
||||
char *p;
|
||||
const struct mafield *f;
|
||||
|
||||
strcpy(buffer, "dmi");
|
||||
p = buffer + 3; left = buffer_size - 4;
|
||||
|
||||
for (f = fields; f->prefix && left > 0; f++) {
|
||||
const char *c;
|
||||
char *t;
|
||||
|
||||
c = dmi_get_system_info(f->field);
|
||||
if (!c)
|
||||
continue;
|
||||
|
||||
t = kmalloc(strlen(c) + 1, GFP_KERNEL);
|
||||
if (!t)
|
||||
break;
|
||||
ascii_filter(t, c);
|
||||
l = scnprintf(p, left, ":%s%s", f->prefix, t);
|
||||
kfree(t);
|
||||
|
||||
p += l;
|
||||
left -= l;
|
||||
}
|
||||
|
||||
p[0] = ':';
|
||||
p[1] = 0;
|
||||
|
||||
return p - buffer + 1;
|
||||
}
|
||||
|
||||
static ssize_t sys_dmi_modalias_show(struct device *dev,
|
||||
struct device_attribute *attr, char *page)
|
||||
{
|
||||
ssize_t r;
|
||||
r = get_modalias(page, PAGE_SIZE-1);
|
||||
page[r] = '\n';
|
||||
page[r+1] = 0;
|
||||
return r+1;
|
||||
}
|
||||
|
||||
static struct device_attribute sys_dmi_modalias_attr =
|
||||
__ATTR(modalias, 0444, sys_dmi_modalias_show, NULL);
|
||||
|
||||
static struct attribute *sys_dmi_attributes[DMI_STRING_MAX+2];
|
||||
|
||||
static struct attribute_group sys_dmi_attribute_group = {
|
||||
.attrs = sys_dmi_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group* sys_dmi_attribute_groups[] = {
|
||||
&sys_dmi_attribute_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int dmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
ssize_t len;
|
||||
|
||||
if (add_uevent_var(env, "MODALIAS="))
|
||||
return -ENOMEM;
|
||||
len = get_modalias(&env->buf[env->buflen - 1],
|
||||
sizeof(env->buf) - env->buflen);
|
||||
if (len >= (sizeof(env->buf) - env->buflen))
|
||||
return -ENOMEM;
|
||||
env->buflen += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct class dmi_class = {
|
||||
.name = "dmi",
|
||||
.dev_release = (void(*)(struct device *)) kfree,
|
||||
.dev_uevent = dmi_dev_uevent,
|
||||
};
|
||||
|
||||
static struct device *dmi_dev;
|
||||
|
||||
/* Initialization */
|
||||
|
||||
#define ADD_DMI_ATTR(_name, _field) \
|
||||
if (dmi_get_system_info(_field)) \
|
||||
sys_dmi_attributes[i++] = &sys_dmi_##_name##_attr.dev_attr.attr;
|
||||
|
||||
/* In a separate function to keep gcc 3.2 happy - do NOT merge this in
|
||||
dmi_id_init! */
|
||||
static void __init dmi_id_init_attr_table(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Not necessarily all DMI fields are available on all
|
||||
* systems, hence let's built an attribute table of just
|
||||
* what's available */
|
||||
i = 0;
|
||||
ADD_DMI_ATTR(bios_vendor, DMI_BIOS_VENDOR);
|
||||
ADD_DMI_ATTR(bios_version, DMI_BIOS_VERSION);
|
||||
ADD_DMI_ATTR(bios_date, DMI_BIOS_DATE);
|
||||
ADD_DMI_ATTR(sys_vendor, DMI_SYS_VENDOR);
|
||||
ADD_DMI_ATTR(product_name, DMI_PRODUCT_NAME);
|
||||
ADD_DMI_ATTR(product_version, DMI_PRODUCT_VERSION);
|
||||
ADD_DMI_ATTR(product_serial, DMI_PRODUCT_SERIAL);
|
||||
ADD_DMI_ATTR(product_uuid, DMI_PRODUCT_UUID);
|
||||
ADD_DMI_ATTR(board_vendor, DMI_BOARD_VENDOR);
|
||||
ADD_DMI_ATTR(board_name, DMI_BOARD_NAME);
|
||||
ADD_DMI_ATTR(board_version, DMI_BOARD_VERSION);
|
||||
ADD_DMI_ATTR(board_serial, DMI_BOARD_SERIAL);
|
||||
ADD_DMI_ATTR(board_asset_tag, DMI_BOARD_ASSET_TAG);
|
||||
ADD_DMI_ATTR(chassis_vendor, DMI_CHASSIS_VENDOR);
|
||||
ADD_DMI_ATTR(chassis_type, DMI_CHASSIS_TYPE);
|
||||
ADD_DMI_ATTR(chassis_version, DMI_CHASSIS_VERSION);
|
||||
ADD_DMI_ATTR(chassis_serial, DMI_CHASSIS_SERIAL);
|
||||
ADD_DMI_ATTR(chassis_asset_tag, DMI_CHASSIS_ASSET_TAG);
|
||||
sys_dmi_attributes[i++] = &sys_dmi_modalias_attr.attr;
|
||||
}
|
||||
|
||||
static int __init dmi_id_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!dmi_available)
|
||||
return -ENODEV;
|
||||
|
||||
dmi_id_init_attr_table();
|
||||
|
||||
ret = class_register(&dmi_class);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dmi_dev = kzalloc(sizeof(*dmi_dev), GFP_KERNEL);
|
||||
if (!dmi_dev) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_class_unregister;
|
||||
}
|
||||
|
||||
dmi_dev->class = &dmi_class;
|
||||
dev_set_name(dmi_dev, "id");
|
||||
dmi_dev->groups = sys_dmi_attribute_groups;
|
||||
|
||||
ret = device_register(dmi_dev);
|
||||
if (ret)
|
||||
goto fail_free_dmi_dev;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_free_dmi_dev:
|
||||
kfree(dmi_dev);
|
||||
fail_class_unregister:
|
||||
|
||||
class_unregister(&dmi_class);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
arch_initcall(dmi_id_init);
|
697
drivers/firmware/dmi-sysfs.c
Normal file
697
drivers/firmware/dmi-sysfs.c
Normal file
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
* dmi-sysfs.c
|
||||
*
|
||||
* This module exports the DMI tables read-only to userspace through the
|
||||
* sysfs file system.
|
||||
*
|
||||
* Data is currently found below
|
||||
* /sys/firmware/dmi/...
|
||||
*
|
||||
* DMI attributes are presented in attribute files with names
|
||||
* formatted using %d-%d, so that the first integer indicates the
|
||||
* structure type (0-255), and the second field is the instance of that
|
||||
* entry.
|
||||
*
|
||||
* Copyright 2011 Google, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
|
||||
the top entry type is only 8 bits */
|
||||
|
||||
struct dmi_sysfs_entry {
|
||||
struct dmi_header dh;
|
||||
struct kobject kobj;
|
||||
int instance;
|
||||
int position;
|
||||
struct list_head list;
|
||||
struct kobject *child;
|
||||
};
|
||||
|
||||
/*
|
||||
* Global list of dmi_sysfs_entry. Even though this should only be
|
||||
* manipulated at setup and teardown, the lazy nature of the kobject
|
||||
* system means we get lazy removes.
|
||||
*/
|
||||
static LIST_HEAD(entry_list);
|
||||
static DEFINE_SPINLOCK(entry_list_lock);
|
||||
|
||||
/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
|
||||
struct dmi_sysfs_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
|
||||
};
|
||||
|
||||
#define DMI_SYSFS_ATTR(_entry, _name) \
|
||||
struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = 0400}, \
|
||||
.show = dmi_sysfs_##_entry##_##_name, \
|
||||
}
|
||||
|
||||
/*
|
||||
* dmi_sysfs_mapped_attribute - Attribute where we require the entry be
|
||||
* mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops.
|
||||
*/
|
||||
struct dmi_sysfs_mapped_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_header *dh,
|
||||
char *buf);
|
||||
};
|
||||
|
||||
#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
|
||||
struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = 0400}, \
|
||||
.show = dmi_sysfs_##_entry##_##_name, \
|
||||
}
|
||||
|
||||
/*************************************************
|
||||
* Generic DMI entry support.
|
||||
*************************************************/
|
||||
static void dmi_entry_free(struct kobject *kobj)
|
||||
{
|
||||
kfree(kobj);
|
||||
}
|
||||
|
||||
static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
|
||||
{
|
||||
return container_of(kobj, struct dmi_sysfs_entry, kobj);
|
||||
}
|
||||
|
||||
static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
|
||||
{
|
||||
return container_of(attr, struct dmi_sysfs_attribute, attr);
|
||||
}
|
||||
|
||||
static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
|
||||
struct attribute *_attr, char *buf)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry = to_entry(kobj);
|
||||
struct dmi_sysfs_attribute *attr = to_attr(_attr);
|
||||
|
||||
/* DMI stuff is only ever admin visible */
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
return attr->show(entry, buf);
|
||||
}
|
||||
|
||||
static const struct sysfs_ops dmi_sysfs_attr_ops = {
|
||||
.show = dmi_sysfs_attr_show,
|
||||
};
|
||||
|
||||
typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
|
||||
const struct dmi_header *dh, void *);
|
||||
|
||||
struct find_dmi_data {
|
||||
struct dmi_sysfs_entry *entry;
|
||||
dmi_callback callback;
|
||||
void *private;
|
||||
int instance_countdown;
|
||||
ssize_t ret;
|
||||
};
|
||||
|
||||
static void find_dmi_entry_helper(const struct dmi_header *dh,
|
||||
void *_data)
|
||||
{
|
||||
struct find_dmi_data *data = _data;
|
||||
struct dmi_sysfs_entry *entry = data->entry;
|
||||
|
||||
/* Is this the entry we want? */
|
||||
if (dh->type != entry->dh.type)
|
||||
return;
|
||||
|
||||
if (data->instance_countdown != 0) {
|
||||
/* try the next instance? */
|
||||
data->instance_countdown--;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't ever revisit the instance. Short circuit later
|
||||
* instances by letting the instance_countdown run negative
|
||||
*/
|
||||
data->instance_countdown--;
|
||||
|
||||
/* Found the entry */
|
||||
data->ret = data->callback(entry, dh, data->private);
|
||||
}
|
||||
|
||||
/* State for passing the read parameters through dmi_find_entry() */
|
||||
struct dmi_read_state {
|
||||
char *buf;
|
||||
loff_t pos;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
|
||||
dmi_callback callback, void *private)
|
||||
{
|
||||
struct find_dmi_data data = {
|
||||
.entry = entry,
|
||||
.callback = callback,
|
||||
.private = private,
|
||||
.instance_countdown = entry->instance,
|
||||
.ret = -EIO, /* To signal the entry disappeared */
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = dmi_walk(find_dmi_entry_helper, &data);
|
||||
/* This shouldn't happen, but just in case. */
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
return data.ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate and return the byte length of the dmi entry identified by
|
||||
* dh. This includes both the formatted portion as well as the
|
||||
* unformatted string space, including the two trailing nul characters.
|
||||
*/
|
||||
static size_t dmi_entry_length(const struct dmi_header *dh)
|
||||
{
|
||||
const char *p = (const char *)dh;
|
||||
|
||||
p += dh->length;
|
||||
|
||||
while (p[0] || p[1])
|
||||
p++;
|
||||
|
||||
return 2 + p - (const char *)dh;
|
||||
}
|
||||
|
||||
/*************************************************
|
||||
* Support bits for specialized DMI entry support
|
||||
*************************************************/
|
||||
struct dmi_entry_attr_show_data {
|
||||
struct attribute *attr;
|
||||
char *buf;
|
||||
};
|
||||
|
||||
static ssize_t dmi_entry_attr_show_helper(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_header *dh,
|
||||
void *_data)
|
||||
{
|
||||
struct dmi_entry_attr_show_data *data = _data;
|
||||
struct dmi_sysfs_mapped_attribute *attr;
|
||||
|
||||
attr = container_of(data->attr,
|
||||
struct dmi_sysfs_mapped_attribute, attr);
|
||||
return attr->show(entry, dh, data->buf);
|
||||
}
|
||||
|
||||
static ssize_t dmi_entry_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct dmi_entry_attr_show_data data = {
|
||||
.attr = attr,
|
||||
.buf = buf,
|
||||
};
|
||||
/* Find the entry according to our parent and call the
|
||||
* normalized show method hanging off of the attribute */
|
||||
return find_dmi_entry(to_entry(kobj->parent),
|
||||
dmi_entry_attr_show_helper, &data);
|
||||
}
|
||||
|
||||
static const struct sysfs_ops dmi_sysfs_specialize_attr_ops = {
|
||||
.show = dmi_entry_attr_show,
|
||||
};
|
||||
|
||||
/*************************************************
|
||||
* Specialized DMI entry support.
|
||||
*************************************************/
|
||||
|
||||
/*** Type 15 - System Event Table ***/
|
||||
|
||||
#define DMI_SEL_ACCESS_METHOD_IO8 0x00
|
||||
#define DMI_SEL_ACCESS_METHOD_IO2x8 0x01
|
||||
#define DMI_SEL_ACCESS_METHOD_IO16 0x02
|
||||
#define DMI_SEL_ACCESS_METHOD_PHYS32 0x03
|
||||
#define DMI_SEL_ACCESS_METHOD_GPNV 0x04
|
||||
|
||||
struct dmi_system_event_log {
|
||||
struct dmi_header header;
|
||||
u16 area_length;
|
||||
u16 header_start_offset;
|
||||
u16 data_start_offset;
|
||||
u8 access_method;
|
||||
u8 status;
|
||||
u32 change_token;
|
||||
union {
|
||||
struct {
|
||||
u16 index_addr;
|
||||
u16 data_addr;
|
||||
} io;
|
||||
u32 phys_addr32;
|
||||
u16 gpnv_handle;
|
||||
u32 access_method_address;
|
||||
};
|
||||
u8 header_format;
|
||||
u8 type_descriptors_supported_count;
|
||||
u8 per_log_type_descriptor_length;
|
||||
u8 supported_log_type_descriptos[0];
|
||||
} __packed;
|
||||
|
||||
#define DMI_SYSFS_SEL_FIELD(_field) \
|
||||
static ssize_t dmi_sysfs_sel_##_field(struct dmi_sysfs_entry *entry, \
|
||||
const struct dmi_header *dh, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct dmi_system_event_log sel; \
|
||||
if (sizeof(sel) > dmi_entry_length(dh)) \
|
||||
return -EIO; \
|
||||
memcpy(&sel, dh, sizeof(sel)); \
|
||||
return sprintf(buf, "%u\n", sel._field); \
|
||||
} \
|
||||
static DMI_SYSFS_MAPPED_ATTR(sel, _field)
|
||||
|
||||
DMI_SYSFS_SEL_FIELD(area_length);
|
||||
DMI_SYSFS_SEL_FIELD(header_start_offset);
|
||||
DMI_SYSFS_SEL_FIELD(data_start_offset);
|
||||
DMI_SYSFS_SEL_FIELD(access_method);
|
||||
DMI_SYSFS_SEL_FIELD(status);
|
||||
DMI_SYSFS_SEL_FIELD(change_token);
|
||||
DMI_SYSFS_SEL_FIELD(access_method_address);
|
||||
DMI_SYSFS_SEL_FIELD(header_format);
|
||||
DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count);
|
||||
DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length);
|
||||
|
||||
static struct attribute *dmi_sysfs_sel_attrs[] = {
|
||||
&dmi_sysfs_attr_sel_area_length.attr,
|
||||
&dmi_sysfs_attr_sel_header_start_offset.attr,
|
||||
&dmi_sysfs_attr_sel_data_start_offset.attr,
|
||||
&dmi_sysfs_attr_sel_access_method.attr,
|
||||
&dmi_sysfs_attr_sel_status.attr,
|
||||
&dmi_sysfs_attr_sel_change_token.attr,
|
||||
&dmi_sysfs_attr_sel_access_method_address.attr,
|
||||
&dmi_sysfs_attr_sel_header_format.attr,
|
||||
&dmi_sysfs_attr_sel_type_descriptors_supported_count.attr,
|
||||
&dmi_sysfs_attr_sel_per_log_type_descriptor_length.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
||||
static struct kobj_type dmi_system_event_log_ktype = {
|
||||
.release = dmi_entry_free,
|
||||
.sysfs_ops = &dmi_sysfs_specialize_attr_ops,
|
||||
.default_attrs = dmi_sysfs_sel_attrs,
|
||||
};
|
||||
|
||||
typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel,
|
||||
loff_t offset);
|
||||
|
||||
static DEFINE_MUTEX(io_port_lock);
|
||||
|
||||
static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel,
|
||||
loff_t offset)
|
||||
{
|
||||
u8 ret;
|
||||
|
||||
mutex_lock(&io_port_lock);
|
||||
outb((u8)offset, sel->io.index_addr);
|
||||
ret = inb(sel->io.data_addr);
|
||||
mutex_unlock(&io_port_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel,
|
||||
loff_t offset)
|
||||
{
|
||||
u8 ret;
|
||||
|
||||
mutex_lock(&io_port_lock);
|
||||
outb((u8)offset, sel->io.index_addr);
|
||||
outb((u8)(offset >> 8), sel->io.index_addr + 1);
|
||||
ret = inb(sel->io.data_addr);
|
||||
mutex_unlock(&io_port_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel,
|
||||
loff_t offset)
|
||||
{
|
||||
u8 ret;
|
||||
|
||||
mutex_lock(&io_port_lock);
|
||||
outw((u16)offset, sel->io.index_addr);
|
||||
ret = inb(sel->io.data_addr);
|
||||
mutex_unlock(&io_port_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static sel_io_reader sel_io_readers[] = {
|
||||
[DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io,
|
||||
[DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io,
|
||||
[DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io,
|
||||
};
|
||||
|
||||
static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_system_event_log *sel,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
ssize_t wrote = 0;
|
||||
|
||||
sel_io_reader io_reader = sel_io_readers[sel->access_method];
|
||||
|
||||
while (count && pos < sel->area_length) {
|
||||
count--;
|
||||
*(buf++) = io_reader(sel, pos++);
|
||||
wrote++;
|
||||
}
|
||||
|
||||
return wrote;
|
||||
}
|
||||
|
||||
static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_system_event_log *sel,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
u8 __iomem *mapped;
|
||||
ssize_t wrote = 0;
|
||||
|
||||
mapped = ioremap(sel->access_method_address, sel->area_length);
|
||||
if (!mapped)
|
||||
return -EIO;
|
||||
|
||||
while (count && pos < sel->area_length) {
|
||||
count--;
|
||||
*(buf++) = readb(mapped + pos++);
|
||||
wrote++;
|
||||
}
|
||||
|
||||
iounmap(mapped);
|
||||
return wrote;
|
||||
}
|
||||
|
||||
static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_header *dh,
|
||||
void *_state)
|
||||
{
|
||||
struct dmi_read_state *state = _state;
|
||||
struct dmi_system_event_log sel;
|
||||
|
||||
if (sizeof(sel) > dmi_entry_length(dh))
|
||||
return -EIO;
|
||||
|
||||
memcpy(&sel, dh, sizeof(sel));
|
||||
|
||||
switch (sel.access_method) {
|
||||
case DMI_SEL_ACCESS_METHOD_IO8:
|
||||
case DMI_SEL_ACCESS_METHOD_IO2x8:
|
||||
case DMI_SEL_ACCESS_METHOD_IO16:
|
||||
return dmi_sel_raw_read_io(entry, &sel, state->buf,
|
||||
state->pos, state->count);
|
||||
case DMI_SEL_ACCESS_METHOD_PHYS32:
|
||||
return dmi_sel_raw_read_phys32(entry, &sel, state->buf,
|
||||
state->pos, state->count);
|
||||
case DMI_SEL_ACCESS_METHOD_GPNV:
|
||||
pr_info("dmi-sysfs: GPNV support missing.\n");
|
||||
return -EIO;
|
||||
default:
|
||||
pr_info("dmi-sysfs: Unknown access method %02x\n",
|
||||
sel.access_method);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry = to_entry(kobj->parent);
|
||||
struct dmi_read_state state = {
|
||||
.buf = buf,
|
||||
.pos = pos,
|
||||
.count = count,
|
||||
};
|
||||
|
||||
return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state);
|
||||
}
|
||||
|
||||
static struct bin_attribute dmi_sel_raw_attr = {
|
||||
.attr = {.name = "raw_event_log", .mode = 0400},
|
||||
.read = dmi_sel_raw_read,
|
||||
};
|
||||
|
||||
static int dmi_system_event_log(struct dmi_sysfs_entry *entry)
|
||||
{
|
||||
int ret;
|
||||
|
||||
entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL);
|
||||
if (!entry->child)
|
||||
return -ENOMEM;
|
||||
ret = kobject_init_and_add(entry->child,
|
||||
&dmi_system_event_log_ktype,
|
||||
&entry->kobj,
|
||||
"system_event_log");
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr);
|
||||
if (ret)
|
||||
goto out_del;
|
||||
|
||||
return 0;
|
||||
|
||||
out_del:
|
||||
kobject_del(entry->child);
|
||||
out_free:
|
||||
kfree(entry->child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*************************************************
|
||||
* Generic DMI entry support.
|
||||
*************************************************/
|
||||
|
||||
static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", entry->dh.length);
|
||||
}
|
||||
|
||||
static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", entry->dh.handle);
|
||||
}
|
||||
|
||||
static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", entry->dh.type);
|
||||
}
|
||||
|
||||
static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", entry->instance);
|
||||
}
|
||||
|
||||
static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", entry->position);
|
||||
}
|
||||
|
||||
static DMI_SYSFS_ATTR(entry, length);
|
||||
static DMI_SYSFS_ATTR(entry, handle);
|
||||
static DMI_SYSFS_ATTR(entry, type);
|
||||
static DMI_SYSFS_ATTR(entry, instance);
|
||||
static DMI_SYSFS_ATTR(entry, position);
|
||||
|
||||
static struct attribute *dmi_sysfs_entry_attrs[] = {
|
||||
&dmi_sysfs_attr_entry_length.attr,
|
||||
&dmi_sysfs_attr_entry_handle.attr,
|
||||
&dmi_sysfs_attr_entry_type.attr,
|
||||
&dmi_sysfs_attr_entry_instance.attr,
|
||||
&dmi_sysfs_attr_entry_position.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
|
||||
const struct dmi_header *dh,
|
||||
void *_state)
|
||||
{
|
||||
struct dmi_read_state *state = _state;
|
||||
size_t entry_length;
|
||||
|
||||
entry_length = dmi_entry_length(dh);
|
||||
|
||||
return memory_read_from_buffer(state->buf, state->count,
|
||||
&state->pos, dh, entry_length);
|
||||
}
|
||||
|
||||
static ssize_t dmi_entry_raw_read(struct file *filp,
|
||||
struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry = to_entry(kobj);
|
||||
struct dmi_read_state state = {
|
||||
.buf = buf,
|
||||
.pos = pos,
|
||||
.count = count,
|
||||
};
|
||||
|
||||
return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
|
||||
}
|
||||
|
||||
static const struct bin_attribute dmi_entry_raw_attr = {
|
||||
.attr = {.name = "raw", .mode = 0400},
|
||||
.read = dmi_entry_raw_read,
|
||||
};
|
||||
|
||||
static void dmi_sysfs_entry_release(struct kobject *kobj)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry = to_entry(kobj);
|
||||
|
||||
spin_lock(&entry_list_lock);
|
||||
list_del(&entry->list);
|
||||
spin_unlock(&entry_list_lock);
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
static struct kobj_type dmi_sysfs_entry_ktype = {
|
||||
.release = dmi_sysfs_entry_release,
|
||||
.sysfs_ops = &dmi_sysfs_attr_ops,
|
||||
.default_attrs = dmi_sysfs_entry_attrs,
|
||||
};
|
||||
|
||||
static struct kobject *dmi_kobj;
|
||||
static struct kset *dmi_kset;
|
||||
|
||||
/* Global count of all instances seen. Only for setup */
|
||||
static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
|
||||
|
||||
/* Global positional count of all entries seen. Only for setup */
|
||||
static int __initdata position_count;
|
||||
|
||||
static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
|
||||
void *_ret)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry;
|
||||
int *ret = _ret;
|
||||
|
||||
/* If a previous entry saw an error, short circuit */
|
||||
if (*ret)
|
||||
return;
|
||||
|
||||
/* Allocate and register a new entry into the entries set */
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
*ret = -ENOMEM;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the key */
|
||||
memcpy(&entry->dh, dh, sizeof(*dh));
|
||||
entry->instance = instance_counts[dh->type]++;
|
||||
entry->position = position_count++;
|
||||
|
||||
entry->kobj.kset = dmi_kset;
|
||||
*ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
|
||||
"%d-%d", dh->type, entry->instance);
|
||||
|
||||
if (*ret) {
|
||||
kfree(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Thread on the global list for cleanup */
|
||||
spin_lock(&entry_list_lock);
|
||||
list_add_tail(&entry->list, &entry_list);
|
||||
spin_unlock(&entry_list_lock);
|
||||
|
||||
/* Handle specializations by type */
|
||||
switch (dh->type) {
|
||||
case DMI_ENTRY_SYSTEM_EVENT_LOG:
|
||||
*ret = dmi_system_event_log(entry);
|
||||
break;
|
||||
default:
|
||||
/* No specialization */
|
||||
break;
|
||||
}
|
||||
if (*ret)
|
||||
goto out_err;
|
||||
|
||||
/* Create the raw binary file to access the entry */
|
||||
*ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
|
||||
if (*ret)
|
||||
goto out_err;
|
||||
|
||||
return;
|
||||
out_err:
|
||||
kobject_put(entry->child);
|
||||
kobject_put(&entry->kobj);
|
||||
return;
|
||||
}
|
||||
|
||||
static void cleanup_entry_list(void)
|
||||
{
|
||||
struct dmi_sysfs_entry *entry, *next;
|
||||
|
||||
/* No locks, we are on our way out */
|
||||
list_for_each_entry_safe(entry, next, &entry_list, list) {
|
||||
kobject_put(entry->child);
|
||||
kobject_put(&entry->kobj);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init dmi_sysfs_init(void)
|
||||
{
|
||||
int error = -ENOMEM;
|
||||
int val;
|
||||
|
||||
/* Set up our directory */
|
||||
dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
|
||||
if (!dmi_kobj)
|
||||
goto err;
|
||||
|
||||
dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
|
||||
if (!dmi_kset)
|
||||
goto err;
|
||||
|
||||
val = 0;
|
||||
error = dmi_walk(dmi_sysfs_register_handle, &val);
|
||||
if (error)
|
||||
goto err;
|
||||
if (val) {
|
||||
error = val;
|
||||
goto err;
|
||||
}
|
||||
|
||||
pr_debug("dmi-sysfs: loaded.\n");
|
||||
|
||||
return 0;
|
||||
err:
|
||||
cleanup_entry_list();
|
||||
kset_unregister(dmi_kset);
|
||||
kobject_put(dmi_kobj);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* clean up everything. */
|
||||
static void __exit dmi_sysfs_exit(void)
|
||||
{
|
||||
pr_debug("dmi-sysfs: unloading.\n");
|
||||
cleanup_entry_list();
|
||||
kset_unregister(dmi_kset);
|
||||
kobject_del(dmi_kobj);
|
||||
kobject_put(dmi_kobj);
|
||||
}
|
||||
|
||||
module_init(dmi_sysfs_init);
|
||||
module_exit(dmi_sysfs_exit);
|
||||
|
||||
MODULE_AUTHOR("Mike Waychison <mikew@google.com>");
|
||||
MODULE_DESCRIPTION("DMI sysfs support");
|
||||
MODULE_LICENSE("GPL");
|
878
drivers/firmware/dmi_scan.c
Normal file
878
drivers/firmware/dmi_scan.c
Normal file
|
@ -0,0 +1,878 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/random.h>
|
||||
#include <asm/dmi.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
/*
|
||||
* DMI stands for "Desktop Management Interface". It is part
|
||||
* of and an antecedent to, SMBIOS, which stands for System
|
||||
* Management BIOS. See further: http://www.dmtf.org/standards
|
||||
*/
|
||||
static const char dmi_empty_string[] = " ";
|
||||
|
||||
static u16 __initdata dmi_ver;
|
||||
/*
|
||||
* Catch too early calls to dmi_check_system():
|
||||
*/
|
||||
static int dmi_initialized;
|
||||
|
||||
/* DMI system identification string used during boot */
|
||||
static char dmi_ids_string[128] __initdata;
|
||||
|
||||
static struct dmi_memdev_info {
|
||||
const char *device;
|
||||
const char *bank;
|
||||
u16 handle;
|
||||
} *dmi_memdev;
|
||||
static int dmi_memdev_nr;
|
||||
|
||||
static const char * __init dmi_string_nosave(const struct dmi_header *dm, u8 s)
|
||||
{
|
||||
const u8 *bp = ((u8 *) dm) + dm->length;
|
||||
|
||||
if (s) {
|
||||
s--;
|
||||
while (s > 0 && *bp) {
|
||||
bp += strlen(bp) + 1;
|
||||
s--;
|
||||
}
|
||||
|
||||
if (*bp != 0) {
|
||||
size_t len = strlen(bp)+1;
|
||||
size_t cmp_len = len > 8 ? 8 : len;
|
||||
|
||||
if (!memcmp(bp, dmi_empty_string, cmp_len))
|
||||
return dmi_empty_string;
|
||||
return bp;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char * __init dmi_string(const struct dmi_header *dm, u8 s)
|
||||
{
|
||||
const char *bp = dmi_string_nosave(dm, s);
|
||||
char *str;
|
||||
size_t len;
|
||||
|
||||
if (bp == dmi_empty_string)
|
||||
return dmi_empty_string;
|
||||
|
||||
len = strlen(bp) + 1;
|
||||
str = dmi_alloc(len);
|
||||
if (str != NULL)
|
||||
strcpy(str, bp);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to be cautious here. We have seen BIOSes with DMI pointers
|
||||
* pointing to completely the wrong place for example
|
||||
*/
|
||||
static void dmi_table(u8 *buf, int len, int num,
|
||||
void (*decode)(const struct dmi_header *, void *),
|
||||
void *private_data)
|
||||
{
|
||||
u8 *data = buf;
|
||||
int i = 0;
|
||||
|
||||
/*
|
||||
* Stop when we see all the items the table claimed to have
|
||||
* OR we run off the end of the table (also happens)
|
||||
*/
|
||||
while ((i < num) && (data - buf + sizeof(struct dmi_header)) <= len) {
|
||||
const struct dmi_header *dm = (const struct dmi_header *)data;
|
||||
|
||||
/*
|
||||
* We want to know the total length (formatted area and
|
||||
* strings) before decoding to make sure we won't run off the
|
||||
* table in dmi_decode or dmi_string
|
||||
*/
|
||||
data += dm->length;
|
||||
while ((data - buf < len - 1) && (data[0] || data[1]))
|
||||
data++;
|
||||
if (data - buf < len - 1)
|
||||
decode(dm, private_data);
|
||||
data += 2;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 dmi_base;
|
||||
static u16 dmi_len;
|
||||
static u16 dmi_num;
|
||||
|
||||
static int __init dmi_walk_early(void (*decode)(const struct dmi_header *,
|
||||
void *))
|
||||
{
|
||||
u8 *buf;
|
||||
|
||||
buf = dmi_early_remap(dmi_base, dmi_len);
|
||||
if (buf == NULL)
|
||||
return -1;
|
||||
|
||||
dmi_table(buf, dmi_len, dmi_num, decode, NULL);
|
||||
|
||||
add_device_randomness(buf, dmi_len);
|
||||
|
||||
dmi_early_unmap(buf, dmi_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init dmi_checksum(const u8 *buf, u8 len)
|
||||
{
|
||||
u8 sum = 0;
|
||||
int a;
|
||||
|
||||
for (a = 0; a < len; a++)
|
||||
sum += buf[a];
|
||||
|
||||
return sum == 0;
|
||||
}
|
||||
|
||||
static const char *dmi_ident[DMI_STRING_MAX];
|
||||
static LIST_HEAD(dmi_devices);
|
||||
int dmi_available;
|
||||
|
||||
/*
|
||||
* Save a DMI string
|
||||
*/
|
||||
static void __init dmi_save_ident(const struct dmi_header *dm, int slot,
|
||||
int string)
|
||||
{
|
||||
const char *d = (const char *) dm;
|
||||
const char *p;
|
||||
|
||||
if (dmi_ident[slot])
|
||||
return;
|
||||
|
||||
p = dmi_string(dm, d[string]);
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
dmi_ident[slot] = p;
|
||||
}
|
||||
|
||||
static void __init dmi_save_uuid(const struct dmi_header *dm, int slot,
|
||||
int index)
|
||||
{
|
||||
const u8 *d = (u8 *) dm + index;
|
||||
char *s;
|
||||
int is_ff = 1, is_00 = 1, i;
|
||||
|
||||
if (dmi_ident[slot])
|
||||
return;
|
||||
|
||||
for (i = 0; i < 16 && (is_ff || is_00); i++) {
|
||||
if (d[i] != 0x00)
|
||||
is_00 = 0;
|
||||
if (d[i] != 0xFF)
|
||||
is_ff = 0;
|
||||
}
|
||||
|
||||
if (is_ff || is_00)
|
||||
return;
|
||||
|
||||
s = dmi_alloc(16*2+4+1);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
/*
|
||||
* As of version 2.6 of the SMBIOS specification, the first 3 fields of
|
||||
* the UUID are supposed to be little-endian encoded. The specification
|
||||
* says that this is the defacto standard.
|
||||
*/
|
||||
if (dmi_ver >= 0x0206)
|
||||
sprintf(s, "%pUL", d);
|
||||
else
|
||||
sprintf(s, "%pUB", d);
|
||||
|
||||
dmi_ident[slot] = s;
|
||||
}
|
||||
|
||||
static void __init dmi_save_type(const struct dmi_header *dm, int slot,
|
||||
int index)
|
||||
{
|
||||
const u8 *d = (u8 *) dm + index;
|
||||
char *s;
|
||||
|
||||
if (dmi_ident[slot])
|
||||
return;
|
||||
|
||||
s = dmi_alloc(4);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
sprintf(s, "%u", *d & 0x7F);
|
||||
dmi_ident[slot] = s;
|
||||
}
|
||||
|
||||
static void __init dmi_save_one_device(int type, const char *name)
|
||||
{
|
||||
struct dmi_device *dev;
|
||||
|
||||
/* No duplicate device */
|
||||
if (dmi_find_device(type, name, NULL))
|
||||
return;
|
||||
|
||||
dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1);
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
dev->type = type;
|
||||
strcpy((char *)(dev + 1), name);
|
||||
dev->name = (char *)(dev + 1);
|
||||
dev->device_data = NULL;
|
||||
list_add(&dev->list, &dmi_devices);
|
||||
}
|
||||
|
||||
static void __init dmi_save_devices(const struct dmi_header *dm)
|
||||
{
|
||||
int i, count = (dm->length - sizeof(struct dmi_header)) / 2;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
const char *d = (char *)(dm + 1) + (i * 2);
|
||||
|
||||
/* Skip disabled device */
|
||||
if ((*d & 0x80) == 0)
|
||||
continue;
|
||||
|
||||
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm)
|
||||
{
|
||||
int i, count = *(u8 *)(dm + 1);
|
||||
struct dmi_device *dev;
|
||||
|
||||
for (i = 1; i <= count; i++) {
|
||||
const char *devname = dmi_string(dm, i);
|
||||
|
||||
if (devname == dmi_empty_string)
|
||||
continue;
|
||||
|
||||
dev = dmi_alloc(sizeof(*dev));
|
||||
if (!dev)
|
||||
break;
|
||||
|
||||
dev->type = DMI_DEV_TYPE_OEM_STRING;
|
||||
dev->name = devname;
|
||||
dev->device_data = NULL;
|
||||
|
||||
list_add(&dev->list, &dmi_devices);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init dmi_save_ipmi_device(const struct dmi_header *dm)
|
||||
{
|
||||
struct dmi_device *dev;
|
||||
void *data;
|
||||
|
||||
data = dmi_alloc(dm->length);
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
memcpy(data, dm, dm->length);
|
||||
|
||||
dev = dmi_alloc(sizeof(*dev));
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
dev->type = DMI_DEV_TYPE_IPMI;
|
||||
dev->name = "IPMI controller";
|
||||
dev->device_data = data;
|
||||
|
||||
list_add_tail(&dev->list, &dmi_devices);
|
||||
}
|
||||
|
||||
static void __init dmi_save_dev_onboard(int instance, int segment, int bus,
|
||||
int devfn, const char *name)
|
||||
{
|
||||
struct dmi_dev_onboard *onboard_dev;
|
||||
|
||||
onboard_dev = dmi_alloc(sizeof(*onboard_dev) + strlen(name) + 1);
|
||||
if (!onboard_dev)
|
||||
return;
|
||||
|
||||
onboard_dev->instance = instance;
|
||||
onboard_dev->segment = segment;
|
||||
onboard_dev->bus = bus;
|
||||
onboard_dev->devfn = devfn;
|
||||
|
||||
strcpy((char *)&onboard_dev[1], name);
|
||||
onboard_dev->dev.type = DMI_DEV_TYPE_DEV_ONBOARD;
|
||||
onboard_dev->dev.name = (char *)&onboard_dev[1];
|
||||
onboard_dev->dev.device_data = onboard_dev;
|
||||
|
||||
list_add(&onboard_dev->dev.list, &dmi_devices);
|
||||
}
|
||||
|
||||
static void __init dmi_save_extended_devices(const struct dmi_header *dm)
|
||||
{
|
||||
const u8 *d = (u8 *) dm + 5;
|
||||
|
||||
/* Skip disabled device */
|
||||
if ((*d & 0x80) == 0)
|
||||
return;
|
||||
|
||||
dmi_save_dev_onboard(*(d+1), *(u16 *)(d+2), *(d+4), *(d+5),
|
||||
dmi_string_nosave(dm, *(d-1)));
|
||||
dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d - 1)));
|
||||
}
|
||||
|
||||
static void __init count_mem_devices(const struct dmi_header *dm, void *v)
|
||||
{
|
||||
if (dm->type != DMI_ENTRY_MEM_DEVICE)
|
||||
return;
|
||||
dmi_memdev_nr++;
|
||||
}
|
||||
|
||||
static void __init save_mem_devices(const struct dmi_header *dm, void *v)
|
||||
{
|
||||
const char *d = (const char *)dm;
|
||||
static int nr;
|
||||
|
||||
if (dm->type != DMI_ENTRY_MEM_DEVICE)
|
||||
return;
|
||||
if (nr >= dmi_memdev_nr) {
|
||||
pr_warn(FW_BUG "Too many DIMM entries in SMBIOS table\n");
|
||||
return;
|
||||
}
|
||||
dmi_memdev[nr].handle = get_unaligned(&dm->handle);
|
||||
dmi_memdev[nr].device = dmi_string(dm, d[0x10]);
|
||||
dmi_memdev[nr].bank = dmi_string(dm, d[0x11]);
|
||||
nr++;
|
||||
}
|
||||
|
||||
void __init dmi_memdev_walk(void)
|
||||
{
|
||||
if (!dmi_available)
|
||||
return;
|
||||
|
||||
if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) {
|
||||
dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr);
|
||||
if (dmi_memdev)
|
||||
dmi_walk_early(save_mem_devices);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a DMI table entry. Right now all we care about are the BIOS
|
||||
* and machine entries. For 2.5 we should pull the smbus controller info
|
||||
* out of here.
|
||||
*/
|
||||
static void __init dmi_decode(const struct dmi_header *dm, void *dummy)
|
||||
{
|
||||
switch (dm->type) {
|
||||
case 0: /* BIOS Information */
|
||||
dmi_save_ident(dm, DMI_BIOS_VENDOR, 4);
|
||||
dmi_save_ident(dm, DMI_BIOS_VERSION, 5);
|
||||
dmi_save_ident(dm, DMI_BIOS_DATE, 8);
|
||||
break;
|
||||
case 1: /* System Information */
|
||||
dmi_save_ident(dm, DMI_SYS_VENDOR, 4);
|
||||
dmi_save_ident(dm, DMI_PRODUCT_NAME, 5);
|
||||
dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6);
|
||||
dmi_save_ident(dm, DMI_PRODUCT_SERIAL, 7);
|
||||
dmi_save_uuid(dm, DMI_PRODUCT_UUID, 8);
|
||||
break;
|
||||
case 2: /* Base Board Information */
|
||||
dmi_save_ident(dm, DMI_BOARD_VENDOR, 4);
|
||||
dmi_save_ident(dm, DMI_BOARD_NAME, 5);
|
||||
dmi_save_ident(dm, DMI_BOARD_VERSION, 6);
|
||||
dmi_save_ident(dm, DMI_BOARD_SERIAL, 7);
|
||||
dmi_save_ident(dm, DMI_BOARD_ASSET_TAG, 8);
|
||||
break;
|
||||
case 3: /* Chassis Information */
|
||||
dmi_save_ident(dm, DMI_CHASSIS_VENDOR, 4);
|
||||
dmi_save_type(dm, DMI_CHASSIS_TYPE, 5);
|
||||
dmi_save_ident(dm, DMI_CHASSIS_VERSION, 6);
|
||||
dmi_save_ident(dm, DMI_CHASSIS_SERIAL, 7);
|
||||
dmi_save_ident(dm, DMI_CHASSIS_ASSET_TAG, 8);
|
||||
break;
|
||||
case 10: /* Onboard Devices Information */
|
||||
dmi_save_devices(dm);
|
||||
break;
|
||||
case 11: /* OEM Strings */
|
||||
dmi_save_oem_strings_devices(dm);
|
||||
break;
|
||||
case 38: /* IPMI Device Information */
|
||||
dmi_save_ipmi_device(dm);
|
||||
break;
|
||||
case 41: /* Onboard Devices Extended Information */
|
||||
dmi_save_extended_devices(dm);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init print_filtered(char *buf, size_t len, const char *info)
|
||||
{
|
||||
int c = 0;
|
||||
const char *p;
|
||||
|
||||
if (!info)
|
||||
return c;
|
||||
|
||||
for (p = info; *p; p++)
|
||||
if (isprint(*p))
|
||||
c += scnprintf(buf + c, len - c, "%c", *p);
|
||||
else
|
||||
c += scnprintf(buf + c, len - c, "\\x%02x", *p & 0xff);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void __init dmi_format_ids(char *buf, size_t len)
|
||||
{
|
||||
int c = 0;
|
||||
const char *board; /* Board Name is optional */
|
||||
|
||||
c += print_filtered(buf + c, len - c,
|
||||
dmi_get_system_info(DMI_SYS_VENDOR));
|
||||
c += scnprintf(buf + c, len - c, " ");
|
||||
c += print_filtered(buf + c, len - c,
|
||||
dmi_get_system_info(DMI_PRODUCT_NAME));
|
||||
|
||||
board = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
if (board) {
|
||||
c += scnprintf(buf + c, len - c, "/");
|
||||
c += print_filtered(buf + c, len - c, board);
|
||||
}
|
||||
c += scnprintf(buf + c, len - c, ", BIOS ");
|
||||
c += print_filtered(buf + c, len - c,
|
||||
dmi_get_system_info(DMI_BIOS_VERSION));
|
||||
c += scnprintf(buf + c, len - c, " ");
|
||||
c += print_filtered(buf + c, len - c,
|
||||
dmi_get_system_info(DMI_BIOS_DATE));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for DMI/SMBIOS headers in the system firmware image. Any
|
||||
* SMBIOS header must start 16 bytes before the DMI header, so take a
|
||||
* 32 byte buffer and check for DMI at offset 16 and SMBIOS at offset
|
||||
* 0. If the DMI header is present, set dmi_ver accordingly (SMBIOS
|
||||
* takes precedence) and return 0. Otherwise return 1.
|
||||
*/
|
||||
static int __init dmi_present(const u8 *buf)
|
||||
{
|
||||
int smbios_ver;
|
||||
|
||||
if (memcmp(buf, "_SM_", 4) == 0 &&
|
||||
buf[5] < 32 && dmi_checksum(buf, buf[5])) {
|
||||
smbios_ver = (buf[6] << 8) + buf[7];
|
||||
|
||||
/* Some BIOS report weird SMBIOS version, fix that up */
|
||||
switch (smbios_ver) {
|
||||
case 0x021F:
|
||||
case 0x0221:
|
||||
pr_debug("SMBIOS version fixup(2.%d->2.%d)\n",
|
||||
smbios_ver & 0xFF, 3);
|
||||
smbios_ver = 0x0203;
|
||||
break;
|
||||
case 0x0233:
|
||||
pr_debug("SMBIOS version fixup(2.%d->2.%d)\n", 51, 6);
|
||||
smbios_ver = 0x0206;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
smbios_ver = 0;
|
||||
}
|
||||
|
||||
buf += 16;
|
||||
|
||||
if (memcmp(buf, "_DMI_", 5) == 0 && dmi_checksum(buf, 15)) {
|
||||
dmi_num = (buf[13] << 8) | buf[12];
|
||||
dmi_len = (buf[7] << 8) | buf[6];
|
||||
dmi_base = (buf[11] << 24) | (buf[10] << 16) |
|
||||
(buf[9] << 8) | buf[8];
|
||||
|
||||
if (dmi_walk_early(dmi_decode) == 0) {
|
||||
if (smbios_ver) {
|
||||
dmi_ver = smbios_ver;
|
||||
pr_info("SMBIOS %d.%d present.\n",
|
||||
dmi_ver >> 8, dmi_ver & 0xFF);
|
||||
} else {
|
||||
dmi_ver = (buf[14] & 0xF0) << 4 |
|
||||
(buf[14] & 0x0F);
|
||||
pr_info("Legacy DMI %d.%d present.\n",
|
||||
dmi_ver >> 8, dmi_ver & 0xFF);
|
||||
}
|
||||
dmi_format_ids(dmi_ids_string, sizeof(dmi_ids_string));
|
||||
printk(KERN_DEBUG "DMI: %s\n", dmi_ids_string);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void __init dmi_scan_machine(void)
|
||||
{
|
||||
char __iomem *p, *q;
|
||||
char buf[32];
|
||||
|
||||
if (efi_enabled(EFI_CONFIG_TABLES)) {
|
||||
if (efi.smbios == EFI_INVALID_TABLE_ADDR)
|
||||
goto error;
|
||||
|
||||
/* This is called as a core_initcall() because it isn't
|
||||
* needed during early boot. This also means we can
|
||||
* iounmap the space when we're done with it.
|
||||
*/
|
||||
p = dmi_early_remap(efi.smbios, 32);
|
||||
if (p == NULL)
|
||||
goto error;
|
||||
memcpy_fromio(buf, p, 32);
|
||||
dmi_early_unmap(p, 32);
|
||||
|
||||
if (!dmi_present(buf)) {
|
||||
dmi_available = 1;
|
||||
goto out;
|
||||
}
|
||||
} else if (IS_ENABLED(CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK)) {
|
||||
p = dmi_early_remap(0xF0000, 0x10000);
|
||||
if (p == NULL)
|
||||
goto error;
|
||||
|
||||
/*
|
||||
* Iterate over all possible DMI header addresses q.
|
||||
* Maintain the 32 bytes around q in buf. On the
|
||||
* first iteration, substitute zero for the
|
||||
* out-of-range bytes so there is no chance of falsely
|
||||
* detecting an SMBIOS header.
|
||||
*/
|
||||
memset(buf, 0, 16);
|
||||
for (q = p; q < p + 0x10000; q += 16) {
|
||||
memcpy_fromio(buf + 16, q, 16);
|
||||
if (!dmi_present(buf)) {
|
||||
dmi_available = 1;
|
||||
dmi_early_unmap(p, 0x10000);
|
||||
goto out;
|
||||
}
|
||||
memcpy(buf, buf + 16, 16);
|
||||
}
|
||||
dmi_early_unmap(p, 0x10000);
|
||||
}
|
||||
error:
|
||||
pr_info("DMI not present or invalid.\n");
|
||||
out:
|
||||
dmi_initialized = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmi_set_dump_stack_arch_desc - set arch description for dump_stack()
|
||||
*
|
||||
* Invoke dump_stack_set_arch_desc() with DMI system information so that
|
||||
* DMI identifiers are printed out on task dumps. Arch boot code should
|
||||
* call this function after dmi_scan_machine() if it wants to print out DMI
|
||||
* identifiers on task dumps.
|
||||
*/
|
||||
void __init dmi_set_dump_stack_arch_desc(void)
|
||||
{
|
||||
dump_stack_set_arch_desc("%s", dmi_ids_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* dmi_matches - check if dmi_system_id structure matches system DMI data
|
||||
* @dmi: pointer to the dmi_system_id structure to check
|
||||
*/
|
||||
static bool dmi_matches(const struct dmi_system_id *dmi)
|
||||
{
|
||||
int i;
|
||||
|
||||
WARN(!dmi_initialized, KERN_ERR "dmi check: not initialized yet.\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dmi->matches); i++) {
|
||||
int s = dmi->matches[i].slot;
|
||||
if (s == DMI_NONE)
|
||||
break;
|
||||
if (dmi_ident[s]) {
|
||||
if (!dmi->matches[i].exact_match &&
|
||||
strstr(dmi_ident[s], dmi->matches[i].substr))
|
||||
continue;
|
||||
else if (dmi->matches[i].exact_match &&
|
||||
!strcmp(dmi_ident[s], dmi->matches[i].substr))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* No match */
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmi_is_end_of_table - check for end-of-table marker
|
||||
* @dmi: pointer to the dmi_system_id structure to check
|
||||
*/
|
||||
static bool dmi_is_end_of_table(const struct dmi_system_id *dmi)
|
||||
{
|
||||
return dmi->matches[0].slot == DMI_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmi_check_system - check system DMI data
|
||||
* @list: array of dmi_system_id structures to match against
|
||||
* All non-null elements of the list must match
|
||||
* their slot's (field index's) data (i.e., each
|
||||
* list string must be a substring of the specified
|
||||
* DMI slot's string data) to be considered a
|
||||
* successful match.
|
||||
*
|
||||
* Walk the blacklist table running matching functions until someone
|
||||
* returns non zero or we hit the end. Callback function is called for
|
||||
* each successful match. Returns the number of matches.
|
||||
*/
|
||||
int dmi_check_system(const struct dmi_system_id *list)
|
||||
{
|
||||
int count = 0;
|
||||
const struct dmi_system_id *d;
|
||||
|
||||
for (d = list; !dmi_is_end_of_table(d); d++)
|
||||
if (dmi_matches(d)) {
|
||||
count++;
|
||||
if (d->callback && d->callback(d))
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_check_system);
|
||||
|
||||
/**
|
||||
* dmi_first_match - find dmi_system_id structure matching system DMI data
|
||||
* @list: array of dmi_system_id structures to match against
|
||||
* All non-null elements of the list must match
|
||||
* their slot's (field index's) data (i.e., each
|
||||
* list string must be a substring of the specified
|
||||
* DMI slot's string data) to be considered a
|
||||
* successful match.
|
||||
*
|
||||
* Walk the blacklist table until the first match is found. Return the
|
||||
* pointer to the matching entry or NULL if there's no match.
|
||||
*/
|
||||
const struct dmi_system_id *dmi_first_match(const struct dmi_system_id *list)
|
||||
{
|
||||
const struct dmi_system_id *d;
|
||||
|
||||
for (d = list; !dmi_is_end_of_table(d); d++)
|
||||
if (dmi_matches(d))
|
||||
return d;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_first_match);
|
||||
|
||||
/**
|
||||
* dmi_get_system_info - return DMI data value
|
||||
* @field: data index (see enum dmi_field)
|
||||
*
|
||||
* Returns one DMI data value, can be used to perform
|
||||
* complex DMI data checks.
|
||||
*/
|
||||
const char *dmi_get_system_info(int field)
|
||||
{
|
||||
return dmi_ident[field];
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_get_system_info);
|
||||
|
||||
/**
|
||||
* dmi_name_in_serial - Check if string is in the DMI product serial information
|
||||
* @str: string to check for
|
||||
*/
|
||||
int dmi_name_in_serial(const char *str)
|
||||
{
|
||||
int f = DMI_PRODUCT_SERIAL;
|
||||
if (dmi_ident[f] && strstr(dmi_ident[f], str))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmi_name_in_vendors - Check if string is in the DMI system or board vendor name
|
||||
* @str: Case sensitive Name
|
||||
*/
|
||||
int dmi_name_in_vendors(const char *str)
|
||||
{
|
||||
static int fields[] = { DMI_SYS_VENDOR, DMI_BOARD_VENDOR, DMI_NONE };
|
||||
int i;
|
||||
for (i = 0; fields[i] != DMI_NONE; i++) {
|
||||
int f = fields[i];
|
||||
if (dmi_ident[f] && strstr(dmi_ident[f], str))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_name_in_vendors);
|
||||
|
||||
/**
|
||||
* dmi_find_device - find onboard device by type/name
|
||||
* @type: device type or %DMI_DEV_TYPE_ANY to match all device types
|
||||
* @name: device name string or %NULL to match all
|
||||
* @from: previous device found in search, or %NULL for new search.
|
||||
*
|
||||
* Iterates through the list of known onboard devices. If a device is
|
||||
* found with a matching @vendor and @device, a pointer to its device
|
||||
* structure is returned. Otherwise, %NULL is returned.
|
||||
* A new search is initiated by passing %NULL as the @from argument.
|
||||
* If @from is not %NULL, searches continue from next device.
|
||||
*/
|
||||
const struct dmi_device *dmi_find_device(int type, const char *name,
|
||||
const struct dmi_device *from)
|
||||
{
|
||||
const struct list_head *head = from ? &from->list : &dmi_devices;
|
||||
struct list_head *d;
|
||||
|
||||
for (d = head->next; d != &dmi_devices; d = d->next) {
|
||||
const struct dmi_device *dev =
|
||||
list_entry(d, struct dmi_device, list);
|
||||
|
||||
if (((type == DMI_DEV_TYPE_ANY) || (dev->type == type)) &&
|
||||
((name == NULL) || (strcmp(dev->name, name) == 0)))
|
||||
return dev;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_find_device);
|
||||
|
||||
/**
|
||||
* dmi_get_date - parse a DMI date
|
||||
* @field: data index (see enum dmi_field)
|
||||
* @yearp: optional out parameter for the year
|
||||
* @monthp: optional out parameter for the month
|
||||
* @dayp: optional out parameter for the day
|
||||
*
|
||||
* The date field is assumed to be in the form resembling
|
||||
* [mm[/dd]]/yy[yy] and the result is stored in the out
|
||||
* parameters any or all of which can be omitted.
|
||||
*
|
||||
* If the field doesn't exist, all out parameters are set to zero
|
||||
* and false is returned. Otherwise, true is returned with any
|
||||
* invalid part of date set to zero.
|
||||
*
|
||||
* On return, year, month and day are guaranteed to be in the
|
||||
* range of [0,9999], [0,12] and [0,31] respectively.
|
||||
*/
|
||||
bool dmi_get_date(int field, int *yearp, int *monthp, int *dayp)
|
||||
{
|
||||
int year = 0, month = 0, day = 0;
|
||||
bool exists;
|
||||
const char *s, *y;
|
||||
char *e;
|
||||
|
||||
s = dmi_get_system_info(field);
|
||||
exists = s;
|
||||
if (!exists)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Determine year first. We assume the date string resembles
|
||||
* mm/dd/yy[yy] but the original code extracted only the year
|
||||
* from the end. Keep the behavior in the spirit of no
|
||||
* surprises.
|
||||
*/
|
||||
y = strrchr(s, '/');
|
||||
if (!y)
|
||||
goto out;
|
||||
|
||||
y++;
|
||||
year = simple_strtoul(y, &e, 10);
|
||||
if (y != e && year < 100) { /* 2-digit year */
|
||||
year += 1900;
|
||||
if (year < 1996) /* no dates < spec 1.0 */
|
||||
year += 100;
|
||||
}
|
||||
if (year > 9999) /* year should fit in %04d */
|
||||
year = 0;
|
||||
|
||||
/* parse the mm and dd */
|
||||
month = simple_strtoul(s, &e, 10);
|
||||
if (s == e || *e != '/' || !month || month > 12) {
|
||||
month = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
s = e + 1;
|
||||
day = simple_strtoul(s, &e, 10);
|
||||
if (s == y || s == e || *e != '/' || day > 31)
|
||||
day = 0;
|
||||
out:
|
||||
if (yearp)
|
||||
*yearp = year;
|
||||
if (monthp)
|
||||
*monthp = month;
|
||||
if (dayp)
|
||||
*dayp = day;
|
||||
return exists;
|
||||
}
|
||||
EXPORT_SYMBOL(dmi_get_date);
|
||||
|
||||
/**
|
||||
* dmi_walk - Walk the DMI table and get called back for every record
|
||||
* @decode: Callback function
|
||||
* @private_data: Private data to be passed to the callback function
|
||||
*
|
||||
* Returns -1 when the DMI table can't be reached, 0 on success.
|
||||
*/
|
||||
int dmi_walk(void (*decode)(const struct dmi_header *, void *),
|
||||
void *private_data)
|
||||
{
|
||||
u8 *buf;
|
||||
|
||||
if (!dmi_available)
|
||||
return -1;
|
||||
|
||||
buf = dmi_remap(dmi_base, dmi_len);
|
||||
if (buf == NULL)
|
||||
return -1;
|
||||
|
||||
dmi_table(buf, dmi_len, dmi_num, decode, private_data);
|
||||
|
||||
dmi_unmap(buf);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dmi_walk);
|
||||
|
||||
/**
|
||||
* dmi_match - compare a string to the dmi field (if exists)
|
||||
* @f: DMI field identifier
|
||||
* @str: string to compare the DMI field to
|
||||
*
|
||||
* Returns true if the requested field equals to the str (including NULL).
|
||||
*/
|
||||
bool dmi_match(enum dmi_field f, const char *str)
|
||||
{
|
||||
const char *info = dmi_get_system_info(f);
|
||||
|
||||
if (info == NULL || str == NULL)
|
||||
return info == str;
|
||||
|
||||
return !strcmp(info, str);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dmi_match);
|
||||
|
||||
void dmi_memdev_name(u16 handle, const char **bank, const char **device)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (dmi_memdev == NULL)
|
||||
return;
|
||||
|
||||
for (n = 0; n < dmi_memdev_nr; n++) {
|
||||
if (handle == dmi_memdev[n].handle) {
|
||||
*bank = dmi_memdev[n].bank;
|
||||
*device = dmi_memdev[n].device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dmi_memdev_name);
|
801
drivers/firmware/edd.c
Normal file
801
drivers/firmware/edd.c
Normal file
|
@ -0,0 +1,801 @@
|
|||
/*
|
||||
* linux/drivers/firmware/edd.c
|
||||
* Copyright (C) 2002, 2003, 2004 Dell Inc.
|
||||
* by Matt Domsch <Matt_Domsch@dell.com>
|
||||
* disk signature by Matt Domsch, Andrew Wilks, and Sandeep K. Shandilya
|
||||
* legacy CHS by Patrick J. LoPresti <patl@users.sourceforge.net>
|
||||
*
|
||||
* BIOS Enhanced Disk Drive Services (EDD)
|
||||
* conformant to T13 Committee www.t13.org
|
||||
* projects 1572D, 1484D, 1386D, 1226DT
|
||||
*
|
||||
* This code takes information provided by BIOS EDD calls
|
||||
* fn41 - Check Extensions Present and
|
||||
* fn48 - Get Device Parameters with EDD extensions
|
||||
* made in setup.S, copied to safe structures in setup.c,
|
||||
* and presents it in sysfs.
|
||||
*
|
||||
* Please see http://linux.dell.com/edd/results.html for
|
||||
* the list of BIOSs which have been reported to implement EDD.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/edd.h>
|
||||
|
||||
#define EDD_VERSION "0.16"
|
||||
#define EDD_DATE "2004-Jun-25"
|
||||
|
||||
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
|
||||
MODULE_DESCRIPTION("sysfs interface to BIOS EDD information");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EDD_VERSION);
|
||||
|
||||
#define left (PAGE_SIZE - (p - buf) - 1)
|
||||
|
||||
struct edd_device {
|
||||
unsigned int index;
|
||||
unsigned int mbr_signature;
|
||||
struct edd_info *info;
|
||||
struct kobject kobj;
|
||||
};
|
||||
|
||||
struct edd_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t(*show) (struct edd_device * edev, char *buf);
|
||||
int (*test) (struct edd_device * edev);
|
||||
};
|
||||
|
||||
/* forward declarations */
|
||||
static int edd_dev_is_type(struct edd_device *edev, const char *type);
|
||||
static struct pci_dev *edd_get_pci_dev(struct edd_device *edev);
|
||||
|
||||
static struct edd_device *edd_devices[EDD_MBR_SIG_MAX];
|
||||
|
||||
#define EDD_DEVICE_ATTR(_name,_mode,_show,_test) \
|
||||
struct edd_attribute edd_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode }, \
|
||||
.show = _show, \
|
||||
.test = _test, \
|
||||
};
|
||||
|
||||
static int
|
||||
edd_has_mbr_signature(struct edd_device *edev)
|
||||
{
|
||||
return edev->index < min_t(unsigned char, edd.mbr_signature_nr, EDD_MBR_SIG_MAX);
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_edd_info(struct edd_device *edev)
|
||||
{
|
||||
return edev->index < min_t(unsigned char, edd.edd_info_nr, EDDMAXNR);
|
||||
}
|
||||
|
||||
static inline struct edd_info *
|
||||
edd_dev_get_info(struct edd_device *edev)
|
||||
{
|
||||
return edev->info;
|
||||
}
|
||||
|
||||
static inline void
|
||||
edd_dev_set_info(struct edd_device *edev, int i)
|
||||
{
|
||||
edev->index = i;
|
||||
if (edd_has_mbr_signature(edev))
|
||||
edev->mbr_signature = edd.mbr_signature[i];
|
||||
if (edd_has_edd_info(edev))
|
||||
edev->info = &edd.edd_info[i];
|
||||
}
|
||||
|
||||
#define to_edd_attr(_attr) container_of(_attr,struct edd_attribute,attr)
|
||||
#define to_edd_device(obj) container_of(obj,struct edd_device,kobj)
|
||||
|
||||
static ssize_t
|
||||
edd_attr_show(struct kobject * kobj, struct attribute *attr, char *buf)
|
||||
{
|
||||
struct edd_device *dev = to_edd_device(kobj);
|
||||
struct edd_attribute *edd_attr = to_edd_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (edd_attr->show)
|
||||
ret = edd_attr->show(dev, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops edd_attr_ops = {
|
||||
.show = edd_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
edd_show_host_bus(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
int i;
|
||||
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (isprint(info->params.host_bus_type[i])) {
|
||||
p += scnprintf(p, left, "%c", info->params.host_bus_type[i]);
|
||||
} else {
|
||||
p += scnprintf(p, left, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (!strncmp(info->params.host_bus_type, "ISA", 3)) {
|
||||
p += scnprintf(p, left, "\tbase_address: %x\n",
|
||||
info->params.interface_path.isa.base_address);
|
||||
} else if (!strncmp(info->params.host_bus_type, "PCIX", 4) ||
|
||||
!strncmp(info->params.host_bus_type, "PCI", 3) ||
|
||||
!strncmp(info->params.host_bus_type, "XPRS", 4)) {
|
||||
p += scnprintf(p, left,
|
||||
"\t%02x:%02x.%d channel: %u\n",
|
||||
info->params.interface_path.pci.bus,
|
||||
info->params.interface_path.pci.slot,
|
||||
info->params.interface_path.pci.function,
|
||||
info->params.interface_path.pci.channel);
|
||||
} else if (!strncmp(info->params.host_bus_type, "IBND", 4) ||
|
||||
!strncmp(info->params.host_bus_type, "HTPT", 4)) {
|
||||
p += scnprintf(p, left,
|
||||
"\tTBD: %llx\n",
|
||||
info->params.interface_path.ibnd.reserved);
|
||||
|
||||
} else {
|
||||
p += scnprintf(p, left, "\tunknown: %llx\n",
|
||||
info->params.interface_path.unknown.reserved);
|
||||
}
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_interface(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
int i;
|
||||
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (isprint(info->params.interface_type[i])) {
|
||||
p += scnprintf(p, left, "%c", info->params.interface_type[i]);
|
||||
} else {
|
||||
p += scnprintf(p, left, " ");
|
||||
}
|
||||
}
|
||||
if (!strncmp(info->params.interface_type, "ATAPI", 5)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u lun: %u\n",
|
||||
info->params.device_path.atapi.device,
|
||||
info->params.device_path.atapi.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "ATA", 3)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u\n",
|
||||
info->params.device_path.ata.device);
|
||||
} else if (!strncmp(info->params.interface_type, "SCSI", 4)) {
|
||||
p += scnprintf(p, left, "\tid: %u lun: %llu\n",
|
||||
info->params.device_path.scsi.id,
|
||||
info->params.device_path.scsi.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "USB", 3)) {
|
||||
p += scnprintf(p, left, "\tserial_number: %llx\n",
|
||||
info->params.device_path.usb.serial_number);
|
||||
} else if (!strncmp(info->params.interface_type, "1394", 4)) {
|
||||
p += scnprintf(p, left, "\teui: %llx\n",
|
||||
info->params.device_path.i1394.eui);
|
||||
} else if (!strncmp(info->params.interface_type, "FIBRE", 5)) {
|
||||
p += scnprintf(p, left, "\twwid: %llx lun: %llx\n",
|
||||
info->params.device_path.fibre.wwid,
|
||||
info->params.device_path.fibre.lun);
|
||||
} else if (!strncmp(info->params.interface_type, "I2O", 3)) {
|
||||
p += scnprintf(p, left, "\tidentity_tag: %llx\n",
|
||||
info->params.device_path.i2o.identity_tag);
|
||||
} else if (!strncmp(info->params.interface_type, "RAID", 4)) {
|
||||
p += scnprintf(p, left, "\tidentity_tag: %x\n",
|
||||
info->params.device_path.raid.array_number);
|
||||
} else if (!strncmp(info->params.interface_type, "SATA", 4)) {
|
||||
p += scnprintf(p, left, "\tdevice: %u\n",
|
||||
info->params.device_path.sata.device);
|
||||
} else {
|
||||
p += scnprintf(p, left, "\tunknown: %llx %llx\n",
|
||||
info->params.device_path.unknown.reserved1,
|
||||
info->params.device_path.unknown.reserved2);
|
||||
}
|
||||
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* edd_show_raw_data() - copies raw data to buffer for userspace to parse
|
||||
* @edev: target edd_device
|
||||
* @buf: output buffer
|
||||
*
|
||||
* Returns: number of bytes written, or -EINVAL on failure
|
||||
*/
|
||||
static ssize_t
|
||||
edd_show_raw_data(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
ssize_t len = sizeof (info->params);
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE))
|
||||
len = info->params.length;
|
||||
|
||||
/* In case of buggy BIOSs */
|
||||
if (len > (sizeof(info->params)))
|
||||
len = sizeof(info->params);
|
||||
|
||||
memcpy(buf, &info->params, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_version(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "0x%02x\n", info->version);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_mbr_signature(struct edd_device *edev, char *buf)
|
||||
{
|
||||
char *p = buf;
|
||||
p += scnprintf(p, left, "0x%08x\n", edev->mbr_signature);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_extensions(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) {
|
||||
p += scnprintf(p, left, "Fixed disk access\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) {
|
||||
p += scnprintf(p, left, "Device locking and ejecting\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) {
|
||||
p += scnprintf(p, left, "Enhanced Disk Drive support\n");
|
||||
}
|
||||
if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) {
|
||||
p += scnprintf(p, left, "64-bit extensions\n");
|
||||
}
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_info_flags(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (info->params.info_flags & EDD_INFO_DMA_BOUNDARY_ERROR_TRANSPARENT)
|
||||
p += scnprintf(p, left, "DMA boundary error transparent\n");
|
||||
if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID)
|
||||
p += scnprintf(p, left, "geometry valid\n");
|
||||
if (info->params.info_flags & EDD_INFO_REMOVABLE)
|
||||
p += scnprintf(p, left, "removable\n");
|
||||
if (info->params.info_flags & EDD_INFO_WRITE_VERIFY)
|
||||
p += scnprintf(p, left, "write verify\n");
|
||||
if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION)
|
||||
p += scnprintf(p, left, "media change notification\n");
|
||||
if (info->params.info_flags & EDD_INFO_LOCKABLE)
|
||||
p += scnprintf(p, left, "lockable\n");
|
||||
if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT)
|
||||
p += scnprintf(p, left, "no media present\n");
|
||||
if (info->params.info_flags & EDD_INFO_USE_INT13_FN50)
|
||||
p += scnprintf(p, left, "use int13 fn50\n");
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_max_cylinder(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_max_cylinder);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_max_head(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_max_head);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_legacy_sectors_per_track(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += snprintf(p, left, "%u\n", info->legacy_sectors_per_track);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_cylinders(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.num_default_cylinders);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_heads(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.num_default_heads);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_default_sectors_per_track(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%u\n", info->params.sectors_per_track);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
edd_show_sectors(struct edd_device *edev, char *buf)
|
||||
{
|
||||
struct edd_info *info;
|
||||
char *p = buf;
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
p += scnprintf(p, left, "%llu\n", info->params.number_of_sectors);
|
||||
return (p - buf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Some device instances may not have all the above attributes,
|
||||
* or the attribute values may be meaningless (i.e. if
|
||||
* the device is < EDD 3.0, it won't have host_bus and interface
|
||||
* information), so don't bother making files for them. Likewise
|
||||
* if the default_{cylinders,heads,sectors_per_track} values
|
||||
* are zero, the BIOS doesn't provide sane values, don't bother
|
||||
* creating files for them either.
|
||||
*/
|
||||
|
||||
static int
|
||||
edd_has_legacy_max_cylinder(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_max_cylinder > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_legacy_max_head(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_max_head > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_legacy_sectors_per_track(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->legacy_sectors_per_track > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_cylinders(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.num_default_cylinders > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_heads(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.num_default_heads > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_default_sectors_per_track(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
return info->params.sectors_per_track > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_has_edd30(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info;
|
||||
int i;
|
||||
u8 csum = 0;
|
||||
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
if (!info)
|
||||
return 0;
|
||||
|
||||
if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* We support only T13 spec */
|
||||
if (info->params.device_path_info_length != 44)
|
||||
return 0;
|
||||
|
||||
for (i = 30; i < info->params.device_path_info_length + 30; i++)
|
||||
csum += *(((u8 *)&info->params) + i);
|
||||
|
||||
if (csum)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static EDD_DEVICE_ATTR(raw_data, 0444, edd_show_raw_data, edd_has_edd_info);
|
||||
static EDD_DEVICE_ATTR(version, 0444, edd_show_version, edd_has_edd_info);
|
||||
static EDD_DEVICE_ATTR(extensions, 0444, edd_show_extensions, edd_has_edd_info);
|
||||
static EDD_DEVICE_ATTR(info_flags, 0444, edd_show_info_flags, edd_has_edd_info);
|
||||
static EDD_DEVICE_ATTR(sectors, 0444, edd_show_sectors, edd_has_edd_info);
|
||||
static EDD_DEVICE_ATTR(legacy_max_cylinder, 0444,
|
||||
edd_show_legacy_max_cylinder,
|
||||
edd_has_legacy_max_cylinder);
|
||||
static EDD_DEVICE_ATTR(legacy_max_head, 0444, edd_show_legacy_max_head,
|
||||
edd_has_legacy_max_head);
|
||||
static EDD_DEVICE_ATTR(legacy_sectors_per_track, 0444,
|
||||
edd_show_legacy_sectors_per_track,
|
||||
edd_has_legacy_sectors_per_track);
|
||||
static EDD_DEVICE_ATTR(default_cylinders, 0444, edd_show_default_cylinders,
|
||||
edd_has_default_cylinders);
|
||||
static EDD_DEVICE_ATTR(default_heads, 0444, edd_show_default_heads,
|
||||
edd_has_default_heads);
|
||||
static EDD_DEVICE_ATTR(default_sectors_per_track, 0444,
|
||||
edd_show_default_sectors_per_track,
|
||||
edd_has_default_sectors_per_track);
|
||||
static EDD_DEVICE_ATTR(interface, 0444, edd_show_interface, edd_has_edd30);
|
||||
static EDD_DEVICE_ATTR(host_bus, 0444, edd_show_host_bus, edd_has_edd30);
|
||||
static EDD_DEVICE_ATTR(mbr_signature, 0444, edd_show_mbr_signature, edd_has_mbr_signature);
|
||||
|
||||
|
||||
/* These are default attributes that are added for every edd
|
||||
* device discovered. There are none.
|
||||
*/
|
||||
static struct attribute * def_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* These attributes are conditional and only added for some devices. */
|
||||
static struct edd_attribute * edd_attrs[] = {
|
||||
&edd_attr_raw_data,
|
||||
&edd_attr_version,
|
||||
&edd_attr_extensions,
|
||||
&edd_attr_info_flags,
|
||||
&edd_attr_sectors,
|
||||
&edd_attr_legacy_max_cylinder,
|
||||
&edd_attr_legacy_max_head,
|
||||
&edd_attr_legacy_sectors_per_track,
|
||||
&edd_attr_default_cylinders,
|
||||
&edd_attr_default_heads,
|
||||
&edd_attr_default_sectors_per_track,
|
||||
&edd_attr_interface,
|
||||
&edd_attr_host_bus,
|
||||
&edd_attr_mbr_signature,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/**
|
||||
* edd_release - free edd structure
|
||||
* @kobj: kobject of edd structure
|
||||
*
|
||||
* This is called when the refcount of the edd structure
|
||||
* reaches 0. This should happen right after we unregister,
|
||||
* but just in case, we use the release callback anyway.
|
||||
*/
|
||||
|
||||
static void edd_release(struct kobject * kobj)
|
||||
{
|
||||
struct edd_device * dev = to_edd_device(kobj);
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static struct kobj_type edd_ktype = {
|
||||
.release = edd_release,
|
||||
.sysfs_ops = &edd_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
static struct kset *edd_kset;
|
||||
|
||||
|
||||
/**
|
||||
* edd_dev_is_type() - is this EDD device a 'type' device?
|
||||
* @edev: target edd_device
|
||||
* @type: a host bus or interface identifier string per the EDD spec
|
||||
*
|
||||
* Returns 1 (TRUE) if it is a 'type' device, 0 otherwise.
|
||||
*/
|
||||
static int
|
||||
edd_dev_is_type(struct edd_device *edev, const char *type)
|
||||
{
|
||||
struct edd_info *info;
|
||||
if (!edev)
|
||||
return 0;
|
||||
info = edd_dev_get_info(edev);
|
||||
|
||||
if (type && info) {
|
||||
if (!strncmp(info->params.host_bus_type, type, strlen(type)) ||
|
||||
!strncmp(info->params.interface_type, type, strlen(type)))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* edd_get_pci_dev() - finds pci_dev that matches edev
|
||||
* @edev: edd_device
|
||||
*
|
||||
* Returns pci_dev if found, or NULL
|
||||
*/
|
||||
static struct pci_dev *
|
||||
edd_get_pci_dev(struct edd_device *edev)
|
||||
{
|
||||
struct edd_info *info = edd_dev_get_info(edev);
|
||||
|
||||
if (edd_dev_is_type(edev, "PCI") || edd_dev_is_type(edev, "XPRS")) {
|
||||
return pci_get_bus_and_slot(info->params.interface_path.pci.bus,
|
||||
PCI_DEVFN(info->params.interface_path.pci.slot,
|
||||
info->params.interface_path.pci.
|
||||
function));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
edd_create_symlink_to_pcidev(struct edd_device *edev)
|
||||
{
|
||||
|
||||
struct pci_dev *pci_dev = edd_get_pci_dev(edev);
|
||||
int ret;
|
||||
if (!pci_dev)
|
||||
return 1;
|
||||
ret = sysfs_create_link(&edev->kobj,&pci_dev->dev.kobj,"pci_dev");
|
||||
pci_dev_put(pci_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void
|
||||
edd_device_unregister(struct edd_device *edev)
|
||||
{
|
||||
kobject_put(&edev->kobj);
|
||||
}
|
||||
|
||||
static void edd_populate_dir(struct edd_device * edev)
|
||||
{
|
||||
struct edd_attribute * attr;
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; (attr = edd_attrs[i]) && !error; i++) {
|
||||
if (!attr->test ||
|
||||
(attr->test && attr->test(edev)))
|
||||
error = sysfs_create_file(&edev->kobj,&attr->attr);
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
edd_create_symlink_to_pcidev(edev);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
edd_device_register(struct edd_device *edev, int i)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!edev)
|
||||
return 1;
|
||||
edd_dev_set_info(edev, i);
|
||||
edev->kobj.kset = edd_kset;
|
||||
error = kobject_init_and_add(&edev->kobj, &edd_ktype, NULL,
|
||||
"int13_dev%02x", 0x80 + i);
|
||||
if (!error) {
|
||||
edd_populate_dir(edev);
|
||||
kobject_uevent(&edev->kobj, KOBJ_ADD);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static inline int edd_num_devices(void)
|
||||
{
|
||||
return max_t(unsigned char,
|
||||
min_t(unsigned char, EDD_MBR_SIG_MAX, edd.mbr_signature_nr),
|
||||
min_t(unsigned char, EDDMAXNR, edd.edd_info_nr));
|
||||
}
|
||||
|
||||
/**
|
||||
* edd_init() - creates sysfs tree of EDD data
|
||||
*/
|
||||
static int __init
|
||||
edd_init(void)
|
||||
{
|
||||
int i;
|
||||
int rc=0;
|
||||
struct edd_device *edev;
|
||||
|
||||
printk(KERN_INFO "BIOS EDD facility v%s %s, %d devices found\n",
|
||||
EDD_VERSION, EDD_DATE, edd_num_devices());
|
||||
|
||||
if (!edd_num_devices()) {
|
||||
printk(KERN_INFO "EDD information not available.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
edd_kset = kset_create_and_add("edd", NULL, firmware_kobj);
|
||||
if (!edd_kset)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < edd_num_devices(); i++) {
|
||||
edev = kzalloc(sizeof (*edev), GFP_KERNEL);
|
||||
if (!edev) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = edd_device_register(edev, i);
|
||||
if (rc) {
|
||||
kfree(edev);
|
||||
goto out;
|
||||
}
|
||||
edd_devices[i] = edev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
while (--i >= 0)
|
||||
edd_device_unregister(edd_devices[i]);
|
||||
kset_unregister(edd_kset);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit
|
||||
edd_exit(void)
|
||||
{
|
||||
int i;
|
||||
struct edd_device *edev;
|
||||
|
||||
for (i = 0; i < edd_num_devices(); i++) {
|
||||
if ((edev = edd_devices[i]))
|
||||
edd_device_unregister(edev);
|
||||
}
|
||||
kset_unregister(edd_kset);
|
||||
}
|
||||
|
||||
late_initcall(edd_init);
|
||||
module_exit(edd_exit);
|
66
drivers/firmware/efi/Kconfig
Normal file
66
drivers/firmware/efi/Kconfig
Normal file
|
@ -0,0 +1,66 @@
|
|||
menu "EFI (Extensible Firmware Interface) Support"
|
||||
depends on EFI
|
||||
|
||||
config EFI_VARS
|
||||
tristate "EFI Variable Support via sysfs"
|
||||
depends on EFI
|
||||
default n
|
||||
help
|
||||
If you say Y here, you are able to get EFI (Extensible Firmware
|
||||
Interface) variable information via sysfs. You may read,
|
||||
write, create, and destroy EFI variables through this interface.
|
||||
|
||||
Note that using this driver in concert with efibootmgr requires
|
||||
at least test release version 0.5.0-test3 or later, which is
|
||||
available from Matt Domsch's website located at:
|
||||
<http://linux.dell.com/efibootmgr/testing/efibootmgr-0.5.0-test3.tar.gz>
|
||||
|
||||
Subsequent efibootmgr releases may be found at:
|
||||
<http://linux.dell.com/efibootmgr>
|
||||
|
||||
config EFI_VARS_PSTORE
|
||||
tristate "Register efivars backend for pstore"
|
||||
depends on EFI_VARS && PSTORE
|
||||
default y
|
||||
help
|
||||
Say Y here to enable use efivars as a backend to pstore. This
|
||||
will allow writing console messages, crash dumps, or anything
|
||||
else supported by pstore to EFI variables.
|
||||
|
||||
config EFI_VARS_PSTORE_DEFAULT_DISABLE
|
||||
bool "Disable using efivars as a pstore backend by default"
|
||||
depends on EFI_VARS_PSTORE
|
||||
default n
|
||||
help
|
||||
Saying Y here will disable the use of efivars as a storage
|
||||
backend for pstore by default. This setting can be overridden
|
||||
using the efivars module's pstore_disable parameter.
|
||||
|
||||
config EFI_RUNTIME_MAP
|
||||
bool "Export efi runtime maps to sysfs"
|
||||
depends on X86 && EFI && KEXEC
|
||||
default y
|
||||
help
|
||||
Export efi runtime memory maps to /sys/firmware/efi/runtime-map.
|
||||
That memory map is used for example by kexec to set up efi virtual
|
||||
mapping the 2nd kernel, but can also be used for debugging purposes.
|
||||
|
||||
See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map.
|
||||
|
||||
config EFI_PARAMS_FROM_FDT
|
||||
bool
|
||||
help
|
||||
Select this config option from the architecture Kconfig if
|
||||
the EFI runtime support gets system table address, memory
|
||||
map address, and other parameters from the device tree.
|
||||
|
||||
config EFI_RUNTIME_WRAPPERS
|
||||
bool
|
||||
|
||||
config EFI_ARMSTUB
|
||||
bool
|
||||
|
||||
endmenu
|
||||
|
||||
config UEFI_CPER
|
||||
bool
|
10
drivers/firmware/efi/Makefile
Normal file
10
drivers/firmware/efi/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for linux kernel
|
||||
#
|
||||
obj-$(CONFIG_EFI) += efi.o vars.o reboot.o
|
||||
obj-$(CONFIG_EFI_VARS) += efivars.o
|
||||
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
|
||||
obj-$(CONFIG_UEFI_CPER) += cper.o
|
||||
obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o
|
||||
obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
|
||||
obj-$(CONFIG_EFI_ARM_STUB) += libstub/
|
492
drivers/firmware/efi/cper.c
Normal file
492
drivers/firmware/efi/cper.c
Normal file
|
@ -0,0 +1,492 @@
|
|||
/*
|
||||
* UEFI Common Platform Error Record (CPER) support
|
||||
*
|
||||
* Copyright (C) 2010, Intel Corp.
|
||||
* Author: Huang Ying <ying.huang@intel.com>
|
||||
*
|
||||
* CPER is the format used to describe platform hardware error by
|
||||
* various tables, such as ERST, BERT and HEST etc.
|
||||
*
|
||||
* For more information about CPER, please refer to Appendix N of UEFI
|
||||
* Specification version 2.4.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/cper.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/aer.h>
|
||||
|
||||
#define INDENT_SP " "
|
||||
|
||||
static char rcd_decode_str[CPER_REC_LEN];
|
||||
|
||||
/*
|
||||
* CPER record ID need to be unique even after reboot, because record
|
||||
* ID is used as index for ERST storage, while CPER records from
|
||||
* multiple boot may co-exist in ERST.
|
||||
*/
|
||||
u64 cper_next_record_id(void)
|
||||
{
|
||||
static atomic64_t seq;
|
||||
|
||||
if (!atomic64_read(&seq))
|
||||
atomic64_set(&seq, ((u64)get_seconds()) << 32);
|
||||
|
||||
return atomic64_inc_return(&seq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_next_record_id);
|
||||
|
||||
static const char * const severity_strs[] = {
|
||||
"recoverable",
|
||||
"fatal",
|
||||
"corrected",
|
||||
"info",
|
||||
};
|
||||
|
||||
const char *cper_severity_str(unsigned int severity)
|
||||
{
|
||||
return severity < ARRAY_SIZE(severity_strs) ?
|
||||
severity_strs[severity] : "unknown";
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_severity_str);
|
||||
|
||||
/*
|
||||
* cper_print_bits - print strings for set bits
|
||||
* @pfx: prefix for each line, including log level and prefix string
|
||||
* @bits: bit mask
|
||||
* @strs: string array, indexed by bit position
|
||||
* @strs_size: size of the string array: @strs
|
||||
*
|
||||
* For each set bit in @bits, print the corresponding string in @strs.
|
||||
* If the output length is longer than 80, multiple line will be
|
||||
* printed, with @pfx is printed at the beginning of each line.
|
||||
*/
|
||||
void cper_print_bits(const char *pfx, unsigned int bits,
|
||||
const char * const strs[], unsigned int strs_size)
|
||||
{
|
||||
int i, len = 0;
|
||||
const char *str;
|
||||
char buf[84];
|
||||
|
||||
for (i = 0; i < strs_size; i++) {
|
||||
if (!(bits & (1U << i)))
|
||||
continue;
|
||||
str = strs[i];
|
||||
if (!str)
|
||||
continue;
|
||||
if (len && len + strlen(str) + 2 > 80) {
|
||||
printk("%s\n", buf);
|
||||
len = 0;
|
||||
}
|
||||
if (!len)
|
||||
len = snprintf(buf, sizeof(buf), "%s%s", pfx, str);
|
||||
else
|
||||
len += snprintf(buf+len, sizeof(buf)-len, ", %s", str);
|
||||
}
|
||||
if (len)
|
||||
printk("%s\n", buf);
|
||||
}
|
||||
|
||||
static const char * const proc_type_strs[] = {
|
||||
"IA32/X64",
|
||||
"IA64",
|
||||
};
|
||||
|
||||
static const char * const proc_isa_strs[] = {
|
||||
"IA32",
|
||||
"IA64",
|
||||
"X64",
|
||||
};
|
||||
|
||||
static const char * const proc_error_type_strs[] = {
|
||||
"cache error",
|
||||
"TLB error",
|
||||
"bus error",
|
||||
"micro-architectural error",
|
||||
};
|
||||
|
||||
static const char * const proc_op_strs[] = {
|
||||
"unknown or generic",
|
||||
"data read",
|
||||
"data write",
|
||||
"instruction execution",
|
||||
};
|
||||
|
||||
static const char * const proc_flag_strs[] = {
|
||||
"restartable",
|
||||
"precise IP",
|
||||
"overflow",
|
||||
"corrected",
|
||||
};
|
||||
|
||||
static void cper_print_proc_generic(const char *pfx,
|
||||
const struct cper_sec_proc_generic *proc)
|
||||
{
|
||||
if (proc->validation_bits & CPER_PROC_VALID_TYPE)
|
||||
printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type,
|
||||
proc->proc_type < ARRAY_SIZE(proc_type_strs) ?
|
||||
proc_type_strs[proc->proc_type] : "unknown");
|
||||
if (proc->validation_bits & CPER_PROC_VALID_ISA)
|
||||
printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa,
|
||||
proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ?
|
||||
proc_isa_strs[proc->proc_isa] : "unknown");
|
||||
if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) {
|
||||
printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type);
|
||||
cper_print_bits(pfx, proc->proc_error_type,
|
||||
proc_error_type_strs,
|
||||
ARRAY_SIZE(proc_error_type_strs));
|
||||
}
|
||||
if (proc->validation_bits & CPER_PROC_VALID_OPERATION)
|
||||
printk("%s""operation: %d, %s\n", pfx, proc->operation,
|
||||
proc->operation < ARRAY_SIZE(proc_op_strs) ?
|
||||
proc_op_strs[proc->operation] : "unknown");
|
||||
if (proc->validation_bits & CPER_PROC_VALID_FLAGS) {
|
||||
printk("%s""flags: 0x%02x\n", pfx, proc->flags);
|
||||
cper_print_bits(pfx, proc->flags, proc_flag_strs,
|
||||
ARRAY_SIZE(proc_flag_strs));
|
||||
}
|
||||
if (proc->validation_bits & CPER_PROC_VALID_LEVEL)
|
||||
printk("%s""level: %d\n", pfx, proc->level);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_VERSION)
|
||||
printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_ID)
|
||||
printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS)
|
||||
printk("%s""target_address: 0x%016llx\n",
|
||||
pfx, proc->target_addr);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID)
|
||||
printk("%s""requestor_id: 0x%016llx\n",
|
||||
pfx, proc->requestor_id);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID)
|
||||
printk("%s""responder_id: 0x%016llx\n",
|
||||
pfx, proc->responder_id);
|
||||
if (proc->validation_bits & CPER_PROC_VALID_IP)
|
||||
printk("%s""IP: 0x%016llx\n", pfx, proc->ip);
|
||||
}
|
||||
|
||||
static const char * const mem_err_type_strs[] = {
|
||||
"unknown",
|
||||
"no error",
|
||||
"single-bit ECC",
|
||||
"multi-bit ECC",
|
||||
"single-symbol chipkill ECC",
|
||||
"multi-symbol chipkill ECC",
|
||||
"master abort",
|
||||
"target abort",
|
||||
"parity error",
|
||||
"watchdog timeout",
|
||||
"invalid address",
|
||||
"mirror Broken",
|
||||
"memory sparing",
|
||||
"scrub corrected error",
|
||||
"scrub uncorrected error",
|
||||
"physical memory map-out event",
|
||||
};
|
||||
|
||||
const char *cper_mem_err_type_str(unsigned int etype)
|
||||
{
|
||||
return etype < ARRAY_SIZE(mem_err_type_strs) ?
|
||||
mem_err_type_strs[etype] : "unknown";
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_mem_err_type_str);
|
||||
|
||||
static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
|
||||
if (!msg)
|
||||
return 0;
|
||||
|
||||
n = 0;
|
||||
len = CPER_REC_LEN - 1;
|
||||
if (mem->validation_bits & CPER_MEM_VALID_NODE)
|
||||
n += scnprintf(msg + n, len - n, "node: %d ", mem->node);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_CARD)
|
||||
n += scnprintf(msg + n, len - n, "card: %d ", mem->card);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_MODULE)
|
||||
n += scnprintf(msg + n, len - n, "module: %d ", mem->module);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
|
||||
n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BANK)
|
||||
n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_DEVICE)
|
||||
n += scnprintf(msg + n, len - n, "device: %d ", mem->device);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ROW)
|
||||
n += scnprintf(msg + n, len - n, "row: %d ", mem->row);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_COLUMN)
|
||||
n += scnprintf(msg + n, len - n, "column: %d ", mem->column);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION)
|
||||
n += scnprintf(msg + n, len - n, "bit_position: %d ",
|
||||
mem->bit_pos);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
|
||||
n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ",
|
||||
mem->requestor_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
|
||||
n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ",
|
||||
mem->responder_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID)
|
||||
scnprintf(msg + n, len - n, "target_id: 0x%016llx ",
|
||||
mem->target_id);
|
||||
|
||||
msg[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
const char *bank = NULL, *device = NULL;
|
||||
|
||||
if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE))
|
||||
return 0;
|
||||
|
||||
n = 0;
|
||||
len = CPER_REC_LEN - 1;
|
||||
dmi_memdev_name(mem->mem_dev_handle, &bank, &device);
|
||||
if (bank && device)
|
||||
n = snprintf(msg, len, "DIMM location: %s %s ", bank, device);
|
||||
else
|
||||
n = snprintf(msg, len,
|
||||
"DIMM location: not present. DMI handle: 0x%.4x ",
|
||||
mem->mem_dev_handle);
|
||||
|
||||
msg[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
void cper_mem_err_pack(const struct cper_sec_mem_err *mem,
|
||||
struct cper_mem_err_compact *cmem)
|
||||
{
|
||||
cmem->validation_bits = mem->validation_bits;
|
||||
cmem->node = mem->node;
|
||||
cmem->card = mem->card;
|
||||
cmem->module = mem->module;
|
||||
cmem->bank = mem->bank;
|
||||
cmem->device = mem->device;
|
||||
cmem->row = mem->row;
|
||||
cmem->column = mem->column;
|
||||
cmem->bit_pos = mem->bit_pos;
|
||||
cmem->requestor_id = mem->requestor_id;
|
||||
cmem->responder_id = mem->responder_id;
|
||||
cmem->target_id = mem->target_id;
|
||||
cmem->rank = mem->rank;
|
||||
cmem->mem_array_handle = mem->mem_array_handle;
|
||||
cmem->mem_dev_handle = mem->mem_dev_handle;
|
||||
}
|
||||
|
||||
const char *cper_mem_err_unpack(struct trace_seq *p,
|
||||
struct cper_mem_err_compact *cmem)
|
||||
{
|
||||
const char *ret = p->buffer + p->len;
|
||||
|
||||
if (cper_mem_err_location(cmem, rcd_decode_str))
|
||||
trace_seq_printf(p, "%s", rcd_decode_str);
|
||||
if (cper_dimm_err_location(cmem, rcd_decode_str))
|
||||
trace_seq_printf(p, "%s", rcd_decode_str);
|
||||
trace_seq_putc(p, '\0');
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem)
|
||||
{
|
||||
struct cper_mem_err_compact cmem;
|
||||
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS)
|
||||
printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_PA)
|
||||
printk("%s""physical_address: 0x%016llx\n",
|
||||
pfx, mem->physical_addr);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_PA_MASK)
|
||||
printk("%s""physical_address_mask: 0x%016llx\n",
|
||||
pfx, mem->physical_addr_mask);
|
||||
cper_mem_err_pack(mem, &cmem);
|
||||
if (cper_mem_err_location(&cmem, rcd_decode_str))
|
||||
printk("%s%s\n", pfx, rcd_decode_str);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) {
|
||||
u8 etype = mem->error_type;
|
||||
printk("%s""error_type: %d, %s\n", pfx, etype,
|
||||
cper_mem_err_type_str(etype));
|
||||
}
|
||||
if (cper_dimm_err_location(&cmem, rcd_decode_str))
|
||||
printk("%s%s\n", pfx, rcd_decode_str);
|
||||
}
|
||||
|
||||
static const char * const pcie_port_type_strs[] = {
|
||||
"PCIe end point",
|
||||
"legacy PCI end point",
|
||||
"unknown",
|
||||
"unknown",
|
||||
"root port",
|
||||
"upstream switch port",
|
||||
"downstream switch port",
|
||||
"PCIe to PCI/PCI-X bridge",
|
||||
"PCI/PCI-X to PCIe bridge",
|
||||
"root complex integrated endpoint device",
|
||||
"root complex event collector",
|
||||
};
|
||||
|
||||
static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie,
|
||||
const struct acpi_hest_generic_data *gdata)
|
||||
{
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE)
|
||||
printk("%s""port_type: %d, %s\n", pfx, pcie->port_type,
|
||||
pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ?
|
||||
pcie_port_type_strs[pcie->port_type] : "unknown");
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_VERSION)
|
||||
printk("%s""version: %d.%d\n", pfx,
|
||||
pcie->version.major, pcie->version.minor);
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS)
|
||||
printk("%s""command: 0x%04x, status: 0x%04x\n", pfx,
|
||||
pcie->command, pcie->status);
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) {
|
||||
const __u8 *p;
|
||||
printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx,
|
||||
pcie->device_id.segment, pcie->device_id.bus,
|
||||
pcie->device_id.device, pcie->device_id.function);
|
||||
printk("%s""slot: %d\n", pfx,
|
||||
pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT);
|
||||
printk("%s""secondary_bus: 0x%02x\n", pfx,
|
||||
pcie->device_id.secondary_bus);
|
||||
printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx,
|
||||
pcie->device_id.vendor_id, pcie->device_id.device_id);
|
||||
p = pcie->device_id.class_code;
|
||||
printk("%s""class_code: %02x%02x%02x\n", pfx, p[0], p[1], p[2]);
|
||||
}
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER)
|
||||
printk("%s""serial number: 0x%04x, 0x%04x\n", pfx,
|
||||
pcie->serial_number.lower, pcie->serial_number.upper);
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS)
|
||||
printk(
|
||||
"%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n",
|
||||
pfx, pcie->bridge.secondary_status, pcie->bridge.control);
|
||||
}
|
||||
|
||||
static void cper_estatus_print_section(
|
||||
const char *pfx, const struct acpi_hest_generic_data *gdata, int sec_no)
|
||||
{
|
||||
uuid_le *sec_type = (uuid_le *)gdata->section_type;
|
||||
__u16 severity;
|
||||
char newpfx[64];
|
||||
|
||||
severity = gdata->error_severity;
|
||||
printk("%s""Error %d, type: %s\n", pfx, sec_no,
|
||||
cper_severity_str(severity));
|
||||
if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
|
||||
printk("%s""fru_id: %pUl\n", pfx, (uuid_le *)gdata->fru_id);
|
||||
if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
|
||||
printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text);
|
||||
|
||||
snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP);
|
||||
if (!uuid_le_cmp(*sec_type, CPER_SEC_PROC_GENERIC)) {
|
||||
struct cper_sec_proc_generic *proc_err = (void *)(gdata + 1);
|
||||
printk("%s""section_type: general processor error\n", newpfx);
|
||||
if (gdata->error_data_length >= sizeof(*proc_err))
|
||||
cper_print_proc_generic(newpfx, proc_err);
|
||||
else
|
||||
goto err_section_too_small;
|
||||
} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PLATFORM_MEM)) {
|
||||
struct cper_sec_mem_err *mem_err = (void *)(gdata + 1);
|
||||
printk("%s""section_type: memory error\n", newpfx);
|
||||
if (gdata->error_data_length >= sizeof(*mem_err))
|
||||
cper_print_mem(newpfx, mem_err);
|
||||
else
|
||||
goto err_section_too_small;
|
||||
} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PCIE)) {
|
||||
struct cper_sec_pcie *pcie = (void *)(gdata + 1);
|
||||
printk("%s""section_type: PCIe error\n", newpfx);
|
||||
if (gdata->error_data_length >= sizeof(*pcie))
|
||||
cper_print_pcie(newpfx, pcie, gdata);
|
||||
else
|
||||
goto err_section_too_small;
|
||||
} else
|
||||
printk("%s""section type: unknown, %pUl\n", newpfx, sec_type);
|
||||
|
||||
return;
|
||||
|
||||
err_section_too_small:
|
||||
pr_err(FW_WARN "error section length is too small\n");
|
||||
}
|
||||
|
||||
void cper_estatus_print(const char *pfx,
|
||||
const struct acpi_hest_generic_status *estatus)
|
||||
{
|
||||
struct acpi_hest_generic_data *gdata;
|
||||
unsigned int data_len, gedata_len;
|
||||
int sec_no = 0;
|
||||
char newpfx[64];
|
||||
__u16 severity;
|
||||
|
||||
severity = estatus->error_severity;
|
||||
if (severity == CPER_SEV_CORRECTED)
|
||||
printk("%s%s\n", pfx,
|
||||
"It has been corrected by h/w "
|
||||
"and requires no further action");
|
||||
printk("%s""event severity: %s\n", pfx, cper_severity_str(severity));
|
||||
data_len = estatus->data_length;
|
||||
gdata = (struct acpi_hest_generic_data *)(estatus + 1);
|
||||
snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP);
|
||||
while (data_len >= sizeof(*gdata)) {
|
||||
gedata_len = gdata->error_data_length;
|
||||
cper_estatus_print_section(newpfx, gdata, sec_no);
|
||||
data_len -= gedata_len + sizeof(*gdata);
|
||||
gdata = (void *)(gdata + 1) + gedata_len;
|
||||
sec_no++;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_estatus_print);
|
||||
|
||||
int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus)
|
||||
{
|
||||
if (estatus->data_length &&
|
||||
estatus->data_length < sizeof(struct acpi_hest_generic_data))
|
||||
return -EINVAL;
|
||||
if (estatus->raw_data_length &&
|
||||
estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_estatus_check_header);
|
||||
|
||||
int cper_estatus_check(const struct acpi_hest_generic_status *estatus)
|
||||
{
|
||||
struct acpi_hest_generic_data *gdata;
|
||||
unsigned int data_len, gedata_len;
|
||||
int rc;
|
||||
|
||||
rc = cper_estatus_check_header(estatus);
|
||||
if (rc)
|
||||
return rc;
|
||||
data_len = estatus->data_length;
|
||||
gdata = (struct acpi_hest_generic_data *)(estatus + 1);
|
||||
while (data_len >= sizeof(*gdata)) {
|
||||
gedata_len = gdata->error_data_length;
|
||||
if (gedata_len > data_len - sizeof(*gdata))
|
||||
return -EINVAL;
|
||||
data_len -= gedata_len + sizeof(*gdata);
|
||||
gdata = (void *)(gdata + 1) + gedata_len;
|
||||
}
|
||||
if (data_len)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_estatus_check);
|
402
drivers/firmware/efi/efi-pstore.c
Normal file
402
drivers/firmware/efi/efi-pstore.c
Normal file
|
@ -0,0 +1,402 @@
|
|||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pstore.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
|
||||
#define DUMP_NAME_LEN 52
|
||||
|
||||
static bool efivars_pstore_disable =
|
||||
IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
|
||||
|
||||
module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
|
||||
|
||||
#define PSTORE_EFI_ATTRIBUTES \
|
||||
(EFI_VARIABLE_NON_VOLATILE | \
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
|
||||
EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
|
||||
static int efi_pstore_open(struct pstore_info *psi)
|
||||
{
|
||||
psi->data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_pstore_close(struct pstore_info *psi)
|
||||
{
|
||||
psi->data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pstore_read_data {
|
||||
u64 *id;
|
||||
enum pstore_type_id *type;
|
||||
int *count;
|
||||
struct timespec *timespec;
|
||||
bool *compressed;
|
||||
char **buf;
|
||||
};
|
||||
|
||||
static inline u64 generic_id(unsigned long timestamp,
|
||||
unsigned int part, int count)
|
||||
{
|
||||
return ((u64) timestamp * 100 + part) * 1000 + count;
|
||||
}
|
||||
|
||||
static int efi_pstore_read_func(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
struct pstore_read_data *cb_data = data;
|
||||
char name[DUMP_NAME_LEN], data_type;
|
||||
int i;
|
||||
int cnt;
|
||||
unsigned int part;
|
||||
unsigned long time, size;
|
||||
|
||||
if (efi_guidcmp(entry->var.VendorGuid, vendor))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
name[i] = entry->var.VariableName[i];
|
||||
|
||||
if (sscanf(name, "dump-type%u-%u-%d-%lu-%c",
|
||||
cb_data->type, &part, &cnt, &time, &data_type) == 5) {
|
||||
*cb_data->id = generic_id(time, part, cnt);
|
||||
*cb_data->count = cnt;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
if (data_type == 'C')
|
||||
*cb_data->compressed = true;
|
||||
else
|
||||
*cb_data->compressed = false;
|
||||
} else if (sscanf(name, "dump-type%u-%u-%d-%lu",
|
||||
cb_data->type, &part, &cnt, &time) == 4) {
|
||||
*cb_data->id = generic_id(time, part, cnt);
|
||||
*cb_data->count = cnt;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
*cb_data->compressed = false;
|
||||
} else if (sscanf(name, "dump-type%u-%u-%lu",
|
||||
cb_data->type, &part, &time) == 3) {
|
||||
/*
|
||||
* Check if an old format,
|
||||
* which doesn't support holding
|
||||
* multiple logs, remains.
|
||||
*/
|
||||
*cb_data->id = generic_id(time, part, 0);
|
||||
*cb_data->count = 0;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
*cb_data->compressed = false;
|
||||
} else
|
||||
return 0;
|
||||
|
||||
entry->var.DataSize = 1024;
|
||||
__efivar_entry_get(entry, &entry->var.Attributes,
|
||||
&entry->var.DataSize, entry->var.Data);
|
||||
size = entry->var.DataSize;
|
||||
memcpy(*cb_data->buf, entry->var.Data,
|
||||
(size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_pstore_scan_sysfs_enter
|
||||
* @entry: scanning entry
|
||||
* @next: next entry
|
||||
* @head: list head
|
||||
*/
|
||||
static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
|
||||
struct efivar_entry *next,
|
||||
struct list_head *head)
|
||||
{
|
||||
pos->scanning = true;
|
||||
if (&next->list != head)
|
||||
next->scanning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* __efi_pstore_scan_sysfs_exit
|
||||
* @entry: deleting entry
|
||||
* @turn_off_scanning: Check if a scanning flag should be turned off
|
||||
*/
|
||||
static inline void __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
|
||||
bool turn_off_scanning)
|
||||
{
|
||||
if (entry->deleting) {
|
||||
list_del(&entry->list);
|
||||
efivar_entry_iter_end();
|
||||
efivar_unregister(entry);
|
||||
efivar_entry_iter_begin();
|
||||
} else if (turn_off_scanning)
|
||||
entry->scanning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_pstore_scan_sysfs_exit
|
||||
* @pos: scanning entry
|
||||
* @next: next entry
|
||||
* @head: list head
|
||||
* @stop: a flag checking if scanning will stop
|
||||
*/
|
||||
static void efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
|
||||
struct efivar_entry *next,
|
||||
struct list_head *head, bool stop)
|
||||
{
|
||||
__efi_pstore_scan_sysfs_exit(pos, true);
|
||||
if (stop)
|
||||
__efi_pstore_scan_sysfs_exit(next, &next->list != head);
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_pstore_sysfs_entry_iter
|
||||
*
|
||||
* @data: function-specific data to pass to callback
|
||||
* @pos: entry to begin iterating from
|
||||
*
|
||||
* You MUST call efivar_enter_iter_begin() before this function, and
|
||||
* efivar_entry_iter_end() afterwards.
|
||||
*
|
||||
* It is possible to begin iteration from an arbitrary entry within
|
||||
* the list by passing @pos. @pos is updated on return to point to
|
||||
* the next entry of the last one passed to efi_pstore_read_func().
|
||||
* To begin iterating from the beginning of the list @pos must be %NULL.
|
||||
*/
|
||||
static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos)
|
||||
{
|
||||
struct efivar_entry *entry, *n;
|
||||
struct list_head *head = &efivar_sysfs_list;
|
||||
int size = 0;
|
||||
|
||||
if (!*pos) {
|
||||
list_for_each_entry_safe(entry, n, head, list) {
|
||||
efi_pstore_scan_sysfs_enter(entry, n, head);
|
||||
|
||||
size = efi_pstore_read_func(entry, data);
|
||||
efi_pstore_scan_sysfs_exit(entry, n, head, size < 0);
|
||||
if (size)
|
||||
break;
|
||||
}
|
||||
*pos = n;
|
||||
return size;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe_from((*pos), n, head, list) {
|
||||
efi_pstore_scan_sysfs_enter((*pos), n, head);
|
||||
|
||||
size = efi_pstore_read_func((*pos), data);
|
||||
efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
|
||||
if (size)
|
||||
break;
|
||||
}
|
||||
*pos = n;
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_pstore_read
|
||||
*
|
||||
* This function returns a size of NVRAM entry logged via efi_pstore_write().
|
||||
* The meaning and behavior of efi_pstore/pstore are as below.
|
||||
*
|
||||
* size > 0: Got data of an entry logged via efi_pstore_write() successfully,
|
||||
* and pstore filesystem will continue reading subsequent entries.
|
||||
* size == 0: Entry was not logged via efi_pstore_write(),
|
||||
* and efi_pstore driver will continue reading subsequent entries.
|
||||
* size < 0: Failed to get data of entry logging via efi_pstore_write(),
|
||||
* and pstore will stop reading entry.
|
||||
*/
|
||||
static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
|
||||
int *count, struct timespec *timespec,
|
||||
char **buf, bool *compressed,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
struct pstore_read_data data;
|
||||
ssize_t size;
|
||||
|
||||
data.id = id;
|
||||
data.type = type;
|
||||
data.count = count;
|
||||
data.timespec = timespec;
|
||||
data.compressed = compressed;
|
||||
data.buf = buf;
|
||||
|
||||
*data.buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
|
||||
if (!*data.buf)
|
||||
return -ENOMEM;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
size = efi_pstore_sysfs_entry_iter(&data,
|
||||
(struct efivar_entry **)&psi->data);
|
||||
efivar_entry_iter_end();
|
||||
if (size <= 0)
|
||||
kfree(*data.buf);
|
||||
return size;
|
||||
}
|
||||
|
||||
static int efi_pstore_write(enum pstore_type_id type,
|
||||
enum kmsg_dump_reason reason, u64 *id,
|
||||
unsigned int part, int count, bool compressed, size_t size,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
char name[DUMP_NAME_LEN];
|
||||
efi_char16_t efi_name[DUMP_NAME_LEN];
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
int i, ret = 0;
|
||||
|
||||
sprintf(name, "dump-type%u-%u-%d-%lu-%c", type, part, count,
|
||||
get_seconds(), compressed ? 'C' : 'D');
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name[i] = name[i];
|
||||
|
||||
efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
|
||||
!pstore_cannot_block_path(reason),
|
||||
size, psi->buf);
|
||||
|
||||
if (reason == KMSG_DUMP_OOPS)
|
||||
efivar_run_worker();
|
||||
|
||||
*id = part;
|
||||
return ret;
|
||||
};
|
||||
|
||||
struct pstore_erase_data {
|
||||
u64 id;
|
||||
enum pstore_type_id type;
|
||||
int count;
|
||||
struct timespec time;
|
||||
efi_char16_t *name;
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up an entry with the same name
|
||||
*/
|
||||
static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
struct pstore_erase_data *ed = data;
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
efi_char16_t efi_name_old[DUMP_NAME_LEN];
|
||||
efi_char16_t *efi_name = ed->name;
|
||||
unsigned long ucs2_len = ucs2_strlen(ed->name);
|
||||
char name_old[DUMP_NAME_LEN];
|
||||
int i;
|
||||
|
||||
if (efi_guidcmp(entry->var.VendorGuid, vendor))
|
||||
return 0;
|
||||
|
||||
if (ucs2_strncmp(entry->var.VariableName,
|
||||
efi_name, (size_t)ucs2_len)) {
|
||||
/*
|
||||
* Check if an old format, which doesn't support
|
||||
* holding multiple logs, remains.
|
||||
*/
|
||||
sprintf(name_old, "dump-type%u-%u-%lu", ed->type,
|
||||
(unsigned int)ed->id, ed->time.tv_sec);
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name_old[i] = name_old[i];
|
||||
|
||||
if (ucs2_strncmp(entry->var.VariableName, efi_name_old,
|
||||
ucs2_strlen(efi_name_old)))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (entry->scanning) {
|
||||
/*
|
||||
* Skip deletion because this entry will be deleted
|
||||
* after scanning is completed.
|
||||
*/
|
||||
entry->deleting = true;
|
||||
} else
|
||||
list_del(&entry->list);
|
||||
|
||||
/* found */
|
||||
__efivar_entry_delete(entry);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
|
||||
struct timespec time, struct pstore_info *psi)
|
||||
{
|
||||
struct pstore_erase_data edata;
|
||||
struct efivar_entry *entry = NULL;
|
||||
char name[DUMP_NAME_LEN];
|
||||
efi_char16_t efi_name[DUMP_NAME_LEN];
|
||||
int found, i;
|
||||
unsigned int part;
|
||||
|
||||
do_div(id, 1000);
|
||||
part = do_div(id, 100);
|
||||
sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, time.tv_sec);
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name[i] = name[i];
|
||||
|
||||
edata.id = part;
|
||||
edata.type = type;
|
||||
edata.count = count;
|
||||
edata.time = time;
|
||||
edata.name = efi_name;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry);
|
||||
|
||||
if (found && !entry->scanning) {
|
||||
efivar_entry_iter_end();
|
||||
efivar_unregister(entry);
|
||||
} else
|
||||
efivar_entry_iter_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pstore_info efi_pstore_info = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "efi",
|
||||
.flags = PSTORE_FLAGS_FRAGILE,
|
||||
.open = efi_pstore_open,
|
||||
.close = efi_pstore_close,
|
||||
.read = efi_pstore_read,
|
||||
.write = efi_pstore_write,
|
||||
.erase = efi_pstore_erase,
|
||||
};
|
||||
|
||||
static __init int efivars_pstore_init(void)
|
||||
{
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return 0;
|
||||
|
||||
if (!efivars_kobject())
|
||||
return 0;
|
||||
|
||||
if (efivars_pstore_disable)
|
||||
return 0;
|
||||
|
||||
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
|
||||
if (!efi_pstore_info.buf)
|
||||
return -ENOMEM;
|
||||
|
||||
efi_pstore_info.bufsize = 1024;
|
||||
spin_lock_init(&efi_pstore_info.buf_lock);
|
||||
|
||||
if (pstore_register(&efi_pstore_info)) {
|
||||
kfree(efi_pstore_info.buf);
|
||||
efi_pstore_info.buf = NULL;
|
||||
efi_pstore_info.bufsize = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __exit void efivars_pstore_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(efivars_pstore_init);
|
||||
module_exit(efivars_pstore_exit);
|
||||
|
||||
MODULE_DESCRIPTION("EFI variable backend for pstore");
|
||||
MODULE_LICENSE("GPL");
|
504
drivers/firmware/efi/efi.c
Normal file
504
drivers/firmware/efi/efi.c
Normal file
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* efi.c - EFI subsystem
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
* Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
|
||||
*
|
||||
* This code registers /sys/firmware/efi{,/efivars} when EFI is supported,
|
||||
* allowing the efivarfs to be mounted or the efivars module to be loaded.
|
||||
* The existance of /sys/firmware/efi may also be used by userspace to
|
||||
* determine that the system supports EFI.
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct efi __read_mostly efi = {
|
||||
.mps = EFI_INVALID_TABLE_ADDR,
|
||||
.acpi = EFI_INVALID_TABLE_ADDR,
|
||||
.acpi20 = EFI_INVALID_TABLE_ADDR,
|
||||
.smbios = EFI_INVALID_TABLE_ADDR,
|
||||
.sal_systab = EFI_INVALID_TABLE_ADDR,
|
||||
.boot_info = EFI_INVALID_TABLE_ADDR,
|
||||
.hcdp = EFI_INVALID_TABLE_ADDR,
|
||||
.uga = EFI_INVALID_TABLE_ADDR,
|
||||
.uv_systab = EFI_INVALID_TABLE_ADDR,
|
||||
.fw_vendor = EFI_INVALID_TABLE_ADDR,
|
||||
.runtime = EFI_INVALID_TABLE_ADDR,
|
||||
.config_table = EFI_INVALID_TABLE_ADDR,
|
||||
};
|
||||
EXPORT_SYMBOL(efi);
|
||||
|
||||
static bool disable_runtime;
|
||||
static int __init setup_noefi(char *arg)
|
||||
{
|
||||
disable_runtime = true;
|
||||
return 0;
|
||||
}
|
||||
early_param("noefi", setup_noefi);
|
||||
|
||||
bool efi_runtime_disabled(void)
|
||||
{
|
||||
return disable_runtime;
|
||||
}
|
||||
|
||||
static int __init parse_efi_cmdline(char *str)
|
||||
{
|
||||
if (parse_option_str(str, "noruntime"))
|
||||
disable_runtime = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_param("efi", parse_efi_cmdline);
|
||||
|
||||
static struct kobject *efi_kobj;
|
||||
static struct kobject *efivars_kobj;
|
||||
|
||||
/*
|
||||
* Let's not leave out systab information that snuck into
|
||||
* the efivars driver
|
||||
*/
|
||||
static ssize_t systab_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
char *str = buf;
|
||||
|
||||
if (!kobj || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (efi.mps != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "MPS=0x%lx\n", efi.mps);
|
||||
if (efi.acpi20 != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20);
|
||||
if (efi.acpi != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI=0x%lx\n", efi.acpi);
|
||||
if (efi.smbios != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios);
|
||||
if (efi.hcdp != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp);
|
||||
if (efi.boot_info != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info);
|
||||
if (efi.uga != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "UGA=0x%lx\n", efi.uga);
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static struct kobj_attribute efi_attr_systab =
|
||||
__ATTR(systab, 0400, systab_show, NULL);
|
||||
|
||||
#define EFI_FIELD(var) efi.var
|
||||
|
||||
#define EFI_ATTR_SHOW(name) \
|
||||
static ssize_t name##_show(struct kobject *kobj, \
|
||||
struct kobj_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \
|
||||
}
|
||||
|
||||
EFI_ATTR_SHOW(fw_vendor);
|
||||
EFI_ATTR_SHOW(runtime);
|
||||
EFI_ATTR_SHOW(config_table);
|
||||
|
||||
static struct kobj_attribute efi_attr_fw_vendor = __ATTR_RO(fw_vendor);
|
||||
static struct kobj_attribute efi_attr_runtime = __ATTR_RO(runtime);
|
||||
static struct kobj_attribute efi_attr_config_table = __ATTR_RO(config_table);
|
||||
|
||||
static struct attribute *efi_subsys_attrs[] = {
|
||||
&efi_attr_systab.attr,
|
||||
&efi_attr_fw_vendor.attr,
|
||||
&efi_attr_runtime.attr,
|
||||
&efi_attr_config_table.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t efi_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
if (attr == &efi_attr_fw_vendor.attr) {
|
||||
if (efi_enabled(EFI_PARAVIRT) ||
|
||||
efi.fw_vendor == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
} else if (attr == &efi_attr_runtime.attr) {
|
||||
if (efi.runtime == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
} else if (attr == &efi_attr_config_table.attr) {
|
||||
if (efi.config_table == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
static struct attribute_group efi_subsys_attr_group = {
|
||||
.attrs = efi_subsys_attrs,
|
||||
.is_visible = efi_attr_is_visible,
|
||||
};
|
||||
|
||||
static struct efivars generic_efivars;
|
||||
static struct efivar_operations generic_ops;
|
||||
|
||||
static int generic_ops_register(void)
|
||||
{
|
||||
generic_ops.get_variable = efi.get_variable;
|
||||
generic_ops.set_variable = efi.set_variable;
|
||||
generic_ops.get_next_variable = efi.get_next_variable;
|
||||
generic_ops.query_variable_store = efi_query_variable_store;
|
||||
|
||||
return efivars_register(&generic_efivars, &generic_ops, efi_kobj);
|
||||
}
|
||||
|
||||
static void generic_ops_unregister(void)
|
||||
{
|
||||
efivars_unregister(&generic_efivars);
|
||||
}
|
||||
|
||||
/*
|
||||
* We register the efi subsystem with the firmware subsystem and the
|
||||
* efivars subsystem with the efi subsystem, if the system was booted with
|
||||
* EFI.
|
||||
*/
|
||||
static int __init efisubsys_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!efi_enabled(EFI_BOOT))
|
||||
return 0;
|
||||
|
||||
/* We register the efi directory at /sys/firmware/efi */
|
||||
efi_kobj = kobject_create_and_add("efi", firmware_kobj);
|
||||
if (!efi_kobj) {
|
||||
pr_err("efi: Firmware registration failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
error = generic_ops_register();
|
||||
if (error)
|
||||
goto err_put;
|
||||
|
||||
error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group);
|
||||
if (error) {
|
||||
pr_err("efi: Sysfs attribute export failed with error %d.\n",
|
||||
error);
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
error = efi_runtime_map_init(efi_kobj);
|
||||
if (error)
|
||||
goto err_remove_group;
|
||||
|
||||
/* and the standard mountpoint for efivarfs */
|
||||
efivars_kobj = kobject_create_and_add("efivars", efi_kobj);
|
||||
if (!efivars_kobj) {
|
||||
pr_err("efivars: Subsystem registration failed.\n");
|
||||
error = -ENOMEM;
|
||||
goto err_remove_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_group:
|
||||
sysfs_remove_group(efi_kobj, &efi_subsys_attr_group);
|
||||
err_unregister:
|
||||
generic_ops_unregister();
|
||||
err_put:
|
||||
kobject_put(efi_kobj);
|
||||
return error;
|
||||
}
|
||||
|
||||
subsys_initcall(efisubsys_init);
|
||||
|
||||
|
||||
/*
|
||||
* We can't ioremap data in EFI boot services RAM, because we've already mapped
|
||||
* it as RAM. So, look it up in the existing EFI memory map instead. Only
|
||||
* callable after efi_enter_virtual_mode and before efi_free_boot_services.
|
||||
*/
|
||||
void __iomem *efi_lookup_mapped_addr(u64 phys_addr)
|
||||
{
|
||||
struct efi_memory_map *map;
|
||||
void *p;
|
||||
map = efi.memmap;
|
||||
if (!map)
|
||||
return NULL;
|
||||
if (WARN_ON(!map->map))
|
||||
return NULL;
|
||||
for (p = map->map; p < map->map_end; p += map->desc_size) {
|
||||
efi_memory_desc_t *md = p;
|
||||
u64 size = md->num_pages << EFI_PAGE_SHIFT;
|
||||
u64 end = md->phys_addr + size;
|
||||
if (!(md->attribute & EFI_MEMORY_RUNTIME) &&
|
||||
md->type != EFI_BOOT_SERVICES_CODE &&
|
||||
md->type != EFI_BOOT_SERVICES_DATA)
|
||||
continue;
|
||||
if (!md->virt_addr)
|
||||
continue;
|
||||
if (phys_addr >= md->phys_addr && phys_addr < end) {
|
||||
phys_addr += md->virt_addr - md->phys_addr;
|
||||
return (__force void __iomem *)(unsigned long)phys_addr;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static __initdata efi_config_table_type_t common_tables[] = {
|
||||
{ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20},
|
||||
{ACPI_TABLE_GUID, "ACPI", &efi.acpi},
|
||||
{HCDP_TABLE_GUID, "HCDP", &efi.hcdp},
|
||||
{MPS_TABLE_GUID, "MPS", &efi.mps},
|
||||
{SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab},
|
||||
{SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios},
|
||||
{UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga},
|
||||
{NULL_GUID, NULL, NULL},
|
||||
};
|
||||
|
||||
static __init int match_config_table(efi_guid_t *guid,
|
||||
unsigned long table,
|
||||
efi_config_table_type_t *table_types)
|
||||
{
|
||||
u8 str[EFI_VARIABLE_GUID_LEN + 1];
|
||||
int i;
|
||||
|
||||
if (table_types) {
|
||||
efi_guid_unparse(guid, str);
|
||||
|
||||
for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) {
|
||||
efi_guid_unparse(&table_types[i].guid, str);
|
||||
|
||||
if (!efi_guidcmp(*guid, table_types[i].guid)) {
|
||||
*(table_types[i].ptr) = table;
|
||||
pr_cont(" %s=0x%lx ",
|
||||
table_types[i].name, table);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __init efi_config_init(efi_config_table_type_t *arch_tables)
|
||||
{
|
||||
void *config_tables, *tablep;
|
||||
int i, sz;
|
||||
|
||||
if (efi_enabled(EFI_64BIT))
|
||||
sz = sizeof(efi_config_table_64_t);
|
||||
else
|
||||
sz = sizeof(efi_config_table_32_t);
|
||||
|
||||
/*
|
||||
* Let's see what config tables the firmware passed to us.
|
||||
*/
|
||||
config_tables = early_memremap(efi.systab->tables,
|
||||
efi.systab->nr_tables * sz);
|
||||
if (config_tables == NULL) {
|
||||
pr_err("Could not map Configuration table!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
tablep = config_tables;
|
||||
pr_info("");
|
||||
for (i = 0; i < efi.systab->nr_tables; i++) {
|
||||
efi_guid_t guid;
|
||||
unsigned long table;
|
||||
|
||||
if (efi_enabled(EFI_64BIT)) {
|
||||
u64 table64;
|
||||
guid = ((efi_config_table_64_t *)tablep)->guid;
|
||||
table64 = ((efi_config_table_64_t *)tablep)->table;
|
||||
table = table64;
|
||||
#ifndef CONFIG_64BIT
|
||||
if (table64 >> 32) {
|
||||
pr_cont("\n");
|
||||
pr_err("Table located above 4GB, disabling EFI.\n");
|
||||
early_memunmap(config_tables,
|
||||
efi.systab->nr_tables * sz);
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
guid = ((efi_config_table_32_t *)tablep)->guid;
|
||||
table = ((efi_config_table_32_t *)tablep)->table;
|
||||
}
|
||||
|
||||
if (!match_config_table(&guid, table, common_tables))
|
||||
match_config_table(&guid, table, arch_tables);
|
||||
|
||||
tablep += sz;
|
||||
}
|
||||
pr_cont("\n");
|
||||
early_memunmap(config_tables, efi.systab->nr_tables * sz);
|
||||
|
||||
set_bit(EFI_CONFIG_TABLES, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI_VARS_MODULE
|
||||
static int __init efi_load_efivars(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return 0;
|
||||
|
||||
pdev = platform_device_register_simple("efivars", 0, NULL, 0);
|
||||
return IS_ERR(pdev) ? PTR_ERR(pdev) : 0;
|
||||
}
|
||||
device_initcall(efi_load_efivars);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EFI_PARAMS_FROM_FDT
|
||||
|
||||
#define UEFI_PARAM(name, prop, field) \
|
||||
{ \
|
||||
{ name }, \
|
||||
{ prop }, \
|
||||
offsetof(struct efi_fdt_params, field), \
|
||||
FIELD_SIZEOF(struct efi_fdt_params, field) \
|
||||
}
|
||||
|
||||
static __initdata struct {
|
||||
const char name[32];
|
||||
const char propname[32];
|
||||
int offset;
|
||||
int size;
|
||||
} dt_params[] = {
|
||||
UEFI_PARAM("System Table", "linux,uefi-system-table", system_table),
|
||||
UEFI_PARAM("MemMap Address", "linux,uefi-mmap-start", mmap),
|
||||
UEFI_PARAM("MemMap Size", "linux,uefi-mmap-size", mmap_size),
|
||||
UEFI_PARAM("MemMap Desc. Size", "linux,uefi-mmap-desc-size", desc_size),
|
||||
UEFI_PARAM("MemMap Desc. Version", "linux,uefi-mmap-desc-ver", desc_ver)
|
||||
};
|
||||
|
||||
struct param_info {
|
||||
int verbose;
|
||||
int found;
|
||||
void *params;
|
||||
};
|
||||
|
||||
static int __init fdt_find_uefi_params(unsigned long node, const char *uname,
|
||||
int depth, void *data)
|
||||
{
|
||||
struct param_info *info = data;
|
||||
const void *prop;
|
||||
void *dest;
|
||||
u64 val;
|
||||
int i, len;
|
||||
|
||||
if (depth != 1 ||
|
||||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dt_params); i++) {
|
||||
prop = of_get_flat_dt_prop(node, dt_params[i].propname, &len);
|
||||
if (!prop)
|
||||
return 0;
|
||||
dest = info->params + dt_params[i].offset;
|
||||
info->found++;
|
||||
|
||||
val = of_read_number(prop, len / sizeof(u32));
|
||||
|
||||
if (dt_params[i].size == sizeof(u32))
|
||||
*(u32 *)dest = val;
|
||||
else
|
||||
*(u64 *)dest = val;
|
||||
|
||||
if (info->verbose)
|
||||
pr_info(" %s: 0x%0*llx\n", dt_params[i].name,
|
||||
dt_params[i].size * 2, val);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __init efi_get_fdt_params(struct efi_fdt_params *params, int verbose)
|
||||
{
|
||||
struct param_info info;
|
||||
int ret;
|
||||
|
||||
pr_info("Getting EFI parameters from FDT:\n");
|
||||
|
||||
info.verbose = verbose;
|
||||
info.found = 0;
|
||||
info.params = params;
|
||||
|
||||
ret = of_scan_flat_dt(fdt_find_uefi_params, &info);
|
||||
if (!info.found)
|
||||
pr_info("UEFI not found.\n");
|
||||
else if (!ret)
|
||||
pr_err("Can't find '%s' in device tree!\n",
|
||||
dt_params[info.found].name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_EFI_PARAMS_FROM_FDT */
|
||||
|
||||
static __initdata char memory_type_name[][20] = {
|
||||
"Reserved",
|
||||
"Loader Code",
|
||||
"Loader Data",
|
||||
"Boot Code",
|
||||
"Boot Data",
|
||||
"Runtime Code",
|
||||
"Runtime Data",
|
||||
"Conventional Memory",
|
||||
"Unusable Memory",
|
||||
"ACPI Reclaim Memory",
|
||||
"ACPI Memory NVS",
|
||||
"Memory Mapped I/O",
|
||||
"MMIO Port Space",
|
||||
"PAL Code"
|
||||
};
|
||||
|
||||
char * __init efi_md_typeattr_format(char *buf, size_t size,
|
||||
const efi_memory_desc_t *md)
|
||||
{
|
||||
char *pos;
|
||||
int type_len;
|
||||
u64 attr;
|
||||
|
||||
pos = buf;
|
||||
if (md->type >= ARRAY_SIZE(memory_type_name))
|
||||
type_len = snprintf(pos, size, "[type=%u", md->type);
|
||||
else
|
||||
type_len = snprintf(pos, size, "[%-*s",
|
||||
(int)(sizeof(memory_type_name[0]) - 1),
|
||||
memory_type_name[md->type]);
|
||||
if (type_len >= size)
|
||||
return buf;
|
||||
|
||||
pos += type_len;
|
||||
size -= type_len;
|
||||
|
||||
attr = md->attribute;
|
||||
if (attr & ~(EFI_MEMORY_UC | EFI_MEMORY_WC | EFI_MEMORY_WT |
|
||||
EFI_MEMORY_WB | EFI_MEMORY_UCE | EFI_MEMORY_WP |
|
||||
EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RUNTIME))
|
||||
snprintf(pos, size, "|attr=0x%016llx]",
|
||||
(unsigned long long)attr);
|
||||
else
|
||||
snprintf(pos, size, "|%3s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]",
|
||||
attr & EFI_MEMORY_RUNTIME ? "RUN" : "",
|
||||
attr & EFI_MEMORY_XP ? "XP" : "",
|
||||
attr & EFI_MEMORY_RP ? "RP" : "",
|
||||
attr & EFI_MEMORY_WP ? "WP" : "",
|
||||
attr & EFI_MEMORY_UCE ? "UCE" : "",
|
||||
attr & EFI_MEMORY_WB ? "WB" : "",
|
||||
attr & EFI_MEMORY_WT ? "WT" : "",
|
||||
attr & EFI_MEMORY_WC ? "WC" : "",
|
||||
attr & EFI_MEMORY_UC ? "UC" : "");
|
||||
return buf;
|
||||
}
|
755
drivers/firmware/efi/efivars.c
Normal file
755
drivers/firmware/efi/efivars.c
Normal file
|
@ -0,0 +1,755 @@
|
|||
/*
|
||||
* Originally from efivars.c,
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
*
|
||||
* This code takes all variables accessible from EFI runtime and
|
||||
* exports them via sysfs
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 17 May 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* remove check for efi_enabled in exit
|
||||
* add MODULE_VERSION
|
||||
*
|
||||
* 26 Apr 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* minor bug fixes
|
||||
*
|
||||
* 21 Apr 2004 - Matt Tolentino <matthew.e.tolentino@intel.com)
|
||||
* converted driver to export variable information via sysfs
|
||||
* and moved to drivers/firmware directory
|
||||
* bumped revision number to v0.07 to reflect conversion & move
|
||||
*
|
||||
* 10 Dec 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* fix locking per Peter Chubb's findings
|
||||
*
|
||||
* 25 Mar 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* move uuid_unparse() to include/asm-ia64/efi.h:efi_guid_unparse()
|
||||
*
|
||||
* 12 Feb 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* use list_for_each_safe when deleting vars.
|
||||
* remove ifdef CONFIG_SMP around include <linux/smp.h>
|
||||
* v0.04 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 20 April 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Moved vars from /proc/efi to /proc/efi/vars, and made
|
||||
* efi.c own the /proc/efi directory.
|
||||
* v0.03 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* At the request of Stephane, moved ownership of /proc/efi
|
||||
* to efi.c, and now efivars lives under /proc/efi/vars.
|
||||
*
|
||||
* 12 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Feedback received from Stephane Eranian incorporated.
|
||||
* efivar_write() checks copy_from_user() return value.
|
||||
* efivar_read/write() returns proper errno.
|
||||
* v0.02 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 February 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* v0.01 release to linux-ia64@linuxia64.org
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
#define EFIVARS_VERSION "0.08"
|
||||
#define EFIVARS_DATE "2004-May-17"
|
||||
|
||||
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
|
||||
MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EFIVARS_VERSION);
|
||||
MODULE_ALIAS("platform:efivars");
|
||||
|
||||
LIST_HEAD(efivar_sysfs_list);
|
||||
EXPORT_SYMBOL_GPL(efivar_sysfs_list);
|
||||
|
||||
static struct kset *efivars_kset;
|
||||
|
||||
static struct bin_attribute *efivars_new_var;
|
||||
static struct bin_attribute *efivars_del_var;
|
||||
|
||||
struct compat_efi_variable {
|
||||
efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
|
||||
efi_guid_t VendorGuid;
|
||||
__u32 DataSize;
|
||||
__u8 Data[1024];
|
||||
__u32 Status;
|
||||
__u32 Attributes;
|
||||
} __packed;
|
||||
|
||||
struct efivar_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show) (struct efivar_entry *entry, char *buf);
|
||||
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
||||
struct efivar_attribute efivar_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
|
||||
#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
|
||||
|
||||
/*
|
||||
* Prototype for sysfs creation function
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var);
|
||||
|
||||
static ssize_t
|
||||
efivar_guid_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
efi_guid_unparse(&var->VendorGuid, str);
|
||||
str += strlen(str);
|
||||
str += sprintf(str, "\n");
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_attr_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
|
||||
str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
|
||||
if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
|
||||
str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
|
||||
if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes &
|
||||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
|
||||
str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_size_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
str += sprintf(str, "0x%lx\n", var->DataSize);
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_data_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, var->Data, var->DataSize);
|
||||
return var->DataSize;
|
||||
}
|
||||
|
||||
static inline int
|
||||
sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long size, u32 attributes, u8 *data)
|
||||
{
|
||||
/*
|
||||
* If only updating the variable data, then the name
|
||||
* and guid should remain the same
|
||||
*/
|
||||
if (memcmp(name, var->VariableName, sizeof(var->VariableName)) ||
|
||||
efi_guidcmp(vendor, var->VendorGuid)) {
|
||||
printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((size <= 0) || (attributes == 0)){
|
||||
printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
||||
efivar_validate(name, data, size) == false) {
|
||||
printk(KERN_ERR "efivars: Malformed variable content\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool is_compat(void)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src)
|
||||
{
|
||||
memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN);
|
||||
memcpy(dst->Data, src->Data, sizeof(src->Data));
|
||||
|
||||
dst->VendorGuid = src->VendorGuid;
|
||||
dst->DataSize = src->DataSize;
|
||||
dst->Attributes = src->Attributes;
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow each variable to be edited via rewriting the
|
||||
* entire efi variable structure.
|
||||
*/
|
||||
static ssize_t
|
||||
efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
|
||||
{
|
||||
struct efi_variable *new_var, *var = &entry->var;
|
||||
efi_char16_t *name;
|
||||
unsigned long size;
|
||||
efi_guid_t vendor;
|
||||
u32 attributes;
|
||||
u8 *data;
|
||||
int err;
|
||||
|
||||
if (is_compat()) {
|
||||
struct compat_efi_variable *compat;
|
||||
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
attributes = compat->Attributes;
|
||||
vendor = compat->VendorGuid;
|
||||
name = compat->VariableName;
|
||||
size = compat->DataSize;
|
||||
data = compat->Data;
|
||||
|
||||
err = sanity_check(var, name, vendor, size, attributes, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
copy_out_compat(&entry->var, compat);
|
||||
} else {
|
||||
if (count != sizeof(struct efi_variable))
|
||||
return -EINVAL;
|
||||
|
||||
new_var = (struct efi_variable *)buf;
|
||||
|
||||
attributes = new_var->Attributes;
|
||||
vendor = new_var->VendorGuid;
|
||||
name = new_var->VariableName;
|
||||
size = new_var->DataSize;
|
||||
data = new_var->Data;
|
||||
|
||||
err = sanity_check(var, name, vendor, size, attributes, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(&entry->var, new_var, count);
|
||||
}
|
||||
|
||||
err = efivar_entry_set(entry, attributes, size, data, NULL);
|
||||
if (err) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_show_raw(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
struct compat_efi_variable *compat;
|
||||
size_t size;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &entry->var.Attributes,
|
||||
&entry->var.DataSize, entry->var.Data))
|
||||
return -EIO;
|
||||
|
||||
if (is_compat()) {
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
|
||||
size = sizeof(*compat);
|
||||
memcpy(compat->VariableName, var->VariableName,
|
||||
EFI_VAR_NAME_LEN);
|
||||
memcpy(compat->Data, var->Data, sizeof(compat->Data));
|
||||
|
||||
compat->VendorGuid = var->VendorGuid;
|
||||
compat->DataSize = var->DataSize;
|
||||
compat->Attributes = var->Attributes;
|
||||
} else {
|
||||
size = sizeof(*var);
|
||||
memcpy(buf, var, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic read/write functions that call the specific functions of
|
||||
* the attributes...
|
||||
*/
|
||||
static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->show) {
|
||||
ret = efivar_attr->show(var, buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->store)
|
||||
ret = efivar_attr->store(var, buf, count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops efivar_attr_ops = {
|
||||
.show = efivar_attr_show,
|
||||
.store = efivar_attr_store,
|
||||
};
|
||||
|
||||
static void efivar_release(struct kobject *kobj)
|
||||
{
|
||||
struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj);
|
||||
kfree(var);
|
||||
}
|
||||
|
||||
static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
|
||||
static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
|
||||
static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
|
||||
static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
|
||||
static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
|
||||
|
||||
static struct attribute *def_attrs[] = {
|
||||
&efivar_attr_guid.attr,
|
||||
&efivar_attr_size.attr,
|
||||
&efivar_attr_attributes.attr,
|
||||
&efivar_attr_data.attr,
|
||||
&efivar_attr_raw_var.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct kobj_type efivar_ktype = {
|
||||
.release = efivar_release,
|
||||
.sysfs_ops = &efivar_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;
|
||||
struct efi_variable *new_var = (struct efi_variable *)buf;
|
||||
struct efivar_entry *new_entry;
|
||||
bool need_compat = is_compat();
|
||||
efi_char16_t *name;
|
||||
unsigned long size;
|
||||
u32 attributes;
|
||||
u8 *data;
|
||||
int err;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (need_compat) {
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
attributes = compat->Attributes;
|
||||
name = compat->VariableName;
|
||||
size = compat->DataSize;
|
||||
data = compat->Data;
|
||||
} else {
|
||||
if (count != sizeof(*new_var))
|
||||
return -EINVAL;
|
||||
|
||||
attributes = new_var->Attributes;
|
||||
name = new_var->VariableName;
|
||||
size = new_var->DataSize;
|
||||
data = new_var->Data;
|
||||
}
|
||||
|
||||
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
||||
efivar_validate(name, data, size) == false) {
|
||||
printk(KERN_ERR "efivars: Malformed variable content\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||
if (!new_entry)
|
||||
return -ENOMEM;
|
||||
|
||||
if (need_compat)
|
||||
copy_out_compat(&new_entry->var, compat);
|
||||
else
|
||||
memcpy(&new_entry->var, new_var, sizeof(*new_var));
|
||||
|
||||
err = efivar_entry_set(new_entry, attributes, size,
|
||||
data, &efivar_sysfs_list);
|
||||
if (err) {
|
||||
if (err == -EEXIST)
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (efivar_create_sysfs_entry(new_entry)) {
|
||||
printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
|
||||
kfree(new_entry);
|
||||
}
|
||||
return count;
|
||||
|
||||
out:
|
||||
kfree(new_entry);
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct efi_variable *del_var = (struct efi_variable *)buf;
|
||||
struct compat_efi_variable *compat;
|
||||
struct efivar_entry *entry;
|
||||
efi_char16_t *name;
|
||||
efi_guid_t vendor;
|
||||
int err = 0;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (is_compat()) {
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
name = compat->VariableName;
|
||||
vendor = compat->VendorGuid;
|
||||
} else {
|
||||
if (count != sizeof(*del_var))
|
||||
return -EINVAL;
|
||||
|
||||
name = del_var->VariableName;
|
||||
vendor = del_var->VendorGuid;
|
||||
}
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
|
||||
if (!entry)
|
||||
err = -EINVAL;
|
||||
else if (__efivar_entry_delete(entry))
|
||||
err = -EIO;
|
||||
|
||||
if (err) {
|
||||
efivar_entry_iter_end();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!entry->scanning) {
|
||||
efivar_entry_iter_end();
|
||||
efivar_unregister(entry);
|
||||
} else
|
||||
efivar_entry_iter_end();
|
||||
|
||||
/* It's dead Jim.... */
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_create_sysfs_entry - create a new entry in sysfs
|
||||
* @new_var: efivar entry to create
|
||||
*
|
||||
* Returns 1 on failure, 0 on success
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var)
|
||||
{
|
||||
int i, short_name_size;
|
||||
char *short_name;
|
||||
unsigned long variable_name_size;
|
||||
efi_char16_t *variable_name;
|
||||
|
||||
variable_name = new_var->var.VariableName;
|
||||
variable_name_size = ucs2_strlen(variable_name) * sizeof(efi_char16_t);
|
||||
|
||||
/*
|
||||
* Length of the variable bytes in ASCII, plus the '-' separator,
|
||||
* plus the GUID, plus trailing NUL
|
||||
*/
|
||||
short_name_size = variable_name_size / sizeof(efi_char16_t)
|
||||
+ 1 + EFI_VARIABLE_GUID_LEN + 1;
|
||||
|
||||
short_name = kzalloc(short_name_size, GFP_KERNEL);
|
||||
|
||||
if (!short_name)
|
||||
return 1;
|
||||
|
||||
/* Convert Unicode to normal chars (assume top bits are 0),
|
||||
ala UTF-8 */
|
||||
for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
|
||||
short_name[i] = variable_name[i] & 0xFF;
|
||||
}
|
||||
/* This is ugly, but necessary to separate one vendor's
|
||||
private variables from another's. */
|
||||
|
||||
*(short_name + strlen(short_name)) = '-';
|
||||
efi_guid_unparse(&new_var->var.VendorGuid,
|
||||
short_name + strlen(short_name));
|
||||
|
||||
new_var->kobj.kset = efivars_kset;
|
||||
|
||||
i = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
|
||||
NULL, "%s", short_name);
|
||||
kfree(short_name);
|
||||
if (i)
|
||||
return 1;
|
||||
|
||||
kobject_uevent(&new_var->kobj, KOBJ_ADD);
|
||||
efivar_entry_add(new_var, &efivar_sysfs_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
create_efivars_bin_attributes(void)
|
||||
{
|
||||
struct bin_attribute *attr;
|
||||
int error;
|
||||
|
||||
/* new_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr)
|
||||
return -ENOMEM;
|
||||
|
||||
attr->attr.name = "new_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_create;
|
||||
efivars_new_var = attr;
|
||||
|
||||
/* del_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr) {
|
||||
error = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
attr->attr.name = "del_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_delete;
|
||||
efivars_del_var = attr;
|
||||
|
||||
sysfs_bin_attr_init(efivars_new_var);
|
||||
sysfs_bin_attr_init(efivars_del_var);
|
||||
|
||||
/* Register */
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create new_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create del_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_free:
|
||||
kfree(efivars_del_var);
|
||||
efivars_del_var = NULL;
|
||||
kfree(efivars_new_var);
|
||||
efivars_new_var = NULL;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long name_size, void *data)
|
||||
{
|
||||
struct efivar_entry *entry = data;
|
||||
|
||||
if (efivar_entry_find(name, vendor, &efivar_sysfs_list, false))
|
||||
return 0;
|
||||
|
||||
memcpy(entry->var.VariableName, name, name_size);
|
||||
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void efivar_update_sysfs_entries(struct work_struct *work)
|
||||
{
|
||||
struct efivar_entry *entry;
|
||||
int err;
|
||||
|
||||
/* Add new sysfs entries */
|
||||
while (1) {
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
err = efivar_init(efivar_update_sysfs_entry, entry,
|
||||
true, false, &efivar_sysfs_list);
|
||||
if (!err)
|
||||
break;
|
||||
|
||||
efivar_create_sysfs_entry(entry);
|
||||
}
|
||||
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long name_size, void *data)
|
||||
{
|
||||
struct efivar_entry *entry;
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(entry->var.VariableName, name, name_size);
|
||||
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
|
||||
|
||||
efivar_create_sysfs_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
efivar_entry_remove(entry);
|
||||
efivar_unregister(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void efivars_sysfs_exit(void)
|
||||
{
|
||||
/* Remove all entries and destroy */
|
||||
__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);
|
||||
|
||||
if (efivars_new_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (efivars_del_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
kfree(efivars_new_var);
|
||||
kfree(efivars_del_var);
|
||||
kset_unregister(efivars_kset);
|
||||
}
|
||||
|
||||
int efivars_sysfs_init(void)
|
||||
{
|
||||
struct kobject *parent_kobj = efivars_kobject();
|
||||
int error = 0;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return -ENODEV;
|
||||
|
||||
/* No efivars has been registered yet */
|
||||
if (!parent_kobj)
|
||||
return 0;
|
||||
|
||||
printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
|
||||
EFIVARS_DATE);
|
||||
|
||||
efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
|
||||
if (!efivars_kset) {
|
||||
printk(KERN_ERR "efivars: Subsystem registration failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
efivar_init(efivars_sysfs_callback, NULL, false,
|
||||
true, &efivar_sysfs_list);
|
||||
|
||||
error = create_efivars_bin_attributes();
|
||||
if (error) {
|
||||
efivars_sysfs_exit();
|
||||
return error;
|
||||
}
|
||||
|
||||
INIT_WORK(&efivar_work, efivar_update_sysfs_entries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivars_sysfs_init);
|
||||
|
||||
module_init(efivars_sysfs_init);
|
||||
module_exit(efivars_sysfs_exit);
|
26
drivers/firmware/efi/libstub/Makefile
Normal file
26
drivers/firmware/efi/libstub/Makefile
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# The stub may be linked into the kernel proper or into a separate boot binary,
|
||||
# but in either case, it executes before the kernel does (with MMU disabled) so
|
||||
# things like ftrace and stack-protector are likely to cause trouble if left
|
||||
# enabled, even if doing so doesn't break the build.
|
||||
#
|
||||
cflags-$(CONFIG_X86_32) := -march=i386
|
||||
cflags-$(CONFIG_X86_64) := -mcmodel=small
|
||||
cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2 \
|
||||
-fPIC -fno-strict-aliasing -mno-red-zone \
|
||||
-mno-mmx -mno-sse -DDISABLE_BRANCH_PROFILING
|
||||
|
||||
cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS))
|
||||
cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \
|
||||
-fno-builtin -fpic -mno-single-pic-base
|
||||
|
||||
KBUILD_CFLAGS := $(cflags-y) \
|
||||
$(call cc-option,-ffreestanding) \
|
||||
$(call cc-option,-fno-stack-protector)
|
||||
|
||||
GCOV_PROFILE := n
|
||||
|
||||
lib-y := efi-stub-helper.o
|
||||
lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o
|
||||
|
||||
CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/
|
288
drivers/firmware/efi/libstub/arm-stub.c
Normal file
288
drivers/firmware/efi/libstub/arm-stub.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* EFI stub implementation that is shared by arm and arm64 architectures.
|
||||
* This should be #included by the EFI stub implementation files.
|
||||
*
|
||||
* Copyright (C) 2013,2014 Linaro Limited
|
||||
* Roy Franz <roy.franz@linaro.org
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
* Mark Salter <msalter@redhat.com>
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available under the
|
||||
* terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "efistub.h"
|
||||
|
||||
static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg)
|
||||
{
|
||||
static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID;
|
||||
static efi_char16_t const var_name[] __initconst = {
|
||||
'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 };
|
||||
|
||||
efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable;
|
||||
unsigned long size = sizeof(u8);
|
||||
efi_status_t status;
|
||||
u8 val;
|
||||
|
||||
status = f_getvar((efi_char16_t *)var_name, (efi_guid_t *)&var_guid,
|
||||
NULL, &size, &val);
|
||||
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
return val;
|
||||
case EFI_NOT_FOUND:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg,
|
||||
void *__image, void **__fh)
|
||||
{
|
||||
efi_file_io_interface_t *io;
|
||||
efi_loaded_image_t *image = __image;
|
||||
efi_file_handle_t *fh;
|
||||
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
|
||||
efi_status_t status;
|
||||
void *handle = (void *)(unsigned long)image->device_handle;
|
||||
|
||||
status = sys_table_arg->boottime->handle_protocol(handle,
|
||||
&fs_proto, (void **)&io);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to handle fs_proto\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
status = io->open_volume(io, &fh);
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_printk(sys_table_arg, "Failed to open volume\n");
|
||||
|
||||
*__fh = fh;
|
||||
return status;
|
||||
}
|
||||
|
||||
efi_status_t efi_file_close(void *handle)
|
||||
{
|
||||
efi_file_handle_t *fh = handle;
|
||||
|
||||
return fh->close(handle);
|
||||
}
|
||||
|
||||
efi_status_t
|
||||
efi_file_read(void *handle, unsigned long *size, void *addr)
|
||||
{
|
||||
efi_file_handle_t *fh = handle;
|
||||
|
||||
return fh->read(handle, size, addr);
|
||||
}
|
||||
|
||||
|
||||
efi_status_t
|
||||
efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
|
||||
efi_char16_t *filename_16, void **handle, u64 *file_sz)
|
||||
{
|
||||
efi_file_handle_t *h, *fh = __fh;
|
||||
efi_file_info_t *info;
|
||||
efi_status_t status;
|
||||
efi_guid_t info_guid = EFI_FILE_INFO_ID;
|
||||
unsigned long info_sz;
|
||||
|
||||
status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to open file: ");
|
||||
efi_char16_printk(sys_table_arg, filename_16);
|
||||
efi_printk(sys_table_arg, "\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
*handle = h;
|
||||
|
||||
info_sz = 0;
|
||||
status = h->get_info(h, &info_guid, &info_sz, NULL);
|
||||
if (status != EFI_BUFFER_TOO_SMALL) {
|
||||
efi_printk(sys_table_arg, "Failed to get file info size\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
grow:
|
||||
status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA,
|
||||
info_sz, (void **)&info);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to alloc mem for file info\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
status = h->get_info(h, &info_guid, &info_sz,
|
||||
info);
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
sys_table_arg->boottime->free_pool(info);
|
||||
goto grow;
|
||||
}
|
||||
|
||||
*file_sz = info->file_size;
|
||||
sys_table_arg->boottime->free_pool(info);
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_printk(sys_table_arg, "Failed to get initrd info\n");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void efi_char16_printk(efi_system_table_t *sys_table_arg,
|
||||
efi_char16_t *str)
|
||||
{
|
||||
struct efi_simple_text_output_protocol *out;
|
||||
|
||||
out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out;
|
||||
out->output_string(out, str);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function handles the architcture specific differences between arm and
|
||||
* arm64 regarding where the kernel image must be loaded and any memory that
|
||||
* must be reserved. On failure it is required to free all
|
||||
* all allocations it has made.
|
||||
*/
|
||||
efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
|
||||
unsigned long *image_addr,
|
||||
unsigned long *image_size,
|
||||
unsigned long *reserve_addr,
|
||||
unsigned long *reserve_size,
|
||||
unsigned long dram_base,
|
||||
efi_loaded_image_t *image);
|
||||
/*
|
||||
* EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint
|
||||
* that is described in the PE/COFF header. Most of the code is the same
|
||||
* for both archictectures, with the arch-specific code provided in the
|
||||
* handle_kernel_image() function.
|
||||
*/
|
||||
unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table,
|
||||
unsigned long *image_addr)
|
||||
{
|
||||
efi_loaded_image_t *image;
|
||||
efi_status_t status;
|
||||
unsigned long image_size = 0;
|
||||
unsigned long dram_base;
|
||||
/* addr/point and size pairs for memory management*/
|
||||
unsigned long initrd_addr;
|
||||
u64 initrd_size = 0;
|
||||
unsigned long fdt_addr = 0; /* Original DTB */
|
||||
u64 fdt_size = 0; /* We don't get size from configuration table */
|
||||
char *cmdline_ptr = NULL;
|
||||
int cmdline_size = 0;
|
||||
unsigned long new_fdt_addr;
|
||||
efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
|
||||
unsigned long reserve_addr = 0;
|
||||
unsigned long reserve_size = 0;
|
||||
|
||||
/* Check if we were booted by the EFI firmware */
|
||||
if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
|
||||
goto fail;
|
||||
|
||||
pr_efi(sys_table, "Booting Linux Kernel...\n");
|
||||
|
||||
/*
|
||||
* Get a handle to the loaded image protocol. This is used to get
|
||||
* information about the running image, such as size and the command
|
||||
* line.
|
||||
*/
|
||||
status = sys_table->boottime->handle_protocol(handle,
|
||||
&loaded_image_proto, (void *)&image);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Failed to get loaded image protocol\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dram_base = get_dram_base(sys_table);
|
||||
if (dram_base == EFI_ERROR) {
|
||||
pr_efi_err(sys_table, "Failed to find DRAM base\n");
|
||||
goto fail;
|
||||
}
|
||||
status = handle_kernel_image(sys_table, image_addr, &image_size,
|
||||
&reserve_addr,
|
||||
&reserve_size,
|
||||
dram_base, image);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Failed to relocate kernel\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the command line from EFI, using the LOADED_IMAGE
|
||||
* protocol. We are going to copy the command line into the
|
||||
* device tree, so this can be allocated anywhere.
|
||||
*/
|
||||
cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size);
|
||||
if (!cmdline_ptr) {
|
||||
pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n");
|
||||
goto fail_free_image;
|
||||
}
|
||||
|
||||
status = efi_parse_options(cmdline_ptr);
|
||||
if (status != EFI_SUCCESS)
|
||||
pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n");
|
||||
|
||||
/*
|
||||
* Unauthenticated device tree data is a security hazard, so
|
||||
* ignore 'dtb=' unless UEFI Secure Boot is disabled.
|
||||
*/
|
||||
if (efi_secureboot_enabled(sys_table)) {
|
||||
pr_efi(sys_table, "UEFI Secure Boot is enabled.\n");
|
||||
} else {
|
||||
status = handle_cmdline_files(sys_table, image, cmdline_ptr,
|
||||
"dtb=",
|
||||
~0UL, (unsigned long *)&fdt_addr,
|
||||
(unsigned long *)&fdt_size);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Failed to load device tree!\n");
|
||||
goto fail_free_cmdline;
|
||||
}
|
||||
}
|
||||
if (!fdt_addr)
|
||||
/* Look for a device tree configuration table entry. */
|
||||
fdt_addr = (uintptr_t)get_fdt(sys_table);
|
||||
|
||||
status = handle_cmdline_files(sys_table, image, cmdline_ptr,
|
||||
"initrd=", dram_base + SZ_512M,
|
||||
(unsigned long *)&initrd_addr,
|
||||
(unsigned long *)&initrd_size);
|
||||
if (status != EFI_SUCCESS)
|
||||
pr_efi_err(sys_table, "Failed initrd from command line!\n");
|
||||
|
||||
new_fdt_addr = fdt_addr;
|
||||
status = allocate_new_fdt_and_exit_boot(sys_table, handle,
|
||||
&new_fdt_addr, dram_base + MAX_FDT_OFFSET,
|
||||
initrd_addr, initrd_size, cmdline_ptr,
|
||||
fdt_addr, fdt_size);
|
||||
|
||||
/*
|
||||
* If all went well, we need to return the FDT address to the
|
||||
* calling function so it can be passed to kernel as part of
|
||||
* the kernel boot protocol.
|
||||
*/
|
||||
if (status == EFI_SUCCESS)
|
||||
return new_fdt_addr;
|
||||
|
||||
pr_efi_err(sys_table, "Failed to update FDT and exit boot services\n");
|
||||
|
||||
efi_free(sys_table, initrd_size, initrd_addr);
|
||||
efi_free(sys_table, fdt_size, fdt_addr);
|
||||
|
||||
fail_free_cmdline:
|
||||
efi_free(sys_table, cmdline_size, (unsigned long)cmdline_ptr);
|
||||
|
||||
fail_free_image:
|
||||
efi_free(sys_table, image_size, *image_addr);
|
||||
efi_free(sys_table, reserve_size, reserve_addr);
|
||||
fail:
|
||||
return EFI_ERROR;
|
||||
}
|
690
drivers/firmware/efi/libstub/efi-stub-helper.c
Normal file
690
drivers/firmware/efi/libstub/efi-stub-helper.c
Normal file
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
* Helper functions used by the EFI stub on multiple
|
||||
* architectures. This should be #included by the EFI stub
|
||||
* implementation files.
|
||||
*
|
||||
* Copyright 2011 Intel Corporation; author Matt Fleming
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "efistub.h"
|
||||
|
||||
/*
|
||||
* Some firmware implementations have problems reading files in one go.
|
||||
* A read chunk size of 1MB seems to work for most platforms.
|
||||
*
|
||||
* Unfortunately, reading files in chunks triggers *other* bugs on some
|
||||
* platforms, so we provide a way to disable this workaround, which can
|
||||
* be done by passing "efi=nochunk" on the EFI boot stub command line.
|
||||
*
|
||||
* If you experience issues with initrd images being corrupt it's worth
|
||||
* trying efi=nochunk, but chunking is enabled by default because there
|
||||
* are far more machines that require the workaround than those that
|
||||
* break with it enabled.
|
||||
*/
|
||||
#define EFI_READ_CHUNK_SIZE (1024 * 1024)
|
||||
|
||||
static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
|
||||
|
||||
struct file_info {
|
||||
efi_file_handle_t *handle;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
void efi_printk(efi_system_table_t *sys_table_arg, char *str)
|
||||
{
|
||||
char *s8;
|
||||
|
||||
for (s8 = str; *s8; s8++) {
|
||||
efi_char16_t ch[2] = { 0 };
|
||||
|
||||
ch[0] = *s8;
|
||||
if (*s8 == '\n') {
|
||||
efi_char16_t nl[2] = { '\r', 0 };
|
||||
efi_char16_printk(sys_table_arg, nl);
|
||||
}
|
||||
|
||||
efi_char16_printk(sys_table_arg, ch);
|
||||
}
|
||||
}
|
||||
|
||||
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
|
||||
efi_memory_desc_t **map,
|
||||
unsigned long *map_size,
|
||||
unsigned long *desc_size,
|
||||
u32 *desc_ver,
|
||||
unsigned long *key_ptr)
|
||||
{
|
||||
efi_memory_desc_t *m = NULL;
|
||||
efi_status_t status;
|
||||
unsigned long key;
|
||||
u32 desc_version;
|
||||
|
||||
*map_size = sizeof(*m) * 32;
|
||||
again:
|
||||
/*
|
||||
* Add an additional efi_memory_desc_t because we're doing an
|
||||
* allocation which may be in a new descriptor region.
|
||||
*/
|
||||
*map_size += sizeof(*m);
|
||||
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
|
||||
*map_size, (void **)&m);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
*desc_size = 0;
|
||||
key = 0;
|
||||
status = efi_call_early(get_memory_map, map_size, m,
|
||||
&key, desc_size, &desc_version);
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
efi_call_early(free_pool, m);
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_call_early(free_pool, m);
|
||||
|
||||
if (key_ptr && status == EFI_SUCCESS)
|
||||
*key_ptr = key;
|
||||
if (desc_ver && status == EFI_SUCCESS)
|
||||
*desc_ver = desc_version;
|
||||
|
||||
fail:
|
||||
*map = m;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg)
|
||||
{
|
||||
efi_status_t status;
|
||||
unsigned long map_size;
|
||||
unsigned long membase = EFI_ERROR;
|
||||
struct efi_memory_map map;
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map,
|
||||
&map_size, &map.desc_size, NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
return membase;
|
||||
|
||||
map.map_end = map.map + map_size;
|
||||
|
||||
for_each_efi_memory_desc(&map, md)
|
||||
if (md->attribute & EFI_MEMORY_WB)
|
||||
if (membase > md->phys_addr)
|
||||
membase = md->phys_addr;
|
||||
|
||||
efi_call_early(free_pool, map.map);
|
||||
|
||||
return membase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate at the highest possible address that is not above 'max'.
|
||||
*/
|
||||
efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long *addr, unsigned long max)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
efi_memory_desc_t *map;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
u64 max_addr = 0;
|
||||
int i;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
||||
NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Enforce minimum alignment that EFI requires when requesting
|
||||
* a specific address. We are doing page-based allocations,
|
||||
* so we must be aligned to a page.
|
||||
*/
|
||||
if (align < EFI_PAGE_SIZE)
|
||||
align = EFI_PAGE_SIZE;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
again:
|
||||
for (i = 0; i < map_size / desc_size; i++) {
|
||||
efi_memory_desc_t *desc;
|
||||
unsigned long m = (unsigned long)map;
|
||||
u64 start, end;
|
||||
|
||||
desc = (efi_memory_desc_t *)(m + (i * desc_size));
|
||||
if (desc->type != EFI_CONVENTIONAL_MEMORY)
|
||||
continue;
|
||||
|
||||
if (desc->num_pages < nr_pages)
|
||||
continue;
|
||||
|
||||
start = desc->phys_addr;
|
||||
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
|
||||
|
||||
if (end > max)
|
||||
end = max;
|
||||
|
||||
if ((start + size) > end)
|
||||
continue;
|
||||
|
||||
if (round_down(end - size, align) < start)
|
||||
continue;
|
||||
|
||||
start = round_down(end - size, align);
|
||||
|
||||
/*
|
||||
* Don't allocate at 0x0. It will confuse code that
|
||||
* checks pointers against NULL.
|
||||
*/
|
||||
if (start == 0x0)
|
||||
continue;
|
||||
|
||||
if (start > max_addr)
|
||||
max_addr = start;
|
||||
}
|
||||
|
||||
if (!max_addr)
|
||||
status = EFI_NOT_FOUND;
|
||||
else {
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
max = max_addr;
|
||||
max_addr = 0;
|
||||
goto again;
|
||||
}
|
||||
|
||||
*addr = max_addr;
|
||||
}
|
||||
|
||||
efi_call_early(free_pool, map);
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate at the lowest possible address.
|
||||
*/
|
||||
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long *addr)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
efi_memory_desc_t *map;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
int i;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
||||
NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Enforce minimum alignment that EFI requires when requesting
|
||||
* a specific address. We are doing page-based allocations,
|
||||
* so we must be aligned to a page.
|
||||
*/
|
||||
if (align < EFI_PAGE_SIZE)
|
||||
align = EFI_PAGE_SIZE;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
for (i = 0; i < map_size / desc_size; i++) {
|
||||
efi_memory_desc_t *desc;
|
||||
unsigned long m = (unsigned long)map;
|
||||
u64 start, end;
|
||||
|
||||
desc = (efi_memory_desc_t *)(m + (i * desc_size));
|
||||
|
||||
if (desc->type != EFI_CONVENTIONAL_MEMORY)
|
||||
continue;
|
||||
|
||||
if (desc->num_pages < nr_pages)
|
||||
continue;
|
||||
|
||||
start = desc->phys_addr;
|
||||
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* Don't allocate at 0x0. It will confuse code that
|
||||
* checks pointers against NULL. Skip the first 8
|
||||
* bytes so we start at a nice even number.
|
||||
*/
|
||||
if (start == 0x0)
|
||||
start += 8;
|
||||
|
||||
start = round_up(start, align);
|
||||
if ((start + size) > end)
|
||||
continue;
|
||||
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &start);
|
||||
if (status == EFI_SUCCESS) {
|
||||
*addr = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == map_size / desc_size)
|
||||
status = EFI_NOT_FOUND;
|
||||
|
||||
efi_call_early(free_pool, map);
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
void efi_free(efi_system_table_t *sys_table_arg, unsigned long size,
|
||||
unsigned long addr)
|
||||
{
|
||||
unsigned long nr_pages;
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
efi_call_early(free_pages, addr, nr_pages);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the ASCII string 'cmdline' for EFI options, denoted by the efi=
|
||||
* option, e.g. efi=nochunk.
|
||||
*
|
||||
* It should be noted that efi= is parsed in two very different
|
||||
* environments, first in the early boot environment of the EFI boot
|
||||
* stub, and subsequently during the kernel boot.
|
||||
*/
|
||||
efi_status_t efi_parse_options(char *cmdline)
|
||||
{
|
||||
char *str;
|
||||
|
||||
/*
|
||||
* If no EFI parameters were specified on the cmdline we've got
|
||||
* nothing to do.
|
||||
*/
|
||||
str = strstr(cmdline, "efi=");
|
||||
if (!str)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
/* Skip ahead to first argument */
|
||||
str += strlen("efi=");
|
||||
|
||||
/*
|
||||
* Remember, because efi= is also used by the kernel we need to
|
||||
* skip over arguments we don't understand.
|
||||
*/
|
||||
while (*str) {
|
||||
if (!strncmp(str, "nochunk", 7)) {
|
||||
str += strlen("nochunk");
|
||||
__chunk_size = -1UL;
|
||||
}
|
||||
|
||||
/* Group words together, delimited by "," */
|
||||
while (*str && *str != ',')
|
||||
str++;
|
||||
|
||||
if (*str == ',')
|
||||
str++;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the cmdline for a LILO-style file= arguments.
|
||||
*
|
||||
* We only support loading a file from the same filesystem as
|
||||
* the kernel image.
|
||||
*/
|
||||
efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
|
||||
efi_loaded_image_t *image,
|
||||
char *cmd_line, char *option_string,
|
||||
unsigned long max_addr,
|
||||
unsigned long *load_addr,
|
||||
unsigned long *load_size)
|
||||
{
|
||||
struct file_info *files;
|
||||
unsigned long file_addr;
|
||||
u64 file_size_total;
|
||||
efi_file_handle_t *fh = NULL;
|
||||
efi_status_t status;
|
||||
int nr_files;
|
||||
char *str;
|
||||
int i, j, k;
|
||||
|
||||
file_addr = 0;
|
||||
file_size_total = 0;
|
||||
|
||||
str = cmd_line;
|
||||
|
||||
j = 0; /* See close_handles */
|
||||
|
||||
if (!load_addr || !load_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
*load_addr = 0;
|
||||
*load_size = 0;
|
||||
|
||||
if (!str || !*str)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
for (nr_files = 0; *str; nr_files++) {
|
||||
str = strstr(str, option_string);
|
||||
if (!str)
|
||||
break;
|
||||
|
||||
str += strlen(option_string);
|
||||
|
||||
/* Skip any leading slashes */
|
||||
while (*str == '/' || *str == '\\')
|
||||
str++;
|
||||
|
||||
while (*str && *str != ' ' && *str != '\n')
|
||||
str++;
|
||||
}
|
||||
|
||||
if (!nr_files)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
|
||||
nr_files * sizeof(*files), (void **)&files);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to alloc mem for file handle list\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
str = cmd_line;
|
||||
for (i = 0; i < nr_files; i++) {
|
||||
struct file_info *file;
|
||||
efi_char16_t filename_16[256];
|
||||
efi_char16_t *p;
|
||||
|
||||
str = strstr(str, option_string);
|
||||
if (!str)
|
||||
break;
|
||||
|
||||
str += strlen(option_string);
|
||||
|
||||
file = &files[i];
|
||||
p = filename_16;
|
||||
|
||||
/* Skip any leading slashes */
|
||||
while (*str == '/' || *str == '\\')
|
||||
str++;
|
||||
|
||||
while (*str && *str != ' ' && *str != '\n') {
|
||||
if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
|
||||
break;
|
||||
|
||||
if (*str == '/') {
|
||||
*p++ = '\\';
|
||||
str++;
|
||||
} else {
|
||||
*p++ = *str++;
|
||||
}
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
|
||||
/* Only open the volume once. */
|
||||
if (!i) {
|
||||
status = efi_open_volume(sys_table_arg, image,
|
||||
(void **)&fh);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto free_files;
|
||||
}
|
||||
|
||||
status = efi_file_size(sys_table_arg, fh, filename_16,
|
||||
(void **)&file->handle, &file->size);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto close_handles;
|
||||
|
||||
file_size_total += file->size;
|
||||
}
|
||||
|
||||
if (file_size_total) {
|
||||
unsigned long addr;
|
||||
|
||||
/*
|
||||
* Multiple files need to be at consecutive addresses in memory,
|
||||
* so allocate enough memory for all the files. This is used
|
||||
* for loading multiple files.
|
||||
*/
|
||||
status = efi_high_alloc(sys_table_arg, file_size_total, 0x1000,
|
||||
&file_addr, max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to alloc highmem for files\n");
|
||||
goto close_handles;
|
||||
}
|
||||
|
||||
/* We've run out of free low memory. */
|
||||
if (file_addr > max_addr) {
|
||||
pr_efi_err(sys_table_arg, "We've run out of free low memory\n");
|
||||
status = EFI_INVALID_PARAMETER;
|
||||
goto free_file_total;
|
||||
}
|
||||
|
||||
addr = file_addr;
|
||||
for (j = 0; j < nr_files; j++) {
|
||||
unsigned long size;
|
||||
|
||||
size = files[j].size;
|
||||
while (size) {
|
||||
unsigned long chunksize;
|
||||
if (size > __chunk_size)
|
||||
chunksize = __chunk_size;
|
||||
else
|
||||
chunksize = size;
|
||||
|
||||
status = efi_file_read(files[j].handle,
|
||||
&chunksize,
|
||||
(void *)addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to read file\n");
|
||||
goto free_file_total;
|
||||
}
|
||||
addr += chunksize;
|
||||
size -= chunksize;
|
||||
}
|
||||
|
||||
efi_file_close(files[j].handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
efi_call_early(free_pool, files);
|
||||
|
||||
*load_addr = file_addr;
|
||||
*load_size = file_size_total;
|
||||
|
||||
return status;
|
||||
|
||||
free_file_total:
|
||||
efi_free(sys_table_arg, file_size_total, file_addr);
|
||||
|
||||
close_handles:
|
||||
for (k = j; k < i; k++)
|
||||
efi_file_close(files[k].handle);
|
||||
free_files:
|
||||
efi_call_early(free_pool, files);
|
||||
fail:
|
||||
*load_addr = 0;
|
||||
*load_size = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
/*
|
||||
* Relocate a kernel image, either compressed or uncompressed.
|
||||
* In the ARM64 case, all kernel images are currently
|
||||
* uncompressed, and as such when we relocate it we need to
|
||||
* allocate additional space for the BSS segment. Any low
|
||||
* memory that this function should avoid needs to be
|
||||
* unavailable in the EFI memory map, as if the preferred
|
||||
* address is not available the lowest available address will
|
||||
* be used.
|
||||
*/
|
||||
efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
|
||||
unsigned long *image_addr,
|
||||
unsigned long image_size,
|
||||
unsigned long alloc_size,
|
||||
unsigned long preferred_addr,
|
||||
unsigned long alignment)
|
||||
{
|
||||
unsigned long cur_image_addr;
|
||||
unsigned long new_addr = 0;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
efi_physical_addr_t efi_addr = preferred_addr;
|
||||
|
||||
if (!image_addr || !image_size || !alloc_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
if (alloc_size < image_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
cur_image_addr = *image_addr;
|
||||
|
||||
/*
|
||||
* The EFI firmware loader could have placed the kernel image
|
||||
* anywhere in memory, but the kernel has restrictions on the
|
||||
* max physical address it can run at. Some architectures
|
||||
* also have a prefered address, so first try to relocate
|
||||
* to the preferred address. If that fails, allocate as low
|
||||
* as possible while respecting the required alignment.
|
||||
*/
|
||||
nr_pages = round_up(alloc_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &efi_addr);
|
||||
new_addr = efi_addr;
|
||||
/*
|
||||
* If preferred address allocation failed allocate as low as
|
||||
* possible.
|
||||
*/
|
||||
if (status != EFI_SUCCESS) {
|
||||
status = efi_low_alloc(sys_table_arg, alloc_size, alignment,
|
||||
&new_addr);
|
||||
}
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* We know source/dest won't overlap since both memory ranges
|
||||
* have been allocated by UEFI, so we can safely use memcpy.
|
||||
*/
|
||||
memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
|
||||
|
||||
/* Return the new address of the relocated image. */
|
||||
*image_addr = new_addr;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the number of UTF-8 bytes corresponding to an UTF-16 character.
|
||||
* This overestimates for surrogates, but that is okay.
|
||||
*/
|
||||
static int efi_utf8_bytes(u16 c)
|
||||
{
|
||||
return 1 + (c >= 0x80) + (c >= 0x800);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an UTF-16 string, not necessarily null terminated, to UTF-8.
|
||||
*/
|
||||
static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n)
|
||||
{
|
||||
unsigned int c;
|
||||
|
||||
while (n--) {
|
||||
c = *src++;
|
||||
if (n && c >= 0xd800 && c <= 0xdbff &&
|
||||
*src >= 0xdc00 && *src <= 0xdfff) {
|
||||
c = 0x10000 + ((c & 0x3ff) << 10) + (*src & 0x3ff);
|
||||
src++;
|
||||
n--;
|
||||
}
|
||||
if (c >= 0xd800 && c <= 0xdfff)
|
||||
c = 0xfffd; /* Unmatched surrogate */
|
||||
if (c < 0x80) {
|
||||
*dst++ = c;
|
||||
continue;
|
||||
}
|
||||
if (c < 0x800) {
|
||||
*dst++ = 0xc0 + (c >> 6);
|
||||
goto t1;
|
||||
}
|
||||
if (c < 0x10000) {
|
||||
*dst++ = 0xe0 + (c >> 12);
|
||||
goto t2;
|
||||
}
|
||||
*dst++ = 0xf0 + (c >> 18);
|
||||
*dst++ = 0x80 + ((c >> 12) & 0x3f);
|
||||
t2:
|
||||
*dst++ = 0x80 + ((c >> 6) & 0x3f);
|
||||
t1:
|
||||
*dst++ = 0x80 + (c & 0x3f);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the unicode UEFI command line to ASCII to pass to kernel.
|
||||
* Size of memory allocated return in *cmd_line_len.
|
||||
* Returns NULL on error.
|
||||
*/
|
||||
char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
|
||||
efi_loaded_image_t *image,
|
||||
int *cmd_line_len)
|
||||
{
|
||||
const u16 *s2;
|
||||
u8 *s1 = NULL;
|
||||
unsigned long cmdline_addr = 0;
|
||||
int load_options_chars = image->load_options_size / 2; /* UTF-16 */
|
||||
const u16 *options = image->load_options;
|
||||
int options_bytes = 0; /* UTF-8 bytes */
|
||||
int options_chars = 0; /* UTF-16 chars */
|
||||
efi_status_t status;
|
||||
u16 zero = 0;
|
||||
|
||||
if (options) {
|
||||
s2 = options;
|
||||
while (*s2 && *s2 != '\n'
|
||||
&& options_chars < load_options_chars) {
|
||||
options_bytes += efi_utf8_bytes(*s2++);
|
||||
options_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!options_chars) {
|
||||
/* No command line options, so return empty string*/
|
||||
options = &zero;
|
||||
}
|
||||
|
||||
options_bytes++; /* NUL termination */
|
||||
|
||||
status = efi_low_alloc(sys_table_arg, options_bytes, 0, &cmdline_addr);
|
||||
if (status != EFI_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
s1 = (u8 *)cmdline_addr;
|
||||
s2 = (const u16 *)options;
|
||||
|
||||
s1 = efi_utf16_to_utf8(s1, s2, options_chars);
|
||||
*s1 = '\0';
|
||||
|
||||
*cmd_line_len = options_bytes;
|
||||
return (char *)cmdline_addr;
|
||||
}
|
42
drivers/firmware/efi/libstub/efistub.h
Normal file
42
drivers/firmware/efi/libstub/efistub.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H
|
||||
#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H
|
||||
|
||||
/* error code which can't be mistaken for valid address */
|
||||
#define EFI_ERROR (~0UL)
|
||||
|
||||
void efi_char16_printk(efi_system_table_t *, efi_char16_t *);
|
||||
|
||||
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image,
|
||||
void **__fh);
|
||||
|
||||
efi_status_t efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
|
||||
efi_char16_t *filename_16, void **handle,
|
||||
u64 *file_sz);
|
||||
|
||||
efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr);
|
||||
|
||||
efi_status_t efi_file_close(void *handle);
|
||||
|
||||
unsigned long get_dram_base(efi_system_table_t *sys_table_arg);
|
||||
|
||||
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
|
||||
unsigned long orig_fdt_size,
|
||||
void *fdt, int new_fdt_size, char *cmdline_ptr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
efi_memory_desc_t *memory_map,
|
||||
unsigned long map_size, unsigned long desc_size,
|
||||
u32 desc_ver);
|
||||
|
||||
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
|
||||
void *handle,
|
||||
unsigned long *new_fdt_addr,
|
||||
unsigned long max_addr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
char *cmdline_ptr,
|
||||
unsigned long fdt_addr,
|
||||
unsigned long fdt_size);
|
||||
|
||||
void *get_fdt(efi_system_table_t *sys_table);
|
||||
|
||||
#endif
|
287
drivers/firmware/efi/libstub/fdt.c
Normal file
287
drivers/firmware/efi/libstub/fdt.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* FDT related Helper functions used by the EFI stub on multiple
|
||||
* architectures. This should be #included by the EFI stub
|
||||
* implementation files.
|
||||
*
|
||||
* Copyright 2013 Linaro Limited; author Roy Franz
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
|
||||
unsigned long orig_fdt_size,
|
||||
void *fdt, int new_fdt_size, char *cmdline_ptr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
efi_memory_desc_t *memory_map,
|
||||
unsigned long map_size, unsigned long desc_size,
|
||||
u32 desc_ver)
|
||||
{
|
||||
int node, prev, num_rsv;
|
||||
int status;
|
||||
u32 fdt_val32;
|
||||
u64 fdt_val64;
|
||||
|
||||
/* Do some checks on provided FDT, if it exists*/
|
||||
if (orig_fdt) {
|
||||
if (fdt_check_header(orig_fdt)) {
|
||||
pr_efi_err(sys_table, "Device Tree header not valid!\n");
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
/*
|
||||
* We don't get the size of the FDT if we get if from a
|
||||
* configuration table.
|
||||
*/
|
||||
if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) {
|
||||
pr_efi_err(sys_table, "Truncated device tree! foo!\n");
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (orig_fdt)
|
||||
status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
|
||||
else
|
||||
status = fdt_create_empty_tree(fdt, new_fdt_size);
|
||||
|
||||
if (status != 0)
|
||||
goto fdt_set_fail;
|
||||
|
||||
/*
|
||||
* Delete any memory nodes present. We must delete nodes which
|
||||
* early_init_dt_scan_memory may try to use.
|
||||
*/
|
||||
prev = 0;
|
||||
for (;;) {
|
||||
const char *type;
|
||||
int len;
|
||||
|
||||
node = fdt_next_node(fdt, prev, NULL);
|
||||
if (node < 0)
|
||||
break;
|
||||
|
||||
type = fdt_getprop(fdt, node, "device_type", &len);
|
||||
if (type && strncmp(type, "memory", len) == 0) {
|
||||
fdt_del_node(fdt, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
prev = node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all memory reserve map entries. When booting via UEFI,
|
||||
* kernel will use the UEFI memory map to find reserved regions.
|
||||
*/
|
||||
num_rsv = fdt_num_mem_rsv(fdt);
|
||||
while (num_rsv-- > 0)
|
||||
fdt_del_mem_rsv(fdt, num_rsv);
|
||||
|
||||
node = fdt_subnode_offset(fdt, 0, "chosen");
|
||||
if (node < 0) {
|
||||
node = fdt_add_subnode(fdt, 0, "chosen");
|
||||
if (node < 0) {
|
||||
status = node; /* node is error code when negative */
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
|
||||
status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
|
||||
strlen(cmdline_ptr) + 1);
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
|
||||
/* Set initrd address/end in device tree, if present */
|
||||
if (initrd_size != 0) {
|
||||
u64 initrd_image_end;
|
||||
u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
|
||||
|
||||
status = fdt_setprop(fdt, node, "linux,initrd-start",
|
||||
&initrd_image_start, sizeof(u64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
|
||||
status = fdt_setprop(fdt, node, "linux,initrd-end",
|
||||
&initrd_image_end, sizeof(u64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
|
||||
/* Add FDT entries for EFI runtime services in chosen node. */
|
||||
node = fdt_subnode_offset(fdt, 0, "chosen");
|
||||
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-system-table",
|
||||
&fdt_val64, sizeof(fdt_val64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-start",
|
||||
&fdt_val64, sizeof(fdt_val64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(map_size);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-size",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(desc_size);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(desc_ver);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
/*
|
||||
* Add kernel version banner so stub/kernel match can be
|
||||
* verified.
|
||||
*/
|
||||
status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver",
|
||||
linux_banner);
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
|
||||
fdt_set_fail:
|
||||
if (status == -FDT_ERR_NOSPACE)
|
||||
return EFI_BUFFER_TOO_SMALL;
|
||||
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
|
||||
#ifndef EFI_FDT_ALIGN
|
||||
#define EFI_FDT_ALIGN EFI_PAGE_SIZE
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Allocate memory for a new FDT, then add EFI, commandline, and
|
||||
* initrd related fields to the FDT. This routine increases the
|
||||
* FDT allocation size until the allocated memory is large
|
||||
* enough. EFI allocations are in EFI_PAGE_SIZE granules,
|
||||
* which are fixed at 4K bytes, so in most cases the first
|
||||
* allocation should succeed.
|
||||
* EFI boot services are exited at the end of this function.
|
||||
* There must be no allocations between the get_memory_map()
|
||||
* call and the exit_boot_services() call, so the exiting of
|
||||
* boot services is very tightly tied to the creation of the FDT
|
||||
* with the final memory map in it.
|
||||
*/
|
||||
|
||||
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
|
||||
void *handle,
|
||||
unsigned long *new_fdt_addr,
|
||||
unsigned long max_addr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
char *cmdline_ptr,
|
||||
unsigned long fdt_addr,
|
||||
unsigned long fdt_size)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
u32 desc_ver;
|
||||
unsigned long mmap_key;
|
||||
efi_memory_desc_t *memory_map;
|
||||
unsigned long new_fdt_size;
|
||||
efi_status_t status;
|
||||
|
||||
/*
|
||||
* Estimate size of new FDT, and allocate memory for it. We
|
||||
* will allocate a bigger buffer if this ends up being too
|
||||
* small, so a rough guess is OK here.
|
||||
*/
|
||||
new_fdt_size = fdt_size + EFI_PAGE_SIZE;
|
||||
while (1) {
|
||||
status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
|
||||
new_fdt_addr, max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we have done our final memory allocation (and free)
|
||||
* we can get the memory map key needed for
|
||||
* exit_boot_services().
|
||||
*/
|
||||
status = efi_get_memory_map(sys_table, &memory_map, &map_size,
|
||||
&desc_size, &desc_ver, &mmap_key);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail_free_new_fdt;
|
||||
|
||||
status = update_fdt(sys_table,
|
||||
(void *)fdt_addr, fdt_size,
|
||||
(void *)*new_fdt_addr, new_fdt_size,
|
||||
cmdline_ptr, initrd_addr, initrd_size,
|
||||
memory_map, map_size, desc_size, desc_ver);
|
||||
|
||||
/* Succeeding the first time is the expected case. */
|
||||
if (status == EFI_SUCCESS)
|
||||
break;
|
||||
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
/*
|
||||
* We need to allocate more space for the new
|
||||
* device tree, so free existing buffer that is
|
||||
* too small. Also free memory map, as we will need
|
||||
* to get new one that reflects the free/alloc we do
|
||||
* on the device tree buffer.
|
||||
*/
|
||||
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
|
||||
sys_table->boottime->free_pool(memory_map);
|
||||
new_fdt_size += EFI_PAGE_SIZE;
|
||||
} else {
|
||||
pr_efi_err(sys_table, "Unable to constuct new device tree.\n");
|
||||
goto fail_free_mmap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we are ready to exit_boot_services.*/
|
||||
status = sys_table->boottime->exit_boot_services(handle, mmap_key);
|
||||
|
||||
|
||||
if (status == EFI_SUCCESS)
|
||||
return status;
|
||||
|
||||
pr_efi_err(sys_table, "Exit boot services failed.\n");
|
||||
|
||||
fail_free_mmap:
|
||||
sys_table->boottime->free_pool(memory_map);
|
||||
|
||||
fail_free_new_fdt:
|
||||
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
|
||||
|
||||
fail:
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
|
||||
void *get_fdt(efi_system_table_t *sys_table)
|
||||
{
|
||||
efi_guid_t fdt_guid = DEVICE_TREE_GUID;
|
||||
efi_config_table_t *tables;
|
||||
void *fdt;
|
||||
int i;
|
||||
|
||||
tables = (efi_config_table_t *) sys_table->tables;
|
||||
fdt = NULL;
|
||||
|
||||
for (i = 0; i < sys_table->nr_tables; i++)
|
||||
if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) {
|
||||
fdt = (void *) tables[i].table;
|
||||
break;
|
||||
}
|
||||
|
||||
return fdt;
|
||||
}
|
56
drivers/firmware/efi/reboot.c
Normal file
56
drivers/firmware/efi/reboot.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Intel Corporation; author Matt Fleming
|
||||
* Copyright (c) 2014 Red Hat, Inc., Mark Salter <msalter@redhat.com>
|
||||
*/
|
||||
#include <linux/efi.h>
|
||||
#include <linux/reboot.h>
|
||||
|
||||
int efi_reboot_quirk_mode = -1;
|
||||
|
||||
void efi_reboot(enum reboot_mode reboot_mode, const char *__unused)
|
||||
{
|
||||
int efi_mode;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return;
|
||||
|
||||
switch (reboot_mode) {
|
||||
case REBOOT_WARM:
|
||||
case REBOOT_SOFT:
|
||||
efi_mode = EFI_RESET_WARM;
|
||||
break;
|
||||
default:
|
||||
efi_mode = EFI_RESET_COLD;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a quirk forced an EFI reset mode, always use that.
|
||||
*/
|
||||
if (efi_reboot_quirk_mode != -1)
|
||||
efi_mode = efi_reboot_quirk_mode;
|
||||
|
||||
efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
bool __weak efi_poweroff_required(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static void efi_power_off(void)
|
||||
{
|
||||
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static int __init efi_shutdown_init(void)
|
||||
{
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return -ENODEV;
|
||||
|
||||
if (efi_poweroff_required())
|
||||
pm_power_off = efi_power_off;
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(efi_shutdown_init);
|
202
drivers/firmware/efi/runtime-map.c
Normal file
202
drivers/firmware/efi/runtime-map.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* linux/drivers/efi/runtime-map.c
|
||||
* Copyright (C) 2013 Red Hat, Inc., Dave Young <dyoung@redhat.com>
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
|
||||
static void *efi_runtime_map;
|
||||
static int nr_efi_runtime_map;
|
||||
static u32 efi_memdesc_size;
|
||||
|
||||
struct efi_runtime_map_entry {
|
||||
efi_memory_desc_t md;
|
||||
struct kobject kobj; /* kobject for each entry */
|
||||
};
|
||||
|
||||
static struct efi_runtime_map_entry **map_entries;
|
||||
|
||||
struct map_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct efi_runtime_map_entry *entry, char *buf);
|
||||
};
|
||||
|
||||
static inline struct map_attribute *to_map_attr(struct attribute *attr)
|
||||
{
|
||||
return container_of(attr, struct map_attribute, attr);
|
||||
}
|
||||
|
||||
static ssize_t type_show(struct efi_runtime_map_entry *entry, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "0x%x\n", entry->md.type);
|
||||
}
|
||||
|
||||
#define EFI_RUNTIME_FIELD(var) entry->md.var
|
||||
|
||||
#define EFI_RUNTIME_U64_ATTR_SHOW(name) \
|
||||
static ssize_t name##_show(struct efi_runtime_map_entry *entry, char *buf) \
|
||||
{ \
|
||||
return snprintf(buf, PAGE_SIZE, "0x%llx\n", EFI_RUNTIME_FIELD(name)); \
|
||||
}
|
||||
|
||||
EFI_RUNTIME_U64_ATTR_SHOW(phys_addr);
|
||||
EFI_RUNTIME_U64_ATTR_SHOW(virt_addr);
|
||||
EFI_RUNTIME_U64_ATTR_SHOW(num_pages);
|
||||
EFI_RUNTIME_U64_ATTR_SHOW(attribute);
|
||||
|
||||
static inline struct efi_runtime_map_entry *to_map_entry(struct kobject *kobj)
|
||||
{
|
||||
return container_of(kobj, struct efi_runtime_map_entry, kobj);
|
||||
}
|
||||
|
||||
static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct efi_runtime_map_entry *entry = to_map_entry(kobj);
|
||||
struct map_attribute *map_attr = to_map_attr(attr);
|
||||
|
||||
return map_attr->show(entry, buf);
|
||||
}
|
||||
|
||||
static struct map_attribute map_type_attr = __ATTR_RO(type);
|
||||
static struct map_attribute map_phys_addr_attr = __ATTR_RO(phys_addr);
|
||||
static struct map_attribute map_virt_addr_attr = __ATTR_RO(virt_addr);
|
||||
static struct map_attribute map_num_pages_attr = __ATTR_RO(num_pages);
|
||||
static struct map_attribute map_attribute_attr = __ATTR_RO(attribute);
|
||||
|
||||
/*
|
||||
* These are default attributes that are added for every memmap entry.
|
||||
*/
|
||||
static struct attribute *def_attrs[] = {
|
||||
&map_type_attr.attr,
|
||||
&map_phys_addr_attr.attr,
|
||||
&map_virt_addr_attr.attr,
|
||||
&map_num_pages_attr.attr,
|
||||
&map_attribute_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct sysfs_ops map_attr_ops = {
|
||||
.show = map_attr_show,
|
||||
};
|
||||
|
||||
static void map_release(struct kobject *kobj)
|
||||
{
|
||||
struct efi_runtime_map_entry *entry;
|
||||
|
||||
entry = to_map_entry(kobj);
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
static struct kobj_type __refdata map_ktype = {
|
||||
.sysfs_ops = &map_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
.release = map_release,
|
||||
};
|
||||
|
||||
static struct kset *map_kset;
|
||||
|
||||
static struct efi_runtime_map_entry *
|
||||
add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
|
||||
{
|
||||
int ret;
|
||||
struct efi_runtime_map_entry *entry;
|
||||
|
||||
if (!map_kset) {
|
||||
map_kset = kset_create_and_add("runtime-map", NULL, kobj);
|
||||
if (!map_kset)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
kset_unregister(map_kset);
|
||||
return entry;
|
||||
}
|
||||
|
||||
memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size,
|
||||
sizeof(efi_memory_desc_t));
|
||||
|
||||
kobject_init(&entry->kobj, &map_ktype);
|
||||
entry->kobj.kset = map_kset;
|
||||
ret = kobject_add(&entry->kobj, NULL, "%d", nr);
|
||||
if (ret) {
|
||||
kobject_put(&entry->kobj);
|
||||
kset_unregister(map_kset);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
int efi_get_runtime_map_size(void)
|
||||
{
|
||||
return nr_efi_runtime_map * efi_memdesc_size;
|
||||
}
|
||||
|
||||
int efi_get_runtime_map_desc_size(void)
|
||||
{
|
||||
return efi_memdesc_size;
|
||||
}
|
||||
|
||||
int efi_runtime_map_copy(void *buf, size_t bufsz)
|
||||
{
|
||||
size_t sz = efi_get_runtime_map_size();
|
||||
|
||||
if (sz > bufsz)
|
||||
sz = bufsz;
|
||||
|
||||
memcpy(buf, efi_runtime_map, sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size)
|
||||
{
|
||||
efi_runtime_map = map;
|
||||
nr_efi_runtime_map = nr_entries;
|
||||
efi_memdesc_size = desc_size;
|
||||
}
|
||||
|
||||
int __init efi_runtime_map_init(struct kobject *efi_kobj)
|
||||
{
|
||||
int i, j, ret = 0;
|
||||
struct efi_runtime_map_entry *entry;
|
||||
|
||||
if (!efi_runtime_map)
|
||||
return 0;
|
||||
|
||||
map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL);
|
||||
if (!map_entries) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_efi_runtime_map; i++) {
|
||||
entry = add_sysfs_runtime_map_entry(efi_kobj, i);
|
||||
if (IS_ERR(entry)) {
|
||||
ret = PTR_ERR(entry);
|
||||
goto out_add_entry;
|
||||
}
|
||||
*(map_entries + i) = entry;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_add_entry:
|
||||
for (j = i - 1; j >= 0; j--) {
|
||||
entry = *(map_entries + j);
|
||||
kobject_put(&entry->kobj);
|
||||
}
|
||||
if (map_kset)
|
||||
kset_unregister(map_kset);
|
||||
out:
|
||||
return ret;
|
||||
}
|
305
drivers/firmware/efi/runtime-wrappers.c
Normal file
305
drivers/firmware/efi/runtime-wrappers.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* runtime-wrappers.c - Runtime Services function call wrappers
|
||||
*
|
||||
* Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org>
|
||||
*
|
||||
* Split off from arch/x86/platform/efi/efi.c
|
||||
*
|
||||
* Copyright (C) 1999 VA Linux Systems
|
||||
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
|
||||
* Copyright (C) 1999-2002 Hewlett-Packard Co.
|
||||
* Copyright (C) 2005-2008 Intel Co.
|
||||
* Copyright (C) 2013 SuSE Labs
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
/*
|
||||
* According to section 7.1 of the UEFI spec, Runtime Services are not fully
|
||||
* reentrant, and there are particular combinations of calls that need to be
|
||||
* serialized. (source: UEFI Specification v2.4A)
|
||||
*
|
||||
* Table 31. Rules for Reentry Into Runtime Services
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | If previous call is busy in | Forbidden to call |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | Any | SetVirtualAddressMap() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | ConvertPointer() | ConvertPointer() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | SetVariable() | ResetSystem() |
|
||||
* | UpdateCapsule() | |
|
||||
* | SetTime() | |
|
||||
* | SetWakeupTime() | |
|
||||
* | GetNextHighMonotonicCount() | |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | GetVariable() | GetVariable() |
|
||||
* | GetNextVariableName() | GetNextVariableName() |
|
||||
* | SetVariable() | SetVariable() |
|
||||
* | QueryVariableInfo() | QueryVariableInfo() |
|
||||
* | UpdateCapsule() | UpdateCapsule() |
|
||||
* | QueryCapsuleCapabilities() | QueryCapsuleCapabilities() |
|
||||
* | GetNextHighMonotonicCount() | GetNextHighMonotonicCount() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | GetTime() | GetTime() |
|
||||
* | SetTime() | SetTime() |
|
||||
* | GetWakeupTime() | GetWakeupTime() |
|
||||
* | SetWakeupTime() | SetWakeupTime() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
*
|
||||
* Due to the fact that the EFI pstore may write to the variable store in
|
||||
* interrupt context, we need to use a spinlock for at least the groups that
|
||||
* contain SetVariable() and QueryVariableInfo(). That leaves little else, as
|
||||
* none of the remaining functions are actually ever called at runtime.
|
||||
* So let's just use a single spinlock to serialize all Runtime Services calls.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(efi_runtime_lock);
|
||||
|
||||
/*
|
||||
* Some runtime services calls can be reentrant under NMI, even if the table
|
||||
* above says they are not. (source: UEFI Specification v2.4A)
|
||||
*
|
||||
* Table 32. Functions that may be called after Machine Check, INIT and NMI
|
||||
* +----------------------------+------------------------------------------+
|
||||
* | Function | Called after Machine Check, INIT and NMI |
|
||||
* +----------------------------+------------------------------------------+
|
||||
* | GetTime() | Yes, even if previously busy. |
|
||||
* | GetVariable() | Yes, even if previously busy |
|
||||
* | GetNextVariableName() | Yes, even if previously busy |
|
||||
* | QueryVariableInfo() | Yes, even if previously busy |
|
||||
* | SetVariable() | Yes, even if previously busy |
|
||||
* | UpdateCapsule() | Yes, even if previously busy |
|
||||
* | QueryCapsuleCapabilities() | Yes, even if previously busy |
|
||||
* | ResetSystem() | Yes, even if previously busy |
|
||||
* +----------------------------+------------------------------------------+
|
||||
*
|
||||
* In order to prevent deadlocks under NMI, the wrappers for these functions
|
||||
* may only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi().
|
||||
* However, not all of the services listed are reachable through NMI code paths,
|
||||
* so the the special handling as suggested by the UEFI spec is only implemented
|
||||
* for QueryVariableInfo() and SetVariable(), as these can be reached in NMI
|
||||
* context through efi_pstore_write().
|
||||
*/
|
||||
|
||||
/*
|
||||
* As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"),
|
||||
* the EFI specification requires that callers of the time related runtime
|
||||
* functions serialize with other CMOS accesses in the kernel, as the EFI time
|
||||
* functions may choose to also use the legacy CMOS RTC.
|
||||
*/
|
||||
__weak DEFINE_SPINLOCK(rtc_lock);
|
||||
|
||||
static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(get_time, tm, tc);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_time(efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(set_time, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
|
||||
efi_bool_t *pending,
|
||||
efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(set_wakeup_time, enabled, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor,
|
||||
u32 *attr,
|
||||
unsigned long *data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
|
||||
efi_char16_t *name,
|
||||
efi_guid_t *vendor)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_next_variable, name_size, name, vendor);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor,
|
||||
u32 attr,
|
||||
unsigned long data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t
|
||||
virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
|
||||
u32 attr, unsigned long data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
|
||||
return EFI_NOT_READY;
|
||||
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static efi_status_t virt_efi_query_variable_info(u32 attr,
|
||||
u64 *storage_space,
|
||||
u64 *remaining_space,
|
||||
u64 *max_variable_size)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(query_variable_info, attr, storage_space,
|
||||
remaining_space, max_variable_size);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_next_high_mono_count, count);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void virt_efi_reset_system(int reset_type,
|
||||
efi_status_t status,
|
||||
unsigned long data_size,
|
||||
efi_char16_t *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
__efi_call_virt(reset_system, reset_type, status, data_size, data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
|
||||
unsigned long count,
|
||||
unsigned long sg_list)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(update_capsule, capsules, count, sg_list);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
|
||||
unsigned long count,
|
||||
u64 *max_size,
|
||||
int *reset_type)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
|
||||
reset_type);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
void efi_native_runtime_setup(void)
|
||||
{
|
||||
efi.get_time = virt_efi_get_time;
|
||||
efi.set_time = virt_efi_set_time;
|
||||
efi.get_wakeup_time = virt_efi_get_wakeup_time;
|
||||
efi.set_wakeup_time = virt_efi_set_wakeup_time;
|
||||
efi.get_variable = virt_efi_get_variable;
|
||||
efi.get_next_variable = virt_efi_get_next_variable;
|
||||
efi.set_variable = virt_efi_set_variable;
|
||||
efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
|
||||
efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
|
||||
efi.reset_system = virt_efi_reset_system;
|
||||
efi.query_variable_info = virt_efi_query_variable_info;
|
||||
efi.update_capsule = virt_efi_update_capsule;
|
||||
efi.query_capsule_caps = virt_efi_query_capsule_caps;
|
||||
}
|
1096
drivers/firmware/efi/vars.c
Normal file
1096
drivers/firmware/efi/vars.c
Normal file
File diff suppressed because it is too large
Load diff
31
drivers/firmware/google/Kconfig
Normal file
31
drivers/firmware/google/Kconfig
Normal file
|
@ -0,0 +1,31 @@
|
|||
config GOOGLE_FIRMWARE
|
||||
bool "Google Firmware Drivers"
|
||||
depends on X86
|
||||
default n
|
||||
help
|
||||
These firmware drivers are used by Google's servers. They are
|
||||
only useful if you are working directly on one of their
|
||||
proprietary servers. If in doubt, say "N".
|
||||
|
||||
menu "Google Firmware Drivers"
|
||||
depends on GOOGLE_FIRMWARE
|
||||
|
||||
config GOOGLE_SMI
|
||||
tristate "SMI interface for Google platforms"
|
||||
depends on ACPI && DMI && EFI
|
||||
select EFI_VARS
|
||||
help
|
||||
Say Y here if you want to enable SMI callbacks for Google
|
||||
platforms. This provides an interface for writing to and
|
||||
clearing the EFI event log and reading and writing NVRAM
|
||||
variables.
|
||||
|
||||
config GOOGLE_MEMCONSOLE
|
||||
tristate "Firmware Memory Console"
|
||||
depends on DMI
|
||||
help
|
||||
This option enables the kernel to search for a firmware log in
|
||||
the EBDA on Google servers. If found, this log is exported to
|
||||
userland in the file /sys/firmware/log.
|
||||
|
||||
endmenu
|
3
drivers/firmware/google/Makefile
Normal file
3
drivers/firmware/google/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
|
942
drivers/firmware/google/gsmi.c
Normal file
942
drivers/firmware/google/gsmi.c
Normal file
|
@ -0,0 +1,942 @@
|
|||
/*
|
||||
* Copyright 2010 Google Inc. All Rights Reserved.
|
||||
* Author: dlaurie@google.com (Duncan Laurie)
|
||||
*
|
||||
* Re-worked to expose sysfs APIs by mikew@google.com (Mike Waychison)
|
||||
*
|
||||
* EFI SMI interface for Google platforms
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/kdebug.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
|
||||
#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */
|
||||
/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
|
||||
#define GSMI_SHUTDOWN_NMIWDT 1 /* NMI Watchdog */
|
||||
#define GSMI_SHUTDOWN_PANIC 2 /* Panic */
|
||||
#define GSMI_SHUTDOWN_OOPS 3 /* Oops */
|
||||
#define GSMI_SHUTDOWN_DIE 4 /* Die -- No longer meaningful */
|
||||
#define GSMI_SHUTDOWN_MCE 5 /* Machine Check */
|
||||
#define GSMI_SHUTDOWN_SOFTWDT 6 /* Software Watchdog */
|
||||
#define GSMI_SHUTDOWN_MBE 7 /* Uncorrected ECC */
|
||||
#define GSMI_SHUTDOWN_TRIPLE 8 /* Triple Fault */
|
||||
|
||||
#define DRIVER_VERSION "1.0"
|
||||
#define GSMI_GUID_SIZE 16
|
||||
#define GSMI_BUF_SIZE 1024
|
||||
#define GSMI_BUF_ALIGN sizeof(u64)
|
||||
#define GSMI_CALLBACK 0xef
|
||||
|
||||
/* SMI return codes */
|
||||
#define GSMI_SUCCESS 0x00
|
||||
#define GSMI_UNSUPPORTED2 0x03
|
||||
#define GSMI_LOG_FULL 0x0b
|
||||
#define GSMI_VAR_NOT_FOUND 0x0e
|
||||
#define GSMI_HANDSHAKE_SPIN 0x7d
|
||||
#define GSMI_HANDSHAKE_CF 0x7e
|
||||
#define GSMI_HANDSHAKE_NONE 0x7f
|
||||
#define GSMI_INVALID_PARAMETER 0x82
|
||||
#define GSMI_UNSUPPORTED 0x83
|
||||
#define GSMI_BUFFER_TOO_SMALL 0x85
|
||||
#define GSMI_NOT_READY 0x86
|
||||
#define GSMI_DEVICE_ERROR 0x87
|
||||
#define GSMI_NOT_FOUND 0x8e
|
||||
|
||||
#define QUIRKY_BOARD_HASH 0x78a30a50
|
||||
|
||||
/* Internally used commands passed to the firmware */
|
||||
#define GSMI_CMD_GET_NVRAM_VAR 0x01
|
||||
#define GSMI_CMD_GET_NEXT_VAR 0x02
|
||||
#define GSMI_CMD_SET_NVRAM_VAR 0x03
|
||||
#define GSMI_CMD_SET_EVENT_LOG 0x08
|
||||
#define GSMI_CMD_CLEAR_EVENT_LOG 0x09
|
||||
#define GSMI_CMD_CLEAR_CONFIG 0x20
|
||||
#define GSMI_CMD_HANDSHAKE_TYPE 0xC1
|
||||
|
||||
/* Magic entry type for kernel events */
|
||||
#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD
|
||||
|
||||
/* SMI buffers must be in 32bit physical address space */
|
||||
struct gsmi_buf {
|
||||
u8 *start; /* start of buffer */
|
||||
size_t length; /* length of buffer */
|
||||
dma_addr_t handle; /* dma allocation handle */
|
||||
u32 address; /* physical address of buffer */
|
||||
};
|
||||
|
||||
struct gsmi_device {
|
||||
struct platform_device *pdev; /* platform device */
|
||||
struct gsmi_buf *name_buf; /* variable name buffer */
|
||||
struct gsmi_buf *data_buf; /* generic data buffer */
|
||||
struct gsmi_buf *param_buf; /* parameter buffer */
|
||||
spinlock_t lock; /* serialize access to SMIs */
|
||||
u16 smi_cmd; /* SMI command port */
|
||||
int handshake_type; /* firmware handler interlock type */
|
||||
struct dma_pool *dma_pool; /* DMA buffer pool */
|
||||
} gsmi_dev;
|
||||
|
||||
/* Packed structures for communicating with the firmware */
|
||||
struct gsmi_nvram_var_param {
|
||||
efi_guid_t guid;
|
||||
u32 name_ptr;
|
||||
u32 attributes;
|
||||
u32 data_len;
|
||||
u32 data_ptr;
|
||||
} __packed;
|
||||
|
||||
struct gsmi_get_next_var_param {
|
||||
u8 guid[GSMI_GUID_SIZE];
|
||||
u32 name_ptr;
|
||||
u32 name_len;
|
||||
} __packed;
|
||||
|
||||
struct gsmi_set_eventlog_param {
|
||||
u32 data_ptr;
|
||||
u32 data_len;
|
||||
u32 type;
|
||||
} __packed;
|
||||
|
||||
/* Event log formats */
|
||||
struct gsmi_log_entry_type_1 {
|
||||
u16 type;
|
||||
u32 instance;
|
||||
} __packed;
|
||||
|
||||
|
||||
/*
|
||||
* Some platforms don't have explicit SMI handshake
|
||||
* and need to wait for SMI to complete.
|
||||
*/
|
||||
#define GSMI_DEFAULT_SPINCOUNT 0x10000
|
||||
static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT;
|
||||
module_param(spincount, uint, 0600);
|
||||
MODULE_PARM_DESC(spincount,
|
||||
"The number of loop iterations to use when using the spin handshake.");
|
||||
|
||||
static struct gsmi_buf *gsmi_buf_alloc(void)
|
||||
{
|
||||
struct gsmi_buf *smibuf;
|
||||
|
||||
smibuf = kzalloc(sizeof(*smibuf), GFP_KERNEL);
|
||||
if (!smibuf) {
|
||||
printk(KERN_ERR "gsmi: out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* allocate buffer in 32bit address space */
|
||||
smibuf->start = dma_pool_alloc(gsmi_dev.dma_pool, GFP_KERNEL,
|
||||
&smibuf->handle);
|
||||
if (!smibuf->start) {
|
||||
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
|
||||
kfree(smibuf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* fill in the buffer handle */
|
||||
smibuf->length = GSMI_BUF_SIZE;
|
||||
smibuf->address = (u32)virt_to_phys(smibuf->start);
|
||||
|
||||
return smibuf;
|
||||
}
|
||||
|
||||
static void gsmi_buf_free(struct gsmi_buf *smibuf)
|
||||
{
|
||||
if (smibuf) {
|
||||
if (smibuf->start)
|
||||
dma_pool_free(gsmi_dev.dma_pool, smibuf->start,
|
||||
smibuf->handle);
|
||||
kfree(smibuf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a call to gsmi func(sub). GSMI error codes are translated to
|
||||
* in-kernel errnos (0 on success, -ERRNO on error).
|
||||
*/
|
||||
static int gsmi_exec(u8 func, u8 sub)
|
||||
{
|
||||
u16 cmd = (sub << 8) | func;
|
||||
u16 result = 0;
|
||||
int rc = 0;
|
||||
|
||||
/*
|
||||
* AH : Subfunction number
|
||||
* AL : Function number
|
||||
* EBX : Parameter block address
|
||||
* DX : SMI command port
|
||||
*
|
||||
* Three protocols here. See also the comment in gsmi_init().
|
||||
*/
|
||||
if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) {
|
||||
/*
|
||||
* If handshake_type == HANDSHAKE_CF then set CF on the
|
||||
* way in and wait for the handler to clear it; this avoids
|
||||
* corrupting register state on those chipsets which have
|
||||
* a delay between writing the SMI trigger register and
|
||||
* entering SMM.
|
||||
*/
|
||||
asm volatile (
|
||||
"stc\n"
|
||||
"outb %%al, %%dx\n"
|
||||
"1: jc 1b\n"
|
||||
: "=a" (result)
|
||||
: "0" (cmd),
|
||||
"d" (gsmi_dev.smi_cmd),
|
||||
"b" (gsmi_dev.param_buf->address)
|
||||
: "memory", "cc"
|
||||
);
|
||||
} else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) {
|
||||
/*
|
||||
* If handshake_type == HANDSHAKE_SPIN we spin a
|
||||
* hundred-ish usecs to ensure the SMI has triggered.
|
||||
*/
|
||||
asm volatile (
|
||||
"outb %%al, %%dx\n"
|
||||
"1: loop 1b\n"
|
||||
: "=a" (result)
|
||||
: "0" (cmd),
|
||||
"d" (gsmi_dev.smi_cmd),
|
||||
"b" (gsmi_dev.param_buf->address),
|
||||
"c" (spincount)
|
||||
: "memory", "cc"
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* If handshake_type == HANDSHAKE_NONE we do nothing;
|
||||
* either we don't need to or it's legacy firmware that
|
||||
* doesn't understand the CF protocol.
|
||||
*/
|
||||
asm volatile (
|
||||
"outb %%al, %%dx\n\t"
|
||||
: "=a" (result)
|
||||
: "0" (cmd),
|
||||
"d" (gsmi_dev.smi_cmd),
|
||||
"b" (gsmi_dev.param_buf->address)
|
||||
: "memory", "cc"
|
||||
);
|
||||
}
|
||||
|
||||
/* check return code from SMI handler */
|
||||
switch (result) {
|
||||
case GSMI_SUCCESS:
|
||||
break;
|
||||
case GSMI_VAR_NOT_FOUND:
|
||||
/* not really an error, but let the caller know */
|
||||
rc = 1;
|
||||
break;
|
||||
case GSMI_INVALID_PARAMETER:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
case GSMI_BUFFER_TOO_SMALL:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
case GSMI_UNSUPPORTED:
|
||||
case GSMI_UNSUPPORTED2:
|
||||
if (sub != GSMI_CMD_HANDSHAKE_TYPE)
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n",
|
||||
cmd);
|
||||
rc = -ENOSYS;
|
||||
break;
|
||||
case GSMI_NOT_READY:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd);
|
||||
rc = -EBUSY;
|
||||
break;
|
||||
case GSMI_DEVICE_ERROR:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
case GSMI_NOT_FOUND:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd);
|
||||
rc = -ENOENT;
|
||||
break;
|
||||
case GSMI_LOG_FULL:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd);
|
||||
rc = -ENOSPC;
|
||||
break;
|
||||
case GSMI_HANDSHAKE_CF:
|
||||
case GSMI_HANDSHAKE_SPIN:
|
||||
case GSMI_HANDSHAKE_NONE:
|
||||
rc = result;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n",
|
||||
cmd, result);
|
||||
rc = -ENXIO;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static efi_status_t gsmi_get_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor, u32 *attr,
|
||||
unsigned long *data_size,
|
||||
void *data)
|
||||
{
|
||||
struct gsmi_nvram_var_param param = {
|
||||
.name_ptr = gsmi_dev.name_buf->address,
|
||||
.data_ptr = gsmi_dev.data_buf->address,
|
||||
.data_len = (u32)*data_size,
|
||||
};
|
||||
efi_status_t ret = EFI_SUCCESS;
|
||||
unsigned long flags;
|
||||
size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
|
||||
int rc;
|
||||
|
||||
if (name_len >= GSMI_BUF_SIZE / 2)
|
||||
return EFI_BAD_BUFFER_SIZE;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* Vendor guid */
|
||||
memcpy(¶m.guid, vendor, sizeof(param.guid));
|
||||
|
||||
/* variable name, already in UTF-16 */
|
||||
memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
|
||||
memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
|
||||
|
||||
/* data pointer */
|
||||
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
|
||||
|
||||
/* parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NVRAM_VAR);
|
||||
if (rc < 0) {
|
||||
printk(KERN_ERR "gsmi: Get Variable failed\n");
|
||||
ret = EFI_LOAD_ERROR;
|
||||
} else if (rc == 1) {
|
||||
/* variable was not found */
|
||||
ret = EFI_NOT_FOUND;
|
||||
} else {
|
||||
/* Get the arguments back */
|
||||
memcpy(¶m, gsmi_dev.param_buf->start, sizeof(param));
|
||||
|
||||
/* The size reported is the min of all of our buffers */
|
||||
*data_size = min_t(unsigned long, *data_size,
|
||||
gsmi_dev.data_buf->length);
|
||||
*data_size = min_t(unsigned long, *data_size, param.data_len);
|
||||
|
||||
/* Copy data back to return buffer. */
|
||||
memcpy(data, gsmi_dev.data_buf->start, *data_size);
|
||||
|
||||
/* All variables are have the following attributes */
|
||||
*attr = EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static efi_status_t gsmi_get_next_variable(unsigned long *name_size,
|
||||
efi_char16_t *name,
|
||||
efi_guid_t *vendor)
|
||||
{
|
||||
struct gsmi_get_next_var_param param = {
|
||||
.name_ptr = gsmi_dev.name_buf->address,
|
||||
.name_len = gsmi_dev.name_buf->length,
|
||||
};
|
||||
efi_status_t ret = EFI_SUCCESS;
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
|
||||
/* For the moment, only support buffers that exactly match in size */
|
||||
if (*name_size != GSMI_BUF_SIZE)
|
||||
return EFI_BAD_BUFFER_SIZE;
|
||||
|
||||
/* Let's make sure the thing is at least null-terminated */
|
||||
if (ucs2_strnlen(name, GSMI_BUF_SIZE / 2) == GSMI_BUF_SIZE / 2)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* guid */
|
||||
memcpy(¶m.guid, vendor, sizeof(param.guid));
|
||||
|
||||
/* variable name, already in UTF-16 */
|
||||
memcpy(gsmi_dev.name_buf->start, name, *name_size);
|
||||
|
||||
/* parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NEXT_VAR);
|
||||
if (rc < 0) {
|
||||
printk(KERN_ERR "gsmi: Get Next Variable Name failed\n");
|
||||
ret = EFI_LOAD_ERROR;
|
||||
} else if (rc == 1) {
|
||||
/* variable not found -- end of list */
|
||||
ret = EFI_NOT_FOUND;
|
||||
} else {
|
||||
/* copy variable data back to return buffer */
|
||||
memcpy(¶m, gsmi_dev.param_buf->start, sizeof(param));
|
||||
|
||||
/* Copy the name back */
|
||||
memcpy(name, gsmi_dev.name_buf->start, GSMI_BUF_SIZE);
|
||||
*name_size = ucs2_strnlen(name, GSMI_BUF_SIZE / 2) * 2;
|
||||
|
||||
/* copy guid to return buffer */
|
||||
memcpy(vendor, ¶m.guid, sizeof(param.guid));
|
||||
ret = EFI_SUCCESS;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static efi_status_t gsmi_set_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor,
|
||||
u32 attr,
|
||||
unsigned long data_size,
|
||||
void *data)
|
||||
{
|
||||
struct gsmi_nvram_var_param param = {
|
||||
.name_ptr = gsmi_dev.name_buf->address,
|
||||
.data_ptr = gsmi_dev.data_buf->address,
|
||||
.data_len = (u32)data_size,
|
||||
.attributes = EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS,
|
||||
};
|
||||
size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2);
|
||||
efi_status_t ret = EFI_SUCCESS;
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
|
||||
if (name_len >= GSMI_BUF_SIZE / 2)
|
||||
return EFI_BAD_BUFFER_SIZE;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* guid */
|
||||
memcpy(¶m.guid, vendor, sizeof(param.guid));
|
||||
|
||||
/* variable name, already in UTF-16 */
|
||||
memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length);
|
||||
memcpy(gsmi_dev.name_buf->start, name, name_len * 2);
|
||||
|
||||
/* data pointer */
|
||||
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
|
||||
memcpy(gsmi_dev.data_buf->start, data, data_size);
|
||||
|
||||
/* parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_NVRAM_VAR);
|
||||
if (rc < 0) {
|
||||
printk(KERN_ERR "gsmi: Set Variable failed\n");
|
||||
ret = EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct efivar_operations efivar_ops = {
|
||||
.get_variable = gsmi_get_variable,
|
||||
.set_variable = gsmi_set_variable,
|
||||
.get_next_variable = gsmi_get_next_variable,
|
||||
};
|
||||
|
||||
static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct gsmi_set_eventlog_param param = {
|
||||
.data_ptr = gsmi_dev.data_buf->address,
|
||||
};
|
||||
int rc = 0;
|
||||
unsigned long flags;
|
||||
|
||||
/* Pull the type out */
|
||||
if (count < sizeof(u32))
|
||||
return -EINVAL;
|
||||
param.type = *(u32 *)buf;
|
||||
count -= sizeof(u32);
|
||||
buf += sizeof(u32);
|
||||
|
||||
/* The remaining buffer is the data payload */
|
||||
if (count > gsmi_dev.data_buf->length)
|
||||
return -EINVAL;
|
||||
param.data_len = count - sizeof(u32);
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* data pointer */
|
||||
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
|
||||
memcpy(gsmi_dev.data_buf->start, buf, param.data_len);
|
||||
|
||||
/* parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
|
||||
if (rc < 0)
|
||||
printk(KERN_ERR "gsmi: Set Event Log failed\n");
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
static struct bin_attribute eventlog_bin_attr = {
|
||||
.attr = {.name = "append_to_eventlog", .mode = 0200},
|
||||
.write = eventlog_write,
|
||||
};
|
||||
|
||||
static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
unsigned long val;
|
||||
struct {
|
||||
u32 percentage;
|
||||
u32 data_type;
|
||||
} param;
|
||||
|
||||
rc = kstrtoul(buf, 0, &val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Value entered is a percentage, 0 through 100, anything else
|
||||
* is invalid.
|
||||
*/
|
||||
if (val > 100)
|
||||
return -EINVAL;
|
||||
|
||||
/* data_type here selects the smbios event log. */
|
||||
param.percentage = val;
|
||||
param.data_type = 0;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_EVENT_LOG);
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute gsmi_clear_eventlog_attr = {
|
||||
.attr = {.name = "clear_eventlog", .mode = 0200},
|
||||
.store = gsmi_clear_eventlog_store,
|
||||
};
|
||||
|
||||
static ssize_t gsmi_clear_config_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
/* clear parameter buffer */
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_CONFIG);
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute gsmi_clear_config_attr = {
|
||||
.attr = {.name = "clear_config", .mode = 0200},
|
||||
.store = gsmi_clear_config_store,
|
||||
};
|
||||
|
||||
static const struct attribute *gsmi_attrs[] = {
|
||||
&gsmi_clear_config_attr.attr,
|
||||
&gsmi_clear_eventlog_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int gsmi_shutdown_reason(int reason)
|
||||
{
|
||||
struct gsmi_log_entry_type_1 entry = {
|
||||
.type = GSMI_LOG_ENTRY_TYPE_KERNEL,
|
||||
.instance = reason,
|
||||
};
|
||||
struct gsmi_set_eventlog_param param = {
|
||||
.data_len = sizeof(entry),
|
||||
.type = 1,
|
||||
};
|
||||
static int saved_reason;
|
||||
int rc = 0;
|
||||
unsigned long flags;
|
||||
|
||||
/* avoid duplicate entries in the log */
|
||||
if (saved_reason & (1 << reason))
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
saved_reason |= (1 << reason);
|
||||
|
||||
/* data pointer */
|
||||
memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length);
|
||||
memcpy(gsmi_dev.data_buf->start, &entry, sizeof(entry));
|
||||
|
||||
/* parameter buffer */
|
||||
param.data_ptr = gsmi_dev.data_buf->address;
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param));
|
||||
|
||||
rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG);
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
if (rc < 0)
|
||||
printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n");
|
||||
else
|
||||
printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n",
|
||||
reason);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int gsmi_reboot_callback(struct notifier_block *nb,
|
||||
unsigned long reason, void *arg)
|
||||
{
|
||||
gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block gsmi_reboot_notifier = {
|
||||
.notifier_call = gsmi_reboot_callback
|
||||
};
|
||||
|
||||
static int gsmi_die_callback(struct notifier_block *nb,
|
||||
unsigned long reason, void *arg)
|
||||
{
|
||||
if (reason == DIE_OOPS)
|
||||
gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block gsmi_die_notifier = {
|
||||
.notifier_call = gsmi_die_callback
|
||||
};
|
||||
|
||||
static int gsmi_panic_callback(struct notifier_block *nb,
|
||||
unsigned long reason, void *arg)
|
||||
{
|
||||
gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block gsmi_panic_notifier = {
|
||||
.notifier_call = gsmi_panic_callback,
|
||||
};
|
||||
|
||||
/*
|
||||
* This hash function was blatantly copied from include/linux/hash.h.
|
||||
* It is used by this driver to obfuscate a board name that requires a
|
||||
* quirk within this driver.
|
||||
*
|
||||
* Please do not remove this copy of the function as any changes to the
|
||||
* global utility hash_64() function would break this driver's ability
|
||||
* to identify a board and provide the appropriate quirk -- mikew@google.com
|
||||
*/
|
||||
static u64 __init local_hash_64(u64 val, unsigned bits)
|
||||
{
|
||||
u64 hash = val;
|
||||
|
||||
/* Sigh, gcc can't optimise this alone like it does for 32 bits. */
|
||||
u64 n = hash;
|
||||
n <<= 18;
|
||||
hash -= n;
|
||||
n <<= 33;
|
||||
hash -= n;
|
||||
n <<= 3;
|
||||
hash += n;
|
||||
n <<= 3;
|
||||
hash -= n;
|
||||
n <<= 4;
|
||||
hash += n;
|
||||
n <<= 2;
|
||||
hash += n;
|
||||
|
||||
/* High bits are more random, so use them. */
|
||||
return hash >> (64 - bits);
|
||||
}
|
||||
|
||||
static u32 __init hash_oem_table_id(char s[8])
|
||||
{
|
||||
u64 input;
|
||||
memcpy(&input, s, 8);
|
||||
return local_hash_64(input, 32);
|
||||
}
|
||||
|
||||
static struct dmi_system_id gsmi_dmi_table[] __initdata = {
|
||||
{
|
||||
.ident = "Google Board",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
|
||||
|
||||
static __init int gsmi_system_valid(void)
|
||||
{
|
||||
u32 hash;
|
||||
|
||||
if (!dmi_check_system(gsmi_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Only newer firmware supports the gsmi interface. All older
|
||||
* firmware that didn't support this interface used to plug the
|
||||
* table name in the first four bytes of the oem_table_id field.
|
||||
* Newer firmware doesn't do that though, so use that as the
|
||||
* discriminant factor. We have to do this in order to
|
||||
* whitewash our board names out of the public driver.
|
||||
*/
|
||||
if (!strncmp(acpi_gbl_FADT.header.oem_table_id, "FACP", 4)) {
|
||||
printk(KERN_INFO "gsmi: Board is too old\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Disable on board with 1.0 BIOS due to Google bug 2602657 */
|
||||
hash = hash_oem_table_id(acpi_gbl_FADT.header.oem_table_id);
|
||||
if (hash == QUIRKY_BOARD_HASH) {
|
||||
const char *bios_ver = dmi_get_system_info(DMI_BIOS_VERSION);
|
||||
if (strncmp(bios_ver, "1.0", 3) == 0) {
|
||||
pr_info("gsmi: disabled on this board's BIOS %s\n",
|
||||
bios_ver);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for valid SMI command port in ACPI FADT */
|
||||
if (acpi_gbl_FADT.smi_command == 0) {
|
||||
pr_info("gsmi: missing smi_command\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Found */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kobject *gsmi_kobj;
|
||||
static struct efivars efivars;
|
||||
|
||||
static const struct platform_device_info gsmi_dev_info = {
|
||||
.name = "gsmi",
|
||||
.id = -1,
|
||||
/* SMI callbacks require 32bit addresses */
|
||||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
static __init int gsmi_init(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
ret = gsmi_system_valid();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
|
||||
|
||||
/* register device */
|
||||
gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info);
|
||||
if (IS_ERR(gsmi_dev.pdev)) {
|
||||
printk(KERN_ERR "gsmi: unable to register platform device\n");
|
||||
return PTR_ERR(gsmi_dev.pdev);
|
||||
}
|
||||
|
||||
/* SMI access needs to be serialized */
|
||||
spin_lock_init(&gsmi_dev.lock);
|
||||
|
||||
ret = -ENOMEM;
|
||||
gsmi_dev.dma_pool = dma_pool_create("gsmi", &gsmi_dev.pdev->dev,
|
||||
GSMI_BUF_SIZE, GSMI_BUF_ALIGN, 0);
|
||||
if (!gsmi_dev.dma_pool)
|
||||
goto out_err;
|
||||
|
||||
/*
|
||||
* pre-allocate buffers because sometimes we are called when
|
||||
* this is not feasible: oops, panic, die, mce, etc
|
||||
*/
|
||||
gsmi_dev.name_buf = gsmi_buf_alloc();
|
||||
if (!gsmi_dev.name_buf) {
|
||||
printk(KERN_ERR "gsmi: failed to allocate name buffer\n");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
gsmi_dev.data_buf = gsmi_buf_alloc();
|
||||
if (!gsmi_dev.data_buf) {
|
||||
printk(KERN_ERR "gsmi: failed to allocate data buffer\n");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
gsmi_dev.param_buf = gsmi_buf_alloc();
|
||||
if (!gsmi_dev.param_buf) {
|
||||
printk(KERN_ERR "gsmi: failed to allocate param buffer\n");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine type of handshake used to serialize the SMI
|
||||
* entry. See also gsmi_exec().
|
||||
*
|
||||
* There's a "behavior" present on some chipsets where writing the
|
||||
* SMI trigger register in the southbridge doesn't result in an
|
||||
* immediate SMI. Rather, the processor can execute "a few" more
|
||||
* instructions before the SMI takes effect. To ensure synchronous
|
||||
* behavior, implement a handshake between the kernel driver and the
|
||||
* firmware handler to spin until released. This ioctl determines
|
||||
* the type of handshake.
|
||||
*
|
||||
* NONE: The firmware handler does not implement any
|
||||
* handshake. Either it doesn't need to, or it's legacy firmware
|
||||
* that doesn't know it needs to and never will.
|
||||
*
|
||||
* CF: The firmware handler will clear the CF in the saved
|
||||
* state before returning. The driver may set the CF and test for
|
||||
* it to clear before proceeding.
|
||||
*
|
||||
* SPIN: The firmware handler does not implement any handshake
|
||||
* but the driver should spin for a hundred or so microseconds
|
||||
* to ensure the SMI has triggered.
|
||||
*
|
||||
* Finally, the handler will return -ENOSYS if
|
||||
* GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies
|
||||
* HANDSHAKE_NONE.
|
||||
*/
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN;
|
||||
gsmi_dev.handshake_type =
|
||||
gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE);
|
||||
if (gsmi_dev.handshake_type == -ENOSYS)
|
||||
gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE;
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
/* Remove and clean up gsmi if the handshake could not complete. */
|
||||
if (gsmi_dev.handshake_type == -ENXIO) {
|
||||
printk(KERN_INFO "gsmi version " DRIVER_VERSION
|
||||
" failed to load\n");
|
||||
ret = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* Register in the firmware directory */
|
||||
ret = -ENOMEM;
|
||||
gsmi_kobj = kobject_create_and_add("gsmi", firmware_kobj);
|
||||
if (!gsmi_kobj) {
|
||||
printk(KERN_INFO "gsmi: Failed to create firmware kobj\n");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* Setup eventlog access */
|
||||
ret = sysfs_create_bin_file(gsmi_kobj, &eventlog_bin_attr);
|
||||
if (ret) {
|
||||
printk(KERN_INFO "gsmi: Failed to setup eventlog");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* Other attributes */
|
||||
ret = sysfs_create_files(gsmi_kobj, gsmi_attrs);
|
||||
if (ret) {
|
||||
printk(KERN_INFO "gsmi: Failed to add attrs");
|
||||
goto out_remove_bin_file;
|
||||
}
|
||||
|
||||
ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj);
|
||||
if (ret) {
|
||||
printk(KERN_INFO "gsmi: Failed to register efivars\n");
|
||||
goto out_remove_sysfs_files;
|
||||
}
|
||||
|
||||
register_reboot_notifier(&gsmi_reboot_notifier);
|
||||
register_die_notifier(&gsmi_die_notifier);
|
||||
atomic_notifier_chain_register(&panic_notifier_list,
|
||||
&gsmi_panic_notifier);
|
||||
|
||||
printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_remove_sysfs_files:
|
||||
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
|
||||
out_remove_bin_file:
|
||||
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
|
||||
out_err:
|
||||
kobject_put(gsmi_kobj);
|
||||
gsmi_buf_free(gsmi_dev.param_buf);
|
||||
gsmi_buf_free(gsmi_dev.data_buf);
|
||||
gsmi_buf_free(gsmi_dev.name_buf);
|
||||
if (gsmi_dev.dma_pool)
|
||||
dma_pool_destroy(gsmi_dev.dma_pool);
|
||||
platform_device_unregister(gsmi_dev.pdev);
|
||||
pr_info("gsmi: failed to load: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit gsmi_exit(void)
|
||||
{
|
||||
unregister_reboot_notifier(&gsmi_reboot_notifier);
|
||||
unregister_die_notifier(&gsmi_die_notifier);
|
||||
atomic_notifier_chain_unregister(&panic_notifier_list,
|
||||
&gsmi_panic_notifier);
|
||||
efivars_unregister(&efivars);
|
||||
|
||||
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
|
||||
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
|
||||
kobject_put(gsmi_kobj);
|
||||
gsmi_buf_free(gsmi_dev.param_buf);
|
||||
gsmi_buf_free(gsmi_dev.data_buf);
|
||||
gsmi_buf_free(gsmi_dev.name_buf);
|
||||
dma_pool_destroy(gsmi_dev.dma_pool);
|
||||
platform_device_unregister(gsmi_dev.pdev);
|
||||
}
|
||||
|
||||
module_init(gsmi_init);
|
||||
module_exit(gsmi_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
171
drivers/firmware/google/memconsole.c
Normal file
171
drivers/firmware/google/memconsole.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* memconsole.c
|
||||
*
|
||||
* Infrastructure for importing the BIOS memory based console
|
||||
* into the kernel log ringbuffer.
|
||||
*
|
||||
* Copyright 2010 Google Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/bios_ebda.h>
|
||||
|
||||
#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
|
||||
#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
|
||||
|
||||
struct biosmemcon_ebda {
|
||||
u32 signature;
|
||||
union {
|
||||
struct {
|
||||
u8 enabled;
|
||||
u32 buffer_addr;
|
||||
u16 start;
|
||||
u16 end;
|
||||
u16 num_chars;
|
||||
u8 wrapped;
|
||||
} __packed v1;
|
||||
struct {
|
||||
u32 buffer_addr;
|
||||
/* Misdocumented as number of pages! */
|
||||
u16 num_bytes;
|
||||
u16 start;
|
||||
u16 end;
|
||||
} __packed v2;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
static u32 memconsole_baseaddr;
|
||||
static size_t memconsole_length;
|
||||
|
||||
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
char *memconsole;
|
||||
ssize_t ret;
|
||||
|
||||
memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length);
|
||||
if (!memconsole) {
|
||||
pr_err("memconsole: ioremap_cache failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = memory_read_from_buffer(buf, count, &pos, memconsole,
|
||||
memconsole_length);
|
||||
iounmap(memconsole);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bin_attribute memconsole_bin_attr = {
|
||||
.attr = {.name = "log", .mode = 0444},
|
||||
.read = memconsole_read,
|
||||
};
|
||||
|
||||
|
||||
static void __init found_v1_header(struct biosmemcon_ebda *hdr)
|
||||
{
|
||||
pr_info("BIOS console v1 EBDA structure found at %p\n", hdr);
|
||||
pr_info("BIOS console buffer at 0x%.8x, "
|
||||
"start = %d, end = %d, num = %d\n",
|
||||
hdr->v1.buffer_addr, hdr->v1.start,
|
||||
hdr->v1.end, hdr->v1.num_chars);
|
||||
|
||||
memconsole_length = hdr->v1.num_chars;
|
||||
memconsole_baseaddr = hdr->v1.buffer_addr;
|
||||
}
|
||||
|
||||
static void __init found_v2_header(struct biosmemcon_ebda *hdr)
|
||||
{
|
||||
pr_info("BIOS console v2 EBDA structure found at %p\n", hdr);
|
||||
pr_info("BIOS console buffer at 0x%.8x, "
|
||||
"start = %d, end = %d, num_bytes = %d\n",
|
||||
hdr->v2.buffer_addr, hdr->v2.start,
|
||||
hdr->v2.end, hdr->v2.num_bytes);
|
||||
|
||||
memconsole_length = hdr->v2.end - hdr->v2.start;
|
||||
memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search through the EBDA for the BIOS Memory Console, and
|
||||
* set the global variables to point to it. Return true if found.
|
||||
*/
|
||||
static bool __init found_memconsole(void)
|
||||
{
|
||||
unsigned int address;
|
||||
size_t length, cur;
|
||||
|
||||
address = get_bios_ebda();
|
||||
if (!address) {
|
||||
pr_info("BIOS EBDA non-existent.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* EBDA length is byte 0 of EBDA (in KB) */
|
||||
length = *(u8 *)phys_to_virt(address);
|
||||
length <<= 10; /* convert to bytes */
|
||||
|
||||
/*
|
||||
* Search through EBDA for BIOS memory console structure
|
||||
* note: signature is not necessarily dword-aligned
|
||||
*/
|
||||
for (cur = 0; cur < length; cur++) {
|
||||
struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
|
||||
|
||||
/* memconsole v1 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
|
||||
found_v1_header(hdr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* memconsole v2 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
|
||||
found_v2_header(hdr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("BIOS console EBDA structure not found!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct dmi_system_id memconsole_dmi_table[] __initdata = {
|
||||
{
|
||||
.ident = "Google Board",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
|
||||
|
||||
static int __init memconsole_init(void)
|
||||
{
|
||||
if (!dmi_check_system(memconsole_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
if (!found_memconsole())
|
||||
return -ENODEV;
|
||||
|
||||
memconsole_bin_attr.size = memconsole_length;
|
||||
return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
|
||||
}
|
||||
|
||||
static void __exit memconsole_exit(void)
|
||||
{
|
||||
sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
|
||||
}
|
||||
|
||||
module_init(memconsole_init);
|
||||
module_exit(memconsole_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
822
drivers/firmware/iscsi_ibft.c
Normal file
822
drivers/firmware/iscsi_ibft.c
Normal file
|
@ -0,0 +1,822 @@
|
|||
/*
|
||||
* Copyright 2007-2010 Red Hat, Inc.
|
||||
* by Peter Jones <pjones@redhat.com>
|
||||
* Copyright 2008 IBM, Inc.
|
||||
* by Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Copyright 2008
|
||||
* by Konrad Rzeszutek <ketuzsezr@darnok.org>
|
||||
*
|
||||
* This code exposes the iSCSI Boot Format Table to userland via sysfs.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 06 Jan 2010 - Peter Jones <pjones@redhat.com>
|
||||
* New changelog entries are in the git log from now on. Not here.
|
||||
*
|
||||
* 14 Mar 2008 - Konrad Rzeszutek <ketuzsezr@darnok.org>
|
||||
* Updated comments and copyrights. (v0.4.9)
|
||||
*
|
||||
* 11 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Converted to using ibft_addr. (v0.4.8)
|
||||
*
|
||||
* 8 Feb 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Combined two functions in one: reserve_ibft_region. (v0.4.7)
|
||||
*
|
||||
* 30 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added logic to handle IPv6 addresses. (v0.4.6)
|
||||
*
|
||||
* 25 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added logic to handle badly not-to-spec iBFT. (v0.4.5)
|
||||
*
|
||||
* 4 Jan 2008 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added __init to function declarations. (v0.4.4)
|
||||
*
|
||||
* 21 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Updated kobject registration, combined unregister functions in one
|
||||
* and code and style cleanup. (v0.4.3)
|
||||
*
|
||||
* 5 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added end-markers to enums and re-organized kobject registration. (v0.4.2)
|
||||
*
|
||||
* 4 Dec 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Created 'device' sysfs link to the NIC and style cleanup. (v0.4.1)
|
||||
*
|
||||
* 28 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added sysfs-ibft documentation, moved 'find_ibft' function to
|
||||
* in its own file and added text attributes for every struct field. (v0.4)
|
||||
*
|
||||
* 21 Nov 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added text attributes emulating OpenFirmware /proc/device-tree naming.
|
||||
* Removed binary /sysfs interface (v0.3)
|
||||
*
|
||||
* 29 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Added functionality in setup.c to reserve iBFT region. (v0.2)
|
||||
*
|
||||
* 27 Aug 2007 - Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* First version exposing iBFT data via a binary /sysfs. (v0.1)
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/iscsi_ibft.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/iscsi_boot_sysfs.h>
|
||||
|
||||
#define IBFT_ISCSI_VERSION "0.5.0"
|
||||
#define IBFT_ISCSI_DATE "2010-Feb-25"
|
||||
|
||||
MODULE_AUTHOR("Peter Jones <pjones@redhat.com> and "
|
||||
"Konrad Rzeszutek <ketuzsezr@darnok.org>");
|
||||
MODULE_DESCRIPTION("sysfs interface to BIOS iBFT information");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(IBFT_ISCSI_VERSION);
|
||||
|
||||
struct ibft_hdr {
|
||||
u8 id;
|
||||
u8 version;
|
||||
u16 length;
|
||||
u8 index;
|
||||
u8 flags;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ibft_control {
|
||||
struct ibft_hdr hdr;
|
||||
u16 extensions;
|
||||
u16 initiator_off;
|
||||
u16 nic0_off;
|
||||
u16 tgt0_off;
|
||||
u16 nic1_off;
|
||||
u16 tgt1_off;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ibft_initiator {
|
||||
struct ibft_hdr hdr;
|
||||
char isns_server[16];
|
||||
char slp_server[16];
|
||||
char pri_radius_server[16];
|
||||
char sec_radius_server[16];
|
||||
u16 initiator_name_len;
|
||||
u16 initiator_name_off;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ibft_nic {
|
||||
struct ibft_hdr hdr;
|
||||
char ip_addr[16];
|
||||
u8 subnet_mask_prefix;
|
||||
u8 origin;
|
||||
char gateway[16];
|
||||
char primary_dns[16];
|
||||
char secondary_dns[16];
|
||||
char dhcp[16];
|
||||
u16 vlan;
|
||||
char mac[6];
|
||||
u16 pci_bdf;
|
||||
u16 hostname_len;
|
||||
u16 hostname_off;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ibft_tgt {
|
||||
struct ibft_hdr hdr;
|
||||
char ip_addr[16];
|
||||
u16 port;
|
||||
char lun[8];
|
||||
u8 chap_type;
|
||||
u8 nic_assoc;
|
||||
u16 tgt_name_len;
|
||||
u16 tgt_name_off;
|
||||
u16 chap_name_len;
|
||||
u16 chap_name_off;
|
||||
u16 chap_secret_len;
|
||||
u16 chap_secret_off;
|
||||
u16 rev_chap_name_len;
|
||||
u16 rev_chap_name_off;
|
||||
u16 rev_chap_secret_len;
|
||||
u16 rev_chap_secret_off;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
/*
|
||||
* The kobject different types and its names.
|
||||
*
|
||||
*/
|
||||
enum ibft_id {
|
||||
id_reserved = 0, /* We don't support. */
|
||||
id_control = 1, /* Should show up only once and is not exported. */
|
||||
id_initiator = 2,
|
||||
id_nic = 3,
|
||||
id_target = 4,
|
||||
id_extensions = 5, /* We don't support. */
|
||||
id_end_marker,
|
||||
};
|
||||
|
||||
/*
|
||||
* The kobject and attribute structures.
|
||||
*/
|
||||
|
||||
struct ibft_kobject {
|
||||
struct acpi_table_ibft *header;
|
||||
union {
|
||||
struct ibft_initiator *initiator;
|
||||
struct ibft_nic *nic;
|
||||
struct ibft_tgt *tgt;
|
||||
struct ibft_hdr *hdr;
|
||||
};
|
||||
};
|
||||
|
||||
static struct iscsi_boot_kset *boot_kset;
|
||||
|
||||
static const char nulls[16];
|
||||
|
||||
/*
|
||||
* Helper functions to parse data properly.
|
||||
*/
|
||||
static ssize_t sprintf_ipaddr(char *buf, u8 *ip)
|
||||
{
|
||||
char *str = buf;
|
||||
|
||||
if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 &&
|
||||
ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 &&
|
||||
ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff) {
|
||||
/*
|
||||
* IPV4
|
||||
*/
|
||||
str += sprintf(buf, "%pI4", ip + 12);
|
||||
} else {
|
||||
/*
|
||||
* IPv6
|
||||
*/
|
||||
str += sprintf(str, "%pI6", ip);
|
||||
}
|
||||
str += sprintf(str, "\n");
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t sprintf_string(char *str, int len, char *buf)
|
||||
{
|
||||
return sprintf(str, "%.*s\n", len, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to verify the IBFT header.
|
||||
*/
|
||||
static int ibft_verify_hdr(char *t, struct ibft_hdr *hdr, int id, int length)
|
||||
{
|
||||
if (hdr->id != id) {
|
||||
printk(KERN_ERR "iBFT error: We expected the %s " \
|
||||
"field header.id to have %d but " \
|
||||
"found %d instead!\n", t, id, hdr->id);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (hdr->length != length) {
|
||||
printk(KERN_ERR "iBFT error: We expected the %s " \
|
||||
"field header.length to have %d but " \
|
||||
"found %d instead!\n", t, length, hdr->length);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Routines for parsing the iBFT data to be human readable.
|
||||
*/
|
||||
static ssize_t ibft_attr_show_initiator(void *data, int type, char *buf)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_initiator *initiator = entry->initiator;
|
||||
void *ibft_loc = entry->header;
|
||||
char *str = buf;
|
||||
|
||||
if (!initiator)
|
||||
return 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_INI_INDEX:
|
||||
str += sprintf(str, "%d\n", initiator->hdr.index);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_FLAGS:
|
||||
str += sprintf(str, "%d\n", initiator->hdr.flags);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_ISNS_SERVER:
|
||||
str += sprintf_ipaddr(str, initiator->isns_server);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_SLP_SERVER:
|
||||
str += sprintf_ipaddr(str, initiator->slp_server);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_PRI_RADIUS_SERVER:
|
||||
str += sprintf_ipaddr(str, initiator->pri_radius_server);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_SEC_RADIUS_SERVER:
|
||||
str += sprintf_ipaddr(str, initiator->sec_radius_server);
|
||||
break;
|
||||
case ISCSI_BOOT_INI_INITIATOR_NAME:
|
||||
str += sprintf_string(str, initiator->initiator_name_len,
|
||||
(char *)ibft_loc +
|
||||
initiator->initiator_name_off);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t ibft_attr_show_nic(void *data, int type, char *buf)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_nic *nic = entry->nic;
|
||||
void *ibft_loc = entry->header;
|
||||
char *str = buf;
|
||||
__be32 val;
|
||||
|
||||
if (!nic)
|
||||
return 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_ETH_INDEX:
|
||||
str += sprintf(str, "%d\n", nic->hdr.index);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_FLAGS:
|
||||
str += sprintf(str, "%d\n", nic->hdr.flags);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_IP_ADDR:
|
||||
str += sprintf_ipaddr(str, nic->ip_addr);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_SUBNET_MASK:
|
||||
val = cpu_to_be32(~((1 << (32-nic->subnet_mask_prefix))-1));
|
||||
str += sprintf(str, "%pI4", &val);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_ORIGIN:
|
||||
str += sprintf(str, "%d\n", nic->origin);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_GATEWAY:
|
||||
str += sprintf_ipaddr(str, nic->gateway);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_PRIMARY_DNS:
|
||||
str += sprintf_ipaddr(str, nic->primary_dns);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_SECONDARY_DNS:
|
||||
str += sprintf_ipaddr(str, nic->secondary_dns);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_DHCP:
|
||||
str += sprintf_ipaddr(str, nic->dhcp);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_VLAN:
|
||||
str += sprintf(str, "%d\n", nic->vlan);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_MAC:
|
||||
str += sprintf(str, "%pM\n", nic->mac);
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_HOSTNAME:
|
||||
str += sprintf_string(str, nic->hostname_len,
|
||||
(char *)ibft_loc + nic->hostname_off);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str - buf;
|
||||
};
|
||||
|
||||
static ssize_t ibft_attr_show_target(void *data, int type, char *buf)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_tgt *tgt = entry->tgt;
|
||||
void *ibft_loc = entry->header;
|
||||
char *str = buf;
|
||||
int i;
|
||||
|
||||
if (!tgt)
|
||||
return 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_TGT_INDEX:
|
||||
str += sprintf(str, "%d\n", tgt->hdr.index);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_FLAGS:
|
||||
str += sprintf(str, "%d\n", tgt->hdr.flags);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_IP_ADDR:
|
||||
str += sprintf_ipaddr(str, tgt->ip_addr);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_PORT:
|
||||
str += sprintf(str, "%d\n", tgt->port);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_LUN:
|
||||
for (i = 0; i < 8; i++)
|
||||
str += sprintf(str, "%x", (u8)tgt->lun[i]);
|
||||
str += sprintf(str, "\n");
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_NIC_ASSOC:
|
||||
str += sprintf(str, "%d\n", tgt->nic_assoc);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_CHAP_TYPE:
|
||||
str += sprintf(str, "%d\n", tgt->chap_type);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_NAME:
|
||||
str += sprintf_string(str, tgt->tgt_name_len,
|
||||
(char *)ibft_loc + tgt->tgt_name_off);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_CHAP_NAME:
|
||||
str += sprintf_string(str, tgt->chap_name_len,
|
||||
(char *)ibft_loc + tgt->chap_name_off);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_CHAP_SECRET:
|
||||
str += sprintf_string(str, tgt->chap_secret_len,
|
||||
(char *)ibft_loc + tgt->chap_secret_off);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_REV_CHAP_NAME:
|
||||
str += sprintf_string(str, tgt->rev_chap_name_len,
|
||||
(char *)ibft_loc +
|
||||
tgt->rev_chap_name_off);
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_REV_CHAP_SECRET:
|
||||
str += sprintf_string(str, tgt->rev_chap_secret_len,
|
||||
(char *)ibft_loc +
|
||||
tgt->rev_chap_secret_off);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static int __init ibft_check_device(void)
|
||||
{
|
||||
int len;
|
||||
u8 *pos;
|
||||
u8 csum = 0;
|
||||
|
||||
len = ibft_addr->header.length;
|
||||
|
||||
/* Sanity checking of iBFT. */
|
||||
if (ibft_addr->header.revision != 1) {
|
||||
printk(KERN_ERR "iBFT module supports only revision 1, " \
|
||||
"while this is %d.\n",
|
||||
ibft_addr->header.revision);
|
||||
return -ENOENT;
|
||||
}
|
||||
for (pos = (u8 *)ibft_addr; pos < (u8 *)ibft_addr + len; pos++)
|
||||
csum += *pos;
|
||||
|
||||
if (csum) {
|
||||
printk(KERN_ERR "iBFT has incorrect checksum (0x%x)!\n", csum);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper routiners to check to determine if the entry is valid
|
||||
* in the proper iBFT structure.
|
||||
*/
|
||||
static umode_t ibft_check_nic_for(void *data, int type)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_nic *nic = entry->nic;
|
||||
umode_t rc = 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_ETH_INDEX:
|
||||
case ISCSI_BOOT_ETH_FLAGS:
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_IP_ADDR:
|
||||
if (memcmp(nic->ip_addr, nulls, sizeof(nic->ip_addr)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_SUBNET_MASK:
|
||||
if (nic->subnet_mask_prefix)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_ORIGIN:
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_GATEWAY:
|
||||
if (memcmp(nic->gateway, nulls, sizeof(nic->gateway)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_PRIMARY_DNS:
|
||||
if (memcmp(nic->primary_dns, nulls,
|
||||
sizeof(nic->primary_dns)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_SECONDARY_DNS:
|
||||
if (memcmp(nic->secondary_dns, nulls,
|
||||
sizeof(nic->secondary_dns)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_DHCP:
|
||||
if (memcmp(nic->dhcp, nulls, sizeof(nic->dhcp)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_VLAN:
|
||||
case ISCSI_BOOT_ETH_MAC:
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_ETH_HOSTNAME:
|
||||
if (nic->hostname_off)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static umode_t __init ibft_check_tgt_for(void *data, int type)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_tgt *tgt = entry->tgt;
|
||||
umode_t rc = 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_TGT_INDEX:
|
||||
case ISCSI_BOOT_TGT_FLAGS:
|
||||
case ISCSI_BOOT_TGT_IP_ADDR:
|
||||
case ISCSI_BOOT_TGT_PORT:
|
||||
case ISCSI_BOOT_TGT_LUN:
|
||||
case ISCSI_BOOT_TGT_NIC_ASSOC:
|
||||
case ISCSI_BOOT_TGT_CHAP_TYPE:
|
||||
rc = S_IRUGO;
|
||||
case ISCSI_BOOT_TGT_NAME:
|
||||
if (tgt->tgt_name_len)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_CHAP_NAME:
|
||||
case ISCSI_BOOT_TGT_CHAP_SECRET:
|
||||
if (tgt->chap_name_len)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_TGT_REV_CHAP_NAME:
|
||||
case ISCSI_BOOT_TGT_REV_CHAP_SECRET:
|
||||
if (tgt->rev_chap_name_len)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static umode_t __init ibft_check_initiator_for(void *data, int type)
|
||||
{
|
||||
struct ibft_kobject *entry = data;
|
||||
struct ibft_initiator *init = entry->initiator;
|
||||
umode_t rc = 0;
|
||||
|
||||
switch (type) {
|
||||
case ISCSI_BOOT_INI_INDEX:
|
||||
case ISCSI_BOOT_INI_FLAGS:
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_INI_ISNS_SERVER:
|
||||
if (memcmp(init->isns_server, nulls,
|
||||
sizeof(init->isns_server)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_INI_SLP_SERVER:
|
||||
if (memcmp(init->slp_server, nulls,
|
||||
sizeof(init->slp_server)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_INI_PRI_RADIUS_SERVER:
|
||||
if (memcmp(init->pri_radius_server, nulls,
|
||||
sizeof(init->pri_radius_server)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_INI_SEC_RADIUS_SERVER:
|
||||
if (memcmp(init->sec_radius_server, nulls,
|
||||
sizeof(init->sec_radius_server)))
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
case ISCSI_BOOT_INI_INITIATOR_NAME:
|
||||
if (init->initiator_name_len)
|
||||
rc = S_IRUGO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ibft_kobj_release(void *data)
|
||||
{
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for ibft_register_kobjects.
|
||||
*/
|
||||
static int __init ibft_create_kobject(struct acpi_table_ibft *header,
|
||||
struct ibft_hdr *hdr)
|
||||
{
|
||||
struct iscsi_boot_kobj *boot_kobj = NULL;
|
||||
struct ibft_kobject *ibft_kobj = NULL;
|
||||
struct ibft_nic *nic = (struct ibft_nic *)hdr;
|
||||
struct pci_dev *pci_dev;
|
||||
int rc = 0;
|
||||
|
||||
ibft_kobj = kzalloc(sizeof(*ibft_kobj), GFP_KERNEL);
|
||||
if (!ibft_kobj)
|
||||
return -ENOMEM;
|
||||
|
||||
ibft_kobj->header = header;
|
||||
ibft_kobj->hdr = hdr;
|
||||
|
||||
switch (hdr->id) {
|
||||
case id_initiator:
|
||||
rc = ibft_verify_hdr("initiator", hdr, id_initiator,
|
||||
sizeof(*ibft_kobj->initiator));
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
boot_kobj = iscsi_boot_create_initiator(boot_kset, hdr->index,
|
||||
ibft_kobj,
|
||||
ibft_attr_show_initiator,
|
||||
ibft_check_initiator_for,
|
||||
ibft_kobj_release);
|
||||
if (!boot_kobj) {
|
||||
rc = -ENOMEM;
|
||||
goto free_ibft_obj;
|
||||
}
|
||||
break;
|
||||
case id_nic:
|
||||
rc = ibft_verify_hdr("ethernet", hdr, id_nic,
|
||||
sizeof(*ibft_kobj->nic));
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
boot_kobj = iscsi_boot_create_ethernet(boot_kset, hdr->index,
|
||||
ibft_kobj,
|
||||
ibft_attr_show_nic,
|
||||
ibft_check_nic_for,
|
||||
ibft_kobj_release);
|
||||
if (!boot_kobj) {
|
||||
rc = -ENOMEM;
|
||||
goto free_ibft_obj;
|
||||
}
|
||||
break;
|
||||
case id_target:
|
||||
rc = ibft_verify_hdr("target", hdr, id_target,
|
||||
sizeof(*ibft_kobj->tgt));
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
boot_kobj = iscsi_boot_create_target(boot_kset, hdr->index,
|
||||
ibft_kobj,
|
||||
ibft_attr_show_target,
|
||||
ibft_check_tgt_for,
|
||||
ibft_kobj_release);
|
||||
if (!boot_kobj) {
|
||||
rc = -ENOMEM;
|
||||
goto free_ibft_obj;
|
||||
}
|
||||
break;
|
||||
case id_reserved:
|
||||
case id_control:
|
||||
case id_extensions:
|
||||
/* Fields which we don't support. Ignore them */
|
||||
rc = 1;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "iBFT has unknown structure type (%d). " \
|
||||
"Report this bug to %.6s!\n", hdr->id,
|
||||
header->header.oem_id);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
/* Skip adding this kobject, but exit with non-fatal error. */
|
||||
rc = 0;
|
||||
goto free_ibft_obj;
|
||||
}
|
||||
|
||||
if (hdr->id == id_nic) {
|
||||
/*
|
||||
* We don't search for the device in other domains than
|
||||
* zero. This is because on x86 platforms the BIOS
|
||||
* executes only devices which are in domain 0. Furthermore, the
|
||||
* iBFT spec doesn't have a domain id field :-(
|
||||
*/
|
||||
pci_dev = pci_get_bus_and_slot((nic->pci_bdf & 0xff00) >> 8,
|
||||
(nic->pci_bdf & 0xff));
|
||||
if (pci_dev) {
|
||||
rc = sysfs_create_link(&boot_kobj->kobj,
|
||||
&pci_dev->dev.kobj, "device");
|
||||
pci_dev_put(pci_dev);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
free_ibft_obj:
|
||||
kfree(ibft_kobj);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan the IBFT table structure for the NIC and Target fields. When
|
||||
* found add them on the passed-in list. We do not support the other
|
||||
* fields at this point, so they are skipped.
|
||||
*/
|
||||
static int __init ibft_register_kobjects(struct acpi_table_ibft *header)
|
||||
{
|
||||
struct ibft_control *control = NULL;
|
||||
void *ptr, *end;
|
||||
int rc = 0;
|
||||
u16 offset;
|
||||
u16 eot_offset;
|
||||
|
||||
control = (void *)header + sizeof(*header);
|
||||
end = (void *)control + control->hdr.length;
|
||||
eot_offset = (void *)header + header->header.length - (void *)control;
|
||||
rc = ibft_verify_hdr("control", (struct ibft_hdr *)control, id_control,
|
||||
sizeof(*control));
|
||||
|
||||
/* iBFT table safety checking */
|
||||
rc |= ((control->hdr.index) ? -ENODEV : 0);
|
||||
if (rc) {
|
||||
printk(KERN_ERR "iBFT error: Control header is invalid!\n");
|
||||
return rc;
|
||||
}
|
||||
for (ptr = &control->initiator_off; ptr < end; ptr += sizeof(u16)) {
|
||||
offset = *(u16 *)ptr;
|
||||
if (offset && offset < header->header.length &&
|
||||
offset < eot_offset) {
|
||||
rc = ibft_create_kobject(header,
|
||||
(void *)header + offset);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ibft_unregister(void)
|
||||
{
|
||||
struct iscsi_boot_kobj *boot_kobj, *tmp_kobj;
|
||||
struct ibft_kobject *ibft_kobj;
|
||||
|
||||
list_for_each_entry_safe(boot_kobj, tmp_kobj,
|
||||
&boot_kset->kobj_list, list) {
|
||||
ibft_kobj = boot_kobj->data;
|
||||
if (ibft_kobj->hdr->id == id_nic)
|
||||
sysfs_remove_link(&boot_kobj->kobj, "device");
|
||||
};
|
||||
}
|
||||
|
||||
static void ibft_cleanup(void)
|
||||
{
|
||||
if (boot_kset) {
|
||||
ibft_unregister();
|
||||
iscsi_boot_destroy_kset(boot_kset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __exit ibft_exit(void)
|
||||
{
|
||||
ibft_cleanup();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct {
|
||||
char *sign;
|
||||
} ibft_signs[] = {
|
||||
/*
|
||||
* One spec says "IBFT", the other says "iBFT". We have to check
|
||||
* for both.
|
||||
*/
|
||||
{ ACPI_SIG_IBFT },
|
||||
{ "iBFT" },
|
||||
{ "BIFT" }, /* Broadcom iSCSI Offload */
|
||||
};
|
||||
|
||||
static void __init acpi_find_ibft_region(void)
|
||||
{
|
||||
int i;
|
||||
struct acpi_table_header *table = NULL;
|
||||
|
||||
if (acpi_disabled)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ibft_signs) && !ibft_addr; i++) {
|
||||
acpi_get_table(ibft_signs[i].sign, 0, &table);
|
||||
ibft_addr = (struct acpi_table_ibft *)table;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void __init acpi_find_ibft_region(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ibft_init() - creates sysfs tree entries for the iBFT data.
|
||||
*/
|
||||
static int __init ibft_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/*
|
||||
As on UEFI systems the setup_arch()/find_ibft_region()
|
||||
is called before ACPI tables are parsed and it only does
|
||||
legacy finding.
|
||||
*/
|
||||
if (!ibft_addr)
|
||||
acpi_find_ibft_region();
|
||||
|
||||
if (ibft_addr) {
|
||||
pr_info("iBFT detected.\n");
|
||||
|
||||
rc = ibft_check_device();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
boot_kset = iscsi_boot_create_kset("ibft");
|
||||
if (!boot_kset)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Scan the IBFT for data and register the kobjects. */
|
||||
rc = ibft_register_kobjects(ibft_addr);
|
||||
if (rc)
|
||||
goto out_free;
|
||||
} else
|
||||
printk(KERN_INFO "No iBFT detected.\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
ibft_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
module_init(ibft_init);
|
||||
module_exit(ibft_exit);
|
112
drivers/firmware/iscsi_ibft_find.c
Normal file
112
drivers/firmware/iscsi_ibft_find.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright 2007-2010 Red Hat, Inc.
|
||||
* by Peter Jones <pjones@redhat.com>
|
||||
* Copyright 2007 IBM, Inc.
|
||||
* by Konrad Rzeszutek <konradr@linux.vnet.ibm.com>
|
||||
* Copyright 2008
|
||||
* by Konrad Rzeszutek <ketuzsezr@darnok.org>
|
||||
*
|
||||
* This code finds the iSCSI Boot Format Table.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/iscsi_ibft.h>
|
||||
|
||||
#include <asm/mmzone.h>
|
||||
|
||||
/*
|
||||
* Physical location of iSCSI Boot Format Table.
|
||||
*/
|
||||
struct acpi_table_ibft *ibft_addr;
|
||||
EXPORT_SYMBOL_GPL(ibft_addr);
|
||||
|
||||
static const struct {
|
||||
char *sign;
|
||||
} ibft_signs[] = {
|
||||
{ "iBFT" },
|
||||
{ "BIFT" }, /* Broadcom iSCSI Offload */
|
||||
};
|
||||
|
||||
#define IBFT_SIGN_LEN 4
|
||||
#define IBFT_START 0x80000 /* 512kB */
|
||||
#define IBFT_END 0x100000 /* 1MB */
|
||||
#define VGA_MEM 0xA0000 /* VGA buffer */
|
||||
#define VGA_SIZE 0x20000 /* 128kB */
|
||||
|
||||
static int __init find_ibft_in_mem(void)
|
||||
{
|
||||
unsigned long pos;
|
||||
unsigned int len = 0;
|
||||
void *virt;
|
||||
int i;
|
||||
|
||||
for (pos = IBFT_START; pos < IBFT_END; pos += 16) {
|
||||
/* The table can't be inside the VGA BIOS reserved space,
|
||||
* so skip that area */
|
||||
if (pos == VGA_MEM)
|
||||
pos += VGA_SIZE;
|
||||
virt = isa_bus_to_virt(pos);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ibft_signs); i++) {
|
||||
if (memcmp(virt, ibft_signs[i].sign, IBFT_SIGN_LEN) ==
|
||||
0) {
|
||||
unsigned long *addr =
|
||||
(unsigned long *)isa_bus_to_virt(pos + 4);
|
||||
len = *addr;
|
||||
/* if the length of the table extends past 1M,
|
||||
* the table cannot be valid. */
|
||||
if (pos + len <= (IBFT_END-1)) {
|
||||
ibft_addr = (struct acpi_table_ibft *)virt;
|
||||
pr_info("iBFT found at 0x%lx.\n", pos);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
done:
|
||||
return len;
|
||||
}
|
||||
/*
|
||||
* Routine used to find the iSCSI Boot Format Table. The logical
|
||||
* kernel address is set in the ibft_addr global variable.
|
||||
*/
|
||||
unsigned long __init find_ibft_region(unsigned long *sizep)
|
||||
{
|
||||
ibft_addr = NULL;
|
||||
|
||||
/* iBFT 1.03 section 1.4.3.1 mandates that UEFI machines will
|
||||
* only use ACPI for this */
|
||||
|
||||
if (!efi_enabled(EFI_BOOT))
|
||||
find_ibft_in_mem();
|
||||
|
||||
if (ibft_addr) {
|
||||
*sizep = PAGE_ALIGN(ibft_addr->header.length);
|
||||
return (u64)isa_virt_to_bus(ibft_addr);
|
||||
}
|
||||
|
||||
*sizep = 0;
|
||||
return 0;
|
||||
}
|
424
drivers/firmware/memmap.c
Normal file
424
drivers/firmware/memmap.c
Normal file
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
* linux/drivers/firmware/memmap.c
|
||||
* Copyright (C) 2008 SUSE LINUX Products GmbH
|
||||
* by Bernhard Walle <bernhard.walle@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/firmware-map.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
/*
|
||||
* Data types ------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Firmware map entry. Because firmware memory maps are flat and not
|
||||
* hierarchical, it's ok to organise them in a linked list. No parent
|
||||
* information is necessary as for the resource tree.
|
||||
*/
|
||||
struct firmware_map_entry {
|
||||
/*
|
||||
* start and end must be u64 rather than resource_size_t, because e820
|
||||
* resources can lie at addresses above 4G.
|
||||
*/
|
||||
u64 start; /* start of the memory range */
|
||||
u64 end; /* end of the memory range (incl.) */
|
||||
const char *type; /* type of the memory range */
|
||||
struct list_head list; /* entry for the linked list */
|
||||
struct kobject kobj; /* kobject for each entry */
|
||||
};
|
||||
|
||||
/*
|
||||
* Forward declarations --------------------------------------------------------
|
||||
*/
|
||||
static ssize_t memmap_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf);
|
||||
static ssize_t start_show(struct firmware_map_entry *entry, char *buf);
|
||||
static ssize_t end_show(struct firmware_map_entry *entry, char *buf);
|
||||
static ssize_t type_show(struct firmware_map_entry *entry, char *buf);
|
||||
|
||||
static struct firmware_map_entry * __meminit
|
||||
firmware_map_find_entry(u64 start, u64 end, const char *type);
|
||||
|
||||
/*
|
||||
* Static data -----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
struct memmap_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct firmware_map_entry *entry, char *buf);
|
||||
};
|
||||
|
||||
static struct memmap_attribute memmap_start_attr = __ATTR_RO(start);
|
||||
static struct memmap_attribute memmap_end_attr = __ATTR_RO(end);
|
||||
static struct memmap_attribute memmap_type_attr = __ATTR_RO(type);
|
||||
|
||||
/*
|
||||
* These are default attributes that are added for every memmap entry.
|
||||
*/
|
||||
static struct attribute *def_attrs[] = {
|
||||
&memmap_start_attr.attr,
|
||||
&memmap_end_attr.attr,
|
||||
&memmap_type_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct sysfs_ops memmap_attr_ops = {
|
||||
.show = memmap_attr_show,
|
||||
};
|
||||
|
||||
/* Firmware memory map entries. */
|
||||
static LIST_HEAD(map_entries);
|
||||
static DEFINE_SPINLOCK(map_entries_lock);
|
||||
|
||||
/*
|
||||
* For memory hotplug, there is no way to free memory map entries allocated
|
||||
* by boot mem after the system is up. So when we hot-remove memory whose
|
||||
* map entry is allocated by bootmem, we need to remember the storage and
|
||||
* reuse it when the memory is hot-added again.
|
||||
*/
|
||||
static LIST_HEAD(map_entries_bootmem);
|
||||
static DEFINE_SPINLOCK(map_entries_bootmem_lock);
|
||||
|
||||
|
||||
static inline struct firmware_map_entry *
|
||||
to_memmap_entry(struct kobject *kobj)
|
||||
{
|
||||
return container_of(kobj, struct firmware_map_entry, kobj);
|
||||
}
|
||||
|
||||
static void __meminit release_firmware_map_entry(struct kobject *kobj)
|
||||
{
|
||||
struct firmware_map_entry *entry = to_memmap_entry(kobj);
|
||||
|
||||
if (PageReserved(virt_to_page(entry))) {
|
||||
/*
|
||||
* Remember the storage allocated by bootmem, and reuse it when
|
||||
* the memory is hot-added again. The entry will be added to
|
||||
* map_entries_bootmem here, and deleted from &map_entries in
|
||||
* firmware_map_remove_entry().
|
||||
*/
|
||||
spin_lock(&map_entries_bootmem_lock);
|
||||
list_add(&entry->list, &map_entries_bootmem);
|
||||
spin_unlock(&map_entries_bootmem_lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
static struct kobj_type __refdata memmap_ktype = {
|
||||
.release = release_firmware_map_entry,
|
||||
.sysfs_ops = &memmap_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
/*
|
||||
* Registration functions ------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range (exclusive).
|
||||
* @type: Type of the memory range.
|
||||
* @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised
|
||||
* entry.
|
||||
*
|
||||
* Common implementation of firmware_map_add() and firmware_map_add_early()
|
||||
* which expects a pre-allocated struct firmware_map_entry.
|
||||
**/
|
||||
static int firmware_map_add_entry(u64 start, u64 end,
|
||||
const char *type,
|
||||
struct firmware_map_entry *entry)
|
||||
{
|
||||
BUG_ON(start > end);
|
||||
|
||||
entry->start = start;
|
||||
entry->end = end - 1;
|
||||
entry->type = type;
|
||||
INIT_LIST_HEAD(&entry->list);
|
||||
kobject_init(&entry->kobj, &memmap_ktype);
|
||||
|
||||
spin_lock(&map_entries_lock);
|
||||
list_add_tail(&entry->list, &map_entries);
|
||||
spin_unlock(&map_entries_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_map_remove_entry() - Does the real work to remove a firmware
|
||||
* memmap entry.
|
||||
* @entry: removed entry.
|
||||
*
|
||||
* The caller must hold map_entries_lock, and release it properly.
|
||||
**/
|
||||
static inline void firmware_map_remove_entry(struct firmware_map_entry *entry)
|
||||
{
|
||||
list_del(&entry->list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add memmap entry on sysfs
|
||||
*/
|
||||
static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
|
||||
{
|
||||
static int map_entries_nr;
|
||||
static struct kset *mmap_kset;
|
||||
|
||||
if (entry->kobj.state_in_sysfs)
|
||||
return -EEXIST;
|
||||
|
||||
if (!mmap_kset) {
|
||||
mmap_kset = kset_create_and_add("memmap", NULL, firmware_kobj);
|
||||
if (!mmap_kset)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
entry->kobj.kset = mmap_kset;
|
||||
if (kobject_add(&entry->kobj, NULL, "%d", map_entries_nr++))
|
||||
kobject_put(&entry->kobj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove memmap entry on sysfs
|
||||
*/
|
||||
static inline void remove_sysfs_fw_map_entry(struct firmware_map_entry *entry)
|
||||
{
|
||||
kobject_put(&entry->kobj);
|
||||
}
|
||||
|
||||
/*
|
||||
* firmware_map_find_entry_in_list() - Search memmap entry in a given list.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range (exclusive).
|
||||
* @type: Type of the memory range.
|
||||
* @list: In which to find the entry.
|
||||
*
|
||||
* This function is to find the memmap entey of a given memory range in a
|
||||
* given list. The caller must hold map_entries_lock, and must not release
|
||||
* the lock until the processing of the returned entry has completed.
|
||||
*
|
||||
* Return: Pointer to the entry to be found on success, or NULL on failure.
|
||||
*/
|
||||
static struct firmware_map_entry * __meminit
|
||||
firmware_map_find_entry_in_list(u64 start, u64 end, const char *type,
|
||||
struct list_head *list)
|
||||
{
|
||||
struct firmware_map_entry *entry;
|
||||
|
||||
list_for_each_entry(entry, list, list)
|
||||
if ((entry->start == start) && (entry->end == end) &&
|
||||
(!strcmp(entry->type, type))) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* firmware_map_find_entry() - Search memmap entry in map_entries.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range (exclusive).
|
||||
* @type: Type of the memory range.
|
||||
*
|
||||
* This function is to find the memmap entey of a given memory range.
|
||||
* The caller must hold map_entries_lock, and must not release the lock
|
||||
* until the processing of the returned entry has completed.
|
||||
*
|
||||
* Return: Pointer to the entry to be found on success, or NULL on failure.
|
||||
*/
|
||||
static struct firmware_map_entry * __meminit
|
||||
firmware_map_find_entry(u64 start, u64 end, const char *type)
|
||||
{
|
||||
return firmware_map_find_entry_in_list(start, end, type, &map_entries);
|
||||
}
|
||||
|
||||
/*
|
||||
* firmware_map_find_entry_bootmem() - Search memmap entry in map_entries_bootmem.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range (exclusive).
|
||||
* @type: Type of the memory range.
|
||||
*
|
||||
* This function is similar to firmware_map_find_entry except that it find the
|
||||
* given entry in map_entries_bootmem.
|
||||
*
|
||||
* Return: Pointer to the entry to be found on success, or NULL on failure.
|
||||
*/
|
||||
static struct firmware_map_entry * __meminit
|
||||
firmware_map_find_entry_bootmem(u64 start, u64 end, const char *type)
|
||||
{
|
||||
return firmware_map_find_entry_in_list(start, end, type,
|
||||
&map_entries_bootmem);
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
|
||||
* memory hotplug.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range (exclusive)
|
||||
* @type: Type of the memory range.
|
||||
*
|
||||
* Adds a firmware mapping entry. This function is for memory hotplug, it is
|
||||
* similar to function firmware_map_add_early(). The only difference is that
|
||||
* it will create the syfs entry dynamically.
|
||||
*
|
||||
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
|
||||
**/
|
||||
int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
|
||||
{
|
||||
struct firmware_map_entry *entry;
|
||||
|
||||
entry = firmware_map_find_entry(start, end - 1, type);
|
||||
if (entry)
|
||||
return 0;
|
||||
|
||||
entry = firmware_map_find_entry_bootmem(start, end - 1, type);
|
||||
if (!entry) {
|
||||
entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
/* Reuse storage allocated by bootmem. */
|
||||
spin_lock(&map_entries_bootmem_lock);
|
||||
list_del(&entry->list);
|
||||
spin_unlock(&map_entries_bootmem_lock);
|
||||
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
}
|
||||
|
||||
firmware_map_add_entry(start, end, type, entry);
|
||||
/* create the memmap entry */
|
||||
add_sysfs_fw_map_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_map_add_early() - Adds a firmware mapping entry.
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range.
|
||||
* @type: Type of the memory range.
|
||||
*
|
||||
* Adds a firmware mapping entry. This function uses the bootmem allocator
|
||||
* for memory allocation.
|
||||
*
|
||||
* That function must be called before late_initcall.
|
||||
*
|
||||
* Returns 0 on success, or -ENOMEM if no memory could be allocated.
|
||||
**/
|
||||
int __init firmware_map_add_early(u64 start, u64 end, const char *type)
|
||||
{
|
||||
struct firmware_map_entry *entry;
|
||||
|
||||
entry = memblock_virt_alloc(sizeof(struct firmware_map_entry), 0);
|
||||
if (WARN_ON(!entry))
|
||||
return -ENOMEM;
|
||||
|
||||
return firmware_map_add_entry(start, end, type, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_map_remove() - remove a firmware mapping entry
|
||||
* @start: Start of the memory range.
|
||||
* @end: End of the memory range.
|
||||
* @type: Type of the memory range.
|
||||
*
|
||||
* removes a firmware mapping entry.
|
||||
*
|
||||
* Returns 0 on success, or -EINVAL if no entry.
|
||||
**/
|
||||
int __meminit firmware_map_remove(u64 start, u64 end, const char *type)
|
||||
{
|
||||
struct firmware_map_entry *entry;
|
||||
|
||||
spin_lock(&map_entries_lock);
|
||||
entry = firmware_map_find_entry(start, end - 1, type);
|
||||
if (!entry) {
|
||||
spin_unlock(&map_entries_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
firmware_map_remove_entry(entry);
|
||||
spin_unlock(&map_entries_lock);
|
||||
|
||||
/* remove the memmap entry */
|
||||
remove_sysfs_fw_map_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sysfs functions -------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static ssize_t start_show(struct firmware_map_entry *entry, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
|
||||
(unsigned long long)entry->start);
|
||||
}
|
||||
|
||||
static ssize_t end_show(struct firmware_map_entry *entry, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "0x%llx\n",
|
||||
(unsigned long long)entry->end);
|
||||
}
|
||||
|
||||
static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", entry->type);
|
||||
}
|
||||
|
||||
static inline struct memmap_attribute *to_memmap_attr(struct attribute *attr)
|
||||
{
|
||||
return container_of(attr, struct memmap_attribute, attr);
|
||||
}
|
||||
|
||||
static ssize_t memmap_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
{
|
||||
struct firmware_map_entry *entry = to_memmap_entry(kobj);
|
||||
struct memmap_attribute *memmap_attr = to_memmap_attr(attr);
|
||||
|
||||
return memmap_attr->show(entry, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialises stuff and adds the entries in the map_entries list to
|
||||
* sysfs. Important is that firmware_map_add() and firmware_map_add_early()
|
||||
* must be called before late_initcall. That's just because that function
|
||||
* is called as late_initcall() function, which means that if you call
|
||||
* firmware_map_add() or firmware_map_add_early() afterwards, the entries
|
||||
* are not added to sysfs.
|
||||
*/
|
||||
static int __init firmware_memmap_init(void)
|
||||
{
|
||||
struct firmware_map_entry *entry;
|
||||
|
||||
list_for_each_entry(entry, &map_entries, list)
|
||||
add_sysfs_fw_map_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(firmware_memmap_init);
|
||||
|
136
drivers/firmware/pcdp.c
Normal file
136
drivers/firmware/pcdp.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Parse the EFI PCDP table to locate the console device.
|
||||
*
|
||||
* (c) Copyright 2002, 2003, 2004 Hewlett-Packard Development Company, L.P.
|
||||
* Khalid Aziz <khalid.aziz@hp.com>
|
||||
* Alex Williamson <alex.williamson@hp.com>
|
||||
* Bjorn Helgaas <bjorn.helgaas@hp.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/serial.h>
|
||||
#include <linux/serial_8250.h>
|
||||
#include <asm/vga.h>
|
||||
#include "pcdp.h"
|
||||
|
||||
static int __init
|
||||
setup_serial_console(struct pcdp_uart *uart)
|
||||
{
|
||||
#ifdef CONFIG_SERIAL_8250_CONSOLE
|
||||
int mmio;
|
||||
static char options[64], *p = options;
|
||||
char parity;
|
||||
|
||||
mmio = (uart->addr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY);
|
||||
p += sprintf(p, "uart8250,%s,0x%llx",
|
||||
mmio ? "mmio" : "io", uart->addr.address);
|
||||
if (uart->baud) {
|
||||
p += sprintf(p, ",%llu", uart->baud);
|
||||
if (uart->bits) {
|
||||
switch (uart->parity) {
|
||||
case 0x2: parity = 'e'; break;
|
||||
case 0x3: parity = 'o'; break;
|
||||
default: parity = 'n';
|
||||
}
|
||||
p += sprintf(p, "%c%d", parity, uart->bits);
|
||||
}
|
||||
}
|
||||
|
||||
add_preferred_console("uart", 8250, &options[9]);
|
||||
return setup_early_serial8250_console(options);
|
||||
#else
|
||||
return -ENODEV;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int __init
|
||||
setup_vga_console(struct pcdp_device *dev)
|
||||
{
|
||||
#if defined(CONFIG_VT) && defined(CONFIG_VGA_CONSOLE)
|
||||
u8 *if_ptr;
|
||||
|
||||
if_ptr = ((u8 *)dev + sizeof(struct pcdp_device));
|
||||
if (if_ptr[0] == PCDP_IF_PCI) {
|
||||
struct pcdp_if_pci if_pci;
|
||||
|
||||
/* struct copy since ifptr might not be correctly aligned */
|
||||
|
||||
memcpy(&if_pci, if_ptr, sizeof(if_pci));
|
||||
|
||||
if (if_pci.trans & PCDP_PCI_TRANS_IOPORT)
|
||||
vga_console_iobase = if_pci.ioport_tra;
|
||||
|
||||
if (if_pci.trans & PCDP_PCI_TRANS_MMIO)
|
||||
vga_console_membase = if_pci.mmio_tra;
|
||||
}
|
||||
|
||||
if (efi_mem_type(vga_console_membase + 0xA0000) == EFI_CONVENTIONAL_MEMORY) {
|
||||
printk(KERN_ERR "PCDP: VGA selected, but frame buffer is not MMIO!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
conswitchp = &vga_con;
|
||||
printk(KERN_INFO "PCDP: VGA console\n");
|
||||
return 0;
|
||||
#else
|
||||
return -ENODEV;
|
||||
#endif
|
||||
}
|
||||
|
||||
int __init
|
||||
efi_setup_pcdp_console(char *cmdline)
|
||||
{
|
||||
struct pcdp *pcdp;
|
||||
struct pcdp_uart *uart;
|
||||
struct pcdp_device *dev, *end;
|
||||
int i, serial = 0;
|
||||
int rc = -ENODEV;
|
||||
|
||||
if (efi.hcdp == EFI_INVALID_TABLE_ADDR)
|
||||
return -ENODEV;
|
||||
|
||||
pcdp = early_ioremap(efi.hcdp, 4096);
|
||||
printk(KERN_INFO "PCDP: v%d at 0x%lx\n", pcdp->rev, efi.hcdp);
|
||||
|
||||
if (strstr(cmdline, "console=hcdp")) {
|
||||
if (pcdp->rev < 3)
|
||||
serial = 1;
|
||||
} else if (strstr(cmdline, "console=")) {
|
||||
printk(KERN_INFO "Explicit \"console=\"; ignoring PCDP\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pcdp->rev < 3 && efi_uart_console_only())
|
||||
serial = 1;
|
||||
|
||||
for (i = 0, uart = pcdp->uart; i < pcdp->num_uarts; i++, uart++) {
|
||||
if (uart->flags & PCDP_UART_PRIMARY_CONSOLE || serial) {
|
||||
if (uart->type == PCDP_CONSOLE_UART) {
|
||||
rc = setup_serial_console(uart);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end = (struct pcdp_device *) ((u8 *) pcdp + pcdp->length);
|
||||
for (dev = (struct pcdp_device *) (pcdp->uart + pcdp->num_uarts);
|
||||
dev < end;
|
||||
dev = (struct pcdp_device *) ((u8 *) dev + dev->length)) {
|
||||
if (dev->flags & PCDP_PRIMARY_CONSOLE) {
|
||||
if (dev->type == PCDP_CONSOLE_VGA) {
|
||||
rc = setup_vga_console(dev);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
early_iounmap(pcdp, 4096);
|
||||
return rc;
|
||||
}
|
111
drivers/firmware/pcdp.h
Normal file
111
drivers/firmware/pcdp.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Definitions for PCDP-defined console devices
|
||||
*
|
||||
* For DIG64_HCDPv10a_01.pdf and DIG64_PCDPv20.pdf (v1.0a and v2.0 resp.),
|
||||
* please see <http://www.dig64.org/specifications/>
|
||||
*
|
||||
* (c) Copyright 2002, 2004 Hewlett-Packard Development Company, L.P.
|
||||
* Khalid Aziz <khalid.aziz@hp.com>
|
||||
* Bjorn Helgaas <bjorn.helgaas@hp.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define PCDP_CONSOLE 0
|
||||
#define PCDP_DEBUG 1
|
||||
#define PCDP_CONSOLE_OUTPUT 2
|
||||
#define PCDP_CONSOLE_INPUT 3
|
||||
|
||||
#define PCDP_UART (0 << 3)
|
||||
#define PCDP_VGA (1 << 3)
|
||||
#define PCDP_USB (2 << 3)
|
||||
|
||||
/* pcdp_uart.type and pcdp_device.type */
|
||||
#define PCDP_CONSOLE_UART (PCDP_UART | PCDP_CONSOLE)
|
||||
#define PCDP_DEBUG_UART (PCDP_UART | PCDP_DEBUG)
|
||||
#define PCDP_CONSOLE_VGA (PCDP_VGA | PCDP_CONSOLE_OUTPUT)
|
||||
#define PCDP_CONSOLE_USB (PCDP_USB | PCDP_CONSOLE_INPUT)
|
||||
|
||||
/* pcdp_uart.flags */
|
||||
#define PCDP_UART_EDGE_SENSITIVE (1 << 0)
|
||||
#define PCDP_UART_ACTIVE_LOW (1 << 1)
|
||||
#define PCDP_UART_PRIMARY_CONSOLE (1 << 2)
|
||||
#define PCDP_UART_IRQ (1 << 6) /* in pci_func for rev < 3 */
|
||||
#define PCDP_UART_PCI (1 << 7) /* in pci_func for rev < 3 */
|
||||
|
||||
struct pcdp_uart {
|
||||
u8 type;
|
||||
u8 bits;
|
||||
u8 parity;
|
||||
u8 stop_bits;
|
||||
u8 pci_seg;
|
||||
u8 pci_bus;
|
||||
u8 pci_dev;
|
||||
u8 pci_func;
|
||||
u64 baud;
|
||||
struct acpi_generic_address addr;
|
||||
u16 pci_dev_id;
|
||||
u16 pci_vendor_id;
|
||||
u32 gsi;
|
||||
u32 clock_rate;
|
||||
u8 pci_prog_intfc;
|
||||
u8 flags;
|
||||
u16 conout_index;
|
||||
u32 reserved;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define PCDP_IF_PCI 1
|
||||
|
||||
/* pcdp_if_pci.trans */
|
||||
#define PCDP_PCI_TRANS_IOPORT 0x02
|
||||
#define PCDP_PCI_TRANS_MMIO 0x01
|
||||
|
||||
struct pcdp_if_pci {
|
||||
u8 interconnect;
|
||||
u8 reserved;
|
||||
u16 length;
|
||||
u8 segment;
|
||||
u8 bus;
|
||||
u8 dev;
|
||||
u8 fun;
|
||||
u16 dev_id;
|
||||
u16 vendor_id;
|
||||
u32 acpi_interrupt;
|
||||
u64 mmio_tra;
|
||||
u64 ioport_tra;
|
||||
u8 flags;
|
||||
u8 trans;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct pcdp_vga {
|
||||
u8 count; /* address space descriptors */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* pcdp_device.flags */
|
||||
#define PCDP_PRIMARY_CONSOLE 1
|
||||
|
||||
struct pcdp_device {
|
||||
u8 type;
|
||||
u8 flags;
|
||||
u16 length;
|
||||
u16 efi_index;
|
||||
/* next data is pcdp_if_pci or pcdp_if_acpi (not yet supported) */
|
||||
/* next data is device specific type (currently only pcdp_vga) */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct pcdp {
|
||||
u8 signature[4];
|
||||
u32 length;
|
||||
u8 rev; /* PCDP v2.0 is rev 3 */
|
||||
u8 chksum;
|
||||
u8 oemid[6];
|
||||
u8 oem_tabid[8];
|
||||
u32 oem_rev;
|
||||
u8 creator_id[4];
|
||||
u32 creator_rev;
|
||||
u32 num_uarts;
|
||||
struct pcdp_uart uart[0]; /* actual size is num_uarts */
|
||||
/* remainder of table is pcdp_device structures */
|
||||
} __attribute__((packed));
|
Loading…
Add table
Add a link
Reference in a new issue