mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 18:13:04 +00:00
a912460efa
The per-CPU statistics (struct fc_stats) is updated by getting a stable per-CPU pointer via get_cpu() + per_cpu_ptr() and then performing the increment. This can be optimized by using this_cpu_*() which will do whatever is needed on the architecture to perform the update safe and efficient. The read out of the individual value (fc_get_host_stats()) should be done by using READ_ONCE() instead of a plain-C access. The difference is that READ_ONCE() will always perform a single access while the plain-C access can be split by the compiler into two loads if it appears beneficial. The usage of u64 has the side-effect that it is also 64bit wide on 32bit architectures and the read is always split into two loads. The can lead to strange values if the read happens during an update which alters both 32bit parts of the 64bit value. This can be circumvented by either using a 32bit variables on 32bit architecures or extending the statistics with a sequence counter. Use this_cpu_*() API to update the statistics and READ_ONCE() to read it. Link: https://lore.kernel.org/r/20220506105758.283887-3-bigeasy@linutronix.de Reviewed-by: Davidlohr Bueso <dave@stgolabs.net> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
2314 lines
60 KiB
C
2314 lines
60 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright(c) 2007 Intel Corporation. All rights reserved.
|
|
* Copyright(c) 2008 Red Hat, Inc. All rights reserved.
|
|
* Copyright(c) 2008 Mike Christie
|
|
*
|
|
* Maintained at www.Open-FCoE.org
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/err.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <scsi/scsi_tcq.h>
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <scsi/scsi_cmnd.h>
|
|
|
|
#include <scsi/fc/fc_fc2.h>
|
|
|
|
#include <scsi/libfc.h>
|
|
|
|
#include "fc_encode.h"
|
|
#include "fc_libfc.h"
|
|
|
|
static struct kmem_cache *scsi_pkt_cachep;
|
|
|
|
/* SRB state definitions */
|
|
#define FC_SRB_FREE 0 /* cmd is free */
|
|
#define FC_SRB_CMD_SENT (1 << 0) /* cmd has been sent */
|
|
#define FC_SRB_RCV_STATUS (1 << 1) /* response has arrived */
|
|
#define FC_SRB_ABORT_PENDING (1 << 2) /* cmd abort sent to device */
|
|
#define FC_SRB_ABORTED (1 << 3) /* abort acknowledged */
|
|
#define FC_SRB_DISCONTIG (1 << 4) /* non-sequential data recvd */
|
|
#define FC_SRB_COMPL (1 << 5) /* fc_io_compl has been run */
|
|
#define FC_SRB_FCP_PROCESSING_TMO (1 << 6) /* timer function processing */
|
|
|
|
#define FC_SRB_READ (1 << 1)
|
|
#define FC_SRB_WRITE (1 << 0)
|
|
|
|
static struct libfc_cmd_priv *libfc_priv(struct scsi_cmnd *cmd)
|
|
{
|
|
return scsi_cmd_priv(cmd);
|
|
}
|
|
|
|
/**
|
|
* struct fc_fcp_internal - FCP layer internal data
|
|
* @scsi_pkt_pool: Memory pool to draw FCP packets from
|
|
* @scsi_queue_lock: Protects the scsi_pkt_queue
|
|
* @scsi_pkt_queue: Current FCP packets
|
|
* @last_can_queue_ramp_down_time: ramp down time
|
|
* @last_can_queue_ramp_up_time: ramp up time
|
|
* @max_can_queue: max can_queue size
|
|
*/
|
|
struct fc_fcp_internal {
|
|
mempool_t *scsi_pkt_pool;
|
|
spinlock_t scsi_queue_lock;
|
|
struct list_head scsi_pkt_queue;
|
|
unsigned long last_can_queue_ramp_down_time;
|
|
unsigned long last_can_queue_ramp_up_time;
|
|
int max_can_queue;
|
|
};
|
|
|
|
#define fc_get_scsi_internal(x) ((struct fc_fcp_internal *)(x)->scsi_priv)
|
|
|
|
/*
|
|
* function prototypes
|
|
* FC scsi I/O related functions
|
|
*/
|
|
static void fc_fcp_recv_data(struct fc_fcp_pkt *, struct fc_frame *);
|
|
static void fc_fcp_recv(struct fc_seq *, struct fc_frame *, void *);
|
|
static void fc_fcp_resp(struct fc_fcp_pkt *, struct fc_frame *);
|
|
static void fc_fcp_complete_locked(struct fc_fcp_pkt *);
|
|
static void fc_tm_done(struct fc_seq *, struct fc_frame *, void *);
|
|
static void fc_fcp_error(struct fc_fcp_pkt *, struct fc_frame *);
|
|
static void fc_fcp_recovery(struct fc_fcp_pkt *, u8 code);
|
|
static void fc_fcp_timeout(struct timer_list *);
|
|
static void fc_fcp_rec(struct fc_fcp_pkt *);
|
|
static void fc_fcp_rec_error(struct fc_fcp_pkt *, struct fc_frame *);
|
|
static void fc_fcp_rec_resp(struct fc_seq *, struct fc_frame *, void *);
|
|
static void fc_io_compl(struct fc_fcp_pkt *);
|
|
|
|
static void fc_fcp_srr(struct fc_fcp_pkt *, enum fc_rctl, u32);
|
|
static void fc_fcp_srr_resp(struct fc_seq *, struct fc_frame *, void *);
|
|
static void fc_fcp_srr_error(struct fc_fcp_pkt *, struct fc_frame *);
|
|
|
|
/*
|
|
* command status codes
|
|
*/
|
|
#define FC_COMPLETE 0
|
|
#define FC_CMD_ABORTED 1
|
|
#define FC_CMD_RESET 2
|
|
#define FC_CMD_PLOGO 3
|
|
#define FC_SNS_RCV 4
|
|
#define FC_TRANS_ERR 5
|
|
#define FC_DATA_OVRRUN 6
|
|
#define FC_DATA_UNDRUN 7
|
|
#define FC_ERROR 8
|
|
#define FC_HRD_ERROR 9
|
|
#define FC_CRC_ERROR 10
|
|
#define FC_TIMED_OUT 11
|
|
#define FC_TRANS_RESET 12
|
|
|
|
/*
|
|
* Error recovery timeout values.
|
|
*/
|
|
#define FC_SCSI_TM_TOV (10 * HZ)
|
|
#define FC_HOST_RESET_TIMEOUT (30 * HZ)
|
|
#define FC_CAN_QUEUE_PERIOD (60 * HZ)
|
|
|
|
#define FC_MAX_ERROR_CNT 5
|
|
#define FC_MAX_RECOV_RETRY 3
|
|
|
|
#define FC_FCP_DFLT_QUEUE_DEPTH 32
|
|
|
|
/**
|
|
* fc_fcp_pkt_alloc() - Allocate a fcp_pkt
|
|
* @lport: The local port that the FCP packet is for
|
|
* @gfp: GFP flags for allocation
|
|
*
|
|
* Return value: fcp_pkt structure or null on allocation failure.
|
|
* Context: Can be called from process context, no lock is required.
|
|
*/
|
|
static struct fc_fcp_pkt *fc_fcp_pkt_alloc(struct fc_lport *lport, gfp_t gfp)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
struct fc_fcp_pkt *fsp;
|
|
|
|
fsp = mempool_alloc(si->scsi_pkt_pool, gfp);
|
|
if (fsp) {
|
|
memset(fsp, 0, sizeof(*fsp));
|
|
fsp->lp = lport;
|
|
fsp->xfer_ddp = FC_XID_UNKNOWN;
|
|
refcount_set(&fsp->ref_cnt, 1);
|
|
timer_setup(&fsp->timer, NULL, 0);
|
|
INIT_LIST_HEAD(&fsp->list);
|
|
spin_lock_init(&fsp->scsi_pkt_lock);
|
|
} else {
|
|
this_cpu_inc(lport->stats->FcpPktAllocFails);
|
|
}
|
|
return fsp;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_pkt_release() - Release hold on a fcp_pkt
|
|
* @fsp: The FCP packet to be released
|
|
*
|
|
* Context: Can be called from process or interrupt context,
|
|
* no lock is required.
|
|
*/
|
|
static void fc_fcp_pkt_release(struct fc_fcp_pkt *fsp)
|
|
{
|
|
if (refcount_dec_and_test(&fsp->ref_cnt)) {
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(fsp->lp);
|
|
|
|
mempool_free(fsp, si->scsi_pkt_pool);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_pkt_hold() - Hold a fcp_pkt
|
|
* @fsp: The FCP packet to be held
|
|
*/
|
|
static void fc_fcp_pkt_hold(struct fc_fcp_pkt *fsp)
|
|
{
|
|
refcount_inc(&fsp->ref_cnt);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_pkt_destroy() - Release hold on a fcp_pkt
|
|
* @seq: The sequence that the FCP packet is on (required by destructor API)
|
|
* @fsp: The FCP packet to be released
|
|
*
|
|
* This routine is called by a destructor callback in the fc_exch_seq_send()
|
|
* routine of the libfc Transport Template. The 'struct fc_seq' is a required
|
|
* argument even though it is not used by this routine.
|
|
*
|
|
* Context: No locking required.
|
|
*/
|
|
static void fc_fcp_pkt_destroy(struct fc_seq *seq, void *fsp)
|
|
{
|
|
fc_fcp_pkt_release(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_lock_pkt() - Lock a fcp_pkt and increase its reference count
|
|
* @fsp: The FCP packet to be locked and incremented
|
|
*
|
|
* We should only return error if we return a command to SCSI-ml before
|
|
* getting a response. This can happen in cases where we send a abort, but
|
|
* do not wait for the response and the abort and command can be passing
|
|
* each other on the wire/network-layer.
|
|
*
|
|
* Note: this function locks the packet and gets a reference to allow
|
|
* callers to call the completion function while the lock is held and
|
|
* not have to worry about the packets refcount.
|
|
*
|
|
* TODO: Maybe we should just have callers grab/release the lock and
|
|
* have a function that they call to verify the fsp and grab a ref if
|
|
* needed.
|
|
*/
|
|
static inline int fc_fcp_lock_pkt(struct fc_fcp_pkt *fsp)
|
|
{
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
if (fsp->state & FC_SRB_COMPL) {
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
return -EPERM;
|
|
}
|
|
|
|
fc_fcp_pkt_hold(fsp);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_unlock_pkt() - Release a fcp_pkt's lock and decrement its
|
|
* reference count
|
|
* @fsp: The FCP packet to be unlocked and decremented
|
|
*/
|
|
static inline void fc_fcp_unlock_pkt(struct fc_fcp_pkt *fsp)
|
|
{
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
fc_fcp_pkt_release(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_timer_set() - Start a timer for a fcp_pkt
|
|
* @fsp: The FCP packet to start a timer for
|
|
* @delay: The timeout period in jiffies
|
|
*/
|
|
static void fc_fcp_timer_set(struct fc_fcp_pkt *fsp, unsigned long delay)
|
|
{
|
|
if (!(fsp->state & FC_SRB_COMPL)) {
|
|
mod_timer(&fsp->timer, jiffies + delay);
|
|
fsp->timer_delay = delay;
|
|
}
|
|
}
|
|
|
|
static void fc_fcp_abort_done(struct fc_fcp_pkt *fsp)
|
|
{
|
|
fsp->state |= FC_SRB_ABORTED;
|
|
fsp->state &= ~FC_SRB_ABORT_PENDING;
|
|
|
|
if (fsp->wait_for_comp)
|
|
complete(&fsp->tm_done);
|
|
else
|
|
fc_fcp_complete_locked(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_send_abort() - Send an abort for exchanges associated with a
|
|
* fcp_pkt
|
|
* @fsp: The FCP packet to abort exchanges on
|
|
*/
|
|
static int fc_fcp_send_abort(struct fc_fcp_pkt *fsp)
|
|
{
|
|
int rc;
|
|
|
|
if (!fsp->seq_ptr)
|
|
return -EINVAL;
|
|
|
|
this_cpu_inc(fsp->lp->stats->FcpPktAborts);
|
|
|
|
fsp->state |= FC_SRB_ABORT_PENDING;
|
|
rc = fc_seq_exch_abort(fsp->seq_ptr, 0);
|
|
/*
|
|
* fc_seq_exch_abort() might return -ENXIO if
|
|
* the sequence is already completed
|
|
*/
|
|
if (rc == -ENXIO) {
|
|
fc_fcp_abort_done(fsp);
|
|
rc = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_retry_cmd() - Retry a fcp_pkt
|
|
* @fsp: The FCP packet to be retried
|
|
* @status_code: The FCP status code to set
|
|
*
|
|
* Sets the status code to be FC_ERROR and then calls
|
|
* fc_fcp_complete_locked() which in turn calls fc_io_compl().
|
|
* fc_io_compl() will notify the SCSI-ml that the I/O is done.
|
|
* The SCSI-ml will retry the command.
|
|
*/
|
|
static void fc_fcp_retry_cmd(struct fc_fcp_pkt *fsp, int status_code)
|
|
{
|
|
if (fsp->seq_ptr) {
|
|
fc_exch_done(fsp->seq_ptr);
|
|
fsp->seq_ptr = NULL;
|
|
}
|
|
|
|
fsp->state &= ~FC_SRB_ABORT_PENDING;
|
|
fsp->io_status = 0;
|
|
fsp->status_code = status_code;
|
|
fc_fcp_complete_locked(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_ddp_setup() - Calls a LLD's ddp_setup routine to set up DDP context
|
|
* @fsp: The FCP packet that will manage the DDP frames
|
|
* @xid: The XID that will be used for the DDP exchange
|
|
*/
|
|
void fc_fcp_ddp_setup(struct fc_fcp_pkt *fsp, u16 xid)
|
|
{
|
|
struct fc_lport *lport;
|
|
|
|
lport = fsp->lp;
|
|
if ((fsp->req_flags & FC_SRB_READ) &&
|
|
(lport->lro_enabled) && (lport->tt.ddp_setup)) {
|
|
if (lport->tt.ddp_setup(lport, xid, scsi_sglist(fsp->cmd),
|
|
scsi_sg_count(fsp->cmd)))
|
|
fsp->xfer_ddp = xid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_ddp_done() - Calls a LLD's ddp_done routine to release any
|
|
* DDP related resources for a fcp_pkt
|
|
* @fsp: The FCP packet that DDP had been used on
|
|
*/
|
|
void fc_fcp_ddp_done(struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_lport *lport;
|
|
|
|
if (!fsp)
|
|
return;
|
|
|
|
if (fsp->xfer_ddp == FC_XID_UNKNOWN)
|
|
return;
|
|
|
|
lport = fsp->lp;
|
|
if (lport->tt.ddp_done) {
|
|
fsp->xfer_len = lport->tt.ddp_done(lport, fsp->xfer_ddp);
|
|
fsp->xfer_ddp = FC_XID_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_can_queue_ramp_up() - increases can_queue
|
|
* @lport: lport to ramp up can_queue
|
|
*/
|
|
static void fc_fcp_can_queue_ramp_up(struct fc_lport *lport)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
unsigned long flags;
|
|
int can_queue;
|
|
|
|
spin_lock_irqsave(lport->host->host_lock, flags);
|
|
|
|
if (si->last_can_queue_ramp_up_time &&
|
|
(time_before(jiffies, si->last_can_queue_ramp_up_time +
|
|
FC_CAN_QUEUE_PERIOD)))
|
|
goto unlock;
|
|
|
|
if (time_before(jiffies, si->last_can_queue_ramp_down_time +
|
|
FC_CAN_QUEUE_PERIOD))
|
|
goto unlock;
|
|
|
|
si->last_can_queue_ramp_up_time = jiffies;
|
|
|
|
can_queue = lport->host->can_queue << 1;
|
|
if (can_queue >= si->max_can_queue) {
|
|
can_queue = si->max_can_queue;
|
|
si->last_can_queue_ramp_down_time = 0;
|
|
}
|
|
lport->host->can_queue = can_queue;
|
|
shost_printk(KERN_ERR, lport->host, "libfc: increased "
|
|
"can_queue to %d.\n", can_queue);
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(lport->host->host_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_can_queue_ramp_down() - reduces can_queue
|
|
* @lport: lport to reduce can_queue
|
|
*
|
|
* If we are getting memory allocation failures, then we may
|
|
* be trying to execute too many commands. We let the running
|
|
* commands complete or timeout, then try again with a reduced
|
|
* can_queue. Eventually we will hit the point where we run
|
|
* on all reserved structs.
|
|
*/
|
|
static bool fc_fcp_can_queue_ramp_down(struct fc_lport *lport)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
unsigned long flags;
|
|
int can_queue;
|
|
bool changed = false;
|
|
|
|
spin_lock_irqsave(lport->host->host_lock, flags);
|
|
|
|
if (si->last_can_queue_ramp_down_time &&
|
|
(time_before(jiffies, si->last_can_queue_ramp_down_time +
|
|
FC_CAN_QUEUE_PERIOD)))
|
|
goto unlock;
|
|
|
|
si->last_can_queue_ramp_down_time = jiffies;
|
|
|
|
can_queue = lport->host->can_queue;
|
|
can_queue >>= 1;
|
|
if (!can_queue)
|
|
can_queue = 1;
|
|
lport->host->can_queue = can_queue;
|
|
changed = true;
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(lport->host->host_lock, flags);
|
|
return changed;
|
|
}
|
|
|
|
/*
|
|
* fc_fcp_frame_alloc() - Allocates fc_frame structure and buffer.
|
|
* @lport: fc lport struct
|
|
* @len: payload length
|
|
*
|
|
* Allocates fc_frame structure and buffer but if fails to allocate
|
|
* then reduce can_queue.
|
|
*/
|
|
static inline struct fc_frame *fc_fcp_frame_alloc(struct fc_lport *lport,
|
|
size_t len)
|
|
{
|
|
struct fc_frame *fp;
|
|
|
|
fp = fc_frame_alloc(lport, len);
|
|
if (likely(fp))
|
|
return fp;
|
|
|
|
this_cpu_inc(lport->stats->FcpFrameAllocFails);
|
|
/* error case */
|
|
fc_fcp_can_queue_ramp_down(lport);
|
|
shost_printk(KERN_ERR, lport->host,
|
|
"libfc: Could not allocate frame, "
|
|
"reducing can_queue to %d.\n", lport->host->can_queue);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_fsp_rec_tov() - Helper function to get REC_TOV
|
|
* @fsp: the FCP packet
|
|
*
|
|
* Returns rec tov in jiffies as rpriv->e_d_tov + 1 second
|
|
*/
|
|
static inline unsigned int get_fsp_rec_tov(struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_rport_libfc_priv *rpriv = fsp->rport->dd_data;
|
|
unsigned int e_d_tov = FC_DEF_E_D_TOV;
|
|
|
|
if (rpriv && rpriv->e_d_tov > e_d_tov)
|
|
e_d_tov = rpriv->e_d_tov;
|
|
return msecs_to_jiffies(e_d_tov) + HZ;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_recv_data() - Handler for receiving SCSI-FCP data from a target
|
|
* @fsp: The FCP packet the data is on
|
|
* @fp: The data frame
|
|
*/
|
|
static void fc_fcp_recv_data(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
struct scsi_cmnd *sc = fsp->cmd;
|
|
struct fc_lport *lport = fsp->lp;
|
|
struct fc_frame_header *fh;
|
|
size_t start_offset;
|
|
size_t offset;
|
|
u32 crc;
|
|
u32 copy_len = 0;
|
|
size_t len;
|
|
void *buf;
|
|
struct scatterlist *sg;
|
|
u32 nents;
|
|
u8 host_bcode = FC_COMPLETE;
|
|
|
|
fh = fc_frame_header_get(fp);
|
|
offset = ntohl(fh->fh_parm_offset);
|
|
start_offset = offset;
|
|
len = fr_len(fp) - sizeof(*fh);
|
|
buf = fc_frame_payload_get(fp, 0);
|
|
|
|
/*
|
|
* if this I/O is ddped then clear it and initiate recovery since data
|
|
* frames are expected to be placed directly in that case.
|
|
*
|
|
* Indicate error to scsi-ml because something went wrong with the
|
|
* ddp handling to get us here.
|
|
*/
|
|
if (fsp->xfer_ddp != FC_XID_UNKNOWN) {
|
|
fc_fcp_ddp_done(fsp);
|
|
FC_FCP_DBG(fsp, "DDP I/O in fc_fcp_recv_data set ERROR\n");
|
|
host_bcode = FC_ERROR;
|
|
goto err;
|
|
}
|
|
if (offset + len > fsp->data_len) {
|
|
/* this should never happen */
|
|
if ((fr_flags(fp) & FCPHF_CRC_UNCHECKED) &&
|
|
fc_frame_crc_check(fp))
|
|
goto crc_err;
|
|
FC_FCP_DBG(fsp, "data received past end. len %zx offset %zx "
|
|
"data_len %x\n", len, offset, fsp->data_len);
|
|
|
|
/* Data is corrupted indicate scsi-ml should retry */
|
|
host_bcode = FC_DATA_OVRRUN;
|
|
goto err;
|
|
}
|
|
if (offset != fsp->xfer_len)
|
|
fsp->state |= FC_SRB_DISCONTIG;
|
|
|
|
sg = scsi_sglist(sc);
|
|
nents = scsi_sg_count(sc);
|
|
|
|
if (!(fr_flags(fp) & FCPHF_CRC_UNCHECKED)) {
|
|
copy_len = fc_copy_buffer_to_sglist(buf, len, sg, &nents,
|
|
&offset, NULL);
|
|
} else {
|
|
crc = crc32(~0, (u8 *) fh, sizeof(*fh));
|
|
copy_len = fc_copy_buffer_to_sglist(buf, len, sg, &nents,
|
|
&offset, &crc);
|
|
buf = fc_frame_payload_get(fp, 0);
|
|
if (len % 4)
|
|
crc = crc32(crc, buf + len, 4 - (len % 4));
|
|
|
|
if (~crc != le32_to_cpu(fr_crc(fp))) {
|
|
crc_err:
|
|
this_cpu_inc(lport->stats->ErrorFrames);
|
|
/* per cpu count, not total count, but OK for limit */
|
|
if (this_cpu_inc_return(lport->stats->InvalidCRCCount) < FC_MAX_ERROR_CNT)
|
|
printk(KERN_WARNING "libfc: CRC error on data "
|
|
"frame for port (%6.6x)\n",
|
|
lport->port_id);
|
|
/*
|
|
* Assume the frame is total garbage.
|
|
* We may have copied it over the good part
|
|
* of the buffer.
|
|
* If so, we need to retry the entire operation.
|
|
* Otherwise, ignore it.
|
|
*/
|
|
if (fsp->state & FC_SRB_DISCONTIG) {
|
|
host_bcode = FC_CRC_ERROR;
|
|
goto err;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fsp->xfer_contig_end == start_offset)
|
|
fsp->xfer_contig_end += copy_len;
|
|
fsp->xfer_len += copy_len;
|
|
|
|
/*
|
|
* In the very rare event that this data arrived after the response
|
|
* and completes the transfer, call the completion handler.
|
|
*/
|
|
if (unlikely(fsp->state & FC_SRB_RCV_STATUS) &&
|
|
fsp->xfer_len == fsp->data_len - fsp->scsi_resid) {
|
|
FC_FCP_DBG( fsp, "complete out-of-order sequence\n" );
|
|
fc_fcp_complete_locked(fsp);
|
|
}
|
|
return;
|
|
err:
|
|
fc_fcp_recovery(fsp, host_bcode);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_send_data() - Send SCSI data to a target
|
|
* @fsp: The FCP packet the data is on
|
|
* @seq: The sequence the data is to be sent on
|
|
* @offset: The starting offset for this data request
|
|
* @seq_blen: The burst length for this data request
|
|
*
|
|
* Called after receiving a Transfer Ready data descriptor.
|
|
* If the LLD is capable of sequence offload then send down the
|
|
* seq_blen amount of data in single frame, otherwise send
|
|
* multiple frames of the maximum frame payload supported by
|
|
* the target port.
|
|
*/
|
|
static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
|
|
size_t offset, size_t seq_blen)
|
|
{
|
|
struct fc_exch *ep;
|
|
struct scsi_cmnd *sc;
|
|
struct scatterlist *sg;
|
|
struct fc_frame *fp = NULL;
|
|
struct fc_lport *lport = fsp->lp;
|
|
struct page *page;
|
|
size_t remaining;
|
|
size_t t_blen;
|
|
size_t tlen;
|
|
size_t sg_bytes;
|
|
size_t frame_offset, fh_parm_offset;
|
|
size_t off;
|
|
int error;
|
|
void *data = NULL;
|
|
void *page_addr;
|
|
int using_sg = lport->sg_supp;
|
|
u32 f_ctl;
|
|
|
|
WARN_ON(seq_blen <= 0);
|
|
if (unlikely(offset + seq_blen > fsp->data_len)) {
|
|
/* this should never happen */
|
|
FC_FCP_DBG(fsp, "xfer-ready past end. seq_blen %zx "
|
|
"offset %zx\n", seq_blen, offset);
|
|
fc_fcp_send_abort(fsp);
|
|
return 0;
|
|
} else if (offset != fsp->xfer_len) {
|
|
/* Out of Order Data Request - no problem, but unexpected. */
|
|
FC_FCP_DBG(fsp, "xfer-ready non-contiguous. "
|
|
"seq_blen %zx offset %zx\n", seq_blen, offset);
|
|
}
|
|
|
|
/*
|
|
* if LLD is capable of seq_offload then set transport
|
|
* burst length (t_blen) to seq_blen, otherwise set t_blen
|
|
* to max FC frame payload previously set in fsp->max_payload.
|
|
*/
|
|
t_blen = fsp->max_payload;
|
|
if (lport->seq_offload) {
|
|
t_blen = min(seq_blen, (size_t)lport->lso_max);
|
|
FC_FCP_DBG(fsp, "fsp=%p:lso:blen=%zx lso_max=0x%x t_blen=%zx\n",
|
|
fsp, seq_blen, lport->lso_max, t_blen);
|
|
}
|
|
|
|
if (t_blen > 512)
|
|
t_blen &= ~(512 - 1); /* round down to block size */
|
|
sc = fsp->cmd;
|
|
|
|
remaining = seq_blen;
|
|
fh_parm_offset = frame_offset = offset;
|
|
tlen = 0;
|
|
seq = fc_seq_start_next(seq);
|
|
f_ctl = FC_FC_REL_OFF;
|
|
WARN_ON(!seq);
|
|
|
|
sg = scsi_sglist(sc);
|
|
|
|
while (remaining > 0 && sg) {
|
|
if (offset >= sg->length) {
|
|
offset -= sg->length;
|
|
sg = sg_next(sg);
|
|
continue;
|
|
}
|
|
if (!fp) {
|
|
tlen = min(t_blen, remaining);
|
|
|
|
/*
|
|
* TODO. Temporary workaround. fc_seq_send() can't
|
|
* handle odd lengths in non-linear skbs.
|
|
* This will be the final fragment only.
|
|
*/
|
|
if (tlen % 4)
|
|
using_sg = 0;
|
|
fp = fc_frame_alloc(lport, using_sg ? 0 : tlen);
|
|
if (!fp)
|
|
return -ENOMEM;
|
|
|
|
data = fc_frame_header_get(fp) + 1;
|
|
fh_parm_offset = frame_offset;
|
|
fr_max_payload(fp) = fsp->max_payload;
|
|
}
|
|
|
|
off = offset + sg->offset;
|
|
sg_bytes = min(tlen, sg->length - offset);
|
|
sg_bytes = min(sg_bytes,
|
|
(size_t) (PAGE_SIZE - (off & ~PAGE_MASK)));
|
|
page = sg_page(sg) + (off >> PAGE_SHIFT);
|
|
if (using_sg) {
|
|
get_page(page);
|
|
skb_fill_page_desc(fp_skb(fp),
|
|
skb_shinfo(fp_skb(fp))->nr_frags,
|
|
page, off & ~PAGE_MASK, sg_bytes);
|
|
fp_skb(fp)->data_len += sg_bytes;
|
|
fr_len(fp) += sg_bytes;
|
|
fp_skb(fp)->truesize += PAGE_SIZE;
|
|
} else {
|
|
/*
|
|
* The scatterlist item may be bigger than PAGE_SIZE,
|
|
* but we must not cross pages inside the kmap.
|
|
*/
|
|
page_addr = kmap_atomic(page);
|
|
memcpy(data, (char *)page_addr + (off & ~PAGE_MASK),
|
|
sg_bytes);
|
|
kunmap_atomic(page_addr);
|
|
data += sg_bytes;
|
|
}
|
|
offset += sg_bytes;
|
|
frame_offset += sg_bytes;
|
|
tlen -= sg_bytes;
|
|
remaining -= sg_bytes;
|
|
|
|
if ((skb_shinfo(fp_skb(fp))->nr_frags < FC_FRAME_SG_LEN) &&
|
|
(tlen))
|
|
continue;
|
|
|
|
/*
|
|
* Send sequence with transfer sequence initiative in case
|
|
* this is last FCP frame of the sequence.
|
|
*/
|
|
if (remaining == 0)
|
|
f_ctl |= FC_FC_SEQ_INIT | FC_FC_END_SEQ;
|
|
|
|
ep = fc_seq_exch(seq);
|
|
fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid,
|
|
FC_TYPE_FCP, f_ctl, fh_parm_offset);
|
|
|
|
/*
|
|
* send fragment using for a sequence.
|
|
*/
|
|
error = fc_seq_send(lport, seq, fp);
|
|
if (error) {
|
|
WARN_ON(1); /* send error should be rare */
|
|
return error;
|
|
}
|
|
fp = NULL;
|
|
}
|
|
fsp->xfer_len += seq_blen; /* premature count? */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_abts_resp() - Receive an ABTS response
|
|
* @fsp: The FCP packet that is being aborted
|
|
* @fp: The response frame
|
|
*/
|
|
static void fc_fcp_abts_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
int ba_done = 1;
|
|
struct fc_ba_rjt *brp;
|
|
struct fc_frame_header *fh;
|
|
|
|
fh = fc_frame_header_get(fp);
|
|
switch (fh->fh_r_ctl) {
|
|
case FC_RCTL_BA_ACC:
|
|
break;
|
|
case FC_RCTL_BA_RJT:
|
|
brp = fc_frame_payload_get(fp, sizeof(*brp));
|
|
if (brp && brp->br_reason == FC_BA_RJT_LOG_ERR)
|
|
break;
|
|
fallthrough;
|
|
default:
|
|
/*
|
|
* we will let the command timeout
|
|
* and scsi-ml recover in this case,
|
|
* therefore cleared the ba_done flag.
|
|
*/
|
|
ba_done = 0;
|
|
}
|
|
|
|
if (ba_done)
|
|
fc_fcp_abort_done(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_recv() - Receive an FCP frame
|
|
* @seq: The sequence the frame is on
|
|
* @fp: The received frame
|
|
* @arg: The related FCP packet
|
|
*
|
|
* Context: Called from Soft IRQ context. Can not be called
|
|
* holding the FCP packet list lock.
|
|
*/
|
|
static void fc_fcp_recv(struct fc_seq *seq, struct fc_frame *fp, void *arg)
|
|
{
|
|
struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)arg;
|
|
struct fc_lport *lport = fsp->lp;
|
|
struct fc_frame_header *fh;
|
|
struct fcp_txrdy *dd;
|
|
u8 r_ctl;
|
|
int rc = 0;
|
|
|
|
if (IS_ERR(fp)) {
|
|
fc_fcp_error(fsp, fp);
|
|
return;
|
|
}
|
|
|
|
fh = fc_frame_header_get(fp);
|
|
r_ctl = fh->fh_r_ctl;
|
|
|
|
if (lport->state != LPORT_ST_READY) {
|
|
FC_FCP_DBG(fsp, "lport state %d, ignoring r_ctl %x\n",
|
|
lport->state, r_ctl);
|
|
goto out;
|
|
}
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
|
|
if (fh->fh_type == FC_TYPE_BLS) {
|
|
fc_fcp_abts_resp(fsp, fp);
|
|
goto unlock;
|
|
}
|
|
|
|
if (fsp->state & (FC_SRB_ABORTED | FC_SRB_ABORT_PENDING)) {
|
|
FC_FCP_DBG(fsp, "command aborted, ignoring r_ctl %x\n", r_ctl);
|
|
goto unlock;
|
|
}
|
|
|
|
if (r_ctl == FC_RCTL_DD_DATA_DESC) {
|
|
/*
|
|
* received XFER RDY from the target
|
|
* need to send data to the target
|
|
*/
|
|
WARN_ON(fr_flags(fp) & FCPHF_CRC_UNCHECKED);
|
|
dd = fc_frame_payload_get(fp, sizeof(*dd));
|
|
WARN_ON(!dd);
|
|
|
|
rc = fc_fcp_send_data(fsp, seq,
|
|
(size_t) ntohl(dd->ft_data_ro),
|
|
(size_t) ntohl(dd->ft_burst_len));
|
|
if (!rc)
|
|
seq->rec_data = fsp->xfer_len;
|
|
} else if (r_ctl == FC_RCTL_DD_SOL_DATA) {
|
|
/*
|
|
* received a DATA frame
|
|
* next we will copy the data to the system buffer
|
|
*/
|
|
WARN_ON(fr_len(fp) < sizeof(*fh)); /* len may be 0 */
|
|
fc_fcp_recv_data(fsp, fp);
|
|
seq->rec_data = fsp->xfer_contig_end;
|
|
} else if (r_ctl == FC_RCTL_DD_CMD_STATUS) {
|
|
WARN_ON(fr_flags(fp) & FCPHF_CRC_UNCHECKED);
|
|
|
|
fc_fcp_resp(fsp, fp);
|
|
} else {
|
|
FC_FCP_DBG(fsp, "unexpected frame. r_ctl %x\n", r_ctl);
|
|
}
|
|
unlock:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_frame_free(fp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_resp() - Handler for FCP responses
|
|
* @fsp: The FCP packet the response is for
|
|
* @fp: The response frame
|
|
*/
|
|
static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
struct fc_frame_header *fh;
|
|
struct fcp_resp *fc_rp;
|
|
struct fcp_resp_ext *rp_ex;
|
|
struct fcp_resp_rsp_info *fc_rp_info;
|
|
u32 plen;
|
|
u32 expected_len;
|
|
u32 respl = 0;
|
|
u32 snsl = 0;
|
|
u8 flags = 0;
|
|
|
|
plen = fr_len(fp);
|
|
fh = (struct fc_frame_header *)fr_hdr(fp);
|
|
if (unlikely(plen < sizeof(*fh) + sizeof(*fc_rp)))
|
|
goto len_err;
|
|
plen -= sizeof(*fh);
|
|
fc_rp = (struct fcp_resp *)(fh + 1);
|
|
fsp->cdb_status = fc_rp->fr_status;
|
|
flags = fc_rp->fr_flags;
|
|
fsp->scsi_comp_flags = flags;
|
|
expected_len = fsp->data_len;
|
|
|
|
/* if ddp, update xfer len */
|
|
fc_fcp_ddp_done(fsp);
|
|
|
|
if (unlikely((flags & ~FCP_CONF_REQ) || fc_rp->fr_status)) {
|
|
rp_ex = (void *)(fc_rp + 1);
|
|
if (flags & (FCP_RSP_LEN_VAL | FCP_SNS_LEN_VAL)) {
|
|
if (plen < sizeof(*fc_rp) + sizeof(*rp_ex))
|
|
goto len_err;
|
|
fc_rp_info = (struct fcp_resp_rsp_info *)(rp_ex + 1);
|
|
if (flags & FCP_RSP_LEN_VAL) {
|
|
respl = ntohl(rp_ex->fr_rsp_len);
|
|
if ((respl != FCP_RESP_RSP_INFO_LEN4) &&
|
|
(respl != FCP_RESP_RSP_INFO_LEN8))
|
|
goto len_err;
|
|
if (fsp->wait_for_comp) {
|
|
/* Abuse cdb_status for rsp code */
|
|
fsp->cdb_status = fc_rp_info->rsp_code;
|
|
complete(&fsp->tm_done);
|
|
/*
|
|
* tmfs will not have any scsi cmd so
|
|
* exit here
|
|
*/
|
|
return;
|
|
}
|
|
}
|
|
if (flags & FCP_SNS_LEN_VAL) {
|
|
snsl = ntohl(rp_ex->fr_sns_len);
|
|
if (snsl > SCSI_SENSE_BUFFERSIZE)
|
|
snsl = SCSI_SENSE_BUFFERSIZE;
|
|
memcpy(fsp->cmd->sense_buffer,
|
|
(char *)fc_rp_info + respl, snsl);
|
|
}
|
|
}
|
|
if (flags & (FCP_RESID_UNDER | FCP_RESID_OVER)) {
|
|
if (plen < sizeof(*fc_rp) + sizeof(rp_ex->fr_resid))
|
|
goto len_err;
|
|
if (flags & FCP_RESID_UNDER) {
|
|
fsp->scsi_resid = ntohl(rp_ex->fr_resid);
|
|
/*
|
|
* The cmnd->underflow is the minimum number of
|
|
* bytes that must be transferred for this
|
|
* command. Provided a sense condition is not
|
|
* present, make sure the actual amount
|
|
* transferred is at least the underflow value
|
|
* or fail.
|
|
*/
|
|
if (!(flags & FCP_SNS_LEN_VAL) &&
|
|
(fc_rp->fr_status == 0) &&
|
|
(scsi_bufflen(fsp->cmd) -
|
|
fsp->scsi_resid) < fsp->cmd->underflow)
|
|
goto err;
|
|
expected_len -= fsp->scsi_resid;
|
|
} else {
|
|
fsp->status_code = FC_ERROR;
|
|
}
|
|
}
|
|
}
|
|
fsp->state |= FC_SRB_RCV_STATUS;
|
|
|
|
/*
|
|
* Check for missing or extra data frames.
|
|
*/
|
|
if (unlikely(fsp->cdb_status == SAM_STAT_GOOD &&
|
|
fsp->xfer_len != expected_len)) {
|
|
if (fsp->xfer_len < expected_len) {
|
|
/*
|
|
* Some data may be queued locally,
|
|
* Wait a at least one jiffy to see if it is delivered.
|
|
* If this expires without data, we may do SRR.
|
|
*/
|
|
if (fsp->lp->qfull) {
|
|
FC_FCP_DBG(fsp, "tgt %6.6x queue busy retry\n",
|
|
fsp->rport->port_id);
|
|
return;
|
|
}
|
|
FC_FCP_DBG(fsp, "tgt %6.6x xfer len %zx data underrun "
|
|
"len %x, data len %x\n",
|
|
fsp->rport->port_id,
|
|
fsp->xfer_len, expected_len, fsp->data_len);
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
return;
|
|
}
|
|
fsp->status_code = FC_DATA_OVRRUN;
|
|
FC_FCP_DBG(fsp, "tgt %6.6x xfer len %zx greater than expected, "
|
|
"len %x, data len %x\n",
|
|
fsp->rport->port_id,
|
|
fsp->xfer_len, expected_len, fsp->data_len);
|
|
}
|
|
fc_fcp_complete_locked(fsp);
|
|
return;
|
|
|
|
len_err:
|
|
FC_FCP_DBG(fsp, "short FCP response. flags 0x%x len %u respl %u "
|
|
"snsl %u\n", flags, fr_len(fp), respl, snsl);
|
|
err:
|
|
fsp->status_code = FC_ERROR;
|
|
fc_fcp_complete_locked(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_complete_locked() - Complete processing of a fcp_pkt with the
|
|
* fcp_pkt lock held
|
|
* @fsp: The FCP packet to be completed
|
|
*
|
|
* This function may sleep if a timer is pending. The packet lock must be
|
|
* held, and the host lock must not be held.
|
|
*/
|
|
static void fc_fcp_complete_locked(struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_lport *lport = fsp->lp;
|
|
struct fc_seq *seq;
|
|
struct fc_exch *ep;
|
|
u32 f_ctl;
|
|
|
|
if (fsp->state & FC_SRB_ABORT_PENDING)
|
|
return;
|
|
|
|
if (fsp->state & FC_SRB_ABORTED) {
|
|
if (!fsp->status_code)
|
|
fsp->status_code = FC_CMD_ABORTED;
|
|
} else {
|
|
/*
|
|
* Test for transport underrun, independent of response
|
|
* underrun status.
|
|
*/
|
|
if (fsp->cdb_status == SAM_STAT_GOOD &&
|
|
fsp->xfer_len < fsp->data_len && !fsp->io_status &&
|
|
(!(fsp->scsi_comp_flags & FCP_RESID_UNDER) ||
|
|
fsp->xfer_len < fsp->data_len - fsp->scsi_resid)) {
|
|
FC_FCP_DBG(fsp, "data underrun, xfer %zx data %x\n",
|
|
fsp->xfer_len, fsp->data_len);
|
|
fsp->status_code = FC_DATA_UNDRUN;
|
|
}
|
|
}
|
|
|
|
seq = fsp->seq_ptr;
|
|
if (seq) {
|
|
fsp->seq_ptr = NULL;
|
|
if (unlikely(fsp->scsi_comp_flags & FCP_CONF_REQ)) {
|
|
struct fc_frame *conf_frame;
|
|
struct fc_seq *csp;
|
|
|
|
csp = fc_seq_start_next(seq);
|
|
conf_frame = fc_fcp_frame_alloc(fsp->lp, 0);
|
|
if (conf_frame) {
|
|
f_ctl = FC_FC_SEQ_INIT;
|
|
f_ctl |= FC_FC_LAST_SEQ | FC_FC_END_SEQ;
|
|
ep = fc_seq_exch(seq);
|
|
fc_fill_fc_hdr(conf_frame, FC_RCTL_DD_SOL_CTL,
|
|
ep->did, ep->sid,
|
|
FC_TYPE_FCP, f_ctl, 0);
|
|
fc_seq_send(lport, csp, conf_frame);
|
|
}
|
|
}
|
|
fc_exch_done(seq);
|
|
}
|
|
/*
|
|
* Some resets driven by SCSI are not I/Os and do not have
|
|
* SCSI commands associated with the requests. We should not
|
|
* call I/O completion if we do not have a SCSI command.
|
|
*/
|
|
if (fsp->cmd)
|
|
fc_io_compl(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_cleanup_cmd() - Cancel the active exchange on a fcp_pkt
|
|
* @fsp: The FCP packet whose exchanges should be canceled
|
|
* @error: The reason for the cancellation
|
|
*/
|
|
static void fc_fcp_cleanup_cmd(struct fc_fcp_pkt *fsp, int error)
|
|
{
|
|
if (fsp->seq_ptr) {
|
|
fc_exch_done(fsp->seq_ptr);
|
|
fsp->seq_ptr = NULL;
|
|
}
|
|
fsp->status_code = error;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_cleanup_each_cmd() - Cancel all exchanges on a local port
|
|
* @lport: The local port whose exchanges should be canceled
|
|
* @id: The target's ID
|
|
* @lun: The LUN
|
|
* @error: The reason for cancellation
|
|
*
|
|
* If lun or id is -1, they are ignored.
|
|
*/
|
|
static void fc_fcp_cleanup_each_cmd(struct fc_lport *lport, unsigned int id,
|
|
unsigned int lun, int error)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
struct fc_fcp_pkt *fsp;
|
|
struct scsi_cmnd *sc_cmd;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
restart:
|
|
list_for_each_entry(fsp, &si->scsi_pkt_queue, list) {
|
|
sc_cmd = fsp->cmd;
|
|
if (id != -1 && scmd_id(sc_cmd) != id)
|
|
continue;
|
|
|
|
if (lun != -1 && sc_cmd->device->lun != lun)
|
|
continue;
|
|
|
|
fc_fcp_pkt_hold(fsp);
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
if (!(fsp->state & FC_SRB_COMPL)) {
|
|
fsp->state |= FC_SRB_COMPL;
|
|
/*
|
|
* TODO: dropping scsi_pkt_lock and then reacquiring
|
|
* again around fc_fcp_cleanup_cmd() is required,
|
|
* since fc_fcp_cleanup_cmd() calls into
|
|
* fc_seq_set_resp() and that func preempts cpu using
|
|
* schedule. May be schedule and related code should be
|
|
* removed instead of unlocking here to avoid scheduling
|
|
* while atomic bug.
|
|
*/
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
|
|
fc_fcp_cleanup_cmd(fsp, error);
|
|
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
fc_io_compl(fsp);
|
|
}
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
|
|
fc_fcp_pkt_release(fsp);
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
/*
|
|
* while we dropped the lock multiple pkts could
|
|
* have been released, so we have to start over.
|
|
*/
|
|
goto restart;
|
|
}
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_abort_io() - Abort all FCP-SCSI exchanges on a local port
|
|
* @lport: The local port whose exchanges are to be aborted
|
|
*/
|
|
static void fc_fcp_abort_io(struct fc_lport *lport)
|
|
{
|
|
fc_fcp_cleanup_each_cmd(lport, -1, -1, FC_HRD_ERROR);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_pkt_send() - Send a fcp_pkt
|
|
* @lport: The local port to send the FCP packet on
|
|
* @fsp: The FCP packet to send
|
|
*
|
|
* Return: Zero for success and -1 for failure
|
|
* Locks: Called without locks held
|
|
*/
|
|
static int fc_fcp_pkt_send(struct fc_lport *lport, struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
libfc_priv(fsp->cmd)->fsp = fsp;
|
|
fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
|
|
fsp->cdb_cmd.fc_flags = fsp->req_flags & ~FCP_CFL_LEN_MASK;
|
|
|
|
int_to_scsilun(fsp->cmd->device->lun, &fsp->cdb_cmd.fc_lun);
|
|
memcpy(fsp->cdb_cmd.fc_cdb, fsp->cmd->cmnd, fsp->cmd->cmd_len);
|
|
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
list_add_tail(&fsp->list, &si->scsi_pkt_queue);
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
rc = lport->tt.fcp_cmd_send(lport, fsp, fc_fcp_recv);
|
|
if (unlikely(rc)) {
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
libfc_priv(fsp->cmd)->fsp = NULL;
|
|
list_del(&fsp->list);
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_cmd_send() - Send a FCP command
|
|
* @lport: The local port to send the command on
|
|
* @fsp: The FCP packet the command is on
|
|
* @resp: The handler for the response
|
|
*/
|
|
static int fc_fcp_cmd_send(struct fc_lport *lport, struct fc_fcp_pkt *fsp,
|
|
void (*resp)(struct fc_seq *,
|
|
struct fc_frame *fp,
|
|
void *arg))
|
|
{
|
|
struct fc_frame *fp;
|
|
struct fc_seq *seq;
|
|
struct fc_rport *rport;
|
|
struct fc_rport_libfc_priv *rpriv;
|
|
const size_t len = sizeof(fsp->cdb_cmd);
|
|
int rc = 0;
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
return 0;
|
|
|
|
fp = fc_fcp_frame_alloc(lport, sizeof(fsp->cdb_cmd));
|
|
if (!fp) {
|
|
rc = -1;
|
|
goto unlock;
|
|
}
|
|
|
|
memcpy(fc_frame_payload_get(fp, len), &fsp->cdb_cmd, len);
|
|
fr_fsp(fp) = fsp;
|
|
rport = fsp->rport;
|
|
fsp->max_payload = rport->maxframe_size;
|
|
rpriv = rport->dd_data;
|
|
|
|
fc_fill_fc_hdr(fp, FC_RCTL_DD_UNSOL_CMD, rport->port_id,
|
|
rpriv->local_port->port_id, FC_TYPE_FCP,
|
|
FC_FCTL_REQ, 0);
|
|
|
|
seq = fc_exch_seq_send(lport, fp, resp, fc_fcp_pkt_destroy, fsp, 0);
|
|
if (!seq) {
|
|
rc = -1;
|
|
goto unlock;
|
|
}
|
|
fsp->seq_ptr = seq;
|
|
fc_fcp_pkt_hold(fsp); /* hold for fc_fcp_pkt_destroy */
|
|
|
|
fsp->timer.function = fc_fcp_timeout;
|
|
if (rpriv->flags & FC_RP_FLAGS_REC_SUPPORTED)
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
|
|
unlock:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_error() - Handler for FCP layer errors
|
|
* @fsp: The FCP packet the error is on
|
|
* @fp: The frame that has errored
|
|
*/
|
|
static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
int error = PTR_ERR(fp);
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
return;
|
|
|
|
if (error == -FC_EX_CLOSED) {
|
|
fc_fcp_retry_cmd(fsp, FC_ERROR);
|
|
goto unlock;
|
|
}
|
|
|
|
/*
|
|
* clear abort pending, because the lower layer
|
|
* decided to force completion.
|
|
*/
|
|
fsp->state &= ~FC_SRB_ABORT_PENDING;
|
|
fsp->status_code = FC_CMD_PLOGO;
|
|
fc_fcp_complete_locked(fsp);
|
|
unlock:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_pkt_abort() - Abort a fcp_pkt
|
|
* @fsp: The FCP packet to abort on
|
|
*
|
|
* Called to send an abort and then wait for abort completion
|
|
*/
|
|
static int fc_fcp_pkt_abort(struct fc_fcp_pkt *fsp)
|
|
{
|
|
int rc = FAILED;
|
|
unsigned long ticks_left;
|
|
|
|
FC_FCP_DBG(fsp, "pkt abort state %x\n", fsp->state);
|
|
if (fc_fcp_send_abort(fsp)) {
|
|
FC_FCP_DBG(fsp, "failed to send abort\n");
|
|
return FAILED;
|
|
}
|
|
|
|
if (fsp->state & FC_SRB_ABORTED) {
|
|
FC_FCP_DBG(fsp, "target abort cmd completed\n");
|
|
return SUCCESS;
|
|
}
|
|
|
|
init_completion(&fsp->tm_done);
|
|
fsp->wait_for_comp = 1;
|
|
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
ticks_left = wait_for_completion_timeout(&fsp->tm_done,
|
|
FC_SCSI_TM_TOV);
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
fsp->wait_for_comp = 0;
|
|
|
|
if (!ticks_left) {
|
|
FC_FCP_DBG(fsp, "target abort cmd failed\n");
|
|
} else if (fsp->state & FC_SRB_ABORTED) {
|
|
FC_FCP_DBG(fsp, "target abort cmd passed\n");
|
|
rc = SUCCESS;
|
|
fc_fcp_complete_locked(fsp);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* fc_lun_reset_send() - Send LUN reset command
|
|
* @t: Timer context used to fetch the FSP packet
|
|
*/
|
|
static void fc_lun_reset_send(struct timer_list *t)
|
|
{
|
|
struct fc_fcp_pkt *fsp = from_timer(fsp, t, timer);
|
|
struct fc_lport *lport = fsp->lp;
|
|
|
|
if (lport->tt.fcp_cmd_send(lport, fsp, fc_tm_done)) {
|
|
if (fsp->recov_retry++ >= FC_MAX_RECOV_RETRY)
|
|
return;
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
return;
|
|
fsp->timer.function = fc_lun_reset_send;
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
fc_fcp_unlock_pkt(fsp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fc_lun_reset() - Send a LUN RESET command to a device
|
|
* and wait for the reply
|
|
* @lport: The local port to sent the command on
|
|
* @fsp: The FCP packet that identifies the LUN to be reset
|
|
* @id: The SCSI command ID
|
|
* @lun: The LUN ID to be reset
|
|
*/
|
|
static int fc_lun_reset(struct fc_lport *lport, struct fc_fcp_pkt *fsp,
|
|
unsigned int id, unsigned int lun)
|
|
{
|
|
int rc;
|
|
|
|
fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
|
|
fsp->cdb_cmd.fc_tm_flags = FCP_TMF_LUN_RESET;
|
|
int_to_scsilun(lun, &fsp->cdb_cmd.fc_lun);
|
|
|
|
fsp->wait_for_comp = 1;
|
|
init_completion(&fsp->tm_done);
|
|
|
|
fc_lun_reset_send(&fsp->timer);
|
|
|
|
/*
|
|
* wait for completion of reset
|
|
* after that make sure all commands are terminated
|
|
*/
|
|
rc = wait_for_completion_timeout(&fsp->tm_done, FC_SCSI_TM_TOV);
|
|
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
fsp->state |= FC_SRB_COMPL;
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
|
|
del_timer_sync(&fsp->timer);
|
|
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
if (fsp->seq_ptr) {
|
|
fc_exch_done(fsp->seq_ptr);
|
|
fsp->seq_ptr = NULL;
|
|
}
|
|
fsp->wait_for_comp = 0;
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
|
|
if (!rc) {
|
|
FC_SCSI_DBG(lport, "lun reset failed\n");
|
|
return FAILED;
|
|
}
|
|
|
|
/* cdb_status holds the tmf's rsp code */
|
|
if (fsp->cdb_status != FCP_TMF_CMPL)
|
|
return FAILED;
|
|
|
|
FC_SCSI_DBG(lport, "lun reset to lun %u completed\n", lun);
|
|
fc_fcp_cleanup_each_cmd(lport, id, lun, FC_CMD_ABORTED);
|
|
return SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* fc_tm_done() - Task Management response handler
|
|
* @seq: The sequence that the response is on
|
|
* @fp: The response frame
|
|
* @arg: The FCP packet the response is for
|
|
*/
|
|
static void fc_tm_done(struct fc_seq *seq, struct fc_frame *fp, void *arg)
|
|
{
|
|
struct fc_fcp_pkt *fsp = arg;
|
|
struct fc_frame_header *fh;
|
|
|
|
if (IS_ERR(fp)) {
|
|
/*
|
|
* If there is an error just let it timeout or wait
|
|
* for TMF to be aborted if it timedout.
|
|
*
|
|
* scsi-eh will escalate for when either happens.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
|
|
/*
|
|
* raced with eh timeout handler.
|
|
*/
|
|
if (!fsp->seq_ptr || !fsp->wait_for_comp)
|
|
goto out_unlock;
|
|
|
|
fh = fc_frame_header_get(fp);
|
|
if (fh->fh_type != FC_TYPE_BLS)
|
|
fc_fcp_resp(fsp, fp);
|
|
fsp->seq_ptr = NULL;
|
|
fc_exch_done(seq);
|
|
out_unlock:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_frame_free(fp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_cleanup() - Cleanup all FCP exchanges on a local port
|
|
* @lport: The local port to be cleaned up
|
|
*/
|
|
static void fc_fcp_cleanup(struct fc_lport *lport)
|
|
{
|
|
fc_fcp_cleanup_each_cmd(lport, -1, -1, FC_ERROR);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_timeout() - Handler for fcp_pkt timeouts
|
|
* @t: Timer context used to fetch the FSP packet
|
|
*
|
|
* If REC is supported then just issue it and return. The REC exchange will
|
|
* complete or time out and recovery can continue at that point. Otherwise,
|
|
* if the response has been received without all the data it has been
|
|
* ER_TIMEOUT since the response was received. If the response has not been
|
|
* received we see if data was received recently. If it has been then we
|
|
* continue waiting, otherwise, we abort the command.
|
|
*/
|
|
static void fc_fcp_timeout(struct timer_list *t)
|
|
{
|
|
struct fc_fcp_pkt *fsp = from_timer(fsp, t, timer);
|
|
struct fc_rport *rport = fsp->rport;
|
|
struct fc_rport_libfc_priv *rpriv = rport->dd_data;
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
return;
|
|
|
|
if (fsp->cdb_cmd.fc_tm_flags)
|
|
goto unlock;
|
|
|
|
if (fsp->lp->qfull) {
|
|
FC_FCP_DBG(fsp, "fcp timeout, resetting timer delay %d\n",
|
|
fsp->timer_delay);
|
|
fsp->timer.function = fc_fcp_timeout;
|
|
fc_fcp_timer_set(fsp, fsp->timer_delay);
|
|
goto unlock;
|
|
}
|
|
FC_FCP_DBG(fsp, "fcp timeout, delay %d flags %x state %x\n",
|
|
fsp->timer_delay, rpriv->flags, fsp->state);
|
|
fsp->state |= FC_SRB_FCP_PROCESSING_TMO;
|
|
|
|
if (rpriv->flags & FC_RP_FLAGS_REC_SUPPORTED)
|
|
fc_fcp_rec(fsp);
|
|
else if (fsp->state & FC_SRB_RCV_STATUS)
|
|
fc_fcp_complete_locked(fsp);
|
|
else
|
|
fc_fcp_recovery(fsp, FC_TIMED_OUT);
|
|
fsp->state &= ~FC_SRB_FCP_PROCESSING_TMO;
|
|
unlock:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_rec() - Send a REC ELS request
|
|
* @fsp: The FCP packet to send the REC request on
|
|
*/
|
|
static void fc_fcp_rec(struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_lport *lport;
|
|
struct fc_frame *fp;
|
|
struct fc_rport *rport;
|
|
struct fc_rport_libfc_priv *rpriv;
|
|
|
|
lport = fsp->lp;
|
|
rport = fsp->rport;
|
|
rpriv = rport->dd_data;
|
|
if (!fsp->seq_ptr || rpriv->rp_state != RPORT_ST_READY) {
|
|
fsp->status_code = FC_HRD_ERROR;
|
|
fsp->io_status = 0;
|
|
fc_fcp_complete_locked(fsp);
|
|
return;
|
|
}
|
|
|
|
fp = fc_fcp_frame_alloc(lport, sizeof(struct fc_els_rec));
|
|
if (!fp)
|
|
goto retry;
|
|
|
|
fr_seq(fp) = fsp->seq_ptr;
|
|
fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, rport->port_id,
|
|
rpriv->local_port->port_id, FC_TYPE_ELS,
|
|
FC_FCTL_REQ, 0);
|
|
if (lport->tt.elsct_send(lport, rport->port_id, fp, ELS_REC,
|
|
fc_fcp_rec_resp, fsp,
|
|
2 * lport->r_a_tov)) {
|
|
fc_fcp_pkt_hold(fsp); /* hold while REC outstanding */
|
|
return;
|
|
}
|
|
retry:
|
|
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
else
|
|
fc_fcp_recovery(fsp, FC_TIMED_OUT);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_rec_resp() - Handler for REC ELS responses
|
|
* @seq: The sequence the response is on
|
|
* @fp: The response frame
|
|
* @arg: The FCP packet the response is on
|
|
*
|
|
* If the response is a reject then the scsi layer will handle
|
|
* the timeout. If the response is a LS_ACC then if the I/O was not completed
|
|
* set the timeout and return. If the I/O was completed then complete the
|
|
* exchange and tell the SCSI layer.
|
|
*/
|
|
static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
|
|
{
|
|
struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)arg;
|
|
struct fc_els_rec_acc *recp;
|
|
struct fc_els_ls_rjt *rjt;
|
|
u32 e_stat;
|
|
u8 opcode;
|
|
u32 offset;
|
|
enum dma_data_direction data_dir;
|
|
enum fc_rctl r_ctl;
|
|
struct fc_rport_libfc_priv *rpriv;
|
|
|
|
if (IS_ERR(fp)) {
|
|
fc_fcp_rec_error(fsp, fp);
|
|
return;
|
|
}
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
|
|
fsp->recov_retry = 0;
|
|
opcode = fc_frame_payload_op(fp);
|
|
if (opcode == ELS_LS_RJT) {
|
|
rjt = fc_frame_payload_get(fp, sizeof(*rjt));
|
|
switch (rjt->er_reason) {
|
|
default:
|
|
FC_FCP_DBG(fsp,
|
|
"device %x invalid REC reject %d/%d\n",
|
|
fsp->rport->port_id, rjt->er_reason,
|
|
rjt->er_explan);
|
|
fallthrough;
|
|
case ELS_RJT_UNSUP:
|
|
FC_FCP_DBG(fsp, "device does not support REC\n");
|
|
rpriv = fsp->rport->dd_data;
|
|
/*
|
|
* if we do not spport RECs or got some bogus
|
|
* reason then resetup timer so we check for
|
|
* making progress.
|
|
*/
|
|
rpriv->flags &= ~FC_RP_FLAGS_REC_SUPPORTED;
|
|
break;
|
|
case ELS_RJT_LOGIC:
|
|
case ELS_RJT_UNAB:
|
|
FC_FCP_DBG(fsp, "device %x REC reject %d/%d\n",
|
|
fsp->rport->port_id, rjt->er_reason,
|
|
rjt->er_explan);
|
|
/*
|
|
* If response got lost or is stuck in the
|
|
* queue somewhere we have no idea if and when
|
|
* the response will be received. So quarantine
|
|
* the xid and retry the command.
|
|
*/
|
|
if (rjt->er_explan == ELS_EXPL_OXID_RXID) {
|
|
struct fc_exch *ep = fc_seq_exch(fsp->seq_ptr);
|
|
ep->state |= FC_EX_QUARANTINE;
|
|
fsp->state |= FC_SRB_ABORTED;
|
|
fc_fcp_retry_cmd(fsp, FC_TRANS_RESET);
|
|
break;
|
|
}
|
|
fc_fcp_recovery(fsp, FC_TRANS_RESET);
|
|
break;
|
|
}
|
|
} else if (opcode == ELS_LS_ACC) {
|
|
if (fsp->state & FC_SRB_ABORTED)
|
|
goto unlock_out;
|
|
|
|
data_dir = fsp->cmd->sc_data_direction;
|
|
recp = fc_frame_payload_get(fp, sizeof(*recp));
|
|
offset = ntohl(recp->reca_fc4value);
|
|
e_stat = ntohl(recp->reca_e_stat);
|
|
|
|
if (e_stat & ESB_ST_COMPLETE) {
|
|
|
|
/*
|
|
* The exchange is complete.
|
|
*
|
|
* For output, we must've lost the response.
|
|
* For input, all data must've been sent.
|
|
* We lost may have lost the response
|
|
* (and a confirmation was requested) and maybe
|
|
* some data.
|
|
*
|
|
* If all data received, send SRR
|
|
* asking for response. If partial data received,
|
|
* or gaps, SRR requests data at start of gap.
|
|
* Recovery via SRR relies on in-order-delivery.
|
|
*/
|
|
if (data_dir == DMA_TO_DEVICE) {
|
|
r_ctl = FC_RCTL_DD_CMD_STATUS;
|
|
} else if (fsp->xfer_contig_end == offset) {
|
|
r_ctl = FC_RCTL_DD_CMD_STATUS;
|
|
} else {
|
|
offset = fsp->xfer_contig_end;
|
|
r_ctl = FC_RCTL_DD_SOL_DATA;
|
|
}
|
|
fc_fcp_srr(fsp, r_ctl, offset);
|
|
} else if (e_stat & ESB_ST_SEQ_INIT) {
|
|
/*
|
|
* The remote port has the initiative, so just
|
|
* keep waiting for it to complete.
|
|
*/
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
} else {
|
|
|
|
/*
|
|
* The exchange is incomplete, we have seq. initiative.
|
|
* Lost response with requested confirmation,
|
|
* lost confirmation, lost transfer ready or
|
|
* lost write data.
|
|
*
|
|
* For output, if not all data was received, ask
|
|
* for transfer ready to be repeated.
|
|
*
|
|
* If we received or sent all the data, send SRR to
|
|
* request response.
|
|
*
|
|
* If we lost a response, we may have lost some read
|
|
* data as well.
|
|
*/
|
|
r_ctl = FC_RCTL_DD_SOL_DATA;
|
|
if (data_dir == DMA_TO_DEVICE) {
|
|
r_ctl = FC_RCTL_DD_CMD_STATUS;
|
|
if (offset < fsp->data_len)
|
|
r_ctl = FC_RCTL_DD_DATA_DESC;
|
|
} else if (offset == fsp->xfer_contig_end) {
|
|
r_ctl = FC_RCTL_DD_CMD_STATUS;
|
|
} else if (fsp->xfer_contig_end < offset) {
|
|
offset = fsp->xfer_contig_end;
|
|
}
|
|
fc_fcp_srr(fsp, r_ctl, offset);
|
|
}
|
|
}
|
|
unlock_out:
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_fcp_pkt_release(fsp); /* drop hold for outstanding REC */
|
|
fc_frame_free(fp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_rec_error() - Handler for REC errors
|
|
* @fsp: The FCP packet the error is on
|
|
* @fp: The REC frame
|
|
*/
|
|
static void fc_fcp_rec_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
int error = PTR_ERR(fp);
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
|
|
switch (error) {
|
|
case -FC_EX_CLOSED:
|
|
FC_FCP_DBG(fsp, "REC %p fid %6.6x exchange closed\n",
|
|
fsp, fsp->rport->port_id);
|
|
fc_fcp_retry_cmd(fsp, FC_ERROR);
|
|
break;
|
|
|
|
default:
|
|
FC_FCP_DBG(fsp, "REC %p fid %6.6x error unexpected error %d\n",
|
|
fsp, fsp->rport->port_id, error);
|
|
fsp->status_code = FC_CMD_PLOGO;
|
|
fallthrough;
|
|
|
|
case -FC_EX_TIMEOUT:
|
|
/*
|
|
* Assume REC or LS_ACC was lost.
|
|
* The exchange manager will have aborted REC, so retry.
|
|
*/
|
|
FC_FCP_DBG(fsp, "REC %p fid %6.6x exchange timeout retry %d/%d\n",
|
|
fsp, fsp->rport->port_id, fsp->recov_retry,
|
|
FC_MAX_RECOV_RETRY);
|
|
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
|
|
fc_fcp_rec(fsp);
|
|
else
|
|
fc_fcp_recovery(fsp, FC_ERROR);
|
|
break;
|
|
}
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_fcp_pkt_release(fsp); /* drop hold for outstanding REC */
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_recovery() - Handler for fcp_pkt recovery
|
|
* @fsp: The FCP pkt that needs to be aborted
|
|
* @code: The FCP status code to set
|
|
*/
|
|
static void fc_fcp_recovery(struct fc_fcp_pkt *fsp, u8 code)
|
|
{
|
|
FC_FCP_DBG(fsp, "start recovery code %x\n", code);
|
|
fsp->status_code = code;
|
|
fsp->cdb_status = 0;
|
|
fsp->io_status = 0;
|
|
/*
|
|
* if this fails then we let the scsi command timer fire and
|
|
* scsi-ml escalate.
|
|
*/
|
|
fc_fcp_send_abort(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_srr() - Send a SRR request (Sequence Retransmission Request)
|
|
* @fsp: The FCP packet the SRR is to be sent on
|
|
* @r_ctl: The R_CTL field for the SRR request
|
|
* @offset: The SRR relative offset
|
|
* This is called after receiving status but insufficient data, or
|
|
* when expecting status but the request has timed out.
|
|
*/
|
|
static void fc_fcp_srr(struct fc_fcp_pkt *fsp, enum fc_rctl r_ctl, u32 offset)
|
|
{
|
|
struct fc_lport *lport = fsp->lp;
|
|
struct fc_rport *rport;
|
|
struct fc_rport_libfc_priv *rpriv;
|
|
struct fc_exch *ep = fc_seq_exch(fsp->seq_ptr);
|
|
struct fc_seq *seq;
|
|
struct fcp_srr *srr;
|
|
struct fc_frame *fp;
|
|
|
|
rport = fsp->rport;
|
|
rpriv = rport->dd_data;
|
|
|
|
if (!(rpriv->flags & FC_RP_FLAGS_RETRY) ||
|
|
rpriv->rp_state != RPORT_ST_READY)
|
|
goto retry; /* shouldn't happen */
|
|
fp = fc_fcp_frame_alloc(lport, sizeof(*srr));
|
|
if (!fp)
|
|
goto retry;
|
|
|
|
srr = fc_frame_payload_get(fp, sizeof(*srr));
|
|
memset(srr, 0, sizeof(*srr));
|
|
srr->srr_op = ELS_SRR;
|
|
srr->srr_ox_id = htons(ep->oxid);
|
|
srr->srr_rx_id = htons(ep->rxid);
|
|
srr->srr_r_ctl = r_ctl;
|
|
srr->srr_rel_off = htonl(offset);
|
|
|
|
fc_fill_fc_hdr(fp, FC_RCTL_ELS4_REQ, rport->port_id,
|
|
rpriv->local_port->port_id, FC_TYPE_FCP,
|
|
FC_FCTL_REQ, 0);
|
|
|
|
seq = fc_exch_seq_send(lport, fp, fc_fcp_srr_resp,
|
|
fc_fcp_pkt_destroy,
|
|
fsp, get_fsp_rec_tov(fsp));
|
|
if (!seq)
|
|
goto retry;
|
|
|
|
fsp->recov_seq = seq;
|
|
fsp->xfer_len = offset;
|
|
fsp->xfer_contig_end = offset;
|
|
fsp->state &= ~FC_SRB_RCV_STATUS;
|
|
fc_fcp_pkt_hold(fsp); /* hold for outstanding SRR */
|
|
return;
|
|
retry:
|
|
fc_fcp_retry_cmd(fsp, FC_TRANS_RESET);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_srr_resp() - Handler for SRR response
|
|
* @seq: The sequence the SRR is on
|
|
* @fp: The SRR frame
|
|
* @arg: The FCP packet the SRR is on
|
|
*/
|
|
static void fc_fcp_srr_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
|
|
{
|
|
struct fc_fcp_pkt *fsp = arg;
|
|
struct fc_frame_header *fh;
|
|
|
|
if (IS_ERR(fp)) {
|
|
fc_fcp_srr_error(fsp, fp);
|
|
return;
|
|
}
|
|
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
|
|
fh = fc_frame_header_get(fp);
|
|
/*
|
|
* BUG? fc_fcp_srr_error calls fc_exch_done which would release
|
|
* the ep. But if fc_fcp_srr_error had got -FC_EX_TIMEOUT,
|
|
* then fc_exch_timeout would be sending an abort. The fc_exch_done
|
|
* call by fc_fcp_srr_error would prevent fc_exch.c from seeing
|
|
* an abort response though.
|
|
*/
|
|
if (fh->fh_type == FC_TYPE_BLS) {
|
|
fc_fcp_unlock_pkt(fsp);
|
|
return;
|
|
}
|
|
|
|
switch (fc_frame_payload_op(fp)) {
|
|
case ELS_LS_ACC:
|
|
fsp->recov_retry = 0;
|
|
fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
|
|
break;
|
|
case ELS_LS_RJT:
|
|
default:
|
|
fc_fcp_recovery(fsp, FC_ERROR);
|
|
break;
|
|
}
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_exch_done(seq);
|
|
fc_frame_free(fp);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_srr_error() - Handler for SRR errors
|
|
* @fsp: The FCP packet that the SRR error is on
|
|
* @fp: The SRR frame
|
|
*/
|
|
static void fc_fcp_srr_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
|
|
{
|
|
if (fc_fcp_lock_pkt(fsp))
|
|
goto out;
|
|
switch (PTR_ERR(fp)) {
|
|
case -FC_EX_TIMEOUT:
|
|
FC_FCP_DBG(fsp, "SRR timeout, retries %d\n", fsp->recov_retry);
|
|
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
|
|
fc_fcp_rec(fsp);
|
|
else
|
|
fc_fcp_recovery(fsp, FC_TIMED_OUT);
|
|
break;
|
|
case -FC_EX_CLOSED: /* e.g., link failure */
|
|
FC_FCP_DBG(fsp, "SRR error, exchange closed\n");
|
|
fallthrough;
|
|
default:
|
|
fc_fcp_retry_cmd(fsp, FC_ERROR);
|
|
break;
|
|
}
|
|
fc_fcp_unlock_pkt(fsp);
|
|
out:
|
|
fc_exch_done(fsp->recov_seq);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_lport_queue_ready() - Determine if the lport and it's queue is ready
|
|
* @lport: The local port to be checked
|
|
*/
|
|
static inline int fc_fcp_lport_queue_ready(struct fc_lport *lport)
|
|
{
|
|
/* lock ? */
|
|
return (lport->state == LPORT_ST_READY) &&
|
|
lport->link_up && !lport->qfull;
|
|
}
|
|
|
|
/**
|
|
* fc_queuecommand() - The queuecommand function of the SCSI template
|
|
* @shost: The Scsi_Host that the command was issued to
|
|
* @sc_cmd: The scsi_cmnd to be executed
|
|
*
|
|
* This is the i/o strategy routine, called by the SCSI layer.
|
|
*/
|
|
int fc_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *sc_cmd)
|
|
{
|
|
struct fc_lport *lport = shost_priv(shost);
|
|
struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
|
|
struct fc_fcp_pkt *fsp;
|
|
int rval;
|
|
int rc = 0;
|
|
|
|
rval = fc_remote_port_chkready(rport);
|
|
if (rval) {
|
|
sc_cmd->result = rval;
|
|
scsi_done(sc_cmd);
|
|
return 0;
|
|
}
|
|
|
|
if (!*(struct fc_remote_port **)rport->dd_data) {
|
|
/*
|
|
* rport is transitioning from blocked/deleted to
|
|
* online
|
|
*/
|
|
sc_cmd->result = DID_IMM_RETRY << 16;
|
|
scsi_done(sc_cmd);
|
|
goto out;
|
|
}
|
|
|
|
if (!fc_fcp_lport_queue_ready(lport)) {
|
|
if (lport->qfull) {
|
|
if (fc_fcp_can_queue_ramp_down(lport))
|
|
shost_printk(KERN_ERR, lport->host,
|
|
"libfc: queue full, "
|
|
"reducing can_queue to %d.\n",
|
|
lport->host->can_queue);
|
|
}
|
|
rc = SCSI_MLQUEUE_HOST_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
fsp = fc_fcp_pkt_alloc(lport, GFP_ATOMIC);
|
|
if (fsp == NULL) {
|
|
rc = SCSI_MLQUEUE_HOST_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* build the libfc request pkt
|
|
*/
|
|
fsp->cmd = sc_cmd; /* save the cmd */
|
|
fsp->rport = rport; /* set the remote port ptr */
|
|
|
|
/*
|
|
* set up the transfer length
|
|
*/
|
|
fsp->data_len = scsi_bufflen(sc_cmd);
|
|
fsp->xfer_len = 0;
|
|
|
|
/*
|
|
* setup the data direction
|
|
*/
|
|
if (sc_cmd->sc_data_direction == DMA_FROM_DEVICE) {
|
|
fsp->req_flags = FC_SRB_READ;
|
|
this_cpu_inc(lport->stats->InputRequests);
|
|
this_cpu_add(lport->stats->InputBytes, fsp->data_len);
|
|
} else if (sc_cmd->sc_data_direction == DMA_TO_DEVICE) {
|
|
fsp->req_flags = FC_SRB_WRITE;
|
|
this_cpu_inc(lport->stats->OutputRequests);
|
|
this_cpu_add(lport->stats->OutputBytes, fsp->data_len);
|
|
} else {
|
|
fsp->req_flags = 0;
|
|
this_cpu_inc(lport->stats->ControlRequests);
|
|
}
|
|
|
|
/*
|
|
* send it to the lower layer
|
|
* if we get -1 return then put the request in the pending
|
|
* queue.
|
|
*/
|
|
rval = fc_fcp_pkt_send(lport, fsp);
|
|
if (rval != 0) {
|
|
fsp->state = FC_SRB_FREE;
|
|
fc_fcp_pkt_release(fsp);
|
|
rc = SCSI_MLQUEUE_HOST_BUSY;
|
|
}
|
|
out:
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(fc_queuecommand);
|
|
|
|
/**
|
|
* fc_io_compl() - Handle responses for completed commands
|
|
* @fsp: The FCP packet that is complete
|
|
*
|
|
* Translates fcp_pkt errors to a Linux SCSI errors.
|
|
* The fcp packet lock must be held when calling.
|
|
*/
|
|
static void fc_io_compl(struct fc_fcp_pkt *fsp)
|
|
{
|
|
struct fc_fcp_internal *si;
|
|
struct scsi_cmnd *sc_cmd;
|
|
struct fc_lport *lport;
|
|
unsigned long flags;
|
|
|
|
/* release outstanding ddp context */
|
|
fc_fcp_ddp_done(fsp);
|
|
|
|
fsp->state |= FC_SRB_COMPL;
|
|
if (!(fsp->state & FC_SRB_FCP_PROCESSING_TMO)) {
|
|
spin_unlock_bh(&fsp->scsi_pkt_lock);
|
|
del_timer_sync(&fsp->timer);
|
|
spin_lock_bh(&fsp->scsi_pkt_lock);
|
|
}
|
|
|
|
lport = fsp->lp;
|
|
si = fc_get_scsi_internal(lport);
|
|
|
|
/*
|
|
* if can_queue ramp down is done then try can_queue ramp up
|
|
* since commands are completing now.
|
|
*/
|
|
if (si->last_can_queue_ramp_down_time)
|
|
fc_fcp_can_queue_ramp_up(lport);
|
|
|
|
sc_cmd = fsp->cmd;
|
|
libfc_priv(sc_cmd)->status = fsp->cdb_status;
|
|
switch (fsp->status_code) {
|
|
case FC_COMPLETE:
|
|
if (fsp->cdb_status == 0) {
|
|
/*
|
|
* good I/O status
|
|
*/
|
|
sc_cmd->result = DID_OK << 16;
|
|
if (fsp->scsi_resid)
|
|
libfc_priv(sc_cmd)->resid_len = fsp->scsi_resid;
|
|
} else {
|
|
/*
|
|
* transport level I/O was ok but scsi
|
|
* has non zero status
|
|
*/
|
|
sc_cmd->result = (DID_OK << 16) | fsp->cdb_status;
|
|
}
|
|
break;
|
|
case FC_ERROR:
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
|
|
"due to FC_ERROR\n");
|
|
sc_cmd->result = DID_ERROR << 16;
|
|
break;
|
|
case FC_DATA_UNDRUN:
|
|
if ((fsp->cdb_status == 0) && !(fsp->req_flags & FC_SRB_READ)) {
|
|
/*
|
|
* scsi status is good but transport level
|
|
* underrun.
|
|
*/
|
|
if (fsp->state & FC_SRB_RCV_STATUS) {
|
|
sc_cmd->result = DID_OK << 16;
|
|
} else {
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml"
|
|
" due to FC_DATA_UNDRUN (trans)\n");
|
|
sc_cmd->result = DID_ERROR << 16;
|
|
}
|
|
} else {
|
|
/*
|
|
* scsi got underrun, this is an error
|
|
*/
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
|
|
"due to FC_DATA_UNDRUN (scsi)\n");
|
|
libfc_priv(sc_cmd)->resid_len = fsp->scsi_resid;
|
|
sc_cmd->result = (DID_ERROR << 16) | fsp->cdb_status;
|
|
}
|
|
break;
|
|
case FC_DATA_OVRRUN:
|
|
/*
|
|
* overrun is an error
|
|
*/
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
|
|
"due to FC_DATA_OVRRUN\n");
|
|
sc_cmd->result = (DID_ERROR << 16) | fsp->cdb_status;
|
|
break;
|
|
case FC_CMD_ABORTED:
|
|
if (host_byte(sc_cmd->result) == DID_TIME_OUT)
|
|
FC_FCP_DBG(fsp, "Returning DID_TIME_OUT to scsi-ml "
|
|
"due to FC_CMD_ABORTED\n");
|
|
else {
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
|
|
"due to FC_CMD_ABORTED\n");
|
|
set_host_byte(sc_cmd, DID_ERROR);
|
|
}
|
|
sc_cmd->result |= fsp->io_status;
|
|
break;
|
|
case FC_CMD_RESET:
|
|
FC_FCP_DBG(fsp, "Returning DID_RESET to scsi-ml "
|
|
"due to FC_CMD_RESET\n");
|
|
sc_cmd->result = (DID_RESET << 16);
|
|
break;
|
|
case FC_TRANS_RESET:
|
|
FC_FCP_DBG(fsp, "Returning DID_SOFT_ERROR to scsi-ml "
|
|
"due to FC_TRANS_RESET\n");
|
|
sc_cmd->result = (DID_SOFT_ERROR << 16);
|
|
break;
|
|
case FC_HRD_ERROR:
|
|
FC_FCP_DBG(fsp, "Returning DID_NO_CONNECT to scsi-ml "
|
|
"due to FC_HRD_ERROR\n");
|
|
sc_cmd->result = (DID_NO_CONNECT << 16);
|
|
break;
|
|
case FC_CRC_ERROR:
|
|
FC_FCP_DBG(fsp, "Returning DID_PARITY to scsi-ml "
|
|
"due to FC_CRC_ERROR\n");
|
|
sc_cmd->result = (DID_PARITY << 16);
|
|
break;
|
|
case FC_TIMED_OUT:
|
|
FC_FCP_DBG(fsp, "Returning DID_BUS_BUSY to scsi-ml "
|
|
"due to FC_TIMED_OUT\n");
|
|
sc_cmd->result = (DID_BUS_BUSY << 16) | fsp->io_status;
|
|
break;
|
|
default:
|
|
FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
|
|
"due to unknown error\n");
|
|
sc_cmd->result = (DID_ERROR << 16);
|
|
break;
|
|
}
|
|
|
|
if (lport->state != LPORT_ST_READY && fsp->status_code != FC_COMPLETE)
|
|
sc_cmd->result = (DID_TRANSPORT_DISRUPTED << 16);
|
|
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
list_del(&fsp->list);
|
|
libfc_priv(sc_cmd)->fsp = NULL;
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
scsi_done(sc_cmd);
|
|
|
|
/* release ref from initial allocation in queue command */
|
|
fc_fcp_pkt_release(fsp);
|
|
}
|
|
|
|
/**
|
|
* fc_eh_abort() - Abort a command
|
|
* @sc_cmd: The SCSI command to abort
|
|
*
|
|
* From SCSI host template.
|
|
* Send an ABTS to the target device and wait for the response.
|
|
*/
|
|
int fc_eh_abort(struct scsi_cmnd *sc_cmd)
|
|
{
|
|
struct fc_fcp_pkt *fsp;
|
|
struct fc_lport *lport;
|
|
struct fc_fcp_internal *si;
|
|
int rc = FAILED;
|
|
unsigned long flags;
|
|
int rval;
|
|
|
|
rval = fc_block_scsi_eh(sc_cmd);
|
|
if (rval)
|
|
return rval;
|
|
|
|
lport = shost_priv(sc_cmd->device->host);
|
|
if (lport->state != LPORT_ST_READY)
|
|
return rc;
|
|
else if (!lport->link_up)
|
|
return rc;
|
|
|
|
si = fc_get_scsi_internal(lport);
|
|
spin_lock_irqsave(&si->scsi_queue_lock, flags);
|
|
fsp = libfc_priv(sc_cmd)->fsp;
|
|
if (!fsp) {
|
|
/* command completed while scsi eh was setting up */
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
return SUCCESS;
|
|
}
|
|
/* grab a ref so the fsp and sc_cmd cannot be released from under us */
|
|
fc_fcp_pkt_hold(fsp);
|
|
spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
|
|
|
|
if (fc_fcp_lock_pkt(fsp)) {
|
|
/* completed while we were waiting for timer to be deleted */
|
|
rc = SUCCESS;
|
|
goto release_pkt;
|
|
}
|
|
|
|
rc = fc_fcp_pkt_abort(fsp);
|
|
fc_fcp_unlock_pkt(fsp);
|
|
|
|
release_pkt:
|
|
fc_fcp_pkt_release(fsp);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(fc_eh_abort);
|
|
|
|
/**
|
|
* fc_eh_device_reset() - Reset a single LUN
|
|
* @sc_cmd: The SCSI command which identifies the device whose
|
|
* LUN is to be reset
|
|
*
|
|
* Set from SCSI host template.
|
|
*/
|
|
int fc_eh_device_reset(struct scsi_cmnd *sc_cmd)
|
|
{
|
|
struct fc_lport *lport;
|
|
struct fc_fcp_pkt *fsp;
|
|
struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
|
|
int rc = FAILED;
|
|
int rval;
|
|
|
|
rval = fc_block_scsi_eh(sc_cmd);
|
|
if (rval)
|
|
return rval;
|
|
|
|
lport = shost_priv(sc_cmd->device->host);
|
|
|
|
if (lport->state != LPORT_ST_READY)
|
|
return rc;
|
|
|
|
FC_SCSI_DBG(lport, "Resetting rport (%6.6x)\n", rport->port_id);
|
|
|
|
fsp = fc_fcp_pkt_alloc(lport, GFP_NOIO);
|
|
if (fsp == NULL) {
|
|
printk(KERN_WARNING "libfc: could not allocate scsi_pkt\n");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Build the libfc request pkt. Do not set the scsi cmnd, because
|
|
* the sc passed in is not setup for execution like when sent
|
|
* through the queuecommand callout.
|
|
*/
|
|
fsp->rport = rport; /* set the remote port ptr */
|
|
|
|
/*
|
|
* flush outstanding commands
|
|
*/
|
|
rc = fc_lun_reset(lport, fsp, scmd_id(sc_cmd), sc_cmd->device->lun);
|
|
fsp->state = FC_SRB_FREE;
|
|
fc_fcp_pkt_release(fsp);
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(fc_eh_device_reset);
|
|
|
|
/**
|
|
* fc_eh_host_reset() - Reset a Scsi_Host.
|
|
* @sc_cmd: The SCSI command that identifies the SCSI host to be reset
|
|
*/
|
|
int fc_eh_host_reset(struct scsi_cmnd *sc_cmd)
|
|
{
|
|
struct Scsi_Host *shost = sc_cmd->device->host;
|
|
struct fc_lport *lport = shost_priv(shost);
|
|
unsigned long wait_tmo;
|
|
|
|
FC_SCSI_DBG(lport, "Resetting host\n");
|
|
|
|
fc_lport_reset(lport);
|
|
wait_tmo = jiffies + FC_HOST_RESET_TIMEOUT;
|
|
while (!fc_fcp_lport_queue_ready(lport) && time_before(jiffies,
|
|
wait_tmo))
|
|
msleep(1000);
|
|
|
|
if (fc_fcp_lport_queue_ready(lport)) {
|
|
shost_printk(KERN_INFO, shost, "libfc: Host reset succeeded "
|
|
"on port (%6.6x)\n", lport->port_id);
|
|
return SUCCESS;
|
|
} else {
|
|
shost_printk(KERN_INFO, shost, "libfc: Host reset failed, "
|
|
"port (%6.6x) is not ready.\n",
|
|
lport->port_id);
|
|
return FAILED;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(fc_eh_host_reset);
|
|
|
|
/**
|
|
* fc_slave_alloc() - Configure the queue depth of a Scsi_Host
|
|
* @sdev: The SCSI device that identifies the SCSI host
|
|
*
|
|
* Configures queue depth based on host's cmd_per_len. If not set
|
|
* then we use the libfc default.
|
|
*/
|
|
int fc_slave_alloc(struct scsi_device *sdev)
|
|
{
|
|
struct fc_rport *rport = starget_to_rport(scsi_target(sdev));
|
|
|
|
if (!rport || fc_remote_port_chkready(rport))
|
|
return -ENXIO;
|
|
|
|
scsi_change_queue_depth(sdev, FC_FCP_DFLT_QUEUE_DEPTH);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(fc_slave_alloc);
|
|
|
|
/**
|
|
* fc_fcp_destroy() - Tear down the FCP layer for a given local port
|
|
* @lport: The local port that no longer needs the FCP layer
|
|
*/
|
|
void fc_fcp_destroy(struct fc_lport *lport)
|
|
{
|
|
struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
|
|
|
|
if (!list_empty(&si->scsi_pkt_queue))
|
|
printk(KERN_ERR "libfc: Leaked SCSI packets when destroying "
|
|
"port (%6.6x)\n", lport->port_id);
|
|
|
|
mempool_destroy(si->scsi_pkt_pool);
|
|
kfree(si);
|
|
lport->scsi_priv = NULL;
|
|
}
|
|
EXPORT_SYMBOL(fc_fcp_destroy);
|
|
|
|
int fc_setup_fcp(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
scsi_pkt_cachep = kmem_cache_create("libfc_fcp_pkt",
|
|
sizeof(struct fc_fcp_pkt),
|
|
0, SLAB_HWCACHE_ALIGN, NULL);
|
|
if (!scsi_pkt_cachep) {
|
|
printk(KERN_ERR "libfc: Unable to allocate SRB cache, "
|
|
"module load failed!");
|
|
rc = -ENOMEM;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void fc_destroy_fcp(void)
|
|
{
|
|
kmem_cache_destroy(scsi_pkt_cachep);
|
|
}
|
|
|
|
/**
|
|
* fc_fcp_init() - Initialize the FCP layer for a local port
|
|
* @lport: The local port to initialize the exchange layer for
|
|
*/
|
|
int fc_fcp_init(struct fc_lport *lport)
|
|
{
|
|
int rc;
|
|
struct fc_fcp_internal *si;
|
|
|
|
if (!lport->tt.fcp_cmd_send)
|
|
lport->tt.fcp_cmd_send = fc_fcp_cmd_send;
|
|
|
|
if (!lport->tt.fcp_cleanup)
|
|
lport->tt.fcp_cleanup = fc_fcp_cleanup;
|
|
|
|
if (!lport->tt.fcp_abort_io)
|
|
lport->tt.fcp_abort_io = fc_fcp_abort_io;
|
|
|
|
si = kzalloc(sizeof(struct fc_fcp_internal), GFP_KERNEL);
|
|
if (!si)
|
|
return -ENOMEM;
|
|
lport->scsi_priv = si;
|
|
si->max_can_queue = lport->host->can_queue;
|
|
INIT_LIST_HEAD(&si->scsi_pkt_queue);
|
|
spin_lock_init(&si->scsi_queue_lock);
|
|
|
|
si->scsi_pkt_pool = mempool_create_slab_pool(2, scsi_pkt_cachep);
|
|
if (!si->scsi_pkt_pool) {
|
|
rc = -ENOMEM;
|
|
goto free_internal;
|
|
}
|
|
return 0;
|
|
|
|
free_internal:
|
|
kfree(si);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(fc_fcp_init);
|