162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * dcdbas.c: Dell Systems Management Base Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * The Dell Systems Management Base Driver provides a sysfs interface for 662306a36Sopenharmony_ci * systems management software to perform System Management Interrupts (SMIs) 762306a36Sopenharmony_ci * and Host Control Actions (power cycle or power off after OS shutdown) on 862306a36Sopenharmony_ci * Dell systems. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * See Documentation/driver-api/dcdbas.rst for more information. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Copyright (C) 1995-2006 Dell Inc. 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/acpi.h> 1762306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1862306a36Sopenharmony_ci#include <linux/dmi.h> 1962306a36Sopenharmony_ci#include <linux/errno.h> 2062306a36Sopenharmony_ci#include <linux/cpu.h> 2162306a36Sopenharmony_ci#include <linux/gfp.h> 2262306a36Sopenharmony_ci#include <linux/init.h> 2362306a36Sopenharmony_ci#include <linux/io.h> 2462306a36Sopenharmony_ci#include <linux/kernel.h> 2562306a36Sopenharmony_ci#include <linux/mc146818rtc.h> 2662306a36Sopenharmony_ci#include <linux/module.h> 2762306a36Sopenharmony_ci#include <linux/reboot.h> 2862306a36Sopenharmony_ci#include <linux/sched.h> 2962306a36Sopenharmony_ci#include <linux/smp.h> 3062306a36Sopenharmony_ci#include <linux/spinlock.h> 3162306a36Sopenharmony_ci#include <linux/string.h> 3262306a36Sopenharmony_ci#include <linux/types.h> 3362306a36Sopenharmony_ci#include <linux/mutex.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include "dcdbas.h" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define DRIVER_NAME "dcdbas" 3862306a36Sopenharmony_ci#define DRIVER_VERSION "5.6.0-3.4" 3962306a36Sopenharmony_ci#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct platform_device *dcdbas_pdev; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE; 4462306a36Sopenharmony_cistatic DEFINE_MUTEX(smi_data_lock); 4562306a36Sopenharmony_cistatic u8 *bios_buffer; 4662306a36Sopenharmony_cistatic struct smi_buffer smi_buf; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic unsigned int host_control_action; 4962306a36Sopenharmony_cistatic unsigned int host_control_smi_type; 5062306a36Sopenharmony_cistatic unsigned int host_control_on_shutdown; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic bool wsmt_enabled; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ciint dcdbas_smi_alloc(struct smi_buffer *smi_buffer, unsigned long size) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci smi_buffer->virt = dma_alloc_coherent(&dcdbas_pdev->dev, size, 5762306a36Sopenharmony_ci &smi_buffer->dma, GFP_KERNEL); 5862306a36Sopenharmony_ci if (!smi_buffer->virt) { 5962306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, 6062306a36Sopenharmony_ci "%s: failed to allocate memory size %lu\n", 6162306a36Sopenharmony_ci __func__, size); 6262306a36Sopenharmony_ci return -ENOMEM; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci smi_buffer->size = size; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", 6762306a36Sopenharmony_ci __func__, (u32)smi_buffer->dma, smi_buffer->size); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dcdbas_smi_alloc); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_civoid dcdbas_smi_free(struct smi_buffer *smi_buffer) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci if (!smi_buffer->virt) 7662306a36Sopenharmony_ci return; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", 7962306a36Sopenharmony_ci __func__, (u32)smi_buffer->dma, smi_buffer->size); 8062306a36Sopenharmony_ci dma_free_coherent(&dcdbas_pdev->dev, smi_buffer->size, 8162306a36Sopenharmony_ci smi_buffer->virt, smi_buffer->dma); 8262306a36Sopenharmony_ci smi_buffer->virt = NULL; 8362306a36Sopenharmony_ci smi_buffer->dma = 0; 8462306a36Sopenharmony_ci smi_buffer->size = 0; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dcdbas_smi_free); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/** 8962306a36Sopenharmony_ci * smi_data_buf_free: free SMI data buffer 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_cistatic void smi_data_buf_free(void) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci if (!smi_buf.virt || wsmt_enabled) 9462306a36Sopenharmony_ci return; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci dcdbas_smi_free(&smi_buf); 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/** 10062306a36Sopenharmony_ci * smi_data_buf_realloc: grow SMI data buffer if needed 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_cistatic int smi_data_buf_realloc(unsigned long size) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct smi_buffer tmp; 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (smi_buf.size >= size) 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (size > max_smi_data_buf_size) 11162306a36Sopenharmony_ci return -EINVAL; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* new buffer is needed */ 11462306a36Sopenharmony_ci ret = dcdbas_smi_alloc(&tmp, size); 11562306a36Sopenharmony_ci if (ret) 11662306a36Sopenharmony_ci return ret; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* memory zeroed by dma_alloc_coherent */ 11962306a36Sopenharmony_ci if (smi_buf.virt) 12062306a36Sopenharmony_ci memcpy(tmp.virt, smi_buf.virt, smi_buf.size); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* free any existing buffer */ 12362306a36Sopenharmony_ci smi_data_buf_free(); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* set up new buffer for use */ 12662306a36Sopenharmony_ci smi_buf = tmp; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic ssize_t smi_data_buf_phys_addr_show(struct device *dev, 13262306a36Sopenharmony_ci struct device_attribute *attr, 13362306a36Sopenharmony_ci char *buf) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci return sprintf(buf, "%x\n", (u32)smi_buf.dma); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic ssize_t smi_data_buf_size_show(struct device *dev, 13962306a36Sopenharmony_ci struct device_attribute *attr, 14062306a36Sopenharmony_ci char *buf) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci return sprintf(buf, "%lu\n", smi_buf.size); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic ssize_t smi_data_buf_size_store(struct device *dev, 14662306a36Sopenharmony_ci struct device_attribute *attr, 14762306a36Sopenharmony_ci const char *buf, size_t count) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci unsigned long buf_size; 15062306a36Sopenharmony_ci ssize_t ret; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci buf_size = simple_strtoul(buf, NULL, 10); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* make sure SMI data buffer is at least buf_size */ 15562306a36Sopenharmony_ci mutex_lock(&smi_data_lock); 15662306a36Sopenharmony_ci ret = smi_data_buf_realloc(buf_size); 15762306a36Sopenharmony_ci mutex_unlock(&smi_data_lock); 15862306a36Sopenharmony_ci if (ret) 15962306a36Sopenharmony_ci return ret; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return count; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic ssize_t smi_data_read(struct file *filp, struct kobject *kobj, 16562306a36Sopenharmony_ci struct bin_attribute *bin_attr, 16662306a36Sopenharmony_ci char *buf, loff_t pos, size_t count) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci ssize_t ret; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci mutex_lock(&smi_data_lock); 17162306a36Sopenharmony_ci ret = memory_read_from_buffer(buf, count, &pos, smi_buf.virt, 17262306a36Sopenharmony_ci smi_buf.size); 17362306a36Sopenharmony_ci mutex_unlock(&smi_data_lock); 17462306a36Sopenharmony_ci return ret; 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic ssize_t smi_data_write(struct file *filp, struct kobject *kobj, 17862306a36Sopenharmony_ci struct bin_attribute *bin_attr, 17962306a36Sopenharmony_ci char *buf, loff_t pos, size_t count) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci ssize_t ret; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if ((pos + count) > max_smi_data_buf_size) 18462306a36Sopenharmony_ci return -EINVAL; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci mutex_lock(&smi_data_lock); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci ret = smi_data_buf_realloc(pos + count); 18962306a36Sopenharmony_ci if (ret) 19062306a36Sopenharmony_ci goto out; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci memcpy(smi_buf.virt + pos, buf, count); 19362306a36Sopenharmony_ci ret = count; 19462306a36Sopenharmony_ciout: 19562306a36Sopenharmony_ci mutex_unlock(&smi_data_lock); 19662306a36Sopenharmony_ci return ret; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic ssize_t host_control_action_show(struct device *dev, 20062306a36Sopenharmony_ci struct device_attribute *attr, 20162306a36Sopenharmony_ci char *buf) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci return sprintf(buf, "%u\n", host_control_action); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic ssize_t host_control_action_store(struct device *dev, 20762306a36Sopenharmony_ci struct device_attribute *attr, 20862306a36Sopenharmony_ci const char *buf, size_t count) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci ssize_t ret; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* make sure buffer is available for host control command */ 21362306a36Sopenharmony_ci mutex_lock(&smi_data_lock); 21462306a36Sopenharmony_ci ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); 21562306a36Sopenharmony_ci mutex_unlock(&smi_data_lock); 21662306a36Sopenharmony_ci if (ret) 21762306a36Sopenharmony_ci return ret; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci host_control_action = simple_strtoul(buf, NULL, 10); 22062306a36Sopenharmony_ci return count; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic ssize_t host_control_smi_type_show(struct device *dev, 22462306a36Sopenharmony_ci struct device_attribute *attr, 22562306a36Sopenharmony_ci char *buf) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci return sprintf(buf, "%u\n", host_control_smi_type); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic ssize_t host_control_smi_type_store(struct device *dev, 23162306a36Sopenharmony_ci struct device_attribute *attr, 23262306a36Sopenharmony_ci const char *buf, size_t count) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci host_control_smi_type = simple_strtoul(buf, NULL, 10); 23562306a36Sopenharmony_ci return count; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic ssize_t host_control_on_shutdown_show(struct device *dev, 23962306a36Sopenharmony_ci struct device_attribute *attr, 24062306a36Sopenharmony_ci char *buf) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci return sprintf(buf, "%u\n", host_control_on_shutdown); 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic ssize_t host_control_on_shutdown_store(struct device *dev, 24662306a36Sopenharmony_ci struct device_attribute *attr, 24762306a36Sopenharmony_ci const char *buf, size_t count) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci host_control_on_shutdown = simple_strtoul(buf, NULL, 10); 25062306a36Sopenharmony_ci return count; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic int raise_smi(void *par) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct smi_cmd *smi_cmd = par; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (smp_processor_id() != 0) { 25862306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", 25962306a36Sopenharmony_ci __func__); 26062306a36Sopenharmony_ci return -EBUSY; 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* generate SMI */ 26462306a36Sopenharmony_ci /* inb to force posted write through and make SMI happen now */ 26562306a36Sopenharmony_ci asm volatile ( 26662306a36Sopenharmony_ci "outb %b0,%w1\n" 26762306a36Sopenharmony_ci "inb %w1" 26862306a36Sopenharmony_ci : /* no output args */ 26962306a36Sopenharmony_ci : "a" (smi_cmd->command_code), 27062306a36Sopenharmony_ci "d" (smi_cmd->command_address), 27162306a36Sopenharmony_ci "b" (smi_cmd->ebx), 27262306a36Sopenharmony_ci "c" (smi_cmd->ecx) 27362306a36Sopenharmony_ci : "memory" 27462306a36Sopenharmony_ci ); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return 0; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci/** 27962306a36Sopenharmony_ci * dcdbas_smi_request: generate SMI request 28062306a36Sopenharmony_ci * 28162306a36Sopenharmony_ci * Called with smi_data_lock. 28262306a36Sopenharmony_ci */ 28362306a36Sopenharmony_ciint dcdbas_smi_request(struct smi_cmd *smi_cmd) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci int ret; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci if (smi_cmd->magic != SMI_CMD_MAGIC) { 28862306a36Sopenharmony_ci dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", 28962306a36Sopenharmony_ci __func__); 29062306a36Sopenharmony_ci return -EBADR; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci /* SMI requires CPU 0 */ 29462306a36Sopenharmony_ci cpus_read_lock(); 29562306a36Sopenharmony_ci ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); 29662306a36Sopenharmony_ci cpus_read_unlock(); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci return ret; 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ciEXPORT_SYMBOL(dcdbas_smi_request); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci/** 30362306a36Sopenharmony_ci * smi_request_store: 30462306a36Sopenharmony_ci * 30562306a36Sopenharmony_ci * The valid values are: 30662306a36Sopenharmony_ci * 0: zero SMI data buffer 30762306a36Sopenharmony_ci * 1: generate calling interface SMI 30862306a36Sopenharmony_ci * 2: generate raw SMI 30962306a36Sopenharmony_ci * 31062306a36Sopenharmony_ci * User application writes smi_cmd to smi_data before telling driver 31162306a36Sopenharmony_ci * to generate SMI. 31262306a36Sopenharmony_ci */ 31362306a36Sopenharmony_cistatic ssize_t smi_request_store(struct device *dev, 31462306a36Sopenharmony_ci struct device_attribute *attr, 31562306a36Sopenharmony_ci const char *buf, size_t count) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct smi_cmd *smi_cmd; 31862306a36Sopenharmony_ci unsigned long val = simple_strtoul(buf, NULL, 10); 31962306a36Sopenharmony_ci ssize_t ret; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci mutex_lock(&smi_data_lock); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (smi_buf.size < sizeof(struct smi_cmd)) { 32462306a36Sopenharmony_ci ret = -ENODEV; 32562306a36Sopenharmony_ci goto out; 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci smi_cmd = (struct smi_cmd *)smi_buf.virt; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci switch (val) { 33062306a36Sopenharmony_ci case 2: 33162306a36Sopenharmony_ci /* Raw SMI */ 33262306a36Sopenharmony_ci ret = dcdbas_smi_request(smi_cmd); 33362306a36Sopenharmony_ci if (!ret) 33462306a36Sopenharmony_ci ret = count; 33562306a36Sopenharmony_ci break; 33662306a36Sopenharmony_ci case 1: 33762306a36Sopenharmony_ci /* 33862306a36Sopenharmony_ci * Calling Interface SMI 33962306a36Sopenharmony_ci * 34062306a36Sopenharmony_ci * Provide physical address of command buffer field within 34162306a36Sopenharmony_ci * the struct smi_cmd to BIOS. 34262306a36Sopenharmony_ci * 34362306a36Sopenharmony_ci * Because the address that smi_cmd (smi_buf.virt) points to 34462306a36Sopenharmony_ci * will be from memremap() of a non-memory address if WSMT 34562306a36Sopenharmony_ci * is present, we can't use virt_to_phys() on smi_cmd, so 34662306a36Sopenharmony_ci * we have to use the physical address that was saved when 34762306a36Sopenharmony_ci * the virtual address for smi_cmd was received. 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_ci smi_cmd->ebx = (u32)smi_buf.dma + 35062306a36Sopenharmony_ci offsetof(struct smi_cmd, command_buffer); 35162306a36Sopenharmony_ci ret = dcdbas_smi_request(smi_cmd); 35262306a36Sopenharmony_ci if (!ret) 35362306a36Sopenharmony_ci ret = count; 35462306a36Sopenharmony_ci break; 35562306a36Sopenharmony_ci case 0: 35662306a36Sopenharmony_ci memset(smi_buf.virt, 0, smi_buf.size); 35762306a36Sopenharmony_ci ret = count; 35862306a36Sopenharmony_ci break; 35962306a36Sopenharmony_ci default: 36062306a36Sopenharmony_ci ret = -EINVAL; 36162306a36Sopenharmony_ci break; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ciout: 36562306a36Sopenharmony_ci mutex_unlock(&smi_data_lock); 36662306a36Sopenharmony_ci return ret; 36762306a36Sopenharmony_ci} 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci/** 37062306a36Sopenharmony_ci * host_control_smi: generate host control SMI 37162306a36Sopenharmony_ci * 37262306a36Sopenharmony_ci * Caller must set up the host control command in smi_buf.virt. 37362306a36Sopenharmony_ci */ 37462306a36Sopenharmony_cistatic int host_control_smi(void) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci struct apm_cmd *apm_cmd; 37762306a36Sopenharmony_ci u8 *data; 37862306a36Sopenharmony_ci unsigned long flags; 37962306a36Sopenharmony_ci u32 num_ticks; 38062306a36Sopenharmony_ci s8 cmd_status; 38162306a36Sopenharmony_ci u8 index; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci apm_cmd = (struct apm_cmd *)smi_buf.virt; 38462306a36Sopenharmony_ci apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci switch (host_control_smi_type) { 38762306a36Sopenharmony_ci case HC_SMITYPE_TYPE1: 38862306a36Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 38962306a36Sopenharmony_ci /* write SMI data buffer physical address */ 39062306a36Sopenharmony_ci data = (u8 *)&smi_buf.dma; 39162306a36Sopenharmony_ci for (index = PE1300_CMOS_CMD_STRUCT_PTR; 39262306a36Sopenharmony_ci index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); 39362306a36Sopenharmony_ci index++, data++) { 39462306a36Sopenharmony_ci outb(index, 39562306a36Sopenharmony_ci (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); 39662306a36Sopenharmony_ci outb(*data, 39762306a36Sopenharmony_ci (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* first set status to -1 as called by spec */ 40162306a36Sopenharmony_ci cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; 40262306a36Sopenharmony_ci outb((u8) cmd_status, PCAT_APM_STATUS_PORT); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* generate SMM call */ 40562306a36Sopenharmony_ci outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); 40662306a36Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci /* wait a few to see if it executed */ 40962306a36Sopenharmony_ci num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; 41062306a36Sopenharmony_ci while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) { 41162306a36Sopenharmony_ci num_ticks--; 41262306a36Sopenharmony_ci if (num_ticks == EXPIRED_TIMER) 41362306a36Sopenharmony_ci return -ETIME; 41462306a36Sopenharmony_ci } 41562306a36Sopenharmony_ci break; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci case HC_SMITYPE_TYPE2: 41862306a36Sopenharmony_ci case HC_SMITYPE_TYPE3: 41962306a36Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 42062306a36Sopenharmony_ci /* write SMI data buffer physical address */ 42162306a36Sopenharmony_ci data = (u8 *)&smi_buf.dma; 42262306a36Sopenharmony_ci for (index = PE1400_CMOS_CMD_STRUCT_PTR; 42362306a36Sopenharmony_ci index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); 42462306a36Sopenharmony_ci index++, data++) { 42562306a36Sopenharmony_ci outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); 42662306a36Sopenharmony_ci outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci /* generate SMM call */ 43062306a36Sopenharmony_ci if (host_control_smi_type == HC_SMITYPE_TYPE3) 43162306a36Sopenharmony_ci outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); 43262306a36Sopenharmony_ci else 43362306a36Sopenharmony_ci outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* restore RTC index pointer since it was written to above */ 43662306a36Sopenharmony_ci CMOS_READ(RTC_REG_C); 43762306a36Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci /* read control port back to serialize write */ 44062306a36Sopenharmony_ci cmd_status = inb(PE1400_APM_CONTROL_PORT); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci /* wait a few to see if it executed */ 44362306a36Sopenharmony_ci num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; 44462306a36Sopenharmony_ci while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { 44562306a36Sopenharmony_ci num_ticks--; 44662306a36Sopenharmony_ci if (num_ticks == EXPIRED_TIMER) 44762306a36Sopenharmony_ci return -ETIME; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci break; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci default: 45262306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", 45362306a36Sopenharmony_ci __func__, host_control_smi_type); 45462306a36Sopenharmony_ci return -ENOSYS; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci return 0; 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci/** 46162306a36Sopenharmony_ci * dcdbas_host_control: initiate host control 46262306a36Sopenharmony_ci * 46362306a36Sopenharmony_ci * This function is called by the driver after the system has 46462306a36Sopenharmony_ci * finished shutting down if the user application specified a 46562306a36Sopenharmony_ci * host control action to perform on shutdown. It is safe to 46662306a36Sopenharmony_ci * use smi_buf.virt at this point because the system has finished 46762306a36Sopenharmony_ci * shutting down and no userspace apps are running. 46862306a36Sopenharmony_ci */ 46962306a36Sopenharmony_cistatic void dcdbas_host_control(void) 47062306a36Sopenharmony_ci{ 47162306a36Sopenharmony_ci struct apm_cmd *apm_cmd; 47262306a36Sopenharmony_ci u8 action; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (host_control_action == HC_ACTION_NONE) 47562306a36Sopenharmony_ci return; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci action = host_control_action; 47862306a36Sopenharmony_ci host_control_action = HC_ACTION_NONE; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci if (!smi_buf.virt) { 48162306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); 48262306a36Sopenharmony_ci return; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (smi_buf.size < sizeof(struct apm_cmd)) { 48662306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", 48762306a36Sopenharmony_ci __func__); 48862306a36Sopenharmony_ci return; 48962306a36Sopenharmony_ci } 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci apm_cmd = (struct apm_cmd *)smi_buf.virt; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci /* power off takes precedence */ 49462306a36Sopenharmony_ci if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { 49562306a36Sopenharmony_ci apm_cmd->command = ESM_APM_POWER_CYCLE; 49662306a36Sopenharmony_ci apm_cmd->reserved = 0; 49762306a36Sopenharmony_ci *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; 49862306a36Sopenharmony_ci host_control_smi(); 49962306a36Sopenharmony_ci } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { 50062306a36Sopenharmony_ci apm_cmd->command = ESM_APM_POWER_CYCLE; 50162306a36Sopenharmony_ci apm_cmd->reserved = 0; 50262306a36Sopenharmony_ci *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; 50362306a36Sopenharmony_ci host_control_smi(); 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci} 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci/* WSMT */ 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cistatic u8 checksum(u8 *buffer, u8 length) 51062306a36Sopenharmony_ci{ 51162306a36Sopenharmony_ci u8 sum = 0; 51262306a36Sopenharmony_ci u8 *end = buffer + length; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci while (buffer < end) 51562306a36Sopenharmony_ci sum += *buffer++; 51662306a36Sopenharmony_ci return sum; 51762306a36Sopenharmony_ci} 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_cistatic inline struct smm_eps_table *check_eps_table(u8 *addr) 52062306a36Sopenharmony_ci{ 52162306a36Sopenharmony_ci struct smm_eps_table *eps = (struct smm_eps_table *)addr; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0) 52462306a36Sopenharmony_ci return NULL; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci if (checksum(addr, eps->length) != 0) 52762306a36Sopenharmony_ci return NULL; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci return eps; 53062306a36Sopenharmony_ci} 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_cistatic int dcdbas_check_wsmt(void) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci const struct dmi_device *dev = NULL; 53562306a36Sopenharmony_ci struct acpi_table_wsmt *wsmt = NULL; 53662306a36Sopenharmony_ci struct smm_eps_table *eps = NULL; 53762306a36Sopenharmony_ci u64 bios_buf_paddr; 53862306a36Sopenharmony_ci u64 remap_size; 53962306a36Sopenharmony_ci u8 *addr; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); 54262306a36Sopenharmony_ci if (!wsmt) 54362306a36Sopenharmony_ci return 0; 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci /* Check if WSMT ACPI table shows that protection is enabled */ 54662306a36Sopenharmony_ci if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) || 54762306a36Sopenharmony_ci !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) 54862306a36Sopenharmony_ci return 0; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* 55162306a36Sopenharmony_ci * BIOS could provide the address/size of the protected buffer 55262306a36Sopenharmony_ci * in an SMBIOS string or in an EPS structure in 0xFxxxx. 55362306a36Sopenharmony_ci */ 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* Check SMBIOS for buffer address */ 55662306a36Sopenharmony_ci while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) 55762306a36Sopenharmony_ci if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr, 55862306a36Sopenharmony_ci &remap_size) == 2) 55962306a36Sopenharmony_ci goto remap; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci /* Scan for EPS (entry point structure) */ 56262306a36Sopenharmony_ci for (addr = (u8 *)__va(0xf0000); 56362306a36Sopenharmony_ci addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table)); 56462306a36Sopenharmony_ci addr += 16) { 56562306a36Sopenharmony_ci eps = check_eps_table(addr); 56662306a36Sopenharmony_ci if (eps) 56762306a36Sopenharmony_ci break; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (!eps) { 57162306a36Sopenharmony_ci dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); 57262306a36Sopenharmony_ci return -ENODEV; 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci bios_buf_paddr = eps->smm_comm_buff_addr; 57562306a36Sopenharmony_ci remap_size = eps->num_of_4k_pages * PAGE_SIZE; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ciremap: 57862306a36Sopenharmony_ci /* 57962306a36Sopenharmony_ci * Get physical address of buffer and map to virtual address. 58062306a36Sopenharmony_ci * Table gives size in 4K pages, regardless of actual system page size. 58162306a36Sopenharmony_ci */ 58262306a36Sopenharmony_ci if (upper_32_bits(bios_buf_paddr + 8)) { 58362306a36Sopenharmony_ci dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); 58462306a36Sopenharmony_ci return -EINVAL; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci /* 58762306a36Sopenharmony_ci * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 58862306a36Sopenharmony_ci * bytes are used for a semaphore, not the data buffer itself). 58962306a36Sopenharmony_ci */ 59062306a36Sopenharmony_ci if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8) 59162306a36Sopenharmony_ci remap_size = MAX_SMI_DATA_BUF_SIZE + 8; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); 59462306a36Sopenharmony_ci if (!bios_buffer) { 59562306a36Sopenharmony_ci dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); 59662306a36Sopenharmony_ci return -ENOMEM; 59762306a36Sopenharmony_ci } 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci /* First 8 bytes is for a semaphore, not part of the smi_buf.virt */ 60062306a36Sopenharmony_ci smi_buf.dma = bios_buf_paddr + 8; 60162306a36Sopenharmony_ci smi_buf.virt = bios_buffer + 8; 60262306a36Sopenharmony_ci smi_buf.size = remap_size - 8; 60362306a36Sopenharmony_ci max_smi_data_buf_size = smi_buf.size; 60462306a36Sopenharmony_ci wsmt_enabled = true; 60562306a36Sopenharmony_ci dev_info(&dcdbas_pdev->dev, 60662306a36Sopenharmony_ci "WSMT found, using firmware-provided SMI buffer.\n"); 60762306a36Sopenharmony_ci return 1; 60862306a36Sopenharmony_ci} 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci/** 61162306a36Sopenharmony_ci * dcdbas_reboot_notify: handle reboot notification for host control 61262306a36Sopenharmony_ci */ 61362306a36Sopenharmony_cistatic int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, 61462306a36Sopenharmony_ci void *unused) 61562306a36Sopenharmony_ci{ 61662306a36Sopenharmony_ci switch (code) { 61762306a36Sopenharmony_ci case SYS_DOWN: 61862306a36Sopenharmony_ci case SYS_HALT: 61962306a36Sopenharmony_ci case SYS_POWER_OFF: 62062306a36Sopenharmony_ci if (host_control_on_shutdown) { 62162306a36Sopenharmony_ci /* firmware is going to perform host control action */ 62262306a36Sopenharmony_ci printk(KERN_WARNING "Please wait for shutdown " 62362306a36Sopenharmony_ci "action to complete...\n"); 62462306a36Sopenharmony_ci dcdbas_host_control(); 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci break; 62762306a36Sopenharmony_ci } 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci return NOTIFY_DONE; 63062306a36Sopenharmony_ci} 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_cistatic struct notifier_block dcdbas_reboot_nb = { 63362306a36Sopenharmony_ci .notifier_call = dcdbas_reboot_notify, 63462306a36Sopenharmony_ci .next = NULL, 63562306a36Sopenharmony_ci .priority = INT_MIN 63662306a36Sopenharmony_ci}; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic DCDBAS_BIN_ATTR_RW(smi_data); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic struct bin_attribute *dcdbas_bin_attrs[] = { 64162306a36Sopenharmony_ci &bin_attr_smi_data, 64262306a36Sopenharmony_ci NULL 64362306a36Sopenharmony_ci}; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(smi_data_buf_size); 64662306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); 64762306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_WO(smi_request); 64862306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_action); 64962306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_smi_type); 65062306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_cistatic struct attribute *dcdbas_dev_attrs[] = { 65362306a36Sopenharmony_ci &dev_attr_smi_data_buf_size.attr, 65462306a36Sopenharmony_ci &dev_attr_smi_data_buf_phys_addr.attr, 65562306a36Sopenharmony_ci &dev_attr_smi_request.attr, 65662306a36Sopenharmony_ci &dev_attr_host_control_action.attr, 65762306a36Sopenharmony_ci &dev_attr_host_control_smi_type.attr, 65862306a36Sopenharmony_ci &dev_attr_host_control_on_shutdown.attr, 65962306a36Sopenharmony_ci NULL 66062306a36Sopenharmony_ci}; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_cistatic const struct attribute_group dcdbas_attr_group = { 66362306a36Sopenharmony_ci .attrs = dcdbas_dev_attrs, 66462306a36Sopenharmony_ci .bin_attrs = dcdbas_bin_attrs, 66562306a36Sopenharmony_ci}; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_cistatic int dcdbas_probe(struct platform_device *dev) 66862306a36Sopenharmony_ci{ 66962306a36Sopenharmony_ci int error; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci host_control_action = HC_ACTION_NONE; 67262306a36Sopenharmony_ci host_control_smi_type = HC_SMITYPE_NONE; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci dcdbas_pdev = dev; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci /* Check if ACPI WSMT table specifies protected SMI buffer address */ 67762306a36Sopenharmony_ci error = dcdbas_check_wsmt(); 67862306a36Sopenharmony_ci if (error < 0) 67962306a36Sopenharmony_ci return error; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci /* 68262306a36Sopenharmony_ci * BIOS SMI calls require buffer addresses be in 32-bit address space. 68362306a36Sopenharmony_ci * This is done by setting the DMA mask below. 68462306a36Sopenharmony_ci */ 68562306a36Sopenharmony_ci error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); 68662306a36Sopenharmony_ci if (error) 68762306a36Sopenharmony_ci return error; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); 69062306a36Sopenharmony_ci if (error) 69162306a36Sopenharmony_ci return error; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci register_reboot_notifier(&dcdbas_reboot_nb); 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci dev_info(&dev->dev, "%s (version %s)\n", 69662306a36Sopenharmony_ci DRIVER_DESCRIPTION, DRIVER_VERSION); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci return 0; 69962306a36Sopenharmony_ci} 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_cistatic void dcdbas_remove(struct platform_device *dev) 70262306a36Sopenharmony_ci{ 70362306a36Sopenharmony_ci unregister_reboot_notifier(&dcdbas_reboot_nb); 70462306a36Sopenharmony_ci sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); 70562306a36Sopenharmony_ci} 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_cistatic struct platform_driver dcdbas_driver = { 70862306a36Sopenharmony_ci .driver = { 70962306a36Sopenharmony_ci .name = DRIVER_NAME, 71062306a36Sopenharmony_ci }, 71162306a36Sopenharmony_ci .probe = dcdbas_probe, 71262306a36Sopenharmony_ci .remove_new = dcdbas_remove, 71362306a36Sopenharmony_ci}; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic const struct platform_device_info dcdbas_dev_info __initconst = { 71662306a36Sopenharmony_ci .name = DRIVER_NAME, 71762306a36Sopenharmony_ci .id = PLATFORM_DEVID_NONE, 71862306a36Sopenharmony_ci .dma_mask = DMA_BIT_MASK(32), 71962306a36Sopenharmony_ci}; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic struct platform_device *dcdbas_pdev_reg; 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci/** 72462306a36Sopenharmony_ci * dcdbas_init: initialize driver 72562306a36Sopenharmony_ci */ 72662306a36Sopenharmony_cistatic int __init dcdbas_init(void) 72762306a36Sopenharmony_ci{ 72862306a36Sopenharmony_ci int error; 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci error = platform_driver_register(&dcdbas_driver); 73162306a36Sopenharmony_ci if (error) 73262306a36Sopenharmony_ci return error; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); 73562306a36Sopenharmony_ci if (IS_ERR(dcdbas_pdev_reg)) { 73662306a36Sopenharmony_ci error = PTR_ERR(dcdbas_pdev_reg); 73762306a36Sopenharmony_ci goto err_unregister_driver; 73862306a36Sopenharmony_ci } 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci return 0; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci err_unregister_driver: 74362306a36Sopenharmony_ci platform_driver_unregister(&dcdbas_driver); 74462306a36Sopenharmony_ci return error; 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci/** 74862306a36Sopenharmony_ci * dcdbas_exit: perform driver cleanup 74962306a36Sopenharmony_ci */ 75062306a36Sopenharmony_cistatic void __exit dcdbas_exit(void) 75162306a36Sopenharmony_ci{ 75262306a36Sopenharmony_ci /* 75362306a36Sopenharmony_ci * make sure functions that use dcdbas_pdev are called 75462306a36Sopenharmony_ci * before platform_device_unregister 75562306a36Sopenharmony_ci */ 75662306a36Sopenharmony_ci unregister_reboot_notifier(&dcdbas_reboot_nb); 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci /* 75962306a36Sopenharmony_ci * We have to free the buffer here instead of dcdbas_remove 76062306a36Sopenharmony_ci * because only in module exit function we can be sure that 76162306a36Sopenharmony_ci * all sysfs attributes belonging to this module have been 76262306a36Sopenharmony_ci * released. 76362306a36Sopenharmony_ci */ 76462306a36Sopenharmony_ci if (dcdbas_pdev) 76562306a36Sopenharmony_ci smi_data_buf_free(); 76662306a36Sopenharmony_ci if (bios_buffer) 76762306a36Sopenharmony_ci memunmap(bios_buffer); 76862306a36Sopenharmony_ci platform_device_unregister(dcdbas_pdev_reg); 76962306a36Sopenharmony_ci platform_driver_unregister(&dcdbas_driver); 77062306a36Sopenharmony_ci} 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_cisubsys_initcall_sync(dcdbas_init); 77362306a36Sopenharmony_cimodule_exit(dcdbas_exit); 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); 77662306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 77762306a36Sopenharmony_ciMODULE_AUTHOR("Dell Inc."); 77862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 77962306a36Sopenharmony_ci/* Any System or BIOS claiming to be by Dell */ 78062306a36Sopenharmony_ciMODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); 781