Re: [External] Re: [PATCH v2 3/3] platform/x86: think-lmi: Add WMI interface support on Lenovo platforms

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for the review Hans,

On 2021-05-19 1:06 p.m., Hans de Goede wrote:
> Hi,
> 
> On 5/9/21 3:57 AM, Mark Pearson wrote:
<snip>
>>
>> diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
>> index 8ea59fea4..31d1becfa 100644
>> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
>> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
>> @@ -185,6 +185,17 @@ Description:
>>  					A write only value that when used in tandem with
>>  					current_password will reset a system or admin password.
>>  
>> +		encoding:
>> +					For platforms that require it (currently Lenovo) the
>> +					encoding method that is used. This can be either "ascii"
>> +					or "scancode". Default is set to "ascii"
>> +
>> +		kbdlang:
>> +					For platforms that require it (currently Lenovo only) the
>> +					keyboard language method that is used. This is generally a
>> +					two char code (e.g. "us", "fr", "gr") and may vary per platform.
>> +					Default is set to "us"
>> +
> 
> I should have caught this during my review of v1, these are Lenovo specific, so please prefix
> them with lenovo_ (the same is already done for dell specific sysfs attributes) and move them
> to a separate "Lenovo specific class extensions" part of the doc.
> 
No problem

<snip>

>>  
>>  What:		/sys/class/firmware-attributes/*/attributes/pending_reboot
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index b0e1e5f65..0511c54fc 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -639,6 +639,18 @@ config THINKPAD_ACPI_HOTKEY_POLL
>>  	  If you are not sure, say Y here.  The driver enables polling only if
>>  	  it is strictly necessary to do so.
>>  
>> +config THINKPAD_LMI
>> +	tristate "Lenovo WMI-based systems management driver"
>> +	default m
> 
> default n (or no default at all) please.
Ack

<snip>
>> diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c
>> new file mode 100644
>> index 000000000..2fa989e98
>> --- /dev/null
>> +++ b/drivers/platform/x86/think-lmi.c
>> @@ -0,0 +1,890 @@
<snip>
>> +
>> +/* ---- Authentication sysfs --------------------------------------------------------- */
>> +static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +					  char *buf)
>> +{
>> +	struct tlmi_pwd_setting *setting = container_of(kobj, struct tlmi_pwd_setting, kobj);
>> +
>> +	if (setting->valid)
>> +		return sysfs_emit(buf, "1\n");
>> +	else
>> +		return sysfs_emit(buf, "0\n");
> 
> Maybe use:
> 
> 	sysfs_emit(buf, "%d\n", settings->valid);
> 
> instead?
Definitely :)

