firmware: dcdbas: Move dcdbas to drivers/platform/x86
authorStuart Hayes <stuart.w.hayes@gmail.com>
Wed, 26 Sep 2018 21:50:20 +0000 (16:50 -0500)
committerAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Thu, 27 Sep 2018 09:18:15 +0000 (12:18 +0300)
Move dcdbas to the more appropriate directory drivers/platform/x86.

Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
drivers/firmware/Kconfig
drivers/firmware/Makefile
drivers/firmware/dcdbas.c [deleted file]
drivers/firmware/dcdbas.h [deleted file]
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/dcdbas.c [new file with mode: 0644]
drivers/platform/x86/dcdbas.h [new file with mode: 0644]
drivers/platform/x86/dell-smbios-smm.c

index 02f39d2..6d0c28f 100644 (file)
@@ -145,22 +145,6 @@ config EFI_PCDP
          See DIG64_HCDPv20_042804.pdf available from
          <http://www.dig64.org/specifications/> 
 
-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
index 61887ba..edda420 100644 (file)
@@ -11,7 +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_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
diff --git a/drivers/firmware/dcdbas.c b/drivers/firmware/dcdbas.c
deleted file mode 100644 (file)
index ae28e48..0000000
+++ /dev/null
@@ -1,761 +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/acpi.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.3"
-#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 unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE;
-static u32 smi_data_buf_phys_addr;
-static DEFINE_MUTEX(smi_data_lock);
-static u8 *eps_buffer;
-
-static unsigned int host_control_action;
-static unsigned int host_control_smi_type;
-static unsigned int host_control_on_shutdown;
-
-static bool wsmt_enabled;
-
-/**
- * smi_data_buf_free: free SMI data buffer
- */
-static void smi_data_buf_free(void)
-{
-       if (!smi_data_buf || wsmt_enabled)
-               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
-                *
-                * Provide physical address of command buffer field within
-                * the struct smi_cmd to BIOS.
-                *
-                * Because the address that smi_cmd (smi_data_buf) points to
-                * will be from memremap() of a non-memory address if WSMT
-                * is present, we can't use virt_to_phys() on smi_cmd, so
-                * we have to use the physical address that was saved when
-                * the virtual address for smi_cmd was received.
-                */
-               smi_cmd->ebx = smi_data_buf_phys_addr +
-                               offsetof(struct 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();
-       }
-}
-
-/* WSMT */
-
-static u8 checksum(u8 *buffer, u8 length)
-{
-       u8 sum = 0;
-       u8 *end = buffer + length;
-
-       while (buffer < end)
-               sum += *buffer++;
-       return sum;
-}
-
-static inline struct smm_eps_table *check_eps_table(u8 *addr)
-{
-       struct smm_eps_table *eps = (struct smm_eps_table *)addr;
-
-       if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0)
-               return NULL;
-
-       if (checksum(addr, eps->length) != 0)
-               return NULL;
-
-       return eps;
-}
-
-static int dcdbas_check_wsmt(void)
-{
-       struct acpi_table_wsmt *wsmt = NULL;
-       struct smm_eps_table *eps = NULL;
-       u64 remap_size;
-       u8 *addr;
-
-       acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt);
-       if (!wsmt)
-               return 0;
-
-       /* Check if WSMT ACPI table shows that protection is enabled */
-       if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) ||
-           !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION))
-               return 0;
-
-       /* Scan for EPS (entry point structure) */
-       for (addr = (u8 *)__va(0xf0000);
-            addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table));
-            addr += 16) {
-               eps = check_eps_table(addr);
-               if (eps)
-                       break;
-       }
-
-       if (!eps) {
-               dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no EPS found\n");
-               return -ENODEV;
-       }
-
-       /*
-        * Get physical address of buffer and map to virtual address.
-        * Table gives size in 4K pages, regardless of actual system page size.
-        */
-       if (upper_32_bits(eps->smm_comm_buff_addr + 8)) {
-               dev_warn(&dcdbas_pdev->dev, "found WSMT, but EPS buffer address is above 4GB\n");
-               return -EINVAL;
-       }
-       /*
-        * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8
-        * bytes are used for a semaphore, not the data buffer itself).
-        */
-       remap_size = eps->num_of_4k_pages * PAGE_SIZE;
-       if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8)
-               remap_size = MAX_SMI_DATA_BUF_SIZE + 8;
-       eps_buffer = memremap(eps->smm_comm_buff_addr, remap_size, MEMREMAP_WB);
-       if (!eps_buffer) {
-               dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map EPS buffer\n");
-               return -ENOMEM;
-       }
-
-       /* First 8 bytes is for a semaphore, not part of the smi_data_buf */
-       smi_data_buf_phys_addr = eps->smm_comm_buff_addr + 8;
-       smi_data_buf = eps_buffer + 8;
-       smi_data_buf_size = remap_size - 8;
-       max_smi_data_buf_size = smi_data_buf_size;
-       wsmt_enabled = true;
-       dev_info(&dcdbas_pdev->dev,
-                "WSMT found, using firmware-provided SMI buffer.\n");
-       return 1;
-}
-
-/**
- * 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;
-
-       /* Check if ACPI WSMT table specifies protected SMI buffer address */
-       error = dcdbas_check_wsmt();
-       if (error < 0)
-               return error;
-
-       /*
-        * 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();
-       if (eps_buffer)
-               memunmap(eps_buffer);
-       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]*:*");
diff --git a/drivers/firmware/dcdbas.h b/drivers/firmware/dcdbas.h
deleted file mode 100644 (file)
index 52729a4..0000000
+++ /dev/null
@@ -1,117 +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 SMM_EPS_SIG                            "$SCB"
-
-#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);
-
-struct smm_eps_table {
-       char smm_comm_buff_anchor[4];
-       u8 length;
-       u8 checksum;
-       u8 version;
-       u64 smm_comm_buff_addr;
-       u64 num_of_4k_pages;
-} __packed;
-
-#endif /* _DCDBAS_H_ */
-
index 097212c..4283239 100644 (file)
@@ -108,6 +108,22 @@ config ASUS_LAPTOP
 
          If you have an ACPI-compatible ASUS laptop, say Y or M here.
 
