root/drivers/infiniband/hw/erdma/erdma_cmdq.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause

/* Authors: Cheng Xu <chengyou@linux.alibaba.com> */
/*          Kai Shen <kaishen@linux.alibaba.com> */
/* Copyright (c) 2020-2022, Alibaba Group. */

#include "erdma.h"

static void arm_cmdq_cq(struct erdma_cmdq *cmdq)
{
        struct erdma_dev *dev = container_of(cmdq, struct erdma_dev, cmdq);
        u64 db_data = FIELD_PREP(ERDMA_CQDB_CI_MASK, cmdq->cq.ci) |
                      FIELD_PREP(ERDMA_CQDB_ARM_MASK, 1) |
                      FIELD_PREP(ERDMA_CQDB_CMDSN_MASK, cmdq->cq.cmdsn) |
                      FIELD_PREP(ERDMA_CQDB_IDX_MASK, cmdq->cq.cmdsn);

        *cmdq->cq.dbrec = db_data;
        writeq(db_data, dev->func_bar + ERDMA_CMDQ_CQDB_REG);

        atomic64_inc(&cmdq->cq.armed_num);
}

static void kick_cmdq_db(struct erdma_cmdq *cmdq)
{
        struct erdma_dev *dev = container_of(cmdq, struct erdma_dev, cmdq);
        u64 db_data = FIELD_PREP(ERDMA_CMD_HDR_WQEBB_INDEX_MASK, cmdq->sq.pi);

        *cmdq->sq.dbrec = db_data;
        writeq(db_data, dev->func_bar + ERDMA_CMDQ_SQDB_REG);
}

static struct erdma_comp_wait *get_comp_wait(struct erdma_cmdq *cmdq)
{
        int comp_idx;

        spin_lock(&cmdq->lock);
        comp_idx = find_first_zero_bit(cmdq->comp_wait_bitmap,
                                       cmdq->max_outstandings);
        if (comp_idx == cmdq->max_outstandings) {
                spin_unlock(&cmdq->lock);
                return ERR_PTR(-ENOMEM);
        }

        __set_bit(comp_idx, cmdq->comp_wait_bitmap);
        spin_unlock(&cmdq->lock);

        return &cmdq->wait_pool[comp_idx];
}

static void put_comp_wait(struct erdma_cmdq *cmdq,
                          struct erdma_comp_wait *comp_wait)
{
        int used;

        cmdq->wait_pool[comp_wait->ctx_id].cmd_status = ERDMA_CMD_STATUS_INIT;
        spin_lock(&cmdq->lock);
        used = __test_and_clear_bit(comp_wait->ctx_id, cmdq->comp_wait_bitmap);
        spin_unlock(&cmdq->lock);

        WARN_ON(!used);
}

static int erdma_cmdq_wait_res_init(struct erdma_dev *dev,
                                    struct erdma_cmdq *cmdq)
{
        int i;

        cmdq->wait_pool =
                devm_kcalloc(&dev->pdev->dev, cmdq->max_outstandings,
                             sizeof(struct erdma_comp_wait), GFP_KERNEL);
        if (!cmdq->wait_pool)
                return -ENOMEM;

        spin_lock_init(&cmdq->lock);
        cmdq->comp_wait_bitmap = devm_bitmap_zalloc(
                &dev->pdev->dev, cmdq->max_outstandings, GFP_KERNEL);
        if (!cmdq->comp_wait_bitmap)
                return -ENOMEM;

        for (i = 0; i < cmdq->max_outstandings; i++) {
                init_completion(&cmdq->wait_pool[i].wait_event);
                cmdq->wait_pool[i].ctx_id = i;
        }

        return 0;
}

static int erdma_cmdq_sq_init(struct erdma_dev *dev)
{
        struct erdma_cmdq *cmdq = &dev->cmdq;
        struct erdma_cmdq_sq *sq = &cmdq->sq;

        sq->wqebb_cnt = SQEBB_COUNT(ERDMA_CMDQ_SQE_SIZE);
        sq->depth = cmdq->max_outstandings * sq->wqebb_cnt;

        sq->qbuf = dma_alloc_coherent(&dev->pdev->dev, sq->depth << SQEBB_SHIFT,
                                      &sq->qbuf_dma_addr, GFP_KERNEL);
        if (!sq->qbuf)
                return -ENOMEM;

        sq->dbrec = dma_pool_zalloc(dev->db_pool, GFP_KERNEL, &sq->dbrec_dma);
        if (!sq->dbrec)
                goto err_out;

        spin_lock_init(&sq->lock);

        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_SQ_ADDR_H_REG,
                          upper_32_bits(sq->qbuf_dma_addr));
        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_SQ_ADDR_L_REG,
                          lower_32_bits(sq->qbuf_dma_addr));
        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_DEPTH_REG, sq->depth);
        erdma_reg_write64(dev, ERDMA_CMDQ_SQ_DB_HOST_ADDR_REG, sq->dbrec_dma);

        return 0;

err_out:
        dma_free_coherent(&dev->pdev->dev, sq->depth << SQEBB_SHIFT,
                          sq->qbuf, sq->qbuf_dma_addr);

        return -ENOMEM;
}

static int erdma_cmdq_cq_init(struct erdma_dev *dev)
{
        struct erdma_cmdq *cmdq = &dev->cmdq;
        struct erdma_cmdq_cq *cq = &cmdq->cq;

        cq->depth = cmdq->sq.depth;
        cq->qbuf = dma_alloc_coherent(&dev->pdev->dev, cq->depth << CQE_SHIFT,
                                      &cq->qbuf_dma_addr, GFP_KERNEL);
        if (!cq->qbuf)
                return -ENOMEM;

        spin_lock_init(&cq->lock);

        cq->dbrec = dma_pool_zalloc(dev->db_pool, GFP_KERNEL, &cq->dbrec_dma);
        if (!cq->dbrec)
                goto err_out;

        atomic64_set(&cq->armed_num, 0);

        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_CQ_ADDR_H_REG,
                          upper_32_bits(cq->qbuf_dma_addr));
        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_CQ_ADDR_L_REG,
                          lower_32_bits(cq->qbuf_dma_addr));
        erdma_reg_write64(dev, ERDMA_CMDQ_CQ_DB_HOST_ADDR_REG, cq->dbrec_dma);

        return 0;

err_out:
        dma_free_coherent(&dev->pdev->dev, cq->depth << CQE_SHIFT, cq->qbuf,
                          cq->qbuf_dma_addr);

        return -ENOMEM;
}

static int erdma_cmdq_eq_init(struct erdma_dev *dev)
{
        struct erdma_cmdq *cmdq = &dev->cmdq;
        struct erdma_eq *eq = &cmdq->eq;
        int ret;

        ret = erdma_eq_common_init(dev, eq, cmdq->max_outstandings);
        if (ret)
                return ret;

        eq->db = dev->func_bar + ERDMA_REGS_CEQ_DB_BASE_REG;

        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_EQ_ADDR_H_REG,
                          upper_32_bits(eq->qbuf_dma_addr));
        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_EQ_ADDR_L_REG,
                          lower_32_bits(eq->qbuf_dma_addr));
        erdma_reg_write32(dev, ERDMA_REGS_CMDQ_EQ_DEPTH_REG, eq->depth);
        erdma_reg_write64(dev, ERDMA_CMDQ_EQ_DB_HOST_ADDR_REG, eq->dbrec_dma);

        return 0;
}

int erdma_cmdq_init(struct erdma_dev *dev)
{
        struct erdma_cmdq *cmdq = &dev->cmdq;
        int err;

        cmdq->max_outstandings = ERDMA_CMDQ_MAX_OUTSTANDING;

        sema_init(&cmdq->credits, cmdq->max_outstandings);

        err = erdma_cmdq_wait_res_init(dev, cmdq);
        if (err)
                return err;

        err = erdma_cmdq_sq_init(dev);
        if (err)
                return err;

        err = erdma_cmdq_cq_init(dev);
        if (err)
                goto err_destroy_sq;

        err = erdma_cmdq_eq_init(dev);
        if (err)
                goto err_destroy_cq;

        set_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state);

        return 0;

err_destroy_cq:
        dma_free_coherent(&dev->pdev->dev, cmdq->cq.depth << CQE_SHIFT,
                          cmdq->cq.qbuf, cmdq->cq.qbuf_dma_addr);

        dma_pool_free(dev->db_pool, cmdq->cq.dbrec, cmdq->cq.dbrec_dma);

err_destroy_sq:
        dma_free_coherent(&dev->pdev->dev, cmdq->sq.depth << SQEBB_SHIFT,
                          cmdq->sq.qbuf, cmdq->sq.qbuf_dma_addr);

        dma_pool_free(dev->db_pool, cmdq->sq.dbrec, cmdq->sq.dbrec_dma);

        return err;
}

void erdma_finish_cmdq_init(struct erdma_dev *dev)
{
        arm_cmdq_cq(&dev->cmdq);
}

void erdma_cmdq_destroy(struct erdma_dev *dev)
{
        struct erdma_cmdq *cmdq = &dev->cmdq;

        clear_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state);

        erdma_eq_destroy(dev, &cmdq->eq);

        dma_free_coherent(&dev->pdev->dev, cmdq->sq.depth << SQEBB_SHIFT,
                          cmdq->sq.qbuf, cmdq->sq.qbuf_dma_addr);

        dma_pool_free(dev->db_pool, cmdq->sq.dbrec, cmdq->sq.dbrec_dma);

        dma_free_coherent(&dev->pdev->dev, cmdq->cq.depth << CQE_SHIFT,
                          cmdq->cq.qbuf, cmdq->cq.qbuf_dma_addr);

        dma_pool_free(dev->db_pool, cmdq->cq.dbrec, cmdq->cq.dbrec_dma);
}

static void *get_next_valid_cmdq_cqe(struct erdma_cmdq *cmdq)
{
        __be32 *cqe = get_queue_entry(cmdq->cq.qbuf, cmdq->cq.ci,
                                      cmdq->cq.depth, CQE_SHIFT);
        u32 owner = FIELD_GET(ERDMA_CQE_HDR_OWNER_MASK,
                              be32_to_cpu(READ_ONCE(*cqe)));

        return owner ^ !!(cmdq->cq.ci & cmdq->cq.depth) ? cqe : NULL;
}

static void push_cmdq_sqe(struct erdma_cmdq *cmdq, u64 *req, size_t req_len,
                          struct erdma_comp_wait *comp_wait)
{
        __le64 *wqe;
        u64 hdr = *req;

        comp_wait->cmd_status = ERDMA_CMD_STATUS_ISSUED;
        reinit_completion(&comp_wait->wait_event);
        comp_wait->sq_pi = cmdq->sq.pi;

        wqe = get_queue_entry(cmdq->sq.qbuf, cmdq->sq.pi, cmdq->sq.depth,
                              SQEBB_SHIFT);
        memcpy(wqe, req, req_len);

        cmdq->sq.pi += cmdq->sq.wqebb_cnt;
        hdr |= FIELD_PREP(ERDMA_CMD_HDR_WQEBB_INDEX_MASK, cmdq->sq.pi) |
               FIELD_PREP(ERDMA_CMD_HDR_CONTEXT_COOKIE_MASK,
                          comp_wait->ctx_id) |
               FIELD_PREP(ERDMA_CMD_HDR_WQEBB_CNT_MASK, cmdq->sq.wqebb_cnt - 1);
        *wqe = cpu_to_le64(hdr);

        kick_cmdq_db(cmdq);
}

static int erdma_poll_single_cmd_completion(struct erdma_cmdq *cmdq)
{
        struct erdma_comp_wait *comp_wait;
        u32 hdr0, sqe_idx;
        __be32 *cqe;
        u16 ctx_id;
        u64 *sqe;

        cqe = get_next_valid_cmdq_cqe(cmdq);
        if (!cqe)
                return -EAGAIN;

        cmdq->cq.ci++;

        dma_rmb();
        hdr0 = be32_to_cpu(*cqe);
        sqe_idx = be32_to_cpu(*(cqe + 1));

        sqe = get_queue_entry(cmdq->sq.qbuf, sqe_idx, cmdq->sq.depth,
                              SQEBB_SHIFT);
        ctx_id = FIELD_GET(ERDMA_CMD_HDR_CONTEXT_COOKIE_MASK, *sqe);
        comp_wait = &cmdq->wait_pool[ctx_id];
        if (comp_wait->cmd_status != ERDMA_CMD_STATUS_ISSUED)
                return -EIO;

        comp_wait->cmd_status = ERDMA_CMD_STATUS_FINISHED;
        comp_wait->comp_status = FIELD_GET(ERDMA_CQE_HDR_SYNDROME_MASK, hdr0);
        cmdq->sq.ci += cmdq->sq.wqebb_cnt;
        /* Copy 16B comp data after cqe hdr to outer */
        be32_to_cpu_array(comp_wait->comp_data, cqe + 2, 4);

        complete(&comp_wait->wait_event);

        return 0;
}

