efivarfs: Move file system code to fs/efivarfs/ efivars.c has grown far too large and weighs in at nearly 2000 lines. It contains code for, * EFI variable handling at the firmware-level * handling EFI variables via sysfs * a pstore backend * an EFI variable filesystem Start chainsawing things up now before it gets any worse. Move all the efivarfs filesystem-specific code to fs/efivarfs/. This has necessitated introducing an EFI variable interface in efivars.c, see efivar_list_add() and efivar_list_del_unlock(). It also looks like efivarfs only ever worked with the CONFIG_EFI_VARS backend, not CONFIG_GOOGLE_SMI, because it accesses '__efivars' directly. Interestingly, if anyone was running with both of those options enabled efivarfs would have been registered twice via register_filesystem(). There's no reason that efivarfs couldn't be made to work with the CONFIG_GOOGLE_SMI variable backend, apart from the fact that the efivar code doesn't currently support such a scheme. Cc: Seiji Aguchi <seiji.aguchi@hds.com> Cc: Kees Cook <keescook@chromium.org> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Tony Luck <tony.luck@intel.com> Cc: Mike Waychison <mikew@google.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com>
diff --git a/MAINTAINERS b/MAINTAINERS index 212c255..ad1d94e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS
@@ -2894,6 +2894,15 @@ F: drivers/firmware/efivars.c F: include/linux/efi*.h +EFI VARIABLE FILESYSTEM +M: Matt Fleming <matt.fleming@intel.com> +M: Matthew Garrett <mjg59@srcf.ucam.org> +M: Jeremy Kerr <jeremy.kerr@canonical.com> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi.git +L: linux-efi@vger.kernel.org +S: Maintained +F: fs/efivarfs/ + EFIFB FRAMEBUFFER DRIVER L: linux-fbdev@vger.kernel.org M: Peter Jones <pjones@redhat.com>
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 10088fd..7c9b5e6 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c
@@ -97,42 +97,15 @@ #define DUMP_NAME_LEN 52 -/* - * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")) - * not including trailing NUL - */ -#define GUID_LEN 36 - -/* - * The maximum size of VariableName + Data = 1024 - * Therefore, it's reasonable to save that much - * space in each part of the structure, - * and we use a page for reading/writing. - */ - -struct efi_variable { - efi_char16_t VariableName[1024/sizeof(efi_char16_t)]; - efi_guid_t VendorGuid; - unsigned long DataSize; - __u8 Data[1024]; - efi_status_t Status; - __u32 Attributes; -} __attribute__((packed)); - -struct efivar_entry { - struct efivars *efivars; - struct efi_variable var; - struct list_head list; - struct kobject kobj; -}; - struct efivar_attribute { struct attribute attr; ssize_t (*show) (struct efivar_entry *entry, char *buf); ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count); }; -static struct efivars __efivars; +struct efivars __efivars; +EXPORT_SYMBOL_GPL(__efivars); + static struct efivar_operations ops; #define PSTORE_EFI_ATTRIBUTES \ @@ -159,23 +132,6 @@ efi_char16_t *variable_name, efi_guid_t *vendor_guid); -/* Return the number of unicode characters in data */ -static unsigned long -utf16_strnlen(efi_char16_t *s, size_t maxlength) -{ - unsigned long length = 0; - - while (*s++ != 0 && length < maxlength) - length++; - return length; -} - -static inline unsigned long -utf16_strlen(efi_char16_t *s) -{ - return utf16_strnlen(s, ~0UL); -} - /* * Return the number of bytes is the length of this string * Note: this is NOT the same as the number of unicode characters @@ -351,8 +307,7 @@ { "", NULL }, }; -static bool -validate_var(struct efi_variable *var, u8 *data, unsigned long len) +bool efivar_validate(struct efi_variable *var, u8 *data, unsigned long len) { int i; u16 *unicode_name = var->VariableName; @@ -387,6 +342,7 @@ return true; } +EXPORT_SYMBOL_GPL(efivar_validate); static efi_status_t get_var_data_locked(struct efivars *efivars, struct efi_variable *var) @@ -533,7 +489,7 @@ } if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || - validate_var(new_var, new_var->Data, new_var->DataSize) == false) { + efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { printk(KERN_ERR "efivars: Malformed variable content\n"); return -EINVAL; } @@ -648,617 +604,6 @@ kobject_put(&var->kobj); } -static int efivarfs_file_open(struct inode *inode, struct file *file) -{ - file->private_data = inode->i_private; - return 0; -} - -static int efi_status_to_err(efi_status_t status) -{ - int err; - - switch (status) { - case EFI_INVALID_PARAMETER: - err = -EINVAL; - break; - case EFI_OUT_OF_RESOURCES: - err = -ENOSPC; - break; - case EFI_DEVICE_ERROR: - err = -EIO; - break; - case EFI_WRITE_PROTECTED: - err = -EROFS; - break; - case EFI_SECURITY_VIOLATION: - err = -EACCES; - break; - case EFI_NOT_FOUND: - err = -EIO; - break; - default: - err = -EINVAL; - } - - return err; -} - -static ssize_t efivarfs_file_write(struct file *file, - const char __user *userbuf, size_t count, loff_t *ppos) -{ - struct efivar_entry *var = file->private_data; - struct efivars *efivars; - efi_status_t status; - void *data; - u32 attributes; - struct inode *inode = file->f_mapping->host; - unsigned long datasize = count - sizeof(attributes); - unsigned long newdatasize; - u64 storage_size, remaining_size, max_size; - ssize_t bytes = 0; - - if (count < sizeof(attributes)) - return -EINVAL; - - if (copy_from_user(&attributes, userbuf, sizeof(attributes))) - return -EFAULT; - - if (attributes & ~(EFI_VARIABLE_MASK)) - return -EINVAL; - - efivars = var->efivars; - - /* - * Ensure that the user can't allocate arbitrarily large - * amounts of memory. Pick a default size of 64K if - * QueryVariableInfo() isn't supported by the firmware. - */ - spin_lock(&efivars->lock); - - if (!efivars->ops->query_variable_info) - status = EFI_UNSUPPORTED; - else { - const struct efivar_operations *fops = efivars->ops; - status = fops->query_variable_info(attributes, &storage_size, - &remaining_size, &max_size); - } - - spin_unlock(&efivars->lock); - - if (status != EFI_SUCCESS) { - if (status != EFI_UNSUPPORTED) - return efi_status_to_err(status); - - remaining_size = 65536; - } - - if (datasize > remaining_size) - return -ENOSPC; - - data = kmalloc(datasize, GFP_KERNEL); - if (!data) - return -ENOMEM; - - if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) { - bytes = -EFAULT; - goto out; - } - - if (validate_var(&var->var, data, datasize) == false) { - bytes = -EINVAL; - goto out; - } - - /* - * The lock here protects the get_variable call, the conditional - * set_variable call, and removal of the variable from the efivars - * list (in the case of an authenticated delete). - */ - spin_lock(&efivars->lock); - - status = efivars->ops->set_variable(var->var.VariableName, - &var->var.VendorGuid, - attributes, datasize, - data); - - if (status != EFI_SUCCESS) { - spin_unlock(&efivars->lock); - kfree(data); - - return efi_status_to_err(status); - } - - bytes = count; - - /* - * Writing to the variable may have caused a change in size (which - * could either be an append or an overwrite), or the variable to be - * deleted. Perform a GetVariable() so we can tell what actually - * happened. - */ - newdatasize = 0; - status = efivars->ops->get_variable(var->var.VariableName, - &var->var.VendorGuid, - NULL, &newdatasize, - NULL); - - if (status == EFI_BUFFER_TOO_SMALL) { - spin_unlock(&efivars->lock); - mutex_lock(&inode->i_mutex); - i_size_write(inode, newdatasize + sizeof(attributes)); - mutex_unlock(&inode->i_mutex); - - } else if (status == EFI_NOT_FOUND) { - list_del(&var->list); - spin_unlock(&efivars->lock); - efivar_unregister(var); - drop_nlink(inode); - d_delete(file->f_dentry); - dput(file->f_dentry); - - } else { - spin_unlock(&efivars->lock); - pr_warn("efivarfs: inconsistent EFI variable implementation? " - "status = %lx\n", status); - } - -out: - kfree(data); - - return bytes; -} - -static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct efivar_entry *var = file->private_data; - struct efivars *efivars = var->efivars; - efi_status_t status; - unsigned long datasize = 0; - u32 attributes; - void *data; - ssize_t size = 0; - - spin_lock(&efivars->lock); - status = efivars->ops->get_variable(var->var.VariableName, - &var->var.VendorGuid, - &attributes, &datasize, NULL); - spin_unlock(&efivars->lock); - - if (status != EFI_BUFFER_TOO_SMALL) - return efi_status_to_err(status); - - data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL); - - if (!data) - return -ENOMEM; - - spin_lock(&efivars->lock); - status = efivars->ops->get_variable(var->var.VariableName, - &var->var.VendorGuid, - &attributes, &datasize, - (data + sizeof(attributes))); - spin_unlock(&efivars->lock); - - if (status != EFI_SUCCESS) { - size = efi_status_to_err(status); - goto out_free; - } - - memcpy(data, &attributes, sizeof(attributes)); - size = simple_read_from_buffer(userbuf, count, ppos, - data, datasize + sizeof(attributes)); -out_free: - kfree(data); - - return size; -} - -static void efivarfs_evict_inode(struct inode *inode) -{ - clear_inode(inode); -} - -static const struct super_operations efivarfs_ops = { - .statfs = simple_statfs, - .drop_inode = generic_delete_inode, - .evict_inode = efivarfs_evict_inode, - .show_options = generic_show_options, -}; - -static struct super_block *efivarfs_sb; - -static const struct inode_operations efivarfs_dir_inode_operations; - -static const struct file_operations efivarfs_file_operations = { - .open = efivarfs_file_open, - .read = efivarfs_file_read, - .write = efivarfs_file_write, - .llseek = no_llseek, -}; - -static struct inode *efivarfs_get_inode(struct super_block *sb, - const struct inode *dir, int mode, dev_t dev) -{ - struct inode *inode = new_inode(sb); - - if (inode) { - inode->i_ino = get_next_ino(); - inode->i_mode = mode; - inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; - switch (mode & S_IFMT) { - case S_IFREG: - inode->i_fop = &efivarfs_file_operations; - break; - case S_IFDIR: - inode->i_op = &efivarfs_dir_inode_operations; - inode->i_fop = &simple_dir_operations; - inc_nlink(inode); - break; - } - } - return inode; -} - -/* - * Return 1 if 'str' is a valid efivarfs filename of the form, - * - * VariableName-12345678-1234-1234-1234-1234567891bc - */ -static int efivarfs_valid_name(const char *str, int len) -{ - const char *s; - int i, j; - int ranges[2][5] = { - { 0, 9, 14, 19, 24 }, - { 8, 13, 18, 23, 36 } - }; - - /* - * We need a GUID, plus at least one letter for the variable name, - * plus the '-' separator - */ - if (len < GUID_LEN + 2) - return 0; - - s = strchr(str, '-'); - if (!s) - return 0; - - s++; /* Skip '-' */ - - /* Ensure we have enough characters for a GUID */ - if (len - (s - str) != GUID_LEN) - return 0; - - /* - * Validate that 's' is of the correct format, e.g. - * - * 12345678-1234-1234-1234-123456789abc - */ - for (i = 0; i < 5; i++) { - for (j = ranges[0][i]; j < ranges[1][i]; j++) { - if (hex_to_bin(s[j]) < 0) - return 0; - } - - if (j < GUID_LEN && s[j] != '-') - return 0; - } - - return 1; -} - -static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid) -{ - guid->b[0] = hex_to_bin(str[6]) << 4 | hex_to_bin(str[7]); - guid->b[1] = hex_to_bin(str[4]) << 4 | hex_to_bin(str[5]); - guid->b[2] = hex_to_bin(str[2]) << 4 | hex_to_bin(str[3]); - guid->b[3] = hex_to_bin(str[0]) << 4 | hex_to_bin(str[1]); - guid->b[4] = hex_to_bin(str[11]) << 4 | hex_to_bin(str[12]); - guid->b[5] = hex_to_bin(str[9]) << 4 | hex_to_bin(str[10]); - guid->b[6] = hex_to_bin(str[16]) << 4 | hex_to_bin(str[17]); - guid->b[7] = hex_to_bin(str[14]) << 4 | hex_to_bin(str[15]); - guid->b[8] = hex_to_bin(str[19]) << 4 | hex_to_bin(str[20]); - guid->b[9] = hex_to_bin(str[21]) << 4 | hex_to_bin(str[22]); - guid->b[10] = hex_to_bin(str[24]) << 4 | hex_to_bin(str[25]); - guid->b[11] = hex_to_bin(str[26]) << 4 | hex_to_bin(str[27]); - guid->b[12] = hex_to_bin(str[28]) << 4 | hex_to_bin(str[29]); - guid->b[13] = hex_to_bin(str[30]) << 4 | hex_to_bin(str[31]); - guid->b[14] = hex_to_bin(str[32]) << 4 | hex_to_bin(str[33]); - guid->b[15] = hex_to_bin(str[34]) << 4 | hex_to_bin(str[35]); -} - -static int efivarfs_create(struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) -{ - struct inode *inode; - struct efivars *efivars = &__efivars; - struct efivar_entry *var; - int namelen, i = 0, err = 0; - - if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) - return -EINVAL; - - inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0); - if (!inode) - return -ENOMEM; - - var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); - if (!var) { - err = -ENOMEM; - goto out; - } - - /* length of the variable name itself: remove GUID and separator */ - namelen = dentry->d_name.len - GUID_LEN - 1; - - efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1, - &var->var.VendorGuid); - - for (i = 0; i < namelen; i++) - var->var.VariableName[i] = dentry->d_name.name[i]; - - var->var.VariableName[i] = '\0'; - - inode->i_private = var; - var->efivars = efivars; - var->kobj.kset = efivars->kset; - - err = kobject_init_and_add(&var->kobj, &efivar_ktype, NULL, "%s", - dentry->d_name.name); - if (err) - goto out; - - kobject_uevent(&var->kobj, KOBJ_ADD); - spin_lock(&efivars->lock); - list_add(&var->list, &efivars->list); - spin_unlock(&efivars->lock); - d_instantiate(dentry, inode); - dget(dentry); -out: - if (err) { - kfree(var); - iput(inode); - } - return err; -} - -static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) -{ - struct efivar_entry *var = dentry->d_inode->i_private; - struct efivars *efivars = var->efivars; - efi_status_t status; - - spin_lock(&efivars->lock); - - status = efivars->ops->set_variable(var->var.VariableName, - &var->var.VendorGuid, - 0, 0, NULL); - - if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) { - list_del(&var->list); - spin_unlock(&efivars->lock); - efivar_unregister(var); - drop_nlink(dentry->d_inode); - dput(dentry); - return 0; - } - - spin_unlock(&efivars->lock); - return -EINVAL; -}; - -/* - * Compare two efivarfs file names. - * - * An efivarfs filename is composed of two parts, - * - * 1. A case-sensitive variable name - * 2. A case-insensitive GUID - * - * So we need to perform a case-sensitive match on part 1 and a - * case-insensitive match on part 2. - */ -static int efivarfs_d_compare(const struct dentry *parent, const struct inode *pinode, - const struct dentry *dentry, const struct inode *inode, - unsigned int len, const char *str, - const struct qstr *name) -{ - const char *q; - int guid; - - /* - * If the string we're being asked to compare doesn't match - * the expected format return "no match". - */ - if (!efivarfs_valid_name(str, len)) - return 1; - - if (!(q = strchr(name->name, '-'))) - return 1; - - /* Find part 1, the variable name. */ - guid = q - (const char *)name->name; - - /* Case-sensitive compare for the variable name */ - if (memcmp(str, name->name, guid)) - return 1; - - /* Case-insensitive compare for the GUID */ - return strcasecmp(&name->name[guid], &str[guid]); -} - -static int efivarfs_d_hash(const struct dentry *dentry, - const struct inode *inode, struct qstr *qstr) -{ - const unsigned char *us; - char lower[NAME_MAX]; - int guid; - - if (!efivarfs_valid_name(qstr->name, qstr->len)) - return -EINVAL; - - if (qstr->len > NAME_MAX) - return -ENAMETOOLONG; - - us = strchr(qstr->name, '-'); - if (!us) - return -EINVAL; - - /* The variable name part is case-sensitive */ - guid = us - qstr->name; - memcpy(lower, qstr->name, guid); - - /* GUID is case-insensitive. */ - for (us = &qstr->name[guid]; guid < qstr->len; guid++) - lower[guid] = tolower(*us++); - lower[guid] = '\0'; - - qstr->hash = full_name_hash(lower, qstr->len); - return 0; -} - -/* - * Retaining negative dentries for an in-memory filesystem just wastes - * memory and lookup time: arrange for them to be deleted immediately. - */ -static int efivarfs_delete_dentry(const struct dentry *dentry) -{ - return 1; -} - -static struct dentry_operations efivarfs_d_ops = { - .d_compare = efivarfs_d_compare, - .d_hash = efivarfs_d_hash, - .d_delete = efivarfs_delete_dentry, -}; - -static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) -{ - struct inode *inode = NULL; - struct dentry *root; - struct efivar_entry *entry, *n; - struct efivars *efivars = &__efivars; - char *name; - - efivarfs_sb = sb; - - sb->s_maxbytes = MAX_LFS_FILESIZE; - sb->s_blocksize = PAGE_CACHE_SIZE; - sb->s_blocksize_bits = PAGE_CACHE_SHIFT; - sb->s_magic = EFIVARFS_MAGIC; - sb->s_op = &efivarfs_ops; - sb->s_d_op = &efivarfs_d_ops; - sb->s_time_gran = 1; - - inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0); - if (!inode) - return -ENOMEM; - inode->i_op = &efivarfs_dir_inode_operations; - - root = d_make_root(inode); - sb->s_root = root; - if (!root) - return -ENOMEM; - - list_for_each_entry_safe(entry, n, &efivars->list, list) { - struct dentry *dentry, *root = efivarfs_sb->s_root; - unsigned long size = 0; - int len, i; - - inode = NULL; - - len = utf16_strlen(entry->var.VariableName); - - /* name, plus '-', plus GUID, plus NUL*/ - name = kmalloc(len + 1 + GUID_LEN + 1, GFP_ATOMIC); - if (!name) - goto fail; - - for (i = 0; i < len; i++) - name[i] = entry->var.VariableName[i] & 0xFF; - - name[len] = '-'; - - efi_guid_unparse(&entry->var.VendorGuid, name + len + 1); - - name[len+GUID_LEN+1] = '\0'; - - inode = efivarfs_get_inode(efivarfs_sb, root->d_inode, - S_IFREG | 0644, 0); - if (!inode) - goto fail_name; - - dentry = d_alloc_name(root, name); - if (!dentry) - goto fail_inode; - - /* copied by the above to local storage in the dentry. */ - kfree(name); - - spin_lock(&efivars->lock); - efivars->ops->get_variable(entry->var.VariableName, - &entry->var.VendorGuid, - &entry->var.Attributes, - &size, - NULL); - spin_unlock(&efivars->lock); - - mutex_lock(&inode->i_mutex); - inode->i_private = entry; - i_size_write(inode, size + sizeof(entry->var.Attributes)); - mutex_unlock(&inode->i_mutex); - d_add(dentry, inode); - } - - return 0; - -fail_inode: - iput(inode); -fail_name: - kfree(name); -fail: - return -ENOMEM; -} - -static struct dentry *efivarfs_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) -{ - return mount_single(fs_type, flags, data, efivarfs_fill_super); -} - -static void efivarfs_kill_sb(struct super_block *sb) -{ - kill_litter_super(sb); - efivarfs_sb = NULL; -} - -static struct file_system_type efivarfs_type = { - .name = "efivarfs", - .mount = efivarfs_mount, - .kill_sb = efivarfs_kill_sb, -}; - -/* - * Handle negative dentry. - */ -static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry, - unsigned int flags) -{ - if (dentry->d_name.len > NAME_MAX) - return ERR_PTR(-ENAMETOOLONG); - d_add(dentry, NULL); - return NULL; -} - -static const struct inode_operations efivarfs_dir_inode_operations = { - .lookup = efivarfs_lookup, - .unlink = efivarfs_unlink, - .create = efivarfs_create, -}; - static struct pstore_info efi_pstore_info; #ifdef CONFIG_PSTORE @@ -1449,12 +794,7 @@ } if (found) - list_del(&found->list); - - spin_unlock(&efivars->lock); - - if (found) - efivar_unregister(found); + efivar_list_del_unlock(found); return 0; } @@ -1516,7 +856,7 @@ return -EACCES; if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || - validate_var(new_var, new_var->Data, new_var->DataSize) == false) { + efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { printk(KERN_ERR "efivars: Malformed variable content\n"); return -EINVAL; } @@ -1621,10 +961,7 @@ spin_unlock(&efivars->lock); return -EIO; } - list_del(&search_efivar->list); - /* We need to release this lock before unregistering. */ - spin_unlock(&efivars->lock); - efivar_unregister(search_efivar); + efivar_list_del_unlock(search_efivar); /* It's dead Jim.... */ return count; @@ -1674,6 +1011,62 @@ static struct kobject *efi_kobj; +/** + * efivar_list_add - initialise @entry and add to variable list + * @efivars: efivars structure containing variable list + * @name: ASCII variable name + * @entry: new variable entry + * + * Initialise @entry, create a kobject in the necessary parent and + * insert the new entry into the @efivars list. + */ +int efivar_list_add(struct efivars *efivars, char *name, + struct efivar_entry *entry) +{ + int err; + + entry->kobj.kset = efivars->kset; + entry->efivars = efivars; + + err = kobject_init_and_add(&entry->kobj, &efivar_ktype, + NULL, "%s", name); + if (err) + return err; + + kobject_uevent(&entry->kobj, KOBJ_ADD); + + spin_lock(&efivars->lock); + list_add(&entry->list, &efivars->list); + spin_unlock(&efivars->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(efivar_list_add); + +/** + * efivar_list_del_unlock - remove an entry from variable list + * @entry: entry to remove + * + * Remove @entry from the variable list and decrement its reference + * count (unregister it). + * + * NOTE: slightly weird locking semantics here - we expect to be + * called with the efivars lock already held, and we release it before + * returning. This is because this function is usually called after + * ->set_variable() while the lock is still held. + */ +void efivar_list_del_unlock(struct efivar_entry *entry) +{ + WARN_ON(!spin_is_locked(&entry->efivars->lock)); + + list_del(&entry->list); + spin_unlock(&entry->efivars->lock); + + /* We need to release this lock before unregistering. */ + efivar_unregister(entry); +} +EXPORT_SYMBOL_GPL(efivar_list_del_unlock); + /* * efivar_create_sysfs_entry() * Requires: @@ -1698,7 +1091,7 @@ * plus the GUID, plus trailing NUL */ short_name_size = variable_name_size / sizeof(efi_char16_t) - + 1 + GUID_LEN + 1; + + 1 + EFI_VARIABLE_GUID_LEN + 1; short_name = kzalloc(short_name_size, GFP_KERNEL); new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); @@ -1709,7 +1102,6 @@ return 1; } - new_efivar->efivars = efivars; memcpy(new_efivar->var.VariableName, variable_name, variable_name_size); memcpy(&(new_efivar->var.VendorGuid), vendor_guid, sizeof(efi_guid_t)); @@ -1725,23 +1117,16 @@ *(short_name + strlen(short_name)) = '-'; efi_guid_unparse(vendor_guid, short_name + strlen(short_name)); - new_efivar->kobj.kset = efivars->kset; - i = kobject_init_and_add(&new_efivar->kobj, &efivar_ktype, NULL, - "%s", short_name); + i = efivar_list_add(efivars, short_name, new_efivar); if (i) { kfree(short_name); kfree(new_efivar); return 1; } - kobject_uevent(&new_efivar->kobj, KOBJ_ADD); kfree(short_name); short_name = NULL; - spin_lock(&efivars->lock); - list_add(&new_efivar->list, &efivars->list); - spin_unlock(&efivars->lock); - return 0; } @@ -1810,9 +1195,7 @@ list_for_each_entry_safe(entry, n, &efivars->list, list) { spin_lock(&efivars->lock); - list_del(&entry->list); - spin_unlock(&efivars->lock); - efivar_unregister(entry); + efivar_list_del_unlock(entry); } if (efivars->new_var) sysfs_remove_bin_file(&efivars->kset->kobj, efivars->new_var); @@ -1902,8 +1285,6 @@ pstore_register(&efivars->efi_pstore_info); } - register_filesystem(&efivarfs_type); - out: kfree(variable_name);
diff --git a/fs/Kconfig b/fs/Kconfig index 780725a..c229f82 100644 --- a/fs/Kconfig +++ b/fs/Kconfig
@@ -211,6 +211,7 @@ source "fs/ufs/Kconfig" source "fs/exofs/Kconfig" source "fs/f2fs/Kconfig" +source "fs/efivarfs/Kconfig" endif # MISC_FILESYSTEMS
diff --git a/fs/Makefile b/fs/Makefile index 9d53192..0fde6a3 100644 --- a/fs/Makefile +++ b/fs/Makefile
@@ -127,3 +127,4 @@ obj-y += exofs/ # Multiple modules obj-$(CONFIG_CEPH_FS) += ceph/ obj-$(CONFIG_PSTORE) += pstore/ +obj-$(CONFIG_EFIVAR_FS) += efivarfs/
diff --git a/fs/efivarfs/Kconfig b/fs/efivarfs/Kconfig new file mode 100644 index 0000000..1fb2b7f --- /dev/null +++ b/fs/efivarfs/Kconfig
@@ -0,0 +1,12 @@ +config EFIVAR_FS + tristate "EFI Variable filesystem" + depends on EFI_VARS + help + efivarfs is a replacement filesystem for the old EFI + variable support via sysfs, as it doesn't suffer from the + same 1024-byte variable size limit. + + To compile this file system support as a module, choose M + here. The module will be called efivarfs. + + If unsure, say N.
diff --git a/fs/efivarfs/Makefile b/fs/efivarfs/Makefile new file mode 100644 index 0000000..955d478 --- /dev/null +++ b/fs/efivarfs/Makefile
@@ -0,0 +1,7 @@ +# +# Makefile for the efivarfs filesystem +# + +obj-$(CONFIG_EFIVAR_FS) += efivarfs.o + +efivarfs-objs := inode.o file.o super.o
diff --git a/fs/efivarfs/file.c b/fs/efivarfs/file.c new file mode 100644 index 0000000..d6f1636 --- /dev/null +++ b/fs/efivarfs/file.c
@@ -0,0 +1,215 @@ +#include <linux/efi.h> +#include <linux/fs.h> + +static int efivarfs_file_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int efi_status_to_err(efi_status_t status) +{ + int err; + + switch (status) { + case EFI_INVALID_PARAMETER: + err = -EINVAL; + break; + case EFI_OUT_OF_RESOURCES: + err = -ENOSPC; + break; + case EFI_DEVICE_ERROR: + err = -EIO; + break; + case EFI_WRITE_PROTECTED: + err = -EROFS; + break; + case EFI_SECURITY_VIOLATION: + err = -EACCES; + break; + case EFI_NOT_FOUND: + err = -EIO; + break; + default: + err = -EINVAL; + } + + return err; +} + +static ssize_t efivarfs_file_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct efivar_entry *var = file->private_data; + struct efivars *efivars; + efi_status_t status; + void *data; + u32 attributes; + struct inode *inode = file->f_mapping->host; + unsigned long datasize = count - sizeof(attributes); + unsigned long newdatasize; + u64 storage_size, remaining_size, max_size; + ssize_t bytes = 0; + + if (count < sizeof(attributes)) + return -EINVAL; + + if (copy_from_user(&attributes, userbuf, sizeof(attributes))) + return -EFAULT; + + if (attributes & ~(EFI_VARIABLE_MASK)) + return -EINVAL; + + efivars = var->efivars; + + /* + * Ensure that the user can't allocate arbitrarily large + * amounts of memory. Pick a default size of 64K if + * QueryVariableInfo() isn't supported by the firmware. + */ + spin_lock(&efivars->lock); + + if (!efivars->ops->query_variable_info) + status = EFI_UNSUPPORTED; + else { + const struct efivar_operations *fops = efivars->ops; + status = fops->query_variable_info(attributes, &storage_size, + &remaining_size, &max_size); + } + + spin_unlock(&efivars->lock); + + if (status != EFI_SUCCESS) { + if (status != EFI_UNSUPPORTED) + return efi_status_to_err(status); + + remaining_size = 65536; + } + + if (datasize > remaining_size) + return -ENOSPC; + + data = kmalloc(datasize, GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) { + bytes = -EFAULT; + goto out; + } + + if (efivar_validate(&var->var, data, datasize) == false) { + bytes = -EINVAL; + goto out; + } + + /* + * The lock here protects the get_variable call, the conditional + * set_variable call, and removal of the variable from the efivars + * list (in the case of an authenticated delete). + */ + spin_lock(&efivars->lock); + + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, + attributes, datasize, + data); + + if (status != EFI_SUCCESS) { + spin_unlock(&efivars->lock); + kfree(data); + + return efi_status_to_err(status); + } + + bytes = count; + + /* + * Writing to the variable may have caused a change in size (which + * could either be an append or an overwrite), or the variable to be + * deleted. Perform a GetVariable() so we can tell what actually + * happened. + */ + newdatasize = 0; + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + NULL, &newdatasize, + NULL); + + if (status == EFI_BUFFER_TOO_SMALL) { + spin_unlock(&efivars->lock); + mutex_lock(&inode->i_mutex); + i_size_write(inode, newdatasize + sizeof(attributes)); + mutex_unlock(&inode->i_mutex); + + } else if (status == EFI_NOT_FOUND) { + efivar_list_del_unlock(var); + drop_nlink(inode); + d_delete(file->f_dentry); + dput(file->f_dentry); + + } else { + spin_unlock(&efivars->lock); + pr_warn("efivarfs: inconsistent EFI variable implementation? " + "status = %lx\n", status); + } + +out: + kfree(data); + + return bytes; +} + +static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct efivar_entry *var = file->private_data; + struct efivars *efivars = var->efivars; + efi_status_t status; + unsigned long datasize = 0; + u32 attributes; + void *data; + ssize_t size = 0; + + spin_lock(&efivars->lock); + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, NULL); + spin_unlock(&efivars->lock); + + if (status != EFI_BUFFER_TOO_SMALL) + return efi_status_to_err(status); + + data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL); + + if (!data) + return -ENOMEM; + + spin_lock(&efivars->lock); + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, + (data + sizeof(attributes))); + spin_unlock(&efivars->lock); + + if (status != EFI_SUCCESS) { + size = efi_status_to_err(status); + goto out_free; + } + + memcpy(data, &attributes, sizeof(attributes)); + size = simple_read_from_buffer(userbuf, count, ppos, + data, datasize + sizeof(attributes)); +out_free: + kfree(data); + + return size; +} + +const struct file_operations efivarfs_file_operations = { + .open = efivarfs_file_open, + .read = efivarfs_file_read, + .write = efivarfs_file_write, + .llseek = no_llseek, +}; +
diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c new file mode 100644 index 0000000..f99703f --- /dev/null +++ b/fs/efivarfs/inode.c
@@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 Matthew Garrett + * Copyright (C) 2012 Jeremy Kerr + */ + +#include <linux/efi.h> +#include <linux/fs.h> + +#include "internal.h" + +struct inode *efivarfs_get_inode(struct super_block *sb, + const struct inode *dir, int mode, dev_t dev) +{ + struct inode *inode = new_inode(sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + switch (mode & S_IFMT) { + case S_IFREG: + inode->i_fop = &efivarfs_file_operations; + break; + case S_IFDIR: + inode->i_op = &efivarfs_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + break; + } + } + return inode; +} + +/* + * Return 1 if 'str' is a valid efivarfs filename of the form, + * + * VariableName-12345678-1234-1234-1234-1234567891bc + */ +int efivarfs_valid_name(const char *str, int len) +{ + const char *s; + int i, j; + int ranges[2][5] = { + { 0, 9, 14, 19, 24 }, + { 8, 13, 18, 23, 36 } + }; + + /* + * We need a GUID, plus at least one letter for the variable name, + * plus the '-' separator + */ + if (len < EFI_VARIABLE_GUID_LEN + 2) + return 0; + + s = strchr(str, '-'); + if (!s) + return 0; + + s++; /* Skip '-' */ + + /* Ensure we have enough characters for a GUID */ + if (len - (s - str) != EFI_VARIABLE_GUID_LEN) + return 0; + + /* + * Validate that 's' is of the correct format, e.g. + * + * 12345678-1234-1234-1234-123456789abc + */ + for (i = 0; i < 5; i++) { + for (j = ranges[0][i]; j < ranges[1][i]; j++) { + if (hex_to_bin(s[j]) < 0) + return 0; + } + + if (j < EFI_VARIABLE_GUID_LEN && s[j] != '-') + return 0; + } + + return 1; +} + +static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid) +{ + guid->b[0] = hex_to_bin(str[6]) << 4 | hex_to_bin(str[7]); + guid->b[1] = hex_to_bin(str[4]) << 4 | hex_to_bin(str[5]); + guid->b[2] = hex_to_bin(str[2]) << 4 | hex_to_bin(str[3]); + guid->b[3] = hex_to_bin(str[0]) << 4 | hex_to_bin(str[1]); + guid->b[4] = hex_to_bin(str[11]) << 4 | hex_to_bin(str[12]); + guid->b[5] = hex_to_bin(str[9]) << 4 | hex_to_bin(str[10]); + guid->b[6] = hex_to_bin(str[16]) << 4 | hex_to_bin(str[17]); + guid->b[7] = hex_to_bin(str[14]) << 4 | hex_to_bin(str[15]); + guid->b[8] = hex_to_bin(str[19]) << 4 | hex_to_bin(str[20]); + guid->b[9] = hex_to_bin(str[21]) << 4 | hex_to_bin(str[22]); + guid->b[10] = hex_to_bin(str[24]) << 4 | hex_to_bin(str[25]); + guid->b[11] = hex_to_bin(str[26]) << 4 | hex_to_bin(str[27]); + guid->b[12] = hex_to_bin(str[28]) << 4 | hex_to_bin(str[29]); + guid->b[13] = hex_to_bin(str[30]) << 4 | hex_to_bin(str[31]); + guid->b[14] = hex_to_bin(str[32]) << 4 | hex_to_bin(str[33]); + guid->b[15] = hex_to_bin(str[34]) << 4 | hex_to_bin(str[35]); +} + +static int efivarfs_create(struct inode *dir, struct dentry *dentry, + umode_t mode, bool excl) +{ + struct inode *inode; + struct efivars *efivars = &__efivars; + struct efivar_entry *var; + int namelen, i = 0, err = 0; + + if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) + return -EINVAL; + + inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0); + if (!inode) + return -ENOMEM; + + var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); + if (!var) { + err = -ENOMEM; + goto out; + } + + /* length of the variable name itself: remove GUID and separator */ + namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; + + efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1, + &var->var.VendorGuid); + + for (i = 0; i < namelen; i++) + var->var.VariableName[i] = dentry->d_name.name[i]; + + var->var.VariableName[i] = '\0'; + + inode->i_private = var; + + err = efivar_list_add(efivars, (char *)dentry->d_name.name, var); + if (err) + goto out; + + d_instantiate(dentry, inode); + dget(dentry); +out: + if (err) { + kfree(var); + iput(inode); + } + return err; +} + +static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct efivar_entry *var = dentry->d_inode->i_private; + struct efivars *efivars = var->efivars; + efi_status_t status; + + spin_lock(&efivars->lock); + + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, + 0, 0, NULL); + + if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) { + efivar_list_del_unlock(var); + drop_nlink(dentry->d_inode); + dput(dentry); + return 0; + } + + spin_unlock(&efivars->lock); + return -EINVAL; +}; + +/* + * Handle negative dentry. + */ +static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + if (dentry->d_name.len > NAME_MAX) + return ERR_PTR(-ENAMETOOLONG); + d_add(dentry, NULL); + return NULL; +} + +const struct inode_operations efivarfs_dir_inode_operations = { + .lookup = efivarfs_lookup, + .unlink = efivarfs_unlink, + .create = efivarfs_create, +};
diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h new file mode 100644 index 0000000..582aa37 --- /dev/null +++ b/fs/efivarfs/internal.h
@@ -0,0 +1,9 @@ +extern struct efivars __efivars; + +extern const struct file_operations efivarfs_file_operations; +extern const struct inode_operations efivarfs_dir_inode_operations; + +extern int efivarfs_valid_name(const char *str, int len); +extern struct inode *efivarfs_get_inode(struct super_block *sb, + const struct inode *dir, int mode, dev_t dev); +
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c new file mode 100644 index 0000000..e87b65a --- /dev/null +++ b/fs/efivarfs/super.c
@@ -0,0 +1,228 @@ +#include <linux/ctype.h> +#include <linux/efi.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/pagemap.h> + +#include "internal.h" + +static void efivarfs_evict_inode(struct inode *inode) +{ + clear_inode(inode); +} + +static const struct super_operations efivarfs_ops = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, + .evict_inode = efivarfs_evict_inode, + .show_options = generic_show_options, +}; + +static struct super_block *efivarfs_sb; + +/* + * Compare two efivarfs file names. + * + * An efivarfs filename is composed of two parts, + * + * 1. A case-sensitive variable name + * 2. A case-insensitive GUID + * + * So we need to perform a case-sensitive match on part 1 and a + * case-insensitive match on part 2. + */ +static int efivarfs_d_compare(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, + const struct qstr *name) +{ + const char *q; + int guid; + + /* + * If the string we're being asked to compare doesn't match + * the expected format return "no match". + */ + if (!efivarfs_valid_name(str, len)) + return 1; + + if (!(q = strchr(name->name, '-'))) + return 1; + + /* Find part 1, the variable name. */ + guid = q - (const char *)name->name; + + /* Case-sensitive compare for the variable name */ + if (memcmp(str, name->name, guid)) + return 1; + + /* Case-insensitive compare for the GUID */ + return strcasecmp(&name->name[guid], &str[guid]); +} + +static int efivarfs_d_hash(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + const unsigned char *us; + char lower[NAME_MAX]; + int guid; + + if (!efivarfs_valid_name(qstr->name, qstr->len)) + return -EINVAL; + + if (qstr->len > NAME_MAX) + return -ENAMETOOLONG; + + us = strchr(qstr->name, '-'); + if (!us) + return -EINVAL; + + /* The variable name part is case-sensitive */ + guid = us - qstr->name; + memcpy(lower, qstr->name, guid); + + /* GUID is case-insensitive. */ + for (us = &qstr->name[guid]; guid < qstr->len; guid++) + lower[guid] = tolower(*us++); + lower[guid] = '\0'; + + qstr->hash = full_name_hash(lower, qstr->len); + return 0; +} + +/* + * Retaining negative dentries for an in-memory filesystem just wastes + * memory and lookup time: arrange for them to be deleted immediately. + */ +static int efivarfs_delete_dentry(const struct dentry *dentry) +{ + return 1; +} + +static struct dentry_operations efivarfs_d_ops = { + .d_compare = efivarfs_d_compare, + .d_hash = efivarfs_d_hash, + .d_delete = efivarfs_delete_dentry, +}; + +static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *inode = NULL; + struct dentry *root; + struct efivar_entry *entry, *n; + struct efivars *efivars = &__efivars; + char *name; + + efivarfs_sb = sb; + + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = EFIVARFS_MAGIC; + sb->s_op = &efivarfs_ops; + sb->s_d_op = &efivarfs_d_ops; + sb->s_time_gran = 1; + + inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0); + if (!inode) + return -ENOMEM; + inode->i_op = &efivarfs_dir_inode_operations; + + root = d_make_root(inode); + sb->s_root = root; + if (!root) + return -ENOMEM; + + list_for_each_entry_safe(entry, n, &efivars->list, list) { + struct dentry *dentry, *root = efivarfs_sb->s_root; + unsigned long size = 0; + int len, i; + + inode = NULL; + + len = utf16_strlen(entry->var.VariableName); + + /* name, plus '-', plus GUID, plus NUL*/ + name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_ATOMIC); + if (!name) + goto fail; + + for (i = 0; i < len; i++) + name[i] = entry->var.VariableName[i] & 0xFF; + + name[len] = '-'; + + efi_guid_unparse(&entry->var.VendorGuid, name + len + 1); + + name[len+EFI_VARIABLE_GUID_LEN+1] = '\0'; + + inode = efivarfs_get_inode(efivarfs_sb, root->d_inode, + S_IFREG | 0644, 0); + if (!inode) + goto fail_name; + + dentry = d_alloc_name(root, name); + if (!dentry) + goto fail_inode; + + /* copied by the above to local storage in the dentry. */ + kfree(name); + + spin_lock(&efivars->lock); + efivars->ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + &entry->var.Attributes, + &size, + NULL); + spin_unlock(&efivars->lock); + + mutex_lock(&inode->i_mutex); + inode->i_private = entry; + i_size_write(inode, size + sizeof(entry->var.Attributes)); + mutex_unlock(&inode->i_mutex); + d_add(dentry, inode); + } + + return 0; + +fail_inode: + iput(inode); +fail_name: + kfree(name); +fail: + return -ENOMEM; +} + +static struct dentry *efivarfs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, efivarfs_fill_super); +} + +static void efivarfs_kill_sb(struct super_block *sb) +{ + kill_litter_super(sb); + efivarfs_sb = NULL; +} + +static struct file_system_type efivarfs_type = { + .name = "efivarfs", + .mount = efivarfs_mount, + .kill_sb = efivarfs_kill_sb, +}; + +static __init int efivarfs_init(void) +{ + return register_filesystem(&efivarfs_type); +} + +static __init void efivarfs_exit(void) +{ +} + +MODULE_AUTHOR("Matthew Garrett, Jeremy Kerr"); +MODULE_DESCRIPTION("EFI Variable Filesystem"); +MODULE_LICENSE("GPL"); + +module_init(efivarfs_init); +module_exit(efivarfs_exit);
diff --git a/include/linux/efi.h b/include/linux/efi.h index 7a9498a..6fd8a4b 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h
@@ -663,6 +663,12 @@ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \ EFI_VARIABLE_APPEND_WRITE) /* + * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")) + * not including trailing NUL + */ +#define EFI_VARIABLE_GUID_LEN 36 + +/* * The type of search to perform when calling boottime->locate_handle */ #define EFI_LOCATE_ALL_HANDLES 0 @@ -719,6 +725,23 @@ *addr &= PAGE_MASK; } +/* Return the number of unicode characters in data */ +static inline unsigned long +utf16_strnlen(efi_char16_t *s, size_t maxlength) +{ + unsigned long length = 0; + + while (*s++ != 0 && length < maxlength) + length++; + return length; +} + +static inline unsigned long +utf16_strlen(efi_char16_t *s) +{ + return utf16_strnlen(s, ~0UL); +} + #if defined(CONFIG_EFI_VARS) || defined(CONFIG_EFI_VARS_MODULE) /* * EFI Variable support. @@ -753,11 +776,39 @@ struct pstore_info efi_pstore_info; }; +/* + * The maximum size of VariableName + Data = 1024 + * Therefore, it's reasonable to save that much + * space in each part of the structure, + * and we use a page for reading/writing. + */ + +struct efi_variable { + efi_char16_t VariableName[1024/sizeof(efi_char16_t)]; + efi_guid_t VendorGuid; + unsigned long DataSize; + __u8 Data[1024]; + efi_status_t Status; + __u32 Attributes; +} __attribute__((packed)); + +struct efivar_entry { + struct efivars *efivars; + struct efi_variable var; + struct list_head list; + struct kobject kobj; +}; + int register_efivars(struct efivars *efivars, const struct efivar_operations *ops, struct kobject *parent_kobj); void unregister_efivars(struct efivars *efivars); +int efivar_list_add(struct efivars *efivars, char *name, + struct efivar_entry *entry); +void efivar_list_del_unlock(struct efivar_entry *entry); +bool efivar_validate(struct efi_variable *var, u8 *data, unsigned long len); + #endif /* CONFIG_EFI_VARS */ #endif /* _LINUX_EFI_H */