From: Stuart Hayes Date: Wed, 26 Sep 2018 21:50:20 +0000 (-0500) Subject: firmware: dcdbas: Move dcdbas to drivers/platform/x86 X-Git-Tag: microblaze-v5.0-rc1~50^2~48 X-Git-Url: http://git.monstr.eu/?a=commitdiff_plain;h=8e5cddd1262cdee59257d554440cc9a80e5fcb7b;p=linux-2.6-microblaze.git firmware: dcdbas: Move dcdbas to drivers/platform/x86 Move dcdbas to the more appropriate directory drivers/platform/x86. Signed-off-by: Stuart Hayes Signed-off-by: Andy Shevchenko --- diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 02f39d20efce..6d0c28fd3bad 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -145,22 +145,6 @@ config EFI_PCDP See DIG64_HCDPv20_042804.pdf available from -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 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 diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 61887ba9df1d..edda4206d8fc 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -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 index ae28e48ff7dc..000000000000 --- a/drivers/firmware/dcdbas.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 index 52729a494b00..000000000000 --- a/drivers/firmware/dcdbas.h +++ /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 -#include -#include - -#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_ */ - diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 097212c712f7..4283239115ca 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -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 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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 8843f8e22127..4e2712c9c0b0 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -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 index 000000000000..ae28e48ff7dc --- /dev/null +++ b/drivers/platform/x86/dcdbas.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000000..52729a494b00 --- /dev/null +++ b/drivers/platform/x86/dcdbas.h @@ -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 +#include +#include + +#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_ */ + diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c index 97a90bebc360..ab9b822a6dfe 100644 --- a/drivers/platform/x86/dell-smbios-smm.c +++ b/drivers/platform/x86/dell-smbios-smm.c @@ -18,7 +18,7 @@ #include #include #include -#include "../../firmware/dcdbas.h" +#include "dcdbas.h" #include "dell-smbios.h" static int da_command_address;