[PATCH 4/5] nvmet_fc: Rework target side abort handling
Sagi Grimberg
sagi at grimberg.me
Thu Apr 20 04:15:26 PDT 2017
On 24/03/17 06:43, jsmart2021 at gmail.com wrote:
> From: James Smart <jsmart2021 at gmail.com>
>
> 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.
Would have been better to split this.
> 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 */
locking semantics is better documented at the function signature and/or
foo_locked() naming. Just a suggestion though.
> + 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);
Can wda be true with abort? or can this be converted to a state machine
which is easier to follow and less error prone?
> 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;
> +}
Why is this receiving an abort flag?
> +
> /*
> * 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.
> + */
nit: kernel-doc-nano-HOWTO advise a slightly different style.
More information about the Linux-nvme
mailing list