efivarfs: Move file system code to fs/efivarfs/

efivars.c has grown far too large and weighs in at nearly 2000
lines. It contains code for,

  * EFI variable handling at the firmware-level
  * handling EFI variables via sysfs
  * a pstore backend
  * an EFI variable filesystem

Start chainsawing things up now before it gets any worse. Move all the
efivarfs filesystem-specific code to fs/efivarfs/. This has
necessitated introducing an EFI variable interface in efivars.c, see
efivar_list_add() and efivar_list_del_unlock().

It also looks like efivarfs only ever worked with the CONFIG_EFI_VARS
backend, not CONFIG_GOOGLE_SMI, because it accesses '__efivars'
directly. Interestingly, if anyone was running with both of those
options enabled efivarfs would have been registered twice via
register_filesystem().

There's no reason that efivarfs couldn't be made to work with the
CONFIG_GOOGLE_SMI variable backend, apart from the fact that the
efivar code doesn't currently support such a scheme.

Cc: Seiji Aguchi <seiji.aguchi@hds.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Matthew Garrett <mjg59@srcf.ucam.org>
Cc: Jeremy Kerr <jeremy.kerr@canonical.com>
Cc: Tony Luck <tony.luck@intel.com>
Cc: Mike Waychison <mikew@google.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index 212c255..ad1d94e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2894,6 +2894,15 @@
 F:	drivers/firmware/efivars.c
 F:	include/linux/efi*.h
 
+EFI VARIABLE FILESYSTEM
+M:	Matt Fleming <matt.fleming@intel.com>
+M:	Matthew Garrett <mjg59@srcf.ucam.org>
+M:	Jeremy Kerr <jeremy.kerr@canonical.com>
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi.git
+L:	linux-efi@vger.kernel.org
+S:	Maintained
+F:	fs/efivarfs/
+
 EFIFB FRAMEBUFFER DRIVER
 L:	linux-fbdev@vger.kernel.org
 M:	Peter Jones <pjones@redhat.com>
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 10088fd..7c9b5e6 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -97,42 +97,15 @@
 
 #define DUMP_NAME_LEN 52
 
