From 9bd6ac16e6e53d8d5c7548d04e3017a244c22720 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 30 Jun 2026 14:29:25 -0400 Subject: [PATCH 01/16] Revert "scsi: qla2xxx: Sanitize payload size to prevent member overflow" This reverts commit 19bc5f2a6962dfaa0e32d0e0bc2271993d85d414. So we have been seeing failures in our internal qla2xxx FPIN testing for a while. These failures are due to this commit and has been reported internally at Red Hat. Recently Sustaining Engineering asked us to put in a patch to resolve this issue. The fix is to rever the patch: commit 19bc5f2a6962dfaa0e32d0e0bc2271993d85d414 Author: Jiasheng Jiang Date: Tue Jan 6 20:53:44 2026 +0000 scsi: qla2xxx: Sanitize payload size to prevent member overflow Unfortunately this fix does not work. Moveover, truncating the received FPIN data seemed like a bad idea in general. There was a previous attempt to fix this, but it did not work right either: commit 6f4b10226b6b1e7d1ff3cdb006cf0f6da6eed71e Author: Gustavo A. R. Silva Date: Wed Aug 13 16:07:44 2025 -0400 scsi: qla2xxx: Fix memcpy() field-spanning write issue So John reverted it: commit 285654d58a747eb431ee538c803c077d90525674 Author: John Meneghini Date: Thu Sep 25 09:07:29 2025 -0400 Revert "scsi: qla2xxx: Fix memcpy() field-spanning write issue" Signed-off-by: John Meneghini Reviewed-by: Ewan D. Milne Reviewed-by: David Jeffery --- drivers/scsi/qla2xxx/qla_isr.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index 33776330956c93..7e67e0ce3cbd25 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -878,9 +878,6 @@ qla27xx_copy_multiple_pkt(struct scsi_qla_host *vha, void **pkt, payload_size = sizeof(purex->els_frame_payload); } - if (total_bytes > sizeof(item->iocb.iocb)) - total_bytes = sizeof(item->iocb.iocb); - pending_bytes = total_bytes; no_bytes = (pending_bytes > payload_size) ? payload_size : pending_bytes; @@ -1166,10 +1163,6 @@ qla27xx_copy_fpin_pkt(struct scsi_qla_host *vha, void **pkt, total_bytes = (le16_to_cpu(purex->frame_size) & 0x0FFF) - PURX_ELS_HEADER_SIZE; - - if (total_bytes > sizeof(item->iocb.iocb)) - total_bytes = sizeof(item->iocb.iocb); - pending_bytes = total_bytes; entry_count = entry_count_remaining = purex->entry_count; no_bytes = (pending_bytes > sizeof(purex->els_frame_payload)) ? From c9563e00bce26ee5778f59a4fedc8fe216dbd291 Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Tue, 24 Jun 2025 16:20:15 -0400 Subject: [PATCH 02/16] fc_els: use 'union fc_tlv_desc' Introduce 'union fc_tlv_desc' to have a common structure for all FC ELS TLV structures and avoid type casts. Fixes: 44b6169ada7f ("scsi: fc: Avoid -Wflex-array-member-not-at-end warnings") Commit 44b6169ada7f ("scsi: fc: Avoid -Wflex-array-member-not-at-end warnings") conflicts with this change. To solve the problem I moved the fc_els_rdf_hdr and fc_els_rdf_resp_hdr definitions out of fc_els.h and into lpfc_hw4.h. Cc: Gustavo A. R. Silva Co-developed-by: John Meneghini Signed-off-by: John Meneghini Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney --- drivers/scsi/lpfc/lpfc_els.c | 77 +++++++------- drivers/scsi/lpfc/lpfc_hw4.h | 12 ++- drivers/scsi/scsi_transport_fc.c | 27 ++--- include/uapi/scsi/fc/fc_els.h | 174 ++++++++++++++++--------------- 4 files changed, 146 insertions(+), 144 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index 52fc5058976d99..11143da24b54d7 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -3888,7 +3888,7 @@ lpfc_issue_els_rdf(struct lpfc_vport *vport, uint8_t retry) memset(prdf, 0, cmdsize); prdf->rdf.fpin_cmd = ELS_RDF; prdf->rdf.desc_len = cpu_to_be32(sizeof(struct lpfc_els_rdf_req) - - sizeof(struct fc_els_rdf_hdr)); + offsetof(struct fc_els_rdf, desc)); prdf->reg_d1.reg_desc.desc_tag = cpu_to_be32(ELS_DTAG_FPIN_REGISTER); prdf->reg_d1.reg_desc.desc_len = cpu_to_be32( FC_TLV_DESC_LENGTH_FROM_SZ(prdf->reg_d1)); @@ -4075,7 +4075,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, { IOCB_t *irsp_iocb; struct fc_els_edc_resp *edc_rsp; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; struct fc_diag_cg_sig_desc *pcgd; struct fc_diag_lnkflt_desc *plnkflt; struct lpfc_dmabuf *pcmd, *prsp; @@ -4166,7 +4166,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, goto out; } - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_FAULT_CAP: if (bytes_remain < FC_TLV_DESC_SZ_FROM_LENGTH(tlv) || @@ -4181,7 +4181,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, sizeof(struct fc_diag_lnkflt_desc)); goto out; } - plnkflt = (struct fc_diag_lnkflt_desc *)tlv; + plnkflt = &tlv->lnkflt; lpfc_printf_log(phba, KERN_INFO, LOG_ELS | LOG_LDS_EVENT, "4617 Link Fault Desc Data: 0x%08x 0x%08x " @@ -4208,7 +4208,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, goto out; } - pcgd = (struct fc_diag_cg_sig_desc *)tlv; + pcgd = &tlv->cg_sig; lpfc_printf_log( phba, KERN_INFO, LOG_ELS | LOG_CGN_MGMT, "4616 CGN Desc Data: 0x%08x 0x%08x " @@ -4263,10 +4263,8 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, } static void -lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_diag_lnkflt_desc *lft) { - struct fc_diag_lnkflt_desc *lft = (struct fc_diag_lnkflt_desc *)tlv; - lft->desc_tag = cpu_to_be32(ELS_DTAG_LNK_FAULT_CAP); lft->desc_len = cpu_to_be32( FC_TLV_DESC_LENGTH_FROM_SZ(struct fc_diag_lnkflt_desc)); @@ -4279,10 +4277,8 @@ lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) } static void -lpfc_format_edc_cgn_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_format_edc_cgn_desc(struct lpfc_hba *phba, struct fc_diag_cg_sig_desc *cgd) { - struct fc_diag_cg_sig_desc *cgd = (struct fc_diag_cg_sig_desc *)tlv; - /* We are assuming cgd was zero'ed before calling this routine */ /* Configure the congestion detection capability */ @@ -4381,7 +4377,7 @@ lpfc_issue_els_edc(struct lpfc_vport *vport, uint8_t retry) struct lpfc_hba *phba = vport->phba; struct lpfc_iocbq *elsiocb; struct fc_els_edc *edc_req; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; u16 cmdsize; struct lpfc_nodelist *ndlp; u8 *pcmd = NULL; @@ -4420,13 +4416,13 @@ lpfc_issue_els_edc(struct lpfc_vport *vport, uint8_t retry) tlv = edc_req->desc; if (cgn_desc_size) { - lpfc_format_edc_cgn_desc(phba, tlv); + lpfc_format_edc_cgn_desc(phba, &tlv->cg_sig); phba->cgn_sig_freq = lpfc_fabric_cgn_frequency; tlv = fc_tlv_next_desc(tlv); } if (lft_desc_size) - lpfc_format_edc_lft_desc(phba, tlv); + lpfc_format_edc_lft_desc(phba, &tlv->lnkflt); lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS | LOG_CGN_MGMT, "4623 Xmit EDC to remote " @@ -5999,7 +5995,7 @@ lpfc_issue_els_edc_rsp(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, { struct lpfc_hba *phba = vport->phba; struct fc_els_edc_resp *edc_rsp; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; struct lpfc_iocbq *elsiocb; IOCB_t *icmd, *cmd; union lpfc_wqe128 *wqe; @@ -6043,10 +6039,10 @@ lpfc_issue_els_edc_rsp(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, FC_TLV_DESC_LENGTH_FROM_SZ(struct fc_els_lsri_desc)); edc_rsp->lsri.rqst_w0.cmd = ELS_EDC; tlv = edc_rsp->desc; - lpfc_format_edc_cgn_desc(phba, tlv); + lpfc_format_edc_cgn_desc(phba, &tlv->cg_sig); tlv = fc_tlv_next_desc(tlv); if (lft_desc_size) - lpfc_format_edc_lft_desc(phba, tlv); + lpfc_format_edc_lft_desc(phba, &tlv->lnkflt); lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_RSP, "Issue EDC ACC: did:x%x flg:x%lx refcnt %d", @@ -9439,7 +9435,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, { struct lpfc_hba *phba = vport->phba; struct fc_els_edc *edc_req; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; uint8_t *payload; uint32_t *ptr, dtag; const char *dtag_nm; @@ -9482,7 +9478,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, goto out; } - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_FAULT_CAP: if (bytes_remain < FC_TLV_DESC_SZ_FROM_LENGTH(tlv) || @@ -9497,7 +9493,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, sizeof(struct fc_diag_lnkflt_desc)); goto out; } - plnkflt = (struct fc_diag_lnkflt_desc *)tlv; + plnkflt = &tlv->lnkflt; lpfc_printf_log(phba, KERN_INFO, LOG_ELS | LOG_LDS_EVENT, "4626 Link Fault Desc Data: x%08x len x%x " @@ -9534,7 +9530,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, phba->cgn_sig_freq = lpfc_fabric_cgn_frequency; lpfc_least_capable_settings( - phba, (struct fc_diag_cg_sig_desc *)tlv); + phba, &tlv->cg_sig); break; default: dtag_nm = lpfc_get_tlv_dtag_nm(dtag); @@ -10124,14 +10120,13 @@ lpfc_display_fpin_wwpn(struct lpfc_hba *phba, __be64 *wwnlist, u32 cnt) /** * lpfc_els_rcv_fpin_li - Process an FPIN Link Integrity Event. * @phba: Pointer to phba object. - * @tlv: Pointer to the Link Integrity Notification Descriptor. + * @li: Pointer to the Link Integrity Notification Descriptor. * * This function processes a Link Integrity FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_fn_li_desc *li) { - struct fc_fn_li_desc *li = (struct fc_fn_li_desc *)tlv; const char *li_evt_str; u32 li_evt, cnt; @@ -10155,14 +10150,13 @@ lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_del - Process an FPIN Delivery Event. * @phba: Pointer to hba object. - * @tlv: Pointer to the Delivery Notification Descriptor TLV + * @del: Pointer to the Delivery Notification Descriptor TLV * * This function processes a Delivery FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_fn_deli_desc *del) { - struct fc_fn_deli_desc *del = (struct fc_fn_deli_desc *)tlv; const char *del_rsn_str; u32 del_rsn; __be32 *frame; @@ -10193,14 +10187,14 @@ lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_peer_cgn - Process a FPIN Peer Congestion Event. * @phba: Pointer to hba object. - * @tlv: Pointer to the Peer Congestion Notification Descriptor TLV + * @pc: Pointer to the Peer Congestion Notification Descriptor TLV * * This function processes a Peer Congestion FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, + struct fc_fn_peer_congn_desc *pc) { - struct fc_fn_peer_congn_desc *pc = (struct fc_fn_peer_congn_desc *)tlv; const char *pc_evt_str; u32 pc_evt, cnt; @@ -10228,7 +10222,7 @@ lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_cgn - Process an FPIN Congestion notification * @phba: Pointer to hba object. - * @tlv: Pointer to the Congestion Notification Descriptor TLV + * @cgn: Pointer to the Congestion Notification Descriptor TLV * * This function processes an FPIN Congestion Notifiction. The notification * could be an Alarm or Warning. This routine feeds that data into driver's @@ -10237,10 +10231,9 @@ lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) * to the upper layer or 0 to indicate don't deliver it. **/ static int -lpfc_els_rcv_fpin_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_cgn(struct lpfc_hba *phba, struct fc_fn_congn_desc *cgn) { struct lpfc_cgn_info *cp; - struct fc_fn_congn_desc *cgn = (struct fc_fn_congn_desc *)tlv; const char *cgn_evt_str; u32 cgn_evt; const char *cgn_sev_str; @@ -10341,7 +10334,7 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) { struct lpfc_hba *phba = vport->phba; struct fc_els_fpin *fpin = (struct fc_els_fpin *)p; - struct fc_tlv_desc *tlv, *first_tlv, *current_tlv; + union fc_tlv_desc *tlv, *first_tlv, *current_tlv; const char *dtag_nm; int desc_cnt = 0, bytes_remain, cnt; u32 dtag, deliver = 0; @@ -10366,7 +10359,7 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) return; } - tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; + tlv = &fpin->fpin_desc[0]; first_tlv = tlv; bytes_remain = fpin_length - offsetof(struct fc_els_fpin, fpin_desc); bytes_remain = min_t(u32, bytes_remain, be32_to_cpu(fpin->desc_len)); @@ -10374,22 +10367,22 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) /* process each descriptor separately */ while (bytes_remain >= FC_TLV_DESC_HDR_SZ && bytes_remain >= FC_TLV_DESC_SZ_FROM_LENGTH(tlv)) { - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_INTEGRITY: - lpfc_els_rcv_fpin_li(phba, tlv); + lpfc_els_rcv_fpin_li(phba, &tlv->li); deliver = 1; break; case ELS_DTAG_DELIVERY: - lpfc_els_rcv_fpin_del(phba, tlv); + lpfc_els_rcv_fpin_del(phba, &tlv->deli); deliver = 1; break; case ELS_DTAG_PEER_CONGEST: - lpfc_els_rcv_fpin_peer_cgn(phba, tlv); + lpfc_els_rcv_fpin_peer_cgn(phba, &tlv->peer_congn); deliver = 1; break; case ELS_DTAG_CONGESTION: - deliver = lpfc_els_rcv_fpin_cgn(phba, tlv); + deliver = lpfc_els_rcv_fpin_cgn(phba, &tlv->congn); break; default: dtag_nm = lpfc_get_tlv_dtag_nm(dtag); @@ -10402,12 +10395,12 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) return; } lpfc_cgn_update_stat(phba, dtag); - cnt = be32_to_cpu(tlv->desc_len); + cnt = be32_to_cpu(tlv->hdr.desc_len); /* Sanity check descriptor length. The desc_len value does not * include space for the desc_tag and the desc_len fields. */ - len -= (cnt + sizeof(struct fc_tlv_desc)); + len -= (cnt + sizeof(struct fc_tlv_desc_hdr)); if (len < 0) { dtag_nm = lpfc_get_tlv_dtag_nm(dtag); lpfc_printf_log(phba, KERN_WARNING, LOG_CGN_MGMT, diff --git a/drivers/scsi/lpfc/lpfc_hw4.h b/drivers/scsi/lpfc/lpfc_hw4.h index 41fa8f3329daec..10490b3a05cd54 100644 --- a/drivers/scsi/lpfc/lpfc_hw4.h +++ b/drivers/scsi/lpfc/lpfc_hw4.h @@ -4926,12 +4926,20 @@ struct lpfc_els_rdf_reg_desc { }; struct lpfc_els_rdf_req { - struct fc_els_rdf_hdr rdf; /* hdr up to descriptors */ + struct { + __u8 fpin_cmd; /* command (0x19) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes) */ + } rdf; /* hdr up to descriptors */ struct lpfc_els_rdf_reg_desc reg_d1; /* 1st descriptor */ }; struct lpfc_els_rdf_rsp { - struct fc_els_rdf_resp_hdr rdf_resp; /* hdr up to descriptors */ + struct { + struct fc_els_ls_acc acc_hdr; + __be32 desc_list_len; /* Length of response (in bytes) */ + struct fc_els_lsri_desc lsri; + } rdf_resp; /* hdr up to descriptors */ struct lpfc_els_rdf_reg_desc reg_d1; /* 1st descriptor */ }; diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 173ed6373f04ba..02c58f4591d838 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -776,12 +776,11 @@ fc_fpin_pname_stats_update(struct Scsi_Host *shost, * */ static void -fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv) +fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_fn_li_desc *li_desc) { 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; u16 event_type = be16_to_cpu(li_desc->event_type); rport = fc_find_rport_by_wwpn(shost, @@ -812,12 +811,11 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv) */ static void fc_fpin_delivery_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_deli_desc *dn_desc) { 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, @@ -843,12 +841,10 @@ fc_fpin_delivery_stats_update(struct Scsi_Host *shost, */ static void fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_peer_congn_desc *pc_desc) { 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); rport = fc_find_rport_by_wwpn(shost, @@ -876,10 +872,9 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost, */ static void fc_fpin_congn_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_congn_desc *congn) { 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); @@ -899,32 +894,32 @@ fc_host_fpin_rcv(struct Scsi_Host *shost, u32 fpin_len, char *fpin_buf, u8 event_acknowledge) { struct fc_els_fpin *fpin = (struct fc_els_fpin *)fpin_buf; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; u32 bytes_remain; u32 dtag; enum fc_host_event_code event_code = event_acknowledge ? FCH_EVT_LINK_FPIN_ACK : FCH_EVT_LINK_FPIN; /* Update Statistics */ - tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; + tlv = &fpin->fpin_desc[0]; bytes_remain = fpin_len - offsetof(struct fc_els_fpin, fpin_desc); bytes_remain = min_t(u32, bytes_remain, be32_to_cpu(fpin->desc_len)); while (bytes_remain >= FC_TLV_DESC_HDR_SZ && bytes_remain >= FC_TLV_DESC_SZ_FROM_LENGTH(tlv)) { - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_INTEGRITY: - fc_fpin_li_stats_update(shost, tlv); + fc_fpin_li_stats_update(shost, &tlv->li); break; case ELS_DTAG_DELIVERY: - fc_fpin_delivery_stats_update(shost, tlv); + fc_fpin_delivery_stats_update(shost, &tlv->deli); break; case ELS_DTAG_PEER_CONGEST: - fc_fpin_peer_congn_stats_update(shost, tlv); + fc_fpin_peer_congn_stats_update(shost, &tlv->peer_congn); break; case ELS_DTAG_CONGESTION: - fc_fpin_congn_stats_update(shost, tlv); + fc_fpin_congn_stats_update(shost, &tlv->congn); } bytes_remain -= FC_TLV_DESC_SZ_FROM_LENGTH(tlv); diff --git a/include/uapi/scsi/fc/fc_els.h b/include/uapi/scsi/fc/fc_els.h index dca6a28f4e862a..b5653e1dee13c7 100644 --- a/include/uapi/scsi/fc/fc_els.h +++ b/include/uapi/scsi/fc/fc_els.h @@ -259,12 +259,12 @@ enum fc_ls_tlv_dtag { /* - * Generic Link Service TLV Descriptor format + * Generic Link Service TLV Descriptor header * * This structure, as it defines no payload, will also be referred to * as the "tlv header" - which contains the tag and len fields. */ -struct fc_tlv_desc { +struct fc_tlv_desc_hdr { __be32 desc_tag; /* Notification Descriptor Tag */ __be32 desc_len; /* Length of Descriptor (in bytes). * Size of descriptor excluding @@ -273,36 +273,6 @@ struct fc_tlv_desc { __u8 desc_value[]; /* Descriptor Value */ }; -/* Descriptor tag and len fields are considered the mandatory header - * for a descriptor - */ -#define FC_TLV_DESC_HDR_SZ sizeof(struct fc_tlv_desc) - -/* - * Macro, used when initializing payloads, to return the descriptor length. - * Length is size of descriptor minus the tag and len fields. - */ -#define FC_TLV_DESC_LENGTH_FROM_SZ(desc) \ - (sizeof(desc) - FC_TLV_DESC_HDR_SZ) - -/* Macro, used on received payloads, to return the descriptor length */ -#define FC_TLV_DESC_SZ_FROM_LENGTH(tlv) \ - (__be32_to_cpu((tlv)->desc_len) + FC_TLV_DESC_HDR_SZ) - -/* - * This helper is used to walk descriptors in a descriptor list. - * Given the address of the current descriptor, which minimally contains a - * tag and len field, calculate the address of the next descriptor based - * on the len field. - */ -static inline void *fc_tlv_next_desc(void *desc) -{ - struct fc_tlv_desc *tlv = desc; - - return (desc + FC_TLV_DESC_SZ_FROM_LENGTH(tlv)); -} - - /* * Link Service Request Information Descriptor */ @@ -1100,19 +1070,6 @@ struct fc_fn_congn_desc { __u8 resv[3]; /* reserved - must be zero */ }; -/* - * ELS_FPIN - Fabric Performance Impact Notification - */ -struct fc_els_fpin { - __u8 fpin_cmd; /* command (0x16) */ - __u8 fpin_zero[3]; /* specified as zero - part of cmd */ - __be32 desc_len; /* Length of Descriptor List (in bytes). - * Size of ELS excluding fpin_cmd, - * fpin_zero and desc_len fields. - */ - struct fc_tlv_desc fpin_desc[]; /* Descriptor list */ -}; - /* Diagnostic Function Descriptor - FPIN Registration */ struct fc_df_desc_fpin_reg { /* New members MUST be added within the __struct_group() macro below. */ @@ -1136,42 +1093,6 @@ struct fc_df_desc_fpin_reg { _Static_assert(offsetof(struct fc_df_desc_fpin_reg, desc_tags) == sizeof(struct fc_df_desc_fpin_reg_hdr), "struct member likely outside of __struct_group()"); -/* - * ELS_RDF - Register Diagnostic Functions - */ -struct fc_els_rdf { - /* New members MUST be added within the __struct_group() macro below. */ - __struct_group(fc_els_rdf_hdr, __hdr, /* no attrs */, - __u8 fpin_cmd; /* command (0x19) */ - __u8 fpin_zero[3]; /* specified as zero - part of cmd */ - __be32 desc_len; /* Length of Descriptor List (in bytes). - * Size of ELS excluding fpin_cmd, - * fpin_zero and desc_len fields. - */ - ); - struct fc_tlv_desc desc[]; /* Descriptor list */ -}; -_Static_assert(offsetof(struct fc_els_rdf, desc) == sizeof(struct fc_els_rdf_hdr), - "struct member likely outside of __struct_group()"); - -/* - * ELS RDF LS_ACC Response. - */ -struct fc_els_rdf_resp { - /* New members MUST be added within the __struct_group() macro below. */ - __struct_group(fc_els_rdf_resp_hdr, __hdr, /* no attrs */, - struct fc_els_ls_acc acc_hdr; - __be32 desc_list_len; /* Length of response (in - * bytes). Excludes acc_hdr - * and desc_list_len fields. - */ - struct fc_els_lsri_desc lsri; - ); - struct fc_tlv_desc desc[]; /* Supported Descriptor list */ -}; -_Static_assert(offsetof(struct fc_els_rdf_resp, desc) == sizeof(struct fc_els_rdf_resp_hdr), - "struct member likely outside of __struct_group()"); - /* * Diagnostic Capability Descriptors for EDC ELS */ @@ -1241,6 +1162,65 @@ struct fc_diag_cg_sig_desc { struct fc_diag_cg_sig_freq rcv_signal_frequency; }; +/* + * Generic Link Service TLV Descriptor format + * + * This structure, as it defines no payload, will also be referred to + * as the "tlv header" - which contains the tag and len fields. + */ +union fc_tlv_desc { + struct fc_tlv_desc_hdr hdr; + struct fc_els_lsri_desc lsri; + struct fc_fn_li_desc li; + struct fc_fn_deli_desc deli; + struct fc_fn_peer_congn_desc peer_congn; + struct fc_fn_congn_desc congn; + struct fc_df_desc_fpin_reg fpin_reg; + struct fc_diag_lnkflt_desc lnkflt; + struct fc_diag_cg_sig_desc cg_sig; +}; + +/* Descriptor tag and len fields are considered the mandatory header + * for a descriptor + */ +#define FC_TLV_DESC_HDR_SZ sizeof(struct fc_tlv_desc_hdr) + +/* + * Macro, used when initializing payloads, to return the descriptor length. + * Length is size of descriptor minus the tag and len fields. + */ +#define FC_TLV_DESC_LENGTH_FROM_SZ(desc) \ + (sizeof(desc) - FC_TLV_DESC_HDR_SZ) + +/* Macro, used on received payloads, to return the descriptor length */ +#define FC_TLV_DESC_SZ_FROM_LENGTH(tlv) \ + (__be32_to_cpu((tlv)->hdr.desc_len) + FC_TLV_DESC_HDR_SZ) + +/* + * This helper is used to walk descriptors in a descriptor list. + * Given the address of the current descriptor, which minimally contains a + * tag and len field, calculate the address of the next descriptor based + * on the len field. + */ +static inline union fc_tlv_desc *fc_tlv_next_desc(union fc_tlv_desc *desc) +{ + return (union fc_tlv_desc *)((__u8 *)desc + FC_TLV_DESC_SZ_FROM_LENGTH(desc)); +} + + +/* + * ELS_FPIN - Fabric Performance Impact Notification + */ +struct fc_els_fpin { + __u8 fpin_cmd; /* command (0x16) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes). + * Size of ELS excluding fpin_cmd, + * fpin_zero and desc_len fields. + */ + union fc_tlv_desc fpin_desc[]; /* Descriptor list */ +}; + /* * ELS_EDC - Exchange Diagnostic Capabilities */ @@ -1251,10 +1231,37 @@ struct fc_els_edc { * Size of ELS excluding edc_cmd, * edc_zero and desc_len fields. */ - struct fc_tlv_desc desc[]; + union fc_tlv_desc desc[]; /* Diagnostic Descriptor list */ }; +/* + * ELS_RDF - Register Diagnostic Functions + */ +struct fc_els_rdf { + __u8 fpin_cmd; /* command (0x19) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes). + * Size of ELS excluding fpin_cmd, + * fpin_zero and desc_len fields. + */ + union fc_tlv_desc desc[]; /* Descriptor list */ +}; + +/* + * ELS RDF LS_ACC Response. + */ +struct fc_els_rdf_resp { + struct fc_els_ls_acc acc_hdr; + __be32 desc_list_len; /* Length of response (in + * bytes). Excludes acc_hdr + * and desc_list_len fields. + */ + struct fc_els_lsri_desc lsri; + union fc_tlv_desc desc[]; /* Supported Descriptor list */ +}; + + /* * ELS EDC LS_ACC Response. */ @@ -1265,9 +1272,8 @@ struct fc_els_edc_resp { * and desc_list_len fields. */ struct fc_els_lsri_desc lsri; - struct fc_tlv_desc desc[]; + union fc_tlv_desc desc[]; /* Supported Diagnostic Descriptor list */ }; - #endif /* _FC_ELS_H_ */ From bf833cddec2b7173ac01dd3f1f1381d8c8638c69 Mon Sep 17 00:00:00 2001 From: Bryan Gurney Date: Mon, 7 Jul 2025 08:48:26 -0400 Subject: [PATCH 03/16] nvme: add NVME_CTRL_MARGINAL flag Add a new controller flag, NVME_CTRL_MARGINAL, to help multipath I/O policies to react to a path that is set to a "marginal" state. The flag is cleared on controller reset, which is often the case when faulty cabling or transceiver hardware is replaced. Signed-off-by: Bryan Gurney --- drivers/nvme/host/core.c | 1 + drivers/nvme/host/fc.c | 4 ++++ drivers/nvme/host/nvme.h | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 453c1f0b2dd094..957090af217126 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5165,6 +5165,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, WRITE_ONCE(ctrl->state, NVME_CTRL_NEW); ctrl->passthru_err_log_enabled = false; clear_bit(NVME_CTRL_FAILFAST_EXPIRED, &ctrl->flags); + clear_bit(NVME_CTRL_MARGINAL, &ctrl->flags); spin_lock_init(&ctrl->lock); mutex_init(&ctrl->namespaces_lock); diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 04363b9c4489e0..d2ca65f70a0c55 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -788,6 +788,10 @@ nvme_fc_ctrl_connectivity_loss(struct nvme_fc_ctrl *ctrl) "Reconnect", ctrl->cnum); set_bit(ASSOC_FAILED, &ctrl->flags); + + /* clear 'marginal' flag as controller will be reset */ + clear_bit(NVME_CTRL_MARGINAL, &ctrl->flags); + nvme_reset_ctrl(&ctrl->ctrl); } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 824651cc898dbe..3616742d6788a7 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -329,6 +329,7 @@ enum nvme_ctrl_flags { NVME_CTRL_SKIP_ID_CNS_CS = 4, NVME_CTRL_DIRTY_CAPABILITY = 5, NVME_CTRL_FROZEN = 6, + NVME_CTRL_MARGINAL = 7, }; struct nvme_ctrl { @@ -479,6 +480,11 @@ static inline enum nvme_ctrl_state nvme_ctrl_state(struct nvme_ctrl *ctrl) return READ_ONCE(ctrl->state); } +static inline bool nvme_ctrl_is_marginal(struct nvme_ctrl *ctrl) +{ + return test_bit(NVME_CTRL_MARGINAL, &ctrl->flags); +} + enum nvme_iopolicy { NVME_IOPOLICY_NUMA, NVME_IOPOLICY_RR, From 691086784661eac9ce0f257b2008040d1c79e963 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 22 Jun 2026 11:00:19 -0400 Subject: [PATCH 04/16] nvme-fc: marginal path handling for numa policy FPIN LI (link integrity) messages are received when the attached fabric detects hardware errors. In response to these messages I/O should be directed away from the affected ports, and only used if no other non-marginal paths are available. To handle this a new controller flag 'NVME_CTRL_MARGINAL' is added which will cause the multipath scheduler to skip these paths when checking for 'optimized' paths. Signed-off-by: Jesse Taube --- This is a distinct change from the previous commit which treated marginal paths as non-optimized but still usable. This changes the priority of marginal paths to be lower than non-optimized paths. --- drivers/nvme/host/multipath.c | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9b9a657fa330ff..02936c580a3734 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -305,10 +305,44 @@ static bool nvme_path_is_disabled(struct nvme_ns *ns) return false; } +/* + * Returns true if the new distance is better than the old one. + */ +static bool is_best_distance(bool found_is_marginal, bool marginal, + int old_distance, int distance) +{ + if (found_is_marginal) { + if (marginal) { + /* + * A marginal path has already been found, + * or this is the first path found. + * This one is also marginal, but closer + * to the NUMA node, so prefer it. + */ + if (distance < old_distance) + return true; + } else { + /* Found a non-marginal path, use it over a marginal one. */ + return true; + } + } else { + /* A non-marginal path has already found. This one is marginal, so skip it. */ + if (marginal) + return false; + + /* Found a closer non-marginal path, use it. */ + if (distance < old_distance) + return true; + } + + return false; +} + static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) { int found_distance = INT_MAX, fallback_distance = INT_MAX, distance; struct nvme_ns *found = NULL, *fallback = NULL, *ns; + bool found_is_marginal = true, fallback_is_marginal = true; list_for_each_entry_srcu(ns, &head->list, siblings, srcu_read_lock_held(&head->srcu)) { @@ -323,15 +357,19 @@ static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) switch (ns->ana_state) { case NVME_ANA_OPTIMIZED: - if (distance < found_distance) { + if (is_best_distance(found_is_marginal, nvme_ctrl_is_marginal(ns->ctrl), + found_distance, distance)) { found_distance = distance; found = ns; + found_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); } break; case NVME_ANA_NONOPTIMIZED: - if (distance < fallback_distance) { + if (is_best_distance(fallback_is_marginal, nvme_ctrl_is_marginal(ns->ctrl), + fallback_distance, distance)) { fallback_distance = distance; fallback = ns; + fallback_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); } break; default: @@ -339,6 +377,14 @@ static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) } } + /* + * Use non-optimized path only if it is not marginal + * and no optimized path is marginal. + */ + if (found_is_marginal && !fallback_is_marginal) + found = fallback; + + /* No optimized path found, use the fallback */ if (!found) found = fallback; if (found) @@ -444,7 +490,8 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) static inline bool nvme_path_is_optimized(struct nvme_ns *ns) { return nvme_ctrl_state(ns->ctrl) == NVME_CTRL_LIVE && - ns->ana_state == NVME_ANA_OPTIMIZED; + ns->ana_state == NVME_ANA_OPTIMIZED && + !nvme_ctrl_is_marginal(ns->ctrl); } static struct nvme_ns *nvme_numa_path(struct nvme_ns_head *head) From 08fd79e5bc87e835268cdd51b044a412c529da33 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 1 Jul 2025 13:57:42 -0400 Subject: [PATCH 05/16] nvme-multipath: queue-depth support for marginal paths Exclude marginal paths from queue-depth io policy. In the case where all paths are marginal and no optimized or non-optimized path is found, we fall back and select the best marginal path. Signed-off-by: Jesse Taube Signed-off-by: John Meneghini --- drivers/nvme/host/multipath.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 02936c580a3734..8df7ef5e3c5be8 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -454,6 +454,8 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) { struct nvme_ns *best_opt = NULL, *best_nonopt = NULL, *ns; unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX; + bool opt_is_marginal = true, nonopt_is_marginal = true, marginal; + unsigned int depth; list_for_each_entry_srcu(ns, &head->list, siblings, @@ -462,18 +464,23 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) continue; depth = atomic_read(&ns->ctrl->nr_active); + marginal = nvme_ctrl_is_marginal(ns->ctrl); switch (ns->ana_state) { case NVME_ANA_OPTIMIZED: - if (depth < min_depth_opt) { + if (is_best_distance(opt_is_marginal, marginal, + min_depth_opt, depth)) { min_depth_opt = depth; best_opt = ns; + opt_is_marginal = marginal; } break; case NVME_ANA_NONOPTIMIZED: - if (depth < min_depth_nonopt) { + if (is_best_distance(nonopt_is_marginal, marginal, + min_depth_nonopt, depth)) { min_depth_nonopt = depth; best_nonopt = ns; + nonopt_is_marginal = marginal; } break; default: From 02e37740c150f761a8b2ccd493b138a2309b2c2d Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Thu, 18 Jun 2026 14:55:21 -0400 Subject: [PATCH 06/16] nvme-multipath: round-robin support for marginal paths Exclude marginal paths from round-robin io policy. In the case where all paths are marginal and no optimized or non-optimized path is found, we fall back and perform round-robin on the marginal paths. Signed-off-by: Jesse Taube --- drivers/nvme/host/multipath.c | 47 +++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 8df7ef5e3c5be8..285fed4a039524 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -405,6 +405,7 @@ static struct nvme_ns *nvme_next_ns(struct nvme_ns_head *head, static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) { struct nvme_ns *ns, *found = NULL; + bool found_is_marginal = true; int node = numa_node_id(); struct nvme_ns *old = srcu_dereference(head->current_path[node], &head->srcu); @@ -425,22 +426,58 @@ static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) continue; if (ns->ana_state == NVME_ANA_OPTIMIZED) { + if (found_is_marginal && nvme_ctrl_is_marginal(ns->ctrl)) { + /* + * A marginal path has already found, + * or this is the first path found. + * This one is also marginal, but optimized, + * so prefer it. + */ + found = ns; + found_is_marginal = 1; + continue; + } + + + /* + * A non-marginal path has already found. + * This one is marginal, so skip it. + */ + if (nvme_ctrl_is_marginal(ns->ctrl)) + continue; + + /* Found a non-marginal, optimized path use it. */ found = ns; goto out; } - if (ns->ana_state == NVME_ANA_NONOPTIMIZED) + if (ns->ana_state == NVME_ANA_NONOPTIMIZED) { + /* + * A path has already found. This one is marginal, + * so skip it. + */ + if (found && nvme_ctrl_is_marginal(ns->ctrl)) + continue; found = ns; + found_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); + } } /* * The loop above skips the current path for round-robin semantics. * Fall back to the current path if either: - * - no other optimized path found and current is optimized, + * - no other non-marginal optimized path found and current is, + * optimized and not marginal. * - no other usable path found and current is usable. */ - if (!nvme_path_is_disabled(old) && - (old->ana_state == NVME_ANA_OPTIMIZED || - (!found && old->ana_state == NVME_ANA_NONOPTIMIZED))) + /* no other usable path found and current is usable. */ + if (!nvme_path_is_disabled(old) && !found) + return old; + /* + * no other non-marginal optimized path found and current is, + * optimized and not marginal. + */ + if (!nvme_path_is_disabled(old) && !nvme_ctrl_is_marginal(old->ctrl) && + (old->ana_state == NVME_ANA_OPTIMIZED || found_is_marginal)) return old; if (!found) From 964a1460d8b2b3170ee4141feefec695d856ab4b Mon Sep 17 00:00:00 2001 From: Bryan Gurney Date: Tue, 24 Jun 2025 16:20:20 -0400 Subject: [PATCH 07/16] nvme: sysfs: emit the marginal path state in show_state() If a controller has received a link integrity or congestion event, and has the NVME_CTRL_MARGINAL flag set, emit "marginal" in the state instead of "live", to identify the marginal paths. Co-developed-by: John Meneghini Signed-off-by: John Meneghini Reviewed-by: Hannes Reinecke Reviewed-by: Chaitanya Kulkarni Tested-by: Muneendra Kumar Signed-off-by: Bryan Gurney --- drivers/nvme/host/sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 75b2d69b59578e..01c771dc88cb9f 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -527,7 +527,9 @@ static ssize_t nvme_sysfs_show_state(struct device *dev, }; if (state < ARRAY_SIZE(state_name) && state_name[state]) - return sysfs_emit(buf, "%s\n", state_name[state]); + return sysfs_emit(buf, "%s\n", + (nvme_ctrl_is_marginal(ctrl)) ? "marginal" : + state_name[state]); return sysfs_emit(buf, "unknown state\n"); } From 5703241d421fb76414760f9a147c363639ad2586 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 14:52:17 -0400 Subject: [PATCH 08/16] scsi: scsi_transport_fc: Add set_rport_marginal to fc_function_template Add fc_fpin_set_marginal function to evaluate the FPIN LI TLV information and set the 'marginal' path status for all affected nvme rports. Then call set_rport_marginal() callback added to fc_function_template. The implementation of set_rport_marginal() is almost identical to set_rport_dev_loss_tmo implementation. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- why do we check rport->roles & FC_PORT_ROLE_NVME_TARGET but not in the sysfs commit --- drivers/scsi/scsi_transport_fc.c | 20 ++++++++++++++++++++ include/scsi/scsi_transport_fc.h | 1 + 2 files changed, 21 insertions(+) diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 02c58f4591d838..df1edf3c23fa8d 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -737,6 +737,25 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats) } } +static void fc_fpin_set_marginal(struct Scsi_Host *shost, struct fc_rport *rport) +{ + struct fc_internal *i = to_fc_internal(shost->transportt); + unsigned long flags; + + spin_lock_irqsave(shost->host_lock, flags); + + if (rport->port_state == FC_PORTSTATE_ONLINE && + rport->roles & FC_PORT_ROLE_NVME_TARGET) { + rport->port_state = FC_PORTSTATE_MARGINAL; + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, true); + return; + } + + spin_unlock_irqrestore(shost->host_lock, flags); +} + static void fc_fpin_pname_stats_update(struct Scsi_Host *shost, struct fc_rport *attach_rport, u16 event_type, @@ -764,6 +783,7 @@ fc_fpin_pname_stats_update(struct Scsi_Host *shost, if (rport == attach_rport) continue; stats_update(event_type, &rport->fpin_stats); + fc_fpin_set_marginal(shost, rport); } } } diff --git a/include/scsi/scsi_transport_fc.h b/include/scsi/scsi_transport_fc.h index 9f30625aa0d304..bc7c3f8323992c 100644 --- a/include/scsi/scsi_transport_fc.h +++ b/include/scsi/scsi_transport_fc.h @@ -684,6 +684,7 @@ struct fc_host_attrs { struct fc_function_template { void (*get_rport_dev_loss_tmo)(struct fc_rport *); void (*set_rport_dev_loss_tmo)(struct fc_rport *, u32); + void (*set_rport_marginal)(struct fc_rport *rport, bool marginal); void (*get_starget_node_name)(struct scsi_target *); void (*get_starget_port_name)(struct scsi_target *); From b53f81ad32cbe695fd3d3bb1f3e61acf62bde9e5 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 23 Sep 2025 20:00:57 -0400 Subject: [PATCH 09/16] scsi: scsi_transport_fc: user support for clearing NVME_CTRL_MARGINAL Refactor and fc_rport_set_marginal_state smp safe by holding `shost->host_lock` around all `rport->port_state` accesses. Call nvme_fc_modify_rport_fpin_state() when FC_PORTSTATE_MARGINAL is set or cleared. This allows the user to quickly set or clear the NVME_CTRL_MARGINAL state from sysfs. E.g.: echo "Marginal" > /sys/class/fc_remote_ports/rport-13:0-5/port_state echo "Online" > /sys/class/fc_remote_ports/rport-13:0-5/port_state Note: nvme_fc_modify_rport_fpin_state() will only affect rports that have FC_PORT_ROLE_NVME_TARGET set. Signed-off-by: John Meneghini --- drivers/scsi/scsi_transport_fc.c | 42 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index df1edf3c23fa8d..af309a7bdc2215 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -1251,34 +1251,58 @@ static ssize_t fc_rport_set_marginal_state(struct device *dev, const char *buf, size_t count) { struct fc_rport *rport = transport_class_to_rport(dev); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); enum fc_port_state port_state; int ret = 0; + unsigned long flags; ret = get_fc_port_state_match(buf, &port_state); if (ret) return -EINVAL; - if (port_state == FC_PORTSTATE_MARGINAL) { + + spin_lock_irqsave(shost->host_lock, flags); + + switch (port_state) { + case FC_PORTSTATE_MARGINAL: /* * Change the state to Marginal only if the * current rport state is Online * Allow only Online->Marginal */ - if (rport->port_state == FC_PORTSTATE_ONLINE) + if (rport->port_state == FC_PORTSTATE_ONLINE) { rport->port_state = port_state; - else if (port_state != rport->port_state) - return -EINVAL; - } else if (port_state == FC_PORTSTATE_ONLINE) { + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, true); + return count; + } + break; + + case FC_PORTSTATE_ONLINE: /* * Change the state to Online only if the * current rport state is Marginal * Allow only Marginal->Online */ - if (rport->port_state == FC_PORTSTATE_MARGINAL) + if (rport->port_state == FC_PORTSTATE_MARGINAL) { rport->port_state = port_state; - else if (port_state != rport->port_state) - return -EINVAL; - } else + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, false); + return count; + } + break; + default: + break; + } + + if (port_state != rport->port_state) { + spin_unlock_irqrestore(shost->host_lock, flags); return -EINVAL; + } + + spin_unlock_irqrestore(shost->host_lock, flags); return count; } From 4b316decb9cfcc02076faff3b9704f31261208c5 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Tue, 9 Jun 2026 16:58:46 -0400 Subject: [PATCH 10/16] nvme-fc: add nvme_fc_set_remoteport_fpin() Add nvme_fc_set_remoteport_fpin() and supporting functions. This function is called by the SCSI FC transport and driver layer to set or clear the 'marginal' path status for a specific rport. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/nvme/host/fc.c | 16 ++++++++++++++++ include/linux/nvme-fc-driver.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index d2ca65f70a0c55..4acf23ea8aea22 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -895,6 +895,22 @@ nvme_fc_set_remoteport_devloss(struct nvme_fc_remote_port *portptr, } EXPORT_SYMBOL_GPL(nvme_fc_set_remoteport_devloss); +void +nvme_fc_set_remoteport_fpin(struct nvme_fc_remote_port *portptr, bool marginal) +{ + struct nvme_fc_rport *rport = remoteport_to_rport(portptr); + struct nvme_fc_ctrl *ctrl; + + spin_lock_irq(&rport->lock); + list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) { + if (marginal) + set_bit(NVME_CTRL_MARGINAL, &ctrl->ctrl.flags); + else + clear_bit(NVME_CTRL_MARGINAL, &ctrl->ctrl.flags); + } + spin_unlock_irq(&rport->lock); +} +EXPORT_SYMBOL_GPL(nvme_fc_set_remoteport_fpin); /* *********************** FC-NVME DMA Handling **************************** */ diff --git a/include/linux/nvme-fc-driver.h b/include/linux/nvme-fc-driver.h index 9f6acadfe0c868..95d79386d126a5 100644 --- a/include/linux/nvme-fc-driver.h +++ b/include/linux/nvme-fc-driver.h @@ -536,6 +536,8 @@ void nvme_fc_rescan_remoteport(struct nvme_fc_remote_port *remoteport); int nvme_fc_set_remoteport_devloss(struct nvme_fc_remote_port *remoteport, u32 dev_loss_tmo); +void nvme_fc_set_remoteport_fpin(struct nvme_fc_remote_port *portptr, bool marginal); + /* * Routine called to pass a NVME-FC LS request, received by the lldd, * to the nvme-fc transport. From 5b1baeaf9f8acd7aae8a204ded16430f5225b1fe Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 14:59:59 -0400 Subject: [PATCH 11/16] scsi: qla2xxx: enable FPIN notification for NVMe Implement set_rport_marginal callback using nvme_fc_set_remoteport_fpin() to enable FPIN notifications for NVMe. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/scsi/qla2xxx/qla_attr.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index 6a05ce195aa05b..d97d58e7eacc81 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -2757,6 +2757,15 @@ qla2x00_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout) rport->dev_loss_tmo); } +static inline void +qla2x00_set_rport_marginal(struct fc_rport *rport, bool marginal) +{ + fc_port_t *fcport = *(fc_port_t **)rport->dd_data; + + if (IS_ENABLED(CONFIG_NVME_FC) && fcport && fcport->nvme_remote_port) + nvme_fc_set_remoteport_fpin(fcport->nvme_remote_port, marginal); +} + static void qla2x00_dev_loss_tmo_callbk(struct fc_rport *rport) { @@ -3346,6 +3355,8 @@ struct fc_function_template qla2xxx_transport_functions = { .set_rport_dev_loss_tmo = qla2x00_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = qla2x00_set_rport_marginal, + .issue_fc_host_lip = qla2x00_issue_lip, .dev_loss_tmo_callbk = qla2x00_dev_loss_tmo_callbk, .terminate_rport_io = qla2x00_terminate_rport_io, @@ -3394,6 +3405,8 @@ struct fc_function_template qla2xxx_transport_vport_functions = { .set_rport_dev_loss_tmo = qla2x00_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = qla2x00_set_rport_marginal, + .issue_fc_host_lip = qla2x00_issue_lip, .dev_loss_tmo_callbk = qla2x00_dev_loss_tmo_callbk, .terminate_rport_io = qla2x00_terminate_rport_io, From e5ea09861cb1571f98202880610368c070fd482c Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 15:07:26 -0400 Subject: [PATCH 12/16] scsi: lpfc: enable FPIN notification for NVMe Implement set_rport_marginal callback using nvme_fc_set_remoteport_fpin() to enable FPIN notifications for NVMe. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/scsi/lpfc/lpfc_attr.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index f4e8164b94abba..4de44a69a6a39d 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -7134,6 +7134,30 @@ lpfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout) #endif } +static void +lpfc_set_rport_marginal(struct fc_rport *rport, bool marginal) +{ + struct lpfc_rport_data *rdata = rport->dd_data; + struct lpfc_nodelist *ndlp = rdata->pnode; + struct lpfc_nvme_rport *nrport = NULL; + + /* Break early if NVME_FC is not enabled */ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return; + + if (!ndlp) { + dev_info(&rport->dev, "Cannot find remote node to " + "set rport dev loss tmo, port_id x%x\n", + rport->port_id); + return; + } + + nrport = lpfc_ndlp_get_nrport(ndlp); + + if (nrport && nrport->remoteport) + nvme_fc_set_remoteport_fpin(nrport->remoteport, marginal); +} + /* * lpfc_rport_show_function - Return rport target information * @@ -7244,6 +7268,8 @@ struct fc_function_template lpfc_transport_functions = { .set_rport_dev_loss_tmo = lpfc_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = lpfc_set_rport_marginal, + .get_starget_port_id = lpfc_get_starget_port_id, .show_starget_port_id = 1, @@ -7315,6 +7341,8 @@ struct fc_function_template lpfc_vport_transport_functions = { .set_rport_dev_loss_tmo = lpfc_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = lpfc_set_rport_marginal, + .get_starget_port_id = lpfc_get_starget_port_id, .show_starget_port_id = 1, From 4151158259f0ae5cb39cc06a17cacf7943abd7df Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Wed, 30 Jul 2025 16:54:52 -0600 Subject: [PATCH 13/16] scsi: qla2xxx: Fix 2 memcpy field-spanning write issue purex_item.iocb is defined as a 64-element u8 array, but 64 is the minimum size and it can be allocated larger. This makes it a standard empty flex array. This was motivated by field-spanning write warnings during FPIN testing. > kernel: memcpy: detected field-spanning write (size 60) of single > field "((uint8_t *)fpin_pkt + buffer_copy_offset)" > at drivers/scsi/qla2xxx/qla_isr.c:1221 (size 44) I removed the outer wrapper from the iocb flex array, so that it can be linked to `purex_item.size` with `__counted_by`. These changes remove the default minimum 64-byte allocation, requiring further changes. In `struct scsi_qla_host` the embedded `default_item` is now followed by `__default_item_iocb[QLA_DEFAULT_PAYLOAD_SIZE]` to reserve space that will be used as `default_item.iocb`. This is wrapped using the `TRAILING_OVERLAP()` macro helper, which effectively creates a union between flexible-array member `default_item.iocb` and `__default_item_iocb`. Since `struct pure_item` now contains a flexible-array member, the helper must be placed at the end of `struct scsi_qla_host` to prevent a `-Wflex-array-member-not-at-end` warning. `qla24xx_alloc_purex_item()` is adjusted to no longer expect the default minimum size to be part of `sizeof(struct purex_item)`, the entire flexible array size is added to the structure size for allocation. This also slightly changes the layout of the purex_item struct, as 2-bytes of padding are added between `size` and `iocb`. The resulting size is the same, but iocb is shifted 2-bytes (the original `purex_item` structure was padded at the end, after the 64-byte defined array size). I don't think this is a problem. In qla_os.c:qla24xx_process_purex_rdp() To avoid a null pointer dereference the vha->default_item should be set to 0 last if the item pointer passed to the function matches. Also use a local variable to avoid multiple de-referencing of the item. Tested-by: Bryan Gurney Co-developed-by: Chris Leech Signed-off-by: Chris Leech Signed-off-by: Gustavo A. R. Silva --- drivers/scsi/qla2xxx/qla_def.h | 10 ++++++---- drivers/scsi/qla2xxx/qla_isr.c | 17 ++++++++--------- drivers/scsi/qla2xxx/qla_nvme.c | 2 +- drivers/scsi/qla2xxx/qla_os.c | 9 ++++++--- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index 5593ad7fad274b..9704beb0a7a206 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -4913,9 +4913,7 @@ struct purex_item { struct purex_item *pkt); atomic_t in_use; uint16_t size; - struct { - uint8_t iocb[64]; - } iocb; + uint8_t iocb[] __counted_by(size); }; #include "qla_edif.h" @@ -5124,7 +5122,6 @@ typedef struct scsi_qla_host { struct list_head head; spinlock_t lock; } purex_list; - struct purex_item default_item; struct name_list_extended gnl; /* Count of active session/fcport */ @@ -5153,6 +5150,11 @@ typedef struct scsi_qla_host { #define DPORT_DIAG_IN_PROGRESS BIT_0 #define DPORT_DIAG_CHIP_RESET_IN_PROGRESS BIT_1 uint16_t dport_status; + + /* Must be last --ends in a flexible-array member. */ + TRAILING_OVERLAP(struct purex_item, default_item, iocb, + uint8_t __default_item_iocb[QLA_DEFAULT_PAYLOAD_SIZE]; + ); } scsi_qla_host_t; struct qla27xx_image_status { diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index 7e67e0ce3cbd25..319179e7292ec4 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -1077,17 +1077,17 @@ static struct purex_item * qla24xx_alloc_purex_item(scsi_qla_host_t *vha, uint16_t size) { struct purex_item *item = NULL; - uint8_t item_hdr_size = sizeof(*item); if (size > QLA_DEFAULT_PAYLOAD_SIZE) { - item = kzalloc(item_hdr_size + - (size - QLA_DEFAULT_PAYLOAD_SIZE), GFP_ATOMIC); + item = kzalloc(struct_size(item, iocb, size), GFP_ATOMIC); } else { if (atomic_inc_return(&vha->default_item.in_use) == 1) { item = &vha->default_item; goto initialize_purex_header; } else { - item = kzalloc(item_hdr_size, GFP_ATOMIC); + item = kzalloc( + struct_size(item, iocb, QLA_DEFAULT_PAYLOAD_SIZE), + GFP_ATOMIC); } } if (!item) { @@ -1127,17 +1127,16 @@ qla24xx_queue_purex_item(scsi_qla_host_t *vha, struct purex_item *pkt, * @vha: SCSI driver HA context * @pkt: ELS packet */ -static struct purex_item -*qla24xx_copy_std_pkt(struct scsi_qla_host *vha, void *pkt) +static struct purex_item * +qla24xx_copy_std_pkt(struct scsi_qla_host *vha, void *pkt) { struct purex_item *item; - item = qla24xx_alloc_purex_item(vha, - QLA_DEFAULT_PAYLOAD_SIZE); + item = qla24xx_alloc_purex_item(vha, QLA_DEFAULT_PAYLOAD_SIZE); if (!item) return item; - memcpy(&item->iocb, pkt, sizeof(item->iocb)); + memcpy(&item->iocb, pkt, QLA_DEFAULT_PAYLOAD_SIZE); return item; } diff --git a/drivers/scsi/qla2xxx/qla_nvme.c b/drivers/scsi/qla2xxx/qla_nvme.c index 2531e71c39dc8d..6ae5bd8f291a6b 100644 --- a/drivers/scsi/qla2xxx/qla_nvme.c +++ b/drivers/scsi/qla2xxx/qla_nvme.c @@ -1308,7 +1308,7 @@ void qla2xxx_process_purls_iocb(void **pkt, struct rsp_que **rsp) ql_dbg(ql_dbg_unsol, vha, 0x2121, "PURLS OP[%01x] size %d xchg addr 0x%x portid %06x\n", - item->iocb.iocb[3], item->size, uctx->exchange_address, + item->iocb[3], item->size, uctx->exchange_address, fcport->d_id.b24); /* +48 0 1 2 3 4 5 6 7 8 9 A B C D E F * ----- ----------------------------------------------- diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 72b1c28e4dae02..be7bad785f2da8 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -6462,9 +6462,12 @@ void qla24xx_process_purex_rdp(struct scsi_qla_host *vha, void qla24xx_free_purex_item(struct purex_item *item) { - if (item == &item->vha->default_item) - memset(&item->vha->default_item, 0, sizeof(struct purex_item)); - else + scsi_qla_host_t *base_vha = item->vha; + + if (item == &base_vha->default_item) { + memset(&base_vha->__default_item_iocb, 0, QLA_DEFAULT_PAYLOAD_SIZE); + memset(&base_vha->default_item, 0, sizeof(struct purex_item)); + } else kfree(item); } From 4654c33e10a7fb3d4206118ca200db48f2832f11 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Wed, 17 Jun 2026 15:08:00 -0400 Subject: [PATCH 14/16] Add debug statements to show fpin packets This is useful for comparing the fpin packets on lpfc vs qla2xxx to see if there are differences that could explain why the qla2xxx driver is not working. It was found that the qla2xxx driver is providing a truncated version of the fpin packet to fc_host_fpin_rcv. Signed-off-by: Jesse Taube --- drivers/scsi/scsi_transport_fc.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index af309a7bdc2215..395fe431452b34 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -900,6 +900,20 @@ fc_fpin_congn_stats_update(struct Scsi_Host *shost, &fc_host->fpin_stats); } + +void fpin_dump_buffer(const void *buf, uint size); +void fpin_dump_buffer(const void *buf, uint size) +{ + uint cnt; + + pr_warn("%-+5d 0 1 2 3 4 5 6 7 8 9 A B C D E F\n", size); + pr_warn("----- -----------------------------------------------\n"); + for (cnt = 0; cnt < size; cnt += 16) { + pr_warn("%04x: ", cnt); + print_hex_dump(KERN_CONT, "", DUMP_PREFIX_NONE, 16, 1, + buf + cnt, min(16U, size - cnt), false); + } +} /** * fc_host_fpin_rcv - routine to process a received FPIN. * @shost: host the FPIN was received on @@ -920,17 +934,24 @@ fc_host_fpin_rcv(struct Scsi_Host *shost, u32 fpin_len, char *fpin_buf, enum fc_host_event_code event_code = event_acknowledge ? FCH_EVT_LINK_FPIN_ACK : FCH_EVT_LINK_FPIN; + fpin_dump_buffer(fpin_buf, fpin_len); /* Update Statistics */ tlv = &fpin->fpin_desc[0]; bytes_remain = fpin_len - offsetof(struct fc_els_fpin, fpin_desc); bytes_remain = min_t(u32, bytes_remain, be32_to_cpu(fpin->desc_len)); - + pr_warn("%s: Got FPIN event", __func__); + pr_warn("%s: FPIN bytes_remain: %d", __func__, bytes_remain); + pr_warn("%s: FPIN FC_TLV_DESC_HDR_SZ: %ld", __func__, FC_TLV_DESC_HDR_SZ); + pr_warn("%s: FPIN FC_TLV_DESC_SZ_FROM_LENGTH: %ld", __func__, FC_TLV_DESC_SZ_FROM_LENGTH(tlv)); while (bytes_remain >= FC_TLV_DESC_HDR_SZ && bytes_remain >= FC_TLV_DESC_SZ_FROM_LENGTH(tlv)) { dtag = be32_to_cpu(tlv->hdr.desc_tag); + pr_warn("%s: Got FPIN event dtag: %x", __func__, dtag); + pr_warn("%s: expected FPIN event dtag: %x", __func__, ELS_DTAG_LNK_INTEGRITY); switch (dtag) { case ELS_DTAG_LNK_INTEGRITY: fc_fpin_li_stats_update(shost, &tlv->li); + pr_warn("%s: Got FPIN link integrity event", __func__); break; case ELS_DTAG_DELIVERY: fc_fpin_delivery_stats_update(shost, &tlv->deli); From cb1b86735327a4a56dca66d9508fc887ac3e5b45 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 23 Dec 2025 20:18:14 -0500 Subject: [PATCH 15/16] scsi: lpfc/qla2xxx: makefile change - do not commit Signed-off-by: John Meneghini --- drivers/scsi/lpfc/Makefile | 1 + drivers/scsi/qla2xxx/Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/scsi/lpfc/Makefile b/drivers/scsi/lpfc/Makefile index bbd1faf41e8065..04a8b17c0091f0 100644 --- a/drivers/scsi/lpfc/Makefile +++ b/drivers/scsi/lpfc/Makefile @@ -24,6 +24,7 @@ ccflags-$(GCOV) := -fprofile-arcs -ftest-coverage ccflags-$(GCOV) += -O0 +ccflags-y += -Wflex-array-member-not-at-end ifdef WARNINGS_BECOME_ERRORS ccflags-y += -Werror endif diff --git a/drivers/scsi/qla2xxx/Makefile b/drivers/scsi/qla2xxx/Makefile index cbc1303e761e43..b52cf96943f01b 100644 --- a/drivers/scsi/qla2xxx/Makefile +++ b/drivers/scsi/qla2xxx/Makefile @@ -6,3 +6,4 @@ qla2xxx-y := qla_os.o qla_init.o qla_mbx.o qla_iocb.o qla_isr.o qla_gs.o \ obj-$(CONFIG_SCSI_QLA_FC) += qla2xxx.o obj-$(CONFIG_TCM_QLA2XXX) += tcm_qla2xxx.o +ccflags-y += -Wflex-array-member-not-at-end From 2d7cceb4a47f818135303b32eaa2cd6ad7619a04 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Wed, 1 Jul 2026 10:08:34 -0400 Subject: [PATCH 16/16] tmp --- drivers/nvme/host/multipath.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 285fed4a039524..11f474704fdd71 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -490,7 +490,7 @@ static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) { struct nvme_ns *best_opt = NULL, *best_nonopt = NULL, *ns; - unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX; + int min_depth_opt = INT_MAX, min_depth_nonopt = INT_MAX; bool opt_is_marginal = true, nonopt_is_marginal = true, marginal; unsigned int depth; @@ -524,10 +524,17 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) break; } - if (min_depth_opt == 0) + if (min_depth_opt == 0 && !opt_is_marginal) return best_opt; } + /* + * Prefer non-marginal non-optimized path + * over a marginal optimized path. + */ + if (opt_is_marginal && !nonopt_is_marginal && best_nonopt) + return best_nonopt; + return best_opt ? best_opt : best_nonopt; }