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