* [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages
@ 2026-08-31 19:00 Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
` (7 more replies)
0 siblings, 8 replies; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
This patch series adds FPIN (fabric performance impact notification)
support to the ibmvfc (IBM Virtual Fibre Channel) driver. This comes
in three flavors:
- basic, to recognize existing FPIN messages from the virtual I/O
server (VIOS) (patch 1)
- full, supporting additional FPIN information and using its own
asynchronous sub-queue and interrupt (patches 3-7)
- extended, supporting FC-LS-5 (patch 8)
Full and extended FPIN support requires a new asynchronous sub-queue
with its own interrupt. The asynchronous sub-queue support requires
ibmvfc to also support a new IBMVFC_NOOP command, which the driver
recognizes and ignores (patch 2).
The asynchronous sub-queue work is split across three patches: patch 3
defines the necessary data structures, patch 5 adds the interrupt and
drain routines for the queue, and patch 6 refactors channel
registration and deregistration to accommodate the new queue. Patch 7
wires everything together, registering the async sub-queue during
channel initialization and advertising support to VIOS during NPIV
login.
All three modes convert an incoming FPIN message from VIOS to an FC
extended link service message, in some cases using default values for
information not provided by the VIOS FPIN message but expected in the
FC ELS message. This FC ELS message is passed to fc_host_rcv_fpin for
updating statistics and sending the information upstream by netlink
multicast, where it may be read by listeners including the DM
multipath daemon "multipathd."
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
Changes in v7:
- Removed CRQ valid bit clearing to callers of ibmvfc_handle_async in
all cases
- Added validity checks that IBMVFC_ASYNC_ID_IS_ASSOC_ID is clear
before using node_name
- Fixed error path cleanup in ibmvfc_init_sub_crqs
- Deregister and reregister async sub-CRQ in CRQ reset and reenable
code.
- Moved work queue destruction after sub CRQs teardown in ibmvfc_remove
- Modified async event handling code to use a tagged union rather than
void *.
- Use WQ_UNBOUND rather than WQ_PERCPU for fpin workqueue, as FPIN
events should be rare. Hold q_lock only in
ibmvfc_drain_async_subq(), like ibmvfc_drain_sub_crq().
- Hardened KUnit tests to use host_lock and kref.
- Link to v6: https://patch.msgid.link/20260817-ibmvfc-fpin-support-v6-0-62a2d525e70d@linux.ibm.com
Changes in v6:
- Use alloc_workqueue rather than devm_alloc_workqueue, and add
explicit destroy_workqueue calls
- Moved ibmvfc_find_target into patch 1
- Link to v5: https://patch.msgid.link/20260806-ibmvfc-fpin-support-v5-0-13fd6f8a8f8b@linux.ibm.com
Changes in v5:
- Incorporate changes for NVME-over-FC.
- Removed now redundant fabric login patch.
- Link to v4: https://patch.msgid.link/20260710-ibmvfc-fpin-support-v4-0-ef031ac19520@linux.ibm.com
Changes in v4:
- Refactored channel registration
- Check whether async work queue is allocated before using or freeing
- Fixed work queue allocation/destruction
- Skip basic KUnit test when there are no ibmvfc devices available
- Fix target not found condition in ibmvfc_process_async_work
- Link to v3: https://patch.msgid.link/20260702-ibmvfc-fpin-support-v3-0-d95b9747cf88@linux.ibm.com
Changes in v3:
- Fixed latent bug, exposed by VFC_NOOP, related to dataless CRQs and events
- Fixed FPIN TLV descriptor length calculations
- Use safe list walker to walk targets in ibmvfc_process_async_work
- Added write memory barriers after clearing CRQ valid field
- Use per-vhost work queue for FPIN work
- Link to v2: https://patch.msgid.link/20260608-ibmvfc-fpin-support-v2-0-d41f540fba5c@linux.ibm.com
Highlights of changes in v2:
- Refactored mostly common FPIN conversion routines and async event
processing into single routines with wrappers for differences.
- Moved FPIN processing to a work queue to avoid conflicts with
fc_host_fpin_rcv and memory allocation
- Set descriptor sizes correctly
- Use target WWPN for basic FPIN descriptor
- Split patch 4 into 3 patches, for definition, allocation, and use of
the asynchronous sub-queue for events
- Link to v1: https://patch.msgid.link/20260408-ibmvfc-fpin-support-v1-0-52b06c464e03@linux.ibm.com
---
Dave Marquardt (8):
scsi: ibmvfc: add basic FPIN support
scsi: ibmvfc: Add NOOP command support
scsi: ibmvfc: add FPIN extended flag and async sub-CRQ queue handle
scsi: ibmvfc: extend async event handlers to handle async sub queue events
scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ
scsi: ibmvfc: extend channel registration and deregistration for async subq
scsi: ibmvfc: register and use asynchronous sub CRQ for events
scsi: ibmvfc: handle extended FPIN events
drivers/scsi/Kconfig | 10 +
drivers/scsi/ibmvscsi/Makefile | 1 +
drivers/scsi/ibmvscsi/ibmvfc-core.c | 690 ++++++++++++++++++++++++++++++++---
drivers/scsi/ibmvscsi/ibmvfc.h | 63 ++++
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 398 ++++++++++++++++++++
5 files changed, 1105 insertions(+), 57 deletions(-)
---
base-commit: 12e67eb89eb2b9516685c744d3f7de0a2d1bd701
change-id: 20260407-ibmvfc-fpin-support-b9b575cd2da1
Best regards,
--
Dave Marquardt <davemarq@linux.ibm.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 21:30 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
` (6 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Implement support for a basic level of Fabric Performance Impact
Notifications (FPIN) in the ibmvfc driver to enable monitoring of
fabric congestion and link integrity events.
Add async event handler for IBMVFC_AE_FPIN events that offloads FPIN
processing to a dedicated workqueue. Convert VIOS FPIN messages to
standard fc_els_fpin structures and pass them to fc_host_fpin_rcv() for
processing by the FC transport layer.
Introduce common FPIN conversion routines that will be reused for full
and extended FPIN support in subsequent patches. Add KUnit test
infrastructure to validate FPIN event handling and statistics updates.
Changes include:
- Add FPIN async event handling in ibmvfc_handle_async()
- Create dedicated workqueue for FPIN processing
- Implement FPIN message conversion to fc_els_fpin format
- Add support for link congestion, port congestion, port cleared, port
degraded, and congestion cleared events
- Add KUnit test module for FPIN functionality
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/Kconfig | 10 ++
drivers/scsi/ibmvscsi/Makefile | 1 +
drivers/scsi/ibmvscsi/ibmvfc-core.c | 285 ++++++++++++++++++++++++++++++++++-
drivers/scsi/ibmvscsi/ibmvfc.h | 17 +++
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 146 ++++++++++++++++++
5 files changed, 456 insertions(+), 3 deletions(-)
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 4a2af0f702e1..352abe10da58 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -759,6 +759,16 @@ config SCSI_IBMVFC
To compile this driver as a module, choose M here: the
module will be called ibmvfc.
+config SCSI_IBMVFC_KUNIT_TEST
+ tristate "KUnit tests for the IBM POWER Virtual FC Client" if !KUNIT_ALL_TESTS
+ depends on SCSI_IBMVFC && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Compile IBM POWER Virtual FC client KUnit tests. These tests
+ specifically test FPIN functionality. To compile this driver
+ as a module, choose M here: the module will be called
+ ibmvfc_kunit.
+
config SCSI_IBMVFC_TRACE
bool "enable driver internal trace"
depends on SCSI_IBMVFC
diff --git a/drivers/scsi/ibmvscsi/Makefile b/drivers/scsi/ibmvscsi/Makefile
index 9408c7f4cdee..a227bc633f3a 100644
--- a/drivers/scsi/ibmvscsi/Makefile
+++ b/drivers/scsi/ibmvscsi/Makefile
@@ -3,3 +3,4 @@ ibmvfc-objs := ibmvfc-core.o ibmvfc-nvme.o
obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi.o
obj-$(CONFIG_SCSI_IBMVFC) += ibmvfc.o
+obj-$(CONFIG_SCSI_IBMVFC_KUNIT_TEST) += ibmvfc_kunit.o
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index b3bc3ce872d6..78be00d31a06 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -31,6 +31,9 @@
#include <scsi/scsi_tcq.h>
#include <scsi/scsi_transport_fc.h>
#include <scsi/scsi_bsg_fc.h>
+#include <kunit/visibility.h>
+#include <scsi/fc/fc_els.h>
+#include <linux/overflow.h>
#include "ibmvfc.h"
static unsigned int init_timeout = IBMVFC_INIT_TIMEOUT;
@@ -1433,6 +1436,7 @@ 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);
}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_release_tgt);
/**
* ibmvfc_get_starget_node_name - Get SCSI target's node name
@@ -3213,6 +3217,7 @@ static const struct ibmvfc_async_desc ae_desc [] = {
{ "Halt", IBMVFC_AE_HALT, IBMVFC_DEFAULT_LOG_LEVEL },
{ "Resume", IBMVFC_AE_RESUME, IBMVFC_DEFAULT_LOG_LEVEL },
{ "Adapter Failed", IBMVFC_AE_ADAPTER_FAILED, IBMVFC_DEFAULT_LOG_LEVEL },
+ { "FPIN", IBMVFC_AE_FPIN, IBMVFC_DEFAULT_LOG_LEVEL },
};
static const struct ibmvfc_async_desc unknown_ae = {
@@ -3261,16 +3266,259 @@ static const char *ibmvfc_get_link_state(enum ibmvfc_ae_link_state state)
return "";
}
+#define IBMVFC_FPIN_CONGN_DESC_SZ (sizeof(struct fc_els_fpin) + sizeof(struct fc_fn_congn_desc))
+#define IBMVFC_FPIN_LI_DESC_SZ (sizeof(struct fc_els_fpin) + \
+ struct_size_t(struct fc_fn_li_desc, pname_list, 1))
+#define IBMVFC_FPIN_PEER_CONGN_DESC_SZ (sizeof(struct fc_els_fpin) + \
+ struct_size_t(struct fc_fn_peer_congn_desc, pname_list, 1))
+
+/**
+ * ibmvfc_fpin_size_helper(): compute fpin structure size based on fpin status
+ * @fpin_status: status value
+ *
+ * Return:
+ * 0: invalid fpin_status
+ * other: valid size
+ */
+static size_t ibmvfc_fpin_size_helper(u8 fpin_status)
+{
+ size_t size = 0;
+
+ switch (fpin_status) {
+ case IBMVFC_AE_FPIN_LINK_CONGESTED:
+ case IBMVFC_AE_FPIN_CONGESTION_CLEARED:
+ size = IBMVFC_FPIN_CONGN_DESC_SZ;
+ break;
+ case IBMVFC_AE_FPIN_PORT_CONGESTED:
+ case IBMVFC_AE_FPIN_PORT_CLEARED:
+ size = IBMVFC_FPIN_PEER_CONGN_DESC_SZ;
+ break;
+ case IBMVFC_AE_FPIN_PORT_DEGRADED:
+ size = IBMVFC_FPIN_LI_DESC_SZ;
+ break;
+ default:
+ break;
+ }
+
+ return size;
+}
+
+/**
+ * ibmvfc_common_fpin_to_desc(): allocate and populate a struct fc_els_fpin struct
+ * containing a descriptor.
+ *
+ * Allocate a struct fc_els_fpin containing a descriptor and populate
+ * based on data from *ibmvfc_fpin.
+ *
+ * Return:
+ * NULL - unable to allocate structure
+ * non-NULL - pointer to populated struct fc_els_fpin
+ */
+static struct fc_els_fpin *
+ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 type, __be16 modifier,
+ __be32 threshold, __be32 event_count)
+{
+ struct fc_fn_peer_congn_desc *pdesc;
+ struct fc_fn_congn_desc *cdesc;
+ struct fc_fn_li_desc *ldesc;
+ struct fc_els_fpin *fpin;
+ size_t size;
+
+ size = ibmvfc_fpin_size_helper(fpin_status);
+ if (!size)
+ return NULL;
+
+ fpin = kzalloc(size, GFP_KERNEL);
+ if (!fpin)
+ return NULL;
+
+ fpin->fpin_cmd = ELS_FPIN;
+
+ switch (fpin_status) {
+ case IBMVFC_AE_FPIN_CONGESTION_CLEARED:
+ case IBMVFC_AE_FPIN_LINK_CONGESTED:
+ fpin->desc_len = cpu_to_be32(sizeof(struct fc_fn_congn_desc));
+ cdesc = (struct fc_fn_congn_desc *)fpin->fpin_desc;
+ cdesc->desc_tag = cpu_to_be32(ELS_DTAG_CONGESTION);
+ cdesc->desc_len = cpu_to_be32(FC_TLV_DESC_LENGTH_FROM_SZ(*cdesc));
+ cdesc->event_type = type;
+ cdesc->event_modifier = modifier;
+ cdesc->event_period = cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD);
+ cdesc->severity = FPIN_CONGN_SEVERITY_WARNING;
+ break;
+ case IBMVFC_AE_FPIN_PORT_CONGESTED:
+ case IBMVFC_AE_FPIN_PORT_CLEARED:
+ fpin->desc_len =
+ cpu_to_be32(struct_size_t(struct fc_fn_peer_congn_desc, pname_list, 1));
+ pdesc = (struct fc_fn_peer_congn_desc *)fpin->fpin_desc;
+ pdesc->desc_tag = cpu_to_be32(ELS_DTAG_PEER_CONGEST);
+ pdesc->desc_len = cpu_to_be32(struct_size_t(struct fc_fn_peer_congn_desc,
+ pname_list, 1) - FC_TLV_DESC_HDR_SZ);
+ pdesc->event_type = type;
+ pdesc->event_modifier = modifier;
+ pdesc->event_period = cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD);
+ pdesc->attached_wwpn = wwpn;
+ pdesc->pname_count = cpu_to_be32(1);
+ pdesc->pname_list[0] = wwpn;
+ break;
+ case IBMVFC_AE_FPIN_PORT_DEGRADED:
+ fpin->desc_len = cpu_to_be32(struct_size_t(struct fc_fn_li_desc, pname_list, 1));
+ ldesc = (struct fc_fn_li_desc *)fpin->fpin_desc;
+ ldesc->desc_tag = cpu_to_be32(ELS_DTAG_LNK_INTEGRITY);
+ ldesc->desc_len = cpu_to_be32(struct_size_t(struct fc_fn_li_desc,
+ pname_list, 1) - FC_TLV_DESC_HDR_SZ);
+ ldesc->event_type = type;
+ ldesc->event_modifier = modifier;
+ ldesc->event_threshold = threshold;
+ ldesc->event_count = event_count;
+ ldesc->attached_wwpn = wwpn;
+ ldesc->pname_count = cpu_to_be32(1);
+ ldesc->pname_list[0] = wwpn;
+ break;
+ default:
+ /* This should be caught above. */
+ kfree(fpin);
+ fpin = NULL;
+ break;
+ }
+
+ return fpin;
+}
+
+/**
+ * ibmvfc_basic_fpin_to_desc(): allocate and populate a struct fc_els_fpin struct
+ * containing a descriptor.
+ * @ibmvfc_fpin: Pointer to async crq
+ *
+ * Allocate a struct fc_els_fpin containing a descriptor and populate
+ * based on data from *ibmvfc_fpin.
+ *
+ * Return:
+ * NULL - unable to allocate structure
+ * non-NULL - pointer to populated struct fc_els_fpin
+ */
+static struct fc_els_fpin *
+ibmvfc_basic_fpin_to_desc(struct ibmvfc_async_crq *crq, u64 wwpn)
+{
+ __be16 type;
+
+ switch (crq->fpin_status) {
+ case IBMVFC_AE_FPIN_LINK_CONGESTED:
+ case IBMVFC_AE_FPIN_PORT_CONGESTED:
+ type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ break;
+ case IBMVFC_AE_FPIN_PORT_CLEARED:
+ case IBMVFC_AE_FPIN_CONGESTION_CLEARED:
+ type = cpu_to_be16(FPIN_CONGN_CLEAR);
+ break;
+ case IBMVFC_AE_FPIN_PORT_DEGRADED:
+ type = cpu_to_be16(FPIN_LI_UNKNOWN);
+ break;
+ default:
+ return NULL;
+ }
+
+ return ibmvfc_common_fpin_to_desc(crq->fpin_status, cpu_to_be64(wwpn),
+ type, cpu_to_be16(0),
+ cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
+ cpu_to_be32(1));
+}
+
+/**
+ * ibmvfc_find_target - Search for a target in a target list
+ * @target_list: list head of targets to search
+ * @scsi_id: SCSI ID to match (0 to skip this check)
+ * @wwpn: WWPN to match (0 to skip this check)
+ * @node_name: Node name to match (0 to skip this check)
+ *
+ * Returns:
+ * Pointer to matching target, or NULL if not found
+ **/
+static struct ibmvfc_target *ibmvfc_find_target(struct list_head *target_list,
+ __be64 scsi_id, __be64 wwpn,
+ __be64 node_name)
+{
+ struct ibmvfc_target *tgt;
+
+ list_for_each_entry(tgt, target_list, queue) {
+ if (scsi_id && cpu_to_be64(tgt->scsi_id) != scsi_id)
+ continue;
+ if (wwpn && cpu_to_be64(tgt->ids.port_name) != wwpn)
+ continue;
+ if (node_name && cpu_to_be64(tgt->ids.node_name) != node_name)
+ continue;
+ if (!tgt->rport)
+ continue;
+ return tgt;
+ }
+
+ return NULL;
+}
+
+/**
+ * ibmvfc_process_async_work - Process IBMVFC_AE_FPIN async CRQ from work queue
+ * @work: pointer to work_struct
+ */
+static void ibmvfc_process_async_work(struct work_struct *work)
+{
+ struct ibmvfc_async_work *aw;
+ struct ibmvfc_async_crq *crq;
+ struct ibmvfc_target *tgt;
+ struct ibmvfc_host *vhost;
+ struct fc_els_fpin *fpin;
+ unsigned long flags;
+
+ aw = container_of_const(work, struct ibmvfc_async_work, async_work_s);
+ vhost = aw->vhost;
+ crq = &aw->crq;
+
+ if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ goto free;
+
+ spin_lock_irqsave(vhost->host->host_lock, flags);
+ tgt = ibmvfc_find_target(&vhost->scsi_scrqs.targets, crq->scsi_id,
+ crq->wwpn, crq->node_name);
+ if (!tgt) {
+ /* Target not found in scsi_scrqs, search nvme_scrqs */
+ tgt = ibmvfc_find_target(&vhost->nvme_scrqs.targets,
+ crq->scsi_id, crq->wwpn,
+ crq->node_name);
+ }
+
+ if (tgt) {
+ kref_get(&tgt->kref);
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ } else {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ dev_err_ratelimited(vhost->dev, "Invalid target for FPIN\n");
+ goto free;
+ }
+
+ fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
+ if (fpin) {
+ fc_host_fpin_rcv(tgt->vhost->host,
+ sizeof(*fpin) + be32_to_cpu(fpin->desc_len),
+ (char *)fpin, 0);
+ kfree(fpin);
+ } else
+ dev_err_ratelimited(vhost->dev, "FPIN event received, unable to process\n");
+
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+ free:
+ kfree(aw);
+}
+
/**
* ibmvfc_handle_async - Handle an async event from the adapter
* @crq: crq to process
* @vhost: ibmvfc host struct
*
**/
-static void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
- struct ibmvfc_host *vhost)
+VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
+ struct ibmvfc_host *vhost)
{
const struct ibmvfc_async_desc *desc = ibmvfc_get_ae_desc(be64_to_cpu(crq->event));
+ struct ibmvfc_async_work *aw;
struct ibmvfc_target *tgt;
ibmvfc_log(vhost, desc->log_level, "%s event received. scsi_id: %llx, wwpn: %llx,"
@@ -3361,11 +3609,23 @@ static void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
case IBMVFC_AE_HALT:
ibmvfc_link_down(vhost, IBMVFC_HALTED);
break;
+ case IBMVFC_AE_FPIN:
+ aw = kzalloc(sizeof(struct ibmvfc_async_work), GFP_ATOMIC);
+ if (aw) {
+ INIT_WORK(&aw->async_work_s, ibmvfc_process_async_work);
+ aw->vhost = vhost;
+ aw->crq = *crq;
+ queue_work(vhost->fpin_workq, &aw->async_work_s);
+ } else
+ dev_err_ratelimited(vhost->dev,
+ "can't offload async CRQ to work queue\n");
+ break;
default:
dev_err(vhost->dev, "Unknown async event received: %lld\n", crq->event);
break;
}
}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_handle_async);
/**
* ibmvfc_handle_crq - Handles and frees received events in the CRQ
@@ -6874,9 +7134,15 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id)
INIT_WORK(&vhost->rport_add_work_q, ibmvfc_rport_add_thread);
mutex_init(&vhost->passthru_mutex);
- if ((rc = ibmvfc_alloc_mem(vhost)))
+ vhost->fpin_workq = alloc_workqueue("%s-fpin-workq-%u", WQ_UNBOUND, 0,
+ IBMVFC_NAME, shost->host_no);
+ if (vhost->fpin_workq == NULL)
goto free_scsi_host;
+ rc = ibmvfc_alloc_mem(vhost);
+ if (rc)
+ goto free_workq;
+
vhost->work_thread = kthread_run(ibmvfc_work, vhost, "%s_%d", IBMVFC_NAME,
shost->host_no);
@@ -6922,6 +7188,9 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id)
kthread_stop(vhost->work_thread);
free_host_mem:
ibmvfc_free_mem(vhost);
+free_workq:
+ destroy_workqueue(vhost->fpin_workq);
+ vhost->fpin_workq = NULL;
free_scsi_host:
scsi_host_put(shost);
out:
@@ -6962,6 +7231,8 @@ static void ibmvfc_remove(struct vio_dev *vdev)
ibmvfc_complete_purge(&purge);
ibmvfc_release_sub_crqs(vhost);
ibmvfc_release_crq_queue(vhost);
+ destroy_workqueue(vhost->fpin_workq);
+ vhost->fpin_workq = NULL;
ibmvfc_free_mem(vhost);
spin_lock(&ibmvfc_driver_lock);
@@ -7114,5 +7385,13 @@ static void __exit ibmvfc_module_exit(void)
fc_release_transport(ibmvfc_transport_template);
}
+#if IS_ENABLED(CONFIG_KUNIT)
+VISIBLE_IF_KUNIT struct list_head *ibmvfc_get_headp(void)
+{
+ return &ibmvfc_head;
+}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_get_headp);
+#endif
+
module_init(ibmvfc_module_init);
module_exit(ibmvfc_module_exit);
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index ca80ceffe53a..646aaa01177c 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -750,8 +750,12 @@ enum ibmvfc_ae_fpin_status {
IBMVFC_AE_FPIN_PORT_CONGESTED = 0x2,
IBMVFC_AE_FPIN_PORT_CLEARED = 0x3,
IBMVFC_AE_FPIN_PORT_DEGRADED = 0x4,
+ IBMVFC_AE_FPIN_CONGESTION_CLEARED = 0x5,
};
+#define IBMVFC_FPIN_DEFAULT_EVENT_PERIOD (5*60*MSEC_PER_SEC) /* 5 minutes */
+#define IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD (5*60*MSEC_PER_SEC/2) /* 2.5 minutes */
+
struct ibmvfc_async_crq {
volatile u8 valid;
u8 link_state;
@@ -781,6 +785,12 @@ struct ibmvfc_async_sub_crq {
} id;
} __packed __aligned(8);
+struct ibmvfc_async_work {
+ struct ibmvfc_host *vhost;
+ struct ibmvfc_async_crq crq;
+ struct work_struct async_work_s;
+};
+
union ibmvfc_iu {
struct ibmvfc_mad_common mad_common;
struct ibmvfc_npiv_login_mad npiv_login;
@@ -1022,6 +1032,7 @@ struct ibmvfc_host {
wait_queue_head_t work_wait_q;
struct nvme_fc_local_port *nvme_local_port;
struct completion nvme_delete_done;
+ struct workqueue_struct *fpin_workq;
};
struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int reserved);
@@ -1088,4 +1099,10 @@ static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channel
#define ibmvfc_remove_trace_file(kobj, attr) do { } while (0)
#endif
+#if IS_ENABLED(CONFIG_KUNIT)
+#include <kunit/visibility.h>
+VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq, struct ibmvfc_host *vhost);
+VISIBLE_IF_KUNIT struct list_head *ibmvfc_get_headp(void);
+#endif
+
#endif
diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
new file mode 100644
index 000000000000..8a73ea1b1da8
--- /dev/null
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <kunit/test.h>
+#include <kunit/visibility.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_transport_fc.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include "ibmvfc.h"
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
+/**
+ * ibmvfc_async_fpin_event_test - unit test for IBMVFC_AE_FPIN parts of
+ * ibmvfc_handle_async
+ * @test: pointer to kunit structure
+ *
+ * Tests
+ * - error returns from ibmvfc_handle_async
+ * - statistics updates
+ *
+ * Return: void
+ */
+static void ibmvfc_async_fpin_test(struct kunit *test)
+{
+ u64 post[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ u64 pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ enum ibmvfc_ae_fpin_status fs;
+ struct fc_host_attrs *fc_host;
+ struct ibmvfc_async_crq crq[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ struct ibmvfc_target *tgt;
+ struct ibmvfc_host *vhost;
+ struct list_head *queue;
+ struct list_head *headp;
+ unsigned long flags;
+
+ headp = ibmvfc_get_headp();
+ if (list_empty(headp))
+ kunit_skip(test, "No ibmvfc devices available");
+ queue = headp->next;
+ vhost = container_of_const(queue, struct ibmvfc_host, queue);
+
+ spin_lock_irqsave(vhost->host->host_lock, flags);
+ if (vhost->scsi_scrqs.num_targets < 1) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No targets");
+ }
+ tgt = list_first_entry(&vhost->scsi_scrqs.targets, struct ibmvfc_target, queue);
+ if (!tgt->rport) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No rport");
+ }
+ kref_get(&tgt->kref);
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+
+ fc_host = shost_to_fc_host(vhost->host);
+
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ for (fs = IBMVFC_AE_FPIN_LINK_CONGESTED; fs <= IBMVFC_AE_FPIN_CONGESTION_CLEARED; fs++) {
+ crq[fs].valid = 0x80;
+ crq[fs].link_state = IBMVFC_AE_LS_LINK_UP;
+ crq[fs].fpin_status = fs;
+ crq[fs].event = cpu_to_be64(IBMVFC_AE_FPIN);
+ crq[fs].scsi_id = cpu_to_be64(tgt->scsi_id);
+ crq[fs].wwpn = cpu_to_be64(tgt->wwpn);
+ crq[fs].node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&crq[fs], vhost);
+ crq[fs].valid = 0;
+ wmb();
+ }
+ flush_workqueue(vhost->fpin_workq);
+
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_LINK_CONGESTED],
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_CONGESTED],
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_CLEARED],
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_DEGRADED],
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_CONGESTION_CLEARED],
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED]+1);
+
+ /* bad path */
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ crq[0].valid = 0x80;
+ crq[0].link_state = IBMVFC_AE_LS_LINK_UP;
+ crq[0].fpin_status = 0; /* bad value */
+ crq[0].event = cpu_to_be64(IBMVFC_AE_FPIN);
+ crq[0].scsi_id = cpu_to_be64(tgt->scsi_id);
+ crq[0].wwpn = cpu_to_be64(tgt->wwpn);
+ crq[0].node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&crq[0], vhost);
+ crq[0].valid = 0;
+ wmb();
+ flush_workqueue(vhost->fpin_workq);
+
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_LINK_CONGESTED],
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_CONGESTED],
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_CLEARED],
+ post[IBMVFC_AE_FPIN_PORT_CLEARED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_DEGRADED],
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED],
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED]);
+
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+}
+
+static struct kunit_case ibmvfc_fpin_test_cases[] = {
+ KUNIT_CASE(ibmvfc_async_fpin_test),
+ {},
+};
+
+static struct kunit_suite ibmvfc_fpin_test_suite = {
+ .name = "ibmvfc-fpin-test",
+ .test_cases = ibmvfc_fpin_test_cases,
+};
+kunit_test_init_section_suite(ibmvfc_fpin_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Marquardt <davemarq@linux.ibm.com>");
+MODULE_DESCRIPTION("Test module for IBM Virtual Fibre Channel Driver");
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 21:40 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 3/8] scsi: ibmvfc: add FPIN extended flag and async sub-CRQ queue handle Dave Marquardt via B4 Relay
` (5 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Add support for VFC_NOOP messages from VIOS to enable keep-alive
functionality between the client and server.
Define the VFC_NOOP CRQ format and add handling in both the main CRQ
handler (ibmvfc_handle_crq) and sub-CRQ handler (ibmvfc_handle_scrq).
Log unexpected NOOP messages if received before VIOS advertises support
during NPIV login. Also fix a typo in ibmvfc_handle_scrq.
Set the IBMVFC_CAN_USE_NOOP_CMD capability bit during NPIV login to
inform VIOS that the client can handle NOOP commands.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index 78be00d31a06..e972e4ec5be0 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -1578,7 +1578,9 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
login_info->flags |= cpu_to_be16(IBMVFC_CLIENT_MIGRATED);
login_info->max_cmds = cpu_to_be32(max_cmds);
- login_info->capabilities = cpu_to_be64(IBMVFC_CAN_MIGRATE | IBMVFC_CAN_SEND_VF_WWPN);
+ login_info->capabilities =
+ cpu_to_be64(IBMVFC_CAN_MIGRATE | IBMVFC_CAN_SEND_VF_WWPN |
+ IBMVFC_CAN_USE_NOOP_CMD);
if (vhost->mq_enabled || vhost->using_channels) {
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_CHANNELS);
@@ -3693,6 +3695,14 @@ static void ibmvfc_handle_crq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost,
if (crq->format == IBMVFC_ASYNC_EVENT)
return;
+ if (crq->format == IBMVFC_NOOP) {
+ if (vhost->state == IBMVFC_ACTIVE &&
+ !ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NOOP_CMD))
+ dev_err_ratelimited(vhost->dev,
+ "Received unexpected NOOP command from partner\n");
+ return;
+ }
+
/* The only kind of payload CRQs we should get are responses to
* things we send. Make sure this response is to something we
* actually sent
@@ -4223,7 +4233,15 @@ static void ibmvfc_handle_scrq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost
case IBMVFC_CRQ_XPORT_EVENT:
return;
default:
- dev_err(vhost->dev, "Got and invalid message type 0x%02x\n", crq->valid);
+ dev_err(vhost->dev, "Got an invalid message type 0x%02x\n", crq->valid);
+ return;
+ }
+
+ if (crq->format == IBMVFC_NOOP)
+ return;
+
+ if (unlikely(!evt)) {
+ dev_err(vhost->dev, "Received null event\n");
return;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 3/8] scsi: ibmvfc: add FPIN extended flag and async sub-CRQ queue handle
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 4/8] scsi: ibmvfc: extend async event handlers to handle async sub queue events Dave Marquardt via B4 Relay
` (4 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Add IBMVFC_ASYNC_IS_FPIN_EXT flag bit (0x02) to ibmvfc_async_sub_crq.flags
to distinguish extended FPIN events from standard ones.
Add async_sub_crq field of type ibmvfc_queue to ibmvfc_host to hold the
queue handle for the asynchronous sub-CRQ, alongside the existing crq and
async_crq queues.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index 646aaa01177c..3848fa936e38 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -773,6 +773,7 @@ struct ibmvfc_async_sub_crq {
volatile u8 valid;
u8 flags;
#define IBMVFC_ASYNC_ID_IS_ASSOC_ID 0x01
+#define IBMVFC_ASYNC_IS_FPIN_EXT 0x02
u8 link_state;
u8 fpin_status;
__be16 event;
@@ -990,6 +991,7 @@ struct ibmvfc_host {
mempool_t *tgt_pool;
struct ibmvfc_queue crq;
struct ibmvfc_queue async_crq;
+ struct ibmvfc_queue async_sub_crq;
struct ibmvfc_channels scsi_scrqs;
struct ibmvfc_channels nvme_scrqs;
struct ibmvfc_npiv_login login_info;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 4/8] scsi: ibmvfc: extend async event handlers to handle async sub queue events
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (2 preceding siblings ...)
2026-08-31 19:00 ` [PATCH v7 3/8] scsi: ibmvfc: add FPIN extended flag and async sub-CRQ queue handle Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ Dave Marquardt via B4 Relay
` (3 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Refactor async event handling to support both traditional async CRQs and
new asynchronous sub-queue CRQs.
Introduce struct ibmvfc_async_crq_event, a tagged union that wraps
either an ibmvfc_async_crq (main CRQ) or an ibmvfc_async_sub_crq
(async sub-CRQ), with an enum ibmvfc_async_crq_type discriminator.
Replace the ibmvfc_async_work bare union and is_subq bool with a
single event field of this type.
Modify ibmvfc_handle_async() to accept a struct ibmvfc_async_crq_event *
instead of a void * plus a bool flag. Update ibmvfc_process_async_work()
to dispatch based on event.type.
Add ibmvfc_full_fpin_to_desc() to convert full FPIN messages from async
sub-queue format to fc_els_fpin structures. Update FPIN processing logic
to extract WWPN, node_name, and scsi_id from the appropriate union member
based on event type.
Update KUnit tests to use struct ibmvfc_async_crq_event arrays at call
sites.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 162 +++++++++++++++++++++++++++--------
drivers/scsi/ibmvscsi/ibmvfc.h | 18 +++-
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 162 +++++++++++++++++++++++++++++++----
3 files changed, 284 insertions(+), 58 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index e972e4ec5be0..229b06effd4c 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -3426,6 +3426,45 @@ ibmvfc_basic_fpin_to_desc(struct ibmvfc_async_crq *crq, u64 wwpn)
cpu_to_be32(1));
}
+/**
+ * ibmvfc_full_fpin_to_desc(): allocate and populate a struct fc_els_fpin struct
+ * containing a descriptor.
+ * @ibmvfc_fpin: Pointer to async subq FPIN data
+ *
+ * Allocate a struct fc_els_fpin containing a descriptor and populate
+ * based on data from *ibmvfc_fpin.
+ *
+ * Return:
+ * NULL - unable to allocate structure
+ * non-NULL - pointer to populated struct fc_els_fpin
+ */
+static struct fc_els_fpin *
+ibmvfc_full_fpin_to_desc(struct ibmvfc_async_sub_crq *ibmvfc_fpin)
+{
+ __be16 type;
+
+ switch (ibmvfc_fpin->fpin_status) {
+ case IBMVFC_AE_FPIN_LINK_CONGESTED:
+ case IBMVFC_AE_FPIN_PORT_CONGESTED:
+ type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ break;
+ case IBMVFC_AE_FPIN_PORT_CLEARED:
+ case IBMVFC_AE_FPIN_CONGESTION_CLEARED:
+ type = cpu_to_be16(FPIN_CONGN_CLEAR);
+ break;
+ case IBMVFC_AE_FPIN_PORT_DEGRADED:
+ type = cpu_to_be16(FPIN_LI_UNKNOWN);
+ break;
+ default:
+ return NULL;
+ }
+
+ return ibmvfc_common_fpin_to_desc(ibmvfc_fpin->fpin_status, ibmvfc_fpin->wwpn,
+ type, cpu_to_be16(0),
+ cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
+ cpu_to_be32(1));
+}
+
/**
* ibmvfc_find_target - Search for a target in a target list
* @target_list: list head of targets to search
@@ -3463,28 +3502,39 @@ static struct ibmvfc_target *ibmvfc_find_target(struct list_head *target_list,
*/
static void ibmvfc_process_async_work(struct work_struct *work)
{
+ struct ibmvfc_async_sub_crq *subq = NULL;
struct ibmvfc_async_work *aw;
- struct ibmvfc_async_crq *crq;
+ struct ibmvfc_async_crq *crq = NULL;
struct ibmvfc_target *tgt;
struct ibmvfc_host *vhost;
- struct fc_els_fpin *fpin;
+ struct fc_els_fpin *fpin = NULL;
unsigned long flags;
+ __be64 node_name;
+ __be64 scsi_id;
+ __be64 wwpn;
aw = container_of_const(work, struct ibmvfc_async_work, async_work_s);
vhost = aw->vhost;
- crq = &aw->crq;
+ if (aw->event.type == IBMVFC_ASYNC_CRQ_SUB) {
+ subq = &aw->event.subq;
+ scsi_id = 0;
+ wwpn = subq->wwpn;
+ node_name = (subq->flags & IBMVFC_ASYNC_ID_IS_ASSOC_ID) ? 0 : subq->id.node_name;
+ } else {
+ crq = &aw->event.async_crq;
+ scsi_id = crq->scsi_id;
+ wwpn = crq->wwpn;
+ node_name = crq->node_name;
+ }
- if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ if (!scsi_id && !wwpn && !node_name)
goto free;
spin_lock_irqsave(vhost->host->host_lock, flags);
- tgt = ibmvfc_find_target(&vhost->scsi_scrqs.targets, crq->scsi_id,
- crq->wwpn, crq->node_name);
+ tgt = ibmvfc_find_target(&vhost->scsi_scrqs.targets, scsi_id, wwpn, node_name);
if (!tgt) {
/* Target not found in scsi_scrqs, search nvme_scrqs */
- tgt = ibmvfc_find_target(&vhost->nvme_scrqs.targets,
- crq->scsi_id, crq->wwpn,
- crq->node_name);
+ tgt = ibmvfc_find_target(&vhost->nvme_scrqs.targets, scsi_id, wwpn, node_name);
}
if (tgt) {
@@ -3496,7 +3546,11 @@ static void ibmvfc_process_async_work(struct work_struct *work)
goto free;
}
- fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
+ if (crq)
+ fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
+ else
+ fpin = ibmvfc_full_fpin_to_desc(subq);
+
if (fpin) {
fc_host_fpin_rcv(tgt->vhost->host,
sizeof(*fpin) + be32_to_cpu(fpin->desc_len),
@@ -3512,25 +3566,51 @@ static void ibmvfc_process_async_work(struct work_struct *work)
/**
* ibmvfc_handle_async - Handle an async event from the adapter
- * @crq: crq to process
+ * @ae: tagged union wrapping either an ibmvfc_async_crq (main CRQ) or an
+ * ibmvfc_async_sub_crq (async sub-CRQ); the type field identifies which
* @vhost: ibmvfc host struct
*
**/
-VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
+VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq_event *ae,
struct ibmvfc_host *vhost)
{
- const struct ibmvfc_async_desc *desc = ibmvfc_get_ae_desc(be64_to_cpu(crq->event));
+ struct ibmvfc_async_crq *async_crq = NULL;
+ struct ibmvfc_async_sub_crq *subq = NULL;
+ const struct ibmvfc_async_desc *desc;
struct ibmvfc_async_work *aw;
struct ibmvfc_target *tgt;
-
- ibmvfc_log(vhost, desc->log_level, "%s event received. scsi_id: %llx, wwpn: %llx,"
- " node_name: %llx%s\n", desc->desc, be64_to_cpu(crq->scsi_id),
- be64_to_cpu(crq->wwpn), be64_to_cpu(crq->node_name),
- ibmvfc_get_link_state(crq->link_state));
-
- switch (be64_to_cpu(crq->event)) {
+ __be64 node_name;
+ __be64 scsi_id;
+ u8 link_state;
+ __be64 wwpn;
+ u64 event;
+
+ if (ae->type == IBMVFC_ASYNC_CRQ_SUB) {
+ subq = &ae->subq;
+ event = be16_to_cpu(subq->event);
+ link_state = subq->link_state;
+ scsi_id = 0;
+ wwpn = subq->wwpn;
+ node_name = subq->flags & IBMVFC_ASYNC_ID_IS_ASSOC_ID ? 0 : subq->id.node_name;
+ } else {
+ async_crq = &ae->async_crq;
+ event = be64_to_cpu(async_crq->event);
+ link_state = async_crq->link_state;
+ scsi_id = async_crq->scsi_id;
+ wwpn = async_crq->wwpn;
+ node_name = async_crq->node_name;
+ }
+
+ desc = ibmvfc_get_ae_desc(event);
+ ibmvfc_log(vhost, desc->log_level,
+ "%s event received. scsi_id: %llx, wwpn: %llx, node_name: %llx, event %llx%s\n",
+ desc->desc, be64_to_cpu(scsi_id),
+ be64_to_cpu(wwpn), be64_to_cpu(node_name), event,
+ ibmvfc_get_link_state(link_state));
+
+ switch (event) {
case IBMVFC_AE_RESUME:
- switch (crq->link_state) {
+ switch (link_state) {
case IBMVFC_AE_LS_LINK_DOWN:
ibmvfc_link_down(vhost, IBMVFC_LINK_DOWN);
break;
@@ -3569,33 +3649,33 @@ VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
case IBMVFC_AE_ELS_PRLO:
case IBMVFC_AE_ELS_PLOGI:
list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) {
- if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ if (!scsi_id && !wwpn && !node_name)
break;
- if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id)
+ if (scsi_id && cpu_to_be64(tgt->scsi_id) != scsi_id)
continue;
- if (crq->wwpn && cpu_to_be64(tgt->ids.port_name) != crq->wwpn)
+ if (wwpn && cpu_to_be64(tgt->ids.port_name) != wwpn)
continue;
- if (crq->node_name && cpu_to_be64(tgt->ids.node_name) != crq->node_name)
+ if (node_name && cpu_to_be64(tgt->ids.node_name) != node_name)
continue;
- if (tgt->need_login && be64_to_cpu(crq->event) == IBMVFC_AE_ELS_LOGO)
+ if (tgt->need_login && event == IBMVFC_AE_ELS_LOGO)
tgt->logo_rcvd = 1;
- if (!tgt->need_login || be64_to_cpu(crq->event) == IBMVFC_AE_ELS_PLOGI) {
+ if (!tgt->need_login || event == IBMVFC_AE_ELS_PLOGI) {
ibmvfc_del_tgt(tgt);
ibmvfc_reinit_host(vhost);
}
}
list_for_each_entry(tgt, &vhost->nvme_scrqs.targets, queue) {
- if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ if (!scsi_id && !wwpn && !node_name)
break;
- if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id)
+ if (scsi_id && cpu_to_be64(tgt->scsi_id) != scsi_id)
continue;
- if (crq->wwpn && cpu_to_be64(tgt->ids.port_name) != crq->wwpn)
+ if (wwpn && cpu_to_be64(tgt->ids.port_name) != wwpn)
continue;
- if (crq->node_name && cpu_to_be64(tgt->ids.node_name) != crq->node_name)
+ if (node_name && cpu_to_be64(tgt->ids.node_name) != node_name)
continue;
- if (tgt->need_login && be64_to_cpu(crq->event) == IBMVFC_AE_ELS_LOGO)
+ if (tgt->need_login && event == IBMVFC_AE_ELS_LOGO)
tgt->logo_rcvd = 1;
- if (!tgt->need_login || be64_to_cpu(crq->event) == IBMVFC_AE_ELS_PLOGI) {
+ if (!tgt->need_login || event == IBMVFC_AE_ELS_PLOGI) {
ibmvfc_del_tgt(tgt);
ibmvfc_reinit_host(vhost);
}
@@ -3616,14 +3696,14 @@ VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
if (aw) {
INIT_WORK(&aw->async_work_s, ibmvfc_process_async_work);
aw->vhost = vhost;
- aw->crq = *crq;
+ aw->event = *ae;
queue_work(vhost->fpin_workq, &aw->async_work_s);
} else
dev_err_ratelimited(vhost->dev,
"can't offload async CRQ to work queue\n");
break;
default:
- dev_err(vhost->dev, "Unknown async event received: %lld\n", crq->event);
+ dev_err(vhost->dev, "Unknown async event received: %llu\n", event);
break;
}
}
@@ -4164,7 +4244,11 @@ static void ibmvfc_tasklet(void *data)
while (!done) {
/* Pull all the valid messages off the async CRQ */
while ((async = ibmvfc_next_async_crq(vhost)) != NULL) {
- ibmvfc_handle_async(async, vhost);
+ struct ibmvfc_async_crq_event ae = {
+ .type = IBMVFC_ASYNC_CRQ_MAIN,
+ .async_crq = *async,
+ };
+ ibmvfc_handle_async(&ae, vhost);
async->valid = 0;
wmb();
}
@@ -4178,8 +4262,12 @@ static void ibmvfc_tasklet(void *data)
vio_enable_interrupts(vdev);
if ((async = ibmvfc_next_async_crq(vhost)) != NULL) {
+ struct ibmvfc_async_crq_event ae = {
+ .type = IBMVFC_ASYNC_CRQ_MAIN,
+ .async_crq = *async,
+ };
vio_disable_interrupts(vdev);
- ibmvfc_handle_async(async, vhost);
+ ibmvfc_handle_async(&ae, vhost);
async->valid = 0;
wmb();
} else if ((crq = ibmvfc_next_crq(vhost)) != NULL) {
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index 3848fa936e38..464baf1077e0 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -786,9 +786,22 @@ struct ibmvfc_async_sub_crq {
} id;
} __packed __aligned(8);
+enum ibmvfc_async_crq_type {
+ IBMVFC_ASYNC_CRQ_MAIN = 0,
+ IBMVFC_ASYNC_CRQ_SUB,
+};
+
+struct ibmvfc_async_crq_event {
+ enum ibmvfc_async_crq_type type;
+ union {
+ struct ibmvfc_async_crq async_crq;
+ struct ibmvfc_async_sub_crq subq;
+ };
+};
+
struct ibmvfc_async_work {
struct ibmvfc_host *vhost;
- struct ibmvfc_async_crq crq;
+ struct ibmvfc_async_crq_event event;
struct work_struct async_work_s;
};
@@ -1103,7 +1116,8 @@ static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channel
#if IS_ENABLED(CONFIG_KUNIT)
#include <kunit/visibility.h>
-VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq, struct ibmvfc_host *vhost);
+VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq_event *event,
+ struct ibmvfc_host *vhost);
VISIBLE_IF_KUNIT struct list_head *ibmvfc_get_headp(void);
#endif
diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
index 8a73ea1b1da8..bc6e542f11ce 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -24,9 +24,11 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
{
u64 post[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
u64 pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ struct ibmvfc_async_crq_event ae[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1] = {
+ [0 ... IBMVFC_AE_FPIN_CONGESTION_CLEARED] = { .type = IBMVFC_ASYNC_CRQ_MAIN },
+ };
enum ibmvfc_ae_fpin_status fs;
struct fc_host_attrs *fc_host;
- struct ibmvfc_async_crq crq[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
struct ibmvfc_target *tgt;
struct ibmvfc_host *vhost;
struct list_head *queue;
@@ -61,15 +63,15 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
for (fs = IBMVFC_AE_FPIN_LINK_CONGESTED; fs <= IBMVFC_AE_FPIN_CONGESTION_CLEARED; fs++) {
- crq[fs].valid = 0x80;
- crq[fs].link_state = IBMVFC_AE_LS_LINK_UP;
- crq[fs].fpin_status = fs;
- crq[fs].event = cpu_to_be64(IBMVFC_AE_FPIN);
- crq[fs].scsi_id = cpu_to_be64(tgt->scsi_id);
- crq[fs].wwpn = cpu_to_be64(tgt->wwpn);
- crq[fs].node_name = cpu_to_be64(tgt->ids.node_name);
- ibmvfc_handle_async(&crq[fs], vhost);
- crq[fs].valid = 0;
+ ae[fs].async_crq.valid = 0x80;
+ ae[fs].async_crq.link_state = IBMVFC_AE_LS_LINK_UP;
+ ae[fs].async_crq.fpin_status = fs;
+ ae[fs].async_crq.event = cpu_to_be64(IBMVFC_AE_FPIN);
+ ae[fs].async_crq.scsi_id = cpu_to_be64(tgt->scsi_id);
+ ae[fs].async_crq.wwpn = cpu_to_be64(tgt->wwpn);
+ ae[fs].async_crq.node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&ae[fs], vhost);
+ ae[fs].async_crq.valid = 0;
wmb();
}
flush_workqueue(vhost->fpin_workq);
@@ -98,15 +100,136 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
pre[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
- crq[0].valid = 0x80;
- crq[0].link_state = IBMVFC_AE_LS_LINK_UP;
- crq[0].fpin_status = 0; /* bad value */
- crq[0].event = cpu_to_be64(IBMVFC_AE_FPIN);
- crq[0].scsi_id = cpu_to_be64(tgt->scsi_id);
- crq[0].wwpn = cpu_to_be64(tgt->wwpn);
- crq[0].node_name = cpu_to_be64(tgt->ids.node_name);
- ibmvfc_handle_async(&crq[0], vhost);
- crq[0].valid = 0;
+ ae[0].async_crq.valid = 0x80;
+ ae[0].async_crq.link_state = IBMVFC_AE_LS_LINK_UP;
+ ae[0].async_crq.fpin_status = 0; /* bad value */
+ ae[0].async_crq.event = cpu_to_be64(IBMVFC_AE_FPIN);
+ ae[0].async_crq.scsi_id = cpu_to_be64(tgt->scsi_id);
+ ae[0].async_crq.wwpn = cpu_to_be64(tgt->wwpn);
+ ae[0].async_crq.node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&ae[0], vhost);
+ ae[0].async_crq.valid = 0;
+ wmb();
+ flush_workqueue(vhost->fpin_workq);
+
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_LINK_CONGESTED],
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_CONGESTED],
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_CLEARED],
+ post[IBMVFC_AE_FPIN_PORT_CLEARED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_PORT_DEGRADED],
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED]);
+ KUNIT_EXPECT_EQ(test, pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED],
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED]);
+
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+}
+
+/**
+ * ibmvfc_full_fpin_event_test - unit test for IBMVFC_AE_FPIN parts of
+ * ibmvfc_handle_async
+ * @test: pointer to kunit structure
+ *
+ * Tests
+ * - error returns from ibmvfc_handle_async
+ * - statistics updates
+ *
+ * Return: void
+ */
+static void ibmvfc_full_fpin_test(struct kunit *test)
+{
+ u64 post[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ u64 pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1];
+ struct ibmvfc_async_crq_event ae[IBMVFC_AE_FPIN_CONGESTION_CLEARED + 1] = {
+ [0 ... IBMVFC_AE_FPIN_CONGESTION_CLEARED] = { .type = IBMVFC_ASYNC_CRQ_SUB },
+ };
+ enum ibmvfc_ae_fpin_status fs;
+ struct fc_host_attrs *fc_host;
+ struct ibmvfc_target *tgt;
+ struct ibmvfc_host *vhost;
+ struct list_head *queue;
+ struct list_head *headp;
+ unsigned long flags;
+
+ headp = ibmvfc_get_headp();
+ if (list_empty(headp))
+ kunit_skip(test, "No ibmvfc devices available");
+ queue = headp->next;
+ vhost = container_of_const(queue, struct ibmvfc_host, queue);
+
+ spin_lock_irqsave(vhost->host->host_lock, flags);
+ if (vhost->scsi_scrqs.num_targets < 1) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No targets");
+ }
+ tgt = list_first_entry(&vhost->scsi_scrqs.targets, struct ibmvfc_target, queue);
+ if (!tgt->rport) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No rport");
+ }
+ kref_get(&tgt->kref);
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+
+ fc_host = shost_to_fc_host(vhost->host);
+
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ for (fs = IBMVFC_AE_FPIN_LINK_CONGESTED; fs <= IBMVFC_AE_FPIN_CONGESTION_CLEARED; fs++) {
+ ae[fs].subq.valid = 0x80;
+ ae[fs].subq.link_state = IBMVFC_AE_LS_LINK_UP;
+ ae[fs].subq.fpin_status = fs;
+ ae[fs].subq.event = cpu_to_be16(IBMVFC_AE_FPIN);
+ ae[fs].subq.wwpn = cpu_to_be64(tgt->wwpn);
+ ae[fs].subq.id.node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&ae[fs], vhost);
+ ae[fs].subq.valid = 0;
+ wmb();
+ }
+ flush_workqueue(vhost->fpin_workq);
+
+ post[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn);
+ post[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ post[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ post[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_LINK_CONGESTED],
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_CONGESTED],
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_CLEARED],
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_PORT_DEGRADED],
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED]+1);
+ KUNIT_EXPECT_GE(test, post[IBMVFC_AE_FPIN_CONGESTION_CLEARED],
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED]+1);
+
+ /* bad path */
+ pre[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
+ pre[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ pre[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
+ pre[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
+
+ ae[0].subq.valid = 0x80;
+ ae[0].subq.link_state = IBMVFC_AE_LS_LINK_UP;
+ ae[0].subq.fpin_status = 0; /* bad value */
+ ae[0].subq.event = cpu_to_be16(IBMVFC_AE_FPIN);
+ ae[0].subq.wwpn = cpu_to_be64(tgt->wwpn);
+ ae[0].subq.id.node_name = cpu_to_be64(tgt->ids.node_name);
+ ibmvfc_handle_async(&ae[0], vhost);
+ ae[0].subq.valid = 0;
wmb();
flush_workqueue(vhost->fpin_workq);
@@ -132,6 +255,7 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
static struct kunit_case ibmvfc_fpin_test_cases[] = {
KUNIT_CASE(ibmvfc_async_fpin_test),
+ KUNIT_CASE(ibmvfc_full_fpin_test),
{},
};
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (3 preceding siblings ...)
2026-08-31 19:00 ` [PATCH v7 4/8] scsi: ibmvfc: extend async event handlers to handle async sub queue events Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 22:18 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq Dave Marquardt via B4 Relay
` (2 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Add ibmvfc_interrupt_async_subq(), an IRQ handler dedicated to
asynchronous sub-CRQ events from the adapter. The handler disables
the sub-CRQ IRQ and then calls ibmvfc_drain_async_subq() to consume
all pending entries before re-enabling interrupts. The handler is
marked as __maybe_unused until a later patch when it is used.
ibmvfc_drain_async_subq() holds the per-queue q_lock while
processing. It loops over available CRQ entries via ibmvfc_next_scrq(),
wrapping each in a typed struct ibmvfc_async_crq_event and dispatching
it to ibmvfc_handle_async(), then clears the valid bit and issues a
write barrier. After draining, it re-enables the sub-CRQ IRQ and performs
one final check for a newly arrived entry to close the IRQ-enable race;
if one is found it is processed before exiting the loop.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 50 +++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index 229b06effd4c..a28875c1f983 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -4369,6 +4369,56 @@ static struct ibmvfc_crq *ibmvfc_next_scrq(struct ibmvfc_queue *scrq)
return crq;
}
+static void ibmvfc_drain_async_subq(struct ibmvfc_queue *scrq)
+{
+ struct ibmvfc_crq *crq;
+ int done = 0;
+
+ spin_lock(scrq->q_lock);
+ while (!done) {
+ while ((crq = ibmvfc_next_scrq(scrq)) != NULL) {
+ struct ibmvfc_async_crq_event ae = {
+ .type = IBMVFC_ASYNC_CRQ_SUB,
+ .subq = *(struct ibmvfc_async_sub_crq *)crq,
+ };
+ ibmvfc_handle_async(&ae, scrq->vhost);
+ crq->valid = 0;
+ wmb(); /* complete write */
+ }
+
+ ibmvfc_toggle_scrq_irq(scrq, 1);
+ crq = ibmvfc_next_scrq(scrq);
+ if (crq != NULL) {
+ struct ibmvfc_async_crq_event ae = {
+ .type = IBMVFC_ASYNC_CRQ_SUB,
+ .subq = *(struct ibmvfc_async_sub_crq *)crq,
+ };
+ ibmvfc_toggle_scrq_irq(scrq, 0);
+ ibmvfc_handle_async(&ae, scrq->vhost);
+ crq->valid = 0;
+ wmb(); /* complete write */
+ } else
+ done = 1;
+ }
+ spin_unlock(scrq->q_lock);
+}
+
+/**
+ * ibmvfc_interrupt_async_subq - Handle an async event from the adapter
+ * @irq: interrupt request
+ * @scrq_instance: async subq
+ *
+ **/
+static irqreturn_t __maybe_unused ibmvfc_interrupt_async_subq(int irq, void *scrq_instance)
+{
+ struct ibmvfc_queue *scrq = (struct ibmvfc_queue *)scrq_instance;
+
+ ibmvfc_toggle_scrq_irq(scrq, 0);
+ ibmvfc_drain_async_subq(scrq);
+
+ return IRQ_HANDLED;
+}
+
static void ibmvfc_drain_sub_crq(struct ibmvfc_queue *scrq)
{
struct ibmvfc_crq *crq;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (4 preceding siblings ...)
2026-08-31 19:00 ` [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 22:34 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
ibmvfc_register_channel() and ibmvfc_deregister_channel() previously only
handled indexed sub-CRQ channels drawn from the channels->scrqs[] array.
The async sub-CRQ (vhost->async_sub_crq) had no registration path through
these helpers, requiring separate handling.
Extend both functions to accept a negative index as a sentinel value
signalling that the async sub-CRQ should be operated on instead of an
indexed scrq entry. When index < 0, the queue pointer is set to
&vhost->async_sub_crq, the IRQ is named "ibmvfc-<addr>-async", and the
handler is set to ibmvfc_interrupt_async_subq rather than the per-protocol
ibmvfc_interrupt_mq handler. hwq_id assignment is skipped for the async
queue since it has no meaningful hardware queue index.
Stopped marking ibmvfc_interrupt_async_subq as __maybe_unused.
Error messages in both paths are updated to distinguish async sub-CRQ
failures from indexed sub-CRQ failures. Kernel-doc headers are added to
both functions documenting the negative-index convention.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 93 +++++++++++++++++++++++++++---------
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 2 +-
2 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index a28875c1f983..cc1ef8258060 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -4409,7 +4409,7 @@ static void ibmvfc_drain_async_subq(struct ibmvfc_queue *scrq)
* @scrq_instance: async subq
*
**/
-static irqreturn_t __maybe_unused ibmvfc_interrupt_async_subq(int irq, void *scrq_instance)
+static irqreturn_t ibmvfc_interrupt_async_subq(int irq, void *scrq_instance)
{
struct ibmvfc_queue *scrq = (struct ibmvfc_queue *)scrq_instance;
@@ -6799,13 +6799,29 @@ static int ibmvfc_init_crq(struct ibmvfc_host *vhost)
return retrc;
}
+/**
+ * ibmvfc_register_channel - Register a sub-CRQ channel with the hypervisor
+ * @vhost: ibmvfc host struct
+ * @channels: ibmvfc channels struct containing the channel array and protocol
+ * @index: index into the channels array for the queue to register, or
+ * a negative value to register the async sub-CRQ
+ *
+ * Register a sub-CRQ with the hypervisor via h_reg_sub_crq, map its hardware
+ * IRQ to a Linux IRQ, and bind an interrupt handler to it. The handler is
+ * selected based on the channel protocol (SCSI or NVMe) for normal queues, or
+ * set to the async sub-CRQ handler when @index is negative.
+ *
+ * Return value:
+ * 0 on success / non-zero on failure
+ **/
static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
struct ibmvfc_channels *channels,
int index)
{
struct device *dev = vhost->dev;
struct vio_dev *vdev = to_vio_dev(dev);
- struct ibmvfc_queue *scrq = &channels->scrqs[index];
+ bool is_async = index < 0;
+ struct ibmvfc_queue *scrq = !is_async ? &channels->scrqs[index] : &vhost->async_sub_crq;
int rc = -ENOMEM;
ENTER;
@@ -6825,36 +6841,49 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
if (!scrq->irq) {
rc = -EINVAL;
- dev_err(dev, "Error mapping sub-crq[%d] irq\n", index);
+ if (!is_async)
+ dev_err(dev, "Error mapping sub-crq[%d] irq\n", index);
+ else
+ dev_err(dev, "Error mapping async sub-crq irq\n");
goto irq_failed;
}
- switch (channels->protocol) {
- case IBMVFC_PROTO_SCSI:
- snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-scsi%d",
- vdev->unit_address, index);
- scrq->handler = ibmvfc_interrupt_mq;
- break;
- case IBMVFC_PROTO_NVME:
- snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-nvmf%d",
- vdev->unit_address, index);
- scrq->handler = ibmvfc_interrupt_mq;
- break;
- default:
- dev_err(dev, "Unknown channel protocol (%d)\n",
- channels->protocol);
- goto irq_failed;
+ if (!is_async) {
+ switch (channels->protocol) {
+ case IBMVFC_PROTO_SCSI:
+ snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-scsi%d",
+ vdev->unit_address, index);
+ scrq->handler = ibmvfc_interrupt_mq;
+ break;
+ case IBMVFC_PROTO_NVME:
+ snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-nvmf%d",
+ vdev->unit_address, index);
+ scrq->handler = ibmvfc_interrupt_mq;
+ break;
+ default:
+ dev_err(dev, "Unknown channel protocol (%d)\n",
+ channels->protocol);
+ goto irq_failed;
+ }
+ } else {
+ snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-async",
+ vdev->unit_address);
+ scrq->handler = ibmvfc_interrupt_async_subq;
}
rc = request_irq(scrq->irq, scrq->handler, 0, scrq->name, scrq);
if (rc) {
- dev_err(dev, "Couldn't register sub-crq[%d] irq\n", index);
+ if (!is_async)
+ dev_err(dev, "Couldn't register sub-crq[%d] irq\n", index);
+ else
+ dev_err(dev, "Couldn't register async sub-crq irq\n");
irq_dispose_mapping(scrq->irq);
goto irq_failed;
}
- scrq->hwq_id = index;
+ if (!is_async)
+ scrq->hwq_id = index;
LEAVE;
return 0;
@@ -6868,13 +6897,26 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
return rc;
}
+/**
+ * ibmvfc_deregister_channel - Deregister a sub-CRQ channel with the hypervisor
+ * @vhost: ibmvfc host struct
+ * @channels: ibmvfc channels struct containing the sub-CRQ array
+ * @index: index into the sub-CRQ array, or -1 to deregister the
+ * asynchronous sub-CRQ
+ *
+ * Frees the IRQ, disposes of the IRQ mapping, and calls H_FREE_SUB_CRQ to
+ * release the sub-CRQ with the hypervisor. On success the queue message
+ * buffer is zeroed and the current index is reset. If H_FREE_SUB_CRQ fails,
+ * an error is logged but the channel resources are cleaned up regardless.
+ */
static void ibmvfc_deregister_channel(struct ibmvfc_host *vhost,
struct ibmvfc_channels *channels,
int index)
{
struct device *dev = vhost->dev;
struct vio_dev *vdev = to_vio_dev(dev);
- struct ibmvfc_queue *scrq = &channels->scrqs[index];
+ bool is_async = index < 0;
+ struct ibmvfc_queue *scrq = !is_async ? &channels->scrqs[index] : &vhost->async_sub_crq;
long rc;
ENTER;
@@ -6888,8 +6930,13 @@ static void ibmvfc_deregister_channel(struct ibmvfc_host *vhost,
scrq->cookie);
} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
- if (rc)
- dev_err(dev, "Failed to free sub-crq[%d]: rc=%ld\n", index, rc);
+ if (rc) {
+ if (!is_async)
+ dev_err(dev, "Failed to free sub-crq[%d]: rc=%ld\n",
+ index, rc);
+ else
+ dev_err(dev, "Failed to free async sub-crq: rc=%ld\n", rc);
+ }
/* Clean out the queue */
memset(scrq->msgs.crq, 0, PAGE_SIZE);
diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
index bc6e542f11ce..47f7c4e79cd8 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -199,7 +199,7 @@ static void ibmvfc_full_fpin_test(struct kunit *test)
flush_workqueue(vhost->fpin_workq);
post[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
- post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn);
+ post[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
post[IBMVFC_AE_FPIN_PORT_CLEARED] = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
post[IBMVFC_AE_FPIN_PORT_DEGRADED] = READ_ONCE(tgt->rport->fpin_stats.li_failure_unknown);
post[IBMVFC_AE_FPIN_CONGESTION_CLEARED] = READ_ONCE(fc_host->fpin_stats.cn_clear);
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (5 preceding siblings ...)
2026-08-31 19:00 ` [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 22:46 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Wire the async sub-CRQ into the sub-CRQ lifecycle so it is allocated,
registered, and freed alongside the SCSI channel queues.
In ibmvfc_init_sub_crqs(), allocate the async sub-CRQ queue buffer via
ibmvfc_alloc_queue() before allocating the SCSI channels. Register it
with the VIOS by calling ibmvfc_register_channel() with index -1 (the
negative-index sentinel introduced in the previous commit). Either
failure disables multi-queue and aborts init.
In ibmvfc_release_sub_crqs(), deregister and free the async sub-CRQ
before tearing down the SCSI channels.
In ibmvfc_channel_setup_done(), capture the async sub-CRQ handle
returned by the VIOS in the channel setup response and store it in
vhost->async_sub_crq.vios_cookie.
In ibmvfc_set_login_info(), advertise IBMVFC_USE_ASYNC_SUBQ,
IBMVFC_CAN_HANDLE_FPIN, and IBMVFC_YES_SCSI capabilities whenever
multi-queue channels are enabled. IBMVFC_YES_SCSI was previously only
set for NVMe-enabled configurations; move it to the common multi-queue
path so it is always advertised when channels are in use.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 45 ++++++++++++++++++++++++++++++++-----
1 file changed, 40 insertions(+), 5 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index cc1ef8258060..adc730af67d2 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -192,6 +192,8 @@ static void ibmvfc_tgt_move_login(struct ibmvfc_target *);
static void ibmvfc_dereg_sub_crqs(struct ibmvfc_host *, struct ibmvfc_channels *);
static void ibmvfc_reg_sub_crqs(struct ibmvfc_host *, struct ibmvfc_channels *);
+static void ibmvfc_deregister_channel(struct ibmvfc_host *, struct ibmvfc_channels *, int);
+static int ibmvfc_register_channel(struct ibmvfc_host *, struct ibmvfc_channels *, int);
static const char *unknown_error = "unknown error";
@@ -964,6 +966,7 @@ static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost)
struct vio_dev *vdev = to_vio_dev(vhost->dev);
unsigned long flags;
+ ibmvfc_deregister_channel(vhost, &vhost->scsi_scrqs, -1);
ibmvfc_dereg_sub_crqs(vhost, &vhost->scsi_scrqs);
ibmvfc_dereg_sub_crqs(vhost, &vhost->nvme_scrqs);
@@ -986,6 +989,7 @@ static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost)
spin_unlock(vhost->crq.q_lock);
spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ ibmvfc_register_channel(vhost, &vhost->scsi_scrqs, -1);
ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs);
ibmvfc_reg_sub_crqs(vhost, &vhost->nvme_scrqs);
@@ -1006,6 +1010,7 @@ static int ibmvfc_reset_crq(struct ibmvfc_host *vhost)
struct vio_dev *vdev = to_vio_dev(vhost->dev);
struct ibmvfc_queue *crq = &vhost->crq;
+ ibmvfc_deregister_channel(vhost, &vhost->scsi_scrqs, -1);
ibmvfc_dereg_sub_crqs(vhost, &vhost->scsi_scrqs);
ibmvfc_dereg_sub_crqs(vhost, &vhost->nvme_scrqs);
@@ -1042,6 +1047,7 @@ static int ibmvfc_reset_crq(struct ibmvfc_host *vhost)
spin_unlock(vhost->crq.q_lock);
spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ ibmvfc_register_channel(vhost, &vhost->scsi_scrqs, -1);
ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs);
ibmvfc_reg_sub_crqs(vhost, &vhost->nvme_scrqs);
@@ -1584,9 +1590,11 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
if (vhost->mq_enabled || vhost->using_channels) {
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_CHANNELS);
+ login_info->capabilities |= cpu_to_be64(IBMVFC_USE_ASYNC_SUBQ);
+ login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_HANDLE_FPIN);
+ login_info->capabilities |= cpu_to_be64(IBMVFC_YES_SCSI);
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);
}
}
@@ -5800,6 +5808,7 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt)
for (i = 0; i < nvme->active_queues; i++)
nvme->scrqs[i].vios_cookie =
be64_to_cpu(setup->channel_handles[scsi->active_queues + i]);
+ vhost->async_sub_crq.vios_cookie = be64_to_cpu(setup->async_sub_crq_handle);
ibmvfc_dbg(vhost, "Using %u SCSI channels\n",
scsi->active_queues);
@@ -5859,6 +5868,7 @@ static void ibmvfc_channel_setup(struct ibmvfc_host *vhost)
for (i = 0; i < nvme_channels; i++)
setup_buf->channel_handles[scsi_channels + i] =
cpu_to_be64(nvme->scrqs[i].cookie);
+ setup_buf->async_sub_crq_handle = cpu_to_be64(vhost->async_sub_crq.cookie);
}
ibmvfc_init_event(evt, ibmvfc_channel_setup_done, IBMVFC_MAD_FORMAT);
@@ -6823,6 +6833,7 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
bool is_async = index < 0;
struct ibmvfc_queue *scrq = !is_async ? &channels->scrqs[index] : &vhost->async_sub_crq;
int rc = -ENOMEM;
+ int hcall_rc;
ENTER;
@@ -6890,8 +6901,8 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
irq_failed:
do {
- rc = plpar_hcall_norets(H_FREE_SUB_CRQ, vdev->unit_address, scrq->cookie);
- } while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
+ hcall_rc = plpar_hcall_norets(H_FREE_SUB_CRQ, vdev->unit_address, scrq->cookie);
+ } while (hcall_rc == H_BUSY || H_IS_LONG_BUSY(hcall_rc));
reg_failed:
LEAVE;
return rc;
@@ -6957,7 +6968,9 @@ static void ibmvfc_reg_sub_crqs(struct ibmvfc_host *vhost,
for (i = 0; i < channels->max_queues; i++) {
if (ibmvfc_register_channel(vhost, channels, i)) {
for (j = i; j > 0; j--)
- ibmvfc_deregister_channel(vhost, channels, j - 1);
+ ibmvfc_deregister_channel(
+ vhost, channels, j - 1);
+
vhost->do_enquiry = 0;
return;
}
@@ -7012,16 +7025,26 @@ static int ibmvfc_alloc_channels(struct ibmvfc_host *vhost,
static void ibmvfc_init_sub_crqs(struct ibmvfc_host *vhost)
{
+ int rc = 0;
+
ENTER;
if (!vhost->mq_enabled)
return;
- if (ibmvfc_alloc_channels(vhost, &vhost->scsi_scrqs)) {
+ rc = ibmvfc_alloc_queue(vhost, &vhost->async_sub_crq, IBMVFC_SUB_CRQ_FMT);
+ if (rc) {
vhost->do_enquiry = 0;
vhost->mq_enabled = 0;
return;
}
+ /* register async_sub_crq channel */
+ if (ibmvfc_register_channel(vhost, &vhost->scsi_scrqs, -1))
+ goto free_async_sub_crq;
+
+ if (ibmvfc_alloc_channels(vhost, &vhost->scsi_scrqs))
+ goto deregister_async_sub_crq;
+
ibmvfc_reg_sub_crqs(vhost, &vhost->scsi_scrqs);
if (vhost->nvme_enabled) {
@@ -7032,6 +7055,15 @@ static void ibmvfc_init_sub_crqs(struct ibmvfc_host *vhost)
}
LEAVE;
+ return;
+
+ deregister_async_sub_crq:
+ ibmvfc_deregister_channel(vhost, &vhost->scsi_scrqs, -1);
+free_async_sub_crq:
+ ibmvfc_free_queue(vhost, &vhost->async_sub_crq);
+ vhost->do_enquiry = 0;
+ vhost->mq_enabled = 0;
+ return;
}
static void ibmvfc_release_channels(struct ibmvfc_host *vhost,
@@ -7055,6 +7087,9 @@ static void ibmvfc_release_channels(struct ibmvfc_host *vhost,
static void ibmvfc_release_sub_crqs(struct ibmvfc_host *vhost)
{
ENTER;
+ ibmvfc_deregister_channel(vhost, &vhost->scsi_scrqs, -1);
+ ibmvfc_free_queue(vhost, &vhost->async_sub_crq);
+
if (!vhost->scsi_scrqs.scrqs)
return;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (6 preceding siblings ...)
2026-08-31 19:00 ` [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events Dave Marquardt via B4 Relay
@ 2026-08-31 19:00 ` Dave Marquardt via B4 Relay
2026-08-31 22:56 ` sashiko-bot
7 siblings, 1 reply; 15+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-08-31 19:00 UTC (permalink / raw)
To: James E.J. Bottomley, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Tyrel Datwyler,
Martin K. Petersen
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Implement support for extended FPIN messages received via the
asynchronous sub-queue, completing full FPIN functionality.
Extended FPIN messages provide more detailed information about fabric
events compared to basic FPIN messages, including specific event types,
modifiers, thresholds, and event counts.
Add ibmvfc_ext_fpin_to_desc() to convert extended FPIN messages from
async sub-queue format to fc_els_fpin structures with complete descriptor
information. Update ibmvfc_process_async_work() to handle extended FPIN
events from the async sub-queue.
Set the IBMVFC_CAN_HANDLE_FPIN_EXT capability bit during login to inform
VIOS that the client can process extended FPIN messages. Also add the
IBMVFC_SUPPORT_FPIN_EXT response capability bit definition used to gate
extended FPIN processing.
Add comprehensive KUnit tests to validate extended FPIN event handling
and verify proper statistics updates for all FPIN event types.
Signed-off-by: Dave Marquardt <davemarq@linux.ibm.com>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 67 ++++++++++++++++--
drivers/scsi/ibmvscsi/ibmvfc.h | 30 ++++++++
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 128 +++++++++++++++++++++++++++++++++++
3 files changed, 221 insertions(+), 4 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index adc730af67d2..8351c9851b53 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -1593,6 +1593,7 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
login_info->capabilities |= cpu_to_be64(IBMVFC_USE_ASYNC_SUBQ);
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_HANDLE_FPIN);
login_info->capabilities |= cpu_to_be64(IBMVFC_YES_SCSI);
+ login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_HANDLE_FPIN_EXT);
if (vhost->nvme_enabled) {
login_info->capabilities |= cpu_to_be64(IBMVFC_YES_NVMEOF);
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_WWPN_ALL);
@@ -3473,6 +3474,41 @@ ibmvfc_full_fpin_to_desc(struct ibmvfc_async_sub_crq *ibmvfc_fpin)
cpu_to_be32(1));
}
+/**
+ * ibmvfc_ext_fpin_to_desc(): allocate and populate a struct fc_els_fpin struct
+ * containing a descriptor.
+ * @ibmvfc_fpin: Pointer to async subq FPIN data
+ *
+ * Allocate a struct fc_els_fpin containing a descriptor and populate
+ * based on data from *ibmvfc_fpin.
+ *
+ * Return:
+ * NULL - unable to allocate structure
+ * non-NULL - pointer to populated struct fc_els_fpin
+ */
+static struct fc_els_fpin *
+ibmvfc_ext_fpin_to_desc(struct ibmvfc_async_subq_fpin *ibmvfc_fpin)
+{
+ u8 flags = ibmvfc_fpin->fpin_data.flags;
+ __be32 threshold = cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD);
+ __be16 modifier = 0;
+ __be32 count = cpu_to_be32(1);
+ __be16 type = 0;
+
+ if (flags & IBMVFC_FPIN_EVENT_TYPE_VALID)
+ type = ibmvfc_fpin->fpin_data.event_type;
+ if (flags & IBMVFC_FPIN_MODIFIER_VALID)
+ modifier = ibmvfc_fpin->fpin_data.event_type_modifier;
+ if (flags & IBMVFC_FPIN_THRESHOLD_VALID)
+ threshold = ibmvfc_fpin->fpin_data.event_threshold;
+ if (flags & IBMVFC_FPIN_EVENT_COUNT_VALID)
+ count = ibmvfc_fpin->fpin_data.event_data.event_count;
+
+ return ibmvfc_common_fpin_to_desc(ibmvfc_fpin->fpin_status,
+ ibmvfc_fpin->wwpn, type,
+ modifier, threshold, count);
+}
+
/**
* ibmvfc_find_target - Search for a target in a target list
* @target_list: list head of targets to search
@@ -3511,6 +3547,7 @@ static struct ibmvfc_target *ibmvfc_find_target(struct list_head *target_list,
static void ibmvfc_process_async_work(struct work_struct *work)
{
struct ibmvfc_async_sub_crq *subq = NULL;
+ struct ibmvfc_async_subq_fpin *sqfpin;
struct ibmvfc_async_work *aw;
struct ibmvfc_async_crq *crq = NULL;
struct ibmvfc_target *tgt;
@@ -3527,7 +3564,10 @@ static void ibmvfc_process_async_work(struct work_struct *work)
subq = &aw->event.subq;
scsi_id = 0;
wwpn = subq->wwpn;
- node_name = (subq->flags & IBMVFC_ASYNC_ID_IS_ASSOC_ID) ? 0 : subq->id.node_name;
+ if (subq->flags & (IBMVFC_ASYNC_IS_FPIN_EXT | IBMVFC_ASYNC_ID_IS_ASSOC_ID))
+ node_name = 0;
+ else
+ node_name = subq->id.node_name;
} else {
crq = &aw->event.async_crq;
scsi_id = crq->scsi_id;
@@ -3556,8 +3596,24 @@ static void ibmvfc_process_async_work(struct work_struct *work)
if (crq)
fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
- else
- fpin = ibmvfc_full_fpin_to_desc(subq);
+ else {
+ static_assert(sizeof(struct ibmvfc_async_subq_fpin) ==
+ sizeof(struct ibmvfc_async_sub_crq));
+ static_assert(offsetof(struct ibmvfc_async_subq_fpin, fpin_data) ==
+ offsetof(struct ibmvfc_async_sub_crq, nport_id));
+ sqfpin = (struct ibmvfc_async_subq_fpin *)subq;
+ if ((subq->flags & IBMVFC_ASYNC_IS_FPIN_EXT) == 0) {
+ fpin = ibmvfc_full_fpin_to_desc(subq);
+ } else if (!(sqfpin->fpin_data.flags & IBMVFC_FPIN_EVENT_TYPE_VALID)) {
+ dev_err_ratelimited(vhost->dev,
+ "Invalid extended FPIN event received\n");
+ } else if (!ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_FPIN_EXT)) {
+ dev_err_ratelimited(vhost->dev,
+ "Unexpected extended FPIN event received\n");
+ } else {
+ fpin = ibmvfc_ext_fpin_to_desc(sqfpin);
+ }
+ }
if (fpin) {
fc_host_fpin_rcv(tgt->vhost->host,
@@ -3599,7 +3655,10 @@ VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq_event *ae,
link_state = subq->link_state;
scsi_id = 0;
wwpn = subq->wwpn;
- node_name = subq->flags & IBMVFC_ASYNC_ID_IS_ASSOC_ID ? 0 : subq->id.node_name;
+ if (subq->flags & (IBMVFC_ASYNC_IS_FPIN_EXT | IBMVFC_ASYNC_ID_IS_ASSOC_ID))
+ node_name = 0;
+ else
+ node_name = subq->id.node_name;
} else {
async_crq = &ae->async_crq;
event = be64_to_cpu(async_crq->event);
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index 464baf1077e0..a5ac84de1f08 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -210,6 +210,7 @@ struct ibmvfc_npiv_login {
#define IBMVFC_CAN_USE_WWPN_ALL 0x080
#define IBMVFC_USE_ASYNC_SUBQ 0x100
#define IBMVFC_CAN_USE_NOOP_CMD 0x200
+#define IBMVFC_CAN_HANDLE_FPIN_EXT 0x800
__be64 node_name;
struct srp_direct_buf async;
u8 partition_name[IBMVFC_MAX_NAME];
@@ -261,6 +262,7 @@ struct ibmvfc_npiv_login_resp {
#define IBMVFC_SUPPORT_WWPN_ALL 0x0400
#define IBMVFC_ASYNC_SUBQ 0x0800
#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
+#define IBMVFC_SUPPORT_FPIN_EXT 0x2000
__be32 max_cmds;
__be32 scsi_id_sz;
__be64 max_dma_len;
@@ -786,6 +788,34 @@ struct ibmvfc_async_sub_crq {
} id;
} __packed __aligned(8);
+struct ibmvfc_fpin_data {
+#define IBMVFC_FPIN_EVENT_TYPE_VALID 0x01
+#define IBMVFC_FPIN_MODIFIER_VALID 0x02
+#define IBMVFC_FPIN_THRESHOLD_VALID 0x04
+#define IBMVFC_FPIN_SEVERITY_VALID 0x08
+#define IBMVFC_FPIN_EVENT_COUNT_VALID 0x10
+ u8 flags;
+ u8 reserved[3];
+ __be16 event_type;
+ __be16 event_type_modifier;
+ __be32 event_threshold;
+ union {
+ u8 severity;
+ __be32 event_count;
+ } event_data;
+} __packed __aligned(8);
+
+struct ibmvfc_async_subq_fpin {
+ volatile u8 valid;
+ u8 flags;
+ u8 link_state;
+ u8 fpin_status;
+ __be16 event;
+ __be16 pad;
+ volatile __be64 wwpn;
+ struct ibmvfc_fpin_data fpin_data;
+} __packed __aligned(8);
+
enum ibmvfc_async_crq_type {
IBMVFC_ASYNC_CRQ_MAIN = 0,
IBMVFC_ASYNC_CRQ_SUB,
diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
index 47f7c4e79cd8..b016c7da24cd 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -3,6 +3,7 @@
#include <kunit/visibility.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_transport_fc.h>
+#include <scsi/fc/fc_els.h>
#include <linux/list.h>
#include <linux/delay.h>
#include "ibmvfc.h"
@@ -253,9 +254,136 @@ static void ibmvfc_full_fpin_test(struct kunit *test)
kref_put(&tgt->kref, ibmvfc_release_tgt);
}
+#define IBMVFC_TEST_FPIN_EXT(fs, ev, stat, crq) { \
+ struct ibmvfc_async_crq_event ae = { .type = IBMVFC_ASYNC_CRQ_SUB }; \
+ (crq).valid = 0x80; \
+ (crq).flags = IBMVFC_ASYNC_IS_FPIN_EXT; \
+ (crq).link_state = IBMVFC_AE_LS_LINK_UP; \
+ (crq).fpin_status = (fs); \
+ (crq).event = cpu_to_be16(IBMVFC_AE_FPIN); \
+ (crq).wwpn = cpu_to_be64(tgt->wwpn); \
+ (crq).fpin_data.flags = IBMVFC_FPIN_EVENT_TYPE_VALID; \
+ (crq).fpin_data.event_type = cpu_to_be16((ev)); \
+ ae.subq = *(struct ibmvfc_async_sub_crq *)&(crq); \
+ pre = READ_ONCE(tgt->rport->fpin_stats.stat); \
+ ibmvfc_handle_async(&ae, vhost); \
+ flush_workqueue(vhost->fpin_workq); \
+ post = READ_ONCE(tgt->rport->fpin_stats.stat); \
+}
+
+/**
+ * ibmvfc_extended_fpin_test - unit test for extended FPIN events
+ * @test: pointer to kunit structure
+ *
+ * Tests
+ *
+ * Return: void
+ */
+static void ibmvfc_extended_fpin_test(struct kunit *test)
+{
+ enum ibmvfc_ae_fpin_status fs;
+ struct ibmvfc_async_subq_fpin crq[IBMVFC_AE_FPIN_CONGESTION_CLEARED+1] = {};
+ struct ibmvfc_async_subq_fpin
+ crqcn[IBMVFC_AE_FPIN_PORT_CONGESTED][FPIN_CONGN_DEVICE_SPEC+1] = {};
+ struct ibmvfc_async_subq_fpin crqportdg[FPIN_LI_DEVICE_SPEC+1] = {};
+ struct ibmvfc_target *tgt;
+ struct ibmvfc_host *vhost;
+ struct list_head *headp;
+ LIST_HEAD(evt_doneq);
+ unsigned long flags;
+ u64 pre, post;
+
+ headp = ibmvfc_get_headp();
+ KUNIT_ASSERT_FALSE_MSG(test, list_empty(headp), "No ibmvfc devices available\n");
+ vhost = list_first_entry(headp, struct ibmvfc_host, queue);
+
+ spin_lock_irqsave(vhost->host->host_lock, flags);
+ if (vhost->scsi_scrqs.num_targets < 1) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No targets");
+ }
+ tgt = list_first_entry(&vhost->scsi_scrqs.targets, struct ibmvfc_target, queue);
+ if (!tgt->rport) {
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+ kunit_skip(test, "No rport");
+ }
+ kref_get(&tgt->kref);
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+
+ for (fs = IBMVFC_AE_FPIN_LINK_CONGESTED; fs <= IBMVFC_AE_FPIN_CONGESTION_CLEARED; fs++) {
+ switch (fs) {
+ case IBMVFC_AE_FPIN_PORT_CLEARED:
+ case IBMVFC_AE_FPIN_CONGESTION_CLEARED: {
+ struct ibmvfc_async_crq_event ae = { .type = IBMVFC_ASYNC_CRQ_SUB };
+
+ crq[fs].valid = 0x80;
+ crq[fs].flags = IBMVFC_ASYNC_IS_FPIN_EXT;
+ crq[fs].link_state = IBMVFC_AE_LS_LINK_UP;
+ crq[fs].fpin_status = fs;
+ crq[fs].event = cpu_to_be16(IBMVFC_AE_FPIN);
+ crq[fs].wwpn = cpu_to_be64(tgt->wwpn);
+ crq[fs].fpin_data.flags = IBMVFC_FPIN_EVENT_TYPE_VALID;
+ crq[fs].fpin_data.event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
+ ae.subq = *(struct ibmvfc_async_sub_crq *)&crq[fs];
+ pre = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ ibmvfc_handle_async(&ae, vhost);
+ flush_workqueue(vhost->fpin_workq);
+ post = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ break;
+ }
+ case IBMVFC_AE_FPIN_LINK_CONGESTED:
+ case IBMVFC_AE_FPIN_PORT_CONGESTED:
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_CONGN_CLEAR, cn_clear,
+ crqcn[fs-1][FPIN_CONGN_CLEAR]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_CONGN_LOST_CREDIT,
+ cn_lost_credit,
+ crqcn[fs-1][FPIN_CONGN_LOST_CREDIT]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_CONGN_CREDIT_STALL,
+ cn_credit_stall,
+ crqcn[fs-1][FPIN_CONGN_CREDIT_STALL]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_CONGN_OVERSUBSCRIPTION,
+ cn_oversubscription,
+ crqcn[fs-1][FPIN_CONGN_OVERSUBSCRIPTION]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_CONGN_DEVICE_SPEC,
+ cn_device_specific,
+ crqcn[fs-1][FPIN_CONGN_DEVICE_SPEC]);
+ break;
+ case IBMVFC_AE_FPIN_PORT_DEGRADED:
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_UNKNOWN,
+ li_failure_unknown,
+ crqportdg[FPIN_LI_UNKNOWN]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_LINK_FAILURE,
+ li_link_failure_count,
+ crqportdg[FPIN_LI_LINK_FAILURE]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_LOSS_OF_SYNC,
+ li_loss_of_sync_count,
+ crqportdg[FPIN_LI_LOSS_OF_SYNC]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_LOSS_OF_SIG,
+ li_loss_of_signals_count,
+ crqportdg[FPIN_LI_LOSS_OF_SIG]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_PRIM_SEQ_ERR,
+ li_prim_seq_err_count,
+ crqportdg[FPIN_LI_PRIM_SEQ_ERR]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_INVALID_TX_WD,
+ li_invalid_tx_word_count,
+ crqportdg[FPIN_LI_INVALID_TX_WD]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_INVALID_CRC,
+ li_invalid_crc_count,
+ crqportdg[FPIN_LI_INVALID_CRC]);
+ IBMVFC_TEST_FPIN_EXT(fs, FPIN_LI_DEVICE_SPEC,
+ li_device_specific,
+ crqportdg[FPIN_LI_DEVICE_SPEC]);
+ break;
+ }
+ }
+
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+}
+
static struct kunit_case ibmvfc_fpin_test_cases[] = {
KUNIT_CASE(ibmvfc_async_fpin_test),
KUNIT_CASE(ibmvfc_full_fpin_test),
+ KUNIT_CASE(ibmvfc_extended_fpin_test),
{},
};
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support
2026-08-31 19:00 ` [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
@ 2026-08-31 21:30 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 21:30 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Teardown ordering race allows FPIN works to execute on an unregistered `fc_host`.
- [Medium] Lockless traversal of global `ibmvfc_head` list in KUnit test risks use-after-free.
- [Medium] The KUnit test accesses `tgt->rport` outside of the `host_lock`.
--
commit 6a259111a28ef932ad14496cabbef71084312dfe
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: add basic FPIN support
Implement support for a basic level of Fabric Performance Impact
Notifications (FPIN) in the ibmvfc driver to enable monitoring of
fabric congestion and link integrity events. Offloads FPIN processing
to a dedicated workqueue and adds a KUnit test module for validation.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> index b3bc3ce872d6d..78be00d31a064 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
[ ... ]
> @@ -6962,6 +7231,8 @@ static void ibmvfc_remove(struct vio_dev *vdev)
> ibmvfc_complete_purge(&purge);
> ibmvfc_release_sub_crqs(vhost);
> ibmvfc_release_crq_queue(vhost);
> + destroy_workqueue(vhost->fpin_workq);
> + vhost->fpin_workq = NULL;
>
> ibmvfc_free_mem(vhost);
> spin_lock(&ibmvfc_driver_lock);
[Severity: High]
Could this teardown ordering race cause a regression by allowing FPIN works to
execute on an unregistered fc_host?
Earlier in ibmvfc_remove(), fc_remove_host() is called which destroys
fc_host->work_q and sets it to NULL. This happens before disabling FPIN
interrupts via ibmvfc_release_sub_crqs() and ibmvfc_release_crq_queue(), and
before destroying the new fpin_workq here.
If an FPIN interrupt arrives concurrently before the queues are released,
it can queue a work item to fpin_workq. When that work executes
fc_host_fpin_rcv(), it calls fc_queue_event(). Because scsi_remove_host() has
not yet executed to clear state_in_sysfs, fc_queue_event() may attempt to
queue work to the already destroyed fc_host->work_q via queue_work(NULL, ...),
resulting in a kernel crash.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
> new file mode 100644
> index 0000000000000..8a73ea1b1da8c
> --- /dev/null
> +++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
[ ... ]
> @@ -0,0 +1,146 @@
[ ... ]
> +static void ibmvfc_async_fpin_test(struct kunit *test)
> +{
[ ... ]
> + headp = ibmvfc_get_headp();
> + if (list_empty(headp))
> + kunit_skip(test, "No ibmvfc devices available");
> + queue = headp->next;
> + vhost = container_of_const(queue, struct ibmvfc_host, queue);
[Severity: Medium]
Could this lockless traversal of the global ibmvfc_head list lead to a
use-after-free regression?
If ibmvfc_remove() executes concurrently (for instance, during a device
hot-unplug), it could remove and free the vhost structure while the KUnit test
is still traversing the list.
> +
> + spin_lock_irqsave(vhost->host->host_lock, flags);
> + if (vhost->scsi_scrqs.num_targets < 1) {
> + spin_unlock_irqrestore(vhost->host->host_lock, flags);
> + kunit_skip(test, "No targets");
> + }
> + tgt = list_first_entry(&vhost->scsi_scrqs.targets, struct ibmvfc_target, queue);
> + if (!tgt->rport) {
> + spin_unlock_irqrestore(vhost->host->host_lock, flags);
> + kunit_skip(test, "No rport");
> + }
> + kref_get(&tgt->kref);
> + spin_unlock_irqrestore(vhost->host->host_lock, flags);
> +
> + fc_host = shost_to_fc_host(vhost->host);
> +
> + pre[IBMVFC_AE_FPIN_LINK_CONGESTED] = READ_ONCE(fc_host->fpin_stats.cn_device_specific);
> + pre[IBMVFC_AE_FPIN_PORT_CONGESTED] = READ_ONCE(tgt->rport->fpin_stats.cn_device_specific);
[Severity: Medium]
Is there a potential use-after-free regression here if tgt->rport is
dereferenced outside of the host_lock?
The test grabs a reference to tgt and drops vhost->host->host_lock, but then
reads tgt->rport->fpin_stats locklessly. If an event like a link down triggers
ibmvfc_del_tgt() concurrently, it can schedule the removal of the remote port.
The worker thread would then call fc_remote_port_delete(rport), freeing the
rport while the test is still dereferencing it.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support
2026-08-31 19:00 ` [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
@ 2026-08-31 21:40 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 21:40 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] The code suppresses logging of unexpected NOOP messages received before NPIV login completes, directly contradicting the commit message claim of logging early messages.
Pre-existing issues:
- [High] Dereferencing untrusted pointer `evt->queue` before validation in `ibmvfc_handle_scrq` can lead to a kernel panic or out-of-bounds read if the partner sends a malicious or corrupted non-NULL correlation token.
--
commit a2f1ce40ef9fb5e6c86a16b1ac92eb6c526f0a48
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: Add NOOP command support
This commit adds support for VFC_NOOP messages from VIOS to enable keep-alive
functionality between the client and server. It defines the VFC_NOOP CRQ
format, adds handling in the CRQ handlers, and sets the NPIV login capability
bit.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> index 78be00d31a064..e972e4ec5be00 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
[ ... ]
> @@ -3693,6 +3695,14 @@ static void ibmvfc_handle_crq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost,
> if (crq->format == IBMVFC_ASYNC_EVENT)
> return;
>
> + if (crq->format == IBMVFC_NOOP) {
> + if (vhost->state == IBMVFC_ACTIVE &&
> + !ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_NOOP_CMD))
> + dev_err_ratelimited(vhost->dev,
> + "Received unexpected NOOP command from partner\n");
> + return;
> + }
> +
[Severity: Medium]
The commit message states the intent is to "Log unexpected NOOP messages if
received before VIOS advertises support during NPIV login." Does this
condition match that intent?
In drivers/scsi/ibmvscsi/ibmvfc-core.c:ibmvfc_handle_crq(), the code checks:
if (vhost->state == IBMVFC_ACTIVE && !ibmvfc_check_caps(...))
By requiring the host state to be IBMVFC_ACTIVE, this appears to suppress
logging of unexpected NOOP messages received during the IBMVFC_INITIALIZING
state, and only logs them after NPIV login has completed.
> /* The only kind of payload CRQs we should get are responses to
> * things we send. Make sure this response is to something we
> * actually sent
[ ... ]
> @@ -4223,7 +4233,15 @@ static void ibmvfc_handle_scrq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost
> case IBMVFC_CRQ_XPORT_EVENT:
> return;
> default:
> - dev_err(vhost->dev, "Got and invalid message type 0x%02x\n", crq->valid);
> + dev_err(vhost->dev, "Got an invalid message type 0x%02x\n", crq->valid);
> + return;
> + }
> +
> + if (crq->format == IBMVFC_NOOP)
> + return;
> +
> + if (unlikely(!evt)) {
> + dev_err(vhost->dev, "Received null event\n");
> return;
> }
[Severity: High]
This isn't a bug introduced by this patch, but can this lead to an
out-of-bounds read or kernel panic if the VIOS partner sends a corrupted or
malicious non-NULL correlation token?
Just below this new null check in
drivers/scsi/ibmvscsi/ibmvfc-core.c:ibmvfc_handle_scrq(), the untrusted
evt pointer is dereferenced prior to validation:
if (unlikely(!ibmvfc_valid_event(&evt->queue->evt_pool, evt))) {
Could a malformed CRQ payload with a corrupted crq->ioba token cause a
crash when accessing evt->queue before verifying that evt is a legitimate
pointer from the event pool?
Passing the scrq pointer from ibmvfc_drain_sub_crq() instead of vhost might
be a way to avoid this.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ
2026-08-31 19:00 ` [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ Dave Marquardt via B4 Relay
@ 2026-08-31 22:18 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 22:18 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The `ibmvfc_drain_async_subq()` function calls `ibmvfc_handle_async()` without holding the primary SCSI host lock (`vhost->host->host_lock`). This leads to severe data races and list corruption because `ibmvfc_handle_async()` mutates shared state and iterates over lists that are concurrently modified by other threads holding the lock.
--
commit d6b23b1e849e5108314e933b9d7ab6b8580d0218
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ
Add ibmvfc_interrupt_async_subq(), an IRQ handler dedicated to
asynchronous sub-CRQ events from the adapter. The handler disables
the sub-CRQ IRQ and then calls ibmvfc_drain_async_subq() to consume
all pending entries before re-enabling interrupts.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> index 229b06effd4cf..a28875c1f9830 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> @@ -4369,6 +4369,56 @@ static struct ibmvfc_crq *ibmvfc_next_scrq(struct ibmvfc_queue *scrq)
> return crq;
> }
>
> +static void ibmvfc_drain_async_subq(struct ibmvfc_queue *scrq)
> +{
> + struct ibmvfc_crq *crq;
> + int done = 0;
> +
> + spin_lock(scrq->q_lock);
> + while (!done) {
> + while ((crq = ibmvfc_next_scrq(scrq)) != NULL) {
> + struct ibmvfc_async_crq_event ae = {
> + .type = IBMVFC_ASYNC_CRQ_SUB,
> + .subq = *(struct ibmvfc_async_sub_crq *)crq,
> + };
> + ibmvfc_handle_async(&ae, scrq->vhost);
[Severity: High]
Does this code need to hold the host_lock before calling ibmvfc_handle_async()?
Calling ibmvfc_handle_async() with only the local queue lock (scrq->q_lock)
held can lead to unprotected list iterations:
drivers/scsi/ibmvscsi/ibmvfc-core.c:ibmvfc_handle_async() {
...
list_for_each_entry(tgt, &vhost->scsi_scrqs.targets, queue) {
...
}
It can also lead to unprotected read-modify-write operations on shared state:
drivers/scsi/ibmvscsi/ibmvfc-core.c:ibmvfc_handle_async() {
...
vhost->events_to_log |= IBMVFC_AE_LINKUP;
...
}
If it races with normal target initialization, addition, or removal routines
running on other CPUs holding the host_lock, could it result in list corruption
or use-after-free?
> + crq->valid = 0;
> + wmb(); /* complete write */
> + }
> +
> + ibmvfc_toggle_scrq_irq(scrq, 1);
> + crq = ibmvfc_next_scrq(scrq);
> + if (crq != NULL) {
> + struct ibmvfc_async_crq_event ae = {
> + .type = IBMVFC_ASYNC_CRQ_SUB,
> + .subq = *(struct ibmvfc_async_sub_crq *)crq,
> + };
> + ibmvfc_toggle_scrq_irq(scrq, 0);
> + ibmvfc_handle_async(&ae, scrq->vhost);
[Severity: High]
Does this call to ibmvfc_handle_async() also need the host_lock for the same
reasons as above?
> + crq->valid = 0;
> + wmb(); /* complete write */
> + } else
> + done = 1;
> + }
> + spin_unlock(scrq->q_lock);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq
2026-08-31 19:00 ` [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq Dave Marquardt via B4 Relay
@ 2026-08-31 22:34 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 22:34 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Race condition and lockless list access in async sub-CRQ handler.
Pre-existing issues:
- [Medium] Stale IRQ mapping handle left on error path causes free_irq kernel WARNING.
--
commit 99a10b7da4dba7f584bda8695781cb27e1b928b0
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: extend channel registration and deregistration for async subq
This commit extends ibmvfc_register_channel() and
ibmvfc_deregister_channel() to handle the async sub-CRQ by accepting a
negative index. It also removes the __maybe_unused attribute from the async
interrupt handler to enable its use.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> @@ -4409,7 +4409,7 @@ static void ibmvfc_drain_async_subq(struct ibmvfc_queue *scrq)
> * @scrq_instance: async subq
> *
> **/
> -static irqreturn_t __maybe_unused ibmvfc_interrupt_async_subq(int irq, void *scrq_instance)
> +static irqreturn_t ibmvfc_interrupt_async_subq(int irq, void *scrq_instance)
> {
> struct ibmvfc_queue *scrq = (struct ibmvfc_queue *)scrq_instance;
>
[Severity: High]
Now that the async sub-CRQ handler is enabled by this patch, can its
execution race with target teardown or host reset?
In drivers/scsi/ibmvscsi/ibmvfc-core.c, the interrupt handler calls:
ibmvfc_interrupt_async_subq() -> ibmvfc_drain_async_subq() ->
ibmvfc_handle_async().
The ibmvfc_handle_async() function expects to hold vhost->host->host_lock,
but this call chain only holds scrq->q_lock. Consequently, it iterates over
and modifies vhost->scsi_scrqs.targets without the proper lock.
Could this lockless list traversal cause a use-after-free or data corruption
when handling dynamic async events sent by the hypervisor?
[ ... ]
> @@ -6825,36 +6841,49 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
[ ... ]
> rc = request_irq(scrq->irq, scrq->handler, 0, scrq->name, scrq);
>
> if (rc) {
> - dev_err(dev, "Couldn't register sub-crq[%d] irq\n", index);
> + if (!is_async)
> + dev_err(dev, "Couldn't register sub-crq[%d] irq\n", index);
> + else
> + dev_err(dev, "Couldn't register async sub-crq irq\n");
> irq_dispose_mapping(scrq->irq);
> goto irq_failed;
> }
[Severity: Medium]
This is a pre-existing issue, but does leaving scrq->irq populated with a
stale value after irq_dispose_mapping() cause a kernel warning during
teardown?
If request_irq() fails in ibmvfc_register_channel(), the error path calls
irq_dispose_mapping(scrq->irq) but does not clear the irq field to 0. Later,
when ibmvfc_deregister_channel() is unconditionally called for the async
sub-CRQ during teardown, it might call free_irq(scrq->irq, scrq) on this
stale mapping ID (or on 0 if it was never registered).
Should scrq->irq be explicitly cleared to 0 in this error path to prevent
invalid IRQ descriptor warnings?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=6
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events
2026-08-31 19:00 ` [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events Dave Marquardt via B4 Relay
@ 2026-08-31 22:46 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 22:46 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Unconditional deregistration of the uninitialized async sub-CRQ causes a NULL pointer dereference.
Pre-existing issues:
- [High] Buffer overflow due to out-of-bounds array writes and reads when processing unvalidated queue counts from the VIOS.
--
commit 908a7af2b04b0b17f4fc0a40814f0dacec88332a
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: register and use asynchronous sub CRQ for events
This commit wires the asynchronous sub-CRQ into the sub-CRQ lifecycle so it is
allocated, registered, and freed alongside the SCSI channel queues. It modifies
multi-queue initialization and teardown paths to accommodate the new queue and
advertises relevant capabilities to the VIOS.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
> index cc1ef82580608..adc730af67d2b 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
[ ... ]
> @@ -5800,6 +5808,7 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt)
> for (i = 0; i < nvme->active_queues; i++)
> nvme->scrqs[i].vios_cookie =
> be64_to_cpu(setup->channel_handles[scsi->active_queues + i]);
> + vhost->async_sub_crq.vios_cookie = be64_to_cpu(setup->async_sub_crq_handle);
>
> ibmvfc_dbg(vhost, "Using %u SCSI channels\n",
[Severity: High]
This is a pre-existing issue, but can this out-of-bounds write occur if the
VIOS returns untrusted queue counts?
In ibmvfc_channel_setup_done(), the driver reads num_scsi_subq_channels and
num_nvme_subq_channels from the VIOS response into active_queues without
validating them against the dynamically allocated max_queues array size:
ibmvfc_channel_setup_done() {
scsi->active_queues = be32_to_cpu(setup->num_scsi_subq_channels);
nvme->active_queues = be32_to_cpu(setup->num_nvme_subq_channels);
...
}
The loop just above this patch hunk then directly uses this unvalidated value
to write to the scrqs array:
ibmvfc_channel_setup_done() {
...
for (i = 0; i < scsi->active_queues; i++)
scsi->scrqs[i].vios_cookie =
be64_to_cpu(setup->channel_handles[i]);
...
}
Could a compromised or malfunctioning VIOS provide values that exceed the
locally allocated array size and cause a buffer overflow here?
[ ... ]
> @@ -7055,6 +7087,9 @@ static void ibmvfc_release_channels(struct ibmvfc_host *vhost,
> static void ibmvfc_release_sub_crqs(struct ibmvfc_host *vhost)
> {
> ENTER;
> + ibmvfc_deregister_channel(vhost, &vhost->scsi_scrqs, -1);
> + ibmvfc_free_queue(vhost, &vhost->async_sub_crq);
> +
> if (!vhost->scsi_scrqs.scrqs)
> return;
[Severity: High]
Does this unconditional teardown cause a NULL pointer dereference when
multi-queue is disabled?
During module unload or reset, if multi-queue was disabled (mq_enabled=0) or
initialization failed, vhost->async_sub_crq is left zero-initialized.
Unconditionally calling ibmvfc_deregister_channel() routes to a cleanup path
that behaves as follows:
ibmvfc_deregister_channel() {
...
/* Clean out the queue */
memset(scrq->msgs.crq, 0, PAGE_SIZE);
...
}
Since msgs.crq aliases the unallocated msgs.handle (which is NULL), will this
memset() write 4KB of zeroes to a NULL pointer and cause a deterministic
kernel panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=7
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events
2026-08-31 19:00 ` [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
@ 2026-08-31 22:56 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-08-31 22:56 UTC (permalink / raw)
To: Dave Marquardt; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `ibmvfc_extended_fpin_test` test suite is entirely invalid due to missing KUnit assertions and incorrect statistics targeting.
--
commit 7f2470a8cbfd29182f9253c971ce356e69e43c8e
Author: Dave Marquardt <davemarq@linux.ibm.com>
scsi: ibmvfc: handle extended FPIN events
Implement support for extended FPIN messages received via the asynchronous
sub-queue. Add parsing and processing logic to convert these into
fc_els_fpin structures. Include comprehensive KUnit tests to validate the
extended FPIN event handling.
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
> index 47f7c4e79cd81..b016c7da24cd6 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
[ ... ]
> +#define IBMVFC_TEST_FPIN_EXT(fs, ev, stat, crq) { \
> + struct ibmvfc_async_crq_event ae = { .type = IBMVFC_ASYNC_CRQ_SUB }; \
> + (crq).valid = 0x80; \
> + (crq).flags = IBMVFC_ASYNC_IS_FPIN_EXT; \
> + (crq).link_state = IBMVFC_AE_LS_LINK_UP; \
> + (crq).fpin_status = (fs); \
> + (crq).event = cpu_to_be16(IBMVFC_AE_FPIN); \
> + (crq).wwpn = cpu_to_be64(tgt->wwpn); \
> + (crq).fpin_data.flags = IBMVFC_FPIN_EVENT_TYPE_VALID; \
> + (crq).fpin_data.event_type = cpu_to_be16((ev)); \
> + ae.subq = *(struct ibmvfc_async_sub_crq *)&(crq); \
> + pre = READ_ONCE(tgt->rport->fpin_stats.stat); \
> + ibmvfc_handle_async(&ae, vhost); \
> + flush_workqueue(vhost->fpin_workq); \
> + post = READ_ONCE(tgt->rport->fpin_stats.stat); \
> +}
[Severity: Medium]
Does this macro actually verify the statistics have changed? It captures the
pre and post state, but doesn't appear to perform any KUnit assertions
(like KUNIT_EXPECT_GE) to validate the test results.
Additionally, this macro always queries tgt->rport->fpin_stats for all events.
Is this correct for host-level link congestion events which might update
fc_host->fpin_stats instead?
> +
> +/**
> + * ibmvfc_extended_fpin_test - unit test for extended FPIN events
> + * @test: pointer to kunit structure
> + *
> + * Tests
> + *
> + * Return: void
> + */
> +static void ibmvfc_extended_fpin_test(struct kunit *test)
> +{
> + enum ibmvfc_ae_fpin_status fs;
[ ... ]
> + for (fs = IBMVFC_AE_FPIN_LINK_CONGESTED; fs <= IBMVFC_AE_FPIN_CONGESTION_CLEARED; fs++) {
> + switch (fs) {
> + case IBMVFC_AE_FPIN_PORT_CLEARED:
> + case IBMVFC_AE_FPIN_CONGESTION_CLEARED: {
> + struct ibmvfc_async_crq_event ae = { .type = IBMVFC_ASYNC_CRQ_SUB };
> +
> + crq[fs].valid = 0x80;
> + crq[fs].flags = IBMVFC_ASYNC_IS_FPIN_EXT;
> + crq[fs].link_state = IBMVFC_AE_LS_LINK_UP;
> + crq[fs].fpin_status = fs;
> + crq[fs].event = cpu_to_be16(IBMVFC_AE_FPIN);
> + crq[fs].wwpn = cpu_to_be64(tgt->wwpn);
> + crq[fs].fpin_data.flags = IBMVFC_FPIN_EVENT_TYPE_VALID;
> + crq[fs].fpin_data.event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
> + ae.subq = *(struct ibmvfc_async_sub_crq *)&crq[fs];
> + pre = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
> + ibmvfc_handle_async(&ae, vhost);
> + flush_workqueue(vhost->fpin_workq);
> + post = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
> + break;
> + }
[Severity: Medium]
Similar to the macro above, is there a missing KUnit assertion here in
ibmvfc_extended_fpin_test() to validate that the statistics were properly
updated?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831-ibmvfc-fpin-support-v7-0-77d0b9809080@linux.ibm.com?part=8
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-31 22:56 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 19:00 [PATCH v7 0/8] scsi: ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 1/8] scsi: ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
2026-08-31 21:30 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 2/8] scsi: ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
2026-08-31 21:40 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 3/8] scsi: ibmvfc: add FPIN extended flag and async sub-CRQ queue handle Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 4/8] scsi: ibmvfc: extend async event handlers to handle async sub queue events Dave Marquardt via B4 Relay
2026-08-31 19:00 ` [PATCH v7 5/8] scsi: ibmvfc: add interrupt routine for asynchronous sub CRQ Dave Marquardt via B4 Relay
2026-08-31 22:18 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 6/8] scsi: ibmvfc: extend channel registration and deregistration for async subq Dave Marquardt via B4 Relay
2026-08-31 22:34 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 7/8] scsi: ibmvfc: register and use asynchronous sub CRQ for events Dave Marquardt via B4 Relay
2026-08-31 22:46 ` sashiko-bot
2026-08-31 19:00 ` [PATCH v7 8/8] scsi: ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
2026-08-31 22:56 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox