diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2019-06-11 11:33:34 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2019-06-11 11:33:34 +0200 |
commit | ad06295b2983cc46cb6c4dcf2b482f37731f7a3e (patch) | |
tree | 7c8aec2fb29c565dadcc40ef5b6b9dfc2d922fc9 | |
parent | 06b8bc4d708b8ff4eba1dd9b48ec110a8019d2c8 (diff) | |
parent | 25d6c568a867821e419e4a6b5cdb41a0c98b3ad8 (diff) | |
download | barebox-ad06295b2983cc46cb6c4dcf2b482f37731f7a3e.tar.gz barebox-ad06295b2983cc46cb6c4dcf2b482f37731f7a3e.tar.xz |
Merge branch 'for-next/ubootenv'
-rw-r--r-- | Documentation/devicetree/bindings/barebox/barebox,uboot-environment.rst | 43 | ||||
-rw-r--r-- | Documentation/filesystems/ubootvarfs.rst | 28 | ||||
-rw-r--r-- | arch/arm/dts/imx51-zii-rdu1.dts | 21 | ||||
-rw-r--r-- | arch/arm/dts/imx6qdl-zii-rdu2.dtsi | 27 | ||||
-rw-r--r-- | common/filetype.c | 8 | ||||
-rw-r--r-- | drivers/misc/Kconfig | 12 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/ubootvar.c | 360 | ||||
-rw-r--r-- | fs/Kconfig | 8 | ||||
-rw-r--r-- | fs/Makefile | 1 | ||||
-rw-r--r-- | fs/ubootvarfs.c | 499 | ||||
-rw-r--r-- | include/driver.h | 4 | ||||
-rw-r--r-- | include/filetype.h | 1 |
13 files changed, 1013 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/barebox/barebox,uboot-environment.rst b/Documentation/devicetree/bindings/barebox/barebox,uboot-environment.rst new file mode 100644 index 0000000000..da0ccd2c25 --- /dev/null +++ b/Documentation/devicetree/bindings/barebox/barebox,uboot-environment.rst @@ -0,0 +1,43 @@ +U-Boot environment device +========================= + +This driver provides a unified device exposing U-Boot environment +varaible data, sans the low-level parts. Resulting device is intended +to be used with corresponding filesystem driver to expose environment +data as a filesystem. + +Required properties: + +* ``compatible``: should be ``barebox,uboot-environment`` +* ``device-path``: phandle of the partition the device environment is + on (single partiton configuration) +* ``device-path-0`` and ``device-path-1``: phandle of the partition + the environment is on (redundant configuration) + +Example: + +.. code-block:: none + + environment { + compatible = "barebox,uboot-environment"; + device-path-0 = &uboot_env_0; + device-path-1 = &uboot_env_1; + }; + + &usdhc4 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + uboot_env_0: partition@c0000 { + label = "uboot-environment-0"; + reg = <0xc0000 0x4000>; + }; + + uboot_env_1: partition@cc800 { + label = "uboot-environment-1"; + reg = <0xcc800 0x4000>; + }; + }; + }; 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/arch/arm/dts/imx51-zii-rdu1.dts b/arch/arm/dts/imx51-zii-rdu1.dts index a4f6654e3f..857c9ad96c 100644 --- a/arch/arm/dts/imx51-zii-rdu1.dts +++ b/arch/arm/dts/imx51-zii-rdu1.dts @@ -23,6 +23,11 @@ compatible = "barebox,environment"; device-path = &spinor, "partname:barebox-environment"; }; + + ubootenv { + compatible = "barebox,uboot-environment"; + device-path = &uboot_env; + }; }; reserved-memory { @@ -78,6 +83,22 @@ }; }; +&esdhc1 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* + * Layout info is taken from /etc/fw_env.config + */ + uboot_env: partition@c0000 { + label = "uboot-environment"; + reg = <0xc0000 0x20000>; + }; + }; +}; + &mdio_gpio { switch: switch@0 {}; }; diff --git a/arch/arm/dts/imx6qdl-zii-rdu2.dtsi b/arch/arm/dts/imx6qdl-zii-rdu2.dtsi index 5d141fcb34..c31a279048 100644 --- a/arch/arm/dts/imx6qdl-zii-rdu2.dtsi +++ b/arch/arm/dts/imx6qdl-zii-rdu2.dtsi @@ -47,6 +47,12 @@ compatible = "barebox,environment"; device-path = &nor_flash, "partname:barebox-environment"; }; + + ubootenv { + compatible = "barebox,uboot-environment"; + device-path-0 = &uboot_env_0; + device-path-1 = &uboot_env_1; + }; }; device-info { @@ -266,6 +272,27 @@ dr_mode = "otg"; }; +&usdhc4 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* + * Layout info is taken from /etc/fw_env.config + */ + uboot_env_0: partition@c0000 { + label = "uboot-environment-0"; + reg = <0xc0000 0x4000>; + }; + + uboot_env_1: partition@cc800 { + label = "uboot-environment-1"; + reg = <0xcc800 0x4000>; + }; + }; +}; + &gpio3 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio3_hog>; diff --git a/common/filetype.c b/common/filetype.c index 9675009eb0..329f5144bf 100644 --- a/common/filetype.c +++ b/common/filetype.c @@ -77,6 +77,8 @@ static const struct filetype_str filetype_str[] = { [filetype_imx_image_v2] = { "i.MX image (v2)", "imx-image-v2" }, [filetype_layerscape_image] = { "Layerscape image", "layerscape-PBL" }, [filetype_layerscape_qspi_image] = { "Layerscape QSPI image", "layerscape-qspi-PBL" }, + [filetype_ubootvar] = { "U-Boot environmemnt variable data", + "ubootvar" }, }; const char *file_type_to_string(enum filetype f) @@ -423,6 +425,11 @@ enum filetype cdev_detect_type(const char *name) if (!cdev) return type; + if (cdev->filetype != filetype_unknown) { + type = cdev->filetype; + goto cdev_close; + } + buf = xzalloc(FILE_TYPE_SAFE_BUFSIZE); ret = cdev_read(cdev, buf, FILE_TYPE_SAFE_BUFSIZE, 0, 0); if (ret < 0) @@ -432,6 +439,7 @@ enum filetype cdev_detect_type(const char *name) err_out: free(buf); +cdev_close: cdev_close(cdev); return type; } diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4c8a769c4c..0f736f8bde 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -23,4 +23,16 @@ config STATE_DRV config DEV_MEM bool "Generic memory I/O device (/dev/mem)" +config UBOOTVAR + bool "U-Boot environment storage" + help + This driver exposes U-Boot environment variable storage as a + single mmap-able device, hiding various low-level details + such as: + - Preamble format differences + - Read/write logic in presence of redundant partition + + While it can be used standalone, it is best when coupled + with corresponding filesystem driver. + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d4e616d51a..bc1c01ea4d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_JTAG) += jtag.o obj-$(CONFIG_SRAM) += sram.o obj-$(CONFIG_STATE_DRV) += state.o obj-$(CONFIG_DEV_MEM) += mem.o +obj-$(CONFIG_UBOOTVAR) += ubootvar.o diff --git a/drivers/misc/ubootvar.c b/drivers/misc/ubootvar.c new file mode 100644 index 0000000000..27f2515e7d --- /dev/null +++ b/drivers/misc/ubootvar.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * U-Boot environment vriable blob driver + * + * Copyright (C) 2019 Zodiac Inflight Innovations + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <of.h> +#include <malloc.h> +#include <partition.h> +#include <envfs.h> +#include <fs.h> +#include <libfile.h> +#include <command.h> +#include <crc.h> + +enum ubootvar_flag_scheme { + FLAG_NONE, + FLAG_BOOLEAN, + FLAG_INCREMENTAL, +}; + +struct ubootvar_data { + struct cdev cdev; + char *path[2]; + bool current; + uint8_t flag; + int count; + void *data; + size_t size; +}; + +static int ubootvar_flush(struct cdev *cdev) +{ + struct device_d *dev = cdev->dev; + struct ubootvar_data *ubdata = dev->priv; + const char *path = ubdata->path[!ubdata->current]; + uint32_t crc = 0xffffffff; + resource_size_t size; + const void *data; + int fd, ret = 0; + + fd = open(path, O_WRONLY); + if (fd < 0) { + dev_err(dev, "Failed to open %s\n", path); + return -errno; + } + /* + * FIXME: This code needs to do a proper protect/unprotect and + * erase calls to work on MTD devices + */ + + /* + * Write a dummy CRC first as a way of invalidating the + * environment in case we fail mid-flushing + */ + if (write_full(fd, &crc, sizeof(crc)) != sizeof(crc)) { + dev_err(dev, "Failed to write dummy CRC\n"); + ret = -errno; + goto close_fd; + } + + if (ubdata->count > 1) { + /* + * FIXME: This assumes FLAG_INCREMENTAL + */ + const uint8_t flag = ++ubdata->flag; + + if (write_full(fd, &flag, sizeof(flag)) != sizeof(flag)) { + dev_dbg(dev, "Failed to write flag\n"); + ret = -errno; + goto close_fd; + } + } + + data = (const void *)ubdata->data; + size = ubdata->size; + + /* + * Write out and flush all of the new environment data + */ + if (write_full(fd, data, size) != size) { + dev_dbg(dev, "Failed to write data\n"); + ret = -errno; + goto close_fd; + } + + if (flush(fd)) { + dev_dbg(dev, "Failed to flush written data\n"); + ret = -errno; + goto close_fd; + } + /* + * Now that all of the environment data is out, we can go back + * to the start of the block and write correct CRC, to finish + * the processs. + */ + if (lseek(fd, 0, SEEK_SET) != 0) { + dev_dbg(dev, "lseek() failed\n"); + ret = -errno; + goto close_fd; + } + + crc = crc32(0, data, size); + if (write_full(fd, &crc, sizeof(crc)) != sizeof(crc)) { + dev_dbg(dev, "Failed to write valid CRC\n"); + ret = -errno; + goto close_fd; + } + /* + * Now that we've successfully written new environment blob + * out, switch current partition. + */ + ubdata->current = !ubdata->current; + +close_fd: + close(fd); + return ret; +} + +static ssize_t +ubootvar_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, + unsigned long flags) +{ + struct device_d *dev = cdev->dev; + struct ubootvar_data *ubdata = dev->priv; + + WARN_ON(flags & O_RWSIZE_MASK); + + memcpy(buf, ubdata->data + offset, count); + + return count; +} + +static ssize_t +ubootvar_write(struct cdev *cdev, const void *buf, size_t count, + loff_t offset, unsigned long flags) +{ + struct device_d *dev = cdev->dev; + struct ubootvar_data *ubdata = dev->priv; + + WARN_ON(flags & O_RWSIZE_MASK); + + memcpy(ubdata->data + offset, buf, count); + + return count; +} + +static int ubootvar_memmap(struct cdev *cdev, void **map, int flags) +{ + struct device_d *dev = cdev->dev; + struct ubootvar_data *ubdata = dev->priv; + + *map = ubdata->data; + + return 0; +} + +static struct cdev_operations ubootvar_ops = { + .read = ubootvar_read, + .write = ubootvar_write, + .memmap = ubootvar_memmap, + .flush = ubootvar_flush, +}; + +static void ubootenv_info(struct device_d *dev) +{ + struct ubootvar_data *ubdata = dev->priv; + + printf("Current environment copy: %s\n", + ubdata->path[ubdata->current]); +} + +static int ubootenv_probe(struct device_d *dev) +{ + struct ubootvar_data *ubdata; + unsigned int crc_ok = 0; + int ret, i, current, count = 0; + uint32_t crc[2]; + uint8_t flag[2]; + size_t size[2]; + void *blob[2] = { NULL, NULL }; + uint8_t *data[2]; + + /* + * FIXME: Flag scheme is determined by the type of underlined + * non-volatible device, so it should probably come from + * Device Tree binding. Currently we just assume incremental + * scheme since that is what is used on SD/eMMC devices. + */ + enum ubootvar_flag_scheme flag_scheme = FLAG_INCREMENTAL; + + ubdata = xzalloc(sizeof(*ubdata)); + + ret = of_find_path(dev->device_node, "device-path-0", + &ubdata->path[0], + OF_FIND_PATH_FLAGS_BB); + if (ret) + ret = of_find_path(dev->device_node, "device-path", + &ubdata->path[0], + OF_FIND_PATH_FLAGS_BB); + + if (ret) { + dev_err(dev, "Failed to find first device\n"); + goto out; + } + + count++; + + if (!of_find_path(dev->device_node, "device-path-1", + &ubdata->path[1], + OF_FIND_PATH_FLAGS_BB)) { + count++; + } else { + /* + * If there's no redundant environment partition we + * configure both paths to point to the same device, + * so that writing logic could stay the same for both + * redundant and non-redundant cases + */ + ubdata->path[1] = strdup(ubdata->path[0]); + } + + for (i = 0; i < count; i++) { + data[i] = blob[i] = read_file(ubdata->path[i], &size[i]); + if (!blob[i]) { + dev_err(dev, "Failed to read U-Boot environment\n"); + ret = -EIO; + goto out; + } + + crc[i] = *(uint32_t *)data[i]; + + size[i] -= sizeof(uint32_t); + data[i] += sizeof(uint32_t); + + if (count > 1) { + /* + * When used in primary/redundant + * configuration, environment header has an + * additional "flag" byte + */ + flag[i] = *data[i]; + size[i] -= sizeof(uint8_t); + data[i] += sizeof(uint8_t); + } + + crc_ok |= (crc32(0, data[i], size[i]) == crc[i]) << i; + } + + switch (crc_ok) { + case 0b00: + current = 0; + memset(data[0], 0, size[0]); + dev_info(dev, "No good partitions found, creating an empty one\n"); + break; + case 0b11: + /* + * Both partition are valid, so we need to examine + * flags to determine which one to use as current + */ + switch (flag_scheme) { + case FLAG_INCREMENTAL: + if ((flag[0] == 0xff && flag[1] == 0) || + (flag[1] == 0xff && flag[0] == 0)) { + /* + * When flag overflow happens current + * partition is the one whose counter + * reached zero first. That is if + * flag[1] == 0 is true (1), then i + * would be 1 as well + */ + current = flag[1] == 0; + } else { + /* + * In no-overflow case the partition + * with higher flag value is + * considered current + */ + current = flag[1] > flag[0]; + } + break; + default: + ret = -EINVAL; + dev_err(dev, "Unknown flag scheme %u\n", flag_scheme); + goto out; + } + break; + default: + /* + * Only one partition is valid, so the choice of the + * current one is obvious + */ + current = __ffs(crc_ok); + break; + }; + + ubdata->data = data[current]; + ubdata->size = size[current]; + + ubdata->cdev.name = basprintf("ubootvar%d", + cdev_find_free_index("ubootvar")); + ubdata->cdev.size = size[current]; + ubdata->cdev.ops = &ubootvar_ops; + ubdata->cdev.dev = dev; + ubdata->cdev.filetype = filetype_ubootvar; + ubdata->current = current; + ubdata->count = count; + ubdata->flag = flag[current]; + + dev->priv = ubdata; + + ret = devfs_create(&ubdata->cdev); + if (ret) { + dev_err(dev, "Failed to create corresponding cdev\n"); + goto out; + } + + cdev_create_default_automount(&ubdata->cdev); + + if (count > 1) { + /* + * We won't be using read data from redundant + * parttion, so we may as well free at this point + */ + free(blob[!current]); + } + + dev->info = ubootenv_info; + + return 0; +out: + for (i = 0; i < count; i++) + free(blob[i]); + + free(ubdata->path[0]); + free(ubdata->path[1]); + free(ubdata); + + return ret; +} + +static struct of_device_id ubootenv_dt_ids[] = { + { + .compatible = "barebox,uboot-environment", + }, { + /* sentinel */ + } +}; + +static struct driver_d ubootenv_driver = { + .name = "uboot-environment", + .probe = ubootenv_probe, + .of_compatible = ubootenv_dt_ids, +}; +late_platform_driver(ubootenv_driver); 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); diff --git a/include/driver.h b/include/driver.h index 26ec413bd6..300603fa32 100644 --- a/include/driver.h +++ b/include/driver.h @@ -22,6 +22,7 @@ #include <linux/list.h> #include <linux/ioport.h> #include <of.h> +#include <filetype.h> #define FORMAT_DRIVER_NAME_ID "%s%d" @@ -419,6 +420,8 @@ int platform_driver_register(struct driver_d *drv); register_driver_macro(device,platform,drv) #define console_platform_driver(drv) \ register_driver_macro(console,platform,drv) +#define late_platform_driver(drv) \ + register_driver_macro(late,platform,drv) int platform_device_register(struct device_d *new_device); @@ -464,6 +467,7 @@ struct cdev { struct list_head link_entry, links; struct list_head partition_entry, partitions; struct cdev *master; + enum filetype filetype; }; int devfs_create(struct cdev *); diff --git a/include/filetype.h b/include/filetype.h index dcb331a6c9..f1be04e816 100644 --- a/include/filetype.h +++ b/include/filetype.h @@ -47,6 +47,7 @@ enum filetype { filetype_imx_image_v2, filetype_layerscape_image, filetype_layerscape_qspi_image, + filetype_ubootvar, filetype_max, }; |