// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
/* QLogic qed NIC Driver
* Copyright (c) 2015-2017 QLogic Corporation
* Copyright (c) 2019-2020 Marvell International Ltd.
*/
#include <linux/types.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/qed/qed_chain.h>
#include "qed.h"
#include "qed_hsi.h"
#include "qed_hw.h"
#include "qed_reg_addr.h"
#include "qed_sriov.h"
#define QED_BAR_ACQUIRE_TIMEOUT 1000
/* Invalid values */
#define QED_BAR_INVALID_OFFSET (cpu_to_le32(-1))
struct qed_ptt {
struct list_head list_entry;
unsigned int idx;
struct pxp_ptt_entry pxp;
u8 hwfn_id;
};
struct qed_ptt_pool {
struct list_head free_list;
spinlock_t lock; /* ptt synchronized access */
struct qed_ptt ptts[PXP_EXTERNAL_BAR_PF_WINDOW_NUM];
};
int qed_ptt_pool_alloc(struct qed_hwfn *p_hwfn)
{
struct qed_ptt_pool *p_pool = kmalloc(sizeof(*p_pool), GFP_KERNEL);
int i;
if (!p_pool)
return -ENOMEM;
INIT_LIST_HEAD(&p_pool->free_list);
for (i = 0; i < PXP_EXTERNAL_BAR_PF_WINDOW_NUM; i++) {
p_pool->ptts[i].idx = i;
p_pool->ptts[i].pxp.offset = QED_BAR_INVALID_OFFSET;
p_pool->ptts[i].pxp.pretend.control = 0;
p_pool->ptts[i].hwfn_id = p_hwfn->my_id;
if (i >= RESERVED_PTT_MAX)
list_add(&p_pool->ptts[i].list_entry,
&p_pool->free_list);
}
p_hwfn->p_ptt_pool = p_pool;
spin_lock_init(&p_pool->lock);
return 0;
}
void qed_ptt_invalidate(struct qed_hwfn *p_hwfn)
{
struct qed_ptt *p_ptt;
int i;
for (i = 0; i < PXP_EXTERNAL_BAR_PF_WINDOW_NUM; i++) {
p_ptt = &p_hwfn->p_ptt_pool->ptts[i];
p_ptt->pxp.offset = QED_BAR_INVALID_OFFSET;
}
}
void qed_ptt_pool_free(struct qed_hwfn *p_hwfn)
{
kfree(p_hwfn->p_ptt_pool);
p_hwfn->p_ptt_pool = NULL;
}
struct qed_ptt *qed_ptt_acquire(struct qed_hwfn *p_hwfn)
{
struct qed_ptt *p_ptt;
unsigned int i;
/* Take the free PTT from the list */
for (i = 0; i < QED_BAR_ACQUIRE_TIMEOUT; i++) {
spin_lock_bh(&p_hwfn->p_ptt_pool->lock);
if (!list_empty(&p_hwfn->p_ptt_pool->free_list)) {
p_ptt = list_first_entry(&p_hwfn->p_ptt_pool->free_list,
struct qed_ptt, list_entry);
list_del(&p_ptt->list_entry);
spin_unlock_bh(&p_hwfn->p_ptt_pool->lock);
DP_VERBOSE(p_hwfn, NETIF_MSG_HW,
"allocated ptt %d\n", p_ptt->idx);
return p_ptt;
}
spin_unlock_bh(&p_hwfn->p_ptt_pool->lock);
usleep_range(1000, 2000);
}
DP_NOTICE(p_hwfn, "PTT acquire timeout - failed to allocate PTT\n");
return NULL;
}
void qed_ptt_release(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
spin_lock_bh(&p_hwfn->p_ptt_pool->lock);
list_add(&p_ptt->list_entry, &p_hwfn->p_ptt_pool->free_list);
spin_unlock_bh(&p_hwfn->p_ptt_pool->lock);
}
u32 qed_ptt_get_hw_addr(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
/* The HW is using DWORDS and we need to translate it to Bytes */
return le32_to_cpu(p_ptt->pxp.offset) << 2;
}
static u32 qed_ptt_config_addr(struct qed_ptt *p_ptt)
{
return PXP_PF_WINDOW_ADMIN_PER_PF_START +
p_ptt->idx * sizeof(struct pxp_ptt_entry);
}
u32 qed_ptt_get_bar_addr(struct qed_ptt *p_ptt)
{
return PXP_EXTERNAL_BAR_PF_WINDOW_START +
p_ptt->idx * PXP_EXTERNAL_BAR_PF_WINDOW_SINGLE_SIZE;
}
void qed_ptt_set_win(struct qed_hwfn *p_hwfn,
struct qed_ptt *p_ptt, u32 new_hw_addr)
{
u32 prev_hw_addr;
prev_hw_addr = qed_ptt_get_hw_addr(p_hwfn, p_ptt);
if (new_hw_addr == prev_hw_addr)
return;
/* Update PTT entery in admin window */
DP_VERBOSE(p_hwfn, NETIF_MSG_HW,
"Updating PTT entry %d to offset 0x%x\n",
p_ptt->idx, new_hw_addr);
/* The HW is using DWORDS and the address is in Bytes */
p_ptt->pxp.offset = cpu_to_le32(new_hw_addr >> 2);
REG_WR(p_hwfn,
qed_ptt_config_addr(p_ptt) +
offsetof(struct pxp_ptt_entry, offset),
le32_to_cpu(p_ptt->pxp.offset));
}
static