162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Functions corresponding to SET methods under BIOS attributes interface GUID for use
462306a36Sopenharmony_ci * with dell-wmi-sysman
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  Copyright (c) 2020 Dell Inc.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/wmi.h>
1062306a36Sopenharmony_ci#include "dell-wmi-sysman.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define SETDEFAULTVALUES_METHOD_ID					0x02
1362306a36Sopenharmony_ci#define SETBIOSDEFAULTS_METHOD_ID					0x03
1462306a36Sopenharmony_ci#define SETATTRIBUTE_METHOD_ID						0x04
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size,
1762306a36Sopenharmony_ci					int method_id)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
2062306a36Sopenharmony_ci	struct acpi_buffer input;
2162306a36Sopenharmony_ci	union acpi_object *obj;
2262306a36Sopenharmony_ci	acpi_status status;
2362306a36Sopenharmony_ci	int ret = -EIO;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	input.length =  (acpi_size) size;
2662306a36Sopenharmony_ci	input.pointer = in_args;
2762306a36Sopenharmony_ci	status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output);
2862306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
2962306a36Sopenharmony_ci		return -EIO;
3062306a36Sopenharmony_ci	obj = (union acpi_object *)output.pointer;
3162306a36Sopenharmony_ci	if (obj->type == ACPI_TYPE_INTEGER)
3262306a36Sopenharmony_ci		ret = obj->integer.value;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (wmi_priv.pending_changes == 0) {
3562306a36Sopenharmony_ci		wmi_priv.pending_changes = 1;
3662306a36Sopenharmony_ci		/* let userland know it may need to check reboot pending again */
3762306a36Sopenharmony_ci		kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci	kfree(output.pointer);
4062306a36Sopenharmony_ci	return map_wmi_error(ret);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * set_attribute() - Update an attribute value
4562306a36Sopenharmony_ci * @a_name: The attribute name
4662306a36Sopenharmony_ci * @a_value: The attribute value
4762306a36Sopenharmony_ci *
4862306a36Sopenharmony_ci * Sets an attribute to new value
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_ciint set_attribute(const char *a_name, const char *a_value)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	size_t security_area_size, buffer_size;
5362306a36Sopenharmony_ci	size_t a_name_size, a_value_size;
5462306a36Sopenharmony_ci	char *buffer = NULL, *start;
5562306a36Sopenharmony_ci	int ret;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	mutex_lock(&wmi_priv.mutex);
5862306a36Sopenharmony_ci	if (!wmi_priv.bios_attr_wdev) {
5962306a36Sopenharmony_ci		ret = -ENODEV;
6062306a36Sopenharmony_ci		goto out;
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* build/calculate buffer */
6462306a36Sopenharmony_ci	security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
6562306a36Sopenharmony_ci	a_name_size = calculate_string_buffer(a_name);
6662306a36Sopenharmony_ci	a_value_size = calculate_string_buffer(a_value);
6762306a36Sopenharmony_ci	buffer_size = security_area_size + a_name_size + a_value_size;
6862306a36Sopenharmony_ci	buffer = kzalloc(buffer_size, GFP_KERNEL);
6962306a36Sopenharmony_ci	if (!buffer) {
7062306a36Sopenharmony_ci		ret = -ENOMEM;
7162306a36Sopenharmony_ci		goto out;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	/* build security area */
7562306a36Sopenharmony_ci	populate_security_buffer(buffer, wmi_priv.current_admin_password);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* build variables to set */
7862306a36Sopenharmony_ci	start = buffer + security_area_size;
7962306a36Sopenharmony_ci	ret = populate_string_buffer(start, a_name_size, a_name);
8062306a36Sopenharmony_ci	if (ret < 0)
8162306a36Sopenharmony_ci		goto out;
8262306a36Sopenharmony_ci	start += ret;
8362306a36Sopenharmony_ci	ret = populate_string_buffer(start, a_value_size, a_value);
8462306a36Sopenharmony_ci	if (ret < 0)
8562306a36Sopenharmony_ci		goto out;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
8862306a36Sopenharmony_ci	ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
8962306a36Sopenharmony_ci					    buffer, buffer_size,
9062306a36Sopenharmony_ci					    SETATTRIBUTE_METHOD_ID);
9162306a36Sopenharmony_ci	if (ret == -EOPNOTSUPP)
9262306a36Sopenharmony_ci		dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n");
9362306a36Sopenharmony_ci	else if (ret == -EACCES)
9462306a36Sopenharmony_ci		dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n");
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ciout:
9762306a36Sopenharmony_ci	kfree(buffer);
9862306a36Sopenharmony_ci	mutex_unlock(&wmi_priv.mutex);
9962306a36Sopenharmony_ci	return ret;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/**
10362306a36Sopenharmony_ci * set_bios_defaults() - Resets BIOS defaults
10462306a36Sopenharmony_ci * @deftype: the type of BIOS value reset to issue.
10562306a36Sopenharmony_ci *
10662306a36Sopenharmony_ci * Resets BIOS defaults
10762306a36Sopenharmony_ci */
10862306a36Sopenharmony_ciint set_bios_defaults(u8 deftype)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	size_t security_area_size, buffer_size;
11162306a36Sopenharmony_ci	size_t integer_area_size = sizeof(u8);
11262306a36Sopenharmony_ci	char *buffer = NULL;
11362306a36Sopenharmony_ci	u8 *defaultType;
11462306a36Sopenharmony_ci	int ret;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	mutex_lock(&wmi_priv.mutex);
11762306a36Sopenharmony_ci	if (!wmi_priv.bios_attr_wdev) {
11862306a36Sopenharmony_ci		ret = -ENODEV;
11962306a36Sopenharmony_ci		goto out;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
12362306a36Sopenharmony_ci	buffer_size = security_area_size + integer_area_size;
12462306a36Sopenharmony_ci	buffer = kzalloc(buffer_size, GFP_KERNEL);
12562306a36Sopenharmony_ci	if (!buffer) {
12662306a36Sopenharmony_ci		ret = -ENOMEM;
12762306a36Sopenharmony_ci		goto out;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* build security area */
13162306a36Sopenharmony_ci	populate_security_buffer(buffer, wmi_priv.current_admin_password);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	defaultType = buffer + security_area_size;
13462306a36Sopenharmony_ci	*defaultType = deftype;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size,
13762306a36Sopenharmony_ci					    SETBIOSDEFAULTS_METHOD_ID);
13862306a36Sopenharmony_ci	if (ret)
13962306a36Sopenharmony_ci		dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	kfree(buffer);
14262306a36Sopenharmony_ciout:
14362306a36Sopenharmony_ci	mutex_unlock(&wmi_priv.mutex);
14462306a36Sopenharmony_ci	return ret;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	mutex_lock(&wmi_priv.mutex);
15062306a36Sopenharmony_ci	wmi_priv.bios_attr_wdev = wdev;
15162306a36Sopenharmony_ci	mutex_unlock(&wmi_priv.mutex);
15262306a36Sopenharmony_ci	return 0;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void bios_attr_set_interface_remove(struct wmi_device *wdev)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	mutex_lock(&wmi_priv.mutex);
15862306a36Sopenharmony_ci	wmi_priv.bios_attr_wdev = NULL;
15962306a36Sopenharmony_ci	mutex_unlock(&wmi_priv.mutex);
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic const struct wmi_device_id bios_attr_set_interface_id_table[] = {
16362306a36Sopenharmony_ci	{ .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID },
16462306a36Sopenharmony_ci	{ },
16562306a36Sopenharmony_ci};
16662306a36Sopenharmony_cistatic struct wmi_driver bios_attr_set_interface_driver = {
16762306a36Sopenharmony_ci	.driver = {
16862306a36Sopenharmony_ci		.name = DRIVER_NAME
16962306a36Sopenharmony_ci	},
17062306a36Sopenharmony_ci	.probe = bios_attr_set_interface_probe,
17162306a36Sopenharmony_ci	.remove = bios_attr_set_interface_remove,
17262306a36Sopenharmony_ci	.id_table = bios_attr_set_interface_id_table,
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ciint init_bios_attr_set_interface(void)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	return wmi_driver_register(&bios_attr_set_interface_driver);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_civoid exit_bios_attr_set_interface(void)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	wmi_driver_unregister(&bios_attr_set_interface_driver);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);
186