162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2012 Red Hat, Inc. 462306a36Sopenharmony_ci * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/ctype.h> 862306a36Sopenharmony_ci#include <linux/efi.h> 962306a36Sopenharmony_ci#include <linux/fs.h> 1062306a36Sopenharmony_ci#include <linux/fs_context.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/pagemap.h> 1362306a36Sopenharmony_ci#include <linux/ucs2_string.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/magic.h> 1662306a36Sopenharmony_ci#include <linux/statfs.h> 1762306a36Sopenharmony_ci#include <linux/printk.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "internal.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ciLIST_HEAD(efivarfs_list); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic void efivarfs_evict_inode(struct inode *inode) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci clear_inode(inode); 2662306a36Sopenharmony_ci} 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci const u32 attr = EFI_VARIABLE_NON_VOLATILE | 3162306a36Sopenharmony_ci EFI_VARIABLE_BOOTSERVICE_ACCESS | 3262306a36Sopenharmony_ci EFI_VARIABLE_RUNTIME_ACCESS; 3362306a36Sopenharmony_ci u64 storage_space, remaining_space, max_variable_size; 3462306a36Sopenharmony_ci efi_status_t status; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* Some UEFI firmware does not implement QueryVariableInfo() */ 3762306a36Sopenharmony_ci storage_space = remaining_space = 0; 3862306a36Sopenharmony_ci if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) { 3962306a36Sopenharmony_ci status = efivar_query_variable_info(attr, &storage_space, 4062306a36Sopenharmony_ci &remaining_space, 4162306a36Sopenharmony_ci &max_variable_size); 4262306a36Sopenharmony_ci if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) 4362306a36Sopenharmony_ci pr_warn_ratelimited("query_variable_info() failed: 0x%lx\n", 4462306a36Sopenharmony_ci status); 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* 4862306a36Sopenharmony_ci * This is not a normal filesystem, so no point in pretending it has a block 4962306a36Sopenharmony_ci * size; we declare f_bsize to 1, so that we can then report the exact value 5062306a36Sopenharmony_ci * sent by EFI QueryVariableInfo in f_blocks and f_bfree 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_ci buf->f_bsize = 1; 5362306a36Sopenharmony_ci buf->f_namelen = NAME_MAX; 5462306a36Sopenharmony_ci buf->f_blocks = storage_space; 5562306a36Sopenharmony_ci buf->f_bfree = remaining_space; 5662306a36Sopenharmony_ci buf->f_type = dentry->d_sb->s_magic; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* 5962306a36Sopenharmony_ci * In f_bavail we declare the free space that the kernel will allow writing 6062306a36Sopenharmony_ci * when the storage_paranoia x86 quirk is active. To use more, users 6162306a36Sopenharmony_ci * should boot the kernel with efi_no_storage_paranoia. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci if (remaining_space > efivar_reserved_space()) 6462306a36Sopenharmony_ci buf->f_bavail = remaining_space - efivar_reserved_space(); 6562306a36Sopenharmony_ci else 6662306a36Sopenharmony_ci buf->f_bavail = 0; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci return 0; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_cistatic const struct super_operations efivarfs_ops = { 7162306a36Sopenharmony_ci .statfs = efivarfs_statfs, 7262306a36Sopenharmony_ci .drop_inode = generic_delete_inode, 7362306a36Sopenharmony_ci .evict_inode = efivarfs_evict_inode, 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* 7762306a36Sopenharmony_ci * Compare two efivarfs file names. 7862306a36Sopenharmony_ci * 7962306a36Sopenharmony_ci * An efivarfs filename is composed of two parts, 8062306a36Sopenharmony_ci * 8162306a36Sopenharmony_ci * 1. A case-sensitive variable name 8262306a36Sopenharmony_ci * 2. A case-insensitive GUID 8362306a36Sopenharmony_ci * 8462306a36Sopenharmony_ci * So we need to perform a case-sensitive match on part 1 and a 8562306a36Sopenharmony_ci * case-insensitive match on part 2. 8662306a36Sopenharmony_ci */ 8762306a36Sopenharmony_cistatic int efivarfs_d_compare(const struct dentry *dentry, 8862306a36Sopenharmony_ci unsigned int len, const char *str, 8962306a36Sopenharmony_ci const struct qstr *name) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci int guid = len - EFI_VARIABLE_GUID_LEN; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (name->len != len) 9462306a36Sopenharmony_ci return 1; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* Case-sensitive compare for the variable name */ 9762306a36Sopenharmony_ci if (memcmp(str, name->name, guid)) 9862306a36Sopenharmony_ci return 1; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* Case-insensitive compare for the GUID */ 10162306a36Sopenharmony_ci return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic int efivarfs_d_hash(const struct dentry *dentry, struct qstr *qstr) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci unsigned long hash = init_name_hash(dentry); 10762306a36Sopenharmony_ci const unsigned char *s = qstr->name; 10862306a36Sopenharmony_ci unsigned int len = qstr->len; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (!efivarfs_valid_name(s, len)) 11162306a36Sopenharmony_ci return -EINVAL; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci while (len-- > EFI_VARIABLE_GUID_LEN) 11462306a36Sopenharmony_ci hash = partial_name_hash(*s++, hash); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* GUID is case-insensitive. */ 11762306a36Sopenharmony_ci while (len--) 11862306a36Sopenharmony_ci hash = partial_name_hash(tolower(*s++), hash); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci qstr->hash = end_name_hash(hash); 12162306a36Sopenharmony_ci return 0; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic const struct dentry_operations efivarfs_d_ops = { 12562306a36Sopenharmony_ci .d_compare = efivarfs_d_compare, 12662306a36Sopenharmony_ci .d_hash = efivarfs_d_hash, 12762306a36Sopenharmony_ci .d_delete = always_delete_dentry, 12862306a36Sopenharmony_ci}; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct dentry *d; 13362306a36Sopenharmony_ci struct qstr q; 13462306a36Sopenharmony_ci int err; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci q.name = name; 13762306a36Sopenharmony_ci q.len = strlen(name); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci err = efivarfs_d_hash(parent, &q); 14062306a36Sopenharmony_ci if (err) 14162306a36Sopenharmony_ci return ERR_PTR(err); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci d = d_alloc(parent, &q); 14462306a36Sopenharmony_ci if (d) 14562306a36Sopenharmony_ci return d; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, 15162306a36Sopenharmony_ci unsigned long name_size, void *data) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct super_block *sb = (struct super_block *)data; 15462306a36Sopenharmony_ci struct efivar_entry *entry; 15562306a36Sopenharmony_ci struct inode *inode = NULL; 15662306a36Sopenharmony_ci struct dentry *dentry, *root = sb->s_root; 15762306a36Sopenharmony_ci unsigned long size = 0; 15862306a36Sopenharmony_ci char *name; 15962306a36Sopenharmony_ci int len; 16062306a36Sopenharmony_ci int err = -ENOMEM; 16162306a36Sopenharmony_ci bool is_removable = false; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 16762306a36Sopenharmony_ci if (!entry) 16862306a36Sopenharmony_ci return err; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci memcpy(entry->var.VariableName, name16, name_size); 17162306a36Sopenharmony_ci memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci len = ucs2_utf8size(entry->var.VariableName); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* name, plus '-', plus GUID, plus NUL*/ 17662306a36Sopenharmony_ci name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); 17762306a36Sopenharmony_ci if (!name) 17862306a36Sopenharmony_ci goto fail; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ucs2_as_utf8(name, entry->var.VariableName, len); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if (efivar_variable_is_removable(entry->var.VendorGuid, name, len)) 18362306a36Sopenharmony_ci is_removable = true; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci name[len] = '-'; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci efi_guid_to_str(&entry->var.VendorGuid, name + len + 1); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ 19262306a36Sopenharmony_ci strreplace(name, '/', '!'); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0, 19562306a36Sopenharmony_ci is_removable); 19662306a36Sopenharmony_ci if (!inode) 19762306a36Sopenharmony_ci goto fail_name; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci dentry = efivarfs_alloc_dentry(root, name); 20062306a36Sopenharmony_ci if (IS_ERR(dentry)) { 20162306a36Sopenharmony_ci err = PTR_ERR(dentry); 20262306a36Sopenharmony_ci goto fail_inode; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci __efivar_entry_get(entry, NULL, &size, NULL); 20662306a36Sopenharmony_ci __efivar_entry_add(entry, &efivarfs_list); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* copied by the above to local storage in the dentry. */ 20962306a36Sopenharmony_ci kfree(name); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci inode_lock(inode); 21262306a36Sopenharmony_ci inode->i_private = entry; 21362306a36Sopenharmony_ci i_size_write(inode, size + sizeof(entry->var.Attributes)); 21462306a36Sopenharmony_ci inode_unlock(inode); 21562306a36Sopenharmony_ci d_add(dentry, inode); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return 0; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cifail_inode: 22062306a36Sopenharmony_ci iput(inode); 22162306a36Sopenharmony_cifail_name: 22262306a36Sopenharmony_ci kfree(name); 22362306a36Sopenharmony_cifail: 22462306a36Sopenharmony_ci kfree(entry); 22562306a36Sopenharmony_ci return err; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic int efivarfs_destroy(struct efivar_entry *entry, void *data) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci efivar_entry_remove(entry); 23162306a36Sopenharmony_ci kfree(entry); 23262306a36Sopenharmony_ci return 0; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct inode *inode = NULL; 23862306a36Sopenharmony_ci struct dentry *root; 23962306a36Sopenharmony_ci int err; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (!efivar_is_available()) 24262306a36Sopenharmony_ci return -EOPNOTSUPP; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci sb->s_maxbytes = MAX_LFS_FILESIZE; 24562306a36Sopenharmony_ci sb->s_blocksize = PAGE_SIZE; 24662306a36Sopenharmony_ci sb->s_blocksize_bits = PAGE_SHIFT; 24762306a36Sopenharmony_ci sb->s_magic = EFIVARFS_MAGIC; 24862306a36Sopenharmony_ci sb->s_op = &efivarfs_ops; 24962306a36Sopenharmony_ci sb->s_d_op = &efivarfs_d_ops; 25062306a36Sopenharmony_ci sb->s_time_gran = 1; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (!efivar_supports_writes()) 25362306a36Sopenharmony_ci sb->s_flags |= SB_RDONLY; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true); 25662306a36Sopenharmony_ci if (!inode) 25762306a36Sopenharmony_ci return -ENOMEM; 25862306a36Sopenharmony_ci inode->i_op = &efivarfs_dir_inode_operations; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci root = d_make_root(inode); 26162306a36Sopenharmony_ci sb->s_root = root; 26262306a36Sopenharmony_ci if (!root) 26362306a36Sopenharmony_ci return -ENOMEM; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci INIT_LIST_HEAD(&efivarfs_list); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list); 26862306a36Sopenharmony_ci if (err) 26962306a36Sopenharmony_ci efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci return err; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic int efivarfs_get_tree(struct fs_context *fc) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci return get_tree_single(fc, efivarfs_fill_super); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic int efivarfs_reconfigure(struct fs_context *fc) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci if (!efivar_supports_writes() && !(fc->sb_flags & SB_RDONLY)) { 28262306a36Sopenharmony_ci pr_err("Firmware does not support SetVariableRT. Can not remount with rw\n"); 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return 0; 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic const struct fs_context_operations efivarfs_context_ops = { 29062306a36Sopenharmony_ci .get_tree = efivarfs_get_tree, 29162306a36Sopenharmony_ci .reconfigure = efivarfs_reconfigure, 29262306a36Sopenharmony_ci}; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic int efivarfs_init_fs_context(struct fs_context *fc) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci fc->ops = &efivarfs_context_ops; 29762306a36Sopenharmony_ci return 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic void efivarfs_kill_sb(struct super_block *sb) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct efivarfs_fs_info *sfi = sb->s_fs_info; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci kill_litter_super(sb); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (!efivar_is_available()) 30762306a36Sopenharmony_ci return; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci /* Remove all entries and destroy */ 31062306a36Sopenharmony_ci efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL); 31162306a36Sopenharmony_ci kfree(sfi); 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic struct file_system_type efivarfs_type = { 31562306a36Sopenharmony_ci .owner = THIS_MODULE, 31662306a36Sopenharmony_ci .name = "efivarfs", 31762306a36Sopenharmony_ci .init_fs_context = efivarfs_init_fs_context, 31862306a36Sopenharmony_ci .kill_sb = efivarfs_kill_sb, 31962306a36Sopenharmony_ci}; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic __init int efivarfs_init(void) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci return register_filesystem(&efivarfs_type); 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic __exit void efivarfs_exit(void) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci unregister_filesystem(&efivarfs_type); 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett, Jeremy Kerr"); 33262306a36Sopenharmony_ciMODULE_DESCRIPTION("EFI Variable Filesystem"); 33362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 33462306a36Sopenharmony_ciMODULE_ALIAS_FS("efivarfs"); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cimodule_init(efivarfs_init); 33762306a36Sopenharmony_cimodule_exit(efivarfs_exit); 338