> 
> 
>> +}
>> +
>> +static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled);
>> +
>> +static ssize_t current_password_store(struct kobject *kobj,
>> +				      struct kobj_attribute *attr,
>> +				      const char *buf, size_t count)
>> +{
>> +	struct tlmi_pwd_setting *setting = container_of(kobj, struct tlmi_pwd_setting, kobj);
>> +	int length;
>> +
>> +	length = strlen(buf);
>> +	if (buf[length-1] == '\n')
>> +		length--;
>> +
>> +	if (length >= TLMI_PWD_MAXLEN)
>> +		return -EINVAL;
>> +
>> +	memcpy(setting->password, buf, length);
>> +	setting->password[length] = '\0';
>> +	return count;
>> +}
>> +
>> +static struct kobj_attribute auth_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)
>> +{
>> +	struct tlmi_pwd_setting *setting = container_of(kobj, struct tlmi_pwd_setting, kobj);
>> +	char *p, *new_pwd;
>> +	char *auth_str;
>> +	int ret = 0, len;
>> +
>> +	if (!capable(CAP_SYS_ADMIN))
>> +		return -EPERM;
>> +
>> +	if (!tlmi_priv.can_set_bios_password)
>> +		return -EOPNOTSUPP;
>> +
>> +	new_pwd = kstrdup(buf, GFP_KERNEL);
>> +	if (!new_pwd)
>> +		return -ENOMEM;
>> +
>> +	p = strchr(new_pwd, '\n');
>> +	if (p)
>> +		*p = '\0';
>> +
>> +	if (strlen(new_pwd) > setting->maxlen) {
>> +		ret = -EINVAL;
>> +		goto out;
>> +	}
>> +
>> +	/* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
>> +	len = strlen(setting->password) + strlen(encoding_options[setting->encoding])
>> +		+ strlen(setting->kbdlang) + 3 /* type */
>> +		+ strlen(new_pwd) + 6 /* punctuation and terminator*/;
>> +	auth_str = kzalloc(len, GFP_KERNEL);
>> +	snprintf(auth_str, len, "%s,%s,%s,%s,%s;",
>> +		 setting->pwd_type, setting->password, new_pwd,
>> +		 encoding_options[setting->encoding], setting->kbdlang);
>> +	ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str);
> 
> So I guess on success any subsequent calls need to use the new password,
> so the user is expected to write the new password to the current_password
> file after changing the password this way?
> 
> I just checked the dell-wmi-sysman code and that does this:
> 
>         /* clear current_password here and use user input from wmi_priv.current_password */
>         if (!ret)
>                 memset(current_password, 0, MAX_BUFF);
> 
> Where current_password points to either the user or admin cached password,
> depending on which one is being changed.
> 
> So that seems to work the same way as what you are doing here (the user needs to
> write the new password to current_password after changing it through the
> new_password sysfs attribute). Can you add a patch to the patch-set documenting
> this in Documentation/ABI/testing/sysfs-class-firmware-attributes ?
> 
> Also you may want to consider clearing out the old cached password on success
> like the Dell code is doing.
> 
Would you have any objections/concerns if, on a successful password
update, this function just updates the stored password setting to the
new password.
Seems nicer from a user point of view then having to go and feed the
current_password field again.
e.g: strncpy(new_pwd, setting->password, TLMI_PWD_MAXLEN)

I know it would make Dell and Lenovo operate differently (I can add that
detail to the documentation) but it just feels like a nicer design.

<snip>

>> +
>> +static struct kobj_attribute auth_kbdlang = __ATTR_RW(kbdlang);
>> +
>> +{
>> +	if (strcmp(kobj->name, "Admin") == 0)
>> +		return sprintf(buf, "bios-admin\n");
>> +	else if (strcmp(kobj->name, "System") == 0)
>> +		return sprintf(buf, "power-on\n");
>> +	return -EIO;
>> +}
> 
> IMHO it would be nice to add a "const char *role" to struct tlmi_pwd_setting and
> then change this to:
> 
>> +static ssize_t encoding_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +			 char *buf)
>> +{
>> +	struct tlmi_pwd_setting *setting = container_of(kobj, struct tlmi_pwd_setting, kobj);
>> +
>> +	return sysfs_emit(buf, "%s\n", setting->role);
>> +}
> 
> That would make this function consistent with the other password related functions.
Agreed - I'll make that change

<snip>

