[PATCH 08/13] scsi: fnic: Handle NVMe LS frames in FDLS

From: Karan Tilak Kumar

Date: Thu May 21 2026 - 14:10:29 EST


Classify NVMe LS request OXIDs, route NVMe LS responses and ABTS frames
through the FCS receive path, and reset NVMe exchanges when FDLS tears
down target ports.

Extend FDLS link-down and frame-processing paths so NVMe LS traffic
follows the same discovery and cleanup state machine as FCP traffic.

Reviewed-by: Sesidhar Baddela <sebaddel@xxxxxxxxx>
Reviewed-by: Arulprabhu Ponnusamy <arulponn@xxxxxxxxx>
Reviewed-by: Gian Carlo Boffa <gcboffa@xxxxxxxxx>
Reviewed-by: Arun Easi <aeasi@xxxxxxxxx>
Reviewed-by: Hannes Reinecke <hare@xxxxxxxxxx>
Reviewed-by: Lee Duncan <lduncan@xxxxxxxx>
Signed-off-by: Karan Tilak Kumar <kartilak@xxxxxxxxx>
Co-developed-by: Hannes Reinecke <hare@xxxxxxxxxx>
---
drivers/scsi/fnic/fdls_disc.c | 30 ++++++++++++++++++++++++++++++
drivers/scsi/fnic/fnic_fcs.c | 33 +++++++++++++++++++++++++++------
2 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/drivers/scsi/fnic/fdls_disc.c b/drivers/scsi/fnic/fdls_disc.c
index 9ecbb967be2e..0fcdfdea8fb4 100644
--- a/drivers/scsi/fnic/fdls_disc.c
+++ b/drivers/scsi/fnic/fdls_disc.c
@@ -387,10 +387,16 @@ static bool fdls_is_oxid_tgt_req(uint16_t oxid)
return true;
}

+static inline bool fdls_is_oxid_nvme_req(uint16_t oxid)
+{
+ return FNIC_FRAME_TYPE(oxid) == FNIC_FRAME_TYPE_NVME_LS;
+}
+
static void fdls_reset_oxid_pool(struct fnic_iport_s *iport)
{
struct fnic_oxid_pool_s *oxid_pool = &iport->oxid_pool;

+ bitmap_clear(oxid_pool->bitmap, 0, FNIC_OXID_POOL_SZ);
oxid_pool->next_idx = 0;
}

@@ -1288,6 +1294,10 @@ bool fdls_delete_tport(struct fnic_iport_s *iport, struct fnic_tport_s *tport)
spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
fnic_rport_exch_reset(iport->fnic, tport->fcid);
spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
+ } else if (IS_FNIC_NVME_INITIATOR(fnic)) {
+ spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
+ nvfnic_exch_reset(iport, tport);
+ spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
}

