summaryrefslogtreecommitdiffstats
path: root/fs/devfs-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/devfs-core.c')
-rw-r--r--fs/devfs-core.c318
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;