diff options
Diffstat (limited to 'drivers/platform/x86/think-lmi.c')
| -rw-r--r-- | drivers/platform/x86/think-lmi.c | 238 | 
1 files changed, 176 insertions, 62 deletions
| diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 79346881cadb..3a396b763c49 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -15,7 +15,7 @@  #include <linux/errno.h>  #include <linux/fs.h>  #include <linux/mutex.h> -#include <linux/string.h> +#include <linux/string_helpers.h>  #include <linux/types.h>  #include <linux/dmi.h>  #include <linux/wmi.h> @@ -198,14 +198,6 @@ static struct think_lmi tlmi_priv;  static struct class *fw_attr_class;  static DEFINE_MUTEX(tlmi_mutex); -/* ------ Utility functions ------------*/ -/* Strip out CR if one is present */ -static void strip_cr(char *str) -{ -	char *p = strchrnul(str, '\n'); -	*p = '\0'; -} -  /* Convert BIOS WMI error string to suitable error code */  static int tlmi_errstr_to_err(const char *errstr)  { @@ -411,7 +403,7 @@ static ssize_t current_password_store(struct kobject *kobj,  	strscpy(setting->password, buf, setting->maxlen);  	/* Strip out CR if one is present, setting password won't work if it is present */ -	strip_cr(setting->password); +	strreplace(setting->password, '\n', '\0');  	return count;  } @@ -432,13 +424,11 @@ static ssize_t new_password_store(struct kobject *kobj,  	if (!tlmi_priv.can_set_bios_password)  		return -EOPNOTSUPP; -	new_pwd = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present, setting password won't work if it is present */ +	new_pwd = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!new_pwd)  		return -ENOMEM; -	/* Strip out CR if one is present, setting password won't work if it is present */ -	strip_cr(new_pwd); -  	/* Use lock in case multiple WMI operations needed */  	mutex_lock(&tlmi_mutex); @@ -709,13 +699,11 @@ static ssize_t cert_to_password_store(struct kobject *kobj,  	if (!setting->signature || !setting->signature[0])  		return -EACCES; -	passwd = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present */ +	passwd = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!passwd)  		return -ENOMEM; -	/* Strip out CR if one is present */ -	strip_cr(passwd); -  	/* Format: 'Password,Signature' */  	auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature);  	if (!auth_str) { @@ -765,11 +753,10 @@ static ssize_t certificate_store(struct kobject *kobj,  		return ret ?: count;  	} -	new_cert = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present */ +	new_cert = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!new_cert)  		return -ENOMEM; -	/* Strip out CR if one is present */ -	strip_cr(new_cert);  	if (setting->cert_installed) {  		/* Certificate is installed so this is an update */ @@ -817,13 +804,11 @@ static ssize_t signature_store(struct kobject *kobj,  	if (!tlmi_priv.certificate_support)  		return -EOPNOTSUPP; -	new_signature = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present */ +	new_signature = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!new_signature)  		return -ENOMEM; -	/* Strip out CR if one is present */ -	strip_cr(new_signature); -  	/* Free any previous signature */  	kfree(setting->signature);  	setting->signature = new_signature; @@ -846,13 +831,11 @@ static ssize_t save_signature_store(struct kobject *kobj,  	if (!tlmi_priv.certificate_support)  		return -EOPNOTSUPP; -	new_signature = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present */ +	new_signature = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!new_signature)  		return -ENOMEM; -	/* Strip out CR if one is present */ -	strip_cr(new_signature); -  	/* Free any previous signature */  	kfree(setting->save_signature);  	setting->save_signature = new_signature; @@ -930,7 +913,7 @@ static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *at  static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)  {  	struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); -	char *item, *value, *p; +	char *item, *value;  	int ret;  	ret = tlmi_setting(setting->index, &item, LENOVO_BIOS_SETTING_GUID); @@ -943,8 +926,7 @@ static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *a  		ret = -EINVAL;  	else {  		/* On Workstations remove the Options part after the value */ -		p = strchrnul(value, ';'); -		*p = '\0'; +		strreplace(value, ';', '\0');  		ret = sysfs_emit(buf, "%s\n", value + 1);  	}  	kfree(item); @@ -985,12 +967,17 @@ static ssize_t current_value_store(struct kobject *kobj,  	if (!tlmi_priv.can_set_bios_settings)  		return -EOPNOTSUPP; -	new_setting = kstrdup(buf, GFP_KERNEL); -	if (!new_setting) -		return -ENOMEM; +	/* +	 * If we are using bulk saves a reboot should be done once save has +	 * been called +	 */ +	if (tlmi_priv.save_mode == TLMI_SAVE_BULK && tlmi_priv.reboot_required) +		return -EPERM;  	/* Strip out CR if one is present */ -	strip_cr(new_setting); +	new_setting = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL); +	if (!new_setting) +		return -ENOMEM;  	/* Use lock in case multiple WMI operations needed */  	mutex_lock(&tlmi_mutex); @@ -1011,10 +998,11 @@ static ssize_t current_value_store(struct kobject *kobj,  		ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str);  		if (ret)  			goto out; -		ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, -				tlmi_priv.pwd_admin->save_signature); -		if (ret) -			goto out; +		if (tlmi_priv.save_mode == TLMI_SAVE_BULK) +			tlmi_priv.save_required = true; +		else +			ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, +					       tlmi_priv.pwd_admin->save_signature);  	} else if (tlmi_priv.opcode_support) {  		/*  		 * If opcode support is present use that interface. @@ -1033,14 +1021,17 @@ static ssize_t current_value_store(struct kobject *kobj,  		if (ret)  			goto out; -		if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { -			ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", -						  tlmi_priv.pwd_admin->password); -			if (ret) -				goto out; +		if (tlmi_priv.save_mode == TLMI_SAVE_BULK) { +			tlmi_priv.save_required = true; +		} else { +			if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { +				ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", +							  tlmi_priv.pwd_admin->password); +				if (ret) +					goto out; +			} +			ret = tlmi_save_bios_settings("");  		} - -		ret = tlmi_save_bios_settings("");  	} else { /* old non-opcode based authentication method (deprecated) */  		if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {  			auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", @@ -1068,10 +1059,14 @@ static ssize_t current_value_store(struct kobject *kobj,  		if (ret)  			goto out; -		if (auth_str) -			ret = tlmi_save_bios_settings(auth_str); -		else -			ret = tlmi_save_bios_settings(""); +		if (tlmi_priv.save_mode == TLMI_SAVE_BULK) { +			tlmi_priv.save_required = true; +		} else { +			if (auth_str) +				ret = tlmi_save_bios_settings(auth_str); +			else +				ret = tlmi_save_bios_settings(""); +		}  	}  	if (!ret && !tlmi_priv.pending_changes) {  		tlmi_priv.pending_changes = true; @@ -1152,6 +1147,107 @@ static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *  static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); +static const char * const save_mode_strings[] = { +	[TLMI_SAVE_SINGLE] = "single", +	[TLMI_SAVE_BULK] = "bulk", +	[TLMI_SAVE_SAVE] = "save" +}; + +static ssize_t save_settings_show(struct kobject *kobj, struct kobj_attribute *attr, +				  char *buf) +{ +	/* Check that setting is valid */ +	if (WARN_ON(tlmi_priv.save_mode < TLMI_SAVE_SINGLE || +		    tlmi_priv.save_mode > TLMI_SAVE_BULK)) +		return -EIO; +	return sysfs_emit(buf, "%s\n", save_mode_strings[tlmi_priv.save_mode]); +} + +static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *attr, +				   const char *buf, size_t count) +{ +	char *auth_str = NULL; +	int ret = 0; +	int cmd; + +	cmd = sysfs_match_string(save_mode_strings, buf); +	if (cmd < 0) +		return cmd; + +	/* Use lock in case multiple WMI operations needed */ +	mutex_lock(&tlmi_mutex); + +	switch (cmd) { +	case TLMI_SAVE_SINGLE: +	case TLMI_SAVE_BULK: +		tlmi_priv.save_mode = cmd; +		goto out; +	case TLMI_SAVE_SAVE: +		/* Check if supported*/ +		if (!tlmi_priv.can_set_bios_settings || +		    tlmi_priv.save_mode == TLMI_SAVE_SINGLE) { +			ret = -EOPNOTSUPP; +			goto out; +		} +		/* Check there is actually something to save */ +		if (!tlmi_priv.save_required) { +			ret = -ENOENT; +			goto out; +		} +		/* Check if certificate authentication is enabled and active */ +		if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) { +			if (!tlmi_priv.pwd_admin->signature || +			    !tlmi_priv.pwd_admin->save_signature) { +				ret = -EINVAL; +				goto out; +			} +			ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, +					       tlmi_priv.pwd_admin->save_signature); +			if (ret) +				goto out; +		} else if (tlmi_priv.opcode_support) { +			if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { +				ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", +							  tlmi_priv.pwd_admin->password); +				if (ret) +					goto out; +			} +			ret = tlmi_save_bios_settings(""); +		} else { /* old non-opcode based authentication method (deprecated) */ +			if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { +				auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", +						     tlmi_priv.pwd_admin->password, +						     encoding_options[tlmi_priv.pwd_admin->encoding], +						     tlmi_priv.pwd_admin->kbdlang); +				if (!auth_str) { +					ret = -ENOMEM; +					goto out; +				} +			} + +			if (auth_str) +				ret = tlmi_save_bios_settings(auth_str); +			else +				ret = tlmi_save_bios_settings(""); +		} +		tlmi_priv.save_required = false; +		tlmi_priv.reboot_required = true; + +		if (!ret && !tlmi_priv.pending_changes) { +			tlmi_priv.pending_changes = true; +			/* let userland know it may need to check reboot pending again */ +			kobject_uevent(&tlmi_priv.class_dev->kobj, KOBJ_CHANGE); +		} +		break; +	} +out: +	mutex_unlock(&tlmi_mutex); +	kfree(auth_str); +	return ret ?: count; +} + +static struct kobj_attribute save_settings = __ATTR_RW(save_settings); +  /* ---- Debug interface--------------------------------------------------------- */  static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr,  				const char *buf, size_t count) @@ -1163,13 +1259,11 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr  	if (!tlmi_priv.can_debug_cmd)  		return -EOPNOTSUPP; -	new_setting = kstrdup(buf, GFP_KERNEL); +	/* Strip out CR if one is present */ +	new_setting = kstrdup_and_replace(buf, '\n', '\0', GFP_KERNEL);  	if (!new_setting)  		return -ENOMEM; -	/* Strip out CR if one is present */ -	strip_cr(new_setting); -  	if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {  		auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",  				tlmi_priv.pwd_admin->password, @@ -1221,6 +1315,8 @@ static void tlmi_release_attr(void)  		}  	}  	sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); +	sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr); +  	if (tlmi_priv.can_debug_cmd && debug_support)  		sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); @@ -1248,6 +1344,24 @@ static void tlmi_release_attr(void)  	kset_unregister(tlmi_priv.authentication_kset);  } +static int tlmi_validate_setting_name(struct kset *attribute_kset, char *name) +{ +	struct kobject *duplicate; + +	if (!strcmp(name, "Reserved")) +		return -EINVAL; + +	duplicate = kset_find_obj(attribute_kset, name); +	if (duplicate) { +		pr_debug("Duplicate attribute name found - %s\n", name); +		/* kset_find_obj() returns a reference */ +		kobject_put(duplicate); +		return -EBUSY; +	} + +	return 0; +} +  static int tlmi_sysfs_init(void)  {  	int i, ret; @@ -1276,10 +1390,8 @@ static int tlmi_sysfs_init(void)  			continue;  		/* check for duplicate or reserved values */ -		if (kset_find_obj(tlmi_priv.attribute_kset, tlmi_priv.setting[i]->display_name) || -		    !strcmp(tlmi_priv.setting[i]->display_name, "Reserved")) { -			pr_debug("duplicate or reserved attribute name found - %s\n", -				tlmi_priv.setting[i]->display_name); +		if (tlmi_validate_setting_name(tlmi_priv.attribute_kset, +					       tlmi_priv.setting[i]->display_name) < 0) {  			kfree(tlmi_priv.setting[i]->possible_values);  			kfree(tlmi_priv.setting[i]);  			tlmi_priv.setting[i] = NULL; @@ -1302,6 +1414,10 @@ static int tlmi_sysfs_init(void)  	if (ret)  		goto fail_create_attr; +	ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr); +	if (ret) +		goto fail_create_attr; +  	if (tlmi_priv.can_debug_cmd && debug_support) {  		ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr);  		if (ret) @@ -1431,7 +1547,6 @@ static int tlmi_analyze(void)  	for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) {  		struct tlmi_attr_setting *setting;  		char *item = NULL; -		char *p;  		tlmi_priv.setting[i] = NULL;  		ret = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID); @@ -1448,8 +1563,7 @@ static int tlmi_analyze(void)  		strreplace(item, '/', '\\');  		/* Remove the value part */ -		p = strchrnul(item, ','); -		*p = '\0'; +		strreplace(item, ',', '\0');  		/* Create a setting entry */  		setting = kzalloc(sizeof(*setting), GFP_KERNEL); |