Commit 549b4930 authored by Mario Limonciello's avatar Mario Limonciello Committed by Darren Hart (VMware)

platform/x86: dell-smbios: Introduce dispatcher for SMM calls

This splits up the dell-smbios driver into two drivers:
* dell-smbios
* dell-smbios-smm

dell-smbios can operate with multiple different dispatcher drivers to
perform SMBIOS operations.

Also modify the interface that dell-laptop and dell-wmi use align to this
model more closely.  Rather than a single global buffer being allocated
for all drivers, each driver will allocate and be responsible for it's own
buffer. The pointer will be passed to the calling function and each
dispatcher driver will then internally copy it to the proper location to
perform it's call.

Add defines for calls used by these methods in the dell-smbios.h header
for tracking purposes.
Signed-off-by: default avatarMario Limonciello <mario.limonciello@dell.com>
Reviewed-by: default avatarEdward O'Callaghan <quasisec@google.com>
Signed-off-by: default avatarDarren Hart (VMware) <dvhart@infradead.org>
parent 33b9ca1e
......@@ -3985,6 +3985,12 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell-smbios.*
DELL SMBIOS SMM DRIVER
M: Mario Limonciello <mario.limonciello@dell.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell-smbios-smm.c
DELL LAPTOP DRIVER
M: Matthew Garrett <mjg59@srcf.ucam.org>
M: Pali Rohár <pali.rohar@gmail.com>
......
......@@ -93,12 +93,19 @@ config ASUS_LAPTOP
config DELL_SMBIOS
tristate
select DCDBAS
config DELL_SMBIOS_SMM
tristate "Dell SMBIOS calling interface (SMM implementation)"
depends on DCDBAS
default DCDBAS
select DELL_SMBIOS
---help---
This module provides common functions for kernel modules using
Dell SMBIOS.
This provides an implementation for the Dell SMBIOS calling interface
communicated over SMI/SMM.
If you have a Dell laptop, say Y or M here.
If you have a Dell computer from <=2017 you should say Y or M here.
If you aren't sure and this module doesn't work for your computer
it just won't load.
config DELL_LAPTOP
tristate "Dell Laptop Extras"
......
......@@ -12,6 +12,7 @@ obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o
obj-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o
obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o
......
This diff is collapsed.
/*
* 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 __init 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 __init 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;
}
static int __init dell_smbios_smm_init(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);
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_platform_device_alloc:
free_page((unsigned long)buffer);
return ret;
}
static void __exit dell_smbios_smm_exit(void)
{
if (platform_device) {
dell_smbios_unregister_device(&platform_device->dev);
platform_device_unregister(platform_device);
free_page((unsigned long)buffer);
}
}
subsys_initcall(dell_smbios_smm_init);
module_exit(dell_smbios_smm_exit);
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
MODULE_DESCRIPTION("Dell SMBIOS communications over SMI");
MODULE_LICENSE("GPL");
......@@ -19,33 +19,26 @@
#include <linux/capability.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include "../../firmware/dcdbas.h"
#include "dell-smbios.h"
struct calling_interface_structure {
struct dmi_header header;
u16 cmdIOAddress;
u8 cmdIOCode;
u32 supportedCmds;
struct calling_interface_token tokens[];
} __packed;
static struct calling_interface_buffer *buffer;
static DEFINE_MUTEX(buffer_mutex);
static int da_command_address;
static int da_command_code;
static int da_num_tokens;
static struct platform_device *platform_device;
static struct calling_interface_token *da_tokens;
static struct device_attribute *token_location_attrs;
static struct device_attribute *token_value_attrs;
static struct attribute **token_attrs;
static DEFINE_MUTEX(smbios_mutex);
struct smbios_device {
struct list_head list;
struct device *device;
int (*call_fn)(struct calling_interface_buffer *);
};
static LIST_HEAD(smbios_device_list);
int dell_smbios_error(int value)
{
......@@ -62,42 +55,71 @@ int dell_smbios_error(int value)
}
EXPORT_SYMBOL_GPL(dell_smbios_error);
struct calling_interface_buffer *dell_smbios_get_buffer(void)
int dell_smbios_register_device(struct device *d, void *call_fn)
{
mutex_lock(&buffer_mutex);
dell_smbios_clear_buffer();
return buffer;
}
EXPORT_SYMBOL_GPL(dell_smbios_get_buffer);
struct smbios_device *priv;
void dell_smbios_clear_buffer(void)
{
memset(buffer, 0, sizeof(struct calling_interface_buffer));
priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL);
if (!priv)
return -ENOMEM;
get_device(d);
priv->device = d;
priv->call_fn = call_fn;
mutex_lock(&smbios_mutex);
list_add_tail(&priv->list, &smbios_device_list);
mutex_unlock(&smbios_mutex);
dev_dbg(d, "Added device: %s\n", d->driver->name);
return 0;
}
EXPORT_SYMBOL_GPL(dell_smbios_clear_buffer);
EXPORT_SYMBOL_GPL(dell_smbios_register_device);
void dell_smbios_release_buffer(void)
void dell_smbios_unregister_device(struct device *d)
{
mutex_unlock(&buffer_mutex);
struct smbios_device *priv;
mutex_lock(&smbios_mutex);
list_for_each_entry(priv, &smbios_device_list, list) {
if (priv->device == d) {
list_del(&priv->list);
put_device(d);
break;
}
}
mutex_unlock(&smbios_mutex);
dev_dbg(d, "Remove device: %s\n", d->driver->name);
}
EXPORT_SYMBOL_GPL(dell_smbios_release_buffer);
EXPORT_SYMBOL_GPL(dell_smbios_unregister_device);
void dell_smbios_send_request(int class, int select)
int dell_smbios_call(struct calling_interface_buffer *buffer)
{
struct smi_cmd command;
int (*call_fn)(struct calling_interface_buffer *) = NULL;
struct device *selected_dev = NULL;
struct smbios_device *priv;
int ret;
mutex_lock(&smbios_mutex);
list_for_each_entry(priv, &smbios_device_list, list) {
if (!selected_dev || priv->device->id >= selected_dev->id) {
dev_dbg(priv->device, "Trying device ID: %d\n",
priv->device->id);
call_fn = priv->call_fn;
selected_dev = priv->device;
}
}
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;
if (!selected_dev) {
ret = -ENODEV;
pr_err("No dell-smbios drivers are loaded\n");
goto out_smbios_call;
}
buffer->cmd_class = class;
buffer->cmd_select = select;
ret = call_fn(buffer);
dcdbas_smi_request(&command);
out_smbios_call:
mutex_unlock(&smbios_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(dell_smbios_send_request);
EXPORT_SYMBOL_GPL(dell_smbios_call);
struct calling_interface_token *dell_smbios_find_token(int tokenid)
{
......@@ -146,9 +168,6 @@ static void __init parse_da_table(const struct dmi_header *dm)
if (dm->length < 17)
return;
da_command_address = table->cmdIOAddress;
da_command_code = table->cmdIOCode;
new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
sizeof(struct calling_interface_token),
GFP_KERNEL);
......@@ -344,7 +363,6 @@ static void free_group(struct platform_device *pdev)
kfree(token_location_attrs);
}
static int __init dell_smbios_init(void)
{
const struct dmi_device *valid;
......@@ -363,15 +381,6 @@ static int __init dell_smbios_init(void)
return -ENODEV;
}
/*
* 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) {
ret = -ENOMEM;
goto fail_buffer;
}
ret = platform_driver_register(&platform_driver);
if (ret)
goto fail_platform_driver;
......@@ -404,22 +413,20 @@ static int __init dell_smbios_init(void)
platform_driver_unregister(&platform_driver);
fail_platform_driver:
free_page((unsigned long)buffer);
fail_buffer:
kfree(da_tokens);
return ret;
}
static void __exit dell_smbios_exit(void)
{
mutex_lock(&smbios_mutex);
if (platform_device) {
free_group(platform_device);
platform_device_unregister(platform_device);
platform_driver_unregister(&platform_driver);
}
free_page((unsigned long)buffer);
kfree(da_tokens);
mutex_unlock(&smbios_mutex);
}
subsys_initcall(dell_smbios_init);
......
......@@ -16,6 +16,35 @@
#ifndef _DELL_SMBIOS_H_
#define _DELL_SMBIOS_H_
#include <linux/device.h>
/* Classes and selects used in kernel drivers */
#define CLASS_TOKEN_READ 0
#define CLASS_TOKEN_WRITE 1
#define SELECT_TOKEN_STD 0
#define SELECT_TOKEN_BAT 1
#define SELECT_TOKEN_AC 2
#define CLASS_KBD_BACKLIGHT 4
#define SELECT_KBD_BACKLIGHT 11
#define CLASS_INFO 17
#define SELECT_RFKILL 11
#define SELECT_APP_REGISTRATION 3
/* Tokens used in kernel drivers, any of these
* should be filtered from userspace access
*/
#define BRIGHTNESS_TOKEN 0x007d
#define KBD_LED_AC_TOKEN 0x0451
#define KBD_LED_OFF_TOKEN 0x01E1
#define KBD_LED_ON_TOKEN 0x01E2
#define KBD_LED_AUTO_TOKEN 0x01E3
#define KBD_LED_AUTO_25_TOKEN 0x02EA
#define KBD_LED_AUTO_50_TOKEN 0x02EB
#define KBD_LED_AUTO_75_TOKEN 0x02EC
#define KBD_LED_AUTO_100_TOKEN 0x02F6
#define GLOBAL_MIC_MUTE_ENABLE 0x0364
#define GLOBAL_MIC_MUTE_DISABLE 0x0365
struct notifier_block;
/* This structure will be modified by the firmware when we enter
......@@ -37,12 +66,19 @@ struct calling_interface_token {
};
};
int dell_smbios_error(int value);
struct calling_interface_structure {
struct dmi_header header;
u16 cmdIOAddress;
u8 cmdIOCode;
u32 supportedCmds;
struct calling_interface_token tokens[];
} __packed;
struct calling_interface_buffer *dell_smbios_get_buffer(void);
void dell_smbios_clear_buffer(void);
void dell_smbios_release_buffer(void);
void dell_smbios_send_request(int class, int select);
int dell_smbios_register_device(struct device *d, void *call_fn);
void dell_smbios_unregister_device(struct device *d);
int dell_smbios_error(int value);
int dell_smbios_call(struct calling_interface_buffer *buffer);
struct calling_interface_token *dell_smbios_find_token(int tokenid);
......
......@@ -638,13 +638,16 @@ static int dell_wmi_events_set_enabled(bool enable)
struct calling_interface_buffer *buffer;
int ret;
buffer = dell_smbios_get_buffer();
buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL);
buffer->cmd_class = CLASS_INFO;
buffer->cmd_select = SELECT_APP_REGISTRATION;
buffer->input[0] = 0x10000;
buffer->input[1] = 0x51534554;
buffer->input[3] = enable;
dell_smbios_send_request(17, 3);
ret = buffer->output[0];
dell_smbios_release_buffer();
ret = dell_smbios_call(buffer);
if (ret == 0)
ret = buffer->output[0];
kfree(buffer);
return dell_smbios_error(ret);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment