root/drivers/net/ethernet/huawei/hinic/hinic_hw_eqs.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/log2.h>
#include <asm/byteorder.h>
#include <asm/barrier.h>

#include "hinic_hw_dev.h"
#include "hinic_hw_csr.h"
#include "hinic_hw_if.h"
#include "hinic_hw_eqs.h"

#define HINIC_EQS_WQ_NAME                       "hinic_eqs"

#define GET_EQ_NUM_PAGES(eq, pg_size)           \
                (ALIGN((eq)->q_len * (eq)->elem_size, pg_size) / (pg_size))

#define GET_EQ_NUM_ELEMS_IN_PG(eq, pg_size)     ((pg_size) / (eq)->elem_size)

#define EQ_CONS_IDX_REG_ADDR(eq)        (((eq)->type == HINIC_AEQ) ? \
                        HINIC_CSR_AEQ_CONS_IDX_ADDR((eq)->q_id) : \
                        HINIC_CSR_CEQ_CONS_IDX_ADDR((eq)->q_id))

#define EQ_PROD_IDX_REG_ADDR(eq)        (((eq)->type == HINIC_AEQ) ? \
                        HINIC_CSR_AEQ_PROD_IDX_ADDR((eq)->q_id) : \
                        HINIC_CSR_CEQ_PROD_IDX_ADDR((eq)->q_id))

#define EQ_HI_PHYS_ADDR_REG(eq, pg_num) (((eq)->type == HINIC_AEQ) ? \
                        HINIC_CSR_AEQ_HI_PHYS_ADDR_REG((eq)->q_id, pg_num) : \
                        HINIC_CSR_CEQ_HI_PHYS_ADDR_REG((eq)->q_id, pg_num))

#define EQ_LO_PHYS_ADDR_REG(eq, pg_num) (((eq)->type == HINIC_AEQ) ? \
                        HINIC_CSR_AEQ_LO_PHYS_ADDR_REG((eq)->q_id, pg_num) : \
                        HINIC_CSR_CEQ_LO_PHYS_ADDR_REG((eq)->q_id, pg_num))

#define GET_EQ_ELEMENT(eq, idx)         \
                ((eq)->virt_addr[(idx) / (eq)->num_elem_in_pg] + \
                 (((idx) & ((eq)->num_elem_in_pg - 1)) * (eq)->elem_size))

#define GET_AEQ_ELEM(eq, idx)           ((struct hinic_aeq_elem *) \
                                        GET_EQ_ELEMENT(eq, idx))

#define GET_CEQ_ELEM(eq, idx)           ((u32 *) \
                                         GET_EQ_ELEMENT(eq, idx))

#define GET_CURR_AEQ_ELEM(eq)           GET_AEQ_ELEM(eq, (eq)->cons_idx)

#define GET_CURR_CEQ_ELEM(eq)           GET_CEQ_ELEM(eq, (eq)->cons_idx)

#define PAGE_IN_4K(page_size)           ((page_size) >> 12)
#define EQ_SET_HW_PAGE_SIZE_VAL(eq)     (ilog2(PAGE_IN_4K((eq)->page_size)))

#define ELEMENT_SIZE_IN_32B(eq)         (((eq)->elem_size) >> 5)
#define EQ_SET_HW_ELEM_SIZE_VAL(eq)     (ilog2(ELEMENT_SIZE_IN_32B(eq)))

#define EQ_MAX_PAGES                    8

#define CEQE_TYPE_SHIFT                 23
#define CEQE_TYPE_MASK                  0x7

#define CEQE_TYPE(ceqe)                 (((ceqe) >> CEQE_TYPE_SHIFT) &  \
                                         CEQE_TYPE_MASK)

#define CEQE_DATA_MASK                  0x3FFFFFF
#define CEQE_DATA(ceqe)                 ((ceqe) & CEQE_DATA_MASK)

#define aeq_to_aeqs(eq)                 \
                container_of((eq) - (eq)->q_id, struct hinic_aeqs, aeq[0])

#define ceq_to_ceqs(eq)                 \
                container_of((eq) - (eq)->q_id, struct hinic_ceqs, ceq[0])

#define work_to_aeq_work(work)          \
                container_of(work, struct hinic_eq_work, work)

#define DMA_ATTR_AEQ_DEFAULT            0
#define DMA_ATTR_CEQ_DEFAULT            0

/* No coalescence */
#define THRESH_CEQ_DEFAULT              0

enum eq_int_mode {
        EQ_INT_MODE_ARMED,
        EQ_INT_MODE_ALWAYS
};

enum eq_arm_state {
        EQ_NOT_ARMED,
        EQ_ARMED
};

/**
 * hinic_aeq_register_hw_cb - register AEQ callback for specific event
 * @aeqs: pointer to Async eqs of the chip
 * @event: aeq event to register callback for it
 * @handle: private data will be used by the callback
 * @hwe_handler: callback function
 **/
void hinic_aeq_register_hw_cb(struct hinic_aeqs *aeqs,
                              enum hinic_aeq_type event, void *handle,
                              void (*hwe_handler)(void *handle, void *data,
                                                  u8 size))
{
        struct hinic_hw_event_cb *hwe_cb = &aeqs->hwe_cb[event];

        hwe_cb->hwe_handler = hwe_handler;
        hwe_cb->handle = handle;
        hwe_cb->hwe_state = HINIC_EQE_ENABLED;
}

/**
 * hinic_aeq_unregister_hw_cb - unregister the AEQ callback for specific event
 * @aeqs: pointer to Async eqs of the chip
 * @event: aeq event to unregister callback for it
 **/
void hinic_aeq_unregister_hw_cb(struct hinic_aeqs *aeqs,
                                enum hinic_aeq_type event)
{
        struct hinic_hw_event_cb *hwe_cb = &aeqs->hwe_cb[event];

        hwe_cb->hwe_state &= ~HINIC_EQE_ENABLED;

        while (hwe_cb->hwe_state & HINIC_EQE_RUNNING)
                schedule();

        hwe_cb->hwe_handler = NULL;
}

/**
 * hinic_ceq_register_cb - register CEQ callback for specific event
 * @ceqs: pointer to Completion eqs part of the chip
 * @event: ceq event to register callback for it
 * @handle: private data will be used by the callback
 * @handler: callback function
 **/
void hinic_ceq_register_cb(struct hinic_ceqs *ceqs,
                           enum hinic_ceq_type event, void *handle,
                           void (*handler)(void *handle, u32 ceqe_data))
{
        struct hinic_ceq_cb *ceq_cb = &ceqs->ceq_cb[event];

        ceq_cb->handler = handler;
        ceq_cb->handle = handle;
        ceq_cb->ceqe_state = HINIC_EQE_ENABLED;
}

/**
 * hinic_ceq_unregister_cb - unregister the CEQ callback for specific event
 * @ceqs: pointer to Completion eqs part of the chip
 * @event: ceq event to unregister callback for it
 **/
void hinic_ceq_unregister_cb(struct hinic_ceqs *ceqs,
                             enum hinic_ceq_type event)
{
        struct hinic_ceq_cb *ceq_cb = &ceqs->ceq_cb[event];

        ceq_cb->ceqe_state &= ~HINIC_EQE_ENABLED;

        while (ceq_cb->ceqe_state & HINIC_EQE_RUNNING)
                schedule();

        ceq_cb->handler = NULL;
}

static u8 eq_cons_idx_checksum_set(u32 val)
{
        u8 checksum = 0;
        int idx;

        for (idx = 0; idx < 32; idx += 4)
                checksum ^= ((val >> idx) & 0xF);

        return (checksum & 0xF);
}

/**
 * eq_update_ci - update the HW cons idx of event queue
 * @eq: the event queue to update the cons idx for
 * @arm_state: the arm bit value of eq's interrupt
 **/
static void eq_update_ci(struct hinic_eq *eq, u32 arm_state)
{
        u32 val, addr = EQ_CONS_IDX_REG_ADDR(eq);

        /* Read Modify Write */
        val = hinic_hwif_read_reg(eq->hwif, addr);

        val = HINIC_EQ_CI_CLEAR(val, IDX)       &
              HINIC_EQ_CI_CLEAR(val, WRAPPED)   &
              HINIC_EQ_CI_CLEAR(val, INT_ARMED) &
              HINIC_EQ_CI_CLEAR(val, XOR_CHKSUM);

        val |= HINIC_EQ_CI_SET(eq->cons_idx, IDX)    |
               HINIC_EQ_CI_SET(eq->wrapped, WRAPPED) |
               HINIC_EQ_CI_SET(arm_state, INT_ARMED);

        val |= HINIC_EQ_CI_SET(eq_cons_idx_checksum_set(val), XOR_CHKSUM);

        hinic_hwif_write_reg(eq->hwif, addr, val);
}

/**
 * aeq_irq_handler - handler for the AEQ event
 * @eq: the Async Event Queue that received the event
 **/
static void aeq_irq_handler(struct hinic_eq *eq)
{
        struct hinic_aeqs *aeqs = aeq_to_aeqs(eq);
        struct hinic_hwif *hwif = aeqs->hwif;
        struct pci_dev *pdev = hwif->pdev;
        struct hinic_aeq_elem *aeqe_curr;
        struct hinic_hw_event_cb *hwe_cb;
        enum hinic_aeq_type event;
        unsigned long eqe_state;
        u32 aeqe_desc;
        int i, size;

        for (i = 0; i < eq->q_len; i++) {
                aeqe_curr = GET_CURR_AEQ_ELEM(eq);

                /* Data in HW is in Big endian Format */
                aeqe_desc = be32_to_cpu(aeqe_curr->desc);

                /* HW toggles the wrapped bit, when it adds eq element */
                if (HINIC_EQ_ELEM_DESC_GET(aeqe_desc, WRAPPED) == eq->wrapped)
                        break;

                dma_rmb();

                event = HINIC_EQ_ELEM_DESC_GET(aeqe_desc, TYPE);
                if (event >= HINIC_MAX_AEQ_EVENTS) {
                        dev_err(&pdev->dev, "Unknown AEQ Event %d\n", event);
                        return;
                }

                if (!HINIC_EQ_ELEM_DESC_GET(aeqe_desc, SRC)) {
                        hwe_cb = &aeqs->hwe_cb[event];

                        size = HINIC_EQ_ELEM_DESC_GET(aeqe_desc, SIZE);

                        eqe_state = cmpxchg(&hwe_cb->hwe_state,
                                            HINIC_EQE_ENABLED,
                                            HINIC_EQE_ENABLED |
                                            HINIC_EQE_RUNNING);
                        if (eqe_state == HINIC_EQE_ENABLED &&
                            hwe_cb->hwe_handler)
                                hwe_cb->hwe_handler(hwe_cb->handle,
                                                    aeqe_curr->data, size);
                        else
                                dev_err(&pdev->dev, "Unhandled AEQ Event %d\n",
                                        event);

                        hwe_cb->hwe_state &= ~HINIC_EQE_RUNNING;
                }

                eq->cons_idx++;

                if (eq->cons_idx == eq->q_len) {
                        eq->cons_idx = 0;
                        eq->wrapped = !eq->wrapped;
                }
        }
}

/**
 * ceq_event_handler - handler for the ceq events
 * @ceqs: ceqs part of the chip
 * @ceqe: ceq element that describes the event
 **/
static void ceq_event_handler(struct hinic_ceqs *ceqs, u32 ceqe)
{
        struct hinic_hwif *hwif = ceqs->hwif;
        struct pci_dev *pdev = hwif->pdev;
        struct hinic_ceq_cb *ceq_cb;
        enum hinic_ceq_type event;
        unsigned long eqe_state;

        event = CEQE_TYPE(ceqe);
        if (event >= HINIC_MAX_CEQ_EVENTS) {
                dev_err(&pdev->dev, "Unknown CEQ event, event = %d\n", event);
                return;
        }

        ceq_cb = &ceqs->ceq_cb[event];

        eqe_state = cmpxchg(&ceq_cb->ceqe_state,
                            HINIC_EQE_ENABLED,
                            HINIC_EQE_ENABLED | HINIC_EQE_RUNNING);

        if (eqe_state == HINIC_EQE_ENABLED && ceq_cb->handler)
                ceq_cb->handler(ceq_cb->handle, CEQE_DATA(ceqe));
        else
                dev_err(&pdev->dev, "Unhandled CEQ Event %d\n", event);

        ceq_cb->ceqe_state &= ~HINIC_EQE_RUNNING;
}

/**
 * ceq_irq_handler - handler for the CEQ event
 * @eq: the Completion Event Queue that received the event
 **/
static void ceq_irq_handler(struct hinic_eq *eq)
{
        struct hinic_ceqs *ceqs = ceq_to_ceqs(eq);
        u32 ceqe;
        int i;

        for (i = 0; i < eq->q_len; i++) {
                ceqe = *(GET_CURR_CEQ_ELEM(eq));

                /* Data in HW is in Big endian Format */
                ceqe = be32_to_cpu(ceqe);

                /* HW toggles the wrapped bit, when it adds eq element event */
                if (HINIC_EQ_ELEM_DESC_GET(ceqe, WRAPPED) == eq->wrapped)
                        break;

                ceq_event_handler(ceqs, ceqe);

                eq->cons_idx++;

                if (eq->cons_idx == eq->q_len) {
                        eq->cons_idx = 0;
                        eq->wrapped = !eq->wrapped;
                }
        }
}

/**
 * eq_irq_handler - handler for the EQ event
 * @data: the Event Queue that received the event
 **/
static void eq_irq_handler(void *data)
{
        struct hinic_eq *eq = data;

        if (eq->type == HINIC_AEQ)
                aeq_irq_handler(eq);
        else if (eq->type == HINIC_CEQ)
                ceq_irq_handler(eq);

        eq_update_ci(eq, EQ_ARMED);
}

/**
 * eq_irq_work - the work of the EQ that received the event
 * @work: the work struct that is associated with the EQ
 **/
static void eq_irq_work(struct work_struct *work)
{
        struct hinic_eq_work *aeq_work = work_to_aeq_work(work);
        struct hinic_eq *aeq;

        aeq = aeq_work->data;
        eq_irq_handler(aeq);
}

/**
 * ceq_tasklet - the tasklet of the EQ that received the event
 * @t: the tasklet struct pointer
 **/
static void ceq_tasklet(struct tasklet_struct *t)
{
        struct hinic_eq *ceq = from_tasklet(ceq, t, ceq_tasklet);

        eq_irq_handler(ceq);
}

/**
 * aeq_interrupt - aeq interrupt handler
 * @irq: irq number
 * @data: the Async Event Queue that collected the event
 **/
static irqreturn_t aeq_interrupt(int irq, void *data)
{
        struct hinic_eq_work *aeq_work;
        struct hinic_eq *aeq = data;
        struct hinic_aeqs *aeqs;

        /* clear resend timer cnt register */
        hinic_msix_attr_cnt_clear(aeq->hwif, aeq->msix_entry.entry);

        aeq_work = &aeq->aeq_work;
        aeq_work->data = aeq;

        aeqs = aeq_to_aeqs(aeq);
        queue_work(aeqs->workq, &aeq_work->work);

        return IRQ_HANDLED;
}

/**
 * ceq_interrupt - ceq interrupt handler
 * @irq: irq number
 * @data: the Completion Event Queue that collected the event
 **/
static irqreturn_t ceq_interrupt(int irq, void *data)
{
        struct hinic_eq *ceq = data;

        /* clear resend timer cnt register */
        hinic_msix_attr_cnt_clear(ceq->hwif, ceq->msix_entry.entry);

        tasklet_schedule(&ceq->ceq_tasklet);

        return IRQ_HANDLED;
}

static u32 get_ctrl0_val(struct hinic_eq *eq, u32 addr)
{
        struct msix_entry *msix_entry = &eq->msix_entry;
        enum hinic_eq_type type = eq->type;
        u32 val, ctrl0;

        if (type == HINIC_AEQ) {
                /* RMW Ctrl0 */
                addr = HINIC_CSR_AEQ_CTRL_0_ADDR(eq->q_id);

                val = hinic_hwif_read_reg(eq->hwif, addr);

                val = HINIC_AEQ_CTRL_0_CLEAR(val, INT_IDX)      &
                      HINIC_AEQ_CTRL_0_CLEAR(val, DMA_ATTR)     &
                      HINIC_AEQ_CTRL_0_CLEAR(val, PCI_INTF_IDX) &
                      HINIC_AEQ_CTRL_0_CLEAR(val, INT_MODE);

                ctrl0 = HINIC_AEQ_CTRL_0_SET(msix_entry->entry, INT_IDX)     |
                        HINIC_AEQ_CTRL_0_SET(DMA_ATTR_AEQ_DEFAULT, DMA_ATTR) |
                        HINIC_AEQ_CTRL_0_SET(HINIC_HWIF_PCI_INTF(eq->hwif),
                                             PCI_INTF_IDX)                   |
                        HINIC_AEQ_CTRL_0_SET(EQ_INT_MODE_ARMED, INT_MODE);

                val |= ctrl0;
        } else {
                /* RMW Ctrl0 */
                addr = HINIC_CSR_CEQ_CTRL_0_ADDR(eq->q_id);

                val = hinic_hwif_read_reg(eq->hwif, addr);

                val = HINIC_CEQ_CTRL_0_CLEAR(val, INTR_IDX)     &
                      HINIC_CEQ_CTRL_0_CLEAR(val, DMA_ATTR)     &
                      HINIC_CEQ_CTRL_0_CLEAR(val, KICK_THRESH)  &
                      HINIC_CEQ_CTRL_0_CLEAR(val, PCI_INTF_IDX) &
                      HINIC_CEQ_CTRL_0_CLEAR(val, INTR_MODE);

                ctrl0 = HINIC_CEQ_CTRL_0_SET(msix_entry->entry, INTR_IDX)     |
                        HINIC_CEQ_CTRL_0_SET(DMA_ATTR_CEQ_DEFAULT, DMA_ATTR)  |
                        HINIC_CEQ_CTRL_0_SET(THRESH_CEQ_DEFAULT, KICK_THRESH) |
                        HINIC_CEQ_CTRL_0_SET(HINIC_HWIF_PCI_INTF(eq->hwif),
                                             PCI_INTF_IDX)                    |
                        HINIC_CEQ_CTRL_0_SET(EQ_INT_MODE_ARMED, INTR_MODE);

                val |= ctrl0;
        }
        return val;
}

static void set_ctrl0(struct hinic_eq *eq)
{
        u32 val, addr;

        if (eq->type == HINIC_AEQ)
                addr = HINIC_CSR_AEQ_CTRL_0_ADDR(eq->q_id);
        else
                addr = HINIC_CSR_CEQ_CTRL_0_ADDR(eq->q_id);

        val = get_ctrl0_val(eq, addr);

        hinic_hwif_write_reg(eq->hwif, addr, val);
}

static u32 get_ctrl1_val(struct hinic_eq *eq, u32 addr)
{
        u32 page_size_val, elem_size, val, ctrl1;
        enum hinic_eq_type type = eq->type;

        if (type == HINIC_AEQ) {
                /* RMW Ctrl1 */
                addr = HINIC_CSR_AEQ_CTRL_1_ADDR(eq->q_id);

                page_size_val = EQ_SET_HW_PAGE_SIZE_VAL(eq);
                elem_size = EQ_SET_HW_ELEM_SIZE_VAL(eq);

                val = hinic_hwif_read_reg(eq->hwif, addr);

                val = HINIC_AEQ_CTRL_1_CLEAR(val, LEN)          &
                      HINIC_AEQ_CTRL_1_CLEAR(val, ELEM_SIZE)    &
                      HINIC_AEQ_CTRL_1_CLEAR(val, PAGE_SIZE);

                ctrl1 = HINIC_AEQ_CTRL_1_SET(eq->q_len, LEN)            |
                        HINIC_AEQ_CTRL_1_SET(elem_size, ELEM_SIZE)      |
                        HINIC_AEQ_CTRL_1_SET(page_size_val, PAGE_SIZE);

                val |= ctrl1;
        } else {
                /* RMW Ctrl1 */
                addr = HINIC_CSR_CEQ_CTRL_1_ADDR(eq->q_id);

                page_size_val = EQ_SET_HW_PAGE_SIZE_VAL(eq);

                val = hinic_hwif_read_reg(eq->hwif, addr);

                val = HINIC_CEQ_CTRL_1_CLEAR(val, LEN) &
                      HINIC_CEQ_CTRL_1_CLEAR(val, PAGE_SIZE);

                ctrl1 = HINIC_CEQ_CTRL_1_SET(eq->q_len, LEN) |
                        HINIC_CEQ_CTRL_1_SET(page_size_val, PAGE_SIZE);

                val |= ctrl1;
        }
        return val;
}

static void set_ctrl1(struct hinic_eq *eq)
{
        u32 addr, val;

        if (eq->type == HINIC_AEQ)
                addr = HINIC_CSR_AEQ_CTRL_1_ADDR(eq->q_id);
        else
                addr = HINIC_CSR_CEQ_CTRL_1_ADDR(eq->q_id);

        val = get_ctrl1_val(eq, addr);

        hinic_hwif_write_reg(eq->hwif, addr, val);
}

static int set_ceq_ctrl_reg(struct hinic_eq *eq)
{
        struct hinic_ceq_ctrl_reg ceq_ctrl = {0};
        struct hinic_hwdev *hwdev = eq->hwdev;
        u16 out_size = sizeof(ceq_ctrl);
        u16 in_size = sizeof(ceq_ctrl);
        struct hinic_pfhwdev *pfhwdev;
        u32 addr;
        int err;

        pfhwdev = container_of(hwdev, struct hinic_pfhwdev, hwdev);

        addr = HINIC_CSR_CEQ_CTRL_0_ADDR(eq->q_id);
        ceq_ctrl.ctrl0 = get_ctrl0_val(eq, addr);
        addr = HINIC_CSR_CEQ_CTRL_1_ADDR(eq->q_id);
        ceq_ctrl.ctrl1 = get_ctrl1_val(eq, addr);

        ceq_ctrl.func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        ceq_ctrl.q_id = eq->q_id;

        err = hinic_msg_to_mgmt(&pfhwdev->pf_to_mgmt, HINIC_MOD_COMM,
                                HINIC_COMM_CMD_CEQ_CTRL_REG_WR_BY_UP,
                                &ceq_ctrl, in_size,
                                &ceq_ctrl, &out_size, HINIC_MGMT_MSG_SYNC);
        if (err || !out_size || ceq_ctrl.status) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to set ceq %d ctrl reg, err: %d status: 0x%x, out_size: 0x%x\n",
                        eq->q_id, err, ceq_ctrl.status, out_size);
                return -EFAULT;
        }

        return 0;
}

/**
 * set_eq_ctrls - setting eq's ctrl registers
 * @eq: the Event Queue for setting
 **/
static int set_eq_ctrls(struct hinic_eq *eq)
{
        if (HINIC_IS_VF(eq->hwif) && eq->type == HINIC_CEQ)
                return set_ceq_ctrl_reg(eq);

        set_ctrl0(eq);
        set_ctrl1(eq);
        return 0;
}

/**
 * aeq_elements_init - initialize all the elements in the aeq
 * @eq: the Async Event Queue
 * @init_val: value to initialize the elements with it
 **/
static void aeq_elements_init(struct hinic_eq *eq, u32 init_val)
{
        struct hinic_aeq_elem *aeqe;
        int i;

        for (i = 0; i < eq->q_len; i++) {
                aeqe = GET_AEQ_ELEM(eq, i);
                aeqe->desc = cpu_to_be32(init_val);
        }

        wmb();  /* Write the initilzation values */
}

/**
 * ceq_elements_init - Initialize all the elements in the ceq
 * @eq: the event queue
 * @init_val: value to init the elements with
 **/
static void ceq_elements_init(struct hinic_eq *eq, u32 init_val)
{
        u32 *ceqe;
        int i;

        for (i = 0; i < eq->q_len; i++) {
                ceqe = GET_CEQ_ELEM(eq, i);
                *(ceqe) = cpu_to_be32(init_val);
        }

        wmb();  /* Write the initilzation values */
}

/**
 * alloc_eq_pages - allocate the pages for the queue
 * @eq: the event queue
 *
 * Return 0 - Success, Negative - Failure
 **/
static int alloc_eq_pages(struct hinic_eq *eq)
{
        struct hinic_hwif *hwif = eq->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u32 init_val, addr, val;
        int err, pg;

        eq->dma_addr = devm_kcalloc(&pdev->dev, eq->num_pages,
                                    sizeof(*eq->dma_addr), GFP_KERNEL);
        if (!eq->dma_addr)
                return -ENOMEM;

        eq->virt_addr = devm_kcalloc(&pdev->dev, eq->num_pages,
                                     sizeof(*eq->virt_addr), GFP_KERNEL);
        if (!eq->virt_addr) {
                err = -ENOMEM;
                goto err_virt_addr_alloc;
        }

        for (pg = 0; pg < eq->num_pages; pg++) {
                eq->virt_addr[pg] = dma_alloc_coherent(&pdev->dev,
                                                       eq->page_size,
                                                       &eq->dma_addr[pg],
                                                       GFP_KERNEL);
                if (!eq->virt_addr[pg]) {
                        err = -ENOMEM;
                        goto err_dma_alloc;
                }

                addr = EQ_HI_PHYS_ADDR_REG(eq, pg);
                val = upper_32_bits(eq->dma_addr[pg]);

                hinic_hwif_write_reg(hwif, addr, val);

                addr = EQ_LO_PHYS_ADDR_REG(eq, pg);
                val = lower_32_bits(eq->dma_addr[pg]);

                hinic_hwif_write_reg(hwif, addr, val);
        }

        init_val = HINIC_EQ_ELEM_DESC_SET(eq->wrapped, WRAPPED);

        if (eq->type == HINIC_AEQ)
                aeq_elements_init(eq, init_val);
        else if (eq->type == HINIC_CEQ)
                ceq_elements_init(eq, init_val);

        return 0;

err_dma_alloc:
        while (--pg >= 0)
                dma_free_coherent(&pdev->dev, eq->page_size,
                                  eq->virt_addr[pg],
                                  eq->dma_addr[pg]);

        devm_kfree(&pdev->dev, eq->virt_addr);

err_virt_addr_alloc:
        devm_kfree(&pdev->dev, eq->dma_addr);
        return err;
}

/**
 * free_eq_pages - free the pages of the queue
 * @eq: the Event Queue
 **/
static void free_eq_pages(struct hinic_eq *eq)
{
        struct hinic_hwif *hwif = eq->hwif;
        struct pci_dev *pdev = hwif->pdev;
        int pg;

        for (pg = 0; pg < eq->num_pages; pg++)
                dma_free_coherent(&pdev->dev, eq->page_size,
                                  eq->virt_addr[pg],
                                  eq->dma_addr[pg]);

        devm_kfree(&pdev->dev, eq->virt_addr);
        devm_kfree(&pdev->dev, eq->dma_addr);
}

/**
 * init_eq - initialize Event Queue
 * @eq: the event queue
 * @hwif: the HW interface of a PCI function device
 * @type: the type of the event queue, aeq or ceq
 * @q_id: Queue id number
 * @q_len: the number of EQ elements
 * @page_size: the page size of the pages in the event queue
 * @entry: msix entry associated with the event queue
 *
 * Return 0 - Success, Negative - Failure
 **/
static int init_eq(struct hinic_eq *eq, struct hinic_hwif *hwif,
                   enum hinic_eq_type type, int q_id, u32 q_len, u32 page_size,
                   struct msix_entry entry)
{
        struct pci_dev *pdev = hwif->pdev;
        int err;

        eq->hwif = hwif;
        eq->type = type;
        eq->q_id = q_id;
        eq->q_len = q_len;
        eq->page_size = page_size;

        /* Clear PI and CI, also clear the ARM bit */
        hinic_hwif_write_reg(eq->hwif, EQ_CONS_IDX_REG_ADDR(eq), 0);
        hinic_hwif_write_reg(eq->hwif, EQ_PROD_IDX_REG_ADDR(eq), 0);

        eq->cons_idx = 0;
        eq->wrapped = 0;

        if (type == HINIC_AEQ) {
                eq->elem_size = HINIC_AEQE_SIZE;
        } else if (type == HINIC_CEQ) {
                eq->elem_size = HINIC_CEQE_SIZE;
        } else {
                dev_err(&pdev->dev, "Invalid EQ type\n");
                return -EINVAL;
        }

        eq->num_pages = GET_EQ_NUM_PAGES(eq, page_size);
        eq->num_elem_in_pg = GET_EQ_NUM_ELEMS_IN_PG(eq, page_size);