-/*
- * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
- * not including trailing NUL
- */
-#define GUID_LEN 36
-
-/*
- * 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 efivars *efivars;
-	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);
 	ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
 };
 
-static struct efivars __efivars;
+struct efivars __efivars;
+EXPORT_SYMBOL_GPL(__efivars);
+
 static struct efivar_operations ops;
 
 #define PSTORE_EFI_ATTRIBUTES \
@@ -159,23 +132,6 @@
 			  efi_char16_t *variable_name,
 			  efi_guid_t *vendor_guid);
 
-/* 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);
-}
-
 /*
  * Return the number of bytes is the length of this string
  * Note: this is NOT the same as the number of unicode characters
@@ -351,8 +307,7 @@
 	{ "", 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;
@@ -387,6 +342,7 @@
 
 	return true;
 }
+EXPORT_SYMBOL_GPL(efivar_validate);
 
 static efi_status_t
 get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
@@ -533,7 +489,7 @@
 	}
 
 	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;
 	}
@@ -648,617 +604,6 @@
 	kobject_put(&var->kobj);
 }
 
-static int efivarfs_file_open(struct inode *inode, struct file *file)
-{
-	file->private_data = inode->i_private;
-	return 0;
-}
-
-static int efi_status_to_err(efi_status_t status)
-{
-	int err;
-
-	switch (status) {
-	case EFI_INVALID_PARAMETER:
-		err = -EINVAL;
-		break;
-	case EFI_OUT_OF_RESOURCES:
-		err = -ENOSPC;
-		break;
-	case EFI_DEVICE_ERROR:
-		err = -EIO;
-		break;
-	case EFI_WRITE_PROTECTED:
-		err = -EROFS;
-		break;
-	case EFI_SECURITY_VIOLATION:
-		err = -EACCES;
-		break;
-	case EFI_NOT_FOUND:
-		err = -EIO;
-		break;
-	default:
-		err = -EINVAL;
-	}
-
-	return err;
-}
-
-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;
-	efi_status_t status;
-	void *data;
-	u32 attributes;
-	struct inode *inode = file->f_mapping->host;
-	unsigned long datasize = count - sizeof(attributes);
-	unsigned long newdatasize;
-	u64 storage_size, remaining_size, max_size;
-	ssize_t bytes = 0;
-
-	if (count < sizeof(attributes))
-		return -EINVAL;
-
-	if (copy_from_user(&attributes, userbuf, sizeof(attributes)))
-		return -EFAULT;
-
-	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
-	 * QueryVariableInfo() isn't supported by the firmware.
-	 */
-	spin_lock(&efivars->lock);
-
-	if (!efivars->ops->query_variable_info)
-		status = EFI_UNSUPPORTED;
-	else {
-		const struct efivar_operations *fops = efivars->ops;
-		status = fops->query_variable_info(attributes, &storage_size,
-						   &remaining_size, &max_size);
-	}
-
-	spin_unlock(&efivars->lock);
-
-	if (status != EFI_SUCCESS) {
-		if (status != EFI_UNSUPPORTED)
-			return efi_status_to_err(status);
-
-		remaining_size = 65536;
-	}
-
-	if (datasize > remaining_size)
-		return -ENOSPC;
-
-	data = kmalloc(datasize, GFP_KERNEL);
-	if (!data)
-		return -ENOMEM;
-
-	if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) {
-		bytes = -EFAULT;
-		goto out;
-	}
-
-	if (validate_var(&var->var, data, datasize) == false) {
-		bytes = -EINVAL;
-		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(&efivars->lock);
-
-	status = efivars->ops->set_variable(var->var.VariableName,
-					    &var->var.VendorGuid,
-					    attributes, datasize,
-					    data);
-
-	if (status != EFI_SUCCESS) {
-		spin_unlock(&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(&efivars->lock);
-		mutex_lock(&inode->i_mutex);
-		i_size_write(inode, newdatasize + sizeof(attributes));
-		mutex_unlock(&inode->i_mutex);
-
-	} else if (status == EFI_NOT_FOUND) {
-		list_del(&var->list);
-		spin_unlock(&efivars->lock);
-		efivar_unregister(var);
-		drop_nlink(inode);
-		d_delete(file->f_dentry);
-		dput(file->f_dentry);
-
-	} else {
-		spin_unlock(&efivars->lock);
-		pr_warn("efivarfs: inconsistent EFI variable implementation? "
-				"status = %lx\n", status);
-	}
-
-out:
-	kfree(data);
-
-	return bytes;
-}
-
-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;
-	efi_status_t status;
-	unsigned long datasize = 0;
-	u32 attributes;
-	void *data;
-	ssize_t size = 0;
-
-	spin_lock(&efivars->lock);
-	status = efivars->ops->get_variable(var->var.VariableName,
-					    &var->var.VendorGuid,
-					    &attributes, &datasize, NULL);
-	spin_unlock(&efivars->lock);
-
-	if (status != EFI_BUFFER_TOO_SMALL)
-		return efi_status_to_err(status);
-
-	data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL);
-
-	if (!data)
-		return -ENOMEM;
-
-	spin_lock(&efivars->lock);
-	status = efivars->ops->get_variable(var->var.VariableName,
-					    &var->var.VendorGuid,
-					    &attributes, &datasize,
-					    (data + sizeof(attributes)));
-	spin_unlock(&efivars->lock);
-
-	if (status != EFI_SUCCESS) {
-		size = efi_status_to_err(status);
-		goto out_free;
-	}
-
-	memcpy(data, &attributes, sizeof(attributes));
-	size = simple_read_from_buffer(userbuf, count, ppos,
-				       data, datasize + sizeof(attributes));
-out_free:
-	kfree(data);
-
-	return size;
-}
-
-static void efivarfs_evict_inode(struct inode *inode)
-{
-	clear_inode(inode);
-}
-
-static const struct super_operations efivarfs_ops = {
-	.statfs = simple_statfs,
-	.drop_inode = generic_delete_inode,
-	.evict_inode = efivarfs_evict_inode,
-	.show_options = generic_show_options,
-};
-
-static struct super_block *efivarfs_sb;
-
-static const struct inode_operations efivarfs_dir_inode_operations;
-
-static const struct file_operations efivarfs_file_operations = {
-	.open	= efivarfs_file_open,
-	.read	= efivarfs_file_read,
-	.write	= efivarfs_file_write,
-	.llseek	= no_llseek,
-};
-
-static struct inode *efivarfs_get_inode(struct super_block *sb,
-				const struct inode *dir, int mode, dev_t dev)
-{
-	struct inode *inode = new_inode(sb);
-
-	if (inode) {
-		inode->i_ino = get_next_ino();
-		inode->i_mode = mode;
-		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
-		switch (mode & S_IFMT) {
-		case S_IFREG:
-			inode->i_fop = &efivarfs_file_operations;
-			break;
-		case S_IFDIR:
-			inode->i_op = &efivarfs_dir_inode_operations;
-			inode->i_fop = &simple_dir_operations;
-			inc_nlink(inode);
-			break;
-		}
-	}
-	return inode;
-}
-
-/*
- * Return 1 if 'str' is a valid efivarfs filename of the form,
- *
- *	VariableName-12345678-1234-1234-1234-1234567891bc
- */
-static int efivarfs_valid_name(const char *str, int len)
-{
-	const char *s;
-	int i, j;
-	int ranges[2][5] = {
-		{ 0, 9, 14, 19, 24 },
-		{ 8, 13, 18, 23, 36 }
-	};
-
-	/*
-	 * We need a GUID, plus at least one letter for the variable name,
-	 * plus the '-' separator
-	 */
-	if (len < GUID_LEN + 2)
-		return 0;
-
-	s = strchr(str, '-');
-	if (!s)
-		return 0;
-
-	s++;			/* Skip '-' */
-
-	/* Ensure we have enough characters for a GUID */
-	if (len - (s - str) != GUID_LEN)
-		return 0;
-
-	/*
-	 * Validate that 's' is of the correct format, e.g.
-	 *
-	 *	12345678-1234-1234-1234-123456789abc
-	 */
-	for (i = 0; i < 5; i++) {
-		for (j = ranges[0][i]; j < ranges[1][i]; j++) {
-			if (hex_to_bin(s[j]) < 0)
-				return 0;
-		}
-
-		if (j < GUID_LEN && s[j] != '-')
-			return 0;
-	}
-
-	return 1;
-}
-
-static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
-{
-	guid->b[0] = hex_to_bin(str[6]) << 4 | hex_to_bin(str[7]);
-	guid->b[1] = hex_to_bin(str[4]) << 4 | hex_to_bin(str[5]);
-	guid->b[2] = hex_to_bin(str[2]) << 4 | hex_to_bin(str[3]);
-	guid->b[3] = hex_to_bin(str[0]) << 4 | hex_to_bin(str[1]);
-	guid->b[4] = hex_to_bin(str[11]) << 4 | hex_to_bin(str[12]);
-	guid->b[5] = hex_to_bin(str[9]) << 4 | hex_to_bin(str[10]);
-	guid->b[6] = hex_to_bin(str[16]) << 4 | hex_to_bin(str[17]);
-	guid->b[7] = hex_to_bin(str[14]) << 4 | hex_to_bin(str[15]);
-	guid->b[8] = hex_to_bin(str[19]) << 4 | hex_to_bin(str[20]);
-	guid->b[9] = hex_to_bin(str[21]) << 4 | hex_to_bin(str[22]);
-	guid->b[10] = hex_to_bin(str[24]) << 4 | hex_to_bin(str[25]);
-	guid->b[11] = hex_to_bin(str[26]) << 4 | hex_to_bin(str[27]);
-	guid->b[12] = hex_to_bin(str[28]) << 4 | hex_to_bin(str[29]);
-	guid->b[13] = hex_to_bin(str[30]) << 4 | hex_to_bin(str[31]);
-	guid->b[14] = hex_to_bin(str[32]) << 4 | hex_to_bin(str[33]);
-	guid->b[15] = hex_to_bin(str[34]) << 4 | hex_to_bin(str[35]);
-}
-
-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;
-
-	if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len))
-		return -EINVAL;
-
-	inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
-	if (!inode)
-		return -ENOMEM;
-
-	var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
-	if (!var) {
-		err = -ENOMEM;
-		goto out;
-	}
-
-	/* length of the variable name itself: remove GUID and separator */
-	namelen = dentry->d_name.len - GUID_LEN - 1;
-
-	efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
-			&var->var.VendorGuid);
-
-	for (i = 0; i < namelen; i++)
-		var->var.VariableName[i] = dentry->d_name.name[i];
-
-	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",
-			     dentry->d_name.name);
-	if (err)
-		goto out;
-
-	kobject_uevent(&var->kobj, KOBJ_ADD);
-	spin_lock(&efivars->lock);
-	list_add(&var->list, &efivars->list);
-	spin_unlock(&efivars->lock);
-	d_instantiate(dentry, inode);
-	dget(dentry);
-out:
-	if (err) {
-		kfree(var);
-		iput(inode);
-	}
-	return err;
-}
-
-static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
-{
-	struct efivar_entry *var = dentry->d_inode->i_private;
-	struct efivars *efivars = var->efivars;
-	efi_status_t status;
-
-	spin_lock(&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(&efivars->lock);
-		efivar_unregister(var);
-		drop_nlink(dentry->d_inode);
-		dput(dentry);
-		return 0;
-	}
-
-	spin_unlock(&efivars->lock);
-	return -EINVAL;
-};
-
-/*
- * Compare two efivarfs file names.
- *
- * An efivarfs filename is composed of two parts,
- *
- *	1. A case-sensitive variable name
- *	2. A case-insensitive GUID
- *
- * So we need to perform a case-sensitive match on part 1 and a
- * case-insensitive match on part 2.
- */
-static int efivarfs_d_compare(const struct dentry *parent, const struct inode *pinode,
-			      const struct dentry *dentry, const struct inode *inode,
-			      unsigned int len, const char *str,
-			      const struct qstr *name)
-{
-	const char *q;
-	int guid;
-
-	/*
-	 * If the string we're being asked to compare doesn't match
-	 * the expected format return "no match".
-	 */
-	if (!efivarfs_valid_name(str, len))
-		return 1;
-
-	if (!(q = strchr(name->name, '-')))
-		return 1;
-
-	/* Find part 1, the variable name. */
-	guid = q - (const char *)name->name;
-
-	/* Case-sensitive compare for the variable name */
-	if (memcmp(str, name->name, guid))
-		return 1;
-
-	/* Case-insensitive compare for the GUID */
-	return strcasecmp(&name->name[guid], &str[guid]);
-}
-
-static int efivarfs_d_hash(const struct dentry *dentry,
-			   const struct inode *inode, struct qstr *qstr)
-{
-	const unsigned char *us;
-	char lower[NAME_MAX];
-	int guid;
-
-	if (!efivarfs_valid_name(qstr->name, qstr->len))
-		return -EINVAL;
-
-	if (qstr->len > NAME_MAX)
-		return -ENAMETOOLONG;
-
-	us = strchr(qstr->name, '-');
-	if (!us)
-		return -EINVAL;
-
-	/* The variable name part is case-sensitive */
-	guid = us - qstr->name;
-	memcpy(lower, qstr->name, guid);
-
-	/* GUID is case-insensitive. */
-	for (us = &qstr->name[guid]; guid < qstr->len; guid++)
-		lower[guid] = tolower(*us++);
-	lower[guid] = '\0';
-
-	qstr->hash = full_name_hash(lower, qstr->len);
-	return 0;
-}
-
-/*
- * Retaining negative dentries for an in-memory filesystem just wastes
- * memory and lookup time: arrange for them to be deleted immediately.
- */
-static int efivarfs_delete_dentry(const struct dentry *dentry)
-{
-	return 1;
-}
-
-static struct dentry_operations efivarfs_d_ops = {
-	.d_compare = efivarfs_d_compare,
-	.d_hash = efivarfs_d_hash,
-	.d_delete = efivarfs_delete_dentry,
-};
-
-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;
-	char *name;
-
-	efivarfs_sb = sb;
-
-	sb->s_maxbytes          = MAX_LFS_FILESIZE;
-	sb->s_blocksize         = PAGE_CACHE_SIZE;
-	sb->s_blocksize_bits    = PAGE_CACHE_SHIFT;
-	sb->s_magic             = EFIVARFS_MAGIC;
-	sb->s_op                = &efivarfs_ops;
-	sb->s_d_op		= &efivarfs_d_ops;
-	sb->s_time_gran         = 1;
-
-	inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
-	if (!inode)
-		return -ENOMEM;
-	inode->i_op = &efivarfs_dir_inode_operations;
-
-	root = d_make_root(inode);
-	sb->s_root = root;
-	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;
-
-		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 = d_alloc_name(root, name);
-		if (!dentry)
-			goto fail_inode;
-
-		/* copied by the above to local storage in the dentry. */
-		kfree(name);
-
-		spin_lock(&efivars->lock);
-		efivars->ops->get_variable(entry->var.VariableName,
-					   &entry->var.VendorGuid,
-					   &entry->var.Attributes,
-					   &size,
-					   NULL);
-		spin_unlock(&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;
-
-fail_inode:
-	iput(inode);
-fail_name:
-	kfree(name);
-fail:
-	return -ENOMEM;
-}
-
-static struct dentry *efivarfs_mount(struct file_system_type *fs_type,
-				    int flags, const char *dev_name, void *data)
-{
-	return mount_single(fs_type, flags, data, efivarfs_fill_super);
-}
-
-static void efivarfs_kill_sb(struct super_block *sb)
-{
-	kill_litter_super(sb);
-	efivarfs_sb = NULL;
-}
-
-static struct file_system_type efivarfs_type = {
-	.name    = "efivarfs",
-	.mount   = efivarfs_mount,
-	.kill_sb = efivarfs_kill_sb,
-};
-
-/*
- * Handle negative dentry.
- */
-static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry,
-				      unsigned int flags)
-{
-	if (dentry->d_name.len > NAME_MAX)
-		return ERR_PTR(-ENAMETOOLONG);
-	d_add(dentry, NULL);
-	return NULL;
-}
-
-static const struct inode_operations efivarfs_dir_inode_operations = {
-	.lookup = efivarfs_lookup,
-	.unlink = efivarfs_unlink,
-	.create = efivarfs_create,
-};
-
 static struct pstore_info efi_pstore_info;
 
 #ifdef CONFIG_PSTORE
@@ -1449,12 +794,7 @@
 	}
 
 	if (found)
-		list_del(&found->list);
-
-	spin_unlock(&efivars->lock);
-
-	if (found)
-		efivar_unregister(found);
+		efivar_list_del_unlock(found);
 
 	return 0;
 }
@@ -1516,7 +856,7 @@
 		return -EACCES;
 
 	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;
 	}
@@ -1621,10 +961,7 @@
 		spin_unlock(&efivars->lock);
 		return -EIO;
 	}
-	list_del(&search_efivar->list);
-	/* We need to release this lock before unregistering. */
-	spin_unlock(&efivars->lock);
-	efivar_unregister(search_efivar);
+	efivar_list_del_unlock(search_efivar);
 
 	/* It's dead Jim.... */
 	return count;
@@ -1674,6 +1011,62 @@
 
 static struct kobject *efi_kobj;
 
+/**
+ * efivar_list_add - initialise @entry and add to variable list
+ * @efivars: efivars structure containing variable list
+ * @name: ASCII variable name
+ * @entry: new variable entry
+ *
+ * Initialise @entry, create a kobject in the necessary parent and
+ * insert the new entry into the @efivars list.
+ */
+int efivar_list_add(struct efivars *efivars, char *name,
+		    struct efivar_entry *entry)
+{
+	int err;
+
+	entry->kobj.kset = efivars->kset;
+	entry->efivars = efivars;
+
+	err = kobject_init_and_add(&entry->kobj, &efivar_ktype,
+				   NULL, "%s", name);
+	if (err)
+		return err;
+
+	kobject_uevent(&entry->kobj, KOBJ_ADD);
+
+	spin_lock(&efivars->lock);
+	list_add(&entry->list, &efivars->list);
+	spin_unlock(&efivars->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(efivar_list_add);
+
+/**
+ * efivar_list_del_unlock - remove an entry from variable list
+ * @entry: entry to remove
+ *
+ * Remove @entry from the variable list and decrement its reference
+ * count (unregister it).
+ *
+ * NOTE: slightly weird locking semantics here - we expect to be
+ * called with the efivars lock already held, and we release it before
+ * returning. This is because this function is usually called after
+ * ->set_variable() while the lock is still held.
+ */
+void efivar_list_del_unlock(struct efivar_entry *entry)
+{
+	WARN_ON(!spin_is_locked(&entry->efivars->lock));
+
+	list_del(&entry->list);
+	spin_unlock(&entry->efivars->lock);
+
+	/* We need to release this lock before unregistering. */
+	efivar_unregister(entry);
+}
+EXPORT_SYMBOL_GPL(efivar_list_del_unlock);
+
 /*
  * efivar_create_sysfs_entry()
  * Requires:
@@ -1698,7 +1091,7 @@
 	 * plus the GUID, plus trailing NUL
 	 */
 	short_name_size = variable_name_size / sizeof(efi_char16_t)
-				+ 1 + GUID_LEN + 1;
+				+ 1 + EFI_VARIABLE_GUID_LEN + 1;
 
 	short_name = kzalloc(short_name_size, GFP_KERNEL);
 	new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
@@ -1709,7 +1102,6 @@
 		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));
