Skip to content

Commit 318d978

Browse files
mrhpearsonjwrdegoede
authored andcommitted
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>
1 parent 423c336 commit 318d978

3 files changed

Lines changed: 183 additions & 15 deletions

File tree

Documentation/ABI/testing/sysfs-class-firmware-attributes

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,36 @@ Description:
383383
Note that any changes to this attribute requires a reboot
384384
for changes to take effect.
385385

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+
386416
What: /sys/class/firmware-attributes/*/attributes/debug_cmd
387417
Date: July 2021
388418
KernelVersion: 5.14

drivers/platform/x86/think-lmi.c

Lines changed: 137 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,13 @@ static ssize_t current_value_store(struct kobject *kobj,
985985
if (!tlmi_priv.can_set_bios_settings)
986986
return -EOPNOTSUPP;
987987

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+
988995
new_setting = kstrdup(buf, GFP_KERNEL);
989996
if (!new_setting)
990997
return -ENOMEM;
@@ -1011,10 +1018,11 @@ static ssize_t current_value_store(struct kobject *kobj,
10111018
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str);
10121019
if (ret)
10131020
goto out;
1014-
ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
1015-
tlmi_priv.pwd_admin->save_signature);
1016-
if (ret)
1017-
goto out;
1021+
if (tlmi_priv.save_mode == TLMI_SAVE_BULK)
1022+
tlmi_priv.save_required = true;
1023+
else
1024+
ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
1025+
tlmi_priv.pwd_admin->save_signature);
10181026
} else if (tlmi_priv.opcode_support) {
10191027
/*
10201028
* If opcode support is present use that interface.
@@ -1033,14 +1041,17 @@ static ssize_t current_value_store(struct kobject *kobj,
10331041
if (ret)
10341042
goto out;
10351043

1036-
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
1037-
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
1038-
tlmi_priv.pwd_admin->password);
1039-
if (ret)
1040-
goto out;
1044+
if (tlmi_priv.save_mode == TLMI_SAVE_BULK) {
1045+
tlmi_priv.save_required = true;
1046+
} else {
1047+
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
1048+
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
1049+
tlmi_priv.pwd_admin->password);
1050+
if (ret)
1051+
goto out;
1052+
}
1053+
ret = tlmi_save_bios_settings("");
10411054
}
1042-
1043-
ret = tlmi_save_bios_settings("");
10441055
} else { /* old non-opcode based authentication method (deprecated) */
10451056
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
10461057
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
@@ -1068,10 +1079,14 @@ static ssize_t current_value_store(struct kobject *kobj,
10681079
if (ret)
10691080
goto out;
10701081

1071-
if (auth_str)
1072-
ret = tlmi_save_bios_settings(auth_str);
1073-
else
1074-
ret = tlmi_save_bios_settings("");
1082+
if (tlmi_priv.save_mode == TLMI_SAVE_BULK) {
1083+
tlmi_priv.save_required = true;
1084+
} else {
1085+
if (auth_str)
1086+
ret = tlmi_save_bios_settings(auth_str);
1087+
else
1088+
ret = tlmi_save_bios_settings("");
1089+
}
10751090
}
10761091
if (!ret && !tlmi_priv.pending_changes) {
10771092
tlmi_priv.pending_changes = true;
@@ -1152,6 +1167,107 @@ static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *
11521167

11531168
static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
11541169

1170+
static const char * const save_mode_strings[] = {
1171+
[TLMI_SAVE_SINGLE] = "single",
1172+
[TLMI_SAVE_BULK] = "bulk",
1173+
[TLMI_SAVE_SAVE] = "save"
1174+
};
1175+
1176+
static ssize_t save_settings_show(struct kobject *kobj, struct kobj_attribute *attr,
1177+
char *buf)
1178+
{
1179+
/* Check that setting is valid */
1180+
if (WARN_ON(tlmi_priv.save_mode < TLMI_SAVE_SINGLE ||
1181+
tlmi_priv.save_mode > TLMI_SAVE_BULK))
1182+
return -EIO;
1183+
return sysfs_emit(buf, "%s\n", save_mode_strings[tlmi_priv.save_mode]);
1184+
}
1185+
1186+
static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *attr,
1187+
const char *buf, size_t count)
1188+
{
1189+
char *auth_str = NULL;
1190+
int ret = 0;
1191+
int cmd;
1192+
1193+
cmd = sysfs_match_string(save_mode_strings, buf);
1194+
if (cmd < 0)
1195+
return cmd;
1196+
1197+
/* Use lock in case multiple WMI operations needed */
1198+
mutex_lock(&tlmi_mutex);
1199+
1200+
switch (cmd) {
1201+
case TLMI_SAVE_SINGLE:
1202+
case TLMI_SAVE_BULK:
1203+
tlmi_priv.save_mode = cmd;
1204+
goto out;
1205+
case TLMI_SAVE_SAVE:
1206+
/* Check if supported*/
1207+
if (!tlmi_priv.can_set_bios_settings ||
1208+
tlmi_priv.save_mode == TLMI_SAVE_SINGLE) {
1209+
ret = -EOPNOTSUPP;
1210+
goto out;
1211+
}
1212+
/* Check there is actually something to save */
1213+
if (!tlmi_priv.save_required) {
1214+
ret = -ENOENT;
1215+
goto out;
1216+
}
1217+
/* Check if certificate authentication is enabled and active */
1218+
if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) {
1219+
if (!tlmi_priv.pwd_admin->signature ||
1220+
!tlmi_priv.pwd_admin->save_signature) {
1221+
ret = -EINVAL;
1222+
goto out;
1223+
}
1224+
ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
1225+
tlmi_priv.pwd_admin->save_signature);
1226+
if (ret)
1227+
goto out;
1228+
} else if (tlmi_priv.opcode_support) {
1229+
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
1230+
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
1231+
tlmi_priv.pwd_admin->password);
1232+
if (ret)
1233+
goto out;
1234+
}
1235+
ret = tlmi_save_bios_settings("");
1236+
} else { /* old non-opcode based authentication method (deprecated) */
1237+
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
1238+
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
1239+
tlmi_priv.pwd_admin->password,
1240+
encoding_options[tlmi_priv.pwd_admin->encoding],
1241+
tlmi_priv.pwd_admin->kbdlang);
1242+
if (!auth_str) {
1243+
ret = -ENOMEM;
1244+
goto out;
1245+
}
1246+
}
1247+
1248+
if (auth_str)
1249+
ret = tlmi_save_bios_settings(auth_str);
1250+
else
1251+
ret = tlmi_save_bios_settings("");
1252+
}
1253+
tlmi_priv.save_required = false;
1254+
tlmi_priv.reboot_required = true;
1255+
1256+
if (!ret && !tlmi_priv.pending_changes) {
1257+
tlmi_priv.pending_changes = true;
1258+
/* let userland know it may need to check reboot pending again */
1259+
kobject_uevent(&tlmi_priv.class_dev->kobj, KOBJ_CHANGE);
1260+
}
1261+
break;
1262+
}
1263+
out:
1264+
mutex_unlock(&tlmi_mutex);
1265+
kfree(auth_str);
1266+
return ret ?: count;
1267+
}
1268+
1269+
static struct kobj_attribute save_settings = __ATTR_RW(save_settings);
1270+
11551271
/* ---- Debug interface--------------------------------------------------------- */
11561272
static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr,
11571273
const char *buf, size_t count)
@@ -1221,6 +1337,8 @@ static void tlmi_release_attr(void)
12211337
}
12221338
}
12231339
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr);
1340+
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr);
1341+
12241342
if (tlmi_priv.can_debug_cmd && debug_support)
12251343
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr);
12261344

@@ -1302,6 +1420,10 @@ static int tlmi_sysfs_init(void)
13021420
if (ret)
13031421
goto fail_create_attr;
13041422

1423+
ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr);
1424+
if (ret)
1425+
goto fail_create_attr;
1426+
13051427
if (tlmi_priv.can_debug_cmd && debug_support) {
13061428
ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr);
13071429
if (ret)

drivers/platform/x86/think-lmi.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ enum level_option {
2727
TLMI_LEVEL_MASTER,
2828
};
2929

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+
3043
/* password configuration details */
3144
struct tlmi_pwdcfg_core {
3245
uint32_t password_mode;
@@ -86,6 +99,9 @@ struct think_lmi {
8699
bool can_debug_cmd;
87100
bool opcode_support;
88101
bool certificate_support;
102+
enum save_mode save_mode;
103+
bool save_required;
104+
bool reboot_required;
89105

90106
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
91107
struct device *class_dev;

0 commit comments

Comments
 (0)