>> +static ssize_t current_value_store(struct kobject *kobj,
>> +		struct kobj_attribute *attr,
>> +		const char *buf, size_t count)
>> +{
>> +	struct tlmi_attr_setting *setting = container_of(kobj, struct tlmi_attr_setting, kobj);
>> +	int alloc_len, auth_len = 0;
>> +	int str_ix = 0;
>> +	char *auth_str = NULL;
>> +	char *set_str, *new_setting, *p;
>> +	int ret;
>> +
>> +	if (!tlmi_priv.can_set_bios_settings)
>> +		return -EOPNOTSUPP;
>> +
>> +	new_setting = kstrdup(buf, GFP_KERNEL);
>> +	if (!new_setting)
>> +		return -ENOMEM;
>> +	p = strchr(new_setting, '\n');
>> +	if (p)
>> +		*p = '\0';
>> +
>> +	alloc_len = strlen(setting->display_name) + 3 + strlen(new_setting);
>> +
>> +	if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password) {
>> +		auth_len += strlen(tlmi_priv.pwd_admin->password) + 1
>> +			+ strlen(encoding_options[tlmi_priv.pwd_admin->encoding]) + 1
>> +			+ strlen(tlmi_priv.pwd_admin->kbdlang) + 1 + 1;
>> +		auth_str = kmalloc(auth_len, GFP_KERNEL);
>> +		if (!auth_str) {
>> +			ret = -ENOMEM;
>> +			goto out;
> 
> You end-up kfree-ing set_str here without it being initialized (compiler warning?)
> Please just initialize all 3 strings which you kfree at the end to NULL, I know
> this is not necessary for at least the new_setting string but it makes it easier
> for someone reading the code to verify that the kfree() does not happens on
> an uninitialized pointer.

Agreed. Thanks.
> 
>> +		}
>> +
>> +		sprintf(auth_str, "%s,%s,%s;",
>> +				tlmi_priv.pwd_admin->password,
>> +				encoding_options[tlmi_priv.pwd_admin->encoding],
>> +				tlmi_priv.pwd_admin->kbdlang);
>> +		alloc_len += auth_len;
>> +	}
>> +
>> +	set_str = kmalloc(alloc_len, GFP_KERNEL);
>> +	if (!set_str) {
>> +		ret = -ENOMEM;
>> +		goto out;
>> +	}
>> +
>> +	str_ix += sprintf(set_str, "%s,%s", setting->display_name, new_setting);
>> +
>> +	if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password)
> 
> Maybe use:
> 
> 	if (auth_str) 
> 
> here? This way if the condition of the above if block ever changes, you can't
> end up passing a NULL auth_Str to the sprintf.

Agreed - makes sense
<snip>

>> +
>> +static const struct sysfs_ops tlmi_kobj_sysfs_ops = {
>> +	.show	= tlmi_attr_show,
>> +	.store	= tlmi_attr_store,
>> +};
>> +
>> +static struct kobj_type attr_name_ktype = {
>> +	.sysfs_ops	= &tlmi_kobj_sysfs_ops,
>> +};
> 
> You need to re-add a release function here, sorry I was not clear
> about that in my review of v1. Since you do a kobject_put and not a
> direct kfree() on cleanup, you need to define a release function to
> do the actual free once the refcount drops to 0.
> 
> And since you have 2 kind of structs embedding the kobjects you will
> need 2 release functions:
> 
> static void tmli_attr_setting_release(struct kobject *kobj)
> {
> 	struct tlmi_attr_setting *setting = container_of(kobj, struct tlmi_attr_setting, kobj);
> 
> 	kfree(setting);
> }
> 
> static void tmli_pwd_setting_release(struct kobject *kobj)
> {
> 	struct tlmi_pwd_setting *pwd = container_of(kobj, struct tlmi_pwd_setting, kobj);
> 
> 	kfree(pwd);
> }
> 
> And then have 2 kobject-types:
> 
> static struct kobj_type tmli_attr_setting_ktype = {
> 	.release	= tmli_attr_setting_release,
> 	.sysfs_ops	= &tlmi_kobj_sysfs_ops,
> };
> 
> static struct kobj_type tmli_pwd_setting_ktype = {
> 	.release	= tmli_pwd_setting_release,
> 	.sysfs_ops	= &tlmi_kobj_sysfs_ops,
> };
> 
> And use the right type when creating the objects.
> 
Ah - got it. I had a feeling I was missing something here. Thanks!

<snip>

>> +
>> +module_wmi_driver(tlmi_driver);
>> diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h
>> new file mode 100644
>> index 000000000..2c1484304
>> --- /dev/null
>> +++ b/drivers/platform/x86/think-lmi.h
>> @@ -0,0 +1,73 @@
<snip>>> +
>> +/* password setting details */
>> +struct tlmi_pwd_setting {
>> +	struct kobject kobj;
>> +	bool valid;
>> +	char display_name[TLMI_PWDTYPE_MAXLEN];
>> +	char password[TLMI_PWD_MAXLEN];
>> +	char pwd_type[TLMI_PWDTYPE_LEN];
> 
> Note you can just make this a "const char *" and init it like this:
> 
> 	pwd_type = "pop";
> 
> The "pop" string will be part of the const data section so its
> address will still be valid after the function has exited.
> 
Ack




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux