diff options
Diffstat (limited to 'drivers/mtd/ubi/cdev.c')
-rw-r--r-- | drivers/mtd/ubi/cdev.c | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c new file mode 100644 index 0000000000..30b8308dcc --- /dev/null +++ b/drivers/mtd/ubi/cdev.c @@ -0,0 +1,238 @@ +#include <common.h> +#include <fcntl.h> +#include <fs.h> +#include <ioctl.h> +#include "ubi-barebox.h" +#include "ubi.h" + +struct ubi_volume_cdev_priv { + struct ubi_device *ubi; + struct ubi_volume *vol; + int updating; + unsigned long mode; +}; + +static ssize_t ubi_volume_cdev_read(struct cdev *cdev, void *buf, size_t size, + unsigned long offset, unsigned long flags) +{ + struct ubi_volume_cdev_priv *priv = cdev->priv; + struct ubi_volume *vol = priv->vol; + struct ubi_device *ubi = priv->ubi; + int err, lnum, off, len; + size_t count_save = size; + unsigned long long tmp; + loff_t offp = offset; + int usable_leb_size = vol->usable_leb_size; + + printf("%s: %d @ 0x%08x\n", __func__, size, offset); + + len = size > usable_leb_size ? usable_leb_size : size; + + tmp = offp; + off = do_div(tmp, usable_leb_size); + lnum = tmp; + do { + if (off + len >= usable_leb_size) + len = usable_leb_size - off; + + err = ubi_eba_read_leb(ubi, vol, lnum, buf, off, len, 0); + if (err) { + printf("read err %x\n", err); + break; + } + off += len; + if (off == usable_leb_size) { + lnum += 1; + off -= usable_leb_size; + } + + size -= len; + offp += len; + + buf += len; + len = size > usable_leb_size ? usable_leb_size : size; + } while (size); + + return count_save; +} + +static ssize_t ubi_volume_cdev_write(struct cdev* cdev, const void *buf, + size_t size, unsigned long offset, unsigned long flags) +{ + struct ubi_volume_cdev_priv *priv = cdev->priv; + struct ubi_volume *vol = priv->vol; + struct ubi_device *ubi = priv->ubi; + int err; + + if (!priv->updating) { + err = ubi_start_update(ubi, vol, 16*1024*1024); + if (err < 0) { + printf("Cannot start volume update\n"); + return err; + } + priv->updating = 1; + } + + err = ubi_more_update_data(ubi, vol, buf, size); + if (err < 0) { + printf("Couldnt or partially wrote data \n"); + return err; + } + + return err; +} + +static int ubi_volume_cdev_open(struct cdev *cdev, struct filep *f) +{ + struct ubi_volume_cdev_priv *priv = cdev->priv; + + /* only allow read or write, but not both */ + if ((f->flags & O_ACCMODE) == O_RDWR) + return -EINVAL; + + priv->updating = 0; + priv->mode = f->flags & O_ACCMODE; + + return 0; +} + +static int ubi_volume_cdev_close(struct cdev *cdev, struct filep *f) +{ + struct ubi_volume_cdev_priv *priv = cdev->priv; + struct ubi_volume *vol = priv->vol; + struct ubi_device *ubi = priv->ubi; + int err; + + if (priv->updating) { + err = ubi_finish_update(ubi, vol); + if (err) + return err; + + err = ubi_check_volume(ubi, vol->vol_id); + if (err < 0) { + printf("check failed: %s\n", strerror(err)); + return err; + } + + if (err) { + ubi_warn("volume %d on UBI device %d is corrupted", + vol->vol_id, ubi->ubi_num); + vol->corrupted = 1; + } + + vol->checked = 1; + ubi_gluebi_updated(vol); + } + + return 0; +} + +static off_t ubi_volume_cdev_lseek(struct cdev *cdev, off_t ofs) +{ + struct ubi_volume_cdev_priv *priv = cdev->priv; + + /* We can only update ubi volumes sequentially */ + if (priv->mode == O_WRONLY) + return -EINVAL; + + return ofs; +} + +static struct file_operations ubi_volume_fops = { + .open = ubi_volume_cdev_open, + .close = ubi_volume_cdev_close, + .read = ubi_volume_cdev_read, + .write = ubi_volume_cdev_write, + .lseek = ubi_volume_cdev_lseek, +}; + +int ubi_volume_cdev_add(struct ubi_device *ubi, struct ubi_volume *vol) +{ + struct cdev *cdev = &vol->cdev; + struct ubi_volume_cdev_priv *priv; + int ret; + + priv = xzalloc(sizeof(*priv)); + + priv->vol = vol; + priv->ubi = ubi; + + cdev->ops = &ubi_volume_fops; + cdev->name = asprintf("ubi%d.%s", ubi->ubi_num, vol->name); + cdev->priv = priv; + cdev->size = vol->used_bytes; + printf("registering %s as /dev/%s\n", vol->name, cdev->name); + ret = devfs_create(cdev); + if (ret) { + free(priv); + free(cdev->name); + } + + return 0; +} + +void ubi_volume_cdev_remove(struct ubi_volume *vol) +{ + struct cdev *cdev = &vol->cdev; + struct ubi_volume_cdev_priv *priv = cdev->priv; + + devfs_remove(cdev); + free(cdev->name); + free(priv); +} + +static int ubi_cdev_ioctl(struct cdev *cdev, int cmd, void *buf) +{ + struct ubi_volume_desc *desc; + struct ubi_device *ubi = cdev->priv; + struct ubi_mkvol_req *req = buf; + + switch (cmd) { + case UBI_IOCRMVOL: + desc = ubi_open_volume_nm(ubi->ubi_num, req->name, + UBI_EXCLUSIVE); + if (IS_ERR(desc)) + return PTR_ERR(desc); + ubi_remove_volume(desc); + break; + case UBI_IOCMKVOL: + if (!req->bytes) + req->bytes = ubi->avail_pebs * ubi->leb_size; + return ubi_create_volume(ubi, req); + }; + + return 0; +} + + +static struct file_operations ubi_fops = { + .ioctl = ubi_cdev_ioctl, +}; + +int ubi_cdev_add(struct ubi_device *ubi) +{ + struct cdev *cdev = &ubi->cdev; + int ret; + + cdev->ops = &ubi_fops; + cdev->name = asprintf("ubi%d", ubi->ubi_num); + cdev->priv = ubi; + cdev->size = 0; + + printf("registering /dev/%s\n", cdev->name); + ret = devfs_create(cdev); + if (ret) + free(cdev->name); + + return ret; +} + +void ubi_cdev_remove(struct ubi_device *ubi) +{ + struct cdev *cdev = &ubi->cdev; + + printf("removing %s\n", cdev->name); + + devfs_remove(cdev); + free(cdev->name); +} |