[PATCH 07/43] qla2xxx: Add ability to track IOCB resource for FW

Himanshu Madhani
2017-12-20T06:57:05Z

FW has a finite number of IOCB resource. Driver will
track it via the ql2xtrackfwres module parameter. User
will be able to reserve X number of IOCBs for either
the target path or initiator path. The left over IOCBs
are shared between the 2 modes. The shared pool will be
used 1st before tapping into the reserve pool.

usage:
modprobe qla2xxx ql2xtrackfwres=1 qlini_mode=dual
echo 256 > /sys/class/scsi_host/hostX/device/reserve_ini_iocbs
echo 256 > /sys/class/scsi_host/hostX/device/reserve_tgt_iocbs

Signed-off-by: Quinn Tran <quinn.tran@cavium.com>
Signed-off-by: Himanshu Madhani <himanshu.madhani@cavium.com>
---
drivers/scsi/qla2xxx/qla_def.h | 50 +++++-
drivers/scsi/qla2xxx/qla_dfs.c | 315 ++++++++++++++++++++++++++++++++++++++
drivers/scsi/qla2xxx/qla_gbl.h | 1 +
drivers/scsi/qla2xxx/qla_init.c | 36 +++++
drivers/scsi/qla2xxx/qla_inline.h | 102 ++++++++++++
drivers/scsi/qla2xxx/qla_iocb.c | 45 ++++++
drivers/scsi/qla2xxx/qla_isr.c | 10 +-
drivers/scsi/qla2xxx/qla_os.c | 10 ++
drivers/scsi/qla2xxx/qla_target.c | 24 +++
drivers/scsi/qla2xxx/qla_target.h | 3 +-
10 files changed, 592 insertions(+), 4 deletions(-)

diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h
index 5e509763a419..8fbe1736a1a4 100644
--- a/drivers/scsi/qla2xxx/qla_def.h
+++ b/drivers/scsi/qla2xxx/qla_def.h
@@ -549,6 +549,12 @@ enum {
TYPE_TGT_CMD,
};

+struct iocb_resource {
+ u8 res_type;
+ u8 pad;
+ u16 iocb_cnt;
+};
+
typedef struct srb {
/*
* Do not move cmd_type field, it needs to
@@ -556,6 +562,7 @@ typedef struct srb {
*/
uint8_t cmd_type;
uint8_t pad[3];
+ struct iocb_resource iores;
atomic_t ref_count;
wait_queue_head_t nvme_ls_waitq;
struct fc_port *fcport;
@@ -3396,6 +3403,7 @@ struct qla_qpair {
uint32_t fw_started:1;
uint32_t enable_class_2:1;
uint32_t enable_explicit_conf:1;
+ uint32_t fw_res_tracking:1;
uint32_t use_shadow_reg:1;

uint16_t id; /* qp number used with FW */
@@ -3433,6 +3441,24 @@ struct scsi_qlt_host {
struct qla_tgt *qla_tgt;
};

+struct qla_fw_resources {
+ spinlock_t rescnt_lock;
+#define DEF_RES_INI_IOCBS 256
+#define DEF_RES_TGT_IOCBS 256
+#define DEF_RES_BUSY_IOCBS 32
+ u32 tgt_iocbs_reserve;
+ u32 ini_iocbs_reserve;
+ u32 busy_iocbs_reserve;
+ u32 tgt_iocbs_max;
+ u32 ini_iocbs_max;
+ u32 share_iocbs_max;
+
+ /* these fields start high */
+ atomic_t share_iocbs_used;
+ atomic_t tgt_iocbs_used;
+ atomic_t ini_iocbs_used;
+};
+
struct qlt_hw_data {
/* Protected by hw lock */
uint32_t node_name_set:1;
@@ -3461,6 +3487,9 @@ struct qlt_hw_data {
struct dentry *dfs_tgt_sess;
struct dentry *dfs_tgt_port_database;
struct dentry *dfs_naqp;
+ struct dentry *dfs_ini_iocbs;
+ struct dentry *dfs_tgt_iocbs;
+ struct dentry *dfs_busy_iocbs;

struct list_head q_full_list;
uint32_t num_pend_cmds;
@@ -3540,7 +3569,6 @@ struct qla_hw_data {
uint32_t n2n_ae:1;
uint32_t fw_started:1;
uint32_t fw_init_done:1;
-
uint32_t detected_lr_sfp:1;
uint32_t using_lr_setting:1;
} flags;
@@ -4103,6 +4131,7 @@ struct qla_hw_data {

struct qlt_hw_data tgt;
int allow_cna_fw_dump;
+ struct qla_fw_resources fwres;
uint32_t fw_ability_mask;
uint16_t min_link_speed;
uint16_t max_speed_sup;
@@ -4406,7 +4435,6 @@ struct qla2_sgx {
#define QLA_QPAIR_MARK_NOT_BUSY(__qpair) \
atomic_dec(&__qpair->ref_count); \

-
#define QLA_ENA_CONF(_ha) {\
int i;\
_ha->base_qpair->enable_explicit_conf = 1; \
@@ -4425,6 +4453,24 @@ struct qla2_sgx {
} \
}

+#define QLA_ENA_FW_RES_TRACKING(_ha) { \
+ int i; \
+ _ha->base_qpair->fw_res_tracking = 1; \
+ for (i = 0; i < _ha->max_qpairs; i++) { \
+ if (_ha->queue_pair_map[i]) \
+ _ha->queue_pair_map[i]->fw_res_tracking = 1; \
+ } \
+}
+
+#define QLA_DIS_FW_RES_TRACKING(_ha) { \
+ int i; \
+ _ha->base_qpair->fw_res_tracking = 0; \
+ for (i = 0; i < _ha->max_qpairs; i++) { \
+ if (_ha->queue_pair_map[i]) \
+ _ha->queue_pair_map[i]->fw_res_tracking = 0; \
+ } \
+}
+
/*
* qla2x00 local function return status codes
*/
diff --git a/drivers/scsi/qla2xxx/qla_dfs.c b/drivers/scsi/qla2xxx/qla_dfs.c
index d231e7156134..87a18a00d509 100644
--- a/drivers/scsi/qla2xxx/qla_dfs.c
+++ b/drivers/scsi/qla2xxx/qla_dfs.c
@@ -418,6 +418,278 @@ static const struct file_operations dfs_naqp_ops = {
.write = qla_dfs_naqp_write,
};

+static int
+qla_dfs_ini_iocbs_show(struct seq_file *s, void *unused)
+{
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+
+ if (!qla_dual_mode_enabled(vha)) {
+ seq_puts(s,
+ "This field requires Dual Mode to be enabled\n");
+ return 0;
+ }
+
+ seq_printf(s, "%d\n", ha->fwres.ini_iocbs_reserve);
+
+ return 0;
+}
+
+static int
+qla_dfs_ini_iocbs_open(struct inode *inode, struct file *file)
+{
+ struct scsi_qla_host *vha = inode->i_private;
+
+ return single_open(file, qla_dfs_ini_iocbs_show, vha);
+}
+
+static ssize_t qla_dfs_ini_iocbs_write(struct file *file,
+ const char __user *buffer, size_t count, loff_t *pos)
+{
+ struct seq_file *s = file->private_data;
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+ char *buf;
+ int rc = 0;
+ u32 v = 0;
+
+ buf = memdup_user_nul(buffer, count);
+ if (IS_ERR(buf)) {
+ pr_err("host%ld: fail to copy user buffer",
+ vha->host_no);
+ return PTR_ERR(buf);
+ }
+
+ rc = kstrtouint(buf, 0, &v);
+ if (rc < 0) {
+ ql_log(ql_log_info, vha, 0x707b,
+ "Unable to set initiator reserve iocbs\n");
+ goto out_free;
+ }
+
+ if (qla_dual_mode_enabled(vha)) {
+ if ((v < ha->orig_fw_iocb_count) &&
+ (ha->fwres.ini_iocbs_reserve != v)) {
+ ha->fwres.ini_iocbs_reserve = v;
+ ql_log(ql_log_info, vha, 0x7024,
+ "Resetting. User change initiator reserve iocbs (%d/%d)\n",
+ v, ha->orig_fw_iocb_count);
+
+ set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
+ qla2xxx_wake_dpc(vha);
+ qla2x00_wait_for_chip_reset(vha);
+ } else {
+ ql_log(ql_log_warn, vha, 0x702e,
+ "Unable to set initiator reserve iocbs (%d/%d)\n",
+ v, ha->orig_fw_iocb_count);
+ }
+ } else {
+ if (v < (ha->orig_fw_iocb_count - ha->fwres.tgt_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve))
+ ha->fwres.ini_iocbs_reserve = v;
+ else
+ ql_log(ql_log_warn, vha, 0x7039,
+ "Unable to set initiator reserve iocbs (%d/%d/%d/%d)\n",
+ v, ha->orig_fw_iocb_count,
+ ha->fwres.tgt_iocbs_reserve,
+ ha->fwres.busy_iocbs_reserve);
+ }
+
+ rc = count;
+out_free:
+ kfree(buf);
+ return rc;
+}
+
+static const struct file_operations dfs_ini_iocbs_ops = {
+ .open = qla_dfs_ini_iocbs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = qla_dfs_ini_iocbs_write,
+};
+
+
+static int
+qla_dfs_tgt_iocbs_show(struct seq_file *s, void *unused)
+{
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+
+ if (!qla_dual_mode_enabled(vha)) {
+ seq_puts(s,
+ "This field requires Dual Mode to be enabled\n");
+ return 0;
+ }
+
+ seq_printf(s, "%d\n", ha->fwres.tgt_iocbs_reserve);
+
+ return 0;
+}
+
+static int
+qla_dfs_tgt_iocbs_open(struct inode *inode, struct file *file)
+{
+ struct scsi_qla_host *vha = inode->i_private;
+
+ return single_open(file, qla_dfs_tgt_iocbs_show, vha);
+}
+
+static ssize_t
+qla_dfs_tgt_iocbs_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct seq_file *s = file->private_data;
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+ char *buf;
+ int rc = 0;
+ u32 v = 0;
+
+ buf = memdup_user_nul(buffer, count);
+ if (IS_ERR(buf)) {
+ pr_err("host%ld: fail to copy user buffer.",
+ vha->host_no);
+ return PTR_ERR(buf);
+ }
+ rc = kstrtouint(buf, 0, &v);
+ if (rc < 0) {
+ ql_log(ql_log_info, vha, 0x70a5,
+ "Unable to set initiator reserve iocbs.\n");
+ goto out_free;
+ }
+
+ if (qla_dual_mode_enabled(vha)) {
+ if ((v < ha->orig_fw_iocb_count) &&
+ (ha->fwres.ini_iocbs_reserve != v)) {
+ ha->fwres.ini_iocbs_reserve = v;
+ ql_log(ql_log_info, vha, 0x703b,
+ "Resetting. User changed target reserve iocbs (%d/%d).\n",
+ v, ha->orig_fw_iocb_count);
+ set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
+ qla2xxx_wake_dpc(vha);
+ qla2x00_wait_for_chip_reset(vha);
+ } else
+ ql_log(ql_log_warn, vha, 0x7045,
+ "Unable to set target reserve iocbs (%d/%d).\n",
+ v, ha->orig_fw_iocb_count);
+ } else {
+ if (v < (ha->orig_fw_iocb_count - ha->fwres.ini_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve))
+ ha->fwres.tgt_iocbs_reserve = v;
+ else
+ ql_log(ql_log_warn, vha, 0x7047,
+ "Unable to set target reserve iocbs (%d/%d/%d/%d).\n",
+ v, ha->orig_fw_iocb_count,
+ ha->fwres.ini_iocbs_reserve,
+ ha->fwres.busy_iocbs_reserve);
+ }
+
+ rc = count;
+out_free:
+ kfree(buf);
+ return rc;
+}
+
+static const struct file_operations dfs_tgt_iocbs_ops = {
+ .open = qla_dfs_tgt_iocbs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = qla_dfs_tgt_iocbs_write,
+};
+
+static int
+qla_dfs_busy_iocbs_show(struct seq_file *s, void *unused)
+{
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+
+ if (!qla_dual_mode_enabled(vha)) {
+ seq_puts(s,
+ "This field requires Dual Mode to be enabled\n");
+ return 0;
+ }
+
+ seq_printf(s, "%d\n", ha->fwres.busy_iocbs_reserve);
+
+ return 0;
+}
+
+static int
+qla_dfs_busy_iocbs_open(struct inode *inode, struct file *file)
+{
+ struct scsi_qla_host *vha = inode->i_private;
+
+ return single_open(file, qla_dfs_busy_iocbs_show, vha);
+}
+
+static ssize_t
+qla_dfs_busy_iocbs_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct seq_file *s = file->private_data;
+ struct scsi_qla_host *vha = s->private;
+ struct qla_hw_data *ha = vha->hw;
+ char *buf;
+ int rc = 0;
+ u32 v = 0;
+
+ buf = memdup_user_nul(buffer, count);
+ if (IS_ERR(buf)) {
+ pr_err("host%ld: fail to copy user buffer.",
+ vha->host_no);
+ return PTR_ERR(buf);
+ }
+
+ rc = kstrtouint(buf, 0, &v);
+ if (rc < 0) {
+ ql_log(ql_log_info, vha, 0x70a6,
+ "Unable to set initiator reserve iocbs.\n");
+ goto out_free;
+ }
+
+ if (qla_dual_mode_enabled(vha)) {
+ if ((v < ha->orig_fw_iocb_count) &&
+ (ha->fwres.busy_iocbs_reserve != v)) {
+ ha->fwres.busy_iocbs_reserve = v;
+ ql_log(ql_log_info, vha, 0x7073,
+ "Resetting. User change Busy reserve iocbs (%d/%d).\n",
+ v, ha->orig_fw_iocb_count);
+ set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
+ qla2xxx_wake_dpc(vha);
+ qla2x00_wait_for_chip_reset(vha);
+ } else {
+ ql_log(ql_log_warn, vha, 0x7074,
+ "Unable to set busy reserve iocbs (%d/%d).\n",
+ v, ha->orig_fw_iocb_count);
+ }
+ } else {
+ if (v < (ha->orig_fw_iocb_count - ha->fwres.ini_iocbs_reserve -
+ ha->fwres.tgt_iocbs_reserve))
+ ha->fwres.busy_iocbs_reserve = v;
+ else
+ ql_log(ql_log_warn, vha, 0x7075,
+ "Unable to set busy reserve iocbs (%d/%d/%d/%d).\n",
+ v, ha->orig_fw_iocb_count,
+ ha->fwres.ini_iocbs_reserve,
+ ha->fwres.tgt_iocbs_reserve);
+ }
+
+ rc = count;
+out_free:
+ kfree(buf);
+ return rc;
+}
+
+static const struct file_operations dfs_busy_iocbs_ops = {
+ .open = qla_dfs_busy_iocbs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = qla_dfs_busy_iocbs_write,
+};
+

int
qla2x00_dfs_setup(scsi_qla_host_t *vha)
@@ -504,6 +776,33 @@ qla2x00_dfs_setup(scsi_qla_host_t *vha)
"Unable to create debugFS naqp node.\n");
goto out;
}
+
+ if (ql2xtrackfwres) {
+ ha->tgt.dfs_ini_iocbs =
+ debugfs_create_file("reserve_ini_iocbs",
+ 0400, ha->dfs_dir, vha, &dfs_ini_iocbs_ops);
+ if (!ha->tgt.dfs_ini_iocbs) {
+ ql_log(ql_log_warn, vha, 0xd011,
+ "Unable to create debugFS reserve_ini_iocbs node.\n");
+ goto out;
+ }
+ ha->tgt.dfs_tgt_iocbs =
+ debugfs_create_file("reserve_tgt_iocbs",
+ 0400, ha->dfs_dir, vha, &dfs_tgt_iocbs_ops);
+ if (!ha->tgt.dfs_tgt_iocbs) {
+ ql_log(ql_log_warn, vha, 0xd011,
+ "Unable to create debugFS reserve_tgt_iocbs node.\n");
+ goto out;
+ }
+ ha->tgt.dfs_busy_iocbs =
+ debugfs_create_file("reserve_busy_iocbs",
+ 0400, ha->dfs_dir, vha, &dfs_busy_iocbs_ops);
+ if (!ha->tgt.dfs_busy_iocbs) {
+ ql_log(ql_log_warn, vha, 0xd011,
+ "Unable to create debugFS reserve_busy_iocbs node.\n");
+ goto out;
+ }
+ }
}
out:
return 0;
@@ -514,6 +813,22 @@ qla2x00_dfs_remove(scsi_qla_host_t *vha)
{
struct qla_hw_data *ha = vha->hw;

+
+ if (ha->tgt.dfs_ini_iocbs) {
+ debugfs_remove(ha->tgt.dfs_ini_iocbs);
+ ha->tgt.dfs_ini_iocbs = NULL;
+ }
+
+ if (ha->tgt.dfs_tgt_iocbs) {
+ debugfs_remove(ha->tgt.dfs_tgt_iocbs);
+ ha->tgt.dfs_tgt_iocbs = NULL;
+ }
+
+ if (ha->tgt.dfs_busy_iocbs) {
+ debugfs_remove(ha->tgt.dfs_busy_iocbs);
+ ha->tgt.dfs_busy_iocbs = NULL;
+ }
+
if (ha->tgt.dfs_naqp) {
debugfs_remove(ha->tgt.dfs_naqp);
ha->tgt.dfs_naqp = NULL;
diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h
index bf907386f177..66dcbdb91244 100644
--- a/drivers/scsi/qla2xxx/qla_gbl.h
+++ b/drivers/scsi/qla2xxx/qla_gbl.h
@@ -148,6 +148,7 @@ extern int ql2xuctrlirq;
extern int ql2xnvmeenable;
extern int ql2xautodetectsfp;
extern int ql2xenablemsix;
+extern int ql2xtrackfwres;

extern int qla2x00_loop_reset(scsi_qla_host_t *);
extern void qla2x00_abort_all_cmds(scsi_qla_host_t *, int);
diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c
index 24d0f9d419d2..429305caef47 100644
--- a/drivers/scsi/qla2xxx/qla_init.c
+++ b/drivers/scsi/qla2xxx/qla_init.c
@@ -3108,6 +3108,42 @@ qla2x00_setup_chip(scsi_qla_host_t *vha)
MIN_MULTI_ID_FABRIC - 1;
}
qla2x00_get_resource_cnts(vha);
+ if (ql2xtrackfwres) {
+ if (qla_dual_mode_enabled(vha)) {
+ ha->fwres.tgt_iocbs_max =
+ ha->orig_fw_iocb_count -
+ ha->fwres.ini_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve;
+ ha->fwres.ini_iocbs_max =
+ ha->orig_fw_iocb_count -
+ ha->fwres.tgt_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve;
+ ha->fwres.share_iocbs_max =
+ ha->orig_fw_iocb_count -
+ ha->fwres.ini_iocbs_reserve -
+ ha->fwres.tgt_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve;
+
+ } else if (qla_tgt_mode_enabled(vha)) {
+ ha->fwres.tgt_iocbs_max =
+ ha->orig_fw_iocb_count -
+ ha->fwres.busy_iocbs_reserve;
+ ha->fwres.ini_iocbs_max = 0;
+ ha->fwres.share_iocbs_max =
+ ha->orig_fw_iocb_count -
+ ha->fwres.ini_iocbs_reserve -
+ ha->fwres.tgt_iocbs_reserve -
+ ha->fwres.busy_iocbs_reserve;
+ } else
+ QLA_DIS_FW_RES_TRACKING(ha);
+
+ atomic_set(&ha->fwres.ini_iocbs_used,
+ ha->fwres.ini_iocbs_max);
+ atomic_set(&ha->fwres.tgt_iocbs_used,
+ ha->fwres.tgt_iocbs_max);
+ atomic_set(&ha->fwres.share_iocbs_used,
+ ha->fwres.share_iocbs_max);
+ }

/*
* Allocate the array of outstanding commands
diff --git a/drivers/scsi/qla2xxx/qla_inline.h b/drivers/scsi/qla2xxx/qla_inline.h
index 4d32426393c7..d8dc6dea1525 100644
--- a/drivers/scsi/qla2xxx/qla_inline.h
+++ b/drivers/scsi/qla2xxx/qla_inline.h
@@ -221,6 +221,7 @@ qla2xxx_get_qpair_sp(struct qla_qpair *qpair, fc_port_t *fcport, gfp_t flag)
sp->fcport = fcport;
sp->iocbs = 1;
sp->vha = qpair->vha;
+ sp->qpair = qpair;
done:
if (!sp)
QLA_QPAIR_MARK_NOT_BUSY(qpair);
@@ -253,6 +254,7 @@ qla2x00_get_sp(scsi_qla_host_t *vha, fc_port_t *fcport, gfp_t flag)
sp->cmd_type = TYPE_SRB;
sp->iocbs = 1;
sp->vha = vha;
+ sp->qpair = vha->hw->base_qpair;
done:
if (!sp)
QLA_VHA_MARK_NOT_BUSY(vha);
@@ -366,3 +368,103 @@ qla_83xx_start_iocbs(struct qla_qpair *qpair)

WRT_REG_DWORD(req->req_q_in, req->ring_index);
}
+
+enum {
+ RESOURCE_NONE,
+ RESOURCE_INI,
+ RESOURCE_TGT,
+ RESOURCE_SHR,
+};
+
+static inline int
+qla_get_iocbs(struct qla_hw_data *ha, struct iocb_resource *iores)
+{
+ unsigned long flags;
+
+ switch (iores->res_type) {
+ case RESOURCE_TGT:
+ /* spin lock is required here because atomic lib is unable
+ * to do "check 1st and sub" under 1 atomic operaton.
+ */
+ spin_lock_irqsave(&ha->fwres.rescnt_lock, flags);
+ if (atomic_read(&ha->fwres.share_iocbs_used) <
+ iores->iocb_cnt) {
+ /* share pool is emptied */
+ if (atomic_read(&ha->fwres.tgt_iocbs_used) <
+ iores->iocb_cnt) {
+ iores->res_type = RESOURCE_NONE;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock,
+ flags);
+ return -EAGAIN;
+ }
+
+ atomic_sub(iores->iocb_cnt, &ha->fwres.tgt_iocbs_used);
+ iores->res_type = RESOURCE_TGT;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock, flags);
+ return 0;
+ }
+
+ atomic_sub(iores->iocb_cnt, &ha->fwres.share_iocbs_used);
+ iores->res_type = RESOURCE_SHR;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock, flags);
+
+ return 0;
+
+ case RESOURCE_INI:
+ spin_lock_irqsave(&ha->fwres.rescnt_lock, flags);
+ if (atomic_read(&ha->fwres.share_iocbs_used) <
+ iores->iocb_cnt) {
+ if (atomic_read(&ha->fwres.ini_iocbs_used) <
+ iores->iocb_cnt) {
+ /* fail to get resource */
+ iores->res_type = RESOURCE_NONE;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock,
+ flags);
+ return -EAGAIN;
+ } else {
+ atomic_sub(iores->iocb_cnt,
+ &ha->fwres.ini_iocbs_used);
+ iores->res_type = RESOURCE_INI;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock,
+ flags);
+ return 0;
+ }
+ }
+ atomic_sub(iores->iocb_cnt, &ha->fwres.share_iocbs_used);
+ iores->res_type = RESOURCE_SHR;
+ spin_unlock_irqrestore(&ha->fwres.rescnt_lock, flags);
+ return 0;
+
+ default:
+ break;
+ }
+
+ return -EIO;
+}
+
+static inline void
+qla_put_iocbs(struct qla_qpair *qpair, struct iocb_resource *iores)
+{
+ struct scsi_qla_host *vha;
+
+ if (!qpair || !qpair->fw_res_tracking)
+ return;
+
+ vha = qpair->vha;
+
+ switch (iores->res_type) {
+ case RESOURCE_TGT:
+ atomic_add(iores->iocb_cnt, &vha->hw->fwres.tgt_iocbs_used);
+ break;
+ case RESOURCE_INI:
+ atomic_add(iores->iocb_cnt, &vha->hw->fwres.ini_iocbs_used);
+ break;
+ case RESOURCE_SHR:
+ atomic_add(iores->iocb_cnt, &vha->hw->fwres.share_iocbs_used);
+ break;
+ default:
+ break;
+ }
+
+ iores->res_type = RESOURCE_NONE;
+}
diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/qla_iocb.c
index 62b3d0a8a961..417c40f21244 100644
--- a/drivers/scsi/qla2xxx/qla_iocb.c
+++ b/drivers/scsi/qla2xxx/qla_iocb.c
@@ -1460,6 +1460,14 @@ qla24xx_start_scsi(srb_t *sp)

tot_dsds = nseg;
req_cnt = qla24xx_calc_iocbs(vha, tot_dsds);
+
+ if (ha->base_qpair->fw_res_tracking) {
+ sp->iores.iocb_cnt = req_cnt;
+ sp->iores.res_type = RESOURCE_INI;
+ if (qla_get_iocbs(ha, &sp->iores))
+ goto queuing_error;
+ }
+
if (req->cnt < (req_cnt + 2)) {
cnt = IS_SHADOW_REG_CAPABLE(ha) ? *req->out_ptr :
RD_REG_DWORD_RELAXED(req->req_q_out);
@@ -1538,6 +1546,7 @@ qla24xx_start_scsi(srb_t *sp)
if (tot_dsds)
scsi_dma_unmap(cmd);

+ qla_put_iocbs(ha->base_qpair, &sp->iores);
spin_unlock_irqrestore(&ha->hardware_lock, flags);

return QLA_FUNCTION_FAILED;
@@ -1661,6 +1670,13 @@ qla24xx_dif_start_scsi(srb_t *sp)
/* Total Data and protection sg segment(s) */
tot_prot_dsds = nseg;
tot_dsds += nseg;
+ if (ha->base_qpair->fw_res_tracking) {
+ sp->iores.iocb_cnt = qla24xx_calc_iocbs(vha, tot_dsds);
+ sp->iores.res_type = RESOURCE_INI;
+ if (qla_get_iocbs(ha, &sp->iores))
+ goto queuing_error;
+ }
+
if (req->cnt < (req_cnt + 2)) {
cnt = IS_SHADOW_REG_CAPABLE(ha) ? *req->out_ptr :
RD_REG_DWORD_RELAXED(req->req_q_out);
@@ -1739,6 +1755,7 @@ qla24xx_dif_start_scsi(srb_t *sp)
req->outstanding_cmds[handle] = NULL;
req->cnt += req_cnt;
}
+ qla_put_iocbs(ha->base_qpair, &sp->iores);
/* Cleanup will be performed by the caller (queuecommand) */

spin_unlock_irqrestore(&ha->hardware_lock, flags);
@@ -1813,6 +1830,13 @@ qla2xxx_start_scsi_mq(srb_t *sp)

tot_dsds = nseg;
req_cnt = qla24xx_calc_iocbs(vha, tot_dsds);
+ if (qpair->fw_res_tracking) {
+ sp->iores.iocb_cnt = req_cnt;
+ sp->iores.res_type = RESOURCE_INI;
+ if (qla_get_iocbs(ha, &sp->iores))
+ goto queuing_error;
+ }
+
if (req->cnt < (req_cnt + 2)) {
cnt = IS_SHADOW_REG_CAPABLE(ha) ? *req->out_ptr :
RD_REG_DWORD_RELAXED(req->req_q_out);
@@ -1890,6 +1914,7 @@ qla2xxx_start_scsi_mq(srb_t *sp)
if (tot_dsds)
scsi_dma_unmap(cmd);

+ qla_put_iocbs(qpair, &sp->iores);
spin_unlock_irqrestore(&qpair->qp_lock, flags);

return QLA_FUNCTION_FAILED;
@@ -2028,6 +2053,14 @@ qla2xxx_dif_start_scsi_mq(srb_t *sp)
/* Total Data and protection sg segment(s) */
tot_prot_dsds = nseg;
tot_dsds += nseg;
+
+ if (qpair->fw_res_tracking) {
+ sp->iores.iocb_cnt = qla24xx_calc_iocbs(vha, tot_dsds);
+ sp->iores.res_type = RESOURCE_INI;
+ if (qla_get_iocbs(ha, &sp->iores))
+ goto queuing_error;
+ }
+
if (req->cnt < (req_cnt + 2)) {
cnt = IS_SHADOW_REG_CAPABLE(ha) ? *req->out_ptr :
RD_REG_DWORD_RELAXED(req->req_q_out);
@@ -2103,6 +2136,7 @@ qla2xxx_dif_start_scsi_mq(srb_t *sp)
req->outstanding_cmds[handle] = NULL;
req->cnt += req_cnt;
}
+ qla_put_iocbs(qpair, &sp->iores);
/* Cleanup will be performed by the caller (queuecommand) */

spin_unlock_irqrestore(&qpair->qp_lock, flags);
@@ -3625,6 +3659,14 @@ qla2x00_start_bidir(srb_t *sp, struct scsi_qla_host *vha, uint32_t tot_dsds)

/* Calculate number of IOCB required */
req_cnt = qla24xx_calc_iocbs(vha, tot_dsds);
+ if (ha->base_qpair->fw_res_tracking) {
+ sp->iores.iocb_cnt = req_cnt;
+ sp->iores.res_type = RESOURCE_INI;
+ if (qla_get_iocbs(ha, &sp->iores)) {
+ rval = EXT_STATUS_BUSY;
+ goto queuing_error;
+ }
+ }

/* Check for room on request queue. */
if (req->cnt < req_cnt + 2) {
@@ -3667,6 +3709,9 @@ qla2x00_start_bidir(srb_t *sp, struct scsi_qla_host *vha, uint32_t tot_dsds)
wmb();
qla2x00_start_iocbs(vha, req);
queuing_error:
+ if (rval)
+ qla_put_iocbs(ha->base_qpair, &sp->iores);
+
spin_unlock_irqrestore(&ha->hardware_lock, flags);
return rval;
}
diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c
index 33865e0bb29f..7ae8d4d22952 100644
--- a/drivers/scsi/qla2xxx/qla_isr.c
+++ b/drivers/scsi/qla2xxx/qla_isr.c
@@ -1283,6 +1283,8 @@ qla2x00_process_completed_request(struct scsi_qla_host *vha,
/* Free outstanding command slot. */
req->outstanding_cmds[index] = NULL;

+ qla_put_iocbs(sp->qpair, &sp->iores);
+
/* Save ISP completion status */
sp->done(sp, DID_OK << 16);
} else {
@@ -2368,6 +2370,9 @@ qla25xx_process_bidir_status_iocb(scsi_qla_host_t *vha, void *pkt,
bsg_job->reply_len = sizeof(struct fc_bsg_reply);
/* Always return DID_OK, bsg will send the vendor specific response
* in this case only */
+
+ qla_put_iocbs(sp->qpair, &sp->iores);
+
sp->done(sp, DID_OK << 6);

}
@@ -2745,8 +2750,10 @@ qla2x00_status_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, void *pkt)
cp->cmnd, scsi_bufflen(cp), rsp_info_len,
resid_len, fw_resid_len, sp, cp);

- if (rsp->status_srb == NULL)
+ if (rsp->status_srb == NULL) {
+ qla_put_iocbs(sp->qpair, &sp->iores);
sp->done(sp, res);
+ }
}

