| /* |
| * Huawei HiNIC PCI Express Linux driver |
| * Copyright(c) 2017 Huawei Technologies Co., Ltd |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| * |
| */ |
| |
| #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_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 |
| * @hw_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 |
| **/ |
| static void eq_update_ci(struct hinic_eq *eq) |
| { |
| 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(EQ_ARMED, 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; |
| |
| 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_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 |
| * @ceq_data: the eq |
| **/ |
| static void ceq_tasklet(unsigned long ceq_data) |
| { |
| struct hinic_eq *ceq = (struct hinic_eq *)ceq_data; |
| |
| 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 void set_ctrl0(struct hinic_eq *eq) |
| { |
| struct msix_entry *msix_entry = &eq->msix_entry; |
| enum hinic_eq_type type = eq->type; |
| u32 addr, 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; |
| |
| hinic_hwif_write_reg(eq->hwif, addr, val); |
| } else if (type == HINIC_CEQ) { |
| /* 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; |
| |
| hinic_hwif_write_reg(eq->hwif, addr, val); |
| } |
| } |
| |
| static void set_ctrl1(struct hinic_eq *eq) |
| { |
| enum hinic_eq_type type = eq->type; |
| u32 page_size_val, elem_size; |
| u32 addr, val, ctrl1; |
| |
| 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; |
| |
| hinic_hwif_write_reg(eq->hwif, addr, val); |
| } else if (type == HINIC_CEQ) { |
| /* 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; |
| |
| hinic_hwif_write_reg(eq->hwif, addr, val); |
| } |
| } |
| |
| /** |
| * set_eq_ctrls - setting eq's ctrl registers |
| * @eq: the Event Queue for setting |
| **/ |
| static void set_eq_ctrls(struct hinic_eq *eq) |
| { |
| set_ctrl0(eq); |
| set_ctrl1(eq); |
| } |
| |
| /** |
| * 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 with it the elements |
| **/ |
| 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; |
| size_t addr_size; |
| int err, pg; |
| |
| addr_size = eq->num_pages * sizeof(*eq->dma_addr); |
| eq->dma_addr = devm_kzalloc(&pdev->dev, addr_size, GFP_KERNEL); |
| if (!eq->dma_addr) |
| return -ENOMEM; |
| |
| addr_size = eq->num_pages * sizeof(*eq->virt_addr); |
| eq->virt_addr = devm_kzalloc(&pdev->dev, addr_size, 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_zalloc_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; |
| } |
| |
| set_eq_ctrls(eq); |
| eq_update_ci(eq); |
| |
| 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_init(&eq->ceq_tasklet, ceq_tasklet, |
| (unsigned long)eq); |
| } |
| |
| /* 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) |
| err = request_irq(entry.vector, aeq_interrupt, 0, |
| "hinic_aeq", eq); |
| else if (type == HINIC_CEQ) |
| err = request_irq(entry.vector, ceq_interrupt, 0, |
| "hinic_ceq", 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) |
| { |
| struct msix_entry *entry = &eq->msix_entry; |
| |
| free_irq(entry->vector, eq); |
| |
| if (eq->type == HINIC_AEQ) { |
| struct hinic_eq_work *aeq_work = &eq->aeq_work; |
| |
| cancel_work_sync(&aeq_work->work); |
| } else if (eq->type == HINIC_CEQ) { |
| tasklet_kill(&eq->ceq_tasklet); |
| } |
| |
| 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++) { |
| 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]); |
| } |