Skip to content
Permalink
Browse files
IOCTL support for dell-wmi-sysman driver
Perform BIOS Management calls on supported Dell machines
through the Dell WMI System Management interface.

This interface provides IOCTL's to perform bundled
BIOS Setting transactions.

Cc: Hans de Goede <hdegoede@redhat.com>

Signed-off-by: Prasanth KSR <prasanth.ksr@dell.com>
Co-developed-by: Divya Bharathi <divya.bharathi@dell.com>
Signed-off-by: Divya Bharathi <divya.bharathi@dell.com>
Co-developed-by: Mario Limonciello <mario.limonciello@dell.com>
Signed-off-by: Mario Limonciello <mario.limonciello@dell.com>
  • Loading branch information
prasanthksr authored and intel-lab-lkp committed Feb 9, 2021
1 parent 6155670 commit 00141bcb2495c75a902d3070e149760b1050322e
Show file tree
Hide file tree
Showing 11 changed files with 946 additions and 89 deletions.
@@ -0,0 +1,39 @@
What: /dev/wmi/dell-wmi-sysman
Date: November 2021
KernelVersion: 5.15
Contact: "Divya Bharathi" <divya.bharathi@dell.com>
"Mario Limonciello" <mario.limonciello@dell.com>
"Prasanth K S R" <prasanth.ksr@dell.com>
Description:
Perform BIOS Management calls on supported Dell machines
through the Dell WMI System Management interface.

This interface provides IOCTL's to perform bundled
BIOS Setting transactions.

IOCTL's and buffer formats are defined in:
<uapi/linux/wmi.h>

1) To perform a BIOS System Management call from userspace,
you'll need to first determine the minimum size of the
system management interface buffer for your machine.
Platforms that contain larger buffers can return larger
objects from the system firmware.
Commonly this size is either 4k or 32k.

To determine the size of the buffer read() a u64 dword from
the WMI character device /dev/wmi/dell-wmi-sysman.

2) After you've determined the minimum size of the system management
interface buffer, you can allocate a structure that represents
the structure documented above (struct dell_wmi_sysman_buffer).

3) In this buffer object, prepare as necessary for the BIOS System
Management call you're interested in. Typically System Management
buffers have "length", "command" , "count" and "admin_password"
defined to values that coincide with the "data" you are interested in.

4) Run the call by using ioctl() as described in the header.

5) The output will be returned in the buffer object and
make sure to free up the allocated buffer.
@@ -6,12 +6,14 @@
* Copyright (c) 2020 Dell Inc.
*/

#include <uapi/linux/wmi.h>
#include <linux/wmi.h>
#include "dell-wmi-sysman.h"

#define SETDEFAULTVALUES_METHOD_ID 0x02
#define SETBIOSDEFAULTS_METHOD_ID 0x03
#define SETATTRIBUTE_METHOD_ID 0x04
#define SETATTRIBUTES_METHOD_ID 0x05

static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size,
int method_id)
@@ -41,17 +43,17 @@ static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args,
}

/**
* set_attribute() - Update an attribute value
* @a_name: The attribute name
* @a_value: The attribute value
* set_bios_defaults() - Resets BIOS defaults
* @deftype: the type of BIOS value reset to issue.
*
* Sets an attribute to new value
* Resets BIOS defaults
*/
int set_attribute(const char *a_name, const char *a_value)
int set_bios_defaults(u8 deftype)
{
size_t security_area_size, buffer_size;
size_t a_name_size, a_value_size;
char *buffer = NULL, *start;
size_t integer_area_size = sizeof(u8);
char *buffer = NULL;
u8 *defaultType;
int ret;

mutex_lock(&wmi_priv.mutex);
@@ -60,11 +62,8 @@ int set_attribute(const char *a_name, const char *a_value)
goto out;
}

/* build/calculate buffer */
security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
a_name_size = calculate_string_buffer(a_name);
a_value_size = calculate_string_buffer(a_value);
buffer_size = security_area_size + a_name_size + a_value_size;
buffer_size = security_area_size + integer_area_size;
buffer = kzalloc(buffer_size, GFP_KERNEL);
if (!buffer) {
ret = -ENOMEM;
@@ -74,53 +73,86 @@ int set_attribute(const char *a_name, const char *a_value)
/* build security area */
populate_security_buffer(buffer, wmi_priv.current_admin_password);

/* build variables to set */
start = buffer + security_area_size;
ret = populate_string_buffer(start, a_name_size, a_name);
if (ret < 0)
goto out;
start += ret;
ret = populate_string_buffer(start, a_value_size, a_value);
if (ret < 0)
goto out;
defaultType = buffer + security_area_size;
*defaultType = deftype;

print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
buffer, buffer_size,
SETATTRIBUTE_METHOD_ID);
if (ret == -EOPNOTSUPP)
dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n");
else if (ret == -EACCES)
dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n");
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size,
SETBIOSDEFAULTS_METHOD_ID);
if (ret)
dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret);

out:
kfree(buffer);
out:
mutex_unlock(&wmi_priv.mutex);
return ret;
}

/**
* set_bios_defaults() - Resets BIOS defaults
* @deftype: the type of BIOS value reset to issue.
* calculate_array_length() - calculate total size of string array
* @str_arr: array of strings
* @str_count: string count
*
* Resets BIOS defaults
*/
int set_bios_defaults(u8 deftype)
* Method to calculate the total size of array of string
**/
static int calculate_array_length(char **str_arr, int str_count)
{
size_t security_area_size, buffer_size;
size_t integer_area_size = sizeof(u8);
char *buffer = NULL;
u8 *defaultType;
int ret;
int ret = 0, i;

for (i = 0; i < str_count; ++i)
ret += calculate_string_buffer(str_arr[i]);
return ret;
}

/**
* set_attributes() - Update multiple attribute values
* @in_data: input set data
* @a_count: Number of atributes to be set
* @command: command to decide set user input value or default
*
* Sets attributes to user input value of defaut value
**/
int set_attributes(struct dell_set_data *in_data, int a_count, unsigned short command)
{
size_t security_area_size, string_area_size, buffer_size, attr_count_area;
char **a_names, **a_values;
char *buffer = NULL, *start;
int ret, method_id, i;
u32 *attr_count;

mutex_lock(&wmi_priv.mutex);
if (!wmi_priv.bios_attr_wdev) {
ret = -ENODEV;
goto out;
}

//allocate memory to hold set inputs
a_names = kmalloc(a_count * (sizeof(char *)), GFP_KERNEL);
if (!a_names) {
ret = -ENOMEM;
goto out;
}
if (command == SET_ATTRIBUTES) {
a_values = kmalloc(a_count * (sizeof(char *)), GFP_KERNEL);
if (!a_values) {
ret = -ENOMEM;
goto out;
}
}

//assign inputs to single array and send to set functions
for (i = 0; i < a_count; i++) {
a_names[i] = in_data[i].attribute_name;
if (command == SET_ATTRIBUTES)
a_values[i] = in_data[i].attribute_value;
}

security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
buffer_size = security_area_size + integer_area_size;
attr_count_area = sizeof(u32);
string_area_size = (calculate_array_length(a_names, a_count));
if (command == SET_ATTRIBUTES)
string_area_size += (calculate_array_length(a_values, a_count));
buffer_size = security_area_size + attr_count_area + string_area_size
+ (sizeof(u16) * a_count);
buffer = kzalloc(buffer_size, GFP_KERNEL);
if (!buffer) {
ret = -ENOMEM;
@@ -130,26 +162,153 @@ int set_bios_defaults(u8 deftype)
/* build security area */
populate_security_buffer(buffer, wmi_priv.current_admin_password);

defaultType = buffer + security_area_size;
*defaultType = deftype;
/* build variables to set */
attr_count = (u32 *)(buffer + security_area_size);
*attr_count = (u32)a_count;
start = (u8 *)(attr_count) + attr_count_area;

ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size,
SETBIOSDEFAULTS_METHOD_ID);
if (ret)
dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret);
for (i = 0; i < a_count; i++) {
ret = populate_string_buffer(start, calculate_string_buffer(a_names[i]),
a_names[i]);
if (ret < 0)
goto out;
start += ret;
}

if (command == SET_ATTRIBUTES) {
for (i = 0; i < a_count; i++) {
ret = populate_string_buffer(start, calculate_string_buffer(a_values[i]),
a_values[i]);
if (ret < 0)
goto out;
start += ret;
}
method_id = SETATTRIBUTES_METHOD_ID;
} else {
method_id = SETDEFAULTVALUES_METHOD_ID;
}

print_hex_dump_bytes("set multiple attribute: ", DUMP_PREFIX_NONE, buffer, buffer_size);
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
buffer, buffer_size, method_id);

if (ret == -EOPNOTSUPP)
dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n");
else if (ret == -EACCES)
dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n");

