Merge tag 'platform-drivers-x86-v4.20-1' of git://git.infradead.org/linux-platform-drivers-x86
Pull x86 platform driver updates from Darren Hart: - Move the Dell dcdbas and dell_rbu drivers into platform/drivers/x86 as they are closely coupled with other drivers in this location. - Improve _init* usage for acerhdf and fix some usage issues with messages and module parameters. - Simplify asus-wmi by calling ACPI/WMI methods directly, eliminating workqueue overhead, eliminate double reporting of keyboard backlight. - Fix wake from USB failure on Bay Trail devices (intel_int0002_vgpio). - Notify intel_telemetry users when IPC1 device is not enabled. - Update various drivers with new laptop model IDs. - Update several intel drivers to use SPDX identifers and order headers alphabetically. * tag 'platform-drivers-x86-v4.20-1' of git://git.infradead.org/linux-platform-drivers-x86: (64 commits) HID: asus: only support backlight when it's not driven by WMI platform/x86: asus-wmi: export function for evaluating WMI methods platform/x86: asus-wmi: Only notify kbd LED hw_change by fn-key pressed platform/x86: wmi: declare device_type structure as constant platform/x86: ideapad: Add Y530-15ICH to no_hw_rfkill platform/x86: Add Intel AtomISP2 dummy / power-management driver platform/x86: touchscreen_dmi: Add min-x and min-y settings for various models platform/x86: touchscreen_dmi: Add info for the Onda V80 Plus v3 tablet platform/x86: touchscreen_dmi: Add info for the Trekstor Primetab T13B tablet platform/x86: intel_telemetry: Get rid of custom macro platform/x86: intel_telemetry: report debugfs failure MAINTAINERS: intel_telemetry: Update maintainers info platform/x86: Add LG Gram laptop special features driver platform/x86: asus-wmi: Simplify the keyboard brightness updating process platform/x86: touchscreen_dmi: Add info for the Trekstor Primebook C11 convertible platform/x86: mlx-platform: Properly use mlxplat_mlxcpld_msn201x_items MAINTAINERS: intel_pmc_core: Update MAINTAINERS firmware: dcdbas: include linux/io.h platform/x86: intel-wmi-thunderbolt: Add dynamic debugging platform/x86: intel-wmi-thunderbolt: Convert to use SPDX identifier ...
This commit is contained in:
@@ -145,34 +145,6 @@ config EFI_PCDP
|
||||
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
|
||||
|
||||
@@ -11,8 +11,6 @@ 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
|
||||
|
||||
@@ -1,650 +0,0 @@
|
||||
/*
|
||||
* 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/cpu.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;
|
||||
}
|
||||
|
||||
static int raise_smi(void *par)
|
||||
{
|
||||
struct smi_cmd *smi_cmd = par;
|
||||
|
||||
if (smp_processor_id() != 0) {
|
||||
dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
|
||||
__func__);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* 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"
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* dcdbas_smi_request: generate SMI request
|
||||
*
|
||||
* Called with smi_data_lock.
|
||||
*/
|
||||
int dcdbas_smi_request(struct smi_cmd *smi_cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (smi_cmd->magic != SMI_CMD_MAGIC) {
|
||||
dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
|
||||
__func__);
|
||||
return -EBADR;
|
||||
}
|
||||
|
||||
/* SMI requires CPU 0 */
|
||||
get_online_cpus();
|
||||
ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true);
|
||||
put_online_cpus();
|
||||
|
||||
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 const 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,
|
||||
},
|
||||
.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);
|
||||
}
|
||||
|
||||
subsys_initcall_sync(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]*:*");
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* 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_ */
|
||||
|
||||
@@ -1,745 +0,0 @@
|
||||
/*
|
||||
* 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 = kcalloc(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
|
||||
*/
|
||||
Reference in New Issue
Block a user