root/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Functions corresponding to SET methods under BIOS attributes interface GUID for use
 * with dell-wmi-sysman
 *
 *  Copyright (c) 2020 Dell Inc.
 */

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

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

static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size,
                                        int method_id)
{
        struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
        struct acpi_buffer input;
        union acpi_object *obj;
        acpi_status status;
        int ret = -EIO;

        input.length =  (acpi_size) size;
        input.pointer = in_args;
        status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output);
        if (ACPI_FAILURE(status))
                return -EIO;
        obj = (union acpi_object *)output.pointer;
        if (obj->type == ACPI_TYPE_INTEGER)
                ret = obj->integer.value;

        if (wmi_priv.pending_changes == 0) {
                wmi_priv.pending_changes = 1;
                /* let userland know it may need to check reboot pending again */
                kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
        }
        kfree(output.pointer);
        return map_wmi_error(ret);
}

/**
 * set_attribute() - Update an attribute value
 * @a_name: The attribute name
 * @a_value: The attribute value
 *
 * Sets an attribute to new value
 */
int set_attribute(const char *a_name, const char *a_value)
{
        size_t security_area_size, buffer_size;
        size_t a_name_size, a_value_size;
        char *buffer = NULL, *start;
        int ret;

        mutex_lock(&wmi_priv.mutex);
        if (!wmi_priv.bios_attr_wdev) {
                ret = -ENODEV;
                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 = kzalloc(buffer_size, GFP_KERNEL);
        if (!buffer) {
                ret = -ENOMEM;
                goto out;
        }

        /* 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;

        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");

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

/**
 * set_bios_defaults() - Resets BIOS defaults
 * @deftype: the type of BIOS value reset to issue.
 *
 * Resets BIOS defaults
 */
int set_bios_defaults(u8 deftype)
{
        size_t security_area_size, buffer_size;
        size_t integer_area_size = sizeof(u8);
        char *buffer = NULL;
        u8 *defaultType;
        int ret;

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

        security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
        buffer_size = security_area_size + integer_area_size;
        buffer = kzalloc(buffer_size, GFP_KERNEL);
        if (!buffer) {
                ret = -ENOMEM;
                goto out;
        }

        /* build security area */
        populate_security_buffer(buffer, wmi_priv.current_admin_password);

        defaultType = buffer + security_area_size;
        *defaultType = deftype;

        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);

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

static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
{
        mutex_lock(&wmi_priv.mutex);
        wmi_priv.bios_attr_wdev = wdev;
        mutex_unlock(&wmi_priv.mutex);
        return 0;
}

static void bios_attr_set_interface_remove(struct wmi_device *wdev)
{
        mutex_lock(&wmi_priv.mutex);
        wmi_priv.bios_attr_wdev = NULL;
        mutex_unlock(&wmi_priv.mutex);
}

static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
        { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID },
        { },
};
static struct wmi_driver bios_attr_set_interface_driver = {
        .driver = {
                .name = DRIVER_NAME
        },
        .probe = bios_attr_set_interface_probe,
        .remove = bios_attr_set_interface_remove,
        .id_table = bios_attr_set_interface_id_table,
};

int init_bios_attr_set_interface(void)
{
        return wmi_driver_register(&bios_attr_set_interface_driver);
}

void exit_bios_attr_set_interface(void)
{
        wmi_driver_unregister(&bios_attr_set_interface_driver);
}

MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);