]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - drivers/scsi/cxlflash/main.c
scsi: cxlflash: Schedule asynchronous reset of the host
[mirror_ubuntu-zesty-kernel.git] / drivers / scsi / cxlflash / main.c
index 5d068696eee45ce594444a783e1ce123223957f1..20c2c5e111b44364f98a15c53293bf0b9fdb6bb1 100644 (file)
@@ -189,55 +189,59 @@ static void cmd_complete(struct afu_cmd *cmd)
 }
 
 /**
- * context_reset() - reset command owner context via specified register
- * @cmd:       AFU command that timed out.
+ * context_reset() - reset context via specified register
+ * @hwq:       Hardware queue owning the context to be reset.
  * @reset_reg: MMIO register to perform reset.
+ *
+ * Return: 0 on success, -errno on failure
  */
-static void context_reset(struct afu_cmd *cmd, __be64 __iomem *reset_reg)
+static int context_reset(struct hwq *hwq, __be64 __iomem *reset_reg)
 {
-       int nretry = 0;
-       u64 rrin = 0x1;
-       struct afu *afu = cmd->parent;
-       struct cxlflash_cfg *cfg = afu->parent;
+       struct cxlflash_cfg *cfg = hwq->afu->parent;
        struct device *dev = &cfg->dev->dev;
+       int rc = -ETIMEDOUT;
+       int nretry = 0;
+       u64 val = 0x1;
 
-       dev_dbg(dev, "%s: cmd=%p\n", __func__, cmd);
+       dev_dbg(dev, "%s: hwq=%p\n", __func__, hwq);
 
-       writeq_be(rrin, reset_reg);
+       writeq_be(val, reset_reg);
        do {
-               rrin = readq_be(reset_reg);
-               if (rrin != 0x1)
+               val = readq_be(reset_reg);
+               if ((val & 0x1) == 0x0) {
+                       rc = 0;
                        break;
+               }
+
                /* Double delay each time */
                udelay(1 << nretry);
        } while (nretry++ < MC_ROOM_RETRY_CNT);
 
-       dev_dbg(dev, "%s: returning rrin=%016llx nretry=%d\n",
-               __func__, rrin, nretry);
+       dev_dbg(dev, "%s: returning rc=%d, val=%016llx nretry=%d\n",
+               __func__, rc, val, nretry);
+       return rc;
 }
 
 /**
- * context_reset_ioarrin() - reset command owner context via IOARRIN register
- * @cmd:       AFU command that timed out.
+ * context_reset_ioarrin() - reset context via IOARRIN register
+ * @hwq:       Hardware queue owning the context to be reset.
+ *
+ * Return: 0 on success, -errno on failure
  */
-static void context_reset_ioarrin(struct afu_cmd *cmd)
+static int context_reset_ioarrin(struct hwq *hwq)
 {
-       struct afu *afu = cmd->parent;
-       struct hwq *hwq = get_hwq(afu, cmd->hwq_index);
-
-       context_reset(cmd, &hwq->host_map->ioarrin);
+       return context_reset(hwq, &hwq->host_map->ioarrin);
 }
 
 /**
- * context_reset_sq() - reset command owner context w/ SQ Context Reset register
- * @cmd:       AFU command that timed out.
+ * context_reset_sq() - reset context via SQ_CONTEXT_RESET register
+ * @hwq:       Hardware queue owning the context to be reset.
+ *
+ * Return: 0 on success, -errno on failure
  */
-static void context_reset_sq(struct afu_cmd *cmd)
+static int context_reset_sq(struct hwq *hwq)
 {
-       struct afu *afu = cmd->parent;
-       struct hwq *hwq = get_hwq(afu, cmd->hwq_index);
-
-       context_reset(cmd, &hwq->host_map->sq_ctx_reset);
+       return context_reset(hwq, &hwq->host_map->sq_ctx_reset);
 }
 
 /**
@@ -261,7 +265,7 @@ static int send_cmd_ioarrin(struct afu *afu, struct afu_cmd *cmd)
         * To avoid the performance penalty of MMIO, spread the update of
         * 'room' over multiple commands.
         */
