]> www.infradead.org Git - users/griffoul/linux.git/commitdiff
hinic3: Async Event Queue interfaces
authorFan Gong <gongfan1@huawei.com>
Wed, 20 Aug 2025 09:31:18 +0000 (17:31 +0800)
committerJakub Kicinski <kuba@kernel.org>
Sat, 23 Aug 2025 00:05:06 +0000 (17:05 -0700)
Add async event queue interfaces initialization.
It allows driver to handle async events reported by HW.

Co-developed-by: Xin Guo <guoxin09@huawei.com>
Signed-off-by: Xin Guo <guoxin09@huawei.com>
Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
Link: https://patch.msgid.link/553ebd562b61cd854a2beb25c3d4d98ad3073db0.1755673097.git.zhuyikai1@h-partners.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/huawei/hinic3/Makefile
drivers/net/ethernet/huawei/hinic3/hinic3_common.c
drivers/net/ethernet/huawei/hinic3/hinic3_common.h
drivers/net/ethernet/huawei/hinic3/hinic3_csr.h [new file with mode: 0644]
drivers/net/ethernet/huawei/hinic3/hinic3_eqs.c [new file with mode: 0644]
drivers/net/ethernet/huawei/hinic3/hinic3_eqs.h [new file with mode: 0644]
drivers/net/ethernet/huawei/hinic3/hinic3_hw_cfg.c
drivers/net/ethernet/huawei/hinic3/hinic3_hwif.c
drivers/net/ethernet/huawei/hinic3/hinic3_hwif.h

index 509dfbfb0e96ef3aeb6ca2dd79ad41f4da3f335c..5fb4d13700497ef44a6c6a1fd58a4dc0aced9c52 100644 (file)
@@ -4,6 +4,7 @@
 obj-$(CONFIG_HINIC3) += hinic3.o
 
 hinic3-objs := hinic3_common.o \
+              hinic3_eqs.o \
               hinic3_hw_cfg.o \
               hinic3_hw_comm.o \
               hinic3_hwdev.o \
index 0aa42068728cdb1918294c70f4596028f49f4ce5..b8d29e26e2da63e080c093ffd553d9d385a9af29 100644 (file)
@@ -51,3 +51,12 @@ void hinic3_dma_free_coherent_align(struct device *dev,
        dma_free_coherent(dev, mem_align->real_size,
                          mem_align->ori_vaddr, mem_align->ori_paddr);
 }
+
+/* Data provided to/by cmdq is arranged in structs with little endian fields but
+ * every dword (32bits) should be swapped since HW swaps it again when it
+ * copies it from/to host memory.
+ */
+void hinic3_cmdq_buf_swab32(void *data, int len)
+{
+       swab32_array(data, len / sizeof(u32));
+}
index bb795dace04c2f2a0f7f9b9b878af3448ec0705a..c8e8a491adbf1918ee9a7030dec10900e437a9f8 100644 (file)
@@ -24,4 +24,6 @@ int hinic3_dma_zalloc_coherent_align(struct device *dev, u32 size, u32 align,
 void hinic3_dma_free_coherent_align(struct device *dev,
                                    struct hinic3_dma_addr_align *mem_align);
 
+void hinic3_cmdq_buf_swab32(void *data, int len);
+
 #endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_csr.h b/drivers/net/ethernet/huawei/hinic3/hinic3_csr.h
new file mode 100644 (file)
index 0000000..39e15fb
--- /dev/null
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
+
+#ifndef _HINIC3_CSR_H_
+#define _HINIC3_CSR_H_
+
+#define HINIC3_CFG_REGS_FLAG                  0x40000000
+#define HINIC3_REGS_FLAG_MASK                 0x3FFFFFFF
+
+#define HINIC3_VF_CFG_REG_OFFSET              0x2000
+
+/* HW interface registers */
+#define HINIC3_CSR_FUNC_ATTR0_ADDR            (HINIC3_CFG_REGS_FLAG + 0x0)
+#define HINIC3_CSR_FUNC_ATTR1_ADDR            (HINIC3_CFG_REGS_FLAG + 0x4)
+#define HINIC3_CSR_FUNC_ATTR2_ADDR            (HINIC3_CFG_REGS_FLAG + 0x8)
+#define HINIC3_CSR_FUNC_ATTR3_ADDR            (HINIC3_CFG_REGS_FLAG + 0xC)
+#define HINIC3_CSR_FUNC_ATTR4_ADDR            (HINIC3_CFG_REGS_FLAG + 0x10)
+#define HINIC3_CSR_FUNC_ATTR5_ADDR            (HINIC3_CFG_REGS_FLAG + 0x14)
+#define HINIC3_CSR_FUNC_ATTR6_ADDR            (HINIC3_CFG_REGS_FLAG + 0x18)
+
+#define HINIC3_FUNC_CSR_MAILBOX_DATA_OFF      0x80
+#define HINIC3_FUNC_CSR_MAILBOX_CONTROL_OFF   (HINIC3_CFG_REGS_FLAG + 0x0100)
+#define HINIC3_FUNC_CSR_MAILBOX_INT_OFF       (HINIC3_CFG_REGS_FLAG + 0x0104)
+#define HINIC3_FUNC_CSR_MAILBOX_RESULT_H_OFF  (HINIC3_CFG_REGS_FLAG + 0x0108)
+#define HINIC3_FUNC_CSR_MAILBOX_RESULT_L_OFF  (HINIC3_CFG_REGS_FLAG + 0x010C)
+
+#define HINIC3_CSR_DMA_ATTR_TBL_ADDR          (HINIC3_CFG_REGS_FLAG + 0x380)
+#define HINIC3_CSR_DMA_ATTR_INDIR_IDX_ADDR    (HINIC3_CFG_REGS_FLAG + 0x390)
+
+/* MSI-X registers */
+#define HINIC3_CSR_FUNC_MSI_CLR_WR_ADDR       (HINIC3_CFG_REGS_FLAG + 0x58)
+
+#define HINIC3_MSI_CLR_INDIR_RESEND_TIMER_CLR_MASK  BIT(0)
+#define HINIC3_MSI_CLR_INDIR_INT_MSK_SET_MASK       BIT(1)
+#define HINIC3_MSI_CLR_INDIR_INT_MSK_CLR_MASK       BIT(2)
+#define HINIC3_MSI_CLR_INDIR_AUTO_MSK_SET_MASK      BIT(3)
+#define HINIC3_MSI_CLR_INDIR_AUTO_MSK_CLR_MASK      BIT(4)
+#define HINIC3_MSI_CLR_INDIR_SIMPLE_INDIR_IDX_MASK  GENMASK(31, 22)
+#define HINIC3_MSI_CLR_INDIR_SET(val, member)  \
+       FIELD_PREP(HINIC3_MSI_CLR_INDIR_##member##_MASK, val)
+
+/* EQ registers */
+#define HINIC3_AEQ_INDIR_IDX_ADDR      (HINIC3_CFG_REGS_FLAG + 0x210)
+
+#define HINIC3_EQ_INDIR_IDX_ADDR(type)  \
+       HINIC3_AEQ_INDIR_IDX_ADDR
+
+#define HINIC3_AEQ_MTT_OFF_BASE_ADDR   (HINIC3_CFG_REGS_FLAG + 0x240)
+
+#define HINIC3_CSR_EQ_PAGE_OFF_STRIDE  8
+
+#define HINIC3_AEQ_HI_PHYS_ADDR_REG(pg_num)  \
+       (HINIC3_AEQ_MTT_OFF_BASE_ADDR + (pg_num) *  \
+        HINIC3_CSR_EQ_PAGE_OFF_STRIDE)
+
+#define HINIC3_AEQ_LO_PHYS_ADDR_REG(pg_num)  \
+       (HINIC3_AEQ_MTT_OFF_BASE_ADDR + (pg_num) *  \
+        HINIC3_CSR_EQ_PAGE_OFF_STRIDE + 4)
+
+#define HINIC3_CSR_AEQ_CTRL_0_ADDR           (HINIC3_CFG_REGS_FLAG + 0x200)
+#define HINIC3_CSR_AEQ_CTRL_1_ADDR           (HINIC3_CFG_REGS_FLAG + 0x204)
+#define HINIC3_CSR_AEQ_PROD_IDX_ADDR         (HINIC3_CFG_REGS_FLAG + 0x20C)
+#define HINIC3_CSR_AEQ_CI_SIMPLE_INDIR_ADDR  (HINIC3_CFG_REGS_FLAG + 0x50)
+
+#endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_eqs.c b/drivers/net/ethernet/huawei/hinic3/hinic3_eqs.c
new file mode 100644 (file)
index 0000000..15b1345
--- /dev/null
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+
+#include <linux/delay.h>
+
+#include "hinic3_csr.h"
+#include "hinic3_eqs.h"
+#include "hinic3_hwdev.h"
+#include "hinic3_hwif.h"
+#include "hinic3_mbox.h"
+
+#define AEQ_CTRL_0_INTR_IDX_MASK      GENMASK(9, 0)
+#define AEQ_CTRL_0_DMA_ATTR_MASK      GENMASK(17, 12)
+#define AEQ_CTRL_0_PCI_INTF_IDX_MASK  GENMASK(22, 20)
+#define AEQ_CTRL_0_INTR_MODE_MASK     BIT(31)
+#define AEQ_CTRL_0_SET(val, member)  \
+       FIELD_PREP(AEQ_CTRL_0_##member##_MASK, val)
+
+#define AEQ_CTRL_1_LEN_MASK           GENMASK(20, 0)
+#define AEQ_CTRL_1_ELEM_SIZE_MASK     GENMASK(25, 24)
+#define AEQ_CTRL_1_PAGE_SIZE_MASK     GENMASK(31, 28)
+#define AEQ_CTRL_1_SET(val, member)  \
+       FIELD_PREP(AEQ_CTRL_1_##member##_MASK, val)
+
+#define EQ_ELEM_DESC_TYPE_MASK        GENMASK(6, 0)
+#define EQ_ELEM_DESC_SRC_MASK         BIT(7)
+#define EQ_ELEM_DESC_SIZE_MASK        GENMASK(15, 8)
+#define EQ_ELEM_DESC_WRAPPED_MASK     BIT(31)
+#define EQ_ELEM_DESC_GET(val, member)  \
+       FIELD_GET(EQ_ELEM_DESC_##member##_MASK, le32_to_cpu(val))
+
+#define EQ_CI_SIMPLE_INDIR_CI_MASK       GENMASK(20, 0)
+#define EQ_CI_SIMPLE_INDIR_ARMED_MASK    BIT(21)
+#define EQ_CI_SIMPLE_INDIR_AEQ_IDX_MASK  GENMASK(31, 30)
+#define EQ_CI_SIMPLE_INDIR_SET(val, member)  \
+       FIELD_PREP(EQ_CI_SIMPLE_INDIR_##member##_MASK, val)
+
+#define EQ_CI_SIMPLE_INDIR_REG_ADDR  \
+       HINIC3_CSR_AEQ_CI_SIMPLE_INDIR_ADDR
+
+#define EQ_PROD_IDX_REG_ADDR  \
+       HINIC3_CSR_AEQ_PROD_IDX_ADDR
+
+#define EQ_HI_PHYS_ADDR_REG(type, pg_num)  \
+       HINIC3_AEQ_HI_PHYS_ADDR_REG(pg_num)
+
+#define EQ_LO_PHYS_ADDR_REG(type, pg_num)  \
+       HINIC3_AEQ_LO_PHYS_ADDR_REG(pg_num)
+
+#define EQ_MSIX_RESEND_TIMER_CLEAR  1
+
+#define HINIC3_EQ_MAX_PAGES  \
+       HINIC3_AEQ_MAX_PAGES
+
+#define HINIC3_TASK_PROCESS_EQE_LIMIT  1024
+#define HINIC3_EQ_UPDATE_CI_STEP       64
+#define HINIC3_EQS_WQ_NAME             "hinic3_eqs"
+
+#define HINIC3_EQ_VALID_SHIFT          31
+#define HINIC3_EQ_WRAPPED(eq)  \
+       ((eq)->wrapped << HINIC3_EQ_VALID_SHIFT)
+
+#define HINIC3_EQ_WRAPPED_SHIFT        20
+#define HINIC3_EQ_CONS_IDX(eq)  \
+       ((eq)->cons_idx | ((eq)->wrapped << HINIC3_EQ_WRAPPED_SHIFT))
+
+static const struct hinic3_aeq_elem *get_curr_aeq_elem(const struct hinic3_eq *eq)
+{
+       return get_q_element(&eq->qpages, eq->cons_idx, NULL);
+}
+
+int hinic3_aeq_register_cb(struct hinic3_hwdev *hwdev,
+                          enum hinic3_aeq_type event,
+                          hinic3_aeq_event_cb hwe_cb)
+{
+       struct hinic3_aeqs *aeqs;
+
+       aeqs = hwdev->aeqs;
+       aeqs->aeq_cb[event] = hwe_cb;
+       spin_lock_init(&aeqs->aeq_lock);
+
+       return 0;
+}
+
+void hinic3_aeq_unregister_cb(struct hinic3_hwdev *hwdev,
+                             enum hinic3_aeq_type event)
+{
+       struct hinic3_aeqs *aeqs;
+
+       aeqs = hwdev->aeqs;
+
+       spin_lock_bh(&aeqs->aeq_lock);
+       aeqs->aeq_cb[event] = NULL;
+       spin_unlock_bh(&aeqs->aeq_lock);
+}
+
+/* Set consumer index in the hw. */
+static void set_eq_cons_idx(struct hinic3_eq *eq, u32 arm_state)
+{
+       u32 addr = EQ_CI_SIMPLE_INDIR_REG_ADDR;
+       u32 eq_wrap_ci, val;
+
+       eq_wrap_ci = HINIC3_EQ_CONS_IDX(eq);
+       val = EQ_CI_SIMPLE_INDIR_SET(arm_state, ARMED) |
+               EQ_CI_SIMPLE_INDIR_SET(eq_wrap_ci, CI) |
+               EQ_CI_SIMPLE_INDIR_SET(eq->q_id, AEQ_IDX);
+
+       hinic3_hwif_write_reg(eq->hwdev->hwif, addr, val);
+}
+
+static struct hinic3_aeqs *aeq_to_aeqs(const struct hinic3_eq *eq)
+{
+       return container_of(eq, struct hinic3_aeqs, aeq[eq->q_id]);
+}
+
+static void aeq_event_handler(struct hinic3_aeqs *aeqs, __le32 aeqe,
+                             const struct hinic3_aeq_elem *aeqe_pos)
+{
+       struct hinic3_hwdev *hwdev = aeqs->hwdev;
+       u8 data[HINIC3_AEQE_DATA_SIZE], size;
+       enum hinic3_aeq_type event;
+       hinic3_aeq_event_cb hwe_cb;
+
+       if (EQ_ELEM_DESC_GET(aeqe, SRC))
+               return;
+
+       event = EQ_ELEM_DESC_GET(aeqe, TYPE);
+       if (event >= HINIC3_MAX_AEQ_EVENTS) {
+               dev_warn(hwdev->dev, "Aeq unknown event:%d\n", event);
+               return;
+       }
+
+       memcpy(data, aeqe_pos->aeqe_data, HINIC3_AEQE_DATA_SIZE);
+       swab32_array((u32 *)data, HINIC3_AEQE_DATA_SIZE / sizeof(u32));
+       size = EQ_ELEM_DESC_GET(aeqe, SIZE);
+
+       spin_lock_bh(&aeqs->aeq_lock);
+       hwe_cb = aeqs->aeq_cb[event];
+       if (hwe_cb)
+               hwe_cb(aeqs->hwdev, data, size);
+       spin_unlock_bh(&aeqs->aeq_lock);
+}
+
+static int aeq_irq_handler(struct hinic3_eq *eq)
+{
+       const struct hinic3_aeq_elem *aeqe_pos;
+       struct hinic3_aeqs *aeqs;
+       u32 i, eqe_cnt = 0;
+       __le32 aeqe;
+
+       aeqs = aeq_to_aeqs(eq);
+       for (i = 0; i < HINIC3_TASK_PROCESS_EQE_LIMIT; i++) {
+               aeqe_pos = get_curr_aeq_elem(eq);
+               aeqe = (__force __le32)swab32((__force __u32)aeqe_pos->desc);
+               /* HW updates wrapped bit, when it adds eq element event */
+               if (EQ_ELEM_DESC_GET(aeqe, WRAPPED) == eq->wrapped)
+                       return 0;
+
+               /* Prevent speculative reads from element */
+               dma_rmb();
+               aeq_event_handler(aeqs, aeqe, aeqe_pos);
+               eq->cons_idx++;
+               if (eq->cons_idx == eq->eq_len) {
+                       eq->cons_idx = 0;
+                       eq->wrapped = !eq->wrapped;
+               }
+
+               if (++eqe_cnt >= HINIC3_EQ_UPDATE_CI_STEP) {
+                       eqe_cnt = 0;
+                       set_eq_cons_idx(eq, HINIC3_EQ_NOT_ARMED);
+               }
+       }
+
+       return -EAGAIN;
+}
+
+static void reschedule_eq_handler(struct hinic3_eq *eq)
+{
+       struct hinic3_aeqs *aeqs = aeq_to_aeqs(eq);
+
+       queue_work(aeqs->workq, &eq->aeq_work);
+}
+
+static int eq_irq_handler(struct hinic3_eq *eq)
+{
+       int err;
+
+       err = aeq_irq_handler(eq);
+
+       set_eq_cons_idx(eq, err ? HINIC3_EQ_NOT_ARMED :
+                       HINIC3_EQ_ARMED);
+
+       return err;
+}
+
+static void eq_irq_work(struct work_struct *work)
+{
+       struct hinic3_eq *eq = container_of(work, struct hinic3_eq, aeq_work);
+       int err;
+
+       err = eq_irq_handler(eq);
+       if (err)
+               reschedule_eq_handler(eq);
+}
+
+static irqreturn_t aeq_interrupt(int irq, void *data)
+{
+       struct workqueue_struct *workq;
+       struct hinic3_eq *aeq = data;
+       struct hinic3_hwdev *hwdev;
+       struct hinic3_aeqs *aeqs;
+
+       aeqs = aeq_to_aeqs(aeq);
+       hwdev = aeq->hwdev;
+
+       /* clear resend timer cnt register */
+       workq = aeqs->workq;
+       hinic3_msix_intr_clear_resend_bit(hwdev, aeq->msix_entry_idx,
+                                         EQ_MSIX_RESEND_TIMER_CLEAR);
+       queue_work(workq, &aeq->aeq_work);
+
+       return IRQ_HANDLED;
+}
+
+static int set_eq_ctrls(struct hinic3_eq *eq)
+{
+       struct hinic3_hwif *hwif = eq->hwdev->hwif;
+       struct hinic3_queue_pages *qpages;
+       u8 pci_intf_idx, elem_size;
+       u32 mask, ctrl0, ctrl1;
+       u32 page_size_val;
+
+       qpages = &eq->qpages;
+       page_size_val = ilog2(qpages->page_size / HINIC3_MIN_PAGE_SIZE);
+       pci_intf_idx = hwif->attr.pci_intf_idx;
+
+       /* set ctrl0 using read-modify-write */
+       mask = AEQ_CTRL_0_INTR_IDX_MASK |
+              AEQ_CTRL_0_DMA_ATTR_MASK |
+              AEQ_CTRL_0_PCI_INTF_IDX_MASK |
+              AEQ_CTRL_0_INTR_MODE_MASK;
+       ctrl0 = hinic3_hwif_read_reg(hwif, HINIC3_CSR_AEQ_CTRL_0_ADDR);
+       ctrl0 = (ctrl0 & ~mask) |
+               AEQ_CTRL_0_SET(eq->msix_entry_idx, INTR_IDX) |
+               AEQ_CTRL_0_SET(0, DMA_ATTR) |
+               AEQ_CTRL_0_SET(pci_intf_idx, PCI_INTF_IDX) |
+               AEQ_CTRL_0_SET(HINIC3_INTR_MODE_ARMED, INTR_MODE);
+       hinic3_hwif_write_reg(hwif, HINIC3_CSR_AEQ_CTRL_0_ADDR, ctrl0);
+
+       /* HW expects log2(number of 32 byte units). */
+       elem_size = qpages->elem_size_shift - 5;
+       ctrl1 = AEQ_CTRL_1_SET(eq->eq_len, LEN) |
+               AEQ_CTRL_1_SET(elem_size, ELEM_SIZE) |
+               AEQ_CTRL_1_SET(page_size_val, PAGE_SIZE);
+       hinic3_hwif_write_reg(hwif, HINIC3_CSR_AEQ_CTRL_1_ADDR, ctrl1);
+
+       return 0;
+}
+
+static void aeq_elements_init(struct hinic3_eq *eq, u32 init_val)
+{
+       struct hinic3_aeq_elem *aeqe;
+       u32 i;
+
+       for (i = 0; i < eq->eq_len; i++) {
+               aeqe = get_q_element(&eq->qpages, i, NULL);
+               aeqe->desc = cpu_to_be32(init_val);
+       }
+
+       wmb();    /* Clear aeq elements bit */
+}
+
+static void eq_elements_init(struct hinic3_eq *eq, u32 init_val)
+{
+       aeq_elements_init(eq, init_val);
+}
+
+static int alloc_eq_pages(struct hinic3_eq *eq)
+{
+       struct hinic3_hwif *hwif = eq->hwdev->hwif;
+       struct hinic3_queue_pages *qpages;
+       dma_addr_t page_paddr;
+       u32 reg, init_val;
+       u16 pg_idx;
+       int err;
+
+       qpages = &eq->qpages;
+       err = hinic3_queue_pages_alloc(eq->hwdev, qpages, HINIC3_MIN_PAGE_SIZE);
+       if (err)
+               return err;
+
+       for (pg_idx = 0; pg_idx < qpages->num_pages; pg_idx++) {
+               page_paddr = qpages->pages[pg_idx].align_paddr;
+               reg = EQ_HI_PHYS_ADDR_REG(eq->type, pg_idx);
+               hinic3_hwif_write_reg(hwif, reg, upper_32_bits(page_paddr));
+               reg = EQ_LO_PHYS_ADDR_REG(eq->type, pg_idx);
+               hinic3_hwif_write_reg(hwif, reg, lower_32_bits(page_paddr));
+       }
+
+       init_val = HINIC3_EQ_WRAPPED(eq);
+       eq_elements_init(eq, init_val);
+
+       return 0;
+}
+
+static void eq_calc_page_size_and_num(struct hinic3_eq *eq, u32 elem_size)
+{
+       u32 max_pages, min_page_size, page_size, total_size;
+
+       /* No need for complicated arithmetic. All values must be power of 2.
+        * Multiplications give power of 2 and divisions give power of 2 without
+        * remainder.
+        */
+       max_pages = HINIC3_EQ_MAX_PAGES;
+       min_page_size = HINIC3_MIN_PAGE_SIZE;
+       total_size = eq->eq_len * elem_size;
+
+       if (total_size <= max_pages * min_page_size)
+               page_size = min_page_size;
+       else
+               page_size = total_size / max_pages;
+
+       hinic3_queue_pages_init(&eq->qpages, eq->eq_len, page_size, elem_size);
+}
+
+static int request_eq_irq(struct hinic3_eq *eq)
+{
+       INIT_WORK(&eq->aeq_work, eq_irq_work);
+       snprintf(eq->irq_name, sizeof(eq->irq_name),
+                "hinic3_aeq%u@pci:%s", eq->q_id,
+                pci_name(eq->hwdev->pdev));
+
+       return request_irq(eq->irq_id, aeq_interrupt, 0,
+                         eq->irq_name, eq);
+}
+
+static void reset_eq(struct hinic3_eq *eq)
+{
+       /* clear eq_len to force eqe drop in hardware */
+       hinic3_hwif_write_reg(eq->hwdev->hwif,
+                             HINIC3_CSR_AEQ_CTRL_1_ADDR, 0);
+
+       hinic3_hwif_write_reg(eq->hwdev->hwif, EQ_PROD_IDX_REG_ADDR, 0);
+}
+
+static int init_eq(struct hinic3_eq *eq, struct hinic3_hwdev *hwdev, u16 q_id,
+                  u32 q_len, enum hinic3_eq_type type,
+                  struct msix_entry *msix_entry)
+{
+       u32 elem_size;
+       int err;
+
+       eq->hwdev = hwdev;
+       eq->q_id = q_id;
+       eq->type = type;
+       eq->eq_len = q_len;
+
+       /* Indirect access should set q_id first */
+       hinic3_hwif_write_reg(hwdev->hwif, HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
+                             eq->q_id);
+
+       reset_eq(eq);
+
+       eq->cons_idx = 0;
+       eq->wrapped = 0;
+
+       elem_size = HINIC3_AEQE_SIZE;
+       eq_calc_page_size_and_num(eq, elem_size);
+
+       err = alloc_eq_pages(eq);
+       if (err) {
+               dev_err(hwdev->dev, "Failed to allocate pages for eq\n");
+               return err;
+       }
+
+       eq->msix_entry_idx = msix_entry->entry;
+       eq->irq_id = msix_entry->vector;
+
+       err = set_eq_ctrls(eq);
+       if (err) {
+               dev_err(hwdev->dev, "Failed to set ctrls for eq\n");
+               goto err_free_queue_pages;
+       }
+
+       set_eq_cons_idx(eq, HINIC3_EQ_ARMED);
+
+       err = request_eq_irq(eq);
+       if (err) {
+               dev_err(hwdev->dev,
+                       "Failed to request irq for the eq, err: %d\n", err);
+               goto err_free_queue_pages;
+       }
+
+       hinic3_set_msix_state(hwdev, eq->msix_entry_idx, HINIC3_MSIX_DISABLE);
+
+       return 0;
+
+err_free_queue_pages:
+       hinic3_queue_pages_free(hwdev, &eq->qpages);
+
+       return err;
+}
+
+static void remove_eq(struct hinic3_eq *eq)
+{
+       hinic3_set_msix_state(eq->hwdev, eq->msix_entry_idx,
+                             HINIC3_MSIX_DISABLE);
+       free_irq(eq->irq_id, eq);
+       /* Indirect access should set q_id first */
+       hinic3_hwif_write_reg(eq->hwdev->hwif,
+                             HINIC3_EQ_INDIR_IDX_ADDR(eq->type),
+                             eq->q_id);
+
+       disable_work_sync(&eq->aeq_work);
+       /* clear eq_len to avoid hw access host memory */
+       hinic3_hwif_write_reg(eq->hwdev->hwif,
+                             HINIC3_CSR_AEQ_CTRL_1_ADDR, 0);
+
+       /* update consumer index to avoid invalid interrupt */
+       eq->cons_idx = hinic3_hwif_read_reg(eq->hwdev->hwif,
+                                           EQ_PROD_IDX_REG_ADDR);
+       set_eq_cons_idx(eq, HINIC3_EQ_NOT_ARMED);
+       hinic3_queue_pages_free(eq->hwdev, &eq->qpages);
+}
+
+int hinic3_aeqs_init(struct hinic3_hwdev *hwdev, u16 num_aeqs,
+                    struct msix_entry *msix_entries)
+{
+       struct hinic3_aeqs *aeqs;
+       u16 q_id;
+       int err;
+
+       aeqs = kzalloc(sizeof(*aeqs), GFP_KERNEL);
+       if (!aeqs)
+               return -ENOMEM;
+
+       hwdev->aeqs = aeqs;
+       aeqs->hwdev = hwdev;
+       aeqs->num_aeqs = num_aeqs;
+       aeqs->workq = alloc_workqueue(HINIC3_EQS_WQ_NAME, WQ_MEM_RECLAIM,
+                                     HINIC3_MAX_AEQS);
+       if (!aeqs->workq) {
+               dev_err(hwdev->dev, "Failed to initialize aeq workqueue\n");
+               err = -ENOMEM;
+               goto err_free_aeqs;
+       }
+
+       for (q_id = 0; q_id < num_aeqs; q_id++) {
+               err = init_eq(&aeqs->aeq[q_id], hwdev, q_id,
+                             HINIC3_DEFAULT_AEQ_LEN, HINIC3_AEQ,
+                             &msix_entries[q_id]);
+               if (err) {
+                       dev_err(hwdev->dev, "Failed to init aeq %u\n",
+                               q_id);
+                       goto err_remove_eqs;
+               }
+       }
+       for (q_id = 0; q_id < num_aeqs; q_id++)
+               hinic3_set_msix_state(hwdev, aeqs->aeq[q_id].msix_entry_idx,
+                                     HINIC3_MSIX_ENABLE);
+
+       return 0;
+
+err_remove_eqs:
+       while (q_id > 0) {
+               q_id--;
+               remove_eq(&aeqs->aeq[q_id]);
+       }
+
+       destroy_workqueue(aeqs->workq);
+
+err_free_aeqs:
+       kfree(aeqs);
+
+       return err;
+}
+
+void hinic3_aeqs_free(struct hinic3_hwdev *hwdev)
+{
+       struct hinic3_aeqs *aeqs = hwdev->aeqs;
+       enum hinic3_aeq_type aeq_event;
+       struct hinic3_eq *eq;
+       u16 q_id;
+
+       for (q_id = 0; q_id < aeqs->num_aeqs; q_id++) {
+               eq = aeqs->aeq + q_id;
+               remove_eq(eq);
+               hinic3_free_irq(hwdev, eq->irq_id);
+       }
+
+       for (aeq_event = 0; aeq_event < HINIC3_MAX_AEQ_EVENTS; aeq_event++)
+               hinic3_aeq_unregister_cb(hwdev, aeq_event);
+
+       destroy_workqueue(aeqs->workq);
+
+       kfree(aeqs);
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_eqs.h b/drivers/net/ethernet/huawei/hinic3/hinic3_eqs.h
new file mode 100644 (file)
index 0000000..74f40da
--- /dev/null
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. */
+
+#ifndef _HINIC3_EQS_H_
+#define _HINIC3_EQS_H_
+
+#include <linux/interrupt.h>
+
+#include "hinic3_hw_cfg.h"
+#include "hinic3_queue_common.h"
+
+#define HINIC3_MAX_AEQS              4
+
+#define HINIC3_AEQ_MAX_PAGES         4
+
+#define HINIC3_AEQE_SIZE             64
+
+#define HINIC3_AEQE_DESC_SIZE        4
+#define HINIC3_AEQE_DATA_SIZE        (HINIC3_AEQE_SIZE - HINIC3_AEQE_DESC_SIZE)
+
+#define HINIC3_DEFAULT_AEQ_LEN       0x10000
+
+#define HINIC3_EQ_IRQ_NAME_LEN       64
+
+#define HINIC3_EQ_USLEEP_LOW_BOUND   900
+#define HINIC3_EQ_USLEEP_HIGH_BOUND  1000
+
+enum hinic3_eq_type {
+       HINIC3_AEQ = 0,
+};
+
+enum hinic3_eq_intr_mode {
+       HINIC3_INTR_MODE_ARMED  = 0,
+       HINIC3_INTR_MODE_ALWAYS = 1,
+};
+
+enum hinic3_eq_ci_arm_state {
+       HINIC3_EQ_NOT_ARMED = 0,
+       HINIC3_EQ_ARMED     = 1,
+};
+
+struct hinic3_eq {
+       struct hinic3_hwdev       *hwdev;
+       struct hinic3_queue_pages qpages;
+       u16                       q_id;
+       enum hinic3_eq_type       type;
+       u32                       eq_len;
+       u32                       cons_idx;
+       u8                        wrapped;
+       u32                       irq_id;
+       u16                       msix_entry_idx;
+       char                      irq_name[HINIC3_EQ_IRQ_NAME_LEN];
+       struct work_struct        aeq_work;
+};
+
+struct hinic3_aeq_elem {
+       u8     aeqe_data[HINIC3_AEQE_DATA_SIZE];
+       __be32 desc;
+};
+
+enum hinic3_aeq_type {
+       HINIC3_HW_INTER_INT   = 0,
+       HINIC3_MBX_FROM_FUNC  = 1,
+       HINIC3_MSG_FROM_FW    = 2,
+       HINIC3_MAX_AEQ_EVENTS = 6,
+};
+
+typedef void (*hinic3_aeq_event_cb)(struct hinic3_hwdev *hwdev, u8 *data,
+                                   u8 size);
+
+struct hinic3_aeqs {
+       struct hinic3_hwdev     *hwdev;
+       hinic3_aeq_event_cb     aeq_cb[HINIC3_MAX_AEQ_EVENTS];
+       struct hinic3_eq        aeq[HINIC3_MAX_AEQS];
+       u16                     num_aeqs;
+       struct workqueue_struct *workq;
+       /* lock for aeq event flag */
+       spinlock_t              aeq_lock;
+};
+
+int hinic3_aeqs_init(struct hinic3_hwdev *hwdev, u16 num_aeqs,
+                    struct msix_entry *msix_entries);
+void hinic3_aeqs_free(struct hinic3_hwdev *hwdev);
+int hinic3_aeq_register_cb(struct hinic3_hwdev *hwdev,
+                          enum hinic3_aeq_type event,
+                          hinic3_aeq_event_cb hwe_cb);
+void hinic3_aeq_unregister_cb(struct hinic3_hwdev *hwdev,
+                             enum hinic3_aeq_type event);
+
+#endif
index 87d9450c30cab0860dc97440e37a69134ece1e92..0599fc4f3fb0793da8271ab0576331b18514322a 100644 (file)
@@ -8,6 +8,49 @@
 #include "hinic3_hwif.h"
 #include "hinic3_mbox.h"
 
+int hinic3_alloc_irqs(struct hinic3_hwdev *hwdev, u16 num,
+                     struct msix_entry *alloc_arr, u16 *act_num)
+{
+       struct hinic3_irq_info *irq_info;
+       struct hinic3_irq *curr;
+       u16 i, found = 0;
+
+       irq_info = &hwdev->cfg_mgmt->irq_info;
+       mutex_lock(&irq_info->irq_mutex);
+       for (i = 0; i < irq_info->num_irq && found < num; i++) {
+               curr = irq_info->irq + i;
+               if (curr->allocated)
+                       continue;
+               curr->allocated = true;
+               alloc_arr[found].vector = curr->irq_id;
+               alloc_arr[found].entry = curr->msix_entry_idx;
+               found++;
+       }
+       mutex_unlock(&irq_info->irq_mutex);
+
+       *act_num = found;
+
+       return found == 0 ? -ENOMEM : 0;
+}
+
+void hinic3_free_irq(struct hinic3_hwdev *hwdev, u32 irq_id)
+{
+       struct hinic3_irq_info *irq_info;
+       struct hinic3_irq *curr;
+       u16 i;
+
+       irq_info = &hwdev->cfg_mgmt->irq_info;
+       mutex_lock(&irq_info->irq_mutex);
+       for (i = 0; i < irq_info->num_irq; i++) {
+               curr = irq_info->irq + i;
+               if (curr->irq_id == irq_id) {
+                       curr->allocated = false;
+                       break;
+               }
+       }
+       mutex_unlock(&irq_info->irq_mutex);
+}
+
 bool hinic3_support_nic(struct hinic3_hwdev *hwdev)
 {
        return hwdev->cfg_mgmt->cap.supp_svcs_bitmap &
index 0865453bf0e7c76ca2f870aa32369b661d0f9b92..27baa9693d20341dcbfadc4a68581774c6e34161 100644 (file)
@@ -6,15 +6,43 @@
 #include <linux/io.h>
 
 #include "hinic3_common.h"
+#include "hinic3_csr.h"
 #include "hinic3_hwdev.h"
 #include "hinic3_hwif.h"
 
+#define HINIC3_GET_REG_ADDR(reg)  ((reg) & (HINIC3_REGS_FLAG_MASK))
+
+static void __iomem *hinic3_reg_addr(struct hinic3_hwif *hwif, u32 reg)
+{
+       return hwif->cfg_regs_base + HINIC3_GET_REG_ADDR(reg);
+}
+
+u32 hinic3_hwif_read_reg(struct hinic3_hwif *hwif, u32 reg)
+{
+       void __iomem *addr = hinic3_reg_addr(hwif, reg);
+
+       return ioread32be(addr);
+}
+
+void hinic3_hwif_write_reg(struct hinic3_hwif *hwif, u32 reg, u32 val)
+{
+       void __iomem *addr = hinic3_reg_addr(hwif, reg);
+
+       iowrite32be(val, addr);
+}
+
 void hinic3_set_msix_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
                           enum hinic3_msix_state flag)
 {
        /* Completed by later submission due to LoC limit. */
 }
 
+void hinic3_msix_intr_clear_resend_bit(struct hinic3_hwdev *hwdev, u16 msix_idx,
+                                      u8 clear_resend_en)
+{
+       /* Completed by later submission due to LoC limit. */
+}
+
 u16 hinic3_global_func_id(struct hinic3_hwdev *hwdev)
 {
        return hwdev->hwif->attr.func_global_idx;
index 513c9680e6b61374eaaecd599439a73093114502..2e300fb0ba256e15533bb3f29bc4e0fe32c839eb 100644 (file)
@@ -50,8 +50,13 @@ enum hinic3_msix_state {
        HINIC3_MSIX_DISABLE,
 };
 
+u32 hinic3_hwif_read_reg(struct hinic3_hwif *hwif, u32 reg);
+void hinic3_hwif_write_reg(struct hinic3_hwif *hwif, u32 reg, u32 val);
+
 void hinic3_set_msix_state(struct hinic3_hwdev *hwdev, u16 msix_idx,
                           enum hinic3_msix_state flag);
+void hinic3_msix_intr_clear_resend_bit(struct hinic3_hwdev *hwdev, u16 msix_idx,
+                                      u8 clear_resend_en);
 
 u16 hinic3_global_func_id(struct hinic3_hwdev *hwdev);