Commit 188c3568 authored by Keith Busch's avatar Keith Busch Committed by Jens Axboe

NVMe: Reference count open namespaces

Dynamic namespace attachment means the namespace may be removed at any
time, so the namespace reference count can not be tied to the device
reference count. This fixes a NULL dereference if an opened namespace
is detached from a controller.
Signed-off-by: default avatarKeith Busch <keith.busch@intel.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarJens Axboe <axboe@fb.com>
parent 54ef2b96
...@@ -1943,6 +1943,18 @@ static int nvme_compat_ioctl(struct block_device *bdev, fmode_t mode, ...@@ -1943,6 +1943,18 @@ static int nvme_compat_ioctl(struct block_device *bdev, fmode_t mode,
#define nvme_compat_ioctl NULL #define nvme_compat_ioctl NULL
#endif #endif
static void nvme_free_ns(struct kref *kref)
{
struct nvme_ns *ns = container_of(kref, struct nvme_ns, kref);
spin_lock(&dev_list_lock);
ns->disk->private_data = NULL;
spin_unlock(&dev_list_lock);
put_disk(ns->disk);
kfree(ns);
}
static int nvme_open(struct block_device *bdev, fmode_t mode) static int nvme_open(struct block_device *bdev, fmode_t mode)
{ {
int ret = 0; int ret = 0;
...@@ -1952,21 +1964,25 @@ static int nvme_open(struct block_device *bdev, fmode_t mode) ...@@ -1952,21 +1964,25 @@ static int nvme_open(struct block_device *bdev, fmode_t mode)
ns = bdev->bd_disk->private_data; ns = bdev->bd_disk->private_data;
if (!ns) if (!ns)
ret = -ENXIO; ret = -ENXIO;
else if (!kref_get_unless_zero(&ns->dev->kref)) else if (!kref_get_unless_zero(&ns->kref))
ret = -ENXIO; ret = -ENXIO;
else if (!kref_get_unless_zero(&ns->dev->kref)) {
kref_put(&ns->kref, nvme_free_ns);
ret = -ENXIO;
}
spin_unlock(&dev_list_lock); spin_unlock(&dev_list_lock);
return ret; return ret;
} }
static void nvme_free_dev(struct kref *kref); static void nvme_free_dev(struct kref *kref);
static void nvme_release(struct gendisk *disk, fmode_t mode) static void nvme_release(struct gendisk *disk, fmode_t mode)
{ {
struct nvme_ns *ns = disk->private_data; struct nvme_ns *ns = disk->private_data;
struct nvme_dev *dev = ns->dev; struct nvme_dev *dev = ns->dev;
kref_put(&dev->kref, nvme_free_dev); kref_put(&dev->kref, nvme_free_dev);
kref_put(&ns->kref, nvme_free_ns);
} }
static int nvme_getgeo(struct block_device *bd, struct hd_geometry *geo) static int nvme_getgeo(struct block_device *bd, struct hd_geometry *geo)
...@@ -2126,6 +2142,7 @@ static void nvme_alloc_ns(struct nvme_dev *dev, unsigned nsid) ...@@ -2126,6 +2142,7 @@ static void nvme_alloc_ns(struct nvme_dev *dev, unsigned nsid)
if (!disk) if (!disk)
goto out_free_queue; goto out_free_queue;
kref_init(&ns->kref);
ns->ns_id = nsid; ns->ns_id = nsid;
ns->disk = disk; ns->disk = disk;
ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */ ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */
...@@ -2360,13 +2377,7 @@ static int nvme_setup_io_queues(struct nvme_dev *dev) ...@@ -2360,13 +2377,7 @@ static int nvme_setup_io_queues(struct nvme_dev *dev)
static void nvme_free_namespace(struct nvme_ns *ns) static void nvme_free_namespace(struct nvme_ns *ns)
{ {
list_del(&ns->list); list_del(&ns->list);
kref_put(&ns->kref, nvme_free_ns);
spin_lock(&dev_list_lock);
ns->disk->private_data = NULL;
spin_unlock(&dev_list_lock);
put_disk(ns->disk);
kfree(ns);
} }
static int ns_cmp(void *priv, struct list_head *a, struct list_head *b) static int ns_cmp(void *priv, struct list_head *a, struct list_head *b)
......
...@@ -135,6 +135,7 @@ struct nvme_ns { ...@@ -135,6 +135,7 @@ struct nvme_ns {
struct nvme_dev *dev; struct nvme_dev *dev;
struct request_queue *queue; struct request_queue *queue;
struct gendisk *disk; struct gendisk *disk;
struct kref kref;
unsigned ns_id; unsigned ns_id;
int lba_shift; int lba_shift;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment