Upon detection of a read-only backing device arrange for the btt to
device to be read only.  Implement a catch for the BLKROSET ioctl and
only allow a btt-instance to become read-write when the backing-device
becomes read-write.  Conversely, if a backing-device becomes read-only
arrange for its parent btt to be marked read-only.  Synchronize these
changes under the bus lock.

Signed-off-by: Dan Williams <[email protected]>
---
 drivers/nvdimm/blk.c      |    4 +++
 drivers/nvdimm/btt.c      |   34 ++++++++++++++++++++++++++--
 drivers/nvdimm/btt_devs.c |   42 ++++++++++++++++++++++++++++++++++
 drivers/nvdimm/bus.c      |   55 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/nvdimm/nd-core.h  |   14 +++++++++++
 drivers/nvdimm/nd.h       |    4 +++
 drivers/nvdimm/pmem.c     |    4 +++
 7 files changed, 154 insertions(+), 3 deletions(-)

diff --git a/drivers/nvdimm/blk.c b/drivers/nvdimm/blk.c
index 8a65e5a500d8..adacc27f04f1 100644
--- a/drivers/nvdimm/blk.c
+++ b/drivers/nvdimm/blk.c
@@ -239,6 +239,10 @@ static int nd_blk_rw_bytes(struct gendisk *disk, 
resource_size_t offset,
 static const struct block_device_operations nd_blk_fops = {
        .owner = THIS_MODULE,
        .rw_bytes = nd_blk_rw_bytes,
+       .ioctl = nvdimm_bdev_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = nvdimm_bdev_compat_ioctl,
+#endif
 };
 
 static int nd_blk_probe(struct device *dev)
diff --git a/drivers/nvdimm/btt.c b/drivers/nvdimm/btt.c
index 67484633c322..57d3b271e451 100644
--- a/drivers/nvdimm/btt.c
+++ b/drivers/nvdimm/btt.c
@@ -1248,10 +1248,29 @@ static int btt_getgeo(struct block_device *bd, struct 
hd_geometry *geo)
        return 0;
 }
 
+static int btt_revalidate_disk(struct gendisk *disk)
+{
+       struct btt *btt = disk->private_data;
+       struct nd_btt *nd_btt = btt->nd_btt;
+       struct block_device *bdev = nd_btt->backing_dev;
+       char name[BDEVNAME_SIZE];
+
+       dev_dbg(&nd_btt->dev, "backing dev: %s read-%s", bdevname(bdev, name),
+                       bdev_read_only(bdev) ? "only" : "write");
+       if (bdev_read_only(bdev))
+               set_disk_ro(disk, 1);
+       return 0;
+}
+
 static const struct block_device_operations btt_fops = {
        .owner =                THIS_MODULE,
        .rw_page =              btt_rw_page,
        .getgeo =               btt_getgeo,
+       .ioctl =                nvdimm_bdev_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl =         nvdimm_bdev_compat_ioctl,
+#endif
+       .revalidate_disk =      btt_revalidate_disk,
 };
 
 static int btt_blk_init(struct btt *btt)
@@ -1296,6 +1315,7 @@ static int btt_blk_init(struct btt *btt)
        }
 
        set_capacity(btt->btt_disk, btt->nlba * btt->sector_size >> 9);
+       revalidate_disk(btt->btt_disk);
 
        return 0;
 
