UBI: add UBI devices reference counting
authorArtem Bityutskiy <Artem.Bityutskiy@nokia.com>
Mon, 17 Dec 2007 15:37:26 +0000 (17:37 +0200)
committerArtem Bityutskiy <Artem.Bityutskiy@nokia.com>
Wed, 26 Dec 2007 17:15:17 +0000 (19:15 +0200)
This is one more step on the way to "removable" UBI devices. It
adds reference counting for UBI devices. Every time a volume on
this device is opened - the device's refcount is increased. It
is also increased if someone is reading any sysfs file of this
UBI device or of one of its volumes.

Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
drivers/mtd/ubi/build.c
drivers/mtd/ubi/cdev.c
drivers/mtd/ubi/eba.c
drivers/mtd/ubi/kapi.c
drivers/mtd/ubi/ubi.h
drivers/mtd/ubi/vmt.c
drivers/mtd/ubi/wl.c

index 3f37b16f87743803003e8257c655c15b79b528d9..a4faf71ee3f2205abbd4591732f62a7c4f750af5 100644 (file)
@@ -64,9 +64,6 @@ static int mtd_devs = 0;
 /* MTD devices specification parameters */
 static struct mtd_dev_param mtd_dev_param[UBI_MAX_DEVICES];
 
-/* All UBI devices in system */
-struct ubi_device *ubi_devices[UBI_MAX_DEVICES];
-
 /* Root UBI "class" object (corresponds to '/<sysfs>/class/ubi/') */
 struct class *ubi_class;
 
@@ -83,6 +80,12 @@ static struct miscdevice ubi_ctrl_cdev = {
        .fops = &ubi_ctrl_cdev_operations,
 };
 
+/* All UBI devices in system */
+static struct ubi_device *ubi_devices[UBI_MAX_DEVICES];
+
+/* Protects @ubi_devices and @ubi->ref_count */
+static DEFINE_SPINLOCK(ubi_devices_lock);
+
 /* "Show" method for files in '/<sysfs>/class/ubi/' */
 static ssize_t ubi_version_show(struct class *class, char *buf)
 {
@@ -118,37 +121,145 @@ static struct device_attribute dev_min_io_size =
 static struct device_attribute dev_bgt_enabled =
        __ATTR(bgt_enabled, S_IRUGO, dev_attribute_show, NULL);
 
+/**
+ * ubi_get_device - get UBI device.
+ * @ubi_num: UBI device number
+ *
+ * This function returns UBI device description object for UBI device number
+ * @ubi_num, or %NULL if the device does not exist. This function increases the
+ * device reference count to prevent removal of the device. In other words, the
+ * device cannot be removed if its reference count is not zero.
+ */
+struct ubi_device *ubi_get_device(int ubi_num)
+{
+       struct ubi_device *ubi;
+
+       spin_lock(&ubi_devices_lock);
+       ubi = ubi_devices[ubi_num];
+       if (ubi) {
+               ubi_assert(ubi->ref_count >= 0);
+               ubi->ref_count += 1;
+               get_device(&ubi->dev);
+       }
+       spin_unlock(&ubi_devices_lock);
+
+       return ubi;
+}
+
+/**
+ * ubi_put_device - drop an UBI device reference.
+ * @ubi: UBI device description object
+ */
+void ubi_put_device(struct ubi_device *ubi)
+{
+       spin_lock(&ubi_devices_lock);
+       ubi->ref_count -= 1;
+       put_device(&ubi->dev);
+       spin_unlock(&ubi_devices_lock);
+}
+
+/**
+ * ubi_get_by_major - get UBI device description object by character device
+ *                    major number.
+ * @major: major number
+ *
+ * This function is similar to 'ubi_get_device()', but it searches the device
+ * by its major number.
+ */
+struct ubi_device *ubi_get_by_major(int major)
+{
+       int i;
+       struct ubi_device *ubi;
+
+       spin_lock(&ubi_devices_lock);
+       for (i = 0; i < UBI_MAX_DEVICES; i++) {
+               ubi = ubi_devices[i];
+               if (ubi && MAJOR(ubi->cdev.dev) == major) {
+                       ubi_assert(ubi->ref_count >= 0);
+                       ubi->ref_count += 1;
+                       get_device(&ubi->dev);
+                       spin_unlock(&ubi_devices_lock);
+                       return ubi;
+               }
+       }
+       spin_unlock(&ubi_devices_lock);
+
+       return NULL;
+}
+
+/**
+ * ubi_major2num - get UBI device number by character device major number.
+ * @major: major number
+ *
+ * This function searches UBI device number object by its major number. If UBI
+ * device was not found, this function returns -ENODEV, othewise the UBI device
+ * number is returned.
+ */
+int ubi_major2num(int major)
+{
+       int i, ubi_num = -ENODEV;
+
+       spin_lock(&ubi_devices_lock);
+       for (i = 0; i < UBI_MAX_DEVICES; i++) {
+               struct ubi_device *ubi = ubi_devices[i];
+
+               if (ubi && MAJOR(ubi->cdev.dev) == major) {
+                       ubi_num = ubi->ubi_num;
+                       break;
+               }
+       }
+       spin_unlock(&ubi_devices_lock);
+
+       return ubi_num;
+}
+
 /* "Show" method for files in '/<sysfs>/class/ubi/ubiX/' */
 static ssize_t dev_attribute_show(struct device *dev,
                                  struct device_attribute *attr, char *buf)
 {
-       const struct ubi_device *ubi;
+       ssize_t ret;
+       struct ubi_device *ubi;
 
+       /*
+        * The below code looks weird, but it actually makes sense. We get the
+        * UBI device reference from the contained 'struct ubi_device'. But it
+        * is unclear if the device was removed or not yet. Indeed, if the
+        * device was removed before we increased its reference count,
+        * 'ubi_get_device()' will return -ENODEV and we fail.
+        *
+        * Remember, 'struct ubi_device' is freed in the release function, so
+        * we still can use 'ubi->ubi_num'.
+        */
        ubi = container_of(dev, struct ubi_device, dev);
+       ubi = ubi_get_device(ubi->ubi_num);
+       if (!ubi)
+               return -ENODEV;
+
        if (attr == &dev_eraseblock_size)
-               return sprintf(buf, "%d\n", ubi->leb_size);
+               ret = sprintf(buf, "%d\n", ubi->leb_size);
        else if (attr == &dev_avail_eraseblocks)
-               return sprintf(buf, "%d\n", ubi->avail_pebs);
+               ret = sprintf(buf, "%d\n", ubi->avail_pebs);
        else if (attr == &dev_total_eraseblocks)
-               return sprintf(buf, "%d\n", ubi->good_peb_count);
+               ret = sprintf(buf, "%d\n", ubi->good_peb_count);
        else if (attr == &dev_volumes_count)
-               return sprintf(buf, "%d\n", ubi->vol_count);
+               ret = sprintf(buf, "%d\n", ubi->vol_count);
        else if (attr == &dev_max_ec)
-               return sprintf(buf, "%d\n", ubi->max_ec);
+               ret = sprintf(buf, "%d\n", ubi->max_ec);
        else if (attr == &dev_reserved_for_bad)
-               return sprintf(buf, "%d\n", ubi->beb_rsvd_pebs);
+               ret = sprintf(buf, "%d\n", ubi->beb_rsvd_pebs);
        else if (attr == &dev_bad_peb_count)
-               return sprintf(buf, "%d\n", ubi->bad_peb_count);
+               ret = sprintf(buf, "%d\n", ubi->bad_peb_count);
        else if (attr == &dev_max_vol_count)
-               return sprintf(buf, "%d\n", ubi->vtbl_slots);
+               ret = sprintf(buf, "%d\n", ubi->vtbl_slots);
        else if (attr == &dev_min_io_size)
-               return sprintf(buf, "%d\n", ubi->min_io_size);
+               ret = sprintf(buf, "%d\n", ubi->min_io_size);
        else if (attr == &dev_bgt_enabled)
-               return sprintf(buf, "%d\n", ubi->thread_enabled);
+               ret = sprintf(buf, "%d\n", ubi->thread_enabled);
        else
                BUG();
 
-       return 0;
+       ubi_put_device(ubi);
+       return ret;
 }
 
 /* Fake "release" method for UBI devices */
@@ -670,6 +781,7 @@ static void detach_mtd_dev(struct ubi_device *ubi)
        int ubi_num = ubi->ubi_num, mtd_num = ubi->mtd->index;
 
        dbg_msg("detaching mtd%d from ubi%d", ubi->mtd->index, ubi_num);
+       ubi_assert(ubi->ref_count == 0);
        uif_close(ubi);
        ubi_eba_close(ubi);
        ubi_wl_close(ubi);
index bc900d24cdba3128aff4ccd9c39bbc40423fe04d..01978b57e9cb99197e632c53ad98284ba69fae07 100644 (file)
 #define VOL_CDEV_IOC_MAX_SEQ 2
 #endif
 
-/**
- * major_to_device - get UBI device object by character device major number.
- * @major: major number
- *
- * This function returns a pointer to the UBI device object.
- */
-static struct ubi_device *major_to_device(int major)
-{
-       int i;
-
-       for (i = 0; i < UBI_MAX_DEVICES; i++)
-               if (ubi_devices[i] && MAJOR(ubi_devices[i]->cdev.dev) == major)
-                       return ubi_devices[i];
-       BUG();
-       return NULL;
-}
-
 /**
  * get_exclusive - get exclusive access to an UBI volume.
  * @desc: volume descriptor
@@ -129,9 +112,11 @@ static void revoke_exclusive(struct ubi_volume_desc *desc, int mode)
 static int vol_cdev_open(struct inode *inode, struct file *file)
 {
        struct ubi_volume_desc *desc;
-       const struct ubi_device *ubi = major_to_device(imajor(inode));
-       int vol_id = iminor(inode) - 1;
-       int mode;
+       int vol_id = iminor(inode) - 1, mode, ubi_num;
+
+       ubi_num = ubi_major2num(imajor(inode));
+       if (ubi_num < 0)
+               return ubi_num;
 
        if (file->f_mode & FMODE_WRITE)
                mode = UBI_READWRITE;
@@ -140,7 +125,7 @@ static int vol_cdev_open(struct inode *inode, struct file *file)
 
        dbg_msg("open volume %d, mode %d", vol_id, mode);
 
-       desc = ubi_open_volume(ubi->ubi_num, vol_id, mode);
+       desc = ubi_open_volume(ubi_num, vol_id, mode);
        if (IS_ERR(desc))
                return PTR_ERR(desc);
 
@@ -586,9 +571,9 @@ static int ubi_cdev_ioctl(struct inode *inode, struct file *file,
        if (!capable(CAP_SYS_RESOURCE))
                return -EPERM;
 
-       ubi = major_to_device(imajor(inode));
-       if (IS_ERR(ubi))
-               return PTR_ERR(ubi);
+       ubi = ubi_get_by_major(imajor(inode));
+       if (!ubi)
+               return -ENODEV;
 
        switch (cmd) {
        /* Create volume command */
@@ -695,6 +680,7 @@ static int ubi_cdev_ioctl(struct inode *inode, struct file *file,
                break;
        }
 
+       ubi_put_device(ubi);
        return err;
 }
 
index c94f475758de3b98be9b96a27b88bd90e8e9939d..85297cde4ac5eb2a3c361ce79ffa7663fd2f0e7d 100644 (file)
@@ -339,6 +339,7 @@ int ubi_eba_unmap_leb(struct ubi_device *ubi, struct ubi_volume *vol,
 {
        int err, pnum, vol_id = vol->vol_id;
 
+       ubi_assert(ubi->ref_count > 0);
        ubi_assert(vol->ref_count > 0);
 
        if (ubi->ro_mode)
@@ -389,6 +390,7 @@ int ubi_eba_read_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
        struct ubi_vid_hdr *vid_hdr;
        uint32_t uninitialized_var(crc);
 
+       ubi_assert(ubi->ref_count > 0);
        ubi_assert(vol->ref_count > 0);
 
        err = leb_read_lock(ubi, vol_id, lnum);
@@ -614,6 +616,7 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
        int err, pnum, tries = 0, vol_id = vol->vol_id;
        struct ubi_vid_hdr *vid_hdr;
 
+       ubi_assert(ubi->ref_count > 0);
        ubi_assert(vol->ref_count > 0);
 
        if (ubi->ro_mode)
@@ -749,6 +752,7 @@ int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol,
        struct ubi_vid_hdr *vid_hdr;
        uint32_t crc;
 
+       ubi_assert(ubi->ref_count > 0);
        ubi_assert(vol->ref_count > 0);
 
        if (ubi->ro_mode)
@@ -865,6 +869,7 @@ int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol,
        struct ubi_vid_hdr *vid_hdr;
        uint32_t crc;
 
+       ubi_assert(ubi->ref_count > 0);
        ubi_assert(vol->ref_count > 0);
 
        if (ubi->ro_mode)
index 780c273ff452fb432d4e0f4144ee8c4ca6ca0698..4ec3a33b2577cc80d28bf69fcadd11beeac106c2 100644 (file)
  * @ubi_num: UBI device number
  * @di: the information is stored here
  *
- * This function returns %0 in case of success and a %-ENODEV if there is no
- * such UBI device.
+ * This function returns %0 in case of success, %-EINVAL if the UBI device
+ * number is invalid, and %-ENODEV if there is no such UBI device.
  */
 int ubi_get_device_info(int ubi_num, struct ubi_device_info *di)
 {
-       const struct ubi_device *ubi;
+       struct ubi_device *ubi;
 
-       if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES ||
-           !ubi_devices[ubi_num])
+       if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES)
+               return -EINVAL;
+
+       ubi = ubi_get_device(ubi_num);
+       if (!ubi)
                return -ENODEV;
 
-       ubi = ubi_devices[ubi_num];
        di->ubi_num = ubi->ubi_num;
        di->leb_size = ubi->leb_size;
        di->min_io_size = ubi->min_io_size;
        di->ro_mode = ubi->ro_mode;
        di->cdev = ubi->cdev.dev;
+
+       ubi_put_device(ubi);
        return 0;
 }
 EXPORT_SYMBOL_GPL(ubi_get_device_info);
@@ -111,16 +115,23 @@ struct ubi_volume_desc *ubi_open_volume(int ubi_num, int vol_id, int mode)
            mode != UBI_EXCLUSIVE)
                return ERR_PTR(-EINVAL);
 
-       ubi = ubi_devices[ubi_num];
+       /*
+        * First of all, we have to get the UBI device to prevent its removal.
+        */
+       ubi = ubi_get_device(ubi_num);
        if (!ubi)
                return ERR_PTR(-ENODEV);
 
-       if (vol_id < 0 || vol_id >= ubi->vtbl_slots)
-               return ERR_PTR(-EINVAL);
+       if (vol_id < 0 || vol_id >= ubi->vtbl_slots) {
+               err = -EINVAL;
+               goto out_put_ubi;
+       }
 
        desc = kmalloc(sizeof(struct ubi_volume_desc), GFP_KERNEL);
-       if (!desc)
-               return ERR_PTR(-ENOMEM);
+       if (!desc) {
+               err = -ENOMEM;
+               goto out_put_ubi;
+       }
 
        err = -ENODEV;
        if (!try_module_get(THIS_MODULE))
@@ -188,6 +199,8 @@ out_unlock:
        module_put(THIS_MODULE);
 out_free:
        kfree(desc);
+out_put_ubi:
+       ubi_put_device(ubi);
        return ERR_PTR(err);
 }
 EXPORT_SYMBOL_GPL(ubi_open_volume);
