// SPDX-License-Identifier: GPL-2.0-only /* * dcdbas.c: Dell Systems Management Base Driver * * The Dell Systems Management Base Driver provides a sysfs interface for * systems management software to perform System Management Interrupts (SMIs) * and Host Control Actions (power cycle or power off after OS shutdown) on * Dell systems. * * See Documentation/userspace-api/dcdbas.rst for more information. * * Copyright (C) 1995-2006 Dell Inc.
*/
/* make sure SMI data buffer is at least buf_size */
mutex_lock(&smi_data_lock);
ret = smi_data_buf_realloc(buf_size);
mutex_unlock(&smi_data_lock); if (ret) return ret;
/* make sure buffer is available for host control command */
mutex_lock(&smi_data_lock);
ret = smi_data_buf_realloc(sizeof(struct apm_cmd));
mutex_unlock(&smi_data_lock); if (ret) return ret;
/* SMI requires CPU 0 */
cpus_read_lock();
ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true);
cpus_read_unlock();
return ret;
}
EXPORT_SYMBOL(dcdbas_smi_request);
/** * smi_request_store: * * The valid values are: * 0: zero SMI data buffer * 1: generate calling interface SMI * 2: generate raw SMI * * User application writes smi_cmd to smi_data before telling driver * to generate SMI.
*/ static ssize_t smi_request_store(struct device *dev, struct device_attribute *attr, constchar *buf, size_t count)
{ struct smi_cmd *smi_cmd; unsignedlong val = simple_strtoul(buf, NULL, 10);
ssize_t ret;
mutex_lock(&smi_data_lock);
if (smi_buf.size < sizeof(struct smi_cmd)) {
ret = -ENODEV; goto out;
}
smi_cmd = (struct smi_cmd *)smi_buf.virt;
switch (val) { case 2: /* Raw SMI */
ret = dcdbas_smi_request(smi_cmd); if (!ret)
ret = count; break; case 1: /* * Calling Interface SMI * * Provide physical address of command buffer field within * the struct smi_cmd to BIOS. * * Because the address that smi_cmd (smi_buf.virt) points to * will be from memremap() of a non-memory address if WSMT * is present, we can't use virt_to_phys() on smi_cmd, so * we have to use the physical address that was saved when * the virtual address for smi_cmd was received.
*/
smi_cmd->ebx = (u32)smi_buf.dma +
offsetof(struct smi_cmd, command_buffer);
ret = dcdbas_smi_request(smi_cmd); if (!ret)
ret = count; break; case 0:
memset(smi_buf.virt, 0, smi_buf.size);
ret = count; break; default:
ret = -EINVAL; break;
}
out:
mutex_unlock(&smi_data_lock); return ret;
}
/** * host_control_smi: generate host control SMI * * Caller must set up the host control command in smi_buf.virt.
*/ staticint host_control_smi(void)
{ struct apm_cmd *apm_cmd;
u8 *data; unsignedlong flags;
u32 num_ticks;
s8 cmd_status;
u8 index;
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--; if (num_ticks == EXPIRED_TIMER) return -ETIME;
} break;
case HC_SMITYPE_TYPE2: case HC_SMITYPE_TYPE3:
spin_lock_irqsave(&rtc_lock, flags); /* write SMI data buffer physical address */
data = (u8 *)&smi_buf.dma; for (index = PE1400_CMOS_CMD_STRUCT_PTR;
index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
index++, data++) {
outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
}
/* restore RTC index pointer since it was written to above */
CMOS_READ(RTC_REG_C);
spin_unlock_irqrestore(&rtc_lock, flags);
/* read control port back to serialize write */
cmd_status = inb(PE1400_APM_CONTROL_PORT);
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--; if (num_ticks == EXPIRED_TIMER) return -ETIME;
} break;
/** * dcdbas_host_control: initiate host control * * This function is called by the driver after the system has * finished shutting down if the user application specified a * host control action to perform on shutdown. It is safe to * use smi_buf.virt at this point because the system has finished * shutting down and no userspace apps are running.
*/ staticvoid dcdbas_host_control(void)
{ struct apm_cmd *apm_cmd;
u8 action;
if (host_control_action == HC_ACTION_NONE) return;
acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); if (!wsmt) return 0;
/* Check if WSMT ACPI table shows that protection is enabled */ if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) ||
!(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) return 0;
/* * BIOS could provide the address/size of the protected buffer * in an SMBIOS string or in an EPS structure in 0xFxxxx.
*/
/* Check SMBIOS for buffer address */ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr,
&remap_size) == 2) goto remap;
/* Scan for EPS (entry point structure) */ for (addr = (u8 *)__va(0xf0000);
addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table));
addr += 16) {
eps = check_eps_table(addr); if (eps) break;
}
if (!eps) {
dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); return -ENODEV;
}
bios_buf_paddr = eps->smm_comm_buff_addr;
remap_size = eps->num_of_4k_pages * PAGE_SIZE;
remap: /* * Get physical address of buffer and map to virtual address. * Table gives size in 4K pages, regardless of actual system page size.
*/ if (upper_32_bits(bios_buf_paddr + 8)) {
dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); return -EINVAL;
} /* * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 * bytes are used for a semaphore, not the data buffer itself).
*/ if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8)
remap_size = MAX_SMI_DATA_BUF_SIZE + 8;
bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); if (!bios_buffer) {
dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); return -ENOMEM;
}
/* First 8 bytes is for a semaphore, not part of the smi_buf.virt */
smi_buf.dma = bios_buf_paddr + 8;
smi_buf.virt = bios_buffer + 8;
smi_buf.size = remap_size - 8;
max_smi_data_buf_size = smi_buf.size;
wsmt_enabled = true;
dev_info(&dcdbas_pdev->dev, "WSMT found, using firmware-provided SMI buffer.\n"); return 1;
}
/** * dcdbas_reboot_notify: handle reboot notification for host control
*/ staticint dcdbas_reboot_notify(struct notifier_block *nb, unsignedlong code, void *unused)
{ switch (code) { case SYS_DOWN: case SYS_HALT: case SYS_POWER_OFF: if (host_control_on_shutdown) { /* firmware is going to perform host control action */
printk(KERN_WARNING "Please wait for shutdown " "action to complete...\n");
dcdbas_host_control();
} break;
}
/* * BIOS SMI calls require buffer addresses be in 32-bit address space. * This is done by setting the DMA mask below.
*/
error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); if (error) return error;
error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); if (error) return error;
/** * dcdbas_exit: perform driver cleanup
*/ staticvoid __exit dcdbas_exit(void)
{ /* * make sure functions that use dcdbas_pdev are called * before platform_device_unregister
*/
unregister_reboot_notifier(&dcdbas_reboot_nb);
/* * We have to free the buffer here instead of dcdbas_remove * because only in module exit function we can be sure that * all sysfs attributes belonging to this module have been * released.
*/ if (dcdbas_pdev)
smi_data_buf_free(); if (bios_buffer)
memunmap(bios_buffer);
platform_device_unregister(dcdbas_pdev_reg);
platform_driver_unregister(&dcdbas_driver);
}
MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
MODULE_VERSION(DRIVER_VERSION);
MODULE_AUTHOR("Dell Inc.");
MODULE_LICENSE("GPL"); /* Any System or BIOS claiming to be by Dell */
MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");
Messung V0.5
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.