* [PATCH v2 1/7] ibmvfc: add basic FPIN support
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-12 22:35 ` Tyrel Datwyler
2026-06-08 18:30 ` [PATCH v2 2/7] ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
` (5 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
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 basic FPIN messages to the ibmvfc driver. This includes
- adding FPIN handling support to the async event handler
- offloading processing of FPIN messages to a work queue
- converting the VIOS FPIN message to a struct fc_els_fpin as used by
the Linux kernel
- passing the converted struct fc_els_fpin to fc_host_fpin_rcv for
processing
The FPIN message conversion routines include a common routine that
will also be used in patches 6 and 7, which add full and extended FPIN
support.
---
drivers/scsi/Kconfig | 10 ++
drivers/scsi/ibmvscsi/Makefile | 1 +
drivers/scsi/ibmvscsi/ibmvfc.c | 226 ++++++++++++++++++++++++++++++++++-
drivers/scsi/ibmvscsi/ibmvfc.h | 15 +++
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 131 ++++++++++++++++++++
5 files changed, 379 insertions(+), 4 deletions(-)
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index c3042393af23..d5fc7eb2ebb1 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -758,6 +758,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 5eb1cb1a0028..75dc7aee15a0 100644
--- a/drivers/scsi/ibmvscsi/Makefile
+++ b/drivers/scsi/ibmvscsi/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
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.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index 3dd2adda195e..9e5f0c0f0369 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -30,6 +30,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;
@@ -3137,6 +3140,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 = {
@@ -3185,17 +3189,211 @@ 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 modifier,
+ __be32 period, __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 == 0)
+ return NULL;
+
+ fpin = kzalloc(size, GFP_KERNEL);
+ if (fpin == NULL)
+ 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));
+ if (fpin_status == IBMVFC_AE_FPIN_CONGESTION_CLEARED)
+ cdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
+ else
+ cdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ cdesc->event_modifier = modifier;
+ cdesc->event_period = 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(FC_TLV_DESC_LENGTH_FROM_SZ(*pdesc));
+ if (fpin_status == IBMVFC_AE_FPIN_PORT_CLEARED)
+ pdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
+ else
+ pdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ pdesc->event_modifier = modifier;
+ pdesc->event_period = period;
+ pdesc->detecting_wwpn = cpu_to_be64(0);
+ 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(FC_TLV_DESC_LENGTH_FROM_SZ(*ldesc));
+ ldesc->event_type = cpu_to_be16(FPIN_LI_UNKNOWN);
+ ldesc->event_modifier = modifier;
+ ldesc->event_threshold = threshold;
+ ldesc->event_count = event_count;
+ ldesc->detecting_wwpn = cpu_to_be64(0);
+ 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)
+{
+ return ibmvfc_common_fpin_to_desc(crq->fpin_status, cpu_to_be64(wwpn),
+ cpu_to_be16(0),
+ cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD),
+ cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
+ cpu_to_be32(1));
+}
+
+/**
+ * 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;
+
+ aw = container_of(work, struct ibmvfc_async_work, async_work_s);
+ crq = aw->crq;
+ vhost = aw->vhost;
+
+ if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ goto end;
+ list_for_each_entry(tgt, &vhost->targets, queue) {
+ if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id)
+ continue;
+ if (crq->wwpn && cpu_to_be64(tgt->ids.port_name) != crq->wwpn)
+ continue;
+ if (crq->node_name && cpu_to_be64(tgt->ids.node_name) != crq->node_name)
+ continue;
+ if (!tgt->rport)
+ continue;
+ 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 %u received, unable to process\n",
+ crq->fpin_status);
+ }
+
+ end:
+ crq->valid = 0;
+
+ 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;
+ bool clear_valid = true;
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),
@@ -3269,11 +3467,27 @@ 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) {
+ clear_valid = false;
+ INIT_WORK(&aw->async_work_s, ibmvfc_process_async_work);
+ aw->vhost = vhost;
+ aw->crq = crq;
+ schedule_work(&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;
}
+
+ if (clear_valid)
+ crq->valid = 0;
}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_handle_async);
/**
* ibmvfc_handle_crq - Handles and frees received events in the CRQ
@@ -3803,7 +4017,6 @@ static void ibmvfc_tasklet(void *data)
/* Pull all the valid messages off the async CRQ */
while ((async = ibmvfc_next_async_crq(vhost)) != NULL) {
ibmvfc_handle_async(async, vhost);
- async->valid = 0;
wmb();
}
@@ -3818,7 +4031,6 @@ static void ibmvfc_tasklet(void *data)
if ((async = ibmvfc_next_async_crq(vhost)) != NULL) {
vio_disable_interrupts(vdev);
ibmvfc_handle_async(async, vhost);
- async->valid = 0;
wmb();
} else if ((crq = ibmvfc_next_crq(vhost)) != NULL) {
vio_disable_interrupts(vdev);
@@ -6603,5 +6815,11 @@ static void __exit ibmvfc_module_exit(void)
fc_release_transport(ibmvfc_transport_template);
}
+VISIBLE_IF_KUNIT struct list_head *ibmvfc_get_headp(void)
+{
+ return &ibmvfc_head;
+}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_get_headp);
+
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 c73ed2314ad0..8eb1493cbac8 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -671,8 +671,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;
@@ -686,6 +690,12 @@ struct ibmvfc_async_crq {
__be64 reserved;
} __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;
@@ -953,4 +963,9 @@ struct ibmvfc_host {
#define ibmvfc_remove_trace_file(kobj, attr) do { } while (0)
#endif
+#ifdef VISIBLE_IF_KUNIT
+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..e41e2a49e549
--- /dev/null
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -0,0 +1,131 @@
+// 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;
+
+ headp = ibmvfc_get_headp();
+ KUNIT_ASSERT_FALSE_MSG(test, list_empty(headp), "No ibmvfc devices available\n");
+ queue = headp->next;
+ vhost = container_of(queue, struct ibmvfc_host, queue);
+
+ KUNIT_ASSERT_GE_MSG(test, vhost->num_targets, 1, "No targets");
+ tgt = list_first_entry(&vhost->targets, struct ibmvfc_target, queue);
+ KUNIT_EXPECT_NOT_NULL(test, tgt->rport);
+
+ 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);
+ 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);
+ }
+
+ msleep(500U);
+
+ 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);
+
+ 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);
+ 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);
+
+ /* bad path */
+ 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);
+
+ msleep(500U);
+
+ 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_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]);
+}
+
+static struct kunit_case ibmvfc_fpin_test_cases[] = {
+ KUNIT_CASE_SLOW(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.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v2 1/7] ibmvfc: add basic FPIN support
2026-06-08 18:30 ` [PATCH v2 1/7] ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
@ 2026-06-12 22:35 ` Tyrel Datwyler
0 siblings, 0 replies; 10+ messages in thread
From: Tyrel Datwyler @ 2026-06-12 22:35 UTC (permalink / raw)
To: davemarq, James E.J. Bottomley, Martin K. Petersen,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP)
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch
On 6/8/26 11:30 AM, Dave Marquardt via B4 Relay wrote:
> From: Dave Marquardt <davemarq@linux.ibm.com>
>
> Add support for basic FPIN messages to the ibmvfc driver. This includes
>
> - adding FPIN handling support to the async event handler
> - offloading processing of FPIN messages to a work queue
> - converting the VIOS FPIN message to a struct fc_els_fpin as used by
> the Linux kernel
> - passing the converted struct fc_els_fpin to fc_host_fpin_rcv for
> processing
>
> The FPIN message conversion routines include a common routine that
> will also be used in patches 6 and 7, which add full and extended FPIN
> support.
You are missing a Signed-off-by tag here.
> ---
> drivers/scsi/Kconfig | 10 ++
> drivers/scsi/ibmvscsi/Makefile | 1 +
> drivers/scsi/ibmvscsi/ibmvfc.c | 226 ++++++++++++++++++++++++++++++++++-
> drivers/scsi/ibmvscsi/ibmvfc.h | 15 +++
> drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 131 ++++++++++++++++++++
> 5 files changed, 379 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
> index c3042393af23..d5fc7eb2ebb1 100644
> --- a/drivers/scsi/Kconfig
> +++ b/drivers/scsi/Kconfig
> @@ -758,6 +758,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 5eb1cb1a0028..75dc7aee15a0 100644
> --- a/drivers/scsi/ibmvscsi/Makefile
> +++ b/drivers/scsi/ibmvscsi/Makefile
> @@ -1,3 +1,4 @@
> # SPDX-License-Identifier: GPL-2.0-only
> 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.c b/drivers/scsi/ibmvscsi/ibmvfc.c
> index 3dd2adda195e..9e5f0c0f0369 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc.c
> @@ -30,6 +30,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;
> @@ -3137,6 +3140,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 = {
> @@ -3185,17 +3189,211 @@ 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 modifier,
> + __be32 period, __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 == 0)
To be consistent with other places in the driver !size instead of size == 0 check.
> + return NULL;
> +
> + fpin = kzalloc(size, GFP_KERNEL);
> + if (fpin == NULL)
Same nit here !fpin instead of fpin == NULL.
> + 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));
> + if (fpin_status == IBMVFC_AE_FPIN_CONGESTION_CLEARED)
> + cdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
> + else
> + cdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
> + cdesc->event_modifier = modifier;
> + cdesc->event_period = 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(FC_TLV_DESC_LENGTH_FROM_SZ(*pdesc));
> + if (fpin_status == IBMVFC_AE_FPIN_PORT_CLEARED)
> + pdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
> + else
> + pdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
> + pdesc->event_modifier = modifier;
> + pdesc->event_period = period;
> + pdesc->detecting_wwpn = cpu_to_be64(0);
While not wrong 0 is 0 regardless of endianiess. Is this value always going to
be zero here or is this place holder code that changes later in this patchset?
If its the case this isn't a place holder the original fpin struct was allocated
with kzalloc so this value should be zero already.
> + 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(FC_TLV_DESC_LENGTH_FROM_SZ(*ldesc));
> + ldesc->event_type = cpu_to_be16(FPIN_LI_UNKNOWN);
> + ldesc->event_modifier = modifier;
> + ldesc->event_threshold = threshold;
> + ldesc->event_count = event_count;
> + ldesc->detecting_wwpn = cpu_to_be64(0);
Same comment as above.
> + 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)
> +{
> + return ibmvfc_common_fpin_to_desc(crq->fpin_status, cpu_to_be64(wwpn),
> + cpu_to_be16(0),
> + cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD),
> + cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
> + cpu_to_be32(1));
> +}
> +
> +/**
> + * 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;
> +
> + aw = container_of(work, struct ibmvfc_async_work, async_work_s);
> + crq = aw->crq;
> + vhost = aw->vhost;
> +
> + if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
> + goto end;
> + list_for_each_entry(tgt, &vhost->targets, queue) {
Should we be holding the host lock when iterateing targets?
-Tyrel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 2/7] ibmvfc: Add NOOP command support
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 1/7] ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 3/7] ibmvfc: make ibmvfc login to fabric Dave Marquardt via B4 Relay
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
This patch adds support for receiving and recognizing VFC_NOOP
messages from VIOS. This is done by
- defining the VFC_NOOP CRQ format
- recognizing the VFC_NOOP CRQ format in the CRQ handler. If the VIOS
has not provided the "support VFC_NOOP" bit in its capabilities on
NPIV login, note that the VFC_NOOP is unexpected
- setting the "can use VFC_NOOP" bit in the capabilities sent during
NPIV login
---
drivers/scsi/ibmvscsi/ibmvfc.c | 11 ++++++++++-
drivers/scsi/ibmvscsi/ibmvfc.h | 3 +++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index 9e5f0c0f0369..88386d7c9106 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -1512,7 +1512,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);
@@ -3555,6 +3557,13 @@ static void ibmvfc_handle_crq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost,
if (crq->format == IBMVFC_ASYNC_EVENT)
return;
+ if (crq->format == IBMVFC_VFC_NOOP) {
+ if (!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
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index 8eb1493cbac8..dd26248cac3e 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -180,6 +180,7 @@ struct ibmvfc_npiv_login {
#define IBMVFC_CAN_HANDLE_FPIN 0x04
#define IBMVFC_CAN_USE_MAD_VERSION 0x08
#define IBMVFC_CAN_SEND_VF_WWPN 0x10
+#define IBMVFC_CAN_USE_NOOP_CMD 0x200
__be64 node_name;
struct srp_direct_buf async;
u8 partition_name[IBMVFC_MAX_NAME];
@@ -226,6 +227,7 @@ struct ibmvfc_npiv_login_resp {
#define IBMVFC_MAD_VERSION_CAP 0x20
#define IBMVFC_HANDLE_VF_WWPN 0x40
#define IBMVFC_CAN_SUPPORT_CHANNELS 0x80
+#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
__be32 max_cmds;
__be32 scsi_id_sz;
__be64 max_dma_len;
@@ -621,6 +623,7 @@ struct ibmvfc_trace_entry {
enum ibmvfc_crq_formats {
IBMVFC_CMD_FORMAT = 0x01,
IBMVFC_ASYNC_EVENT = 0x02,
+ IBMVFC_VFC_NOOP = 0x03,
IBMVFC_MAD_FORMAT = 0x04,
};
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 3/7] ibmvfc: make ibmvfc login to fabric
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 1/7] ibmvfc: add basic FPIN support Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 2/7] ibmvfc: Add NOOP command support Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-12 23:11 ` Tyrel Datwyler
2026-06-08 18:30 ` [PATCH v2 4/7] ibmvfc: define asynchronous sub-queue Dave Marquardt via B4 Relay
` (3 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
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 fabric login in order to support the asynchronous
event queue with its own interrupt as required by NPIV specification
to support the asynchronous sub-queue and interrupt in order to
support full and extended FPIN messages.
---
drivers/scsi/ibmvscsi/ibmvfc.c | 94 ++++++++++++++++++++++++++++++++++++++++--
drivers/scsi/ibmvscsi/ibmvfc.h | 16 +++++++
2 files changed, 106 insertions(+), 4 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index 88386d7c9106..a18861808325 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -5244,6 +5244,86 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost)
ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
}
+static void ibmvfc_fabric_login_done(struct ibmvfc_event *evt)
+{
+ struct ibmvfc_fabric_login *rsp = &evt->xfer_iu->fabric_login;
+ u32 mad_status = be16_to_cpu(rsp->common.status);
+ struct ibmvfc_host *vhost = evt->vhost;
+ int level = IBMVFC_DEFAULT_LOG_LEVEL;
+
+ ENTER;
+
+ switch (mad_status) {
+ case IBMVFC_MAD_SUCCESS:
+ fc_host_port_id(vhost->host) = be64_to_cpu(rsp->nport_id);
+ ibmvfc_free_event(evt);
+ break;
+
+ case IBMVFC_MAD_FAILED:
+ if (ibmvfc_retry_cmd(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)))
+ level += ibmvfc_retry_host_init(vhost);
+ else
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
+ ibmvfc_log(vhost, level, "Fabric Login failed: %s (%x:%x)\n",
+ ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)),
+ be16_to_cpu(rsp->status), be16_to_cpu(rsp->error));
+ ibmvfc_free_event(evt);
+ LEAVE;
+ return;
+
+ case IBMVFC_MAD_CRQ_ERROR:
+ ibmvfc_retry_host_init(vhost);
+ fallthrough;
+
+ case IBMVFC_MAD_DRIVER_FAILED:
+ ibmvfc_free_event(evt);
+ LEAVE;
+ return;
+
+ default:
+ dev_err(vhost->dev, "Invalid fabric Login response: 0x%x\n", mad_status);
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
+ ibmvfc_free_event(evt);
+ LEAVE;
+ return;
+ }
+
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
+ wake_up(&vhost->work_wait_q);
+
+ LEAVE;
+}
+
+static void ibmvfc_fabric_login(struct ibmvfc_host *vhost)
+{
+ struct ibmvfc_fabric_login *mad;
+ struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq);
+ int level = IBMVFC_DEFAULT_LOG_LEVEL;
+
+ if (!evt) {
+ ibmvfc_log(vhost, level, "Fabric Login failed: no available events\n");
+ ibmvfc_hard_reset_host(vhost);
+ return;
+ }
+
+ ibmvfc_init_event(evt, ibmvfc_fabric_login_done, IBMVFC_MAD_FORMAT);
+ mad = &evt->iu.fabric_login;
+ memset(mad, 0, sizeof(*mad));
+ if (vhost->scsi_scrqs.protocol == IBMVFC_PROTO_SCSI)
+ mad->common.opcode = cpu_to_be32(IBMVFC_FABRIC_LOGIN);
+ else {
+ ibmvfc_log(vhost, level, "Fabric Login failed: unknown protocol\n");
+ return;
+ }
+ mad->common.version = cpu_to_be32(1);
+ mad->common.length = cpu_to_be16(sizeof(*mad));
+
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT_WAIT);
+
+ if (ibmvfc_send_event(evt, vhost, default_timeout))
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DOWN);
+}
+
static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt)
{
struct ibmvfc_host *vhost = evt->vhost;
@@ -5290,8 +5370,12 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt)
return;
}
- ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
- wake_up(&vhost->work_wait_q);
+ if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_SCSI)) {
+ ibmvfc_fabric_login(vhost);
+ } else {
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
+ wake_up(&vhost->work_wait_q);
+ }
}
static void ibmvfc_channel_setup(struct ibmvfc_host *vhost)
@@ -5482,9 +5566,11 @@ static void ibmvfc_npiv_login_done(struct ibmvfc_event *evt)
vhost->host->can_queue = be32_to_cpu(rsp->max_cmds) - IBMVFC_NUM_INTERNAL_REQ;
vhost->host->max_sectors = npiv_max_sectors;
- if (ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPORT_CHANNELS) && vhost->do_enquiry) {
+ if (ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPORT_CHANNELS) && vhost->do_enquiry)
ibmvfc_channel_enquiry(vhost);
- } else {
+ else if (ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_SCSI))
+ ibmvfc_fabric_login(vhost);
+ else {
vhost->do_enquiry = 0;
ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
wake_up(&vhost->work_wait_q);
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index dd26248cac3e..c996b36d335d 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -138,6 +138,7 @@ enum ibmvfc_mad_types {
IBMVFC_CHANNEL_ENQUIRY = 0x1000,
IBMVFC_CHANNEL_SETUP = 0x2000,
IBMVFC_CONNECTION_INFO = 0x4000,
+ IBMVFC_FABRIC_LOGIN = 0x8000,
};
struct ibmvfc_mad_common {
@@ -227,6 +228,7 @@ struct ibmvfc_npiv_login_resp {
#define IBMVFC_MAD_VERSION_CAP 0x20
#define IBMVFC_HANDLE_VF_WWPN 0x40
#define IBMVFC_CAN_SUPPORT_CHANNELS 0x80
+#define IBMVFC_SUPPORT_SCSI 0x200
#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
__be32 max_cmds;
__be32 scsi_id_sz;
@@ -590,6 +592,19 @@ struct ibmvfc_connection_info {
__be64 reserved[16];
} __packed __aligned(8);
+struct ibmvfc_fabric_login {
+ struct ibmvfc_mad_common common;
+ __be64 flags;
+#define IBMVFC_STRIP_MERGE 0x1
+#define IBMVFC_LINK_COMMANDS 0x2
+ __be64 capabilities;
+ __be64 nport_id;
+ __be16 status;
+ __be16 error;
+ __be32 pad;
+ __be64 reserved[16];
+} __packed __aligned(8);
+
struct ibmvfc_trace_start_entry {
u32 xfer_len;
} __packed;
@@ -715,6 +730,7 @@ union ibmvfc_iu {
struct ibmvfc_channel_enquiry channel_enquiry;
struct ibmvfc_channel_setup_mad channel_setup;
struct ibmvfc_connection_info connection_info;
+ struct ibmvfc_fabric_login fabric_login;
} __packed __aligned(8);
enum ibmvfc_target_action {
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v2 3/7] ibmvfc: make ibmvfc login to fabric
2026-06-08 18:30 ` [PATCH v2 3/7] ibmvfc: make ibmvfc login to fabric Dave Marquardt via B4 Relay
@ 2026-06-12 23:11 ` Tyrel Datwyler
0 siblings, 0 replies; 10+ messages in thread
From: Tyrel Datwyler @ 2026-06-12 23:11 UTC (permalink / raw)
To: davemarq, James E.J. Bottomley, Martin K. Petersen,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP)
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch
On 6/8/26 11:30 AM, Dave Marquardt via B4 Relay wrote:
> From: Dave Marquardt <davemarq@linux.ibm.com>
>
> Add support for fabric login in order to support the asynchronous
> event queue with its own interrupt as required by NPIV specification
> to support the asynchronous sub-queue and interrupt in order to
> support full and extended FPIN messages.
> ---
> drivers/scsi/ibmvscsi/ibmvfc.c | 94 ++++++++++++++++++++++++++++++++++++++++--
> drivers/scsi/ibmvscsi/ibmvfc.h | 16 +++++++
> 2 files changed, 106 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
> index 88386d7c9106..a18861808325 100644
> --- a/drivers/scsi/ibmvscsi/ibmvfc.c
> +++ b/drivers/scsi/ibmvscsi/ibmvfc.c
> @@ -5244,6 +5244,86 @@ static void ibmvfc_discover_targets(struct ibmvfc_host *vhost)
> ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
> }
>
> +static void ibmvfc_fabric_login_done(struct ibmvfc_event *evt)
> +{
> + struct ibmvfc_fabric_login *rsp = &evt->xfer_iu->fabric_login;
> + u32 mad_status = be16_to_cpu(rsp->common.status);
> + struct ibmvfc_host *vhost = evt->vhost;
> + int level = IBMVFC_DEFAULT_LOG_LEVEL;
> +
> + ENTER;
> +
> + switch (mad_status) {
> + case IBMVFC_MAD_SUCCESS:
> + fc_host_port_id(vhost->host) = be64_to_cpu(rsp->nport_id);
> + ibmvfc_free_event(evt);
> + break;
> +
> + case IBMVFC_MAD_FAILED:
> + if (ibmvfc_retry_cmd(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)))
> + level += ibmvfc_retry_host_init(vhost);
> + else
> + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
> + ibmvfc_log(vhost, level, "Fabric Login failed: %s (%x:%x)\n",
> + ibmvfc_get_cmd_error(be16_to_cpu(rsp->status), be16_to_cpu(rsp->error)),
> + be16_to_cpu(rsp->status), be16_to_cpu(rsp->error));
> + ibmvfc_free_event(evt);
> + LEAVE;
> + return;
> +
> + case IBMVFC_MAD_CRQ_ERROR:
> + ibmvfc_retry_host_init(vhost);
> + fallthrough;
> +
> + case IBMVFC_MAD_DRIVER_FAILED:
> + ibmvfc_free_event(evt);
> + LEAVE;
> + return;
> +
> + default:
> + dev_err(vhost->dev, "Invalid fabric Login response: 0x%x\n", mad_status);
> + ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
> + ibmvfc_free_event(evt);
> + LEAVE;
> + return;
> + }
> +
> + ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
> + wake_up(&vhost->work_wait_q);
> +
> + LEAVE;
> +}
> +
> +static void ibmvfc_fabric_login(struct ibmvfc_host *vhost)
> +{
> + struct ibmvfc_fabric_login *mad;
> + struct ibmvfc_event *evt = ibmvfc_get_reserved_event(&vhost->crq);
> + int level = IBMVFC_DEFAULT_LOG_LEVEL;
> +
> + if (!evt) {
> + ibmvfc_log(vhost, level, "Fabric Login failed: no available events\n");
> + ibmvfc_hard_reset_host(vhost);
> + return;
> + }
> +
> + ibmvfc_init_event(evt, ibmvfc_fabric_login_done, IBMVFC_MAD_FORMAT);
> + mad = &evt->iu.fabric_login;
> + memset(mad, 0, sizeof(*mad));
> + if (vhost->scsi_scrqs.protocol == IBMVFC_PROTO_SCSI)
> + mad->common.opcode = cpu_to_be32(IBMVFC_FABRIC_LOGIN);
> + else {
> + ibmvfc_log(vhost, level, "Fabric Login failed: unknown protocol\n");
> + return;
> + }
This check is pretty pedantic. Seeing as you are directly referencing the scsi
sub-crqs. The protocol field exists so we can pass the sub-crqs blindly and the
code once NVMf comes along can determine the protocol.
Also, if somehow this was ever possibly the case you would leak the event
structure. I think we can drop the check all together.
-Tyrel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 4/7] ibmvfc: define asynchronous sub-queue
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (2 preceding siblings ...)
2026-06-08 18:30 ` [PATCH v2 3/7] ibmvfc: make ibmvfc login to fabric Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 5/7] ibmvfc: allocate " Dave Marquardt via B4 Relay
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Adds the asynchronous sub-queue structure, modifies the existing
channel setup structure, adds the asynchronous sub-queue to the
channels structure, and adds flags needed to tell VIOS to use the
sub-queue.
---
drivers/scsi/ibmvscsi/ibmvfc.h | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index c996b36d335d..f026f30f98d3 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -181,6 +181,8 @@ struct ibmvfc_npiv_login {
#define IBMVFC_CAN_HANDLE_FPIN 0x04
#define IBMVFC_CAN_USE_MAD_VERSION 0x08
#define IBMVFC_CAN_SEND_VF_WWPN 0x10
+#define IBMVFC_YES_SCSI 0x40
+#define IBMVFC_USE_ASYNC_SUBQ 0x100
#define IBMVFC_CAN_USE_NOOP_CMD 0x200
__be64 node_name;
struct srp_direct_buf async;
@@ -229,6 +231,7 @@ struct ibmvfc_npiv_login_resp {
#define IBMVFC_HANDLE_VF_WWPN 0x40
#define IBMVFC_CAN_SUPPORT_CHANNELS 0x80
#define IBMVFC_SUPPORT_SCSI 0x200
+#define IBMVFC_SUPPORT_ASYNC_SUBQ 0x800
#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
__be32 max_cmds;
__be32 scsi_id_sz;
@@ -563,7 +566,7 @@ struct ibmvfc_channel_setup_mad {
struct srp_direct_buf buffer;
} __packed __aligned(8);
-#define IBMVFC_MAX_CHANNELS 502
+#define IBMVFC_MAX_CHANNELS 501
struct ibmvfc_channel_setup {
__be32 flags;
@@ -578,6 +581,7 @@ struct ibmvfc_channel_setup {
struct srp_direct_buf buffer;
__be64 reserved2[5];
__be64 channel_handles[IBMVFC_MAX_CHANNELS];
+ __be64 asyncSubqHandle;
} __packed __aligned(8);
struct ibmvfc_connection_info {
@@ -714,6 +718,25 @@ struct ibmvfc_async_work {
struct work_struct async_work_s;
};
+struct ibmvfc_async_subq {
+ volatile u8 valid;
+#define IBMVFC_ASYNC_ID_IS_ASSOC_ID 0x01
+#define IBMVFC_FC_EEH 0x04
+#define IBMVFC_FC_FW_UPDATE 0x08
+#define IBMVFC_FC_FW_DUMP 0x10
+ u8 flags;
+ u8 link_state;
+ u8 fpin_status;
+ __be16 event;
+ __be16 pad;
+ volatile __be64 wwpn;
+ volatile __be64 nport_id;
+ union {
+ __be64 node_name;
+ __be64 assoc_id;
+ } id;
+} __packed __aligned(8);
+
union ibmvfc_iu {
struct ibmvfc_mad_common mad_common;
struct ibmvfc_npiv_login_mad npiv_login;
@@ -853,6 +876,7 @@ struct ibmvfc_queue {
struct ibmvfc_channels {
struct ibmvfc_queue *scrqs;
+ struct ibmvfc_queue *async_scrq;
enum ibmvfc_protocol protocol;
unsigned int active_queues;
unsigned int desired_queues;
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 5/7] ibmvfc: allocate asynchronous sub-queue
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (3 preceding siblings ...)
2026-06-08 18:30 ` [PATCH v2 4/7] ibmvfc: define asynchronous sub-queue Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 6/7] ibmvfc: register and use " Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 7/7] ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
6 siblings, 0 replies; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Allocate and set up the asynchronous sub-queue for asynchronous
events, as required for full and extended FPIN support.
---
drivers/scsi/ibmvscsi/ibmvfc.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index a18861808325..ad1f5636e879 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -5352,6 +5352,8 @@ static void ibmvfc_channel_setup_done(struct ibmvfc_event *evt)
for (i = 0; i < active_queues; i++)
scrqs->scrqs[i].vios_cookie =
be64_to_cpu(setup->channel_handles[i]);
+ scrqs->async_scrq->vios_cookie =
+ be64_to_cpu(setup->asyncSubqHandle);
ibmvfc_dbg(vhost, "Using %u channels\n",
vhost->scsi_scrqs.active_queues);
@@ -5402,6 +5404,7 @@ static void ibmvfc_channel_setup(struct ibmvfc_host *vhost)
setup_buf->num_scsi_subq_channels = cpu_to_be32(num_channels);
for (i = 0; i < num_channels; i++)
setup_buf->channel_handles[i] = cpu_to_be64(scrqs->scrqs[i].cookie);
+ setup_buf->asyncSubqHandle = cpu_to_be64(scrqs->async_scrq->cookie);
}
ibmvfc_init_event(evt, ibmvfc_channel_setup_done, IBMVFC_MAD_FORMAT);
@@ -6369,6 +6372,24 @@ static int ibmvfc_alloc_channels(struct ibmvfc_host *vhost,
if (!channels->scrqs)
return -ENOMEM;
+ channels->async_scrq = kzalloc_obj(*channels->async_scrq, GFP_KERNEL);
+
+ if (!channels->async_scrq) {
+ kfree(channels->scrqs);
+ channels->scrqs = NULL;
+ return -ENOMEM;
+ }
+
+ rc = ibmvfc_alloc_queue(vhost, channels->async_scrq,
+ IBMVFC_SUB_CRQ_FMT);
+ if (rc) {
+ kfree(channels->scrqs);
+ channels->scrqs = NULL;
+ kfree(channels->async_scrq);
+ channels->async_scrq = NULL;
+ return rc;
+ }
+
for (i = 0; i < channels->max_queues; i++) {
scrq = &channels->scrqs[i];
rc = ibmvfc_alloc_queue(vhost, scrq, IBMVFC_SUB_CRQ_FMT);
@@ -6380,6 +6401,9 @@ static int ibmvfc_alloc_channels(struct ibmvfc_host *vhost,
kfree(channels->scrqs);
channels->scrqs = NULL;
channels->active_queues = 0;
+ ibmvfc_free_queue(vhost, channels->async_scrq);
+ kfree(channels->async_scrq);
+ channels->async_scrq = NULL;
return rc;
}
}
@@ -6418,6 +6442,10 @@ static void ibmvfc_release_channels(struct ibmvfc_host *vhost,
kfree(channels->scrqs);
channels->scrqs = NULL;
+
+ ibmvfc_free_queue(vhost, channels->async_scrq);
+ channels->async_scrq = NULL;
+
channels->active_queues = 0;
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 6/7] ibmvfc: register and use asynchronous sub-queue
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (4 preceding siblings ...)
2026-06-08 18:30 ` [PATCH v2 5/7] ibmvfc: allocate " Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
2026-06-08 18:30 ` [PATCH v2 7/7] ibmvfc: handle extended FPIN events Dave Marquardt via B4 Relay
6 siblings, 0 replies; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Refactor existing code for async events into a common routine,
register a channel and interrupt handler for the asynchronous
sub-queue, and set capability bits to request that VIOS use the
asynchronous sub-queue.
---
drivers/scsi/ibmvscsi/ibmvfc.c | 376 +++++++++++++++++++++++++++--------
drivers/scsi/ibmvscsi/ibmvfc.h | 3 +
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 2 +-
3 files changed, 298 insertions(+), 83 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index ad1f5636e879..a2252cd2f44b 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -1514,7 +1514,8 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
login_info->max_cmds = cpu_to_be32(max_cmds);
login_info->capabilities =
cpu_to_be64(IBMVFC_CAN_MIGRATE | IBMVFC_CAN_SEND_VF_WWPN |
- IBMVFC_CAN_USE_NOOP_CMD);
+ IBMVFC_CAN_USE_NOOP_CMD | IBMVFC_YES_SCSI |
+ IBMVFC_USE_ASYNC_SUBQ | IBMVFC_CAN_HANDLE_FPIN);
if (vhost->mq_enabled || vhost->using_channels)
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_CHANNELS);
@@ -3240,8 +3241,8 @@ static size_t ibmvfc_fpin_size_helper(u8 fpin_status)
* 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 modifier,
- __be32 period, __be32 threshold, __be32 event_count)
+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;
@@ -3253,7 +3254,7 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 modifier,
if (size == 0)
return NULL;
- fpin = kzalloc(size, GFP_KERNEL);
+ fpin = kzalloc(size, GFP_ATOMIC);
if (fpin == NULL)
return NULL;
@@ -3266,12 +3267,9 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 modifier,
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));
- if (fpin_status == IBMVFC_AE_FPIN_CONGESTION_CLEARED)
- cdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
- else
- cdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ cdesc->event_type = type;
cdesc->event_modifier = modifier;
- cdesc->event_period = period;
+ cdesc->event_period = cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD);
cdesc->severity = FPIN_CONGN_SEVERITY_WARNING;
break;
case IBMVFC_AE_FPIN_PORT_CONGESTED:
@@ -3281,12 +3279,9 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 modifier,
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(FC_TLV_DESC_LENGTH_FROM_SZ(*pdesc));
- if (fpin_status == IBMVFC_AE_FPIN_PORT_CLEARED)
- pdesc->event_type = cpu_to_be16(FPIN_CONGN_CLEAR);
- else
- pdesc->event_type = cpu_to_be16(FPIN_CONGN_DEVICE_SPEC);
+ pdesc->event_type = type;
pdesc->event_modifier = modifier;
- pdesc->event_period = period;
+ pdesc->event_period = cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD);
pdesc->detecting_wwpn = cpu_to_be64(0);
pdesc->attached_wwpn = wwpn;
pdesc->pname_count = cpu_to_be32(1);
@@ -3297,7 +3292,7 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 modifier,
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(FC_TLV_DESC_LENGTH_FROM_SZ(*ldesc));
- ldesc->event_type = cpu_to_be16(FPIN_LI_UNKNOWN);
+ ldesc->event_type = type;
ldesc->event_modifier = modifier;
ldesc->event_threshold = threshold;
ldesc->event_count = event_count;
@@ -3331,9 +3326,47 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 modifier,
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),
- cpu_to_be16(0),
- cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_PERIOD),
+ type, cpu_to_be16(0),
+ cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
+ 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_subq *ibmvfc_fpin)
+{
+ return ibmvfc_common_fpin_to_desc(ibmvfc_fpin->fpin_status, ibmvfc_fpin->wwpn,
+ cpu_to_be16(0), cpu_to_be16(0),
cpu_to_be32(IBMVFC_FPIN_DEFAULT_EVENT_THRESHOLD),
cpu_to_be32(1));
}
@@ -3344,67 +3377,99 @@ ibmvfc_basic_fpin_to_desc(struct ibmvfc_async_crq *crq, u64 wwpn)
*/
static void ibmvfc_process_async_work(struct work_struct *work)
{
+ struct ibmvfc_async_subq_fpin *sqfpin;
+ struct ibmvfc_target *tgt, *next;
+ struct ibmvfc_async_subq *subq;
struct ibmvfc_async_work *aw;
struct ibmvfc_async_crq *crq;
- struct ibmvfc_target *tgt;
struct ibmvfc_host *vhost;
struct fc_els_fpin *fpin;
+ __be64 node_name;
+ __be64 scsi_id;
+ __be64 wwpn;
aw = container_of(work, struct ibmvfc_async_work, async_work_s);
crq = aw->crq;
+ subq = aw->subq;
vhost = aw->vhost;
- if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ if ((!crq && !subq) || (crq && subq)) {
+ dev_err_ratelimited(vhost->dev,
+ "FPIN event received, unable to process\n");
goto end;
- list_for_each_entry(tgt, &vhost->targets, queue) {
- if (crq->scsi_id && cpu_to_be64(tgt->scsi_id) != crq->scsi_id)
+ }
+
+ if (crq) {
+ wwpn = crq->wwpn;
+ node_name = crq->node_name;
+ scsi_id = crq->scsi_id;
+ } else {
+ wwpn = subq->wwpn;
+ node_name = subq->id.node_name;
+ scsi_id = 0;
+ }
+
+ if (!scsi_id && !wwpn && !node_name)
+ goto end;
+
+ list_for_each_entry_safe(tgt, next, &vhost->targets, queue) {
+ 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->rport)
continue;
- fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
+ if (crq) {
+ fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
+ } else {
+ sqfpin = (struct ibmvfc_async_subq_fpin *)subq;
+ fpin = ibmvfc_full_fpin_to_desc(subq);
+ }
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 %u received, unable to process\n",
- crq->fpin_status);
+ dev_err_ratelimited(vhost->dev, "FPIN event received, unable to process\n");
}
end:
- crq->valid = 0;
+ if (crq)
+ crq->valid = 0;
+ if (subq)
+ subq->valid = 0;
kfree(aw);
}
/**
- * ibmvfc_handle_async - Handle an async event from the adapter
- * @crq: crq to process
+ * ibmvfc_handle_async_common - Handle an async event from the adapter
+ * @event: event to process
+ * @link_state: link state
* @vhost: ibmvfc host struct
+ * @scsi_id: scsi_id (0 if not applicable)
+ * @wwpn: wwpn
+ * @node_name: node_name
+ * @aw_crq: crq pointer for async work (NULL if not needed)
+ * @aw_subq: subq pointer for async work (NULL if not needed)
*
**/
-VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
- struct ibmvfc_host *vhost)
+static void ibmvfc_handle_async_common(u64 event, u8 link_state,
+ struct ibmvfc_host *vhost,
+ u64 scsi_id, u64 wwpn, u64 node_name,
+ struct ibmvfc_async_crq *aw_crq,
+ struct ibmvfc_async_subq *aw_subq)
{
- const struct ibmvfc_async_desc *desc = ibmvfc_get_ae_desc(be64_to_cpu(crq->event));
+ struct ibmvfc_target *tgt, *next;
struct ibmvfc_async_work *aw;
- struct ibmvfc_target *tgt;
bool clear_valid = true;
- 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)) {
+ 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;
@@ -3419,7 +3484,6 @@ VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
__ibmvfc_reset_host(vhost);
break;
}
-
break;
case IBMVFC_AE_LINK_UP:
vhost->events_to_log |= IBMVFC_AE_LINKUP;
@@ -3439,58 +3503,106 @@ VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq,
vhost->events_to_log |= IBMVFC_AE_RSCN;
ibmvfc_reinit_host(vhost);
break;
+ case IBMVFC_AE_LINK_DOWN:
+ case IBMVFC_AE_ADAPTER_FAILED:
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DOWN);
+ break;
+ case IBMVFC_AE_LINK_DEAD:
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
+ break;
+ case IBMVFC_AE_HALT:
+ ibmvfc_link_down(vhost, IBMVFC_HALTED);
+ break;
case IBMVFC_AE_ELS_LOGO:
case IBMVFC_AE_ELS_PRLO:
case IBMVFC_AE_ELS_PLOGI:
- list_for_each_entry(tgt, &vhost->targets, queue) {
- if (!crq->scsi_id && !crq->wwpn && !crq->node_name)
+ list_for_each_entry_safe(tgt, next, &vhost->targets, queue) {
+ 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);
}
}
break;
- case IBMVFC_AE_LINK_DOWN:
- case IBMVFC_AE_ADAPTER_FAILED:
- ibmvfc_link_down(vhost, IBMVFC_LINK_DOWN);
- break;
- case IBMVFC_AE_LINK_DEAD:
- ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
- break;
- 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) {
clear_valid = false;
INIT_WORK(&aw->async_work_s, ibmvfc_process_async_work);
aw->vhost = vhost;
- aw->crq = crq;
+ if (aw_crq)
+ aw->crq = aw_crq;
+ if (aw_subq)
+ aw->subq = aw_subq;
schedule_work(&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;
}
- if (clear_valid)
- crq->valid = 0;
+ if (clear_valid) {
+ if (aw_crq)
+ aw_crq->valid = 0;
+ if (aw_subq)
+ aw_subq->valid = 0;
+ }
+}
+
+/**
+ * ibmvfc_handle_async - Handle an async event from the adapter
+ * @crq: crq to process
+ * @vhost: ibmvfc host struct
+ *
+ **/
+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));
+ u64 event = be64_to_cpu(crq->event);
+
+ 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));
+
+ ibmvfc_handle_async_common(event, crq->link_state, vhost,
+ crq->scsi_id, crq->wwpn, crq->node_name,
+ crq, NULL);
}
EXPORT_SYMBOL_IF_KUNIT(ibmvfc_handle_async);
+VISIBLE_IF_KUNIT void ibmvfc_handle_asyncq(struct ibmvfc_crq *crq_instance,
+ struct ibmvfc_host *vhost)
+{
+ struct ibmvfc_async_subq *crq = (struct ibmvfc_async_subq *)crq_instance;
+ const struct ibmvfc_async_desc *desc = ibmvfc_get_ae_desc(be16_to_cpu(crq->event));
+ u64 event = be16_to_cpu(crq->event);
+
+ ibmvfc_log(vhost, desc->log_level,
+ "%s event received. wwpn: %llx, node_name: %llx%s event 0x%x\n",
+ desc->desc, be64_to_cpu(crq->wwpn), be64_to_cpu(crq->id.node_name),
+ ibmvfc_get_link_state(crq->link_state), be16_to_cpu(crq->event));
+
+ ibmvfc_handle_async_common(event, crq->link_state, vhost,
+ 0, crq->wwpn, crq->id.node_name,
+ NULL, crq);
+}
+EXPORT_SYMBOL_IF_KUNIT(ibmvfc_handle_asyncq);
+
/**
* ibmvfc_handle_crq - Handles and frees received events in the CRQ
* @crq: Command/Response queue
@@ -4117,6 +4229,13 @@ static void ibmvfc_handle_scrq(struct ibmvfc_crq *crq, struct ibmvfc_host *vhost
spin_unlock(&evt->queue->l_lock);
}
+/**
+ * ibmvfc_next_scrq - Returns the next entry in message subqueue
+ * @scrq: Pointer to message subqueue
+ *
+ * Returns:
+ * Pointer to next entry in queue / NULL if empty
+ **/
static struct ibmvfc_crq *ibmvfc_next_scrq(struct ibmvfc_queue *scrq)
{
struct ibmvfc_crq *crq;
@@ -4132,6 +4251,57 @@ 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;
+ unsigned long flags;
+ int done = 0;
+
+ ENTER;
+
+ spin_lock_irqsave(scrq->q_lock, flags);
+ while (!done) {
+ while ((crq = ibmvfc_next_scrq(scrq)) != NULL) {
+ ibmvfc_handle_asyncq(crq, scrq->vhost);
+ crq->valid = 0;
+ wmb(); /* complete write */
+ }
+
+ ibmvfc_toggle_scrq_irq(scrq, 1);
+ crq = ibmvfc_next_scrq(scrq);
+ if (crq != NULL) {
+ ibmvfc_toggle_scrq_irq(scrq, 0);
+ ibmvfc_handle_asyncq(crq, scrq->vhost);
+ crq->valid = 0;
+ wmb(); /* complete write */
+ } else
+ done = 1;
+ }
+ spin_unlock_irqrestore(scrq->q_lock, flags);
+
+ LEAVE;
+}
+
+/**
+ * ibmvfc_interrupt_asyncq - Handle an async event from the adapter
+ * @irq: interrupt request
+ * @scrq_instance: async subq
+ *
+ **/
+static irqreturn_t ibmvfc_interrupt_asyncq(int irq, void *scrq_instance)
+{
+ struct ibmvfc_queue *scrq = (struct ibmvfc_queue *)scrq_instance;
+
+ ENTER;
+
+ ibmvfc_toggle_scrq_irq(scrq, 0);
+ ibmvfc_drain_async_subq(scrq);
+
+ LEAVE;
+
+ return IRQ_HANDLED;
+}
+
static void ibmvfc_drain_sub_crq(struct ibmvfc_queue *scrq)
{
struct ibmvfc_crq *crq;
@@ -5500,6 +5670,8 @@ static void ibmvfc_npiv_login_done(struct ibmvfc_event *evt)
unsigned int npiv_max_sectors;
int level = IBMVFC_DEFAULT_LOG_LEVEL;
+ ENTER;
+
switch (mad_status) {
case IBMVFC_MAD_SUCCESS:
ibmvfc_free_event(evt);
@@ -5578,6 +5750,8 @@ static void ibmvfc_npiv_login_done(struct ibmvfc_event *evt)
ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
wake_up(&vhost->work_wait_q);
}
+
+ LEAVE;
}
/**
@@ -6226,14 +6400,26 @@ static int ibmvfc_init_crq(struct ibmvfc_host *vhost)
return retrc;
}
-static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
- struct ibmvfc_channels *channels,
- int index)
+static inline char *ibmvfc_channel_index(struct ibmvfc_channels *channels,
+ struct ibmvfc_queue *scrq,
+ char *buf, size_t bufsize)
+{
+ if (scrq < channels->scrqs || scrq >= channels->scrqs + channels->active_queues)
+ strscpy(buf, "async", 6);
+ else
+ snprintf(buf, bufsize, "%ld", scrq - channels->scrqs);
+ return buf;
+}
+
+static int ibmvfc_register_channel_handler(struct ibmvfc_host *vhost,
+ struct ibmvfc_channels *channels,
+ struct ibmvfc_queue *scrq,
+ irq_handler_t irq)
{
struct device *dev = vhost->dev;
struct vio_dev *vdev = to_vio_dev(dev);
- struct ibmvfc_queue *scrq = &channels->scrqs[index];
int rc = -ENOMEM;
+ char buf[16];
ENTER;
@@ -6252,20 +6438,23 @@ 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);
+ dev_err(dev, "Error mapping sub-crq[%s] irq\n",
+ ibmvfc_channel_index(channels, scrq, buf, sizeof(buf)));
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;
+ snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-scsi%s",
+ vdev->unit_address,
+ ibmvfc_channel_index(channels, scrq, buf, sizeof(buf)));
+ scrq->handler = irq;
break;
case IBMVFC_PROTO_NVME:
- snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-nvmf%d",
- vdev->unit_address, index);
- scrq->handler = ibmvfc_interrupt_mq;
+ snprintf(scrq->name, sizeof(scrq->name), "ibmvfc-%x-nvmf%s",
+ vdev->unit_address,
+ ibmvfc_channel_index(channels, scrq, buf, sizeof(buf)));
+ scrq->handler = irq;
break;
default:
dev_err(dev, "Unknown channel protocol (%d)\n",
@@ -6276,12 +6465,14 @@ 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);
+ dev_err(dev, "Couldn't register sub-crq[%s] irq\n",
+ ibmvfc_channel_index(channels, scrq, buf, sizeof(buf)));
irq_dispose_mapping(scrq->irq);
goto irq_failed;
}
- scrq->hwq_id = index;
+ if (scrq >= channels->scrqs && scrq < channels->scrqs + channels->max_queues)
+ scrq->hwq_id = scrq - channels->scrqs;
LEAVE;
return 0;
@@ -6295,13 +6486,21 @@ static int ibmvfc_register_channel(struct ibmvfc_host *vhost,
return rc;
}
+static inline int
+ibmvfc_register_channel(struct ibmvfc_host *vhost,
+ struct ibmvfc_channels *channels,
+ struct ibmvfc_queue *scrq)
+{
+ return ibmvfc_register_channel_handler(vhost, channels, scrq, ibmvfc_interrupt_mq);
+}
+
static void ibmvfc_deregister_channel(struct ibmvfc_host *vhost,
struct ibmvfc_channels *channels,
- int index)
+ struct ibmvfc_queue *scrq)
{
struct device *dev = vhost->dev;
struct vio_dev *vdev = to_vio_dev(dev);
- struct ibmvfc_queue *scrq = &channels->scrqs[index];
+ char buf[16];
long rc;
ENTER;
@@ -6316,7 +6515,8 @@ static void ibmvfc_deregister_channel(struct ibmvfc_host *vhost,
} 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);
+ dev_err(dev, "Failed to free sub-crq[%s]: rc=%ld\n",
+ ibmvfc_channel_index(channels, scrq, buf, sizeof(buf)), rc);
/* Clean out the queue */
memset(scrq->msgs.crq, 0, PAGE_SIZE);
@@ -6334,10 +6534,21 @@ static void ibmvfc_reg_sub_crqs(struct ibmvfc_host *vhost,
if (!vhost->mq_enabled || !channels->scrqs)
return;
+ if (ibmvfc_register_channel_handler(vhost, channels,
+ channels->async_scrq,
+ ibmvfc_interrupt_asyncq)) {
+ vhost->do_enquiry = 0;
+ return;
+ }
+
for (i = 0; i < channels->max_queues; i++) {
- if (ibmvfc_register_channel(vhost, channels, i)) {
+ if (ibmvfc_register_channel(vhost, channels, &channels->scrqs[i])) {
for (j = i; j > 0; j--)
- ibmvfc_deregister_channel(vhost, channels, j - 1);
+ ibmvfc_deregister_channel(
+ vhost, channels, &channels->scrqs[j - 1]);
+ ibmvfc_deregister_channel(vhost, channels,
+ channels->async_scrq);
+
vhost->do_enquiry = 0;
return;
}
@@ -6356,7 +6567,8 @@ static void ibmvfc_dereg_sub_crqs(struct ibmvfc_host *vhost,
return;
for (i = 0; i < channels->max_queues; i++)
- ibmvfc_deregister_channel(vhost, channels, i);
+ ibmvfc_deregister_channel(vhost, channels, &channels->scrqs[i]);
+ ibmvfc_deregister_channel(vhost, channels, channels->async_scrq);
LEAVE;
}
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index f026f30f98d3..2e02acde0178 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -715,6 +715,7 @@ struct ibmvfc_async_crq {
struct ibmvfc_async_work {
struct ibmvfc_host *vhost;
struct ibmvfc_async_crq *crq;
+ struct ibmvfc_async_subq *subq;
struct work_struct async_work_s;
};
@@ -1008,6 +1009,8 @@ struct ibmvfc_host {
#ifdef VISIBLE_IF_KUNIT
VISIBLE_IF_KUNIT void ibmvfc_handle_async(struct ibmvfc_async_crq *crq, struct ibmvfc_host *vhost);
+VISIBLE_IF_KUNIT void ibmvfc_handle_asyncq(struct ibmvfc_crq *crq_instance,
+ 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 e41e2a49e549..c8799eaf4927 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
@@ -44,7 +44,7 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
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);
+ 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);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 7/7] ibmvfc: handle extended FPIN events
2026-06-08 18:30 [PATCH v2 0/7] ibmvfc: make ibmvfc support FPIN messages Dave Marquardt via B4 Relay
` (5 preceding siblings ...)
2026-06-08 18:30 ` [PATCH v2 6/7] ibmvfc: register and use " Dave Marquardt via B4 Relay
@ 2026-06-08 18:30 ` Dave Marquardt via B4 Relay
6 siblings, 0 replies; 10+ messages in thread
From: Dave Marquardt via B4 Relay @ 2026-06-08 18:30 UTC (permalink / raw)
To: James E.J. Bottomley, Martin K. Petersen, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Tyrel Datwyler
Cc: linux-kernel, linux-scsi, linuxppc-dev, Brian King, Greg Joyce,
Kyle Mahlkuch, Dave Marquardt
From: Dave Marquardt <davemarq@linux.ibm.com>
Add extended FPIN handling to ibmvfc driver. Tell VIOS ibmvfc can
handle extended FPIN messages, convert any received to struct fc_els
descriptors, and call fc_host_fpin_rcv to update statistics and send
netlink multicast messages to listeners such as multipathd.
---
drivers/scsi/ibmvscsi/ibmvfc.c | 41 +++++++++++-
drivers/scsi/ibmvscsi/ibmvfc.h | 31 +++++++++
drivers/scsi/ibmvscsi/ibmvfc_kunit.c | 122 +++++++++++++++++++++++++++++++++--
3 files changed, 186 insertions(+), 8 deletions(-)
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
index a2252cd2f44b..b034a894e3ec 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -1515,7 +1515,8 @@ static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
login_info->capabilities =
cpu_to_be64(IBMVFC_CAN_MIGRATE | IBMVFC_CAN_SEND_VF_WWPN |
IBMVFC_CAN_USE_NOOP_CMD | IBMVFC_YES_SCSI |
- IBMVFC_USE_ASYNC_SUBQ | IBMVFC_CAN_HANDLE_FPIN);
+ IBMVFC_USE_ASYNC_SUBQ | IBMVFC_CAN_HANDLE_FPIN |
+ IBMVFC_CAN_HANDLE_FPIN_EXT);
if (vhost->mq_enabled || vhost->using_channels)
login_info->capabilities |= cpu_to_be64(IBMVFC_CAN_USE_CHANNELS);
@@ -3254,7 +3255,7 @@ ibmvfc_common_fpin_to_desc(u8 fpin_status, __be64 wwpn, __be16 type, __be16 modi
if (size == 0)
return NULL;
- fpin = kzalloc(size, GFP_ATOMIC);
+ fpin = kzalloc(size, GFP_KERNEL);
if (fpin == NULL)
return NULL;
@@ -3371,6 +3372,28 @@ ibmvfc_full_fpin_to_desc(struct ibmvfc_async_subq *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)
+{
+ return ibmvfc_common_fpin_to_desc(ibmvfc_fpin->fpin_status, ibmvfc_fpin->wwpn,
+ ibmvfc_fpin->fpin_data.event_type,
+ ibmvfc_fpin->fpin_data.event_type_modifier,
+ ibmvfc_fpin->fpin_data.event_threshold,
+ ibmvfc_fpin->fpin_data.event_data.event_count);
+}
+
/**
* ibmvfc_process_async_work - Process IBMVFC_AE_FPIN async CRQ from work queue
* @work: pointer to work_struct
@@ -3425,7 +3448,19 @@ static void ibmvfc_process_async_work(struct work_struct *work)
fpin = ibmvfc_basic_fpin_to_desc(crq, tgt->wwpn);
} else {
sqfpin = (struct ibmvfc_async_subq_fpin *)subq;
- fpin = ibmvfc_full_fpin_to_desc(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");
+ fpin = NULL;
+ } else if (!ibmvfc_check_caps(vhost, IBMVFC_SUPPORT_FPIN_EXT)) {
+ dev_err_ratelimited(vhost->dev,
+ "Unexpected extended FPIN event received");
+ fpin = NULL;
+ } else {
+ fpin = ibmvfc_ext_fpin_to_desc(sqfpin);
+ }
}
if (fpin) {
fc_host_fpin_rcv(tgt->vhost->host,
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index 2e02acde0178..5c4cf4be4b67 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -184,6 +184,7 @@ struct ibmvfc_npiv_login {
#define IBMVFC_YES_SCSI 0x40
#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];
@@ -233,6 +234,7 @@ struct ibmvfc_npiv_login_resp {
#define IBMVFC_SUPPORT_SCSI 0x200
#define IBMVFC_SUPPORT_ASYNC_SUBQ 0x800
#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
+#define IBMVFC_SUPPORT_FPIN_EXT 0x2000
__be32 max_cmds;
__be32 scsi_id_sz;
__be64 max_dma_len;
@@ -722,6 +724,7 @@ struct ibmvfc_async_work {
struct ibmvfc_async_subq {
volatile u8 valid;
#define IBMVFC_ASYNC_ID_IS_ASSOC_ID 0x01
+#define IBMVFC_ASYNC_IS_FPIN_EXT 0x02
#define IBMVFC_FC_EEH 0x04
#define IBMVFC_FC_FW_UPDATE 0x08
#define IBMVFC_FC_FW_DUMP 0x10
@@ -738,6 +741,34 @@ struct ibmvfc_async_subq {
} 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);
+
union ibmvfc_iu {
struct ibmvfc_mad_common mad_common;
struct ibmvfc_npiv_login_mad npiv_login;
diff --git a/drivers/scsi/ibmvscsi/ibmvfc_kunit.c b/drivers/scsi/ibmvscsi/ibmvfc_kunit.c
index c8799eaf4927..2e6cbaaebdba 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"
@@ -58,10 +59,10 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
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);
+ while (crq[fs].valid)
+ msleep(1U);
}
- msleep(500U);
-
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);
@@ -94,8 +95,8 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
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);
-
- msleep(500U);
+ while (crq[0].valid)
+ msleep(1U);
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);
@@ -115,8 +116,119 @@ static void ibmvfc_async_fpin_test(struct kunit *test)
post[IBMVFC_AE_FPIN_CONGESTION_CLEARED]);
}
+#define IBMVFC_TEST_FPIN_EXT(fs, ev, stat, crq) { \
+ 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)); \
+ pre = READ_ONCE(tgt->rport->fpin_stats.stat); \
+ ibmvfc_handle_asyncq((struct ibmvfc_crq *)&crq, vhost); \
+ while (crq.valid) \
+ msleep(1U); \
+ 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);
+ 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);
+ KUNIT_ASSERT_GE_MSG(test, vhost->num_targets, 1, "No targets");
+
+ tgt = list_first_entry(&vhost->targets, struct ibmvfc_target, queue);
+ KUNIT_ASSERT_NOT_NULL(test, tgt->rport);
+
+ 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:
+ 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);
+ pre = READ_ONCE(tgt->rport->fpin_stats.cn_clear);
+ ibmvfc_handle_asyncq((struct ibmvfc_crq *)&crq[fs], vhost);
+ while (crq[fs].valid)
+ msleep(1U);
+ 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;
+ }
+ }
+}
+
static struct kunit_case ibmvfc_fpin_test_cases[] = {
- KUNIT_CASE_SLOW(ibmvfc_async_fpin_test),
+ KUNIT_CASE(ibmvfc_async_fpin_test),
+ KUNIT_CASE(ibmvfc_extended_fpin_test),
{},
};
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread