nvmet_fc: bug fixes and lldd api mods
Subsystems (5)
v2 cover nvmet_fc: bug fixes and lldd api mods
From: James Smart <[email protected]>
This patch set contains modifications to the lldd api on the target side to specify isr vs threaded calling contexts, address the manner in which the transport releases io contexts back to the lldd, and addresses the abort interface.
Any modifications to the lpfc driver are isolated to nvme-specific files. Therefore any mods should be able to go in through the blk tree and avoid the scsi tree.
No changes were made from the prior posting. The patches were recut on the latest block for-4.12/block branch
James Smart (5):
nvmet_fc: add target feature flags for upcall isr contexts
nvmet_fc: add req_release to lldd api
nvme_fcloop: split job struct from transport for req_release
nvmet_fc: Rework target side abort handling
nvmet_fc: add missing reference in add_port
drivers/nvme/target/fc.c | 251 +++++++++++++++++++++++++++++++---------- drivers/nvme/target/fcloop.c | 197 ++++++++++++++++++++++++++------ drivers/scsi/lpfc/lpfc_nvmet.c | 126 ++++++++++++++++----- drivers/scsi/lpfc/lpfc_nvmet.h | 7 +- include/linux/nvme-fc-driver.h | 98 +++++++++++----- 5 files changed, 523 insertions(+), 156 deletions(-)
Signature · 1 line
2.9.3
PATCH v2 1/5 nvmet_fc: add target feature flags for upcall isr contexts
From: James Smart <[email protected]>
Two new feature flags were added to control whether upcalls to the transport result in context switches or stay in the calling context.
NVMET_FCTGTFEAT_CMD_IN_ISR:
By default, if the flag is not set, the transport assumes the lldd is in a non-isr context and in the cpu context it should be for the io queue. As such, the cmd handler is called directly in the calling context.
If the flag is set, indicating the upcall is an isr context, the transport mandates a transition to a workqueue. The workqueue assigned to the queue is used for the context.NVMET_FCTGTFEAT_OPDONE_IN_ISR
By default, if the flag is not set, the transport assumes the lldd is in a non-isr context and in the cpu context it should be for the io queue. As such, the fcp operation done callback is called directly in the calling context.
If the flag is set, indicating the upcall is an isr context, the transport mandates a transition to a workqueue. The workqueue assigned to the queue is used for the context.
Updated lpfc for flags
--- drivers/nvme/target/fc.c | 37 ++++++++++++++++++++++++++++++++++--- drivers/nvme/target/fcloop.c | 5 +++-- drivers/scsi/lpfc/lpfc_nvmet.c | 4 +++- include/linux/nvme-fc-driver.h | 16 ++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-)
drivers/nvme/target/fc.c +34 -3
@@ -86,6 +86,7 @@ struct nvmet_fc_fcp_iod {struct nvmet_req req;struct work_struct work; +struct work_struct done_work;struct nvmet_fc_tgtport *tgtport;struct nvmet_fc_tgt_queue *queue;
@@ -213,6 +214,7 @@ static DEFINE_IDA(nvmet_fc_tgtport_cnt);static void nvmet_fc_handle_ls_rqst_work (struct work_struct *work);static void nvmet_fc_handle_fcp_rqst_work (struct work_struct *work); +static void nvmet_fc_fcp_rqst_op_done_work (struct work_struct *work);static void nvmet_fc_tgt_a_put (struct nvmet_fc_tgt_assoc *assoc);static int nvmet_fc_tgt_a_get (struct nvmet_fc_tgt_assoc *assoc);static void nvmet_fc_tgt_q_put (struct nvmet_fc_tgt_queue *queue);
@@ -414,6 +416,7 @@ nvmet_fc_prep_fcp_iodlist(struct nvmet_fc_tgtport *tgtport,for (i =0 ; i < queue->sqsize; fod++, i++) {INIT_WORK (&fod->work, nvmet_fc_handle_fcp_rqst_work); +INIT_WORK (&fod->done_work, nvmet_fc_fcp_rqst_op_done_work); fod->tgtport = tgtport; fod->queue = queue; fod->active = false;
@@ -1807,10 +1810,13 @@ nvmet_fc_transfer_fcp_data(struct nvmet_fc_tgtport *tgtport, } } +/* +* actual done handler for FCP operations when completed by the lldd +*/ static void -nvmet_fc_xmt_fcp_op_done (struct nvmefc_tgt_fcp_req *fcpreq) +nvmet_fc_fod_op_done (struct nvmet_fc_fcp_iod *fod) { -struct nvmet_fc_fcp_iod *fod = fcpreq->nvmet_fc_private; +struct nvmefc_tgt_fcp_req *fcpreq = fod->fcpreq;struct nvmet_fc_tgtport *tgtport = fod->tgtport;unsigned long flags;bool abort;
@@ -1905,6 +1911,28 @@ nvmet_fc_xmt_fcp_op_done(struct nvmefc_tgt_fcp_req *fcpreq) } } +static void +nvmet_fc_fcp_rqst_op_done_work (struct work_struct *work) +{ +struct nvmet_fc_fcp_iod *fod = + container_of(work, struct nvmet_fc_fcp_iod, done_work); + +nvmet_fc_fod_op_done (fod); +} + +static void +nvmet_fc_xmt_fcp_op_done (struct nvmefc_tgt_fcp_req *fcpreq) +{ +struct nvmet_fc_fcp_iod *fod = fcpreq->nvmet_fc_private; +struct nvmet_fc_tgt_queue *queue = fod->queue; + +if (fod->tgtport->ops->target_features &NVMET_FCTGTFEAT_OPDONE_IN_ISR ) +/* context switch so completion is not in ISR context */ +queue_work_on (queue->cpu, queue->work_q, &fod->done_work); +else +nvmet_fc_fod_op_done (fod); +} +/* * actual completion handler after execution by the nvmet layer */
@@ -2149,7 +2177,10 @@ nvmet_fc_rcv_fcp_req(struct nvmet_fc_target_port *target_port, ((queue->qid - 1) % tgtport->ops->max_hw_queues) : 0;memcpy (&fod->cmdiubuf, cmdiubuf, cmdiubuf_len); -queue_work_on (queue->cpu, queue->work_q, &fod->work); +if (tgtport->ops->target_features &NVMET_FCTGTFEAT_CMD_IN_ISR ) +queue_work_on (queue->cpu, queue->work_q, &fod->work); +else +nvmet_fc_handle_fcp_rqst (tgtport, fod);return 0 ; }
drivers/nvme/target/fcloop.c +3 -2
@@ -575,8 +575,9 @@ struct nvmet_fc_target_template tgttemplate = { .max_dif_sgl_segments = FCLOOP_SGL_SEGS, .dma_boundary = FCLOOP_DMABOUND_4G,/* optional features */ - .target_features =NVMET_FCTGTFEAT_READDATA_RSP | - NVMET_FCTGTFEAT_NEEDS_CMD_CPUSCHED, + .target_features =NVMET_FCTGTFEAT_CMD_IN_ISR | +NVMET_FCTGTFEAT_NEEDS_CMD_CPUSCHED | + NVMET_FCTGTFEAT_OPDONE_IN_ISR,/* sizes of additional private data for data structures */ .target_priv_sz = sizeof(struct fcloop_tport), };
drivers/scsi/lpfc/lpfc_nvmet.c +3 -1
@@ -669,7 +669,9 @@ lpfc_nvmet_create_targetport(struct lpfc_hba *phba) lpfc_tgttemplate.max_hw_queues = phba->cfg_nvme_io_channel; lpfc_tgttemplate.max_sgl_segments = phba->cfg_sg_seg_cnt; lpfc_tgttemplate.target_features =NVMET_FCTGTFEAT_READDATA_RSP | -NVMET_FCTGTFEAT_NEEDS_CMD_CPUSCHED ; +NVMET_FCTGTFEAT_NEEDS_CMD_CPUSCHED | +NVMET_FCTGTFEAT_CMD_IN_ISR | +NVMET_FCTGTFEAT_OPDONE_IN_ISR ;#if (IS_ENABLED (CONFIG_NVME_TARGET_FC )) error = nvmet_fc_register_targetport(&pinfo, &lpfc_tgttemplate,
include/linux/nvme-fc-driver.h +16 -0
@@ -655,6 +655,22 @@ enum {* on. The transport should pick a cpu to schedule the work * on. */ +NVMET_FCTGTFEAT_CMD_IN_ISR = (1 <<2 ), +/* Bit 2: When 0, the LLDD is calling the cmd rcv handler +* in a non-isr context, allowing the transport to finish +* op completion in the calling context. When 1, the LLDD +* is calling the cmd rcv handler in an ISR context, +* requiring the transport to transition to a workqueue +* for op completion. +*/ + NVMET_FCTGTFEAT_OPDONE_IN_ISR = (1 << 3), + /* Bit 3: When 0, the LLDD is calling the op done handler + * in a non-isr context, allowing the transport to finish + * op completion in the calling context. When 1, the LLDD + * is calling the op done handler in an ISR context, + * requiring the transport to transition to a workqueue + * for op completion. + */ };
Signature · 1 line
2.9.3
PATCH v2 1/5 nvmet_fc: add target feature flags for upcall isr contexts
Looks fine,
PATCH v2 2/5 nvmet_fc: add req_release to lldd api
In reply to [email protected]From: James Smart <[email protected]>
With the advent of the opdone calls changing context, the lldd can no longer assume that once the op->done call returns for RSP operations that the request struct is no longer being accessed.
As such, revise the lldd api for a req_release callback that the transport will call when the job is complete. This will also be used with abort cases.
Fixed text in api header for change in io complete semantics.
Revised lpfc to support the new req_release api.
--- drivers/nvme/target/fc.c | 8 +++-- drivers/nvme/target/fcloop.c | 15 ++++++--- drivers/scsi/lpfc/lpfc_nvmet.c | 73 +++++++++++++++++++++++++++++++++++++++--- drivers/scsi/lpfc/lpfc_nvmet.h | 7 ++-- include/linux/nvme-fc-driver.h | 57 ++++++++++++++++++++------------- 5 files changed, 124 insertions(+), 36 deletions(-)
drivers/nvme/target/fc.c +6 -2
@@ -482,6 +482,8 @@ static void nvmet_fc_free_fcp_iod(struct nvmet_fc_tgt_queue *queue, struct nvmet_fc_fcp_iod *fod) { + struct nvmefc_tgt_fcp_req *fcpreq = fod->fcpreq; +struct nvmet_fc_tgtport *tgtport = fod->tgtport;unsigned long flags;spin_lock_irqsave (&queue->qlock, flags);
@@ -493,6 +495,8 @@ nvmet_fc_free_fcp_iod(struct nvmet_fc_tgt_queue *queue,* release the reference taken at queue lookup and fod allocation */ nvmet_fc_tgt_q_put (queue); + + tgtport->ops->fcp_req_release (&tgtport->fc_target_port, fcpreq); } static int
@@ -849,7 +853,7 @@ nvmet_fc_register_targetport(struct nvmet_fc_port_info *pinfo,int ret, idx;if (!template->xmt_ls_rsp || !template->fcp_op || - !template->targetport_delete || + !template->fcp_req_release || !template->targetport_delete || !template->max_hw_queues || !template->max_sgl_segments || !template->max_dif_sgl_segments || !template->dma_boundary) { ret = -EINVAL ;
@@ -2124,7 +2128,7 @@ nvmet_fc_handle_fcp_rqst_work(struct work_struct *work)* If this routine returns error, the lldd should abort the exchange. * * @target_port: pointer to the (registered) target port the FCP CMD IU -* was receive on. +* was received on. * @fcpreq: pointer to a fcpreq request structure to be used to reference * the exchange corresponding to the FCP Exchange. * @cmdiubuf: pointer to the buffer containing the FCP CMD IU
drivers/nvme/target/fcloop.c +10 -5
@@ -492,15 +492,19 @@ fcloop_fcp_op(struct nvmet_fc_target_port *tgtport, tgt_fcpreq->fcp_error = fcp_err; tgt_fcpreq->done (tgt_fcpreq); -if ((!fcp_err) && (op ==NVMET_FCOP_RSP || - op ==NVMET_FCOP_READDATA_RSP || - op ==NVMET_FCOP_ABORT )) -schedule_work (&tfcp_req->work); -return 0 ; }static void +fcloop_fcp_req_release (struct nvmet_fc_target_port *tgtport, +struct nvmefc_tgt_fcp_req *tgt_fcpreq) +{ +struct fcloop_fcpreq *tfcp_req =tgt_fcp_req_to_fcpreq (tgt_fcpreq); + +schedule_work (&tfcp_req->work); +} + +static void fcloop_ls_abort (struct nvme_fc_local_port *localport,struct nvme_fc_remote_port *remoteport,struct nvmefc_ls_req *lsreq)
@@ -570,6 +574,7 @@ struct nvmet_fc_target_template tgttemplate = { .targetport_delete = fcloop_targetport_delete, .xmt_ls_rsp = fcloop_xmt_ls_rsp, .fcp_op = fcloop_fcp_op, + .fcp_req_release = fcloop_fcp_req_release, .max_hw_queues = FCLOOP_HW_QUEUES, .max_sgl_segments = FCLOOP_SGL_SEGS, .max_dif_sgl_segments = FCLOOP_SGL_SEGS,
drivers/scsi/lpfc/lpfc_nvmet.c +68 -5
120 lines not on this page · raw message
drivers/scsi/lpfc/lpfc_nvmet.h +5 -2
@@ -81,6 +81,7 @@ struct lpfc_nvmet_rcv_ctx {struct lpfc_iocbq *wqeq;struct lpfc_iocbq *abort_wqeq;dma_addr_t txrdy_phys; +spinlock_t ctxlock;/* protect flag access */ uint32_t *txrdy;uint32_t sid;uint32_t offset;
@@ -97,8 +98,10 @@ struct lpfc_nvmet_rcv_ctx {#define LPFC_NVMET_STE_RSP 4#define LPFC_NVMET_STE_DONE 5uint16_t flag; -#define LPFC_NVMET_IO_INP 1 -#define LPFC_NVMET_ABORT_OP 2 +#define LPFC_NVMET_IO_INP 0x1 +#define LPFC_NVMET_ABORT_OP 0x2 +#define LPFC_NVMET_CTX_RLS 0x4 +struct rqb_dmabuf *rqb_buffer;#ifdef CONFIG_SCSI_LPFC_DEBUG_FS
include/linux/nvme-fc-driver.h +35 -22
89 lines not on this page · raw message
Signature · 1 line
2.9.3
PATCH v2 2/5 nvmet_fc: add req_release to lldd api
Looks good,
PATCH v2 3/5 nvme_fcloop: split job struct from transport for req_release
In reply to [email protected]From: James Smart <[email protected]>
Current design has the fcloop job struct, used for both initiator and target processing, allocated as part of the initiator request structure. On aborts, the initiator side (based on the request) may terminate, yet the target side wants to continue processing. the target side can't do that if the initiator side goes away.
Revise fcloop to allocate an independent target side structure when it starts an io from the initiator.
Added a lock to the request struct as well to synchronize pointer updates on abort calls.
Modified target downcalls to recognize conditions where initiator has aborted the io (thus nulled the pointer between job structs), thus avoid referencing sgl lists which are gone and no longer making upcalls to the initiator.
In conditions where the targetport is no longer connected, have the initiator return an access failure rather than simulating a command completion.
--- drivers/nvme/target/fcloop.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-)
drivers/nvme/target/fcloop.c +17 -8
@@ -251,6 +251,10 @@ struct fcloop_fcpreq {struct nvmefc_tgt_fcp_req tgt_fcp_req; }; +struct fcloop_ini_fcpreq { +struct nvmefc_fcp_req *fcpreq; +struct fcloop_fcpreq *tfcp_req; +};static inline struct fcloop_lsreq *tgt_ls_req_to_lsreq (struct nvmefc_tgt_ls_req *tgt_lsreq)
@@ -355,6 +359,8 @@ fcloop_tgt_fcprqst_done_work(struct work_struct *work) fcpreq->status = tfcp_req->status; fcpreq->done (fcpreq); } + +kfree (tfcp_req); }
@@ -364,20 +370,23 @@ fcloop_fcp_req(struct nvme_fc_local_port *localport,void *hw_queue_handle, struct nvmefc_fcp_req *fcpreq) { - struct fcloop_fcpreq *tfcp_req = fcpreq->private; struct fcloop_rport *rport = remoteport->private; +struct fcloop_ini_fcpreq *inireq = fcpreq->private; +struct fcloop_fcpreq *tfcp_req;int ret =0 ; -INIT_WORK (&tfcp_req->work, fcloop_tgt_fcprqst_done_work); +if (!rport->targetport) +return -ECONNREFUSED ; -if (!rport->targetport) { - tfcp_req->status =NVME_SC_FC_TRANSPORT_ERROR ; -schedule_work (&tfcp_req->work); -return ret; - } + tfcp_req =kzalloc (sizeof (*tfcp_req),GFP_KERNEL ); +if (!tfcp_req) +return -ENOMEM ; + inireq->fcpreq = fcpreq; + inireq->tfcp_req = tfcp_req; tfcp_req->fcpreq = fcpreq; tfcp_req->tport = rport->targetport->private; +INIT_WORK (&tfcp_req->work, fcloop_tgt_fcprqst_done_work); ret =nvmet_fc_rcv_fcp_req (rport->targetport, &tfcp_req->tgt_fcp_req, fcpreq->cmdaddr, fcpreq->cmdlen);
@@ -567,7 +576,7 @@ struct nvme_fc_port_template fctemplate = { .local_priv_sz = sizeof(struct fcloop_lport), .remote_priv_sz = sizeof(struct fcloop_rport), .lsrqst_priv_sz = sizeof(struct fcloop_lsreq), - .fcprqst_priv_sz = sizeof(struct fcloop_fcpreq), + .fcprqst_priv_sz = sizeof(struct fcloop_ini_fcpreq), };struct nvmet_fc_target_template tgttemplate = {
Signature · 1 line
2.9.3
PATCH v2 3/5 nvme_fcloop: split job struct from transport for req_release
Looks fine,
PATCH v2 4/5 nvmet_fc: Rework target side abort handling
In reply to [email protected]From: James Smart <[email protected]>
target transport:
----------------------
There are cases when there is a need to abort in-progress target operations (writedata) so that controller termination or errors can clean up. That can't happen currently as the abort is another target op type, so it can't be used till the running one finishes (and it may not). Solve by removing the abort op type and creating a separate downcall from the transport to the lldd to request an io to be aborted.
The transport will abort ios on queue teardown or io errors. In general the transport tries to call the lldd abort only when the io state is idle. Meaning: ops that transmit data (readdata or rsp) will always finish their transmit (or the lldd will see a state on the link or initiator port that fails the transmit) and the done call for the operation will occur. The transport will wait for the op done upcall before calling the abort function, and as the io is idle, the io can be cleaned up immediately after the abort call; Similarly, ios that are not waiting for data or transmitting data must be in the nvmet layer being processed. The transport will wait for the nvmet layer completion before calling the abort function, and as the io is idle, the io can be cleaned up immediately after the abort call; As for ops that are waiting for data (writedata), they may be outstanding indefinitely if the lldd doesn't see a condition where the initiatior port or link is bad. In those cases, the transport will call the abort function and wait for the lldd's op done upcall for the operation, where it will then clean up the io.
Additionally, if a lldd receives an ABTS and matches it to an outstanding request in the transport, A new new transport upcall was created to abort the outstanding request in the transport. The transport expects any outstanding op call (readdata or writedata) will completed by the lldd and the operation upcall made. The transport doesn't act on the reported abort (e.g. clean up the io) until an op done upcall occurs, a new op is attempted, or the nvmet layer completes the io processing.
fcloop:
----------------------
Updated to support the new target apis.
On fcp io aborts from the initiator, the loopback context is updated to NULL out the half that has completed. The initiator side is immediately called after the abort request with an io completion (abort status). On fcp io aborts from the target, the io is stopped and the initiator side sees it as an aborted io. Target side ops, perhaps in progress while the initiator side is done, continue but noop the data movement as there's no structure on the initiator side to reference.
patch also contains:
----------------------
Revised lpfc to support the new abort api
commonized rsp buffer syncing and nulling of private data based on calling paths.
errors in op done calls don't take action on the fod. They're bad operations which implies the fod may be bad.
--- drivers/nvme/target/fc.c | 205 ++++++++++++++++++++++++++++++----------- drivers/nvme/target/fcloop.c | 152 +++++++++++++++++++++++++----- drivers/scsi/lpfc/lpfc_nvmet.c | 49 +++++----- include/linux/nvme-fc-driver.h | 25 +++-- 4 files changed, 325 insertions(+), 106 deletions(-)
drivers/nvme/target/fc.c +151 -54
@@ -82,6 +82,8 @@ struct nvmet_fc_fcp_iod {enum nvmet_fcp_datadir io_dir;bool active;bool abort; +bool aborted; +bool writedataactive;spinlock_t flock;struct nvmet_req req;
@@ -420,6 +422,9 @@ nvmet_fc_prep_fcp_iodlist(struct nvmet_fc_tgtport *tgtport, fod->tgtport = tgtport; fod->queue = queue; fod->active = false; + fod->abort = false; + fod->aborted = false; + fod->fcpreq =NULL ;list_add_tail (&fod->fcp_list, &queue->fod_list);spin_lock_init (&fod->flock);
@@ -466,7 +471,6 @@ nvmet_fc_alloc_fcp_iod(struct nvmet_fc_tgt_queue *queue)if (fod) {list_del (&fod->fcp_list); fod->active = true; - fod->abort = false;/* * no queue reference is taken, as it was taken by the * queue lookup just prior to the allocation. The iod
@@ -486,9 +490,18 @@ nvmet_fc_free_fcp_iod(struct nvmet_fc_tgt_queue *queue,struct nvmet_fc_tgtport *tgtport = fod->tgtport;unsigned long flags; +fc_dma_sync_single_for_cpu (tgtport->dev, fod->rspdma, +sizeof (fod->rspiubuf),DMA_TO_DEVICE ); + + fcpreq->nvmet_fc_private =NULL ; +spin_lock_irqsave (&queue->qlock, flags);list_add_tail (&fod->fcp_list, &fod->queue->fod_list); fod->active = false; + fod->abort = false; + fod->aborted = false; + fod->writedataactive = false; + fod->fcpreq =NULL ;spin_unlock_irqrestore (&queue->qlock, flags);/*
@@ -623,32 +636,12 @@ nvmet_fc_tgt_q_get(struct nvmet_fc_tgt_queue *queue)static void -nvmet_fc_abort_op (struct nvmet_fc_tgtport *tgtport, -struct nvmefc_tgt_fcp_req *fcpreq) -{ -int ret; - - fcpreq->op =NVMET_FCOP_ABORT ; - fcpreq->offset =0 ; - fcpreq->timeout =0 ; - fcpreq->transfer_length =0 ; - fcpreq->transferred_length =0 ; - fcpreq->fcp_error =0 ; - fcpreq->sg_cnt =0 ; - - ret = tgtport->ops->fcp_op (&tgtport->fc_target_port, fcpreq); -if (ret) -/* should never reach here !! */ -WARN_ON (1 ); -} - - -static void nvmet_fc_delete_target_queue (struct nvmet_fc_tgt_queue *queue) { +struct nvmet_fc_tgtport *tgtport = queue->assoc->tgtport;struct nvmet_fc_fcp_iod *fod = queue->fod;unsigned long flags; -int i; +int i, writedataactive;bool disconnect; disconnect =atomic_xchg (&queue->connected,0 );
@@ -659,7 +652,20 @@ nvmet_fc_delete_target_queue(struct nvmet_fc_tgt_queue *queue)if (fod->active) {spin_lock (&fod->flock); fod->abort = true; + writedataactive = fod->writedataactive;spin_unlock (&fod->flock); +/* +* only call lldd abort routine if waiting for +* writedata. other outstanding ops should finish +* on their own. +*/ +if (writedataactive) { +spin_lock (&fod->flock); + fod->aborted = true; +spin_unlock (&fod->flock); + tgtport->ops->fcp_abort ( + &tgtport->fc_target_port, fod->fcpreq); + } } }spin_unlock_irqrestore (&queue->qlock, flags);
@@ -853,6 +859,7 @@ nvmet_fc_register_targetport(struct nvmet_fc_port_info *pinfo,int ret, idx;if (!template->xmt_ls_rsp || !template->fcp_op || + !template->fcp_abort || !template->fcp_req_release || !template->targetport_delete || !template->max_hw_queues || !template->max_sgl_segments || !template->max_dif_sgl_segments || !template->dma_boundary) {
@@ -1718,6 +1725,26 @@ nvmet_fc_prep_fcp_rsp(struct nvmet_fc_tgtport *tgtport,static void nvmet_fc_xmt_fcp_op_done (struct nvmefc_tgt_fcp_req *fcpreq);static void +nvmet_fc_abort_op (struct nvmet_fc_tgtport *tgtport, +struct nvmet_fc_fcp_iod *fod) +{ +struct nvmefc_tgt_fcp_req *fcpreq = fod->fcpreq; + +/* data no longer needed */ +nvmet_fc_free_tgt_pgs (fod); + +/* +* if an ABTS was received or we issued the fcp_abort early +* don't call abort routine again. +*/ +/* no need to take lock - lock was taken earlier to get here */ +if (!fod->aborted) + tgtport->ops->fcp_abort (&tgtport->fc_target_port, fcpreq); + +nvmet_fc_free_fcp_iod (fod->queue, fod); +} + +static void nvmet_fc_xmt_fcp_rsp (struct nvmet_fc_tgtport *tgtport,struct nvmet_fc_fcp_iod *fod) {
@@ -1730,7 +1757,7 @@ nvmet_fc_xmt_fcp_rsp(struct nvmet_fc_tgtport *tgtport, ret = tgtport->ops->fcp_op (&tgtport->fc_target_port, fod->fcpreq);if (ret) -nvmet_fc_abort_op (tgtport, fod->fcpreq); +nvmet_fc_abort_op (tgtport, fod); } static void
@@ -1739,6 +1766,7 @@ nvmet_fc_transfer_fcp_data(struct nvmet_fc_tgtport *tgtport, {struct nvmefc_tgt_fcp_req *fcpreq = fod->fcpreq;struct scatterlist *sg, *datasg; +unsigned long flags;u32 tlen, sg_off;int ret;
@@ -1803,10 +1831,13 @@ nvmet_fc_transfer_fcp_data(struct nvmet_fc_tgtport *tgtport,*/ fod->abort = true; -if (op ==NVMET_FCOP_WRITEDATA ) +if (op ==NVMET_FCOP_WRITEDATA ) { +spin_lock_irqsave (&fod->flock, flags); + fod->writedataactive = false; +spin_unlock_irqrestore (&fod->flock, flags);nvmet_req_complete (&fod->req,NVME_SC_FC_TRANSPORT_ERROR ); -else /* NVMET_FCOP_READDATA or NVMET_FCOP_READDATA_RSP */ { + }else /* NVMET_FCOP_READDATA or NVMET_FCOP_READDATA_RSP */ { fcpreq->fcp_error = ret; fcpreq->transferred_length =0 ;nvmet_fc_xmt_fcp_op_done (fod->fcpreq);
@@ -1814,6 +1845,27 @@ nvmet_fc_transfer_fcp_data(struct nvmet_fc_tgtport *tgtport, } } +static inline bool +__nvmet_fc_fod_op_abort (struct nvmet_fc_fcp_iod *fod,bool abort) +{ +struct nvmefc_tgt_fcp_req *fcpreq = fod->fcpreq; +struct nvmet_fc_tgtport *tgtport = fod->tgtport; + +/* if in the middle of an io and we need to tear down */ +if (abort) { +if (fcpreq->op ==NVMET_FCOP_WRITEDATA ) { +nvmet_req_complete (&fod->req, +NVME_SC_FC_TRANSPORT_ERROR ); +return true; + } + +nvmet_fc_abort_op (tgtport, fod); +return true; + } + +return false; +} +/* * actual done handler for FCP operations when completed by the lldd */
@@ -1827,22 +1879,20 @@ nvmet_fc_fod_op_done(struct nvmet_fc_fcp_iod *fod)spin_lock_irqsave (&fod->flock, flags); abort = fod->abort; + fod->writedataactive = false;spin_unlock_irqrestore (&fod->flock, flags); -/* if in the middle of an io and we need to tear down */ -if (abort && fcpreq->op !=NVMET_FCOP_ABORT ) { -/* data no longer needed */ -nvmet_fc_free_tgt_pgs (fod); - -nvmet_req_complete (&fod->req, fcpreq->fcp_error); -return ; - } -switch (fcpreq->op) {case NVMET_FCOP_WRITEDATA : +if (__nvmet_fc_fod_op_abort (fod, abort)) +return ;if (fcpreq->fcp_error || fcpreq->transferred_length != fcpreq->transfer_length) { +spin_lock (&fod->flock); + fod->abort = true; +spin_unlock (&fod->flock); +nvmet_req_complete (&fod->req,NVME_SC_FC_TRANSPORT_ERROR );return ;
@@ -1850,6 +1900,10 @@ nvmet_fc_fod_op_done(struct nvmet_fc_fcp_iod *fod) fod->offset += fcpreq->transferred_length;if (fod->offset != fod->total_length) { +spin_lock_irqsave (&fod->flock, flags); + fod->writedataactive = true; +spin_unlock_irqrestore (&fod->flock, flags); +/* transfer the next chunk */ nvmet_fc_transfer_fcp_data (tgtport, fod,NVMET_FCOP_WRITEDATA );
@@ -1864,12 +1918,11 @@ nvmet_fc_fod_op_done(struct nvmet_fc_fcp_iod *fod)case NVMET_FCOP_READDATA :case NVMET_FCOP_READDATA_RSP : +if (__nvmet_fc_fod_op_abort (fod, abort)) +return ;if (fcpreq->fcp_error || fcpreq->transferred_length != fcpreq->transfer_length) { -/* data no longer needed */ -nvmet_fc_free_tgt_pgs (fod); - -nvmet_fc_abort_op (tgtport, fod->fcpreq); +nvmet_fc_abort_op (tgtport, fod);return ; }
@@ -1878,8 +1931,6 @@ nvmet_fc_fod_op_done(struct nvmet_fc_fcp_iod *fod)if (fcpreq->op ==NVMET_FCOP_READDATA_RSP ) {/* data no longer needed */ nvmet_fc_free_tgt_pgs (fod); -fc_dma_sync_single_for_cpu (tgtport->dev, fod->rspdma, -sizeof (fod->rspiubuf),DMA_TO_DEVICE );nvmet_fc_free_fcp_iod (fod->queue, fod);return ; }
@@ -1902,15 +1953,12 @@ nvmet_fc_fod_op_done(struct nvmet_fc_fcp_iod *fod)break ;case NVMET_FCOP_RSP : -case NVMET_FCOP_ABORT : -fc_dma_sync_single_for_cpu (tgtport->dev, fod->rspdma, -sizeof (fod->rspiubuf),DMA_TO_DEVICE ); +if (__nvmet_fc_fod_op_abort (fod, abort)) +return ;nvmet_fc_free_fcp_iod (fod->queue, fod);break ;default : -nvmet_fc_free_tgt_pgs (fod); -nvmet_fc_abort_op (tgtport, fod->fcpreq);break ; } }
@@ -1958,10 +2006,7 @@ __nvmet_fc_fcp_nvme_cmd_done(struct nvmet_fc_tgtport *tgtport, fod->queue->sqhd = cqe->sq_head;if (abort) { -/* data no longer needed */ -nvmet_fc_free_tgt_pgs (fod); - -nvmet_fc_abort_op (tgtport, fod->fcpreq); +nvmet_fc_abort_op (tgtport, fod);return ; }
@@ -2057,8 +2102,8 @@ nvmet_fc_handle_fcp_rqst(struct nvmet_fc_tgtport *tgtport, &fod->queue->nvme_cq, &fod->queue->nvme_sq, &nvmet_fc_tgt_fcp_ops); -if (!ret) {/* bad SQE content */ -nvmet_fc_abort_op (tgtport, fod->fcpreq); +if (!ret) {/* bad SQE content or invalid ctrl state */ +nvmet_fc_abort_op (tgtport, fod);return ; }
@@ -2098,7 +2143,7 @@ nvmet_fc_handle_fcp_rqst(struct nvmet_fc_tgtport *tgtport,return ; transport_error: -nvmet_fc_abort_op (tgtport, fod->fcpreq); +nvmet_fc_abort_op (tgtport, fod); }/*
@@ -2151,7 +2196,6 @@ nvmet_fc_rcv_fcp_req(struct nvmet_fc_target_port *target_port, (be16_to_cpu(cmdiu->iu_len) != (sizeof(*cmdiu)/4)))return -EIO ; - queue =nvmet_fc_find_target_queue (tgtport,be64_to_cpu (cmdiu->connection_id));if (!queue)
@@ -2190,6 +2234,59 @@ nvmet_fc_rcv_fcp_req(struct nvmet_fc_target_port *target_port, }EXPORT_SYMBOL_GPL (nvmet_fc_rcv_fcp_req); +/** +* nvmet_fc_rcv_fcp_abort - transport entry point called by an LLDD +* upon the reception of an ABTS for a FCP command +* +* Notify the transport that an ABTS has been received for a FCP command +* that had been given to the transport via nvmet_fc_rcv_fcp_req(). The +* LLDD believes the command is still being worked on +* (template_ops->fcp_req_release() has not been called). +* +* The transport will wait for any outstanding work (an op to the LLDD, +* which the lldd should complete with error due to the ABTS; or the +* completion from the nvmet layer of the nvme command), then will +* stop processing and call the nvmet_fc_rcv_fcp_req() callback to +* return the i/o context to the LLDD. The LLDD may send the BA_ACC +* to the ABTS either after return from this function (assuming any +* outstanding op work has been terminated) or upon the callback being +* called. +* +* @target_port: pointer to the (registered) target port the FCP CMD IU +* was received on. +* @fcpreq: pointer to the fcpreq request structure that corresponds +* to the exchange that received the ABTS. +*/ +void +nvmet_fc_rcv_fcp_abort (struct nvmet_fc_target_port *target_port, +struct nvmefc_tgt_fcp_req *fcpreq) +{ +struct nvmet_fc_fcp_iod *fod = fcpreq->nvmet_fc_private; +struct nvmet_fc_tgt_queue *queue; +unsigned long flags; + +if (!fod || fod->fcpreq != fcpreq) +/* job appears to have already completed, ignore abort */ +return ; + + queue = fod->queue; + +spin_lock_irqsave (&queue->qlock, flags); +if (fod->active) { +/* +* mark as abort. The abort handler, invoked upon completion +* of any work, will detect the aborted status and do the +* callback. +*/ +spin_lock (&fod->flock); + fod->abort = true; + fod->aborted = true; +spin_unlock (&fod->flock); + } +spin_unlock_irqrestore (&queue->qlock, flags); +} +EXPORT_SYMBOL_GPL (nvmet_fc_rcv_fcp_abort); +enum {FCT_TRADDR_ERR =0 ,FCT_TRADDR_WWNN =1 <<0 ,
drivers/nvme/target/fcloop.c +130 -22
235 lines not on this page · raw message
drivers/scsi/lpfc/lpfc_nvmet.c +28 -21
67 lines not on this page · raw message
include/linux/nvme-fc-driver.h +16 -9
@@ -533,9 +533,6 @@ enum {* rsp as well */ NVMET_FCOP_RSP = 4, /* send rsp frame */ -NVMET_FCOP_ABORT =5 ,/* abort exchange via ABTS */ -NVMET_FCOP_BA_ACC =6 ,/* send BA_ACC */ - NVMET_FCOP_BA_RJT = 7, /* send BA_RJT */ };/**
@@ -572,8 +569,6 @@ enum {* upon compeletion of the operation. The nvmet-fc layer will also set a * private pointer for its own use in the done routine. * -* Note: the LLDD must never fail a NVMET_FCOP_ABORT request !! -* * Values set by the NVMET-FC layer prior to calling the LLDD fcp_op * entrypoint. * @op: Indicates the FCP IU operation to perform (see NVMET_FCOP_xxx)
@@ -784,10 +779,6 @@ struct nvmet_fc_target_port {* or upon success/failure of FCP_CONF if it is supported, the * LLDD is to set the nvmefc_tgt_fcp_req fcp_error field and * consider the operation complete. -* NVMET_FCOP_ABORT: the LLDD is to terminate the exchange -* corresponding to the fcp operation. The LLDD shall send -* ABTS and follow FC exchange abort-multi rules, including -* ABTS retries and possible logout. * Upon completing the indicated operation, the LLDD is to set the * status fields for the operation (tranferred_length and fcp_error * status) in the request, then call the "done" routine
@@ -808,6 +799,17 @@ struct nvmet_fc_target_port {* Returns 0 on success, -<errno> on failure (Ex: -EIO) * Entrypoint is Mandatory. * +* @fcp_abort: Called by the transport to abort an active command. +* The command may be in-between operations (nothing active in LLDD) +* or may have an active WRITEDATA operation pending. The LLDD is to +* initiate the ABTS process for the command and return from the +* callback. The ABTS does not need to be complete on the command. +* The fcp_abort callback inherently cannot fail. After the +* fcp_abort() callback completes, the transport will wait for any +* outstanding operation (if there was one) to complete, then will +* call the fcp_req_release() callback to return the command's +* exchange context back to the LLDD. +* * @fcp_req_release: Called by the transport to return a nvmefc_tgt_fcp_req * to the LLDD after all operations on the fcp operation are complete. * This may be due to the command completing or upon completion of
@@ -848,6 +850,8 @@ struct nvmet_fc_target_template { struct nvmefc_tgt_ls_req *tls_req);int (*fcp_op)(struct nvmet_fc_target_port *tgtport,struct nvmefc_tgt_fcp_req *fcpreq); +void (*fcp_abort)(struct nvmet_fc_target_port *tgtport, +struct nvmefc_tgt_fcp_req *fcpreq);void (*fcp_req_release)(struct nvmet_fc_target_port *tgtport,struct nvmefc_tgt_fcp_req *fcpreq);
@@ -877,4 +881,7 @@ int nvmet_fc_rcv_fcp_req(struct nvmet_fc_target_port *tgtport,struct nvmefc_tgt_fcp_req *fcpreq, void *cmdiubuf, u32 cmdiubuf_len); +void nvmet_fc_rcv_fcp_abort (struct nvmet_fc_target_port *tgtport, +struct nvmefc_tgt_fcp_req *fcpreq); +#endif /* _NVME_FC_DRIVER_H */
Signature · 1 line
2.9.3
PATCH v2 4/5 nvmet_fc: Rework target side abort handling
This looks ok as a change to the existing code:
But we really need to go to the NVMe technical working group about how to cater for the fact that the FC transport does transport aborts (and thus probably doesn't use NVMe aborts at all, although we'd need to clarify that). Can you reach out to the working group and the T11 folks?
PATCH v2 4/5 nvmet_fc: Rework target side abort handling
Quoted 10 lines · Christoph Hellwig
This looks ok as a change to the existing code:
Reviewed-by Christoph Hellwig <hch at lst.de>But we really need to go to the NVMe technical working group about how to cater for the fact that the FC transport does transport aborts (and thus probably doesn't use NVMe aborts at all, although we'd need to clarify that). Can you reach out to the working group and the T11 folks?
Thanks.
I think there's a mis-understanding. T11 doesn't use transport aborts in lieu of an NVMe Abort. In fact, it's written that if it has an I/O failure and can't recover from it by retransmission/preserving the exchange (and currently it can't, as the T11 1.0 spec deferred retransmission support until 1.1), then it falls back to terminating the connection, which also terminates the association - which is per the language in the NVMe Fabric spec sec 7.1. So, if it sees an ABTS for an exchange, it kills the association. Note: there would be several issues if T11 tried to use ABTS's in lieu of Abort, or ABTS and cmd retry in lieu of real retransmission. So neither are allowed.
What you're probably seeing is the error being detected on the io, and the ABTS being pre-emptively sent for that io, and then that escalating to the connection/association failure, which usually spits out lots more
ABTS's. On the target side implementation in linux, the one io gets
aborted, and it currently doesn't escalate to other commands - it expects the initiator to get the ABTS, thus an io error, thus the initiator to teardown the connection/association and send all the
ABTS's. This may need to be revisited after the T11 1.0 spec comes
out, which I believe requires the target to also ABTS things on connection failure. I do need to check that if the linux nvmet layer kills the association/connection its returning all the outstanding cmds to the transport so I can meet that requirement.
There are perhaps 2 things that could be improved on the linux initiator fc transport:
1) Use NVMe Aborts instead of defaulting to resetting the controller (like rdma). This was held off as: ) NVME Abort are "best effort" and there were a lot of comments in the tech group promoting lazy abort support by always returning Dword 0 bit 0 =1 (cmd not aborted); b) Abort vs SQ cmd delivery is even more asynchronous than on other transports, creating more hit/miss conditions and requiring Abort command retries (what is a reasonable policy?); and c) it really should be something in the core layer and managing vs the Abort Command Limit is a real pain. This can always change in the future.
2) There are a couple of io error cases that detect a transport error and set status to NVME_SC_FC_TRANSPORT and don't ABTS the cmds. They should per T11 spec.
-- james
PATCH v2 5/5 nvmet_fc: add missing reference in add_port
In reply to [email protected]From: James Smart <[email protected]>
Add missing reference in add_port
--- drivers/nvme/target/fc.c | 1 + 1 file changed, 1 insertion(+)
drivers/nvme/target/fc.c +1 -0
@@ -2377,6 +2377,7 @@ nvmet_fc_add_port(struct nvmet_port *port)if (!tgtport->port) { tgtport->port = port; port->priv = tgtport; +nvmet_fc_tgtport_get (tgtport); ret =0 ; }else ret = -EALREADY ;
Signature · 1 line
2.9.3
PATCH v2 5/5 nvmet_fc: add missing reference in add_port
Looks fine,