162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Functions corresponding to methods under BIOS interface GUID
462306a36Sopenharmony_ci * for use with hp-bioscfg driver.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  Copyright (c) 2022 Hewlett-Packard Inc.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/wmi.h>
1062306a36Sopenharmony_ci#include "bioscfg.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci/*
1362306a36Sopenharmony_ci * struct bios_args buffer is dynamically allocated.  New WMI command types
1462306a36Sopenharmony_ci * were introduced that exceeds 128-byte data size.  Changes to handle
1562306a36Sopenharmony_ci * the data size allocation scheme were kept in hp_wmi_perform_query function.
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_cistruct bios_args {
1862306a36Sopenharmony_ci	u32 signature;
1962306a36Sopenharmony_ci	u32 command;
2062306a36Sopenharmony_ci	u32 commandtype;
2162306a36Sopenharmony_ci	u32 datasize;
2262306a36Sopenharmony_ci	u8 data[];
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/**
2662306a36Sopenharmony_ci * hp_set_attribute
2762306a36Sopenharmony_ci *
2862306a36Sopenharmony_ci * @a_name: The attribute name
2962306a36Sopenharmony_ci * @a_value: The attribute value
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci * Sets an attribute to new value
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * Returns zero on success
3462306a36Sopenharmony_ci *	-ENODEV if device is not found
3562306a36Sopenharmony_ci *	-EINVAL if the instance of 'Setup Admin' password is not found.
3662306a36Sopenharmony_ci *	-ENOMEM unable to allocate memory
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_ciint hp_set_attribute(const char *a_name, const char *a_value)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	int security_area_size;
4162306a36Sopenharmony_ci	int a_name_size, a_value_size;
4262306a36Sopenharmony_ci	u16 *buffer = NULL;
4362306a36Sopenharmony_ci	u16 *start;
4462306a36Sopenharmony_ci	int  buffer_size, instance, ret;
4562306a36Sopenharmony_ci	char *auth_token_choice;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	mutex_lock(&bioscfg_drv.mutex);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	instance = hp_get_password_instance_for_type(SETUP_PASSWD);
5062306a36Sopenharmony_ci	if (instance < 0) {
5162306a36Sopenharmony_ci		ret = -EINVAL;
5262306a36Sopenharmony_ci		goto out_set_attribute;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* Select which auth token to use; password or [auth token] */
5662306a36Sopenharmony_ci	if (bioscfg_drv.spm_data.auth_token)
5762306a36Sopenharmony_ci		auth_token_choice = bioscfg_drv.spm_data.auth_token;
5862306a36Sopenharmony_ci	else
5962306a36Sopenharmony_ci		auth_token_choice = bioscfg_drv.password_data[instance].current_password;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	a_name_size = hp_calculate_string_buffer(a_name);
6262306a36Sopenharmony_ci	a_value_size = hp_calculate_string_buffer(a_value);
6362306a36Sopenharmony_ci	security_area_size = hp_calculate_security_buffer(auth_token_choice);
6462306a36Sopenharmony_ci	buffer_size = a_name_size + a_value_size + security_area_size;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
6762306a36Sopenharmony_ci	if (!buffer) {
6862306a36Sopenharmony_ci		ret = -ENOMEM;
6962306a36Sopenharmony_ci		goto out_set_attribute;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* build variables to set */
7362306a36Sopenharmony_ci	start = buffer;
7462306a36Sopenharmony_ci	start = hp_ascii_to_utf16_unicode(start, a_name);
7562306a36Sopenharmony_ci	if (!start) {
7662306a36Sopenharmony_ci		ret = -EINVAL;
7762306a36Sopenharmony_ci		goto out_set_attribute;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	start = hp_ascii_to_utf16_unicode(start, a_value);
8162306a36Sopenharmony_ci	if (!start) {
8262306a36Sopenharmony_ci		ret = -EINVAL;
8362306a36Sopenharmony_ci		goto out_set_attribute;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	ret = hp_populate_security_buffer(start, auth_token_choice);
8762306a36Sopenharmony_ci	if (ret < 0)
8862306a36Sopenharmony_ci		goto out_set_attribute;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	ret = hp_wmi_set_bios_setting(buffer, buffer_size);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ciout_set_attribute:
9362306a36Sopenharmony_ci	kfree(buffer);
9462306a36Sopenharmony_ci	mutex_unlock(&bioscfg_drv.mutex);
9562306a36Sopenharmony_ci	return ret;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/**
9962306a36Sopenharmony_ci * hp_wmi_perform_query
10062306a36Sopenharmony_ci *
10162306a36Sopenharmony_ci * @query:	The commandtype (enum hp_wmi_commandtype)
10262306a36Sopenharmony_ci * @command:	The command (enum hp_wmi_command)
10362306a36Sopenharmony_ci * @buffer:	Buffer used as input and/or output
10462306a36Sopenharmony_ci * @insize:	Size of input buffer
10562306a36Sopenharmony_ci * @outsize:	Size of output buffer
10662306a36Sopenharmony_ci *
10762306a36Sopenharmony_ci * returns zero on success
10862306a36Sopenharmony_ci *         an HP WMI query specific error code (which is positive)
10962306a36Sopenharmony_ci *         -EINVAL if the query was not successful at all
11062306a36Sopenharmony_ci *         -EINVAL if the output buffer size exceeds buffersize
11162306a36Sopenharmony_ci *
11262306a36Sopenharmony_ci * Note: The buffersize must at least be the maximum of the input and output
11362306a36Sopenharmony_ci *       size. E.g. Battery info query is defined to have 1 byte input
11462306a36Sopenharmony_ci *       and 128 byte output. The caller would do:
11562306a36Sopenharmony_ci *       buffer = kzalloc(128, GFP_KERNEL);
11662306a36Sopenharmony_ci *       ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
11762306a36Sopenharmony_ci *				    buffer, 1, 128)
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_ciint hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
12062306a36Sopenharmony_ci			 u32 insize, u32 outsize)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
12362306a36Sopenharmony_ci	struct bios_return *bios_return;
12462306a36Sopenharmony_ci	union acpi_object *obj = NULL;
12562306a36Sopenharmony_ci	struct bios_args *args = NULL;
12662306a36Sopenharmony_ci	int mid, actual_outsize, ret;
12762306a36Sopenharmony_ci	size_t bios_args_size;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	mid = hp_encode_outsize_for_pvsz(outsize);
13062306a36Sopenharmony_ci	if (WARN_ON(mid < 0))
13162306a36Sopenharmony_ci		return mid;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	bios_args_size = struct_size(args, data, insize);
13462306a36Sopenharmony_ci	args = kmalloc(bios_args_size, GFP_KERNEL);
13562306a36Sopenharmony_ci	if (!args)
13662306a36Sopenharmony_ci		return -ENOMEM;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	input.length = bios_args_size;
13962306a36Sopenharmony_ci	input.pointer = args;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/* BIOS expects 'SECU' in hex as the signature value*/
14262306a36Sopenharmony_ci	args->signature = 0x55434553;
14362306a36Sopenharmony_ci	args->command = command;
14462306a36Sopenharmony_ci	args->commandtype = query;
14562306a36Sopenharmony_ci	args->datasize = insize;
14662306a36Sopenharmony_ci	memcpy(args->data, buffer, flex_array_size(args, data, insize));
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
14962306a36Sopenharmony_ci	if (ret)
15062306a36Sopenharmony_ci		goto out_free;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	obj = output.pointer;
15362306a36Sopenharmony_ci	if (!obj) {
15462306a36Sopenharmony_ci		ret = -EINVAL;
15562306a36Sopenharmony_ci		goto out_free;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (obj->type != ACPI_TYPE_BUFFER ||
15962306a36Sopenharmony_ci	    obj->buffer.length < sizeof(*bios_return)) {
16062306a36Sopenharmony_ci		pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
16162306a36Sopenharmony_ci		ret = -EINVAL;
16262306a36Sopenharmony_ci		goto out_free;
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	bios_return = (struct bios_return *)obj->buffer.pointer;
16662306a36Sopenharmony_ci	ret = bios_return->return_code;
16762306a36Sopenharmony_ci	if (ret) {
16862306a36Sopenharmony_ci		if (ret != INVALID_CMD_VALUE && ret != INVALID_CMD_TYPE)
16962306a36Sopenharmony_ci			pr_warn("query 0x%x returned error 0x%x\n", query, ret);
17062306a36Sopenharmony_ci		goto out_free;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* Ignore output data of zero size */
17462306a36Sopenharmony_ci	if (!outsize)
17562306a36Sopenharmony_ci		goto out_free;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return));
17862306a36Sopenharmony_ci	memcpy_and_pad(buffer, outsize, obj->buffer.pointer + sizeof(*bios_return),
17962306a36Sopenharmony_ci		       actual_outsize, 0);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ciout_free:
18262306a36Sopenharmony_ci	ret = hp_wmi_error_and_message(ret);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	kfree(obj);
18562306a36Sopenharmony_ci	kfree(args);
18662306a36Sopenharmony_ci	return ret;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void *utf16_empty_string(u16 *p)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	*p++ = 2;
19262306a36Sopenharmony_ci	*p++ = 0x00;
19362306a36Sopenharmony_ci	return p;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci/**
19762306a36Sopenharmony_ci * hp_ascii_to_utf16_unicode -  Convert ascii string to UTF-16 unicode
19862306a36Sopenharmony_ci *
19962306a36Sopenharmony_ci * BIOS supports UTF-16 characters that are 2 bytes long.  No variable
20062306a36Sopenharmony_ci * multi-byte language supported.
20162306a36Sopenharmony_ci *
20262306a36Sopenharmony_ci * @p:   Unicode buffer address
20362306a36Sopenharmony_ci * @str: string to convert to unicode
20462306a36Sopenharmony_ci *
20562306a36Sopenharmony_ci * Returns a void pointer to the buffer string
20662306a36Sopenharmony_ci */
20762306a36Sopenharmony_civoid *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	int len = strlen(str);
21062306a36Sopenharmony_ci	int ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/*
21362306a36Sopenharmony_ci	 * Add null character when reading an empty string
21462306a36Sopenharmony_ci	 * "02 00 00 00"
21562306a36Sopenharmony_ci	 */
21662306a36Sopenharmony_ci	if (len == 0)
21762306a36Sopenharmony_ci		return utf16_empty_string(p);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	/* Move pointer len * 2 number of bytes */
22062306a36Sopenharmony_ci	*p++ = len * 2;
22162306a36Sopenharmony_ci	ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
22262306a36Sopenharmony_ci	if (ret < 0) {
22362306a36Sopenharmony_ci		dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
22462306a36Sopenharmony_ci		return NULL;
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (ret * sizeof(u16) > U16_MAX) {
22862306a36Sopenharmony_ci		dev_err(bioscfg_drv.class_dev, "Error string too long\n");
22962306a36Sopenharmony_ci		return NULL;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	p += len;
23362306a36Sopenharmony_ci	return p;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci/**
23762306a36Sopenharmony_ci * hp_wmi_set_bios_setting - Set setting's value in BIOS
23862306a36Sopenharmony_ci *
23962306a36Sopenharmony_ci * @input_buffer: Input buffer address
24062306a36Sopenharmony_ci * @input_size:   Input buffer size
24162306a36Sopenharmony_ci *
24262306a36Sopenharmony_ci * Returns: Count of unicode characters written to BIOS if successful, otherwise
24362306a36Sopenharmony_ci *		-ENOMEM unable to allocate memory
24462306a36Sopenharmony_ci *		-EINVAL buffer not allocated or too small
24562306a36Sopenharmony_ci */
24662306a36Sopenharmony_ciint hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	union acpi_object *obj;
24962306a36Sopenharmony_ci	struct acpi_buffer input = {input_size, input_buffer};
25062306a36Sopenharmony_ci	struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
25162306a36Sopenharmony_ci	int ret;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	obj = output.pointer;
25662306a36Sopenharmony_ci	if (!obj)
25762306a36Sopenharmony_ci		return -EINVAL;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	if (obj->type != ACPI_TYPE_INTEGER) {
26062306a36Sopenharmony_ci		ret = -EINVAL;
26162306a36Sopenharmony_ci		goto out_free;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	ret = obj->integer.value;
26562306a36Sopenharmony_ci	if (ret) {
26662306a36Sopenharmony_ci		ret = hp_wmi_error_and_message(ret);
26762306a36Sopenharmony_ci		goto out_free;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ciout_free:
27162306a36Sopenharmony_ci	kfree(obj);
27262306a36Sopenharmony_ci	return ret;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic int hp_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	mutex_lock(&bioscfg_drv.mutex);
27862306a36Sopenharmony_ci	mutex_unlock(&bioscfg_drv.mutex);
27962306a36Sopenharmony_ci	return 0;
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cistatic void hp_attr_set_interface_remove(struct wmi_device *wdev)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	mutex_lock(&bioscfg_drv.mutex);
28562306a36Sopenharmony_ci	mutex_unlock(&bioscfg_drv.mutex);
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic const struct wmi_device_id hp_attr_set_interface_id_table[] = {
28962306a36Sopenharmony_ci	{ .guid_string = HP_WMI_BIOS_GUID},
29062306a36Sopenharmony_ci	{ }
29162306a36Sopenharmony_ci};
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic struct wmi_driver hp_attr_set_interface_driver = {
29462306a36Sopenharmony_ci	.driver = {
29562306a36Sopenharmony_ci		.name = DRIVER_NAME,
29662306a36Sopenharmony_ci	},
29762306a36Sopenharmony_ci	.probe = hp_attr_set_interface_probe,
29862306a36Sopenharmony_ci	.remove = hp_attr_set_interface_remove,
29962306a36Sopenharmony_ci	.id_table = hp_attr_set_interface_id_table,
30062306a36Sopenharmony_ci};
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ciint hp_init_attr_set_interface(void)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	return wmi_driver_register(&hp_attr_set_interface_driver);
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_civoid hp_exit_attr_set_interface(void)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	wmi_driver_unregister(&hp_attr_set_interface_driver);
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(wmi, hp_attr_set_interface_id_table);
313