efivarfs: Move to fs/efivarfs
[linux-3.10.git] / drivers / firmware / efivars.c
index 7b1c374..af39675 100644 (file)
@@ -78,7 +78,7 @@
 #include <linux/kobject.h>
 #include <linux/device.h>
 #include <linux/slab.h>
-#include <linux/pstore.h>
+#include <linux/ctype.h>
 
 #include <linux/fs.h>
 #include <linux/ramfs.h>
@@ -94,36 +94,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables");
 MODULE_LICENSE("GPL");
 MODULE_VERSION(EFIVARS_VERSION);
 
-#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;
-};
+LIST_HEAD(efivar_sysfs_list);
+EXPORT_SYMBOL_GPL(efivar_sysfs_list);
 
 struct efivar_attribute {
        struct attribute attr;
@@ -131,13 +103,13 @@ struct efivar_attribute {
        ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
 };
 
-static struct efivars __efivars;
-static struct efivar_operations ops;
+/* Private pointer to registered efivars */
+static struct efivars *__efivars;
+
+static struct kset *efivars_kset;
 
-#define PSTORE_EFI_ATTRIBUTES \
-       (EFI_VARIABLE_NON_VOLATILE | \
-        EFI_VARIABLE_BOOTSERVICE_ACCESS | \
-        EFI_VARIABLE_RUNTIME_ACCESS)
+static struct bin_attribute *efivars_new_var;
+static struct bin_attribute *efivars_del_var;
 
 #define EFIVAR_ATTR(_name, _mode, _show, _store) \
 struct efivar_attribute efivar_attr_##_name = { \
@@ -153,55 +125,15 @@ struct efivar_attribute efivar_attr_##_name = { \
  * Prototype for sysfs creation function
  */
 static int
-efivar_create_sysfs_entry(struct efivars *efivars,
-                         unsigned long variable_name_size,
-                         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);
-}
+efivar_create_sysfs_entry(struct efivar_entry *new_var);
 
 /*
- * Return the number of bytes is the length of this string
- * Note: this is NOT the same as the number of unicode characters
+ * Prototype for workqueue functions updating sysfs entry
  */
-static inline unsigned long
-utf16_strsize(efi_char16_t *data, unsigned long maxlength)
-{
-       return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t);
-}
 
-static inline int
-utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
-{
-       while (1) {
-               if (len == 0)
-                       return 0;
-               if (*a < *b)
-                       return -1;
-               if (*a > *b)
-                       return 1;
-               if (*a == 0) /* implies *b == 0 */
-                       return 0;
-               a++;
-               b++;
-               len--;
-       }
-}
+static void efivar_update_sysfs_entries(struct work_struct *);
+static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
+static bool efivar_wq_enabled = true;
 
 static bool
 validate_device_path(struct efi_variable *var, int match, u8 *buffer,
@@ -350,8 +282,8 @@ static const struct variable_validate variable_validate[] = {
        { "", 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;
@@ -386,34 +318,28 @@ validate_var(struct efi_variable *var, u8 *data, unsigned long len)
 
        return true;
 }
+EXPORT_SYMBOL_GPL(efivar_validate);
 
 static efi_status_t
-get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
+check_var_size(u32 attributes, unsigned long size)
 {
+       u64 storage_size, remaining_size, max_size;
        efi_status_t status;
+       const struct efivar_operations *fops = __efivars->ops;
 
-       var->DataSize = 1024;
-       status = efivars->ops->get_variable(var->VariableName,
-                                           &var->VendorGuid,
-                                           &var->Attributes,
-                                           &var->DataSize,
-                                           var->Data);
-       return status;
-}
+       if (!fops->query_variable_info)
+               return EFI_UNSUPPORTED;
 
-static efi_status_t
-get_var_data(struct efivars *efivars, struct efi_variable *var)
-{
-       efi_status_t status;
+       status = fops->query_variable_info(attributes, &storage_size,
+                                          &remaining_size, &max_size);
 
-       spin_lock(&efivars->lock);
-       status = get_var_data_locked(efivars, var);
-       spin_unlock(&efivars->lock);
+       if (status != EFI_SUCCESS)
+               return status;
+
+       if (!storage_size || size > remaining_size || size > max_size ||
+           (remaining_size - size) < (storage_size / 2))
+               return EFI_OUT_OF_RESOURCES;
 
-       if (status != EFI_SUCCESS) {
-               printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
-                       status);
-       }
        return status;
 }
 
@@ -438,13 +364,12 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
 {
        struct efi_variable *var = &entry->var;
        char *str = buf;
-       efi_status_t status;
 
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
-       if (status != EFI_SUCCESS)
+       var->DataSize = 1024;
+       if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
                return -EIO;
 
        if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
@@ -472,13 +397,12 @@ efivar_size_read(struct efivar_entry *entry, char *buf)
 {
        struct efi_variable *var = &entry->var;
        char *str = buf;
-       efi_status_t status;
 
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
-       if (status != EFI_SUCCESS)
+       var->DataSize = 1024;
+       if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
                return -EIO;
 
        str += sprintf(str, "0x%lx\n", var->DataSize);
@@ -489,13 +413,12 @@ static ssize_t
 efivar_data_read(struct efivar_entry *entry, char *buf)
 {
        struct efi_variable *var = &entry->var;
-       efi_status_t status;
 
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
-       if (status != EFI_SUCCESS)
+       var->DataSize = 1024;
+       if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
                return -EIO;
 
        memcpy(buf, var->Data, var->DataSize);
@@ -509,8 +432,7 @@ static ssize_t
 efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
 {
        struct efi_variable *new_var, *var = &entry->var;
-       struct efivars *efivars = entry->efivars;
-       efi_status_t status = EFI_NOT_FOUND;
+       int err;
 
        if (count != sizeof(struct efi_variable))
                return -EINVAL;
@@ -532,27 +454,20 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
        }
 
        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;
        }
 
-       spin_lock(&efivars->lock);
-       status = efivars->ops->set_variable(new_var->VariableName,
-                                           &new_var->VendorGuid,
-                                           new_var->Attributes,
-                                           new_var->DataSize,
-                                           new_var->Data);
-
-       spin_unlock(&efivars->lock);
+       memcpy(&entry->var, new_var, count);
 
-       if (status != EFI_SUCCESS) {
-               printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
-                       status);
+       err = efivar_entry_set(entry, new_var->Attributes,
+                              new_var->DataSize, new_var->Data, false);
+       if (err) {
+               printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
                return -EIO;
        }
 
-       memcpy(&entry->var, new_var, count);
        return count;
 }
 
@@ -560,16 +475,17 @@ static ssize_t
 efivar_show_raw(struct efivar_entry *entry, char *buf)
 {
        struct efi_variable *var = &entry->var;
-       efi_status_t status;
 
        if (!entry || !buf)
                return 0;
 
-       status = get_var_data(entry->efivars, var);
-       if (status != EFI_SUCCESS)
+       var->DataSize = 1024;
+       if (efivar_entry_get(entry, &entry->var.Attributes,
+                            &entry->var.DataSize, entry->var.Data))
                return -EIO;
 
        memcpy(buf, var, sizeof(*var));
+
        return sizeof(*var);
 }
 
@@ -641,23 +557,14 @@ static struct kobj_type efivar_ktype = {
        .default_attrs = def_attrs,
 };
 
-static inline void
-efivar_unregister(struct efivar_entry *var)
-{
-       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_SUCCESS:
+               err = 0;
+               break;
        case EFI_INVALID_PARAMETER:
                err = -EINVAL;
                break;
@@ -683,806 +590,161 @@ static int efi_status_to_err(efi_status_t status)
        return err;
 }
 
-static ssize_t efivarfs_file_write(struct file *file,
-               const char __user *userbuf, size_t count, loff_t *ppos)
+static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
+                            struct bin_attribute *bin_attr,
+                            char *buf, loff_t pos, size_t count)
 {
-       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;
+       struct efi_variable *new_var = (struct efi_variable *)buf;
+       struct efivar_entry *new_entry;
+       int err;
 
-       if (copy_from_user(&attributes, userbuf, sizeof(attributes)))
-               return -EFAULT;
+       if (!capable(CAP_SYS_ADMIN))
+               return -EACCES;
 
-       if (attributes & ~(EFI_VARIABLE_MASK))
+       if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
+           efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) {
+               printk(KERN_ERR "efivars: Malformed variable content\n");
                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)
+       new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
+       if (!new_entry)
                return -ENOMEM;
 
-       if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) {
-               bytes = -EFAULT;
-               goto out;
-       }
+       memcpy(&new_entry->var, new_var, sizeof(*new_var));
 