/**
@@ -2849,6 +2856,7 @@ qla2x00_error_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, sts_entry_t *pkt)
case MBX_IOCB_TYPE:
sp = qla2x00_get_sp_from_handle(vha, func, req, pkt);
if (sp) {
+ qla_put_iocbs(sp->qpair, &sp->iores);
sp->done(sp, res);
return 0;
}
diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index 029b95b2bd8a..7f361e93c593 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -277,6 +277,11 @@ MODULE_PARM_DESC(ql2xenablemsix,
" 1 -- enable MSI-X interrupt mechanism.\n"
" 2 -- enable MSI interrupt mechanism.\n");

+int ql2xtrackfwres;
+module_param(ql2xtrackfwres, int, 0444);
+MODULE_PARM_DESC(ql2xtrackfwres,
+ "Track FW resource. 0(default): disabled");
+
/*
* SCSI host template entry points
*/
@@ -1772,6 +1777,7 @@ qla2x00_abort_all_cmds(scsi_qla_host_t *vha, int res)
atomic_dec(
&sp->ref_count);
}
+ qla_put_iocbs(sp->qpair, &sp->iores);
sp->done(sp, res);
} else {
if (!vha->hw->tgt.tgt_ops || !tgt ||
@@ -2790,6 +2796,10 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
ha->link_data_rate = PORT_SPEED_UNKNOWN;
ha->optrom_size = OPTROM_SIZE_2300;
ha->max_exchg = FW_MAX_EXCHANGES_CNT;
+ ha->fwres.ini_iocbs_reserve = DEF_RES_INI_IOCBS;
+ ha->fwres.tgt_iocbs_reserve = DEF_RES_TGT_IOCBS;
+ ha->fwres.busy_iocbs_reserve = DEF_RES_BUSY_IOCBS;
+ spin_lock_init(&ha->fwres.rescnt_lock);

/* Assign ISP specific operations. */
if (IS_QLA2100(ha)) {
diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c
index fcfdbe1420cd..0d77b2f67077 100644
--- a/drivers/scsi/qla2xxx/qla_target.c
+++ b/drivers/scsi/qla2xxx/qla_target.c
@@ -2628,6 +2628,14 @@ static int qlt_pre_xmit_response(struct qla_tgt_cmd *cmd,
}
}

+ if (cmd->qpair->fw_res_tracking) {
+ cmd->iores.iocb_cnt = *full_req_cnt;
+ cmd->iores.res_type = RESOURCE_TGT;
+ if (qla_get_iocbs(cmd->vha->hw, &cmd->iores)) {
+ qlt_unmap_sg(cmd->vha, cmd);
+ return -EAGAIN;
+ }
+ }
return 0;
}

@@ -3202,6 +3210,7 @@ int qlt_xmit_response(struct qla_tgt_cmd *cmd, int xmit_type,
out_unmap_unlock:
qlt_unmap_sg(vha, cmd);
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);
+ qla_put_iocbs(qpair, &cmd->iores);

return res;
}
@@ -3243,6 +3252,14 @@ int qlt_rdy_to_xfer(struct qla_tgt_cmd *cmd)
}

spin_lock_irqsave(qpair->qp_lock_ptr, flags);
+
+ if (cmd->qpair->fw_res_tracking) {
+ cmd->iores.iocb_cnt = prm.req_cnt;
+ cmd->iores.res_type = RESOURCE_TGT;
+ if (qla_get_iocbs(cmd->vha->hw, &cmd->iores))
+ goto out_unlock_free_unmap;
+ }
+
/* Does F/W have an IOCBs for this request */
res = qlt_check_reserve_free_req(qpair, prm.req_cnt);
if (res != 0)
@@ -3281,6 +3298,7 @@ int qlt_rdy_to_xfer(struct qla_tgt_cmd *cmd)
qlt_unmap_sg(vha, cmd);
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);

+ qla_put_iocbs(qpair, &cmd->iores);
return res;
}
EXPORT_SYMBOL(qlt_rdy_to_xfer);
@@ -3810,6 +3828,8 @@ qlt_abort_cmd_on_host_reset(struct scsi_qla_host *vha, struct qla_tgt_cmd *cmd)
dump_stack();
}

+ qla_put_iocbs(cmd->qpair, &cmd->iores);
+
cmd->trc_flags |= TRC_FLUSH;
ha->tgt.tgt_ops->free_cmd(cmd);
}
@@ -3841,6 +3861,7 @@ static void qlt_do_ctio_completion(struct scsi_qla_host *vha,

se_cmd = &cmd->se_cmd;
cmd->cmd_sent_to_fw = 0;
+ qla_put_iocbs(cmd->qpair, &cmd->iores);

qlt_unmap_sg(vha, cmd);

@@ -6412,6 +6433,9 @@ qlt_enable_vha(struct scsi_qla_host *vha)
qla24xx_disable_vp(vha);
qla24xx_enable_vp(vha);
} else {
+ if (ql2xtrackfwres && (IS_QLA83XX(ha) || IS_QLA27XX(ha)))
+ QLA_ENA_FW_RES_TRACKING(ha);
+
set_bit(ISP_ABORT_NEEDED, &base_vha->dpc_flags);
qla2xxx_wake_dpc(base_vha);
qla2x00_wait_for_hba_online(base_vha);
diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h
index aba58d3848a6..588c8bcf1192 100644
--- a/drivers/scsi/qla2xxx/qla_target.h
+++ b/drivers/scsi/qla2xxx/qla_target.h
@@ -877,7 +877,8 @@ struct qla_tgt_cmd {
* Do not move cmd_type field. it needs to line up with srb->cmd_type
*/
uint8_t cmd_type;
- uint8_t pad[7];
+ uint8_t pad[3];
+ struct iocb_resource iores;
struct se_cmd se_cmd;
struct fc_port *sess;
struct qla_qpair *qpair;
--
2.12.0