blob: 659641397c5b69562e3fd924638c95c00d5b536a [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/debugfs.h>
#include <linux/inet.h>
#include <linux/uaccess.h>
#include "pce/cls.h"
#include "pce/debugfs.h"
#include "pce/dipfilter.h"
#include "pce/internal.h"
#include "pce/netsys.h"
#include "pce/pce.h"
#include "pce/tport_map.h"
#define CLS_ENTRY_DBG_CFG(_name, _type, func) \
{ \
.name = #_name, \
.type = _type, \
.func = cls_entry_debug_fill_ ## _name, \
}
#define CLS_ENTRY_DBG_CFG_MASK_DATA(_name) \
CLS_ENTRY_DBG_CFG(_name, CLS_ENTRY_MASK_DATA, fill_mask_data)
#define CLS_ENTRY_DBG_CFG_DATA(_name) \
CLS_ENTRY_DBG_CFG(_name, CLS_ENTRY_DATA, fill_data)
enum cls_entry_config_type {
CLS_ENTRY_MASK_DATA,
CLS_ENTRY_DATA,
};
struct cls_entry_debug_config {
const char *name;
enum cls_entry_config_type type;
union {
int (*fill_mask_data)(struct cls_desc *cdesc, u32 mask, u32 data);
int (*fill_data)(struct cls_desc *cdesc, u32 data);
};
};
struct dentry *debug_root;
static void cls_entry_debug_show_l4(struct seq_file *s, void *priv,
struct cls_desc *cdesc)
{
bool l4_valid = false;
u32 l4_data_m = 0;
u32 l4_data = 0;
seq_printf(s, "\t\tL4 valid mask: 0x%X, data: 0x%X\n",
cdesc->l4_valid_m, cdesc->l4_valid);
if ((cdesc->l4_valid_m & CLS_DESC_VALID_DPORT_BIT)
&& (cdesc->l4_valid & CLS_DESC_VALID_DPORT_BIT))
seq_printf(s, "\t\t\tL4 Dport mask: 0x%04X, data: %05u\n",
cdesc->l4_dport_m, cdesc->l4_dport);
if ((cdesc->l4_valid_m & CLS_DESC_VALID_LOWER_HALF_WORD_BIT)
&& (cdesc->l4_valid & CLS_DESC_VALID_LOWER_HALF_WORD_BIT)) {
l4_valid = true;
l4_data_m |= cdesc->l4_hdr_usr_data_m & 0xFFFF;
l4_data |= cdesc->l4_hdr_usr_data & 0xFFFF;
}
if ((cdesc->l4_valid_m & CLS_DESC_VALID_UPPER_HALF_WORD_BIT)
&& (cdesc->l4_valid & CLS_DESC_VALID_UPPER_HALF_WORD_BIT)) {
l4_valid = true;
l4_data_m |= ((cdesc->l4_hdr_usr_data_m & 0xFFFF0000) >> 16);
l4_data |= ((cdesc->l4_hdr_usr_data & 0xFFFF0000) >> 16);
}
if (!l4_valid)
return;
if ((cdesc->tag_m & CLS_DESC_TAG_MATCH_L4_HDR)
&& (cdesc->tag & CLS_DESC_TAG_MATCH_L4_HDR)) {
seq_printf(s, "\t\t\tL4 header not empty mask: 0x%X, data: 0x%X\n",
cdesc->l4_udp_hdr_nez_m, cdesc->l4_udp_hdr_nez);
seq_printf(s, "\t\t\tL4 header mask: 0x%08X, data: 0x%08X\n",
cdesc->l4_hdr_usr_data_m, cdesc->l4_hdr_usr_data);
} else {
seq_printf(s, "\t\t\tUDP lite valid mask: 0x%X, data: 0x%X\n",
cdesc->l4_udp_hdr_nez_m, cdesc->l4_udp_hdr_nez);
seq_printf(s, "\t\t\tL4 user data mask: 0x%08X, data: 0x%08X\n",
cdesc->l4_hdr_usr_data_m, cdesc->l4_hdr_usr_data);
}
}
static int cls_entry_debug_read(struct seq_file *s, void *private)
{
struct cls_desc cdesc;
bool cls_enabled;
int ret;
u32 i;
for (i = 1; i < FE_MEM_CLS_MAX_INDEX; i++) {
ret = mtk_pce_cls_desc_read(&cdesc, i);
if (ret) {
PCE_ERR("read cls desc: %u failed: %d\n", i, ret);
return 0;
}
cls_enabled = (cdesc.tag && cdesc.tag_m
&& cdesc.tag == (cdesc.tag_m & cdesc.tag));
seq_printf(s, "CLS Entry%02u, Tag mask: 0x%X, data: 0x%X, enabled: %u\n",
i, cdesc.tag_m, cdesc.tag, cls_enabled);
/* cls entry is not enabled */
if (!cls_enabled)
continue;
/* CLS descriptor's tag data should only set 1 bit of [1:0] */
if (cdesc.tag ==
(CLS_DESC_TAG_MATCH_L4_HDR | CLS_DESC_TAG_MATCH_L4_USR)) {
seq_puts(s, "Invalid CLS descriptor tag\n");
continue;
}
seq_puts(s, "\tAction:\n");
seq_printf(s, "\t\tFport: %02u, Tport: %02u, TOPS_ENTRY: %02u, CDRT: %02u, QID: %u, DR_IDX: %u\n",
cdesc.fport, cdesc.tport_idx,
cdesc.tops_entry, cdesc.cdrt_idx,
cdesc.qid, cdesc.dr_idx);
seq_puts(s, "\tContent:\n");
seq_printf(s, "\t\tDIP match mask: 0x%X, data: 0x%X\n",
cdesc.dip_match_m, cdesc.dip_match);
seq_printf(s, "\t\tL4 protocol mask: 0x%02X, data: 0x%02X\n",
cdesc.l4_type_m, cdesc.l4_type);
cls_entry_debug_show_l4(s, private, &cdesc);
seq_printf(s, "\t\tSport map mask: 0x%04X, data: 0x%04X\n",
cdesc.sport_m, cdesc.sport);
}
return 0;
}
static int cls_entry_debug_fill_tag(struct cls_desc *cdesc, u32 mask, u32 data)
{
if (mask > CLS_DESC_TAG_MASK || data > CLS_DESC_TAG_MASK)
return -EINVAL;
cdesc->tag_m = mask;
cdesc->tag = data;
return 0;
}
static int cls_entry_debug_fill_udplite_l4_hdr_nez(struct cls_desc *cdesc,
u32 mask, u32 data)
{
if (mask > CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK
|| data > CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK)
return -EINVAL;
cdesc->l4_udp_hdr_nez_m = mask;
cdesc->l4_udp_hdr_nez = data;
return 0;
}
static int cls_entry_debug_fill_dip_match(struct cls_desc *cdesc, u32 mask, u32 data)
{
if (mask > CLS_DESC_DIP_MATCH || data > CLS_DESC_DIP_MATCH)
return -EINVAL;
cdesc->dip_match_m = mask;
cdesc->dip_match = data;
return 0;
}
static int cls_entry_debug_fill_l4_protocol(struct cls_desc *cdesc,
u32 mask, u32 data)
{
if (mask > CLS_DESC_L4_TYPE_MASK || data > CLS_DESC_L4_TYPE_MASK)
return -EINVAL;
cdesc->l4_type_m = mask;
cdesc->l4_type = data;
return 0;
}
static int cls_entry_debug_fill_l4_valid(struct cls_desc *cdesc, u32 mask, u32 data)
{
if (mask > CLS_DESC_L4_VALID_MASK || data > CLS_DESC_L4_VALID_MASK)
return -EINVAL;
cdesc->l4_valid_m = mask;
cdesc->l4_valid = data;
return 0;
}
static int cls_entry_debug_fill_l4_dport(struct cls_desc *cdesc, u32 mask, u32 data)
{
if (mask > CLS_DESC_L4_DPORT_MASK || data > CLS_DESC_L4_DPORT_MASK)
return -EINVAL;
cdesc->l4_dport_m = mask;
cdesc->l4_dport = data;
return 0;
}
static int cls_entry_debug_fill_l4_data(struct cls_desc *cdesc, u32 mask, u32 data)
{
cdesc->l4_hdr_usr_data_m = mask;
cdesc->l4_hdr_usr_data = data;
return 0;
}
static int cls_entry_debug_fill_fport(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_FPORT_MASK)
return -EINVAL;
cdesc->fport = data;
return 0;
}
static int cls_entry_debug_fill_dr_idx(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_DR_IDX_MASK)
return -EINVAL;
cdesc->dr_idx = data;
return 0;
}
static int cls_entry_debug_fill_qid(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_QID_MASK)
return -EINVAL;
cdesc->qid = data;
return 0;
}
static int cls_entry_debug_fill_cdrt(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_CDRT_MASK)
return -EINVAL;
cdesc->cdrt_idx = data;
return 0;
}
static int cls_entry_debug_fill_tport(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_TPORT_MASK)
return -EINVAL;
cdesc->tport_idx = data;
return 0;
}
static int cls_entry_debug_fill_tops_entry(struct cls_desc *cdesc, u32 data)
{
if (data > CLS_DESC_TOPS_ENTRY_MASK)
return -EINVAL;
cdesc->tops_entry = data;
return 0;
}
static int cls_entry_debug_fill_mask_data(
const char *buf, u32 *ofs,
struct cls_desc *cdesc,
int (*fill_func)(struct cls_desc *cdesc, u32 mask, u32 data))
{
u32 nchar = 0;
u32 mask = 0;
u32 data = 0;
int ret;
if (!fill_func)
return -ENODEV;
ret = sscanf(buf + *ofs, "%x %x %n", &mask, &data, &nchar);
if (ret != 2)
return -EINVAL;
*ofs += nchar;
ret = fill_func(cdesc, mask, data);
if (ret)
PCE_ERR("invalid mask: 0x%x, data: 0x%x\n", mask, data);
return ret;
}
static int cls_entry_debug_fill_data(const char *buf, u32 *ofs,
struct cls_desc *cdesc,
int (*fill_func)(struct cls_desc *cdesc, u32 data))
{
u32 nchar = 0;
u32 data = 0;
int ret;
if (!fill_func)
return -ENODEV;
ret = sscanf(buf + *ofs, "%x %n", &data, &nchar);
if (ret != 1)
return -EINVAL;
*ofs += nchar;
ret = fill_func(cdesc, data);
if (ret)
PCE_ERR("invalid data: 0x%x\n", data);
return ret;
}
struct cls_entry_debug_config cls_entry_dbg_cfgs[] = {
CLS_ENTRY_DBG_CFG_MASK_DATA(tag),
CLS_ENTRY_DBG_CFG_MASK_DATA(udplite_l4_hdr_nez),
CLS_ENTRY_DBG_CFG_MASK_DATA(dip_match),
CLS_ENTRY_DBG_CFG_MASK_DATA(l4_protocol),
CLS_ENTRY_DBG_CFG_MASK_DATA(l4_valid),
CLS_ENTRY_DBG_CFG_MASK_DATA(l4_dport),
CLS_ENTRY_DBG_CFG_MASK_DATA(l4_data),
CLS_ENTRY_DBG_CFG_DATA(fport),
CLS_ENTRY_DBG_CFG_DATA(dr_idx),
CLS_ENTRY_DBG_CFG_DATA(qid),
CLS_ENTRY_DBG_CFG_DATA(cdrt),
CLS_ENTRY_DBG_CFG_DATA(tport),
CLS_ENTRY_DBG_CFG_DATA(tops_entry),
};
static int cls_entry_debug_fill(const char *buf, u32 *ofs, struct cls_desc *cdesc)
{
struct cls_entry_debug_config *cls_entry_dbg_cfg;
char arg[32];
u32 nchar = 0;
u32 i;
int ret;
ret = sscanf(buf + *ofs, "%31s %n", arg, &nchar);
if (ret != 1)
return -EINVAL;
*ofs += nchar;
for (i = 0; i < ARRAY_SIZE(cls_entry_dbg_cfgs); i++) {
cls_entry_dbg_cfg = &cls_entry_dbg_cfgs[i];
if (strcmp(cls_entry_dbg_cfg->name, arg))
continue;
switch (cls_entry_dbg_cfg->type) {
case CLS_ENTRY_MASK_DATA:
ret = cls_entry_debug_fill_mask_data(buf, ofs, cdesc,
cls_entry_dbg_cfg->fill_mask_data);
break;
case CLS_ENTRY_DATA:
ret = cls_entry_debug_fill_data(buf, ofs, cdesc,
cls_entry_dbg_cfg->fill_data);
break;
default:
return -EINVAL;
}
if (ret)
goto err_out;
return ret;
}
err_out:
PCE_ERR("invalid argument: %s\n", arg);
return ret;
}
static ssize_t cls_entry_debug_write(struct file *file, const char __user *buffer,
size_t count, loff_t *data)
{
struct cls_desc cdesc;
char buf[512];
u32 nchar = 0;
u32 ofs = 0;
u32 idx = 0;
int ret;
if (count > sizeof(buf))
return -ENOMEM;
if (copy_from_user(buf, buffer, count))
return -EFAULT;
buf[count] = '\0';
memset(&cdesc, 0, sizeof(struct cls_desc));
ret = sscanf(buf + ofs, "%u %n\n", &idx, &nchar);
if (ret != 1)
return -EINVAL;
ofs += nchar;
if (!idx || idx >= FE_MEM_CLS_MAX_INDEX) {
PCE_ERR("invalid cls entry: %u\n", idx);
return -EINVAL;
}
while (1) {
if (ofs >= count)
break;
ret = cls_entry_debug_fill(buf, &ofs, &cdesc);
if (ret)
return ret;
}
ret = mtk_pce_cls_desc_write(&cdesc, idx);
if (ret)
return ret;
return count;
}
static int cls_entry_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, cls_entry_debug_read, file->private_data);
}
static int dipfilter_debug_read(struct seq_file *s, void *private)
{
struct dip_desc ddesc;
int ret;
u32 i;
for (i = 0; i < FE_MEM_DIPFILTER_MAX_IDX; i++) {
ret = mtk_pce_dipfilter_desc_read(&ddesc, i);
if (ret) {
PCE_ERR("read dipfilter desc: %u failed: %d\n", i, ret);
return 0;
}
seq_printf(s, "DIPFILTER Entry%02u, enabled: %u\n",
i, ddesc.tag != DIPFILTER_DISABLED);
if (ddesc.tag == DIPFILTER_DISABLED) {
continue;
} else if (ddesc.tag == DIPFILTER_IPV4) {
u32 addr = cpu_to_be32(ddesc.ipv4);
seq_printf(s, "IPv4 address: %pI4\n", &addr);
} else if (ddesc.tag == DIPFILTER_IPV6) {
seq_printf(s, "IPv6 address: %pI6\n", ddesc.ipv6);
}
}
return 0;
}
static int dipfilter_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, dipfilter_debug_read, file->private_data);
}
static ssize_t dipfilter_debug_write(struct file *file, const char __user *buffer,
size_t count, loff_t *data)
{
struct dip_desc ddesc;
const char *end;
char buf[512];
char arg[5];
char s_dip[40];
int ret;
if (count > sizeof(buf))
return -ENOMEM;
if (copy_from_user(buf, buffer, count))
return -EFAULT;
buf[count] = '\0';
memset(&ddesc, 0, sizeof(struct dip_desc));
ret = sscanf(buf, "%s %s", arg, s_dip);
if (ret != 2)
return -EINVAL;
if (!strcmp(arg, "ipv4")) {
ddesc.tag = DIPFILTER_IPV4;
if (!in4_pton(s_dip, -1, (u8 *)(&ddesc.ipv4), -1, &end) || (*end)) {
PCE_ERR("invalid IPv4 address: %s\n", s_dip);
return -EINVAL;
}
} else if (!strcmp(arg, "ipv6")) {
ddesc.tag = DIPFILTER_IPV6;
if (!in6_pton(s_dip, -1, (u8 *)ddesc.ipv6, -1, &end) || (*end)) {
PCE_ERR("invalid Ipv6 address: %s\n", s_dip);
return -EINVAL;
}
} else {
return -EINVAL;
}
ret = mtk_pce_dipfilter_entry_add(&ddesc);
if (ret) {
PCE_ERR("add dipfilter entry failed: %d\n", ret);
return ret;
}
return count;
}
static int tport_map_debug_read(struct seq_file *s, void *private)
{
struct tsc_desc tdesc;
int ret;
u64 map;
u32 i;
for (i = 0; i < __PSE_PORT_MAX; i++) {
if (TS_CONFIG_MASK & BIT(i)) {
ret = mtk_pce_tport_map_ts_config_read(i, &tdesc);
if (ret)
return ret;
map = tdesc.tport_map_lower;
map |= ((u64)tdesc.tport_map_upper) << 32;
seq_printf(s, "PSE_PORT%02u ", i);
seq_printf(s, "PORT_MAP: 0x%llX, ", map);
seq_printf(s, "default TPORT_IDX: %02u, ", tdesc.tport);
seq_printf(s, "default CDRT_IDX: %02u, ", tdesc.cdrt_idx);
seq_printf(s, "default TOPS_ENTRY: %02u\n", tdesc.tops_entry);
} else if (PSE_PORT_PPE_MASK & BIT(i)) {
ret = mtk_pce_tport_map_ppe_read(i, &map);
if (ret)
return ret;
seq_printf(s, "PSE PORT%02u ", i);
seq_printf(s, "PORT_MAP: 0x%llX\n", map);
}
}
return 0;
}
static int tport_map_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, tport_map_debug_read, file->private_data);
}
static int tport_map_debug_update_all(const char *buf, int *ofs, u32 tport_idx)
{
enum pse_port port = PSE_PORT_ADMA;
enum pse_port target;
u64 port_map = 0;
int nchar = 0;
int ret = 0;
*ofs += nchar;
ret = sscanf(buf + *ofs, "%llx %n", &port_map, &nchar);
if (ret != 1)
return -EPERM;
while (port_map && port < __PSE_PORT_MAX) {
target = port_map & PSE_PER_PORT_MASK;
port_map >>= PSE_PER_PORT_BITS;
if (TS_CONFIG_MASK & BIT(port) || PSE_PORT_PPE_MASK & BIT(port)) {
ret = mtk_pce_tport_map_pse_port_update(port,
tport_idx,
target);
if (ret) {
PCE_ERR("invalid port: %u\n", port);
return ret;
}
}
port++;
}
return 0;
}
static int tport_map_debug_update_single(const char *buf, int *ofs,
enum pse_port port, u32 tport_idx)
{
u32 target = 0;
int nchar = 0;
int ret = 0;
ret = sscanf(buf + *ofs, "%x %n", &target, &nchar);
if (ret != 1)
return -EPERM;
return mtk_pce_tport_map_pse_port_update(port, tport_idx, target);
}
static ssize_t tport_map_debug_write(struct file *file, const char __user *buffer,
size_t count, loff_t *data)
{
enum pse_port port = PSE_PORT_ADMA;
char arg1[21] = {0};
char cmd[21] = {0};
char buf[512];
u32 tport_idx = 0;
int nchar = 0;
int ret;
if (count > sizeof(buf))
return -ENOMEM;
if (copy_from_user(buf, buffer, count))
return -EFAULT;
buf[count] = '\0';
ret = sscanf(buf, "%20s %20s %u %n", cmd, arg1, &tport_idx, &nchar);
if (ret != 3)
return -EINVAL;
if (tport_idx > TPORT_IDX_MAX) {
PCE_ERR("invalid tport idx: %u\n", tport_idx);
return -EINVAL;
}
if (!strcmp(cmd, "UPDATE")) {
if (!strcmp(arg1, "ALL")) {
ret = tport_map_debug_update_all(buf, &nchar, tport_idx);
if (ret)
return ret;
} else {
ret = kstrtou32(arg1, 10, &port);
if (ret) {
PCE_ERR("invalid pse port: %u\n", port);
return ret;
}
ret = tport_map_debug_update_single(buf, &nchar,
port, tport_idx);
if (ret) {
PCE_ERR("update tport map single failed: %d\n",
ret);
return ret;
}
}
}
return count;
}
static const struct file_operations cls_entry_debug_ops = {
.open = cls_entry_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.write = cls_entry_debug_write,
.release = single_release,
};
static const struct file_operations dipfilter_debug_ops = {
.open = dipfilter_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.write = dipfilter_debug_write,
.release = single_release,
};
static const struct file_operations tport_map_debug_ops = {
.open = tport_map_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.write = tport_map_debug_write,
.release = single_release,
};
int mtk_pce_debugfs_init(struct platform_device *pdev)
{
debug_root = debugfs_create_dir("pce", NULL);
if (!debug_root) {
PCE_ERR("create debugfs root directory failed\n");
return -ENOMEM;
}
debugfs_create_file("cls_entry", 0444, debug_root, NULL,
&cls_entry_debug_ops);
debugfs_create_file("dipfilter_entry", 0444, debug_root, NULL,
&dipfilter_debug_ops);
debugfs_create_file("tport_map", 0444, debug_root, NULL,
&tport_map_debug_ops);
return 0;
}
void mtk_pce_debugfs_deinit(struct platform_device *pdev)
{
debugfs_remove_recursive(debug_root);
debug_root = NULL;
}