efivarfs: Move to fs/efivarfs
[linux-3.10.git] / drivers / firmware / efivars.c
index 47408e8..af39675 100644 (file)
 #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>
+#include <linux/pagemap.h>
 
 #include <asm/uaccess.h>
 
@@ -90,31 +94,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables");
 MODULE_LICENSE("GPL");
 MODULE_VERSION(EFIVARS_VERSION);
 
-#define DUMP_NAME_LEN 52
-
-/*
- * 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;
@@ -122,10 +103,13 @@ struct efivar_attribute {
        ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
 };
 
-#define PSTORE_EFI_ATTRIBUTES \
-       (EFI_VARIABLE_NON_VOLATILE | \
-        EFI_VARIABLE_BOOTSERVICE_ACCESS | \
-        EFI_VARIABLE_RUNTIME_ACCESS)
+/* Private pointer to registered efivars */
+static struct efivars *__efivars;
+
+static struct kset *efivars_kset;
+
+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 = { \
@@ -141,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,
@@ -338,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;
@@ -374,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;
 }
 
@@ -426,21 +364,31 @@ 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 & 0x1)
+       if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
                str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
-       if (var->Attributes & 0x2)
+       if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
                str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
-       if (var->Attributes & 0x4)
+       if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
                str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
+       if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
+               str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
+       if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
+               str += sprintf(str,
+                       "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
+       if (var->Attributes &
+                       EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
+               str += sprintf(str,
+                       "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
+       if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
+               str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
        return str - buf;
 }
 
@@ -449,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);
@@ -466,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);
@@ -486,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;
@@ -509,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;
 }
 
@@ -537,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);
 }
 
@@ -618,317 +557,194 @@ static struct kobj_type efivar_ktype = {
        .default_attrs = def_attrs,
 };
 
-static struct pstore_info efi_pstore_info;
-
-static inline void
-efivar_unregister(struct efivar_entry *var)
-{
-       kobject_put(&var->kobj);
-}
-
-#ifdef CONFIG_PSTORE
-
-static int efi_pstore_open(struct pstore_info *psi)
+static int efi_status_to_err(efi_status_t status)
 {
-       struct efivars *efivars = psi->data;
+       int err;
+
+       switch (status) {
+       case EFI_SUCCESS:
+               err = 0;
+               break;
+       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 = -ENOENT;
+               break;
+       default:
+               err = -EINVAL;
+       }
 
-       spin_lock(&efivars->lock);
-       efivars->walk_entry = list_first_entry(&efivars->list,
-                                              struct efivar_entry, list);
-       return 0;
+       return err;
 }
 
-static int efi_pstore_close(struct pstore_info *psi)
+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 efivars *efivars = psi->data;
+       struct efi_variable *new_var = (struct efi_variable *)buf;
+       struct efivar_entry *new_entry;
+       int err;
 
-       spin_unlock(&efivars->lock);
-       return 0;
-}
+       if (!capable(CAP_SYS_ADMIN))
+               return -EACCES;
 
-static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
-                              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;
-       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-%lu", type, &part, &time) == 3) {
-                               *id = part;
-                               timespec->tv_sec = time;
-                               timespec->tv_nsec = 0;
-                               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);
+       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;
        }
-       return 0;
-}
-
-static int efi_pstore_write(enum pstore_type_id type,
-               enum kmsg_dump_reason reason, u64 *id,
-               unsigned int part, size_t size, struct pstore_info *psi)
-{
-       char name[DUMP_NAME_LEN];
-       char stub_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;
-       struct efivar_entry *entry, *found = NULL;
-       int i, ret = 0;
-
-       sprintf(stub_name, "dump-type%u-%u-", type, part);
-       sprintf(name, "%s%lu", stub_name, get_seconds());
-
-       spin_lock(&efivars->lock);
 
-       for (i = 0; i < DUMP_NAME_LEN; i++)
-               efi_name[i] = stub_name[i];
+       new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
+       if (!new_entry)
+               return -ENOMEM;
 
-       /*
-        * Clean up any entries with the same name
-        */
+       memcpy(&new_entry->var, new_var, sizeof(*new_var));
 
-       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)))
-                       continue;
-               /* Needs to be a prefix */
-               if (entry->var.VariableName[utf16_strlen(efi_name)] == 0)
-                       continue;
-
-               /* found */
-               found = entry;
-               efivars->ops->set_variable(entry->var.VariableName,
-                                          &entry->var.VendorGuid,
-                                          PSTORE_EFI_ATTRIBUTES,
-                                          0, NULL);
+       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;
        }
 
-       if (found)
-               list_del(&found->list);
-
-       for (i = 0; i < DUMP_NAME_LEN; i++)
-               efi_name[i] = name[i];
+       if (efivar_create_sysfs_entry(new_entry)) {
+               printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
+               kfree(new_entry);
+       }
+       return count;
 
-       efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES,
-                                  size, psi->buf);
+out:
+       kfree(new_entry);
+       return err;
+}
 
