scsi: libfc: Fixup disc_mutex handling
authorHannes Reinecke <hare@suse.de>
Fri, 30 Sep 2016 09:01:15 +0000 (11:01 +0200)
committerMartin K. Petersen <martin.petersen@oracle.com>
Tue, 8 Nov 2016 22:29:46 +0000 (17:29 -0500)
The list of attached 'rdata' remote port structures is RCU
protected, so there is no need to take the 'disc_mutex' when
traversing it.
Rather we should be using rcu_read_lock() and kref_get_unless_zero()
to validate the entries.
We need, however, take the disc_mutex when deleting an entry;
otherwise we risk clashes with list_add.

Signed-off-by: Hannes Reinecke <hare@suse.com>
Acked-by: Johannes Thumshirn <jth@kernel.org>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/fcoe/fcoe_ctlr.c
drivers/scsi/libfc/fc_disc.c
drivers/scsi/libfc/fc_lport.c
drivers/scsi/libfc/fc_rport.c

index dcf36537a767c72d9a2e956115e09f7d5f82dde9..9bba58191b3d32da3ad22da4cfc506bafc3ba8ea 100644 (file)
@@ -2145,9 +2145,15 @@ static void fcoe_ctlr_disc_stop_locked(struct fc_lport *lport)
 {
        struct fc_rport_priv *rdata;
 
+       rcu_read_lock();
+       list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) {
+               if (kref_get_unless_zero(&rdata->kref)) {
+                       lport->tt.rport_logoff(rdata);
+                       kref_put(&rdata->kref, lport->tt.rport_destroy);
+               }
+       }
+       rcu_read_unlock();
        mutex_lock(&lport->disc.disc_mutex);
-       list_for_each_entry_rcu(rdata, &lport->disc.rports, peers)
-               lport->tt.rport_logoff(rdata);
        lport->disc.disc_callback = NULL;
        mutex_unlock(&lport->disc.disc_mutex);
 }
@@ -2472,17 +2478,22 @@ static void fcoe_ctlr_vn_add(struct fcoe_ctlr *fip, struct fc_rport_priv *new)
                mutex_unlock(&lport->disc.disc_mutex);
                return;
        }
+       mutex_lock(&rdata->rp_mutex);
+       mutex_unlock(&lport->disc.disc_mutex);
 
        rdata->ops = &fcoe_ctlr_vn_rport_ops;
        rdata->disc_id = lport->disc.disc_id;
 
        ids = &rdata->ids;
        if ((ids->port_name != -1 && ids->port_name != new->ids.port_name) ||
-           (ids->node_name != -1 && ids->node_name != new->ids.node_name))
+           (ids->node_name != -1 && ids->node_name != new->ids.node_name)) {
+               mutex_unlock(&rdata->rp_mutex);
                lport->tt.rport_logoff(rdata);
+               mutex_lock(&rdata->rp_mutex);
+       }
        ids->port_name = new->ids.port_name;
        ids->node_name = new->ids.node_name;
-       mutex_unlock(&lport->disc.disc_mutex);
+       mutex_unlock(&rdata->rp_mutex);
 
        frport = fcoe_ctlr_rport(rdata);
        LIBFCOE_FIP_DBG(fip, "vn_add rport %6.6x %s\n",
@@ -2638,11 +2649,15 @@ static unsigned long fcoe_ctlr_vn_age(struct fcoe_ctlr *fip)
        unsigned long deadline;
 
        next_time = jiffies + msecs_to_jiffies(FIP_VN_BEACON_INT * 10);
-       mutex_lock(&lport->disc.disc_mutex);
+       rcu_read_lock();
        list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) {
+               if (!kref_get_unless_zero(&rdata->kref))
+                       continue;
                frport = fcoe_ctlr_rport(rdata);
-               if (!frport->time)
+               if (!frport->time) {
+                       kref_put(&rdata->kref, lport->tt.rport_destroy);
                        continue;
+               }
                deadline = frport->time +
                           msecs_to_jiffies(FIP_VN_BEACON_INT * 25 / 10);
                if (time_after_eq(jiffies, deadline)) {
@@ -2653,8 +2668,9 @@ static unsigned long fcoe_ctlr_vn_age(struct fcoe_ctlr *fip)
                        lport->tt.rport_logoff(rdata);
                } else if (time_before(deadline, next_time))
                        next_time = deadline;
+               kref_put(&rdata->kref, lport->tt.rport_destroy);
        }
