Merge patch series "ibmvfc: NVMe/FC support over IBM Virtual FC"

Tyrel Datwyler <tyreld@linux.ibm.com> says:

This series adds NVMe/FC initiator support to the ibmvfc driver, enabling
IBM POWER virtual machines to discover and use NVMe namespaces presented by
the IBM Virtual I/O Server (VIOS) over the existing NPIV transport.

The ibmvfc driver communicates with the VIOS via a CRQ-based protocol. With
this series the VIOS can present both SCSI/FCP and NVMe/FC targets through
parallel sets of protocol-specific MAD opcodes, fabric login flows, and
sub-CRQ channels.

The series is organized into three phases:

Patches 1-5: Bug fixes and preparatory refactoring
  Four pre-existing bugs are fixed before any NVMe/FC work is introduced:
  a deadlock in the MAD send-failure path (locked done variant called with
  host_lock already held), a race during driver teardown where
  rport_add_work_q work items can outlive the FC host, a NULL event
  dereference in ibmvfc_tgt_implicit_logout_and_del, and an allocator
  mismatch where mempool-allocated ibmvfc_target structs are freed via kfree
  rather than mempool_free. Patch 5 moves the target list and count from
  struct ibmvfc_host into struct ibmvfc_channels as the structural
  prerequisite for independent per-protocol target tracking.

Patches 6-27: Protocol interface and driver scaffolding
  Patch 6 extends ibmvfc.h with NVMe/FC protocol definitions: MAD opcodes,
  capability flags, the v3 command layout, the fabric login MAD, async
  sub-CRQ event format, and updated channel enquiry/setup fields. Patch 7
  splits ibmvfc.c into ibmvfc-core.c and the new ibmvfc-nvme.c/h, registers
  an nvme_fc_port_template with stub callbacks, and adds NVMe module
  parameters. The remaining patches build the NVMe/FC plumbing: NVMe
  channel-group initialization, sub-CRQ lifecycle management, protocol-
  specific fabric login flow, target discovery, PLOGI/PRLI/query-target,
  implicit logout, move-login, protocol-driven target allocation, NVMe
  target deletion, state machine updates, and local/remote port registration
  with the NVMe-FC transport layer.

Patches 28-33: NVMe-FC LLDD callbacks and I/O path
  Implements the full nvme_fc_port_template: create_queue/delete_queue map
  NVMe controller queues to sub-CRQ handles; ls_req submits FC-LS frames via
  the ibmvfc passthru MAD; fcp_io builds and submits NVMe FCP commands via
  the NVMe sub-CRQ and completes them via nvme_fc_rcv_fcp_rsp(); ls_abort
  and fcp_abort cancel outstanding requests via NVMF cancel MADs. Patch 33
  extends the purge path to fail outstanding NVMe FCP and LS requests during
  host reset and link-down events.

Link: https://patch.msgid.link/20260723000149.969416-1-tyreld@linux.ibm.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Martin K. Petersen 2026-07-26 16:49:52 -04:00
commit 1cd82d710e
5 changed files with 1469 additions and 199 deletions

View File

@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
ibmvfc-objs := ibmvfc-core.o ibmvfc-nvme.o
obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi.o
obj-$(CONFIG_SCSI_IBMVFC) += ibmvfc.o

View File