static void erdma_polling_cmd_completions(struct erdma_cmdq *cmdq)
{
        unsigned long flags;
        u16 comp_num;

        spin_lock_irqsave(&cmdq->cq.lock, flags);

        /* We must have less than # of max_outstandings
         * completions at one time.
         */
        for (comp_num = 0; comp_num < cmdq->max_outstandings; comp_num++)
                if (erdma_poll_single_cmd_completion(cmdq))
                        break;

        spin_unlock_irqrestore(&cmdq->cq.lock, flags);
}

void erdma_cmdq_completion_handler(struct erdma_cmdq *cmdq)
{
        int got_event = 0;

        if (!test_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state))
                return;

        while (get_next_valid_eqe(&cmdq->eq)) {
                cmdq->eq.ci++;
                got_event++;
        }

        if (got_event) {
                cmdq->cq.cmdsn++;
                erdma_polling_cmd_completions(cmdq);
                arm_cmdq_cq(cmdq);
        }

        notify_eq(&cmdq->eq);
}

static int erdma_poll_cmd_completion(struct erdma_comp_wait *comp_ctx,
                                     struct erdma_cmdq *cmdq, u32 timeout)
{
        unsigned long comp_timeout = jiffies + msecs_to_jiffies(timeout);

        while (1) {
                erdma_polling_cmd_completions(cmdq);
                if (comp_ctx->cmd_status != ERDMA_CMD_STATUS_ISSUED)
                        break;

                if (time_is_before_jiffies(comp_timeout))
                        return -ETIME;

                udelay(20);
        }

        return 0;
}

static int erdma_wait_cmd_completion(struct erdma_comp_wait *comp_ctx,
                                     struct erdma_cmdq *cmdq, u32 timeout)
{
        unsigned long flags = 0;

        wait_for_completion_timeout(&comp_ctx->wait_event,
                                    msecs_to_jiffies(timeout));

        if (unlikely(comp_ctx->cmd_status != ERDMA_CMD_STATUS_FINISHED)) {
                spin_lock_irqsave(&cmdq->cq.lock, flags);
                comp_ctx->cmd_status = ERDMA_CMD_STATUS_TIMEOUT;
                spin_unlock_irqrestore(&cmdq->cq.lock, flags);
                return -ETIME;
        }

        return 0;
}

void erdma_cmdq_build_reqhdr(u64 *hdr, u32 mod, u32 op)
{
        *hdr = FIELD_PREP(ERDMA_CMD_HDR_SUB_MOD_MASK, mod) |
               FIELD_PREP(ERDMA_CMD_HDR_OPCODE_MASK, op);
}

int erdma_post_cmd_wait(struct erdma_cmdq *cmdq, void *req, u32 req_size,
                        u64 *resp0, u64 *resp1, bool sleepable)
{
        struct erdma_comp_wait *comp_wait;
        int ret;

        if (!test_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state))
                return -ENODEV;

        if (!sleepable) {
                while (down_trylock(&cmdq->credits))
                        ;
        } else {
                down(&cmdq->credits);
        }

        comp_wait = get_comp_wait(cmdq);
        if (IS_ERR(comp_wait)) {
                clear_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state);
                set_bit(ERDMA_CMDQ_STATE_CTX_ERR_BIT, &cmdq->state);
                up(&cmdq->credits);
                return PTR_ERR(comp_wait);
        }

        spin_lock(&cmdq->sq.lock);
        push_cmdq_sqe(cmdq, req, req_size, comp_wait);
        spin_unlock(&cmdq->sq.lock);

        if (sleepable)
                ret = erdma_wait_cmd_completion(comp_wait, cmdq,
                                                ERDMA_CMDQ_TIMEOUT_MS);
        else
                ret = erdma_poll_cmd_completion(comp_wait, cmdq,
                                                ERDMA_CMDQ_TIMEOUT_MS);

        if (ret) {
                set_bit(ERDMA_CMDQ_STATE_TIMEOUT_BIT, &cmdq->state);
                clear_bit(ERDMA_CMDQ_STATE_OK_BIT, &cmdq->state);
                goto out;
        }

        if (comp_wait->comp_status)
                ret = -EIO;

        if (resp0 && resp1) {
                *resp0 = *((u64 *)&comp_wait->comp_data[0]);
                *resp1 = *((u64 *)&comp_wait->comp_data[2]);
        }
        put_comp_wait(cmdq, comp_wait);

out:
        up(&cmdq->credits);

        return ret;
}