xref: /linux/drivers/platform/x86/dell/dcdbas.c (revision 22c5696e3fe029f4fc2decbe7cc6663b5d281223)
1d9523678SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
290563ec4SDoug Warzecha /*
390563ec4SDoug Warzecha  *  dcdbas.c: Dell Systems Management Base Driver
490563ec4SDoug Warzecha  *
590563ec4SDoug Warzecha  *  The Dell Systems Management Base Driver provides a sysfs interface for
690563ec4SDoug Warzecha  *  systems management software to perform System Management Interrupts (SMIs)
790563ec4SDoug Warzecha  *  and Host Control Actions (power cycle or power off after OS shutdown) on
890563ec4SDoug Warzecha  *  Dell systems.
990563ec4SDoug Warzecha  *
10bb67bf1cSVegard Nossum  *  See Documentation/userspace-api/dcdbas.rst for more information.
1190563ec4SDoug Warzecha  *
12b95936cbSDoug Warzecha  *  Copyright (C) 1995-2006 Dell Inc.
1390563ec4SDoug Warzecha  */
1490563ec4SDoug Warzecha 
15d052d1beSRussell King #include <linux/platform_device.h>
1612c956c4SStuart Hayes #include <linux/acpi.h>
1790563ec4SDoug Warzecha #include <linux/dma-mapping.h>
1847a94c55SStuart Hayes #include <linux/dmi.h>
1990563ec4SDoug Warzecha #include <linux/errno.h>
20e23f22b5SJuergen Gross #include <linux/cpu.h>
215a0e3ad6STejun Heo #include <linux/gfp.h>
2290563ec4SDoug Warzecha #include <linux/init.h>
232991cc22SArnd Bergmann #include <linux/io.h>
2490563ec4SDoug Warzecha #include <linux/kernel.h>
2590563ec4SDoug Warzecha #include <linux/mc146818rtc.h>
2690563ec4SDoug Warzecha #include <linux/module.h>
2790563ec4SDoug Warzecha #include <linux/reboot.h>
2890563ec4SDoug Warzecha #include <linux/sched.h>
2990563ec4SDoug Warzecha #include <linux/smp.h>
3090563ec4SDoug Warzecha #include <linux/spinlock.h>
3190563ec4SDoug Warzecha #include <linux/string.h>
3281e2cc36SHongling Zeng #include <linux/sysfs.h>
3390563ec4SDoug Warzecha #include <linux/types.h>
348ed965d6SArjan van de Ven #include <linux/mutex.h>
3590563ec4SDoug Warzecha 
3690563ec4SDoug Warzecha #include "dcdbas.h"
3790563ec4SDoug Warzecha 
3890563ec4SDoug Warzecha #define DRIVER_NAME		"dcdbas"
3947a94c55SStuart Hayes #define DRIVER_VERSION		"5.6.0-3.4"
4090563ec4SDoug Warzecha #define DRIVER_DESCRIPTION	"Dell Systems Management Base Driver"
4190563ec4SDoug Warzecha 
4290563ec4SDoug Warzecha static struct platform_device *dcdbas_pdev;
4390563ec4SDoug Warzecha 
4412c956c4SStuart Hayes static unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE;
458ed965d6SArjan van de Ven static DEFINE_MUTEX(smi_data_lock);
4647a94c55SStuart Hayes static u8 *bios_buffer;
4777089467SJuergen Gross static struct smi_buffer smi_buf;
4890563ec4SDoug Warzecha 
4990563ec4SDoug Warzecha static unsigned int host_control_action;
5090563ec4SDoug Warzecha static unsigned int host_control_smi_type;
5190563ec4SDoug Warzecha static unsigned int host_control_on_shutdown;
5290563ec4SDoug Warzecha 
5312c956c4SStuart Hayes static bool wsmt_enabled;
5412c956c4SStuart Hayes 
dcdbas_smi_alloc(struct smi_buffer * smi_buffer,unsigned long size)5577089467SJuergen Gross int dcdbas_smi_alloc(struct smi_buffer *smi_buffer, unsigned long size)
5677089467SJuergen Gross {
5777089467SJuergen Gross 	smi_buffer->virt = dma_alloc_coherent(&dcdbas_pdev->dev, size,
5877089467SJuergen Gross 					      &smi_buffer->dma, GFP_KERNEL);
5977089467SJuergen Gross 	if (!smi_buffer->virt) {
6077089467SJuergen Gross 		dev_dbg(&dcdbas_pdev->dev,
6177089467SJuergen Gross 			"%s: failed to allocate memory size %lu\n",
6277089467SJuergen Gross 			__func__, size);
6377089467SJuergen Gross 		return -ENOMEM;
6477089467SJuergen Gross 	}
6577089467SJuergen Gross 	smi_buffer->size = size;
6677089467SJuergen Gross 
6777089467SJuergen Gross 	dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
6877089467SJuergen Gross 		__func__, (u32)smi_buffer->dma, smi_buffer->size);
6977089467SJuergen Gross 
7077089467SJuergen Gross 	return 0;
7177089467SJuergen Gross }
7277089467SJuergen Gross EXPORT_SYMBOL_GPL(dcdbas_smi_alloc);
7377089467SJuergen Gross 
dcdbas_smi_free(struct smi_buffer * smi_buffer)7477089467SJuergen Gross void dcdbas_smi_free(struct smi_buffer *smi_buffer)
7577089467SJuergen Gross {
7677089467SJuergen Gross 	if (!smi_buffer->virt)
7777089467SJuergen Gross 		return;
7877089467SJuergen Gross 
7977089467SJuergen Gross 	dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
8077089467SJuergen Gross 		__func__, (u32)smi_buffer->dma, smi_buffer->size);
8177089467SJuergen Gross 	dma_free_coherent(&dcdbas_pdev->dev, smi_buffer->size,
8277089467SJuergen Gross 			  smi_buffer->virt, smi_buffer->dma);
8377089467SJuergen Gross 	smi_buffer->virt = NULL;
8477089467SJuergen Gross 	smi_buffer->dma = 0;
8577089467SJuergen Gross 	smi_buffer->size = 0;
8677089467SJuergen Gross }
8777089467SJuergen Gross EXPORT_SYMBOL_GPL(dcdbas_smi_free);
8877089467SJuergen Gross 
8990563ec4SDoug Warzecha /**
9090563ec4SDoug Warzecha  * smi_data_buf_free: free SMI data buffer
9190563ec4SDoug Warzecha  */
smi_data_buf_free(void)9290563ec4SDoug Warzecha static void smi_data_buf_free(void)
9390563ec4SDoug Warzecha {
9477089467SJuergen Gross 	if (!smi_buf.virt || wsmt_enabled)
9590563ec4SDoug Warzecha 		return;
9690563ec4SDoug Warzecha 
9777089467SJuergen Gross 	dcdbas_smi_free(&smi_buf);
9890563ec4SDoug Warzecha }
9990563ec4SDoug Warzecha 
10090563ec4SDoug Warzecha /**
10190563ec4SDoug Warzecha  * smi_data_buf_realloc: grow SMI data buffer if needed
10290563ec4SDoug Warzecha  */
smi_data_buf_realloc(unsigned long size)10390563ec4SDoug Warzecha static int smi_data_buf_realloc(unsigned long size)
10490563ec4SDoug Warzecha {
10577089467SJuergen Gross 	struct smi_buffer tmp;
10677089467SJuergen Gross 	int ret;
10790563ec4SDoug Warzecha 
10877089467SJuergen Gross 	if (smi_buf.size >= size)
10990563ec4SDoug Warzecha 		return 0;
11090563ec4SDoug Warzecha 
11112c956c4SStuart Hayes 	if (size > max_smi_data_buf_size)
11290563ec4SDoug Warzecha 		return -EINVAL;
11390563ec4SDoug Warzecha 
11490563ec4SDoug Warzecha 	/* new buffer is needed */
11577089467SJuergen Gross 	ret = dcdbas_smi_alloc(&tmp, size);
11677089467SJuergen Gross 	if (ret)
11777089467SJuergen Gross 		return ret;
11890563ec4SDoug Warzecha 
11977089467SJuergen Gross 	/* memory zeroed by dma_alloc_coherent */
12077089467SJuergen Gross 	if (smi_buf.virt)
12177089467SJuergen Gross 		memcpy(tmp.virt, smi_buf.virt, smi_buf.size);
12290563ec4SDoug Warzecha 
12390563ec4SDoug Warzecha 	/* free any existing buffer */
12490563ec4SDoug Warzecha 	smi_data_buf_free();
12590563ec4SDoug Warzecha 
12690563ec4SDoug Warzecha 	/* set up new buffer for use */
12777089467SJuergen Gross 	smi_buf = tmp;
12890563ec4SDoug Warzecha 
12990563ec4SDoug Warzecha 	return 0;
13090563ec4SDoug Warzecha }
13190563ec4SDoug Warzecha 
smi_data_buf_phys_addr_show(struct device * dev,struct device_attribute * attr,char * buf)13290563ec4SDoug Warzecha static ssize_t smi_data_buf_phys_addr_show(struct device *dev,
13390563ec4SDoug Warzecha 					   struct device_attribute *attr,
13490563ec4SDoug Warzecha 					   char *buf)
13590563ec4SDoug Warzecha {
13681e2cc36SHongling Zeng 	return sysfs_emit(buf, "%x\n", (u32)smi_buf.dma);
13790563ec4SDoug Warzecha }
13890563ec4SDoug Warzecha 
smi_data_buf_size_show(struct device * dev,struct device_attribute * attr,char * buf)13990563ec4SDoug Warzecha static ssize_t smi_data_buf_size_show(struct device *dev,
14090563ec4SDoug Warzecha 				      struct device_attribute *attr,
14190563ec4SDoug Warzecha 				      char *buf)
14290563ec4SDoug Warzecha {
14381e2cc36SHongling Zeng 	return sysfs_emit(buf, "%lu\n", smi_buf.size);
14490563ec4SDoug Warzecha }
14590563ec4SDoug Warzecha 
smi_data_buf_size_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)14690563ec4SDoug Warzecha static ssize_t smi_data_buf_size_store(struct device *dev,
14790563ec4SDoug Warzecha 				       struct device_attribute *attr,
14890563ec4SDoug Warzecha 				       const char *buf, size_t count)
14990563ec4SDoug Warzecha {
15090563ec4SDoug Warzecha 	unsigned long buf_size;
15190563ec4SDoug Warzecha 	ssize_t ret;
15290563ec4SDoug Warzecha 
15390563ec4SDoug Warzecha 	buf_size = simple_strtoul(buf, NULL, 10);
15490563ec4SDoug Warzecha 
15590563ec4SDoug Warzecha 	/* make sure SMI data buffer is at least buf_size */
1568ed965d6SArjan van de Ven 	mutex_lock(&smi_data_lock);
15790563ec4SDoug Warzecha 	ret = smi_data_buf_realloc(buf_size);
1588ed965d6SArjan van de Ven 	mutex_unlock(&smi_data_lock);
15990563ec4SDoug Warzecha 	if (ret)
16090563ec4SDoug Warzecha 		return ret;
16190563ec4SDoug Warzecha 
16290563ec4SDoug Warzecha 	return count;
16390563ec4SDoug Warzecha }
16490563ec4SDoug Warzecha 
smi_data_read(struct file * filp,struct kobject * kobj,const struct bin_attribute * bin_attr,char * buf,loff_t pos,size_t count)1652c3c8beaSChris Wright static ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
166c0cc60b3SThomas Weißschuh 			     const struct bin_attribute *bin_attr,
16791a69029SZhang Rui 			     char *buf, loff_t pos, size_t count)
16890563ec4SDoug Warzecha {
16990563ec4SDoug Warzecha 	ssize_t ret;
17090563ec4SDoug Warzecha 
1718ed965d6SArjan van de Ven 	mutex_lock(&smi_data_lock);
17277089467SJuergen Gross 	ret = memory_read_from_buffer(buf, count, &pos, smi_buf.virt,
17377089467SJuergen Gross 					smi_buf.size);
1748ed965d6SArjan van de Ven 	mutex_unlock(&smi_data_lock);
17590563ec4SDoug Warzecha 	return ret;
17690563ec4SDoug Warzecha }
17790563ec4SDoug Warzecha 
smi_data_write(struct file * filp,struct kobject * kobj,const struct bin_attribute * bin_attr,char * buf,loff_t pos,size_t count)1782c3c8beaSChris Wright static ssize_t smi_data_write(struct file *filp, struct kobject *kobj,
179c0cc60b3SThomas Weißschuh 			      const struct bin_attribute *bin_attr,
18091a69029SZhang Rui 			      char *buf, loff_t pos, size_t count)
18190563ec4SDoug Warzecha {
18290563ec4SDoug Warzecha 	ssize_t ret;
18390563ec4SDoug Warzecha 
18412c956c4SStuart Hayes 	if ((pos + count) > max_smi_data_buf_size)
185b95936cbSDoug Warzecha 		return -EINVAL;
186b95936cbSDoug Warzecha 
1878ed965d6SArjan van de Ven 	mutex_lock(&smi_data_lock);
18890563ec4SDoug Warzecha 
18990563ec4SDoug Warzecha 	ret = smi_data_buf_realloc(pos + count);
19090563ec4SDoug Warzecha 	if (ret)
19190563ec4SDoug Warzecha 		goto out;
19290563ec4SDoug Warzecha 
19377089467SJuergen Gross 	memcpy(smi_buf.virt + pos, buf, count);
19490563ec4SDoug Warzecha 	ret = count;
19590563ec4SDoug Warzecha out:
1968ed965d6SArjan van de Ven 	mutex_unlock(&smi_data_lock);
19790563ec4SDoug Warzecha 	return ret;
19890563ec4SDoug Warzecha }
19990563ec4SDoug Warzecha 
host_control_action_show(struct device * dev,struct device_attribute * attr,char * buf)20090563ec4SDoug Warzecha static ssize_t host_control_action_show(struct device *dev,
20190563ec4SDoug Warzecha 					struct device_attribute *attr,
20290563ec4SDoug Warzecha 					char *buf)
20390563ec4SDoug Warzecha {
20481e2cc36SHongling Zeng 	return sysfs_emit(buf, "%u\n", host_control_action);
20590563ec4SDoug Warzecha }
20690563ec4SDoug Warzecha 
host_control_action_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)20790563ec4SDoug Warzecha static ssize_t host_control_action_store(struct device *dev,
20890563ec4SDoug Warzecha 					 struct device_attribute *attr,
20990563ec4SDoug Warzecha 					 const char *buf, size_t count)
21090563ec4SDoug Warzecha {
21190563ec4SDoug Warzecha 	ssize_t ret;
21290563ec4SDoug Warzecha 
21390563ec4SDoug Warzecha 	/* make sure buffer is available for host control command */
2148ed965d6SArjan van de Ven 	mutex_lock(&smi_data_lock);
21590563ec4SDoug Warzecha 	ret = smi_data_buf_realloc(sizeof(struct apm_cmd));
2168ed965d6SArjan van de Ven 	mutex_unlock(&smi_data_lock);
21790563ec4SDoug Warzecha 	if (ret)
21890563ec4SDoug Warzecha 		return ret;
21990563ec4SDoug Warzecha 
22090563ec4SDoug Warzecha 	host_control_action = simple_strtoul(buf, NULL, 10);
22190563ec4SDoug Warzecha 	return count;
22290563ec4SDoug Warzecha }
22390563ec4SDoug Warzecha 
host_control_smi_type_show(struct device * dev,struct device_attribute * attr,char * buf)22490563ec4SDoug Warzecha static ssize_t host_control_smi_type_show(struct device *dev,
22590563ec4SDoug Warzecha 					  struct device_attribute *attr,
22690563ec4SDoug Warzecha 					  char *buf)
22790563ec4SDoug Warzecha {
22881e2cc36SHongling Zeng 	return sysfs_emit(buf, "%u\n", host_control_smi_type);
22990563ec4SDoug Warzecha }
23090563ec4SDoug Warzecha 
host_control_smi_type_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)23190563ec4SDoug Warzecha static ssize_t host_control_smi_type_store(struct device *dev,
23290563ec4SDoug Warzecha 					   struct device_attribute *attr,
23390563ec4SDoug Warzecha 					   const char *buf, size_t count)
23490563ec4SDoug Warzecha {
23590563ec4SDoug Warzecha 	host_control_smi_type = simple_strtoul(buf, NULL, 10);
23690563ec4SDoug Warzecha 	return count;
23790563ec4SDoug Warzecha }
23890563ec4SDoug Warzecha 
host_control_on_shutdown_show(struct device * dev,struct device_attribute * attr,char * buf)23990563ec4SDoug Warzecha static ssize_t host_control_on_shutdown_show(struct device *dev,
24090563ec4SDoug Warzecha 					     struct device_attribute *attr,
24190563ec4SDoug Warzecha 					     char *buf)
24290563ec4SDoug Warzecha {
24381e2cc36SHongling Zeng 	return sysfs_emit(buf, "%u\n", host_control_on_shutdown);
24490563ec4SDoug Warzecha }
24590563ec4SDoug Warzecha 
host_control_on_shutdown_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)24690563ec4SDoug Warzecha static ssize_t host_control_on_shutdown_store(struct device *dev,
24790563ec4SDoug Warzecha 					      struct device_attribute *attr,
24890563ec4SDoug Warzecha 					      const char *buf, size_t count)
24990563ec4SDoug Warzecha {
25090563ec4SDoug Warzecha 	host_control_on_shutdown = simple_strtoul(buf, NULL, 10);
25190563ec4SDoug Warzecha 	return count;
25290563ec4SDoug Warzecha }
25390563ec4SDoug Warzecha 
raise_smi(void * par)254e23f22b5SJuergen Gross static int raise_smi(void *par)
25590563ec4SDoug Warzecha {
256e23f22b5SJuergen Gross 	struct smi_cmd *smi_cmd = par;
25790563ec4SDoug Warzecha 
25890563ec4SDoug Warzecha 	if (smp_processor_id() != 0) {
25990563ec4SDoug Warzecha 		dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
260eecd5853SHarvey Harrison 			__func__);
261e23f22b5SJuergen Gross 		return -EBUSY;
26290563ec4SDoug Warzecha 	}
26390563ec4SDoug Warzecha 
26490563ec4SDoug Warzecha 	/* generate SMI */
265dd65c736SStuart Hayes 	/* inb to force posted write through and make SMI happen now */
26690563ec4SDoug Warzecha 	asm volatile (
267dd65c736SStuart Hayes 		"outb %b0,%w1\n"
268dd65c736SStuart Hayes 		"inb %w1"
26990563ec4SDoug Warzecha 		: /* no output args */
27090563ec4SDoug Warzecha 		: "a" (smi_cmd->command_code),
27190563ec4SDoug Warzecha 		  "d" (smi_cmd->command_address),
27290563ec4SDoug Warzecha 		  "b" (smi_cmd->ebx),
27390563ec4SDoug Warzecha 		  "c" (smi_cmd->ecx)
27490563ec4SDoug Warzecha 		: "memory"
27590563ec4SDoug Warzecha 	);
27690563ec4SDoug Warzecha 
277e23f22b5SJuergen Gross 	return 0;
278e23f22b5SJuergen Gross }
279e23f22b5SJuergen Gross /**
280e23f22b5SJuergen Gross  * dcdbas_smi_request: generate SMI request
281e23f22b5SJuergen Gross  *
282e23f22b5SJuergen Gross  * Called with smi_data_lock.
283e23f22b5SJuergen Gross  */
dcdbas_smi_request(struct smi_cmd * smi_cmd)284e23f22b5SJuergen Gross int dcdbas_smi_request(struct smi_cmd *smi_cmd)
285e23f22b5SJuergen Gross {
286e23f22b5SJuergen Gross 	int ret;
287e23f22b5SJuergen Gross 
288e23f22b5SJuergen Gross 	if (smi_cmd->magic != SMI_CMD_MAGIC) {
289e23f22b5SJuergen Gross 		dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
290e23f22b5SJuergen Gross 			 __func__);
291e23f22b5SJuergen Gross 		return -EBADR;
292e23f22b5SJuergen Gross 	}
293e23f22b5SJuergen Gross 
294e23f22b5SJuergen Gross 	/* SMI requires CPU 0 */
295560c71d4SSebastian Andrzej Siewior 	cpus_read_lock();
296e23f22b5SJuergen Gross 	ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true);
297560c71d4SSebastian Andrzej Siewior 	cpus_read_unlock();
298e23f22b5SJuergen Gross 
29990563ec4SDoug Warzecha 	return ret;
30090563ec4SDoug Warzecha }
30142f8bcb3SMateusz Jończyk EXPORT_SYMBOL(dcdbas_smi_request);
30290563ec4SDoug Warzecha 
30390563ec4SDoug Warzecha /**
30490563ec4SDoug Warzecha  * smi_request_store:
30590563ec4SDoug Warzecha  *
30690563ec4SDoug Warzecha  * The valid values are:
30790563ec4SDoug Warzecha  * 0: zero SMI data buffer
30890563ec4SDoug Warzecha  * 1: generate calling interface SMI
30990563ec4SDoug Warzecha  * 2: generate raw SMI
31090563ec4SDoug Warzecha  *
31190563ec4SDoug Warzecha  * User application writes smi_cmd to smi_data before telling driver
31290563ec4SDoug Warzecha  * to generate SMI.
31390563ec4SDoug Warzecha  */
smi_request_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)31490563ec4SDoug Warzecha static ssize_t smi_request_store(struct device *dev,
31590563ec4SDoug Warzecha 				 struct device_attribute *attr,
31690563ec4SDoug Warzecha 				 const char *buf, size_t count)
31790563ec4SDoug Warzecha {
31890563ec4SDoug Warzecha 	struct smi_cmd *smi_cmd;
31990563ec4SDoug Warzecha 	unsigned long val = simple_strtoul(buf, NULL, 10);
32090563ec4SDoug Warzecha 	ssize_t ret;
32190563ec4SDoug Warzecha 
3228ed965d6SArjan van de Ven 	mutex_lock(&smi_data_lock);
32390563ec4SDoug Warzecha 
32477089467SJuergen Gross 	if (smi_buf.size < sizeof(struct smi_cmd)) {
32590563ec4SDoug Warzecha 		ret = -ENODEV;
32690563ec4SDoug Warzecha 		goto out;
32790563ec4SDoug Warzecha 	}
32877089467SJuergen Gross 	smi_cmd = (struct smi_cmd *)smi_buf.virt;
32990563ec4SDoug Warzecha 
33090563ec4SDoug Warzecha 	switch (val) {
33190563ec4SDoug Warzecha 	case 2:
33290563ec4SDoug Warzecha 		/* Raw SMI */
3333cab7fd9SMatthew Garrett 		ret = dcdbas_smi_request(smi_cmd);
33490563ec4SDoug Warzecha 		if (!ret)
33590563ec4SDoug Warzecha 			ret = count;
33690563ec4SDoug Warzecha 		break;
33790563ec4SDoug Warzecha 	case 1:
33812c956c4SStuart Hayes 		/*
33912c956c4SStuart Hayes 		 * Calling Interface SMI
34012c956c4SStuart Hayes 		 *
34112c956c4SStuart Hayes 		 * Provide physical address of command buffer field within
34212c956c4SStuart Hayes 		 * the struct smi_cmd to BIOS.
34312c956c4SStuart Hayes 		 *
34477089467SJuergen Gross 		 * Because the address that smi_cmd (smi_buf.virt) points to
34512c956c4SStuart Hayes 		 * will be from memremap() of a non-memory address if WSMT
34612c956c4SStuart Hayes 		 * is present, we can't use virt_to_phys() on smi_cmd, so
34712c956c4SStuart Hayes 		 * we have to use the physical address that was saved when
34812c956c4SStuart Hayes 		 * the virtual address for smi_cmd was received.
34912c956c4SStuart Hayes 		 */
35077089467SJuergen Gross 		smi_cmd->ebx = (u32)smi_buf.dma +
35112c956c4SStuart Hayes 				offsetof(struct smi_cmd, command_buffer);
3523cab7fd9SMatthew Garrett 		ret = dcdbas_smi_request(smi_cmd);
35390563ec4SDoug Warzecha 		if (!ret)
35490563ec4SDoug Warzecha 			ret = count;
35590563ec4SDoug Warzecha 		break;
35690563ec4SDoug Warzecha 	case 0:
35777089467SJuergen Gross 		memset(smi_buf.virt, 0, smi_buf.size);
35890563ec4SDoug Warzecha 		ret = count;
35990563ec4SDoug Warzecha 		break;
36090563ec4SDoug Warzecha 	default:
36190563ec4SDoug Warzecha 		ret = -EINVAL;
36290563ec4SDoug Warzecha 		break;
36390563ec4SDoug Warzecha 	}
36490563ec4SDoug Warzecha 
36590563ec4SDoug Warzecha out:
3668ed965d6SArjan van de Ven 	mutex_unlock(&smi_data_lock);
36790563ec4SDoug Warzecha 	return ret;
36890563ec4SDoug Warzecha }
36990563ec4SDoug Warzecha 
37090563ec4SDoug Warzecha /**
37190563ec4SDoug Warzecha  * host_control_smi: generate host control SMI
37290563ec4SDoug Warzecha  *
37377089467SJuergen Gross  * Caller must set up the host control command in smi_buf.virt.
37490563ec4SDoug Warzecha  */
host_control_smi(void)37590563ec4SDoug Warzecha static int host_control_smi(void)
37690563ec4SDoug Warzecha {
37790563ec4SDoug Warzecha 	struct apm_cmd *apm_cmd;
37890563ec4SDoug Warzecha 	u8 *data;
37990563ec4SDoug Warzecha 	unsigned long flags;
38090563ec4SDoug Warzecha 	u32 num_ticks;
38190563ec4SDoug Warzecha 	s8 cmd_status;
38290563ec4SDoug Warzecha 	u8 index;
38390563ec4SDoug Warzecha 
38477089467SJuergen Gross 	apm_cmd = (struct apm_cmd *)smi_buf.virt;
38590563ec4SDoug Warzecha 	apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL;
38690563ec4SDoug Warzecha 
38790563ec4SDoug Warzecha 	switch (host_control_smi_type) {
38890563ec4SDoug Warzecha 	case HC_SMITYPE_TYPE1:
38990563ec4SDoug Warzecha 		spin_lock_irqsave(&rtc_lock, flags);
39090563ec4SDoug Warzecha 		/* write SMI data buffer physical address */
39177089467SJuergen Gross 		data = (u8 *)&smi_buf.dma;
39290563ec4SDoug Warzecha 		for (index = PE1300_CMOS_CMD_STRUCT_PTR;
39390563ec4SDoug Warzecha 		     index < (PE1300_CMOS_CMD_STRUCT_PTR + 4);
39490563ec4SDoug Warzecha 		     index++, data++) {
39590563ec4SDoug Warzecha 			outb(index,
39690563ec4SDoug Warzecha 			     (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4));
39790563ec4SDoug Warzecha 			outb(*data,
39890563ec4SDoug Warzecha 			     (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4));
39990563ec4SDoug Warzecha 		}
40090563ec4SDoug Warzecha 
40190563ec4SDoug Warzecha 		/* first set status to -1 as called by spec */
40290563ec4SDoug Warzecha 		cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL;
40390563ec4SDoug Warzecha 		outb((u8) cmd_status, PCAT_APM_STATUS_PORT);
40490563ec4SDoug Warzecha 
40590563ec4SDoug Warzecha 		/* generate SMM call */
40690563ec4SDoug Warzecha 		outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
40790563ec4SDoug Warzecha 		spin_unlock_irqrestore(&rtc_lock, flags);
40890563ec4SDoug Warzecha 
40990563ec4SDoug Warzecha 		/* wait a few to see if it executed */
41090563ec4SDoug Warzecha 		num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
411e48af75dSYang Li 		while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) {
41290563ec4SDoug Warzecha 			num_ticks--;
41390563ec4SDoug Warzecha 			if (num_ticks == EXPIRED_TIMER)
41490563ec4SDoug Warzecha 				return -ETIME;
41590563ec4SDoug Warzecha 		}
41690563ec4SDoug Warzecha 		break;
41790563ec4SDoug Warzecha 
41890563ec4SDoug Warzecha 	case HC_SMITYPE_TYPE2:
41990563ec4SDoug Warzecha 	case HC_SMITYPE_TYPE3:
42090563ec4SDoug Warzecha 		spin_lock_irqsave(&rtc_lock, flags);
42190563ec4SDoug Warzecha 		/* write SMI data buffer physical address */
42277089467SJuergen Gross 		data = (u8 *)&smi_buf.dma;
42390563ec4SDoug Warzecha 		for (index = PE1400_CMOS_CMD_STRUCT_PTR;
42490563ec4SDoug Warzecha 		     index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
42590563ec4SDoug Warzecha 		     index++, data++) {
42690563ec4SDoug Warzecha 			outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
42790563ec4SDoug Warzecha 			outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
42890563ec4SDoug Warzecha 		}
42990563ec4SDoug Warzecha 
43090563ec4SDoug Warzecha 		/* generate SMM call */
43190563ec4SDoug Warzecha 		if (host_control_smi_type == HC_SMITYPE_TYPE3)
43290563ec4SDoug Warzecha 			outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
43390563ec4SDoug Warzecha 		else
43490563ec4SDoug Warzecha 			outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT);
43590563ec4SDoug Warzecha 
43690563ec4SDoug Warzecha 		/* restore RTC index pointer since it was written to above */
43790563ec4SDoug Warzecha 		CMOS_READ(RTC_REG_C);
43890563ec4SDoug Warzecha 		spin_unlock_irqrestore(&rtc_lock, flags);
43990563ec4SDoug Warzecha 
44090563ec4SDoug Warzecha 		/* read control port back to serialize write */
44190563ec4SDoug Warzecha 		cmd_status = inb(PE1400_APM_CONTROL_PORT);
44290563ec4SDoug Warzecha 
44390563ec4SDoug Warzecha 		/* wait a few to see if it executed */
44490563ec4SDoug Warzecha 		num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
44590563ec4SDoug Warzecha 		while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
44690563ec4SDoug Warzecha 			num_ticks--;
44790563ec4SDoug Warzecha 			if (num_ticks == EXPIRED_TIMER)
44890563ec4SDoug Warzecha 				return -ETIME;
44990563ec4SDoug Warzecha 		}
45090563ec4SDoug Warzecha 		break;
45190563ec4SDoug Warzecha 
45290563ec4SDoug Warzecha 	default:
45390563ec4SDoug Warzecha 		dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n",
454eecd5853SHarvey Harrison 			__func__, host_control_smi_type);
45590563ec4SDoug Warzecha 		return -ENOSYS;
45690563ec4SDoug Warzecha 	}
45790563ec4SDoug Warzecha 
45890563ec4SDoug Warzecha 	return 0;
45990563ec4SDoug Warzecha }
46090563ec4SDoug Warzecha 
46190563ec4SDoug Warzecha /**
46290563ec4SDoug Warzecha  * dcdbas_host_control: initiate host control
46390563ec4SDoug Warzecha  *
46490563ec4SDoug Warzecha  * This function is called by the driver after the system has
46590563ec4SDoug Warzecha  * finished shutting down if the user application specified a
46690563ec4SDoug Warzecha  * host control action to perform on shutdown.  It is safe to
46777089467SJuergen Gross  * use smi_buf.virt at this point because the system has finished
46890563ec4SDoug Warzecha  * shutting down and no userspace apps are running.
46990563ec4SDoug Warzecha  */
dcdbas_host_control(void)47090563ec4SDoug Warzecha static void dcdbas_host_control(void)
47190563ec4SDoug Warzecha {
47290563ec4SDoug Warzecha 	struct apm_cmd *apm_cmd;
47390563ec4SDoug Warzecha 	u8 action;
47490563ec4SDoug Warzecha 
47590563ec4SDoug Warzecha 	if (host_control_action == HC_ACTION_NONE)
47690563ec4SDoug Warzecha 		return;
47790563ec4SDoug Warzecha 
47890563ec4SDoug Warzecha 	action = host_control_action;
47990563ec4SDoug Warzecha 	host_control_action = HC_ACTION_NONE;
48090563ec4SDoug Warzecha 
48177089467SJuergen Gross 	if (!smi_buf.virt) {
482eecd5853SHarvey Harrison 		dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__);
48390563ec4SDoug Warzecha 		return;
48490563ec4SDoug Warzecha 	}
48590563ec4SDoug Warzecha 
48677089467SJuergen Gross 	if (smi_buf.size < sizeof(struct apm_cmd)) {
48790563ec4SDoug Warzecha 		dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n",
488eecd5853SHarvey Harrison 			__func__);
48990563ec4SDoug Warzecha 		return;
49090563ec4SDoug Warzecha 	}
49190563ec4SDoug Warzecha 
49277089467SJuergen Gross 	apm_cmd = (struct apm_cmd *)smi_buf.virt;
49390563ec4SDoug Warzecha 
49490563ec4SDoug Warzecha 	/* power off takes precedence */
49590563ec4SDoug Warzecha 	if (action & HC_ACTION_HOST_CONTROL_POWEROFF) {
49690563ec4SDoug Warzecha 		apm_cmd->command = ESM_APM_POWER_CYCLE;
49790563ec4SDoug Warzecha 		apm_cmd->reserved = 0;
49890563ec4SDoug Warzecha 		*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0;
49990563ec4SDoug Warzecha 		host_control_smi();
50090563ec4SDoug Warzecha 	} else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) {
50190563ec4SDoug Warzecha 		apm_cmd->command = ESM_APM_POWER_CYCLE;
50290563ec4SDoug Warzecha 		apm_cmd->reserved = 0;
50390563ec4SDoug Warzecha 		*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20;
50490563ec4SDoug Warzecha 		host_control_smi();
50590563ec4SDoug Warzecha 	}
50690563ec4SDoug Warzecha }
50790563ec4SDoug Warzecha 
50812c956c4SStuart Hayes /* WSMT */
50912c956c4SStuart Hayes 
checksum(u8 * buffer,u8 length)51012c956c4SStuart Hayes static u8 checksum(u8 *buffer, u8 length)
51112c956c4SStuart Hayes {
51212c956c4SStuart Hayes 	u8 sum = 0;
51312c956c4SStuart Hayes 	u8 *end = buffer + length;
51412c956c4SStuart Hayes 
51512c956c4SStuart Hayes 	while (buffer < end)
51612c956c4SStuart Hayes 		sum += *buffer++;
51712c956c4SStuart Hayes 	return sum;
51812c956c4SStuart Hayes }
51912c956c4SStuart Hayes 
check_eps_table(u8 * addr)52012c956c4SStuart Hayes static inline struct smm_eps_table *check_eps_table(u8 *addr)
52112c956c4SStuart Hayes {
52212c956c4SStuart Hayes 	struct smm_eps_table *eps = (struct smm_eps_table *)addr;
52312c956c4SStuart Hayes 
52412c956c4SStuart Hayes 	if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0)
52512c956c4SStuart Hayes 		return NULL;
52612c956c4SStuart Hayes 
52712c956c4SStuart Hayes 	if (checksum(addr, eps->length) != 0)
52812c956c4SStuart Hayes 		return NULL;
52912c956c4SStuart Hayes 
53012c956c4SStuart Hayes 	return eps;
53112c956c4SStuart Hayes }
53212c956c4SStuart Hayes 
dcdbas_check_wsmt(void)53312c956c4SStuart Hayes static int dcdbas_check_wsmt(void)
53412c956c4SStuart Hayes {
53547a94c55SStuart Hayes 	const struct dmi_device *dev = NULL;
53612c956c4SStuart Hayes 	struct acpi_table_wsmt *wsmt = NULL;
53712c956c4SStuart Hayes 	struct smm_eps_table *eps = NULL;
53847a94c55SStuart Hayes 	u64 bios_buf_paddr;
53912c956c4SStuart Hayes 	u64 remap_size;
54012c956c4SStuart Hayes 	u8 *addr;
54112c956c4SStuart Hayes 
54212c956c4SStuart Hayes 	acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt);
54312c956c4SStuart Hayes 	if (!wsmt)
54412c956c4SStuart Hayes 		return 0;
54512c956c4SStuart Hayes 
54612c956c4SStuart Hayes 	/* Check if WSMT ACPI table shows that protection is enabled */
54712c956c4SStuart Hayes 	if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) ||
54812c956c4SStuart Hayes 	    !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION))
54912c956c4SStuart Hayes 		return 0;
55012c956c4SStuart Hayes 
55147a94c55SStuart Hayes 	/*
55247a94c55SStuart Hayes 	 * BIOS could provide the address/size of the protected buffer
55347a94c55SStuart Hayes 	 * in an SMBIOS string or in an EPS structure in 0xFxxxx.
55447a94c55SStuart Hayes 	 */
55547a94c55SStuart Hayes 
55647a94c55SStuart Hayes 	/* Check SMBIOS for buffer address */
55747a94c55SStuart Hayes 	while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev)))
55847a94c55SStuart Hayes 		if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr,
55947a94c55SStuart Hayes 		    &remap_size) == 2)
56047a94c55SStuart Hayes 			goto remap;
56147a94c55SStuart Hayes 
56212c956c4SStuart Hayes 	/* Scan for EPS (entry point structure) */
56312c956c4SStuart Hayes 	for (addr = (u8 *)__va(0xf0000);
56412c956c4SStuart Hayes 	     addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table));
56512c956c4SStuart Hayes 	     addr += 16) {
56612c956c4SStuart Hayes 		eps = check_eps_table(addr);
56712c956c4SStuart Hayes 		if (eps)
56812c956c4SStuart Hayes 			break;
56912c956c4SStuart Hayes 	}
57012c956c4SStuart Hayes 
57112c956c4SStuart Hayes 	if (!eps) {
57247a94c55SStuart Hayes 		dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n");
57312c956c4SStuart Hayes 		return -ENODEV;
57412c956c4SStuart Hayes 	}
57547a94c55SStuart Hayes 	bios_buf_paddr = eps->smm_comm_buff_addr;
57647a94c55SStuart Hayes 	remap_size = eps->num_of_4k_pages * PAGE_SIZE;
57712c956c4SStuart Hayes 
57847a94c55SStuart Hayes remap:
57912c956c4SStuart Hayes 	/*
58012c956c4SStuart Hayes 	 * Get physical address of buffer and map to virtual address.
58112c956c4SStuart Hayes 	 * Table gives size in 4K pages, regardless of actual system page size.
58212c956c4SStuart Hayes 	 */
58347a94c55SStuart Hayes 	if (upper_32_bits(bios_buf_paddr + 8)) {
58447a94c55SStuart Hayes 		dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n");
58512c956c4SStuart Hayes 		return -EINVAL;
58612c956c4SStuart Hayes 	}
58712c956c4SStuart Hayes 	/*
58812c956c4SStuart Hayes 	 * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8
58912c956c4SStuart Hayes 	 * bytes are used for a semaphore, not the data buffer itself).
59012c956c4SStuart Hayes 	 */
59112c956c4SStuart Hayes 	if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8)
59212c956c4SStuart Hayes 		remap_size = MAX_SMI_DATA_BUF_SIZE + 8;
59347a94c55SStuart Hayes 
59447a94c55SStuart Hayes 	bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB);
59547a94c55SStuart Hayes 	if (!bios_buffer) {
59647a94c55SStuart Hayes 		dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n");
59712c956c4SStuart Hayes 		return -ENOMEM;
59812c956c4SStuart Hayes 	}
59912c956c4SStuart Hayes 
60077089467SJuergen Gross 	/* First 8 bytes is for a semaphore, not part of the smi_buf.virt */
60177089467SJuergen Gross 	smi_buf.dma = bios_buf_paddr + 8;
60277089467SJuergen Gross 	smi_buf.virt = bios_buffer + 8;
60377089467SJuergen Gross 	smi_buf.size = remap_size - 8;
60477089467SJuergen Gross 	max_smi_data_buf_size = smi_buf.size;
60512c956c4SStuart Hayes 	wsmt_enabled = true;
60612c956c4SStuart Hayes 	dev_info(&dcdbas_pdev->dev,
60712c956c4SStuart Hayes 		 "WSMT found, using firmware-provided SMI buffer.\n");
60812c956c4SStuart Hayes 	return 1;
60912c956c4SStuart Hayes }
61012c956c4SStuart Hayes 
61190563ec4SDoug Warzecha /**
61290563ec4SDoug Warzecha  * dcdbas_reboot_notify: handle reboot notification for host control
61390563ec4SDoug Warzecha  */
dcdbas_reboot_notify(struct notifier_block * nb,unsigned long code,void * unused)61490563ec4SDoug Warzecha static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code,
61590563ec4SDoug Warzecha 				void *unused)
61690563ec4SDoug Warzecha {
61790563ec4SDoug Warzecha 	switch (code) {
61890563ec4SDoug Warzecha 	case SYS_DOWN:
61990563ec4SDoug Warzecha 	case SYS_HALT:
62090563ec4SDoug Warzecha 	case SYS_POWER_OFF:
62190563ec4SDoug Warzecha 		if (host_control_on_shutdown) {
62290563ec4SDoug Warzecha 			/* firmware is going to perform host control action */
623e041c683SAlan Stern 			printk(KERN_WARNING "Please wait for shutdown "
62490563ec4SDoug Warzecha 			       "action to complete...\n");
62590563ec4SDoug Warzecha 			dcdbas_host_control();
62690563ec4SDoug Warzecha 		}
62790563ec4SDoug Warzecha 		break;
62890563ec4SDoug Warzecha 	}
62990563ec4SDoug Warzecha 
63090563ec4SDoug Warzecha 	return NOTIFY_DONE;
63190563ec4SDoug Warzecha }
63290563ec4SDoug Warzecha 
63390563ec4SDoug Warzecha static struct notifier_block dcdbas_reboot_nb = {
63490563ec4SDoug Warzecha 	.notifier_call = dcdbas_reboot_notify,
63590563ec4SDoug Warzecha 	.next = NULL,
636e041c683SAlan Stern 	.priority = INT_MIN
63790563ec4SDoug Warzecha };
63890563ec4SDoug Warzecha 
639c0cc60b3SThomas Weißschuh static const BIN_ATTR_ADMIN_RW(smi_data, 0);
64090563ec4SDoug Warzecha 
641c0cc60b3SThomas Weißschuh static const struct bin_attribute *const dcdbas_bin_attrs[] = {
64290563ec4SDoug Warzecha 	&bin_attr_smi_data,
64390563ec4SDoug Warzecha 	NULL
64490563ec4SDoug Warzecha };
64590563ec4SDoug Warzecha 
64690563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_RW(smi_data_buf_size);
64790563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr);
64890563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_WO(smi_request);
64990563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_RW(host_control_action);
65090563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_RW(host_control_smi_type);
65190563ec4SDoug Warzecha static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown);
65290563ec4SDoug Warzecha 
653a7f3ea72SDmitry Torokhov static struct attribute *dcdbas_dev_attrs[] = {
654a7f3ea72SDmitry Torokhov 	&dev_attr_smi_data_buf_size.attr,
655a7f3ea72SDmitry Torokhov 	&dev_attr_smi_data_buf_phys_addr.attr,
656a7f3ea72SDmitry Torokhov 	&dev_attr_smi_request.attr,
657a7f3ea72SDmitry Torokhov 	&dev_attr_host_control_action.attr,
658a7f3ea72SDmitry Torokhov 	&dev_attr_host_control_smi_type.attr,
659a7f3ea72SDmitry Torokhov 	&dev_attr_host_control_on_shutdown.attr,
66090563ec4SDoug Warzecha 	NULL
66190563ec4SDoug Warzecha };
66290563ec4SDoug Warzecha 
66373e31076SArvind Yadav static const struct attribute_group dcdbas_attr_group = {
664a7f3ea72SDmitry Torokhov 	.attrs = dcdbas_dev_attrs,
665*fb506e31SThomas Weißschuh 	.bin_attrs = dcdbas_bin_attrs,
666a7f3ea72SDmitry Torokhov };
667a7f3ea72SDmitry Torokhov 
dcdbas_probe(struct platform_device * dev)6680fe763c5SGreg Kroah-Hartman static int dcdbas_probe(struct platform_device *dev)
66990563ec4SDoug Warzecha {
6704c33dea7SGreg KH 	int error;
67190563ec4SDoug Warzecha 
67290563ec4SDoug Warzecha 	host_control_action = HC_ACTION_NONE;
67390563ec4SDoug Warzecha 	host_control_smi_type = HC_SMITYPE_NONE;
67490563ec4SDoug Warzecha 
67520d897e4SRussell King 	dcdbas_pdev = dev;
67620d897e4SRussell King 
67712c956c4SStuart Hayes 	/* Check if ACPI WSMT table specifies protected SMI buffer address */
67812c956c4SStuart Hayes 	error = dcdbas_check_wsmt();
67912c956c4SStuart Hayes 	if (error < 0)
68012c956c4SStuart Hayes 		return error;
68112c956c4SStuart Hayes 
68290563ec4SDoug Warzecha 	/*
68390563ec4SDoug Warzecha 	 * BIOS SMI calls require buffer addresses be in 32-bit address space.
68490563ec4SDoug Warzecha 	 * This is done by setting the DMA mask below.
68590563ec4SDoug Warzecha 	 */
68620d897e4SRussell King 	error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32));
68720d897e4SRussell King 	if (error)
68820d897e4SRussell King 		return error;
68990563ec4SDoug Warzecha 
690a7f3ea72SDmitry Torokhov 	error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group);
691a7f3ea72SDmitry Torokhov 	if (error)
692a7f3ea72SDmitry Torokhov 		return error;
693a7f3ea72SDmitry Torokhov 
69490563ec4SDoug Warzecha 	register_reboot_notifier(&dcdbas_reboot_nb);
69590563ec4SDoug Warzecha 
696a7f3ea72SDmitry Torokhov 	dev_info(&dev->dev, "%s (version %s)\n",
69790563ec4SDoug Warzecha 		 DRIVER_DESCRIPTION, DRIVER_VERSION);
69890563ec4SDoug Warzecha 
69990563ec4SDoug Warzecha 	return 0;
70090563ec4SDoug Warzecha }
70190563ec4SDoug Warzecha 
dcdbas_remove(struct platform_device * dev)702467daaf0SUwe Kleine-König static void dcdbas_remove(struct platform_device *dev)
703a7f3ea72SDmitry Torokhov {
704a7f3ea72SDmitry Torokhov 	unregister_reboot_notifier(&dcdbas_reboot_nb);
705a7f3ea72SDmitry Torokhov 	sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
706a7f3ea72SDmitry Torokhov }
707a7f3ea72SDmitry Torokhov 
708a7f3ea72SDmitry Torokhov static struct platform_driver dcdbas_driver = {
709a7f3ea72SDmitry Torokhov 	.driver		= {
710a7f3ea72SDmitry Torokhov 		.name	= DRIVER_NAME,
711a7f3ea72SDmitry Torokhov 	},
712a7f3ea72SDmitry Torokhov 	.probe		= dcdbas_probe,
7133ea5eb68SUwe Kleine-König 	.remove		= dcdbas_remove,
714a7f3ea72SDmitry Torokhov };
715a7f3ea72SDmitry Torokhov 
7163be5588aSAndi Kleen static const struct platform_device_info dcdbas_dev_info __initconst = {
71720d897e4SRussell King 	.name		= DRIVER_NAME,
7188d05fc03SBarnabás Pőcze 	.id		= PLATFORM_DEVID_NONE,
71920d897e4SRussell King 	.dma_mask	= DMA_BIT_MASK(32),
72020d897e4SRussell King };
72120d897e4SRussell King 
72220d897e4SRussell King static struct platform_device *dcdbas_pdev_reg;
72320d897e4SRussell King 
724a7f3ea72SDmitry Torokhov /**
725a7f3ea72SDmitry Torokhov  * dcdbas_init: initialize driver
726a7f3ea72SDmitry Torokhov  */
dcdbas_init(void)727a7f3ea72SDmitry Torokhov static int __init dcdbas_init(void)
728a7f3ea72SDmitry Torokhov {
729a7f3ea72SDmitry Torokhov 	int error;
730a7f3ea72SDmitry Torokhov 
731a7f3ea72SDmitry Torokhov 	error = platform_driver_register(&dcdbas_driver);
732a7f3ea72SDmitry Torokhov 	if (error)
733a7f3ea72SDmitry Torokhov 		return error;
734a7f3ea72SDmitry Torokhov 
73520d897e4SRussell King 	dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info);
73620d897e4SRussell King 	if (IS_ERR(dcdbas_pdev_reg)) {
73720d897e4SRussell King 		error = PTR_ERR(dcdbas_pdev_reg);
738a7f3ea72SDmitry Torokhov 		goto err_unregister_driver;
739a7f3ea72SDmitry Torokhov 	}
740a7f3ea72SDmitry Torokhov 
741a7f3ea72SDmitry Torokhov 	return 0;
742a7f3ea72SDmitry Torokhov 
743a7f3ea72SDmitry Torokhov  err_unregister_driver:
744a7f3ea72SDmitry Torokhov 	platform_driver_unregister(&dcdbas_driver);
745a7f3ea72SDmitry Torokhov 	return error;
746a7f3ea72SDmitry Torokhov }
747a7f3ea72SDmitry Torokhov 
74890563ec4SDoug Warzecha /**
74990563ec4SDoug Warzecha  * dcdbas_exit: perform driver cleanup
75090563ec4SDoug Warzecha  */
dcdbas_exit(void)75190563ec4SDoug Warzecha static void __exit dcdbas_exit(void)
75290563ec4SDoug Warzecha {
753435a80f6SDoug Warzecha 	/*
754435a80f6SDoug Warzecha 	 * make sure functions that use dcdbas_pdev are called
755435a80f6SDoug Warzecha 	 * before platform_device_unregister
756435a80f6SDoug Warzecha 	 */
75790563ec4SDoug Warzecha 	unregister_reboot_notifier(&dcdbas_reboot_nb);
758a7f3ea72SDmitry Torokhov 
759a7f3ea72SDmitry Torokhov 	/*
760a7f3ea72SDmitry Torokhov 	 * We have to free the buffer here instead of dcdbas_remove
761a7f3ea72SDmitry Torokhov 	 * because only in module exit function we can be sure that
762a7f3ea72SDmitry Torokhov 	 * all sysfs attributes belonging to this module have been
763a7f3ea72SDmitry Torokhov 	 * released.
764a7f3ea72SDmitry Torokhov 	 */
76520d897e4SRussell King 	if (dcdbas_pdev)
766a7f3ea72SDmitry Torokhov 		smi_data_buf_free();
76747a94c55SStuart Hayes 	if (bios_buffer)
76847a94c55SStuart Hayes 		memunmap(bios_buffer);
76920d897e4SRussell King 	platform_device_unregister(dcdbas_pdev_reg);
770e3ed249aSAxel Lin 	platform_driver_unregister(&dcdbas_driver);
77190563ec4SDoug Warzecha }
77290563ec4SDoug Warzecha 
77349368c13SDarren Hart (VMware) subsys_initcall_sync(dcdbas_init);
77490563ec4SDoug Warzecha module_exit(dcdbas_exit);
77590563ec4SDoug Warzecha 
77690563ec4SDoug Warzecha MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
77790563ec4SDoug Warzecha MODULE_VERSION(DRIVER_VERSION);
77890563ec4SDoug Warzecha MODULE_AUTHOR("Dell Inc.");
77990563ec4SDoug Warzecha MODULE_LICENSE("GPL");
7808f47f0b6SMatt Domsch /* Any System or BIOS claiming to be by Dell */
7818f47f0b6SMatt Domsch MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");
782