summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Smirnov <andrew.smirnov@gmail.com>2019-06-03 22:20:01 -0700
committerSascha Hauer <s.hauer@pengutronix.de>2019-06-07 09:04:44 +0200
commit8daaa21b3949e5e327586a49a63dfcacd5600aba (patch)
treefa9d459acc1e363c2f2708ff655904d2b37b06f1
parent9fdbd39f320a4dca62c2537dd3b62ed4030b3c38 (diff)
downloadbarebox-8daaa21b3949e5e327586a49a63dfcacd5600aba.tar.gz
barebox-8daaa21b3949e5e327586a49a63dfcacd5600aba.tar.xz
fs: Add a driver to access U-Boot environment variables
Add a driver working on top of ubootvar device and exposing U-Boot environment variable data as files. Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> Signed-off-by: Cory Tusar <cory.tusar@zii.aero> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--Documentation/filesystems/ubootvarfs.rst28
-rw-r--r--fs/Kconfig8
-rw-r--r--fs/Makefile1
-rw-r--r--fs/ubootvarfs.c499
4 files changed, 536 insertions, 0 deletions
diff --git a/Documentation/filesystems/ubootvarfs.rst b/Documentation/filesystems/ubootvarfs.rst
new file mode 100644
index 0000000000..0433b12944
--- /dev/null
+++ b/Documentation/filesystems/ubootvarfs.rst
@@ -0,0 +1,28 @@
+.. index:: ubootvarfs (filesystem)
+
+.. _filesystems_ubootvarfs:
+
+U-Boot environment filesystem
+=============================
+
+barebox supports accessing U-Boot environment contents as a regular
+filesystems in both read and write modes. U-Boot environment data
+(ubootvar) device supports automount, so no explicit mount command
+should be necessary and accessing the environment should be as easy
+as:
+
+.. code-block:: console
+
+ barebox:/ ls -l /mnt/ubootvar0
+
+However the filesystem can be explicitly mounted with the following
+command:
+
+.. code-block:: console
+
+ barebox:/ mount -t ubootvarfs /dev/device /mnt/path
+
+**NOTE** Current implementation of the filesystem driver uses lazy
+synchronization, any changes made to the environment will not be
+written to the medium until the filesystem is unmounted (will happen
+automatically on Barebox shutdown)
diff --git a/fs/Kconfig b/fs/Kconfig
index e3a95321c7..adf281a5b8 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -118,4 +118,12 @@ config FS_RATP
This enables support for transferring files over RATP. A host can
export a directory which can then be mounted under barebox.
+config FS_UBOOTVARFS
+ bool
+ depends on UBOOTVAR
+ prompt "U-Boot environment variable filesystem support"
+ help
+ This filesystem driver provides access to U-Boot environment
+ variables.
+
endmenu
diff --git a/fs/Makefile b/fs/Makefile
index ac3e6a03aa..9889a6507c 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_FS_SMHFS) += smhfs.o
obj-$(CONFIG_FS_PSTORE) += pstore/
obj-$(CONFIG_FS_SQUASHFS) += squashfs/
obj-$(CONFIG_FS_RATP) += ratpfs.o
+obj-$(CONFIG_FS_UBOOTVARFS) += ubootvarfs.o
diff --git a/fs/ubootvarfs.c b/fs/ubootvarfs.c
new file mode 100644
index 0000000000..81ec05d5ef
--- /dev/null
+++ b/fs/ubootvarfs.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Zodiac Inflight Innovations
+ */
+
+#define pr_fmt(fmt) "ubootvarfs: " fmt
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <fs.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/stat.h>
+#include <xfuncs.h>
+#include <fcntl.h>
+#include <efi.h>
+#include <wchar.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+
+/**
+ * Some theory of operation:
+ *
+ * U-Boot environment variable data is expected to be presented as a
+ * single blob containing an arbitrary number "<key>=<value>\0" pairs
+ * without any other auxiliary information (accomplished by ubootvar
+ * driver)
+ *
+ * Filesystem driver code in this file parses above data an creates a
+ * linked list of all of the "variables" found (see @ubootvarfs_var to
+ * what information is recorded).
+ *
+ * With that in place reading or writing file data becomes as trivial
+ * as looking up a variable in the linked list by name and then
+ * memcpy()-ing bytes from its value region.
+ *
+ * The only moderately tricky part is re-sizing a given file/variable
+ * since, given the underlying data format, it requires us to move all
+ * of the key/value data that comes after the given file/variable as
+ * well as to adjust all of the cached offsets stored in variable
+ * linked list. See ubootvarfs_adjust() for the implementation
+ * details.
+ */
+
+/**
+ * struct ubootvarfs_var - U-Boot environment key-value pair
+ *
+ * @list: Linked list head
+ * @name: Pointer to memory containing key string (variable name)
+ * @name_len: Variable name's (above) length
+ * @start: Start of value in memory
+ * @end: End of value in memory
+ */
+struct ubootvarfs_var {
+ struct list_head list;
+ char *name;
+ size_t name_len;
+ char *start;
+ char *end;
+};
+
+/**
+ * struct ubootvarfs_data - U-Boot environment data
+ *
+ * @var_list: Linked list of all of the parsed variables
+ * @fd: File descriptor of underlying ubootvar device
+ * @end: End of U-boot environment
+ * @limit: U-boot environment limit (can't grow to go past the limit)
+ */
+struct ubootvarfs_data {
+ struct list_head var_list;
+ int fd;
+ char *end;
+ const char *limit;
+};
+
+struct ubootvarfs_inode {
+ struct inode inode;
+ struct ubootvarfs_var *var;
+ struct ubootvarfs_data *data;
+};
+
+static struct ubootvarfs_inode *inode_to_node(struct inode *inode)
+{
+ return container_of(inode, struct ubootvarfs_inode, inode);
+}
+
+static const struct inode_operations ubootvarfs_file_inode_operations;
+static const struct file_operations ubootvarfs_dir_operations;
+static const struct inode_operations ubootvarfs_dir_inode_operations;
+static const struct file_operations ubootvarfs_file_operations;
+
+static struct inode *ubootvarfs_get_inode(struct super_block *sb,
+ const struct inode *dir,
+ umode_t mode,
+ struct ubootvarfs_var *var)
+{
+ struct inode *inode = new_inode(sb);
+ struct ubootvarfs_inode *node;
+
+ if (!inode)
+ return NULL;
+
+ inode->i_ino = get_next_ino();
+ inode->i_mode = mode;
+ if (var)
+ inode->i_size = var->end - var->start;
+
+ node = inode_to_node(inode);
+ node->var = var;
+
+ switch (mode & S_IFMT) {
+ default:
+ return NULL;
+ case S_IFREG:
+ inode->i_op = &ubootvarfs_file_inode_operations;
+ inode->i_fop = &ubootvarfs_file_operations;
+ break;
+ case S_IFDIR:
+ inode->i_op = &ubootvarfs_dir_inode_operations;
+ inode->i_fop = &ubootvarfs_dir_operations;
+ inc_nlink(inode);
+ break;
+ }
+
+ return inode;
+}
+
+static struct ubootvarfs_var *
+ubootvarfs_var_by_name(struct ubootvarfs_data *data, const char *name)
+{
+ struct ubootvarfs_var *var;
+ const size_t len = strlen(name);
+
+ list_for_each_entry(var, &data->var_list, list) {
+ if (len == var->name_len &&
+ !memcmp(name, var->name, var->name_len))
+ return var;
+ }
+
+ return NULL;
+}
+
+static struct dentry *ubootvarfs_lookup(struct inode *dir,
+ struct dentry *dentry,
+ unsigned int flags)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct ubootvarfs_data *data = fsdev->dev.priv;
+ struct ubootvarfs_var *var;
+ struct inode *inode;
+
+ var = ubootvarfs_var_by_name(data, dentry->name);
+ if (!var)
+ return NULL;
+
+ inode = ubootvarfs_get_inode(dir->i_sb, dir, S_IFREG | 0777, var);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ d_add(dentry, inode);
+
+ return NULL;
+}
+
+static int ubootvarfs_iterate(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct inode *inode = d_inode(dentry);
+ struct ubootvarfs_inode *node = inode_to_node(inode);
+ struct ubootvarfs_data *data = node->data;
+ struct ubootvarfs_var *var;
+
+ dir_emit_dots(file, ctx);
+
+ list_for_each_entry(var, &data->var_list, list)
+ dir_emit(ctx, var->name, var->name_len, 0, DT_REG);
+
+ return 0;
+}
+
+static const struct file_operations ubootvarfs_dir_operations = {
+ .iterate = ubootvarfs_iterate,
+};
+
+/**
+ * ubootvarfs_relocate_tail() - Move all of the data after given inode by delta
+ *
+ * @node: Inode marking the start of the data
+ * @delta: Offset to move the data by
+ *
+ * This function move all of the environment data that starts after
+ * the given @node by @delta bytes. In case the data is moved towards
+ * the start of the environment data blob trailing leftover data is
+ * zeroed out
+ */
+static void ubootvarfs_relocate_tail(struct ubootvarfs_inode *node,
+ int delta)
+{
+ struct ubootvarfs_var *var = node->var;
+ struct ubootvarfs_data *data = node->data;
+ const size_t n = data->end - var->start;
+ void *src = var->end + 1;
+
+ memmove(src + delta, src, n);
+
+ data->end += delta;
+
+ if (delta < 0) {
+ /*
+ * Remove all of the trailing leftovers
+ */
+ memset(data->end, '\0', -delta);
+ }
+}
+
+/**
+ * ubootvarfs_adjust() - Adjust the size of a variable blob
+ *
+ * @node: Inode marking where to start adjustement from
+ * @delta: Offset to adjust by
+ *
+ * This function move all of the environment data that starts after
+ * the given @node by @delta bytes and updates all of the affected
+ * ubootvarfs_var's in varaible linked list
+ */
+static void ubootvarfs_adjust(struct ubootvarfs_inode *node,
+ int delta)
+{
+ struct ubootvarfs_var *var = node->var;
+ struct ubootvarfs_data *data = node->data;
+
+ ubootvarfs_relocate_tail(node, delta);
+
+ list_for_each_entry_continue(var, &data->var_list, list) {
+ var->name += delta;
+ var->start += delta;
+ var->end += delta;
+ }
+}
+
+static int ubootvarfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *inode = d_inode(dentry);
+
+ if (inode) {
+ struct ubootvarfs_inode *node = inode_to_node(inode);
+ struct ubootvarfs_var *var = node->var;
+ /*
+ * -1 at the end is to account for '\0' at the end
+ * that needs to be removed as well
+ */
+ const int delta = var->name - var->end - 1;
+
+ ubootvarfs_adjust(node, delta);
+
+ list_del(&var->list);
+ free(var);
+ }
+
+ return simple_unlink(dir, dentry);
+}
+
+static int ubootvarfs_create(struct inode *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct ubootvarfs_data *data = fsdev->dev.priv;
+ struct inode *inode;
+ struct ubootvarfs_var *var;
+ size_t len = strlen(dentry->name);
+ /*
+ * We'll be adding <varname>=\0\0 to the end of our data, so
+ * we need to make sure there's enough room for it. Note that
+ * + 3 is to accoutn for '=', and two '\0' from above
+ */
+ if (data->end + len + 3 > data->limit)
+ return -ENOSPC;
+
+ var = xmalloc(sizeof(*var));
+
+ var->name = data->end;
+ memcpy(var->name, dentry->name, len);
+ var->name_len = len;
+ var->start = var->name + len;
+ *var->start++ = '=';
+ *var->start = '\0';
+ var->end = var->start;
+ data->end = var->end + 1;
+ *data->end = '\0';
+
+ list_add_tail(&var->list, &data->var_list);
+
+ inode = ubootvarfs_get_inode(sb, dir, mode, var);
+ d_instantiate(dentry, inode);
+
+ return 0;
+}
+
+static const struct inode_operations ubootvarfs_dir_inode_operations = {
+ .lookup = ubootvarfs_lookup,
+ .unlink = ubootvarfs_unlink,
+ .create = ubootvarfs_create,
+};
+
+static struct inode *ubootvarfs_alloc_inode(struct super_block *sb)
+{
+ struct ubootvarfs_inode *node;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct ubootvarfs_data *data = fsdev->dev.priv;
+
+ node = xzalloc(sizeof(*node));
+ node->data = data;
+
+ return &node->inode;
+}
+
+static void ubootvarfs_destroy_inode(struct inode *inode)
+{
+ struct ubootvarfs_inode *node = inode_to_node(inode);
+
+ free(node->var);
+ free(node);
+}
+
+static const struct super_operations ubootvarfs_ops = {
+ .alloc_inode = ubootvarfs_alloc_inode,
+ .destroy_inode = ubootvarfs_destroy_inode,
+};
+
+static int ubootvarfs_io(struct device_d *dev, FILE *f, void *buf,
+ size_t insize, bool read)
+{
+ struct inode *inode = f->f_inode;
+ struct ubootvarfs_inode *node = inode_to_node(inode);
+ void *ptr = node->var->start + f->pos;
+
+ if (read)
+ memcpy(buf, ptr, insize);
+ else
+ memcpy(ptr, buf, insize);
+
+ return insize;
+}
+
+static int ubootvarfs_read(struct device_d *dev, FILE *f, void *buf,
+ size_t insize)
+{
+ return ubootvarfs_io(dev, f, buf, insize, true);
+}
+
+static int ubootvarfs_write(struct device_d *dev, FILE *f, const void *buf,
+ size_t insize)
+{
+ return ubootvarfs_io(dev, f, (void *)buf, insize, false);
+}
+
+static int ubootvarfs_truncate(struct device_d *dev, FILE *f, loff_t size)
+{
+ struct inode *inode = f->f_inode;
+ struct ubootvarfs_inode *node = inode_to_node(inode);
+ struct ubootvarfs_data *data = node->data;
+ struct ubootvarfs_var *var = node->var;
+ const int delta = size - inode->i_size;
+
+ if (size == inode->i_size)
+ return 0;
+
+ if (data->end + delta >= data->limit)
+ return -ENOSPC;
+
+ ubootvarfs_adjust(node, delta);
+
+ if (delta > 0)
+ memset(var->end, '\0', delta);
+
+ var->end += delta;
+ *var->end = '\0';
+
+ return 0;
+}
+
+static void ubootvarfs_parse(struct ubootvarfs_data *data, char *blob,
+ size_t size)
+{
+ struct ubootvarfs_var *var;
+ const char *start = blob;
+ size_t len;
+ char *sep;
+
+ data->limit = blob + size;
+ INIT_LIST_HEAD(&data->var_list);
+
+ while (*blob) {
+ var = xmalloc(sizeof(*var));
+ len = strnlen(blob, size);
+
+ var->name = blob;
+ var->end = blob + len;
+
+ sep = strchr(blob, '=');
+ if (sep) {
+ var->start = sep + 1;
+ var->name_len = sep - blob;
+
+ list_add_tail(&var->list, &data->var_list);
+ } else {
+ pr_err("No separator in data @ 0x%08x. Skipped.",
+ blob - start);
+ free(var);
+ }
+
+ len++; /* account for '\0' */
+ size -= len;
+ blob += len;
+ };
+
+ data->end = blob;
+}
+
+static int ubootvarfs_probe(struct device_d *dev)
+{
+ struct inode *inode;
+ struct ubootvarfs_data *data = xzalloc(sizeof(*data));
+ struct fs_device_d *fsdev = dev_to_fs_device(dev);
+ struct super_block *sb = &fsdev->sb;
+ struct stat s;
+ void *map;
+ int ret;
+
+ dev->priv = data;
+
+ data->fd = open(fsdev->backingstore, O_RDWR);
+ if (data->fd < 0) {
+ ret = -errno;
+ goto free_data;
+ }
+
+ if (fstat(data->fd, &s) < 0) {
+ ret = -errno;
+ goto exit;
+ }
+
+ map = memmap(data->fd, PROT_READ | PROT_WRITE);
+ if (map == MAP_FAILED) {
+ ret = -errno;
+ goto exit;
+ }
+
+ ubootvarfs_parse(data, map, s.st_size);
+
+ sb->s_op = &ubootvarfs_ops;
+ inode = ubootvarfs_get_inode(sb, NULL, S_IFDIR, NULL);
+ sb->s_root = d_make_root(inode);
+
+ /*
+ * We don't use cdev * directly, but this is needed for
+ * cdev_get_mount_path() to work right
+ */
+ fsdev->cdev = cdev_by_name(devpath_to_name(fsdev->backingstore));
+
+ return 0;
+exit:
+ close(data->fd);
+free_data:
+ free(data);
+ return ret;
+}
+
+static void ubootvarfs_remove(struct device_d *dev)
+{
+ struct ubootvarfs_data *data = dev->priv;
+
+ flush(data->fd);
+ close(data->fd);
+ free(data);
+}
+
+static struct fs_driver_d ubootvarfs_driver = {
+ .truncate = ubootvarfs_truncate,
+ .read = ubootvarfs_read,
+ .write = ubootvarfs_write,
+ .type = filetype_ubootvar,
+ .drv = {
+ .probe = ubootvarfs_probe,
+ .remove = ubootvarfs_remove,
+ .name = "ubootvarfs",
+ }
+};
+
+static int ubootvarfs_init(void)
+{
+ return register_fs_driver(&ubootvarfs_driver);
+}
+coredevice_initcall(ubootvarfs_init);