firewire: fw-sbp2: fix refcounting
authorStefan Richter <stefanr@s5r6.in-berlin.de>
Wed, 7 Nov 2007 00:11:56 +0000 (01:11 +0100)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Wed, 7 Nov 2007 00:59:28 +0000 (01:59 +0100)
Since patch "fw-sbp2: use an own workqueue (fix system responsiveness)"
increased parallelism between fw-sbp2 and fw-core, it was possible that
fw-sbp2 didn't release the SCSI device when the FireWire device was
disconnected.

This happened if sbp2_update() ran during sbp2_login(), because a bus
reset occurred during sbp2_login().  The sbp2_login() work would [try
to] reschedule itself because it failed due to the bus reset, and it
would _not_ drop its reference on the target.  However, sbp2_update()
would schedule sbp2_login() too before sbp2_login() rescheduled itself
and hence sbp2_update() would take an additional reference.  And then
we would have one reference too many.

The fix is to _always_ drop the reference when leaving the sbp2_login()
work.  If the sbp2_login() work reschedules itself, it takes a
reference, but only if it wasn't already rescheduled by sbp2_update().

Ditto in the sbp2_reconnect() work.

The resulting code is actually simpler than before:  We _always_ take
a reference when successfully scheduling work.  And we _always_ drop
a reference when leaving a workqueue job.  No exceptions.

Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
drivers/firewire/fw-sbp2.c

index 5596df65c8ed3b11d49c91d97cc36f3e858478c0..624ff3e082f68116f4efe158c80fcdd0c7f27a74 100644 (file)
@@ -650,13 +650,14 @@ static void sbp2_login(struct work_struct *work)
        if (sbp2_send_management_orb(lu, node_id, generation,
                                SBP2_LOGIN_REQUEST, lu->lun, &response) < 0) {
                if (lu->retries++ < 5) {
-                       queue_delayed_work(sbp2_wq, &lu->work,
-                                          DIV_ROUND_UP(HZ, 5));
+                       if (queue_delayed_work(sbp2_wq, &lu->work,
+                                              DIV_ROUND_UP(HZ, 5)))
+                               kref_get(&lu->tgt->kref);
                } else {
                        fw_error("failed to login to %s LUN %04x\n",
                                 unit->device.bus_id, lu->lun);
-                       kref_put(&lu->tgt->kref, sbp2_release_target);
                }
+               kref_put(&lu->tgt->kref, sbp2_release_target);
                return;
        }
 
@@ -914,7 +915,9 @@ static void sbp2_reconnect(struct work_struct *work)
                        lu->retries = 0;
                        PREPARE_DELAYED_WORK(&lu->work, sbp2_login);
                }
-               queue_delayed_work(sbp2_wq, &lu->work, DIV_ROUND_UP(HZ, 5));
+               if (queue_delayed_work(sbp2_wq, &lu->work, DIV_ROUND_UP(HZ, 5)))
+                       kref_get(&lu->tgt->kref);
+               kref_put(&lu->tgt->kref, sbp2_release_target);
                return;
        }