-       spin_unlock(&efivars->lock);
+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 efi_variable *del_var = (struct efi_variable *)buf;
+       struct efivar_entry *entry;
+       int err = 0;
 
-       if (found)
-               efivar_unregister(found);
+       if (!capable(CAP_SYS_ADMIN))
+               return -EACCES;
 
-       if (size)
-               ret = efivar_create_sysfs_entry(efivars,
-                                         utf16_strsize(efi_name,
-                                                       DUMP_NAME_LEN * 2),
-                                         efi_name, &vendor);
+       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;
 
-       *id = part;
-       return ret;
-};
+       efivar_entry_iter_end();
 
-static int efi_pstore_erase(enum pstore_type_id type, u64 id,
-                           struct pstore_info *psi)
-{
-       efi_pstore_write(type, 0, &id, (unsigned int)id, 0, psi);
+       if (err)
+               return err;
 
-       return 0;
-}
-#else
-static int efi_pstore_open(struct pstore_info *psi)
-{
-       return 0;
-}
+       efivar_unregister(entry);
 
-static int efi_pstore_close(struct pstore_info *psi)
-{
-       return 0;
+       /* It's dead Jim.... */
+       return count;
 }
 
-static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
-                              struct timespec *timespec,
-                              char **buf, struct pstore_info *psi)
+static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
+                               struct list_head *head)
 {
-       return -1;
-}
+       struct efivar_entry *entry, *n;
+       unsigned long strsize1, strsize2;
+       bool found = false;
 
-static int efi_pstore_write(enum pstore_type_id type,
-               enum kmsg_dump_reason reason, u64 *id,
-               unsigned int part, size_t size, struct pstore_info *psi)
-{
-       return 0;
+       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 found;
 }
 
-static int efi_pstore_erase(enum pstore_type_id type, u64 id,
-                           struct pstore_info *psi)
+static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor,
+                                    unsigned long name_size, void *data)
 {
-       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,
-};
+       struct efivar_entry *entry = data;
 
-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 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 (efivar_entry_find(name, vendor, &efivar_sysfs_list, false))
+               return 0;
 
-       if (!capable(CAP_SYS_ADMIN))
-               return -EACCES;
+       memcpy(entry->var.VariableName, name, name_size);
+       memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
 
-       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;
-       }
+       return 1;
+}
 
-       spin_lock(&efivars->lock);
+/*
+ * 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)
+{
+       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);
 }
 
 /*
@@ -975,39 +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 = variable_name_size / sizeof(efi_char16_t) + 38;
+       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);
 
-       short_name = kzalloc(short_name_size + 1, GFP_KERNEL);
-       new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
+       /*
+        * 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 + EFI_VARIABLE_GUID_LEN + 1;
 
-       if (!short_name || !new_efivar)  {
+       short_name = kzalloc(short_name_size, GFP_KERNEL);
+
+       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++) {
@@ -1017,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;
@@ -1053,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);
@@ -1065,85 +873,173 @@ 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);
-       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;
 
+       /* No efivars has been registered yet */
+       if (!parent_kobj)
+               return 0;
+
+       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) {
+               printk(KERN_ERR "efivars: Subsystem registration failed.\n");
+               return -ENOMEM;
+       }
+
+       efivars_kobj = kobject_create_and_add("efivars", parent_kobj);
+       if (!efivars_kobj) {
+               pr_err("efivars: Subsystem registration failed.\n");
+               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_init(&efivars->lock);
-       INIT_LIST_HEAD(&efivars->list);
-       efivars->ops = ops;
-
-       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;
-       }
+       spin_lock_irq(&__efivars->lock);
 
        /*
         * Per EFI spec, the maximum storage allocated for both
@@ -1158,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;
@@ -1171,31 +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);
+       }
+
+       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;
+
+       spin_lock_irq(&__efivars->lock);
+
+       if (head && efivar_entry_find(name, vendor, head, false)) {
+               spin_unlock_irq(&__efivars->lock);
+               return -EEXIST;
+       }
+
+       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);
 
-       efivars->efi_pstore_info = efi_pstore_info;
+       /*
+        * 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;
+               }
 
-       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);
+               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 struct efivars __efivars;
-static struct efivar_operations ops;
+static void generic_ops_unregister(void)
+{
+       efivars_sysfs_exit();
+       efivars_unregister(&generic_efivars);
+}
 
 /*
  * For now we register the efi subsystem with the firmware subsystem
@@ -1208,25 +1744,19 @@ static struct efivar_operations ops;
 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;
-       error = register_efivars(&__efivars, &ops, efi_kobj);
+       error = generic_ops_register();
        if (error)
                goto err_put;
 
@@ -1242,7 +1772,7 @@ efivars_init(void)
        return 0;
 
 err_unregister:
-       unregister_efivars(&__efivars);
+       generic_ops_unregister();
 err_put:
        kobject_put(efi_kobj);
        return error;
@@ -1251,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);
        }
 }