+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.
+
 #
 # The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those
 # backends are selected. The "depends" line prevents a configuration
index 8843f8e..4e2712c 100644 (file)
@@ -12,6 +12,7 @@ obj-$(CONFIG_EEEPC_WMI)               += eeepc-wmi.o
 obj-$(CONFIG_MSI_LAPTOP)       += msi-laptop.o
 obj-$(CONFIG_ACPI_CMPC)                += classmate-laptop.o
 obj-$(CONFIG_COMPAL_LAPTOP)    += compal-laptop.o
+obj-$(CONFIG_DCDBAS)           += dcdbas.o
 obj-$(CONFIG_DELL_SMBIOS)      += dell-smbios.o
 dell-smbios-objs               := dell-smbios-base.o
 dell-smbios-$(CONFIG_DELL_SMBIOS_WMI)  += dell-smbios-wmi.o
diff --git a/drivers/platform/x86/dcdbas.c b/drivers/platform/x86/dcdbas.c
new file mode 100644 (file)
index 0000000..ae28e48
--- /dev/null
@@ -0,0 +1,761 @@
+/*
+ *  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/acpi.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.3"
+#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 unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE;
+static u32 smi_data_buf_phys_addr;
+static DEFINE_MUTEX(smi_data_lock);
+static u8 *eps_buffer;
+
+static unsigned int host_control_action;
+static unsigned int host_control_smi_type;
+static unsigned int host_control_on_shutdown;
+
+static bool wsmt_enabled;
+
+/**
+ * smi_data_buf_free: free SMI data buffer
+ */
+static void smi_data_buf_free(void)
+{
+       if (!smi_data_buf || wsmt_enabled)
+               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
+                *
+                * Provide physical address of command buffer field within
+                * the struct smi_cmd to BIOS.
+                *
+                * Because the address that smi_cmd (smi_data_buf) points to
+                * will be from memremap() of a non-memory address if WSMT
+                * is present, we can't use virt_to_phys() on smi_cmd, so
+                * we have to use the physical address that was saved when
+                * the virtual address for smi_cmd was received.
+                */
+               smi_cmd->ebx = smi_data_buf_phys_addr +
+                               offsetof(struct 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();
+       }
+}
+
+/* WSMT */
+
+static u8 checksum(u8 *buffer, u8 length)
+{
+       u8 sum = 0;
+       u8 *end = buffer + length;
+
+       while (buffer < end)
+               sum += *buffer++;
+       return sum;
+}
+
+static inline struct smm_eps_table *check_eps_table(u8 *addr)
+{
+       struct smm_eps_table *eps = (struct smm_eps_table *)addr;
+
+       if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0)
+               return NULL;
+
+       if (checksum(addr, eps->length) != 0)
+               return NULL;
+
+       return eps;
+}
+
+static int dcdbas_check_wsmt(void)
+{
+       struct acpi_table_wsmt *wsmt = NULL;
+       struct smm_eps_table *eps = NULL;
+       u64 remap_size;
+       u8 *addr;
+
+       acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt);
+       if (!wsmt)
+               return 0;
+
+       /* Check if WSMT ACPI table shows that protection is enabled */
+       if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) ||
+           !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION))
+               return 0;
+
+       /* Scan for EPS (entry point structure) */
+       for (addr = (u8 *)__va(0xf0000);
+            addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table));
+            addr += 16) {
+               eps = check_eps_table(addr);
+               if (eps)
+                       break;
+       }
+
+       if (!eps) {
+               dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no EPS found\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Get physical address of buffer and map to virtual address.
+        * Table gives size in 4K pages, regardless of actual system page size.
+        */
+       if (upper_32_bits(eps->smm_comm_buff_addr + 8)) {
+               dev_warn(&dcdbas_pdev->dev, "found WSMT, but EPS buffer address is above 4GB\n");
+               return -EINVAL;
+       }
+       /*
+        * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8
+        * bytes are used for a semaphore, not the data buffer itself).
+        */
+       remap_size = eps->num_of_4k_pages * PAGE_SIZE;
+       if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8)
+               remap_size = MAX_SMI_DATA_BUF_SIZE + 8;
+       eps_buffer = memremap(eps->smm_comm_buff_addr, remap_size, MEMREMAP_WB);
+       if (!eps_buffer) {
+               dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map EPS buffer\n");
+               return -ENOMEM;
+       }
+
+       /* First 8 bytes is for a semaphore, not part of the smi_data_buf */
+       smi_data_buf_phys_addr = eps->smm_comm_buff_addr + 8;
+       smi_data_buf = eps_buffer + 8;
+       smi_data_buf_size = remap_size - 8;
+       max_smi_data_buf_size = smi_data_buf_size;
+       wsmt_enabled = true;
+       dev_info(&dcdbas_pdev->dev,
+                "WSMT found, using firmware-provided SMI buffer.\n");
+       return 1;
+}
+
+/**
+ * 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;
+
+       /* Check if ACPI WSMT table specifies protected SMI buffer address */
+       error = dcdbas_check_wsmt();
+       if (error < 0)
+               return error;
+
+       /*
+        * 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();
+       if (eps_buffer)
+               memunmap(eps_buffer);
+       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]*:*");
diff --git a/drivers/platform/x86/dcdbas.h b/drivers/platform/x86/dcdbas.h
new file mode 100644 (file)
index 0000000..52729a4
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ *  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 SMM_EPS_SIG                            "$SCB"
+
+#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);
+
+struct smm_eps_table {
+       char smm_comm_buff_anchor[4];
+       u8 length;
+       u8 checksum;
+       u8 version;
+       u64 smm_comm_buff_addr;
+       u64 num_of_4k_pages;
+} __packed;
+
+#endif /* _DCDBAS_H_ */
+
index 97a90be..ab9b822 100644 (file)
@@ -18,7 +18,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
-#include "../../firmware/dcdbas.h"
+#include "dcdbas.h"
 #include "dell-smbios.h"
 
 static int da_command_address;