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