-       mutex_unlock(&lport->disc.disc_mutex);
+       rcu_read_unlock();
        return next_time;
 }
 
@@ -2991,12 +3007,17 @@ static void fcoe_ctlr_vn_disc(struct fcoe_ctlr *fip)
        mutex_lock(&disc->disc_mutex);
        callback = disc->pending ? disc->disc_callback : NULL;
        disc->pending = 0;
+       mutex_unlock(&disc->disc_mutex);
+       rcu_read_lock();
        list_for_each_entry_rcu(rdata, &disc->rports, peers) {
+               if (!kref_get_unless_zero(&rdata->kref))
+                       continue;
                frport = fcoe_ctlr_rport(rdata);
                if (frport->time)
                        lport->tt.rport_login(rdata);
+               kref_put(&rdata->kref, lport->tt.rport_destroy);
        }
-       mutex_unlock(&disc->disc_mutex);
+       rcu_read_unlock();
        if (callback)
                callback(lport, DISC_EV_SUCCESS);
 }
index 880a9068ca1268baf851ea8804f3bdb4216049f6..ad3965f9d03de4d5415f409340e0b5cf16e0cd4f 100644 (file)
@@ -68,10 +68,14 @@ static void fc_disc_stop_rports(struct fc_disc *disc)
 
        lport = fc_disc_lport(disc);
 
-       mutex_lock(&disc->disc_mutex);
-       list_for_each_entry_rcu(rdata, &disc->rports, peers)
-               lport->tt.rport_logoff(rdata);
-       mutex_unlock(&disc->disc_mutex);
+       rcu_read_lock();
+       list_for_each_entry_rcu(rdata, &disc->rports, peers) {
+               if (kref_get_unless_zero(&rdata->kref)) {
+                       lport->tt.rport_logoff(rdata);
+                       kref_put(&rdata->kref, lport->tt.rport_destroy);
+               }
+       }
+       rcu_read_unlock();
 }
 
 /**
@@ -289,15 +293,19 @@ static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event)
         * Skip ports which were never discovered.  These are the dNS port
         * and ports which were created by PLOGI.
         */
+       rcu_read_lock();
        list_for_each_entry_rcu(rdata, &disc->rports, peers) {
-               if (!rdata->disc_id)
+               if (!kref_get_unless_zero(&rdata->kref))
                        continue;
-               if (rdata->disc_id == disc->disc_id)
-                       lport->tt.rport_login(rdata);
-               else
-                       lport->tt.rport_logoff(rdata);
+               if (rdata->disc_id) {
+                       if (rdata->disc_id == disc->disc_id)
+                               lport->tt.rport_login(rdata);
+                       else
+                               lport->tt.rport_logoff(rdata);
+               }
+               kref_put(&rdata->kref, lport->tt.rport_destroy);
        }
-
+       rcu_read_unlock();
        mutex_unlock(&disc->disc_mutex);
        disc->disc_callback(lport, event);
        mutex_lock(&disc->disc_mutex);
@@ -592,7 +600,6 @@ static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
        lport = rdata->local_port;
        disc = &lport->disc;
 
-       mutex_lock(&disc->disc_mutex);
        if (PTR_ERR(fp) == -FC_EX_CLOSED)
                goto out;
        if (IS_ERR(fp))
@@ -607,16 +614,19 @@ static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
                        goto redisc;
                pn = (struct fc_ns_gid_pn *)(cp + 1);
                port_name = get_unaligned_be64(&pn->fn_wwpn);