if ((tport->flags & FNIC_FDLS_SCSI_REGISTERED) ||
@@ -1828,6 +1838,7 @@ static struct fnic_tport_s *fdls_create_tport(struct fnic_iport_s *iport,
tport->fcid = fcid;
tport->wwpn = wwpn;
tport->iport = iport;
+ INIT_LIST_HEAD(&tport->ls_req_list);

FNIC_FCS_DBG(KERN_DEBUG, fnic,
"Need to setup tport timer callback");
@@ -2439,6 +2450,8 @@ static void fdls_tport_timer_callback(struct timer_list *t)
struct fnic *fnic = iport->fnic;
uint16_t oxid;
unsigned long flags;
+ struct fc_frame_header fchdr = {0};
+ uint8_t fcid[3];

spin_lock_irqsave(&fnic->fnic_lock, flags);
if (!tport->timer_pending) {
@@ -2531,6 +2544,12 @@ static void fdls_tport_timer_callback(struct timer_list *t)
FNIC_FCS_DBG(KERN_INFO, fnic,
"0x%x timeout for tport 0x%x unhandled state %d\n",
iport->fcid, tport->fcid, tport->state);
+ if (IS_FNIC_NVME_INITIATOR(fnic)) {
+ hton24(fcid, tport->fcid);
+ FNIC_STD_SET_S_ID(fchdr, fcid);
+ FNIC_STD_SET_OX_ID(fchdr, oxid);
+ nvfnic_process_ls_abts_rsp(iport, &fchdr);
+ }
break;
}
spin_unlock_irqrestore(&fnic->fnic_lock, flags);
@@ -2841,6 +2860,12 @@ fdls_process_tgt_prli_rsp(struct fnic_iport_s *iport,
"mismatched target zoned with FC SCSI initiator: 0x%x",
tgt_fcid);
mismatched_tgt = true;
+ } else if (IS_FNIC_NVME_INITIATOR(fnic) &&
+ prli_rsp->sp.spp_type != FC_TYPE_NVME) {
+ FNIC_FCS_DBG(KERN_ERR, fnic,
+ "mismatched target zoned with NVME initiator: 0x%x",
+ tgt_fcid);
+ mismatched_tgt = true;
}
if (mismatched_tgt) {
fdls_tgt_logout(iport, tport);
@@ -4852,6 +4877,8 @@ fnic_fdls_validate_and_get_frame_type(struct fnic_iport_s *iport,
return FNIC_FDMI_BLS_ABTS_RSP;
} else if (fdls_is_oxid_tgt_req(oxid)) {
return FNIC_TPORT_BLS_ABTS_RSP;
+ } else if (fdls_is_oxid_nvme_req(oxid)) {
+ return FNIC_LS_REQ_ABTS_RSP;
}
FNIC_FCS_DBG(KERN_INFO, fnic,
"Received ABTS rsp with unknown oxid(0x%x) from 0x%x. Dropping frame",
@@ -5084,6 +5111,9 @@ void fnic_fdls_recv_frame(struct fnic_iport_s *iport, void *rx_frame,
case FNIC_FABRIC_BLS_ABTS_RSP:
fdls_process_fabric_abts_rsp(iport, fchdr);
break;
+ case FNIC_LS_REQ_ABTS_RSP:
+ nvfnic_process_ls_abts_rsp(iport, fchdr);
+ break;
case FNIC_FDMI_BLS_ABTS_RSP:
fdls_process_fdmi_abts_rsp(iport, fchdr);
break;
diff --git a/drivers/scsi/fnic/fnic_fcs.c b/drivers/scsi/fnic/fnic_fcs.c
index ac1febaa8474..24fc36c0ad5c 100644
--- a/drivers/scsi/fnic/fnic_fcs.c
+++ b/drivers/scsi/fnic/fnic_fcs.c
@@ -31,6 +31,11 @@ struct workqueue_struct *fnic_event_queue;

static uint8_t FCOE_ALL_FCF_MAC[6] = FC_FCOE_FLOGI_MAC;

+static inline bool fnic_is_nvme_frame(struct fc_frame_header *fchdr)
+{
+ return (fchdr->fh_type == FC_TYPE_NVME);
+}
+
/*
* Internal Functions
* This function will initialize the src_mac address to be
@@ -284,6 +289,7 @@ void fnic_handle_frame(struct work_struct *work)
struct fnic *fnic = container_of(work, struct fnic, frame_work);
struct fnic_frame_list *cur_frame, *next;
int fchdr_offset = 0;
+ struct fc_frame_header *fchdr;

spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
list_for_each_entry_safe(cur_frame, next, &fnic->frame_queue, links) {
@@ -313,8 +319,14 @@ void fnic_handle_frame(struct work_struct *work)
fchdr_offset = (cur_frame->rx_ethhdr_stripped) ?
0 : FNIC_ETH_FCOE_HDRS_OFFSET;

- fnic_fdls_recv_frame(&fnic->iport, cur_frame->fp,
- cur_frame->frame_len, fchdr_offset);
+ fchdr = (struct fc_frame_header *)((u8 *)cur_frame->fp + fchdr_offset);
+ if (IS_FNIC_NVME_INITIATOR(fnic) && fnic_is_nvme_frame(fchdr)) {
+ nvfnic_ls_rsp_recv(&fnic->iport, fchdr,
+ cur_frame->frame_len - fchdr_offset);
+ } else {
+ fnic_fdls_recv_frame(&fnic->iport, cur_frame->fp,
+ cur_frame->frame_len, fchdr_offset);
+ }

mempool_free(cur_frame->fp, fnic->frame_recv_pool);
mempool_free(cur_frame, fnic->frame_elem_pool);
@@ -617,6 +629,9 @@ void fnic_free_rq_buf(struct vnic_rq *rq, struct vnic_rq_buf *buf)
void *rq_buf = buf->os_buf;
struct fnic *fnic = vnic_dev_priv(rq->vdev);

+ if (WARN_ON(!buf))
+ return;
+
dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len,
DMA_FROM_DEVICE);

@@ -651,7 +666,7 @@ static int fnic_send_frame(struct fnic *fnic, void *frame, int frame_len)
dma_unmap_single(&fnic->pdev->dev, pa, frame_len, DMA_TO_DEVICE);
FNIC_FCS_DBG(KERN_INFO, fnic,
"vnic work queue descriptor is not available");
- ret = -1;
+ ret = -ENXIO;
goto fnic_send_frame_end;
}

@@ -685,7 +700,6 @@ fdls_send_fcoe_frame(struct fnic *fnic, void *frame, int frame_size,
struct fcoe_hdr *pfcoe_hdr;
struct fnic_frame_list *frame_elem;
int len = frame_size;
- int ret;
struct fc_frame_header *fchdr = (struct fc_frame_header *) (frame +
FNIC_ETH_FCOE_HDRS_OFFSET);

@@ -723,8 +737,7 @@ fdls_send_fcoe_frame(struct fnic *fnic, void *frame, int frame_size,

fnic_debug_dump_fc_frame(fnic, fchdr, frame_size, "Outgoing");

- ret = fnic_send_frame(fnic, frame, len);
- return ret;
+ return fnic_send_frame(fnic, frame, len);
}

int fnic_send_fcoe_frame(struct fnic_iport_s *iport, void *frame,
@@ -872,6 +885,9 @@ static void fnic_wq_complete_frame_send(struct vnic_wq *wq,
{
struct fnic *fnic = vnic_dev_priv(wq->vdev);

+ if (WARN_ON(!buf))
+ return;
+
dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len,
DMA_TO_DEVICE);
mempool_free(buf->os_buf, fnic->frame_pool);
@@ -917,6 +933,9 @@ void fnic_free_wq_buf(struct vnic_wq *wq, struct vnic_wq_buf *buf)
dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len,
DMA_TO_DEVICE);

+ if (WARN_ON(!buf))
+ return;
+
mempool_free(buf->os_buf, fnic->frame_pool);
buf->os_buf = NULL;
}
@@ -1048,6 +1067,8 @@ void fnic_tport_event_handler(struct work_struct *work)
if (tport->state == FDLS_TGT_STATE_READY) {
if (IS_FNIC_FCP_INITIATOR(fnic))
fnic_fdls_add_tport(&fnic->iport, tport, flags);
+ else if (IS_FNIC_NVME_INITIATOR(fnic))
+ nvfnic_add_tport(fnic, tport);
} else {
FNIC_FCS_DBG(KERN_INFO, fnic,
"Target not ready. Add rport event dropped: 0x%x",
--
2.47.1