        eq->msix_entry = entry;

        if (eq->num_elem_in_pg & (eq->num_elem_in_pg - 1)) {
                dev_err(&pdev->dev, "num elements in eq page != power of 2\n");
                return -EINVAL;
        }

        if (eq->num_pages > EQ_MAX_PAGES) {
                dev_err(&pdev->dev, "too many pages for eq\n");
                return -EINVAL;
        }

        err = set_eq_ctrls(eq);
        if (err) {
                dev_err(&pdev->dev, "Failed to set eq ctrls\n");
                return err;
        }

        eq_update_ci(eq, EQ_ARMED);

        err = alloc_eq_pages(eq);
        if (err) {
                dev_err(&pdev->dev, "Failed to allocate pages for eq\n");
                return err;
        }

        if (type == HINIC_AEQ) {
                struct hinic_eq_work *aeq_work = &eq->aeq_work;

                INIT_WORK(&aeq_work->work, eq_irq_work);
        } else if (type == HINIC_CEQ) {
                tasklet_setup(&eq->ceq_tasklet, ceq_tasklet);
        }

        /* set the attributes of the msix entry */
        hinic_msix_attr_set(eq->hwif, eq->msix_entry.entry,
                            HINIC_EQ_MSIX_PENDING_LIMIT_DEFAULT,
                            HINIC_EQ_MSIX_COALESC_TIMER_DEFAULT,
                            HINIC_EQ_MSIX_LLI_TIMER_DEFAULT,
                            HINIC_EQ_MSIX_LLI_CREDIT_LIMIT_DEFAULT,
                            HINIC_EQ_MSIX_RESEND_TIMER_DEFAULT);

        if (type == HINIC_AEQ) {
                snprintf(eq->irq_name, sizeof(eq->irq_name), "hinic_aeq%d@pci:%s", eq->q_id,
                         pci_name(pdev));
                err = request_irq(entry.vector, aeq_interrupt, 0, eq->irq_name, eq);
        } else if (type == HINIC_CEQ) {
                snprintf(eq->irq_name, sizeof(eq->irq_name), "hinic_ceq%d@pci:%s", eq->q_id,
                         pci_name(pdev));
                err = request_irq(entry.vector, ceq_interrupt, 0, eq->irq_name, eq);
        }

        if (err) {
                dev_err(&pdev->dev, "Failed to request irq for the EQ\n");
                goto err_req_irq;
        }

        return 0;

err_req_irq:
        free_eq_pages(eq);
        return err;
}

/**
 * remove_eq - remove Event Queue
 * @eq: the event queue
 **/
static void remove_eq(struct hinic_eq *eq)
{
        hinic_set_msix_state(eq->hwif, eq->msix_entry.entry,
                             HINIC_MSIX_DISABLE);
        free_irq(eq->msix_entry.vector, eq);

        if (eq->type == HINIC_AEQ) {
                struct hinic_eq_work *aeq_work = &eq->aeq_work;

                cancel_work_sync(&aeq_work->work);
                /* clear aeq_len to avoid hw access host memory */
                hinic_hwif_write_reg(eq->hwif,
                                     HINIC_CSR_AEQ_CTRL_1_ADDR(eq->q_id), 0);
        } else if (eq->type == HINIC_CEQ) {
                tasklet_kill(&eq->ceq_tasklet);
                /* clear ceq_len to avoid hw access host memory */
                hinic_hwif_write_reg(eq->hwif,
                                     HINIC_CSR_CEQ_CTRL_1_ADDR(eq->q_id), 0);
        }

        /* update cons_idx to avoid invalid interrupt */
        eq->cons_idx = hinic_hwif_read_reg(eq->hwif, EQ_PROD_IDX_REG_ADDR(eq));
        eq_update_ci(eq, EQ_NOT_ARMED);

        free_eq_pages(eq);
}

/**
 * hinic_aeqs_init - initialize all the aeqs
 * @aeqs: pointer to Async eqs of the chip
 * @hwif: the HW interface of a PCI function device
 * @num_aeqs: number of AEQs
 * @q_len: number of EQ elements
 * @page_size: the page size of the pages in the event queue
 * @msix_entries: msix entries associated with the event queues
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_aeqs_init(struct hinic_aeqs *aeqs, struct hinic_hwif *hwif,
                    int num_aeqs, u32 q_len, u32 page_size,
                    struct msix_entry *msix_entries)
{
        struct pci_dev *pdev = hwif->pdev;
        int err, i, q_id;

        aeqs->workq = create_singlethread_workqueue(HINIC_EQS_WQ_NAME);
        if (!aeqs->workq)
                return -ENOMEM;

        aeqs->hwif = hwif;
        aeqs->num_aeqs = num_aeqs;

        for (q_id = 0; q_id < num_aeqs; q_id++) {
                err = init_eq(&aeqs->aeq[q_id], hwif, HINIC_AEQ, q_id, q_len,
                              page_size, msix_entries[q_id]);
                if (err) {
                        dev_err(&pdev->dev, "Failed to init aeq %d\n", q_id);
                        goto err_init_aeq;
                }
        }

        return 0;

err_init_aeq:
        for (i = 0; i < q_id; i++)
                remove_eq(&aeqs->aeq[i]);

        destroy_workqueue(aeqs->workq);
        return err;
}

/**
 * hinic_aeqs_free - free all the aeqs
 * @aeqs: pointer to Async eqs of the chip
 **/