@ -0,0 +1,573 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ibmvfc-nvme.c -- IBM Power Virtual Fibre Channel NVMeoF HBA driver
*
* Written By: Tyrel Datwyler <tyreld@linux.ibm.com>, IBM Corporation
*
* Copyright (C) IBM Corporation, 2026
*/
#include <linux/dmapool.h>
#include <scsi/scsi_transport_fc.h>
#include "ibmvfc-nvme.h"
static unsigned int default_timeout = IBMVFC_DEFAULT_TIMEOUT;
static void ibmvfc_nvme_localport_delete(struct nvme_fc_local_port *lport)
{
struct ibmvfc_host *vhost = lport->private;
vhost->nvme_local_port = NULL;
complete(&vhost->nvme_delete_done);
}
static void ibmvfc_nvme_remoteport_delete(struct nvme_fc_remote_port *rport)
{
struct ibmvfc_target *tgt = rport->private;
tgt->nvme_remote_port = NULL;
complete(&tgt->nvme_delete_done);
}
static int ibmvfc_nvme_create_queue(struct nvme_fc_local_port *lport, unsigned int qidx,
u16 qsize, void **handle)
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_nvme_qhandle *qhandle;
if (!vhost->nvme_scrqs.active_queues)
return -ENODEV;
qhandle = kzalloc_obj(struct ibmvfc_nvme_qhandle);
if (!qhandle)
return -ENOMEM;
qhandle->cpu_id = raw_smp_processor_id();
qhandle->qidx = qidx;
/* Admin and first IO queue are both mapped to index 0 */
if (qidx)
qhandle->index = (qidx - 1) % vhost->nvme_scrqs.active_queues;
else
qhandle->index = qidx;
qhandle->queue = &vhost->nvme_scrqs.scrqs[qhandle->index];
*handle = qhandle;
return 0;
}
static void ibmvfc_nvme_delete_queue(struct nvme_fc_local_port *lport, unsigned int qidx,
void *handle)
{
kfree(handle);
}
static void ibmvfc_ls_req_done(struct ibmvfc_event *evt)
{
struct ibmvfc_target *tgt = evt->tgt;
struct ibmvfc_passthru_mad *mad = &evt->xfer_iu->passthru;
struct fcnvme_ls_rqst_w0 *ls_rqst;
struct fcnvme_ls_cr_assoc_acc *ls_resp;
u32 status = be16_to_cpu(mad->common.status);
int rc = 0;
ls_rqst = (struct fcnvme_ls_rqst_w0 *)evt->ls_req->rqstaddr;
ls_resp = (struct fcnvme_ls_cr_assoc_acc *)evt->ls_req->rspaddr;
switch (status) {
case IBMVFC_MAD_SUCCESS:
tgt_dbg(tgt, "ls_req succeeded\n");
if ((ls_rqst->ls_cmd == FCNVME_LS_CREATE_ASSOCIATION) &&
(ls_resp->hdr.w0.ls_cmd == FCNVME_LS_ACC)) {
tgt->assoc_id = be64_to_cpu(ls_resp->associd.association_id);
tgt_dbg(tgt, "assoc_id 0x%llx\n", tgt->assoc_id);
}
break;
case IBMVFC_MAD_DRIVER_FAILED:
break;
case IBMVFC_MAD_FAILED:
default:
tgt_info(tgt, "ls_req failed: %s (%x:%x) rc=0x%02X\n",
ibmvfc_get_cmd_error(be16_to_cpu(mad->iu.status), be16_to_cpu(mad->iu.error)),
be16_to_cpu(mad->iu.status), be16_to_cpu(mad->iu.error), status);
break;
}
if (status)
rc = -EIO;
evt->ls_req->done(evt->ls_req, rc);
kref_put(&tgt->kref, ibmvfc_release_tgt);
ibmvfc_free_event(evt);
}
static void ibmvfc_init_ls_req(struct ibmvfc_event *evt, struct nvmefc_ls_req *ls_req)
{
struct ibmvfc_passthru_mad *mad = &evt->iu.passthru;
memset(mad, 0, sizeof(*mad));
mad->common.version = cpu_to_be32(2);
mad->common.opcode = cpu_to_be32(IBMVFC_NVMF_PASSTHRU);
mad->common.length = cpu_to_be16(sizeof(*mad) - sizeof(mad->fc_iu) - sizeof(mad->iu));
mad->cmd_ioba.va = cpu_to_be64((u64)be64_to_cpu(evt->crq.ioba) +
offsetof(struct ibmvfc_passthru_mad, iu));
mad->cmd_ioba.len = cpu_to_be32(sizeof(mad->iu));
mad->iu.cmd_len = cpu_to_be32(ls_req->rqstlen);
mad->iu.rsp_len = cpu_to_be32(ls_req->rsplen);
mad->iu.cmd.va = cpu_to_be64(ls_req->rqstdma);
mad->iu.cmd.len = cpu_to_be32(ls_req->rqstlen);
mad->iu.rsp.va = cpu_to_be64(ls_req->rspdma);
mad->iu.rsp.len = cpu_to_be32(ls_req->rsplen);
}
static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport,
struct nvme_fc_remote_port *rport,
struct nvmefc_ls_req *ls_req)
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_target *tgt = rport->private;
struct ibmvfc_passthru_mad *mad;
struct ibmvfc_event *evt;
kref_get(&tgt->kref);
evt = ibmvfc_get_event(&vhost->crq);
if (!evt) {
kref_put(&tgt->kref, ibmvfc_release_tgt);
return -EBUSY;
}
ibmvfc_init_event(evt, ibmvfc_ls_req_done, IBMVFC_MAD_FORMAT);
evt->tgt = tgt;
evt->ls_req = ls_req;
ls_req->private = evt;
ibmvfc_init_ls_req(evt, ls_req);
mad = &evt->iu.passthru;
mad->iu.flags = cpu_to_be32(IBMVFC_FC4_LS_DSC_CTRL);
mad->iu.scsi_id = cpu_to_be64(tgt->scsi_id);
mad->iu.cancel_key = cpu_to_be32((u64)evt);
mad->iu.target_wwpn = cpu_to_be64(tgt->wwpn);
ibmvfc_dbg(vhost, "nvme_ls_req\n");
if (ibmvfc_send_event(evt, vhost, IBMVFC_FC4_LS_PLUS_CANCEL_TIMEOUT)) {
kref_put(&tgt->kref, ibmvfc_release_tgt);
return -ENXIO;
}
return 0;
}
static void ibmvfc_sync_nvme_completion(struct ibmvfc_event *evt)
{
/* copy the response back */
if (evt->sync_iu)
*evt->sync_iu = *evt->xfer_iu;
complete(&evt->comp);
}
static void ibmvfc_init_ls_abort(struct ibmvfc_event *evt, struct nvmefc_ls_req *ls_abort)
{
struct ibmvfc_tmf *tmf;
struct ibmvfc_event *abt_evt = ls_abort->private;
struct ibmvfc_target *tgt = abt_evt->tgt;
struct ibmvfc_host *vhost = evt->vhost;
tmf = &evt->iu.tmf;
memset(tmf, 0, sizeof(*tmf));
tmf->common.version = cpu_to_be32(2);
tmf->target_wwpn = cpu_to_be64(tgt->wwpn);
tmf->common.opcode = cpu_to_be32(IBMVFC_NVMF_TMF_MAD);
tmf->common.length = cpu_to_be16(sizeof(*tmf));
if (vhost->state != IBMVFC_ACTIVE)
if (!ibmvfc_check_caps(vhost, IBMVFC_CAN_SUPPRESS_ABTS))
tmf->flags = cpu_to_be32(IBMVFC_TMF_SUPPRESS_ABTS);
tmf->cancel_key = cpu_to_be32((u64)abt_evt);
tmf->my_cancel_key = cpu_to_be32((u64)evt);
tmf->assoc_id = cpu_to_be64(tgt->assoc_id);
init_completion(&evt->comp);
}
static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport,
struct nvme_fc_remote_port *rport,
struct nvmefc_ls_req *ls_abort)
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_target *tgt = rport->private;
struct ibmvfc_event *evt;
union ibmvfc_iu rsp;
unsigned long flags;
u16 status;
evt = ibmvfc_get_event(&vhost->crq);
if (!vhost->logged_in || !evt)
return;
spin_lock_irqsave(vhost->host->host_lock, flags);
kref_get(&tgt->kref);
ibmvfc_init_event(evt, ibmvfc_sync_nvme_completion, IBMVFC_MAD_FORMAT);
ibmvfc_init_ls_abort(evt, ls_abort);
evt->sync_iu = &rsp;
if (ibmvfc_send_event(evt, vhost, default_timeout))
goto out;
spin_unlock_irqrestore(vhost->host->host_lock, flags);
wait_for_completion(&evt->comp);
status = be16_to_cpu(rsp.mad_common.status);
spin_lock_irqsave(vhost->host->host_lock, flags);
ibmvfc_free_event(evt);
out:
spin_unlock_irqrestore(vhost->host->host_lock, flags);
ibmvfc_dbg(vhost, "ls_abort: cancel failed with rc=%x\n", status);
kref_put(&tgt->kref, ibmvfc_release_tgt);
}
static void ibmvfc_nvme_done(struct ibmvfc_event *evt)
{
struct ibmvfc_cmd *vfc_cmd = &evt->xfer_iu->cmd;
struct nvmefc_fcp_req *fcp_req = evt->fcp_req;
struct nvme_fc_ersp_iu *ersp = (struct nvme_fc_ersp_iu *)fcp_req->rspaddr;
struct nvme_completion *cqe = &ersp->cqe;
struct nvme_command *sqe = &((struct nvme_fc_cmd_iu *)fcp_req->cmdaddr)->sqe;
ibmvfc_dbg(evt->vhost, "fc_done: (%x:%x)\n", be16_to_cpu(vfc_cmd->status),
be16_to_cpu(vfc_cmd->error));
ibmvfc_dbg(evt->vhost, "fc_done: cmdlen: %d, rsplen %d, payload_len %d\n",
fcp_req->cmdlen, fcp_req->rsplen, fcp_req->payload_length);
fcp_req->status = 0;
if (!vfc_cmd->status) {
fcp_req->rcv_rsplen = NVME_FC_SIZEOF_ZEROS_RSP;
fcp_req->transferred_length = fcp_req->payload_length;
} else if (be16_to_cpu(vfc_cmd->status) & IBMVFC_FC_NVME_STATUS) {
fcp_req->rcv_rsplen = sizeof(struct nvme_fc_ersp_iu);
fcp_req->transferred_length = be32_to_cpu(ersp->xfrd_len);
if (be16_to_cpu(vfc_cmd->error) & IBMVFC_NVMS_VALID_NODMA_CQE)
cqe->command_id = sqe->common.command_id;
} else {
fcp_req->rcv_rsplen = 0;
fcp_req->transferred_length = 0;
fcp_req->status = NVME_SC_INTERNAL;
}
fcp_req->done(fcp_req);
ibmvfc_free_event(evt);
}
static struct ibmvfc_cmd *ibmvfc_nvme_init_vfc_cmd(struct ibmvfc_event *evt,
struct nvme_fc_remote_port *rport,
struct nvmefc_fcp_req *fcp_req)
{
struct ibmvfc_target *tgt = rport->private;
struct ibmvfc_cmd *vfc_cmd = &evt->iu.cmd;
memset(vfc_cmd, 0, sizeof(*vfc_cmd));
vfc_cmd->resp.va = cpu_to_be64(fcp_req->rspdma);
vfc_cmd->resp.len = cpu_to_be32(fcp_req->rsplen);
vfc_cmd->frame_type = cpu_to_be32(IBMVFC_NVME_FCP_TYPE);
vfc_cmd->flags |= cpu_to_be16(IBMVFC_NVMEOF_PROTOCOL);
vfc_cmd->payload_len = cpu_to_be32(fcp_req->cmdlen);
vfc_cmd->resp_len = cpu_to_be32(fcp_req->rsplen);
vfc_cmd->cancel_key = cpu_to_be32((u64)evt);
vfc_cmd->target_wwpn = cpu_to_be64(rport->port_name);
vfc_cmd->tgt_scsi_id = cpu_to_be64(rport->port_id);
vfc_cmd->assoc_id = cpu_to_be64(tgt->assoc_id);
memcpy(&vfc_cmd->v3nvme.iu, fcp_req->cmdaddr, fcp_req->cmdlen);
return vfc_cmd;
}
static void ibmvfc_nvme_map_sg_list(struct nvmefc_fcp_req *fcp_req,
struct srp_direct_buf *md)
{
int i;
struct scatterlist *sg;
for_each_sg(fcp_req->first_sgl, sg, fcp_req->sg_cnt, i) {
md[i].va = cpu_to_be64(sg_dma_address(sg));
md[i].len = cpu_to_be32(sg_dma_len(sg));
md[i].key = 0;
}
}
static int ibmvfc_nvme_map_sg_data(struct nvmefc_fcp_req *fcp_req,
struct ibmvfc_event *evt,
struct ibmvfc_cmd *vfc_cmd)
{
struct srp_direct_buf *data = &vfc_cmd->ioba;
struct ibmvfc_host *vhost = evt->vhost;
if (!fcp_req->sg_cnt) {
vfc_cmd->flags |= cpu_to_be16(IBMVFC_NO_MEM_DESC);
return 0;
}
if (fcp_req->io_dir == NVMEFC_FCP_WRITE)
vfc_cmd->flags |= cpu_to_be16(IBMVFC_WRITE);
else
vfc_cmd->flags |= cpu_to_be16(IBMVFC_READ);
if (fcp_req->sg_cnt == 1) {
ibmvfc_nvme_map_sg_list(fcp_req, data);
return 0;
}
vfc_cmd->flags |= cpu_to_be16(IBMVFC_SCATTERLIST);
if (!evt->ext_list) {
evt->ext_list = dma_pool_alloc(vhost->sg_pool, GFP_ATOMIC,
&evt->ext_list_token);
if (!evt->ext_list)
return -ENOMEM;
}
ibmvfc_nvme_map_sg_list(fcp_req, evt->ext_list);
data->va = cpu_to_be64(evt->ext_list_token);
data->len = cpu_to_be32(fcp_req->sg_cnt * sizeof(struct srp_direct_buf));
data->key = 0;
return 0;
}
static int ibmvfc_nvme_fcp_io(struct nvme_fc_local_port *lport,
struct nvme_fc_remote_port *rport,
void *hw_queue_handle,
struct nvmefc_fcp_req *fcp_req)
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_nvme_qhandle *qhandle = hw_queue_handle;
struct ibmvfc_cmd *vfc_cmd;
struct ibmvfc_event *evt;
int rc;
ibmvfc_dbg(vhost, "nvme_fcp_io\n");
evt = ibmvfc_get_event(qhandle->queue);
if (!evt)
return -EBUSY;
evt->hwq = qhandle->index;
ibmvfc_dbg(vhost, "vfc-nvme-mq-%d\n", evt->hwq);
ibmvfc_init_event(evt, ibmvfc_nvme_done, IBMVFC_CMD_FORMAT);
evt->fcp_req = fcp_req;
fcp_req->private = evt;
vfc_cmd = ibmvfc_nvme_init_vfc_cmd(evt, rport, fcp_req);
vfc_cmd->correlation = cpu_to_be64((u64)evt);
if (likely(!(rc = ibmvfc_nvme_map_sg_data(fcp_req, evt, vfc_cmd))))
return ibmvfc_send_event(evt, vhost, 0);
ibmvfc_free_event(evt);
return rc;
}
static void ibmvfc_init_fcp_abort(struct ibmvfc_event *evt,
struct nvmefc_fcp_req *abort_req)
{
struct ibmvfc_tmf *tmf;
struct ibmvfc_event *abt_evt = abort_req->private;
struct ibmvfc_target *tgt = abt_evt->tgt;
tmf = &evt->iu.tmf;
memset(tmf, 0, sizeof(*tmf));
tmf->common.version = cpu_to_be32(2);
tmf->common.opcode = cpu_to_be32(IBMVFC_NVMF_TMF_MAD);
tmf->common.length = cpu_to_be16(sizeof(*tmf));
tmf->flags = cpu_to_be32(IBMVFC_TMF_ABORT_TASK | IBMVFC_TMF_NVMF_ASSOC);
tmf->cancel_key = cpu_to_be32((u64)abt_evt);
tmf->my_cancel_key = cpu_to_be32((u64)evt);
tmf->target_wwpn = cpu_to_be64(tgt->wwpn);
tmf->assoc_id = cpu_to_be64(tgt->assoc_id);
tmf->task_tag = cpu_to_be64((u64)abt_evt);
init_completion(&evt->comp);
}
static void ibmvfc_nvme_fcp_abort(struct nvme_fc_local_port *lport,
struct nvme_fc_remote_port *rport,
void *hw_queue_handle,
struct nvmefc_fcp_req *abort_req)
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_target *tgt = rport->private;
struct ibmvfc_event *evt, *abt_evt = abort_req->private;
struct ibmvfc_queue *queue;
union ibmvfc_iu rsp;
unsigned long flags;
u16 status = 0;
if (!abt_evt)
return;
queue = abt_evt->queue;
if (!vhost->logged_in || !queue)
return;
evt = ibmvfc_get_event(queue);
if (!evt)
return;
spin_lock_irqsave(queue->q_lock, flags);
kref_get(&tgt->kref);
ibmvfc_init_event(evt, ibmvfc_sync_nvme_completion, IBMVFC_MAD_FORMAT);
ibmvfc_init_fcp_abort(evt, abort_req);
evt->sync_iu = &rsp;
if (ibmvfc_send_event(evt, vhost, default_timeout))
goto out;
spin_unlock_irqrestore(queue->q_lock, flags);
wait_for_completion(&evt->comp);
status = be16_to_cpu(rsp.mad_common.status);
spin_lock_irqsave(queue->q_lock, flags);
ibmvfc_free_event(evt);
out:
spin_unlock_irqrestore(queue->q_lock, flags);
if (status)
ibmvfc_dbg(vhost, "fcp_abort: cancel failed with rc=%x\n", status);
kref_put(&tgt->kref, ibmvfc_release_tgt);
}
static struct nvme_fc_port_template ibmvfc_nvme_fc_transport = {
.localport_delete = ibmvfc_nvme_localport_delete,
.remoteport_delete = ibmvfc_nvme_remoteport_delete,
.create_queue = ibmvfc_nvme_create_queue,
.delete_queue = ibmvfc_nvme_delete_queue,
.ls_req = ibmvfc_nvme_ls_req,
.ls_abort = ibmvfc_nvme_ls_abort,
.fcp_io = ibmvfc_nvme_fcp_io,
.fcp_abort = ibmvfc_nvme_fcp_abort,
.map_queues = NULL,
.max_hw_queues = IBMVFC_NVME_HW_QUEUES,
.max_sgl_segments = 1024,
.max_dif_sgl_segments = 64,
.dma_boundary = 0xFFFFFFFF,
.local_priv_sz = sizeof(struct ibmvfc_host *),
.remote_priv_sz = sizeof(struct ibmvfc_target *),
.lsrqst_priv_sz = sizeof(struct ibmvfc_event *),
.fcprqst_priv_sz = sizeof(struct ibmvfc_event *),
};
int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt)
{
struct ibmvfc_host *vhost = tgt->vhost;
struct nvme_fc_port_info pinfo;
int rc;
if (!IS_ENABLED(CONFIG_NVME_FC))
return 0;
if (!vhost->nvme_local_port) {
dev_err(vhost->dev, "Attempt to register NVMe fc remoteport without valid localport\n");
return -EINVAL;
}
memset(&pinfo, 0, sizeof(struct nvme_fc_port_info));
pinfo.node_name = tgt->ids.node_name;
pinfo.port_name = tgt->ids.port_name;
pinfo.port_id = tgt->ids.port_id;
pinfo.port_role = FC_PORT_ROLE_NVME_TARGET;
rc = nvme_fc_register_remoteport(vhost->nvme_local_port, &pinfo,
&tgt->nvme_remote_port);
if (!rc) {
ibmvfc_log(vhost, 2, "register_remoteport: traddr=nn-0x%llx:pn-0x%llx PortID:%x\n",
pinfo.node_name, pinfo.port_name, pinfo.port_id);
tgt->nvme_remote_port->private = tgt;
}
return rc;
}
void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt)
{
struct ibmvfc_host *vhost = tgt->vhost;
struct nvme_fc_remote_port *rport = tgt->nvme_remote_port;
int rc;
if (!IS_ENABLED(CONFIG_NVME_FC))
return;
if (!tgt->nvme_remote_port)
return;
ibmvfc_log(vhost, 2, "unregister_remoteport: traddr=nn-0x%llx:pn-0x%llx PortID:%x\n",
rport->node_name, rport->port_name, rport->port_id);
init_completion(&tgt->nvme_delete_done);
rc = nvme_fc_unregister_remoteport(tgt->nvme_remote_port);
if (!rc) {
wait_for_completion(&tgt->nvme_delete_done);
}
}
int ibmvfc_nvme_register(struct ibmvfc_host *vhost)
{
struct nvme_fc_port_info pinfo;
int rc;
if (!IS_ENABLED(CONFIG_NVME_FC))
return 0;
pinfo.node_name = fc_host_node_name(vhost->host);
pinfo.port_name = fc_host_port_name(vhost->host);
pinfo.port_id = fc_host_port_id(vhost->host);
pinfo.port_role = FC_PORT_ROLE_NVME_INITIATOR;
pinfo.dev_loss_tmo = 0;
rc = nvme_fc_register_localport(&pinfo, &ibmvfc_nvme_fc_transport,
get_device(vhost->dev),
&vhost->nvme_local_port);
if (!rc) {
ibmvfc_log(vhost, 2, "register_localport: host-traddr=nn-0x%llx:pn-0x%llx on portID:%x\n",
pinfo.node_name, pinfo.port_name, pinfo.port_id);
vhost->nvme_local_port->private = vhost;
} else {
dev_err(vhost->dev, "Failed to register NVMe fc localport (%d)\n", rc);
put_device(vhost->dev);
}
return rc;
}
void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost)
{
int rc;
if (!IS_ENABLED(CONFIG_NVME_FC))
return;
if (vhost->nvme_local_port) {
ibmvfc_log(vhost, 2, "unregister_localport: host-traddr=nn-0x%llx:pn-0x%llx on portID:%x\n",
vhost->nvme_local_port->node_name,
vhost->nvme_local_port->port_name,
vhost->nvme_local_port->port_id);
init_completion(&vhost->nvme_delete_done);
rc = nvme_fc_unregister_localport(vhost->nvme_local_port);
if (!rc) {
wait_for_completion(&vhost->nvme_delete_done);
} else
dev_err(vhost->dev, "Failed to unregister NVMe fc localport (%d)\n", rc);
put_device(vhost->dev);
}
}

View File

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ibmvfc-nvme.h -- IBM Power Virtual Fibre Channel NVMeoF HBA driver
*
* Written By: Tyrel Datwyler <tyreld@linux.ibm.com>, IBM Corporation
*
* Copyright (C) IBM Corporation, 2026
*/
#ifndef _IBMVFC_NVME_H
#define _IBMVFC_NVME_H
#include <scsi/fc/fc_fs.h>
#include <scsi/fc/fc_els.h>
#include <linux/nvme-fc-driver.h>
#include <linux/nvme.h>
#include <linux/nvme-fc.h>
#include "ibmvfc.h"
#define IBMVFC_NVME 0
#define IBMVFC_NVME_HW_QUEUES 8
#define IBMVFC_MAX_NVME_QUEUES 16
#define IBMVFC_NVME_CHANNELS 8
#define IBMVFC_FC4_LS_TIMEOUT 15
#define IBMVFC_FC4_LS_CANCEL_TIMEOUT 45
#define IBMVFC_FC4_LS_PLUS_CANCEL_TIMEOUT \
(IBMVFC_FC4_LS_TIMEOUT + IBMVFC_FC4_LS_CANCEL_TIMEOUT)
extern unsigned int ibmvfc_debug;
struct ibmvfc_host;
struct ibmvfc_target;
struct ibmvfc_queue;
struct ibmvfc_nvme_qhandle {
unsigned int qidx;
u16 cpu_id;
unsigned long index;
struct ibmvfc_queue *queue;
};
int ibmvfc_nvme_register_remoteport(struct ibmvfc_target *tgt);
void ibmvfc_nvme_unregister_remoteport(struct ibmvfc_target *tgt);
int ibmvfc_nvme_register(struct ibmvfc_host *vhost);
void ibmvfc_nvme_unregister(struct ibmvfc_host *vhost);
#endif