-       spin_lock_irqsave(&hwq->rrin_slock, lock_flags);
+       spin_lock_irqsave(&hwq->hsq_slock, lock_flags);
        if (--hwq->room < 0) {
                room = readq_be(&hwq->host_map->cmd_room);
                if (room <= 0) {
@@ -277,7 +281,7 @@ static int send_cmd_ioarrin(struct afu *afu, struct afu_cmd *cmd)
 
        writeq_be((u64)&cmd->rcb, &hwq->host_map->ioarrin);
 out:
-       spin_unlock_irqrestore(&hwq->rrin_slock, lock_flags);
+       spin_unlock_irqrestore(&hwq->hsq_slock, lock_flags);
        dev_dbg(dev, "%s: cmd=%p len=%u ea=%016llx rc=%d\n", __func__,
                cmd, cmd->rcb.data_len, cmd->rcb.data_ea, rc);
        return rc;
@@ -332,8 +336,7 @@ out:
  * @afu:       AFU associated with the host.
  * @cmd:       AFU command that was sent.
  *
- * Return:
- *     0 on success, -1 on timeout/error
+ * Return: 0 on success, -errno on failure
  */
 static int wait_resp(struct afu *afu, struct afu_cmd *cmd)
 {
@@ -343,20 +346,55 @@ static int wait_resp(struct afu *afu, struct afu_cmd *cmd)
        ulong timeout = msecs_to_jiffies(cmd->rcb.timeout * 2 * 1000);
 
        timeout = wait_for_completion_timeout(&cmd->cevent, timeout);
-       if (!timeout) {
-               afu->context_reset(cmd);
-               rc = -1;
-       }
+       if (!timeout)
+               rc = -ETIMEDOUT;
 
        if (unlikely(cmd->sa.ioasc != 0)) {
                dev_err(dev, "%s: cmd %02x failed, ioasc=%08x\n",
                        __func__, cmd->rcb.cdb[0], cmd->sa.ioasc);
-               rc = -1;
+               rc = -EIO;
        }
 
        return rc;
 }
 
+/**
+ * cmd_to_target_hwq() - selects a target hardware queue for a SCSI command
+ * @host:      SCSI host associated with device.
+ * @scp:       SCSI command to send.
+ * @afu:       SCSI command to send.
+ *
+ * Hashes a command based upon the hardware queue mode.
+ *
+ * Return: Trusted index of target hardware queue
+ */
+static u32 cmd_to_target_hwq(struct Scsi_Host *host, struct scsi_cmnd *scp,
+                            struct afu *afu)
+{
+       u32 tag;
+       u32 hwq = 0;
+
+       if (afu->num_hwqs == 1)
+               return 0;
+
+       switch (afu->hwq_mode) {
+       case HWQ_MODE_RR:
+               hwq = afu->hwq_rr_count++ % afu->num_hwqs;
+               break;
+       case HWQ_MODE_TAG:
+               tag = blk_mq_unique_tag(scp->request);
+               hwq = blk_mq_unique_tag_to_hwq(tag);
+               break;
+       case HWQ_MODE_CPU:
+               hwq = smp_processor_id() % afu->num_hwqs;
+               break;
+       default:
+               WARN_ON_ONCE(1);
+       }
+
+       return hwq;
+}
+
 /**
  * send_tmf() - sends a Task Management Function (TMF)
  * @afu:       AFU to checkout from.
@@ -368,10 +406,12 @@ static int wait_resp(struct afu *afu, struct afu_cmd *cmd)
  */
 static int send_tmf(struct afu *afu, struct scsi_cmnd *scp, u64 tmfcmd)
 {
-       struct cxlflash_cfg *cfg = shost_priv(scp->device->host);
+       struct Scsi_Host *host = scp->device->host;
+       struct cxlflash_cfg *cfg = shost_priv(host);
        struct afu_cmd *cmd = sc_to_afucz(scp);
        struct device *dev = &cfg->dev->dev;
-       struct hwq *hwq = get_hwq(afu, PRIMARY_HWQ);
+       int hwq_index = cmd_to_target_hwq(host, scp, afu);
+       struct hwq *hwq = get_hwq(afu, hwq_index);
        ulong lock_flags;
        int rc = 0;
        ulong to;
@@ -388,7 +428,7 @@ static int send_tmf(struct afu *afu, struct scsi_cmnd *scp, u64 tmfcmd)
        cmd->scp = scp;
        cmd->parent = afu;
        cmd->cmd_tmf = true;
-       cmd->hwq_index = hwq->index;
+       cmd->hwq_index = hwq_index;
 
        cmd->rcb.ctx_id = hwq->ctx_hndl;
        cmd->rcb.msi = SISL_MSI_RRQ_UPDATED;
@@ -448,7 +488,8 @@ static int cxlflash_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scp)
        struct device *dev = &cfg->dev->dev;
        struct afu_cmd *cmd = sc_to_afucz(scp);
        struct scatterlist *sg = scsi_sglist(scp);
-       struct hwq *hwq = get_hwq(afu, PRIMARY_HWQ);
+       int hwq_index = cmd_to_target_hwq(host, scp, afu);
+       struct hwq *hwq = get_hwq(afu, hwq_index);
        u16 req_flags = SISL_REQ_FLAGS_SUP_UNDERRUN;
        ulong lock_flags;
        int rc = 0;
@@ -498,7 +539,7 @@ static int cxlflash_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scp)
 
        cmd->scp = scp;
        cmd->parent = afu;
-       cmd->hwq_index = hwq->index;
+       cmd->hwq_index = hwq_index;
 
        cmd->rcb.ctx_id = hwq->ctx_hndl;
        cmd->rcb.msi = SISL_MSI_RRQ_UPDATED;
@@ -544,6 +585,20 @@ static void free_mem(struct cxlflash_cfg *cfg)
        }
 }
 
+/**
+ * cxlflash_reset_sync() - synchronizing point for asynchronous resets
+ * @cfg:       Internal structure associated with the host.
+ */
+static void cxlflash_reset_sync(struct cxlflash_cfg *cfg)
+{
+       if (cfg->async_reset_cookie == 0)
+               return;
+
+       /* Wait until all async calls prior to this cookie have completed */
+       async_synchronize_cookie(cfg->async_reset_cookie + 1);
+       cfg->async_reset_cookie = 0;
+}
+
 /**
  * stop_afu() - stops the AFU command timers and unmaps the MMIO space
  * @cfg:       Internal structure associated with the host.
@@ -560,13 +615,15 @@ static void stop_afu(struct cxlflash_cfg *cfg)
        int i;
 
        cancel_work_sync(&cfg->work_q);
+       if (!current_is_async())
+               cxlflash_reset_sync(cfg);
 
        if (likely(afu)) {
                while (atomic_read(&afu->cmds_active))
                        ssleep(1);
 
                if (afu_is_irqpoll_enabled(afu)) {
-                       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+                       for (i = 0; i < afu->num_hwqs; i++) {
                                hwq = get_hwq(afu, i);
 
                                irq_poll_disable(&hwq->irqpoll);
@@ -676,13 +733,13 @@ static void term_afu(struct cxlflash_cfg *cfg)
         * 2) Unmap the problem state area
         * 3) Stop each master context
         */
-       for (k = CXLFLASH_NUM_HWQS - 1; k >= 0; k--)
+       for (k = cfg->afu->num_hwqs - 1; k >= 0; k--)
                term_intr(cfg, UNMAP_THREE, k);
 
        if (cfg->afu)
                stop_afu(cfg);
 
-       for (k = CXLFLASH_NUM_HWQS - 1; k >= 0; k--)
+       for (k = cfg->afu->num_hwqs - 1; k >= 0; k--)
                term_mc(cfg, k);
 
        dev_dbg(dev, "%s: returning\n", __func__);
@@ -823,6 +880,7 @@ static int alloc_mem(struct cxlflash_cfg *cfg)
                goto out;
        }
        cfg->afu->parent = cfg;
+       cfg->afu->desired_hwqs = CXLFLASH_DEF_HWQS;
        cfg->afu->afu_map = NULL;
 out:
        return rc;
@@ -1116,7 +1174,7 @@ static void afu_err_intr_init(struct afu *afu)
        /* IOARRIN yet), so there is nothing to clear. */
 
        /* set LISN#, it is always sent to the context that wrote IOARRIN */
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
 
                writeq_be(SISL_MSI_SYNC_ERROR, &hwq->host_map->ctx_ctrl);
@@ -1551,7 +1609,7 @@ static void init_pcr(struct cxlflash_cfg *cfg)
        }
 
        /* Copy frequently used fields into hwq */
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
 
                hwq->ctx_hndl = (u16) cxl_process_element(hwq->ctx);
@@ -1586,7 +1644,7 @@ static int init_global(struct cxlflash_cfg *cfg)
        }
 
        /* Set up RRQ and SQ in HWQ for master issued cmds */
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
                hmap = hwq->host_map;
 
@@ -1640,7 +1698,7 @@ static int init_global(struct cxlflash_cfg *cfg)
        /* Set up master's own CTX_CAP to allow real mode, host translation */
        /* tables, afu cmds and read/write GSCSI cmds. */
        /* First, unlock ctx_cap write by reading mbox */
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
 
                (void)readq_be(&hwq->ctrl_map->mbox_r); /* unlock ctx_cap */
@@ -1670,7 +1728,7 @@ static int start_afu(struct cxlflash_cfg *cfg)
        init_pcr(cfg);
 
        /* Initialize each HWQ */
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
 
                /* After an AFU reset, RRQ entries are stale, clear them */
@@ -1681,7 +1739,10 @@ static int start_afu(struct cxlflash_cfg *cfg)
                hwq->hrrq_end = &hwq->rrq_entry[NUM_RRQ_ENTRY - 1];
                hwq->hrrq_curr = hwq->hrrq_start;
                hwq->toggle = 1;
+
+               /* Initialize spin locks */
                spin_lock_init(&hwq->hrrq_slock);
+               spin_lock_init(&hwq->hsq_slock);
 
                /* Initialize SQ */
                if (afu_is_sq_cmd_mode(afu)) {
@@ -1690,7 +1751,6 @@ static int start_afu(struct cxlflash_cfg *cfg)
                        hwq->hsq_end = &hwq->sq[NUM_SQ_ENTRY - 1];
                        hwq->hsq_curr = hwq->hsq_start;
 
-                       spin_lock_init(&hwq->hsq_slock);
                        atomic_set(&hwq->hsq_credits, NUM_SQ_ENTRY - 1);
                }
 
@@ -1888,7 +1948,8 @@ static int init_afu(struct cxlflash_cfg *cfg)
 
        cxl_perst_reloads_same_image(cfg->cxl_afu, true);
 
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       afu->num_hwqs = afu->desired_hwqs;
+       for (i = 0; i < afu->num_hwqs; i++) {
                rc = init_mc(cfg, i);
                if (rc) {
                        dev_err(dev, "%s: init_mc failed rc=%d index=%d\n",
@@ -1939,10 +2000,9 @@ static int init_afu(struct cxlflash_cfg *cfg)
        }
 
        afu_err_intr_init(cfg->afu);
-       for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+       for (i = 0; i < afu->num_hwqs; i++) {
                hwq = get_hwq(afu, i);
 
-               spin_lock_init(&hwq->rrin_slock);
                hwq->room = readq_be(&hwq->host_map->cmd_room);
        }
 
@@ -1953,13 +2013,98 @@ out:
        return rc;
 
 err1:
-       for (i = CXLFLASH_NUM_HWQS - 1; i >= 0; i--) {
+       for (i = afu->num_hwqs - 1; i >= 0; i--) {
                term_intr(cfg, UNMAP_THREE, i);
                term_mc(cfg, i);
        }
        goto out;
 }
 
+/**
+ * afu_reset() - resets the AFU
+ * @cfg:       Internal structure associated with the host.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+static int afu_reset(struct cxlflash_cfg *cfg)
+{
+       struct device *dev = &cfg->dev->dev;
+       int rc = 0;
+
+       /* Stop the context before the reset. Since the context is
+        * no longer available restart it after the reset is complete
+        */
+       term_afu(cfg);
+
+       rc = init_afu(cfg);
+
+       dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc);
+       return rc;
+}
+
+/**
+ * drain_ioctls() - wait until all currently executing ioctls have completed
+ * @cfg:       Internal structure associated with the host.
+ *
+ * Obtain write access to read/write semaphore that wraps ioctl
+ * handling to 'drain' ioctls currently executing.
+ */
+static void drain_ioctls(struct cxlflash_cfg *cfg)
+{
+       down_write(&cfg->ioctl_rwsem);
+       up_write(&cfg->ioctl_rwsem);
+}
+
+/**
+ * cxlflash_async_reset_host() - asynchronous host reset handler
+ * @data:      Private data provided while scheduling reset.
+ * @cookie:    Cookie that can be used for checkpointing.
+ */
+static void cxlflash_async_reset_host(void *data, async_cookie_t cookie)
+{
+       struct cxlflash_cfg *cfg = data;
+       struct device *dev = &cfg->dev->dev;
+       int rc = 0;
+
+       if (cfg->state != STATE_RESET) {
+               dev_dbg(dev, "%s: Not performing a reset, state=%d\n",
+                       __func__, cfg->state);
+               goto out;
+       }
+
+       drain_ioctls(cfg);
+       cxlflash_mark_contexts_error(cfg);
+       rc = afu_reset(cfg);
+       if (rc)
+               cfg->state = STATE_FAILTERM;
+       else
+               cfg->state = STATE_NORMAL;
+       wake_up_all(&cfg->reset_waitq);
+
+out:
+       scsi_unblock_requests(cfg->host);
+}
+
+/**
+ * cxlflash_schedule_async_reset() - schedule an asynchronous host reset
+ * @cfg:       Internal structure associated with the host.
+ */
+static void cxlflash_schedule_async_reset(struct cxlflash_cfg *cfg)
+{
+       struct device *dev = &cfg->dev->dev;
+
+       if (cfg->state != STATE_NORMAL) {
+               dev_dbg(dev, "%s: Not performing reset state=%d\n",
+                       __func__, cfg->state);
+               return;
+       }
+
+       cfg->state = STATE_RESET;
+       scsi_block_requests(cfg->host);
+       cfg->async_reset_cookie = async_schedule(cxlflash_async_reset_host,
+                                                cfg);
+}
+
 /**
  * cxlflash_afu_sync() - builds and sends an AFU sync command
  * @afu:       AFU associated with the host.
@@ -1979,8 +2124,7 @@ err1:
  * going away).
  *
  * Return:
- *     0 on success
- *     -1 on failure
+ *     0 on success, -errno on failure
  */
 int cxlflash_afu_sync(struct afu *afu, ctx_hndl_t ctx_hndl_u,
                      res_hndl_t res_hndl_u, u8 mode)
@@ -1991,6 +2135,7 @@ int cxlflash_afu_sync(struct afu *afu, ctx_hndl_t ctx_hndl_u,
        struct hwq *hwq = get_hwq(afu, PRIMARY_HWQ);
        char *buf = NULL;
        int rc = 0;
+       int nretry = 0;
        static DEFINE_MUTEX(sync_active);
 
        if (cfg->state != STATE_NORMAL) {
@@ -2004,16 +2149,19 @@ int cxlflash_afu_sync(struct afu *afu, ctx_hndl_t ctx_hndl_u,
        buf = kzalloc(sizeof(*cmd) + __alignof__(*cmd) - 1, GFP_KERNEL);
        if (unlikely(!buf)) {
                dev_err(dev, "%s: no memory for command\n", __func__);
-               rc = -1;
+               rc = -ENOMEM;
                goto out;
        }
 
        cmd = (struct afu_cmd *)PTR_ALIGN(buf, __alignof__(*cmd));
+
+retry:
        init_completion(&cmd->cevent);
        cmd->parent = afu;
        cmd->hwq_index = hwq->index;
 
-       dev_dbg(dev, "%s: afu=%p cmd=%p %d\n", __func__, afu, cmd, ctx_hndl_u);
+       dev_dbg(dev, "%s: afu=%p cmd=%p ctx=%d nretry=%d\n",
+               __func__, afu, cmd, ctx_hndl_u, nretry);
 
        cmd->rcb.req_flags = SISL_REQ_FLAGS_AFU_CMD;
        cmd->rcb.ctx_id = hwq->ctx_hndl;
@@ -2028,12 +2176,19 @@ int cxlflash_afu_sync(struct afu *afu, ctx_hndl_t ctx_hndl_u,
        *((__be32 *)&cmd->rcb.cdb[4]) = cpu_to_be32(res_hndl_u);
 
        rc = afu->send_cmd(afu, cmd);
-       if (unlikely(rc))
+       if (unlikely(rc)) {
+               rc = -ENOBUFS;
                goto out;
+       }
 
        rc = wait_resp(afu, cmd);
-       if (unlikely(rc))
-               rc = -1;
+       if (rc == -ETIMEDOUT) {
+               rc = afu->context_reset(hwq);
+               if (!rc && ++nretry < 2)
+                       goto retry;
+               cxlflash_schedule_async_reset(cfg);
+       }
+
 out:
        atomic_dec(&afu->cmds_active);
        mutex_unlock(&sync_active);
@@ -2042,41 +2197,6 @@ out:
        return rc;
 }
 
-/**
- * afu_reset() - resets the AFU
- * @cfg:       Internal structure associated with the host.
- *
- * Return: 0 on success, -errno on failure
- */
-static int afu_reset(struct cxlflash_cfg *cfg)
-{
-       struct device *dev = &cfg->dev->dev;
-       int rc = 0;
-
-       /* Stop the context before the reset. Since the context is
-        * no longer available restart it after the reset is complete
-        */
-       term_afu(cfg);
-
-       rc = init_afu(cfg);
-
-       dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc);
-       return rc;
-}
-
-/**
- * drain_ioctls() - wait until all currently executing ioctls have completed
- * @cfg:       Internal structure associated with the host.
- *
- * Obtain write access to read/write semaphore that wraps ioctl
- * handling to 'drain' ioctls currently executing.
- */
-static void drain_ioctls(struct cxlflash_cfg *cfg)
-{
-       down_write(&cfg->ioctl_rwsem);
-       up_write(&cfg->ioctl_rwsem);
-}
-
 /**
  * cxlflash_eh_device_reset_handler() - reset a single LUN
  * @scp:       SCSI command to send.
@@ -2550,7 +2670,7 @@ static ssize_t irqpoll_weight_store(struct device *dev,
        }
 
        if (afu_is_irqpoll_enabled(afu)) {
-               for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+               for (i = 0; i < afu->num_hwqs; i++) {
                        hwq = get_hwq(afu, i);
 
                        irq_poll_disable(&hwq->irqpoll);
@@ -2560,7 +2680,7 @@ static ssize_t irqpoll_weight_store(struct device *dev,
        afu->irqpoll_weight = weight;
 
        if (weight > 0) {
-               for (i = 0; i < CXLFLASH_NUM_HWQS; i++) {
+               for (i = 0; i < afu->num_hwqs; i++) {
                        hwq = get_hwq(afu, i);
 
                        irq_poll_init(&hwq->irqpoll, weight, cxlflash_irqpoll);
@@ -2570,6 +2690,156 @@ static ssize_t irqpoll_weight_store(struct device *dev,
        return count;
 }
 
+/**
+ * num_hwqs_show() - presents the number of hardware queues for the host
+ * @dev:       Generic device associated with the host.
+ * @attr:      Device attribute representing the number of hardware queues.
+ * @buf:       Buffer of length PAGE_SIZE to report back the number of hardware
+ *             queues in ASCII.
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t num_hwqs_show(struct device *dev,
+                            struct device_attribute *attr, char *buf)
+{
+       struct cxlflash_cfg *cfg = shost_priv(class_to_shost(dev));
+       struct afu *afu = cfg->afu;
+
+       return scnprintf(buf, PAGE_SIZE, "%u\n", afu->num_hwqs);
+}
+
+/**
+ * num_hwqs_store() - sets the number of hardware queues for the host
+ * @dev:       Generic device associated with the host.
+ * @attr:      Device attribute representing the number of hardware queues.
+ * @buf:       Buffer of length PAGE_SIZE containing the number of hardware
+ *             queues in ASCII.
+ * @count:     Length of data resizing in @buf.
+ *
+ * n > 0: num_hwqs = n
+ * n = 0: num_hwqs = num_online_cpus()
+ * n < 0: num_online_cpus() / abs(n)
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t num_hwqs_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct cxlflash_cfg *cfg = shost_priv(class_to_shost(dev));
+       struct afu *afu = cfg->afu;
+       int rc;
+       int nhwqs, num_hwqs;
+
+       rc = kstrtoint(buf, 10, &nhwqs);
+       if (rc)
+               return -EINVAL;
+
+       if (nhwqs >= 1)
+               num_hwqs = nhwqs;
+       else if (nhwqs == 0)
+               num_hwqs = num_online_cpus();
+       else
+               num_hwqs = num_online_cpus() / abs(nhwqs);
+
+       afu->desired_hwqs = min(num_hwqs, CXLFLASH_MAX_HWQS);
+       WARN_ON_ONCE(afu->desired_hwqs == 0);
+
+retry:
+       switch (cfg->state) {
+       case STATE_NORMAL:
+               cfg->state = STATE_RESET;
+               drain_ioctls(cfg);
+               cxlflash_mark_contexts_error(cfg);
+               rc = afu_reset(cfg);
+               if (rc)
+                       cfg->state = STATE_FAILTERM;
+               else
+                       cfg->state = STATE_NORMAL;
+               wake_up_all(&cfg->reset_waitq);
+               break;
+       case STATE_RESET:
+               wait_event(cfg->reset_waitq, cfg->state != STATE_RESET);
+               if (cfg->state == STATE_NORMAL)
+                       goto retry;
+       default:
+               /* Ideally should not happen */
+               dev_err(dev, "%s: Device is not ready, state=%d\n",
+                       __func__, cfg->state);
+               break;
+       }
+
+       return count;
+}
+
+static const char *hwq_mode_name[MAX_HWQ_MODE] = { "rr", "tag", "cpu" };
+
+/**
+ * hwq_mode_show() - presents the HWQ steering mode for the host
+ * @dev:       Generic device associated with the host.
+ * @attr:      Device attribute representing the HWQ steering mode.
+ * @buf:       Buffer of length PAGE_SIZE to report back the HWQ steering mode
+ *             as a character string.
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t hwq_mode_show(struct device *dev,
+                            struct device_attribute *attr, char *buf)
+{
+       struct cxlflash_cfg *cfg = shost_priv(class_to_shost(dev));
+       struct afu *afu = cfg->afu;
+
+       return scnprintf(buf, PAGE_SIZE, "%s\n", hwq_mode_name[afu->hwq_mode]);
+}
+
+/**
+ * hwq_mode_store() - sets the HWQ steering mode for the host
+ * @dev:       Generic device associated with the host.
+ * @attr:      Device attribute representing the HWQ steering mode.
+ * @buf:       Buffer of length PAGE_SIZE containing the HWQ steering mode
+ *             as a character string.
+ * @count:     Length of data resizing in @buf.
+ *
+ * rr = Round-Robin
+ * tag = Block MQ Tagging
+ * cpu = CPU Affinity
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t hwq_mode_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct Scsi_Host *shost = class_to_shost(dev);
+       struct cxlflash_cfg *cfg = shost_priv(shost);
+       struct device *cfgdev = &cfg->dev->dev;
+       struct afu *afu = cfg->afu;
+       int i;
+       u32 mode = MAX_HWQ_MODE;
+
+       for (i = 0; i < MAX_HWQ_MODE; i++) {
+               if (!strncmp(hwq_mode_name[i], buf, strlen(hwq_mode_name[i]))) {
+                       mode = i;
+                       break;
+               }
+       }
+
+       if (mode >= MAX_HWQ_MODE) {
+               dev_info(cfgdev, "Invalid HWQ steering mode.\n");
+               return -EINVAL;
+       }
+
+       if ((mode == HWQ_MODE_TAG) && !shost_use_blk_mq(shost)) {
+               dev_info(cfgdev, "SCSI-MQ is not enabled, use a different "
+                        "HWQ steering mode.\n");
+               return -EINVAL;
+       }
+
+       afu->hwq_mode = mode;
+
+       return count;
+}
+
 /**
  * mode_show() - presents the current mode of the device
  * @dev:       Generic device associated with the device.
@@ -2601,6 +2871,8 @@ static DEVICE_ATTR_RO(port1_lun_table);
 static DEVICE_ATTR_RO(port2_lun_table);
 static DEVICE_ATTR_RO(port3_lun_table);
 static DEVICE_ATTR_RW(irqpoll_weight);
+static DEVICE_ATTR_RW(num_hwqs);
+static DEVICE_ATTR_RW(hwq_mode);
 
 static struct device_attribute *cxlflash_host_attrs[] = {
        &dev_attr_port0,
@@ -2614,6 +2886,8 @@ static struct device_attribute *cxlflash_host_attrs[] = {
        &dev_attr_port2_lun_table,
        &dev_attr_port3_lun_table,
        &dev_attr_irqpoll_weight,
+       &dev_attr_num_hwqs,
+       &dev_attr_hwq_mode,
        NULL
 };