-       if (validate_var(&var->var, data, datasize) == false) {
-               bytes = -EINVAL;
+       err = efivar_entry_set(new_entry, new_var->Attributes, new_var->DataSize,
+                              new_var->Data, &efivar_sysfs_list);
+       if (err) {
+               if (err == -EEXIST)
+                       err = -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);
-               dput(file->f_dentry);
-
-       } else {
-               spin_unlock(&efivars->lock);
-               pr_warn("efivarfs: inconsistent EFI variable implementation? "
-                               "status = %lx\n", status);
+       if (efivar_create_sysfs_entry(new_entry)) {
+               printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
+               kfree(new_entry);
        }
+       return count;
 
 out:
-       kfree(data);
-
-       return bytes;
+       kfree(new_entry);
+       return err;
 }
 
-static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
-               size_t count, loff_t *ppos)
+static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
+                            struct bin_attribute *bin_attr,
+                            char *buf, loff_t pos, size_t count)
 {
-       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);
+       struct efi_variable *del_var = (struct efi_variable *)buf;
+       struct efivar_entry *entry;
+       int err = 0;
 
-       data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL);
+       if (!capable(CAP_SYS_ADMIN))
+               return -EACCES;
 
-       if (!data)
-               return -ENOMEM;
+       efivar_entry_iter_begin();
+       entry = efivar_entry_find(del_var->VariableName, del_var->VendorGuid,
+                                 &efivar_sysfs_list, true);
+       if (!entry)
+               err = -EINVAL;
+       else if (__efivar_entry_delete(entry))
+               err = -EIO;
 
-       spin_lock(&efivars->lock);
-       status = efivars->ops->get_variable(var->var.VariableName,
-                                           &var->var.VendorGuid,
-                                           &attributes, &datasize,
-                                           (data + sizeof(attributes)));
-       spin_unlock(&efivars->lock);
+       efivar_entry_iter_end();
 
-       if (status != EFI_SUCCESS) {
-               size = efi_status_to_err(status);
-               goto out_free;
-       }
+       if (err)
+               return err;
 
-       memcpy(data, &attributes, sizeof(attributes));
-       size = simple_read_from_buffer(userbuf, count, ppos,
-                                      data, datasize + sizeof(attributes));
-out_free:
-       kfree(data);
+       efivar_unregister(entry);
 
-       return size;
+       /* It's dead Jim.... */
+       return count;
 }
 
-static void efivarfs_evict_inode(struct inode *inode)
+static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
+                               struct list_head *head)
 {
-       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,
-};
+       struct efivar_entry *entry, *n;
+       unsigned long strsize1, strsize2;
+       bool found = false;
 
-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);
+       strsize1 = utf16_strsize(variable_name, 1024);
+       list_for_each_entry_safe(entry, n, head, list) {
+               strsize2 = utf16_strsize(entry->var.VariableName, 1024);
+               if (strsize1 == strsize2 &&
+                       !memcmp(variable_name, &(entry->var.VariableName),
+                               strsize2) &&
+                       !efi_guidcmp(entry->var.VendorGuid,
+                               *vendor)) {
+                       found = true;
                        break;
                }
        }
-       return inode;
-}
-
-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]);
+       return found;
 }
 
-static int efivarfs_create(struct inode *dir, struct dentry *dentry,
-                         umode_t mode, bool excl)
+static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor,
+                                    unsigned long name_size, void *data)
 {
-       struct inode *inode;
-       struct efivars *efivars = &__efivars;
-       struct efivar_entry *var;
-       int namelen, i = 0, err = 0;
-
-       /*
-        * We need a GUID, plus at least one letter for the variable name,
-        * plus the '-' separator
-        */
-       if (dentry->d_name.len < GUID_LEN + 2)
-               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);
+       struct efivar_entry *entry = data;
 
-       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(dir);
-               dput(dentry);
+       if (efivar_entry_find(name, vendor, &efivar_sysfs_list, false))
                return 0;
-       }
-
-       spin_unlock(&efivars->lock);
-       return -EINVAL;
-};
-
-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_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+4);
-               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 const struct inode_operations efivarfs_dir_inode_operations = {
-       .lookup = simple_lookup,
-       .unlink = efivarfs_unlink,
-       .create = efivarfs_create,
-};
-
-static struct pstore_info efi_pstore_info;
-
-#ifdef CONFIG_PSTORE
-
-static int efi_pstore_open(struct pstore_info *psi)
-{
-       struct efivars *efivars = psi->data;
-
-       spin_lock(&efivars->lock);
-       efivars->walk_entry = list_first_entry(&efivars->list,
-                                              struct efivar_entry, list);
-       return 0;
-}
-
-static int efi_pstore_close(struct pstore_info *psi)
-{
-       struct efivars *efivars = psi->data;
-
-       spin_unlock(&efivars->lock);
-       return 0;
-}
-
-static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
-                              int *count, struct timespec *timespec,
-                              char **buf, struct pstore_info *psi)
-{
-       efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
-       struct efivars *efivars = psi->data;
-       char name[DUMP_NAME_LEN];
-       int i;
-       int cnt;
-       unsigned int part, size;
-       unsigned long time;
-
-       while (&efivars->walk_entry->list != &efivars->list) {
-               if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid,
-                                vendor)) {
-                       for (i = 0; i < DUMP_NAME_LEN; i++) {
-                               name[i] = efivars->walk_entry->var.VariableName[i];
-                       }
-                       if (sscanf(name, "dump-type%u-%u-%d-%lu",
-                                  type, &part, &cnt, &time) == 4) {
-                               *id = part;
-                               *count = cnt;
-                               timespec->tv_sec = time;
-                               timespec->tv_nsec = 0;
-                       } else if (sscanf(name, "dump-type%u-%u-%lu",
-                                  type, &part, &time) == 3) {
-                               /*
-                                * Check if an old format,
-                                * which doesn't support holding
-                                * multiple logs, remains.
-                                */
-                               *id = part;
-                               *count = 0;
-                               timespec->tv_sec = time;
-                               timespec->tv_nsec = 0;
-                       } else {
-                               efivars->walk_entry = list_entry(
-                                               efivars->walk_entry->list.next,
-                                               struct efivar_entry, list);
-                               continue;
-                       }
-
-                       get_var_data_locked(efivars, &efivars->walk_entry->var);
-                       size = efivars->walk_entry->var.DataSize;
-                       *buf = kmalloc(size, GFP_KERNEL);
-                       if (*buf == NULL)
-                               return -ENOMEM;
-                       memcpy(*buf, efivars->walk_entry->var.Data,
-                              size);
-                       efivars->walk_entry = list_entry(
-                                       efivars->walk_entry->list.next,
-                                       struct efivar_entry, list);
-                       return size;
-               }
-               efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
-                                                struct efivar_entry, list);
-       }
-       return 0;
-}
-
-static int efi_pstore_write(enum pstore_type_id type,
-               enum kmsg_dump_reason reason, u64 *id,
-               unsigned int part, int count, size_t size,
-               struct pstore_info *psi)
-{
-       char name[DUMP_NAME_LEN];
-       efi_char16_t efi_name[DUMP_NAME_LEN];
-       efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
-       struct efivars *efivars = psi->data;
-       int i, ret = 0;
-       u64 storage_space, remaining_space, max_variable_size;
-       efi_status_t status = EFI_NOT_FOUND;
-
-       spin_lock(&efivars->lock);
-
-       /*
-        * Check if there is a space enough to log.
-        * size: a size of logging data
-        * DUMP_NAME_LEN * 2: a maximum size of variable name
-        */
-       status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES,
-                                                  &storage_space,
-                                                  &remaining_space,
-                                                  &max_variable_size);
-       if (status || remaining_space < size + DUMP_NAME_LEN * 2) {
-               spin_unlock(&efivars->lock);
-               *id = part;
-               return -ENOSPC;
-       }
-
-       sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count,
-               get_seconds());
-
-       for (i = 0; i < DUMP_NAME_LEN; i++)
-               efi_name[i] = name[i];
-
-       efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES,
-                                  size, psi->buf);
-
-       spin_unlock(&efivars->lock);
-
-       if (size)
-               ret = efivar_create_sysfs_entry(efivars,
-                                         utf16_strsize(efi_name,
-                                                       DUMP_NAME_LEN * 2),
-                                         efi_name, &vendor);
-
-       *id = part;
-       return ret;
-};
-
-static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
-                           struct timespec time, struct pstore_info *psi)
-{
-       char name[DUMP_NAME_LEN];
-       efi_char16_t efi_name[DUMP_NAME_LEN];
-       char name_old[DUMP_NAME_LEN];
-       efi_char16_t efi_name_old[DUMP_NAME_LEN];
-       efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
-       struct efivars *efivars = psi->data;
-       struct efivar_entry *entry, *found = NULL;
-       int i;
-
-       sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count,
-               time.tv_sec);
-
-       spin_lock(&efivars->lock);
-
-       for (i = 0; i < DUMP_NAME_LEN; i++)
-               efi_name[i] = name[i];
 
-       /*
-        * Clean up an entry with the same name
-        */
-
-       list_for_each_entry(entry, &efivars->list, list) {
-               get_var_data_locked(efivars, &entry->var);
-
-               if (efi_guidcmp(entry->var.VendorGuid, vendor))
-                       continue;
-               if (utf16_strncmp(entry->var.VariableName, efi_name,
-                                 utf16_strlen(efi_name))) {
-                       /*
-                        * Check if an old format,
-                        * which doesn't support holding
-                        * multiple logs, remains.
-                        */
-                       sprintf(name_old, "dump-type%u-%u-%lu", type,
-                               (unsigned int)id, time.tv_sec);
-
-                       for (i = 0; i < DUMP_NAME_LEN; i++)
-                               efi_name_old[i] = name_old[i];
-
-                       if (utf16_strncmp(entry->var.VariableName, efi_name_old,
-                                         utf16_strlen(efi_name_old)))
-                               continue;
-               }
-
-               /* found */
-               found = entry;
-               efivars->ops->set_variable(entry->var.VariableName,
-                                          &entry->var.VendorGuid,
-                                          PSTORE_EFI_ATTRIBUTES,
-                                          0, NULL);
-               break;
-       }
-
-       if (found)
-               list_del(&found->list);
-
-       spin_unlock(&efivars->lock);
+       memcpy(entry->var.VariableName, name, name_size);
+       memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
 
-       if (found)
-               efivar_unregister(found);
-
-       return 0;
-}
-#else
-static int efi_pstore_open(struct pstore_info *psi)
-{
-       return 0;
+       return 1;
 }
 
-static int efi_pstore_close(struct pstore_info *psi)
-{
-       return 0;
-}
-
-static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, int *count,
-                              struct timespec *timespec,
-                              char **buf, struct pstore_info *psi)
-{
-       return -1;
-}
-
-static int efi_pstore_write(enum pstore_type_id type,
-               enum kmsg_dump_reason reason, u64 *id,
-               unsigned int part, int count, size_t size,
-               struct pstore_info *psi)
-{
-       return 0;
-}
-
-static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
-                           struct timespec time, struct pstore_info *psi)
-{
-       return 0;
-}
-#endif
-
-static struct pstore_info efi_pstore_info = {
-       .owner          = THIS_MODULE,
-       .name           = "efi",
-       .open           = efi_pstore_open,
-       .close          = efi_pstore_close,
-       .read           = efi_pstore_read,
-       .write          = efi_pstore_write,
-       .erase          = efi_pstore_erase,
-};
-
-static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
-                            struct bin_attribute *bin_attr,
-                            char *buf, loff_t pos, size_t count)
+/*
+ * Returns the size of variable_name, in bytes, including the
+ * terminating NULL character, or variable_name_size if no NULL
+ * character is found among the first variable_name_size bytes.
+ */
+static unsigned long var_name_strnsize(efi_char16_t *variable_name,
+                                      unsigned long variable_name_size)
 {
-       struct efi_variable *new_var = (struct efi_variable *)buf;
-       struct efivars *efivars = bin_attr->private;
-       struct efivar_entry *search_efivar, *n;
-       unsigned long strsize1, strsize2;
-       efi_status_t status = EFI_NOT_FOUND;
-       int found = 0;
-
-       if (!capable(CAP_SYS_ADMIN))
-               return -EACCES;
-
-       if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
-           validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
-               printk(KERN_ERR "efivars: Malformed variable content\n");
-               return -EINVAL;
-       }
-
-       spin_lock(&efivars->lock);
+       unsigned long len;
+       efi_char16_t c;
 
        /*
-        * Does this variable already exist?
+        * The variable name is, by definition, a NULL-terminated
+        * string, so make absolutely sure that variable_name_size is
+        * the value we expect it to be. If not, return the real size.
         */
