[PATCH] scsi_transport_sas: fix the lifetime of bsg queues

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



We call blk_cleanup_queue too early for bsg queues for SAS. If we hold
a sas_host device (end_device or expander) open, remove the device,
then send a request to it, we get a kernel crash. This bug is similar
to the lifetime of bsg for scsi devices that Pete reported.

We need to call blk_cleanup_queue in the release function as we do
with scsi devices.

It's easy to fix end_device and expander since they have the release
function (we can just move blk_cleanup_queue to sas_expander_release
and sas_end_device_release from sas_bsg_remove).

A sas_host device doesn't have the release function; struct
sas_host_attrs is freed with scsi_host in scsi_host_dev_release. The
probleme is that scsi_host_dev_release can't do transport specific
stuff.

This patch creates sas_host_release by adding struct device to struct
sas_host_attrs. This necessitates changing the bsg_register_queue()
prototype to take two pointers to struct device. bsg holds a ref to
one and passes another to class_device_create since sas_host_attrs's
device isn't registered in sysfs so it can't be passed to
class_device_create.

Another possible solution is adding to a release hook to
scsi_transport_template and scsi_host_dev_release calls it. Probably,
it's more simple though the hook will not be useful for non SAS
transports.

This patch against scsi-post-merge with bsg fixes for scsi devices:

http://marc.info/?l=linux-scsi&m=120692552424155&w=2


===
From: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx>
Subject: [PATCH] scsi_transport_sas: fix the lifetime of bsg queues

We call blk_cleanup_queue too early for bsg queues. If we hold a
sas_host device (end_device, or expander) open, remove the device,
then send a request to it, we get a kernel crash.

We need to call blk_cleanup_queue in the release function as we do
with scsi devices.

This patch moves blk_cleanup_queue to sas_expander_release and
sas_end_device_release from sas_bsg_remove.

This patch also creates sas_host_release by adding struct device to
struct sas_host_attrs. This necessitates changing the
bsg_register_queue() prototype to take two pointers to struct
device. bsg holds a ref to one and passes another to
class_device_create since sas_host_attrs's device isn't registered in
sysfs so it can't be passed to class_device_create.

Signed-off-by: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx>
Cc: Jens Axboe <jens.axboe@xxxxxxxxxx>
Cc: James Bottomley <James.Bottomley@xxxxxxxxxxxxxxxxxxxxx>
---
 block/bsg.c                       |   11 +++++---
 drivers/scsi/scsi_sysfs.c         |    2 +-
 drivers/scsi/scsi_transport_sas.c |   54 ++++++++++++++++++++++++++++++------
 include/linux/bsg.h               |    7 +++-
 4 files changed, 58 insertions(+), 16 deletions(-)

diff --git a/block/bsg.c b/block/bsg.c
index f61d9d4..eedfb0d 100644
--- a/block/bsg.c
+++ b/block/bsg.c
@@ -947,8 +947,8 @@ void bsg_unregister_queue(struct request_queue *q)
 }
 EXPORT_SYMBOL_GPL(bsg_unregister_queue);
 