@@ -205,6 +218,7 @@ struct ubi_volume_desc *ubi_open_volume_nm(int ubi_num, const char *name,
 {
        int i, vol_id = -1, len;
        struct ubi_device *ubi;
+       struct ubi_volume_desc *ret;
 
        dbg_msg("open volume %s, mode %d", name, mode);
 
@@ -218,7 +232,7 @@ struct ubi_volume_desc *ubi_open_volume_nm(int ubi_num, const char *name,
        if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES)
                return ERR_PTR(-EINVAL);
 
-       ubi = ubi_devices[ubi_num];
+       ubi = ubi_get_device(ubi_num);
        if (!ubi)
                return ERR_PTR(-ENODEV);
 
@@ -234,10 +248,17 @@ struct ubi_volume_desc *ubi_open_volume_nm(int ubi_num, const char *name,
        }
        spin_unlock(&ubi->volumes_lock);
 
-       if (vol_id < 0)
-               return ERR_PTR(-ENODEV);
+       if (vol_id >= 0)
+               ret = ubi_open_volume(ubi_num, vol_id, mode);
+       else
+               ret = ERR_PTR(-ENODEV);
 
-       return ubi_open_volume(ubi_num, vol_id, mode);
+       /*
+        * We should put the UBI device even in case of success, because
+        * 'ubi_open_volume()' took a reference as well.
+        */
+       ubi_put_device(ubi);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(ubi_open_volume_nm);
 
@@ -248,10 +269,11 @@ EXPORT_SYMBOL_GPL(ubi_open_volume_nm);
 void ubi_close_volume(struct ubi_volume_desc *desc)
 {
        struct ubi_volume *vol = desc->vol;
+       struct ubi_device *ubi = vol->ubi;
 
        dbg_msg("close volume %d, mode %d", vol->vol_id, desc->mode);
 
-       spin_lock(&vol->ubi->volumes_lock);
+       spin_lock(&ubi->volumes_lock);
        switch (desc->mode) {
        case UBI_READONLY:
                vol->readers -= 1;
@@ -263,10 +285,11 @@ void ubi_close_volume(struct ubi_volume_desc *desc)
                vol->exclusive = 0;
        }
        vol->ref_count -= 1;
-       spin_unlock(&vol->ubi->volumes_lock);
+       spin_unlock(&ubi->volumes_lock);
 
-       put_device(&vol->dev);
        kfree(desc);
+       put_device(&vol->dev);
+       ubi_put_device(ubi);
        module_put(THIS_MODULE);
 }
 EXPORT_SYMBOL_GPL(ubi_close_volume);
index 21c028366fd2e2feca62c1862311e187b3d099d6..91fde0e8ff58c4e4ddbb6fdfae61f87247a4de29 100644 (file)
@@ -245,6 +245,7 @@ struct ubi_wl_entry;
  *                @beb_rsvd_level, @bad_peb_count, @good_peb_count, @vol_count,
  *                @vol->readers, @vol->writers, @vol->exclusive,
  *                @vol->ref_count, @vol->mapping and @vol->eba_tbl.
+ * @ref_count: count of references on the UBI device
  *
  * @rsvd_pebs: count of reserved physical eraseblocks
  * @avail_pebs: count of available physical eraseblocks
@@ -325,6 +326,7 @@ struct ubi_device {
        int vol_count;
        struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];
        spinlock_t volumes_lock;
+       int ref_count;
 
        int rsvd_pebs;
        int avail_pebs;
@@ -401,7 +403,6 @@ extern struct kmem_cache *ubi_wl_entry_slab;
 extern struct file_operations ubi_ctrl_cdev_operations;
 extern struct file_operations ubi_cdev_operations;
 extern struct file_operations ubi_vol_cdev_operations;
-extern struct ubi_device *ubi_devices[];
 extern struct class *ubi_class;
 
 /* vtbl.c */
@@ -479,6 +480,12 @@ int ubi_io_read_vid_hdr(struct ubi_device *ubi, int pnum,
 int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
                         struct ubi_vid_hdr *vid_hdr);
 
+/* build.c */
+struct ubi_device *ubi_get_device(int ubi_num);
+void ubi_put_device(struct ubi_device *ubi);
+struct ubi_device *ubi_get_by_major(int major);
+int ubi_major2num(int major);
+
 /*
  * ubi_rb_for_each_entry - walk an RB-tree.
  * @rb: a pointer to type 'struct rb_node' to to use as a loop counter
index 3ed63dc37386daaff1decb868f7902f31dbeb8f7..42d3dd70f2d02808db6d9b45e704872351d02b14 100644 (file)
@@ -71,11 +71,16 @@ static ssize_t vol_attribute_show(struct device *dev,
 {
        int ret;
        struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev);
-       struct ubi_device *ubi = vol->ubi;
+       struct ubi_device *ubi;
+
+       ubi = ubi_get_device(vol->ubi->ubi_num);
+       if (!ubi)
+               return -ENODEV;
 
        spin_lock(&ubi->volumes_lock);
        if (!ubi->volumes[vol->vol_id]) {
                spin_unlock(&ubi->volumes_lock);
+               ubi_put_device(ubi);
                return -ENODEV;
        }
        /* Take a reference to prevent volume removal */
@@ -108,10 +113,12 @@ static ssize_t vol_attribute_show(struct device *dev,
                /* This must be a bug */
                ret = -EINVAL;
 
+       /* We've done the operation, drop volume and UBI device references */
        spin_lock(&ubi->volumes_lock);
        vol->ref_count -= 1;
        ubi_assert(vol->ref_count >= 0);
        spin_unlock(&ubi->volumes_lock);
+       ubi_put_device(ubi);
        return ret;
 }
 
@@ -260,6 +267,7 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
        }
        ubi->avail_pebs -= vol->reserved_pebs;
        ubi->rsvd_pebs += vol->reserved_pebs;
+       spin_unlock(&ubi->volumes_lock);
 
        vol->vol_id    = vol_id;
        vol->alignment = req->alignment;
@@ -267,9 +275,7 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
        vol->vol_type  = req->vol_type;
        vol->name_len  = req->name_len;
        memcpy(vol->name, req->name, vol->name_len + 1);
-       vol->exclusive = 1;
        vol->ubi = ubi;
-       spin_unlock(&ubi->volumes_lock);
 
        /*
         * Finish all pending erases because there may be some LEBs belonging
@@ -350,8 +356,6 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
                goto out_sysfs;
 
        spin_lock(&ubi->volumes_lock);
-       ubi->vol_count += 1;
-       vol->exclusive = 0;
        ubi->volumes[vol_id] = vol;
        spin_unlock(&ubi->volumes_lock);
 
index 7d32f71d6f1e93c855b7b2a4c4489ca1cf9ea47a..bfc64c82416557292df4ef60eabff2277f898734 100644 (file)
@@ -1303,6 +1303,7 @@ int ubi_wl_flush(struct ubi_device *ubi)
         * Make sure all the works which have been done in parallel are
         * finished.
         */
+       ubi_assert(ubi->ref_count > 0);
        down_write(&ubi->work_sem);
        up_write(&ubi->work_sem);