tools: env: Add support for direct read/write UBI volumes
authorS. Lockwood-Childs <sjl@vctlabs.com>
Wed, 15 Nov 2017 07:01:26 +0000 (23:01 -0800)
committerTom Rini <trini@konsulko.com>
Thu, 30 Nov 2017 03:30:50 +0000 (22:30 -0500)
Up to now we were able to read/write environment data from/to UBI
volumes only indirectly by gluebi driver. This driver creates NAND MTD
on top of UBI volumes, which is quite a workaroung for this use case.

Add support for direct read/write UBI volumes in order to not use
obsolete gluebi driver.

Forward-ported from this patch:
http://patchwork.ozlabs.org/patch/619305/

Original patch:
Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
Forward port:
Signed-off-by: S. Lockwood-Childs <sjl@vctlabs.com>
tools/env/fw_env.c
tools/env/fw_env.config

index 963a6152a50193d0b2fcb51ce7769c2fa3817d6f..18c2324d2fca4d5ea6b61229c3bb09cc06b406ae 100644 (file)
@@ -25,6 +25,7 @@
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <dirent.h>
 
 #ifdef MTD_OLD
 # include <stdint.h>
@@ -34,6 +35,8 @@
 # include <mtd/mtd-user.h>
 #endif
 
+#include <mtd/ubi-user.h>
+
 #include "fw_env_private.h"
 #include "fw_env.h"
 
@@ -58,6 +61,7 @@ struct envdev_s {
        ulong erase_size;               /* device erase size */
        ulong env_sectors;              /* number of environment sectors */
        uint8_t mtd_type;               /* type of the MTD device */
+       int is_ubi;                     /* set if we use UBI volume */
 };
 
 static struct envdev_s envdevices[2] =
@@ -76,6 +80,7 @@ static int dev_current;
 #define DEVESIZE(i)   envdevices[(i)].erase_size
 #define ENVSECTORS(i) envdevices[(i)].env_sectors
 #define DEVTYPE(i)    envdevices[(i)].mtd_type
+#define IS_UBI(i)     envdevices[(i)].is_ubi
 
 #define CUR_ENVSIZE ENVSIZE(dev_current)
 
@@ -120,6 +125,228 @@ static unsigned char obsolete_flag = 0;
 #define DEFAULT_ENV_INSTANCE_STATIC
 #include <env_default.h>
 