-int bsg_register_queue(struct request_queue *q, struct device *gdev,
-		       const char *name)
+int bsg_register_queue(struct request_queue *q, struct device *parent,
+		       struct device *gdev, const char *name)
 {
 	struct bsg_class_device *bcd;
 	dev_t dev;
@@ -956,10 +956,13 @@ int bsg_register_queue(struct request_queue *q, struct device *gdev,
 	struct device *class_dev = NULL;
 	const char *devname;
 
+	if (!gdev)
+		gdev = parent;
+
 	if (name)
 		devname = name;
 	else
-		devname = gdev->bus_id;
+		devname = parent->bus_id;
 
 	/*
 	 * we need a proper transport to send commands, not a stacked device
@@ -992,7 +995,7 @@ int bsg_register_queue(struct request_queue *q, struct device *gdev,
 	bcd->queue = q;
 	bcd->dev = get_device(gdev);
 	dev = MKDEV(bsg_major, bcd->minor);
-	class_dev = device_create(bsg_class, gdev, dev, "%s", devname);
+	class_dev = device_create(bsg_class, parent, dev, "%s", devname);
 	if (IS_ERR(class_dev)) {
 		ret = PTR_ERR(class_dev);
 		goto put_dev;
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 198aa45..049103f 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -889,7 +889,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 		goto out;
 	}
 
-	error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL);
+	error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL, NULL);
 
 	if (error)
 		sdev_printk(KERN_INFO, sdev,
diff --git a/drivers/scsi/scsi_transport_sas.c b/drivers/scsi/scsi_transport_sas.c
index 27ec625..5ee1a85 100644
--- a/drivers/scsi/scsi_transport_sas.c
+++ b/drivers/scsi/scsi_transport_sas.c
@@ -43,6 +43,7 @@ struct sas_host_attrs {
 	struct list_head rphy_list;
 	struct mutex lock;
 	struct request_queue *q;
+	struct device sas_host_dev;
 	u32 next_target_id;
 	u32 next_expander_id;
 	int next_port_id;
@@ -192,11 +193,24 @@ static void sas_non_host_smp_request(struct request_queue *q)
 	sas_smp_request(q, rphy_to_shost(rphy), rphy);
 }
 
+static void sas_host_release(struct device *dev)
+{
+	struct Scsi_Host *shost = dev_to_shost(dev);
+	struct sas_host_attrs *sas_host = to_sas_host_attrs(shost);
+	struct request_queue *q = sas_host->q;
+
+	if (q) {
+		blk_cleanup_queue(q);
+		put_device(dev->parent);
+	}
+}
+
 static int sas_bsg_initialize(struct Scsi_Host *shost, struct sas_rphy *rphy)
 {
+	struct sas_host_attrs *sas_host = to_sas_host_attrs(shost);
 	struct request_queue *q;
 	int error;
-	struct device *dev;
+	struct device *parent, *dev = NULL;
 	char namebuf[BUS_ID_SIZE];
 	const char *name;
 
@@ -207,22 +221,32 @@ static int sas_bsg_initialize(struct Scsi_Host *shost, struct sas_rphy *rphy)
 
 	if (rphy) {
 		q = blk_init_queue(sas_non_host_smp_request, NULL);
-		dev = &rphy->dev;
-		name = dev->bus_id;
+
+		parent = &rphy->dev;
+		name = parent->bus_id;
 	} else {
 		q = blk_init_queue(sas_host_smp_request, NULL);
-		dev = &shost->shost_gendev;
+
+		parent = &shost->shost_gendev;
+		dev = &sas_host->sas_host_dev;
+		device_initialize(dev);
+		dev->parent = get_device(&shost->shost_gendev);
+		dev->release = sas_host_release;
+
 		snprintf(namebuf, sizeof(namebuf),
 			 "sas_host%d", shost->host_no);
 		name = namebuf;
 	}
-	if (!q)
-		return -ENOMEM;
 
-	error = bsg_register_queue(q, dev, name);
+	if (!q) {
+		error = -ENOMEM;
+		goto out;
+	}
+
+	error = bsg_register_queue(q, parent, dev, name);
 	if (error) {
 		blk_cleanup_queue(q);
-		return -ENOMEM;
+		goto out;
 	}
 
 	if (rphy)
@@ -238,6 +262,11 @@ static int sas_bsg_initialize(struct Scsi_Host *shost, struct sas_rphy *rphy)
 	set_bit(QUEUE_FLAG_BIDI, &q->queue_flags);
 
 	return 0;
+out:
+	if (dev)
+		put_device(dev->parent);
+
+	return error;
 }
 
 static void sas_bsg_remove(struct Scsi_Host *shost, struct sas_rphy *rphy)
@@ -253,7 +282,6 @@ static void sas_bsg_remove(struct Scsi_Host *shost, struct sas_rphy *rphy)
 		return;
 
 	bsg_unregister_queue(q);
-	blk_cleanup_queue(q);
 }
 
 /*
@@ -283,8 +311,10 @@ static int sas_host_remove(struct transport_container *tc, struct device *dev,
 			   struct device *cdev)
 {
 	struct Scsi_Host *shost = dev_to_shost(dev);
+	struct sas_host_attrs *sas_host = to_sas_host_attrs(shost);
 
 	sas_bsg_remove(shost, NULL);
+	put_device(&sas_host->sas_host_dev);
 
 	return 0;
 }
@@ -1301,6 +1331,9 @@ static void sas_expander_release(struct device *dev)
 	struct sas_rphy *rphy = dev_to_rphy(dev);
 	struct sas_expander_device *edev = rphy_to_expander_device(rphy);
 
+	if (rphy->q)
+		blk_cleanup_queue(rphy->q);
+
 	put_device(dev->parent);
 	kfree(edev);
 }
@@ -1310,6 +1343,9 @@ static void sas_end_device_release(struct device *dev)
 	struct sas_rphy *rphy = dev_to_rphy(dev);
 	struct sas_end_device *edev = rphy_to_end_device(rphy);
 
+	if (rphy->q)
+		blk_cleanup_queue(rphy->q);
+
 	put_device(dev->parent);
 	kfree(edev);
 }
diff --git a/include/linux/bsg.h b/include/linux/bsg.h
index e8406c5..67e37b9 100644
--- a/include/linux/bsg.h
+++ b/include/linux/bsg.h
@@ -61,10 +61,13 @@ struct bsg_class_device {
 	struct request_queue *queue;
 };
 
-extern int bsg_register_queue(struct request_queue *, struct device *, const char *);
+extern int bsg_register_queue(struct request_queue *, struct device *,
+			      struct device *, const char *);
 extern void bsg_unregister_queue(struct request_queue *);
 #else
-static inline int bsg_register_queue(struct request_queue * rq, struct device *dev, const char *name)
+static inline int bsg_register_queue(struct request_queue *rq,
+				     struct device *parent, struct device *dev,
+				     const char *name)
 {
 	return 0;
 }
-- 
1.5.3.6

--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [SCSI Target Devel]     [Linux SCSI Target Infrastructure]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Linux IIO]     [Samba]     [Device Mapper]
  Powered by Linux