summaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
authorMatt Fleming <matt.fleming@intel.com>2013-02-03 20:16:40 +0000
committerMatt Fleming <matt.fleming@intel.com>2013-04-17 13:23:59 +0100
commite14ab23dde12b80db4c94b684a2e485b72b16af3 (patch)
treeb9c7e3ec35e67d90fc05908ed3387e4018e2c7ba /drivers/firmware
parent4423d779af2a8f1fbd01aeca5c56522efce7262f (diff)
efivars: efivar_entry API
There isn't really a formal interface for dealing with EFI variables or struct efivar_entry. Historically, this has led to various bits of code directly accessing the generic EFI variable ops, which inherently ties it to specific EFI variable operations instead of indirectly using whatever ops were registered with register_efivars(). This lead to the efivarfs code only working with the generic EFI variable ops and not CONFIG_GOOGLE_SMI. Encapsulate everything that needs to access '__efivars' inside an efivar_entry_* API and use the new API in the pstore, sysfs and efivarfs code. Much of the efivars code had to be rewritten to use this new API. For instance, it is now up to the users of the API to build the initial list of EFI variables in their efivar_init() callback function. The variable list needs to be passed to efivar_init() which allows us to keep work arounds for things like implementation bugs in GetNextVariable() in a central location. Allowing users of the API to use a callback function to build the list greatly benefits the efivarfs code which needs to allocate inodes and dentries for every variable. It previously did this in a racy way because the code ran without holding the variable spinlock. Both the sysfs and efivarfs code maintain their own lists which means the two interfaces can be running simultaneously without interference, though it should be noted that because no synchronisation is performed it is very easy to create inconsistencies. efibootmgr doesn't currently use efivarfs and users are likely to also require the old sysfs interface, so it makes sense to allow both to be built. Reviewed-by: Tom Gundersen <teg@jklm.no> Tested-by: Tom Gundersen <teg@jklm.no> Cc: Seiji Aguchi <seiji.aguchi@hds.com> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Jeremy Kerr <jk@ozlabs.org> Cc: Tony Luck <tony.luck@intel.com> Cc: Mike Waychison <mikew@google.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com>
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/efivars.c1819
-rw-r--r--drivers/firmware/google/gsmi.c11
2 files changed, 1107 insertions, 723 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index eab4ec41c523..82fd145ead3b 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -97,39 +97,14 @@ MODULE_VERSION(EFIVARS_VERSION);
#define DUMP_NAME_LEN 52
-/*
- * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
- * not including trailing NUL
- */
-#define GUID_LEN 36
+static LIST_HEAD(efivarfs_list);
+static LIST_HEAD(efivar_sysfs_list);
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
- * space in each part of the structure,
- * and we use a page for reading/writing.
- */
-
-struct efi_variable {
- efi_char16_t VariableName[1024/sizeof(efi_char16_t)];
- efi_guid_t VendorGuid;
- unsigned long DataSize;
- __u8 Data[1024];
- efi_status_t Status;
- __u32 Attributes;
-} __attribute__((packed));
-
-struct efivar_entry {
- struct efi_variable var;
- struct list_head list;
- struct kobject kobj;
-};
-
struct efivar_attribute {
struct attribute attr;
ssize_t (*show) (struct efivar_entry *entry, char *buf);
@@ -144,6 +119,11 @@ static struct efivars *__efivars;
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
+static struct kset *efivars_kset;
+
+static struct bin_attribute *efivars_new_var;
+static struct bin_attribute *efivars_del_var;
+
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
struct efivar_attribute efivar_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode}, \
@@ -158,10 +138,7 @@ struct efivar_attribute efivar_attr_##_name = { \
* Prototype for sysfs creation function
*/
static int
-efivar_create_sysfs_entry(struct efivars *efivars,
- unsigned long variable_name_size,
- efi_char16_t *variable_name,
- efi_guid_t *vendor_guid);
+efivar_create_sysfs_entry(struct efivar_entry *new_var);
/*
* Prototype for workqueue functions updating sysfs entry
@@ -346,8 +323,8 @@ static const struct variable_validate variable_validate[] = {
{ "", NULL },
};
-static bool
-validate_var(struct efi_variable *var, u8 *data, unsigned long len)
+bool
+efivar_validate(struct efi_variable *var, u8 *data, unsigned long len)
{
int i;
u16 *unicode_name = var->VariableName;
@@ -382,47 +359,16 @@ validate_var(struct efi_variable *var, u8 *data, unsigned long len)
return true;
}
+EXPORT_SYMBOL_GPL(efivar_validate);
static efi_status_t
-get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
-{
- efi_status_t status;
-
- var->DataSize = 1024;
- status = efivars->ops->get_variable(var->VariableName,
- &var->VendorGuid,
- &var->Attributes,
- &var->DataSize,
- var->Data);
- return status;
-}
-
-static efi_status_t
-get_var_data(struct efivars *efivars, struct efi_variable *var)
-{
- efi_status_t status;
- unsigned long flags;
-
- spin_lock_irqsave(&efivars->lock, flags);
- status = get_var_data_locked(efivars, var);
- spin_unlock_irqrestore(&efivars->lock, flags);
-
- if (status != EFI_SUCCESS) {
- printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
- status);
- }
- return status;
-}
-
-static efi_status_t
-check_var_size_locked(struct efivars *efivars, u32 attributes,
- unsigned long size)
+check_var_size(u32 attributes, unsigned long size)
{
u64 storage_size, remaining_size, max_size;
efi_status_t status;
- const struct efivar_operations *fops = efivars->ops;
+ const struct efivar_operations *fops = __efivars->ops;
- if (!efivars->ops->query_variable_info)
+ if (!fops->query_variable_info)
return EFI_UNSUPPORTED;
status = fops->query_variable_info(attributes, &storage_size,
@@ -438,20 +384,6 @@ check_var_size_locked(struct efivars *efivars, u32 attributes,
return status;
}
-
-static efi_status_t
-check_var_size(struct efivars *efivars, u32 attributes, unsigned long size)
-{
- efi_status_t status;
- unsigned long flags;
-
- spin_lock_irqsave(&efivars->lock, flags);
- status = check_var_size_locked(efivars, attributes, size);
- spin_unlock_irqrestore(&efivars->lock, flags);
-
- return status;
-}
-
static ssize_t
efivar_guid_read(struct efivar_entry *entry, char *buf)
{
@@ -473,13 +405,12 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
char *str = buf;
- efi_status_t status;
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(__efivars, var);
- if (status != EFI_SUCCESS)
+ var->DataSize = 1024;
+ if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
@@ -507,13 +438,12 @@ efivar_size_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
char *str = buf;
- efi_status_t status;
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(__efivars, var);
- if (status != EFI_SUCCESS)
+ var->DataSize = 1024;
+ if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
str += sprintf(str, "0x%lx\n", var->DataSize);
@@ -524,13 +454,12 @@ static ssize_t
efivar_data_read(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
- efi_status_t status;
if (!entry || !buf)
return -EINVAL;
- status = get_var_data(__efivars, var);
- if (status != EFI_SUCCESS)
+ var->DataSize = 1024;
+ if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
return -EIO;
memcpy(buf, var->Data, var->DataSize);
@@ -544,8 +473,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 = __efivars;
- efi_status_t status = EFI_NOT_FOUND;
+ int err;
if (count != sizeof(struct efi_variable))
return -EINVAL;
@@ -567,32 +495,20 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
}
if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
- validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
+ efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) {
printk(KERN_ERR "efivars: Malformed variable content\n");
return -EINVAL;
}
- spin_lock_irq(&efivars->lock);
-
- status = check_var_size_locked(efivars, new_var->Attributes,
- new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
-
- if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
- status = efivars->ops->set_variable(new_var->VariableName,
- &new_var->VendorGuid,
- new_var->Attributes,
- new_var->DataSize,
- new_var->Data);
-
- spin_unlock_irq(&efivars->lock);
+ memcpy(&entry->var, new_var, count);
- if (status != EFI_SUCCESS) {
- printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
- status);
+ err = efivar_entry_set(entry, new_var->Attributes,
+ new_var->DataSize, new_var->Data, false);
+ if (err) {
+ printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
return -EIO;
}
- memcpy(&entry->var, new_var, count);
return count;
}
@@ -600,16 +516,17 @@ static ssize_t
efivar_show_raw(struct efivar_entry *entry, char *buf)
{
struct efi_variable *var = &entry->var;
- efi_status_t status;
if (!entry || !buf)
return 0;
- status = get_var_data(__efivars, var);
- if (status != EFI_SUCCESS)
+ var->DataSize = 1024;
+ if (efivar_entry_get(entry, &entry->var.Attributes,
+ &entry->var.DataSize, entry->var.Data))
return -EIO;
memcpy(buf, var, sizeof(*var));
+
return sizeof(*var);
}
@@ -698,6 +615,9 @@ static int efi_status_to_err(efi_status_t status)
int err;
switch (status) {
+ case EFI_SUCCESS:
+ err = 0;
+ break;
case EFI_INVALID_PARAMETER:
err = -EINVAL;
break;
@@ -714,7 +634,7 @@ static int efi_status_to_err(efi_status_t status)
err = -EACCES;
break;
case EFI_NOT_FOUND:
- err = -EIO;
+ err = -ENOENT;
break;
default:
err = -EINVAL;
@@ -727,14 +647,12 @@ 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 = __efivars;
- efi_status_t status;
void *data;
u32 attributes;
struct inode *inode = file->f_mapping->host;
unsigned long datasize = count - sizeof(attributes);
- unsigned long newdatasize, varsize;
ssize_t bytes = 0;
+ bool set = false;
if (count < sizeof(attributes))
return -EINVAL;
@@ -745,23 +663,6 @@ static ssize_t efivarfs_file_write(struct file *file,
if (attributes & ~(EFI_VARIABLE_MASK))
return -EINVAL;
- /*
- * Ensure that the user can't allocate arbitrarily large
- * amounts of memory. Pick a default size of 64K if
- * QueryVariableInfo() isn't supported by the firmware.
- */
-
- varsize = datasize + utf16_strsize(var->var.VariableName, 1024);
- status = check_var_size(efivars, attributes, varsize);
-
- if (status != EFI_SUCCESS) {
- if (status != EFI_UNSUPPORTED)
- return efi_status_to_err(status);
-
- if (datasize > 65536)
- return -ENOSPC;
- }
-
data = kmalloc(datasize, GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -771,76 +672,24 @@ static ssize_t efivarfs_file_write(struct file *file,
goto out;
}
- if (validate_var(&var->var, data, datasize) == false) {
- bytes = -EINVAL;
+ bytes = efivar_entry_set_get_size(var, attributes, &datasize,
+ data, &set);
+ if (!set && bytes)
goto out;
- }
-
- /*
- * The lock here protects the get_variable call, the conditional
- * set_variable call, and removal of the variable from the efivars
- * list (in the case of an authenticated delete).
- */
- spin_lock_irq(&efivars->lock);
-
- /*
- * Ensure that the available space hasn't shrunk below the safe level
- */
-
- status = check_var_size_locked(efivars, attributes, varsize);
-
- if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) {
- spin_unlock_irq(&efivars->lock);
- kfree(data);
- return efi_status_to_err(status);
- }
-
- status = efivars->ops->set_variable(var->var.VariableName,
- &var->var.VendorGuid,
- attributes, datasize,
- data);
-
- if (status != EFI_SUCCESS) {
- spin_unlock_irq(&efivars->lock);
- kfree(data);
-
- return efi_status_to_err(status);
- }
-
- bytes = count;
-
- /*
- * Writing to the variable may have caused a change in size (which
- * could either be an append or an overwrite), or the variable to be
- * deleted. Perform a GetVariable() so we can tell what actually
- * happened.
- */
- newdatasize = 0;
- status = efivars->ops->get_variable(var->var.VariableName,
- &var->var.VendorGuid,
- NULL, &newdatasize,
- NULL);
-
- if (status == EFI_BUFFER_TOO_SMALL) {
- spin_unlock_irq(&efivars->lock);
+ if (!bytes) {
mutex_lock(&inode->i_mutex);
- i_size_write(inode, newdatasize + sizeof(attributes));
+ i_size_write(inode, datasize + sizeof(attributes));
mutex_unlock(&inode->i_mutex);
-
- } else if (status == EFI_NOT_FOUND) {
- list_del(&var->list);
- spin_unlock_irq(&efivars->lock);
- efivar_unregister(var);
+ } else if (bytes == -ENOENT) {
drop_nlink(inode);
d_delete(file->f_dentry);
dput(file->f_dentry);
-
- } else {
- spin_unlock_irq(&efivars->lock);
+ } else
pr_warn("efivarfs: inconsistent EFI variable implementation? "
- "status = %lx\n", status);
- }
+ "status=%zu\n", bytes);
+
+ bytes = count;
out:
kfree(data);
@@ -852,38 +701,25 @@ 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 = __efivars;
- efi_status_t status;
unsigned long datasize = 0;
u32 attributes;
void *data;
ssize_t size = 0;
+ int err;
- spin_lock_irq(&efivars->lock);
- status = efivars->ops->get_variable(var->var.VariableName,
- &var->var.VendorGuid,
- &attributes, &datasize, NULL);
- spin_unlock_irq(&efivars->lock);
-
- if (status != EFI_BUFFER_TOO_SMALL)
- return efi_status_to_err(status);
+ err = efivar_entry_size(var, &datasize);
+ if (err)
+ return err;
data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL);
if (!data)
return -ENOMEM;
- spin_lock_irq(&efivars->lock);
- status = efivars->ops->get_variable(var->var.VariableName,
- &var->var.VendorGuid,
- &attributes, &datasize,
- (data + sizeof(attributes)));
- spin_unlock_irq(&efivars->lock);
-
- if (status != EFI_SUCCESS) {
- size = efi_status_to_err(status);
+ size = efivar_entry_get(var, &attributes, &datasize,
+ data + sizeof(attributes));
+ if (size)
goto out_free;
- }
memcpy(data, &attributes, sizeof(attributes));
size = simple_read_from_buffer(userbuf, count, ppos,
@@ -947,17 +783,17 @@ static struct inode *efivarfs_get_inode(struct super_block *sb,
*/
static bool efivarfs_valid_name(const char *str, int len)
{
- static const char dashes[GUID_LEN] = {
+ static const char dashes[EFI_VARIABLE_GUID_LEN] = {
[8] = 1, [13] = 1, [18] = 1, [23] = 1
};
- const char *s = str + len - GUID_LEN;
+ const char *s = str + len - EFI_VARIABLE_GUID_LEN;
int i;
/*
* We need a GUID, plus at least one letter for the variable name,
* plus the '-' separator
*/
- if (len < GUID_LEN + 2)
+ if (len < EFI_VARIABLE_GUID_LEN + 2)
return false;
/* GUID must be preceded by a '-' */
@@ -969,7 +805,7 @@ static bool efivarfs_valid_name(const char *str, int len)
*
* 12345678-1234-1234-1234-123456789abc
*/
- for (i = 0; i < GUID_LEN; i++) {
+ for (i = 0; i < EFI_VARIABLE_GUID_LEN; i++) {
if (dashes[i]) {
if (*s++ != '-')
return false;
@@ -1006,7 +842,6 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
umode_t mode, bool excl)
{
struct inode *inode;
- struct efivars *efivars = __efivars;
struct efivar_entry *var;
int namelen, i = 0, err = 0;
@@ -1024,7 +859,7 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
}
/* length of the variable name itself: remove GUID and separator */
- namelen = dentry->d_name.len - GUID_LEN - 1;
+ namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1;
efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
&var->var.VendorGuid);
@@ -1035,17 +870,8 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
var->var.VariableName[i] = '\0';
inode->i_private = var;
- var->kobj.kset = efivars->kset;
- err = kobject_init_and_add(&var->kobj, &efivar_ktype, NULL, "%s",
- dentry->d_name.name);
- if (err)
- goto out;
-
- kobject_uevent(&var->kobj, KOBJ_ADD);
- spin_lock_irq(&efivars->lock);
- list_add(&var->list, &efivars->list);
- spin_unlock_irq(&efivars->lock);
+ efivar_entry_add(var, &efivarfs_list);
d_instantiate(dentry, inode);
dget(dentry);
out:
@@ -1059,26 +885,13 @@ out:
static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct efivar_entry *var = dentry->d_inode->i_private;
- struct efivars *efivars = __efivars;
- efi_status_t status;
-
- spin_lock_irq(&efivars->lock);
- status = efivars->ops->set_variable(var->var.VariableName,
- &var->var.VendorGuid,
- 0, 0, NULL);
-
- if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) {
- list_del(&var->list);
- spin_unlock_irq(&efivars->lock);
- efivar_unregister(var);
- drop_nlink(dentry->d_inode);
- dput(dentry);
- return 0;
- }
+ if (efivar_entry_delete(var))
+ return -EINVAL;
- spin_unlock_irq(&efivars->lock);
- return -EINVAL;
+ drop_nlink(dentry->d_inode);
+ dput(dentry);
+ return 0;
};
/*
@@ -1097,7 +910,7 @@ static int efivarfs_d_compare(const struct dentry *parent, const struct inode *p
unsigned int len, const char *str,
const struct qstr *name)
{
- int guid = len - GUID_LEN;
+ int guid = len - EFI_VARIABLE_GUID_LEN;
if (name->len != len)
return 1;
@@ -1107,7 +920,7 @@ static int efivarfs_d_compare(const struct dentry *parent, const struct inode *p
return 1;
/* Case-insensitive compare for the GUID */
- return strncasecmp(name->name + guid, str + guid, GUID_LEN);
+ return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN);
}
static int efivarfs_d_hash(const struct dentry *dentry,
@@ -1120,7 +933,7 @@ static int efivarfs_d_hash(const struct dentry *dentry,
if (!efivarfs_valid_name(s, len))
return -EINVAL;
- while (len-- > GUID_LEN)
+ while (len-- > EFI_VARIABLE_GUID_LEN)
hash = partial_name_hash(*s++, hash);
/* GUID is case-insensitive. */
@@ -1166,15 +979,87 @@ static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)
return ERR_PTR(-ENOMEM);
}
-static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
+static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
+ unsigned long name_size, void *data)
{
+ struct super_block *sb = (struct super_block *)data;
+ struct efivar_entry *entry;
struct inode *inode = NULL;
- struct dentry *root;
- struct efivar_entry *entry, *n;
- struct efivars *efivars = __efivars;
+ struct dentry *dentry, *root = sb->s_root;
+ unsigned long size = 0;
char *name;
+ int len, i;
int err = -ENOMEM;
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return err;
+
+ memcpy(entry->var.VariableName, name16, name_size);
+ memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
+
+ len = utf16_strlen(entry->var.VariableName);
+
+ /* name, plus '-', plus GUID, plus NUL*/
+ name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
+ if (!name)
+ goto fail;
+
+ for (i = 0; i < len; i++)
+ name[i] = entry->var.VariableName[i] & 0xFF;
+
+ name[len] = '-';
+
+ efi_guid_unparse(&entry->var.VendorGuid, name + len + 1);
+
+ name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
+
+ inode = efivarfs_get_inode(sb, root->d_inode, S_IFREG | 0644, 0);
+ if (!inode)
+ goto fail_name;
+
+ dentry = efivarfs_alloc_dentry(root, name);
+ if (IS_ERR(dentry)) {
+ err = PTR_ERR(dentry);
+ goto fail_inode;
+ }
+
+ /* copied by the above to local storage in the dentry. */
+ kfree(name);
+
+ efivar_entry_size(entry, &size);
+ efivar_entry_add(entry, &efivarfs_list);
+
+ mutex_lock(&inode->i_mutex);
+ inode->i_private = entry;
+ i_size_write(inode, size + sizeof(entry->var.Attributes));
+ mutex_unlock(&inode->i_mutex);
+ d_add(dentry, inode);
+
+ return 0;
+
+fail_inode:
+ iput(inode);
+fail_name:
+ kfree(name);
+fail:
+ kfree(entry);
+ return err;
+}
+
+static int efivarfs_destroy(struct efivar_entry *entry, void *data)
+{
+ efivar_entry_remove(entry);
+ kfree(entry);
+ return 0;
+}
+
+static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
+{
+ struct inode *inode = NULL;
+ struct dentry *root;
+ int err;
+
efivarfs_sb = sb;
sb->s_maxbytes = MAX_LFS_FILESIZE;
@@ -1195,65 +1080,13 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
if (!root)
return -ENOMEM;
- list_for_each_entry_safe(entry, n, &efivars->list, list) {
- struct dentry *dentry, *root = efivarfs_sb->s_root;
- unsigned long size = 0;
- int len, i;
-
- inode = NULL;
-
- len = utf16_strlen(entry->var.VariableName);
-
- /* name, plus '-', plus GUID, plus NUL*/
- name = kmalloc(len + 1 + GUID_LEN + 1, GFP_ATOMIC);
- if (!name)
- goto fail;
+ INIT_LIST_HEAD(&efivarfs_list);
- for (i = 0; i < len; i++)
- name[i] = entry->var.VariableName[i] & 0xFF;
-
- name[len] = '-';
-
- efi_guid_unparse(&entry->var.VendorGuid, name + len + 1);
-
- name[len+GUID_LEN+1] = '\0';
-
- inode = efivarfs_get_inode(efivarfs_sb, root->d_inode,
- S_IFREG | 0644, 0);
- if (!inode)
- goto fail_name;
-
- dentry = efivarfs_alloc_dentry(root, name);
- if (IS_ERR(dentry)) {
- err = PTR_ERR(dentry);
- goto fail_inode;
- }
-
- /* copied by the above to local storage in the dentry. */
- kfree(name);
-
- spin_lock_irq(&efivars->lock);
- efivars->ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- &entry->var.Attributes,
- &size,
- NULL);
- spin_unlock_irq(&efivars->lock);
-
- mutex_lock(&inode->i_mutex);
- inode->i_private = entry;
- i_size_write(inode, size + sizeof(entry->var.Attributes));
- mutex_unlock(&inode->i_mutex);
- d_add(dentry, inode);
- }
-
- return 0;
+ err = efivar_init(efivarfs_callback, (void *)sb, false,
+ true, &efivarfs_list);
+ if (err)
+ __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL);
-fail_inode:
- iput(inode);
-fail_name:
- kfree(name);
-fail:
return err;
}
@@ -1267,6 +1100,9 @@ static void efivarfs_kill_sb(struct super_block *sb)
{
kill_litter_super(sb);
efivarfs_sb = NULL;
+
+ /* Remove all entries and destroy */
+ __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL);
}
static struct file_system_type efivarfs_type = {
@@ -1298,80 +1134,84 @@ static const struct inode_operations efivarfs_dir_inode_operations = {
static int efi_pstore_open(struct pstore_info *psi)
{
- struct efivars *efivars = __efivars;
-
- spin_lock_irq(&efivars->lock);
- efivars->walk_entry = list_first_entry(&efivars->list,
- struct efivar_entry, list);
+ efivar_entry_iter_begin();
+ psi->data = NULL;
return 0;
}
static int efi_pstore_close(struct pstore_info *psi)
{
- struct efivars *efivars = __efivars;
-
- spin_unlock_irq(&efivars->lock);
+ efivar_entry_iter_end();
+ psi->data = NULL;
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)
+struct pstore_read_data {
+ u64 *id;
+ enum pstore_type_id *type;
+ int *count;
+ struct timespec *timespec;
+ char **buf;
+};
+
+static int efi_pstore_read_func(struct efivar_entry *entry, void *data)
{
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- struct efivars *efivars = __efivars;
+ struct pstore_read_data *cb_data = data;
char name[DUMP_NAME_LEN];
int i;
int cnt;
- unsigned int part, size;
- unsigned long time;
-
- while (&efivars->walk_entry->list != &efivars->list) {
- if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid,
- vendor)) {
- for (i = 0; i < DUMP_NAME_LEN; i++) {
- name[i] = efivars->walk_entry->var.VariableName[i];
- }
- if (sscanf(name, "dump-type%u-%u-%d-%lu",
- type, &part, &cnt, &time) == 4) {
- *id = part;
- *count = cnt;
- timespec->tv_sec = time;
- timespec->tv_nsec = 0;
- } else if (sscanf(name, "dump-type%u-%u-%lu",
- type, &part, &time) == 3) {
- /*
- * Check if an old format,
- * which doesn't support holding
- * multiple logs, remains.
- */
- *id = part;
- *count = 0;
- timespec->tv_sec = time;
- timespec->tv_nsec = 0;
- } else {
- efivars->walk_entry = list_entry(
- efivars->walk_entry->list.next,
- struct efivar_entry, list);
- continue;
- }
+ unsigned int part;
+ unsigned long time, size;
- get_var_data_locked(efivars, &efivars->walk_entry->var);
- size = efivars->walk_entry->var.DataSize;
- *buf = kmalloc(size, GFP_KERNEL);
- if (*buf == NULL)
- return -ENOMEM;
- memcpy(*buf, efivars->walk_entry->var.Data,
- size);
- efivars->walk_entry = list_entry(
- efivars->walk_entry->list.next,
- struct efivar_entry, list);
- return size;
- }
- efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
- struct efivar_entry, list);
- }
- return 0;
+ if (efi_guidcmp(entry->var.VendorGuid, vendor))
+ return 0;
+
+ for (i = 0; i < DUMP_NAME_LEN; i++)
+ name[i] = entry->var.VariableName[i];
+
+ if (sscanf(name, "dump-type%u-%u-%d-%lu",
+ cb_data->type, &part, &cnt, &time) == 4) {
+ *cb_data->id = part;
+ *cb_data->count = cnt;
+ cb_data->timespec->tv_sec = time;
+ cb_data->timespec->tv_nsec = 0;
+ } else if (sscanf(name, "dump-type%u-%u-%lu",
+ cb_data->type, &part, &time) == 3) {
+ /*
+ * Check if an old format,
+ * which doesn't support holding
+ * multiple logs, remains.
+ */
+ *cb_data->id = part;
+ *cb_data->count = 0;
+ cb_data->timespec->tv_sec = time;
+ cb_data->timespec->tv_nsec = 0;
+ } else
+ return 0;
+
+ __efivar_entry_size(entry, &size);
+ *cb_data->buf = kmalloc(size, GFP_KERNEL);
+ if (*cb_data->buf == NULL)
+ return -ENOMEM;
+ memcpy(*cb_data->buf, entry->var.Data, size);
+ return size;
+}
+
+static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
+ int *count, struct timespec *timespec,
+ char **buf, struct pstore_info *psi)
+{
+ struct pstore_read_data data;
+
+ data.id = id;
+ data.type = type;
+ data.count = count;
+ data.timespec = timespec;
+ data.buf = buf;
+
+ return __efivar_entry_iter(efi_pstore_read_func, &efivar_sysfs_list, &data,
+ (struct efivar_entry **)&psi->data);
}
static int efi_pstore_write(enum pstore_type_id type,
@@ -1382,36 +1222,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 = __efivars;
int i, ret = 0;
- efi_status_t status = EFI_NOT_FOUND;
- unsigned long flags;
-
- if (pstore_cannot_block_path(reason)) {
- /*
- * If the lock is taken by another cpu in non-blocking path,
- * this driver returns without entering firmware to avoid
- * hanging up.
- */
- if (!spin_trylock_irqsave(&efivars->lock, flags))
- return -EBUSY;
- } else
- 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
- */
-
- 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;
- }
sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count,
get_seconds());
@@ -1419,10 +1230,9 @@ static int efi_pstore_write(enum pstore_type_id type,
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
- efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES,
- size, psi->buf);
-
- spin_unlock_irqrestore(&efivars->lock, flags);
+ ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
+ !pstore_cannot_block_path(reason),
+ size, psi->buf);
if (reason == KMSG_DUMP_OOPS && efivar_wq_enabled)
schedule_work(&efivar_work);
@@ -1431,69 +1241,79 @@ static int efi_pstore_write(enum pstore_type_id type,
return ret;
};
-static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
- struct timespec time, struct pstore_info *psi)
+struct pstore_erase_data {
+ u64 id;
+ enum pstore_type_id type;
+ int count;
+ struct timespec time;
+ efi_char16_t *name;
+};
+
+/*
+ * Clean up an entry with the same name
+ */
+static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
{
- char name[DUMP_NAME_LEN];
- efi_char16_t efi_name[DUMP_NAME_LEN];
- char name_old[DUMP_NAME_LEN];
- efi_char16_t efi_name_old[DUMP_NAME_LEN];
+ struct pstore_erase_data *ed = data;
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- struct efivars *efivars = __efivars;
- struct efivar_entry *entry, *found = NULL;
+ efi_char16_t efi_name_old[DUMP_NAME_LEN];
+ efi_char16_t *efi_name = ed->name;
+ unsigned long utf16_len = utf16_strlen(ed->name);
+ char name_old[DUMP_NAME_LEN];
int i;
- sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count,
- time.tv_sec);
-
- spin_lock_irq(&efivars->lock);
+ if (efi_guidcmp(entry->var.VendorGuid, vendor))
+ return 0;
- for (i = 0; i < DUMP_NAME_LEN; i++)
- efi_name[i] = name[i];
+ if (utf16_strncmp(entry->var.VariableName,
+ efi_name, (size_t)utf16_len)) {
+ /*
+ * Check if an old format, which doesn't support
+ * holding multiple logs, remains.
+ */
+ sprintf(name_old, "dump-type%u-%u-%lu", ed->type,
+ (unsigned int)ed->id, ed->time.tv_sec);
- /*
- * Clean up an entry with the same name
- */
+ for (i = 0; i < DUMP_NAME_LEN; i++)
+ efi_name_old[i] = name_old[i];
- list_for_each_entry(entry, &efivars->list, list) {
- get_var_data_locked(efivars, &entry->var);
+ if (utf16_strncmp(entry->var.VariableName, efi_name_old,
+ utf16_strlen(efi_name_old)))
+ return 0;
+ }
- if (efi_guidcmp(entry->var.VendorGuid, vendor))
- continue;
- if (utf16_strncmp(entry->var.VariableName, efi_name,
- utf16_strlen(efi_name))) {
- /*
- * Check if an old format,
- * which doesn't support holding
- * multiple logs, remains.
- */
- sprintf(name_old, "dump-type%u-%u-%lu", type,
- (unsigned int)id, time.tv_sec);
+ /* found */
+ __efivar_entry_delete(entry);
+ return 1;
+}
- for (i = 0; i < DUMP_NAME_LEN; i++)
- efi_name_old[i] = name_old[i];
+static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
+ struct timespec time, struct pstore_info *psi)
+{
+ struct pstore_erase_data edata;
+ struct efivar_entry *entry;
+ char name[DUMP_NAME_LEN];
+ efi_char16_t efi_name[DUMP_NAME_LEN];
+ int found, i;
- if (utf16_strncmp(entry->var.VariableName, efi_name_old,
- utf16_strlen(efi_name_old)))
- continue;
- }
+ sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count,
+ time.tv_sec);
- /* found */
- found = entry;
- efivars->ops->set_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- PSTORE_EFI_ATTRIBUTES,
- 0, NULL);
- break;
- }
+ for (i = 0; i < DUMP_NAME_LEN; i++)
+ efi_name[i] = name[i];
- if (found)
- list_del(&found->list);
+ edata.id = id;
+ edata.type = type;
+ edata.count = count;
+ edata.time = time;
+ edata.name = efi_name;
- spin_unlock_irq(&efivars->lock);
+ efivar_entry_iter_begin();
+ found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry);
+ efivar_entry_iter_end();
if (found)
- efivar_unregister(found);
+ efivar_unregister(entry);
return 0;
}
@@ -1508,19 +1328,17 @@ static struct pstore_info efi_pstore_info = {
.erase = efi_pstore_erase,
};
-static void efivar_pstore_register(struct efivars *efivars)
+static void efivar_pstore_register(void)
{
- 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);
+ efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
+ if (efi_pstore_info.buf) {
+ efi_pstore_info.bufsize = 1024;
+ spin_lock_init(&efi_pstore_info.buf_lock);
+ pstore_register(&efi_pstore_info);
}
}