>From 4cb24e33a63a8f9dd5a2ab56b1b183c1ef26c4d0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 30 Mar 2025 13:24:18 +0200 Subject: [PATCH] [DRAFT] efivarfs: support freeze/thaw for suspend/hibernate The efivarfs subsystem wants to partake in system hibernation and suspend. To this end it needs to gain freeze/thaw support. - Don't expose efivarfs freeze/thaw to userspace. It's not just pointless it also would complicate the implementation because we would need to handle userspace initiated freezed in combination with hibernation initiated freezes. IOW, userspace could freeze efivarfs and we get a notification about an imminent freeze request from the power subsystem but since we're already frozen by userspace we never actually sync variables. So this is useful on two fronts. - Unregister the notifier before we call kill_litter_super() because by that time the filesystems is already dead so no need bothering with reacting to hibernation. We wont't resurrect it anyway. - Let the notifier set a global variable to indicate that hibernation is ongoing and resync variable state when efivars is actually going to be unfrozen via efivarfs_thaw_super()'s call to efivarfs_unfreeze_fs(). Signed-off-by: Christian Brauner --- fs/efivarfs/super.c | 141 ++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 90 deletions(-) diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c index 0486e9b68bc6..ce0f7ebeed1d 100644 --- a/fs/efivarfs/super.c +++ b/fs/efivarfs/super.c @@ -119,12 +119,20 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) return 0; } + +static int efivarfs_freeze_super(struct super_block *sb, enum freeze_holder who); +static int efivarfs_thaw_super(struct super_block *sb, enum freeze_holder who); +static int efivarfs_unfreeze_fs(struct super_block *sb); + static const struct super_operations efivarfs_ops = { .statfs = efivarfs_statfs, .drop_inode = generic_delete_inode, .alloc_inode = efivarfs_alloc_inode, .free_inode = efivarfs_free_inode, .show_options = efivarfs_show_options, + .freeze_super = efivarfs_freeze_super, + .thaw_super = efivarfs_thaw_super, + .unfreeze_fs = efivarfs_unfreeze_fs, }; /* @@ -368,7 +376,6 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) return err; register_pm_notifier(&sfi->pm_nb); - return efivar_init(efivarfs_callback, sb, true); } @@ -474,111 +481,61 @@ static int efivarfs_check_missing(efi_char16_t *name16, efi_guid_t vendor, return err; } -static void efivarfs_deactivate_super_work(struct work_struct *work) -{ - struct super_block *s = container_of(work, struct super_block, - destroy_work); - /* - * note: here s->destroy_work is free for reuse (which - * will happen in deactivate_super) - */ - deactivate_super(s); -} - static struct file_system_type efivarfs_type; -static int efivarfs_pm_notify(struct notifier_block *nb, unsigned long action, - void *ptr) +static int efivarfs_freeze_super(struct super_block *sb, enum freeze_holder who) { - struct efivarfs_fs_info *sfi = container_of(nb, struct efivarfs_fs_info, - pm_nb); - struct path path; - struct efivarfs_ctx ectx = { - .ctx = { - .actor = efivarfs_actor, - }, - .sb = sfi->sb, - }; - struct file *file; - struct super_block *s = sfi->sb; - static bool rescan_done = true; - - if (action == PM_HIBERNATION_PREPARE) { - rescan_done = false; - return NOTIFY_OK; - } else if (action != PM_POST_HIBERNATION) { - return NOTIFY_DONE; - } + /* We only support freezing from the kernel. */ + if (!(who & FREEZE_HOLDER_KERNEL)) + return -EOPNOTSUPP; - if (rescan_done) - return NOTIFY_DONE; + return freeze_super(sb, who); +} - /* ensure single superblock is alive and pin it */ - if (!atomic_inc_not_zero(&s->s_active)) - return NOTIFY_DONE; +static int efivarfs_thaw_super(struct super_block *sb, enum freeze_holder who) +{ + /* We only support freezing from the kernel. */ + if (!(who & FREEZE_HOLDER_KERNEL)) + return -EOPNOTSUPP; - pr_info("efivarfs: resyncing variable state\n"); + return thaw_super(sb, who); +} - path.dentry = sfi->sb->s_root; +/* + * Only accessed by the power management notifier before ->unfreeze_fs() + * is ever called so this is serialized through the power management + * system. + */ +static bool need_unfreeze_fs = false; - /* - * do not add SB_KERNMOUNT which a single superblock could - * expose to userspace and which also causes MNT_INTERNAL, see - * below - */ - path.mnt = vfs_kern_mount(&efivarfs_type, 0, - efivarfs_type.name, NULL); - if (IS_ERR(path.mnt)) { - pr_err("efivarfs: internal mount failed\n"); - /* - * We may be the last pinner of the superblock but - * calling efivarfs_kill_sb from within the notifier - * here would deadlock trying to unregister it - */ - INIT_WORK(&s->destroy_work, efivarfs_deactivate_super_work); - schedule_work(&s->destroy_work); - return PTR_ERR(path.mnt); +static int efivarfs_pm_notify(struct notifier_block *nb, unsigned long action, void *ptr) +{ + if (action == PM_HIBERNATION_PREPARE) { + need_unfreeze_fs = true; + return NOTIFY_OK; + } else if (action == PM_POST_HIBERNATION) { + need_unfreeze_fs = false; + return NOTIFY_OK; } - /* path.mnt now has pin on superblock, so this must be above one */ - atomic_dec(&s->s_active); + return NOTIFY_DONE; +} - file = kernel_file_open(&path, O_RDONLY | O_DIRECTORY | O_NOATIME, - current_cred()); - /* - * safe even if last put because no MNT_INTERNAL means this - * will do delayed deactivate_super and not deadlock - */ - mntput(path.mnt); - if (IS_ERR(file)) - return NOTIFY_DONE; +static int efivarfs_unfreeze_fs(struct super_block *sb) +{ + /* This isn't a hibernation call so there's nothing for us to do. */ + if (!need_unfreeze_fs) + return 0; - rescan_done = true; + pr_info("efivarfs: resyncing variable state\n"); /* - * First loop over the directory and verify each entry exists, - * removing it if it doesn't + * TODO: Now do the variable resyncing thing. vfs_kern_mount() + * won't work because we'd deadlock with ->thaw_super() fwiw. */ - file->f_pos = 2; /* skip . and .. */ - do { - ectx.dentry = NULL; - iterate_dir(file, &ectx.ctx); - if (ectx.dentry) { - pr_info("efivarfs: removing variable %pd\n", - ectx.dentry); - simple_recursive_removal(ectx.dentry, NULL); - dput(ectx.dentry); - } - } while (ectx.dentry); - fput(file); - /* - * then loop over variables, creating them if there's no matching - * dentry - */ - efivar_init(efivarfs_check_missing, sfi->sb, false); + return 0; - return NOTIFY_OK; } static int efivarfs_init_fs_context(struct fs_context *fc) @@ -609,8 +566,12 @@ static void efivarfs_kill_sb(struct super_block *sb) struct efivarfs_fs_info *sfi = sb->s_fs_info; blocking_notifier_chain_unregister(&efivar_ops_nh, &sfi->nb); - kill_litter_super(sb); + /* + * Unregister the pm notifier right now as that superblock is + * already dead. + */ unregister_pm_notifier(&sfi->pm_nb); + kill_litter_super(sb); kfree(sfi); } -- 2.47.2