ubuntu-linux-kernel/drivers/net/ethernet/huawei/hinic/hinic_hw_eqs.c

887 lines
22 KiB
C

/*
* 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]);
}