@@ -1725,23 +1117,16 @@
 	*(short_name + strlen(short_name)) = '-';
 	efi_guid_unparse(vendor_guid, short_name + strlen(short_name));
 
-	new_efivar->kobj.kset = efivars->kset;
-	i = kobject_init_and_add(&new_efivar->kobj, &efivar_ktype, NULL,
-				 "%s", short_name);
+	i = efivar_list_add(efivars, short_name, new_efivar);
 	if (i) {
 		kfree(short_name);
 		kfree(new_efivar);
 		return 1;
 	}
 
-	kobject_uevent(&new_efivar->kobj, KOBJ_ADD);
 	kfree(short_name);
 	short_name = NULL;
 
-	spin_lock(&efivars->lock);
-	list_add(&new_efivar->list, &efivars->list);
-	spin_unlock(&efivars->lock);
-
 	return 0;
 }
 
@@ -1810,9 +1195,7 @@
 
 	list_for_each_entry_safe(entry, n, &efivars->list, list) {
 		spin_lock(&efivars->lock);
-		list_del(&entry->list);
-		spin_unlock(&efivars->lock);
-		efivar_unregister(entry);
+		efivar_list_del_unlock(entry);
 	}
 	if (efivars->new_var)
 		sysfs_remove_bin_file(&efivars->kset->kobj, efivars->new_var);
@@ -1902,8 +1285,6 @@
 		pstore_register(&efivars->efi_pstore_info);
 	}
 
-	register_filesystem(&efivarfs_type);
-
 out:
 	kfree(variable_name);
 
diff --git a/fs/Kconfig b/fs/Kconfig
index 780725a..c229f82 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -211,6 +211,7 @@
 source "fs/ufs/Kconfig"
 source "fs/exofs/Kconfig"
 source "fs/f2fs/Kconfig"
+source "fs/efivarfs/Kconfig"
 
 endif # MISC_FILESYSTEMS
 
diff --git a/fs/Makefile b/fs/Makefile
index 9d53192..0fde6a3 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -127,3 +127,4 @@
 obj-y				+= exofs/ # Multiple modules
 obj-$(CONFIG_CEPH_FS)		+= ceph/
 obj-$(CONFIG_PSTORE)		+= pstore/
+obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
diff --git a/fs/efivarfs/Kconfig b/fs/efivarfs/Kconfig
new file mode 100644
index 0000000..1fb2b7f
--- /dev/null
+++ b/fs/efivarfs/Kconfig
@@ -0,0 +1,12 @@
+config EFIVAR_FS
+	tristate "EFI Variable filesystem"
+	depends on EFI_VARS
+	help
+	  efivarfs is a replacement filesystem for the old EFI
+	  variable support via sysfs, as it doesn't suffer from the
+	  same 1024-byte variable size limit.
+
+	  To compile this file system support as a module, choose M
+	  here. The module will be called efivarfs.
+
+	  If unsure, say N.
diff --git a/fs/efivarfs/Makefile b/fs/efivarfs/Makefile
new file mode 100644
index 0000000..955d478
--- /dev/null
+++ b/fs/efivarfs/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the efivarfs filesystem
+#
+
+obj-$(CONFIG_EFIVAR_FS)		+= efivarfs.o
+
+efivarfs-objs			:= inode.o file.o super.o
diff --git a/fs/efivarfs/file.c b/fs/efivarfs/file.c
new file mode 100644
index 0000000..d6f1636
--- /dev/null
+++ b/fs/efivarfs/file.c
@@ -0,0 +1,215 @@
+#include <linux/efi.h>
+#include <linux/fs.h>
+
+static int efivarfs_file_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static int efi_status_to_err(efi_status_t status)
+{
+	int err;
+
+	switch (status) {
+	case EFI_INVALID_PARAMETER:
+		err = -EINVAL;
+		break;
+	case EFI_OUT_OF_RESOURCES:
+		err = -ENOSPC;
+		break;
+	case EFI_DEVICE_ERROR:
+		err = -EIO;
+		break;
+	case EFI_WRITE_PROTECTED:
+		err = -EROFS;
+		break;
+	case EFI_SECURITY_VIOLATION:
+		err = -EACCES;
+		break;
+	case EFI_NOT_FOUND:
+		err = -EIO;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	return err;
+}
+
+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;
+	efi_status_t status;
+	void *data;
+	u32 attributes;
+	struct inode *inode = file->f_mapping->host;
+	unsigned long datasize = count - sizeof(attributes);
+	unsigned long newdatasize;
+	u64 storage_size, remaining_size, max_size;
+	ssize_t bytes = 0;
+
+	if (count < sizeof(attributes))
+		return -EINVAL;
+
+	if (copy_from_user(&attributes, userbuf, sizeof(attributes)))
+		return -EFAULT;
+
+	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
+	 * QueryVariableInfo() isn't supported by the firmware.
+	 */
+	spin_lock(&efivars->lock);
+
+	if (!efivars->ops->query_variable_info)
+		status = EFI_UNSUPPORTED;
+	else {
+		const struct efivar_operations *fops = efivars->ops;
+		status = fops->query_variable_info(attributes, &storage_size,
+						   &remaining_size, &max_size);
+	}
+
+	spin_unlock(&efivars->lock);
+
+	if (status != EFI_SUCCESS) {
+		if (status != EFI_UNSUPPORTED)
+			return efi_status_to_err(status);
+
+		remaining_size = 65536;
+	}
+
+	if (datasize > remaining_size)
+		return -ENOSPC;
+
+	data = kmalloc(datasize, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) {
+		bytes = -EFAULT;
+		goto out;
+	}
+
+	if (efivar_validate(&var->var, data, datasize) == false) {
+		bytes = -EINVAL;
+		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(&efivars->lock);
+
+	status = efivars->ops->set_variable(var->var.VariableName,
+					    &var->var.VendorGuid,
+					    attributes, datasize,
+					    data);
+
+	if (status != EFI_SUCCESS) {
+		spin_unlock(&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(&efivars->lock);
+		mutex_lock(&inode->i_mutex);
+		i_size_write(inode, newdatasize + sizeof(attributes));
+		mutex_unlock(&inode->i_mutex);
+
+	} else if (status == EFI_NOT_FOUND) {
+		efivar_list_del_unlock(var);
+		drop_nlink(inode);
+		d_delete(file->f_dentry);
+		dput(file->f_dentry);
+
+	} else {
+		spin_unlock(&efivars->lock);
+		pr_warn("efivarfs: inconsistent EFI variable implementation? "
+				"status = %lx\n", status);
+	}
+
+out:
+	kfree(data);
+
+	return bytes;
+}
+
+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;
+	efi_status_t status;
+	unsigned long datasize = 0;
+	u32 attributes;
+	void *data;
+	ssize_t size = 0;
+
+	spin_lock(&efivars->lock);
+	status = efivars->ops->get_variable(var->var.VariableName,
+					    &var->var.VendorGuid,
+					    &attributes, &datasize, NULL);
+	spin_unlock(&efivars->lock);
+
+	if (status != EFI_BUFFER_TOO_SMALL)
+		return efi_status_to_err(status);
+
+	data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL);
+
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock(&efivars->lock);
+	status = efivars->ops->get_variable(var->var.VariableName,
+					    &var->var.VendorGuid,
+					    &attributes, &datasize,
+					    (data + sizeof(attributes)));
+	spin_unlock(&efivars->lock);
+
+	if (status != EFI_SUCCESS) {
+		size = efi_status_to_err(status);
+		goto out_free;
+	}
+
+	memcpy(data, &attributes, sizeof(attributes));
+	size = simple_read_from_buffer(userbuf, count, ppos,
+				       data, datasize + sizeof(attributes));
+out_free:
+	kfree(data);
+
+	return size;
+}
+
+const struct file_operations efivarfs_file_operations = {
+	.open	= efivarfs_file_open,
+	.read	= efivarfs_file_read,
+	.write	= efivarfs_file_write,
+	.llseek	= no_llseek,
+};
+
diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c
new file mode 100644
index 0000000..f99703f
--- /dev/null
+++ b/fs/efivarfs/inode.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2012 Matthew Garrett
+ * Copyright (C) 2012 Jeremy Kerr
+ */
+
+#include <linux/efi.h>
+#include <linux/fs.h>
+
+#include "internal.h"
+
+struct inode *efivarfs_get_inode(struct super_block *sb,
+			const struct inode *dir, int mode, dev_t dev)
+{
+	struct inode *inode = new_inode(sb);
+
+	if (inode) {
+		inode->i_ino = get_next_ino();
+		inode->i_mode = mode;
+		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+		switch (mode & S_IFMT) {
+		case S_IFREG:
+			inode->i_fop = &efivarfs_file_operations;
+			break;
+		case S_IFDIR:
+			inode->i_op = &efivarfs_dir_inode_operations;
+			inode->i_fop = &simple_dir_operations;
+			inc_nlink(inode);
+			break;
+		}
+	}
+	return inode;
+}
+
+/*
+ * Return 1 if 'str' is a valid efivarfs filename of the form,
+ *
+ *	VariableName-12345678-1234-1234-1234-1234567891bc
+ */
+int efivarfs_valid_name(const char *str, int len)
+{
+	const char *s;
+	int i, j;
+	int ranges[2][5] = {
+		{ 0, 9, 14, 19, 24 },
+		{ 8, 13, 18, 23, 36 }
+	};
+
+	/*
+	 * We need a GUID, plus at least one letter for the variable name,
+	 * plus the '-' separator
+	 */
+	if (len < EFI_VARIABLE_GUID_LEN + 2)
+		return 0;
+
+	s = strchr(str, '-');
+	if (!s)
+		return 0;
+
+	s++;			/* Skip '-' */
+
+	/* Ensure we have enough characters for a GUID */
+	if (len - (s - str) != EFI_VARIABLE_GUID_LEN)
+		return 0;
+
+	/*
+	 * Validate that 's' is of the correct format, e.g.
+	 *
+	 *	12345678-1234-1234-1234-123456789abc
+	 */
+	for (i = 0; i < 5; i++) {
+		for (j = ranges[0][i]; j < ranges[1][i]; j++) {
+			if (hex_to_bin(s[j]) < 0)
+				return 0;
+		}
+
+		if (j < EFI_VARIABLE_GUID_LEN && s[j] != '-')
+			return 0;
+	}
+
+	return 1;
+}
+
+static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
+{
+	guid->b[0] = hex_to_bin(str[6]) << 4 | hex_to_bin(str[7]);
+	guid->b[1] = hex_to_bin(str[4]) << 4 | hex_to_bin(str[5]);
+	guid->b[2] = hex_to_bin(str[2]) << 4 | hex_to_bin(str[3]);
+	guid->b[3] = hex_to_bin(str[0]) << 4 | hex_to_bin(str[1]);
+	guid->b[4] = hex_to_bin(str[11]) << 4 | hex_to_bin(str[12]);
+	guid->b[5] = hex_to_bin(str[9]) << 4 | hex_to_bin(str[10]);
+	guid->b[6] = hex_to_bin(str[16]) << 4 | hex_to_bin(str[17]);
+	guid->b[7] = hex_to_bin(str[14]) << 4 | hex_to_bin(str[15]);
+	guid->b[8] = hex_to_bin(str[19]) << 4 | hex_to_bin(str[20]);
+	guid->b[9] = hex_to_bin(str[21]) << 4 | hex_to_bin(str[22]);
+	guid->b[10] = hex_to_bin(str[24]) << 4 | hex_to_bin(str[25]);
+	guid->b[11] = hex_to_bin(str[26]) << 4 | hex_to_bin(str[27]);
+	guid->b[12] = hex_to_bin(str[28]) << 4 | hex_to_bin(str[29]);
+	guid->b[13] = hex_to_bin(str[30]) << 4 | hex_to_bin(str[31]);
+	guid->b[14] = hex_to_bin(str[32]) << 4 | hex_to_bin(str[33]);
+	guid->b[15] = hex_to_bin(str[34]) << 4 | hex_to_bin(str[35]);
+}
+
+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;
+
+	if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len))
+		return -EINVAL;
+
+	inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
+	if (!inode)
+		return -ENOMEM;
+
+	var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
+	if (!var) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	/* length of the variable name itself: remove GUID and separator */
+	namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1;
+
+	efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
+			&var->var.VendorGuid);
+
+	for (i = 0; i < namelen; i++)
+		var->var.VariableName[i] = dentry->d_name.name[i];
+
+	var->var.VariableName[i] = '\0';
+
+	inode->i_private = var;
+
+	err = efivar_list_add(efivars, (char *)dentry->d_name.name, var);
+	if (err)
+		goto out;
+
+	d_instantiate(dentry, inode);
+	dget(dentry);
+out:
+	if (err) {
+		kfree(var);
+		iput(inode);
+	}
+	return err;
+}
+
+static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct efivar_entry *var = dentry->d_inode->i_private;
+	struct efivars *efivars = var->efivars;
+	efi_status_t status;
+
+	spin_lock(&efivars->lock);
+
+	status = efivars->ops->set_variable(var->var.VariableName,
+					    &var->var.VendorGuid,
+					    0, 0, NULL);
+
+	if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) {
+		efivar_list_del_unlock(var);
+		drop_nlink(dentry->d_inode);
+		dput(dentry);
+		return 0;
+	}
+
+	spin_unlock(&efivars->lock);
+	return -EINVAL;
+};
+
+/*
+ * Handle negative dentry.
+ */
+static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry,
+				      unsigned int flags)
+{
+	if (dentry->d_name.len > NAME_MAX)
+		return ERR_PTR(-ENAMETOOLONG);
+	d_add(dentry, NULL);
+	return NULL;
+}
+
+const struct inode_operations efivarfs_dir_inode_operations = {
+	.lookup = efivarfs_lookup,
+	.unlink = efivarfs_unlink,
+	.create = efivarfs_create,
+};
diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h
new file mode 100644
index 0000000..582aa37
--- /dev/null
+++ b/fs/efivarfs/internal.h
@@ -0,0 +1,9 @@
+extern struct efivars __efivars;
+
+extern const struct file_operations efivarfs_file_operations;
+extern const struct inode_operations efivarfs_dir_inode_operations;
+
+extern int efivarfs_valid_name(const char *str, int len);
+extern struct inode *efivarfs_get_inode(struct super_block *sb,
+			const struct inode *dir, int mode, dev_t dev);
+
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c
new file mode 100644
index 0000000..e87b65a
--- /dev/null
+++ b/fs/efivarfs/super.c
@@ -0,0 +1,228 @@
+#include <linux/ctype.h>
+#include <linux/efi.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+
+#include "internal.h"
+
+static void efivarfs_evict_inode(struct inode *inode)
+{
+	clear_inode(inode);
+}
+
+static const struct super_operations efivarfs_ops = {
+	.statfs = simple_statfs,
+	.drop_inode = generic_delete_inode,
+	.evict_inode = efivarfs_evict_inode,
+	.show_options = generic_show_options,
+};
+
+static struct super_block *efivarfs_sb;
+
+/*
+ * Compare two efivarfs file names.
+ *
+ * An efivarfs filename is composed of two parts,
+ *
+ *	1. A case-sensitive variable name
+ *	2. A case-insensitive GUID
+ *
+ * So we need to perform a case-sensitive match on part 1 and a
+ * case-insensitive match on part 2.
+ */
+static int efivarfs_d_compare(const struct dentry *parent, const struct inode *pinode,
+			      const struct dentry *dentry, const struct inode *inode,
+			      unsigned int len, const char *str,
+			      const struct qstr *name)
+{
+	const char *q;
+	int guid;
+
+	/*
+	 * If the string we're being asked to compare doesn't match
+	 * the expected format return "no match".
+	 */
+	if (!efivarfs_valid_name(str, len))
+		return 1;
+
+	if (!(q = strchr(name->name, '-')))
+		return 1;
+
+	/* Find part 1, the variable name. */
+	guid = q - (const char *)name->name;
+
+	/* Case-sensitive compare for the variable name */
+	if (memcmp(str, name->name, guid))
+		return 1;
+
+	/* Case-insensitive compare for the GUID */
+	return strcasecmp(&name->name[guid], &str[guid]);
+}
+
+static int efivarfs_d_hash(const struct dentry *dentry,
+			   const struct inode *inode, struct qstr *qstr)
+{
+	const unsigned char *us;
+	char lower[NAME_MAX];
+	int guid;
+
+	if (!efivarfs_valid_name(qstr->name, qstr->len))
+		return -EINVAL;
+
+	if (qstr->len > NAME_MAX)
+		return -ENAMETOOLONG;
+
+	us = strchr(qstr->name, '-');
+	if (!us)
+		return -EINVAL;
+
+	/* The variable name part is case-sensitive */
+	guid = us - qstr->name;
+	memcpy(lower, qstr->name, guid);
+
+	/* GUID is case-insensitive. */
+	for (us = &qstr->name[guid]; guid < qstr->len; guid++)
+		lower[guid] = tolower(*us++);
+	lower[guid] = '\0';
+
+	qstr->hash = full_name_hash(lower, qstr->len);
+	return 0;
+}
+
+/*
+ * Retaining negative dentries for an in-memory filesystem just wastes
+ * memory and lookup time: arrange for them to be deleted immediately.
+ */
+static int efivarfs_delete_dentry(const struct dentry *dentry)
+{
+	return 1;
+}
+
+static struct dentry_operations efivarfs_d_ops = {
+	.d_compare = efivarfs_d_compare,
+	.d_hash = efivarfs_d_hash,
+	.d_delete = efivarfs_delete_dentry,
+};
+
+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;
+	char *name;
+
+	efivarfs_sb = sb;
+
+	sb->s_maxbytes          = MAX_LFS_FILESIZE;
+	sb->s_blocksize         = PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits    = PAGE_CACHE_SHIFT;
+	sb->s_magic             = EFIVARFS_MAGIC;
+	sb->s_op                = &efivarfs_ops;
+	sb->s_d_op		= &efivarfs_d_ops;
+	sb->s_time_gran         = 1;
+
+	inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
+	if (!inode)
+		return -ENOMEM;
+	inode->i_op = &efivarfs_dir_inode_operations;
+
+	root = d_make_root(inode);
+	sb->s_root = root;
+	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 + EFI_VARIABLE_GUID_LEN + 1, GFP_ATOMIC);
+		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(efivarfs_sb, root->d_inode,
+					  S_IFREG | 0644, 0);
+		if (!inode)
+			goto fail_name;
+
+		dentry = d_alloc_name(root, name);
+		if (!dentry)
+			goto fail_inode;
+
+		/* copied by the above to local storage in the dentry. */
+		kfree(name);
+
+		spin_lock(&efivars->lock);
+		efivars->ops->get_variable(entry->var.VariableName,
+					   &entry->var.VendorGuid,
+					   &entry->var.Attributes,
+					   &size,
+					   NULL);
+		spin_unlock(&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;
+
+fail_inode:
+	iput(inode);
+fail_name:
+	kfree(name);
+fail:
+	return -ENOMEM;
+}
+
+static struct dentry *efivarfs_mount(struct file_system_type *fs_type,
+				    int flags, const char *dev_name, void *data)
+{
+	return mount_single(fs_type, flags, data, efivarfs_fill_super);
+}
+
+static void efivarfs_kill_sb(struct super_block *sb)
+{
+	kill_litter_super(sb);
+	efivarfs_sb = NULL;
+}
+
+static struct file_system_type efivarfs_type = {
+	.name    = "efivarfs",
+	.mount   = efivarfs_mount,
+	.kill_sb = efivarfs_kill_sb,
+};
+
+static __init int efivarfs_init(void)
+{
+	return register_filesystem(&efivarfs_type);
+}
+
+static __init void efivarfs_exit(void)
+{
+}
+
+MODULE_AUTHOR("Matthew Garrett, Jeremy Kerr");
+MODULE_DESCRIPTION("EFI Variable Filesystem");
+MODULE_LICENSE("GPL");
+
+module_init(efivarfs_init);
+module_exit(efivarfs_exit);
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 7a9498a..6fd8a4b 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -663,6 +663,12 @@
 				EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \
 				EFI_VARIABLE_APPEND_WRITE)
 /*
+ * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
+ * not including trailing NUL
+ */
+#define EFI_VARIABLE_GUID_LEN 36
+
+/*
  * The type of search to perform when calling boottime->locate_handle
  */
 #define EFI_LOCATE_ALL_HANDLES			0
@@ -719,6 +725,23 @@
 	*addr &= PAGE_MASK;
 }
 
+/* Return the number of unicode characters in data */
+static inline 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);
+}
+
 #if defined(CONFIG_EFI_VARS) || defined(CONFIG_EFI_VARS_MODULE)
 /*
  * EFI Variable support.
@@ -753,11 +776,39 @@
 	struct pstore_info efi_pstore_info;
 };
 
+/*
+ * 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 efivars *efivars;
+	struct efi_variable var;
+	struct list_head list;
+	struct kobject kobj;
+};
+
 int register_efivars(struct efivars *efivars,
 		     const struct efivar_operations *ops,
 		     struct kobject *parent_kobj);
 void unregister_efivars(struct efivars *efivars);
 
+int efivar_list_add(struct efivars *efivars, char *name,
+		    struct efivar_entry *entry);
+void efivar_list_del_unlock(struct efivar_entry *entry);
+bool efivar_validate(struct efi_variable *var, u8 *data, unsigned long len);
+
 #endif /* CONFIG_EFI_VARS */
 
 #endif /* _LINUX_EFI_H */