// SPDX-License-Identifier: GPL-2.0+ /* * U-Boot environment vriable blob driver * * Copyright (C) 2019 Zodiac Inflight Innovations */ #include #include #include #include #include #include #include #include #include #include #include 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);