*/
#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
} __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;
.mount = efivarfs_mount,
.kill_sb = efivarfs_kill_sb,
};
+MODULE_ALIAS_FS("efivarfs");
/*
* Handle negative dentry.
.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,
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;
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,
.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;
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,
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);
}
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);
}
}