+               mutex_lock(&rdata->rp_mutex);
                if (rdata->ids.port_name == -1)
                        rdata->ids.port_name = port_name;
                else if (rdata->ids.port_name != port_name) {
                        FC_DISC_DBG(disc, "GPN_ID accepted.  WWPN changed. "
                                    "Port-id %6.6x wwpn %16.16llx\n",
                                    rdata->ids.port_id, port_name);
+                       mutex_unlock(&rdata->rp_mutex);
                        lport->tt.rport_logoff(rdata);
-
+                       mutex_lock(&lport->disc.disc_mutex);
                        new_rdata = lport->tt.rport_create(lport,
                                                           rdata->ids.port_id);
+                       mutex_unlock(&lport->disc.disc_mutex);
                        if (new_rdata) {
                                new_rdata->disc_id = disc->disc_id;
                                lport->tt.rport_login(new_rdata);
@@ -624,6 +634,7 @@ static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
                        goto out;
                }
                rdata->disc_id = disc->disc_id;
+               mutex_unlock(&rdata->rp_mutex);
                lport->tt.rport_login(rdata);
        } else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
                FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n",
@@ -633,10 +644,11 @@ static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
                FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n",
                            ntohs(cp->ct_cmd));
 redisc:
+               mutex_lock(&disc->disc_mutex);
                fc_disc_restart(disc);
+               mutex_unlock(&disc->disc_mutex);
        }
 out:
-       mutex_unlock(&disc->disc_mutex);
        kref_put(&rdata->kref, lport->tt.rport_destroy);
 }
 
index 04ce7cfb6d1b6272d26ce138a9b11ee952a20d6f..4e11c90c783c6acc46fb122569904f9c09801362 100644 (file)
@@ -237,16 +237,19 @@ static const char *fc_lport_state(struct fc_lport *lport)
  * @remote_fid:         The FID of the ptp rport
  * @remote_wwpn: The WWPN of the ptp rport
  * @remote_wwnn: The WWNN of the ptp rport
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
  */
 static void fc_lport_ptp_setup(struct fc_lport *lport,
                               u32 remote_fid, u64 remote_wwpn,
                               u64 remote_wwnn)
 {
-       mutex_lock(&lport->disc.disc_mutex);
        if (lport->ptp_rdata) {
                lport->tt.rport_logoff(lport->ptp_rdata);
                kref_put(&lport->ptp_rdata->kref, lport->tt.rport_destroy);
        }
+       mutex_lock(&lport->disc.disc_mutex);
        lport->ptp_rdata = lport->tt.rport_create(lport, remote_fid);
        kref_get(&lport->ptp_rdata->kref);
        lport->ptp_rdata->ids.port_name = remote_wwpn;
@@ -1007,8 +1010,10 @@ EXPORT_SYMBOL(fc_lport_reset);
  */
 static void fc_lport_reset_locked(struct fc_lport *lport)
 {
-       if (lport->dns_rdata)
+       if (lport->dns_rdata) {
                lport->tt.rport_logoff(lport->dns_rdata);
+               lport->dns_rdata = NULL;
+       }
 
        if (lport->ptp_rdata) {
                lport->tt.rport_logoff(lport->ptp_rdata);
index 4ec896e42120151ce103e79c93756452386663ce..a0ceba10c679c4e15c92b5b4c30960b0f22ffe11 100644 (file)
@@ -378,7 +378,9 @@ static void fc_rport_work(struct work_struct *work)
                                mutex_unlock(&rdata->rp_mutex);
                        } else {
                                FC_RPORT_DBG(rdata, "work delete\n");
+                               mutex_lock(&lport->disc.disc_mutex);
                                list_del_rcu(&rdata->peers);
+                               mutex_unlock(&lport->disc.disc_mutex);
                                mutex_unlock(&rdata->rp_mutex);
                                kref_put(&rdata->kref, lport->tt.rport_destroy);
                        }