kfree(buffer);
out:
kfree(buffer);
kfree(a_names);
if (command == SET_ATTRIBUTES)
kfree(a_values);
mutex_unlock(&wmi_priv.mutex);
return ret;
}

__u64 get_attrs_size(void)
{
__u64 size = sizeof(struct dell_attributes_data) *
(get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID) +
get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID) +
get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID));
return size;
}

int run_sysman_call(struct dell_wmi_sysman_buffer *buf)
{
struct dell_set_password *pass_set_data;
struct dell_set_data *in_data;
int ret = -ENOIOCTLCMD;
char *tmp_system = NULL;
char *tmp_admin = NULL;

switch (buf->command) {
case ENUMERATE_ALL:
buf->count = get_attrs_size() / sizeof(struct dell_attributes_data);
get_enumeration_data(buf);
get_integer_data(buf);
get_string_data(buf);
ret = 0;
break;
case SET_ATTRIBUTES:
case SET_DEFAULTS:
if (!buf->count)
goto out;
in_data = (struct dell_set_data *)buf->data;
tmp_admin = kstrdup(wmi_priv.current_admin_password, GFP_KERNEL);
strlcpy_attr(wmi_priv.current_admin_password, buf->admin_password);
ret = set_attributes(in_data, buf->count, buf->command);
strlcpy_attr(wmi_priv.current_admin_password, tmp_admin);
kfree(tmp_admin);
break;
case GET_PASS:
get_po_data(buf);
ret = 0;
break;
case SET_PASS:
pass_set_data = (struct dell_set_password *)buf->data;
tmp_admin = kstrdup(wmi_priv.current_admin_password, GFP_KERNEL);
strlcpy_attr(wmi_priv.current_admin_password, buf->admin_password);

if (strcmp(pass_set_data->attribute_name, "System") == 0) {
tmp_system = kstrdup(wmi_priv.current_system_password, GFP_KERNEL);
strlcpy_attr(wmi_priv.current_system_password,
pass_set_data->system_password);
}

ret = set_new_password(pass_set_data->attribute_name, pass_set_data->new_password);
strlcpy_attr(wmi_priv.current_admin_password, tmp_admin);
kfree(tmp_admin);

if (tmp_system != NULL) {
strlcpy_attr(wmi_priv.current_system_password, tmp_system);
kfree(tmp_system);
}
break;
}
out:
return ret;
}


static long bios_attr_set_interface_filter(struct wmi_device *wdev, unsigned int cmd,
struct wmi_ioctl_buffer *arg)
{
struct dell_wmi_sysman_buffer *buf;
struct dell_resetBIOS *reset_buf;
char *tmp_admin = NULL;
int ret = -ENOIOCTLCMD;

switch (cmd) {
case DELL_WMI_SYSMAN_CMD:
buf = (struct dell_wmi_sysman_buffer *) arg;
ret = run_sysman_call(buf);
break;
case DELL_WMI_SYSMAN_RESET_BIOS:
reset_buf = (struct dell_resetBIOS *) arg;
if (reset_buf->option > 0) {
tmp_admin = kstrdup(wmi_priv.current_admin_password, GFP_KERNEL);
strlcpy_attr(wmi_priv.current_admin_password, reset_buf->admin_password);
ret = set_bios_defaults(reset_buf->option);
strlcpy_attr(wmi_priv.current_admin_password, tmp_admin);
kfree(tmp_admin);
}
break;
}
return ret;
}

static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
{
__u32 req_buf_size;
mutex_lock(&wmi_priv.mutex);
wmi_priv.bios_attr_wdev = wdev;
mutex_unlock(&wmi_priv.mutex);
return 0;
req_buf_size = get_attrs_size();
/* add in size of struct dell_wmi_sysman_buffer which is used internally with ioctl */
req_buf_size += sizeof(struct dell_wmi_sysman_buffer);
return set_required_buffer_size(wdev, req_buf_size);
}

static int bios_attr_set_interface_remove(struct wmi_device *wdev)
@@ -171,6 +330,7 @@ static struct wmi_driver bios_attr_set_interface_driver = {
.probe = bios_attr_set_interface_probe,
.remove = bios_attr_set_interface_remove,
.id_table = bios_attr_set_interface_id_table,
.filter_callback = bios_attr_set_interface_filter
};

int init_bios_attr_set_interface(void)
@@ -184,3 +344,4 @@ void exit_bios_attr_set_interface(void)
}

MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);

0 comments on commit 00141bc

Please sign in to comment.