efivars: pstore: Do not check size when erasing variable
[pandora-kernel.git] / drivers / firmware / efivars.c
index 81346ae..b15c0aa 100644 (file)
@@ -92,6 +92,11 @@ MODULE_VERSION(EFIVARS_VERSION);
 
 #define DUMP_NAME_LEN 52
 
+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
@@ -122,6 +127,8 @@ struct efivar_attribute {
        ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
 };
 
+static struct efivars __efivars;
+
 #define PSTORE_EFI_ATTRIBUTES \
        (EFI_VARIABLE_NON_VOLATILE | \
         EFI_VARIABLE_BOOTSERVICE_ACCESS | \
@@ -146,6 +153,14 @@ efivar_create_sysfs_entry(struct efivars *efivars,
                          efi_char16_t *variable_name,
                          efi_guid_t *vendor_guid);
 
+/*
+ * Prototype for workqueue functions updating sysfs entry
+ */
+
+static void efivar_update_sysfs_entries(struct work_struct *);
+static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
+static bool efivar_wq_enabled = true;
+
 /* Return the number of unicode characters in data */
 static unsigned long
 utf16_strnlen(efi_char16_t *s, size_t maxlength)
@@ -659,8 +674,6 @@ 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)
 {
@@ -697,7 +710,7 @@ static int efi_status_to_err(efi_status_t status)
        return err;
 }
 
-#ifdef CONFIG_PSTORE
+#ifdef CONFIG_EFI_VARS_PSTORE
 
 static int efi_pstore_open(struct pstore_info *psi)
 {
@@ -774,19 +787,21 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
 
        spin_lock_irqsave(&efivars->lock, flags);
 
-       /*
-        * 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
-        */
+       if (size) {
+               /*
+                * 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 = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES,
-                                        size + DUMP_NAME_LEN * 2);
+               status = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES,
+                                              size + DUMP_NAME_LEN * 2);
 
-       if (status) {
-               spin_unlock_irqrestore(&efivars->lock, flags);
-               *id = part;
-               return -ENOSPC;
+               if (status) {
+                       spin_unlock_irqrestore(&efivars->lock, flags);
+                       *id = part;
+                       return -ENOSPC;
+               }
        }
 
        for (i = 0; i < DUMP_NAME_LEN; i++)
@@ -830,11 +845,8 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
        if (found)
                efivar_unregister(found);
 
-       if (size)
-               ret = efivar_create_sysfs_entry(efivars,
-                                         utf16_strsize(efi_name,
-                                                       DUMP_NAME_LEN * 2),
-                                         efi_name, &vendor);
+       if (efivar_wq_enabled)
+               schedule_work(&efivar_work);
 
        *id = part;
        return ret;
@@ -847,36 +859,6 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id,
 
        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,
-                              struct timespec *timespec,
-                              char **buf, struct pstore_info *psi)
-{
-       return -1;
-}
-
-static int efi_pstore_write(enum pstore_type_id type, u64 *id,
-               unsigned int part, size_t size, struct pstore_info *psi)
-{
-       return 0;
-}
-
-static int efi_pstore_erase(enum pstore_type_id type, u64 id,
-                           struct pstore_info *psi)
-{
-       return 0;
-}
-#endif
 
 static struct pstore_info efi_pstore_info = {
        .owner          = THIS_MODULE,
@@ -888,6 +870,24 @@ 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)
@@ -1025,6 +1025,103 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
        return count;
 }
 
+static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor)
+{
+       struct efivar_entry *entry, *n;
+       struct efivars *efivars = &__efivars;
+       unsigned long strsize1, strsize2;
+       bool found = false;
+
+       strsize1 = utf16_strsize(variable_name, 1024);
+       list_for_each_entry_safe(entry, n, &efivars->list, 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;
+}
+
+/*
+ * 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;
+       efi_guid_t vendor;
+       efi_char16_t *variable_name;
+       unsigned long variable_name_size = 1024;
+       efi_status_t status = EFI_NOT_FOUND;
+       bool found;
+
+       /* Add new sysfs entries */
+       while (1) {
+               variable_name = kzalloc(variable_name_size, GFP_KERNEL);
+               if (!variable_name) {
+                       pr_err("efivars: Memory allocation failed.\n");
+                       return;
+               }
+
+               spin_lock_irq(&efivars->lock);
+               found = false;
+               while (1) {
+                       variable_name_size = 1024;
+                       status = efivars->ops->get_next_variable(
+                                                       &variable_name_size,
+                                                       variable_name,
+                                                       &vendor);
+                       if (status != EFI_SUCCESS) {
+                               break;
+                       } else {
+                               if (!variable_is_present(variable_name,
+                                   &vendor)) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+               }
+               spin_unlock_irq(&efivars->lock);
+
+               if (!found) {
+                       kfree(variable_name);
+                       break;
+               } else {
+                       variable_name_size = var_name_strnsize(variable_name,
+                                                              variable_name_size);
+                       efivar_create_sysfs_entry(efivars,
+                                                 variable_name_size,
+                                                 variable_name, &vendor);
+               }
+       }
+}
+
 /*
  * Let's not leave out systab information that snuck into
  * the efivars driver
@@ -1212,6 +1309,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)
@@ -1252,6 +1378,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,
@@ -1271,15 +1415,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);
 
 out:
        kfree(variable_name);
@@ -1288,7 +1425,6 @@ out:
 }
 EXPORT_SYMBOL_GPL(register_efivars);
 
-static struct efivars __efivars;
 static struct efivar_operations ops;
 
 /*
@@ -1346,6 +1482,8 @@ err_put:
 static void __exit
 efivars_exit(void)
 {
+       cancel_work_sync(&efivar_work);
+
        if (efi_enabled(EFI_RUNTIME_SERVICES)) {
                unregister_efivars(&__efivars);
                kobject_put(efi_kobj);