-       list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-               strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
-               strsize2 = utf16_strsize(new_var->VariableName, 1024);
-               if (strsize1 == strsize2 &&
-                       !memcmp(&(search_efivar->var.VariableName),
-                               new_var->VariableName, strsize1) &&
-                       !efi_guidcmp(search_efivar->var.VendorGuid,
-                               new_var->VendorGuid)) {
-                       found = 1;
+       for (len = 2; len <= variable_name_size; len += sizeof(c)) {
+               c = variable_name[(len / sizeof(c)) - 1];
+               if (!c)
                        break;
-               }
-       }
-       if (found) {
-               spin_unlock(&efivars->lock);
-               return -EINVAL;
        }
 
-       /* now *really* create the variable via EFI */
-       status = efivars->ops->set_variable(new_var->VariableName,
-                                           &new_var->VendorGuid,
-                                           new_var->Attributes,
-                                           new_var->DataSize,
-                                           new_var->Data);
-
-       if (status != EFI_SUCCESS) {
-               printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
-                       status);
-               spin_unlock(&efivars->lock);
-               return -EIO;
-       }
-       spin_unlock(&efivars->lock);
-
-       /* Create the entry in sysfs.  Locking is not required here */
-       status = efivar_create_sysfs_entry(efivars,
-                                          utf16_strsize(new_var->VariableName,
-                                                        1024),
-                                          new_var->VariableName,
-                                          &new_var->VendorGuid);
-       if (status) {
-               printk(KERN_WARNING "efivars: variable created, but sysfs entry wasn't.\n");
-       }
-       return count;
+       return min(len, variable_name_size);
 }
 
-static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
-                            struct bin_attribute *bin_attr,
-                            char *buf, loff_t pos, size_t count)
+static void efivar_update_sysfs_entries(struct work_struct *work)
 {
-       struct efi_variable *del_var = (struct efi_variable *)buf;
-       struct efivars *efivars = bin_attr->private;
-       struct efivar_entry *search_efivar, *n;
-       unsigned long strsize1, strsize2;
-       efi_status_t status = EFI_NOT_FOUND;
-       int found = 0;
+       struct efivar_entry *entry;
+       int err;
 
-       if (!capable(CAP_SYS_ADMIN))
-               return -EACCES;
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return;
 
-       spin_lock(&efivars->lock);
+       /* Add new sysfs entries */
+       while (1) {
+               memset(entry, 0, sizeof(*entry));
 
-       /*
-        * Does this variable already exist?
-        */
-       list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-               strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
-               strsize2 = utf16_strsize(del_var->VariableName, 1024);
-               if (strsize1 == strsize2 &&
-                       !memcmp(&(search_efivar->var.VariableName),
-                               del_var->VariableName, strsize1) &&
-                       !efi_guidcmp(search_efivar->var.VendorGuid,
-                               del_var->VendorGuid)) {
-                       found = 1;
+               err = efivar_init(efivar_update_sysfs_entry, entry,
+                                 true, false, &efivar_sysfs_list);
+               if (!err)
                        break;
-               }
-       }
-       if (!found) {
-               spin_unlock(&efivars->lock);
-               return -EINVAL;
-       }
-       /* force the Attributes/DataSize to 0 to ensure deletion */
-       del_var->Attributes = 0;
-       del_var->DataSize = 0;
-
-       status = efivars->ops->set_variable(del_var->VariableName,
-                                           &del_var->VendorGuid,
-                                           del_var->Attributes,
-                                           del_var->DataSize,
-                                           del_var->Data);
 
-       if (status != EFI_SUCCESS) {
-               printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
-                       status);
-               spin_unlock(&efivars->lock);
-               return -EIO;
+               efivar_create_sysfs_entry(entry);
        }
-       list_del(&search_efivar->list);
-       /* We need to release this lock before unregistering. */
-       spin_unlock(&efivars->lock);
-       efivar_unregister(search_efivar);
 
-       /* It's dead Jim.... */
-       return count;
+       kfree(entry);
 }
 
 /*
@@ -1529,46 +791,37 @@ static struct attribute_group efi_subsys_attr_group = {
 
 static struct kobject *efi_kobj;
 
-/*
- * efivar_create_sysfs_entry()
- * Requires:
- *    variable_name_size = number of bytes required to hold
- *                         variable_name (not counting the NULL
- *                         character at the end.
- *    efivars->lock is not held on entry or exit.
+/**
+ * efivar_create_sysfs_entry - create a new entry in sysfs
+ * @new_var: efivar entry to create
+ *
  * Returns 1 on failure, 0 on success
  */
 static int
-efivar_create_sysfs_entry(struct efivars *efivars,
-                         unsigned long variable_name_size,
-                         efi_char16_t *variable_name,
-                         efi_guid_t *vendor_guid)
+efivar_create_sysfs_entry(struct efivar_entry *new_var)
 {
        int i, short_name_size;
        char *short_name;
-       struct efivar_entry *new_efivar;
+       unsigned long variable_name_size;
+       efi_char16_t *variable_name;
+
+       variable_name = new_var->var.VariableName;
+       variable_name_size = utf16_strlen(variable_name) * sizeof(efi_char16_t);
 
        /*
         * Length of the variable bytes in ASCII, plus the '-' separator,
         * 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);
 
-       if (!short_name || !new_efivar)  {
+       if (!short_name) {
                kfree(short_name);
-               kfree(new_efivar);
                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));
-
        /* Convert Unicode to normal chars (assume top bits are 0),
           ala UTF-8 */
        for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
@@ -1578,30 +831,25 @@ efivar_create_sysfs_entry(struct efivars *efivars,
           private variables from another's.         */
 
        *(short_name + strlen(short_name)) = '-';
-       efi_guid_unparse(vendor_guid, short_name + strlen(short_name));
+       efi_guid_unparse(&new_var->var.VendorGuid,
+                        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);
-       if (i) {
-               kfree(short_name);
-               kfree(new_efivar);
-               return 1;
-       }
+       new_var->kobj.kset = efivars_kset;
 
-       kobject_uevent(&new_efivar->kobj, KOBJ_ADD);
+       i = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
+                                  NULL, "%s", short_name);
        kfree(short_name);
-       short_name = NULL;
+       if (i)
+               return 1;
 
-       spin_lock(&efivars->lock);
-       list_add(&new_efivar->list, &efivars->list);
-       spin_unlock(&efivars->lock);
+       kobject_uevent(&new_var->kobj, KOBJ_ADD);
+       efivar_entry_add(new_var, &efivar_sysfs_list);
 
        return 0;
 }
 
 static int
