/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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) HINIC_CSR_AEQ_CONS_IDX_ADDR((eq)->q_id) #define EQ_PROD_IDX_REG_ADDR(eq) HINIC_CSR_AEQ_PROD_IDX_ADDR((eq)->q_id) #define EQ_HI_PHYS_ADDR_REG(eq, pg_num) \ HINIC_CSR_AEQ_HI_PHYS_ADDR_REG((eq)->q_id, pg_num) #define EQ_LO_PHYS_ADDR_REG(eq, pg_num) \ HINIC_CSR_AEQ_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_CURR_AEQ_ELEM(eq) GET_AEQ_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 aeq_to_aeqs(eq) \ container_of((eq) - (eq)->q_id, struct hinic_aeqs, aeq[0]) #define work_to_aeq_work(work) \ container_of(work, struct hinic_eq_work, work) #define DMA_ATTR_AEQ_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; } 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; } } } /** * 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); 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); } /** * 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; } 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); } } 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); } } /** * 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 */ } /** * 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); 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 { 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); } /* 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); 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); } 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); }