void hinic_aeqs_free(struct hinic_aeqs *aeqs)
{
        int q_id;

        for (q_id = 0; q_id < aeqs->num_aeqs ; q_id++)
                remove_eq(&aeqs->aeq[q_id]);

        destroy_workqueue(aeqs->workq);
}

/**
 * hinic_ceqs_init - init all the ceqs
 * @ceqs: ceqs part of the chip
 * @hwif: the hardware interface of a pci function device
 * @num_ceqs: number of CEQs
 * @q_len: number of EQ elements
 * @page_size: the page size of the event queue
 * @msix_entries: msix entries associated with the event queues
 *
 * Return 0 - Success, Negative - Failure
 **/
int hinic_ceqs_init(struct hinic_ceqs *ceqs, struct hinic_hwif *hwif,
                    int num_ceqs, u32 q_len, u32 page_size,
                    struct msix_entry *msix_entries)
{
        struct pci_dev *pdev = hwif->pdev;
        int i, q_id, err;

        ceqs->hwif = hwif;
        ceqs->num_ceqs = num_ceqs;

        for (q_id = 0; q_id < num_ceqs; q_id++) {
                ceqs->ceq[q_id].hwdev = ceqs->hwdev;
                err = init_eq(&ceqs->ceq[q_id], hwif, HINIC_CEQ, q_id, q_len,
                              page_size, msix_entries[q_id]);
                if (err) {
                        dev_err(&pdev->dev, "Failed to init ceq %d\n", q_id);
                        goto err_init_ceq;
                }
        }

        return 0;

err_init_ceq:
        for (i = 0; i < q_id; i++)
                remove_eq(&ceqs->ceq[i]);

        return err;
}

/**
 * hinic_ceqs_free - free all the ceqs
 * @ceqs: ceqs part of the chip
 **/
void hinic_ceqs_free(struct hinic_ceqs *ceqs)
{
        int q_id;

        for (q_id = 0; q_id < ceqs->num_ceqs; q_id++)
                remove_eq(&ceqs->ceq[q_id]);
}

void hinic_dump_ceq_info(struct hinic_hwdev *hwdev)
{
        struct hinic_eq *eq = NULL;
        u32 addr, ci, pi;
        int q_id;

        for (q_id = 0; q_id < hwdev->func_to_io.ceqs.num_ceqs; q_id++) {
                eq = &hwdev->func_to_io.ceqs.ceq[q_id];
                addr = EQ_CONS_IDX_REG_ADDR(eq);
                ci = hinic_hwif_read_reg(hwdev->hwif, addr);
                addr = EQ_PROD_IDX_REG_ADDR(eq);
                pi = hinic_hwif_read_reg(hwdev->hwif, addr);
                dev_err(&hwdev->hwif->pdev->dev, "Ceq id: %d, ci: 0x%08x, sw_ci: 0x%08x, pi: 0x%x, tasklet_state: 0x%lx, wrap: %d, ceqe: 0x%x\n",
                        q_id, ci, eq->cons_idx, pi,
                        eq->ceq_tasklet.state,
                        eq->wrapped, be32_to_cpu(*(__be32 *)(GET_CURR_CEQ_ELEM(eq))));
        }
}

void hinic_dump_aeq_info(struct hinic_hwdev *hwdev)
{
        struct hinic_aeq_elem *aeqe_pos = NULL;
        struct hinic_eq *eq = NULL;
        u32 addr, ci, pi;
        int q_id;

        for (q_id = 0; q_id < hwdev->aeqs.num_aeqs; q_id++) {
                eq = &hwdev->aeqs.aeq[q_id];
                addr = EQ_CONS_IDX_REG_ADDR(eq);
                ci = hinic_hwif_read_reg(hwdev->hwif, addr);
                addr = EQ_PROD_IDX_REG_ADDR(eq);
                pi = hinic_hwif_read_reg(hwdev->hwif, addr);
                aeqe_pos = GET_CURR_AEQ_ELEM(eq);
                dev_err(&hwdev->hwif->pdev->dev, "Aeq id: %d, ci: 0x%08x, pi: 0x%x, work_state: 0x%x, wrap: %d, desc: 0x%x\n",
                        q_id, ci, pi, work_busy(&eq->aeq_work.work),
                        eq->wrapped, be32_to_cpu(aeqe_pos->desc));
        }
}