-create_efivars_bin_attributes(struct efivars *efivars)
+create_efivars_bin_attributes(void)
 {
        struct bin_attribute *attr;
        int error;
@@ -1614,8 +862,7 @@ create_efivars_bin_attributes(struct efivars *efivars)
        attr->attr.name = "new_var";
        attr->attr.mode = 0200;
        attr->write = efivar_create;
-       attr->private = efivars;
-       efivars->new_var = attr;
+       efivars_new_var = attr;
 
        /* del_var */
        attr = kzalloc(sizeof(*attr), GFP_KERNEL);
@@ -1626,95 +873,174 @@ create_efivars_bin_attributes(struct efivars *efivars)
        attr->attr.name = "del_var";
        attr->attr.mode = 0200;
        attr->write = efivar_delete;
-       attr->private = efivars;
-       efivars->del_var = attr;
+       efivars_del_var = attr;
 
-       sysfs_bin_attr_init(efivars->new_var);
-       sysfs_bin_attr_init(efivars->del_var);
+       sysfs_bin_attr_init(efivars_new_var);
+       sysfs_bin_attr_init(efivars_del_var);
 
        /* Register */
-       error = sysfs_create_bin_file(&efivars->kset->kobj,
-                                     efivars->new_var);
+       error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
        if (error) {
                printk(KERN_ERR "efivars: unable to create new_var sysfs file"
                        " due to error %d\n", error);
                goto out_free;
        }
-       error = sysfs_create_bin_file(&efivars->kset->kobj,
-                                     efivars->del_var);
+
+       error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
        if (error) {
                printk(KERN_ERR "efivars: unable to create del_var sysfs file"
                        " due to error %d\n", error);
-               sysfs_remove_bin_file(&efivars->kset->kobj,
-                                     efivars->new_var);
+               sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
                goto out_free;
        }
 
        return 0;
 out_free:
-       kfree(efivars->del_var);
-       efivars->del_var = NULL;
-       kfree(efivars->new_var);
-       efivars->new_var = NULL;
+       kfree(efivars_del_var);
+       efivars_del_var = NULL;
+       kfree(efivars_new_var);
+       efivars_new_var = NULL;
        return error;
 }
 
-void unregister_efivars(struct efivars *efivars)
+static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
+                                 unsigned long name_size, void *data)
 {
-       struct efivar_entry *entry, *n;
+       struct efivar_entry *entry;
 
-       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);
-       }
-       if (efivars->new_var)
-               sysfs_remove_bin_file(&efivars->kset->kobj, efivars->new_var);
-       if (efivars->del_var)
-               sysfs_remove_bin_file(&efivars->kset->kobj, efivars->del_var);
-       kfree(efivars->new_var);
-       kfree(efivars->del_var);
-       kobject_put(efivars->kobject);
-       kset_unregister(efivars->kset);
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return -ENOMEM;
+
+       memcpy(entry->var.VariableName, name, name_size);
+       memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
+
+       efivar_create_sysfs_entry(entry);
+
+       return 0;
 }
-EXPORT_SYMBOL_GPL(unregister_efivars);
 
-int register_efivars(struct efivars *efivars,
-                    const struct efivar_operations *ops,
-                    struct kobject *parent_kobj)
+static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
 {
-       efi_status_t status = EFI_NOT_FOUND;
-       efi_guid_t vendor_guid;
-       efi_char16_t *variable_name;
-       unsigned long variable_name_size = 1024;
+       efivar_entry_remove(entry);
+       efivar_unregister(entry);
+       return 0;
+}
+
+/*
+ * Print a warning when duplicate EFI variables are encountered and
+ * disable the sysfs workqueue since the firmware is buggy.
+ */
+static void dup_variable_bug(efi_char16_t *s16, efi_guid_t *vendor_guid,
+                            unsigned long len16)
+{
+       size_t i, len8 = len16 / sizeof(efi_char16_t);
+       char *s8;
+
+       /*
+        * Disable the workqueue since the algorithm it uses for
+        * detecting new variables won't work with this buggy
+        * implementation of GetNextVariableName().
+        */
+       efivar_wq_enabled = false;
+
+       s8 = kzalloc(len8, GFP_KERNEL);
+       if (!s8)
+               return;
+
+       for (i = 0; i < len8; i++)
+               s8[i] = s16[i];
+
+       printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
+              s8, vendor_guid);
+       kfree(s8);
+}
+
+static struct kobject *efivars_kobj;
+
+void efivars_sysfs_exit(void)
+{
+       /* Remove all entries and destroy */
+       __efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);
+
+       if (efivars_new_var)
+               sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
+       if (efivars_del_var)
+               sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
+       kfree(efivars_new_var);
+       kfree(efivars_del_var);
+       kobject_put(efivars_kobj);
+       kset_unregister(efivars_kset);
+}
+
+int efivars_sysfs_init(void)
+{
+       struct kobject *parent_kobj = efivars_kobject();
        int error = 0;
 
-       variable_name = kzalloc(variable_name_size, GFP_KERNEL);
-       if (!variable_name) {
-               printk(KERN_ERR "efivars: Memory allocation failed.\n");
-               return -ENOMEM;
-       }
+       /* No efivars has been registered yet */
+       if (!parent_kobj)
+               return 0;
 
-       spin_lock_init(&efivars->lock);
-       INIT_LIST_HEAD(&efivars->list);
-       efivars->ops = ops;
+       printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
+              EFIVARS_DATE);
 
-       efivars->kset = kset_create_and_add("vars", NULL, parent_kobj);
-       if (!efivars->kset) {
+       efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
+       if (!efivars_kset) {
                printk(KERN_ERR "efivars: Subsystem registration failed.\n");
-               error = -ENOMEM;
-               goto out;
+               return -ENOMEM;
        }
 
-       efivars->kobject = kobject_create_and_add("efivars", parent_kobj);
-       if (!efivars->kobject) {
+       efivars_kobj = kobject_create_and_add("efivars", parent_kobj);
+       if (!efivars_kobj) {
                pr_err("efivars: Subsystem registration failed.\n");
-               error = -ENOMEM;
-               kset_unregister(efivars->kset);
-               goto out;
+               kset_unregister(efivars_kset);
+               return -ENOMEM;
        }
 
+       efivar_init(efivars_sysfs_callback, NULL, false,
+                   true, &efivar_sysfs_list);
+
+       error = create_efivars_bin_attributes();
+       if (error)
+               efivars_sysfs_exit();
+
+       return error;
+}
+EXPORT_SYMBOL_GPL(efivars_sysfs_init);
+
+/**
+ * efivar_init - build the initial list of EFI variables
+ * @func: callback function to invoke for every variable
+ * @data: function-specific data to pass to @func
+ * @atomic: do we need to execute the @func-loop atomically?
+ * @duplicates: error if we encounter duplicates on @head?
+ * @head: initialised head of variable list
+ *
+ * Get every EFI variable from the firmware and invoke @func. @func
+ * should call efivar_entry_add() to build the list of variables.
+ *
+ * Returns 0 on success, or a kernel error code on failure.
+ */
+int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
+               void *data, bool atomic, bool duplicates,
+               struct list_head *head)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       unsigned long variable_name_size = 1024;
+       efi_char16_t *variable_name;
+       efi_status_t status;
+       efi_guid_t vendor_guid;
+       int err = 0;
+
+       variable_name = kzalloc(variable_name_size, GFP_KERNEL);
+       if (!variable_name) {
+               printk(KERN_ERR "efivars: Memory allocation failed.\n");
+               return -ENOMEM;
+       }
+
+       spin_lock_irq(&__efivars->lock);
+
        /*
         * Per EFI spec, the maximum storage allocated for both
         * the variable name and variable data is 1024 bytes.
@@ -1728,10 +1054,38 @@ int register_efivars(struct efivars *efivars,
                                                &vendor_guid);
                switch (status) {
                case EFI_SUCCESS:
-                       efivar_create_sysfs_entry(efivars,
-                                                 variable_name_size,
-                                                 variable_name,
-                                                 &vendor_guid);
+                       if (!atomic)
+                               spin_unlock_irq(&__efivars->lock);
+
+                       variable_name_size = var_name_strnsize(variable_name,
+                                                              variable_name_size);
+
+                       /*
+                        * Some firmware implementations return the
+                        * same variable name on multiple calls to
+                        * get_next_variable(). Terminate the loop
+                        * immediately as there is no guarantee that
+                        * we'll ever see a different variable name,
+                        * and may end up looping here forever.
+                        */
+                       if (duplicates &&
+                           variable_is_present(variable_name, &vendor_guid, head)) {
+                               dup_variable_bug(variable_name, &vendor_guid,
+                                                variable_name_size);
+                               if (!atomic)
+                                       spin_lock_irq(&__efivars->lock);
+
+                               status = EFI_NOT_FOUND;
+                               break;
+                       }
+
+                       err = func(variable_name, vendor_guid, variable_name_size, data);
+                       if (err)
+                               status = EFI_NOT_FOUND;
+
+                       if (!atomic)
+                               spin_lock_irq(&__efivars->lock);
+
                        break;
                case EFI_NOT_FOUND:
                        break;
