From 9c2aa65000f6f99e543500cabe4c67ff758d3ff6 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:17 -0700 Subject: [PATCH 01/33] scsi: ibmvfc: don't call locked done variant for MADs on send failure MADs are processed with the requirement that the host_lock is held for MAD sends and completions. When a MAD job step is called the host lock is already held and there is an unlikely scenario where h_send_crq fails with something other than H_CLOSED. If this happens there is a special done handler for MADs that tries to take the host lock. In this failure scenario fall back to the saved evt->_done unlocked completion handler. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-2-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 3dd2adda195e..0a5f73df8d7d 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -1774,8 +1774,10 @@ static int ibmvfc_send_event(struct ibmvfc_event *evt, if (evt->cmnd) { evt->cmnd->result = DID_ERROR << 16; evt->done = ibmvfc_scsi_eh_done; - } else + } else { evt->xfer_iu->mad_common.status = cpu_to_be16(IBMVFC_MAD_CRQ_ERROR); + evt->done = evt->_done; + } evt->done(evt); } else { From 7e35396ec853983e7a85de9c4e5ad5b3c090c018 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:18 -0700 Subject: [PATCH 02/33] scsi: ibmvfc: flush rport_add_work_q during driver teardown INIT_WORK is called on rport_add_work_q during driver probe. When the adapter is removed, ibmvfc_remove() stops the work thread and proceeds to tear down the FC host, but any work item queued via schedule_work() that raced the teardown could still be running or pending in the system workqueue. Add a flush_work() call after kthread_stop() and before fc_remove_host() to drain any outstanding rport add work before the FC host and its rports are torn down. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-3-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 0a5f73df8d7d..72317d7840a5 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -6443,6 +6443,7 @@ static void ibmvfc_remove(struct vio_dev *vdev) ibmvfc_wait_while_resetting(vhost); kthread_stop(vhost->work_thread); + flush_work(&vhost->rport_add_work_q); fc_remove_host(vhost->host); scsi_remove_host(vhost->host); From 0122eab60b151d7815f420a91c7315944a037714 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:19 -0700 Subject: [PATCH 03/33] scsi: ibmvfc: check for NULL evt in implicit LOGO and target delete path Unlike ibmvfc_tgt_implicit_logout the ibmvfc_tgt_implicit_logout_and_del path fails to check for a NULL evt returned from ibmvfc_get_reserved_event and passes that directly to ibmvfc_send_event which will unconditionally derefence the evt. Check for a NULL evt and perform same escalation as ibmvfc_implicit_logout. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-4-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 72317d7840a5..59102dd1c787 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -4406,6 +4406,14 @@ static void ibmvfc_tgt_implicit_logout_and_del(struct ibmvfc_target *tgt) evt = __ibmvfc_tgt_get_implicit_logout_evt(tgt, ibmvfc_tgt_implicit_logout_and_del_done); + if (!evt) { + vhost->discovery_threads--; + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); + kref_put(&tgt->kref, ibmvfc_release_tgt); + __ibmvfc_reset_host(vhost); + return; + } + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT); if (ibmvfc_send_event(evt, vhost, default_timeout)) { vhost->discovery_threads--; From 99143433f7f14139ac373c553ec8e1317d0ae58a Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:20 -0700 Subject: [PATCH 04/33] scsi: ibmvfc: free ibmvfc_target allocations with mempool_free The ibmvfc_target *tgt structures for discovery are allocated using mempool_alloc but in turn free'd via kfree in ibmvfc_release_target breaking pool guarentees. Fixup ibmvfc_release_target to call mempool_free instead so that memory is properly returned to the mempool instead of the general allocator. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-5-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 59102dd1c787..412e946abcb7 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -1366,7 +1366,7 @@ static void ibmvfc_set_rport_dev_loss_tmo(struct fc_rport *rport, u32 timeout) static void ibmvfc_release_tgt(struct kref *kref) { struct ibmvfc_target *tgt = container_of(kref, struct ibmvfc_target, kref); - kfree(tgt); + mempool_free(tgt, tgt->vhost->tgt_pool); } /** From de7ac0f3f08b1b411adcc48835e90f36e242f0c3 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:21 -0700 Subject: [PATCH 05/33] scsi: ibmvfc: move target list from host to protocol specific channel groups Prepare the driver for protocol-specific target management by moving the target list and target count out of struct ibmvfc_host and into struct ibmvfc_channels. Today the driver only maintains a single SCSI target list, but NVMe/FC support will require separate target tracking for each protocol-specific channel group. Update the existing target iteration, allocation, and discovery paths to use the SCSI channel group's target list instead of a host-wide list. This is a preparatory refactoring only. No functional change is intended for existing SCSI operation. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-6-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.c | 52 +++++++++++++++++----------------- drivers/scsi/ibmvscsi/ibmvfc.h | 4 +-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 412e946abcb7..78b4ac201a49 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -682,7 +682,7 @@ static void ibmvfc_link_down(struct ibmvfc_host *vhost, ENTER; scsi_block_requests(vhost->host); - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) ibmvfc_del_tgt(tgt); ibmvfc_set_host_state(vhost, state); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_TGT_DEL); @@ -715,7 +715,7 @@ static void ibmvfc_init_host(struct ibmvfc_host *vhost) memset(vhost->async_crq.msgs.async, 0, PAGE_SIZE); vhost->async_crq.cur = 0; - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (vhost->client_migrated) tgt->need_login = 1; else @@ -1232,7 +1232,7 @@ static struct ibmvfc_target *__ibmvfc_get_target(struct scsi_target *starget) struct ibmvfc_host *vhost = shost_priv(shost); struct ibmvfc_target *tgt; - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->target_id == starget->id) { kref_get(&tgt->kref); return tgt; @@ -1834,7 +1834,7 @@ static void ibmvfc_relogin(struct scsi_device *sdev) unsigned long flags; spin_lock_irqsave(vhost->host->host_lock, flags); - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (rport == tgt->rport) { ibmvfc_del_tgt(tgt); break; @@ -2132,7 +2132,7 @@ static int ibmvfc_bsg_plogi(struct ibmvfc_host *vhost, unsigned int port_id) ENTER; spin_lock_irqsave(vhost->host->host_lock, flags); - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->scsi_id == port_id) { issue_login = 0; break; @@ -3104,7 +3104,7 @@ static void ibmvfc_terminate_rport_io(struct fc_rport *rport) spin_lock_irqsave(shost->host_lock, flags); found = 0; - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->scsi_id == rport->port_id) { found++; break; @@ -3244,7 +3244,7 @@ static void ibmvfc_handle_async(struct ibmvfc_async_crq *crq, case IBMVFC_AE_ELS_LOGO: case IBMVFC_AE_ELS_PRLO: case IBMVFC_AE_ELS_PLOGI: - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (!crq->scsi_id && !crq->wwpn && !crq->node_name) break; if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id) @@ -4873,14 +4873,14 @@ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, /* Look to see if we already have a target allocated for this SCSI ID or WWPN */ spin_lock_irqsave(vhost->host->host_lock, flags); - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->wwpn == wwpn) { wtgt = tgt; break; } } - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->scsi_id == scsi_id) { stgt = tgt; break; @@ -4937,7 +4937,7 @@ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, ibmvfc_init_tgt(tgt, ibmvfc_tgt_implicit_logout); spin_lock_irqsave(vhost->host->host_lock, flags); tgt->cancel_key = vhost->task_set++; - list_add_tail(&tgt->queue, &vhost->targets); + list_add_tail(&tgt->queue, &vhost->scsi_scrqs.targets); unlock_out: spin_unlock_irqrestore(vhost->host->host_lock, flags); @@ -4955,7 +4955,7 @@ static int ibmvfc_alloc_targets(struct ibmvfc_host *vhost) { int i, rc; - for (i = 0, rc = 0; !rc && i < vhost->num_targets; i++) + for (i = 0, rc = 0; !rc && i < vhost->scsi_scrqs.num_targets; i++) rc = ibmvfc_alloc_target(vhost, &vhost->scsi_scrqs.disc_buf[i]); return rc; @@ -4976,8 +4976,8 @@ static void ibmvfc_discover_targets_done(struct ibmvfc_event *evt) switch (mad_status) { case IBMVFC_MAD_SUCCESS: ibmvfc_dbg(vhost, "Discover Targets succeeded\n"); - vhost->num_targets = min_t(u32, be32_to_cpu(rsp->num_written), - max_targets); + vhost->scsi_scrqs.num_targets = min_t(u32, be32_to_cpu(rsp->num_written), + max_targets); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_ALLOC_TGTS); break; case IBMVFC_MAD_FAILED: @@ -5393,7 +5393,7 @@ static int ibmvfc_dev_init_to_do(struct ibmvfc_host *vhost) { struct ibmvfc_target *tgt; - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_INIT || tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) return 1; @@ -5413,7 +5413,7 @@ static int ibmvfc_dev_logo_to_do(struct ibmvfc_host *vhost) { struct ibmvfc_target *tgt; - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT || tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) return 1; @@ -5443,10 +5443,10 @@ static int __ibmvfc_work_to_do(struct ibmvfc_host *vhost) case IBMVFC_HOST_ACTION_QUERY_TGTS: if (vhost->discovery_threads == disc_threads) return 0; - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_INIT) return 1; - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) return 0; return 1; @@ -5454,10 +5454,10 @@ static int __ibmvfc_work_to_do(struct ibmvfc_host *vhost) case IBMVFC_HOST_ACTION_TGT_DEL_FAILED: if (vhost->discovery_threads == disc_threads) return 0; - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT) return 1; - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) return 0; return 1; @@ -5645,12 +5645,12 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) vhost->job_step(vhost); break; case IBMVFC_HOST_ACTION_QUERY: - list_for_each_entry(tgt, &vhost->targets, queue) + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) ibmvfc_init_tgt(tgt, ibmvfc_tgt_query_target); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY_TGTS); break; case IBMVFC_HOST_ACTION_QUERY_TGTS: - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_INIT) { tgt->job_step(tgt); break; @@ -5662,7 +5662,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) break; case IBMVFC_HOST_ACTION_TGT_DEL: case IBMVFC_HOST_ACTION_TGT_DEL_FAILED: - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT) { tgt->job_step(tgt); break; @@ -5674,7 +5674,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) return; } - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_DEL_RPORT) { tgt_dbg(tgt, "Deleting rport\n"); rport = tgt->rport; @@ -5749,7 +5749,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) spin_lock_irqsave(vhost->host->host_lock, flags); break; case IBMVFC_HOST_ACTION_TGT_INIT: - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->action == IBMVFC_TGT_ACTION_INIT) { tgt->job_step(tgt); break; @@ -6286,7 +6286,7 @@ static void ibmvfc_rport_add_thread(struct work_struct *work) if (vhost->state != IBMVFC_ACTIVE) break; - list_for_each_entry(tgt, &vhost->targets, queue) { + list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { if (tgt->add_rport) { did_work = 1; tgt->add_rport = 0; @@ -6351,7 +6351,7 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id) shost->nr_hw_queues = mq_enabled ? min(max_scsi_queues, nr_scsi_hw_queues) : 1; vhost = shost_priv(shost); - INIT_LIST_HEAD(&vhost->targets); + INIT_LIST_HEAD(&vhost->scsi_scrqs.targets); INIT_LIST_HEAD(&vhost->purge); sprintf(vhost->name, IBMVFC_NAME); vhost->host = shost; diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index c73ed2314ad0..0e259e9d2e9b 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -828,6 +828,8 @@ struct ibmvfc_channels { unsigned int active_queues; unsigned int desired_queues; unsigned int max_queues; + int num_targets; + struct list_head targets; int disc_buf_sz; struct ibmvfc_discover_targets_entry *disc_buf; dma_addr_t disc_buf_dma; @@ -871,8 +873,6 @@ struct ibmvfc_host { #define IBMVFC_TRACE_SIZE (sizeof(struct ibmvfc_trace_entry) * IBMVFC_NUM_TRACE_ENTRIES) struct ibmvfc_trace_entry *trace; atomic_t trace_index; - int num_targets; - struct list_head targets; struct list_head purge; struct device *dev; struct dma_pool *sg_pool; From 4991c8f57b61480c83736f3224952a5f55e324df Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:22 -0700 Subject: [PATCH 06/33] scsi: ibmvfc: add NVMe/FC protocol interface definitions Add the protocol definitions for client-VIOS interface updates needed to support NVMe/FC over the ibmvfc NPIV transport. Extend the ibmvfc interface with: - NVMe/FC-specific capability bits and opcodes - protocol-specific channel and queue definitions - updated channel enquiry/setup fields for NVMe queues - v3 command layout support for protocol-specific payloads These changes provide the common header and interface plumbing needed by later patches that add NVMe/FC login, discovery, remote-port handling, and I/O submission. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-7-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.h | 153 +++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 34 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 0e259e9d2e9b..f8a2bf92da41 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #define IBMVFC_NAME "ibmvfc" #define IBMVFC_DRIVER_VERSION "1.0.11" @@ -92,6 +94,7 @@ enum ibmvfc_cmd_status_flags { IBMVFC_FC_SCSI_ERROR = 0x0008, IBMVFC_HW_EVENT_LOGGED = 0x0010, IBMVFC_VIOS_LOGGED = 0x0020, + IBMVFC_FC_NVME_STATUS = 0x0040, }; enum ibmvfc_fabric_mapped_errors { @@ -124,20 +127,37 @@ enum ibmvfc_vios_errors { IBMVFC_COMMAND_FAILED = 0x8000, }; +enum ibmvfc_fc_nvme_errors { + IBMVFC_NVMS_VALID_ERSP = 0x0001, + IBMVFC_NVMS_VALID_NODMA_CQE = 0x0002, +}; + enum ibmvfc_mad_types { IBMVFC_NPIV_LOGIN = 0x0001, - IBMVFC_DISC_TARGETS = 0x0002, + IBMVFC_DISC_TARGETS = 0x0002, + IBMVFC_DISC_NVMF_TARGETS = 0x0003, IBMVFC_PORT_LOGIN = 0x0004, - IBMVFC_PROCESS_LOGIN = 0x0008, - IBMVFC_QUERY_TARGET = 0x0010, + IBMVFC_NVMF_PORT_LOGIN = 0x0005, + IBMVFC_PROCESS_LOGIN = 0x0008, + IBMVFC_NVMF_PROCESS_LOGIN = 0x0009, + IBMVFC_QUERY_TARGET = 0x0010, + IBMVFC_NVMF_QUERY_TARGET = 0x0011, IBMVFC_MOVE_LOGIN = 0x0020, - IBMVFC_IMPLICIT_LOGOUT = 0x0040, - IBMVFC_PASSTHRU = 0x0200, - IBMVFC_TMF_MAD = 0x0100, - IBMVFC_NPIV_LOGOUT = 0x0800, - IBMVFC_CHANNEL_ENQUIRY = 0x1000, - IBMVFC_CHANNEL_SETUP = 0x2000, - IBMVFC_CONNECTION_INFO = 0x4000, + IBMVFC_NVMF_MOVE_LOGIN = 0x0021, + IBMVFC_IMPLICIT_LOGOUT = 0x0040, + IBMVFC_NVMF_IMPLICIT_LOGOUT = 0x0041, + IBMVFC_RNID = 0x0080, + IBMVFC_NVMF_RNID = 0x0081, + IBMVFC_TMF_MAD = 0x0100, + IBMVFC_NVMF_TMF_MAD = 0x0101, + IBMVFC_PASSTHRU = 0x0200, + IBMVFC_NVMF_PASSTHRU = 0x0201, + IBMVFC_NPIV_LOGOUT = 0x0800, + IBMVFC_CHANNEL_ENQUIRY = 0x1000, + IBMVFC_CHANNEL_SETUP = 0x2000, + IBMVFC_CONNECTION_INFO = 0x4000, + IBMVFC_FABRIC_LOGIN = 0x8000, + IBMVFC_NVMF_FABRIC_LOGIN = 0x8001, }; struct ibmvfc_mad_common { @@ -175,11 +195,16 @@ struct ibmvfc_npiv_login { #define IBMVFC_FLUSH_ON_HALT 0x02 __be32 max_cmds; __be64 capabilities; -#define IBMVFC_CAN_MIGRATE 0x01 -#define IBMVFC_CAN_USE_CHANNELS 0x02 -#define IBMVFC_CAN_HANDLE_FPIN 0x04 -#define IBMVFC_CAN_USE_MAD_VERSION 0x08 -#define IBMVFC_CAN_SEND_VF_WWPN 0x10 +#define IBMVFC_CAN_MIGRATE 0x001 +#define IBMVFC_CAN_USE_CHANNELS 0x002 +#define IBMVFC_CAN_HANDLE_FPIN 0x004 +#define IBMVFC_CAN_USE_MAD_VERSION 0x008 +#define IBMVFC_CAN_SEND_VF_WWPN 0x010 +#define IBMVFC_YES_NVMEOF 0x020 +#define IBMVFC_YES_SCSI 0x040 +#define IBMVFC_CAN_USE_WWPN_ALL 0x080 +#define IBMVFC_USE_ASYNC_SUBQ 0x100 +#define IBMVFC_CAN_USE_NOOP_CMD 0x200 __be64 node_name; struct srp_direct_buf async; u8 partition_name[IBMVFC_MAX_NAME]; @@ -219,13 +244,18 @@ struct ibmvfc_npiv_login_resp { __be16 error; __be32 flags; #define IBMVFC_NATIVE_FC 0x01 - __be32 reserved; + __be32 possible_nports; __be64 capabilities; -#define IBMVFC_CAN_FLUSH_ON_HALT 0x08 -#define IBMVFC_CAN_SUPPRESS_ABTS 0x10 -#define IBMVFC_MAD_VERSION_CAP 0x20 -#define IBMVFC_HANDLE_VF_WWPN 0x40 -#define IBMVFC_CAN_SUPPORT_CHANNELS 0x80 +#define IBMVFC_CAN_FLUSH_ON_HALT 0x0008 +#define IBMVFC_CAN_SUPPRESS_ABTS 0x0010 +#define IBMVFC_MAD_VERSION_CAP 0x0020 +#define IBMVFC_HANDLE_VF_WWPN 0x0040 +#define IBMVFC_CAN_SUPPORT_CHANNELS 0x0080 +#define IBMVFC_SUPPORT_NVMEOF 0x0100 +#define IBMVFC_SUPPORT_SCSI 0x0200 +#define IBMVFC_SUPPORT_WWPN_ALL 0x0400 +#define IBMVFC_ASYNC_SUBQ 0x0800 +#define IBMVFC_SUPPORT_NOOP_CMD 0x1000 __be32 max_cmds; __be32 scsi_id_sz; __be64 max_dma_len; @@ -238,7 +268,7 @@ struct ibmvfc_npiv_login_resp { u8 port_loc_code[IBMVFC_MAX_NAME]; u8 drc_name[IBMVFC_MAX_NAME]; struct ibmvfc_service_parms service_parms; - __be64 reserved2; + __be64 reserved; } __packed __aligned(8); union ibmvfc_npiv_login_data { @@ -246,6 +276,17 @@ union ibmvfc_npiv_login_data { struct ibmvfc_npiv_login_resp resp; } __packed __aligned(8); +struct ibmvfc_fabric_login_mad { + struct ibmvfc_mad_common common; + __be64 flags; + __be64 capabilities; + __be64 nport_id; + __be16 status; + __be16 error; + __be32 reserved; + __be64 reserved2[16]; +} __packed __aligned(8); + struct ibmvfc_discover_targets_entry { __be32 scsi_id; __be32 pad; @@ -287,6 +328,7 @@ enum ibmvfc_fc_type { IBMVFC_FABRIC_BUSY = 0x04, IBMVFC_PORT_BUSY = 0x05, IBMVFC_BASIC_REJECT = 0x06, + IBMVFC_FC4_LS_REJECT = 0x07, }; enum ibmvfc_gs_explain { @@ -377,20 +419,27 @@ struct ibmvfc_query_tgt { struct ibmvfc_implicit_logout { struct ibmvfc_mad_common common; __be64 old_scsi_id; - __be64 reserved[2]; + __be64 reserved[8]; + __be64 target_wwpn; } __packed __aligned(8); struct ibmvfc_tmf { struct ibmvfc_mad_common common; __be64 scsi_id; - struct scsi_lun lun; + union { + struct scsi_lun lun; + __be64 assoc_id; + }; __be32 flags; -#define IBMVFC_TMF_ABORT_TASK 0x02 -#define IBMVFC_TMF_ABORT_TASK_SET 0x04 -#define IBMVFC_TMF_LUN_RESET 0x10 -#define IBMVFC_TMF_TGT_RESET 0x20 -#define IBMVFC_TMF_LUA_VALID 0x40 -#define IBMVFC_TMF_SUPPRESS_ABTS 0x80 +#define IBMVFC_TMF_ABORT_TASK 0x002 +#define IBMVFC_TMF_ABORT_TASK_SET 0x004 +#define IBMVFC_TMF_LUN_RESET 0x010 +#define IBMVFC_TMF_TGT_RESET 0x020 +#define IBMVFC_TMF_LUA_VALID 0x040 +#define IBMVFC_TMF_SUPPRESS_ABTS 0x080 +#define IBMVFC_TMF_NVMF_ASSOC 0x100 +#define IBMVFC_TMF_NVMF_TARGET 0x200 +#define IBMVFC_TMF_BITMASK_VALID 0x400 __be32 cancel_key; __be32 my_cancel_key; __be32 pad; @@ -446,6 +495,8 @@ enum ibmvfc_cmd_flags { IBMVFC_WRITE = 0x0008, IBMVFC_TMF = 0x0080, IBMVFC_CLASS_3_ERR = 0x0100, + IBMVFC_NVMEOF_PROTOCOL = 0x0200, + IBMVFC_NVMF_SLER = 0x0400, }; enum ibmvfc_fc_task_attr { @@ -493,7 +544,7 @@ struct ibmvfc_cmd { __be64 tgt_scsi_id; __be64 tag; __be64 target_wwpn; - __be64 reserved3; + __be64 assoc_id; union { struct { struct ibmvfc_fcp_cmd_iu iu; @@ -504,6 +555,15 @@ struct ibmvfc_cmd { struct ibmvfc_fcp_cmd_iu iu; struct ibmvfc_fcp_rsp rsp; } v2; + struct { + __be64 reserved5[4]; + struct ibmvfc_fcp_cmd_iu iu; + struct ibmvfc_fcp_rsp rsp; + } v3scsi; + struct { + __be64 reserved[4]; + struct nvme_fc_cmd_iu iu; + } v3nvme; }; } __packed __aligned(8); @@ -522,6 +582,9 @@ struct ibmvfc_passthru_iu { __be32 flags; #define IBMVFC_FC_ELS 0x01 #define IBMVFC_FC_CT_IU 0x02 +#define IBMVFC_PT_PRLI 0x04 +#define IBMVFC_FC4_LS_OTH 0x08 +#define IBMVFC_FC4_LS_DSC_CTRL 0x10 __be32 cancel_key; #define IBMVFC_PASSTHRU_CANCEL_KEY 0x80000000 #define IBMVFC_INTERNAL_CANCEL_KEY 0x80000001 @@ -559,7 +622,7 @@ struct ibmvfc_channel_setup_mad { struct srp_direct_buf buffer; } __packed __aligned(8); -#define IBMVFC_MAX_CHANNELS 502 +#define IBMVFC_MAX_CHANNELS 501 struct ibmvfc_channel_setup { __be32 flags; @@ -574,6 +637,7 @@ struct ibmvfc_channel_setup { struct srp_direct_buf buffer; __be64 reserved2[5]; __be64 channel_handles[IBMVFC_MAX_CHANNELS]; + __be64 async_sub_crq_handle; } __packed __aligned(8); struct ibmvfc_connection_info { @@ -620,7 +684,8 @@ struct ibmvfc_trace_entry { enum ibmvfc_crq_formats { IBMVFC_CMD_FORMAT = 0x01, - IBMVFC_ASYNC_EVENT = 0x02, + IBMVFC_ASYNC_EVENT = 0x02, + IBMVFC_NOOP = 0x03, IBMVFC_MAD_FORMAT = 0x04, }; @@ -639,6 +704,9 @@ enum ibmvfc_async_event { IBMVFC_AE_RESUME = 0x0800, IBMVFC_AE_ADAPTER_FAILED = 0x1000, IBMVFC_AE_FPIN = 0x2000, + IBMVFC_NVME_DISCONNECT = 0x4000, + IBMVFC_NVMEOF_PROTO_AVAIL = 0x40000000, + IBMVFC_SCSI_PROTO_AVAIL = 0x80000000, }; struct ibmvfc_async_desc { @@ -683,13 +751,30 @@ struct ibmvfc_async_crq { volatile __be64 scsi_id; volatile __be64 wwpn; volatile __be64 node_name; - __be64 reserved; + __be64 assoc_id; +} __packed __aligned(8); + +struct ibmvfc_async_sub_crq { + volatile u8 valid; + u8 flags; +#define IBMVFC_ASYNC_ID_IS_ASSOC_ID 0x01 + u8 link_state; + u8 fpin_status; + __be16 event; + __be16 pad; + __be64 wwpn; + __be64 nport_id; + union { + __be64 node_name; + __be64 assoc_id; + } id; } __packed __aligned(8); union ibmvfc_iu { struct ibmvfc_mad_common mad_common; struct ibmvfc_npiv_login_mad npiv_login; struct ibmvfc_npiv_logout_mad npiv_logout; + struct ibmvfc_fabric_login_mad fabric_login; struct ibmvfc_discover_targets discover_targets; struct ibmvfc_port_login plogi; struct ibmvfc_process_login prli; From 848c70852e3fb82d881ea6b917436c4b1046fdc7 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:23 -0700 Subject: [PATCH 07/33] scsi: ibmvfc: split NVMe support into separate source file and add transport stubs Rename ibmvfc.c to ibmvfc-core.c as first step in decoupling each protocol from the core driver logic. Add ibmvfc-nvme.[ch] files, and register an nvme_fc_port_template with empty callback stubs. Add empty registration functions definitions for local and remote ports. No functional NVMe/FC support is added yet. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-8-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/Makefile | 2 + .../scsi/ibmvscsi/{ibmvfc.c => ibmvfc-core.c} | 19 +++- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 97 +++++++++++++++++++ drivers/scsi/ibmvscsi/ibmvfc-nvme.h | 32 ++++++ drivers/scsi/ibmvscsi/ibmvfc.h | 10 +- 5 files changed, 156 insertions(+), 4 deletions(-) rename drivers/scsi/ibmvscsi/{ibmvfc.c => ibmvfc-core.c} (99%) create mode 100644 drivers/scsi/ibmvscsi/ibmvfc-nvme.c create mode 100644 drivers/scsi/ibmvscsi/ibmvfc-nvme.h diff --git a/drivers/scsi/ibmvscsi/Makefile b/drivers/scsi/ibmvscsi/Makefile index 5eb1cb1a0028..9408c7f4cdee 100644 --- a/drivers/scsi/ibmvscsi/Makefile +++ b/drivers/scsi/ibmvscsi/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +ibmvfc-objs := ibmvfc-core.o ibmvfc-nvme.o + obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi.o obj-$(CONFIG_SCSI_IBMVFC) += ibmvfc.o diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c similarity index 99% rename from drivers/scsi/ibmvscsi/ibmvfc.c rename to drivers/scsi/ibmvscsi/ibmvfc-core.c index 78b4ac201a49..56f4fb200a69 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * ibmvfc.c -- driver for IBM Power Virtual Fibre Channel Adapter * * Written By: Brian King , IBM Corporation * - * Copyright (C) IBM Corporation, 2008 + * Copyright (C) IBM Corporation, 2008-2026 */ #include @@ -46,6 +47,9 @@ static unsigned int cls3_error = IBMVFC_CLS3_ERROR; static unsigned int mq_enabled = IBMVFC_MQ; static unsigned int nr_scsi_hw_queues = IBMVFC_SCSI_HW_QUEUES; static unsigned int nr_scsi_channels = IBMVFC_SCSI_CHANNELS; +static unsigned int nvme_enabled = IBMVFC_NVME; +static unsigned int nr_nvme_hw_queues = IBMVFC_NVME_HW_QUEUES; +static unsigned int nr_nvme_channels = IBMVFC_NVME_CHANNELS; static unsigned int mig_channels_only = IBMVFC_MIG_NO_SUB_TO_CRQ; static unsigned int mig_no_less_channels = IBMVFC_MIG_NO_N_TO_M; @@ -74,6 +78,16 @@ module_param_named(mig_no_less_channels, mig_no_less_channels, uint, S_IRUGO); MODULE_PARM_DESC(mig_no_less_channels, "Prevent migration to system with less channels. " "[Default=" __stringify(IBMVFC_MIG_NO_N_TO_M) "]"); +module_param_named(nvme, nvme_enabled, uint, S_IRUGO); +MODULE_PARM_DESC(nvme, "Enable NVMe over FC support. " + "[Default=" __stringify(IBMVFC_NVME) "]"); +module_param_named(nvme_host_queues, nr_nvme_hw_queues, uint, S_IRUGO); +MODULE_PARM_DESC(nvme_host_queues, "Number of NVMeoF Host submission queues. " + "[Default=" __stringify(IBMVFC_NVME_HW_QUEUES) "]"); +module_param_named(nvme_hw_channels, nr_nvme_channels, uint, S_IRUGO); +MODULE_PARM_DESC(nvme_hw_channels, "Number of hw NVMeoF channels to request. " + "[Default=" __stringify(IBMVFC_NVME_CHANNELS) "]"); + module_param_named(init_timeout, init_timeout, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(init_timeout, "Initialization timeout in seconds. " "[Default=" __stringify(IBMVFC_INIT_TIMEOUT) "]"); @@ -468,6 +482,7 @@ static const struct { { IBMVFC_FABRIC_BUSY, "fabric busy" }, { IBMVFC_PORT_BUSY, "port busy" }, { IBMVFC_BASIC_REJECT, "basic reject" }, + { IBMVFC_FC4_LS_REJECT, "fc4 ls reject" }, }; static const char *unknown_fc_type = "unknown fc type"; diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c new file mode 100644 index 000000000000..2ee1c64665d9 --- /dev/null +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * ibmvfc-nvme.c -- IBM Power Virtual Fibre Channel NVMeoF HBA driver + * + * Written By: Tyrel Datwyler , IBM Corporation + * + * Copyright (C) IBM Corporation, 2026 + */ + +#include + +#include "ibmvfc-nvme.h" + +static void ibmvfc_nvme_localport_delete(struct nvme_fc_local_port *lport) +{ +} + +static void ibmvfc_nvme_remoteport_delete(struct nvme_fc_remote_port *rport) +{ +} + +static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *ls_req) +{ + return 0; +} + +static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *ls_abort) +{ +} + +static int ibmvfc_nvme_fcp_io(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + void *hw_queue_handle, + struct nvmefc_fcp_req *fcp_req) +{ + return 0; +} + +static void ibmvfc_nvme_fcp_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + void *hw_queue_handle, + struct nvmefc_fcp_req *abort_req) +{ +} + +static struct nvme_fc_port_template ibmvfc_nvme_fc_transport = { + .localport_delete = ibmvfc_nvme_localport_delete, + .remoteport_delete = ibmvfc_nvme_remoteport_delete, + .create_queue = NULL, + .delete_queue = NULL, + .ls_req = ibmvfc_nvme_ls_req, + .ls_abort = ibmvfc_nvme_ls_abort, + .fcp_io = ibmvfc_nvme_fcp_io, + .fcp_abort = ibmvfc_nvme_fcp_abort, + .map_queues = NULL, + .max_hw_queues = IBMVFC_NVME_HW_QUEUES, + .max_sgl_segments = 1024, + .max_dif_sgl_segments = 64, + .dma_boundary = 0xFFFFFFFF, + .local_priv_sz = sizeof(struct ibmvfc_host *), + .remote_priv_sz = sizeof(struct ibmvfc_target *), + .lsrqst_priv_sz = sizeof(struct ibmvfc_event *), + .fcprqst_priv_sz = sizeof(struct ibmvfc_event *), +}; + +int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt) +{ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return 0; + + return 0; +} + +void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt) +{ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return; +} + +int ibmvfc_nvme_register(struct ibmvfc_host *vhost) +{ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return 0; + + return 0; +} + +void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost) +{ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return; +} diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h new file mode 100644 index 000000000000..97e267871df2 --- /dev/null +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * ibmvfc-nvme.h -- IBM Power Virtual Fibre Channel NVMeoF HBA driver + * + * Written By: Tyrel Datwyler , IBM Corporation + * + * Copyright (C) IBM Corporation, 2026 + */ + +#ifndef _IBMVFC_NVME_H +#define _IBMVFC_NVME_H + +#include +#include +#include + +#include "ibmvfc.h" + +#define IBMVFC_NVME 0 +#define IBMVFC_NVME_HW_QUEUES 8 +#define IBMVFC_MAX_NVME_QUEUES 16 +#define IBMVFC_NVME_CHANNELS 8 + +struct ibmvfc_host; +struct ibmvfc_target; + +int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt); +void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt); +int ibmvfc_nvme_register(struct ibmvfc_host *vhost); +void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost); +#endif diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index f8a2bf92da41..f137b61ce422 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -1,22 +1,27 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * ibmvfc.h -- driver for IBM Power Virtual Fibre Channel Adapter * * Written By: Brian King , IBM Corporation * - * Copyright (C) IBM Corporation, 2008 + * Copyright (C) IBM Corporation, 2008-2026 */ #ifndef _IBMVFC_H #define _IBMVFC_H +#include #include #include +#include #include #include #include -#define IBMVFC_NAME "ibmvfc" +#include "ibmvfc-nvme.h" + +#define IBMVFC_NAME "ibmvfc" #define IBMVFC_DRIVER_VERSION "1.0.11" #define IBMVFC_DRIVER_DATE "(April 12, 2013)" @@ -984,6 +989,7 @@ struct ibmvfc_host { unsigned int mq_enabled:1; unsigned int using_channels:1; unsigned int do_enquiry:1; + unsigned int nvme_enabled:1; unsigned int aborting_passthru:1; unsigned int scan_complete:1; int scan_timeout; From 018fc39652914c403d660da03234c8276e388dc0 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:24 -0700 Subject: [PATCH 08/33] scsi: ibmvfc: initialize NVMe channel configuration during driver probe Initialize the host's NVMe channel-group state during probe. Set up the NVMe channel list head, desired queue count, maximum queue count, protocol identifier, and enablement state alongside the existing SCSI channel-group initialization in ibmvfc_probe(). This prepares the driver with a NVMe/FC channel group that can will be used by later patches for NVMe queue allocation, discovery buffers, target management, and IO submission. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-9-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 8 +++++++- drivers/scsi/ibmvscsi/ibmvfc.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 56f4fb200a69..5b6570232df3 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -6347,7 +6347,8 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id) struct device *dev = &vdev->dev; int rc = -ENOMEM; unsigned int online_cpus = num_online_cpus(); - unsigned int max_scsi_queues = min((unsigned int)IBMVFC_MAX_SCSI_QUEUES, online_cpus); + unsigned int max_scsi_queues = min_t(unsigned int, IBMVFC_MAX_SCSI_QUEUES, online_cpus); + unsigned int max_nvme_queues = min_t(unsigned int, IBMVFC_MAX_NVME_QUEUES, online_cpus); ENTER; shost = scsi_host_alloc(&driver_template, sizeof(*vhost)); @@ -6367,6 +6368,7 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id) vhost = shost_priv(shost); INIT_LIST_HEAD(&vhost->scsi_scrqs.targets); + INIT_LIST_HEAD(&vhost->nvme_scrqs.targets); INIT_LIST_HEAD(&vhost->purge); sprintf(vhost->name, IBMVFC_NAME); vhost->host = shost; @@ -6381,6 +6383,10 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id) vhost->scsi_scrqs.protocol = IBMVFC_PROTO_SCSI; vhost->using_channels = 0; vhost->do_enquiry = 1; + vhost->nvme_enabled = mq_enabled ? nvme_enabled : 0; + vhost->nvme_scrqs.desired_queues = min(max_nvme_queues, nr_nvme_channels); + vhost->nvme_scrqs.max_queues = max_nvme_queues; + vhost->nvme_scrqs.protocol = IBMVFC_PROTO_NVME; vhost->scan_timeout = 0; strcpy(vhost->partition_name, "UNKNOWN"); diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index f137b61ce422..01c49d81b135 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -970,6 +970,7 @@ struct ibmvfc_host { struct ibmvfc_queue crq; struct ibmvfc_queue async_crq; struct ibmvfc_channels scsi_scrqs; + struct ibmvfc_channels nvme_scrqs; struct ibmvfc_npiv_login login_info; union ibmvfc_npiv_login_data *login_buf; dma_addr_t login_buf_dma; From 319f6545a2d44e1f18c0b3cc0445c74daf381651 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:25 -0700 Subject: [PATCH 09/33] scsi: ibmvfc: alloc/dealloc sub-queues for nvme channels Allocate, register, deregister, and release NVMe subordinate CRQs alongside the existing SCSI sub-CRQs. Update the CRQ reset and re-enable paths to tear down and recreate NVMe sub-queues, extend sub-CRQ initialization to allocate NVMe channels when enabled, and release NVMe channel resources during adapter teardown. This keeps the NVMe queue lifecycle aligned with the existing SCSI queue lifecycle so both protocols are reset consistently across probe, remove, and connection recovery. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-10-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 5b6570232df3..d8c2b56258ee 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -945,6 +945,7 @@ static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost) unsigned long flags; ibmvfc_dereg_sub_crqs(vhost, &vhost->scsi_scrqs); + ibmvfc_dereg_sub_crqs(vhost, &vhost->nvme_scrqs); /* Re-enable the CRQ */ do { @@ -964,6 +965,7 @@ static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost) spin_unlock_irqrestore(vhost->host->host_lock, flags); ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs); + ibmvfc_reg_sub_crqs(vhost, &vhost->nvme_scrqs); return rc; } @@ -983,6 +985,7 @@ static int ibmvfc_reset_crq(struct ibmvfc_host *vhost) struct ibmvfc_queue *crq = &vhost->crq; ibmvfc_dereg_sub_crqs(vhost, &vhost->scsi_scrqs); + ibmvfc_dereg_sub_crqs(vhost, &vhost->nvme_scrqs); /* Close the CRQ */ do { @@ -1016,6 +1019,7 @@ static int ibmvfc_reset_crq(struct ibmvfc_host *vhost) spin_unlock_irqrestore(vhost->host->host_lock, flags); ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs); + ibmvfc_reg_sub_crqs(vhost, &vhost->nvme_scrqs); return rc; } @@ -6119,6 +6123,13 @@ static void ibmvfc_init_sub_crqs(struct ibmvfc_host *vhost) ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs); + if (vhost->nvme_enabled) { + if (ibmvfc_alloc_channels(vhost, &vhost->nvme_scrqs)) + vhost->nvme_enabled = 0; + else + ibmvfc_reg_sub_crqs(vhost, &vhost->nvme_scrqs); + } + LEAVE; } @@ -6147,8 +6158,10 @@ static void ibmvfc_release_sub_crqs(struct ibmvfc_host *vhost) return; ibmvfc_dereg_sub_crqs(vhost, &vhost->scsi_scrqs); - ibmvfc_release_channels(vhost, &vhost->scsi_scrqs); + + ibmvfc_dereg_sub_crqs(vhost, &vhost->nvme_scrqs); + ibmvfc_release_channels(vhost, &vhost->nvme_scrqs); LEAVE; } From ecc03d958e371881cff4d8e5ebaa4ec760ab65ec Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:26 -0700 Subject: [PATCH 10/33] scsi: ibmvfc: add logic for protocol specific fabric logins Add support for the protocol-specific fabric login flow introduced by the updated client/VIOS interface. After NPIV login, a VIOS that advertises protocol-specific support requires separate fabric login MADs for SCSI and NVMe/FC. Track whether SCSI and NVMe/FC fabric login are needed, extend channel enquiry/setup handling to negotiate both SCSI and NVMe queue counts, and issue the appropriate fabric login MADs before target discovery begins. Also update command layout selection so the driver uses the v3 command format when the VIOS advertises NVMe/FC-capable framing. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-11-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 230 ++++++++++++++++++++++++---- drivers/scsi/ibmvscsi/ibmvfc.h | 11 +- 2 files changed, 206 insertions(+), 35 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index d8c2b56258ee..e0edd71e9fd4 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -210,7 +210,9 @@ static int ibmvfc_check_caps(struct ibmvfc_host *vhost, unsigned long cap_flags) static struct ibmvfc_fcp_cmd_iu *ibmvfc_get_fcp_iu(struct ibmvfc_host *vhost, struct ibmvfc_cmd *vfc_cmd) { - if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) + if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF)) + return &vfc_cmd->v3scsi.iu; + else if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) return &vfc_cmd->v2.iu; else return &vfc_cmd->v1.iu; @@ -219,7 +221,9 @@ static struct ibmvfc_fcp_cmd_iu *ibmvfc_get_fcp_iu(struct ibmvfc_host *vhost, static struct ibmvfc_fcp_rsp *ibmvfc_get_fcp_rsp(struct ibmvfc_host *vhost, struct ibmvfc_cmd *vfc_cmd) { - if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) + if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF)) + return &vfc_cmd->v3scsi.rsp; + else if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) return &vfc_cmd->v2.rsp; else return &vfc_cmd->v1.rsp; @@ -961,6 +965,8 @@ static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost) spin_lock(vhost->crq.q_lock); vhost->do_enquiry = 1; vhost->using_channels = 0; + vhost->do_scsi_login = 0; + vhost->do_nvme_login = 0; spin_unlock(vhost->crq.q_lock); spin_unlock_irqrestore(vhost->host->host_lock, flags); @@ -1000,6 +1006,8 @@ static int ibmvfc_reset_crq(struct ibmvfc_host *vhost) vhost->logged_in = 0; vhost->do_enquiry = 1; vhost->using_channels = 0; + vhost->do_scsi_login = 0; + vhost->do_nvme_login = 0; /* Clean out the queue */ memset(crq->msgs.crq, 0, PAGE_SIZE); @@ -1512,7 +1520,7 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost) max_cmds = scsi_qdepth + IBMVFC_NUM_INTERNAL_REQ; if (mq_enabled) max_cmds += (scsi_qdepth + IBMVFC_NUM_INTERNAL_SUBQ_REQ) * - vhost->scsi_scrqs.desired_queues; + (vhost->scsi_scrqs.desired_queues + vhost->nvme_scrqs.desired_queues); memset(login_info, 0, sizeof(*login_info)); @@ -1530,8 +1538,14 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost) login_info->max_cmds = cpu_to_be32(max_cmds); login_info->capabilities = cpu_to_be64(IBMVFC_CAN_MIGRATE | IBMVFC_CAN_SEND_VF_WWPN); - if (vhost->mq_enabled || vhost->using_channels) + if (vhost->mq_enabled || vhost->using_channels) { login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_CHANNELS); + if (vhost->nvme_enabled) { + login_info->capabilities |= cpu_to_be64(IBMVFC_YES_NVMEOF); + login_info->capabilities |= cpu_to_be64(IBMVFC_YES_SCSI); + login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_WWPN_ALL); + } + } login_info->async.va = cpu_to_be64(vhost->async_crq.msg_token); login_info->async.len = cpu_to_be32(async_crq->size * @@ -1956,11 +1970,13 @@ static struct ibmvfc_cmd *ibmvfc_init_vfc_cmd(struct ibmvfc_event *evt, struct s size_t offset; memset(vfc_cmd, 0, sizeof(*vfc_cmd)); - if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) { + if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF)) + offset = offsetof(struct ibmvfc_cmd, v3scsi.rsp); + else if (ibmvfc_check_caps(vhost, IBMVFC_HANDLE_VF_WWPN)) offset = offsetof(struct ibmvfc_cmd, v2.rsp); - vfc_cmd->target_wwpn = cpu_to_be64(rport->port_name); - } else + else offset = offsetof(struct ibmvfc_cmd, v1.rsp); + vfc_cmd->target_wwpn = cpu_to_be64(rport->port_name); vfc_cmd->resp.va = cpu_to_be64(be64_to_cpu(evt->crq.ioba) + offset); vfc_cmd->resp.len = cpu_to_be32(sizeof(*rsp)); vfc_cmd->frame_type = cpu_to_be32(IBMVFC_SCSI_FCP_TYPE); @@ -5052,14 +5068,141 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost) ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); } +static void ibmvfc_fabric_login_nvme_done(struct ibmvfc_event *evt) +{ + struct ibmvfc_host *vhost = evt->vhost; + struct ibmvfc_fabric_login_mad *rsp = &evt->xfer_iu->fabric_login; + u32 mad_status = be16_to_cpu(rsp->common.status); + int level = IBMVFC_DEFAULT_LOG_LEVEL; + + switch (mad_status) { + case IBMVFC_MAD_SUCCESS: + ibmvfc_dbg(vhost, "NVMe fabric login succeeded\n"); + break; + case IBMVFC_MAD_FAILED: + if (ibmvfc_retry_cmd(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error))) + level += ibmvfc_retry_host_init(vhost); + else + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); + ibmvfc_log(vhost, level, "NVMe fabric login failed: %s (%x:%x)\n", + ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), + be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)); + ibmvfc_free_event(evt); + return; + case IBMVFC_MAD_DRIVER_FAILED: + ibmvfc_free_event(evt); + return; + default: + dev_err(vhost->dev, "Invalid NVMe fabric login response: 0x%x\n", mad_status); + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); + break; + } + + ibmvfc_free_event(evt); + + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY); + wake_up(&vhost->work_wait_q); +} + +static void ibmvfc_fabric_login_nvme(struct ibmvfc_host *vhost) +{ + struct ibmvfc_fabric_login_mad *mad; + struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq); + + if (!evt) { + ibmvfc_retry_host_init(vhost); + return; + } + + ibmvfc_init_event(evt, ibmvfc_fabric_login_nvme_done, IBMVFC_MAD_FORMAT); + mad = &evt->iu.fabric_login; + memset(mad, 0, sizeof(*mad)); + mad->common.version = cpu_to_be32(1); + mad->common.opcode = cpu_to_be32(IBMVFC_NVMF_FABRIC_LOGIN); + mad->common.length = cpu_to_be16(sizeof(*mad)); + + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT_WAIT); + + if (!ibmvfc_send_event(evt, vhost, default_timeout)) + ibmvfc_dbg(vhost, "Send NVMe fabric login\n"); + else + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); +} + +static void ibmvfc_fabric_login_scsi_done(struct ibmvfc_event *evt) +{ + struct ibmvfc_host *vhost = evt->vhost; + struct ibmvfc_fabric_login_mad *rsp = &evt->xfer_iu->fabric_login; + u32 mad_status = be16_to_cpu(rsp->common.status); + int level = IBMVFC_DEFAULT_LOG_LEVEL; + + switch (mad_status) { + case IBMVFC_MAD_SUCCESS: + ibmvfc_dbg(vhost, "SCSI fabric login succeeded\n"); + break; + case IBMVFC_MAD_FAILED: + if (ibmvfc_retry_cmd(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error))) + level += ibmvfc_retry_host_init(vhost); + else + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); + ibmvfc_log(vhost, level, "SCSI fabric login failed: %s (%x:%x)\n", + ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), + be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)); + ibmvfc_free_event(evt); + return; + case IBMVFC_MAD_DRIVER_FAILED: + ibmvfc_free_event(evt); + return; + default: + dev_err(vhost->dev, "Invalid SCSI fabric login response: 0x%x\n", mad_status); + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); + break; + } + + ibmvfc_free_event(evt); + + if (vhost->do_nvme_login) { + ibmvfc_fabric_login_nvme(vhost); + } else { + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY); + wake_up(&vhost->work_wait_q); + } +} + +static void ibmvfc_fabric_login_scsi(struct ibmvfc_host *vhost) +{ + struct ibmvfc_fabric_login_mad *mad; + struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq); + + if (!evt) { + ibmvfc_retry_host_init(vhost); + return; + } + + ibmvfc_init_event(evt, ibmvfc_fabric_login_scsi_done, IBMVFC_MAD_FORMAT); + mad = &evt->iu.fabric_login; + memset(mad, 0, sizeof(*mad)); + mad->common.version = cpu_to_be32(1); + mad->common.opcode = cpu_to_be32(IBMVFC_FABRIC_LOGIN); + mad->common.length = cpu_to_be16(sizeof(*mad)); + + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT_WAIT); + + if (!ibmvfc_send_event(evt, vhost, default_timeout)) + ibmvfc_dbg(vhost, "Send SCSI fabric login\n"); + else + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); +} + static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt) { struct ibmvfc_host *vhost = evt->vhost; struct ibmvfc_channel_setup *setup = vhost->channel_setup_buf; - struct ibmvfc_channels *scrqs = &vhost->scsi_scrqs; + struct ibmvfc_channels *scsi = &vhost->scsi_scrqs; + struct ibmvfc_channels *nvme = &vhost->nvme_scrqs; u32 mad_status = be16_to_cpu(evt->xfer_iu->channel_setup.common.status); int level = IBMVFC_DEFAULT_LOG_LEVEL; - int flags, active_queues, i; + int flags, i; ibmvfc_free_event(evt); @@ -5068,22 +5211,28 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt) ibmvfc_dbg(vhost, "Channel Setup succeeded\n"); flags = be32_to_cpu(setup->flags); vhost->do_enquiry = 0; - active_queues = be32_to_cpu(setup->num_scsi_subq_channels); - scrqs->active_queues = active_queues; + scsi->active_queues = be32_to_cpu(setup->num_scsi_subq_channels); + nvme->active_queues = be32_to_cpu(setup->num_nvme_subq_channels); if (flags & IBMVFC_CHANNELS_CANCELED) { ibmvfc_dbg(vhost, "Channels Canceled\n"); vhost->using_channels = 0; - } else { - if (active_queues) - vhost->using_channels = 1; - for (i = 0; i < active_queues; i++) - scrqs->scrqs[i].vios_cookie = - be64_to_cpu(setup->channel_handles[i]); - - ibmvfc_dbg(vhost, "Using %u channels\n", - vhost->scsi_scrqs.active_queues); + break; } + + if (scsi->active_queues || nvme->active_queues) + vhost->using_channels = 1; + for (i = 0; i < scsi->active_queues; i++) + scsi->scrqs[i].vios_cookie = + be64_to_cpu(setup->channel_handles[i]); + for (i = 0; i < nvme->active_queues; i++) + nvme->scrqs[i].vios_cookie = + be64_to_cpu(setup->channel_handles[scsi->active_queues + i]); + + ibmvfc_dbg(vhost, "Using %u SCSI channels\n", + scsi->active_queues); + ibmvfc_dbg(vhost, "Using %u NVMe channels\n", + nvme->active_queues); break; case IBMVFC_MAD_FAILED: level += ibmvfc_retry_host_init(vhost); @@ -5098,8 +5247,12 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt) return; } - ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY); - wake_up(&vhost->work_wait_q); + if (vhost->do_scsi_login) { + ibmvfc_fabric_login_scsi(vhost); + } else { + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY); + wake_up(&vhost->work_wait_q); + } } static void ibmvfc_channel_setup(struct ibmvfc_host *vhost) @@ -5107,9 +5260,12 @@ static void ibmvfc_channel_setup(struct ibmvfc_host *vhost) struct ibmvfc_channel_setup_mad *mad; struct ibmvfc_channel_setup *setup_buf = vhost->channel_setup_buf; struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq); - struct ibmvfc_channels *scrqs = &vhost->scsi_scrqs; - unsigned int num_channels = - min(scrqs->desired_queues, vhost->max_vios_scsi_channels); + struct ibmvfc_channels *scsi = &vhost->scsi_scrqs; + struct ibmvfc_channels *nvme = &vhost->nvme_scrqs; + unsigned int scsi_channels = + min(scsi->desired_queues, vhost->max_vios_scsi_channels); + unsigned int nvme_channels = + min(nvme->desired_queues, vhost->max_vios_nvme_channels); int level = IBMVFC_DEFAULT_LOG_LEVEL; int i; @@ -5120,12 +5276,17 @@ static void ibmvfc_channel_setup(struct ibmvfc_host *vhost) } memset(setup_buf, 0, sizeof(*setup_buf)); - if (num_channels == 0) + if (!scsi_channels && !nvme_channels) setup_buf->flags = cpu_to_be32(IBMVFC_CANCEL_CHANNELS); else { - setup_buf->num_scsi_subq_channels = cpu_to_be32(num_channels); - for (i = 0; i < num_channels; i++) - setup_buf->channel_handles[i] = cpu_to_be64(scrqs->scrqs[i].cookie); + setup_buf->num_scsi_subq_channels = cpu_to_be32(scsi_channels); + setup_buf->num_nvme_subq_channels = cpu_to_be32(nvme_channels); + for (i = 0; i < scsi_channels; i++) + setup_buf->channel_handles[i] = + cpu_to_be64(scsi->scrqs[i].cookie); + for (i = 0; i < nvme_channels; i++) + setup_buf->channel_handles[scsi_channels + i] = + cpu_to_be64(nvme->scrqs[i].cookie); } ibmvfc_init_event(evt, ibmvfc_channel_setup_done, IBMVFC_MAD_FORMAT); @@ -5156,6 +5317,7 @@ static void ibmvfc_channel_enquiry_done(struct ibmvfc_event *evt) case IBMVFC_MAD_SUCCESS: ibmvfc_dbg(vhost, "Channel Enquiry succeeded\n"); vhost->max_vios_scsi_channels = be32_to_cpu(rsp->num_scsi_subq_channels); + vhost->max_vios_nvme_channels = be32_to_cpu(rsp->num_nvme_subq_channels); ibmvfc_free_event(evt); break; case IBMVFC_MAD_FAILED: @@ -5290,8 +5452,14 @@ static void ibmvfc_npiv_login_done(struct ibmvfc_event *evt) vhost->host->can_queue = be32_to_cpu(rsp->max_cmds) - IBMVFC_NUM_INTERNAL_REQ; vhost->host->max_sectors = npiv_max_sectors; - if (ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPORT_CHANNELS) && vhost->do_enquiry) { - ibmvfc_channel_enquiry(vhost); + + if (ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPORT_CHANNELS)) { + if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_SCSI)) + vhost->do_scsi_login = 1; + if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF)) + vhost->do_nvme_login = 1; + if (vhost->do_enquiry) + ibmvfc_channel_enquiry(vhost); } else { vhost->do_enquiry = 0; ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY); diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 01c49d81b135..454968de925b 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -617,9 +617,9 @@ struct ibmvfc_channel_enquiry { #define IBMVFC_SUPPORT_VARIABLE_SUBQ_MSG 0x02 #define IBMVFC_NO_N_TO_M_CHANNELS_SUPPORT 0x04 __be32 num_scsi_subq_channels; - __be32 num_nvmeof_subq_channels; + __be32 num_nvme_subq_channels; __be32 num_scsi_vas_channels; - __be32 num_nvmeof_vas_channels; + __be32 num_nvme_vas_channels; } __packed __aligned(8); struct ibmvfc_channel_setup_mad { @@ -636,9 +636,9 @@ struct ibmvfc_channel_setup { #define IBMVFC_CHANNELS_CANCELED 0x04 __be32 reserved; __be32 num_scsi_subq_channels; - __be32 num_nvmeof_subq_channels; + __be32 num_nvme_subq_channels; __be32 num_scsi_vas_channels; - __be32 num_nvmeof_vas_channels; + __be32 num_nvme_vas_channels; struct srp_direct_buf buffer; __be64 reserved2[5]; __be64 channel_handles[IBMVFC_MAX_CHANNELS]; @@ -979,6 +979,7 @@ struct ibmvfc_host { int log_level; struct mutex passthru_mutex; unsigned int max_vios_scsi_channels; + unsigned int max_vios_nvme_channels; int task_set; int init_retries; int discovery_threads; @@ -991,6 +992,8 @@ struct ibmvfc_host { unsigned int using_channels:1; unsigned int do_enquiry:1; unsigned int nvme_enabled:1; + unsigned int do_scsi_login:1; + unsigned int do_nvme_login:1; unsigned int aborting_passthru:1; unsigned int scan_complete:1; int scan_timeout; From c34ca34f1dbdf40c368eed42efe273561c3800ba Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:27 -0700 Subject: [PATCH 11/33] scsi: ibmvfc: add wrapper to get vhost associated with a channel struct Add ibmvfc_channels_to_vhost() to recover the parent struct ibmvfc_host from a protocol-specific struct ibmvfc_channels. Later patches need to operate on either the SCSI or NVMe channel group and still access host-wide state such as the primary CRQ, device, and logging context. Centralize that mapping in a helper instead of open- coding container lookups at each call site. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-12-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 454968de925b..67f546ff092e 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -1011,6 +1011,16 @@ struct ibmvfc_host { wait_queue_head_t work_wait_q; }; +static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channels *channels) +{ + if (channels->protocol == IBMVFC_PROTO_SCSI) + return container_of(channels, struct ibmvfc_host, scsi_scrqs); + else if (channels->protocol == IBMVFC_PROTO_NVME) + return container_of(channels, struct ibmvfc_host, nvme_scrqs); + + return NULL; +} + #define DBG_CMD(CMD) do { if (ibmvfc_debug) CMD; } while (0) #define tgt_dbg(t, fmt, ...) \ From a29ee1473ab5b5c85726efdc2f36c78d1a45ab41 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:28 -0700 Subject: [PATCH 12/33] scsi: ibmvfc: add helper for creating protocol specific discovery event Refactor discover-target event creation so it can be shared by both SCSI and NVMe/FC discovery. Introduce a helper that takes a protocol-specific channel group, selects the correct discover-target opcode, and maps the corresponding discovery buffer into the MAD. This is a preparatory cleanup for issuing protocol-specific discovery MADs in later patches. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-13-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index e0edd71e9fd4..84302f982a6a 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -5033,6 +5033,32 @@ static void ibmvfc_discover_targets_done(struct ibmvfc_event *evt) wake_up(&vhost->work_wait_q); } +static struct ibmvfc_event *ibmvfc_get_disc_event(struct ibmvfc_channels *channels) +{ + struct ibmvfc_discover_targets *mad; + struct ibmvfc_host *vhost = ibmvfc_channels_to_vhost(channels); + struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq); + + if (!evt) + return NULL; + + ibmvfc_init_event(evt, ibmvfc_discover_targets_done, IBMVFC_MAD_FORMAT); + mad = &evt->iu.discover_targets; + memset(mad, 0, sizeof(*mad)); + mad->common.version = cpu_to_be32(1); + if (channels->protocol == IBMVFC_PROTO_SCSI) + mad->common.opcode = cpu_to_be32(IBMVFC_DISC_TARGETS); + else + mad->common.opcode = cpu_to_be32(IBMVFC_DISC_NVMF_TARGETS); + mad->common.length = cpu_to_be16(sizeof(*mad)); + mad->bufflen = cpu_to_be32(channels->disc_buf_sz); + mad->buffer.va = cpu_to_be64(channels->disc_buf_dma); + mad->buffer.len = cpu_to_be32(channels->disc_buf_sz); + mad->flags = cpu_to_be32(IBMVFC_DISC_TGT_PORT_ID_WWPN_LIST); + + return evt; +} + /** * ibmvfc_discover_targets - Send Discover Targets MAD * @vhost: ibmvfc host struct @@ -5040,8 +5066,7 @@ static void ibmvfc_discover_targets_done(struct ibmvfc_event *evt) **/ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost) { - struct ibmvfc_discover_targets *mad; - struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq); + struct ibmvfc_event *evt = ibmvfc_get_disc_event(&vhost->scsi_scrqs); int level = IBMVFC_DEFAULT_LOG_LEVEL; if (!evt) { @@ -5050,16 +5075,6 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost) return; } - ibmvfc_init_event(evt, ibmvfc_discover_targets_done, IBMVFC_MAD_FORMAT); - mad = &evt->iu.discover_targets; - memset(mad, 0, sizeof(*mad)); - mad->common.version = cpu_to_be32(1); - mad->common.opcode = cpu_to_be32(IBMVFC_DISC_TARGETS); - mad->common.length = cpu_to_be16(sizeof(*mad)); - mad->bufflen = cpu_to_be32(vhost->scsi_scrqs.disc_buf_sz); - mad->buffer.va = cpu_to_be64(vhost->scsi_scrqs.disc_buf_dma); - mad->buffer.len = cpu_to_be32(vhost->scsi_scrqs.disc_buf_sz); - mad->flags = cpu_to_be32(IBMVFC_DISC_TGT_PORT_ID_WWPN_LIST); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT_WAIT); if (!ibmvfc_send_event(evt, vhost, default_timeout)) From 8acacfa8b0f8b17e740730b3e96f99ff726065b2 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:29 -0700 Subject: [PATCH 13/33] scsi: ibmvfc: add helper to check NVMe/FC support with active channels It can be the case that NVMeoF is enabled on both the client and VIOS, but no queues are configured making the need to do NVMe target discovery pointless. Add a helper to short hand the capabilities check and active NVMe queue check. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-14-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 84302f982a6a..088167ec04ff 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -207,6 +207,12 @@ static int ibmvfc_check_caps(struct ibmvfc_host *vhost, unsigned long cap_flags) return (host_caps & cap_flags) ? 1 : 0; } +static int ibmvfc_nvme_active(struct ibmvfc_host *vhost) +{ + return (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF) && + vhost->nvme_scrqs.active_queues); +} + static struct ibmvfc_fcp_cmd_iu *ibmvfc_get_fcp_iu(struct ibmvfc_host *vhost, struct ibmvfc_cmd *vfc_cmd) { From fe150862d5b9bef1f0ccc4e2443677dc91c8a332 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:30 -0700 Subject: [PATCH 14/33] scsi: ibmvfc: allocate and free NVMe channel group discover buffer Allocate a discovery buffer for the NVMe channel group and free it on all teardown and error paths. The existing discovery-buffer allocation only covered the SCSI channel group. This patch is prepratory for sending NVMe/FC target discovery MAD. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-15-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 088167ec04ff..a700e2554e69 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -6375,6 +6375,7 @@ static void ibmvfc_free_mem(struct ibmvfc_host *vhost) mempool_destroy(vhost->tgt_pool); kfree(vhost->trace); ibmvfc_free_disc_buf(vhost->dev, &vhost->scsi_scrqs); + ibmvfc_free_disc_buf(vhost->dev, &vhost->nvme_scrqs); dma_free_coherent(vhost->dev, sizeof(*vhost->login_buf), vhost->login_buf, vhost->login_buf_dma); dma_free_coherent(vhost->dev, sizeof(*vhost->channel_setup_buf), @@ -6437,12 +6438,15 @@ static int ibmvfc_alloc_mem(struct ibmvfc_host *vhost) if (ibmvfc_alloc_disc_buf(dev, &vhost->scsi_scrqs)) goto free_login_buffer; + if (ibmvfc_alloc_disc_buf(dev, &vhost->nvme_scrqs)) + goto free_scsi_disc_buffer; + vhost->trace = kzalloc_objs(struct ibmvfc_trace_entry, IBMVFC_NUM_TRACE_ENTRIES); atomic_set(&vhost->trace_index, -1); if (!vhost->trace) - goto free_scsi_disc_buffer; + goto free_nvme_disc_buffer; vhost->tgt_pool = mempool_create_kmalloc_pool(IBMVFC_TGT_MEMPOOL_SZ, sizeof(struct ibmvfc_target)); @@ -6468,6 +6472,8 @@ static int ibmvfc_alloc_mem(struct ibmvfc_host *vhost) mempool_destroy(vhost->tgt_pool); free_trace: kfree(vhost->trace); +free_nvme_disc_buffer: + ibmvfc_free_disc_buf(dev, &vhost->nvme_scrqs); free_scsi_disc_buffer: ibmvfc_free_disc_buf(dev, &vhost->scsi_scrqs); free_login_buffer: From 5e9dd03726ad4d7a8fb57f1c4921fc3dc955c47f Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:31 -0700 Subject: [PATCH 15/33] scsi: ibmvfc: send NVMe target discovery MAD Extend target discovery to send protocol-specific discover-target MADs for NVMe/FC. Use the protocol-aware discovery helper to build an NVMe discover-target request, submit it when NVMe/FC support is active, and process the returned target count using the NVMe channel group's discovery buffer. This allows the driver to discover NVMe/FC targets in parallel with the existing SCSI discovery flow while keeping protocol-specific target data separate. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-16-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 50 +++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index a700e2554e69..1d2a88e9fe94 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -173,6 +173,11 @@ static const struct { { IBMVFC_FC_SCSI_ERROR, IBMVFC_COMMAND_FAILED, DID_ERROR, 0, 1, "PRLI to device failed." }, }; +static const char *proto_type[] = { + "SCSI", + "NVMe", +}; + static void ibmvfc_npiv_login(struct ibmvfc_host *); static void ibmvfc_tgt_send_prli(struct ibmvfc_target *); static void ibmvfc_tgt_send_plogi(struct ibmvfc_target *); @@ -5011,19 +5016,30 @@ static void ibmvfc_discover_targets_done(struct ibmvfc_event *evt) { struct ibmvfc_host *vhost = evt->vhost; struct ibmvfc_discover_targets *rsp = &evt->xfer_iu->discover_targets; + struct ibmvfc_channels *channels; u32 mad_status = be16_to_cpu(rsp->common.status); + u32 opcode = be32_to_cpu(rsp->common.opcode); int level = IBMVFC_DEFAULT_LOG_LEVEL; + if (opcode == IBMVFC_DISC_TARGETS) + channels = &vhost->scsi_scrqs; + else + channels = &vhost->nvme_scrqs; + switch (mad_status) { case IBMVFC_MAD_SUCCESS: - ibmvfc_dbg(vhost, "Discover Targets succeeded\n"); - vhost->scsi_scrqs.num_targets = min_t(u32, be32_to_cpu(rsp->num_written), - max_targets); + ibmvfc_dbg(vhost, "Discover %s Targets succeeded\n", + proto_type[channels->protocol]); + channels->num_targets = min_t(u32, be32_to_cpu(rsp->num_written), + max_targets); + ibmvfc_dbg(vhost, "%d %s targets found\n", channels->num_targets, + proto_type[channels->protocol]); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_ALLOC_TGTS); break; case IBMVFC_MAD_FAILED: level += ibmvfc_retry_host_init(vhost); - ibmvfc_log(vhost, level, "Discover Targets failed: %s (%x:%x)\n", + ibmvfc_log(vhost, level, "Discover %s Targets failed: %s (%x:%x)\n", + proto_type[channels->protocol], ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)); break; @@ -5076,7 +5092,7 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost) int level = IBMVFC_DEFAULT_LOG_LEVEL; if (!evt) { - ibmvfc_log(vhost, level, "Discover Targets failed: no available events\n"); + ibmvfc_log(vhost, level, "Discover SCSI Targets failed: no available events\n"); ibmvfc_hard_reset_host(vhost); return; } @@ -5084,9 +5100,29 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost) ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT_WAIT); if (!ibmvfc_send_event(evt, vhost, default_timeout)) - ibmvfc_dbg(vhost, "Sent discover targets\n"); + ibmvfc_dbg(vhost, "Sent discover SCSI targets\n"); else - ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); + goto link_down; + + if (!ibmvfc_nvme_active(vhost)) + return; + + evt = ibmvfc_get_disc_event(&vhost->nvme_scrqs); + if (!evt) { + ibmvfc_log(vhost, level, "Discover NVMe Targets failed: no available events\n"); + ibmvfc_hard_reset_host(vhost); + return; + } + + if (!ibmvfc_send_event(evt, vhost, default_timeout)) + ibmvfc_dbg(vhost, "Sent discover NVMe targets\n"); + else + goto link_down; + + return; + +link_down: + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD); } static void ibmvfc_fabric_login_nvme_done(struct ibmvfc_event *evt) From 577608a2001977e54589b4357b277cba88dbab83 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:32 -0700 Subject: [PATCH 16/33] scsi: ibmvfc: add NVMe/FC Implicit Logout and Move Login support Add protocol-specific handling for implicit logout and move-login operations on NVMe/FC targets. Select the NVMe/FC-specific implicit logout opcode when operating on an NVMe target and update the associated logging so protocol-specific operations are visible in debug output. This extends the existing target relogin and migration-related flows to work with NVMe targets as well as SCSI targets. These changes are needed so target reauthentication and target movement continue to work once NVMe/FC targets are added to the driver's state machine. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-17-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 33 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 1d2a88e9fe94..66fcff7ac834 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4318,7 +4318,7 @@ static void ibmvfc_tgt_implicit_logout_done(struct ibmvfc_event *evt) switch (status) { case IBMVFC_MAD_SUCCESS: - tgt_dbg(tgt, "Implicit Logout succeeded\n"); + tgt_dbg(tgt, "%s Implicit Logout succeeded\n", proto_type[tgt->protocol]); break; case IBMVFC_MAD_DRIVER_FAILED: kref_put(&tgt->kref, ibmvfc_release_tgt); @@ -4326,7 +4326,8 @@ static void ibmvfc_tgt_implicit_logout_done(struct ibmvfc_event *evt) return; case IBMVFC_MAD_FAILED: default: - tgt_err(tgt, "Implicit Logout failed: rc=0x%02X\n", status); + tgt_err(tgt, "%s Implicit Logout failed: rc=0x%02X\n", + proto_type[tgt->protocol], status); break; } @@ -4359,7 +4360,10 @@ static struct ibmvfc_event *__ibmvfc_tgt_get_implicit_logout_evt(struct ibmvfc_t mad = &evt->iu.implicit_logout; memset(mad, 0, sizeof(*mad)); mad->common.version = cpu_to_be32(1); - mad->common.opcode = cpu_to_be32(IBMVFC_IMPLICIT_LOGOUT); + if (tgt->protocol == IBMVFC_PROTO_SCSI) + mad->common.opcode = cpu_to_be32(IBMVFC_IMPLICIT_LOGOUT); + else + mad->common.opcode = cpu_to_be32(IBMVFC_NVMF_IMPLICIT_LOGOUT); mad->common.length = cpu_to_be16(sizeof(*mad)); mad->old_scsi_id = cpu_to_be64(tgt->scsi_id); return evt; @@ -4395,7 +4399,7 @@ static void ibmvfc_tgt_implicit_logout(struct ibmvfc_target *tgt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent Implicit Logout\n"); + tgt_dbg(tgt, "%s Sent Implicit Logout\n", proto_type[tgt->protocol]); } /** @@ -4425,7 +4429,8 @@ static void ibmvfc_tgt_implicit_logout_and_del_done(struct ibmvfc_event *evt) else ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT); - tgt_dbg(tgt, "Implicit Logout %s\n", (status == IBMVFC_MAD_SUCCESS) ? "succeeded" : "failed"); + tgt_dbg(tgt, "%s Implicit Logout %s\n", proto_type[tgt->protocol], + (status == IBMVFC_MAD_SUCCESS) ? "succeeded" : "failed"); kref_put(&tgt->kref, ibmvfc_release_tgt); wake_up(&vhost->work_wait_q); } @@ -4466,7 +4471,7 @@ static void ibmvfc_tgt_implicit_logout_and_del(struct ibmvfc_target *tgt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DEL_RPORT); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent Implicit Logout\n"); + tgt_dbg(tgt, "%s Sent Implicit Logout\n", proto_type[tgt->protocol]); } /** @@ -4486,7 +4491,8 @@ static void ibmvfc_tgt_move_login_done(struct ibmvfc_event *evt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); switch (status) { case IBMVFC_MAD_SUCCESS: - tgt_dbg(tgt, "Move Login succeeded for new scsi_id: %llX\n", tgt->new_scsi_id); + tgt_dbg(tgt, "%s Move Login succeeded for new scsi_id: %llX\n", + proto_type[tgt->protocol], tgt->new_scsi_id); tgt->ids.node_name = wwn_to_u64(rsp->service_parms.node_name); tgt->ids.port_name = wwn_to_u64(rsp->service_parms.port_name); tgt->scsi_id = tgt->new_scsi_id; @@ -4507,8 +4513,9 @@ static void ibmvfc_tgt_move_login_done(struct ibmvfc_event *evt) level += ibmvfc_retry_tgt_init(tgt, ibmvfc_tgt_move_login); tgt_log(tgt, level, - "Move Login failed: new scsi_id: %llX, flags:%x, vios_flags:%x, rc=0x%02X\n", - tgt->new_scsi_id, be32_to_cpu(rsp->flags), be16_to_cpu(rsp->vios_flags), + "%s Move Login failed: new scsi_id: %llX, flags:%x, vios_flags:%x, rc=0x%02X\n", + proto_type[tgt->protocol], tgt->new_scsi_id, + be32_to_cpu(rsp->flags), be16_to_cpu(rsp->vios_flags), status); break; } @@ -4548,7 +4555,10 @@ static void ibmvfc_tgt_move_login(struct ibmvfc_target *tgt) move = &evt->iu.move_login; memset(move, 0, sizeof(*move)); move->common.version = cpu_to_be32(1); - move->common.opcode = cpu_to_be32(IBMVFC_MOVE_LOGIN); + if (tgt->protocol == IBMVFC_PROTO_SCSI) + move->common.opcode = cpu_to_be32(IBMVFC_MOVE_LOGIN); + else + move->common.opcode = cpu_to_be32(IBMVFC_NVMF_MOVE_LOGIN); move->common.length = cpu_to_be16(sizeof(*move)); move->old_scsi_id = cpu_to_be64(tgt->scsi_id); @@ -4561,7 +4571,8 @@ static void ibmvfc_tgt_move_login(struct ibmvfc_target *tgt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DEL_RPORT); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent Move Login for new scsi_id: %llX\n", tgt->new_scsi_id); + tgt_dbg(tgt, "Sent %s Move Login for new scsi_id: %llX\n", + proto_type[tgt->protocol], tgt->new_scsi_id); } /** From 4bc896bf09592758756a451852af095c0dbc7c0e Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:33 -0700 Subject: [PATCH 17/33] scsi: ibmvfc: add NVMe/FC Port Login support Expand the target login path to issue the NVMe/FC-specific port login MAD for NVMe targets. Select the correct PLOGI MAD opcode based on the target protocol and include the protocol name in success and failure logging. The rest of the target login flow remains shared with the existing SCSI implementation. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-18-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 66fcff7ac834..adc146214e66 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4212,7 +4212,7 @@ static void ibmvfc_tgt_plogi_done(struct ibmvfc_event *evt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); switch (status) { case IBMVFC_MAD_SUCCESS: - tgt_dbg(tgt, "Port Login succeeded\n"); + tgt_dbg(tgt, "%s Port Login succeeded\n", proto_type[tgt->protocol]); if (tgt->ids.port_name && tgt->ids.port_name != wwn_to_u64(rsp->service_parms.port_name)) { vhost->reinit = 1; @@ -4240,9 +4240,9 @@ static void ibmvfc_tgt_plogi_done(struct ibmvfc_event *evt) else ibmvfc_del_tgt(tgt); - tgt_log(tgt, level, "Port Login failed: %s (%x:%x) %s (%x) %s (%x) rc=0x%02X\n", - ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), - be16_to_cpu(rsp->status), be16_to_cpu(rsp->error), + tgt_log(tgt, level, "%s Port Login failed: %s (%x:%x) %s (%x) %s (%x) rc=0x%02X\n", + proto_type[tgt->protocol], ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), + be16_to_cpu(rsp->error)), be16_to_cpu(rsp->status), be16_to_cpu(rsp->error), ibmvfc_get_fc_type(be16_to_cpu(rsp->fc_type)), be16_to_cpu(rsp->fc_type), ibmvfc_get_ls_explain(be16_to_cpu(rsp->fc_explain)), be16_to_cpu(rsp->fc_explain), status); break; @@ -4288,7 +4288,10 @@ static void ibmvfc_tgt_send_plogi(struct ibmvfc_target *tgt) } else { plogi->common.version = cpu_to_be32(1); } - plogi->common.opcode = cpu_to_be32(IBMVFC_PORT_LOGIN); + if (tgt->protocol == IBMVFC_PROTO_SCSI) + plogi->common.opcode = cpu_to_be32(IBMVFC_PORT_LOGIN); + else + plogi->common.opcode = cpu_to_be32(IBMVFC_NVMF_PORT_LOGIN); plogi->common.length = cpu_to_be16(sizeof(*plogi)); plogi->scsi_id = cpu_to_be64(tgt->scsi_id); @@ -4297,7 +4300,7 @@ static void ibmvfc_tgt_send_plogi(struct ibmvfc_target *tgt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent port login\n"); + tgt_dbg(tgt, "Sent %s port login\n", proto_type[tgt->protocol]); } /** From d11c05ddc2b69543353952b569f26d3f791ff339 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:34 -0700 Subject: [PATCH 18/33] scsi: ibmvfc: add NVMe/FC Process Login support Extend PRLI handling code to support NVMe/FC targets. When the target protocol is NVMe/FC, issue the NVMe process login MAD, set the NVMe FC-4 type, and populate NVMe-specific service parameters. On completion, decode the returned PRLI service parameters and derive the appropriate remote-port roles for NVMe initiator, target, and discovery ports. Keep the existing SCSI PRLI flow unchanged while allowing the common target state machine to complete login for NVMe/FC targets. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-19-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 55 ++++++++++++++++++++--------- drivers/scsi/ibmvscsi/ibmvfc.h | 6 ++++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index adc146214e66..5950f143e9a1 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4090,11 +4090,12 @@ static void ibmvfc_tgt_prli_done(struct ibmvfc_event *evt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); switch (status) { case IBMVFC_MAD_SUCCESS: - tgt_dbg(tgt, "Process Login succeeded: %X %02X %04X\n", - parms->type, parms->flags, parms->service_parms); + tgt_dbg(tgt, "%s Process Login succeeded: %X %02X %04X\n", + proto_type[tgt->protocol], parms->type, parms->flags, + parms->service_parms); + index = ibmvfc_get_prli_rsp(be16_to_cpu(parms->flags)); if (parms->type == IBMVFC_SCSI_FCP_TYPE) { - index = ibmvfc_get_prli_rsp(be16_to_cpu(parms->flags)); if (prli_rsp[index].logged_in) { if (be16_to_cpu(parms->flags) & IBMVFC_PRLI_EST_IMG_PAIR) { tgt->need_login = 0; @@ -4110,6 +4111,22 @@ static void ibmvfc_tgt_prli_done(struct ibmvfc_event *evt) ibmvfc_retry_tgt_init(tgt, ibmvfc_tgt_send_prli); else ibmvfc_del_tgt(tgt); + } else if (parms->type == IBMVFC_NVME_FCP_TYPE) { + if (prli_rsp[index].logged_in) { + /* For FC-NVMe PRLI the Image Pair field is always set to zero (see 6.3.3) */ + tgt->need_login = 0; + tgt->ids.roles = 0; + if (be32_to_cpu(parms->service_parms) & IBMVFC_PRLI_NVME_TARGET) + tgt->ids.roles |= FC_PORT_ROLE_NVME_TARGET; + if (be32_to_cpu(parms->service_parms) & IBMVFC_PRLI_NVME_INITIATOR) + tgt->ids.roles |= FC_PORT_ROLE_NVME_INITIATOR; + if (be32_to_cpu(parms->service_parms) & IBMVFC_PRLI_NVME_DISCOVERY) + tgt->ids.roles |= FC_PORT_ROLE_NVME_DISCOVERY; + tgt->add_rport = 1; + } else if (prli_rsp[index].retry) + ibmvfc_retry_tgt_init(tgt, ibmvfc_tgt_send_prli); + else + ibmvfc_del_tgt(tgt); } else ibmvfc_del_tgt(tgt); break; @@ -4130,9 +4147,10 @@ static void ibmvfc_tgt_prli_done(struct ibmvfc_event *evt) else ibmvfc_del_tgt(tgt); - tgt_log(tgt, level, "Process Login failed: %s (%x:%x) rc=0x%02X\n", - ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), - be16_to_cpu(rsp->status), be16_to_cpu(rsp->error), status); + tgt_log(tgt, level, "%s Process Login failed: %s (%x:%x) rc=0x%02X\n", + proto_type[tgt->protocol], ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), + be16_to_cpu(rsp->error)), be16_to_cpu(rsp->status), + be16_to_cpu(rsp->error), status); break; } @@ -4174,25 +4192,30 @@ static void ibmvfc_tgt_send_prli(struct ibmvfc_target *tgt) } else { prli->common.version = cpu_to_be32(1); } - prli->common.opcode = cpu_to_be32(IBMVFC_PROCESS_LOGIN); + if (tgt->protocol == IBMVFC_PROTO_SCSI) { + prli->common.opcode = cpu_to_be32(IBMVFC_PROCESS_LOGIN); + prli->parms.type = IBMVFC_SCSI_FCP_TYPE; + prli->parms.flags = cpu_to_be16(IBMVFC_PRLI_EST_IMG_PAIR); + prli->parms.service_parms = cpu_to_be32(IBMVFC_PRLI_INITIATOR_FUNC); + prli->parms.service_parms |= cpu_to_be32(IBMVFC_PRLI_READ_FCP_XFER_RDY_DISABLED); + + if (cls3_error) + prli->parms.service_parms |= cpu_to_be32(IBMVFC_PRLI_RETRY); + } else { + prli->common.opcode = cpu_to_be32(IBMVFC_NVMF_PROCESS_LOGIN); + prli->parms.type = IBMVFC_NVME_FCP_TYPE; + prli->parms.service_parms = cpu_to_be32(IBMVFC_PRLI_NVME_INITIATOR); + } prli->common.length = cpu_to_be16(sizeof(*prli)); prli->scsi_id = cpu_to_be64(tgt->scsi_id); - prli->parms.type = IBMVFC_SCSI_FCP_TYPE; - prli->parms.flags = cpu_to_be16(IBMVFC_PRLI_EST_IMG_PAIR); - prli->parms.service_parms = cpu_to_be32(IBMVFC_PRLI_INITIATOR_FUNC); - prli->parms.service_parms |= cpu_to_be32(IBMVFC_PRLI_READ_FCP_XFER_RDY_DISABLED); - - if (cls3_error) - prli->parms.service_parms |= cpu_to_be32(IBMVFC_PRLI_RETRY); - ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_INIT_WAIT); if (ibmvfc_send_event(evt, vhost, default_timeout)) { vhost->discovery_threads--; ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent process login\n"); + tgt_dbg(tgt, "%s Sent process login\n", proto_type[tgt->protocol]); } /** diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 67f546ff092e..66025e6ffeed 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -381,6 +381,7 @@ struct ibmvfc_move_login { struct ibmvfc_prli_svc_parms { u8 type; #define IBMVFC_SCSI_FCP_TYPE 0x08 +#define IBMVFC_NVME_FCP_TYPE 0x28 u8 type_ext; __be16 flags; #define IBMVFC_PRLI_ORIG_PA_VALID 0x8000 @@ -396,6 +397,11 @@ struct ibmvfc_prli_svc_parms { #define IBMVFC_PRLI_TARGET_FUNC 0x00000010 #define IBMVFC_PRLI_READ_FCP_XFER_RDY_DISABLED 0x00000002 #define IBMVFC_PRLI_WR_FCP_XFER_RDY_DISABLED 0x00000001 +#define IBMVFC_PRLI_NVME_PI_CTRL 0x00000200 +#define IBMVFC_PRLI_NVME_SLER 0x00000100 +#define IBMVFC_PRLI_NVME_INITIATOR 0x00000020 +#define IBMVFC_PRLI_NVME_TARGET 0x00000010 +#define IBMVFC_PRLI_NVME_DISCOVERY 0x00000008 } __packed __aligned(4); struct ibmvfc_process_login { From 5bdeab3c146786939e980cb5b040a3372aadc28f Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:35 -0700 Subject: [PATCH 19/33] scsi: ibmvfc: add NVMe/FC Query Target support Add protocol-specific query-target support for NVMe/FC targets. Use the NVMe query-target specific MAD when querying an NVMe target and update the associated debug and error logging to include the target protocol. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-20-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 5950f143e9a1..b479537e9e78 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4859,7 +4859,7 @@ static void ibmvfc_tgt_query_target_done(struct ibmvfc_event *evt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); switch (status) { case IBMVFC_MAD_SUCCESS: - tgt_dbg(tgt, "Query Target succeeded\n"); + tgt_dbg(tgt, "%s Query Target succeeded\n", proto_type[tgt->protocol]); if (be64_to_cpu(rsp->scsi_id) != tgt->scsi_id) ibmvfc_del_tgt(tgt); else @@ -4881,9 +4881,9 @@ static void ibmvfc_tgt_query_target_done(struct ibmvfc_event *evt) else ibmvfc_del_tgt(tgt); - tgt_log(tgt, level, "Query Target failed: %s (%x:%x) %s (%x) %s (%x) rc=0x%02X\n", - ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)), - be16_to_cpu(rsp->status), be16_to_cpu(rsp->error), + tgt_log(tgt, level, "%s Query Target failed: %s (%x:%x) %s (%x) %s (%x) rc=0x%02X\n", + proto_type[tgt->protocol], ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), + be16_to_cpu(rsp->error)), be16_to_cpu(rsp->status), be16_to_cpu(rsp->error), ibmvfc_get_fc_type(be16_to_cpu(rsp->fc_type)), be16_to_cpu(rsp->fc_type), ibmvfc_get_gs_explain(be16_to_cpu(rsp->fc_explain)), be16_to_cpu(rsp->fc_explain), status); @@ -4923,7 +4923,10 @@ static void ibmvfc_tgt_query_target(struct ibmvfc_target *tgt) query_tgt = &evt->iu.query_tgt; memset(query_tgt, 0, sizeof(*query_tgt)); query_tgt->common.version = cpu_to_be32(1); - query_tgt->common.opcode = cpu_to_be32(IBMVFC_QUERY_TARGET); + if (tgt->protocol == IBMVFC_PROTO_SCSI) + query_tgt->common.opcode = cpu_to_be32(IBMVFC_QUERY_TARGET); + else + query_tgt->common.opcode = cpu_to_be32(IBMVFC_NVMF_QUERY_TARGET); query_tgt->common.length = cpu_to_be16(sizeof(*query_tgt)); query_tgt->wwpn = cpu_to_be64(tgt->ids.port_name); @@ -4933,7 +4936,7 @@ static void ibmvfc_tgt_query_target(struct ibmvfc_target *tgt) ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); kref_put(&tgt->kref, ibmvfc_release_tgt); } else - tgt_dbg(tgt, "Sent Query Target\n"); + tgt_dbg(tgt, "Sent %s Query Target\n", proto_type[tgt->protocol]); } /** From 249313b3f7b5dbbf1aed31feff339ab48937a6fc Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:36 -0700 Subject: [PATCH 20/33] scsi: ibmvfc: allocate targets based on protocol Allocate discovered targets onto the channel-group list that matches their protocol. When a target is created, use the discovered protocol type to decide which list it belongs on. This keeps protocol-specific discovery results isolated and allows later state-machine and remote-port code to walk the correct target set. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-21-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index b479537e9e78..92c286cfc69c 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4948,25 +4948,32 @@ static void ibmvfc_tgt_query_target(struct ibmvfc_target *tgt) * 0 on success / other on failure **/ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, - struct ibmvfc_discover_targets_entry *target) + struct ibmvfc_discover_targets_entry *target, + enum ibmvfc_protocol protocol) { struct ibmvfc_target *stgt = NULL; struct ibmvfc_target *wtgt = NULL; struct ibmvfc_target *tgt; + struct ibmvfc_channels *channels; unsigned long flags; u64 scsi_id = be32_to_cpu(target->scsi_id) & IBMVFC_DISC_TGT_SCSI_ID_MASK; u64 wwpn = be64_to_cpu(target->wwpn); + if (protocol == IBMVFC_PROTO_SCSI) + channels = &vhost->scsi_scrqs; + else + channels = &vhost->nvme_scrqs; + /* Look to see if we already have a target allocated for this SCSI ID or WWPN */ spin_lock_irqsave(vhost->host->host_lock, flags); - list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { + list_for_each_entry(tgt, &channels->targets, queue) { if (tgt->wwpn == wwpn) { wtgt = tgt; break; } } - list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) { + list_for_each_entry(tgt, &channels->targets, queue) { if (tgt->scsi_id == scsi_id) { stgt = tgt; break; @@ -5014,6 +5021,7 @@ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, tgt = mempool_alloc(vhost->tgt_pool, GFP_NOIO); memset(tgt, 0, sizeof(*tgt)); + tgt->protocol = protocol; tgt->scsi_id = scsi_id; tgt->wwpn = wwpn; tgt->vhost = vhost; @@ -5023,7 +5031,7 @@ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, ibmvfc_init_tgt(tgt, ibmvfc_tgt_implicit_logout); spin_lock_irqsave(vhost->host->host_lock, flags); tgt->cancel_key = vhost->task_set++; - list_add_tail(&tgt->queue, &vhost->scsi_scrqs.targets); + list_add_tail(&tgt->queue, &channels->targets); unlock_out: spin_unlock_irqrestore(vhost->host->host_lock, flags); @@ -5042,7 +5050,11 @@ static int ibmvfc_alloc_targets(struct ibmvfc_host *vhost) int i, rc; for (i = 0, rc = 0; !rc && i < vhost->scsi_scrqs.num_targets; i++) - rc = ibmvfc_alloc_target(vhost, &vhost->scsi_scrqs.disc_buf[i]); + rc = ibmvfc_alloc_target(vhost, &vhost->scsi_scrqs.disc_buf[i], + vhost->scsi_scrqs.protocol); + for (i = 0; !rc && i < vhost->nvme_scrqs.num_targets; i++) + rc = ibmvfc_alloc_target(vhost, &vhost->nvme_scrqs.disc_buf[i], + vhost->nvme_scrqs.protocol); return rc; } From e0fca728a89f14c2edd3e9f5343d0f019d8f50dd Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:37 -0700 Subject: [PATCH 21/33] scsi: ibmvfc: delete NVMe/FC targets as well as SCSI Extend target deletion paths to process NVMe targets as well as SCSI targets. Update link-down, and host reinitialization flows to walk both the SCSI and NVMe target lists when marking targets for deletion. This ensures that protocol-specific target state stays consistent across adapter resets and fabric events. Rename ibmvfc_relogin to ibmvfc_scsi_relogin as it acts on a scsi command. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-22-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 92c286cfc69c..1185d72a8a73 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -714,6 +714,8 @@ static void ibmvfc_link_down(struct ibmvfc_host *vhost, scsi_block_requests(vhost->host); list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) ibmvfc_del_tgt(tgt); + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + ibmvfc_del_tgt(tgt); ibmvfc_set_host_state(vhost, state); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_TGT_DEL); vhost->events_to_log |= IBMVFC_AE_LINKDOWN; @@ -751,6 +753,12 @@ static void ibmvfc_init_host(struct ibmvfc_host *vhost) else ibmvfc_del_tgt(tgt); } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (vhost->client_migrated) + tgt->need_login = 1; + else + ibmvfc_del_tgt(tgt); + } scsi_block_requests(vhost->host); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT); @@ -1870,7 +1878,7 @@ static void ibmvfc_log_error(struct ibmvfc_event *evt) * @sdev: scsi device struct * **/ -static void ibmvfc_relogin(struct scsi_device *sdev) +static void ibmvfc_scsi_relogin(struct scsi_device *sdev) { struct ibmvfc_host *vhost = shost_priv(sdev->host); struct fc_rport *rport = starget_to_rport(scsi_target(sdev)); @@ -1922,7 +1930,7 @@ static void ibmvfc_scsi_done(struct ibmvfc_event *evt) memcpy(cmnd->sense_buffer, rsp->data.sense + rsp_len, sense_len); if ((be16_to_cpu(vfc_cmd->status) & IBMVFC_VIOS_FAILURE) && (be16_to_cpu(vfc_cmd->error) == IBMVFC_PLOGI_REQUIRED)) - ibmvfc_relogin(cmnd->device); + ibmvfc_scsi_relogin(cmnd->device); if (!cmnd->result && (!scsi_get_resid(cmnd) || (rsp->flags & FCP_RESID_OVER))) cmnd->result = (DID_ERROR << 16); @@ -3306,6 +3314,22 @@ static void ibmvfc_handle_async(struct ibmvfc_async_crq *crq, ibmvfc_reinit_host(vhost); } } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (!crq->scsi_id && !crq->wwpn && !crq->node_name) + break; + if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id) + continue; + if (crq->wwpn && cpu_to_be64(tgt->ids.port_name) != crq->wwpn) + continue; + if (crq->node_name && cpu_to_be64(tgt->ids.node_name) != crq->node_name) + continue; + if (tgt->need_login && be64_to_cpu(crq->event) == IBMVFC_AE_ELS_LOGO) + tgt->logo_rcvd = 1; + if (!tgt->need_login || be64_to_cpu(crq->event) == IBMVFC_AE_ELS_PLOGI) { + ibmvfc_del_tgt(tgt); + ibmvfc_reinit_host(vhost); + } + } break; case IBMVFC_AE_LINK_DOWN: case IBMVFC_AE_ADAPTER_FAILED: From 0a3ab63e43b8eeb19a454e5eedca3e99cd737a94 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:38 -0700 Subject: [PATCH 22/33] scsi: ibmvfc: update state machine to process NVMe/FC targets Update the host work loop and target state-machine helpers to process NVMe targets in addition to SCSI targets. Check both protocol-specific target lists when determining whether there is initialization or logout work pending, and extend the query, target init, and target delete phases to dispatch work for NVMe targets using the same common state-machine callbacks. This allows the existing discovery and login state machine to drive NVMe/FC targets through query, login, and deletion without duplicating the control flow. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-23-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 1185d72a8a73..ff84ca0f9f45 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -5718,6 +5718,11 @@ static int ibmvfc_dev_init_to_do(struct ibmvfc_host *vhost) tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) return 1; } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_INIT || + tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) + return 1; + } return 0; } @@ -5738,6 +5743,11 @@ static int ibmvfc_dev_logo_to_do(struct ibmvfc_host *vhost) tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) return 1; } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT || + tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) + return 1; + } return 0; } @@ -5766,9 +5776,15 @@ static int __ibmvfc_work_to_do(struct ibmvfc_host *vhost) list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_INIT) return 1; + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + if (tgt->action == IBMVFC_TGT_ACTION_INIT) + return 1; list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) return 0; + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + if (tgt->action == IBMVFC_TGT_ACTION_INIT_WAIT) + return 0; return 1; case IBMVFC_HOST_ACTION_TGT_DEL: case IBMVFC_HOST_ACTION_TGT_DEL_FAILED: @@ -5777,9 +5793,15 @@ static int __ibmvfc_work_to_do(struct ibmvfc_host *vhost) list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT) return 1; + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT) + return 1; list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) return 0; + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT_WAIT) + return 0; return 1; case IBMVFC_HOST_ACTION_LOGO: case IBMVFC_HOST_ACTION_INIT: @@ -5967,6 +5989,8 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) case IBMVFC_HOST_ACTION_QUERY: list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) ibmvfc_init_tgt(tgt, ibmvfc_tgt_query_target); + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) + ibmvfc_init_tgt(tgt, ibmvfc_tgt_query_target); ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY_TGTS); break; case IBMVFC_HOST_ACTION_QUERY_TGTS: @@ -5976,6 +6000,12 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) break; } } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_INIT) { + tgt->job_step(tgt); + break; + } + } if (!ibmvfc_dev_init_to_do(vhost)) ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_TGT_DEL); @@ -5988,6 +6018,12 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) break; } } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_LOGOUT_RPORT) { + tgt->job_step(tgt); + break; + } + } if (ibmvfc_dev_logo_to_do(vhost)) { spin_unlock_irqrestore(vhost->host->host_lock, flags); @@ -6075,6 +6111,12 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) break; } } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_INIT) { + tgt->job_step(tgt); + break; + } + } if (!ibmvfc_dev_init_to_do(vhost)) ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_TGT_DEL_FAILED); From 13b7fdf3274c2e7445e442cf83a80c9252fba702 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:39 -0700 Subject: [PATCH 23/33] scsi: ibmvfc: implement NVMe/FC stubs for local/remote port registration Implement the initial NVMe/FC local-port and remote-port registration functions that notify the NVMe-FC midlayr of port discovery and loss. Register the local port with the NVMe-FC transport, register discovered remote ports against that local port, and add matching unregister paths that wait for the NVMe-FC core to complete asynchronous deletion before dropping driver references. Also store driver-private host and target pointers in the registered NVMe-FC port objects. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-24-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 80 ++++++++++++++++++++++++++++- drivers/scsi/ibmvscsi/ibmvfc-nvme.h | 4 +- drivers/scsi/ibmvscsi/ibmvfc.h | 4 ++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index 2ee1c64665d9..d679d1ca366a 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -14,10 +14,18 @@ static void ibmvfc_nvme_localport_delete(struct nvme_fc_local_port *lport) { + struct ibmvfc_host *vhost = lport->private; + + vhost->nvme_local_port = NULL; + complete(&vhost->nvme_delete_done); } static void ibmvfc_nvme_remoteport_delete(struct nvme_fc_remote_port *rport) { + struct ibmvfc_target *tgt = rport->private; + + tgt->nvme_remote_port = NULL; + complete(&tgt->nvme_delete_done); } static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport, @@ -70,28 +78,96 @@ static struct nvme_fc_port_template ibmvfc_nvme_fc_transport = { int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt) { + struct ibmvfc_host *vhost = tgt->vhost; + struct nvme_fc_port_info pinfo; + int rc; + if (!IS_ENABLED(CONFIG_NVME_FC)) return 0; - return 0; + if (!vhost->nvme_local_port) { + dev_err(vhost->dev, "Attempt to register NVMe fc remoteport without valid localport\n"); + return -EINVAL; + } + + memset(&pinfo, 0, sizeof(struct nvme_fc_port_info)); + pinfo.node_name = tgt->ids.node_name; + pinfo.port_name = tgt->ids.port_name; + pinfo.port_id = tgt->ids.port_id; + pinfo.port_role = FC_PORT_ROLE_NVME_TARGET; + + rc = nvme_fc_register_remoteport(vhost->nvme_local_port, &pinfo, + &tgt->nvme_remote_port); + + if (!rc) { + ibmvfc_log(vhost, 2, "register_remoteport: traddr=nn-0x%llx:pn-0x%llx PortID:%x\n", + pinfo.node_name, pinfo.port_name, pinfo.port_id); + tgt->nvme_remote_port->private = tgt; + } + + return rc; } void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt) { + struct ibmvfc_host *vhost = tgt->vhost; + struct nvme_fc_remote_port *rport = tgt->nvme_remote_port; + int rc; + if (!IS_ENABLED(CONFIG_NVME_FC)) return; + + if (!tgt->nvme_remote_port) + return; + + ibmvfc_log(vhost, 2, "unregister_remoteport: traddr=nn-0x%llx:pn-0x%llx PortID:%x\n", + rport->node_name, rport->port_name, rport->port_id); + init_completion(&tgt->nvme_delete_done); + rc = nvme_fc_unregister_remoteport(tgt->nvme_remote_port); + + if (!rc) { + wait_for_completion(&tgt->nvme_delete_done); + } } int ibmvfc_nvme_register(struct ibmvfc_host *vhost) { + struct nvme_fc_port_info pinfo; + int rc; + if (!IS_ENABLED(CONFIG_NVME_FC)) return 0; - return 0; + pinfo.node_name = fc_host_node_name(vhost->host); + pinfo.port_name = fc_host_port_name(vhost->host); + pinfo.port_id = fc_host_port_id(vhost->host); + pinfo.port_role = FC_PORT_ROLE_NVME_INITIATOR; + pinfo.dev_loss_tmo = 0; + + rc = nvme_fc_register_localport(&pinfo, &ibmvfc_nvme_fc_transport, + vhost->dev, &vhost->nvme_local_port); + + if (!rc) { + ibmvfc_log(vhost, 2, "register_localport: host-traddr=nn-0x%llx:pn-0x%llx on portID:%x\n", + pinfo.node_name, pinfo.port_name, pinfo.port_id); + vhost->nvme_local_port->private = vhost; + } else + dev_err(vhost->dev, "Failed to register NVMe fc localport (%d)\n", rc); + + return rc; } void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost) { + int rc; + if (!IS_ENABLED(CONFIG_NVME_FC)) return; + + if (vhost->nvme_local_port) { + init_completion(&vhost->nvme_delete_done); + rc = nvme_fc_unregister_localport(vhost->nvme_local_port); + if (!rc) + wait_for_completion(&vhost->nvme_delete_done); + } } diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h index 97e267871df2..0465e8719881 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h @@ -11,8 +11,8 @@ #ifndef _IBMVFC_NVME_H #define _IBMVFC_NVME_H -#include -#include +#include +#include #include #include "ibmvfc.h" diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 66025e6ffeed..d8c2e5f1fdec 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -839,6 +839,8 @@ struct ibmvfc_target { void (*job_step) (struct ibmvfc_target *); struct timer_list timer; struct kref kref; + struct completion nvme_delete_done; + struct nvme_fc_remote_port *nvme_remote_port; }; /* a unit of work for the hosting partition */ @@ -1015,6 +1017,8 @@ struct ibmvfc_host { struct work_struct rport_add_work_q; wait_queue_head_t init_wait_q; wait_queue_head_t work_wait_q; + struct nvme_fc_local_port *nvme_local_port; + struct completion nvme_delete_done; }; static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channels *channels) From 3831863f9f5663680e002d8dc3e85e57ef15bb47 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:40 -0700 Subject: [PATCH 24/33] scsi: ibmvfc: register local nvme fc port after fabric login Register the local NVMe/FC port only after fabric login has completed. The VIOS returns the client port ID in the fabric login response, and that port ID is required to populate the local-port information passed to the NVMe-FC midlayer. Delay local-port registration until that data is available and update the registration helper accordingly. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-25-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 5 +++++ drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index ff84ca0f9f45..81d9229bf388 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -5210,6 +5210,8 @@ static void ibmvfc_fabric_login_nvme_done(struct ibmvfc_event *evt) switch (mad_status) { case IBMVFC_MAD_SUCCESS: + fc_host_port_id(vhost->host) = be64_to_cpu(rsp->nport_id); + ibmvfc_nvme_register(vhost); ibmvfc_dbg(vhost, "NVMe fabric login succeeded\n"); break; case IBMVFC_MAD_FAILED: @@ -5271,6 +5273,7 @@ static void ibmvfc_fabric_login_scsi_done(struct ibmvfc_event *evt) switch (mad_status) { case IBMVFC_MAD_SUCCESS: + fc_host_port_id(vhost->host) = be64_to_cpu(rsp->nport_id); ibmvfc_dbg(vhost, "SCSI fabric login succeeded\n"); break; case IBMVFC_MAD_FAILED: @@ -5653,6 +5656,7 @@ static void ibmvfc_npiv_logout_done(struct ibmvfc_event *evt) case IBMVFC_MAD_SUCCESS: if (list_empty(&vhost->crq.sent) && vhost->action == IBMVFC_HOST_ACTION_LOGO_WAIT) { + ibmvfc_nvme_unregister(vhost); ibmvfc_init_host(vhost); return; } @@ -5931,6 +5935,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) list_splice_init(&vhost->purge, &purge); spin_unlock_irqrestore(vhost->host->host_lock, flags); ibmvfc_complete_purge(&purge); + ibmvfc_nvme_unregister(vhost); rc = ibmvfc_reset_crq(vhost); spin_lock_irqsave(vhost->host->host_lock, flags); diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index d679d1ca366a..637d7d7015cf 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -145,14 +145,17 @@ int ibmvfc_nvme_register(struct ibmvfc_host *vhost) pinfo.dev_loss_tmo = 0; rc = nvme_fc_register_localport(&pinfo, &ibmvfc_nvme_fc_transport, - vhost->dev, &vhost->nvme_local_port); + get_device(vhost->dev), + &vhost->nvme_local_port); if (!rc) { ibmvfc_log(vhost, 2, "register_localport: host-traddr=nn-0x%llx:pn-0x%llx on portID:%x\n", pinfo.node_name, pinfo.port_name, pinfo.port_id); vhost->nvme_local_port->private = vhost; - } else + } else { dev_err(vhost->dev, "Failed to register NVMe fc localport (%d)\n", rc); + put_device(vhost->dev); + } return rc; } @@ -165,9 +168,17 @@ void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost) return; if (vhost->nvme_local_port) { + ibmvfc_log(vhost, 2, "unregister_localport: host-traddr=nn-0x%llx:pn-0x%llx on portID:%x\n", + vhost->nvme_local_port->node_name, + vhost->nvme_local_port->port_name, + vhost->nvme_local_port->port_id); init_completion(&vhost->nvme_delete_done); rc = nvme_fc_unregister_localport(vhost->nvme_local_port); - if (!rc) + if (!rc) { wait_for_completion(&vhost->nvme_delete_done); + } else + dev_err(vhost->dev, "Failed to unregister NVMe fc localport (%d)\n", rc); + + put_device(vhost->dev); } } From 696d1cc2aaa2d7041b64ea714a7460e2d365235c Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:41 -0700 Subject: [PATCH 25/33] scsi: ibmvfc: process NVMe/FC rports in work thread Add an NVMe-specific remote-port add helper and update the rport worker thread to walk the NVMe target list, register new NVMe remote ports, and rescan existing ones through the NVMe-FC midlayer. Also handle delete and delete-with-logout transitions for NVMe remote ports in the same worker context used for SCSI rports. This keeps remote-port registration serialized in the existing worker model while allowing NVMe targets to participate in the common target state machine. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-26-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 94 ++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 81d9229bf388..ffb579816e84 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -5856,6 +5856,51 @@ static void ibmvfc_log_ae(struct ibmvfc_host *vhost, int events) fc_host_post_event(vhost->host, fc_get_event_number(), FCH_EVT_LINKUP, 0); } +/** + * ibmvfc_tgt_add_nvme_rport - Tell the FC transport about a new remote port + * @tgt: ibmvfc target struct + * + **/ +static void ibmvfc_tgt_add_nvme_rport(struct ibmvfc_target *tgt) +{ + struct ibmvfc_host *vhost = tgt->vhost; + struct nvme_fc_remote_port *rport; + unsigned long flags; + + tgt_dbg(tgt, "Adding NVMe rport\n"); + ibmvfc_nvme_register_remoteport(tgt); + spin_lock_irqsave(vhost->host->host_lock, flags); + rport = tgt->nvme_remote_port; + + if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_RPORT) { + tgt_dbg(tgt, "Deleting NVMe rport\n"); + list_del(&tgt->queue); + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DELETED_RPORT); + spin_unlock_irqrestore(vhost->host->host_lock, flags); + ibmvfc_nvme_unregister_remoteport(tgt); + timer_delete_sync(&tgt->timer); + kref_put(&tgt->kref, ibmvfc_release_tgt); + return; + } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { + tgt_dbg(tgt, "Deleting NVMe rport with outstanding I/O\n"); + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_LOGOUT_DELETED_RPORT); + tgt->init_retries = 0; + spin_unlock_irqrestore(vhost->host->host_lock, flags); + ibmvfc_nvme_unregister_remoteport(tgt); + return; + } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DELETED_RPORT) { + spin_unlock_irqrestore(vhost->host->host_lock, flags); + return; + } + + if (rport) { + tgt_dbg(tgt, "NVMe rport add succeeded\n"); + tgt->target_id = tgt->nvme_remote_port->port_id; + } else + tgt_dbg(tgt, "NVMe rport add failed\n"); + spin_unlock_irqrestore(vhost->host->host_lock, flags); +} + /** * ibmvfc_tgt_add_rport - Tell the FC transport about a new remote port * @tgt: ibmvfc target struct @@ -5920,6 +5965,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) struct ibmvfc_target *tgt; unsigned long flags; struct fc_rport *rport; + struct nvme_fc_remote_port *nvme_rport; LIST_HEAD(purge); int rc; @@ -6073,6 +6119,30 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) } } + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->action == IBMVFC_TGT_ACTION_DEL_RPORT) { + tgt_dbg(tgt, "Deleteing NVMe rport\n"); + nvme_rport = tgt->nvme_remote_port; + list_del(&tgt->queue); + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DELETED_RPORT); + spin_unlock_irqrestore(vhost->host->host_lock, flags); + if (nvme_rport) + ibmvfc_nvme_unregister_remoteport(tgt); + timer_delete_sync(&tgt->timer); + kref_put(&tgt->kref, ibmvfc_release_tgt); + return; + } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { + tgt_dbg(tgt, "Deleting NVMe rport with outstanding I/O\n"); + nvme_rport = tgt->nvme_remote_port; + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_LOGOUT_DELETED_RPORT); + tgt->init_retries = 0; + spin_unlock_irqrestore(vhost->host->host_lock, flags); + if (nvme_rport) + ibmvfc_nvme_unregister_remoteport(tgt); + return; + } + } + if (vhost->state == IBMVFC_INITIALIZING) { if (vhost->action == IBMVFC_HOST_ACTION_TGT_DEL_FAILED) { if (vhost->reinit) { @@ -6658,6 +6728,7 @@ static void ibmvfc_rport_add_thread(struct work_struct *work) rport_add_work_q); struct ibmvfc_target *tgt; struct fc_rport *rport; + struct nvme_fc_remote_port *nvme_rport; unsigned long flags; int did_work; @@ -6691,7 +6762,28 @@ static void ibmvfc_rport_add_thread(struct work_struct *work) break; } } - } while(did_work); + + list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) { + if (tgt->add_rport) { + did_work = 1; + tgt->add_rport = 0; + kref_get(&tgt->kref); + nvme_rport = tgt->nvme_remote_port; + if (!nvme_rport) { + spin_unlock_irqrestore(vhost->host->host_lock, flags); + ibmvfc_tgt_add_nvme_rport(tgt); + } else { + spin_unlock_irqrestore(vhost->host->host_lock, flags); + if (IS_ENABLED(CONFIG_NVME_FC)) + nvme_fc_rescan_remoteport(nvme_rport); + } + + kref_put(&tgt->kref, ibmvfc_release_tgt); + spin_lock_irqsave(vhost->host->host_lock, flags); + break; + } + } + } while (did_work); if (vhost->state == IBMVFC_ACTIVE) vhost->scan_complete = 1; From 28ec8670892c738cac121beb68d38e6c735a001c Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:42 -0700 Subject: [PATCH 26/33] scsi: ibmvfc: extend ibmvfc_debug visibility to ibmvfc-nvme.h Export ibmvfc_debug so the NVMe support code can use the existing ibmvfc_dbg logging macro. The debug control variable is currently file-local to the core driver, which prevents protocol-specific code in ibmvfc-nvme.c from using the shared debug infrastructure. Make the variable global within the module and declare it in ibmvfc-nvme.h. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-27-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 3 ++- drivers/scsi/ibmvscsi/ibmvfc-nvme.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index ffb579816e84..6156f10871ac 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -41,7 +41,6 @@ static unsigned int max_requests = IBMVFC_MAX_REQUESTS_DEFAULT; static u16 max_sectors = IBMVFC_MAX_SECTORS; static u16 scsi_qdepth = IBMVFC_SCSI_QDEPTH; static unsigned int disc_threads = IBMVFC_MAX_DISC_THREADS; -static unsigned int ibmvfc_debug = IBMVFC_DEBUG; static unsigned int log_level = IBMVFC_DEFAULT_LOG_LEVEL; static unsigned int cls3_error = IBMVFC_CLS3_ERROR; static unsigned int mq_enabled = IBMVFC_MQ; @@ -53,6 +52,8 @@ static unsigned int nr_nvme_channels = IBMVFC_NVME_CHANNELS; static unsigned int mig_channels_only = IBMVFC_MIG_NO_SUB_TO_CRQ; static unsigned int mig_no_less_channels = IBMVFC_MIG_NO_N_TO_M; +unsigned int ibmvfc_debug = IBMVFC_DEBUG; + static LIST_HEAD(ibmvfc_head); static DEFINE_SPINLOCK(ibmvfc_driver_lock); static struct scsi_transport_template *ibmvfc_transport_template; diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h index 0465e8719881..3aa285788795 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h @@ -22,6 +22,8 @@ #define IBMVFC_MAX_NVME_QUEUES 16 #define IBMVFC_NVME_CHANNELS 8 +extern unsigned int ibmvfc_debug; + struct ibmvfc_host; struct ibmvfc_target; From 6fac8df932d8814e8a49736216614c26f51f83a0 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:43 -0700 Subject: [PATCH 27/33] scsi: ibmvfc: declare global function definitions Some common functions will require visibility by both SCSI and NVMe protocols. Make common ibmvfc helper routines available to the NVMe support code. Remove static from the core event allocation, event initialization, event free, target release, command error, and event send helpers, and declare them in ibmvfc.h. This allows ibmvfc-nvme.c to reuse the existing event and target infrastructure. No functional change is intended. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-28-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 22 ++++++---------------- drivers/scsi/ibmvscsi/ibmvfc.h | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 6156f10871ac..206cc9508dda 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -206,13 +206,6 @@ static long h_reg_sub_crq(unsigned long unit_address, unsigned long ioba, return rc; } -static int ibmvfc_check_caps(struct ibmvfc_host *vhost, unsigned long cap_flags) -{ - u64 host_caps = be64_to_cpu(vhost->login_buf->resp.capabilities); - - return (host_caps & cap_flags) ? 1 : 0; -} - static int ibmvfc_nvme_active(struct ibmvfc_host *vhost) { return (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NVMEOF) && @@ -354,7 +347,7 @@ static int ibmvfc_get_err_index(u16 status, u16 error) * Return value: * error description string **/ -static const char *ibmvfc_get_cmd_error(u16 status, u16 error) +const char *ibmvfc_get_cmd_error(u16 status, u16 error) { int rc = ibmvfc_get_err_index(status, error); if (rc >= 0) @@ -1076,7 +1069,7 @@ static int ibmvfc_valid_event(struct ibmvfc_event_pool *pool, * @evt: ibmvfc_event to be freed * **/ -static void ibmvfc_free_event(struct ibmvfc_event *evt) +void ibmvfc_free_event(struct ibmvfc_event *evt) { struct ibmvfc_event_pool *pool = &evt->queue->evt_pool; unsigned long flags; @@ -1410,7 +1403,7 @@ static void ibmvfc_set_rport_dev_loss_tmo(struct fc_rport *rport, u32 timeout) * @kref: kref struct * **/ -static void ibmvfc_release_tgt(struct kref *kref) +void ibmvfc_release_tgt(struct kref *kref) { struct ibmvfc_target *tgt = container_of(kref, struct ibmvfc_target, kref); mempool_free(tgt, tgt->vhost->tgt_pool); @@ -1588,7 +1581,7 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost) * * Returns a free event from the pool. **/ -static struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int reserved) +struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int reserved) { struct ibmvfc_event *evt = NULL; unsigned long flags; @@ -1612,9 +1605,6 @@ static struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int r return evt; } -#define ibmvfc_get_event(queue) __ibmvfc_get_event(queue, 0) -#define ibmvfc_get_reserved_event(queue) __ibmvfc_get_event(queue, 1) - /** * ibmvfc_locked_done - Calls evt completion with host_lock held * @evt: ibmvfc evt to complete @@ -1639,7 +1629,7 @@ static void ibmvfc_locked_done(struct ibmvfc_event *evt) * @done: Routine to call when the event is responded to * @format: SRP or MAD format **/ -static void ibmvfc_init_event(struct ibmvfc_event *evt, +void ibmvfc_init_event(struct ibmvfc_event *evt, void (*done) (struct ibmvfc_event *), u8 format) { evt->cmnd = NULL; @@ -1764,7 +1754,7 @@ static void ibmvfc_timeout(struct timer_list *t) * * Returns the value returned from ibmvfc_send_crq(). (Zero for success) **/ -static int ibmvfc_send_event(struct ibmvfc_event *evt, +int ibmvfc_send_event(struct ibmvfc_event *evt, struct ibmvfc_host *vhost, unsigned long timeout) { __be64 *crq_as_u64 = (__be64 *) &evt->crq; diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index d8c2e5f1fdec..ece1f379c269 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -1021,6 +1021,23 @@ struct ibmvfc_host { struct completion nvme_delete_done; }; +struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int reserved); +#define ibmvfc_get_event(queue) __ibmvfc_get_event(queue, 0) +#define ibmvfc_get_reserved_event(queue) __ibmvfc_get_event(queue, 1) + +void ibmvfc_init_event(struct ibmvfc_event *evt, void (*done) (struct ibmvfc_event *), u8 format); +void ibmvfc_free_event(struct ibmvfc_event *evt); +void ibmvfc_release_tgt(struct kref *kref); +int ibmvfc_send_event(struct ibmvfc_event *evt, struct ibmvfc_host *vhost, unsigned long timeout); +const char *ibmvfc_get_cmd_error(u16 status, u16 error); + +static inline int ibmvfc_check_caps(struct ibmvfc_host *vhost, unsigned long cap_flags) +{ + u64 host_caps = be64_to_cpu(vhost->login_buf->resp.capabilities); + + return (host_caps & cap_flags) ? 1 : 0; +} + static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channels *channels) { if (channels->protocol == IBMVFC_PROTO_SCSI) From 86e4953580964323a727fc8eb8efb21e47cce1b0 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:44 -0700 Subject: [PATCH 28/33] scsi: ibmvfc: implement LLDD callbacks for mapping nvme-fc queues Implement the NVMe-FC queue create and delete callbacks and map NVMe controller queues onto ibmvfc hardware queues. Use qidx of NVMe controller queue to map onto a ibmvfc_queue channel. The Admin queue is always qidx 0 and general practice among other drivers is to map both the Admin queue and first IO queue to the same HW queue. Add a new ibmvfc_nvme_qhandle struct that will be used as the opaque queue handle by the NVMe-FC layer when issuing fcp IO. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-29-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 38 +++++++++++++++++++++++++++-- drivers/scsi/ibmvscsi/ibmvfc-nvme.h | 7 ++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index 637d7d7015cf..ecf9f4c0549c 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -28,6 +28,40 @@ static void ibmvfc_nvme_remoteport_delete(struct nvme_fc_remote_port *rport) complete(&tgt->nvme_delete_done); } +static int ibmvfc_nvme_create_queue(struct nvme_fc_local_port *lport, unsigned int qidx, + u16 qsize, void **handle) +{ + struct ibmvfc_host *vhost = lport->private; + struct ibmvfc_nvme_qhandle *qhandle; + + if (!vhost->nvme_scrqs.active_queues) + return -ENODEV; + + qhandle = kzalloc_obj(struct ibmvfc_nvme_qhandle); + if (!qhandle) + return -ENOMEM; + + qhandle->cpu_id = raw_smp_processor_id(); + qhandle->qidx = qidx; + + /* Admin and first IO queue are both mapped to index 0 */ + if (qidx) + qhandle->index = (qidx - 1) % vhost->nvme_scrqs.active_queues; + else + qhandle->index = qidx; + + qhandle->queue = &vhost->nvme_scrqs.scrqs[qhandle->index]; + + *handle = qhandle; + return 0; +} + +static void ibmvfc_nvme_delete_queue(struct nvme_fc_local_port *lport, unsigned int qidx, + void *handle) +{ + kfree(handle); +} + static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport, struct nvme_fc_remote_port *rport, struct nvmefc_ls_req *ls_req) @@ -59,8 +93,8 @@ static void ibmvfc_nvme_fcp_abort(struct nvme_fc_local_port *lport, static struct nvme_fc_port_template ibmvfc_nvme_fc_transport = { .localport_delete = ibmvfc_nvme_localport_delete, .remoteport_delete = ibmvfc_nvme_remoteport_delete, - .create_queue = NULL, - .delete_queue = NULL, + .create_queue = ibmvfc_nvme_create_queue, + .delete_queue = ibmvfc_nvme_delete_queue, .ls_req = ibmvfc_nvme_ls_req, .ls_abort = ibmvfc_nvme_ls_abort, .fcp_io = ibmvfc_nvme_fcp_io, diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h index 3aa285788795..4c6146048a6f 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h @@ -27,6 +27,13 @@ extern unsigned int ibmvfc_debug; struct ibmvfc_host; struct ibmvfc_target; +struct ibmvfc_nvme_qhandle { + unsigned int qidx; + u16 cpu_id; + unsigned long index; + struct ibmvfc_queue *queue; +}; + int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt); void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt); int ibmvfc_nvme_register(struct ibmvfc_host *vhost); From 7088e1c8b665eaa50c80f364821e0b19832858c5 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:45 -0700 Subject: [PATCH 29/33] scsi: ibmvfc: implement nvme-fc LS submission transport callback NVMe FC Link Service commands are required to use the ibmvfc_passthru MAD. Initialize a pssthru mad for the target port including the DMA addresses for the FC4_LS request and response as well as the max length of each IU as provided in the nvmefc_ls_req struct. FC4_LS commands are sent via the primary CRQ. Further, store the assoc_id during a create association request as this is a required field in our vfc_cmd struct for nvme_fcp_io commands. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-30-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 1 + drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 89 +++++++++++++++++++++++++++++ drivers/scsi/ibmvscsi/ibmvfc-nvme.h | 8 +++ drivers/scsi/ibmvscsi/ibmvfc.h | 2 + 4 files changed, 100 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 206cc9508dda..45c7bcf69201 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -1633,6 +1633,7 @@ void ibmvfc_init_event(struct ibmvfc_event *evt, void (*done) (struct ibmvfc_event *), u8 format) { evt->cmnd = NULL; + evt->ls_req = NULL; evt->sync_iu = NULL; evt->eh_comp = NULL; evt->crq.format = format; diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index ecf9f4c0549c..cf72f0682297 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -62,10 +62,99 @@ static void ibmvfc_nvme_delete_queue(struct nvme_fc_local_port *lport, unsigned kfree(handle); } +static void ibmvfc_ls_req_done(struct ibmvfc_event *evt) +{ + struct ibmvfc_target *tgt = evt->tgt; + struct ibmvfc_passthru_mad *mad = &evt->xfer_iu->passthru; + struct fcnvme_ls_rqst_w0 *ls_rqst; + struct fcnvme_ls_cr_assoc_acc *ls_resp; + u32 status = be16_to_cpu(mad->common.status); + int rc = 0; + + ls_rqst = (struct fcnvme_ls_rqst_w0 *)evt->ls_req->rqstaddr; + ls_resp = (struct fcnvme_ls_cr_assoc_acc *)evt->ls_req->rspaddr; + + switch (status) { + case IBMVFC_MAD_SUCCESS: + tgt_dbg(tgt, "ls_req succeeded\n"); + if ((ls_rqst->ls_cmd == FCNVME_LS_CREATE_ASSOCIATION) && + (ls_resp->hdr.w0.ls_cmd == FCNVME_LS_ACC)) { + tgt->assoc_id = be64_to_cpu(ls_resp->associd.association_id); + tgt_dbg(tgt, "assoc_id 0x%llx\n", tgt->assoc_id); + } + break; + case IBMVFC_MAD_DRIVER_FAILED: + break; + case IBMVFC_MAD_FAILED: + default: + tgt_info(tgt, "ls_req failed: %s (%x:%x) rc=0x%02X\n", + ibmvfc_get_cmd_error(be16_to_cpu(mad->iu.status), be16_to_cpu(mad->iu.error)), + be16_to_cpu(mad->iu.status), be16_to_cpu(mad->iu.error), status); + break; + } + + if (status) + rc = -EIO; + + evt->ls_req->done(evt->ls_req, rc); + + kref_put(&tgt->kref, ibmvfc_release_tgt); + ibmvfc_free_event(evt); +} + +static void ibmvfc_init_ls_req(struct ibmvfc_event *evt, struct nvmefc_ls_req *ls_req) +{ + struct ibmvfc_passthru_mad *mad = &evt->iu.passthru; + + memset(mad, 0, sizeof(*mad)); + mad->common.version = cpu_to_be32(2); + mad->common.opcode = cpu_to_be32(IBMVFC_NVMF_PASSTHRU); + mad->common.length = cpu_to_be16(sizeof(*mad) - sizeof(mad->fc_iu) - sizeof(mad->iu)); + mad->cmd_ioba.va = cpu_to_be64((u64)be64_to_cpu(evt->crq.ioba) + + offsetof(struct ibmvfc_passthru_mad, iu)); + mad->cmd_ioba.len = cpu_to_be32(sizeof(mad->iu)); + mad->iu.cmd_len = cpu_to_be32(ls_req->rqstlen); + mad->iu.rsp_len = cpu_to_be32(ls_req->rsplen); + mad->iu.cmd.va = cpu_to_be64(ls_req->rqstdma); + mad->iu.cmd.len = cpu_to_be32(ls_req->rqstlen); + mad->iu.rsp.va = cpu_to_be64(ls_req->rspdma); + mad->iu.rsp.len = cpu_to_be32(ls_req->rsplen); +} + static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport, struct nvme_fc_remote_port *rport, struct nvmefc_ls_req *ls_req) { + struct ibmvfc_host *vhost = lport->private; + struct ibmvfc_target *tgt = rport->private; + struct ibmvfc_passthru_mad *mad; + struct ibmvfc_event *evt; + + kref_get(&tgt->kref); + evt = ibmvfc_get_event(&vhost->crq); + if (!evt) { + kref_put(&tgt->kref, ibmvfc_release_tgt); + return -EBUSY; + } + + ibmvfc_init_event(evt, ibmvfc_ls_req_done, IBMVFC_MAD_FORMAT); + evt->tgt = tgt; + evt->ls_req = ls_req; + ls_req->private = evt; + + ibmvfc_init_ls_req(evt, ls_req); + mad = &evt->iu.passthru; + mad->iu.flags = cpu_to_be32(IBMVFC_FC4_LS_DSC_CTRL); + mad->iu.scsi_id = cpu_to_be64(tgt->scsi_id); + mad->iu.cancel_key = cpu_to_be32((u64)evt); + mad->iu.target_wwpn = cpu_to_be64(tgt->wwpn); + + ibmvfc_dbg(vhost, "nvme_ls_req\n"); + if (ibmvfc_send_event(evt, vhost, IBMVFC_FC4_LS_PLUS_CANCEL_TIMEOUT)) { + kref_put(&tgt->kref, ibmvfc_release_tgt); + return -ENXIO; + } + return 0; } diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h index 4c6146048a6f..e245b6ed0875 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.h +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "ibmvfc.h" @@ -22,10 +24,16 @@ #define IBMVFC_MAX_NVME_QUEUES 16 #define IBMVFC_NVME_CHANNELS 8 +#define IBMVFC_FC4_LS_TIMEOUT 15 +#define IBMVFC_FC4_LS_CANCEL_TIMEOUT 45 +#define IBMVFC_FC4_LS_PLUS_CANCEL_TIMEOUT \ + (IBMVFC_FC4_LS_TIMEOUT + IBMVFC_FC4_LS_CANCEL_TIMEOUT) + extern unsigned int ibmvfc_debug; struct ibmvfc_host; struct ibmvfc_target; +struct ibmvfc_queue; struct ibmvfc_nvme_qhandle { unsigned int qidx; diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index ece1f379c269..9f6705e604fd 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -824,6 +824,7 @@ struct ibmvfc_target { u64 scsi_id; u64 wwpn; u64 new_scsi_id; + u64 assoc_id; struct fc_rport *rport; int target_id; enum ibmvfc_target_action action; @@ -851,6 +852,7 @@ struct ibmvfc_event { struct ibmvfc_queue *queue; struct ibmvfc_target *tgt; struct scsi_cmnd *cmnd; + struct nvmefc_ls_req *ls_req; atomic_t free; atomic_t active; union ibmvfc_iu *xfer_iu; From 73c13e30c56f92b7050cc3cd45b901f861b0c588 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:46 -0700 Subject: [PATCH 30/33] scsi: ibmvfc: implement nvme-fc IO command submission callback Add helpers to initialize an ibmvfc command from an nvmefc_fcp_req, map request scatterlists into either an inline descriptor or an external DMA pool list, and submit the request on the selected NVMe hardware queue. On completion, translate ibmvfc status into the NVMe-FC response format, including transferred length and CQE handling for no-DMA responses. Also store the NVMe request pointer in struct ibmvfc_event so the completion path can finish the original request. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-31-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 3 +- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 140 +++++++++++++++++++++++++++- drivers/scsi/ibmvscsi/ibmvfc.h | 1 + 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 45c7bcf69201..bc975f632da2 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -1539,7 +1539,7 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost) login_info->ostype = cpu_to_be32(IBMVFC_OS_LINUX); login_info->max_dma_len = cpu_to_be64(max_sectors << 9); - login_info->max_payload = cpu_to_be32(sizeof(struct ibmvfc_fcp_cmd_iu)); + login_info->max_payload = cpu_to_be32(sizeof(struct nvme_fc_cmd_iu)); login_info->max_response = cpu_to_be32(sizeof(struct ibmvfc_fcp_rsp)); login_info->partition_num = cpu_to_be32(vhost->partition_number); login_info->vfc_frame_version = cpu_to_be32(1); @@ -1633,6 +1633,7 @@ void ibmvfc_init_event(struct ibmvfc_event *evt, void (*done) (struct ibmvfc_event *), u8 format) { evt->cmnd = NULL; + evt->fcp_req = NULL; evt->ls_req = NULL; evt->sync_iu = NULL; evt->eh_comp = NULL; diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index cf72f0682297..d31e7e9ea7a9 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -8,6 +8,7 @@ * Copyright (C) IBM Corporation, 2026 */ +#include #include #include "ibmvfc-nvme.h" @@ -164,12 +165,149 @@ static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport, { } +static void ibmvfc_nvme_done(struct ibmvfc_event *evt) +{ + struct ibmvfc_cmd *vfc_cmd = &evt->xfer_iu->cmd; + struct nvmefc_fcp_req *fcp_req = evt->fcp_req; + struct nvme_fc_ersp_iu *ersp = (struct nvme_fc_ersp_iu *)fcp_req->rspaddr; + struct nvme_completion *cqe = &ersp->cqe; + struct nvme_command *sqe = &((struct nvme_fc_cmd_iu *)fcp_req->cmdaddr)->sqe; + + ibmvfc_dbg(evt->vhost, "fc_done: (%x:%x)\n", be16_to_cpu(vfc_cmd->status), + be16_to_cpu(vfc_cmd->error)); + ibmvfc_dbg(evt->vhost, "fc_done: cmdlen: %d, rsplen %d, payload_len %d\n", + fcp_req->cmdlen, fcp_req->rsplen, fcp_req->payload_length); + + fcp_req->status = 0; + if (!vfc_cmd->status) { + fcp_req->rcv_rsplen = NVME_FC_SIZEOF_ZEROS_RSP; + fcp_req->transferred_length = fcp_req->payload_length; + } else if (be16_to_cpu(vfc_cmd->status) & IBMVFC_FC_NVME_STATUS) { + fcp_req->rcv_rsplen = sizeof(struct nvme_fc_ersp_iu); + fcp_req->transferred_length = be32_to_cpu(ersp->xfrd_len); + if (be16_to_cpu(vfc_cmd->error) & IBMVFC_NVMS_VALID_NODMA_CQE) + cqe->command_id = sqe->common.command_id; + } else { + fcp_req->rcv_rsplen = 0; + fcp_req->transferred_length = 0; + fcp_req->status = NVME_SC_INTERNAL; + } + + fcp_req->done(fcp_req); + ibmvfc_free_event(evt); +} + +static struct ibmvfc_cmd *ibmvfc_nvme_init_vfc_cmd(struct ibmvfc_event *evt, + struct nvme_fc_remote_port *rport, + struct nvmefc_fcp_req *fcp_req) +{ + struct ibmvfc_target *tgt = rport->private; + struct ibmvfc_cmd *vfc_cmd = &evt->iu.cmd; + + memset(vfc_cmd, 0, sizeof(*vfc_cmd)); + + vfc_cmd->resp.va = cpu_to_be64(fcp_req->rspdma); + vfc_cmd->resp.len = cpu_to_be32(fcp_req->rsplen); + vfc_cmd->frame_type = cpu_to_be32(IBMVFC_NVME_FCP_TYPE); + vfc_cmd->flags |= cpu_to_be16(IBMVFC_NVMEOF_PROTOCOL); + vfc_cmd->payload_len = cpu_to_be32(fcp_req->cmdlen); + vfc_cmd->resp_len = cpu_to_be32(fcp_req->rsplen); + vfc_cmd->cancel_key = cpu_to_be32((u64)evt); + vfc_cmd->target_wwpn = cpu_to_be64(rport->port_name); + vfc_cmd->tgt_scsi_id = cpu_to_be64(rport->port_id); + vfc_cmd->assoc_id = cpu_to_be64(tgt->assoc_id); + + memcpy(&vfc_cmd->v3nvme.iu, fcp_req->cmdaddr, fcp_req->cmdlen); + + return vfc_cmd; +} + +static void ibmvfc_nvme_map_sg_list(struct nvmefc_fcp_req *fcp_req, + struct srp_direct_buf *md) +{ + int i; + struct scatterlist *sg; + + for_each_sg(fcp_req->first_sgl, sg, fcp_req->sg_cnt, i) { + md[i].va = cpu_to_be64(sg_dma_address(sg)); + md[i].len = cpu_to_be32(sg_dma_len(sg)); + md[i].key = 0; + } +} + +static int ibmvfc_nvme_map_sg_data(struct nvmefc_fcp_req *fcp_req, + struct ibmvfc_event *evt, + struct ibmvfc_cmd *vfc_cmd) +{ + struct srp_direct_buf *data = &vfc_cmd->ioba; + struct ibmvfc_host *vhost = evt->vhost; + + if (!fcp_req->sg_cnt) { + vfc_cmd->flags |= cpu_to_be16(IBMVFC_NO_MEM_DESC); + return 0; + } + + if (fcp_req->io_dir == NVMEFC_FCP_WRITE) + vfc_cmd->flags |= cpu_to_be16(IBMVFC_WRITE); + else + vfc_cmd->flags |= cpu_to_be16(IBMVFC_READ); + + if (fcp_req->sg_cnt == 1) { + ibmvfc_nvme_map_sg_list(fcp_req, data); + return 0; + } + + vfc_cmd->flags |= cpu_to_be16(IBMVFC_SCATTERLIST); + + if (!evt->ext_list) { + evt->ext_list = dma_pool_alloc(vhost->sg_pool, GFP_ATOMIC, + &evt->ext_list_token); + + if (!evt->ext_list) + return -ENOMEM; + } + + ibmvfc_nvme_map_sg_list(fcp_req, evt->ext_list); + + data->va = cpu_to_be64(evt->ext_list_token); + data->len = cpu_to_be32(fcp_req->sg_cnt * sizeof(struct srp_direct_buf)); + data->key = 0; + return 0; +} + static int ibmvfc_nvme_fcp_io(struct nvme_fc_local_port *lport, struct nvme_fc_remote_port *rport, void *hw_queue_handle, struct nvmefc_fcp_req *fcp_req) { - return 0; + struct ibmvfc_host *vhost = lport->private; + struct ibmvfc_nvme_qhandle *qhandle = hw_queue_handle; + struct ibmvfc_cmd *vfc_cmd; + struct ibmvfc_event *evt; + int rc; + + ibmvfc_dbg(vhost, "nvme_fcp_io\n"); + evt = ibmvfc_get_event(qhandle->queue); + if (!evt) + return -EBUSY; + + evt->hwq = qhandle->index; + ibmvfc_dbg(vhost, "vfc-nvme-mq-%d\n", evt->hwq); + + ibmvfc_init_event(evt, ibmvfc_nvme_done, IBMVFC_CMD_FORMAT); + evt->fcp_req = fcp_req; + fcp_req->private = evt; + + vfc_cmd = ibmvfc_nvme_init_vfc_cmd(evt, rport, fcp_req); + + vfc_cmd->correlation = cpu_to_be64((u64)evt); + + if (likely(!(rc = ibmvfc_nvme_map_sg_data(fcp_req, evt, vfc_cmd)))) + return ibmvfc_send_event(evt, vhost, 0); + + ibmvfc_free_event(evt); + + return rc; } static void ibmvfc_nvme_fcp_abort(struct nvme_fc_local_port *lport, diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h index 9f6705e604fd..ca80ceffe53a 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.h +++ b/drivers/scsi/ibmvscsi/ibmvfc.h @@ -853,6 +853,7 @@ struct ibmvfc_event { struct ibmvfc_target *tgt; struct scsi_cmnd *cmnd; struct nvmefc_ls_req *ls_req; + struct nvmefc_fcp_req *fcp_req; atomic_t free; atomic_t active; union ibmvfc_iu *xfer_iu; From 20bec08f0208fa6e94ef81ecc44a126cca6ab50f Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:47 -0700 Subject: [PATCH 31/33] scsi: ibmvfc: implement nvme-fc LS abort handling callback Implement the NVMe FC-LS abort callback by issuing an ibmvfc cancel MAD to the VIOS for the outstanding link-service request. Use the saved event pointer from the original FC-LS request to identify the command to cancel, submit the cancel operation, and complete the abort request based on the returned status. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-32-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index d31e7e9ea7a9..390410e44f0d 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -13,6 +13,8 @@ #include "ibmvfc-nvme.h" +static unsigned int default_timeout = IBMVFC_DEFAULT_TIMEOUT; + static void ibmvfc_nvme_localport_delete(struct nvme_fc_local_port *lport) { struct ibmvfc_host *vhost = lport->private; @@ -159,10 +161,72 @@ static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport, return 0; } +static void ibmvfc_sync_nvme_completion(struct ibmvfc_event *evt) +{ + /* copy the response back */ + if (evt->sync_iu) + *evt->sync_iu = *evt->xfer_iu; + + complete(&evt->comp); +} + +static void ibmvfc_init_ls_abort(struct ibmvfc_event *evt, struct nvmefc_ls_req *ls_abort) +{ + struct ibmvfc_tmf *tmf; + struct ibmvfc_event *abt_evt = ls_abort->private; + struct ibmvfc_target *tgt = abt_evt->tgt; + struct ibmvfc_host *vhost = evt->vhost; + + tmf = &evt->iu.tmf; + memset(tmf, 0, sizeof(*tmf)); + tmf->common.version = cpu_to_be32(2); + tmf->target_wwpn = cpu_to_be64(tgt->wwpn); + tmf->common.opcode = cpu_to_be32(IBMVFC_NVMF_TMF_MAD); + tmf->common.length = cpu_to_be16(sizeof(*tmf)); + if (vhost->state != IBMVFC_ACTIVE) + if (!ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPRESS_ABTS)) + tmf->flags = cpu_to_be32(IBMVFC_TMF_SUPPRESS_ABTS); + tmf->cancel_key = cpu_to_be32((u64)abt_evt); + tmf->my_cancel_key = cpu_to_be32((u64)evt); + tmf->assoc_id = cpu_to_be64(tgt->assoc_id); + + init_completion(&evt->comp); +} + static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport, struct nvme_fc_remote_port *rport, struct nvmefc_ls_req *ls_abort) { + struct ibmvfc_host *vhost = lport->private; + struct ibmvfc_target *tgt = rport->private; + struct ibmvfc_event *evt; + union ibmvfc_iu rsp; + unsigned long flags; + u16 status; + + evt = ibmvfc_get_event(&vhost->crq); + if (!vhost->logged_in || !evt) + return; + + spin_lock_irqsave(vhost->host->host_lock, flags); + kref_get(&tgt->kref); + ibmvfc_init_event(evt, ibmvfc_sync_nvme_completion, IBMVFC_MAD_FORMAT); + ibmvfc_init_ls_abort(evt, ls_abort); + evt->sync_iu = &rsp; + + if (ibmvfc_send_event(evt, vhost, default_timeout)) + goto out; + + spin_unlock_irqrestore(vhost->host->host_lock, flags); + + wait_for_completion(&evt->comp); + status = be16_to_cpu(rsp.mad_common.status); + spin_lock_irqsave(vhost->host->host_lock, flags); + ibmvfc_free_event(evt); +out: + spin_unlock_irqrestore(vhost->host->host_lock, flags); + ibmvfc_dbg(vhost, "ls_abort: cancel failed with rc=%x\n", status); + kref_put(&tgt->kref, ibmvfc_release_tgt); } static void ibmvfc_nvme_done(struct ibmvfc_event *evt) From 4e70b8795ee3ac9aa24d55bff6c11674af2e7fcf Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:48 -0700 Subject: [PATCH 32/33] scsi: ibmvfc: implement nvme-fc FCP abort callback Implement the NVMe-FC FCP abort callback by issuing an NVMF cancel MAD on the same submission queue used by the original FCP request. Use the original request event stored in abort_req->private to recover the associated ibmvfc queue, then allocate a new event from that queue so the cancel is sent on the matching nvme_scrq. Factor the TMF setup into a dedicated helper, mirroring the LS abort path, and populate the cancel key, task tag, target WWPN, and association ID needed for the VIOS NVMF abort request. The abort path sends the cancel synchronously, waits for completion, frees the temporary event, and logs non-zero MAD status values for debugging. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-33-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c index 390410e44f0d..54f80b6265d4 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c @@ -374,11 +374,75 @@ static int ibmvfc_nvme_fcp_io(struct nvme_fc_local_port *lport, return rc; } +static void ibmvfc_init_fcp_abort(struct ibmvfc_event *evt, + struct nvmefc_fcp_req *abort_req) +{ + struct ibmvfc_tmf *tmf; + struct ibmvfc_event *abt_evt = abort_req->private; + struct ibmvfc_target *tgt = abt_evt->tgt; + + tmf = &evt->iu.tmf; + memset(tmf, 0, sizeof(*tmf)); + tmf->common.version = cpu_to_be32(2); + tmf->common.opcode = cpu_to_be32(IBMVFC_NVMF_TMF_MAD); + tmf->common.length = cpu_to_be16(sizeof(*tmf)); + tmf->flags = cpu_to_be32(IBMVFC_TMF_ABORT_TASK | IBMVFC_TMF_NVMF_ASSOC); + tmf->cancel_key = cpu_to_be32((u64)abt_evt); + tmf->my_cancel_key = cpu_to_be32((u64)evt); + tmf->target_wwpn = cpu_to_be64(tgt->wwpn); + tmf->assoc_id = cpu_to_be64(tgt->assoc_id); + tmf->task_tag = cpu_to_be64((u64)abt_evt); + + init_completion(&evt->comp); +} + static void ibmvfc_nvme_fcp_abort(struct nvme_fc_local_port *lport, struct nvme_fc_remote_port *rport, void *hw_queue_handle, struct nvmefc_fcp_req *abort_req) { + struct ibmvfc_host *vhost = lport->private; + struct ibmvfc_target *tgt = rport->private; + struct ibmvfc_event *evt, *abt_evt = abort_req->private; + struct ibmvfc_queue *queue; + union ibmvfc_iu rsp; + unsigned long flags; + u16 status = 0; + + if (!abt_evt) + return; + + queue = abt_evt->queue; + if (!vhost->logged_in || !queue) + return; + + evt = ibmvfc_get_event(queue); + if (!evt) + return; + + spin_lock_irqsave(queue->q_lock, flags); + kref_get(&tgt->kref); + ibmvfc_init_event(evt, ibmvfc_sync_nvme_completion, IBMVFC_MAD_FORMAT); + ibmvfc_init_fcp_abort(evt, abort_req); + evt->sync_iu = &rsp; + + if (ibmvfc_send_event(evt, vhost, default_timeout)) + goto out; + + spin_unlock_irqrestore(queue->q_lock, flags); + + wait_for_completion(&evt->comp); + status = be16_to_cpu(rsp.mad_common.status); + + spin_lock_irqsave(queue->q_lock, flags); + ibmvfc_free_event(evt); +out: + spin_unlock_irqrestore(queue->q_lock, flags); + + if (status) + ibmvfc_dbg(vhost, "fcp_abort: cancel failed with rc=%x\n", status); + + kref_put(&tgt->kref, ibmvfc_release_tgt); } static struct nvme_fc_port_template ibmvfc_nvme_fc_transport = { From 4857949b58cdb61f8cd41ca4ffc674b85896ef61 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Wed, 22 Jul 2026 17:01:49 -0700 Subject: [PATCH 33/33] scsi: ibmvfc: fail nvme-fc fcp-io and ls requests during transport reset The current purge code for flushing outstanding commands during a transport reset only deals with SCSI commands. Rename the ibmvfc_scsi_eh_done completion handler to ibmvfc_vfc_eh_done and wire it to correctly complete nvme fcp and ls commands when flushing the inflight command list during a reset. Signed-off-by: Tyrel Datwyler Link: https://patch.msgid.link/20260723000149.969416-34-tyreld@linux.ibm.com Signed-off-by: Martin K. Petersen --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 63 ++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index bc975f632da2..dfa231b6cc47 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -1092,19 +1092,29 @@ void ibmvfc_free_event(struct ibmvfc_event *evt) } /** - * ibmvfc_scsi_eh_done - EH done function for queuecommand commands + * ibmvfc_vfc_eh_done - EH done function for queued IO * @evt: ibmvfc event struct * - * This function does not setup any error status, that must be done - * before this function gets called. + * This function does not setup any error status for scsi commands, that must be + * done before this function gets called. **/ -static void ibmvfc_scsi_eh_done(struct ibmvfc_event *evt) +static void ibmvfc_vfc_eh_done(struct ibmvfc_event *evt) { struct scsi_cmnd *cmnd = evt->cmnd; + struct nvmefc_ls_req *ls_req = evt->ls_req; + struct nvmefc_fcp_req *fcp_req = evt->fcp_req; if (cmnd) { scsi_dma_unmap(cmnd); scsi_done(cmnd); + } else if (fcp_req) { + fcp_req->rcv_rsplen = 0; + fcp_req->transferred_length = 0; + fcp_req->status = NVME_SC_INTERNAL; + fcp_req->done(fcp_req); + } else if (ls_req) { + ls_req->done(ls_req, -EIO); + kref_put(&evt->tgt->kref, ibmvfc_release_tgt); } ibmvfc_free_event(evt); @@ -1146,8 +1156,10 @@ static void ibmvfc_fail_request(struct ibmvfc_event *evt, int error_code) BUG_ON(!atomic_dec_and_test(&evt->active)); if (evt->cmnd) { evt->cmnd->result = (error_code << 16); - evt->done = ibmvfc_scsi_eh_done; - } else + evt->done = ibmvfc_vfc_eh_done; + } else if (evt->fcp_req || evt->ls_req) + evt->done = ibmvfc_vfc_eh_done; + else evt->xfer_iu->mad_common.status = cpu_to_be16(IBMVFC_MAD_DRIVER_FAILED); timer_delete(&evt->timer); @@ -1164,13 +1176,16 @@ static void ibmvfc_fail_request(struct ibmvfc_event *evt, int error_code) static void ibmvfc_purge_requests(struct ibmvfc_host *vhost, int error_code) { struct ibmvfc_event *evt, *pos; - struct ibmvfc_queue *queues = vhost->scsi_scrqs.scrqs; + struct ibmvfc_queue *scsi_q = vhost->scsi_scrqs.scrqs; + struct ibmvfc_queue *nvme_q = vhost->nvme_scrqs.scrqs; unsigned long flags; - int hwqs = 0; + int shwqs, nhwqs = 0; int i; - if (vhost->using_channels) - hwqs = vhost->scsi_scrqs.active_queues; + if (vhost->using_channels) { + shwqs = vhost->scsi_scrqs.active_queues; + nhwqs = vhost->nvme_scrqs.active_queues; + } ibmvfc_dbg(vhost, "Purging all requests\n"); spin_lock_irqsave(&vhost->crq.l_lock, flags); @@ -1179,14 +1194,24 @@ static void ibmvfc_purge_requests(struct ibmvfc_host *vhost, int error_code) list_splice_init(&vhost->crq.sent, &vhost->purge); spin_unlock_irqrestore(&vhost->crq.l_lock, flags); - for (i = 0; i < hwqs; i++) { - spin_lock_irqsave(queues[i].q_lock, flags); - spin_lock(&queues[i].l_lock); - list_for_each_entry_safe(evt, pos, &queues[i].sent, queue_list) + for (i = 0; i < shwqs; i++) { + spin_lock_irqsave(scsi_q[i].q_lock, flags); + spin_lock(&scsi_q[i].l_lock); + list_for_each_entry_safe(evt, pos, &scsi_q[i].sent, queue_list) ibmvfc_fail_request(evt, error_code); - list_splice_init(&queues[i].sent, &vhost->purge); - spin_unlock(&queues[i].l_lock); - spin_unlock_irqrestore(queues[i].q_lock, flags); + list_splice_init(&scsi_q[i].sent, &vhost->purge); + spin_unlock(&scsi_q[i].l_lock); + spin_unlock_irqrestore(scsi_q[i].q_lock, flags); + } + + for (i = 0; i < nhwqs; i++) { + spin_lock_irqsave(nvme_q[i].q_lock, flags); + spin_lock(&nvme_q[i].l_lock); + list_for_each_entry_safe(evt, pos, &nvme_q[i].sent, queue_list) + ibmvfc_fail_request(evt, error_code); + list_splice_init(&nvme_q[i].sent, &vhost->purge); + spin_unlock(&nvme_q[i].l_lock); + spin_unlock_irqrestore(nvme_q[i].q_lock, flags); } } @@ -1818,7 +1843,9 @@ int ibmvfc_send_event(struct ibmvfc_event *evt, dev_err(vhost->dev, "Send error (rc=%d)\n", rc); if (evt->cmnd) { evt->cmnd->result = DID_ERROR << 16; - evt->done = ibmvfc_scsi_eh_done; + evt->done = ibmvfc_vfc_eh_done; + } else if (evt->fcp_req || evt->ls_req) { + evt->done = ibmvfc_vfc_eh_done; } else { evt->xfer_iu->mad_common.status = cpu_to_be16(IBMVFC_MAD_CRQ_ERROR); evt->done = evt->_done;