1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2d68772b7SMatt Fleming /*
3d68772b7SMatt Fleming * Copyright (C) 2012 Red Hat, Inc.
4d68772b7SMatt Fleming * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com>
5d68772b7SMatt Fleming */
6d68772b7SMatt Fleming
7d68772b7SMatt Fleming #include <linux/ctype.h>
8d68772b7SMatt Fleming #include <linux/efi.h>
9d68772b7SMatt Fleming #include <linux/fs.h>
1047999745SDavid Howells #include <linux/fs_context.h>
11d68772b7SMatt Fleming #include <linux/module.h>
12d68772b7SMatt Fleming #include <linux/pagemap.h>
13a614e192SMatt Fleming #include <linux/ucs2_string.h>
1420b4fb48SLinus Torvalds #include <linux/slab.h>
1520b4fb48SLinus Torvalds #include <linux/magic.h>
16d86ff333SAnisse Astier #include <linux/statfs.h>
170049fe7eSIlias Apalodimas #include <linux/printk.h>
18d68772b7SMatt Fleming
19d68772b7SMatt Fleming #include "internal.h"
20d68772b7SMatt Fleming
21d68772b7SMatt Fleming LIST_HEAD(efivarfs_list);
22d68772b7SMatt Fleming
efivarfs_evict_inode(struct inode * inode)23d68772b7SMatt Fleming static void efivarfs_evict_inode(struct inode *inode)
24d68772b7SMatt Fleming {
25d68772b7SMatt Fleming clear_inode(inode);
26d68772b7SMatt Fleming }
27d68772b7SMatt Fleming
efivarfs_statfs(struct dentry * dentry,struct kstatfs * buf)28d86ff333SAnisse Astier static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf)
29d86ff333SAnisse Astier {
30d86ff333SAnisse Astier const u32 attr = EFI_VARIABLE_NON_VOLATILE |
31d86ff333SAnisse Astier EFI_VARIABLE_BOOTSERVICE_ACCESS |
32d86ff333SAnisse Astier EFI_VARIABLE_RUNTIME_ACCESS;
33d86ff333SAnisse Astier u64 storage_space, remaining_space, max_variable_size;
34d86ff333SAnisse Astier efi_status_t status;
35d86ff333SAnisse Astier
3679b83606SHeinrich Schuchardt /* Some UEFI firmware does not implement QueryVariableInfo() */
3779b83606SHeinrich Schuchardt storage_space = remaining_space = 0;
3879b83606SHeinrich Schuchardt if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) {
3979b83606SHeinrich Schuchardt status = efivar_query_variable_info(attr, &storage_space,
4079b83606SHeinrich Schuchardt &remaining_space,
41d86ff333SAnisse Astier &max_variable_size);
4279b83606SHeinrich Schuchardt if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED)
4379b83606SHeinrich Schuchardt pr_warn_ratelimited("query_variable_info() failed: 0x%lx\n",
4479b83606SHeinrich Schuchardt status);
4579b83606SHeinrich Schuchardt }
46d86ff333SAnisse Astier
47d86ff333SAnisse Astier /*
48d86ff333SAnisse Astier * This is not a normal filesystem, so no point in pretending it has a block
49d86ff333SAnisse Astier * size; we declare f_bsize to 1, so that we can then report the exact value
50d86ff333SAnisse Astier * sent by EFI QueryVariableInfo in f_blocks and f_bfree
51d86ff333SAnisse Astier */
52d86ff333SAnisse Astier buf->f_bsize = 1;
53d86ff333SAnisse Astier buf->f_namelen = NAME_MAX;
54d86ff333SAnisse Astier buf->f_blocks = storage_space;
55d86ff333SAnisse Astier buf->f_bfree = remaining_space;
56d86ff333SAnisse Astier buf->f_type = dentry->d_sb->s_magic;
57d86ff333SAnisse Astier
58d86ff333SAnisse Astier /*
59d86ff333SAnisse Astier * In f_bavail we declare the free space that the kernel will allow writing
60d86ff333SAnisse Astier * when the storage_paranoia x86 quirk is active. To use more, users
61d86ff333SAnisse Astier * should boot the kernel with efi_no_storage_paranoia.
62d86ff333SAnisse Astier */
63d86ff333SAnisse Astier if (remaining_space > efivar_reserved_space())
64d86ff333SAnisse Astier buf->f_bavail = remaining_space - efivar_reserved_space();
65d86ff333SAnisse Astier else
66d86ff333SAnisse Astier buf->f_bavail = 0;
67d86ff333SAnisse Astier
68d86ff333SAnisse Astier return 0;
69d86ff333SAnisse Astier }
70d68772b7SMatt Fleming static const struct super_operations efivarfs_ops = {
71d86ff333SAnisse Astier .statfs = efivarfs_statfs,
72d68772b7SMatt Fleming .drop_inode = generic_delete_inode,
73d68772b7SMatt Fleming .evict_inode = efivarfs_evict_inode,
74d68772b7SMatt Fleming };
75d68772b7SMatt Fleming
76d68772b7SMatt Fleming /*
77d68772b7SMatt Fleming * Compare two efivarfs file names.
78d68772b7SMatt Fleming *
79d68772b7SMatt Fleming * An efivarfs filename is composed of two parts,
80d68772b7SMatt Fleming *
81d68772b7SMatt Fleming * 1. A case-sensitive variable name
82d68772b7SMatt Fleming * 2. A case-insensitive GUID
83d68772b7SMatt Fleming *
84d68772b7SMatt Fleming * So we need to perform a case-sensitive match on part 1 and a
85d68772b7SMatt Fleming * case-insensitive match on part 2.
86d68772b7SMatt Fleming */
efivarfs_d_compare(const struct dentry * dentry,unsigned int len,const char * str,const struct qstr * name)876fa67e70SAl Viro static int efivarfs_d_compare(const struct dentry *dentry,
88d68772b7SMatt Fleming unsigned int len, const char *str,
89d68772b7SMatt Fleming const struct qstr *name)
90d68772b7SMatt Fleming {
91d68772b7SMatt Fleming int guid = len - EFI_VARIABLE_GUID_LEN;
92d68772b7SMatt Fleming
93d68772b7SMatt Fleming if (name->len != len)
94d68772b7SMatt Fleming return 1;
95d68772b7SMatt Fleming
96d68772b7SMatt Fleming /* Case-sensitive compare for the variable name */
97d68772b7SMatt Fleming if (memcmp(str, name->name, guid))
98d68772b7SMatt Fleming return 1;
99d68772b7SMatt Fleming
100d68772b7SMatt Fleming /* Case-insensitive compare for the GUID */
101d68772b7SMatt Fleming return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN);
102d68772b7SMatt Fleming }
103d68772b7SMatt Fleming
efivarfs_d_hash(const struct dentry * dentry,struct qstr * qstr)104da53be12SLinus Torvalds static int efivarfs_d_hash(const struct dentry *dentry, struct qstr *qstr)
105d68772b7SMatt Fleming {
1068387ff25SLinus Torvalds unsigned long hash = init_name_hash(dentry);
107d68772b7SMatt Fleming const unsigned char *s = qstr->name;
108d68772b7SMatt Fleming unsigned int len = qstr->len;
109d68772b7SMatt Fleming
110d68772b7SMatt Fleming while (len-- > EFI_VARIABLE_GUID_LEN)
111d68772b7SMatt Fleming hash = partial_name_hash(*s++, hash);
112d68772b7SMatt Fleming
113d68772b7SMatt Fleming /* GUID is case-insensitive. */
114d68772b7SMatt Fleming while (len--)
115d68772b7SMatt Fleming hash = partial_name_hash(tolower(*s++), hash);
116d68772b7SMatt Fleming
117d68772b7SMatt Fleming qstr->hash = end_name_hash(hash);
118d68772b7SMatt Fleming return 0;
119d68772b7SMatt Fleming }
120d68772b7SMatt Fleming
121e37dcbfbSFabian Frederick static const struct dentry_operations efivarfs_d_ops = {
122d68772b7SMatt Fleming .d_compare = efivarfs_d_compare,
123d68772b7SMatt Fleming .d_hash = efivarfs_d_hash,
124b26d4cd3SAl Viro .d_delete = always_delete_dentry,
125d68772b7SMatt Fleming };
126d68772b7SMatt Fleming
efivarfs_alloc_dentry(struct dentry * parent,char * name)127d68772b7SMatt Fleming static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)
128d68772b7SMatt Fleming {
129d68772b7SMatt Fleming struct dentry *d;
130d68772b7SMatt Fleming struct qstr q;
131d68772b7SMatt Fleming int err;
132d68772b7SMatt Fleming
133d68772b7SMatt Fleming q.name = name;
134d68772b7SMatt Fleming q.len = strlen(name);
135d68772b7SMatt Fleming
1368387ff25SLinus Torvalds err = efivarfs_d_hash(parent, &q);
137d68772b7SMatt Fleming if (err)
138d68772b7SMatt Fleming return ERR_PTR(err);
139d68772b7SMatt Fleming
140d68772b7SMatt Fleming d = d_alloc(parent, &q);
141d68772b7SMatt Fleming if (d)
142d68772b7SMatt Fleming return d;
143d68772b7SMatt Fleming
144d68772b7SMatt Fleming return ERR_PTR(-ENOMEM);
145d68772b7SMatt Fleming }
146d68772b7SMatt Fleming
efivarfs_callback(efi_char16_t * name16,efi_guid_t vendor,unsigned long name_size,void * data)147d68772b7SMatt Fleming static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
148d68772b7SMatt Fleming unsigned long name_size, void *data)
149d68772b7SMatt Fleming {
150d68772b7SMatt Fleming struct super_block *sb = (struct super_block *)data;
151d68772b7SMatt Fleming struct efivar_entry *entry;
152d68772b7SMatt Fleming struct inode *inode = NULL;
153d68772b7SMatt Fleming struct dentry *dentry, *root = sb->s_root;
154d68772b7SMatt Fleming unsigned long size = 0;
155d68772b7SMatt Fleming char *name;
156e0d64e6aSPeter Jones int len;
157d68772b7SMatt Fleming int err = -ENOMEM;
158ed8b0de5SPeter Jones bool is_removable = false;
159d68772b7SMatt Fleming
16063ffb573SJason A. Donenfeld if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID))
16163ffb573SJason A. Donenfeld return 0;
16263ffb573SJason A. Donenfeld
163c57dcb56SRoss Lagerwall entry = kzalloc(sizeof(*entry), GFP_KERNEL);
164d68772b7SMatt Fleming if (!entry)
165d68772b7SMatt Fleming return err;
166d68772b7SMatt Fleming
167d68772b7SMatt Fleming memcpy(entry->var.VariableName, name16, name_size);
168d68772b7SMatt Fleming memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
169d68772b7SMatt Fleming
170e0d64e6aSPeter Jones len = ucs2_utf8size(entry->var.VariableName);
171d68772b7SMatt Fleming
172d68772b7SMatt Fleming /* name, plus '-', plus GUID, plus NUL*/
173d68772b7SMatt Fleming name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
174d68772b7SMatt Fleming if (!name)
175d68772b7SMatt Fleming goto fail;
176d68772b7SMatt Fleming
177e0d64e6aSPeter Jones ucs2_as_utf8(name, entry->var.VariableName, len);
178d68772b7SMatt Fleming
179ed8b0de5SPeter Jones if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
180ed8b0de5SPeter Jones is_removable = true;
181ed8b0de5SPeter Jones
182d68772b7SMatt Fleming name[len] = '-';
183d68772b7SMatt Fleming
18426e02272SBorislav Petkov efi_guid_to_str(&entry->var.VendorGuid, name + len + 1);
185d68772b7SMatt Fleming
186d68772b7SMatt Fleming name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
187d68772b7SMatt Fleming
188336af6a4SMichael Schaller /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */
189336af6a4SMichael Schaller strreplace(name, '/', '!');
190336af6a4SMichael Schaller
191ed8b0de5SPeter Jones inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
192ed8b0de5SPeter Jones is_removable);
193d68772b7SMatt Fleming if (!inode)
194d68772b7SMatt Fleming goto fail_name;
195d68772b7SMatt Fleming
196d68772b7SMatt Fleming dentry = efivarfs_alloc_dentry(root, name);
197d68772b7SMatt Fleming if (IS_ERR(dentry)) {
198d68772b7SMatt Fleming err = PTR_ERR(dentry);
199d68772b7SMatt Fleming goto fail_inode;
200d68772b7SMatt Fleming }
201d68772b7SMatt Fleming
202ec3507b2SArd Biesheuvel __efivar_entry_get(entry, NULL, &size, NULL);
203ec3507b2SArd Biesheuvel __efivar_entry_add(entry, &efivarfs_list);
204d68772b7SMatt Fleming
20522c2b77fSMatt Fleming /* copied by the above to local storage in the dentry. */
20622c2b77fSMatt Fleming kfree(name);
20722c2b77fSMatt Fleming
2085955102cSAl Viro inode_lock(inode);
209d68772b7SMatt Fleming inode->i_private = entry;
210d68772b7SMatt Fleming i_size_write(inode, size + sizeof(entry->var.Attributes));
2115955102cSAl Viro inode_unlock(inode);
212d68772b7SMatt Fleming d_add(dentry, inode);
213d68772b7SMatt Fleming
214d68772b7SMatt Fleming return 0;
215d68772b7SMatt Fleming
216d68772b7SMatt Fleming fail_inode:
217d68772b7SMatt Fleming iput(inode);
218d68772b7SMatt Fleming fail_name:
219d68772b7SMatt Fleming kfree(name);
220d68772b7SMatt Fleming fail:
221d68772b7SMatt Fleming kfree(entry);
222d68772b7SMatt Fleming return err;
223d68772b7SMatt Fleming }
224d68772b7SMatt Fleming
efivarfs_destroy(struct efivar_entry * entry,void * data)225d68772b7SMatt Fleming static int efivarfs_destroy(struct efivar_entry *entry, void *data)
226d68772b7SMatt Fleming {
2273a75f9f2SArd Biesheuvel efivar_entry_remove(entry);
228d68772b7SMatt Fleming kfree(entry);
229d68772b7SMatt Fleming return 0;
230d68772b7SMatt Fleming }
231d68772b7SMatt Fleming
efivarfs_fill_super(struct super_block * sb,struct fs_context * fc)23247999745SDavid Howells static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
233d68772b7SMatt Fleming {
234d68772b7SMatt Fleming struct inode *inode = NULL;
235d68772b7SMatt Fleming struct dentry *root;
236d68772b7SMatt Fleming int err;
237d68772b7SMatt Fleming
238301de9a2SJohan Hovold if (!efivar_is_available())
239301de9a2SJohan Hovold return -EOPNOTSUPP;
240301de9a2SJohan Hovold
241d68772b7SMatt Fleming sb->s_maxbytes = MAX_LFS_FILESIZE;
24209cbfeafSKirill A. Shutemov sb->s_blocksize = PAGE_SIZE;
24309cbfeafSKirill A. Shutemov sb->s_blocksize_bits = PAGE_SHIFT;
244d68772b7SMatt Fleming sb->s_magic = EFIVARFS_MAGIC;
245d68772b7SMatt Fleming sb->s_op = &efivarfs_ops;
246d68772b7SMatt Fleming sb->s_d_op = &efivarfs_d_ops;
247d68772b7SMatt Fleming sb->s_time_gran = 1;
248d68772b7SMatt Fleming
249f88814ccSArd Biesheuvel if (!efivar_supports_writes())
250f88814ccSArd Biesheuvel sb->s_flags |= SB_RDONLY;
251f88814ccSArd Biesheuvel
252ed8b0de5SPeter Jones inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
253d68772b7SMatt Fleming if (!inode)
254d68772b7SMatt Fleming return -ENOMEM;
255d68772b7SMatt Fleming inode->i_op = &efivarfs_dir_inode_operations;
256d68772b7SMatt Fleming
257d68772b7SMatt Fleming root = d_make_root(inode);
258d68772b7SMatt Fleming sb->s_root = root;
259d68772b7SMatt Fleming if (!root)
260d68772b7SMatt Fleming return -ENOMEM;
261d68772b7SMatt Fleming
262d68772b7SMatt Fleming INIT_LIST_HEAD(&efivarfs_list);
263d68772b7SMatt Fleming
2641cfd6316SJulia Lawall err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list);
265d68772b7SMatt Fleming if (err)
2663a75f9f2SArd Biesheuvel efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
267d68772b7SMatt Fleming
268d68772b7SMatt Fleming return err;
269d68772b7SMatt Fleming }
270d68772b7SMatt Fleming
efivarfs_get_tree(struct fs_context * fc)27147999745SDavid Howells static int efivarfs_get_tree(struct fs_context *fc)
272d68772b7SMatt Fleming {
27347999745SDavid Howells return get_tree_single(fc, efivarfs_fill_super);
27447999745SDavid Howells }
27547999745SDavid Howells
efivarfs_reconfigure(struct fs_context * fc)2760049fe7eSIlias Apalodimas static int efivarfs_reconfigure(struct fs_context *fc)
2770049fe7eSIlias Apalodimas {
2780049fe7eSIlias Apalodimas if (!efivar_supports_writes() && !(fc->sb_flags & SB_RDONLY)) {
2790049fe7eSIlias Apalodimas pr_err("Firmware does not support SetVariableRT. Can not remount with rw\n");
2800049fe7eSIlias Apalodimas return -EINVAL;
2810049fe7eSIlias Apalodimas }
2820049fe7eSIlias Apalodimas
2830049fe7eSIlias Apalodimas return 0;
2840049fe7eSIlias Apalodimas }
2850049fe7eSIlias Apalodimas
28647999745SDavid Howells static const struct fs_context_operations efivarfs_context_ops = {
28747999745SDavid Howells .get_tree = efivarfs_get_tree,
2880049fe7eSIlias Apalodimas .reconfigure = efivarfs_reconfigure,
28947999745SDavid Howells };
29047999745SDavid Howells
efivarfs_init_fs_context(struct fs_context * fc)29147999745SDavid Howells static int efivarfs_init_fs_context(struct fs_context *fc)
29247999745SDavid Howells {
29347999745SDavid Howells fc->ops = &efivarfs_context_ops;
29447999745SDavid Howells return 0;
295d68772b7SMatt Fleming }
296d68772b7SMatt Fleming
efivarfs_kill_sb(struct super_block * sb)297d68772b7SMatt Fleming static void efivarfs_kill_sb(struct super_block *sb)
298d68772b7SMatt Fleming {
299*48be1364SArd Biesheuvel struct efivarfs_fs_info *sfi = sb->s_fs_info;
300*48be1364SArd Biesheuvel
301d68772b7SMatt Fleming kill_litter_super(sb);
302d68772b7SMatt Fleming
303301de9a2SJohan Hovold if (!efivar_is_available())
304301de9a2SJohan Hovold return;
305301de9a2SJohan Hovold
306d68772b7SMatt Fleming /* Remove all entries and destroy */
3073a75f9f2SArd Biesheuvel efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
308*48be1364SArd Biesheuvel kfree(sfi);
309d68772b7SMatt Fleming }
310d68772b7SMatt Fleming
311d68772b7SMatt Fleming static struct file_system_type efivarfs_type = {
312af5a29aeSMathias Krause .owner = THIS_MODULE,
313d68772b7SMatt Fleming .name = "efivarfs",
31447999745SDavid Howells .init_fs_context = efivarfs_init_fs_context,
315d68772b7SMatt Fleming .kill_sb = efivarfs_kill_sb,
316d68772b7SMatt Fleming };
317d68772b7SMatt Fleming
efivarfs_init(void)318d68772b7SMatt Fleming static __init int efivarfs_init(void)
319d68772b7SMatt Fleming {
320d68772b7SMatt Fleming return register_filesystem(&efivarfs_type);
321d68772b7SMatt Fleming }
322d68772b7SMatt Fleming
efivarfs_exit(void)323af5a29aeSMathias Krause static __exit void efivarfs_exit(void)
324af5a29aeSMathias Krause {
325af5a29aeSMathias Krause unregister_filesystem(&efivarfs_type);
326af5a29aeSMathias Krause }
327af5a29aeSMathias Krause
328d68772b7SMatt Fleming MODULE_AUTHOR("Matthew Garrett, Jeremy Kerr");
329d68772b7SMatt Fleming MODULE_DESCRIPTION("EFI Variable Filesystem");
330d68772b7SMatt Fleming MODULE_LICENSE("GPL");
331d68772b7SMatt Fleming MODULE_ALIAS_FS("efivarfs");
332d68772b7SMatt Fleming
333d68772b7SMatt Fleming module_init(efivarfs_init);
334af5a29aeSMathias Krause module_exit(efivarfs_exit);
335