View File

@ -1,20 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ibmvfc.h -- driver for IBM Power Virtual Fibre Channel Adapter
*
* Written By: Brian King <brking@linux.vnet.ibm.com>, IBM Corporation
*
* Copyright (C) IBM Corporation, 2008
* Copyright (C) IBM Corporation, 2008-2026
*/
#ifndef _IBMVFC_H
#define _IBMVFC_H
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/types.h>
#include <scsi/scsi_device.h>
#include <scsi/viosrp.h>
#include <linux/nvme.h>
#include <linux/nvme-fc.h>
#define IBMVFC_NAME "ibmvfc"
#include "ibmvfc-nvme.h"
#define IBMVFC_NAME "ibmvfc"
#define IBMVFC_DRIVER_VERSION "1.0.11"
#define IBMVFC_DRIVER_DATE "(April 12, 2013)"
@ -92,6 +99,7 @@ enum ibmvfc_cmd_status_flags {
IBMVFC_FC_SCSI_ERROR = 0x0008,
IBMVFC_HW_EVENT_LOGGED = 0x0010,
IBMVFC_VIOS_LOGGED = 0x0020,
IBMVFC_FC_NVME_STATUS = 0x0040,
};
enum ibmvfc_fabric_mapped_errors {
@ -124,20 +132,37 @@ enum ibmvfc_vios_errors {
IBMVFC_COMMAND_FAILED = 0x8000,
};
enum ibmvfc_fc_nvme_errors {
IBMVFC_NVMS_VALID_ERSP = 0x0001,
IBMVFC_NVMS_VALID_NODMA_CQE = 0x0002,
};
enum ibmvfc_mad_types {
IBMVFC_NPIV_LOGIN = 0x0001,
IBMVFC_DISC_TARGETS = 0x0002,
IBMVFC_DISC_TARGETS = 0x0002,
IBMVFC_DISC_NVMF_TARGETS = 0x0003,
IBMVFC_PORT_LOGIN = 0x0004,
IBMVFC_PROCESS_LOGIN = 0x0008,
IBMVFC_QUERY_TARGET = 0x0010,
IBMVFC_NVMF_PORT_LOGIN = 0x0005,
IBMVFC_PROCESS_LOGIN = 0x0008,
IBMVFC_NVMF_PROCESS_LOGIN = 0x0009,
IBMVFC_QUERY_TARGET = 0x0010,
IBMVFC_NVMF_QUERY_TARGET = 0x0011,
IBMVFC_MOVE_LOGIN = 0x0020,
IBMVFC_IMPLICIT_LOGOUT = 0x0040,
IBMVFC_PASSTHRU = 0x0200,
IBMVFC_TMF_MAD = 0x0100,
IBMVFC_NPIV_LOGOUT = 0x0800,
IBMVFC_CHANNEL_ENQUIRY = 0x1000,
IBMVFC_CHANNEL_SETUP = 0x2000,
IBMVFC_CONNECTION_INFO = 0x4000,
IBMVFC_NVMF_MOVE_LOGIN = 0x0021,
IBMVFC_IMPLICIT_LOGOUT = 0x0040,
IBMVFC_NVMF_IMPLICIT_LOGOUT = 0x0041,
IBMVFC_RNID = 0x0080,
IBMVFC_NVMF_RNID = 0x0081,
IBMVFC_TMF_MAD = 0x0100,
IBMVFC_NVMF_TMF_MAD = 0x0101,
IBMVFC_PASSTHRU = 0x0200,
IBMVFC_NVMF_PASSTHRU = 0x0201,
IBMVFC_NPIV_LOGOUT = 0x0800,
IBMVFC_CHANNEL_ENQUIRY = 0x1000,
IBMVFC_CHANNEL_SETUP = 0x2000,
IBMVFC_CONNECTION_INFO = 0x4000,
IBMVFC_FABRIC_LOGIN = 0x8000,
IBMVFC_NVMF_FABRIC_LOGIN = 0x8001,
};
struct ibmvfc_mad_common {
@ -175,11 +200,16 @@ struct ibmvfc_npiv_login {
#define IBMVFC_FLUSH_ON_HALT 0x02
__be32 max_cmds;
__be64 capabilities;
#define IBMVFC_CAN_MIGRATE 0x01
#define IBMVFC_CAN_USE_CHANNELS 0x02
#define IBMVFC_CAN_HANDLE_FPIN 0x04
#define IBMVFC_CAN_USE_MAD_VERSION 0x08
#define IBMVFC_CAN_SEND_VF_WWPN 0x10
#define IBMVFC_CAN_MIGRATE 0x001
#define IBMVFC_CAN_USE_CHANNELS 0x002
#define IBMVFC_CAN_HANDLE_FPIN 0x004
#define IBMVFC_CAN_USE_MAD_VERSION 0x008
#define IBMVFC_CAN_SEND_VF_WWPN 0x010
#define IBMVFC_YES_NVMEOF 0x020
#define IBMVFC_YES_SCSI 0x040
#define IBMVFC_CAN_USE_WWPN_ALL 0x080
#define IBMVFC_USE_ASYNC_SUBQ 0x100
#define IBMVFC_CAN_USE_NOOP_CMD 0x200
__be64 node_name;
struct srp_direct_buf async;
u8 partition_name[IBMVFC_MAX_NAME];
@ -219,13 +249,18 @@ struct ibmvfc_npiv_login_resp {
__be16 error;
__be32 flags;
#define IBMVFC_NATIVE_FC 0x01
__be32 reserved;
__be32 possible_nports;
__be64 capabilities;
#define IBMVFC_CAN_FLUSH_ON_HALT 0x08
#define IBMVFC_CAN_SUPPRESS_ABTS 0x10
#define IBMVFC_MAD_VERSION_CAP 0x20
#define IBMVFC_HANDLE_VF_WWPN 0x40
#define IBMVFC_CAN_SUPPORT_CHANNELS 0x80
#define IBMVFC_CAN_FLUSH_ON_HALT 0x0008
#define IBMVFC_CAN_SUPPRESS_ABTS 0x0010
#define IBMVFC_MAD_VERSION_CAP 0x0020
#define IBMVFC_HANDLE_VF_WWPN 0x0040
#define IBMVFC_CAN_SUPPORT_CHANNELS 0x0080
#define IBMVFC_SUPPORT_NVMEOF 0x0100
#define IBMVFC_SUPPORT_SCSI 0x0200
#define IBMVFC_SUPPORT_WWPN_ALL 0x0400
#define IBMVFC_ASYNC_SUBQ 0x0800
#define IBMVFC_SUPPORT_NOOP_CMD 0x1000
__be32 max_cmds;
__be32 scsi_id_sz;
__be64 max_dma_len;
@ -238,7 +273,7 @@ struct ibmvfc_npiv_login_resp {
u8 port_loc_code[IBMVFC_MAX_NAME];
u8 drc_name[IBMVFC_MAX_NAME];
struct ibmvfc_service_parms service_parms;
__be64 reserved2;
__be64 reserved;
} __packed __aligned(8);
union ibmvfc_npiv_login_data {
@ -246,6 +281,17 @@ union ibmvfc_npiv_login_data {
struct ibmvfc_npiv_login_resp resp;
} __packed __aligned(8);
struct ibmvfc_fabric_login_mad {
struct ibmvfc_mad_common common;
__be64 flags;
__be64 capabilities;
__be64 nport_id;
__be16 status;
__be16 error;
__be32 reserved;
__be64 reserved2[16];
} __packed __aligned(8);
struct ibmvfc_discover_targets_entry {
__be32 scsi_id;
__be32 pad;
@ -287,6 +333,7 @@ enum ibmvfc_fc_type {
IBMVFC_FABRIC_BUSY = 0x04,
IBMVFC_PORT_BUSY = 0x05,
IBMVFC_BASIC_REJECT = 0x06,
IBMVFC_FC4_LS_REJECT = 0x07,
};
enum ibmvfc_gs_explain {
@ -334,6 +381,7 @@ struct ibmvfc_move_login {
struct ibmvfc_prli_svc_parms {
u8 type;
#define IBMVFC_SCSI_FCP_TYPE 0x08
#define IBMVFC_NVME_FCP_TYPE 0x28
u8 type_ext;
__be16 flags;
#define IBMVFC_PRLI_ORIG_PA_VALID 0x8000
@ -349,6 +397,11 @@ struct ibmvfc_prli_svc_parms {
#define IBMVFC_PRLI_TARGET_FUNC 0x00000010
#define IBMVFC_PRLI_READ_FCP_XFER_RDY_DISABLED 0x00000002
#define IBMVFC_PRLI_WR_FCP_XFER_RDY_DISABLED 0x00000001
#define IBMVFC_PRLI_NVME_PI_CTRL 0x00000200
#define IBMVFC_PRLI_NVME_SLER 0x00000100
#define IBMVFC_PRLI_NVME_INITIATOR 0x00000020
#define IBMVFC_PRLI_NVME_TARGET 0x00000010
#define IBMVFC_PRLI_NVME_DISCOVERY 0x00000008
} __packed __aligned(4);
struct ibmvfc_process_login {
@ -377,20 +430,27 @@ struct ibmvfc_query_tgt {
struct ibmvfc_implicit_logout {
struct ibmvfc_mad_common common;
__be64 old_scsi_id;
__be64 reserved[2];
__be64 reserved[8];
__be64 target_wwpn;
} __packed __aligned(8);
struct ibmvfc_tmf {
struct ibmvfc_mad_common common;
__be64 scsi_id;
struct scsi_lun lun;
union {
struct scsi_lun lun;
__be64 assoc_id;
};
__be32 flags;
#define IBMVFC_TMF_ABORT_TASK 0x02
#define IBMVFC_TMF_ABORT_TASK_SET 0x04
#define IBMVFC_TMF_LUN_RESET 0x10
#define IBMVFC_TMF_TGT_RESET 0x20
#define IBMVFC_TMF_LUA_VALID 0x40
#define IBMVFC_TMF_SUPPRESS_ABTS 0x80
#define IBMVFC_TMF_ABORT_TASK 0x002
#define IBMVFC_TMF_ABORT_TASK_SET 0x004
#define IBMVFC_TMF_LUN_RESET 0x010
#define IBMVFC_TMF_TGT_RESET 0x020
#define IBMVFC_TMF_LUA_VALID 0x040
#define IBMVFC_TMF_SUPPRESS_ABTS 0x080
#define IBMVFC_TMF_NVMF_ASSOC 0x100
#define IBMVFC_TMF_NVMF_TARGET 0x200
#define IBMVFC_TMF_BITMASK_VALID 0x400
__be32 cancel_key;
__be32 my_cancel_key;
__be32 pad;
@ -446,6 +506,8 @@ enum ibmvfc_cmd_flags {
IBMVFC_WRITE = 0x0008,
IBMVFC_TMF = 0x0080,
IBMVFC_CLASS_3_ERR = 0x0100,
IBMVFC_NVMEOF_PROTOCOL = 0x0200,
IBMVFC_NVMF_SLER = 0x0400,
};
enum ibmvfc_fc_task_attr {
@ -493,7 +555,7 @@ struct ibmvfc_cmd {
__be64 tgt_scsi_id;
__be64 tag;
__be64 target_wwpn;
__be64 reserved3;
__be64 assoc_id;
union {
struct {
struct ibmvfc_fcp_cmd_iu iu;
@ -504,6 +566,15 @@ struct ibmvfc_cmd {
struct ibmvfc_fcp_cmd_iu iu;
struct ibmvfc_fcp_rsp rsp;
} v2;
struct {
__be64 reserved5[4];
struct ibmvfc_fcp_cmd_iu iu;
struct ibmvfc_fcp_rsp rsp;
} v3scsi;
struct {
__be64 reserved[4];
struct nvme_fc_cmd_iu iu;
} v3nvme;
};
} __packed __aligned(8);
@ -522,6 +593,9 @@ struct ibmvfc_passthru_iu {
__be32 flags;
#define IBMVFC_FC_ELS 0x01
#define IBMVFC_FC_CT_IU 0x02
#define IBMVFC_PT_PRLI 0x04
#define IBMVFC_FC4_LS_OTH 0x08
#define IBMVFC_FC4_LS_DSC_CTRL 0x10
__be32 cancel_key;
#define IBMVFC_PASSTHRU_CANCEL_KEY 0x80000000
#define IBMVFC_INTERNAL_CANCEL_KEY 0x80000001
@ -549,9 +623,9 @@ struct ibmvfc_channel_enquiry {
#define IBMVFC_SUPPORT_VARIABLE_SUBQ_MSG 0x02
#define IBMVFC_NO_N_TO_M_CHANNELS_SUPPORT 0x04
__be32 num_scsi_subq_channels;
__be32 num_nvmeof_subq_channels;
__be32 num_nvme_subq_channels;
__be32 num_scsi_vas_channels;
__be32 num_nvmeof_vas_channels;
__be32 num_nvme_vas_channels;
} __packed __aligned(8);
struct ibmvfc_channel_setup_mad {
@ -559,7 +633,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;
@ -568,12 +642,13 @@ struct ibmvfc_channel_setup {
#define IBMVFC_CHANNELS_CANCELED 0x04
__be32 reserved;
__be32 num_scsi_subq_channels;
__be32 num_nvmeof_subq_channels;
__be32 num_nvme_subq_channels;
__be32 num_scsi_vas_channels;
__be32 num_nvmeof_vas_channels;
__be32 num_nvme_vas_channels;
struct srp_direct_buf buffer;
__be64 reserved2[5];
__be64 channel_handles[IBMVFC_MAX_CHANNELS];
__be64 async_sub_crq_handle;
} __packed __aligned(8);
struct ibmvfc_connection_info {
@ -620,7 +695,8 @@ struct ibmvfc_trace_entry {
enum ibmvfc_crq_formats {
IBMVFC_CMD_FORMAT = 0x01,
IBMVFC_ASYNC_EVENT = 0x02,
IBMVFC_ASYNC_EVENT = 0x02,
IBMVFC_NOOP = 0x03,
IBMVFC_MAD_FORMAT = 0x04,
};
@ -639,6 +715,9 @@ enum ibmvfc_async_event {
IBMVFC_AE_RESUME = 0x0800,
IBMVFC_AE_ADAPTER_FAILED = 0x1000,
IBMVFC_AE_FPIN = 0x2000,
IBMVFC_NVME_DISCONNECT = 0x4000,
IBMVFC_NVMEOF_PROTO_AVAIL = 0x40000000,
IBMVFC_SCSI_PROTO_AVAIL = 0x80000000,
};
struct ibmvfc_async_desc {
@ -683,13 +762,30 @@ struct ibmvfc_async_crq {
volatile __be64 scsi_id;
volatile __be64 wwpn;
volatile __be64 node_name;
__be64 reserved;
__be64 assoc_id;
} __packed __aligned(8);
struct ibmvfc_async_sub_crq {
volatile u8 valid;
u8 flags;
#define IBMVFC_ASYNC_ID_IS_ASSOC_ID 0x01
u8 link_state;
u8 fpin_status;
__be16 event;
__be16 pad;
__be64 wwpn;
__be64 nport_id;
union {
__be64 node_name;
__be64 assoc_id;
} id;
} __packed __aligned(8);
union ibmvfc_iu {
struct ibmvfc_mad_common mad_common;
struct ibmvfc_npiv_login_mad npiv_login;
struct ibmvfc_npiv_logout_mad npiv_logout;
struct ibmvfc_fabric_login_mad fabric_login;
struct ibmvfc_discover_targets discover_targets;
struct ibmvfc_port_login plogi;
struct ibmvfc_process_login prli;
@ -728,6 +824,7 @@ struct ibmvfc_target {
u64 scsi_id;
u64 wwpn;
u64 new_scsi_id;
u64 assoc_id;
struct fc_rport *rport;
int target_id;
enum ibmvfc_target_action action;
@ -743,6 +840,8 @@ struct ibmvfc_target {
void (*job_step) (struct ibmvfc_target *);
struct timer_list timer;
struct kref kref;
struct completion nvme_delete_done;
struct nvme_fc_remote_port *nvme_remote_port;
};
/* a unit of work for the hosting partition */
@ -753,6 +852,8 @@ struct ibmvfc_event {
struct ibmvfc_queue *queue;
struct ibmvfc_target *tgt;
struct scsi_cmnd *cmnd;
struct nvmefc_ls_req *ls_req;
struct nvmefc_fcp_req *fcp_req;
atomic_t free;
atomic_t active;
union ibmvfc_iu *xfer_iu;
@ -828,6 +929,8 @@ struct ibmvfc_channels {
unsigned int active_queues;
unsigned int desired_queues;
unsigned int max_queues;
int num_targets;
struct list_head targets;
int disc_buf_sz;
struct ibmvfc_discover_targets_entry *disc_buf;
dma_addr_t disc_buf_dma;
@ -871,8 +974,6 @@ struct ibmvfc_host {
#define IBMVFC_TRACE_SIZE (sizeof(struct ibmvfc_trace_entry) * IBMVFC_NUM_TRACE_ENTRIES)
struct ibmvfc_trace_entry *trace;
atomic_t trace_index;
int num_targets;
struct list_head targets;
struct list_head purge;
struct device *dev;
struct dma_pool *sg_pool;
@ -880,6 +981,7 @@ struct ibmvfc_host {
struct ibmvfc_queue crq;
struct ibmvfc_queue async_crq;
struct ibmvfc_channels scsi_scrqs;
struct ibmvfc_channels nvme_scrqs;
struct ibmvfc_npiv_login login_info;
union ibmvfc_npiv_login_data *login_buf;
dma_addr_t login_buf_dma;
@ -888,6 +990,7 @@ struct ibmvfc_host {
int log_level;
struct mutex passthru_mutex;
unsigned int max_vios_scsi_channels;
unsigned int max_vios_nvme_channels;
int task_set;
int init_retries;
int discovery_threads;
@ -899,6 +1002,9 @@ struct ibmvfc_host {
unsigned int mq_enabled:1;
unsigned int using_channels:1;
unsigned int do_enquiry:1;
unsigned int nvme_enabled:1;
unsigned int do_scsi_login:1;
unsigned int do_nvme_login:1;
unsigned int aborting_passthru:1;
unsigned int scan_complete:1;
int scan_timeout;
@ -914,8 +1020,37 @@ struct ibmvfc_host {
struct work_struct rport_add_work_q;
wait_queue_head_t init_wait_q;
wait_queue_head_t work_wait_q;
struct nvme_fc_local_port *nvme_local_port;
struct completion nvme_delete_done;
};
struct ibmvfc_event *__ibmvfc_get_event(struct ibmvfc_queue *queue, int reserved);
#define ibmvfc_get_event(queue) __ibmvfc_get_event(queue, 0)
#define ibmvfc_get_reserved_event(queue) __ibmvfc_get_event(queue, 1)
void ibmvfc_init_event(struct ibmvfc_event *evt, void (*done) (struct ibmvfc_event *), u8 format);
void ibmvfc_free_event(struct ibmvfc_event *evt);
void ibmvfc_release_tgt(struct kref *kref);
int ibmvfc_send_event(struct ibmvfc_event *evt, struct ibmvfc_host *vhost, unsigned long timeout);
const char *ibmvfc_get_cmd_error(u16 status, u16 error);
static inline int ibmvfc_check_caps(struct ibmvfc_host *vhost, unsigned long cap_flags)
{
u64 host_caps = be64_to_cpu(vhost->login_buf->resp.capabilities);
return (host_caps & cap_flags) ? 1 : 0;
}
static inline struct ibmvfc_host *ibmvfc_channels_to_vhost(struct ibmvfc_channels *channels)
{
if (channels->protocol == IBMVFC_PROTO_SCSI)
return container_of(channels, struct ibmvfc_host, scsi_scrqs);
else if (channels->protocol == IBMVFC_PROTO_NVME)
return container_of(channels, struct ibmvfc_host, nvme_scrqs);
return NULL;
}
#define DBG_CMD(CMD) do { if (ibmvfc_debug) CMD; } while (0)
#define tgt_dbg(t, fmt, ...) \