diff options
Diffstat (limited to 'drivers/firmware/efivars.c')
| -rw-r--r-- | drivers/firmware/efivars.c | 150 | 
1 files changed, 104 insertions, 46 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index fe62aa39223..7acafb80fd4 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 @@ -165,6 +170,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); +static bool efivar_wq_enabled = true;  /* Return the number of unicode characters in data */  static unsigned long @@ -1309,9 +1315,7 @@ 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)  { @@ -1441,7 +1445,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; @@ -1514,38 +1518,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, @@ -1557,6 +1529,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) @@ -1716,6 +1706,31 @@ 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; @@ -1756,10 +1771,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); +		}  	}  } @@ -1958,6 +1976,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) @@ -2006,6 +2053,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, @@ -2025,15 +2090,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);  |