@@ -1335,6 +1355,7 @@ static struct btt *btt_init(struct nd_btt *nd_btt, 
unsigned long long rawsize,
        int ret;
        struct btt *btt;
        struct device *dev = &nd_btt->dev;
+       struct block_device *bdev = nd_btt->backing_dev;
 
        btt = kzalloc(sizeof(struct btt), GFP_KERNEL);
        if (!btt)
@@ -1354,7 +1375,13 @@ static struct btt *btt_init(struct nd_btt *nd_btt, 
unsigned long long rawsize,
                goto out_free;
        }
 
-       if (btt->init_state != INIT_READY) {
+       if (btt->init_state != INIT_READY && bdev_read_only(bdev)) {
+               char name[BDEVNAME_SIZE];
+
+               dev_info(dev, "%s is read-only, unable to init btt metadata\n",
+                               bdevname(bdev, name));
+               goto out_free;
+       } else if (btt->init_state != INIT_READY) {
                btt->num_arenas = (rawsize / ARENA_MAX_SIZE) +
                        ((rawsize % ARENA_MAX_SIZE) ? 1 : 0);
                dev_dbg(dev, "init: %d arenas for %llu rawsize\n",
@@ -1369,7 +1396,7 @@ static struct btt *btt_init(struct nd_btt *nd_btt, 
unsigned long long rawsize,
                ret = btt_meta_init(btt);
                if (ret) {
                        dev_err(dev, "init: error in meta_init: %d\n", ret);
-                       return NULL;
+                       goto out_free;
                }
        }
 
@@ -1481,7 +1508,10 @@ static int nd_btt_remove(struct device *dev)
        struct nd_btt *nd_btt = to_nd_btt(dev);
        struct btt *btt = dev_get_drvdata(dev);
 
+       nvdimm_bus_lock(dev);
        btt_fini(btt);
+       nvdimm_bus_unlock(dev);
+
        unlink_btt(nd_btt);
 
        return 0;
diff --git a/drivers/nvdimm/btt_devs.c b/drivers/nvdimm/btt_devs.c
index 02e125b91e77..bcf77dca1532 100644
--- a/drivers/nvdimm/btt_devs.c
+++ b/drivers/nvdimm/btt_devs.c
@@ -122,7 +122,7 @@ static ssize_t backing_dev_show(struct device *dev,
                return sprintf(buf, "\n");
 }
 
-static const fmode_t nd_btt_devs_mode = FMODE_READ | FMODE_WRITE | FMODE_EXCL;
+static const fmode_t nd_btt_devs_mode = FMODE_READ | FMODE_EXCL;
 
 static void nd_btt_remove_bdev(struct nd_btt *nd_btt, const char *caller)
 {
@@ -363,6 +363,46 @@ u64 nd_btt_sb_checksum(struct btt_sb *btt_sb)
 }
 EXPORT_SYMBOL(nd_btt_sb_checksum);
 
+int set_btt_ro(struct block_device *bdev, struct device *dev, int ro)
+{
+       struct nd_btt *nd_btt = to_nd_btt(dev);
+
+       if (!dev->driver)
+               return 0;
+
+       /* we can only mark a btt device rw if its backing device is rw */
+       if (bdev_read_only(nd_btt->backing_dev) && !ro)
+               return -EBUSY;
+
+       set_device_ro(bdev, ro);
+       return 0;
+}
+
+int set_btt_disk_ro(struct device *dev, void *data)
+{
+       struct block_device *bdev = data;
+       struct nd_btt *nd_btt;
+       struct btt *btt;
+
+       if (!is_nd_btt(dev))
+               return 0;
+
+       nd_btt = to_nd_btt(dev);
+       if (nd_btt->backing_dev != bdev)
+               return 0;
+
+       /*
+        * We have the lock at this point and have flushed probing.  We
+        * are guaranteed that the btt driver is unbound, or has
+        * completed setup operations and is blocked from initiating
+        * disk teardown until we are done walking these pointers.
+        */
+       btt = dev_get_drvdata(dev);
+       if (btt && btt->btt_disk)
+               set_disk_ro(btt->btt_disk, 1);
+       return 0;
+}
+
 static struct nd_btt *__nd_btt_autodetect(struct nvdimm_bus *nvdimm_bus,
                struct block_device *bdev, struct btt_sb *btt_sb)
 {
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c
index d4fbc48f5643..47260ca573e0 100644
--- a/drivers/nvdimm/bus.c
+++ b/drivers/nvdimm/bus.c
@@ -309,6 +309,61 @@ void nvdimm_bus_remove_disk(struct gendisk *disk)
 }
 EXPORT_SYMBOL(nvdimm_bus_remove_disk);
 
+static int set_namespace_ro(struct block_device *bdev,
+               struct nvdimm_bus *nvdimm_bus, int ro)
+{
+       set_device_ro(bdev, ro);
+
+       /*
+        * It's possible to mark the backing device rw while leaving the
+        * btt device read-only.  However, marking a backing device
+        * read-only always marks the parent btt read-only.
+        */
+       if (!ro)
+               return 0;
+       return device_for_each_child(&nvdimm_bus->dev, bdev, set_btt_disk_ro);
+}
+
+int nvdimm_bdev_ioctl(struct block_device *bdev, fmode_t mode,
+               unsigned int cmd, unsigned long arg)
+{
+       int rc, ro;
+       struct gendisk *disk = bdev->bd_disk;
+       struct device *dev = disk->driverfs_dev;
+       struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
+
+       if (cmd != BLKROSET)
+               return -ENOTTY;
+
+       if (get_user(ro, (int __user *)(arg)))
+               return -EFAULT;
+
+       if (ro == 0 || ro == 1)
+               /* pass */;
+       else
+               return -EINVAL;
+
+       nvdimm_bus_lock(&nvdimm_bus->dev);
+       wait_nvdimm_bus_probe_idle(&nvdimm_bus->dev);
+       if (bdev_read_only(bdev) == ro)
+               rc = 0;
+       else if (is_nd_btt(dev))
+               rc = set_btt_ro(bdev, dev, ro);
+       else
+               rc = set_namespace_ro(bdev, nvdimm_bus, ro);
+       nvdimm_bus_unlock(&nvdimm_bus->dev);
+
+       return rc;
+}
+EXPORT_SYMBOL(nvdimm_bdev_ioctl);
+
+int nvdimm_bdev_compat_ioctl(struct block_device *bdev, fmode_t mode,
+               unsigned int cmd, unsigned long arg)
+{
+       return nvdimm_bdev_ioctl(bdev, mode, cmd, arg);
+}
+EXPORT_SYMBOL(nvdimm_bdev_compat_ioctl);
+
 static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
                char *buf)
 {
diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h
index 9a90915e6fd2..ba548d248b4e 100644
--- a/drivers/nvdimm/nd-core.h
+++ b/drivers/nvdimm/nd-core.h
@@ -49,11 +49,14 @@ bool is_nvdimm(struct device *dev);
 bool is_nd_pmem(struct device *dev);
 bool is_nd_blk(struct device *dev);
 struct gendisk;
+struct block_device;
 #if IS_ENABLED(CONFIG_ND_BTT_DEVS)
 bool is_nd_btt(struct device *dev);
 struct nd_btt *nd_btt_create(struct nvdimm_bus *nvdimm_bus);
 void nd_btt_add_disk(struct nvdimm_bus *nvdimm_bus, struct gendisk *disk);
 void nd_btt_remove_disk(struct nvdimm_bus *nvdimm_bus, struct gendisk *disk);
+int set_btt_ro(struct block_device *bdev, struct device *dev, int ro);
+int set_btt_disk_ro(struct device *dev, void *data);
 #else
 static inline bool is_nd_btt(struct device *dev)
 {
@@ -74,6 +77,17 @@ static inline void nd_btt_remove_disk(struct nvdimm_bus 
*nvdimm_bus,
                struct gendisk *disk)
 {
 }
+
+static inline int set_btt_ro(struct block_device *bdev, struct device *dev,
+               int ro)
+{
+       return 0;
+}
+
+static inline int set_btt_disk_ro(struct device *dev, void *data)
+{
+       return 0;
+}
 #endif
 struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev);
 int __init nvdimm_bus_init(void);
diff --git a/drivers/nvdimm/nd.h b/drivers/nvdimm/nd.h
index 3c4c8b6c64ec..2786eb8456ec 100644
--- a/drivers/nvdimm/nd.h
+++ b/drivers/nvdimm/nd.h
@@ -164,6 +164,10 @@ int nvdimm_bus_add_disk(struct gendisk *disk);
 int nvdimm_bus_add_integrity_disk(struct gendisk *disk, u32 lbasize,
                sector_t size);
 void nvdimm_bus_remove_disk(struct gendisk *disk);
+int nvdimm_bdev_ioctl(struct block_device *bdev, fmode_t mode,
+               unsigned int cmd, unsigned long arg);
+int nvdimm_bdev_compat_ioctl(struct block_device *bdev, fmode_t mode,
+               unsigned int cmd, unsigned long arg);
 void nvdimm_drvdata_release(struct kref *kref);
 void put_ndd(struct nvdimm_drvdata *ndd);
 int nd_label_reserve_dpa(struct nvdimm_drvdata *ndd);
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 3fd854a78f09..96964419b72d 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -131,6 +131,10 @@ static const struct block_device_operations pmem_fops = {
        .rw_page =              pmem_rw_page,
        .rw_bytes =             pmem_rw_bytes,
        .direct_access =        pmem_direct_access,
+       .ioctl =                nvdimm_bdev_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl =         nvdimm_bdev_ioctl,
+#endif
 };
 
 static struct pmem_device *pmem_alloc(struct device *dev,

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to