+ * fc_find_rport_by_wwpn - find the fc_rport pointer for a given wwpn
+ * @shost: host the fc_rport is associated with
+ * @wwpn: wwpn of the fc_rport device
+ *
+ * Notes:
+ * This routine assumes no locks are held on entry.
+ */
+struct fc_rport *
+fc_find_rport_by_wwpn(struct Scsi_Host *shost, u64 wwpn)
+{
+ struct fc_rport *rport;
+ unsigned long flags;
+
+ spin_lock_irqsave(shost->host_lock, flags);
+
+ list_for_each_entry(rport, &fc_host_rports(shost), peers) {
+ if (rport->port_state != FC_PORTSTATE_ONLINE)
+ continue;
+
+ if (rport->port_name == wwpn) {
+ spin_unlock_irqrestore(shost->host_lock, flags);
+ return rport;
+ }
+ }
+
+ spin_unlock_irqrestore(shost->host_lock, flags);
+ return NULL;
+}
+EXPORT_SYMBOL(fc_find_rport_by_wwpn);
+
+static void
+fc_li_stats_update(struct fc_fn_li_desc *li_desc,
+ struct fc_fpin_stats *stats)
+{
+ stats->li += be32_to_cpu(li_desc->event_count);
+ switch (be16_to_cpu(li_desc->event_type)) {
+ case FPIN_LI_UNKNOWN:
+ stats->li_failure_unknown +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_LINK_FAILURE:
+ stats->li_link_failure_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_LOSS_OF_SYNC:
+ stats->li_loss_of_sync_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_LOSS_OF_SIG:
+ stats->li_loss_of_signals_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_PRIM_SEQ_ERR:
+ stats->li_prim_seq_err_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_INVALID_TX_WD:
+ stats->li_invalid_tx_word_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_INVALID_CRC:
+ stats->li_invalid_crc_count +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ case FPIN_LI_DEVICE_SPEC:
+ stats->li_device_specific +=
+ be32_to_cpu(li_desc->event_count);
+ break;
+ }
+}
+
+static void
+fc_delivery_stats_update(u32 reason_code, struct fc_fpin_stats *stats)
+{
+ stats->dn++;
+ switch (reason_code) {
+ case FPIN_DELI_UNKNOWN:
+ stats->dn_unknown++;
+ break;
+ case FPIN_DELI_TIMEOUT:
+ stats->dn_timeout++;
+ break;
+ case FPIN_DELI_UNABLE_TO_ROUTE:
+ stats->dn_unable_to_route++;
+ break;
+ case FPIN_DELI_DEVICE_SPEC:
+ stats->dn_device_specific++;
+ break;
+ }
+}
+
+static void
+fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
+{
+ stats->cn++;
+ switch (event_type) {
+ case FPIN_CONGN_CLEAR:
+ stats->cn_clear++;
+ break;
+ case FPIN_CONGN_LOST_CREDIT:
+ stats->cn_lost_credit++;
+ break;
+ case FPIN_CONGN_CREDIT_STALL:
+ stats->cn_credit_stall++;
+ break;
+ case FPIN_CONGN_OVERSUBSCRIPTION:
+ stats->cn_oversubscription++;
+ break;
+ case FPIN_CONGN_DEVICE_SPEC:
+ stats->cn_device_specific++;
+ }
+}
+
+/*
+ * fc_fpin_li_stats_update - routine to update Link Integrity
+ * event statistics.
+ * @shost: host the FPIN was received on
+ * @tlv: pointer to link integrity descriptor
+ *
+ */
+static void
+fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
+{
+ u8 i;
+ struct fc_rport *rport = NULL;
+ struct fc_rport *attach_rport = NULL;
+ struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
+ struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv;
+ u64 wwpn;
+
+ rport = fc_find_rport_by_wwpn(shost,
+ be64_to_cpu(li_desc->attached_wwpn));
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ attach_rport = rport;
+ fc_li_stats_update(li_desc, &attach_rport->fpin_stats);
+ }
+
+ if (be32_to_cpu(li_desc->pname_count) > 0) {
+ for (i = 0;
+ i < be32_to_cpu(li_desc->pname_count);
+ i++) {
+ wwpn = be64_to_cpu(li_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_li_stats_update(li_desc,
+ &rport->fpin_stats);
+ }
+ }
+ }
+
+ if (fc_host->port_name == be64_to_cpu(li_desc->attached_wwpn))
+ fc_li_stats_update(li_desc, &fc_host->fpin_stats);
+}
+
+/*
+ * fc_fpin_delivery_stats_update - routine to update Delivery Notification
+ * event statistics.
+ * @shost: host the FPIN was received on
+ * @tlv: pointer to delivery descriptor
+ *
+ */
+static void
+fc_fpin_delivery_stats_update(struct Scsi_Host *shost,
+ struct fc_tlv_desc *tlv)
+{
+ struct fc_rport *rport = NULL;
+ struct fc_rport *attach_rport = NULL;
+ struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
+ struct fc_fn_deli_desc *dn_desc = (struct fc_fn_deli_desc *)tlv;
+ u32 reason_code = be32_to_cpu(dn_desc->deli_reason_code);
+
+ rport = fc_find_rport_by_wwpn(shost,
+ be64_to_cpu(dn_desc->attached_wwpn));
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ attach_rport = rport;
+ fc_delivery_stats_update(reason_code,
+ &attach_rport->fpin_stats);
+ }
+
+ if (fc_host->port_name == be64_to_cpu(dn_desc->attached_wwpn))
+ fc_delivery_stats_update(reason_code, &fc_host->fpin_stats);
+}
+
+/*
+ * fc_fpin_peer_congn_stats_update - routine to update Peer Congestion
+ * event statistics.
+ * @shost: host the FPIN was received on
+ * @tlv: pointer to peer congestion descriptor
+ *
+ */
+static void
+fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
+ struct fc_tlv_desc *tlv)
+{
+ u8 i;
+ struct fc_rport *rport = NULL;
+ struct fc_rport *attach_rport = NULL;
+ struct fc_fn_peer_congn_desc *pc_desc =
+ (struct fc_fn_peer_congn_desc *)tlv;
+ u16 event_type = be16_to_cpu(pc_desc->event_type);
+ u64 wwpn;
+
+ rport = fc_find_rport_by_wwpn(shost,
+ be64_to_cpu(pc_desc->attached_wwpn));
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ attach_rport = rport;
+ fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
+ }
+
+ if (be32_to_cpu(pc_desc->pname_count) > 0) {
+ for (i = 0;
+ i < be32_to_cpu(pc_desc->pname_count);
+ i++) {
+ wwpn = be64_to_cpu(pc_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_cn_stats_update(event_type,
+ &rport->fpin_stats);
+ }
+ }
+ }
+}
+
+/*
+ * fc_fpin_congn_stats_update - routine to update Congestion
+ * event statistics.
+ * @shost: host the FPIN was received on
+ * @tlv: pointer to congestion descriptor
+ *
+ */
+static void
+fc_fpin_congn_stats_update(struct Scsi_Host *shost,
+ struct fc_tlv_desc *tlv)
+{
+ struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
+ struct fc_fn_congn_desc *congn = (struct fc_fn_congn_desc *)tlv;
+
+ fc_cn_stats_update(be16_to_cpu(congn->event_type),
+ &fc_host->fpin_stats);
+}
+
+/**
+ * fc_host_fpin_rcv - routine to process a received FPIN.