+#define UBI_DEV_START "/dev/ubi"
+#define UBI_SYSFS "/sys/class/ubi"
+#define UBI_VOL_NAME_PATT "ubi%d_%d"
+
+static int is_ubi_devname(const char *devname)
+{
+       return !strncmp(devname, UBI_DEV_START, sizeof(UBI_DEV_START) - 1);
+}
+
+static int ubi_check_volume_sysfs_name(const char *volume_sysfs_name,
+                                      const char *volname)
+{
+       char path[256];
+       FILE *file;
+       char *name;
+       int ret;
+
+       strcpy(path, UBI_SYSFS "/");
+       strcat(path, volume_sysfs_name);
+       strcat(path, "/name");
+
+       file = fopen(path, "r");
+       if (!file)
+               return -1;
+
+       ret = fscanf(file, "%ms", &name);
+       fclose(file);
+       if (ret <= 0 || !name) {
+               fprintf(stderr,
+                       "Failed to read from file %s, ret = %d, name = %s\n",
+                       path, ret, name);
+               return -1;
+       }
+
+       if (!strcmp(name, volname)) {
+               free(name);
+               return 0;
+       }
+       free(name);
+
+       return -1;
+}
+
+static int ubi_get_volnum_by_name(int devnum, const char *volname)
+{
+       DIR *sysfs_ubi;
+       struct dirent *dirent;
+       int ret;
+       int tmp_devnum;
+       int volnum;
+
+       sysfs_ubi = opendir(UBI_SYSFS);
+       if (!sysfs_ubi)
+               return -1;
+
+#ifdef DEBUG
+       fprintf(stderr, "Looking for volume name \"%s\"\n", volname);
+#endif
+
+       while (1) {
+               dirent = readdir(sysfs_ubi);
+               if (!dirent)
+                       return -1;
+
+               ret = sscanf(dirent->d_name, UBI_VOL_NAME_PATT,
+                            &tmp_devnum, &volnum);
+               if (ret == 2 && devnum == tmp_devnum) {
+                       if (ubi_check_volume_sysfs_name(dirent->d_name,
+                                                       volname) == 0)
+                               return volnum;
+               }
+       }
+
+       return -1;
+}
+
+static int ubi_get_devnum_by_devname(const char *devname)
+{
+       int devnum;
+       int ret;
+
+       ret = sscanf(devname + sizeof(UBI_DEV_START) - 1, "%d", &devnum);
+       if (ret != 1)
+               return -1;
+
+       return devnum;
+}
+
+static const char *ubi_get_volume_devname(const char *devname,
+                                         const char *volname)
+{
+       char *volume_devname;
+       int volnum;
+       int devnum;
+       int ret;
+
+       devnum = ubi_get_devnum_by_devname(devname);
+       if (devnum < 0)
+               return NULL;
+
+       volnum = ubi_get_volnum_by_name(devnum, volname);
+       if (volnum < 0)
+               return NULL;
+
+       ret = asprintf(&volume_devname, "%s_%d", devname, volnum);
+       if (ret < 0)
+               return NULL;
+
+#ifdef DEBUG
+       fprintf(stderr, "Found ubi volume \"%s:%s\" -> %s\n",
+               devname, volname, volume_devname);
+#endif
+
+       return volume_devname;
+}
+
+static void ubi_check_dev(unsigned int dev_id)
+{
+       char *devname = (char *)DEVNAME(dev_id);
+       char *pname;
+       const char *volname = NULL;
+       const char *volume_devname;
+
+       if (!is_ubi_devname(DEVNAME(dev_id)))
+               return;
+
+       IS_UBI(dev_id) = 1;
+
+       for (pname = devname; *pname != '\0'; pname++) {
+               if (*pname == ':') {
+                       *pname = '\0';
+                       volname = pname + 1;
+                       break;
+               }
+       }
+
+       if (volname) {
+               /* Let's find real volume device name */
+               volume_devname = ubi_get_volume_devname(devname, volname);
+               if (!volume_devname) {
+                       fprintf(stderr, "Didn't found ubi volume \"%s\"\n",
+                               volname);
+                       return;
+               }
+
+               free(devname);
+               DEVNAME(dev_id) = volume_devname;
+       }
+}
+
+static int ubi_update_start(int fd, int64_t bytes)
+{
+       if (ioctl(fd, UBI_IOCVOLUP, &bytes))
+               return -1;
+       return 0;
+}
+
+static int ubi_read(int fd, void *buf, size_t count)
+{
+       ssize_t ret;
+
+       while (count > 0) {
+               ret = read(fd, buf, count);
+               if (ret > 0) {
+                       count -= ret;
+                       buf += ret;
+
+                       continue;
+               }
+
+               if (ret == 0) {
+                       /*
+                        * Happens in case of too short volume data size. If we
+                        * return error status we will fail it will be treated
+                        * as UBI device error.
+                        *
+                        * Leave catching this error to CRC check.
+                        */
+                       fprintf(stderr, "Warning: end of data on ubi volume\n");
+                       return 0;
+               } else if (errno == EBADF) {
+                       /*
+                        * Happens in case of corrupted volume. The same as
+                        * above, we cannot return error now, as we will still
+                        * be able to successfully write environment later.
+                        */
+                       fprintf(stderr, "Warning: corrupted volume?\n");
+                       return 0;
+               } else if (errno == EINTR) {
+                       continue;
+               }
+
+               fprintf(stderr, "Cannot read %u bytes from ubi volume, %s\n",
+                       (unsigned int)count, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+static int ubi_write(int fd, const void *buf, size_t count)
+{
+       ssize_t ret;
+
+       while (count > 0) {
+               ret = write(fd, buf, count);
+               if (ret <= 0) {
+                       if (ret < 0 && errno == EINTR)
+                               continue;
+
+                       fprintf(stderr, "Cannot write %u bytes to ubi volume\n",
+                               (unsigned int)count);
+                       return -1;
+               }
+
+               count -= ret;
+               buf += ret;
+       }
+
+       return 0;
+}
+
 static int flash_io (int mode);
 static int parse_config(struct env_opts *opts);
 
@@ -960,6 +1187,12 @@ static int flash_write (int fd_current, int fd_target, int dev_target)
                DEVOFFSET (dev_target), DEVNAME (dev_target));
 #endif
 
+       if (IS_UBI(dev_target)) {
+               if (ubi_update_start(fd_target, CUR_ENVSIZE) < 0)
+                       return 0;
+               return ubi_write(fd_target, environment.image, CUR_ENVSIZE);
+       }
+
        rc = flash_write_buf(dev_target, fd_target, environment.image,
                             CUR_ENVSIZE);
        if (rc < 0)
@@ -984,6 +1217,12 @@ static int flash_read (int fd)
 {
        int rc;
 
+       if (IS_UBI(dev_current)) {
+               DEVTYPE(dev_current) = MTD_ABSENT;
+
+               return ubi_read(fd, environment.image, CUR_ENVSIZE);
+       }
+
        rc = flash_read_buf(dev_current, fd, environment.image, CUR_ENVSIZE,
                            DEVOFFSET(dev_current));
        if (rc != CUR_ENVSIZE)
@@ -1165,7 +1404,8 @@ int fw_env_open(struct env_opts *opts)
                           DEVTYPE(!dev_current) == MTD_UBIVOLUME) {
                        environment.flag_scheme = FLAG_INCREMENTAL;
                } else if (DEVTYPE(dev_current) == MTD_ABSENT &&
-                          DEVTYPE(!dev_current) == MTD_ABSENT) {
+                          DEVTYPE(!dev_current) == MTD_ABSENT &&
+                          IS_UBI(dev_current) == IS_UBI(!dev_current)) {
                        environment.flag_scheme = FLAG_INCREMENTAL;
                } else {
                        fprintf (stderr, "Incompatible flash types!\n");
@@ -1271,8 +1511,12 @@ int fw_env_close(struct env_opts *opts)
 static int check_device_config(int dev)
 {
        struct stat st;
+       int32_t lnum = 0;
        int fd, rc = 0;
 
+       /* Fills in IS_UBI(), converts DEVNAME() with ubi volume name */
+       ubi_check_dev(dev);
+
        fd = open(DEVNAME(dev), O_RDONLY);
        if (fd < 0) {
                fprintf(stderr,
@@ -1288,7 +1532,14 @@ static int check_device_config(int dev)
                goto err;
        }
 
-       if (S_ISCHR(st.st_mode)) {
+       if (IS_UBI(dev)) {
+               rc = ioctl(fd, UBI_IOCEBISMAP, &lnum);
+               if (rc < 0) {
+                       fprintf(stderr, "Cannot get UBI information for %s\n",
+                               DEVNAME(dev));
+                       goto err;
+               }
+       } else if (S_ISCHR(st.st_mode)) {
                struct mtd_info_user mtdinfo;
                rc = ioctl(fd, MEMGETINFO, &mtdinfo);
                if (rc < 0) {
index 7916ebdb1f6d1836c49d9374cf4ebfac2bcf0009..053895a2c07b23891e098c2370066204f04b8ea1 100644 (file)
 
 # VFAT example
 #/boot/uboot.env       0x0000          0x4000
+
+# UBI volume
+#/dev/ubi0_0           0x0             0x1f000         0x1f000
+#/dev/ubi0_1           0x0             0x1f000         0x1f000
+
+# UBI volume by name
+#/dev/ubi0:env         0x0             0x1f000         0x1f000
+#/dev/ubi0:env-redund  0x0             0x1f000         0x1f000