efivars: Keep a private global pointer to efivars
[deliverable/linux.git] / drivers / firmware / efivars.c
index bea32d1ef7d546374cd247eaffd3327dcdea3148..eab4ec41c523023d672f4787675e43e2536c2afe 100644 (file)
@@ -103,6 +103,11 @@ MODULE_VERSION(EFIVARS_VERSION);
  */
 #define GUID_LEN 36
 
+static bool efivars_pstore_disable =
+       IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
+
+module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
+
 /*
  * The maximum size of VariableName + Data = 1024
  * Therefore, it's reasonable to save that much
@@ -120,7 +125,6 @@ struct efi_variable {
 } __attribute__((packed));
 
 struct efivar_entry {
-       struct efivars *efivars;
        struct efi_variable var;
        struct list_head list;
        struct kobject kobj;
@@ -132,8 +136,8 @@ 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;
 
 #define PSTORE_EFI_ATTRIBUTES \
        (EFI_VARIABLE_NON_VOLATILE | \
@@ -165,23 +169,7 @@ efivar_create_sysfs_entry(struct efivars *efivars,
 
 static void efivar_update_sysfs_entries(struct work_struct *);
 static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
-
-/* 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);
-}
+static bool efivar_wq_enabled = true;
 
 /*
  * Return the number of bytes is the length of this string
@@ -490,7 +478,7 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
+       status = get_var_data(__efivars, var);
        if (status != EFI_SUCCESS)
                return -EIO;
 
@@ -524,7 +512,7 @@ efivar_size_read(struct efivar_entry *entry, char *buf)
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
+       status = get_var_data(__efivars, var);
        if (status != EFI_SUCCESS)
                return -EIO;
 
@@ -541,7 +529,7 @@ efivar_data_read(struct efivar_entry *entry, char *buf)
        if (!entry || !buf)
                return -EINVAL;
 
-       status = get_var_data(entry->efivars, var);
+       status = get_var_data(__efivars, var);
        if (status != EFI_SUCCESS)
                return -EIO;
 
@@ -556,7 +544,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;
+       struct efivars *efivars = __efivars;
        efi_status_t status = EFI_NOT_FOUND;
 
        if (count != sizeof(struct efi_variable))
@@ -617,7 +605,7 @@ efivar_show_raw(struct efivar_entry *entry, char *buf)
        if (!entry || !buf)
                return 0;
 
-       status = get_var_data(entry->efivars, var);
+       status = get_var_data(__efivars, var);
        if (status != EFI_SUCCESS)
                return -EIO;
 
@@ -739,7 +727,7 @@ static ssize_t efivarfs_file_write(struct file *file,
                const char __user *userbuf, size_t count, loff_t *ppos)
 {
        struct efivar_entry *var = file->private_data;
-       struct efivars *efivars;
+       struct efivars *efivars = __efivars;
        efi_status_t status;
        void *data;
        u32 attributes;
@@ -757,8 +745,6 @@ static ssize_t efivarfs_file_write(struct file *file,
        if (attributes & ~(EFI_VARIABLE_MASK))
                return -EINVAL;
 
-       efivars = var->efivars;
-
        /*
         * Ensure that the user can't allocate arbitrarily large
         * amounts of memory. Pick a default size of 64K if
@@ -866,7 +852,7 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
                size_t count, loff_t *ppos)
 {
        struct efivar_entry *var = file->private_data;
-       struct efivars *efivars = var->efivars;
+       struct efivars *efivars = __efivars;
        efi_status_t status;
        unsigned long datasize = 0;
        u32 attributes;
@@ -1020,7 +1006,7 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
                          umode_t mode, bool excl)
 {
        struct inode *inode;
-       struct efivars *efivars = &__efivars;
+       struct efivars *efivars = __efivars;
        struct efivar_entry *var;
        int namelen, i = 0, err = 0;
 
@@ -1049,7 +1035,6 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
        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",
@@ -1074,7 +1059,7 @@ out:
 static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
 {
        struct efivar_entry *var = dentry->d_inode->i_private;
-       struct efivars *efivars = var->efivars;
+       struct efivars *efivars = __efivars;
        efi_status_t status;
 
        spin_lock_irq(&efivars->lock);
@@ -1186,7 +1171,7 @@ 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;
+       struct efivars *efivars = __efivars;
        char *name;
        int err = -ENOMEM;
 
@@ -1289,6 +1274,7 @@ static struct file_system_type efivarfs_type = {
        .mount   = efivarfs_mount,
        .kill_sb = efivarfs_kill_sb,
 };
+MODULE_ALIAS_FS("efivarfs");
 
 /*
  * Handle negative dentry.
@@ -1308,13 +1294,11 @@ static const struct inode_operations efivarfs_dir_inode_operations = {
        .create = efivarfs_create,
 };
 
-static struct pstore_info efi_pstore_info;
-
-#ifdef CONFIG_PSTORE
+#ifdef CONFIG_EFI_VARS_PSTORE
 
 static int efi_pstore_open(struct pstore_info *psi)
 {
-       struct efivars *efivars = psi->data;
+       struct efivars *efivars = __efivars;
 
        spin_lock_irq(&efivars->lock);
        efivars->walk_entry = list_first_entry(&efivars->list,
@@ -1324,7 +1308,7 @@ static int efi_pstore_open(struct pstore_info *psi)
 
 static int efi_pstore_close(struct pstore_info *psi)
 {
-       struct efivars *efivars = psi->data;
+       struct efivars *efivars = __efivars;
 
        spin_unlock_irq(&efivars->lock);
        return 0;
@@ -1335,7 +1319,7 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
                               char **buf, struct pstore_info *psi)
 {
        efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
-       struct efivars *efivars = psi->data;
+       struct efivars *efivars = __efivars;
        char name[DUMP_NAME_LEN];
        int i;
        int cnt;
@@ -1398,7 +1382,7 @@ static int efi_pstore_write(enum pstore_type_id type,
        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;
+       struct efivars *efivars = __efivars;
        int i, ret = 0;
        efi_status_t status = EFI_NOT_FOUND;
        unsigned long flags;
@@ -1440,7 +1424,7 @@ static int efi_pstore_write(enum pstore_type_id type,
 
        spin_unlock_irqrestore(&efivars->lock, flags);
 
-       if (reason == KMSG_DUMP_OOPS)
+       if (reason == KMSG_DUMP_OOPS && efivar_wq_enabled)
                schedule_work(&efivar_work);
 
        *id = part;
@@ -1455,7 +1439,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
        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 efivars *efivars = __efivars;
        struct efivar_entry *entry, *found = NULL;
        int i;
 
@@ -1513,38 +1497,6 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
 
        return 0;
 }
-#else
-static int efi_pstore_open(struct pstore_info *psi)
-{
-       return 0;
-}
-
-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,
@@ -1556,12 +1508,30 @@ static struct pstore_info efi_pstore_info = {
        .erase          = efi_pstore_erase,
 };
 
+static void efivar_pstore_register(struct efivars *efivars)
+{
+       efivars->efi_pstore_info = efi_pstore_info;
+       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);
+       }
+}
+#else
+static void efivar_pstore_register(struct efivars *efivars)
+{
+       return;
+}
+#endif
+
 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 efivars *efivars = __efivars;
        struct efivar_entry *search_efivar, *n;
        unsigned long strsize1, strsize2;
        efi_status_t status = EFI_NOT_FOUND;
@@ -1638,7 +1608,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
                             char *buf, loff_t pos, size_t count)
 {
        struct efi_variable *del_var = (struct efi_variable *)buf;
-       struct efivars *efivars = bin_attr->private;
+       struct efivars *efivars = __efivars;
        struct efivar_entry *search_efivar, *n;
        unsigned long strsize1, strsize2;
        efi_status_t status = EFI_NOT_FOUND;
@@ -1696,7 +1666,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
 static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor)
 {
        struct efivar_entry *entry, *n;
-       struct efivars *efivars = &__efivars;
+       struct efivars *efivars = __efivars;
        unsigned long strsize1, strsize2;
        bool found = false;
 
@@ -1715,9 +1685,34 @@ static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor)
        return found;
 }
 
+/*
+ * 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;
+
+       /*
+        * 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.
+        */
+       for (len = 2; len <= variable_name_size; len += sizeof(c)) {
+               c = variable_name[(len / sizeof(c)) - 1];
+               if (!c)
+                       break;
+       }
+
+       return min(len, variable_name_size);
+}
+
 static void efivar_update_sysfs_entries(struct work_struct *work)
 {
-       struct efivars *efivars = &__efivars;
+       struct efivars *efivars = __efivars;
        efi_guid_t vendor;
        efi_char16_t *variable_name;
        unsigned long variable_name_size = 1024;
@@ -1755,10 +1750,13 @@ static void efivar_update_sysfs_entries(struct work_struct *work)
                if (!found) {
                        kfree(variable_name);
                        break;
-               } else
+               } else {
+                       variable_name_size = var_name_strnsize(variable_name,
+                                                              variable_name_size);
                        efivar_create_sysfs_entry(efivars,
                                                  variable_name_size,
                                                  variable_name, &vendor);
+               }
        }
 }
 