@@ -1741,30 +1095,643 @@ int register_efivars(struct efivars *efivars,
                        status = EFI_NOT_FOUND;
                        break;
                }
+
        } while (status != EFI_NOT_FOUND);
 
-       error = create_efivars_bin_attributes(efivars);
-       if (error)
-               unregister_efivars(efivars);
+       spin_unlock_irq(&__efivars->lock);
+
+       kfree(variable_name);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(efivar_init);
+
+/**
+ * efivar_entry_add - add entry to variable list
+ * @entry: entry to add to list
+ * @head: list head
+ */
+void efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
+{
+       spin_lock_irq(&__efivars->lock);
+       list_add(&entry->list, head);
+       spin_unlock_irq(&__efivars->lock);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_add);
+
+/**
+ * efivar_entry_remove - remove entry from variable list
+ * @entry: entry to remove from list
+ */
+void efivar_entry_remove(struct efivar_entry *entry)
+{
+       spin_lock_irq(&__efivars->lock);
+       list_del(&entry->list);
+       spin_unlock_irq(&__efivars->lock);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_remove);
+
+/*
+ * efivar_entry_list_del_unlock - remove entry from variable list
+ * @entry: entry to remove
+ *
+ * Remove @entry from the variable list and release the list lock.
+ *
+ * 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.
+ */
+static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
+{
+       WARN_ON(!spin_is_locked(&__efivars->lock));
+
+       list_del(&entry->list);
+       spin_unlock_irq(&__efivars->lock);
+}
+
+/**
+ * __efivar_entry_delete - delete an EFI variable
+ * @entry: entry containing EFI variable to delete
+ *
+ * Delete the variable from the firmware and remove @entry from the
+ * variable list. It is the caller's responsibility to free @entry
+ * once we return.
+ *
+ * This function differs from efivar_entry_delete() because it is
+ * safe to be called from within a efivar_entry_iter_begin() and
+ * efivar_entry_iter_end() region, unlike efivar_entry_delete().
+ *
+ * Returns 0 on success, or a converted EFI status code if
+ * set_variable() fails. If set_variable() fails the entry remains
+ * on the list.
+ */
+int __efivar_entry_delete(struct efivar_entry *entry)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+
+       WARN_ON(!spin_is_locked(&__efivars->lock));
+
+       status = ops->set_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid,
+                                  0, 0, NULL);
+       if (status)
+               return efi_status_to_err(status);
+
+       list_del(&entry->list);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_delete);
+
+/**
+ * efivar_entry_delete - delete variable and remove entry from list
+ * @entry: entry containing variable to delete
+ *
+ * Delete the variable from the firmware and remove @entry from the
+ * variable list. It is the caller's responsibility to free @entry
+ * once we return.
+ *
+ * Returns 0 on success, or a converted EFI status code if
+ * set_variable() fails.
+ */
+int efivar_entry_delete(struct efivar_entry *entry)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+
+       spin_lock_irq(&__efivars->lock);
+       status = ops->set_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid,
+                                  0, 0, NULL);
+       if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
+               spin_unlock_irq(&__efivars->lock);
+               return efi_status_to_err(status);
+       }
 
-       efivars->efi_pstore_info = efi_pstore_info;
+       efivar_entry_list_del_unlock(entry);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_delete);
+
+/**
+ * efivar_entry_set - call set_variable()
+ * @entry: entry containing the EFI variable to write
+ * @attributes: variable attributes
+ * @size: size of @data buffer
+ * @data: buffer containing variable data
+ * @head: head of variable list
+ *
+ * Calls set_variable() for an EFI variable. If creating a new EFI
+ * variable, this function is usually followed by efivar_entry_add().
+ *
+ * Before writing the variable, the remaining EFI variable storage
+ * space is checked to ensure there is enough room available.
+ *
+ * If @head is not NULL a lookup is performed to determine whether
+ * the entry is already on the list.
+ *
+ * Returns 0 on success, -EEXIST if a lookup is performed and the entry
+ * already exists on the list, or a converted EFI status code if
+ * set_variable() fails.
+ */
+int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
+                    unsigned long size, void *data, struct list_head *head)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+       efi_char16_t *name = entry->var.VariableName;
+       efi_guid_t vendor = entry->var.VendorGuid;
 
