diff options
Diffstat (limited to 'drivers/platform/x86/hp')
-rw-r--r-- | drivers/platform/x86/hp/Kconfig | 79 | ||||
-rw-r--r-- | drivers/platform/x86/hp/Makefile | 11 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/Makefile | 11 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c | 312 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 1067 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 487 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c | 457 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/int-attributes.c | 418 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 441 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 556 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c | 381 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/string-attributes.c | 395 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c | 132 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp-wmi.c | 1729 | ||||
-rw-r--r-- | drivers/platform/x86/hp/hp_accel.c | 386 | ||||
-rw-r--r-- | drivers/platform/x86/hp/tc1100-wmi.c | 263 |
16 files changed, 7125 insertions, 0 deletions
diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig new file mode 100644 index 0000000000..7fef4f12e4 --- /dev/null +++ b/drivers/platform/x86/hp/Kconfig @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# X86 Platform Specific Drivers +# +menuconfig X86_PLATFORM_DRIVERS_HP + bool "HP X86 Platform Specific Device Drivers" + depends on X86_PLATFORM_DEVICES + help + Say Y here to get to see options for device drivers for various + HP x86 platforms, including vendor-specific laptop extension drivers. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if X86_PLATFORM_DRIVERS_HP + +config HP_ACCEL + tristate "HP laptop accelerometer" + default m + depends on INPUT && ACPI + depends on SERIO_I8042 + select SENSORS_LIS3LV02D + select NEW_LEDS + select LEDS_CLASS + help + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). + + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/misc-devices/lis3lv02d.rst. + + To compile this driver as a module, choose M here: the module will + be called hp_accel. + +config HP_WMI + tristate "HP WMI extras" + default m + depends on ACPI_WMI + depends on INPUT + depends on RFKILL || RFKILL = n + select INPUT_SPARSEKMAP + select ACPI_PLATFORM_PROFILE + select HWMON + help + Say Y here if you want to support WMI-based hotkeys on HP laptops and + to read data from WMI such as docking or ambient light sensor state. + + To compile this driver as a module, choose M here: the module will + be called hp-wmi. + +config TC1100_WMI + tristate "HP Compaq TC1100 Tablet WMI Extras" + default m + depends on !X86_64 + depends on ACPI + depends on ACPI_WMI + help + This is a driver for the WMI extensions (wireless and bluetooth power + control) of the HP Compaq TC1100 tablet. + +config HP_BIOSCFG + tristate "HP BIOS Configuration Driver" + default m + depends on ACPI_WMI + select NLS + select FW_ATTR_CLASS + help + This driver enables administrators to securely manage BIOS settings + using digital certificates and public-key cryptography that eliminate + the need for passwords for both remote and local management. It supports + changing BIOS settings on many HP machines from 2018 and newer without + the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called hp-bioscfg. + +endif # X86_PLATFORM_DRIVERS_HP diff --git a/drivers/platform/x86/hp/Makefile b/drivers/platform/x86/hp/Makefile new file mode 100644 index 0000000000..e4f908a61a --- /dev/null +++ b/drivers/platform/x86/hp/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/hp +# HP x86 Platform-Specific Drivers +# + +# Hewlett Packard +obj-$(CONFIG_HP_ACCEL) += hp_accel.o +obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o +obj-$(CONFIG_HP_BIOSCFG) += hp-bioscfg/ diff --git a/drivers/platform/x86/hp/hp-bioscfg/Makefile b/drivers/platform/x86/hp/hp-bioscfg/Makefile new file mode 100644 index 0000000000..67be0d9177 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_HP_BIOSCFG) := hp-bioscfg.o + +hp-bioscfg-objs := bioscfg.o \ + biosattr-interface.o \ + enum-attributes.o \ + int-attributes.o \ + order-list-attributes.o \ + passwdobj-attributes.o \ + spmobj-attributes.o \ + string-attributes.o \ + surestart-attributes.o diff --git a/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c new file mode 100644 index 0000000000..dea54f35b8 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to methods under BIOS interface GUID + * for use with hp-bioscfg driver. + * + * Copyright (c) 2022 Hewlett-Packard Inc. + */ + +#include <linux/wmi.h> +#include "bioscfg.h" + +/* + * struct bios_args buffer is dynamically allocated. New WMI command types + * were introduced that exceeds 128-byte data size. Changes to handle + * the data size allocation scheme were kept in hp_wmi_perform_query function. + */ +struct bios_args { + u32 signature; + u32 command; + u32 commandtype; + u32 datasize; + u8 data[]; +}; + +/** + * hp_set_attribute + * + * @a_name: The attribute name + * @a_value: The attribute value + * + * Sets an attribute to new value + * + * Returns zero on success + * -ENODEV if device is not found + * -EINVAL if the instance of 'Setup Admin' password is not found. + * -ENOMEM unable to allocate memory + */ +int hp_set_attribute(const char *a_name, const char *a_value) +{ + int security_area_size; + int a_name_size, a_value_size; + u16 *buffer = NULL; + u16 *start; + int buffer_size, instance, ret; + char *auth_token_choice; + + mutex_lock(&bioscfg_drv.mutex); + + instance = hp_get_password_instance_for_type(SETUP_PASSWD); + if (instance < 0) { + ret = -EINVAL; + goto out_set_attribute; + } + + /* Select which auth token to use; password or [auth token] */ + if (bioscfg_drv.spm_data.auth_token) + auth_token_choice = bioscfg_drv.spm_data.auth_token; + else + auth_token_choice = bioscfg_drv.password_data[instance].current_password; + + a_name_size = hp_calculate_string_buffer(a_name); + a_value_size = hp_calculate_string_buffer(a_value); + security_area_size = hp_calculate_security_buffer(auth_token_choice); + buffer_size = a_name_size + a_value_size + security_area_size; + + buffer = kmalloc(buffer_size + 1, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out_set_attribute; + } + + /* build variables to set */ + start = buffer; + start = hp_ascii_to_utf16_unicode(start, a_name); + if (!start) { + ret = -EINVAL; + goto out_set_attribute; + } + + start = hp_ascii_to_utf16_unicode(start, a_value); + if (!start) { + ret = -EINVAL; + goto out_set_attribute; + } + + ret = hp_populate_security_buffer(start, auth_token_choice); + if (ret < 0) + goto out_set_attribute; + + ret = hp_wmi_set_bios_setting(buffer, buffer_size); + +out_set_attribute: + kfree(buffer); + mutex_unlock(&bioscfg_drv.mutex); + return ret; +} + +/** + * hp_wmi_perform_query + * + * @query: The commandtype (enum hp_wmi_commandtype) + * @command: The command (enum hp_wmi_command) + * @buffer: Buffer used as input and/or output + * @insize: Size of input buffer + * @outsize: Size of output buffer + * + * returns zero on success + * an HP WMI query specific error code (which is positive) + * -EINVAL if the query was not successful at all + * -EINVAL if the output buffer size exceeds buffersize + * + * Note: The buffersize must at least be the maximum of the input and output + * size. E.g. Battery info query is defined to have 1 byte input + * and 128 byte output. The caller would do: + * buffer = kzalloc(128, GFP_KERNEL); + * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ, + * buffer, 1, 128) + */ +int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer, + u32 insize, u32 outsize) +{ + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_return *bios_return; + union acpi_object *obj = NULL; + struct bios_args *args = NULL; + int mid, actual_outsize, ret; + size_t bios_args_size; + + mid = hp_encode_outsize_for_pvsz(outsize); + if (WARN_ON(mid < 0)) + return mid; + + bios_args_size = struct_size(args, data, insize); + args = kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; + + input.length = bios_args_size; + input.pointer = args; + + /* BIOS expects 'SECU' in hex as the signature value*/ + args->signature = 0x55434553; + args->command = command; + args->commandtype = query; + args->datasize = insize; + memcpy(args->data, buffer, flex_array_size(args, data, insize)); + + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; + + obj = output.pointer; + if (!obj) { + ret = -EINVAL; + goto out_free; + } + + if (obj->type != ACPI_TYPE_BUFFER || + obj->buffer.length < sizeof(*bios_return)) { + pr_warn("query 0x%x returned wrong type or too small buffer\n", query); + ret = -EINVAL; + goto out_free; + } + + bios_return = (struct bios_return *)obj->buffer.pointer; + ret = bios_return->return_code; + if (ret) { + if (ret != INVALID_CMD_VALUE && ret != INVALID_CMD_TYPE) + pr_warn("query 0x%x returned error 0x%x\n", query, ret); + goto out_free; + } + + /* Ignore output data of zero size */ + if (!outsize) + goto out_free; + + actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return)); + memcpy_and_pad(buffer, outsize, obj->buffer.pointer + sizeof(*bios_return), + actual_outsize, 0); + +out_free: + ret = hp_wmi_error_and_message(ret); + + kfree(obj); + kfree(args); + return ret; +} + +static void *utf16_empty_string(u16 *p) +{ + *p++ = 2; + *p++ = 0x00; + return p; +} + +/** + * hp_ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode + * + * BIOS supports UTF-16 characters that are 2 bytes long. No variable + * multi-byte language supported. + * + * @p: Unicode buffer address + * @str: string to convert to unicode + * + * Returns a void pointer to the buffer string + */ +void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str) +{ + int len = strlen(str); + int ret; + + /* + * Add null character when reading an empty string + * "02 00 00 00" + */ + if (len == 0) + return utf16_empty_string(p); + + /* Move pointer len * 2 number of bytes */ + *p++ = len * 2; + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len); + if (ret < 0) { + dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n"); + return NULL; + } + + if (ret * sizeof(u16) > U16_MAX) { + dev_err(bioscfg_drv.class_dev, "Error string too long\n"); + return NULL; + } + + p += len; + return p; +} + +/** + * hp_wmi_set_bios_setting - Set setting's value in BIOS + * + * @input_buffer: Input buffer address + * @input_size: Input buffer size + * + * Returns: Count of unicode characters written to BIOS if successful, otherwise + * -ENOMEM unable to allocate memory + * -EINVAL buffer not allocated or too small + */ +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size) +{ + union acpi_object *obj; + struct acpi_buffer input = {input_size, input_buffer}; + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + int ret; + + ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output); + + obj = output.pointer; + if (!obj) + return -EINVAL; + + if (obj->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + ret = obj->integer.value; + if (ret) { + ret = hp_wmi_error_and_message(ret); + goto out_free; + } + +out_free: + kfree(obj); + return ret; +} + +static int hp_attr_set_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&bioscfg_drv.mutex); + mutex_unlock(&bioscfg_drv.mutex); + return 0; +} + +static void hp_attr_set_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&bioscfg_drv.mutex); + mutex_unlock(&bioscfg_drv.mutex); +} + +static const struct wmi_device_id hp_attr_set_interface_id_table[] = { + { .guid_string = HP_WMI_BIOS_GUID}, + { } +}; + +static struct wmi_driver hp_attr_set_interface_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = hp_attr_set_interface_probe, + .remove = hp_attr_set_interface_remove, + .id_table = hp_attr_set_interface_id_table, +}; + +int hp_init_attr_set_interface(void) +{ + return wmi_driver_register(&hp_attr_set_interface_driver); +} + +void hp_exit_attr_set_interface(void) +{ + wmi_driver_unregister(&hp_attr_set_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, hp_attr_set_interface_id_table); diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c new file mode 100644 index 0000000000..6ddca857cc --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common methods for use with hp-bioscfg driver + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/wmi.h> +#include "bioscfg.h" +#include "../../firmware_attributes_class.h" +#include <linux/nls.h> +#include <linux/errno.h> + +MODULE_AUTHOR("Jorge Lopez <jorge.lopez2@hp.com>"); +MODULE_DESCRIPTION("HP BIOS Configuration Driver"); +MODULE_LICENSE("GPL"); + +struct bioscfg_priv bioscfg_drv = { + .mutex = __MUTEX_INITIALIZER(bioscfg_drv.mutex), +}; + +static struct class *fw_attr_class; + +ssize_t display_name_language_code_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", LANG_CODE_STR); +} + +struct kobj_attribute common_display_langcode = + __ATTR_RO(display_name_language_code); + +int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer) +{ + int *ptr = PTR_ALIGN((int *)*buffer, sizeof(int)); + + /* Ensure there is enough space remaining to read the integer */ + if (*buffer_size < sizeof(int)) + return -EINVAL; + + *integer = *(ptr++); + *buffer = (u8 *)ptr; + *buffer_size -= sizeof(int); + + return 0; +} + +int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size) +{ + u16 *src = (u16 *)*buffer; + u16 src_size; + + u16 size; + int i; + int conv_dst_size; + + if (*buffer_size < sizeof(u16)) + return -EINVAL; + + src_size = *(src++); + /* size value in u16 chars */ + size = src_size / sizeof(u16); + + /* Ensure there is enough space remaining to read and convert + * the string + */ + if (*buffer_size < src_size) + return -EINVAL; + + for (i = 0; i < size; i++) + if (src[i] == '\\' || + src[i] == '\r' || + src[i] == '\n' || + src[i] == '\t') + size++; + + /* + * Conversion is limited to destination string max number of + * bytes. + */ + conv_dst_size = size; + if (size > dst_size) + conv_dst_size = dst_size - 1; + + /* + * convert from UTF-16 unicode to ASCII + */ + utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size); + dst[conv_dst_size] = 0; + + for (i = 0; i < conv_dst_size; i++) { + if (*src == '\\' || + *src == '\r' || + *src == '\n' || + *src == '\t') { + dst[i++] = '\\'; + if (i == conv_dst_size) + break; + } + + if (*src == '\r') + dst[i] = 'r'; + else if (*src == '\n') + dst[i] = 'n'; + else if (*src == '\t') + dst[i] = 't'; + else if (*src == '"') + dst[i] = '\''; + else + dst[i] = *src; + src++; + } + + *buffer = (u8 *)src; + *buffer_size -= size * sizeof(u16); + + return size; +} + +int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, + struct common_data *common_data) +{ + int ret = 0; + int reqs; + + // PATH: + ret = hp_get_string_from_buffer(buffer_ptr, buffer_size, common_data->path, + sizeof(common_data->path)); + if (ret < 0) + goto common_exit; + + // IS_READONLY: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->is_readonly); + if (ret < 0) + goto common_exit; + + //DISPLAY_IN_UI: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->display_in_ui); + if (ret < 0) + goto common_exit; + + // REQUIRES_PHYSICAL_PRESENCE: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->requires_physical_presence); + if (ret < 0) + goto common_exit; + + // SEQUENCE: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->sequence); + if (ret < 0) + goto common_exit; + + // PREREQUISITES_SIZE: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->prerequisites_size); + if (ret < 0) + goto common_exit; + + if (common_data->prerequisites_size > MAX_PREREQUISITES_SIZE) { + /* Report a message and limit prerequisite size to maximum value */ + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + common_data->prerequisites_size = MAX_PREREQUISITES_SIZE; + } + + // PREREQUISITES: + for (reqs = 0; reqs < common_data->prerequisites_size; reqs++) { + ret = hp_get_string_from_buffer(buffer_ptr, buffer_size, + common_data->prerequisites[reqs], + sizeof(common_data->prerequisites[reqs])); + if (ret < 0) + break; + } + + // SECURITY_LEVEL: + ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size, + &common_data->security_level); + +common_exit: + return ret; +} + +int hp_enforce_single_line_input(char *buf, size_t count) +{ + char *p; + + p = memchr(buf, '\n', count); + + if (p == buf + count - 1) + *p = '\0'; /* strip trailing newline */ + else if (p) + return -EINVAL; /* enforce single line input */ + + return 0; +} + +/* Set pending reboot value and generate KOBJ_NAME event */ +void hp_set_reboot_and_signal_event(void) +{ + bioscfg_drv.pending_reboot = true; + kobject_uevent(&bioscfg_drv.class_dev->kobj, KOBJ_CHANGE); +} + +/** + * hp_calculate_string_buffer() - determines size of string buffer for + * use with BIOS communication + * + * @str: the string to calculate based upon + */ +size_t hp_calculate_string_buffer(const char *str) +{ + size_t length = strlen(str); + + /* BIOS expects 4 bytes when an empty string is found */ + if (length == 0) + return 4; + + /* u16 length field + one UTF16 char for each input char */ + return sizeof(u16) + strlen(str) * sizeof(u16); +} + +int hp_wmi_error_and_message(int error_code) +{ + char *error_msg = NULL; + int ret; + + switch (error_code) { + case SUCCESS: + error_msg = "Success"; + ret = 0; + break; + case CMD_FAILED: + error_msg = "Command failed"; + ret = -EINVAL; + break; + case INVALID_SIGN: + error_msg = "Invalid signature"; + ret = -EINVAL; + break; + case INVALID_CMD_VALUE: + error_msg = "Invalid command value/Feature not supported"; + ret = -EOPNOTSUPP; + break; + case INVALID_CMD_TYPE: + error_msg = "Invalid command type"; + ret = -EINVAL; + break; + case INVALID_DATA_SIZE: + error_msg = "Invalid data size"; + ret = -EINVAL; + break; + case INVALID_CMD_PARAM: + error_msg = "Invalid command parameter"; + ret = -EINVAL; + break; + case ENCRYP_CMD_REQUIRED: + error_msg = "Secure/encrypted command required"; + ret = -EACCES; + break; + case NO_SECURE_SESSION: + error_msg = "No secure session established"; + ret = -EACCES; + break; + case SECURE_SESSION_FOUND: + error_msg = "Secure session already established"; + ret = -EACCES; + break; + case SECURE_SESSION_FAILED: + error_msg = "Secure session failed"; + ret = -EIO; + break; + case AUTH_FAILED: + error_msg = "Other permission/Authentication failed"; + ret = -EACCES; + break; + case INVALID_BIOS_AUTH: + error_msg = "Invalid BIOS administrator password"; + ret = -EINVAL; + break; + case NONCE_DID_NOT_MATCH: + error_msg = "Nonce did not match"; + ret = -EINVAL; + break; + case GENERIC_ERROR: + error_msg = "Generic/Other error"; + ret = -EIO; + break; + case BIOS_ADMIN_POLICY_NOT_MET: + error_msg = "BIOS Admin password does not meet password policy requirements"; + ret = -EINVAL; + break; + case BIOS_ADMIN_NOT_SET: + error_msg = "BIOS Setup password is not set"; + ret = -EPERM; + break; + case P21_NO_PROVISIONED: + error_msg = "P21 is not provisioned"; + ret = -EPERM; + break; + case P21_PROVISION_IN_PROGRESS: + error_msg = "P21 is already provisioned or provisioning is in progress and a signing key has already been sent"; + ret = -EINPROGRESS; + break; + case P21_IN_USE: + error_msg = "P21 in use (cannot deprovision)"; + ret = -EPERM; + break; + case HEP_NOT_ACTIVE: + error_msg = "HEP not activated"; + ret = -EPERM; + break; + case HEP_ALREADY_SET: + error_msg = "HEP Transport already set"; + ret = -EINVAL; + break; + case HEP_CHECK_STATE: + error_msg = "Check the current HEP state"; + ret = -EINVAL; + break; + default: + error_msg = "Generic/Other error"; + ret = -EIO; + break; + } + + if (error_code) + pr_warn_ratelimited("Returned error 0x%x, \"%s\"\n", error_code, error_msg); + + return ret; +} + +static ssize_t pending_reboot_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", bioscfg_drv.pending_reboot); +} + +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); + +/* + * create_attributes_level_sysfs_files() - Creates pending_reboot attributes + */ +static int create_attributes_level_sysfs_files(void) +{ + return sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, + &pending_reboot.attr); +} + +static void attr_name_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static const struct kobj_type attr_name_ktype = { + .release = attr_name_release, + .sysfs_ops = &kobj_sysfs_ops, +}; + +/** + * hp_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 *hp_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; +} + +/** + * hp_get_instance_count() - Compute total number of instances under guid_string + * + * @guid_string: WMI GUID (in string form) + */ +int hp_get_instance_count(const char *guid_string) +{ + union acpi_object *wmi_obj = NULL; + int i = 0; + + do { + kfree(wmi_obj); + wmi_obj = hp_get_wmiobj_pointer(i, guid_string); + i++; + } while (wmi_obj); + + return i - 1; +} + +/** + * hp_alloc_attributes_data() - Allocate attributes data for a particular type + * + * @attr_type: Attribute type to allocate + */ +static int hp_alloc_attributes_data(int attr_type) +{ + switch (attr_type) { + case HPWMI_STRING_TYPE: + return hp_alloc_string_data(); + + case HPWMI_INTEGER_TYPE: + return hp_alloc_integer_data(); + + case HPWMI_ENUMERATION_TYPE: + return hp_alloc_enumeration_data(); + + case HPWMI_ORDERED_LIST_TYPE: + return hp_alloc_ordered_list_data(); + + case HPWMI_PASSWORD_TYPE: + return hp_alloc_password_data(); + + default: + return 0; + } +} + +int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len) +{ + int ret = 0; + int new_len = 0; + char tmp[] = "0x00"; + char *new_str = NULL; + long ch; + int i; + + if (input_len <= 0 || !input || !str || !len) + return -EINVAL; + + *len = 0; + *str = NULL; + + new_str = kmalloc(input_len, GFP_KERNEL); + if (!new_str) + return -ENOMEM; + + for (i = 0; i < input_len; i += 5) { + strncpy(tmp, input + i, strlen(tmp)); + if (kstrtol(tmp, 16, &ch) == 0) { + // escape char + if (ch == '\\' || + ch == '\r' || + ch == '\n' || ch == '\t') { + if (ch == '\r') + ch = 'r'; + else if (ch == '\n') + ch = 'n'; + else if (ch == '\t') + ch = 't'; + new_str[new_len++] = '\\'; + } + new_str[new_len++] = ch; + if (ch == '\0') + break; + } + } + + if (new_len) { + new_str[new_len] = '\0'; + *str = krealloc(new_str, (new_len + 1) * sizeof(char), + GFP_KERNEL); + if (*str) + *len = new_len; + else + ret = -ENOMEM; + } else { + ret = -EFAULT; + } + + if (ret) + kfree(new_str); + return ret; +} + +/* map output size to the corresponding WMI method id */ +int hp_encode_outsize_for_pvsz(int outsize) +{ + if (outsize > 4096) + return -EINVAL; + if (outsize > 1024) + return 5; + if (outsize > 128) + return 4; + if (outsize > 4) + return 3; + if (outsize > 0) + return 2; + return 1; +} + +/* + * Update friendly display name for several attributes associated to + * 'Schedule Power-On' + */ +void hp_friendly_user_name_update(char *path, const char *attr_name, + char *attr_display, int attr_size) +{ + if (strstr(path, SCHEDULE_POWER_ON)) + snprintf(attr_display, attr_size, "%s - %s", SCHEDULE_POWER_ON, attr_name); + else + strscpy(attr_display, attr_name, attr_size); +} + +/** + * hp_update_attribute_permissions() - Update attributes permissions when + * isReadOnly value is 1 + * + * @is_readonly: bool value to indicate if it a readonly attribute. + * @current_val: kobj_attribute corresponding to attribute. + * + */ +void hp_update_attribute_permissions(bool is_readonly, struct kobj_attribute *current_val) +{ + current_val->attr.mode = is_readonly ? 0444 : 0644; +} + +/** + * 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(&bioscfg_drv.mutex); + + hp_exit_string_attributes(); + hp_exit_integer_attributes(); + hp_exit_enumeration_attributes(); + hp_exit_ordered_list_attributes(); + hp_exit_password_attributes(); + hp_exit_sure_start_attributes(); + hp_exit_secure_platform_attributes(); + + if (bioscfg_drv.authentication_dir_kset) { + destroy_attribute_objs(bioscfg_drv.authentication_dir_kset); + kset_unregister(bioscfg_drv.authentication_dir_kset); + bioscfg_drv.authentication_dir_kset = NULL; + } + if (bioscfg_drv.main_dir_kset) { + sysfs_remove_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr); + destroy_attribute_objs(bioscfg_drv.main_dir_kset); + kset_unregister(bioscfg_drv.main_dir_kset); + bioscfg_drv.main_dir_kset = NULL; + } + mutex_unlock(&bioscfg_drv.mutex); +} + +/** + * hp_add_other_attributes() - Initialize HP custom attributes not + * reported by BIOS and required to support Secure Platform and Sure + * Start. + * + * @attr_type: Custom HP attribute not reported by BIOS + * + * Initialize all 2 types of attributes: Platform and Sure Start + * object. Populates each attribute types respective properties + * under sysfs files. + * + * Returns zero(0) if successful. Otherwise, a negative value. + */ +static int hp_add_other_attributes(int attr_type) +{ + struct kobject *attr_name_kobj; + union acpi_object *obj = NULL; + int ret; + char *attr_name; + + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) + return -ENOMEM; + + mutex_lock(&bioscfg_drv.mutex); + + /* Check if attribute type is supported */ + switch (attr_type) { + case HPWMI_SECURE_PLATFORM_TYPE: + attr_name_kobj->kset = bioscfg_drv.authentication_dir_kset; + attr_name = SPM_STR; + break; + + case HPWMI_SURE_START_TYPE: + attr_name_kobj->kset = bioscfg_drv.main_dir_kset; + attr_name = SURE_START_STR; + break; + + default: + pr_err("Error: Unknown attr_type: %d\n", attr_type); + ret = -EINVAL; + kfree(attr_name_kobj); + goto unlock_drv_mutex; + } + + ret = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, + NULL, "%s", attr_name); + if (ret) { + pr_err("Error encountered [%d]\n", ret); + goto err_other_attr_init; + } + + /* Populate attribute data */ + switch (attr_type) { + case HPWMI_SECURE_PLATFORM_TYPE: + ret = hp_populate_secure_platform_data(attr_name_kobj); + break; + + case HPWMI_SURE_START_TYPE: + ret = hp_populate_sure_start_data(attr_name_kobj); + break; + + default: + ret = -EINVAL; + } + + if (ret) + goto err_other_attr_init; + + mutex_unlock(&bioscfg_drv.mutex); + return 0; + +err_other_attr_init: + kobject_put(attr_name_kobj); +unlock_drv_mutex: + mutex_unlock(&bioscfg_drv.mutex); + kfree(obj); + return ret; +} + +static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, + union acpi_object *obj, + const char *guid, int min_elements, + int instance_id) +{ + struct kobject *attr_name_kobj, *duplicate; + union acpi_object *elements; + struct kset *temp_kset; + + char *str_value = NULL; + int str_len; + int ret = 0; + + /* Take action appropriate to each ACPI TYPE */ + if (obj->package.count < min_elements) { + pr_err("ACPI-package does not have enough elements: %d < %d\n", + obj->package.count, min_elements); + goto pack_attr_exit; + } + + elements = obj->package.elements; + + /* sanity checking */ + if (elements[NAME].type != ACPI_TYPE_STRING) { + pr_debug("incorrect element type\n"); + goto pack_attr_exit; + } + if (strlen(elements[NAME].string.pointer) == 0) { + pr_debug("empty attribute found\n"); + goto pack_attr_exit; + } + + if (attr_type == HPWMI_PASSWORD_TYPE) + temp_kset = bioscfg_drv.authentication_dir_kset; + else + temp_kset = bioscfg_drv.main_dir_kset; + + /* convert attribute name to string */ + ret = hp_convert_hexstr_to_str(elements[NAME].string.pointer, + elements[NAME].string.length, + &str_value, &str_len); + + if (ret) { + pr_debug("Failed to populate integer package data. Error [0%0x]\n", + ret); + kfree(str_value); + return ret; + } + + /* All duplicate attributes found are ignored */ + duplicate = kset_find_obj(temp_kset, str_value); + if (duplicate) { + pr_debug("Duplicate attribute name found - %s\n", str_value); + /* kset_find_obj() returns a reference */ + kobject_put(duplicate); + goto pack_attr_exit; + } + + /* build attribute */ + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) { + ret = -ENOMEM; + goto pack_attr_exit; + } + + attr_name_kobj->kset = temp_kset; + + ret = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, + NULL, "%s", str_value); + + if (ret) { + kobject_put(attr_name_kobj); + goto pack_attr_exit; + } + + /* enumerate all of these attributes */ + switch (attr_type) { + case HPWMI_STRING_TYPE: + ret = hp_populate_string_package_data(elements, + instance_id, + attr_name_kobj); + break; + case HPWMI_INTEGER_TYPE: + ret = hp_populate_integer_package_data(elements, + instance_id, + attr_name_kobj); + break; + case HPWMI_ENUMERATION_TYPE: + ret = hp_populate_enumeration_package_data(elements, + instance_id, + attr_name_kobj); + break; + case HPWMI_ORDERED_LIST_TYPE: + ret = hp_populate_ordered_list_package_data(elements, + instance_id, + attr_name_kobj); + break; + case HPWMI_PASSWORD_TYPE: + ret = hp_populate_password_package_data(elements, + instance_id, + attr_name_kobj); + break; + default: + pr_debug("Unknown attribute type found: 0x%x\n", attr_type); + break; + } + +pack_attr_exit: + kfree(str_value); + return ret; +} + +static int hp_init_bios_buffer_attribute(enum hp_wmi_data_type attr_type, + union acpi_object *obj, + const char *guid, int min_elements, + int instance_id) +{ + struct kobject *attr_name_kobj, *duplicate; + struct kset *temp_kset; + char str[MAX_BUFF_SIZE]; + + char *temp_str = NULL; + char *str_value = NULL; + u8 *buffer_ptr = NULL; + int buffer_size; + int ret = 0; + + buffer_size = obj->buffer.length; + buffer_ptr = obj->buffer.pointer; + + ret = hp_get_string_from_buffer(&buffer_ptr, + &buffer_size, str, MAX_BUFF_SIZE); + + if (ret < 0) + goto buff_attr_exit; + + if (attr_type == HPWMI_PASSWORD_TYPE || + attr_type == HPWMI_SECURE_PLATFORM_TYPE) + temp_kset = bioscfg_drv.authentication_dir_kset; + else + temp_kset = bioscfg_drv.main_dir_kset; + + /* All duplicate attributes found are ignored */ + duplicate = kset_find_obj(temp_kset, str); + if (duplicate) { + pr_debug("Duplicate attribute name found - %s\n", str); + /* kset_find_obj() returns a reference */ + kobject_put(duplicate); + goto buff_attr_exit; + } + + /* build attribute */ + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) { + ret = -ENOMEM; + goto buff_attr_exit; + } + + attr_name_kobj->kset = temp_kset; + + temp_str = str; + if (attr_type == HPWMI_SECURE_PLATFORM_TYPE) + temp_str = "SPM"; + + ret = kobject_init_and_add(attr_name_kobj, + &attr_name_ktype, NULL, "%s", temp_str); + if (ret) { + kobject_put(attr_name_kobj); + goto buff_attr_exit; + } + + /* enumerate all of these attributes */ + switch (attr_type) { + case HPWMI_STRING_TYPE: + ret = hp_populate_string_buffer_data(buffer_ptr, + &buffer_size, + instance_id, + attr_name_kobj); + break; + case HPWMI_INTEGER_TYPE: + ret = hp_populate_integer_buffer_data(buffer_ptr, + &buffer_size, + instance_id, + attr_name_kobj); + break; + case HPWMI_ENUMERATION_TYPE: + ret = hp_populate_enumeration_buffer_data(buffer_ptr, + &buffer_size, + instance_id, + attr_name_kobj); + break; + case HPWMI_ORDERED_LIST_TYPE: + ret = hp_populate_ordered_list_buffer_data(buffer_ptr, + &buffer_size, + instance_id, + attr_name_kobj); + break; + case HPWMI_PASSWORD_TYPE: + ret = hp_populate_password_buffer_data(buffer_ptr, + &buffer_size, + instance_id, + attr_name_kobj); + break; + default: + pr_debug("Unknown attribute type found: 0x%x\n", attr_type); + break; + } + +buff_attr_exit: + kfree(str_value); + return ret; +} + +/** + * hp_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 + * + * Initialize all 5 types of attributes: enumeration, integer, + * string, password, ordered list object. Populates each attribute types + * respective properties under sysfs files + */ +static int hp_init_bios_attributes(enum hp_wmi_data_type attr_type, const char *guid) +{ + union acpi_object *obj = NULL; + 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 cur_instance_id = instance_id; + int ret = 0; + + ret = hp_alloc_attributes_data(attr_type); + if (ret) + return ret; + + switch (attr_type) { + case HPWMI_STRING_TYPE: + min_elements = STR_ELEM_CNT; + break; + case HPWMI_INTEGER_TYPE: + min_elements = INT_ELEM_CNT; + break; + case HPWMI_ENUMERATION_TYPE: + min_elements = ENUM_ELEM_CNT; + break; + case HPWMI_ORDERED_LIST_TYPE: + min_elements = ORD_ELEM_CNT; + break; + case HPWMI_PASSWORD_TYPE: + min_elements = PSWD_ELEM_CNT; + 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 = hp_get_wmiobj_pointer(instance_id, guid); + if (!obj) + return -ENODEV; + + mutex_lock(&bioscfg_drv.mutex); + while (obj) { + /* Take action appropriate to each ACPI TYPE */ + if (obj->type == ACPI_TYPE_PACKAGE) { + ret = hp_init_bios_package_attribute(attr_type, obj, + guid, min_elements, + cur_instance_id); + + } else if (obj->type == ACPI_TYPE_BUFFER) { + ret = hp_init_bios_buffer_attribute(attr_type, obj, + guid, min_elements, + cur_instance_id); + + } else { + pr_err("Expected ACPI-package or buffer type, got: %d\n", + obj->type); + ret = -EIO; + goto err_attr_init; + } + + /* + * Failure reported in one attribute must not + * stop process of the remaining attribute values. + */ + if (ret >= 0) + cur_instance_id++; + + kfree(obj); + instance_id++; + obj = hp_get_wmiobj_pointer(instance_id, guid); + } + +err_attr_init: + mutex_unlock(&bioscfg_drv.mutex); + kfree(obj); + return ret; +} + +static int __init hp_init(void) +{ + int ret; + int hp_bios_capable = wmi_has_guid(HP_WMI_BIOS_GUID); + int set_bios_settings = wmi_has_guid(HP_WMI_SET_BIOS_SETTING_GUID); + + if (!hp_bios_capable) { + pr_err("Unable to run on non-HP system\n"); + return -ENODEV; + } + + if (!set_bios_settings) { + pr_err("Unable to set BIOS settings on HP systems\n"); + return -ENODEV; + } + + ret = hp_init_attr_set_interface(); + if (ret) + return ret; + + ret = fw_attributes_class_get(&fw_attr_class); + if (ret) + goto err_unregister_class; + + bioscfg_drv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(bioscfg_drv.class_dev)) { + ret = PTR_ERR(bioscfg_drv.class_dev); + goto err_unregister_class; + } + + bioscfg_drv.main_dir_kset = kset_create_and_add("attributes", NULL, + &bioscfg_drv.class_dev->kobj); + if (!bioscfg_drv.main_dir_kset) { + ret = -ENOMEM; + pr_debug("Failed to create and add attributes\n"); + goto err_destroy_classdev; + } + + bioscfg_drv.authentication_dir_kset = kset_create_and_add("authentication", NULL, + &bioscfg_drv.class_dev->kobj); + if (!bioscfg_drv.authentication_dir_kset) { + ret = -ENOMEM; + pr_debug("Failed to create and add authentication\n"); + goto err_release_attributes_data; + } + + /* + * sysfs level attributes. + * - pending_reboot + */ + ret = create_attributes_level_sysfs_files(); + if (ret) + pr_debug("Failed to create sysfs level attributes\n"); + + ret = hp_init_bios_attributes(HPWMI_STRING_TYPE, HP_WMI_BIOS_STRING_GUID); + if (ret) + pr_debug("Failed to populate string type attributes\n"); + + ret = hp_init_bios_attributes(HPWMI_INTEGER_TYPE, HP_WMI_BIOS_INTEGER_GUID); + if (ret) + pr_debug("Failed to populate integer type attributes\n"); + + ret = hp_init_bios_attributes(HPWMI_ENUMERATION_TYPE, HP_WMI_BIOS_ENUMERATION_GUID); + if (ret) + pr_debug("Failed to populate enumeration type attributes\n"); + + ret = hp_init_bios_attributes(HPWMI_ORDERED_LIST_TYPE, HP_WMI_BIOS_ORDERED_LIST_GUID); + if (ret) + pr_debug("Failed to populate ordered list object type attributes\n"); + + ret = hp_init_bios_attributes(HPWMI_PASSWORD_TYPE, HP_WMI_BIOS_PASSWORD_GUID); + if (ret) + pr_debug("Failed to populate password object type attributes\n"); + + bioscfg_drv.spm_data.attr_name_kobj = NULL; + ret = hp_add_other_attributes(HPWMI_SECURE_PLATFORM_TYPE); + if (ret) + pr_debug("Failed to populate secure platform object type attribute\n"); + + bioscfg_drv.sure_start_attr_kobj = NULL; + ret = hp_add_other_attributes(HPWMI_SURE_START_TYPE); + if (ret) + pr_debug("Failed to populate sure start object type attribute\n"); + + 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(); + hp_exit_attr_set_interface(); + + return ret; +} + +static void __exit hp_exit(void) +{ + release_attributes_data(); + device_destroy(fw_attr_class, MKDEV(0, 0)); + + fw_attributes_class_put(); + hp_exit_attr_set_interface(); +} + +module_init(hp_init); +module_exit(hp_exit); diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h new file mode 100644 index 0000000000..3166ef328e --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -0,0 +1,487 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Definitions for kernel modules using hp_bioscfg driver + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#ifndef _HP_BIOSCFG_H_ +#define _HP_BIOSCFG_H_ + +#include <linux/wmi.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/nls.h> + +#define DRIVER_NAME "hp-bioscfg" + +#define MAX_BUFF_SIZE 512 +#define MAX_KEY_MOD_SIZE 256 +#define MAX_PASSWD_SIZE 64 +#define MAX_PREREQUISITES_SIZE 20 +#define MAX_REQ_ELEM_SIZE 128 +#define MAX_VALUES_SIZE 16 +#define MAX_ENCODINGS_SIZE 16 +#define MAX_ELEMENTS_SIZE 16 + +#define SPM_STR_DESC "Secure Platform Management" +#define SPM_STR "SPM" +#define SURE_START_DESC "Sure Start" +#define SURE_START_STR "Sure_Start" +#define SETUP_PASSWD "Setup Password" +#define POWER_ON_PASSWD "Power-On Password" + +#define LANG_CODE_STR "en_US.UTF-8" +#define SCHEDULE_POWER_ON "Scheduled Power-On" + +#define COMMA_SEP "," +#define SEMICOLON_SEP ";" + +/* Sure Admin Functions */ + +#define UTF_PREFIX "<utf-16/>" +#define BEAM_PREFIX "<BEAM/>" + +enum mechanism_values { + PASSWORD = 0x00, + SIGNING_KEY = 0x01, + ENDORSEMENT_KEY = 0x02, +}; + +#define BIOS_ADMIN "bios-admin" +#define POWER_ON "power-on" +#define BIOS_SPM "enhanced-bios-auth" + +#define PASSWD_MECHANISM_TYPES "password" + +#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" + +#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C" +#define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05" +#define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133" +#define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745" +#define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D" +#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E" + +enum hp_wmi_spm_commandtype { + HPWMI_SECUREPLATFORM_GET_STATE = 0x10, + HPWMI_SECUREPLATFORM_SET_KEK = 0x11, + HPWMI_SECUREPLATFORM_SET_SK = 0x12, +}; + +enum hp_wmi_surestart_commandtype { + HPWMI_SURESTART_GET_LOG_COUNT = 0x01, + HPWMI_SURESTART_GET_LOG = 0x02, +}; + +enum hp_wmi_command { + HPWMI_READ = 0x01, + HPWMI_WRITE = 0x02, + HPWMI_ODM = 0x03, + HPWMI_SURESTART = 0x20006, + HPWMI_GM = 0x20008, + HPWMI_SECUREPLATFORM = 0x20010, +}; + +struct bios_return { + u32 sigpass; + u32 return_code; +}; + +enum wmi_error_values { + SUCCESS = 0x00, + CMD_FAILED = 0x01, + INVALID_SIGN = 0x02, + INVALID_CMD_VALUE = 0x03, + INVALID_CMD_TYPE = 0x04, + INVALID_DATA_SIZE = 0x05, + INVALID_CMD_PARAM = 0x06, + ENCRYP_CMD_REQUIRED = 0x07, + NO_SECURE_SESSION = 0x08, + SECURE_SESSION_FOUND = 0x09, + SECURE_SESSION_FAILED = 0x0A, + AUTH_FAILED = 0x0B, + INVALID_BIOS_AUTH = 0x0E, + NONCE_DID_NOT_MATCH = 0x18, + GENERIC_ERROR = 0x1C, + BIOS_ADMIN_POLICY_NOT_MET = 0x28, + BIOS_ADMIN_NOT_SET = 0x38, + P21_NO_PROVISIONED = 0x1000, + P21_PROVISION_IN_PROGRESS = 0x1001, + P21_IN_USE = 0x1002, + HEP_NOT_ACTIVE = 0x1004, + HEP_ALREADY_SET = 0x1006, + HEP_CHECK_STATE = 0x1007, +}; + +struct common_data { + u8 display_name[MAX_BUFF_SIZE]; + u8 path[MAX_BUFF_SIZE]; + u32 is_readonly; + u32 display_in_ui; + u32 requires_physical_presence; + u32 sequence; + u32 prerequisites_size; + u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF_SIZE]; + u32 security_level; +}; + +struct string_data { + struct common_data common; + struct kobject *attr_name_kobj; + u8 current_value[MAX_BUFF_SIZE]; + u8 new_value[MAX_BUFF_SIZE]; + u32 min_length; + u32 max_length; +}; + +struct integer_data { + struct common_data common; + struct kobject *attr_name_kobj; + u32 current_value; + u32 new_value; + u32 lower_bound; + u32 upper_bound; + u32 scalar_increment; +}; + +struct enumeration_data { + struct common_data common; + struct kobject *attr_name_kobj; + u8 current_value[MAX_BUFF_SIZE]; + u8 new_value[MAX_BUFF_SIZE]; + u32 possible_values_size; + u8 possible_values[MAX_VALUES_SIZE][MAX_BUFF_SIZE]; +}; + +struct ordered_list_data { + struct common_data common; + struct kobject *attr_name_kobj; + u8 current_value[MAX_BUFF_SIZE]; + u8 new_value[MAX_BUFF_SIZE]; + u32 elements_size; + u8 elements[MAX_ELEMENTS_SIZE][MAX_BUFF_SIZE]; +}; + +struct password_data { + struct common_data common; + struct kobject *attr_name_kobj; + u8 current_password[MAX_PASSWD_SIZE]; + u8 new_password[MAX_PASSWD_SIZE]; + u32 min_password_length; + u32 max_password_length; + u32 encodings_size; + u8 encodings[MAX_ENCODINGS_SIZE][MAX_BUFF_SIZE]; + bool is_enabled; + + /* + * 'role' identifies the type of authentication. + * Two known types are bios-admin and power-on. + * 'bios-admin' represents BIOS administrator password + * 'power-on' represents a password required to use the system + */ + u32 role; + + /* + * 'mechanism' represents the means of authentication. + * Only supported type currently is "password" + */ + u32 mechanism; +}; + +struct secure_platform_data { + struct kobject *attr_name_kobj; + u8 attribute_name[MAX_BUFF_SIZE]; + u8 *endorsement_key; + u8 *signing_key; + u8 *auth_token; + bool is_enabled; + u32 mechanism; +}; + +struct bioscfg_priv { + struct kset *authentication_dir_kset; + struct kset *main_dir_kset; + struct device *class_dev; + struct string_data *string_data; + u32 string_instances_count; + struct integer_data *integer_data; + u32 integer_instances_count; + struct enumeration_data *enumeration_data; + u32 enumeration_instances_count; + struct ordered_list_data *ordered_list_data; + u32 ordered_list_instances_count; + struct password_data *password_data; + u32 password_instances_count; + + struct kobject *sure_start_attr_kobj; + struct secure_platform_data spm_data; + u8 display_name_language_code[MAX_BUFF_SIZE]; + bool pending_reboot; + struct mutex mutex; +}; + +/* global structure used by multiple WMI interfaces */ +extern struct bioscfg_priv bioscfg_drv; + +enum hp_wmi_data_type { + HPWMI_STRING_TYPE, + HPWMI_INTEGER_TYPE, + HPWMI_ENUMERATION_TYPE, + HPWMI_ORDERED_LIST_TYPE, + HPWMI_PASSWORD_TYPE, + HPWMI_SECURE_PLATFORM_TYPE, + HPWMI_SURE_START_TYPE, +}; + +enum hp_wmi_data_elements { + /* Common elements */ + NAME = 0, + VALUE = 1, + PATH = 2, + IS_READONLY = 3, + DISPLAY_IN_UI = 4, + REQUIRES_PHYSICAL_PRESENCE = 5, + SEQUENCE = 6, + PREREQUISITES_SIZE = 7, + PREREQUISITES = 8, + SECURITY_LEVEL = 9, + + /* String elements */ + STR_MIN_LENGTH = 10, + STR_MAX_LENGTH = 11, + STR_ELEM_CNT = 12, + + /* Integer elements */ + INT_LOWER_BOUND = 10, + INT_UPPER_BOUND = 11, + INT_SCALAR_INCREMENT = 12, + INT_ELEM_CNT = 13, + + /* Enumeration elements */ + ENUM_CURRENT_VALUE = 10, + ENUM_SIZE = 11, + ENUM_POSSIBLE_VALUES = 12, + ENUM_ELEM_CNT = 13, + + /* Ordered list elements */ + ORD_LIST_SIZE = 10, + ORD_LIST_ELEMENTS = 11, + ORD_ELEM_CNT = 12, + + /* Password elements */ + PSWD_MIN_LENGTH = 10, + PSWD_MAX_LENGTH = 11, + PSWD_SIZE = 12, + PSWD_ENCODINGS = 13, + PSWD_IS_SET = 14, + PSWD_ELEM_CNT = 15, +}; + +#define GET_INSTANCE_ID(type) \ + static int get_##type##_instance_id(struct kobject *kobj) \ + { \ + int i; \ + \ + for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \ + if (!strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \ + return i; \ + } \ + return -EIO; \ + } + +#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \ + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ + { \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \ + return -EIO; \ + } + +#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \ + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ + { \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \ + return -EIO; \ + } + +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \ + static ssize_t curr_val##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ + { \ + char *attr_value = NULL; \ + int i; \ + int ret = -EIO; \ + \ + attr_value = kstrdup(buf, GFP_KERNEL); \ + if (!attr_value) \ + return -ENOMEM; \ + \ + ret = hp_enforce_single_line_input(attr_value, count); \ + if (!ret) { \ + i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + ret = validate_##type##_input(i, attr_value); \ + } \ + if (!ret) \ + ret = hp_set_attribute(kobj->name, attr_value); \ + if (!ret) { \ + update_##type##_value(i, attr_value); \ + if (bioscfg_drv.type##_data[i].common.requires_physical_presence) \ + hp_set_reboot_and_signal_event(); \ + } \ + hp_clear_all_credentials(); \ + kfree(attr_value); \ + \ + return ret ? ret : count; \ + } + +#define ATTRIBUTE_SPM_N_PROPERTY_SHOW(name, type) \ + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data.name); \ + } + +#define ATTRIBUTE_SPM_S_PROPERTY_SHOW(name, type) \ + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data.name); \ + } + +#define ATTRIBUTE_VALUES_PROPERTY_SHOW(name, type, sep) \ + static ssize_t name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + int i; \ + int len = 0; \ + int instance_id = get_##type##_instance_id(kobj); \ + \ + if (instance_id < 0) \ + return 0; \ + \ + for (i = 0; i < bioscfg_drv.type##_data[instance_id].name##_size; i++) { \ + if (i) \ + len += sysfs_emit_at(buf, len, "%s", sep); \ + \ + len += sysfs_emit_at(buf, len, "%s", \ + bioscfg_drv.type##_data[instance_id].name[i]); \ + } \ + len += sysfs_emit_at(buf, len, "\n"); \ + return len; \ + } + +#define ATTRIBUTE_S_COMMON_PROPERTY_SHOW(name, type) \ + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ + { \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].common.name); \ + return -EIO; \ + } + +extern struct kobj_attribute common_display_langcode; + +/* Prototypes */ + +/* String attributes */ +int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj); +int hp_alloc_string_data(void); +void hp_exit_string_attributes(void); +int hp_populate_string_package_data(union acpi_object *str_obj, + int instance_id, + struct kobject *attr_name_kobj); + +/* Integer attributes */ +int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj); +int hp_alloc_integer_data(void); +void hp_exit_integer_attributes(void); +int hp_populate_integer_package_data(union acpi_object *integer_obj, + int instance_id, + struct kobject *attr_name_kobj); + +/* Enumeration attributes */ +int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj); +int hp_alloc_enumeration_data(void); +void hp_exit_enumeration_attributes(void); +int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int instance_id, + struct kobject *attr_name_kobj); + +/* Ordered list */ +int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, + u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj); +int hp_alloc_ordered_list_data(void); +void hp_exit_ordered_list_attributes(void); +int hp_populate_ordered_list_package_data(union acpi_object *order_obj, + int instance_id, + struct kobject *attr_name_kobj); + +/* Password authentication attributes */ +int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj); +int hp_populate_password_package_data(union acpi_object *password_obj, + int instance_id, + struct kobject *attr_name_kobj); +int hp_alloc_password_data(void); +int hp_get_password_instance_for_type(const char *name); +int hp_clear_all_credentials(void); +int hp_set_attribute(const char *a_name, const char *a_value); + +/* SPM attributes */ +void hp_exit_password_attributes(void); +void hp_exit_secure_platform_attributes(void); +int hp_populate_secure_platform_data(struct kobject *attr_name_kobj); +int hp_populate_security_buffer(u16 *buffer, const char *authentication); + +/* Bios Attributes interface */ +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size); +int hp_wmi_perform_query(int query, enum hp_wmi_command command, + void *buffer, u32 insize, u32 outsize); + +/* Sure Start attributes */ +void hp_exit_sure_start_attributes(void); +int hp_populate_sure_start_data(struct kobject *attr_name_kobj); + +/* Bioscfg */ + +void hp_exit_attr_set_interface(void); +int hp_init_attr_set_interface(void); +size_t hp_calculate_string_buffer(const char *str); +size_t hp_calculate_security_buffer(const char *authentication); +void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str); +int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer); +int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size); +int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len); +int hp_encode_outsize_for_pvsz(int outsize); +int hp_enforce_single_line_input(char *buf, size_t count); +void hp_set_reboot_and_signal_event(void); +ssize_t display_name_language_code_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf); +union acpi_object *hp_get_wmiobj_pointer(int instance_id, const char *guid_string); +int hp_get_instance_count(const char *guid_string); +void hp_update_attribute_permissions(bool isreadonly, struct kobj_attribute *current_val); +void hp_friendly_user_name_update(char *path, const char *attr_name, + char *attr_display, int attr_size); +int hp_wmi_error_and_message(int error_code); +int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, struct common_data *common); + +#endif diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c new file mode 100644 index 0000000000..a2402d31c1 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to enumeration type attributes under + * BIOS Enumeration GUID for use with hp-bioscfg driver. + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" + +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); + + if (instance_id < 0) + return -EIO; + + return sysfs_emit(buf, "%s\n", + bioscfg_drv.enumeration_data[instance_id].current_value); +} + +/** + * validate_enumeration_input() - + * Validate input of current_value against possible values + * + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_enumeration_input(int instance_id, const char *buf) +{ + int i; + int found = 0; + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + + /* Is it a read only attribute */ + if (enum_data->common.is_readonly) + return -EIO; + + for (i = 0; i < enum_data->possible_values_size && !found; i++) + if (!strcmp(enum_data->possible_values[i], buf)) + found = 1; + + if (!found) + return -EINVAL; + + return 0; +} + +static void update_enumeration_value(int instance_id, char *attr_value) +{ + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + + strscpy(enum_data->current_value, + attr_value, + sizeof(enum_data->current_value)); +} + +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration); +static struct kobj_attribute enumeration_display_name = + __ATTR_RO(display_name); + +ATTRIBUTE_PROPERTY_STORE(current_value, enumeration); +static struct kobj_attribute enumeration_current_val = + __ATTR_RW(current_value); + +ATTRIBUTE_VALUES_PROPERTY_SHOW(possible_values, enumeration, SEMICOLON_SEP); +static struct kobj_attribute enumeration_poss_val = + __ATTR_RO(possible_values); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "enumeration\n"); +} + +static struct kobj_attribute enumeration_type = + __ATTR_RO(type); + +static struct attribute *enumeration_attrs[] = { + &common_display_langcode.attr, + &enumeration_display_name.attr, + &enumeration_current_val.attr, + &enumeration_poss_val.attr, + &enumeration_type.attr, + NULL +}; + +static const struct attribute_group enumeration_attr_group = { + .attrs = enumeration_attrs, +}; + +int hp_alloc_enumeration_data(void) +{ + bioscfg_drv.enumeration_instances_count = + hp_get_instance_count(HP_WMI_BIOS_ENUMERATION_GUID); + + bioscfg_drv.enumeration_data = kcalloc(bioscfg_drv.enumeration_instances_count, + sizeof(*bioscfg_drv.enumeration_data), GFP_KERNEL); + if (!bioscfg_drv.enumeration_data) { + bioscfg_drv.enumeration_instances_count = 0; + return -ENOMEM; + } + return 0; +} + +/* Expected Values types associated with each element */ +static const acpi_object_type expected_enum_types[] = { + [NAME] = ACPI_TYPE_STRING, + [VALUE] = ACPI_TYPE_STRING, + [PATH] = ACPI_TYPE_STRING, + [IS_READONLY] = ACPI_TYPE_INTEGER, + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER, + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER, + [SEQUENCE] = ACPI_TYPE_INTEGER, + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER, + [PREREQUISITES] = ACPI_TYPE_STRING, + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER, + [ENUM_CURRENT_VALUE] = ACPI_TYPE_STRING, + [ENUM_SIZE] = ACPI_TYPE_INTEGER, + [ENUM_POSSIBLE_VALUES] = ACPI_TYPE_STRING, +}; + +static int hp_populate_enumeration_elements_from_package(union acpi_object *enum_obj, + int enum_obj_count, + int instance_id) +{ + char *str_value = NULL; + int value_len; + u32 size = 0; + u32 int_value = 0; + int elem = 0; + int reqs; + int pos_values; + int ret; + int eloc; + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + + for (elem = 1, eloc = 1; elem < enum_obj_count; elem++, eloc++) { + /* ONLY look at the first ENUM_ELEM_CNT elements */ + if (eloc == ENUM_ELEM_CNT) + goto exit_enumeration_package; + + switch (enum_obj[elem].type) { + case ACPI_TYPE_STRING: + if (PREREQUISITES != elem && ENUM_POSSIBLE_VALUES != elem) { + ret = hp_convert_hexstr_to_str(enum_obj[elem].string.pointer, + enum_obj[elem].string.length, + &str_value, &value_len); + if (ret) + return -EINVAL; + } + break; + case ACPI_TYPE_INTEGER: + int_value = (u32)enum_obj[elem].integer.value; + break; + default: + pr_warn("Unsupported object type [%d]\n", enum_obj[elem].type); + continue; + } + + /* Check that both expected and read object type match */ + if (expected_enum_types[eloc] != enum_obj[elem].type) { + pr_err("Error expected type %d for elem %d, but got type %d instead\n", + expected_enum_types[eloc], elem, enum_obj[elem].type); + kfree(str_value); + return -EIO; + } + + /* Assign appropriate element value to corresponding field */ + switch (eloc) { + case NAME: + case VALUE: + break; + case PATH: + strscpy(enum_data->common.path, str_value, + sizeof(enum_data->common.path)); + break; + case IS_READONLY: + enum_data->common.is_readonly = int_value; + break; + case DISPLAY_IN_UI: + enum_data->common.display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + enum_data->common.requires_physical_presence = int_value; + break; + case SEQUENCE: + enum_data->common.sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + enum_data->common.prerequisites_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (int_value == 0) + eloc++; + break; + + case PREREQUISITES: + size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); + for (reqs = 0; reqs < size; reqs++) { + if (elem >= enum_obj_count) { + pr_err("Error enum-objects package is too small\n"); + return -EINVAL; + } + + ret = hp_convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer, + enum_obj[elem + reqs].string.length, + &str_value, &value_len); + + if (ret) + return -EINVAL; + + strscpy(enum_data->common.prerequisites[reqs], + str_value, + sizeof(enum_data->common.prerequisites[reqs])); + + kfree(str_value); + str_value = NULL; + } + break; + + case SECURITY_LEVEL: + enum_data->common.security_level = int_value; + break; + + case ENUM_CURRENT_VALUE: + strscpy(enum_data->current_value, + str_value, sizeof(enum_data->current_value)); + break; + case ENUM_SIZE: + if (int_value > MAX_VALUES_SIZE) { + pr_warn("Possible number values size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_VALUES_SIZE; + } + enum_data->possible_values_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. POSSIBLE_VALUES + * object is omitted by BIOS when the size is zero. + */ + if (int_value == 0) + eloc++; + break; + + case ENUM_POSSIBLE_VALUES: + size = enum_data->possible_values_size; + + for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE; + pos_values++) { + if (elem >= enum_obj_count) { + pr_err("Error enum-objects package is too small\n"); + return -EINVAL; + } + + ret = hp_convert_hexstr_to_str(enum_obj[elem + pos_values].string.pointer, + enum_obj[elem + pos_values].string.length, + &str_value, &value_len); + + if (ret) + return -EINVAL; + + /* + * ignore strings when possible values size + * is greater than MAX_VALUES_SIZE + */ + if (size < MAX_VALUES_SIZE) + strscpy(enum_data->possible_values[pos_values], + str_value, + sizeof(enum_data->possible_values[pos_values])); + + kfree(str_value); + str_value = NULL; + } + break; + default: + pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem); + break; + } + + kfree(str_value); + str_value = NULL; + } + +exit_enumeration_package: + kfree(str_value); + return 0; +} + +/** + * hp_populate_enumeration_package_data() - + * Populate all properties of an instance under enumeration attribute + * + * @enum_obj: ACPI object with enumeration data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int instance_id, + struct kobject *attr_name_kobj) +{ + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + + enum_data->attr_name_kobj = attr_name_kobj; + + hp_populate_enumeration_elements_from_package(enum_obj, + enum_obj->package.count, + instance_id); + hp_update_attribute_permissions(enum_data->common.is_readonly, + &enumeration_current_val); + /* + * Several attributes have names such "MONDAY". Friendly + * user nane is generated to make the name more descriptive + */ + hp_friendly_user_name_update(enum_data->common.path, + attr_name_kobj->name, + enum_data->common.display_name, + sizeof(enum_data->common.display_name)); + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); +} + +static int hp_populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size, + int instance_id) +{ + int values; + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + int ret = 0; + + /* + * Only data relevant to this driver and its functionality is + * read. BIOS defines the order in which each * element is + * read. Element 0 data is not relevant to this + * driver hence it is ignored. For clarity, all element names + * (DISPLAY_IN_UI) which defines the order in which is read + * and the name matches the variable where the data is stored. + * + * In earlier implementation, reported errors were ignored + * causing the data to remain uninitialized. It is not + * possible to determine if data read from BIOS is valid or + * not. It is for this reason functions may return a error + * without validating the data itself. + */ + + // VALUE: + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, enum_data->current_value, + sizeof(enum_data->current_value)); + if (ret < 0) + goto buffer_exit; + + // COMMON: + ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &enum_data->common); + if (ret < 0) + goto buffer_exit; + + // ENUM_CURRENT_VALUE: + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, + enum_data->current_value, + sizeof(enum_data->current_value)); + if (ret < 0) + goto buffer_exit; + + // ENUM_SIZE: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &enum_data->possible_values_size); + + if (enum_data->possible_values_size > MAX_VALUES_SIZE) { + /* Report a message and limit possible values size to maximum value */ + pr_warn("Enum Possible size value exceeded the maximum number of elements supported or data may be malformed\n"); + enum_data->possible_values_size = MAX_VALUES_SIZE; + } + + // ENUM_POSSIBLE_VALUES: + for (values = 0; values < enum_data->possible_values_size; values++) { + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, + enum_data->possible_values[values], + sizeof(enum_data->possible_values[values])); + if (ret < 0) + break; + } + +buffer_exit: + return ret; +} + +/** + * hp_populate_enumeration_buffer_data() - + * Populate all properties of an instance under enumeration attribute + * + * @buffer_ptr: Buffer pointer + * @buffer_size: Buffer size + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj) +{ + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + int ret = 0; + + enum_data->attr_name_kobj = attr_name_kobj; + + /* Populate enumeration elements */ + ret = hp_populate_enumeration_elements_from_buffer(buffer_ptr, buffer_size, + instance_id); + if (ret < 0) + return ret; + + hp_update_attribute_permissions(enum_data->common.is_readonly, + &enumeration_current_val); + /* + * Several attributes have names such "MONDAY". A Friendlier + * user nane is generated to make the name more descriptive + */ + hp_friendly_user_name_update(enum_data->common.path, + attr_name_kobj->name, + enum_data->common.display_name, + sizeof(enum_data->common.display_name)); + + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); +} + +/** + * hp_exit_enumeration_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void hp_exit_enumeration_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < bioscfg_drv.enumeration_instances_count; + instance_id++) { + struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id]; + struct kobject *attr_name_kobj = enum_data->attr_name_kobj; + + if (attr_name_kobj) + sysfs_remove_group(attr_name_kobj, &enumeration_attr_group); + } + bioscfg_drv.enumeration_instances_count = 0; + + kfree(bioscfg_drv.enumeration_data); + bioscfg_drv.enumeration_data = NULL; +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c new file mode 100644 index 0000000000..86b7ac63fe --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to integer type attributes under + * BIOS Enumeration GUID for use with hp-bioscfg driver. + * + * Copyright (c) 2022 Hewlett-Packard Inc. + */ + +#include "bioscfg.h" + +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); + + if (instance_id < 0) + return -EIO; + + return sysfs_emit(buf, "%d\n", + bioscfg_drv.integer_data[instance_id].current_value); +} + +/** + * validate_integer_input() - + * Validate input of current_value against lower and upper bound + * + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_integer_input(int instance_id, char *buf) +{ + int in_val; + int ret; + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + + /* BIOS treats it as a read only attribute */ + if (integer_data->common.is_readonly) + return -EIO; + + ret = kstrtoint(buf, 10, &in_val); + if (ret < 0) + return ret; + + if (in_val < integer_data->lower_bound || + in_val > integer_data->upper_bound) + return -ERANGE; + + return 0; +} + +static void update_integer_value(int instance_id, char *attr_value) +{ + int in_val; + int ret; + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + + ret = kstrtoint(attr_value, 10, &in_val); + if (ret == 0) + integer_data->current_value = in_val; + else + pr_warn("Invalid integer value found: %s\n", attr_value); +} + +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer); +static struct kobj_attribute integer_display_name = + __ATTR_RO(display_name); + +ATTRIBUTE_PROPERTY_STORE(current_value, integer); +static struct kobj_attribute integer_current_val = + __ATTR_RW_MODE(current_value, 0644); + +ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer); +static struct kobj_attribute integer_lower_bound = + __ATTR_RO(lower_bound); + +ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer); +static struct kobj_attribute integer_upper_bound = + __ATTR_RO(upper_bound); + +ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer); +static struct kobj_attribute integer_scalar_increment = + __ATTR_RO(scalar_increment); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +static struct kobj_attribute integer_type = + __ATTR_RO(type); + +static struct attribute *integer_attrs[] = { + &common_display_langcode.attr, + &integer_display_name.attr, + &integer_current_val.attr, + &integer_lower_bound.attr, + &integer_upper_bound.attr, + &integer_scalar_increment.attr, + &integer_type.attr, + NULL +}; + +static const struct attribute_group integer_attr_group = { + .attrs = integer_attrs, +}; + +int hp_alloc_integer_data(void) +{ + bioscfg_drv.integer_instances_count = hp_get_instance_count(HP_WMI_BIOS_INTEGER_GUID); + bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count, + sizeof(*bioscfg_drv.integer_data), GFP_KERNEL); + + if (!bioscfg_drv.integer_data) { + bioscfg_drv.integer_instances_count = 0; + return -ENOMEM; + } + return 0; +} + +/* Expected Values types associated with each element */ +static const acpi_object_type expected_integer_types[] = { + [NAME] = ACPI_TYPE_STRING, + [VALUE] = ACPI_TYPE_STRING, + [PATH] = ACPI_TYPE_STRING, + [IS_READONLY] = ACPI_TYPE_INTEGER, + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER, + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER, + [SEQUENCE] = ACPI_TYPE_INTEGER, + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER, + [PREREQUISITES] = ACPI_TYPE_STRING, + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER, + [INT_LOWER_BOUND] = ACPI_TYPE_INTEGER, + [INT_UPPER_BOUND] = ACPI_TYPE_INTEGER, + [INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER, +}; + +static int hp_populate_integer_elements_from_package(union acpi_object *integer_obj, + int integer_obj_count, + int instance_id) +{ + char *str_value = NULL; + int value_len; + int ret; + u32 int_value = 0; + int elem; + int reqs; + int eloc; + int size; + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + + if (!integer_obj) + return -EINVAL; + + for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) { + /* ONLY look at the first INTEGER_ELEM_CNT elements */ + if (eloc == INT_ELEM_CNT) + goto exit_integer_package; + + switch (integer_obj[elem].type) { + case ACPI_TYPE_STRING: + if (elem != PREREQUISITES) { + ret = hp_convert_hexstr_to_str(integer_obj[elem].string.pointer, + integer_obj[elem].string.length, + &str_value, &value_len); + if (ret) + continue; + } + break; + case ACPI_TYPE_INTEGER: + int_value = (u32)integer_obj[elem].integer.value; + break; + default: + pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type); + continue; + } + /* Check that both expected and read object type match */ + if (expected_integer_types[eloc] != integer_obj[elem].type) { + pr_err("Error expected type %d for elem %d, but got type %d instead\n", + expected_integer_types[eloc], elem, integer_obj[elem].type); + kfree(str_value); + return -EIO; + } + /* Assign appropriate element value to corresponding field*/ + switch (eloc) { + case VALUE: + ret = kstrtoint(str_value, 10, &int_value); + if (ret) + continue; + + integer_data->current_value = int_value; + break; + case PATH: + strscpy(integer_data->common.path, str_value, + sizeof(integer_data->common.path)); + break; + case IS_READONLY: + integer_data->common.is_readonly = int_value; + break; + case DISPLAY_IN_UI: + integer_data->common.display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + integer_data->common.requires_physical_presence = int_value; + break; + case SEQUENCE: + integer_data->common.sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + integer_data->common.prerequisites_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (integer_data->common.prerequisites_size == 0) + eloc++; + break; + case PREREQUISITES: + size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); + + for (reqs = 0; reqs < size; reqs++) { + if (elem >= integer_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + + ret = hp_convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer, + integer_obj[elem + reqs].string.length, + &str_value, &value_len); + + if (ret) + continue; + + strscpy(integer_data->common.prerequisites[reqs], + str_value, + sizeof(integer_data->common.prerequisites[reqs])); + kfree(str_value); + str_value = NULL; + } + break; + + case SECURITY_LEVEL: + integer_data->common.security_level = int_value; + break; + case INT_LOWER_BOUND: + integer_data->lower_bound = int_value; + break; + case INT_UPPER_BOUND: + integer_data->upper_bound = int_value; + break; + case INT_SCALAR_INCREMENT: + integer_data->scalar_increment = int_value; + break; + default: + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem); + break; + } + + kfree(str_value); + str_value = NULL; + } +exit_integer_package: + kfree(str_value); + return 0; +} + +/** + * hp_populate_integer_package_data() - + * Populate all properties of an instance under integer attribute + * + * @integer_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_integer_package_data(union acpi_object *integer_obj, + int instance_id, + struct kobject *attr_name_kobj) +{ + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + + integer_data->attr_name_kobj = attr_name_kobj; + hp_populate_integer_elements_from_package(integer_obj, + integer_obj->package.count, + instance_id); + hp_update_attribute_permissions(integer_data->common.is_readonly, + &integer_current_val); + hp_friendly_user_name_update(integer_data->common.path, + attr_name_kobj->name, + integer_data->common.display_name, + sizeof(integer_data->common.display_name)); + return sysfs_create_group(attr_name_kobj, &integer_attr_group); +} + +static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size, + int instance_id) +{ + char *dst = NULL; + int dst_size = *buffer_size / sizeof(u16); + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + int ret = 0; + + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL); + if (!dst) + return -ENOMEM; + + /* + * Only data relevant to this driver and its functionality is + * read. BIOS defines the order in which each * element is + * read. Element 0 data is not relevant to this + * driver hence it is ignored. For clarity, all element names + * (DISPLAY_IN_UI) which defines the order in which is read + * and the name matches the variable where the data is stored. + * + * In earlier implementation, reported errors were ignored + * causing the data to remain uninitialized. It is not + * possible to determine if data read from BIOS is valid or + * not. It is for this reason functions may return a error + * without validating the data itself. + */ + + // VALUE: + integer_data->current_value = 0; + + hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size); + ret = kstrtoint(dst, 10, &integer_data->current_value); + if (ret) + pr_warn("Unable to convert string to integer: %s\n", dst); + kfree(dst); + + // COMMON: + ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &integer_data->common); + if (ret < 0) + goto buffer_exit; + + // INT_LOWER_BOUND: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &integer_data->lower_bound); + if (ret < 0) + goto buffer_exit; + + // INT_UPPER_BOUND: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &integer_data->upper_bound); + if (ret < 0) + goto buffer_exit; + + // INT_SCALAR_INCREMENT: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &integer_data->scalar_increment); + +buffer_exit: + return ret; +} + +/** + * hp_populate_integer_buffer_data() - + * Populate all properties of an instance under integer attribute + * + * @buffer_ptr: Buffer pointer + * @buffer_size: Buffer size + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id, + struct kobject *attr_name_kobj) +{ + struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; + int ret = 0; + + integer_data->attr_name_kobj = attr_name_kobj; + + /* Populate integer elements */ + ret = hp_populate_integer_elements_from_buffer(buffer_ptr, buffer_size, + instance_id); + if (ret < 0) + return ret; + + hp_update_attribute_permissions(integer_data->common.is_readonly, + &integer_current_val); + hp_friendly_user_name_update(integer_data->common.path, + attr_name_kobj->name, + integer_data->common.display_name, + sizeof(integer_data->common.display_name)); + + return sysfs_create_group(attr_name_kobj, &integer_attr_group); +} + +/** + * hp_exit_integer_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void hp_exit_integer_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count; + instance_id++) { + struct kobject *attr_name_kobj = + bioscfg_drv.integer_data[instance_id].attr_name_kobj; + + if (attr_name_kobj) + sysfs_remove_group(attr_name_kobj, &integer_attr_group); + } + bioscfg_drv.integer_instances_count = 0; + + kfree(bioscfg_drv.integer_data); + bioscfg_drv.integer_data = NULL; +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c new file mode 100644 index 0000000000..1ff09dfb7d --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to ordered list type attributes under + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver. + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" + +GET_INSTANCE_ID(ordered_list); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_ordered_list_instance_id(kobj); + + if (instance_id < 0) + return -EIO; + + return sysfs_emit(buf, "%s\n", + bioscfg_drv.ordered_list_data[instance_id].current_value); +} + +static int replace_char_str(u8 *buffer, char *repl_char, char *repl_with) +{ + char *src = buffer; + int buflen = strlen(buffer); + int item; + + if (buflen < 1) + return -EINVAL; + + for (item = 0; item < buflen; item++) + if (src[item] == *repl_char) + src[item] = *repl_with; + + return 0; +} + +/** + * validate_ordered_list_input() - + * Validate input of current_value against possible values + * + * @instance: The instance on which input is validated + * @buf: Input value + */ +static int validate_ordered_list_input(int instance, char *buf) +{ + /* validation is done by BIOS. This validation function will + * convert semicolon to commas. BIOS uses commas as + * separators when reporting ordered-list values. + */ + return replace_char_str(buf, SEMICOLON_SEP, COMMA_SEP); +} + +static void update_ordered_list_value(int instance, char *attr_value) +{ + struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance]; + + strscpy(ordered_list_data->current_value, + attr_value, + sizeof(ordered_list_data->current_value)); +} + +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list); +static struct kobj_attribute ordered_list_display_name = + __ATTR_RO(display_name); + +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list); +static struct kobj_attribute ordered_list_current_val = + __ATTR_RW_MODE(current_value, 0644); + +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list, SEMICOLON_SEP); +static struct kobj_attribute ordered_list_elements_val = + __ATTR_RO(elements); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "ordered-list\n"); +} + +static struct kobj_attribute ordered_list_type = + __ATTR_RO(type); + +static struct attribute *ordered_list_attrs[] = { + &common_display_langcode.attr, + &ordered_list_display_name.attr, + &ordered_list_current_val.attr, + &ordered_list_elements_val.attr, + &ordered_list_type.attr, + NULL +}; + +static const struct attribute_group ordered_list_attr_group = { + .attrs = ordered_list_attrs, +}; + +int hp_alloc_ordered_list_data(void) +{ + bioscfg_drv.ordered_list_instances_count = + hp_get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID); + bioscfg_drv.ordered_list_data = kcalloc(bioscfg_drv.ordered_list_instances_count, + sizeof(*bioscfg_drv.ordered_list_data), + GFP_KERNEL); + if (!bioscfg_drv.ordered_list_data) { + bioscfg_drv.ordered_list_instances_count = 0; + return -ENOMEM; + } + return 0; +} + +/* Expected Values types associated with each element */ +static const acpi_object_type expected_order_types[] = { + [NAME] = ACPI_TYPE_STRING, + [VALUE] = ACPI_TYPE_STRING, + [PATH] = ACPI_TYPE_STRING, + [IS_READONLY] = ACPI_TYPE_INTEGER, + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER, + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER, + [SEQUENCE] = ACPI_TYPE_INTEGER, + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER, + [PREREQUISITES] = ACPI_TYPE_STRING, + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER, + [ORD_LIST_SIZE] = ACPI_TYPE_INTEGER, + [ORD_LIST_ELEMENTS] = ACPI_TYPE_STRING, +}; + +static int hp_populate_ordered_list_elements_from_package(union acpi_object *order_obj, + int order_obj_count, + int instance_id) +{ + char *str_value = NULL; + int value_len = 0; + int ret; + u32 size; + u32 int_value = 0; + int elem; + int olist_elem; + int reqs; + int eloc; + char *tmpstr = NULL; + char *part_tmp = NULL; + int tmp_len = 0; + char *part = NULL; + struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; + + if (!order_obj) + return -EINVAL; + + for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) { + + switch (order_obj[elem].type) { + case ACPI_TYPE_STRING: + if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) { + ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer, + order_obj[elem].string.length, + &str_value, &value_len); + if (ret) + continue; + } + break; + case ACPI_TYPE_INTEGER: + int_value = (u32)order_obj[elem].integer.value; + break; + default: + pr_warn("Unsupported object type [%d]\n", order_obj[elem].type); + continue; + } + + /* Check that both expected and read object type match */ + if (expected_order_types[eloc] != order_obj[elem].type) { + pr_err("Error expected type %d for elem %d, but got type %d instead\n", + expected_order_types[eloc], elem, order_obj[elem].type); + kfree(str_value); + return -EIO; + } + + /* Assign appropriate element value to corresponding field*/ + switch (eloc) { + case VALUE: + strscpy(ordered_list_data->current_value, + str_value, sizeof(ordered_list_data->current_value)); + replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP); + break; + case PATH: + strscpy(ordered_list_data->common.path, str_value, + sizeof(ordered_list_data->common.path)); + break; + case IS_READONLY: + ordered_list_data->common.is_readonly = int_value; + break; + case DISPLAY_IN_UI: + ordered_list_data->common.display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + ordered_list_data->common.requires_physical_presence = int_value; + break; + case SEQUENCE: + ordered_list_data->common.sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + ordered_list_data->common.prerequisites_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (int_value == 0) + eloc++; + break; + case PREREQUISITES: + size = min_t(u32, ordered_list_data->common.prerequisites_size, + MAX_PREREQUISITES_SIZE); + for (reqs = 0; reqs < size; reqs++) { + ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer, + order_obj[elem + reqs].string.length, + &str_value, &value_len); + + if (ret) + continue; + + strscpy(ordered_list_data->common.prerequisites[reqs], + str_value, + sizeof(ordered_list_data->common.prerequisites[reqs])); + + kfree(str_value); + str_value = NULL; + } + break; + + case SECURITY_LEVEL: + ordered_list_data->common.security_level = int_value; + break; + + case ORD_LIST_SIZE: + if (int_value > MAX_ELEMENTS_SIZE) { + pr_warn("Order List size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_ELEMENTS_SIZE; + } + ordered_list_data->elements_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. ORD_LIST_ELEMENTS + * object is omitted by BIOS when the size is + * zero. + */ + if (int_value == 0) + eloc++; + break; + case ORD_LIST_ELEMENTS: + + /* + * Ordered list data is stored in hex and comma separated format + * Convert the data and split it to show each element + */ + ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len); + if (ret) + goto exit_list; + + part_tmp = tmpstr; + part = strsep(&part_tmp, COMMA_SEP); + + for (olist_elem = 0; olist_elem < MAX_ELEMENTS_SIZE && part; olist_elem++) { + strscpy(ordered_list_data->elements[olist_elem], + part, + sizeof(ordered_list_data->elements[olist_elem])); + part = strsep(&part_tmp, COMMA_SEP); + } + ordered_list_data->elements_size = olist_elem; + + kfree(str_value); + str_value = NULL; + break; + default: + pr_warn("Invalid element: %d found in Ordered_List attribute or data may be malformed\n", elem); + break; + } + kfree(tmpstr); + tmpstr = NULL; + kfree(str_value); + str_value = NULL; + } + +exit_list: + kfree(tmpstr); + kfree(str_value); + return 0; +} + +/** + * hp_populate_ordered_list_package_data() - + * Populate all properties of an instance under ordered_list attribute + * + * @order_obj: ACPI object with ordered_list data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; + + ordered_list_data->attr_name_kobj = attr_name_kobj; + + hp_populate_ordered_list_elements_from_package(order_obj, + order_obj->package.count, + instance_id); + hp_update_attribute_permissions(ordered_list_data->common.is_readonly, + &ordered_list_current_val); + hp_friendly_user_name_update(ordered_list_data->common.path, + attr_name_kobj->name, + ordered_list_data->common.display_name, + sizeof(ordered_list_data->common.display_name)); + return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group); +} + +static int hp_populate_ordered_list_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size, + int instance_id) +{ + int values; + struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; + int ret = 0; + + /* + * Only data relevant to this driver and its functionality is + * read. BIOS defines the order in which each * element is + * read. Element 0 data is not relevant to this + * driver hence it is ignored. For clarity, all element names + * (DISPLAY_IN_UI) which defines the order in which is read + * and the name matches the variable where the data is stored. + * + * In earlier implementation, reported errors were ignored + * causing the data to remain uninitialized. It is not + * possible to determine if data read from BIOS is valid or + * not. It is for this reason functions may return a error + * without validating the data itself. + */ + + // VALUE: + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, ordered_list_data->current_value, + sizeof(ordered_list_data->current_value)); + if (ret < 0) + goto buffer_exit; + + replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP); + + // COMMON: + ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, + &ordered_list_data->common); + if (ret < 0) + goto buffer_exit; + + // ORD_LIST_SIZE: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &ordered_list_data->elements_size); + + if (ordered_list_data->elements_size > MAX_ELEMENTS_SIZE) { + /* Report a message and limit elements size to maximum value */ + pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n"); + ordered_list_data->elements_size = MAX_ELEMENTS_SIZE; + } + + // ORD_LIST_ELEMENTS: + for (values = 0; values < ordered_list_data->elements_size; values++) { + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, + ordered_list_data->elements[values], + sizeof(ordered_list_data->elements[values])); + if (ret < 0) + break; + } + +buffer_exit: + return ret; +} + +/** + * hp_populate_ordered_list_buffer_data() - Populate all properties of an + * instance under ordered list attribute + * + * @buffer_ptr: Buffer pointer + * @buffer_size: Buffer size + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id, + struct kobject *attr_name_kobj) +{ + struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; + int ret = 0; + + ordered_list_data->attr_name_kobj = attr_name_kobj; + + /* Populate ordered list elements */ + ret = hp_populate_ordered_list_elements_from_buffer(buffer_ptr, buffer_size, + instance_id); + if (ret < 0) + return ret; + + hp_update_attribute_permissions(ordered_list_data->common.is_readonly, + &ordered_list_current_val); + hp_friendly_user_name_update(ordered_list_data->common.path, + attr_name_kobj->name, + ordered_list_data->common.display_name, + sizeof(ordered_list_data->common.display_name)); + + return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group); +} + +/** + * hp_exit_ordered_list_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void hp_exit_ordered_list_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < bioscfg_drv.ordered_list_instances_count; + instance_id++) { + struct kobject *attr_name_kobj = + bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj; + + if (attr_name_kobj) + sysfs_remove_group(attr_name_kobj, + &ordered_list_attr_group); + } + bioscfg_drv.ordered_list_instances_count = 0; + + kfree(bioscfg_drv.ordered_list_data); + bioscfg_drv.ordered_list_data = NULL; +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c new file mode 100644 index 0000000000..03d0188804 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to password object type attributes under + * BIOS PASSWORD for use with hp-bioscfg driver. + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" +#include <asm-generic/posix_types.h> + +GET_INSTANCE_ID(password); +/* + * Clear all passwords copied to memory for a particular + * authentication instance + */ +static int clear_passwords(const int instance) +{ + struct password_data *password_data = &bioscfg_drv.password_data[instance]; + + if (!password_data->is_enabled) + return 0; + + memset(password_data->current_password, + 0, sizeof(password_data->current_password)); + memset(password_data->new_password, + 0, sizeof(password_data->new_password)); + + return 0; +} + +/* + * Clear all credentials copied to memory for both Power-ON and Setup + * BIOS instances + */ +int hp_clear_all_credentials(void) +{ + int count = bioscfg_drv.password_instances_count; + int instance; + + /* clear all passwords */ + for (instance = 0; instance < count; instance++) + clear_passwords(instance); + + /* clear auth_token */ + kfree(bioscfg_drv.spm_data.auth_token); + bioscfg_drv.spm_data.auth_token = NULL; + + return 0; +} + +int hp_get_password_instance_for_type(const char *name) +{ + int count = bioscfg_drv.password_instances_count; + int instance; + + for (instance = 0; instance < count; instance++) + if (!strcmp(bioscfg_drv.password_data[instance].common.display_name, name)) + return instance; + + return -EINVAL; +} + +static int validate_password_input(int instance_id, const char *buf) +{ + int length; + struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; + + length = strlen(buf); + if (buf[length - 1] == '\n') + length--; + + if (length > MAX_PASSWD_SIZE) + return INVALID_BIOS_AUTH; + + if (password_data->min_password_length > length || + password_data->max_password_length < length) + return INVALID_BIOS_AUTH; + return SUCCESS; +} + +ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password); +static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled); + +static int store_password_instance(struct kobject *kobj, const char *buf, + size_t count, bool is_current) +{ + char *buf_cp; + int id, ret = 0; + + buf_cp = kstrdup(buf, GFP_KERNEL); + if (!buf_cp) + return -ENOMEM; + + ret = hp_enforce_single_line_input(buf_cp, count); + if (!ret) { + id = get_password_instance_id(kobj); + + if (id >= 0) + ret = validate_password_input(id, buf_cp); + } + + if (!ret) { + if (is_current) + strscpy(bioscfg_drv.password_data[id].current_password, + buf_cp, + sizeof(bioscfg_drv.password_data[id].current_password)); + else + strscpy(bioscfg_drv.password_data[id].new_password, + buf_cp, + sizeof(bioscfg_drv.password_data[id].new_password)); + } + + kfree(buf_cp); + return ret < 0 ? ret : count; +} + +static ssize_t current_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return store_password_instance(kobj, buf, count, true); +} + +static struct kobj_attribute password_current_password = __ATTR_WO(current_password); + +static ssize_t new_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return store_password_instance(kobj, buf, count, true); +} + +static struct kobj_attribute password_new_password = __ATTR_WO(new_password); + +ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password); +static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length); + +ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password); +static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + if (!strcmp(kobj->name, SETUP_PASSWD)) + return sysfs_emit(buf, "%s\n", BIOS_ADMIN); + + if (!strcmp(kobj->name, POWER_ON_PASSWD)) + return sysfs_emit(buf, "%s\n", POWER_ON); + + return -EIO; +} + +static struct kobj_attribute password_role = __ATTR_RO(role); + +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int i = get_password_instance_id(kobj); + + if (i < 0) + return i; + + if (bioscfg_drv.password_data[i].mechanism != PASSWORD) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES); +} + +static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism); + +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password, SEMICOLON_SEP); +static struct kobj_attribute password_encodings_val = __ATTR_RO(encodings); + +static struct attribute *password_attrs[] = { + &password_is_password_set.attr, + &password_min_password_length.attr, + &password_max_password_length.attr, + &password_current_password.attr, + &password_new_password.attr, + &password_role.attr, + &password_mechanism.attr, + &password_encodings_val.attr, + NULL +}; + +static const struct attribute_group password_attr_group = { + .attrs = password_attrs +}; + +int hp_alloc_password_data(void) +{ + bioscfg_drv.password_instances_count = hp_get_instance_count(HP_WMI_BIOS_PASSWORD_GUID); + bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count, + sizeof(*bioscfg_drv.password_data), GFP_KERNEL); + if (!bioscfg_drv.password_data) { + bioscfg_drv.password_instances_count = 0; + return -ENOMEM; + } + + return 0; +} + +/* Expected Values types associated with each element */ +static const acpi_object_type expected_password_types[] = { + [NAME] = ACPI_TYPE_STRING, + [VALUE] = ACPI_TYPE_STRING, + [PATH] = ACPI_TYPE_STRING, + [IS_READONLY] = ACPI_TYPE_INTEGER, + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER, + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER, + [SEQUENCE] = ACPI_TYPE_INTEGER, + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER, + [PREREQUISITES] = ACPI_TYPE_STRING, + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER, + [PSWD_MIN_LENGTH] = ACPI_TYPE_INTEGER, + [PSWD_MAX_LENGTH] = ACPI_TYPE_INTEGER, + [PSWD_SIZE] = ACPI_TYPE_INTEGER, + [PSWD_ENCODINGS] = ACPI_TYPE_STRING, + [PSWD_IS_SET] = ACPI_TYPE_INTEGER, +}; + +static int hp_populate_password_elements_from_package(union acpi_object *password_obj, + int password_obj_count, + int instance_id) +{ + char *str_value = NULL; + int value_len; + int ret; + u32 size; + u32 int_value = 0; + int elem; + int reqs; + int eloc; + int pos_values; + struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; + + if (!password_obj) + return -EINVAL; + + for (elem = 1, eloc = 1; elem < password_obj_count; elem++, eloc++) { + /* ONLY look at the first PASSWORD_ELEM_CNT elements */ + if (eloc == PSWD_ELEM_CNT) + goto exit_package; + + switch (password_obj[elem].type) { + case ACPI_TYPE_STRING: + if (PREREQUISITES != elem && PSWD_ENCODINGS != elem) { + ret = hp_convert_hexstr_to_str(password_obj[elem].string.pointer, + password_obj[elem].string.length, + &str_value, &value_len); + if (ret) + continue; + } + break; + case ACPI_TYPE_INTEGER: + int_value = (u32)password_obj[elem].integer.value; + break; + default: + pr_warn("Unsupported object type [%d]\n", password_obj[elem].type); + continue; + } + + /* Check that both expected and read object type match */ + if (expected_password_types[eloc] != password_obj[elem].type) { + pr_err("Error expected type %d for elem %d, but got type %d instead\n", + expected_password_types[eloc], elem, password_obj[elem].type); + kfree(str_value); + return -EIO; + } + + /* Assign appropriate element value to corresponding field*/ + switch (eloc) { + case VALUE: + break; + case PATH: + strscpy(password_data->common.path, str_value, + sizeof(password_data->common.path)); + break; + case IS_READONLY: + password_data->common.is_readonly = int_value; + break; + case DISPLAY_IN_UI: + password_data->common.display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + password_data->common.requires_physical_presence = int_value; + break; + case SEQUENCE: + password_data->common.sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + password_data->common.prerequisites_size = int_value; + + /* This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (int_value == 0) + eloc++; + break; + case PREREQUISITES: + size = min_t(u32, password_data->common.prerequisites_size, + MAX_PREREQUISITES_SIZE); + + for (reqs = 0; reqs < size; reqs++) { + ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer, + password_obj[elem + reqs].string.length, + &str_value, &value_len); + + if (ret) + break; + + strscpy(password_data->common.prerequisites[reqs], + str_value, + sizeof(password_data->common.prerequisites[reqs])); + + kfree(str_value); + str_value = NULL; + + } + break; + case SECURITY_LEVEL: + password_data->common.security_level = int_value; + break; + case PSWD_MIN_LENGTH: + password_data->min_password_length = int_value; + break; + case PSWD_MAX_LENGTH: + password_data->max_password_length = int_value; + break; + case PSWD_SIZE: + + if (int_value > MAX_ENCODINGS_SIZE) { + pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_ENCODINGS_SIZE; + } + password_data->encodings_size = int_value; + + /* This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PSWD_ENCODINGS + * object is omitted by BIOS when the size is + * zero. + */ + if (int_value == 0) + eloc++; + break; + case PSWD_ENCODINGS: + size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); + for (pos_values = 0; pos_values < size; pos_values++) { + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, + password_obj[elem + pos_values].string.length, + &str_value, &value_len); + if (ret) + break; + + strscpy(password_data->encodings[pos_values], + str_value, + sizeof(password_data->encodings[pos_values])); + kfree(str_value); + str_value = NULL; + + } + break; + case PSWD_IS_SET: + password_data->is_enabled = int_value; + break; + default: + pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem); + break; + } + + kfree(str_value); + str_value = NULL; + } + +exit_package: + kfree(str_value); + return 0; +} + +/** + * hp_populate_password_package_data() + * Populate all properties for an instance under password attribute + * + * @password_obj: ACPI object with password data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; + + password_data->attr_name_kobj = attr_name_kobj; + + hp_populate_password_elements_from_package(password_obj, + password_obj->package.count, + instance_id); + + hp_friendly_user_name_update(password_data->common.path, + attr_name_kobj->name, + password_data->common.display_name, + sizeof(password_data->common.display_name)); + + if (!strcmp(attr_name_kobj->name, SETUP_PASSWD)) + return sysfs_create_group(attr_name_kobj, &password_attr_group); + + return sysfs_create_group(attr_name_kobj, &password_attr_group); +} + +static int hp_populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size, + int instance_id) +{ + int values; + int isreadonly; + struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; + int ret = 0; + + /* + * Only data relevant to this driver and its functionality is + * read. BIOS defines the order in which each * element is + * read. Element 0 data is not relevant to this + * driver hence it is ignored. For clarity, all element names + * (DISPLAY_IN_UI) which defines the order in which is read + * and the name matches the variable where the data is stored. + * + * In earlier implementation, reported errors were ignored + * causing the data to remain uninitialized. It is not + * possible to determine if data read from BIOS is valid or + * not. It is for this reason functions may return a error + * without validating the data itself. + */ + + // VALUE: + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, password_data->current_password, + sizeof(password_data->current_password)); + if (ret < 0) + goto buffer_exit; + + // COMMON: + ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, + &password_data->common); + if (ret < 0) + goto buffer_exit; + + // PSWD_MIN_LENGTH: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &password_data->min_password_length); + if (ret < 0) + goto buffer_exit; + + // PSWD_MAX_LENGTH: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &password_data->max_password_length); + if (ret < 0) + goto buffer_exit; + + // PSWD_SIZE: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &password_data->encodings_size); + if (ret < 0) + goto buffer_exit; + + if (password_data->encodings_size > MAX_ENCODINGS_SIZE) { + /* Report a message and limit possible values size to maximum value */ + pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n"); + password_data->encodings_size = MAX_ENCODINGS_SIZE; + } + + // PSWD_ENCODINGS: + for (values = 0; values < password_data->encodings_size; values++) { + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, + password_data->encodings[values], + sizeof(password_data->encodings[values])); + if (ret < 0) + break; + } + + // PSWD_IS_SET: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, &isreadonly); + if (ret < 0) + goto buffer_exit; + + password_data->is_enabled = isreadonly ? true : false; + +buffer_exit: + return ret; +} + +/** + * hp_populate_password_buffer_data() + * Populate all properties for an instance under password object attribute + * + * @buffer_ptr: Buffer pointer + * @buffer_size: Buffer size + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id, + struct kobject *attr_name_kobj) +{ + struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; + int ret = 0; + + password_data->attr_name_kobj = attr_name_kobj; + + /* Populate Password attributes */ + ret = hp_populate_password_elements_from_buffer(buffer_ptr, buffer_size, + instance_id); + if (ret < 0) + return ret; + + hp_friendly_user_name_update(password_data->common.path, + attr_name_kobj->name, + password_data->common.display_name, + sizeof(password_data->common.display_name)); + if (!strcmp(attr_name_kobj->name, SETUP_PASSWD)) + return sysfs_create_group(attr_name_kobj, &password_attr_group); + + return sysfs_create_group(attr_name_kobj, &password_attr_group); +} + +/** + * hp_exit_password_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void hp_exit_password_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < bioscfg_drv.password_instances_count; + instance_id++) { + struct kobject *attr_name_kobj = + bioscfg_drv.password_data[instance_id].attr_name_kobj; + + if (attr_name_kobj) { + if (!strcmp(attr_name_kobj->name, SETUP_PASSWD)) + sysfs_remove_group(attr_name_kobj, + &password_attr_group); + else + sysfs_remove_group(attr_name_kobj, + &password_attr_group); + } + } + bioscfg_drv.password_instances_count = 0; + kfree(bioscfg_drv.password_data); + bioscfg_drv.password_data = NULL; +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c new file mode 100644 index 0000000000..86f9023875 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to secure platform management object type + * attributes under BIOS PASSWORD for use with hp-bioscfg driver + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" + +static const char * const spm_state_types[] = { + "not provisioned", + "provisioned", + "provisioning in progress", +}; + +static const char * const spm_mechanism_types[] = { + "not provisioned", + "signing-key", + "endorsement-key", +}; + +struct secureplatform_provisioning_data { + u8 state; + u8 version[2]; + u8 reserved1; + u32 features; + u32 nonce; + u8 reserved2[28]; + u8 sk_mod[MAX_KEY_MOD_SIZE]; + u8 kek_mod[MAX_KEY_MOD_SIZE]; +}; + +/** + * hp_calculate_security_buffer() - determines size of security buffer + * for authentication scheme + * + * @authentication: the authentication content + * + * Currently only supported type is Admin password + */ +size_t hp_calculate_security_buffer(const char *authentication) +{ + size_t size, authlen; + + if (!authentication) + return sizeof(u16) * 2; + + authlen = strlen(authentication); + if (!authlen) + return sizeof(u16) * 2; + + size = sizeof(u16) + authlen * sizeof(u16); + if (!strstarts(authentication, BEAM_PREFIX)) + size += strlen(UTF_PREFIX) * sizeof(u16); + + return size; +} + +/** + * hp_populate_security_buffer() - builds a security buffer for + * authentication scheme + * + * @authbuf: the security buffer + * @authentication: the authentication content + * + * Currently only supported type is PLAIN TEXT + */ +int hp_populate_security_buffer(u16 *authbuf, const char *authentication) +{ + u16 *auth = authbuf; + char *strprefix = NULL; + int ret = 0; + + if (strstarts(authentication, BEAM_PREFIX)) { + /* + * BEAM_PREFIX is append to authbuf when a signature + * is provided and Sure Admin is enabled in BIOS + */ + /* BEAM_PREFIX found, convert part to unicode */ + auth = hp_ascii_to_utf16_unicode(auth, authentication); + if (!auth) + return -EINVAL; + + } else { + /* + * UTF-16 prefix is append to the * authbuf when a BIOS + * admin password is configured in BIOS + */ + + /* append UTF_PREFIX to part and then convert it to unicode */ + strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX, + authentication); + if (!strprefix) + return -ENOMEM; + + auth = hp_ascii_to_utf16_unicode(auth, strprefix); + kfree(strprefix); + + if (!auth) { + ret = -EINVAL; + goto out_buffer; + } + } + +out_buffer: + return ret; +} + +static ssize_t update_spm_state(void) +{ + struct secureplatform_provisioning_data data; + int ret; + + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE, + HPWMI_SECUREPLATFORM, &data, 0, + sizeof(data)); + if (ret < 0) + return ret; + + bioscfg_drv.spm_data.mechanism = data.state; + if (bioscfg_drv.spm_data.mechanism) + bioscfg_drv.spm_data.is_enabled = 1; + + return 0; +} + +static ssize_t statusbin(struct kobject *kobj, + struct kobj_attribute *attr, + struct secureplatform_provisioning_data *buf) +{ + int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE, + HPWMI_SECUREPLATFORM, buf, 0, + sizeof(*buf)); + + if (ret < 0) + return ret; + + return sizeof(struct secureplatform_provisioning_data); +} + +/* + * status_show - Reads SPM status + */ +static ssize_t status_show(struct kobject *kobj, struct kobj_attribute + *attr, char *buf) +{ + int ret, i; + int len = 0; + struct secureplatform_provisioning_data data; + + ret = statusbin(kobj, attr, &data); + if (ret < 0) + return ret; + + /* + * 'status' is a read-only file that returns ASCII text in + * JSON format reporting the status information. + * + * "State": "not provisioned | provisioned | provisioning in progress ", + * "Version": " Major. Minor ", + * "Nonce": <16-bit unsigned number display in base 10>, + * "FeaturesInUse": <16-bit unsigned number display in base 10>, + * "EndorsementKeyMod": "<256 bytes in base64>", + * "SigningKeyMod": "<256 bytes in base64>" + */ + + len += sysfs_emit_at(buf, len, "{\n"); + len += sysfs_emit_at(buf, len, "\t\"State\": \"%s\",\n", + spm_state_types[data.state]); + len += sysfs_emit_at(buf, len, "\t\"Version\": \"%d.%d\"", + data.version[0], data.version[1]); + + /* + * state == 0 means secure platform management + * feature is not configured in BIOS. + */ + if (data.state == 0) { + len += sysfs_emit_at(buf, len, "\n"); + goto status_exit; + } else { + len += sysfs_emit_at(buf, len, ",\n"); + } + + len += sysfs_emit_at(buf, len, "\t\"Nonce\": %d,\n", data.nonce); + len += sysfs_emit_at(buf, len, "\t\"FeaturesInUse\": %d,\n", data.features); + len += sysfs_emit_at(buf, len, "\t\"EndorsementKeyMod\": \""); + + for (i = 255; i >= 0; i--) + len += sysfs_emit_at(buf, len, " %u", data.kek_mod[i]); + + len += sysfs_emit_at(buf, len, " \",\n"); + len += sysfs_emit_at(buf, len, "\t\"SigningKeyMod\": \""); + + for (i = 255; i >= 0; i--) + len += sysfs_emit_at(buf, len, " %u", data.sk_mod[i]); + + /* Return buf contents */ + len += sysfs_emit_at(buf, len, " \"\n"); + +status_exit: + len += sysfs_emit_at(buf, len, "}\n"); + + return len; +} + +static struct kobj_attribute password_spm_status = __ATTR_RO(status); + +ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm); +static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled); + +static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", + spm_mechanism_types[bioscfg_drv.spm_data.mechanism]); +} + +static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism); + +static ssize_t sk_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int length; + + length = count; + if (buf[length - 1] == '\n') + length--; + + /* allocate space and copy current signing key */ + bioscfg_drv.spm_data.signing_key = kmemdup(buf, length, GFP_KERNEL); + if (!bioscfg_drv.spm_data.signing_key) + return -ENOMEM; + + /* submit signing key payload */ + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK, + HPWMI_SECUREPLATFORM, + (void *)bioscfg_drv.spm_data.signing_key, + count, 0); + + if (!ret) { + bioscfg_drv.spm_data.mechanism = SIGNING_KEY; + hp_set_reboot_and_signal_event(); + } + + kfree(bioscfg_drv.spm_data.signing_key); + bioscfg_drv.spm_data.signing_key = NULL; + + return ret ? ret : count; +} + +static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk); + +static ssize_t kek_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int length; + + length = count; + if (buf[length - 1] == '\n') + length--; + + /* allocate space and copy current signing key */ + bioscfg_drv.spm_data.endorsement_key = kmemdup(buf, length, GFP_KERNEL); + if (!bioscfg_drv.spm_data.endorsement_key) { + ret = -ENOMEM; + goto exit_kek; + } + + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK, + HPWMI_SECUREPLATFORM, + (void *)bioscfg_drv.spm_data.endorsement_key, + count, 0); + + if (!ret) { + bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY; + hp_set_reboot_and_signal_event(); + } + +exit_kek: + kfree(bioscfg_drv.spm_data.endorsement_key); + bioscfg_drv.spm_data.endorsement_key = NULL; + + return ret ? ret : count; +} + +static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", BIOS_SPM); +} + +static struct kobj_attribute password_spm_role = __ATTR_RO(role); + +static ssize_t auth_token_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0; + int length; + + length = count; + if (buf[length - 1] == '\n') + length--; + + /* allocate space and copy current auth token */ + bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL); + if (!bioscfg_drv.spm_data.auth_token) { + ret = -ENOMEM; + goto exit_token; + } + + return count; + +exit_token: + kfree(bioscfg_drv.spm_data.auth_token); + bioscfg_drv.spm_data.auth_token = NULL; + + return ret; +} + +static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token); + +static struct attribute *secure_platform_attrs[] = { + &password_spm_is_key_enabled.attr, + &password_spm_signing_key.attr, + &password_spm_endorsement_key.attr, + &password_spm_key_mechanism.attr, + &password_spm_status.attr, + &password_spm_role.attr, + &password_spm_auth_token.attr, + NULL, +}; + +static const struct attribute_group secure_platform_attr_group = { + .attrs = secure_platform_attrs, +}; + +void hp_exit_secure_platform_attributes(void) +{ + /* remove secure platform sysfs entry and free key data*/ + + kfree(bioscfg_drv.spm_data.endorsement_key); + bioscfg_drv.spm_data.endorsement_key = NULL; + + kfree(bioscfg_drv.spm_data.signing_key); + bioscfg_drv.spm_data.signing_key = NULL; + + kfree(bioscfg_drv.spm_data.auth_token); + bioscfg_drv.spm_data.auth_token = NULL; + + if (bioscfg_drv.spm_data.attr_name_kobj) + sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj, + &secure_platform_attr_group); +} + +int hp_populate_secure_platform_data(struct kobject *attr_name_kobj) +{ + /* Populate data for Secure Platform Management */ + bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj; + + strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR, + sizeof(bioscfg_drv.spm_data.attribute_name)); + + bioscfg_drv.spm_data.is_enabled = 0; + bioscfg_drv.spm_data.mechanism = 0; + bioscfg_drv.pending_reboot = false; + update_spm_state(); + + bioscfg_drv.spm_data.endorsement_key = NULL; + bioscfg_drv.spm_data.signing_key = NULL; + bioscfg_drv.spm_data.auth_token = NULL; + + return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group); +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c new file mode 100644 index 0000000000..f0c2007009 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to string type attributes under + * HP_WMI_BIOS_STRING_GUID for use with hp-bioscfg driver. + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" + +#define WMI_STRING_TYPE "HPBIOS_BIOSString" + +GET_INSTANCE_ID(string); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_string_instance_id(kobj); + + if (instance_id < 0) + return -EIO; + + return sysfs_emit(buf, "%s\n", + bioscfg_drv.string_data[instance_id].current_value); +} + +/** + * validate_string_input() - + * Validate input of current_value against min and max lengths + * + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_string_input(int instance_id, const char *buf) +{ + int in_len = strlen(buf); + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + + /* BIOS treats it as a read only attribute */ + if (string_data->common.is_readonly) + return -EIO; + + if (in_len < string_data->min_length || in_len > string_data->max_length) + return -ERANGE; + + return 0; +} + +static void update_string_value(int instance_id, char *attr_value) +{ + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + + /* Write settings to BIOS */ + strscpy(string_data->current_value, attr_value, sizeof(string_data->current_value)); +} + +/* + * ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, string); + * static struct kobj_attribute string_display_langcode = + * __ATTR_RO(display_name_language_code); + */ + +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, string); +static struct kobj_attribute string_display_name = + __ATTR_RO(display_name); + +ATTRIBUTE_PROPERTY_STORE(current_value, string); +static struct kobj_attribute string_current_val = + __ATTR_RW_MODE(current_value, 0644); + +ATTRIBUTE_N_PROPERTY_SHOW(min_length, string); +static struct kobj_attribute string_min_length = + __ATTR_RO(min_length); + +ATTRIBUTE_N_PROPERTY_SHOW(max_length, string); +static struct kobj_attribute string_max_length = + __ATTR_RO(max_length); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "string\n"); +} + +static struct kobj_attribute string_type = + __ATTR_RO(type); + +static struct attribute *string_attrs[] = { + &common_display_langcode.attr, + &string_display_name.attr, + &string_current_val.attr, + &string_min_length.attr, + &string_max_length.attr, + &string_type.attr, + NULL +}; + +static const struct attribute_group string_attr_group = { + .attrs = string_attrs, +}; + +int hp_alloc_string_data(void) +{ + bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID); + bioscfg_drv.string_data = kcalloc(bioscfg_drv.string_instances_count, + sizeof(*bioscfg_drv.string_data), GFP_KERNEL); + if (!bioscfg_drv.string_data) { + bioscfg_drv.string_instances_count = 0; + return -ENOMEM; + } + return 0; +} + +/* Expected Values types associated with each element */ +static const acpi_object_type expected_string_types[] = { + [NAME] = ACPI_TYPE_STRING, + [VALUE] = ACPI_TYPE_STRING, + [PATH] = ACPI_TYPE_STRING, + [IS_READONLY] = ACPI_TYPE_INTEGER, + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER, + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER, + [SEQUENCE] = ACPI_TYPE_INTEGER, + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER, + [PREREQUISITES] = ACPI_TYPE_STRING, + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER, + [STR_MIN_LENGTH] = ACPI_TYPE_INTEGER, + [STR_MAX_LENGTH] = ACPI_TYPE_INTEGER, +}; + +static int hp_populate_string_elements_from_package(union acpi_object *string_obj, + int string_obj_count, + int instance_id) +{ + char *str_value = NULL; + int value_len; + int ret = 0; + u32 int_value = 0; + int elem; + int reqs; + int eloc; + int size; + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + + if (!string_obj) + return -EINVAL; + + for (elem = 1, eloc = 1; elem < string_obj_count; elem++, eloc++) { + /* ONLY look at the first STRING_ELEM_CNT elements */ + if (eloc == STR_ELEM_CNT) + goto exit_string_package; + + switch (string_obj[elem].type) { + case ACPI_TYPE_STRING: + if (elem != PREREQUISITES) { + ret = hp_convert_hexstr_to_str(string_obj[elem].string.pointer, + string_obj[elem].string.length, + &str_value, &value_len); + + if (ret) + continue; + } + break; + case ACPI_TYPE_INTEGER: + int_value = (u32)string_obj[elem].integer.value; + break; + default: + pr_warn("Unsupported object type [%d]\n", string_obj[elem].type); + continue; + } + + /* Check that both expected and read object type match */ + if (expected_string_types[eloc] != string_obj[elem].type) { + pr_err("Error expected type %d for elem %d, but got type %d instead\n", + expected_string_types[eloc], elem, string_obj[elem].type); + kfree(str_value); + return -EIO; + } + + /* Assign appropriate element value to corresponding field*/ + switch (eloc) { + case VALUE: + strscpy(string_data->current_value, + str_value, sizeof(string_data->current_value)); + break; + case PATH: + strscpy(string_data->common.path, str_value, + sizeof(string_data->common.path)); + break; + case IS_READONLY: + string_data->common.is_readonly = int_value; + break; + case DISPLAY_IN_UI: + string_data->common.display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + string_data->common.requires_physical_presence = int_value; + break; + case SEQUENCE: + string_data->common.sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + string_data->common.prerequisites_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (string_data->common.prerequisites_size == 0) + eloc++; + break; + case PREREQUISITES: + size = min_t(u32, string_data->common.prerequisites_size, + MAX_PREREQUISITES_SIZE); + + for (reqs = 0; reqs < size; reqs++) { + if (elem >= string_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + + ret = hp_convert_hexstr_to_str(string_obj[elem + reqs].string.pointer, + string_obj[elem + reqs].string.length, + &str_value, &value_len); + + if (ret) + continue; + + strscpy(string_data->common.prerequisites[reqs], + str_value, + sizeof(string_data->common.prerequisites[reqs])); + kfree(str_value); + str_value = NULL; + } + break; + + case SECURITY_LEVEL: + string_data->common.security_level = int_value; + break; + case STR_MIN_LENGTH: + string_data->min_length = int_value; + break; + case STR_MAX_LENGTH: + string_data->max_length = int_value; + break; + default: + pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem); + break; + } + + kfree(str_value); + str_value = NULL; + } + +exit_string_package: + kfree(str_value); + return 0; +} + +/** + * hp_populate_string_package_data() - + * Populate all properties of an instance under string attribute + * + * @string_obj: ACPI object with string data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_string_package_data(union acpi_object *string_obj, + int instance_id, + struct kobject *attr_name_kobj) +{ + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + + string_data->attr_name_kobj = attr_name_kobj; + + hp_populate_string_elements_from_package(string_obj, + string_obj->package.count, + instance_id); + + hp_update_attribute_permissions(string_data->common.is_readonly, + &string_current_val); + hp_friendly_user_name_update(string_data->common.path, + attr_name_kobj->name, + string_data->common.display_name, + sizeof(string_data->common.display_name)); + return sysfs_create_group(attr_name_kobj, &string_attr_group); +} + +static int hp_populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size, + int instance_id) +{ + int ret = 0; + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + + /* + * Only data relevant to this driver and its functionality is + * read. BIOS defines the order in which each * element is + * read. Element 0 data is not relevant to this + * driver hence it is ignored. For clarity, all element names + * (DISPLAY_IN_UI) which defines the order in which is read + * and the name matches the variable where the data is stored. + * + * In earlier implementation, reported errors were ignored + * causing the data to remain uninitialized. It is not + * possible to determine if data read from BIOS is valid or + * not. It is for this reason functions may return a error + * without validating the data itself. + */ + + // VALUE: + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, string_data->current_value, + sizeof(string_data->current_value)); + if (ret < 0) + goto buffer_exit; + + // COMMON: + ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &string_data->common); + if (ret < 0) + goto buffer_exit; + + // STR_MIN_LENGTH: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &string_data->min_length); + if (ret < 0) + goto buffer_exit; + + // STR_MAX_LENGTH: + ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, + &string_data->max_length); + +buffer_exit: + + return ret; +} + +/** + * hp_populate_string_buffer_data() - + * Populate all properties of an instance under string attribute + * + * @buffer_ptr: Buffer pointer + * @buffer_size: Buffer size + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size, + int instance_id, + struct kobject *attr_name_kobj) +{ + struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; + int ret = 0; + + string_data->attr_name_kobj = attr_name_kobj; + + ret = hp_populate_string_elements_from_buffer(buffer_ptr, buffer_size, + instance_id); + if (ret < 0) + return ret; + + hp_update_attribute_permissions(string_data->common.is_readonly, + &string_current_val); + hp_friendly_user_name_update(string_data->common.path, + attr_name_kobj->name, + string_data->common.display_name, + sizeof(string_data->common.display_name)); + + return sysfs_create_group(attr_name_kobj, &string_attr_group); +} + +/** + * hp_exit_string_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void hp_exit_string_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < bioscfg_drv.string_instances_count; + instance_id++) { + struct kobject *attr_name_kobj = + bioscfg_drv.string_data[instance_id].attr_name_kobj; + + if (attr_name_kobj) + sysfs_remove_group(attr_name_kobj, &string_attr_group); + } + bioscfg_drv.string_instances_count = 0; + + kfree(bioscfg_drv.string_data); + bioscfg_drv.string_data = NULL; +} diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c new file mode 100644 index 0000000000..b57e42f292 --- /dev/null +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to sure start object type attributes under + * BIOS for use with hp-bioscfg driver + * + * Copyright (c) 2022 HP Development Company, L.P. + */ + +#include "bioscfg.h" +#include <linux/types.h> + +/* Maximum number of log entries supported when log entry size is 16 + * bytes. This value is calculated by dividing 4096 (page size) by + * log entry size. + */ +#define LOG_MAX_ENTRIES 254 + +/* + * Current Log entry size. This value size will change in the + * future. The driver reads a total of 128 bytes for each log entry + * provided by BIOS but only the first 16 bytes are used/read. + */ +#define LOG_ENTRY_SIZE 16 + +/* + * audit_log_entry_count_show - Reports the number of + * existing audit log entries available + * to be read + */ +static ssize_t audit_log_entry_count_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + u32 count = 0; + + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT, + HPWMI_SURESTART, + &count, 1, sizeof(count)); + + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE, + LOG_MAX_ENTRIES); +} + +/* + * audit_log_entries_show() - Return all entries found in log file + */ +static ssize_t audit_log_entries_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + int i; + u32 count = 0; + u8 audit_log_buffer[128]; + + // Get the number of event logs + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT, + HPWMI_SURESTART, + &count, 1, sizeof(count)); + + if (ret < 0) + return ret; + + /* + * The show() api will not work if the audit logs ever go + * beyond 4KB + */ + if (count * LOG_ENTRY_SIZE > PAGE_SIZE) + return -EIO; + + /* + * We are guaranteed the buffer is 4KB so today all the event + * logs will fit + */ + for (i = 0; i < count; i++) { + audit_log_buffer[0] = i + 1; + + /* + * read audit log entry at a time. 'buf' input value + * provides the audit log entry to be read. On + * input, Byte 0 = Audit Log entry number from + * beginning (1..254) + * Entry number 1 is the newest entry whereas the + * highest entry number (number of entries) is the + * oldest entry. + */ + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG, + HPWMI_SURESTART, + audit_log_buffer, 1, 128); + + if (ret < 0 || (LOG_ENTRY_SIZE * i) > PAGE_SIZE) { + /* + * Encountered a failure while reading + * individual logs. Only a partial list of + * audit log will be returned. + */ + break; + } else { + memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE); + buf += LOG_ENTRY_SIZE; + } + } + + return i * LOG_ENTRY_SIZE; +} + +static struct kobj_attribute sure_start_audit_log_entry_count = __ATTR_RO(audit_log_entry_count); +static struct kobj_attribute sure_start_audit_log_entries = __ATTR_RO(audit_log_entries); + +static struct attribute *sure_start_attrs[] = { + &sure_start_audit_log_entry_count.attr, + &sure_start_audit_log_entries.attr, + NULL +}; + +static const struct attribute_group sure_start_attr_group = { + .attrs = sure_start_attrs, +}; + +void hp_exit_sure_start_attributes(void) +{ + sysfs_remove_group(bioscfg_drv.sure_start_attr_kobj, + &sure_start_attr_group); +} + +int hp_populate_sure_start_data(struct kobject *attr_name_kobj) +{ + bioscfg_drv.sure_start_attr_kobj = attr_name_kobj; + return sysfs_create_group(attr_name_kobj, &sure_start_attr_group); +} diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c new file mode 100644 index 0000000000..8ebb7be52e --- /dev/null +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -0,0 +1,1729 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HP WMI hotkeys + * + * Copyright (C) 2008 Red Hat <mjg@redhat.com> + * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi> + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/platform_device.h> +#include <linux/platform_profile.h> +#include <linux/hwmon.h> +#include <linux/acpi.h> +#include <linux/rfkill.h> +#include <linux/string.h> +#include <linux/dmi.h> + +MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); +MODULE_DESCRIPTION("HP laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); +MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); + +#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" +#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" +#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 +#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required + +/* DMI board names of devices that should use the omen specific path for + * thermal profiles. + * This was obtained by taking a look in the windows omen command center + * app and parsing a json file that they use to figure out what capabilities + * the device should have. + * A device is considered an omen if the DisplayName in that list contains + * "OMEN", and it can use the thermal profile stuff if the "Feature" array + * contains "PerformanceControl". + */ +static const char * const omen_thermal_profile_boards[] = { + "84DA", "84DB", "84DC", "8574", "8575", "860A", "87B5", "8572", "8573", + "8600", "8601", "8602", "8605", "8606", "8607", "8746", "8747", "8749", + "874A", "8603", "8604", "8748", "886B", "886C", "878A", "878B", "878C", + "88C8", "88CB", "8786", "8787", "8788", "88D1", "88D2", "88F4", "88FD", + "88F5", "88F6", "88F7", "88FE", "88FF", "8900", "8901", "8902", "8912", + "8917", "8918", "8949", "894A", "89EB" +}; + +/* DMI Board names of Omen laptops that are specifically set to be thermal + * profile version 0 by the Omen Command Center app, regardless of what + * the get system design information WMI call returns + */ +static const char *const omen_thermal_profile_force_v0_boards[] = { + "8607", "8746", "8747", "8749", "874A", "8748" +}; + +/* DMI Board names of Victus laptops */ +static const char * const victus_thermal_profile_boards[] = { + "8A25" +}; + +enum hp_wmi_radio { + HPWMI_WIFI = 0x0, + HPWMI_BLUETOOTH = 0x1, + HPWMI_WWAN = 0x2, + HPWMI_GPS = 0x3, +}; + +enum hp_wmi_event_ids { + HPWMI_DOCK_EVENT = 0x01, + HPWMI_PARK_HDD = 0x02, + HPWMI_SMART_ADAPTER = 0x03, + HPWMI_BEZEL_BUTTON = 0x04, + HPWMI_WIRELESS = 0x05, + HPWMI_CPU_BATTERY_THROTTLE = 0x06, + HPWMI_LOCK_SWITCH = 0x07, + HPWMI_LID_SWITCH = 0x08, + HPWMI_SCREEN_ROTATION = 0x09, + HPWMI_COOLSENSE_SYSTEM_MOBILE = 0x0A, + HPWMI_COOLSENSE_SYSTEM_HOT = 0x0B, + HPWMI_PROXIMITY_SENSOR = 0x0C, + HPWMI_BACKLIT_KB_BRIGHTNESS = 0x0D, + HPWMI_PEAKSHIFT_PERIOD = 0x0F, + HPWMI_BATTERY_CHARGE_PERIOD = 0x10, + HPWMI_SANITIZATION_MODE = 0x17, + HPWMI_CAMERA_TOGGLE = 0x1A, + HPWMI_OMEN_KEY = 0x1D, + HPWMI_SMART_EXPERIENCE_APP = 0x21, +}; + +/* + * struct bios_args buffer is dynamically allocated. New WMI command types + * were introduced that exceeds 128-byte data size. Changes to handle + * the data size allocation scheme were kept in hp_wmi_perform_qurey function. + */ +struct bios_args { + u32 signature; + u32 command; + u32 commandtype; + u32 datasize; + u8 data[]; +}; + +enum hp_wmi_commandtype { + HPWMI_DISPLAY_QUERY = 0x01, + HPWMI_HDDTEMP_QUERY = 0x02, + HPWMI_ALS_QUERY = 0x03, + HPWMI_HARDWARE_QUERY = 0x04, + HPWMI_WIRELESS_QUERY = 0x05, + HPWMI_BATTERY_QUERY = 0x07, + HPWMI_BIOS_QUERY = 0x09, + HPWMI_FEATURE_QUERY = 0x0b, + HPWMI_HOTKEY_QUERY = 0x0c, + HPWMI_FEATURE2_QUERY = 0x0d, + HPWMI_WIRELESS2_QUERY = 0x1b, + HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, + HPWMI_THERMAL_PROFILE_QUERY = 0x4c, +}; + +enum hp_wmi_gm_commandtype { + HPWMI_FAN_SPEED_GET_QUERY = 0x11, + HPWMI_SET_PERFORMANCE_MODE = 0x1A, + HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, + HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, +}; + +enum hp_wmi_command { + HPWMI_READ = 0x01, + HPWMI_WRITE = 0x02, + HPWMI_ODM = 0x03, + HPWMI_GM = 0x20008, +}; + +enum hp_wmi_hardware_mask { + HPWMI_DOCK_MASK = 0x01, + HPWMI_TABLET_MASK = 0x04, +}; + +struct bios_return { + u32 sigpass; + u32 return_code; +}; + +enum hp_return_value { + HPWMI_RET_WRONG_SIGNATURE = 0x02, + HPWMI_RET_UNKNOWN_COMMAND = 0x03, + HPWMI_RET_UNKNOWN_CMDTYPE = 0x04, + HPWMI_RET_INVALID_PARAMETERS = 0x05, +}; + +enum hp_wireless2_bits { + HPWMI_POWER_STATE = 0x01, + HPWMI_POWER_SOFT = 0x02, + HPWMI_POWER_BIOS = 0x04, + HPWMI_POWER_HARD = 0x08, + HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, +}; + +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, +}; + +enum hp_thermal_profile_victus { + HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00, + HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03, +}; + +enum hp_thermal_profile { + HP_THERMAL_PROFILE_PERFORMANCE = 0x00, + HP_THERMAL_PROFILE_DEFAULT = 0x01, + HP_THERMAL_PROFILE_COOL = 0x02, + HP_THERMAL_PROFILE_QUIET = 0x03, +}; + +#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) +#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) + +struct bios_rfkill2_device_state { + u8 radio_type; + u8 bus_type; + u16 vendor_id; + u16 product_id; + u16 subsys_vendor_id; + u16 subsys_product_id; + u8 rfkill_id; + u8 power; + u8 unknown[4]; +}; + +/* 7 devices fit into the 128 byte buffer */ +#define HPWMI_MAX_RFKILL2_DEVICES 7 + +struct bios_rfkill2_state { + u8 unknown[7]; + u8 count; + u8 pad[8]; + struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES]; +}; + +static const struct key_entry hp_wmi_keymap[] = { + { KE_KEY, 0x02, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x03, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x270, { KEY_MICMUTE } }, + { KE_KEY, 0x20e6, { KEY_PROG1 } }, + { KE_KEY, 0x20e8, { KEY_MEDIA } }, + { KE_KEY, 0x2142, { KEY_MEDIA } }, + { KE_KEY, 0x213b, { KEY_INFO } }, + { KE_KEY, 0x2169, { KEY_ROTATE_DISPLAY } }, + { KE_KEY, 0x216a, { KEY_SETUP } }, + { KE_IGNORE, 0x21a4, }, /* Win Lock On */ + { KE_IGNORE, 0x121a4, }, /* Win Lock Off */ + { KE_KEY, 0x21a5, { KEY_PROG2 } }, /* HP Omen Key */ + { KE_KEY, 0x21a7, { KEY_FN_ESC } }, + { KE_KEY, 0x21a8, { KEY_PROG2 } }, /* HP Envy x360 programmable key */ + { KE_KEY, 0x21a9, { KEY_TOUCHPAD_OFF } }, + { KE_KEY, 0x121a9, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 0x231b, { KEY_HELP } }, + { KE_END, 0 } +}; + +static struct input_dev *hp_wmi_input_dev; +static struct input_dev *camera_shutter_input_dev; +static struct platform_device *hp_wmi_platform_dev; +static struct platform_profile_handler platform_profile_handler; +static bool platform_profile_support; +static bool zero_insize_support; + +static struct rfkill *wifi_rfkill; +static struct rfkill *bluetooth_rfkill; +static struct rfkill *wwan_rfkill; + +struct rfkill2_device { + u8 id; + int num; + struct rfkill *rfkill; +}; + +static int rfkill2_count; +static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; + +/* + * Chassis Types values were obtained from SMBIOS reference + * specification version 3.00. A complete list of system enclosures + * and chassis types is available on Table 17. + */ +static const char * const tablet_chassis_types[] = { + "30", /* Tablet*/ + "31", /* Convertible */ + "32" /* Detachable */ +}; + +#define DEVICE_MODE_TABLET 0x06 + +/* map output size to the corresponding WMI method id */ +static inline int encode_outsize_for_pvsz(int outsize) +{ + if (outsize > 4096) + return -EINVAL; + if (outsize > 1024) + return 5; + if (outsize > 128) + return 4; + if (outsize > 4) + return 3; + if (outsize > 0) + return 2; + return 1; +} + +/* + * hp_wmi_perform_query + * + * query: The commandtype (enum hp_wmi_commandtype) + * write: The command (enum hp_wmi_command) + * buffer: Buffer used as input and/or output + * insize: Size of input buffer + * outsize: Size of output buffer + * + * returns zero on success + * an HP WMI query specific error code (which is positive) + * -EINVAL if the query was not successful at all + * -EINVAL if the output buffer size exceeds buffersize + * + * Note: The buffersize must at least be the maximum of the input and output + * size. E.g. Battery info query is defined to have 1 byte input + * and 128 byte output. The caller would do: + * buffer = kzalloc(128, GFP_KERNEL); + * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ, buffer, 1, 128) + */ +static int hp_wmi_perform_query(int query, enum hp_wmi_command command, + void *buffer, int insize, int outsize) +{ + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_return *bios_return; + union acpi_object *obj = NULL; + struct bios_args *args = NULL; + int mid, actual_insize, actual_outsize; + size_t bios_args_size; + int ret; + + mid = encode_outsize_for_pvsz(outsize); + if (WARN_ON(mid < 0)) + return mid; + + actual_insize = max(insize, 128); + bios_args_size = struct_size(args, data, actual_insize); + args = kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; + + input.length = bios_args_size; + input.pointer = args; + + args->signature = 0x55434553; + args->command = command; + args->commandtype = query; + args->datasize = insize; + memcpy(args->data, buffer, flex_array_size(args, data, insize)); + + ret = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; + + obj = output.pointer; + if (!obj) { + ret = -EINVAL; + goto out_free; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); + ret = -EINVAL; + goto out_free; + } + + bios_return = (struct bios_return *)obj->buffer.pointer; + ret = bios_return->return_code; + + if (ret) { + if (ret != HPWMI_RET_UNKNOWN_COMMAND && + ret != HPWMI_RET_UNKNOWN_CMDTYPE) + pr_warn("query 0x%x returned error 0x%x\n", query, ret); + goto out_free; + } + + /* Ignore output data of zero size */ + if (!outsize) + goto out_free; + + actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return))); + memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize); + memset(buffer + actual_outsize, 0, outsize - actual_outsize); + +out_free: + kfree(obj); + kfree(args); + return ret; +} + +static int hp_wmi_get_fan_speed(int fan) +{ + u8 fsh, fsl; + char fan_data[4] = { fan, 0, 0, 0 }; + + int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, + &fan_data, sizeof(char), + sizeof(fan_data)); + + if (ret != 0) + return -EINVAL; + + fsh = fan_data[2]; + fsl = fan_data[3]; + + return (fsh << 8) | fsl; +} + +static int hp_wmi_read_int(int query) +{ + int val = 0, ret; + + ret = hp_wmi_perform_query(query, HPWMI_READ, &val, + zero_if_sup(val), sizeof(val)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return val; +} + +static int hp_wmi_get_dock_state(void) +{ + int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); + + if (state < 0) + return state; + + return !!(state & HPWMI_DOCK_MASK); +} + +static int hp_wmi_get_tablet_mode(void) +{ + char system_device_mode[4] = { 0 }; + const char *chassis_type; + bool tablet_found; + int ret; + + chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); + if (!chassis_type) + return -ENODEV; + + tablet_found = match_string(tablet_chassis_types, + ARRAY_SIZE(tablet_chassis_types), + chassis_type) >= 0; + if (!tablet_found) + return -ENODEV; + + ret = hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ, + system_device_mode, zero_if_sup(system_device_mode), + sizeof(system_device_mode)); + if (ret < 0) + return ret; + + return system_device_mode[0] == DEVICE_MODE_TABLET; +} + +static int omen_thermal_profile_set(int mode) +{ + char buffer[2] = {0, mode}; + int ret; + + ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, + &buffer, sizeof(buffer), 0); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return mode; +} + +static bool is_omen_thermal_profile(void) +{ + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (!board_name) + return false; + + return match_string(omen_thermal_profile_boards, + ARRAY_SIZE(omen_thermal_profile_boards), + board_name) >= 0; +} + +static int omen_get_thermal_policy_version(void) +{ + unsigned char buffer[8] = { 0 }; + int ret; + + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name) { + int matches = match_string(omen_thermal_profile_force_v0_boards, + ARRAY_SIZE(omen_thermal_profile_force_v0_boards), + board_name); + if (matches >= 0) + return 0; + } + + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return buffer[3]; +} + +static int omen_thermal_profile_get(void) +{ + u8 data; + + int ret = ec_read(HP_OMEN_EC_THERMAL_PROFILE_OFFSET, &data); + + if (ret) + return ret; + + return data; +} + +static int hp_wmi_fan_speed_max_set(int enabled) +{ + int ret; + + ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, + &enabled, sizeof(enabled), 0); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return enabled; +} + +static int hp_wmi_fan_speed_max_get(void) +{ + int val = 0, ret; + + ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, + &val, zero_if_sup(val), sizeof(val)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return val; +} + +static int __init hp_wmi_bios_2008_later(void) +{ + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (!ret) + return 1; + + return (ret == HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO; +} + +static int __init hp_wmi_bios_2009_later(void) +{ + u8 state[128]; + int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (!ret) + return 1; + + return (ret == HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO; +} + +static int __init hp_wmi_enable_hotkeys(void) +{ + int value = 0x6e; + int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, HPWMI_WRITE, &value, + sizeof(value), 0); + + return ret <= 0 ? ret : -EINVAL; +} + +static int hp_wmi_set_block(void *data, bool blocked) +{ + enum hp_wmi_radio r = (long)data; + int query = BIT(r + 8) | ((!blocked) << r); + int ret; + + ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, + &query, sizeof(query), 0); + + return ret <= 0 ? ret : -EINVAL; +} + +static const struct rfkill_ops hp_wmi_rfkill_ops = { + .set_block = hp_wmi_set_block, +}; + +static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) +{ + int mask = 0x200 << (r * 8); + + int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); +} + +static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) +{ + int mask = 0x800 << (r * 8); + + int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); +} + +static int hp_wmi_rfkill2_set_block(void *data, bool blocked) +{ + int rfkill_id = (int)(long)data; + char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked }; + int ret; + + ret = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_WRITE, + buffer, sizeof(buffer), 0); + + return ret <= 0 ? ret : -EINVAL; +} + +static const struct rfkill_ops hp_wmi_rfkill2_ops = { + .set_block = hp_wmi_rfkill2_set_block, +}; + +static int hp_wmi_rfkill2_refresh(void) +{ + struct bios_rfkill2_state state; + int err, i; + + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (err) + return err; + + for (i = 0; i < rfkill2_count; i++) { + int num = rfkill2[i].num; + struct bios_rfkill2_device_state *devstate; + + devstate = &state.device[num]; + + if (num >= state.count || + devstate->rfkill_id != rfkill2[i].id) { + pr_warn("power configuration of the wireless devices unexpectedly changed\n"); + continue; + } + + rfkill_set_states(rfkill2[i].rfkill, + IS_SWBLOCKED(devstate->power), + IS_HWBLOCKED(devstate->power)); + } + + return 0; +} + +static ssize_t display_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_read_int(HPWMI_DISPLAY_QUERY); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t hddtemp_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_read_int(HPWMI_HDDTEMP_QUERY); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t als_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_read_int(HPWMI_ALS_QUERY); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t dock_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_get_dock_state(); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t tablet_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_get_tablet_mode(); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t postcode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + /* Get the POST error code of previous boot failure. */ + int value = hp_wmi_read_int(HPWMI_POSTCODEERROR_QUERY); + + if (value < 0) + return value; + return sprintf(buf, "0x%x\n", value); +} + +static ssize_t als_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 tmp; + int ret; + + ret = kstrtou32(buf, 10, &tmp); + if (ret) + return ret; + + ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, + sizeof(tmp), 0); + if (ret) + return ret < 0 ? ret : -EINVAL; + + return count; +} + +static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 tmp = 1; + bool clear; + int ret; + + ret = kstrtobool(buf, &clear); + if (ret) + return ret; + + if (clear == false) + return -EINVAL; + + /* Clear the POST error code. It is kept until cleared. */ + ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, + sizeof(tmp), 0); + if (ret) + return ret < 0 ? ret : -EINVAL; + + return count; +} + +static int camera_shutter_input_setup(void) +{ + int err; + + camera_shutter_input_dev = input_allocate_device(); + if (!camera_shutter_input_dev) + return -ENOMEM; + + camera_shutter_input_dev->name = "HP WMI camera shutter"; + camera_shutter_input_dev->phys = "wmi/input1"; + camera_shutter_input_dev->id.bustype = BUS_HOST; + + __set_bit(EV_SW, camera_shutter_input_dev->evbit); + __set_bit(SW_CAMERA_LENS_COVER, camera_shutter_input_dev->swbit); + + err = input_register_device(camera_shutter_input_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(camera_shutter_input_dev); + camera_shutter_input_dev = NULL; + return err; +} + +static DEVICE_ATTR_RO(display); +static DEVICE_ATTR_RO(hddtemp); +static DEVICE_ATTR_RW(als); +static DEVICE_ATTR_RO(dock); +static DEVICE_ATTR_RO(tablet); +static DEVICE_ATTR_RW(postcode); + +static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, + &dev_attr_hddtemp.attr, + &dev_attr_als.attr, + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, + NULL, +}; +ATTRIBUTE_GROUPS(hp_wmi); + +static void hp_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 event_id, event_data; + union acpi_object *obj; + acpi_status status; + u32 *location; + int key_code; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + + if (!obj) + return; + if (obj->type != ACPI_TYPE_BUFFER) { + pr_info("Unknown response received %d\n", obj->type); + kfree(obj); + return; + } + + /* + * Depending on ACPI version the concatenation of id and event data + * inside _WED function will result in a 8 or 16 byte buffer. + */ + location = (u32 *)obj->buffer.pointer; + if (obj->buffer.length == 8) { + event_id = *location; + event_data = *(location + 1); + } else if (obj->buffer.length == 16) { + event_id = *location; + event_data = *(location + 2); + } else { + pr_info("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return; + } + kfree(obj); + + switch (event_id) { + case HPWMI_DOCK_EVENT: + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_get_dock_state()); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_get_tablet_mode()); + input_sync(hp_wmi_input_dev); + break; + case HPWMI_PARK_HDD: + break; + case HPWMI_SMART_ADAPTER: + break; + case HPWMI_BEZEL_BUTTON: + key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY); + if (key_code < 0) + break; + + if (!sparse_keymap_report_event(hp_wmi_input_dev, + key_code, 1, true)) + pr_info("Unknown key code - 0x%x\n", key_code); + break; + case HPWMI_OMEN_KEY: + if (event_data) /* Only should be true for HP Omen */ + key_code = event_data; + else + key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY); + + if (!sparse_keymap_report_event(hp_wmi_input_dev, + key_code, 1, true)) + pr_info("Unknown key code - 0x%x\n", key_code); + break; + case HPWMI_WIRELESS: + if (rfkill2_count) { + hp_wmi_rfkill2_refresh(); + break; + } + + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); + break; + case HPWMI_CPU_BATTERY_THROTTLE: + pr_info("Unimplemented CPU throttle because of 3 Cell battery event detected\n"); + break; + case HPWMI_LOCK_SWITCH: + break; + case HPWMI_LID_SWITCH: + break; + case HPWMI_SCREEN_ROTATION: + break; + case HPWMI_COOLSENSE_SYSTEM_MOBILE: + break; + case HPWMI_COOLSENSE_SYSTEM_HOT: + break; + case HPWMI_PROXIMITY_SENSOR: + break; + case HPWMI_BACKLIT_KB_BRIGHTNESS: + break; + case HPWMI_PEAKSHIFT_PERIOD: + break; + case HPWMI_BATTERY_CHARGE_PERIOD: + break; + case HPWMI_SANITIZATION_MODE: + break; + case HPWMI_CAMERA_TOGGLE: + if (!camera_shutter_input_dev) + if (camera_shutter_input_setup()) { + pr_err("Failed to setup camera shutter input device\n"); + break; + } + if (event_data == 0xff) + input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 1); + else if (event_data == 0xfe) + input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 0); + else + pr_warn("Unknown camera shutter state - 0x%x\n", event_data); + input_sync(camera_shutter_input_dev); + break; + case HPWMI_SMART_EXPERIENCE_APP: + break; + default: + pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data); + break; + } +} + +static int __init hp_wmi_input_setup(void) +{ + acpi_status status; + int err, val; + + hp_wmi_input_dev = input_allocate_device(); + if (!hp_wmi_input_dev) + return -ENOMEM; + + hp_wmi_input_dev->name = "HP WMI hotkeys"; + hp_wmi_input_dev->phys = "wmi/input0"; + hp_wmi_input_dev->id.bustype = BUS_HOST; + + __set_bit(EV_SW, hp_wmi_input_dev->evbit); + + /* Dock */ + val = hp_wmi_get_dock_state(); + if (!(val < 0)) { + __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_DOCK, val); + } + + /* Tablet mode */ + val = hp_wmi_get_tablet_mode(); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); + } + + err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); + if (err) + goto err_free_dev; + + /* Set initial hardware state */ + input_sync(hp_wmi_input_dev); + + if (!hp_wmi_bios_2009_later() && hp_wmi_bios_2008_later()) + hp_wmi_enable_hotkeys(); + + status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL); + if (ACPI_FAILURE(status)) { + err = -EIO; + goto err_free_dev; + } + + err = input_register_device(hp_wmi_input_dev); + if (err) + goto err_uninstall_notifier; + + return 0; + + err_uninstall_notifier: + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + err_free_dev: + input_free_device(hp_wmi_input_dev); + return err; +} + +static void hp_wmi_input_destroy(void) +{ + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + input_unregister_device(hp_wmi_input_dev); +} + +static int __init hp_wmi_rfkill_setup(struct platform_device *device) +{ + int err, wireless; + + wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + if (wireless < 0) + return wireless; + + err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, &wireless, + sizeof(wireless), 0); + if (err) + return err; + + if (wireless & 0x1) { + wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, + RFKILL_TYPE_WLAN, + &hp_wmi_rfkill_ops, + (void *) HPWMI_WIFI); + if (!wifi_rfkill) + return -ENOMEM; + rfkill_init_sw_state(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI)); + rfkill_set_hw_state(wifi_rfkill, + hp_wmi_get_hw_state(HPWMI_WIFI)); + err = rfkill_register(wifi_rfkill); + if (err) + goto register_wifi_error; + } + + if (wireless & 0x2) { + bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev, + RFKILL_TYPE_BLUETOOTH, + &hp_wmi_rfkill_ops, + (void *) HPWMI_BLUETOOTH); + if (!bluetooth_rfkill) { + err = -ENOMEM; + goto register_bluetooth_error; + } + rfkill_init_sw_state(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH)); + rfkill_set_hw_state(bluetooth_rfkill, + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + err = rfkill_register(bluetooth_rfkill); + if (err) + goto register_bluetooth_error; + } + + if (wireless & 0x4) { + wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev, + RFKILL_TYPE_WWAN, + &hp_wmi_rfkill_ops, + (void *) HPWMI_WWAN); + if (!wwan_rfkill) { + err = -ENOMEM; + goto register_wwan_error; + } + rfkill_init_sw_state(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN)); + rfkill_set_hw_state(wwan_rfkill, + hp_wmi_get_hw_state(HPWMI_WWAN)); + err = rfkill_register(wwan_rfkill); + if (err) + goto register_wwan_error; + } + + return 0; + +register_wwan_error: + rfkill_destroy(wwan_rfkill); + wwan_rfkill = NULL; + if (bluetooth_rfkill) + rfkill_unregister(bluetooth_rfkill); +register_bluetooth_error: + rfkill_destroy(bluetooth_rfkill); + bluetooth_rfkill = NULL; + if (wifi_rfkill) + rfkill_unregister(wifi_rfkill); +register_wifi_error: + rfkill_destroy(wifi_rfkill); + wifi_rfkill = NULL; + return err; +} + +static int __init hp_wmi_rfkill2_setup(struct platform_device *device) +{ + struct bios_rfkill2_state state; + int err, i; + + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (err) + return err < 0 ? err : -EINVAL; + + if (state.count > HPWMI_MAX_RFKILL2_DEVICES) { + pr_warn("unable to parse 0x1b query output\n"); + return -EINVAL; + } + + for (i = 0; i < state.count; i++) { + struct rfkill *rfkill; + enum rfkill_type type; + char *name; + + switch (state.device[i].radio_type) { + case HPWMI_WIFI: + type = RFKILL_TYPE_WLAN; + name = "hp-wifi"; + break; + case HPWMI_BLUETOOTH: + type = RFKILL_TYPE_BLUETOOTH; + name = "hp-bluetooth"; + break; + case HPWMI_WWAN: + type = RFKILL_TYPE_WWAN; + name = "hp-wwan"; + break; + case HPWMI_GPS: + type = RFKILL_TYPE_GPS; + name = "hp-gps"; + break; + default: + pr_warn("unknown device type 0x%x\n", + state.device[i].radio_type); + continue; + } + + if (!state.device[i].vendor_id) { + pr_warn("zero device %d while %d reported\n", + i, state.count); + continue; + } + + rfkill = rfkill_alloc(name, &device->dev, type, + &hp_wmi_rfkill2_ops, (void *)(long)i); + if (!rfkill) { + err = -ENOMEM; + goto fail; + } + + rfkill2[rfkill2_count].id = state.device[i].rfkill_id; + rfkill2[rfkill2_count].num = i; + rfkill2[rfkill2_count].rfkill = rfkill; + + rfkill_init_sw_state(rfkill, + IS_SWBLOCKED(state.device[i].power)); + rfkill_set_hw_state(rfkill, + IS_HWBLOCKED(state.device[i].power)); + + if (!(state.device[i].power & HPWMI_POWER_BIOS)) + pr_info("device %s blocked by BIOS\n", name); + + err = rfkill_register(rfkill); + if (err) { + rfkill_destroy(rfkill); + goto fail; + } + + rfkill2_count++; + } + + return 0; +fail: + for (; rfkill2_count > 0; rfkill2_count--) { + rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill); + rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill); + } + return err; +} + +static int platform_profile_omen_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + int tp; + + tp = omen_thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case HP_OMEN_V0_THERMAL_PROFILE_COOL: + case HP_OMEN_V1_THERMAL_PROFILE_COOL: + *profile = PLATFORM_PROFILE_COOL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int platform_profile_omen_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int err, tp, tp_version; + + tp_version = omen_get_thermal_policy_version(); + + if (tp_version < 0 || tp_version > 1) + return -EOPNOTSUPP; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_DEFAULT; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_COOL: + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_COOL; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_COOL; + break; + default: + return -EOPNOTSUPP; + } + + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; + + return 0; +} + +static int thermal_profile_get(void) +{ + return hp_wmi_read_int(HPWMI_THERMAL_PROFILE_QUERY); +} + +static int thermal_profile_set(int thermal_profile) +{ + return hp_wmi_perform_query(HPWMI_THERMAL_PROFILE_QUERY, HPWMI_WRITE, &thermal_profile, + sizeof(thermal_profile), 0); +} + +static int hp_wmi_platform_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + int tp; + + tp = thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_THERMAL_PROFILE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_THERMAL_PROFILE_DEFAULT: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case HP_THERMAL_PROFILE_COOL: + *profile = PLATFORM_PROFILE_COOL; + break; + case HP_THERMAL_PROFILE_QUIET: + *profile = PLATFORM_PROFILE_QUIET; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int hp_wmi_platform_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp = HP_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + tp = HP_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_COOL: + tp = HP_THERMAL_PROFILE_COOL; + break; + case PLATFORM_PROFILE_QUIET: + tp = HP_THERMAL_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + err = thermal_profile_set(tp); + if (err) + return err; + + return 0; +} + +static bool is_victus_thermal_profile(void) +{ + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (!board_name) + return false; + + return match_string(victus_thermal_profile_boards, + ARRAY_SIZE(victus_thermal_profile_boards), + board_name) >= 0; +} + +static int platform_profile_victus_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + int tp; + + tp = omen_thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_VICTUS_THERMAL_PROFILE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_VICTUS_THERMAL_PROFILE_DEFAULT: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case HP_VICTUS_THERMAL_PROFILE_QUIET: + *profile = PLATFORM_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int platform_profile_victus_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp = HP_VICTUS_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + tp = HP_VICTUS_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_QUIET: + tp = HP_VICTUS_THERMAL_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; + + return 0; +} + +static int thermal_profile_setup(void) +{ + int err, tp; + + if (is_omen_thermal_profile()) { + tp = omen_thermal_profile_get(); + if (tp < 0) + return tp; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables + */ + + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; + + platform_profile_handler.profile_get = platform_profile_omen_get; + platform_profile_handler.profile_set = platform_profile_omen_set; + + set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices); + } else if (is_victus_thermal_profile()) { + tp = omen_thermal_profile_get(); + if (tp < 0) + return tp; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables + */ + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; + + platform_profile_handler.profile_get = platform_profile_victus_get; + platform_profile_handler.profile_set = platform_profile_victus_set; + + set_bit(PLATFORM_PROFILE_QUIET, platform_profile_handler.choices); + } else { + tp = thermal_profile_get(); + + if (tp < 0) + return tp; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables for the DPTF + */ + err = thermal_profile_set(tp); + if (err) + return err; + + platform_profile_handler.profile_get = hp_wmi_platform_profile_get; + platform_profile_handler.profile_set = hp_wmi_platform_profile_set; + + set_bit(PLATFORM_PROFILE_QUIET, platform_profile_handler.choices); + set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices); + } + + set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, platform_profile_handler.choices); + + err = platform_profile_register(&platform_profile_handler); + if (err) + return err; + + platform_profile_support = true; + + return 0; +} + +static int hp_wmi_hwmon_init(void); + +static int __init hp_wmi_bios_setup(struct platform_device *device) +{ + int err; + /* clear detected rfkill devices */ + wifi_rfkill = NULL; + bluetooth_rfkill = NULL; + wwan_rfkill = NULL; + rfkill2_count = 0; + + /* + * In pre-2009 BIOS, command 1Bh return 0x4 to indicate that + * BIOS no longer controls the power for the wireless + * devices. All features supported by this command will no + * longer be supported. + */ + if (!hp_wmi_bios_2009_later()) { + if (hp_wmi_rfkill_setup(device)) + hp_wmi_rfkill2_setup(device); + } + + err = hp_wmi_hwmon_init(); + + if (err < 0) + return err; + + thermal_profile_setup(); + + return 0; +} + +static int __exit hp_wmi_bios_remove(struct platform_device *device) +{ + int i; + + for (i = 0; i < rfkill2_count; i++) { + rfkill_unregister(rfkill2[i].rfkill); + rfkill_destroy(rfkill2[i].rfkill); + } + + if (wifi_rfkill) { + rfkill_unregister(wifi_rfkill); + rfkill_destroy(wifi_rfkill); + } + if (bluetooth_rfkill) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } + if (wwan_rfkill) { + rfkill_unregister(wwan_rfkill); + rfkill_destroy(wwan_rfkill); + } + + if (platform_profile_support) + platform_profile_remove(); + + return 0; +} + +static int hp_wmi_resume_handler(struct device *device) +{ + /* + * Hardware state may have changed while suspended, so trigger + * input events for the current state. As this is a switch, + * the input layer will only actually pass it on if the state + * changed. + */ + if (hp_wmi_input_dev) { + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_get_dock_state()); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_get_tablet_mode()); + input_sync(hp_wmi_input_dev); + } + + if (rfkill2_count) + hp_wmi_rfkill2_refresh(); + + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); + + return 0; +} + +static const struct dev_pm_ops hp_wmi_pm_ops = { + .resume = hp_wmi_resume_handler, + .restore = hp_wmi_resume_handler, +}; + +/* + * hp_wmi_bios_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this is ok because they cannot get unbound at + * runtime. So mark the driver struct with __refdata to prevent modpost + * triggering a section mismatch warning. + */ +static struct platform_driver hp_wmi_driver __refdata = { + .driver = { + .name = "hp-wmi", + .pm = &hp_wmi_pm_ops, + .dev_groups = hp_wmi_groups, + }, + .remove = __exit_p(hp_wmi_bios_remove), +}; + +static umode_t hp_wmi_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + return 0644; + case hwmon_fan: + if (hp_wmi_get_fan_speed(channel) >= 0) + return 0444; + break; + default: + return 0; + } + + return 0; +} + +static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_fan: + ret = hp_wmi_get_fan_speed(channel); + + if (ret < 0) + return ret; + *val = ret; + return 0; + case hwmon_pwm: + switch (hp_wmi_fan_speed_max_get()) { + case 0: + /* 0 is automatic fan, which is 2 for hwmon */ + *val = 2; + return 0; + case 1: + /* 1 is max fan, which is 0 + * (no fan speed control) for hwmon + */ + *val = 0; + return 0; + default: + /* shouldn't happen */ + return -ENODATA; + } + default: + return -EINVAL; + } +} + +static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + switch (val) { + case 0: + /* 0 is no fan speed control (max), which is 1 for us */ + return hp_wmi_fan_speed_max_set(1); + case 2: + /* 2 is automatic speed control, which is 0 for us */ + return hp_wmi_fan_speed_max_set(0); + default: + /* we don't support manual fan speed control */ + return -EINVAL; + } + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info * const info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + NULL +}; + +static const struct hwmon_ops ops = { + .is_visible = hp_wmi_hwmon_is_visible, + .read = hp_wmi_hwmon_read, + .write = hp_wmi_hwmon_write, +}; + +static const struct hwmon_chip_info chip_info = { + .ops = &ops, + .info = info, +}; + +static int hp_wmi_hwmon_init(void) +{ + struct device *dev = &hp_wmi_platform_dev->dev; + struct device *hwmon; + + hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, + &chip_info, NULL); + + if (IS_ERR(hwmon)) { + dev_err(dev, "Could not register hp hwmon device\n"); + return PTR_ERR(hwmon); + } + + return 0; +} + +static int __init hp_wmi_init(void) +{ + int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); + int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID); + int err, tmp = 0; + + if (!bios_capable && !event_capable) + return -ENODEV; + + if (hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, HPWMI_READ, &tmp, + sizeof(tmp), sizeof(tmp)) == HPWMI_RET_INVALID_PARAMETERS) + zero_insize_support = true; + + if (event_capable) { + err = hp_wmi_input_setup(); + if (err) + return err; + } + + if (bios_capable) { + hp_wmi_platform_dev = + platform_device_register_simple("hp-wmi", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(hp_wmi_platform_dev)) { + err = PTR_ERR(hp_wmi_platform_dev); + goto err_destroy_input; + } + + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; + } + + return 0; + +err_unregister_device: + platform_device_unregister(hp_wmi_platform_dev); +err_destroy_input: + if (event_capable) + hp_wmi_input_destroy(); + + return err; +} +module_init(hp_wmi_init); + +static void __exit hp_wmi_exit(void) +{ + if (wmi_has_guid(HPWMI_EVENT_GUID)) + hp_wmi_input_destroy(); + + if (camera_shutter_input_dev) + input_unregister_device(camera_shutter_input_dev); + + if (hp_wmi_platform_dev) { + platform_device_unregister(hp_wmi_platform_dev); + platform_driver_unregister(&hp_wmi_driver); + } +} +module_exit(hp_wmi_exit); diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c new file mode 100644 index 0000000000..5253557677 --- /dev/null +++ b/drivers/platform/x86/hp/hp_accel.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/freezer.h> +#include <linux/uaccess.h> +#include <linux/leds.h> +#include <linux/atomic.h> +#include <linux/acpi.h> +#include <linux/i8042.h> +#include <linux/serio.h> +#include "../../../misc/lis3lv02d/lis3lv02d.h" + +/* Delayed LEDs infrastructure ------------------------------------ */ + +/* Special LED class that can defer work */ +struct delayed_led_classdev { + struct led_classdev led_classdev; + struct work_struct work; + enum led_brightness new_brightness; + + unsigned int led; /* For driver */ + void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); +}; + +static inline void delayed_set_status_worker(struct work_struct *work) +{ + struct delayed_led_classdev *data = + container_of(work, struct delayed_led_classdev, work); + + data->set_brightness(data, data->new_brightness); +} + +static inline void delayed_sysfs_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct delayed_led_classdev *data = container_of(led_cdev, + struct delayed_led_classdev, led_classdev); + data->new_brightness = brightness; + schedule_work(&data->work); +} + +/* HP-specific accelerometer driver ------------------------------------ */ + +/* e0 25, e0 26, e0 27, e0 28 are scan codes that the accelerometer with acpi id + * HPQ6000 sends through the keyboard bus */ +#define ACCEL_1 0x25 +#define ACCEL_2 0x26 +#define ACCEL_3 0x27 +#define ACCEL_4 0x28 + +/* For automatic insertion of the module */ +static const struct acpi_device_id lis3lv02d_device_ids[] = { + {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ + {"HPQ6000", 0}, /* HP Mobile Data Protection System PNP */ + {"HPQ6007", 0}, /* HP Mobile Data Protection System PNP */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); + +/** + * lis3lv02d_acpi_init - initialize the device for ACPI + * @lis3: pointer to the device struct + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_init(struct lis3lv02d *lis3) +{ + return 0; +} + +/** + * lis3lv02d_acpi_read - ACPI ALRD method: read a register + * @lis3: pointer to the device struct + * @reg: the register to read + * @ret: result of the operation + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) +{ + struct acpi_device *dev = lis3->bus_priv; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long lret; + acpi_status status; + + arg0.integer.value = reg; + + status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); + if (ACPI_FAILURE(status)) + return -EINVAL; + *ret = lret; + return 0; +} + +/** + * lis3lv02d_acpi_write - ACPI ALWR method: write to a register + * @lis3: pointer to the device struct + * @reg: the register to write to + * @val: the value to write + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + struct acpi_device *dev = lis3->bus_priv; + unsigned long long ret; /* Not used when writting */ + union acpi_object in_obj[2]; + struct acpi_object_list args = { 2, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = reg; + in_obj[1].type = ACPI_TYPE_INTEGER; + in_obj[1].integer.value = val; + + if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) + return -EINVAL; + + return 0; +} + +static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) +{ + lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); + pr_info("hardware type %s found\n", dmi->ident); + + return 1; +} + +/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). + * If the value is negative, the opposite of the hw value is used. */ +#define DEFINE_CONV(name, x, y, z) \ + static union axis_conversion lis3lv02d_axis_##name = \ + { .as_array = { x, y, z } } +DEFINE_CONV(normal, 1, 2, 3); +DEFINE_CONV(y_inverted, 1, -2, 3); +DEFINE_CONV(x_inverted, -1, 2, 3); +DEFINE_CONV(x_inverted_usd, -1, 2, -3); +DEFINE_CONV(z_inverted, 1, 2, -3); +DEFINE_CONV(xy_swap, 2, 1, 3); +DEFINE_CONV(xy_rotated_left, -2, 1, 3); +DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); +DEFINE_CONV(xy_swap_inverted, -2, -1, 3); +DEFINE_CONV(xy_rotated_right, 2, -1, 3); +DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); + +#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_PRODUCT_NAME, _name) \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} + +#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ + _class2, _name2, \ + _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_##_class1, _name1), \ + DMI_MATCH(DMI_##_class2, _name2), \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} +static const struct dmi_system_id lis3lv02d_dmi_ids[] = { + /* product names are truncated to match all kinds of a same model */ + AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), + AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), + AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), + AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), + AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), + AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), + AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), + AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), + AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), + AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), + AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), + AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), + AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), + AXIS_DMI_MATCH("NC854xx", "HP EliteBook 854", y_inverted), + AXIS_DMI_MATCH("NC273xx", "HP EliteBook 273", y_inverted), + /* Intel-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_I", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3603", + x_inverted), + /* AMD-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_A", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3600", + y_inverted), + AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), + AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), + AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), + AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), + AXIS_DMI_MATCH("HPB440G3", "HP ProBook 440 G3", x_inverted_usd), + AXIS_DMI_MATCH("HPB440G4", "HP ProBook 440 G4", x_inverted), + AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB450G0", "HP ProBook 450 G0", x_inverted), + AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), + AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), + AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), + AXIS_DMI_MATCH("HPB655x", "HP ProBook 655", xy_swap_inverted), + AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), + AXIS_DMI_MATCH("HPB63xx", "HP ProBook 63", xy_swap), + AXIS_DMI_MATCH("HPB64xx", "HP ProBook 64", xy_swap), + AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap), + AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted), + AXIS_DMI_MATCH("HPZBook15", "HP ZBook 15", x_inverted), + AXIS_DMI_MATCH("HPZBook17G5", "HP ZBook 17 G5", x_inverted), + AXIS_DMI_MATCH("HPZBook17", "HP ZBook 17", xy_swap_yz_inverted), + { NULL, } +/* Laptop models without axis info (yet): + * "NC6910" "HP Compaq 6910" + * "NC2400" "HP Compaq nc2400" + * "NX74x0" "HP Compaq nx74" + * "NX6325" "HP Compaq nx6325" + * "NC4400" "HP Compaq nc4400" + */ +}; + +static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) +{ + struct acpi_device *dev = lis3_dev.bus_priv; + unsigned long long ret; /* Not used when writing */ + union acpi_object in_obj[1]; + struct acpi_object_list args = { 1, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = !!value; + + acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); +} + +static struct delayed_led_classdev hpled_led = { + .led_classdev = { + .name = "hp::hddprotect", + .default_trigger = "none", + .brightness_set = delayed_sysfs_set, + .flags = LED_CORE_SUSPENDRESUME, + }, + .set_brightness = hpled_set, +}; + +static bool hp_accel_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + static bool extended; + + if (str & I8042_STR_AUXDATA) + return false; + + if (data == 0xe0) { + extended = true; + return true; + } else if (unlikely(extended)) { + extended = false; + + switch (data) { + case ACCEL_1: + case ACCEL_2: + case ACCEL_3: + case ACCEL_4: + return true; + default: + serio_interrupt(port, 0xe0, 0); + return false; + } + } + + return false; +} + +static int lis3lv02d_probe(struct platform_device *device) +{ + int ret; + + lis3_dev.bus_priv = ACPI_COMPANION(&device->dev); + lis3_dev.init = lis3lv02d_acpi_init; + lis3_dev.read = lis3lv02d_acpi_read; + lis3_dev.write = lis3lv02d_acpi_write; + + /* obtain IRQ number of our device from ACPI */ + ret = platform_get_irq_optional(device, 0); + if (ret > 0) + lis3_dev.irq = ret; + + /* If possible use a "standard" axes order */ + if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { + pr_info("Using custom axes %d,%d,%d\n", + lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); + } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { + pr_info("laptop model unknown, using default axes configuration\n"); + lis3_dev.ac = lis3lv02d_axis_normal; + } + + /* call the core layer do its init */ + ret = lis3lv02d_init_device(&lis3_dev); + if (ret) + return ret; + + /* filter to remove HPQ6000 accelerometer data + * from keyboard bus stream */ + if (strstr(dev_name(&device->dev), "HPQ6000")) + i8042_install_filter(hp_accel_i8042_filter); + + INIT_WORK(&hpled_led.work, delayed_set_status_worker); + ret = led_classdev_register(NULL, &hpled_led.led_classdev); + if (ret) { + i8042_remove_filter(hp_accel_i8042_filter); + lis3lv02d_joystick_disable(&lis3_dev); + lis3lv02d_poweroff(&lis3_dev); + flush_work(&hpled_led.work); + lis3lv02d_remove_fs(&lis3_dev); + return ret; + } + + return ret; +} + +static void lis3lv02d_remove(struct platform_device *device) +{ + i8042_remove_filter(hp_accel_i8042_filter); + lis3lv02d_joystick_disable(&lis3_dev); + lis3lv02d_poweroff(&lis3_dev); + + led_classdev_unregister(&hpled_led.led_classdev); + flush_work(&hpled_led.work); + + lis3lv02d_remove_fs(&lis3_dev); +} + +static int __maybe_unused lis3lv02d_suspend(struct device *dev) +{ + /* make sure the device is off when we suspend */ + lis3lv02d_poweroff(&lis3_dev); + return 0; +} + +static int __maybe_unused lis3lv02d_resume(struct device *dev) +{ + lis3lv02d_poweron(&lis3_dev); + return 0; +} + +static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume); + +/* For the HP MDPS aka 3D Driveguard */ +static struct platform_driver lis3lv02d_driver = { + .probe = lis3lv02d_probe, + .remove_new = lis3lv02d_remove, + .driver = { + .name = "hp_accel", + .pm = &hp_accel_pm, + .acpi_match_table = lis3lv02d_device_ids, + }, +}; +module_platform_driver(lis3lv02d_driver); + +MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/hp/tc1100-wmi.c b/drivers/platform/x86/hp/tc1100-wmi.c new file mode 100644 index 0000000000..5298b0f680 --- /dev/null +++ b/drivers/platform/x86/hp/tc1100-wmi.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HP Compaq TC1100 Tablet WMI Extras Driver + * + * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk> + * Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com> + * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> + * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/platform_device.h> + +#define GUID "C364AC71-36DB-495A-8494-B439D472A505" + +#define TC1100_INSTANCE_WIRELESS 1 +#define TC1100_INSTANCE_JOGDIAL 2 + +MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho"); +MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505"); + +static struct platform_device *tc1100_device; + +struct tc1100_data { + u32 wireless; + u32 jogdial; +}; + +#ifdef CONFIG_PM +static struct tc1100_data suspend_data; +#endif + +/* -------------------------------------------------------------------------- + Device Management + -------------------------------------------------------------------------- */ + +static int get_state(u32 *out, u8 instance) +{ + u32 tmp; + acpi_status status; + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + + if (!out) + return -EINVAL; + + if (instance > 2) + return -ENODEV; + + status = wmi_query_block(GUID, instance, &result); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obj = (union acpi_object *) result.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) { + tmp = obj->integer.value; + } else { + tmp = 0; + } + + if (result.length > 0) + kfree(result.pointer); + + switch (instance) { + case TC1100_INSTANCE_WIRELESS: + *out = (tmp == 3) ? 1 : 0; + return 0; + case TC1100_INSTANCE_JOGDIAL: + *out = (tmp == 1) ? 0 : 1; + return 0; + default: + return -ENODEV; + } +} + +static int set_state(u32 *in, u8 instance) +{ + u32 value; + acpi_status status; + struct acpi_buffer input; + + if (!in) + return -EINVAL; + + if (instance > 2) + return -ENODEV; + + switch (instance) { + case TC1100_INSTANCE_WIRELESS: + value = (*in) ? 1 : 2; + break; + case TC1100_INSTANCE_JOGDIAL: + value = (*in) ? 0 : 1; + break; + default: + return -ENODEV; + } + + input.length = sizeof(u32); + input.pointer = &value; + + status = wmi_set_block(GUID, instance, &input); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; +} + +/* -------------------------------------------------------------------------- + FS Interface (/sys) + -------------------------------------------------------------------------- */ + +/* + * Read/ write bool sysfs macro + */ +#define show_set_bool(value, instance) \ +static ssize_t \ +show_bool_##value(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + u32 result; \ + acpi_status status = get_state(&result, instance); \ + if (ACPI_SUCCESS(status)) \ + return sprintf(buf, "%d\n", result); \ + return sprintf(buf, "Read error\n"); \ +} \ +\ +static ssize_t \ +set_bool_##value(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + u32 tmp = simple_strtoul(buf, NULL, 10); \ + acpi_status status = set_state(&tmp, instance); \ + if (ACPI_FAILURE(status)) \ + return -EINVAL; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, \ + show_bool_##value, set_bool_##value); + +show_set_bool(wireless, TC1100_INSTANCE_WIRELESS); +show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL); + +static struct attribute *tc1100_attributes[] = { + &dev_attr_wireless.attr, + &dev_attr_jogdial.attr, + NULL +}; + +static const struct attribute_group tc1100_attribute_group = { + .attrs = tc1100_attributes, +}; + +/* -------------------------------------------------------------------------- + Driver Model + -------------------------------------------------------------------------- */ + +static int __init tc1100_probe(struct platform_device *device) +{ + return sysfs_create_group(&device->dev.kobj, &tc1100_attribute_group); +} + + +static void tc1100_remove(struct platform_device *device) +{ + sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group); +} + +#ifdef CONFIG_PM +static int tc1100_suspend(struct device *dev) +{ + int ret; + + ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS); + if (ret) + return ret; + + ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL); + if (ret) + return ret; + + return 0; +} + +static int tc1100_resume(struct device *dev) +{ + int ret; + + ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS); + if (ret) + return ret; + + ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops tc1100_pm_ops = { + .suspend = tc1100_suspend, + .resume = tc1100_resume, + .freeze = tc1100_suspend, + .restore = tc1100_resume, +}; +#endif + +static struct platform_driver tc1100_driver = { + .driver = { + .name = "tc1100-wmi", +#ifdef CONFIG_PM + .pm = &tc1100_pm_ops, +#endif + }, + .remove_new = tc1100_remove, +}; + +static int __init tc1100_init(void) +{ + int error; + + if (!wmi_has_guid(GUID)) + return -ENODEV; + + tc1100_device = platform_device_alloc("tc1100-wmi", PLATFORM_DEVID_NONE); + if (!tc1100_device) + return -ENOMEM; + + error = platform_device_add(tc1100_device); + if (error) + goto err_device_put; + + error = platform_driver_probe(&tc1100_driver, tc1100_probe); + if (error) + goto err_device_del; + + pr_info("HP Compaq TC1100 Tablet WMI Extras loaded\n"); + return 0; + + err_device_del: + platform_device_del(tc1100_device); + err_device_put: + platform_device_put(tc1100_device); + return error; +} + +static void __exit tc1100_exit(void) +{ + platform_device_unregister(tc1100_device); + platform_driver_unregister(&tc1100_driver); +} + +module_init(tc1100_init); +module_exit(tc1100_exit); |