@@ -1841,7 +1839,6 @@ efivar_create_sysfs_entry(struct efivars *efivars,
                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));
@@ -1940,6 +1937,8 @@ void unregister_efivars(struct efivars *efivars)
 {
        struct efivar_entry *entry, *n;
 
+       __efivars = NULL;
+
        list_for_each_entry_safe(entry, n, &efivars->list, list) {
                spin_lock_irq(&efivars->lock);
                list_del(&entry->list);
@@ -1957,6 +1956,35 @@ void unregister_efivars(struct efivars *efivars)
 }
 EXPORT_SYMBOL_GPL(unregister_efivars);
 
+/*
+ * 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);
+}
+
 int register_efivars(struct efivars *efivars,
                     const struct efivar_operations *ops,
                     struct kobject *parent_kobj)
@@ -1967,6 +1995,8 @@ int register_efivars(struct efivars *efivars,
        unsigned long variable_name_size = 1024;
        int error = 0;
 
+       __efivars = efivars;
+
        variable_name = kzalloc(variable_name_size, GFP_KERNEL);
        if (!variable_name) {
                printk(KERN_ERR "efivars: Memory allocation failed.\n");
@@ -2005,6 +2035,24 @@ int register_efivars(struct efivars *efivars,
                                                &vendor_guid);
                switch (status) {
                case EFI_SUCCESS:
+                       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 (variable_is_present(variable_name, &vendor_guid)) {
+                               dup_variable_bug(variable_name, &vendor_guid,
+                                                variable_name_size);
+                               status = EFI_NOT_FOUND;
+                               break;
+                       }
+
                        efivar_create_sysfs_entry(efivars,
                                                  variable_name_size,
                                                  variable_name,
@@ -2024,15 +2072,8 @@ int register_efivars(struct efivars *efivars,
        if (error)
                unregister_efivars(efivars);
 
-       efivars->efi_pstore_info = efi_pstore_info;
-
-       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 (!efivars_pstore_disable)
+               efivar_pstore_register(efivars);
 
        register_filesystem(&efivarfs_type);
 
@@ -2043,6 +2084,24 @@ out:
 }
 EXPORT_SYMBOL_GPL(register_efivars);
 
+static struct efivars generic_efivars;
+static struct efivar_operations generic_ops;
+
+static int generic_ops_register(void)
+{
+       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;
+
+       return register_efivars(&generic_efivars, &generic_ops, efi_kobj);
+}
+
+static void generic_ops_unregister(void)
+{
+       unregister_efivars(&generic_efivars);
+}
+
 /*
  * For now we register the efi subsystem with the firmware subsystem
  * and the vars subsystem with the efi subsystem.  In the future, it
@@ -2069,12 +2128,7 @@ efivars_init(void)
                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;
 
@@ -2090,7 +2144,7 @@ efivars_init(void)
        return 0;
 
 err_unregister:
-       unregister_efivars(&__efivars);
+       generic_ops_unregister();
 err_put:
        kobject_put(efi_kobj);
        return error;
@@ -2102,7 +2156,7 @@ efivars_exit(void)
        cancel_work_sync(&efivar_work);
 
        if (efi_enabled(EFI_RUNTIME_SERVICES)) {
-               unregister_efivars(&__efivars);
+               generic_ops_unregister();
                kobject_put(efi_kobj);
        }
 }
This page took 0.047457 seconds and 5 git commands to generate.