#define GUID_LEN 36
static bool efivars_pstore_disable =
- IS_ENABLED(EFI_VARS_PSTORE_DEFAULT_DISABLE);
+ IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
} __attribute__((packed));
struct efivar_entry {
- struct efivars *efivars;
struct efi_variable var;
struct list_head list;
struct kobject kobj;
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 | \
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
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(entry->efivars, var);
+ status = get_var_data(__efivars, var);
if (status != EFI_SUCCESS)
return -EIO;
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(entry->efivars, var);
+ status = get_var_data(__efivars, var);
if (status != EFI_SUCCESS)
return -EIO;
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(entry->efivars, var);
+ status = get_var_data(__efivars, var);
if (status != EFI_SUCCESS)
return -EIO;
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))
if (!entry || !buf)
return 0;
- status = get_var_data(entry->efivars, var);
+ status = get_var_data(__efivars, var);
if (status != EFI_SUCCESS)
return -EIO;
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;
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
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;
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;
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",
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);
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;
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,
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;
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;
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;
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;
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;
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;
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;
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;
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;
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);
+ }
}
}
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));
{
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);
}
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)
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");
&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,
}
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
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;
return 0;
err_unregister:
- unregister_efivars(&__efivars);
+ generic_ops_unregister();
err_put:
kobject_put(efi_kobj);
return error;
cancel_work_sync(&efivar_work);
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
- unregister_efivars(&__efivars);
+ generic_ops_unregister();
kobject_put(efi_kobj);
}
}