X-Git-Url: https://git.efficios.com/?a=blobdiff_plain;f=drivers%2Ffirmware%2Fefivars.c;h=eab4ec41c523023d672f4787675e43e2536c2afe;hb=4423d779af2a8f1fbd01aeca5c56522efce7262f;hp=bea32d1ef7d546374cd247eaffd3327dcdea3148;hpb=cc67708891319dbdc9f29c04154833a67d23212c;p=deliverable%2Flinux.git diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index bea32d1ef7d5..eab4ec41c523 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -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); } }