diff options
Diffstat (limited to 'fs/devfs-core.c')
-rw-r--r-- | fs/devfs-core.c | 318 |
1 files changed, 270 insertions, 48 deletions
diff --git a/fs/devfs-core.c b/fs/devfs-core.c index 5341e39e67..376c62be9e 100644 --- a/fs/devfs-core.c +++ b/fs/devfs-core.c @@ -3,9 +3,6 @@ * * Copyright (c) 2011 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * - * See file CREDITS for list of people who contributed to this - * project. - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. @@ -40,8 +37,8 @@ int devfs_partition_complete(struct string_list *sl, char *instr) len = strlen(instr); - list_for_each_entry(cdev, &cdev_list, list) { - if (cdev->master && + for_each_cdev(cdev) { + if (cdev_is_partition(cdev) && !strncmp(instr, cdev->name, len)) { string_list_add_asprintf(sl, "%s ", cdev->name); } @@ -52,6 +49,9 @@ int devfs_partition_complete(struct string_list *sl, char *instr) struct cdev *cdev_readlink(struct cdev *cdev) { + if (!cdev) + return NULL; + if (cdev->link) cdev = cdev->link; @@ -65,7 +65,7 @@ struct cdev *lcdev_by_name(const char *filename) { struct cdev *cdev; - list_for_each_entry(cdev, &cdev_list, list) { + for_each_cdev(cdev) { if (!strcmp(cdev->name, filename)) return cdev; } @@ -87,10 +87,11 @@ struct cdev *cdev_by_device_node(struct device_node *node) { struct cdev *cdev; - list_for_each_entry(cdev, &cdev_list, list) { - if (!cdev->device_node) - continue; - if (cdev->device_node == node) + if (!node) + return NULL; + + for_each_cdev(cdev) { + if (cdev_of_node(cdev) == node) return cdev_readlink(cdev); } return NULL; @@ -103,8 +104,22 @@ struct cdev *cdev_by_partuuid(const char *partuuid) if (!partuuid) return NULL; - list_for_each_entry(cdev, &cdev_list, list) { - if (!strcasecmp(cdev->partuuid, partuuid)) + for_each_cdev(cdev) { + if (cdev_is_partition(cdev) && !strcasecmp(cdev->partuuid, partuuid)) + return cdev; + } + return NULL; +} + +struct cdev *cdev_by_diskuuid(const char *diskuuid) +{ + struct cdev *cdev; + + if (!diskuuid) + return NULL; + + for_each_cdev(cdev) { + if (!cdev_is_partition(cdev) && !strcasecmp(cdev->diskuuid, diskuuid)) return cdev; } return NULL; @@ -116,10 +131,10 @@ struct cdev *cdev_by_partuuid(const char *partuuid) * @dev: the device which should be searched for partitions * @name: the partition name */ -struct cdev *device_find_partition(struct device_d *dev, const char *name) +struct cdev *device_find_partition(struct device *dev, const char *name) { struct cdev *cdev; - struct device_d *child; + struct device *child; list_for_each_entry(cdev, &dev->cdevs, devices_list) { struct cdev *cdevl; @@ -158,7 +173,42 @@ int cdev_find_free_index(const char *basename) return -EBUSY; /* all indexes are used */ } -struct cdev *cdev_open(const char *name, unsigned long flags) +int cdev_open(struct cdev *cdev, unsigned long flags) +{ + int ret; + + if (cdev->ops->open) { + ret = cdev->ops->open(cdev, flags); + if (ret) + return ret; + } + + cdev->open++; + + return 0; +} + +int cdev_fdopen(struct cdev *cdev, unsigned long flags) +{ + char *path; + int fd; + + if (!cdev) + return -ENODEV; + if (IS_ERR(cdev)) + return PTR_ERR(cdev); + + path = basprintf("/dev/%s", cdev->name); + if (!path) + return -ENOMEM; + + fd = open(path, flags); + + free(path); + return fd; +} + +struct cdev *cdev_open_by_name(const char *name, unsigned long flags) { struct cdev *cdev; int ret; @@ -167,11 +217,9 @@ struct cdev *cdev_open(const char *name, unsigned long flags) if (!cdev) return NULL; - if (cdev->ops->open) { - ret = cdev->ops->open(cdev, flags); - if (ret) - return NULL; - } + ret = cdev_open(cdev, flags); + if (ret) + return NULL; return cdev; } @@ -180,6 +228,8 @@ void cdev_close(struct cdev *cdev) { if (cdev->ops->close) cdev->ops->close(cdev); + + cdev->open--; } ssize_t cdev_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, ulong flags) @@ -222,6 +272,77 @@ int cdev_erase(struct cdev *cdev, loff_t count, loff_t offset) return cdev->ops->erase(cdev, count, cdev->offset + offset); } +int cdev_lseek(struct cdev *cdev, loff_t pos) +{ + int ret; + + if (cdev->ops->lseek) { + ret = cdev->ops->lseek(cdev, pos + cdev->offset); + if (ret < 0) + return ret; + } + + return 0; +} + +int cdev_protect(struct cdev *cdev, size_t count, loff_t offset, int prot) +{ + if (!cdev->ops->protect) + return -ENOSYS; + + return cdev->ops->protect(cdev, count, offset + cdev->offset, prot); +} + +int cdev_discard_range(struct cdev *cdev, loff_t count, loff_t offset) +{ + if (!cdev->ops->discard_range) + return -ENOSYS; + + if (cdev->flags & DEVFS_PARTITION_READONLY) + return -EPERM; + + if (offset >= cdev->size) + return 0; + + if (count + offset > cdev->size) + count = cdev->size - offset; + + return cdev->ops->discard_range(cdev, count, offset + cdev->offset); +} + +int cdev_memmap(struct cdev *cdev, void **map, int flags) +{ + int ret = -ENOSYS; + + if (!cdev->ops->memmap) + return -EINVAL; + + ret = cdev->ops->memmap(cdev, map, flags); + + if (!ret) + *map = (void *)((unsigned long)*map + (unsigned long)cdev->offset); + + return ret; +} + +int cdev_truncate(struct cdev *cdev, size_t size) +{ + if (cdev->ops->truncate) + return cdev->ops->truncate(cdev, size); + + return -EPERM; +} + +static struct cdev *cdev_alloc(const char *name) +{ + struct cdev *new; + + new = xzalloc(sizeof(*new)); + new->name = xstrdup(name); + + return new; +} + int devfs_create(struct cdev *new) { struct cdev *cdev; @@ -237,7 +358,7 @@ int devfs_create(struct cdev *new) if (new->dev) { list_add_tail(&new->devices_list, &new->dev->cdevs); if (!new->device_node) - new->device_node = new->dev->device_node; + new->device_node = new->dev->of_node; } return 0; @@ -256,8 +377,7 @@ int devfs_create_link(struct cdev *cdev, const char *name) */ cdev = cdev_readlink(cdev); - new = xzalloc(sizeof(*new)); - new->name = xstrdup(name); + new = cdev_alloc(name); new->link = cdev; if (cdev->partname) { @@ -274,6 +394,7 @@ int devfs_create_link(struct cdev *cdev, const char *name) } INIT_LIST_HEAD(&new->links); + INIT_LIST_HEAD(&new->partitions); list_add_tail(&new->list, &cdev_list); list_add_tail(&new->link_entry, &cdev->links); @@ -295,7 +416,10 @@ int devfs_remove(struct cdev *cdev) list_for_each_entry_safe(c, tmp, &cdev->links, link_entry) devfs_remove(c); - if (cdev->master) + list_for_each_entry_safe(c, tmp, &cdev->partitions, partition_entry) + cdevfs_del_partition(c); + + if (cdev_is_partition(cdev)) list_del(&cdev->partition_entry); if (cdev->link) @@ -304,11 +428,82 @@ int devfs_remove(struct cdev *cdev) return 0; } +static bool region_identical(loff_t starta, loff_t lena, + loff_t startb, loff_t lenb) +{ + return starta == startb && lena == lenb; +} + +static bool region_overlap(loff_t starta, loff_t lena, + loff_t startb, loff_t lenb) +{ + if (starta + lena <= startb) + return 0; + if (startb + lenb <= starta) + return 0; + return 1; +} + +/** + * check_overlap() - check overlap with existing partitions + * @cdev: parent cdev + * @name: partition name for informational purposes on conflict + * @offset: offset of new partition to be added + * @size: size of new partition to be added + * + * Return: NULL if no overlapping partition found or overlapping + * partition if and only if it's identical in offset and size + * to an existing partition. Otherwise, PTR_ERR(-EINVAL). + */ +static struct cdev *check_overlap(struct cdev *cdev, const char *name, loff_t offset, loff_t size) +{ + struct cdev *cpart; + loff_t cpart_offset; + int ret; + + list_for_each_entry(cpart, &cdev->partitions, partition_entry) { + cpart_offset = cpart->offset; + + /* + * An mtd partition is represented by a separate cdev and its + * cpart is relative to this one. So its .offset is 0 and we + * have to consult .master_offset to get its offset. + */ + if (cpart->mtd) + cpart_offset = cpart->mtd->master_offset; + + if (region_identical(cpart_offset, cpart->size, offset, size)) { + ret = 0; + goto identical; + } + + if (region_overlap(cpart_offset, cpart->size, offset, size)) { + ret = -EINVAL; + goto conflict; + } + } + + return NULL; + +identical: +conflict: + __pr_printk(ret ? MSG_WARNING : MSG_DEBUG, + "New partition %s (0x%08llx-0x%08llx) on %s " + "%s with partition %s (0x%08llx-0x%08llx), not creating it\n", + name, offset, offset + size - 1, cdev->name, + ret ? "conflicts" : "identical", + cpart->name, cpart_offset, cpart_offset + cpart->size - 1); + + return ret ? ERR_PTR(ret) : cpart; +} + static struct cdev *__devfs_add_partition(struct cdev *cdev, const struct devfs_partition *partinfo, loff_t *end) { loff_t offset, size; + loff_t _end = end ? *end : 0; static struct cdev *new; + struct cdev *overlap; if (cdev_by_name(partinfo->name)) return ERR_PTR(-EEXIST); @@ -317,7 +512,7 @@ static struct cdev *__devfs_add_partition(struct cdev *cdev, offset = partinfo->offset; else if (partinfo->offset == 0) /* append to previous partition */ - offset = *end; + offset = _end; else /* relative to end of cdev */ offset = cdev->size + partinfo->offset; @@ -327,18 +522,30 @@ static struct cdev *__devfs_add_partition(struct cdev *cdev, else size = cdev->size + partinfo->size - offset; - if (offset >= 0 && offset < *end) + if (offset >= 0 && offset < _end) pr_debug("partition %s not after previous partition\n", partinfo->name); - *end = offset + size; + _end = offset + size; + if (end) + *end = _end; - if (offset < 0 || *end > cdev->size) { + if (offset < 0 || _end > cdev->size) { pr_warn("partition %s not completely inside device %s\n", partinfo->name, cdev->name); return ERR_PTR(-EINVAL); } + overlap = check_overlap(cdev, partinfo->name, offset, size); + if (overlap) { + if (!IS_ERR(overlap)) { + /* only fails with -EEXIST, which is fine */ + (void)devfs_create_link(overlap, partinfo->name); + } + + return overlap; + } + if (IS_ENABLED(CONFIG_MTD) && cdev->mtd) { struct mtd_info *mtd; @@ -351,8 +558,7 @@ static struct cdev *__devfs_add_partition(struct cdev *cdev, return &mtd->cdev; } - new = xzalloc(sizeof(*new)); - new->name = strdup(partinfo->name); + new = cdev_alloc(partinfo->name); if (!strncmp(cdev->name, partinfo->name, strlen(cdev->name))) new->partname = xstrdup(partinfo->name + strlen(cdev->name) + 1); @@ -373,11 +579,16 @@ static struct cdev *__devfs_add_partition(struct cdev *cdev, return new; } +struct cdev *cdevfs_add_partition(struct cdev *cdev, + const struct devfs_partition *partinfo) +{ + return __devfs_add_partition(cdev, partinfo, NULL); +} + struct cdev *devfs_add_partition(const char *devname, loff_t offset, loff_t size, unsigned int flags, const char *name) { struct cdev *cdev; - loff_t end = 0; const struct devfs_partition partinfo = { .offset = offset, .size = size, @@ -389,28 +600,21 @@ struct cdev *devfs_add_partition(const char *devname, loff_t offset, if (!cdev) return ERR_PTR(-ENOENT); - return __devfs_add_partition(cdev, &partinfo, &end); + return cdevfs_add_partition(cdev, &partinfo); } -int devfs_del_partition(const char *name) +int cdevfs_del_partition(struct cdev *cdev) { - struct cdev *cdev; int ret; - cdev = cdev_by_name(name); - if (!cdev) - return -ENOENT; + if (cdev->flags & DEVFS_PARTITION_FIXED) + return -EPERM; if (IS_ENABLED(CONFIG_MTD) && cdev->mtd) { ret = mtd_del_partition(cdev->mtd); return ret; } - if (!cdev->master) - return -EINVAL; - if (cdev->flags & DEVFS_PARTITION_FIXED) - return -EPERM; - ret = devfs_remove(cdev); if (ret) return ret; @@ -422,6 +626,20 @@ int devfs_del_partition(const char *name) return 0; } +int devfs_del_partition(const char *name) +{ + struct cdev *cdev; + + cdev = cdev_by_name(name); + if (!cdev) + return -ENOENT; + + if (!cdev_is_partition(cdev)) + return -EINVAL; + + return cdevfs_del_partition(cdev); +} + int devfs_create_partitions(const char *devname, const struct devfs_partition partinfo[]) { @@ -484,6 +702,7 @@ static const struct cdev_operations loop_ops = { struct cdev *cdev_create_loop(const char *path, ulong flags, loff_t offset) { + char str[16]; struct cdev *new; struct loop_priv *priv; static int loopno; @@ -497,10 +716,10 @@ struct cdev *cdev_create_loop(const char *path, ulong flags, loff_t offset) return NULL; } - new = xzalloc(sizeof(*new)); + snprintf(str, sizeof(str), "loop%u", loopno++); + new = cdev_alloc(str); new->ops = &loop_ops; - new->name = basprintf("loop%u", loopno++); new->priv = priv; ofs = lseek(priv->fd, 0, SEEK_END); @@ -532,7 +751,7 @@ void cdev_remove_loop(struct cdev *cdev) free(cdev); } -static ssize_t mem_copy(struct device_d *dev, void *dst, const void *src, +ssize_t mem_copy(struct device *dev, void *dst, const void *src, resource_size_t count, resource_size_t offset, unsigned long flags) { @@ -542,7 +761,10 @@ static ssize_t mem_copy(struct device_d *dev, void *dst, const void *src, if (!dev || dev->num_resources < 1) return -1; - count = size = min(count, resource_size(&dev->resource[0]) - offset); + if (resource_size(&dev->resource[0]) > 0 || offset != 0) + count = min(count, resource_size(&dev->resource[0]) - offset); + + size = count; /* no rwsize specification given. Do whatever memcpy likes best */ if (!rwsize) { @@ -580,7 +802,7 @@ out: ssize_t mem_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, unsigned long flags) { - struct device_d *dev = cdev->dev; + struct device *dev = cdev->dev; if (!dev) return -1; @@ -593,7 +815,7 @@ EXPORT_SYMBOL(mem_read); ssize_t mem_write(struct cdev *cdev, const void *buf, size_t count, loff_t offset, unsigned long flags) { - struct device_d *dev = cdev->dev; + struct device *dev = cdev->dev; if (!dev) return -1; |