25d47027e1
Some race conditions were raised due to dell-smbios and its backends not being ready by the time that a consumer would call one of the exported methods. To avoid this problem, guarantee that all initialization has been done by linking them all together and running init for them all. As part of this change the Kconfig needs to be adjusted so that CONFIG_DELL_SMBIOS_SMM and CONFIG_DELL_SMBIOS_WMI are boolean rather than modules. CONFIG_DELL_SMBIOS is a visually selectable option again and both CONFIG_DELL_SMBIOS_WMI and CONFIG_DELL_SMBIOS_SMM are optional. Signed-off-by: Mario Limonciello <mario.limonciello@dell.com> [dvhart: Update prompt and help text for DELL_SMBIOS_* backends] Signed-off-by: Darren Hart (VMware) <dvhart@infradead.org>
187 lines
4.4 KiB
C
187 lines
4.4 KiB
C
/*
|
|
* SMI methods for use with dell-smbios
|
|
*
|
|
* Copyright (c) Red Hat <mjg@redhat.com>
|
|
* Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
|
|
* Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com>
|
|
* Copyright (c) 2017 Dell Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/dmi.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include "../../firmware/dcdbas.h"
|
|
#include "dell-smbios.h"
|
|
|
|
static int da_command_address;
|
|
static int da_command_code;
|
|
static struct calling_interface_buffer *buffer;
|
|
struct platform_device *platform_device;
|
|
static DEFINE_MUTEX(smm_mutex);
|
|
|
|
static const struct dmi_system_id dell_device_table[] __initconst = {
|
|
{
|
|
.ident = "Dell laptop",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
|
|
},
|
|
},
|
|
{
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/
|
|
},
|
|
},
|
|
{
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/
|
|
},
|
|
},
|
|
{
|
|
.ident = "Dell Computer Corporation",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
|
|
DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
|
|
},
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(dmi, dell_device_table);
|
|
|
|
static void parse_da_table(const struct dmi_header *dm)
|
|
{
|
|
struct calling_interface_structure *table =
|
|
container_of(dm, struct calling_interface_structure, header);
|
|
|
|
/* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
|
|
* 6 bytes of entry
|
|
*/
|
|
if (dm->length < 17)
|
|
return;
|
|
|
|
da_command_address = table->cmdIOAddress;
|
|
da_command_code = table->cmdIOCode;
|
|
}
|
|
|
|
static void find_cmd_address(const struct dmi_header *dm, void *dummy)
|
|
{
|
|
switch (dm->type) {
|
|
case 0xda: /* Calling interface */
|
|
parse_da_table(dm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int dell_smbios_smm_call(struct calling_interface_buffer *input)
|
|
{
|
|
struct smi_cmd command;
|
|
size_t size;
|
|
|
|
size = sizeof(struct calling_interface_buffer);
|
|
command.magic = SMI_CMD_MAGIC;
|
|
command.command_address = da_command_address;
|
|
command.command_code = da_command_code;
|
|
command.ebx = virt_to_phys(buffer);
|
|
command.ecx = 0x42534931;
|
|
|
|
mutex_lock(&smm_mutex);
|
|
memcpy(buffer, input, size);
|
|
dcdbas_smi_request(&command);
|
|
memcpy(input, buffer, size);
|
|
mutex_unlock(&smm_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* When enabled this indicates that SMM won't work */
|
|
static bool test_wsmt_enabled(void)
|
|
{
|
|
struct calling_interface_token *wsmt;
|
|
|
|
/* if token doesn't exist, SMM will work */
|
|
wsmt = dell_smbios_find_token(WSMT_EN_TOKEN);
|
|
if (!wsmt)
|
|
return false;
|
|
|
|
/* If token exists, try to access over SMM but set a dummy return.
|
|
* - If WSMT disabled it will be overwritten by SMM
|
|
* - If WSMT enabled then dummy value will remain
|
|
*/
|
|
buffer->cmd_class = CLASS_TOKEN_READ;
|
|
buffer->cmd_select = SELECT_TOKEN_STD;
|
|
memset(buffer, 0, sizeof(struct calling_interface_buffer));
|
|
buffer->input[0] = wsmt->location;
|
|
buffer->output[0] = 99;
|
|
dell_smbios_smm_call(buffer);
|
|
if (buffer->output[0] == 99)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int init_dell_smbios_smm(void)
|
|
{
|
|
int ret;
|
|
/*
|
|
* Allocate buffer below 4GB for SMI data--only 32-bit physical addr
|
|
* is passed to SMI handler.
|
|
*/
|
|
buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
dmi_walk(find_cmd_address, NULL);
|
|
|
|
if (test_wsmt_enabled()) {
|
|
pr_debug("Disabling due to WSMT enabled\n");
|
|
ret = -ENODEV;
|
|
goto fail_wsmt;
|
|
}
|
|
|
|
platform_device = platform_device_alloc("dell-smbios", 1);
|
|
if (!platform_device) {
|
|
ret = -ENOMEM;
|
|
goto fail_platform_device_alloc;
|
|
}
|
|
|
|
ret = platform_device_add(platform_device);
|
|
if (ret)
|
|
goto fail_platform_device_add;
|
|
|
|
ret = dell_smbios_register_device(&platform_device->dev,
|
|
&dell_smbios_smm_call);
|
|
if (ret)
|
|
goto fail_register;
|
|
|
|
return 0;
|
|
|
|
fail_register:
|
|
platform_device_del(platform_device);
|
|
|
|
fail_platform_device_add:
|
|
platform_device_put(platform_device);
|
|
|
|
fail_wsmt:
|
|
fail_platform_device_alloc:
|
|
free_page((unsigned long)buffer);
|
|
return ret;
|
|
}
|
|
|
|
void exit_dell_smbios_smm(void)
|
|
{
|
|
if (platform_device) {
|
|
dell_smbios_unregister_device(&platform_device->dev);
|
|
platform_device_unregister(platform_device);
|
|
free_page((unsigned long)buffer);
|
|
}
|
|
}
|