| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Common methods for use with dell-wmi-sysman |
| * |
| * Copyright (c) 2020 Dell Inc. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/fs.h> |
| #include <linux/dmi.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/wmi.h> |
| #include "dell-wmi-sysman.h" |
| #include "../../firmware_attributes_class.h" |
| |
| #define MAX_TYPES 4 |
| #include <linux/nls.h> |
| |
| struct wmi_sysman_priv wmi_priv = { |
| .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), |
| }; |
| |
| /* reset bios to defaults */ |
| static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; |
| static int reset_option = -1; |
| static const struct class *fw_attr_class; |
| |
| |
| /** |
| * populate_string_buffer() - populates a string buffer |
| * @buffer: the start of the destination buffer |
| * @buffer_len: length of the destination buffer |
| * @str: the string to insert into buffer |
| */ |
| ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) |
| { |
| u16 *length = (u16 *)buffer; |
| u16 *target = length + 1; |
| int ret; |
| |
| ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, |
| target, buffer_len - sizeof(u16)); |
| if (ret < 0) { |
| dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); |
| return ret; |
| } |
| |
| if ((ret * sizeof(u16)) > U16_MAX) { |
| dev_err(wmi_priv.class_dev, "Error string too long\n"); |
| return -ERANGE; |
| } |
| |
| *length = ret * sizeof(u16); |
| return sizeof(u16) + *length; |
| } |
| |
| /** |
| * calculate_string_buffer() - determines size of string buffer for use with BIOS communication |
| * @str: the string to calculate based upon |
| * |
| */ |
| size_t calculate_string_buffer(const char *str) |
| { |
| /* u16 length field + one UTF16 char for each input char */ |
| return sizeof(u16) + strlen(str) * sizeof(u16); |
| } |
| |
| /** |
| * calculate_security_buffer() - determines size of security buffer for authentication scheme |
| * @authentication: the authentication content |
| * |
| * Currently only supported type is Admin password |
| */ |
| size_t calculate_security_buffer(char *authentication) |
| { |
| if (strlen(authentication) > 0) { |
| return (sizeof(u32) * 2) + strlen(authentication) + |
| strlen(authentication) % 2; |
| } |
| return sizeof(u32) * 2; |
| } |
| |
| /** |
| * populate_security_buffer() - builds a security buffer for authentication scheme |
| * @buffer: the buffer to populate |
| * @authentication: the authentication content |
| * |
| * Currently only supported type is PLAIN TEXT |
| */ |
| void populate_security_buffer(char *buffer, char *authentication) |
| { |
| char *auth = buffer + sizeof(u32) * 2; |
| u32 *sectype = (u32 *) buffer; |
| u32 *seclen = sectype + 1; |
| |
| *sectype = strlen(authentication) > 0 ? 1 : 0; |
| *seclen = strlen(authentication); |
| |
| /* plain text */ |
| if (strlen(authentication) > 0) |
| memcpy(auth, authentication, *seclen); |
| } |
| |
| /** |
| * map_wmi_error() - map errors from WMI methods to kernel error codes |
| * @error_code: integer error code returned from Dell's firmware |
| */ |
| int map_wmi_error(int error_code) |
| { |
| switch (error_code) { |
| case 0: |
| /* success */ |
| return 0; |
| case 1: |
| /* failed */ |
| return -EIO; |
| case 2: |
| /* invalid parameter */ |
| return -EINVAL; |
| case 3: |
| /* access denied */ |
| return -EACCES; |
| case 4: |
| /* not supported */ |
| return -EOPNOTSUPP; |
| case 5: |
| /* memory error */ |
| return -ENOMEM; |
| case 6: |
| /* protocol error */ |
| return -EPROTO; |
| } |
| /* unspecified error */ |
| return -EIO; |
| } |
| |
| /** |
| * reset_bios_show() - sysfs implementaton for read reset_bios |
| * @kobj: Kernel object for this attribute |
| * @attr: Kernel object attribute |
| * @buf: The buffer to display to userspace |
| */ |
| static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| char *start = buf; |
| int i; |
| |
| for (i = 0; i < MAX_TYPES; i++) { |
| if (i == reset_option) |
| buf += sprintf(buf, "[%s] ", reset_types[i]); |
| else |
| buf += sprintf(buf, "%s ", reset_types[i]); |
| } |
| buf += sprintf(buf, "\n"); |
| return buf-start; |
| } |
| |
| /** |
| * reset_bios_store() - sysfs implementaton for write reset_bios |
| * @kobj: Kernel object for this attribute |
| * @attr: Kernel object attribute |
| * @buf: The buffer from userspace |
| * @count: the size of the buffer from userspace |
| */ |
| static ssize_t reset_bios_store(struct kobject *kobj, |
| struct kobj_attribute *attr, const char *buf, size_t count) |
| { |
| int type = sysfs_match_string(reset_types, buf); |
| int ret; |
| |
| if (type < 0) |
| return type; |
| |
| ret = set_bios_defaults(type); |
| pr_debug("reset all attributes request type %d: %d\n", type, ret); |
| if (!ret) { |
| reset_option = type; |
| ret = count; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * pending_reboot_show() - sysfs implementaton for read pending_reboot |
| * @kobj: Kernel object for this attribute |
| * @attr: Kernel object attribute |
| * @buf: The buffer to display to userspace |
| * |
| * Stores default value as 0 |
| * When current_value is changed this attribute is set to 1 to notify reboot may be required |
| */ |
| static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "%d\n", wmi_priv.pending_changes); |
| } |
| |
| static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); |
| static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); |
| |
| |
| /** |
| * create_attributes_level_sysfs_files() - Creates reset_bios and |
| * pending_reboot attributes |
| */ |
| static int create_attributes_level_sysfs_files(void) |
| { |
| int ret; |
| |
| ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); |
| if (ret) |
| return ret; |
| |
| ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, |
| char *buf) |
| { |
| struct kobj_attribute *kattr; |
| ssize_t ret = -EIO; |
| |
| kattr = container_of(attr, struct kobj_attribute, attr); |
| if (kattr->show) |
| ret = kattr->show(kobj, kattr, buf); |
| return ret; |
| } |
| |
| static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct kobj_attribute *kattr; |
| ssize_t ret = -EIO; |
| |
| kattr = container_of(attr, struct kobj_attribute, attr); |
| if (kattr->store) |
| ret = kattr->store(kobj, kattr, buf, count); |
| return ret; |
| } |
| |
| static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { |
| .show = wmi_sysman_attr_show, |
| .store = wmi_sysman_attr_store, |
| }; |
| |
| static void attr_name_release(struct kobject *kobj) |
| { |
| kfree(kobj); |
| } |
| |
| static const struct kobj_type attr_name_ktype = { |
| .release = attr_name_release, |
| .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, |
| }; |
| |
| /** |
| * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks |
| * @dest: Where to copy the string to |
| * @src: Where to copy the string from |
| */ |
| void strlcpy_attr(char *dest, char *src) |
| { |
| size_t len = strlen(src) + 1; |
| |
| if (len > 1 && len <= MAX_BUFF) |
| strscpy(dest, src, len); |
| |
| /*len can be zero because any property not-applicable to attribute can |
| * be empty so check only for too long buffers and log error |
| */ |
| if (len > MAX_BUFF) |
| pr_err("Source string returned from BIOS is out of bound!\n"); |
| } |
| |
| /** |
| * get_wmiobj_pointer() - Get Content of WMI block for particular instance |
| * @instance_id: WMI instance ID |
| * @guid_string: WMI GUID (in str form) |
| * |
| * Fetches the content for WMI block (instance_id) under GUID (guid_string) |
| * Caller must kfree the return |
| */ |
| union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) |
| { |
| struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; |
| acpi_status status; |
| |
| status = wmi_query_block(guid_string, instance_id, &out); |
| |
| return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; |
| } |
| |
| /** |
| * get_instance_count() - Compute total number of instances under guid_string |
| * @guid_string: WMI GUID (in string form) |
| */ |
| int get_instance_count(const char *guid_string) |
| { |
| int ret; |
| |
| ret = wmi_instance_count(guid_string); |
| if (ret < 0) |
| return 0; |
| |
| return ret; |
| } |
| |
| /** |
| * alloc_attributes_data() - Allocate attributes data for a particular type |
| * @attr_type: Attribute type to allocate |
| */ |
| static int alloc_attributes_data(int attr_type) |
| { |
| int retval = 0; |
| |
| switch (attr_type) { |
| case ENUM: |
| retval = alloc_enum_data(); |
| break; |
| case INT: |
| retval = alloc_int_data(); |
| break; |
| case STR: |
| retval = alloc_str_data(); |
| break; |
| case PO: |
| retval = alloc_po_data(); |
| break; |
| default: |
| break; |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * destroy_attribute_objs() - Free a kset of kobjects |
| * @kset: The kset to destroy |
| * |
| * Fress kobjects created for each attribute_name under attribute type kset |
| */ |
| static void destroy_attribute_objs(struct kset *kset) |
| { |
| struct kobject *pos, *next; |
| |
| list_for_each_entry_safe(pos, next, &kset->list, entry) { |
| kobject_put(pos); |
| } |
| } |
| |
| /** |
| * release_attributes_data() - Clean-up all sysfs directories and files created |
| */ |
| static void release_attributes_data(void) |
| { |
| mutex_lock(&wmi_priv.mutex); |
| exit_enum_attributes(); |
| exit_int_attributes(); |
| exit_str_attributes(); |
| exit_po_attributes(); |
| if (wmi_priv.authentication_dir_kset) { |
| destroy_attribute_objs(wmi_priv.authentication_dir_kset); |
| kset_unregister(wmi_priv.authentication_dir_kset); |
| wmi_priv.authentication_dir_kset = NULL; |
| } |
| if (wmi_priv.main_dir_kset) { |
| sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); |
| sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); |
| destroy_attribute_objs(wmi_priv.main_dir_kset); |
| kset_unregister(wmi_priv.main_dir_kset); |
| wmi_priv.main_dir_kset = NULL; |
| } |
| mutex_unlock(&wmi_priv.mutex); |
| } |
| |
| /** |
| * init_bios_attributes() - Initialize all attributes for a type |
| * @attr_type: The attribute type to initialize |
| * @guid: The WMI GUID associated with this type to initialize |
| * |
| * Initialiaze all 4 types of attributes enumeration, integer, string and password object. |
| * Populates each attrbute typ's respective properties under sysfs files |
| */ |
| static int init_bios_attributes(int attr_type, const char *guid) |
| { |
| struct kobject *attr_name_kobj; //individual attribute names |
| union acpi_object *obj = NULL; |
| union acpi_object *elements; |
| struct kobject *duplicate; |
| struct kset *tmp_set; |
| int min_elements; |
| |
| /* instance_id needs to be reset for each type GUID |
| * also, instance IDs are unique within GUID but not across |
| */ |
| int instance_id = 0; |
| int retval = 0; |
| |
| retval = alloc_attributes_data(attr_type); |
| if (retval) |
| return retval; |
| |
| switch (attr_type) { |
| case ENUM: min_elements = 8; break; |
| case INT: min_elements = 9; break; |
| case STR: min_elements = 8; break; |
| case PO: min_elements = 4; break; |
| default: |
| pr_err("Error: Unknown attr_type: %d\n", attr_type); |
| return -EINVAL; |
| } |
| |
| /* need to use specific instance_id and guid combination to get right data */ |
| obj = get_wmiobj_pointer(instance_id, guid); |
| if (!obj) |
| return -ENODEV; |
| |
| mutex_lock(&wmi_priv.mutex); |
| while (obj) { |
| if (obj->type != ACPI_TYPE_PACKAGE) { |
| pr_err("Error: Expected ACPI-package type, got: %d\n", obj->type); |
| retval = -EIO; |
| goto err_attr_init; |
| } |
| |
| if (obj->package.count < min_elements) { |
| pr_err("Error: ACPI-package does not have enough elements: %d < %d\n", |
| obj->package.count, min_elements); |
| goto nextobj; |
| } |
| |
| elements = obj->package.elements; |
| |
| /* sanity checking */ |
| if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { |
| pr_debug("incorrect element type\n"); |
| goto nextobj; |
| } |
| if (strlen(elements[ATTR_NAME].string.pointer) == 0) { |
| pr_debug("empty attribute found\n"); |
| goto nextobj; |
| } |
| if (attr_type == PO) |
| tmp_set = wmi_priv.authentication_dir_kset; |
| else |
| tmp_set = wmi_priv.main_dir_kset; |
| |
| duplicate = kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer); |
| if (duplicate) { |
| pr_debug("Duplicate attribute name found - %s\n", |
| elements[ATTR_NAME].string.pointer); |
| kobject_put(duplicate); |
| goto nextobj; |
| } |
| |
| /* build attribute */ |
| attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); |
| if (!attr_name_kobj) { |
| retval = -ENOMEM; |
| goto err_attr_init; |
| } |
| |
| attr_name_kobj->kset = tmp_set; |
| |
| retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", |
| elements[ATTR_NAME].string.pointer); |
| if (retval) { |
| kobject_put(attr_name_kobj); |
| goto err_attr_init; |
| } |
| |
| /* enumerate all of this attribute */ |
| switch (attr_type) { |
| case ENUM: |
| retval = populate_enum_data(elements, instance_id, attr_name_kobj, |
| obj->package.count); |
| break; |
| case INT: |
| retval = populate_int_data(elements, instance_id, attr_name_kobj); |
| break; |
| case STR: |
| retval = populate_str_data(elements, instance_id, attr_name_kobj); |
| break; |
| case PO: |
| retval = populate_po_data(elements, instance_id, attr_name_kobj); |
| break; |
| default: |
| break; |
| } |
| |
| if (retval) { |
| pr_debug("failed to populate %s\n", |
| elements[ATTR_NAME].string.pointer); |
| goto err_attr_init; |
| } |
| |
| nextobj: |
| kfree(obj); |
| instance_id++; |
| obj = get_wmiobj_pointer(instance_id, guid); |
| } |
| |
| mutex_unlock(&wmi_priv.mutex); |
| return 0; |
| |
| err_attr_init: |
| mutex_unlock(&wmi_priv.mutex); |
| kfree(obj); |
| return retval; |
| } |
| |
| static int __init sysman_init(void) |
| { |
| int ret = 0; |
| |
| if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && |
| !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Alienware", NULL) && |
| !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { |
| pr_err("Unable to run on non-Dell system\n"); |
| return -ENODEV; |
| } |
| |
| ret = init_bios_attr_set_interface(); |
| if (ret) |
| return ret; |
| |
| ret = init_bios_attr_pass_interface(); |
| if (ret) |
| goto err_exit_bios_attr_set_interface; |
| |
| if (!wmi_priv.bios_attr_wdev || !wmi_priv.password_attr_wdev) { |
| pr_debug("failed to find set or pass interface\n"); |
| ret = -ENODEV; |
| goto err_exit_bios_attr_pass_interface; |
| } |
| |
| ret = fw_attributes_class_get(&fw_attr_class); |
| if (ret) |
| goto err_exit_bios_attr_pass_interface; |
| |
| wmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), |
| NULL, "%s", DRIVER_NAME); |
| if (IS_ERR(wmi_priv.class_dev)) { |
| ret = PTR_ERR(wmi_priv.class_dev); |
| goto err_unregister_class; |
| } |
| |
| wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, |
| &wmi_priv.class_dev->kobj); |
| if (!wmi_priv.main_dir_kset) { |
| ret = -ENOMEM; |
| goto err_destroy_classdev; |
| } |
| |
| wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, |
| &wmi_priv.class_dev->kobj); |
| if (!wmi_priv.authentication_dir_kset) { |
| ret = -ENOMEM; |
| goto err_release_attributes_data; |
| } |
| |
| ret = create_attributes_level_sysfs_files(); |
| if (ret) { |
| pr_debug("could not create reset BIOS attribute\n"); |
| goto err_release_attributes_data; |
| } |
| |
| ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); |
| if (ret) { |
| pr_debug("failed to populate enumeration type attributes\n"); |
| goto err_release_attributes_data; |
| } |
| |
| ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); |
| if (ret) { |
| pr_debug("failed to populate integer type attributes\n"); |
| goto err_release_attributes_data; |
| } |
| |
| ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); |
| if (ret) { |
| pr_debug("failed to populate string type attributes\n"); |
| goto err_release_attributes_data; |
| } |
| |
| ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); |
| if (ret) { |
| pr_debug("failed to populate pass object type attributes\n"); |
| goto err_release_attributes_data; |
| } |
| |
| return 0; |
| |
| err_release_attributes_data: |
| release_attributes_data(); |
| |
| err_destroy_classdev: |
| device_destroy(fw_attr_class, MKDEV(0, 0)); |
| |
| err_unregister_class: |
| fw_attributes_class_put(); |
| |
| err_exit_bios_attr_pass_interface: |
| exit_bios_attr_pass_interface(); |
| |
| err_exit_bios_attr_set_interface: |
| exit_bios_attr_set_interface(); |
| |
| return ret; |
| } |
| |
| static void __exit sysman_exit(void) |
| { |
| release_attributes_data(); |
| device_destroy(fw_attr_class, MKDEV(0, 0)); |
| fw_attributes_class_put(); |
| exit_bios_attr_set_interface(); |
| exit_bios_attr_pass_interface(); |
| } |
| |
| module_init(sysman_init); |
| module_exit(sysman_exit); |
| |
| MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); |
| MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>"); |
| MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>"); |
| MODULE_DESCRIPTION("Dell platform setting control interface"); |
| MODULE_LICENSE("GPL"); |