Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86: think-lmi: Add bulk save feature

On Lenovo platforms there is a limitation in the number of times an
attribute can be saved. This is an architectural limitation and it limits
the number of attributes that can be modified to 48.
A solution for this is instead of the attribute being saved after every
modification allow a user to bulk set the attributes and then trigger a
final save. This allows unlimited attributes.

This patch introduces a save_settings attribute that can be configured to
either single or bulk mode by the user.
Single mode is the default but customers who want to avoid the 48
attribute limit can enable bulk mode.

Displaying the save_settings attribute will display the enabled mode.

When in bulk mode writing 'save' to the save_settings attribute will
trigger a save. Once this has been done a reboot is required before more
attributes can be modified.

Signed-off-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Link: https://lore.kernel.org/r/20230919141530.4805-1-mpearson-lenovo@squebb.ca
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>

authored by

Mark Pearson and committed by
Hans de Goede
318d9784 423c3361

+183 -15
+30
Documentation/ABI/testing/sysfs-class-firmware-attributes
··· 383 383 Note that any changes to this attribute requires a reboot 384 384 for changes to take effect. 385 385 386 + What: /sys/class/firmware-attributes/*/attributes/save_settings 387 + Date: August 2023 388 + KernelVersion: 6.6 389 + Contact: Mark Pearson <mpearson-lenovo@squebb.ca> 390 + Description: 391 + On Lenovo platforms there is a limitation in the number of times an attribute can be 392 + saved. This is an architectural limitation and it limits the number of attributes 393 + that can be modified to 48. 394 + A solution for this is instead of the attribute being saved after every modification, 395 + to allow a user to bulk set the attributes, and then trigger a final save. This allows 396 + unlimited attributes. 397 + 398 + Read the attribute to check what save mode is enabled (single or bulk). 399 + E.g: 400 + # cat /sys/class/firmware-attributes/thinklmi/attributes/save_settings 401 + single 402 + 403 + Write the attribute with 'bulk' to enable bulk save mode. 404 + Write the attribute with 'single' to enable saving, after every attribute set. 405 + The default setting is single mode. 406 + E.g: 407 + # echo bulk > /sys/class/firmware-attributes/thinklmi/attributes/save_settings 408 + 409 + When in bulk mode write 'save' to trigger a save of all currently modified attributes. 410 + Note, once a save has been triggered, in bulk mode, attributes can no longer be set and 411 + will return a permissions error. This is to prevent users hitting the 48+ save limitation 412 + (which requires entering the BIOS to clear the error condition) 413 + E.g: 414 + # echo save > /sys/class/firmware-attributes/thinklmi/attributes/save_settings 415 + 386 416 What: /sys/class/firmware-attributes/*/attributes/debug_cmd 387 417 Date: July 2021 388 418 KernelVersion: 5.14
+137 -15
drivers/platform/x86/think-lmi.c
··· 985 985 if (!tlmi_priv.can_set_bios_settings) 986 986 return -EOPNOTSUPP; 987 987 988 + /* 989 + * If we are using bulk saves a reboot should be done once save has 990 + * been called 991 + */ 992 + if (tlmi_priv.save_mode == TLMI_SAVE_BULK && tlmi_priv.reboot_required) 993 + return -EPERM; 994 + 988 995 new_setting = kstrdup(buf, GFP_KERNEL); 989 996 if (!new_setting) 990 997 return -ENOMEM; ··· 1018 1011 ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str); 1019 1012 if (ret) 1020 1013 goto out; 1021 - ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, 1022 - tlmi_priv.pwd_admin->save_signature); 1023 - if (ret) 1024 - goto out; 1014 + if (tlmi_priv.save_mode == TLMI_SAVE_BULK) 1015 + tlmi_priv.save_required = true; 1016 + else 1017 + ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, 1018 + tlmi_priv.pwd_admin->save_signature); 1025 1019 } else if (tlmi_priv.opcode_support) { 1026 1020 /* 1027 1021 * If opcode support is present use that interface. ··· 1041 1033 if (ret) 1042 1034 goto out; 1043 1035 1044 - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { 1045 - ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", 1046 - tlmi_priv.pwd_admin->password); 1047 - if (ret) 1048 - goto out; 1036 + if (tlmi_priv.save_mode == TLMI_SAVE_BULK) { 1037 + tlmi_priv.save_required = true; 1038 + } else { 1039 + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { 1040 + ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", 1041 + tlmi_priv.pwd_admin->password); 1042 + if (ret) 1043 + goto out; 1044 + } 1045 + ret = tlmi_save_bios_settings(""); 1049 1046 } 1050 - 1051 - ret = tlmi_save_bios_settings(""); 1052 1047 } else { /* old non-opcode based authentication method (deprecated) */ 1053 1048 if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { 1054 1049 auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", ··· 1079 1068 if (ret) 1080 1069 goto out; 1081 1070 1082 - if (auth_str) 1083 - ret = tlmi_save_bios_settings(auth_str); 1084 - else 1085 - ret = tlmi_save_bios_settings(""); 1071 + if (tlmi_priv.save_mode == TLMI_SAVE_BULK) { 1072 + tlmi_priv.save_required = true; 1073 + } else { 1074 + if (auth_str) 1075 + ret = tlmi_save_bios_settings(auth_str); 1076 + else 1077 + ret = tlmi_save_bios_settings(""); 1078 + } 1086 1079 } 1087 1080 if (!ret && !tlmi_priv.pending_changes) { 1088 1081 tlmi_priv.pending_changes = true; ··· 1167 1152 1168 1153 static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); 1169 1154 1155 + static const char * const save_mode_strings[] = { 1156 + [TLMI_SAVE_SINGLE] = "single", 1157 + [TLMI_SAVE_BULK] = "bulk", 1158 + [TLMI_SAVE_SAVE] = "save" 1159 + }; 1160 + 1161 + static ssize_t save_settings_show(struct kobject *kobj, struct kobj_attribute *attr, 1162 + char *buf) 1163 + { 1164 + /* Check that setting is valid */ 1165 + if (WARN_ON(tlmi_priv.save_mode < TLMI_SAVE_SINGLE || 1166 + tlmi_priv.save_mode > TLMI_SAVE_BULK)) 1167 + return -EIO; 1168 + return sysfs_emit(buf, "%s\n", save_mode_strings[tlmi_priv.save_mode]); 1169 + } 1170 + 1171 + static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *attr, 1172 + const char *buf, size_t count) 1173 + { 1174 + char *auth_str = NULL; 1175 + int ret = 0; 1176 + int cmd; 1177 + 1178 + cmd = sysfs_match_string(save_mode_strings, buf); 1179 + if (cmd < 0) 1180 + return cmd; 1181 + 1182 + /* Use lock in case multiple WMI operations needed */ 1183 + mutex_lock(&tlmi_mutex); 1184 + 1185 + switch (cmd) { 1186 + case TLMI_SAVE_SINGLE: 1187 + case TLMI_SAVE_BULK: 1188 + tlmi_priv.save_mode = cmd; 1189 + goto out; 1190 + case TLMI_SAVE_SAVE: 1191 + /* Check if supported*/ 1192 + if (!tlmi_priv.can_set_bios_settings || 1193 + tlmi_priv.save_mode == TLMI_SAVE_SINGLE) { 1194 + ret = -EOPNOTSUPP; 1195 + goto out; 1196 + } 1197 + /* Check there is actually something to save */ 1198 + if (!tlmi_priv.save_required) { 1199 + ret = -ENOENT; 1200 + goto out; 1201 + } 1202 + /* Check if certificate authentication is enabled and active */ 1203 + if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) { 1204 + if (!tlmi_priv.pwd_admin->signature || 1205 + !tlmi_priv.pwd_admin->save_signature) { 1206 + ret = -EINVAL; 1207 + goto out; 1208 + } 1209 + ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, 1210 + tlmi_priv.pwd_admin->save_signature); 1211 + if (ret) 1212 + goto out; 1213 + } else if (tlmi_priv.opcode_support) { 1214 + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { 1215 + ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin", 1216 + tlmi_priv.pwd_admin->password); 1217 + if (ret) 1218 + goto out; 1219 + } 1220 + ret = tlmi_save_bios_settings(""); 1221 + } else { /* old non-opcode based authentication method (deprecated) */ 1222 + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { 1223 + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", 1224 + tlmi_priv.pwd_admin->password, 1225 + encoding_options[tlmi_priv.pwd_admin->encoding], 1226 + tlmi_priv.pwd_admin->kbdlang); 1227 + if (!auth_str) { 1228 + ret = -ENOMEM; 1229 + goto out; 1230 + } 1231 + } 1232 + 1233 + if (auth_str) 1234 + ret = tlmi_save_bios_settings(auth_str); 1235 + else 1236 + ret = tlmi_save_bios_settings(""); 1237 + } 1238 + tlmi_priv.save_required = false; 1239 + tlmi_priv.reboot_required = true; 1240 + 1241 + if (!ret && !tlmi_priv.pending_changes) { 1242 + tlmi_priv.pending_changes = true; 1243 + /* let userland know it may need to check reboot pending again */ 1244 + kobject_uevent(&tlmi_priv.class_dev->kobj, KOBJ_CHANGE); 1245 + } 1246 + break; 1247 + } 1248 + out: 1249 + mutex_unlock(&tlmi_mutex); 1250 + kfree(auth_str); 1251 + return ret ?: count; 1252 + } 1253 + 1254 + static struct kobj_attribute save_settings = __ATTR_RW(save_settings); 1255 + 1170 1256 /* ---- Debug interface--------------------------------------------------------- */ 1171 1257 static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr, 1172 1258 const char *buf, size_t count) ··· 1337 1221 } 1338 1222 } 1339 1223 sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); 1224 + sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr); 1225 + 1340 1226 if (tlmi_priv.can_debug_cmd && debug_support) 1341 1227 sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); 1342 1228 ··· 1417 1299 } 1418 1300 1419 1301 ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); 1302 + if (ret) 1303 + goto fail_create_attr; 1304 + 1305 + ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr); 1420 1306 if (ret) 1421 1307 goto fail_create_attr; 1422 1308
+16
drivers/platform/x86/think-lmi.h
··· 27 27 TLMI_LEVEL_MASTER, 28 28 }; 29 29 30 + /* 31 + * There are a limit on the number of WMI operations you can do if you use 32 + * the default implementation of saving on every set. This is due to a 33 + * limitation in EFI variable space used. 34 + * Have a 'bulk save' mode where you can manually trigger the save, and can 35 + * therefore set unlimited variables - for users that need it. 36 + */ 37 + enum save_mode { 38 + TLMI_SAVE_SINGLE, 39 + TLMI_SAVE_BULK, 40 + TLMI_SAVE_SAVE, 41 + }; 42 + 30 43 /* password configuration details */ 31 44 struct tlmi_pwdcfg_core { 32 45 uint32_t password_mode; ··· 99 86 bool can_debug_cmd; 100 87 bool opcode_support; 101 88 bool certificate_support; 89 + enum save_mode save_mode; 90 + bool save_required; 91 + bool reboot_required; 102 92 103 93 struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; 104 94 struct device *class_dev;