blob: b064db11a6b9e21b3f9374232273326975f346fc [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2023 Mediatek Inc. All Rights Reserved.
*
* Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
*/
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/refcount.h>
#include <linux/spinlock.h>
#include "pce/dipfilter.h"
#include "pce/internal.h"
#include "pce/netsys.h"
struct dipfilter_entry {
struct dip_desc ddesc;
refcount_t refcnt;
u32 index;
};
struct dipfilter_hw {
struct dipfilter_entry dip_entries[FE_MEM_DIPFILTER_MAX_IDX];
DECLARE_BITMAP(dip_tbl, FE_MEM_DIPFILTER_MAX_IDX);
spinlock_t lock;
};
struct dipfilter_hw dip_hw;
int mtk_pce_dipfilter_enable(void)
{
mtk_pce_netsys_setbits(GLO_MEM_CFG, GDM_DIPFILTER_EN);
return 0;
}
void mtk_pce_dipfilter_disable(void)
{
mtk_pce_netsys_clrbits(GLO_MEM_CFG, GDM_DIPFILTER_EN);
}
/*
* find registered dipfilter info
* return index on matched dipfilter info
* return NULL on not found
*/
static struct dipfilter_entry *__mtk_pce_dip_info_find(struct dip_desc *target)
{
struct dip_desc *ddesc;
u32 idx = 0;
lockdep_assert_held(&dip_hw.lock);
/* can check this before acquiring lock */
if (!target
|| target->tag == DIPFILTER_DISABLED
|| target->tag >= __DIPFILTER_TAG_MAX) {
PCE_NOTICE("dip_desc is not enabled or invalid\n");
return NULL;
}
for_each_set_bit(idx, dip_hw.dip_tbl, FE_MEM_DIPFILTER_MAX_IDX) {
ddesc = &dip_hw.dip_entries[idx].ddesc;
if (target->tag != ddesc->tag)
continue;
if (ddesc->tag == DIPFILTER_IPV4
&& ddesc->ipv4 == target->ipv4) {
return &dip_hw.dip_entries[idx];
} else if (ddesc->tag == DIPFILTER_IPV6
&& !memcmp(ddesc->ipv6, target->ipv6, sizeof(u32) * 4)) {
return &dip_hw.dip_entries[idx];
}
}
return NULL;
}
int mtk_pce_dipfilter_desc_read(struct dip_desc *ddesc, u32 idx)
{
struct fe_mem_msg msg;
unsigned long flag;
int ret;
if (!ddesc || idx > FE_MEM_DIPFILTER_MAX_IDX)
return -EINVAL;
mtk_pce_fe_mem_msg_config(&msg, FE_MEM_CMD_READ, FE_MEM_TYPE_DIPFILTER, idx);
memset(&msg.raw, 0, sizeof(msg.raw));
spin_lock_irqsave(&dip_hw.lock, flag);
ret = mtk_pce_fe_mem_msg_send(&msg);
if (ret)
goto unlock;
memcpy(ddesc, &msg.ddesc, sizeof(struct dip_desc));
unlock:
spin_unlock_irqrestore(&dip_hw.lock, flag);
return ret;
}
static int __mtk_pce_dipfilter_entry_add(struct dip_desc *ddesc)
{
struct fe_mem_msg fmsg;
struct dipfilter_entry *dip_entry;
int ret;
u32 idx;
lockdep_assert_held(&dip_hw.lock);
/* find available entry */
idx = find_first_zero_bit(dip_hw.dip_tbl, FE_MEM_DIPFILTER_MAX_IDX);
if (idx == FE_MEM_DIPFILTER_MAX_IDX) {
PCE_NOTICE("dipfilter full\n");
return -EBUSY;
}
/* prepare fe_mem message */
mtk_pce_fe_mem_msg_config(&fmsg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_DIPFILTER, idx);
memset(&fmsg.raw, 0, sizeof(fmsg.raw));
memcpy(&fmsg.raw, ddesc, sizeof(struct dip_desc));
/* send fe_mem message */
ret = mtk_pce_fe_mem_msg_send(&fmsg);
if (ret) {
PCE_NOTICE("fe_mem send dipfilter desc failed\n");
return ret;
}
/* record installed dipfilter data */
dip_entry = &dip_hw.dip_entries[idx];
memcpy(&dip_entry->ddesc, ddesc, sizeof(struct dip_desc));
refcount_set(&dip_entry->refcnt, 1);
set_bit(idx, dip_hw.dip_tbl);
return 0;
}
int mtk_pce_dipfilter_entry_add(struct dip_desc *ddesc)
{
struct dipfilter_entry *dip_entry;
unsigned long flag;
int ret = 0;
if (unlikely(!ddesc))
return 0;
spin_lock_irqsave(&dip_hw.lock, flag);
dip_entry = __mtk_pce_dip_info_find(ddesc);
if (dip_entry) {
refcount_inc(&dip_entry->refcnt);
goto unlock;
}
ret = __mtk_pce_dipfilter_entry_add(ddesc);
unlock:
spin_unlock_irqrestore(&dip_hw.lock, flag);
return ret;
}
EXPORT_SYMBOL(mtk_pce_dipfilter_entry_add);
int mtk_pce_dipfilter_entry_del(struct dip_desc *ddesc)
{
struct fe_mem_msg fmsg;
struct dipfilter_entry *dip_entry;
unsigned long flag;
int ret;
u32 idx;
if (!ddesc)
return 0;
spin_lock_irqsave(&dip_hw.lock, flag);
dip_entry = __mtk_pce_dip_info_find(ddesc);
if (!dip_entry)
goto unlock;
dip_entry = &dip_hw.dip_entries[idx];
if (!refcount_dec_and_test(&dip_entry->refcnt))
/* dipfilter descriptor is still in use */
return 0;
mtk_pce_fe_mem_msg_config(&fmsg, FE_MEM_CMD_WRITE, FE_MEM_TYPE_DIPFILTER, idx);
memset(&fmsg.raw, 0, sizeof(fmsg.raw));
ret = mtk_pce_fe_mem_msg_send(&fmsg);
if (ret) {
PCE_NOTICE("fe_mem send dipfilter desc failed\n");
return ret;
}
memset(&dip_entry->ddesc, 0, sizeof(struct dip_desc));
clear_bit(idx, dip_hw.dip_tbl);
unlock:
spin_unlock_irqrestore(&dip_hw.lock, flag);
return 0;
}
EXPORT_SYMBOL(mtk_pce_dipfilter_entry_del);
static void mtk_pce_dipfilter_clean_up(void)
{
struct fe_mem_msg fmsg = {
.cmd = FE_MEM_CMD_WRITE,
.type = FE_MEM_TYPE_DIPFILTER,
};
unsigned long flag;
int ret = 0;
u32 i = 0;
memset(&fmsg.raw, 0, sizeof(fmsg.raw));
spin_lock_irqsave(&dip_hw.lock, flag);
/* clear all dipfilter desc on init */
for (i = 0; i < FE_MEM_DIPFILTER_MAX_IDX; i++) {
fmsg.index = i;
ret = mtk_pce_fe_mem_msg_send(&fmsg);
if (ret)
goto unlock;
dip_hw.dip_entries[i].index = i;
}
unlock:
spin_unlock_irqrestore(&dip_hw.lock, flag);
}
int mtk_pce_dipfilter_init(struct platform_device *pdev)
{
spin_lock_init(&dip_hw.lock);
mtk_pce_dipfilter_clean_up();
return 0;
}
void mtk_pce_dipfilter_deinit(struct platform_device *pdev)
{
mtk_pce_dipfilter_clean_up();
}