From: "Prasanth KSR" <prasanth.ksr@xxxxxxxx> 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@xxxxxxxxxx> Signed-off-by: Prasanth KSR <prasanth.ksr@xxxxxxxx> Co-developed-by: Divya Bharathi <divya.bharathi@xxxxxxxx> Signed-off-by: Divya Bharathi <divya.bharathi@xxxxxxxx> Co-developed-by: Mario Limonciello <mario.limonciello@xxxxxxxx> Signed-off-by: Mario Limonciello <mario.limonciello@xxxxxxxx> --- Documentation/ABI/testing/dell-wmi-sysman | 39 ++ .../x86/dell-wmi-sysman/biosattr-interface.c | 257 +++++++++-- .../x86/dell-wmi-sysman/dell-wmi-sysman.h | 20 +- .../x86/dell-wmi-sysman/enum-attributes.c | 45 +- .../x86/dell-wmi-sysman/int-attributes.c | 46 +- .../x86/dell-wmi-sysman/passobj-attributes.c | 23 + .../x86/dell-wmi-sysman/string-attributes.c | 50 +- drivers/platform/x86/dell-wmi-sysman/sysman.c | 48 +- include/uapi/linux/wmi.h | 56 +++ tools/dell-wmi-sysman/Makefile | 19 + .../dell-wmi-sysman/dell-wmi-sysman-example.c | 432 ++++++++++++++++++ 11 files changed, 946 insertions(+), 89 deletions(-) create mode 100644 Documentation/ABI/testing/dell-wmi-sysman create mode 100644 tools/dell-wmi-sysman/Makefile create mode 100644 tools/dell-wmi-sysman/dell-wmi-sysman-example.c diff --git a/Documentation/ABI/testing/dell-wmi-sysman b/Documentation/ABI/testing/dell-wmi-sysman new file mode 100644 index 000000000000..4f3883529a06 --- /dev/null +++ b/Documentation/ABI/testing/dell-wmi-sysman @@ -0,0 +1,39 @@ +What: /dev/wmi/dell-wmi-sysman +Date: November 2021 +KernelVersion: 5.15 +Contact: "Divya Bharathi" <divya.bharathi@xxxxxxxx> + "Mario Limonciello" <mario.limonciello@xxxxxxxx> + "Prasanth K S R" <prasanth.ksr@xxxxxxxx> +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. diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c index f95d8ddace5a..9a82e78fe59e 100644 --- a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c @@ -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,44 +73,51 @@ 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) { @@ -119,8 +125,34 @@ int set_bios_defaults(u8 deftype) 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); + diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h index b80f2a62ea3f..13c216e6ddec 100644 --- a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h +++ b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h @@ -8,6 +8,7 @@ #define _DELL_WMI_BIOS_ATTR_H_ #include <linux/wmi.h> +#include <uapi/linux/wmi.h> #include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> @@ -87,8 +88,6 @@ struct wmi_sysman_priv { /* global structure used by multiple WMI interfaces */ extern struct wmi_sysman_priv wmi_priv; -enum { ENUM, INT, STR, PO }; - enum { ATTR_NAME, DISPL_NAME_LANG_CODE, @@ -134,6 +133,7 @@ static ssize_t curr_val##_store(struct kobject *kobj, \ struct kobj_attribute *attr, \ const char *buf, size_t count) \ { \ + struct dell_set_data *set_data; \ char *p, *buf_cp; \ int i, ret = -EIO; \ buf_cp = kstrdup(buf, GFP_KERNEL); \ @@ -146,15 +146,19 @@ static ssize_t curr_val##_store(struct kobject *kobj, \ i = get_##type##_instance_id(kobj); \ if (i >= 0) \ ret = validate_##type##_input(i, buf_cp); \ + set_data = kzalloc(sizeof(*set_data), GFP_KERNEL); \ + strlcpy_attr(set_data[0].attribute_value, buf_cp); \ + strlcpy_attr(set_data[0].attribute_name, kobj->name); \ if (!ret) \ - ret = set_attribute(kobj->name, buf_cp); \ + ret = set_attributes(set_data, 1, SET_ATTRIBUTES); \ kfree(buf_cp); \ return ret ? ret : count; \ } union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); int get_instance_count(const char *guid_string); -void strlcpy_attr(char *dest, char *src); +int get_current_value(char *buf, int instance_id, const char *guid_string); +void strlcpy_attr(char *dest, const char *src); int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, struct kobject *attr_name_kobj); @@ -174,7 +178,7 @@ int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject int alloc_po_data(void); void exit_po_attributes(void); -int set_attribute(const char *a_name, const char *a_value); +int set_attributes(struct dell_set_data *in_data, int a_count, unsigned short command); int set_bios_defaults(u8 defType); void exit_bios_attr_set_interface(void); @@ -188,4 +192,10 @@ int set_new_password(const char *password_type, const char *new); int init_bios_attr_pass_interface(void); void exit_bios_attr_pass_interface(void); +__u64 get_attrs_size(void); +void get_enumeration_data(struct dell_wmi_sysman_buffer *buf); +void get_integer_data(struct dell_wmi_sysman_buffer *buf); +void get_string_data(struct dell_wmi_sysman_buffer *buf); +void get_po_data(struct dell_wmi_sysman_buffer *attr_data); + #endif diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c index 80f4b7785c6c..b23e10ac00da 100644 --- a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c +++ b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c @@ -13,22 +13,17 @@ get_instance_id(enumeration); static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int instance_id = get_enumeration_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; + int ret; if (instance_id < 0) return instance_id; - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; + ret = get_current_value(buf, instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (ret > 0) { + strcat(buf, "\n"); + return strlen(buf); } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); + /* read error */ return ret; } @@ -171,6 +166,34 @@ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); } +void get_enumeration_data(struct dell_wmi_sysman_buffer *buf) +{ + struct dell_attributes_data *attr_data; + int i; + + attr_data = (struct dell_attributes_data *)buf->data; + for (i = 0; i < wmi_priv.enumeration_instances_count; i++) { + attr_data[i].type = ENUM; + strlcpy_attr(attr_data[i].attribute_name, + wmi_priv.enumeration_data[i].attribute_name); + strlcpy_attr(attr_data[i].display_name, + wmi_priv.enumeration_data[i].display_name); + strlcpy_attr(attr_data[i].display_name_language_code, + wmi_priv.enumeration_data[i].display_name_language_code); + strlcpy_attr(attr_data[i].possible_values, + wmi_priv.enumeration_data[i].possible_values); + strlcpy_attr(attr_data[i].dell_modifier, + wmi_priv.enumeration_data[i].dell_modifier); + strlcpy_attr(attr_data[i].dell_value_modifier, + wmi_priv.enumeration_data[i].dell_value_modifier); + strlcpy_attr(attr_data[i].default_value, + wmi_priv.enumeration_data[i].default_value); + get_current_value(attr_data[i].current_value, i, + DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + + } +} + /** * exit_enum_attributes() - Clear all attribute data * diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c index 75aedbb733be..0155f6189576 100644 --- a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c +++ b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c @@ -15,22 +15,17 @@ get_instance_id(integer); static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int instance_id = get_integer_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; + int ret; if (instance_id < 0) return instance_id; - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { - kfree(obj); - return -EINVAL; + ret = get_current_value(buf, instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (ret > 0) { + strcat(buf, "\n"); + return strlen(buf); } - ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); - kfree(obj); + /* read error */ return ret; } @@ -161,6 +156,35 @@ int populate_int_data(union acpi_object *integer_obj, int instance_id, return sysfs_create_group(attr_name_kobj, &integer_attr_group); } +void get_integer_data(struct dell_wmi_sysman_buffer *buf) +{ + struct dell_attributes_data *attr_data; + int i; + //To populate in same dell_attributes_data, increment after enum data + int a_count = wmi_priv.enumeration_instances_count; + + attr_data = (struct dell_attributes_data *)buf->data; + for (i = 0; i < wmi_priv.integer_instances_count; i++) { + attr_data[a_count].type = INT; + strlcpy_attr(attr_data[a_count].attribute_name, + wmi_priv.integer_data[i].attribute_name); + strlcpy_attr(attr_data[a_count].display_name, + wmi_priv.integer_data[i].display_name); + strlcpy_attr(attr_data[a_count].display_name_language_code, + wmi_priv.integer_data[i].display_name_language_code); + strlcpy_attr(attr_data[a_count].dell_modifier, + wmi_priv.integer_data[i].dell_modifier); + attr_data[a_count].min = wmi_priv.integer_data[i].min_value; + attr_data[a_count].max = wmi_priv.integer_data[i].max_value; + attr_data[a_count].scalar_increment = wmi_priv.integer_data[i].scalar_increment; + snprintf(attr_data[a_count].default_value, PAGE_SIZE, "%d", + wmi_priv.integer_data[i].default_value); + get_current_value(attr_data[a_count].current_value, i, + DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + a_count++; + } +} + /** * exit_int_attributes() - Clear all attribute data * diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c index 3abcd95477c0..9f50989a9f44 100644 --- a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c +++ b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c @@ -169,6 +169,29 @@ int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject return sysfs_create_group(attr_name_kobj, &po_attr_group); } +void get_po_data(struct dell_wmi_sysman_buffer *in_data) +{ + int i; + struct dell_password_data *attr_data; + + in_data->count = wmi_priv.po_instances_count; + attr_data = (struct dell_password_data *)in_data->data; + for (i = 0; i < wmi_priv.po_instances_count; i++) { + union acpi_object *obj; + + strlcpy_attr(attr_data[i].attribute_name, + wmi_priv.po_data[i].attribute_name); + attr_data[i].min_length = wmi_priv.po_data[i].min_password_length; + attr_data[i].max_length = wmi_priv.po_data[i].max_password_length; + + obj = get_wmiobj_pointer(i, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (!obj) + continue; + attr_data[i].is_set = obj->package.elements[IS_PASS_SET].integer.value; + kfree(obj); + } +} + /** * exit_po_attributes() - Clear all attribute data * diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c index ac75dce88a4c..a3847e4a6bf8 100644 --- a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c +++ b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c @@ -15,22 +15,17 @@ get_instance_id(str); static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int instance_id = get_str_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; + int ret; if (instance_id < 0) - return -EIO; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; + return instance_id; + + ret = get_current_value(buf, instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (ret > 0) { + strcat(buf, "\n"); + return strlen(buf); } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); + /* read error */ return ret; } @@ -141,6 +136,35 @@ int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobjec return sysfs_create_group(attr_name_kobj, &str_attr_group); } +void get_string_data(struct dell_wmi_sysman_buffer *buf) +{ + struct dell_attributes_data *attr_data; + int i; + //To populate in same dell_attributes_data, increment after enum+int data + int a_count = wmi_priv.enumeration_instances_count + + wmi_priv.integer_instances_count; + attr_data = (struct dell_attributes_data *)buf->data; + + for (i = 0; i < wmi_priv.str_instances_count; i++) { + attr_data[a_count].type = STR; + strlcpy_attr(attr_data[a_count].attribute_name, + wmi_priv.str_data[i].attribute_name); + strlcpy_attr(attr_data[a_count].display_name, + wmi_priv.str_data[i].display_name); + strlcpy_attr(attr_data[a_count].display_name_language_code, + wmi_priv.str_data[i].display_name_language_code); + strlcpy_attr(attr_data[a_count].dell_modifier, + wmi_priv.str_data[i].dell_modifier); + attr_data[a_count].min = wmi_priv.str_data[i].min_length; + attr_data[a_count].max = wmi_priv.str_data[i].max_length; + strlcpy_attr(attr_data[a_count].default_value, + wmi_priv.str_data[i].default_value); + get_current_value(attr_data[a_count].current_value, i, + DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + a_count++; + } +} + /** * exit_str_attributes() - Clear all attribute data * diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c index cb81010ba1a2..0b77a6a0b8a8 100644 --- a/drivers/platform/x86/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell-wmi-sysman/sysman.c @@ -275,7 +275,7 @@ static struct kobj_type attr_name_ktype = { * @dest: Where to copy the string to * @src: Where to copy the string from */ -void strlcpy_attr(char *dest, char *src) +void strlcpy_attr(char *dest, const char *src) { size_t len = strlen(src) + 1; @@ -307,6 +307,52 @@ union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; } +int validate_acpi_type(union acpi_object *obj, const char *guid_string) +{ + u32 acpi_type; + + if (strcmp(guid_string, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID) == 0) + acpi_type = ACPI_TYPE_INTEGER; + else + acpi_type = ACPI_TYPE_STRING; + + if (obj->package.elements[CURRENT_VAL].type != acpi_type) + return -EIO; + + return 0; +} + +/** + * get_curret_value() - Get current_value of an attribute + * @instance_id: WMI instance ID + * @guid_string: WMI GUID (in string form) + */ +int get_current_value(char *buf, int instance_id, const char *guid_string) +{ + union acpi_object *obj; + int ret; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, guid_string); + if (!obj) + return -EIO; + + ret = validate_acpi_type(obj, guid_string); + if (ret) + goto out; + + if (strcmp(guid_string, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID) == 0) + ret = snprintf(buf, PAGE_SIZE, "%lld", + obj->package.elements[CURRENT_VAL].integer.value); + else + ret = snprintf(buf, PAGE_SIZE, "%s", + obj->package.elements[CURRENT_VAL].string.pointer); + +out: + kfree(obj); + return ret; +} + /** * get_instance_count() - Compute total number of instances under guid_string * @guid_string: WMI GUID (in string form) diff --git a/include/uapi/linux/wmi.h b/include/uapi/linux/wmi.h index 7085c5dca9fa..f160d1eef7cb 100644 --- a/include/uapi/linux/wmi.h +++ b/include/uapi/linux/wmi.h @@ -13,6 +13,11 @@ /* WMI bus will filter all WMI vendor driver requests through this IOC */ #define WMI_IOC 'W' +enum BIOS_ATTRIBUTE_TYPE { ENUM, INT, STR, PO }; +enum IOCTL_COMMAND { ENUMERATE_ALL = 1, SET_ATTRIBUTES, SET_DEFAULTS, GET_PASS, SET_PASS }; + +#define MAX_BUFF 512 + /* All ioctl requests through WMI should declare their size followed by * relevant data objects */ @@ -43,6 +48,53 @@ struct dell_wmi_smbios_buffer { struct dell_wmi_extensions ext; } __packed; +struct dell_wmi_sysman_buffer { + __u64 length; + __u32 count; + __u16 command; + char admin_password[MAX_BUFF]; + __u8 data[]; +} __packed; + +struct dell_attributes_data { + char display_name_language_code[MAX_BUFF]; + char dell_value_modifier[MAX_BUFF]; + char possible_values[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char current_value[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; + int scalar_increment; + int type; + int min; + int max; +} __packed; + +struct dell_set_data { + char attribute_name[MAX_BUFF]; + char attribute_value[MAX_BUFF]; +} __packed; + +struct dell_set_password { + char attribute_name[MAX_BUFF]; + char system_password[MAX_BUFF]; + char new_password[MAX_BUFF]; +} __packed; + +struct dell_password_data { + char attribute_name[MAX_BUFF]; + __u8 is_set; + int min_length; + int max_length; +} __packed; + +struct dell_resetBIOS { + __u64 length; + __u8 option; + char admin_password[MAX_BUFF]; +} __packed; + /* Whitelisted smbios class/select commands */ #define CLASS_TOKEN_READ 0 #define CLASS_TOKEN_WRITE 1 @@ -67,4 +119,8 @@ struct dell_wmi_smbios_buffer { /* Dell SMBIOS calling IOCTL command used by dell-smbios-wmi */ #define DELL_WMI_SMBIOS_CMD _IOWR(WMI_IOC, 0, struct dell_wmi_smbios_buffer) +/* Dell WMI System Management calling IOCTL commands used by dell-wmi-sysman */ +#define DELL_WMI_SYSMAN_CMD _IOWR(WMI_IOC, 0, struct dell_wmi_sysman_buffer) +#define DELL_WMI_SYSMAN_RESET_BIOS _IOW(WMI_IOC, 0, struct dell_resetBIOS) + #endif diff --git a/tools/dell-wmi-sysman/Makefile b/tools/dell-wmi-sysman/Makefile new file mode 100644 index 000000000000..0a01a82a0745 --- /dev/null +++ b/tools/dell-wmi-sysman/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +PREFIX ?= /usr +SBINDIR ?= sbin +INSTALL ?= install +CFLAGS += -D__EXPORTED_HEADERS__ -I../../include/uapi -I../../include -I./ + +TARGET = dell-wmi-sysman-example + +all: $(TARGET) + +%: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -g -o $@ $< + +clean: + $(RM) $(TARGET) + +install: dell-wmi-sysman-example + $(INSTALL) -D -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/$(SBINDIR)/$(TARGET) + diff --git a/tools/dell-wmi-sysman/dell-wmi-sysman-example.c b/tools/dell-wmi-sysman/dell-wmi-sysman-example.c new file mode 100644 index 000000000000..50db8835cea6 --- /dev/null +++ b/tools/dell-wmi-sysman/dell-wmi-sysman-example.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sample application for system management over WMI interface + * Performs the following: + * - Enemeration of all BIOS attributes present in system + * - Set BIOS atributes to user input or default + * - Reset BIOS + * + * Copyright (C) 2021 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. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +/* if uapi header isn't installed, this might not yet exist */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif +#include <linux/wmi.h> + +enum USER_OPTS {GET_ATTRS = 1, SET_ATTRS, SET_DEFS, PASS_MAN, RESET, EXIT }; +enum PASS_OPTS {GET_PASSINFO = 1, SET_OR_CHANGE_PASS, CLEAR_PASS, BACK }; + +static const char *ioctl_devfs = "/dev/wmi/dell-wmi-sysman"; +__u64 buff_size; + +static int query_buffer_size(__u64 *buffer_size) +{ + FILE *f = fopen(ioctl_devfs, "rb"); + + if (!f) + return -EINVAL; + fread(buffer_size, sizeof(__u64), 1, f); + fclose(f); + return EXIT_SUCCESS; +} + +int read_integer_input(void) +{ + int val, in, c; + char follow; + + while (1) { + in = scanf("%d%c", &val, &follow); + if (in == 2) { + if (isspace(follow)) + return val; + printf("Invalid input! Try again...\nEnter - "); + } else if (in == 1) { + return val; + printf("Invalid input! Try again...\nEnter - "); + } + + while ((c = getchar()) != '\n' && c != EOF) + ; + } +} + +static int call_ioctl(struct dell_wmi_sysman_buffer *buffer) +{ + int fd; + int ret; + + fd = open(ioctl_devfs, O_NONBLOCK); + ret = ioctl(fd, DELL_WMI_SYSMAN_CMD, buffer); + close(fd); + return ret; +} + +int is_password_set(unsigned char *password_type) +{ + int ret, i; + int is_set = 0; + struct dell_wmi_sysman_buffer *buff_pwd; + struct dell_password_data *indata; + + buff_pwd = malloc(buff_size); //buff_size large enough to get password data + if (buff_pwd == NULL) { + printf("failed to alloc memory for ioctl\n"); + return -ENOMEM; + } + + buff_pwd->length = buff_size; + buff_pwd->command = GET_PASS; + call_ioctl(buff_pwd); + + indata = (struct dell_password_data *)buff_pwd->data; + + for (i = 0; i < buff_pwd->count; i++) { + if (strcmp(indata[i].attribute_name, password_type) == 0) { + is_set = indata[i].is_set; + break; + } + } + + free(buff_pwd); + return is_set; +} + +/* Enumerate functions start*/ +void display_attributes(struct dell_wmi_sysman_buffer *buff_all) +{ + struct dell_attributes_data *testdata; + int i; + + testdata = (struct dell_attributes_data *)buff_all->data; + for (i = 0; i < buff_all->count; i++) { + printf("\n%d\n", (i + 1)); + printf("AttributeName = %s\n", testdata[i].attribute_name); + printf("Display LangCode = %s\n", testdata[i].display_name_language_code); + printf("Display Name = %s\n", testdata[i].display_name); + printf("Modifier = %s\n", testdata[i].dell_modifier); + printf("Current Value = %s\n", testdata[i].current_value); + printf("Default Value = %s\n", testdata[i].default_value); + if (testdata[i].type == ENUM) { + printf("Value Modifier = %s\n", testdata[i].dell_value_modifier); + printf("PossibleValues = %s\n", testdata[i].possible_values); + } + if (testdata[i].type == INT) { + printf("Lower Bound = %d\n", testdata[i].min); + printf("Upper Bound = %d\n", testdata[i].max); + printf("Scalar incr = %d\n", testdata[i].scalar_increment); + } + if (testdata[i].type == STR) { + printf("Minimum Length = %d\n", testdata[i].min); + printf("Maximum Length = %d\n", testdata[i].max); + } + } + printf("---------------------------------------------------------\n"); +} + +void enumerate_all_attributes(void) +{ + struct dell_wmi_sysman_buffer *buff_all; + int ret; + + buff_all = malloc(buff_size); + if (buff_all == NULL) { + printf("failed to alloc memory for ioctl\n"); + return; + } + buff_all->length = buff_size; + buff_all->command = ENUMERATE_ALL; + ret = call_ioctl(buff_all); + if (ret) { + printf("smbios ioctl failed: %d\n", ret); + goto out; + } + display_attributes(buff_all); +out: + if (buff_all != NULL) + free(buff_all); +} +/* Enumerate functions end*/ + +/* SET functions start */ +int read_set_data(struct dell_wmi_sysman_buffer *buff_set, int option) +{ + int i, admin_pwd_set; + struct dell_set_data *indata; + + admin_pwd_set = is_password_set("Admin"); + if (admin_pwd_set < 0) { + printf("check password call failed!!!\n"); + return EXIT_FAILURE; + } + + printf("How many attributes to set: "); + scanf("%d", &buff_set->count); + + indata = (struct dell_set_data *)buff_set->data; + + if (admin_pwd_set) { + printf("Admin password is set, please enter password - "); + scanf("%s", buff_set->admin_password); + } + + for (i = 0; i < buff_set->count; i++) { + printf("Enter Attribute Name: "); + scanf("%s", indata[i].attribute_name); + + if (option == SET_ATTRIBUTES) { + printf("Enter Attribute Value: "); + scanf("%s", indata[i].attribute_value); + } + } + return 0; +} + +void call_set_cmd(int cmd_opt) +{ + int i, ret; + struct dell_wmi_sysman_buffer *buff_set; + + buff_set = malloc(buff_size); + if (buff_set == NULL) { + printf("failed to alloc memory for ioctl\n"); + return; + } + buff_set->length = buff_size; + buff_set->command = cmd_opt; + if (read_set_data(buff_set, cmd_opt)) + goto out; + + ret = call_ioctl(buff_set); + if (!ret) { + printf("Set Successful...\n"); + goto out; + } + printf("Set failed: %d\n", ret); +out: + free(buff_set); +} +/* SET functions end */ + +/* Password related functions start */ +void get_password_info(void) +{ + int i; + struct dell_wmi_sysman_buffer *buff_pwd; + struct dell_password_data *testdata; + + buff_pwd = malloc(buff_size); //buff_size large enough to get password data + if (buff_pwd == NULL) { + printf("failed to alloc memory for ioctl\n"); + return; + } + buff_pwd->length = buff_size; + buff_pwd->command = GET_PASS; + if (call_ioctl(buff_pwd)) { + free(buff_pwd); + printf("smbios ioctl failed!!!\n"); + return; + } + + testdata = (struct dell_password_data *)buff_pwd->data; + for (i = 0; i < buff_pwd->count; i++) { + printf("\n%d\n", (i + 1)); + printf("AttributeName = %s\n", testdata[i].attribute_name); + printf("Minimum Length = %d\n", testdata[i].min_length); + printf("Maximum Length = %d\n", testdata[i].max_length); + printf("Is Password Set = %d\n", testdata[i].is_set); + } + if (buff_pwd != NULL) + free(buff_pwd); +} + +void call_set_pasword_cmd(int clear_opt) +{ + int ret, is_admin_pwd_set, is_system_pwd_set; + struct dell_wmi_sysman_buffer *buff_pwd; + struct dell_set_password *indata; + + buff_pwd = malloc(buff_size); //buff_size large enough to get password data + if (buff_pwd == NULL) { + printf("failed to alloc memory for ioctl\n"); + return; + } + buff_pwd->length = buff_size; + buff_pwd->command = SET_PASS; + is_admin_pwd_set = is_password_set("Admin"); + is_system_pwd_set = is_password_set("System"); + if ((is_admin_pwd_set < 0) || (is_system_pwd_set < 0)) { + printf("check password call failed!!!\n"); + goto out; + } + + indata = (struct dell_set_password *)buff_pwd->data; + + if (is_admin_pwd_set) { + printf("Admin password is set, please enter current admin password - "); + scanf("%s", buff_pwd->admin_password); + } + + printf("Enter which password to set Admin/System - "); + scanf("%s", indata->attribute_name); + + if (strcmp(indata->attribute_name, "System") == 0 && (is_system_pwd_set)) { + printf("System password is set, please enter current system password - "); + scanf("%s", indata->system_password); + } + + if (clear_opt == 3) { + strcpy(indata->new_password, ""); + } else { + printf("Enter new password - "); + scanf("%s", indata->new_password); + } + ret = call_ioctl(buff_pwd); + if (!ret) { + printf("Set Successful...\n"); + ret = EXIT_SUCCESS; + goto out; + } + printf("Set failed: %d\n", ret); +out: + free(buff_pwd); +} + +void call_password_management(void) +{ + int opt, ret; + + while (1) { + printf("\n##############################\n"); + printf(" Password Management Menu\t\n"); + printf("##############################\n"); + printf("\n1 - Get Password Information\n" + "2 - Set/Change Password\n" + "3 - Clear Password\n" + "4 - Go to back to Main Menu\n\n" + "Enter - "); + opt = read_integer_input(); + + switch (opt) { + case GET_PASSINFO: + get_password_info(); + break; + case SET_OR_CHANGE_PASS: + case CLEAR_PASS: + call_set_pasword_cmd(opt); + break; + case BACK: + return; + default: + printf("Invalid option!\n"); + break; + } + } +} +/* Password related functions end */ + +void call_reset_bios(void) +{ + struct dell_resetBIOS *buff_reset; + unsigned char reset_option; + int fd, ret, is_admin_pwd_set; + + printf("\nReset Options:\n" + "0 - Built-in Safe Defaults\n" + "1 - Last Known Good\n" + "2 - Factory\n" + "3 - Custom\n\n" + "Enter - "); + scanf("%hhu", &reset_option); + buff_reset = malloc(buff_size); + if (buff_reset == NULL) { + printf("failed to alloc memory for ioctl\n"); + return; + } + is_admin_pwd_set = is_password_set("Admin"); + if ((is_admin_pwd_set < 0)) { + printf("check password call failed!!!\n"); + goto out; + } + + buff_reset->length = buff_size; + if (is_admin_pwd_set) { + printf("Admin password is set, please enter current admin password - "); + scanf("%s", buff_reset->admin_password); + } + fd = open(ioctl_devfs, O_NONBLOCK); + ret = ioctl(fd, DELL_WMI_SYSMAN_RESET_BIOS, buff_reset); + close(fd); + if (!ret) { + printf("Reset Successful. Reboot the system\n"); + ret = EXIT_SUCCESS; + goto out; + } + printf("Reset Failed: %d\n", ret); +out: + if (buff_reset != NULL) + free(buff_reset); +} + +int main(void) +{ + int ret, opt; + __u64 value = 0; + + ret = query_buffer_size(&value); + if (ret == EXIT_FAILURE || !value) { + printf("Unable to read buffer size\n"); + return ret; + } + buff_size = value; + while (1) { + printf("\n\n\n##############################\n"); + printf("Dell BIOS System Management Utility\n"); + printf("##############################\n"); + printf("\n1 - Enumerate All Attributes\n" + "2 - Set Attributes\n" + "3 - Set To Defaults\n" + "4 - Password Management\n" + "5 - Reset BIOS\n" + "6 - Exit\n" + "\n\nEnter - "); + opt = read_integer_input(); + + switch (opt) { + case GET_ATTRS: + enumerate_all_attributes(); + break; + case SET_ATTRS: + case SET_DEFS: + call_set_cmd(opt); + break; + case PASS_MAN: + call_password_management(); + break; + case RESET: + call_reset_bios(); + break; + case EXIT: + exit(0); + break; + default: + break; + } + } + return 0; +} -- 2.25.1