Bsg referencing parent device
Subsystems (4)
PATCH 1/1 Bsg referencing parent device
A follow-up on earlier discussions:
[PATCH] bsg referencing bus driver module
https://www.spinics.net/lists/linux-scsi/msg119631.html
[PATCH] Waiting for scsi_host_template release
https://www.spinics.net/lists/linux-scsi/msg119432.html
All these discussions are attempts to fix a crash after SCSI transport driver unload if a user-mode process holds a handle in BSG layer towards the unloaded driver via SCSI mid-layer:
[16834.636216,07] Call Trace:
... scsi_proc_hostdir_rm
[16834.641944,07] [<ffffffff8141723f>] scsi_host_dev_release+0x3f/0x130 [16834.647740,07] [<ffffffff813e4f82>] device_release+0x32/0xa0 [16834.653423,07] [<ffffffff812dc6c7>] kobject_cleanup+0x77/0x190 [16834.659002,07] [<ffffffff812dc585>] kobject_put+0x25/0x50 [16834.664430,07] [<ffffffff813e5277>] put_device+0x17/0x20 [16834.669740,07] [<ffffffff812d0334>] bsg_kref_release_function+0x24/0x30 [16834.675007,07] [<ffffffff812d14a6>] bsg_release+0x166/0x1d0 [16834.680148,07] [<ffffffff8119ba2b>] __fput+0xcb/0x1d0 [16834.685156,07] [<ffffffff8119bb6e>] ____fput+0xe/0x10 [16834.690017,07] [<ffffffff81077476>] task_work_run+0x86/0xb0 [16834.694781,07] [<ffffffff81057043>] exit_to_usermode_loop+0x6b/0x9a [16834.699466,07] [<ffffffff81002875>] syscall_return_slowpath+0x55/0x60 [16834.704110,07] [<ffffffff8172d615>] int_ret_from_sys_call+0x25/0x9f
The latest input from earlier discussions was to cut off access to the unloaded driver at bsg_unregister_queue time by calling blk_cleanup_queue. If we do that we still have to release the reference to the parent device (otherwise we crash with the same stack). The next logical step is, rather than maintaining a "part-time" reference to be dropped early, we discard referencing completely.
Discarding the reference turns out to be the only thing needed to fix the problem: all transport drivers already do blk_cleanup_queue before releasing their reference to the parent device.
Quoted 1 line
From
7eaa9b43f0b99766b1d197044eb4d2e549d11a24Mon Sep 17 00:00:00 2001
From: Anatoliy Glagolev <[email protected]>
Date: Wed, 16 May 2018 17:03:09 -0600
Subject: [PATCH] Bsg referencing parent device
Bsg holding reference to a parent device may result in a crash if user closes a bsg file handle after the parent device driver has unloaded.
Holding a reference is not really needed: parent device must exist between bsg_register_queue and bsg_unregister_queue. Before the device goes away the caller does blk_cleanup_queue so that all in-flight requests to the device are gone and all new requests cannot pass beyond the queue. The queue itself is a refcounted object and it will stay alive with a bsg file.
--- block/bsg.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-)
block/bsg.c +17 -20
@@ -650,18 +650,6 @@ static struct bsg_device *bsg_alloc_device(void)return bd; } -static void bsg_kref_release_function (struct kref *kref) -{ -struct bsg_class_device *bcd = - container_of(kref, struct bsg_class_device, ref); -struct device *parent = bcd->parent; - -if (bcd->release) - bcd->release (bcd->parent); - -put_device (parent); -} -static int bsg_put_device (struct bsg_device *bd) {int ret =0 , do_free;
@@ -694,7 +682,6 @@ static int bsg_put_device(struct bsg_device *bd)kfree (bd); out: -kref_put (&q->bsg_dev.ref, bsg_kref_release_function);if (do_free)blk_put_queue (q);return ret;
@@ -760,8 +747,6 @@ static struct bsg_device *bsg_get_device(struct inode *inode, struct file *file)*/ mutex_lock (&bsg_mutex); bcd =idr_find (&bsg_minor_idr,iminor (inode)); -if (bcd) -kref_get (&bcd->ref);mutex_unlock (&bsg_mutex);if (!bcd)
@@ -772,8 +757,6 @@ static struct bsg_device *bsg_get_device(struct inode *inode, struct file *file)return bd; bd =bsg_add_device (inode, bcd->queue, file); -if (IS_ERR (bd)) -kref_put (&bcd->ref, bsg_kref_release_function);return bd; }
@@ -902,6 +885,8 @@ static long bsg_ioctl(struct file *file, unsigned int cmd, unsigned long arg)void bsg_unregister_queue (struct request_queue *q) { +struct device *parent; +void (*release)(struct device *dev);struct bsg_class_device *bcd = &q->bsg_dev;if (!bcd->class_dev)
@@ -913,8 +898,21 @@ void bsg_unregister_queue(struct request_queue *q)sysfs_remove_link (&q->kobj,"bsg" );device_unregister (bcd->class_dev); bcd->class_dev =NULL ; -kref_put (&bcd->ref, bsg_kref_release_function); + parent = bcd->parent; + release = bcd->release; + bcd->parent =NULL ; + bcd->release =NULL ;mutex_unlock (&bsg_mutex); + +/* +* The caller of bsg_[un]register_queue must hold a reference +* to the parent device between ..register.. and ..unregister.. +* so we do not maintain a refcount to parent here. +* Note: the caller is expected to blk_cleanup_queue(q) +* before releasing the reference to the parent device. +*/ +if (release) +release (parent); }EXPORT_SYMBOL_GPL (bsg_unregister_queue);
@@ -955,10 +953,9 @@ int bsg_register_queue(struct request_queue *q, struct device *parent, bcd->minor = ret; bcd->queue = q; - bcd->parent =get_device (parent); + bcd->parent = parent; bcd->release = release; bcd->ops = ops; -kref_init (&bcd->ref); dev =MKDEV (bsg_major, bcd->minor); class_dev =device_create (bsg_class, parent, dev,NULL ,"%s" , devname);if (IS_ERR (class_dev)) {
Signature · 1 line
1.9.1
The idea looks pretty reasonable, but once that is done we can get rid of the ->release callback entirely and just handle it in the callers. Something like the untested patch below:
block/bsg-lib.c +2 -4
@@ -303,11 +303,9 @@ static void bsg_exit_rq(struct request_queue *q, struct request *req)* @name: device to give bsg device * @job_fn: bsg job handler * @dd_job_size: size of LLD data needed for each job -* @release: @dev release function */ struct request_queue *bsg_setup_queue (struct device *dev,const char *name, -bsg_job_fn *job_fn,int dd_job_size, -void (*release)(struct device *)) +bsg_job_fn *job_fn,int dd_job_size) {struct request_queue *q;int ret;
@@ -331,7 +329,7 @@ struct request_queue *bsg_setup_queue(struct device *dev, const char *name,blk_queue_softirq_done (q, bsg_softirq_done);blk_queue_rq_timeout (q,BLK_DEFAULT_SG_TIMEOUT ); - ret =bsg_register_queue (q, dev, name, &bsg_transport_ops, release); + ret =bsg_register_queue (q, dev, name, &bsg_transport_ops);if (ret) {printk (KERN_ERR "%s: bsg interface failed to " "initialize - register queue\n" , dev->kobj.name);
block/bsg.c +5 -34
@@ -650,18 +650,6 @@ static struct bsg_device *bsg_alloc_device(void)return bd; } -static void bsg_kref_release_function (struct kref *kref) -{ -struct bsg_class_device *bcd = - container_of(kref, struct bsg_class_device, ref); -struct device *parent = bcd->parent; - -if (bcd->release) - bcd->release (bcd->parent); - -put_device (parent); -} -static int bsg_put_device (struct bsg_device *bd) {int ret =0 , do_free;
@@ -694,7 +682,6 @@ static int bsg_put_device(struct bsg_device *bd)kfree (bd); out: -kref_put (&q->bsg_dev.ref, bsg_kref_release_function);if (do_free)blk_put_queue (q);return ret;
@@ -760,8 +747,6 @@ static struct bsg_device *bsg_get_device(struct inode *inode, struct file *file)*/ mutex_lock (&bsg_mutex); bcd =idr_find (&bsg_minor_idr,iminor (inode)); -if (bcd) -kref_get (&bcd->ref);mutex_unlock (&bsg_mutex);if (!bcd)
@@ -772,8 +757,6 @@ static struct bsg_device *bsg_get_device(struct inode *inode, struct file *file)return bd; bd =bsg_add_device (inode, bcd->queue, file); -if (IS_ERR (bd)) -kref_put (&bcd->ref, bsg_kref_release_function);return bd; }
@@ -913,25 +896,17 @@ void bsg_unregister_queue(struct request_queue *q)sysfs_remove_link (&q->kobj,"bsg" );device_unregister (bcd->class_dev); bcd->class_dev =NULL ; -kref_put (&bcd->ref, bsg_kref_release_function);mutex_unlock (&bsg_mutex); }EXPORT_SYMBOL_GPL (bsg_unregister_queue);int bsg_register_queue (struct request_queue *q,struct device *parent, -const char *name,const struct bsg_ops *ops, -void (*release)(struct device *)) +const char *name,const struct bsg_ops *ops) {struct bsg_class_device *bcd;dev_t dev;int ret;struct device *class_dev =NULL ; -const char *devname; - -if (name) - devname = name; -else - devname =dev_name (parent);/* * we need a proper transport to send commands, not a stacked device
@@ -955,15 +930,12 @@ int bsg_register_queue(struct request_queue *q, struct device *parent, bcd->minor = ret; bcd->queue = q; - bcd->parent =get_device (parent); - bcd->release = release; bcd->ops = ops; -kref_init (&bcd->ref); dev =MKDEV (bsg_major, bcd->minor); - class_dev =device_create (bsg_class, parent, dev,NULL ,"%s" , devname); + class_dev =device_create (bsg_class, parent, dev,NULL ,"%s" , name);if (IS_ERR (class_dev)) { ret =PTR_ERR (class_dev); - goto put_dev; + goto idr_remove; } bcd->class_dev = class_dev;
@@ -978,8 +950,7 @@ int bsg_register_queue(struct request_queue *q, struct device *parent, unregister_class_dev:device_unregister (class_dev); -put_dev: -put_device (parent); +idr_remove:idr_remove (&bsg_minor_idr, bcd->minor); unlock:mutex_unlock (&bsg_mutex);
@@ -993,7 +964,7 @@ int bsg_scsi_register_queue(struct request_queue *q, struct device *parent)return -EINVAL ; } -return bsg_register_queue (q, parent,NULL , &bsg_scsi_ops,NULL ); +return bsg_register_queue (q, parent,dev_name (parent), &bsg_scsi_ops); }EXPORT_SYMBOL_GPL (bsg_scsi_register_queue);
drivers/scsi/scsi_transport_fc.c +3 -4
@@ -3781,8 +3781,7 @@ fc_bsg_hostadd(struct Scsi_Host *shost, struct fc_host_attrs *fc_host)snprintf (bsg_name,sizeof (bsg_name),"fc_host%d" , shost->host_no); - q =bsg_setup_queue (dev, bsg_name, fc_bsg_dispatch, i->f->dd_bsg_size, -NULL ); + q =bsg_setup_queue (dev, bsg_name, fc_bsg_dispatch, i->f->dd_bsg_size);if (IS_ERR (q)) { dev_err(dev, "fc_host%d: bsg interface failed to initialize - setup queue\n",
@@ -3827,8 +3826,8 @@ fc_bsg_rportadd(struct Scsi_Host *shost, struct fc_rport *rport)if (!i->f->bsg_request)return -ENOTSUPP ; - q =bsg_setup_queue (dev,NULL , fc_bsg_dispatch, i->f->dd_bsg_size, -NULL ); + q =bsg_setup_queue (dev,dev_name (dev), fc_bsg_dispatch, + i->f->dd_bsg_size);if (IS_ERR (q)) {dev_err (dev,"failed to setup bsg queue\n" );return PTR_ERR (q);
drivers/scsi/scsi_transport_iscsi.c +1 -1
@@ -1542,7 +1542,7 @@ iscsi_bsg_host_add(struct Scsi_Host *shost, struct iscsi_cls_host *ihost)return -ENOTSUPP ;snprintf (bsg_name,sizeof (bsg_name),"iscsi_host%d" , shost->host_no); - q =bsg_setup_queue (dev, bsg_name, iscsi_bsg_host_dispatch,0 ,NULL ); + q =bsg_setup_queue (dev, bsg_name, iscsi_bsg_host_dispatch,0 );if (IS_ERR (q)) {shost_printk (KERN_ERR , shost,"bsg interface failed to " "initialize - no request queue\n" );
drivers/scsi/scsi_transport_sas.c +6 -13
@@ -187,16 +187,6 @@ static int sas_smp_dispatch(struct bsg_job *job)return 0 ; } -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); -} -static int sas_bsg_initialize (struct Scsi_Host *shost,struct sas_rphy *rphy) {struct request_queue *q;
@@ -208,7 +198,7 @@ static int sas_bsg_initialize(struct Scsi_Host *shost, struct sas_rphy *rphy)if (rphy) { q =bsg_setup_queue (&rphy->dev,dev_name (&rphy->dev), - sas_smp_dispatch,0 ,NULL ); + sas_smp_dispatch,0 );if (IS_ERR (q))return PTR_ERR (q); rphy->q = q;
@@ -217,7 +207,7 @@ static int sas_bsg_initialize(struct Scsi_Host *shost, struct sas_rphy *rphy)snprintf (name,sizeof (name),"sas_host%d" , shost->host_no); q =bsg_setup_queue (&shost->shost_gendev, name, - sas_smp_dispatch,0 , sas_host_release); + sas_smp_dispatch,0 );if (IS_ERR (q))return PTR_ERR (q);to_sas_host_attrs (shost)->q = q;
@@ -260,8 +250,11 @@ static int sas_host_remove(struct transport_container *tc, struct device *dev,struct Scsi_Host *shost =dev_to_shost (dev);struct request_queue *q =to_sas_host_attrs (shost)->q; -if (q) +if (q) {bsg_unregister_queue (q); +blk_cleanup_queue (q); + } +return 0 ; }
include/linux/bsg-lib.h +1 -2
@@ -72,8 +72,7 @@ struct bsg_job {void bsg_job_done (struct bsg_job *job,int result,unsigned int reply_payload_rcv_len);struct request_queue *bsg_setup_queue (struct device *dev,const char *name, -bsg_job_fn *job_fn,int dd_job_size, -void (*release)(struct device *)); +bsg_job_fn *job_fn,int dd_job_size);void bsg_job_put (struct bsg_job *job);int __must_check bsg_job_get (struct bsg_job *job);
include/linux/bsg.h +1 -5
@@ -17,17 +17,13 @@ struct bsg_ops {struct bsg_class_device {struct device *class_dev; -struct device *parent;int minor;struct request_queue *queue; -struct kref ref;const struct bsg_ops *ops; -void (*release)(struct device *); };int bsg_register_queue (struct request_queue *q,struct device *parent, -const char *name,const struct bsg_ops *ops, -void (*release)(struct device *)); +const char *name,const struct bsg_ops *ops);int bsg_scsi_register_queue (struct request_queue *q,struct device *parent);void bsg_unregister_queue (struct request_queue *q);#else