-       efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
-       if (efivars->efi_pstore_info.buf) {
-               efivars->efi_pstore_info.bufsize = 1024;
-               efivars->efi_pstore_info.data = efivars;
-               spin_lock_init(&efivars->efi_pstore_info.buf_lock);
-               pstore_register(&efivars->efi_pstore_info);
+       spin_lock_irq(&__efivars->lock);
+
+       if (head && efivar_entry_find(name, vendor, head, false)) {
+               spin_unlock_irq(&__efivars->lock);
+               return -EEXIST;
        }
 
-       register_filesystem(&efivarfs_type);
+       status = check_var_size(attributes, size + utf16_strsize(name, 1024));
+       if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
+               status = ops->set_variable(name, &vendor,
+                                          attributes, size, data);
+
+       spin_unlock_irq(&__efivars->lock);
+
+       return efi_status_to_err(status);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_set);
+
+/**
+ * efivar_entry_set_safe - call set_variable() if enough space in firmware
+ * @name: buffer containing the variable name
+ * @vendor: variable vendor guid
+ * @attributes: variable attributes
+ * @block: can we block in this context?
+ * @size: size of @data buffer
+ * @data: buffer containing variable data
+ *
+ * Ensures there is enough free storage in the firmware for this variable, and
+ * if so, calls set_variable(). If creating a new EFI variable, this function
+ * is usually followed by efivar_entry_add().
+ *
+ * Returns 0 on success, -ENOSPC if the firmware does not have enough
+ * space for set_variable() to succeed, or a converted EFI status code
+ * if set_variable() fails.
+ */
+int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
+                         bool block, unsigned long size, void *data)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       unsigned long flags;
+       efi_status_t status;
+
+       if (!ops->query_variable_info)
+               return -ENOSYS;
+
+       if (!block && !spin_trylock_irqsave(&__efivars->lock, flags))
+               return -EBUSY;
+       else
+               spin_lock_irqsave(&__efivars->lock, flags);
+
+       status = check_var_size(attributes, size + utf16_strsize(name, 1024));
+       if (status != EFI_SUCCESS) {
+               spin_unlock_irqrestore(&__efivars->lock, flags);
+               return -ENOSPC;
+       }
+
+       status = ops->set_variable(name, &vendor, attributes, size, data);
+
+       spin_unlock_irqrestore(&__efivars->lock, flags);
+
+       return efi_status_to_err(status);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_set_safe);
+
+/**
+ * efivar_entry_find - search for an entry
+ * @name: the EFI variable name
+ * @guid: the EFI variable vendor's guid
+ * @head: head of the variable list
+ * @remove: should we remove the entry from the list?
+ *
+ * Search for an entry on the variable list that has the EFI variable
+ * name @name and vendor guid @guid. If an entry is found on the list
+ * and @remove is true, the entry is removed from the list.
+ *
+ * The caller MUST call efivar_entry_iter_begin() and
+ * efivar_entry_iter_end() before and after the invocation of this
+ * function, respectively.
+ *
+ * Returns the entry if found on the list, %NULL otherwise.
+ */
+struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
+                                      struct list_head *head, bool remove)
+{
+       struct efivar_entry *entry, *n;
+       int strsize1, strsize2;
+       bool found = false;
+
+       WARN_ON(!spin_is_locked(&__efivars->lock));
+
+       list_for_each_entry_safe(entry, n, head, list) {
+               strsize1 = utf16_strsize(name, 1024);
+               strsize2 = utf16_strsize(entry->var.VariableName, 1024);
+               if (strsize1 == strsize2 &&
+                   !memcmp(name, &(entry->var.VariableName), strsize1) &&
+                   !efi_guidcmp(guid, entry->var.VendorGuid)) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found)
+               return NULL;
+
+       if (remove)
+               list_del(&entry->list);
+
+       return entry;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_find);
+
+/**
+ * __efivar_entry_size - obtain the size of a variable
+ * @entry: entry for this variable
+ * @size: location to store the variable's size
+ *
+ * The caller MUST call efivar_entry_iter_begin() and
+ * efivar_entry_iter_end() before and after the invocation of this
+ * function, respectively.
+ */
+int __efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+
+       WARN_ON(!spin_is_locked(&__efivars->lock));
+
+       *size = 0;
+       status = ops->get_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid, NULL, size, NULL);
+       if (status != EFI_BUFFER_TOO_SMALL)
+               return efi_status_to_err(status);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_size);
+
+/**
+ * efivar_entry_size - obtain the size of a variable
+ * @entry: entry for this variable
+ * @size: location to store the variable's size
+ */
+int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+
+       *size = 0;
+
+       spin_lock_irq(&__efivars->lock);
+       status = ops->get_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid, NULL, size, NULL);
+       spin_unlock_irq(&__efivars->lock);
+
+       if (status != EFI_BUFFER_TOO_SMALL)
+               return efi_status_to_err(status);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_size);
+
+/**
+ * efivar_entry_get - call get_variable()
+ * @entry: read data for this variable
+ * @attributes: variable attributes
+ * @size: size of @data buffer
+ * @data: buffer to store variable data
+ */
+int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
+                    unsigned long *size, void *data)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_status_t status;
+
+       spin_lock_irq(&__efivars->lock);
+       status = ops->get_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid,
+                                  attributes, size, data);
+       spin_unlock_irq(&__efivars->lock);
+
+       return efi_status_to_err(status);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_get);
+
+/**
+ * efivar_entry_set_get_size - call set_variable() and get new size (atomic)
+ * @entry: entry containing variable to set and get
+ * @attributes: attributes of variable to be written
+ * @size: size of data buffer
+ * @data: buffer containing data to write
+ * @set: did the set_variable() call succeed?
+ *
+ * This is a pretty special (complex) function. See efivarfs_file_write().
+ *
+ * Atomically call set_variable() for @entry and if the call is
+ * successful, return the new size of the variable from get_variable()
+ * in @size. The success of set_variable() is indicated by @set.
+ *
+ * Returns 0 on success, -EINVAL if the variable data is invalid,
+ * -ENOSPC if the firmware does not have enough available space, or a
+ * converted EFI status code if either of set_variable() or
+ * get_variable() fail.
+ *
+ * If the EFI variable does not exist when calling set_variable()
+ * (EFI_NOT_FOUND), @entry is removed from the variable list.
+ */
+int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
+                             unsigned long *size, void *data, bool *set)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       efi_char16_t *name = entry->var.VariableName;
+       efi_guid_t *vendor = &entry->var.VendorGuid;
+       efi_status_t status;
+       int err;
+
+       *set = false;
+
+       if (efivar_validate(&entry->var, data, *size) == false)
+               return -EINVAL;
+
+       /*
+        * 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_irq(&__efivars->lock);
+
+       /*
+        * Ensure that the available space hasn't shrunk below the safe level
+        */
+       status = check_var_size(attributes, *size + utf16_strsize(name, 1024));
+       if (status != EFI_SUCCESS) {
+               if (status != EFI_UNSUPPORTED) {
+                       err = efi_status_to_err(status);
+                       goto out;
+               }
+
+               if (*size > 65536) {
+                       err = -ENOSPC;
+                       goto out;
+               }
+       }
+
+       status = ops->set_variable(name, vendor, attributes, *size, data);
+       if (status != EFI_SUCCESS) {
+               err = efi_status_to_err(status);
+               goto out;
+       }
+
+       *set = true;
+
+       /*
+        * 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.
+        */
+       *size = 0;
+       status = ops->get_variable(entry->var.VariableName,
+                                  &entry->var.VendorGuid,
+                                  NULL, size, NULL);
+
+       if (status == EFI_NOT_FOUND)
+               efivar_entry_list_del_unlock(entry);
+       else
+               spin_unlock_irq(&__efivars->lock);
+
+       if (status && status != EFI_BUFFER_TOO_SMALL)
+               return efi_status_to_err(status);
+
+       return 0;
 
 out:
-       kfree(variable_name);
+       spin_unlock_irq(&__efivars->lock);
+       return err;
+
+}
+EXPORT_SYMBOL_GPL(efivar_entry_set_get_size);
+
+/**
+ * efivar_entry_iter_begin - begin iterating the variable list
+ *
+ * Lock the variable list to prevent entry insertion and removal until
+ * efivar_entry_iter_end() is called. This function is usually used in
+ * conjunction with __efivar_entry_iter() or efivar_entry_iter().
+ */
+void efivar_entry_iter_begin(void)
+{
+       spin_lock_irq(&__efivars->lock);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_iter_begin);
+
+/**
+ * efivar_entry_iter_end - finish iterating the variable list
+ *
+ * Unlock the variable list and allow modifications to the list again.
+ */
+void efivar_entry_iter_end(void)
+{
+       spin_unlock_irq(&__efivars->lock);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
+
+/**
+ * __efivar_entry_iter - iterate over variable list
+ * @func: callback function
+ * @head: head of the variable list
+ * @data: function-specific data to pass to callback
+ * @prev: entry to begin iterating from
+ *
+ * Iterate over the list of EFI variables and call @func with every
+ * entry on the list. It is safe for @func to remove entries in the
+ * list via efivar_entry_delete().
+ *
+ * You MUST call efivar_enter_iter_begin() before this function, and
+ * efivar_entry_iter_end() afterwards.
+ *
+ * It is possible to begin iteration from an arbitrary entry within
+ * the list by passing @prev. @prev is updated on return to point to
+ * the last entry passed to @func. To begin iterating from the
+ * beginning of the list @prev must be %NULL.
+ *
+ * The restrictions for @func are the same as documented for
+ * efivar_entry_iter().
+ */
+int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
+                       struct list_head *head, void *data,
+                       struct efivar_entry **prev)
+{
+       struct efivar_entry *entry, *n;
+       int err = 0;
+
+       if (!prev || !*prev) {
+               list_for_each_entry_safe(entry, n, head, list) {
+                       err = func(entry, data);
+                       if (err)
+                               break;
+               }
+
+               if (prev)
+                       *prev = entry;
+
+               return err;
+       }
+
+
+       list_for_each_entry_safe_continue((*prev), n, head, list) {
+               err = func(*prev, data);
+               if (err)
+                       break;
+       }
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_iter);
+
+/**
+ * efivar_entry_iter - iterate over variable list
+ * @func: callback function
+ * @head: head of variable list
+ * @data: function-specific data to pass to callback
+ *
+ * Iterate over the list of EFI variables and call @func with every
+ * entry on the list. It is safe for @func to remove entries in the
+ * list via efivar_entry_delete() while iterating.
+ *
+ * Some notes for the callback function:
+ *  - a non-zero return value indicates an error and terminates the loop
+ *  - @func is called from atomic context
+ */
+int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
+                     struct list_head *head, void *data)
+{
+       int err = 0;
+
+       efivar_entry_iter_begin();
+       err = __efivar_entry_iter(func, head, data, NULL);
+       efivar_entry_iter_end();
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_iter);
+
+/**
+ * efivars_kobject - get the kobject for the registered efivars
+ *
+ * If efivars_register() has not been called we return NULL,
+ * otherwise return the kobject used at registration time.
+ */
+struct kobject *efivars_kobject(void)
+{
+       if (!__efivars)
+               return NULL;
+
+       return __efivars->kobject;
+}
+EXPORT_SYMBOL_GPL(efivars_kobject);
+
+/**
+ * efivar_run_worker - schedule the efivar worker thread
+ */
+void efivar_run_worker(void)
+{
+       if (efivar_wq_enabled)
+               schedule_work(&efivar_work);
+}
+EXPORT_SYMBOL_GPL(efivar_run_worker);
+
+/**
+ * efivars_register - register an efivars
+ * @efivars: efivars to register
+ * @ops: efivars operations
+ * @kobject: @efivars-specific kobject
+ *
+ * Only a single efivars can be registered at any time.
+ */
+int efivars_register(struct efivars *efivars,
+                    const struct efivar_operations *ops,
+                    struct kobject *kobject)
+{
+       spin_lock_init(&efivars->lock);
+       efivars->ops = ops;
+       efivars->kobject = kobject;
+
+       __efivars = efivars;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(efivars_register);
+
+/**
+ * efivars_unregister - unregister an efivars
+ * @efivars: efivars to unregister
+ *
+ * The caller must have already removed every entry from the list,
+ * failure to do so is an error.
+ */
+int efivars_unregister(struct efivars *efivars)
+{
+       int rv;
+
+       if (!__efivars) {
+               printk(KERN_ERR "efivars not registered\n");
+               rv = -EINVAL;
+               goto out;
+       }
+
+       if (__efivars != efivars) {
+               rv = -EINVAL;
+               goto out;
+       }
+
+       __efivars = NULL;
+
+       rv = 0;
+out:
+       return rv;
+}
+EXPORT_SYMBOL_GPL(efivars_unregister);
+
+static struct efivars generic_efivars;
+static struct efivar_operations generic_ops;
+
+static int generic_ops_register(void)
+{
+       int error;
+
+       generic_ops.get_variable = efi.get_variable;
+       generic_ops.set_variable = efi.set_variable;
+       generic_ops.get_next_variable = efi.get_next_variable;
+       generic_ops.query_variable_info = efi.query_variable_info;
+
+       error = efivars_register(&generic_efivars, &generic_ops, efi_kobj);
+       if (error)
+               return error;
+
+       error = efivars_sysfs_init();
+       if (error)
+               efivars_unregister(&generic_efivars);
 
        return error;
 }
-EXPORT_SYMBOL_GPL(register_efivars);
+
+static void generic_ops_unregister(void)
+{
+       efivars_sysfs_exit();
+       efivars_unregister(&generic_efivars);
+}
 
 /*
  * For now we register the efi subsystem with the firmware subsystem
@@ -1777,27 +1744,19 @@ EXPORT_SYMBOL_GPL(register_efivars);
 static int __init
 efivars_init(void)
 {
-       int error = 0;
-
-       printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
-              EFIVARS_DATE);
+       int error;
 
-       if (!efi_enabled)
+       if (!efi_enabled(EFI_RUNTIME_SERVICES))
                return 0;
 
-       /* For now we'll register the efi directory at /sys/firmware/efi */
+       /* Register the efi directory at /sys/firmware/efi */
        efi_kobj = kobject_create_and_add("efi", firmware_kobj);
        if (!efi_kobj) {
                printk(KERN_ERR "efivars: Firmware registration failed.\n");
                return -ENOMEM;
        }
 
-       ops.get_variable = efi.get_variable;
-       ops.set_variable = efi.set_variable;
-       ops.get_next_variable = efi.get_next_variable;
-       ops.query_variable_info = efi.query_variable_info;
-
-       error = register_efivars(&__efivars, &ops, efi_kobj);
+       error = generic_ops_register();
        if (error)
                goto err_put;
 
@@ -1813,7 +1772,7 @@ efivars_init(void)
        return 0;
 
 err_unregister:
-       unregister_efivars(&__efivars);
+       generic_ops_unregister();
 err_put:
        kobject_put(efi_kobj);
        return error;
@@ -1822,8 +1781,10 @@ err_put:
 static void __exit
 efivars_exit(void)
 {
-       if (efi_enabled) {
-               unregister_efivars(&__efivars);
+       cancel_work_sync(&efivar_work);
+
+       if (efi_enabled(EFI_RUNTIME_SERVICES)) {
+               generic_ops_unregister();
                kobject_put(efi_kobj);
        }
 }