blob: 6bcbf116e46f07daeb3aa72dfd3c2eb8034eb6fa [file] [log] [blame]
/**
* (C) Copyright 2012-2013 Quantenna Communications Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
**/
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/io.h>
#include <linux/hardirq.h>
#include <asm/hardware.h>
#include <asm/board/platform.h>
#include <common/queue.h>
#include <qtn/topaz_dpi.h>
/**
* Each DPI filter is a mix of DPI fields, and DPI IP tuples.
* A field is a versatile way of matching any aspect of packet. An example field,
* which matches only large packets is:
* - anchor on the IPv4 header
* - Offset 0. Offset works with 32 bit words, length field is 1st word
* - Comparison operator >=
* - Mask 0x0000FFFF (Length is bytes 2 & 3, network endian)
* - Val 0x00000400 (1024 bytes or more)
* There are 32 hardware DPI fields.
*
* An IP tuple is a kind of field, specifically for matching whole IPv4 of IPv6 addresses,
* and UDP or TCP ports. One IP tuple has 9 matching words:
* - word0 1st word of ipv6 source addr, or ipv4 source addr
* - word[1-3] other words for ipv6 source addr match
* - word4 1st word of ipv6 dest addr, or ipv4 dest addr
* - word[5-7] other words for ipv6 dest addr match
* - word9 TCP/UDP source + dest ports.
* An IP tuple can match on any combination of the source address, dest address,
* source port, dest port, but it must be an exact match.
* There are 8 hardware IP tuples.
*
* A DPI filter is some combination of fields and/or ip tuples, grouped together with the
* TOPAZ_EMAC_RX_DPI_IPT_GROUP(x) and TOPAZ_EMAC_RX_DPI_FIELD_GROUP(x) registers.
* For example, filter 2 is composed of:
* - IP tuple 3, configured to match on source & dest address (not ports)
* - Field 17 matches something else
* The filter is composed by setting bit 17 TOPAZ_EMAC_RX_DPI_FIELD_GROUP(2), and
* bits 3 (enable ip tuple 3 saddr match) and 11 (enable ip tuple 3 dest addr match)
* of TOPAZ_EMAC_RX_DPI_IPT_GROUP(2).
* There are 16 hardware DPI filters.
*
*
* IP Tuple memory is arranged as:
* - 8 lwords wide (for 8 different ip tuples)
* - 9 lwords deep (called 'entries')
* Entries 0-3 are for IP source address matching
* Entries 4-7 are for IP destination address matching
* Entry 8 is TCP/UDP source/dest address matching
*
* The entire width for an entry is read/written at once, controlled by
* TOPAZ_EMAC_RX_DPI_IPT_MEM_COM (0x620). So when modifying an address, order is:
* - Read entry, so old unchanged values will be retained
* - Poll on entry read complete
* - Modify word (TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(x), 0x600 + 4 * x)
* - Write entry back
* - Poll on write complete
*/
struct topaz_dpi_field {
int refcount;
struct topaz_dpi_field_def def;
};
struct topaz_dpi_iptuple_addr {
int refcount;
struct in6_addr addr;
};
struct topaz_dpi_iptuple_port {
int refcount;
uint16_t port;
};
struct topaz_dpi_filter {
uint8_t index;
TAILQ_ENTRY(topaz_dpi_filter) next;
struct {
uint8_t srcaddr[howmany(TOPAZ_EMAC_NUM_DPI_IPTUPLES, NBBY)];
uint8_t destaddr[howmany(TOPAZ_EMAC_NUM_DPI_IPTUPLES, NBBY)];
uint8_t srcport[howmany(TOPAZ_EMAC_NUM_DPI_IPTUPLES, NBBY)];
uint8_t destport[howmany(TOPAZ_EMAC_NUM_DPI_IPTUPLES, NBBY)];
uint8_t fields[howmany(TOPAZ_EMAC_NUM_DPI_FIELDS, NBBY)];
} used;
};
typedef TAILQ_HEAD(filter_head_s, topaz_dpi_filter) topaz_dpi_filter_head;
struct topaz_dpi_info {
unsigned long base; /* emac ctl address base */
spinlock_t lock;
struct topaz_dpi_field fields[TOPAZ_EMAC_NUM_DPI_FIELDS];
struct topaz_dpi_iptuple_addr ipt_srcaddr[TOPAZ_EMAC_NUM_DPI_IPTUPLES];
struct topaz_dpi_iptuple_addr ipt_destaddr[TOPAZ_EMAC_NUM_DPI_IPTUPLES];
struct topaz_dpi_iptuple_port ipt_srcport[TOPAZ_EMAC_NUM_DPI_IPTUPLES];
struct topaz_dpi_iptuple_port ipt_destport[TOPAZ_EMAC_NUM_DPI_IPTUPLES];
topaz_dpi_filter_head used_filters_head;
topaz_dpi_filter_head unused_filters_head;
struct topaz_dpi_filter filters[TOPAZ_EMAC_NUM_DPI_FILTERS];
};
static void topaz_dpi_lock(struct topaz_dpi_info *info)
{
spin_lock_bh(&info->lock);
}
static void topaz_dpi_unlock(struct topaz_dpi_info *info)
{
spin_unlock_bh(&info->lock);
}
static void topaz_dpi_info_init(struct topaz_dpi_info *info, unsigned long base_addr)
{
int i;
memset(info, 0, sizeof(*info));
info->base = base_addr;
spin_lock_init(&info->lock);
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_IPTUPLES; i++) {
info->ipt_srcaddr[i].refcount = 0;
info->ipt_srcport[i].refcount = 0;
info->ipt_destaddr[i].refcount = 0;
info->ipt_destport[i].refcount = 0;
}
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FIELDS; i++) {
info->fields[i].refcount = 0;
}
TAILQ_INIT(&info->used_filters_head);
TAILQ_INIT(&info->unused_filters_head);
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FILTERS; i++) {
info->filters[i].index = i;
TAILQ_INSERT_TAIL(&info->unused_filters_head, &info->filters[i], next);
}
}
static int topaz_dpi_get_filter_fields(struct topaz_dpi_info *info,
const struct topaz_dpi_filter_request *req, uint8_t *used)
{
unsigned int i;
unsigned int j;
/* look for identical fields */
for (i = 0; i < req->field_count; i++) {
const struct topaz_dpi_field_def *new_field_def = &req->fields[i];
int unused_index = -1;
int found = 0;
for (j = 0; !found && j < TOPAZ_EMAC_NUM_DPI_FIELDS; j++) {
const struct topaz_dpi_field *field = &info->fields[j];
if (field->refcount == 0 && !isset(used, j) && unused_index < 0) {
unused_index = j;
} else if (field->refcount && memcmp(new_field_def, &field->def,
sizeof(*new_field_def)) == 0) {
/* share existing field */
setbit(used, j);
found = 1;
}
}
if (found) {
/* use shared field */
} else if (unused_index >= 0) {
/* use new field */
info->fields[unused_index].def = *new_field_def;
setbit(used, unused_index);
} else {
/* out of fields */
return -1;
}
}
return 0;
}
static int topaz_dpi_get_filter_ipt_addr(struct topaz_dpi_iptuple_addr *addrs,
const struct in6_addr *req_addr, uint8_t *used)
{
int i;
int found = 0;
int unused_index = -1;
const struct in6_addr zero = IN6ADDR_ANY_INIT;
if (memcmp(&zero, req_addr, sizeof(*req_addr)) == 0) {
return 0;
}
for (i = 0; !found && i < TOPAZ_EMAC_NUM_DPI_IPTUPLES; i++) {
if (addrs[i].refcount == 0 && unused_index < 0) {
unused_index = i;
} else if (addrs[i].refcount && memcmp(&addrs[i].addr, req_addr, sizeof(*req_addr)) == 0) {
found = 1;
setbit(used, i);
}
}
if (found) {
/* use shared field */
} else if (unused_index >= 0) {
addrs[unused_index].addr = *req_addr;
setbit(used, unused_index);
} else {
return -1;
}
return 0;
}
static int topaz_dpi_get_filter_ipt_port(struct topaz_dpi_iptuple_port *ports,
uint16_t req_port, uint8_t *used)
{
int i;
int found = 0;
int unused_index = -1;
if (!req_port) {
return 0;
}
for (i = 0; !found && i < TOPAZ_EMAC_NUM_DPI_IPTUPLES; i++) {
if (ports[i].refcount == 0 && unused_index < 0) {
unused_index = i;
} else if (ports[i].refcount && ports[i].port == req_port) {
found = 1;
setbit(used, i);
}
}
if (found) {
/* use shared field */
} else if (unused_index >= 0) {
ports[unused_index].port = req_port;
setbit(used, unused_index);
} else {
return -1;
}
return 0;
}
static struct topaz_dpi_filter * topaz_dpi_get_filter(struct topaz_dpi_info *info,
const struct topaz_dpi_filter_request *req)
{
struct topaz_dpi_filter *filter;
filter = TAILQ_FIRST(&info->unused_filters_head);
if (!filter)
return NULL;
if (topaz_dpi_get_filter_fields(info, req, filter->used.fields))
goto insufficient_fields;
if (topaz_dpi_get_filter_ipt_addr(info->ipt_srcaddr, &req->srcaddr, filter->used.srcaddr))
goto insufficient_fields;
if (topaz_dpi_get_filter_ipt_addr(info->ipt_destaddr, &req->destaddr, filter->used.destaddr))
goto insufficient_fields;
if (topaz_dpi_get_filter_ipt_port(info->ipt_srcport, req->srcport, filter->used.srcport))
goto insufficient_fields;
if (topaz_dpi_get_filter_ipt_port(info->ipt_destport, req->destport, filter->used.destport))
goto insufficient_fields;
TAILQ_REMOVE(&info->unused_filters_head, filter, next);
TAILQ_INSERT_TAIL(&info->used_filters_head, filter, next);
return filter;
insufficient_fields:
/* not enough fields available */
memset(&filter->used, 0, sizeof(filter->used));
return NULL;
}
static struct topaz_dpi_info *topaz_dpi_info_get(unsigned int emac)
{
static struct topaz_dpi_info emac0_dpi_info;
static struct topaz_dpi_info emac1_dpi_info;
return emac ? &emac1_dpi_info : &emac0_dpi_info;
}
static void topaz_dpi_filter_add_set_tidmap(struct topaz_dpi_info *info, uint8_t index, uint8_t tid)
{
uint32_t tidmap_reg_addr = TOPAZ_EMAC_RXP_DPI_TID_MAP_INDEX(index);
uint32_t tidmap_reg_val;
uint32_t tidmap_change_shift = TOPAZ_EMAC_RXP_TID_MAP_INDEX_SHIFT(index);
uint32_t tidmap_change_mask = TOPAZ_EMAC_RXP_TID_MAP_INDEX_MASK(index);
tidmap_reg_val = readl(info->base + tidmap_reg_addr);
tidmap_reg_val = (tidmap_reg_val & ~tidmap_change_mask) |
((tid << tidmap_change_shift) & tidmap_change_mask);
writel(tidmap_reg_val, info->base + tidmap_reg_addr);
}
static void topaz_dpi_iptuple_set(const struct topaz_dpi_info *info,
uint8_t iptuple, uint8_t entry, uint32_t value)
{
topaz_dpi_iptuple_read_entry(info->base, entry);
writel(value, info->base + TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(iptuple));
topaz_dpi_iptuple_write_entry(info->base, entry);
}
static void topaz_dpi_iptuple_set_srcaddr(const struct topaz_dpi_info *info,
uint8_t iptuple, const struct in6_addr *addr)
{
int entry;
for (entry = TOPAZ_EMAC_RX_DPI_IPT_ENTRY_SRCADDR_START;
entry < TOPAZ_EMAC_RX_DPI_IPT_ENTRY_SRCADDR_END; entry++) {
topaz_dpi_iptuple_set(info, iptuple, entry, ntohl(addr->s6_addr32[entry - 0]));
}
}
static void topaz_dpi_iptuple_set_destaddr(const struct topaz_dpi_info *info,
uint8_t iptuple, const struct in6_addr *addr)
{
int entry;
for (entry = TOPAZ_EMAC_RX_DPI_IPT_ENTRY_DESTADDR_START;
entry < TOPAZ_EMAC_RX_DPI_IPT_ENTRY_DESTADDR_END; entry++) {
topaz_dpi_iptuple_set(info, iptuple, entry, ntohl(addr->s6_addr32[entry - 4]));
}
}
static void topaz_dpi_iptuple_set_port(const struct topaz_dpi_info *info,
uint8_t iptuple, uint16_t port, bool is_src)
{
uint32_t reg;
uint16_t srcport;
uint16_t destport;
/*
* ip proto memory ports are little endian
* request format is network endian
*/
topaz_dpi_iptuple_read_entry(info->base, TOPAZ_EMAC_RX_DPI_IPT_ENTRY_PORTS);
reg = readl(info->base + TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(iptuple));
srcport = MS(reg, TOPAZ_EMAC_RX_DPI_IPT_PORT_SRC);
destport = MS(reg, TOPAZ_EMAC_RX_DPI_IPT_PORT_DEST);
if (is_src) {
srcport = ntohs(port);
} else {
destport = ntohs(port);
}
reg = SM(destport, TOPAZ_EMAC_RX_DPI_IPT_PORT_DEST) |
SM(srcport, TOPAZ_EMAC_RX_DPI_IPT_PORT_SRC);
writel(reg, info->base + TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(iptuple));
topaz_dpi_iptuple_write_entry(info->base, TOPAZ_EMAC_RX_DPI_IPT_ENTRY_PORTS);
}
static void topaz_dpi_iptuple_set_srcport(const struct topaz_dpi_info *info, uint8_t iptuple, uint16_t port)
{
topaz_dpi_iptuple_set_port(info, iptuple, port, 1);
}
static void topaz_dpi_iptuple_set_destport(const struct topaz_dpi_info *info, uint8_t iptuple, uint16_t port)
{
topaz_dpi_iptuple_set_port(info, iptuple, port, 0);
}
#if 0
void __topaz_dpi_iptuple_dump(const struct topaz_dpi_info *info, const char *func, int line)
{
int entry;
int reg;
printk("%s caller %s:%d\n", __FUNCTION__, func, line);
for (entry = 0; entry <= 8; entry++) {
topaz_dpi_iptuple_read_entry(info->base, entry);
printk("entry %d: ", entry);
for (reg = 0; reg < 8; reg++) {
printk(" 0x%08x", readl(info->base + TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(reg)));
}
printk("\n");
}
for (reg = 0; reg < 8; reg++) {
printk("ipt %d refs sa %d da %d sp %d dp %d\n",
reg,
info->ipt_srcaddr[reg].refcount,
info->ipt_destaddr[reg].refcount,
info->ipt_srcport[reg].refcount,
info->ipt_destport[reg].refcount);
}
}
#define topaz_dpi_iptuple_dump(_info) __topaz_dpi_iptuple_dump(_info, __FUNCTION__, __LINE__)
#endif
int topaz_dpi_filter_add(unsigned int emac,
const struct topaz_dpi_filter_request *req)
{
struct topaz_dpi_info *info;
struct topaz_dpi_filter *filter;
uint32_t field_group = 0;
uint32_t iptuple_group = 0;
uint32_t out_ctrl;
uint32_t out_combo = 0;
int i;
info = topaz_dpi_info_get(emac);
topaz_dpi_lock(info);
filter = topaz_dpi_get_filter(info, req);
if (filter == NULL) {
topaz_dpi_unlock(info);
return -1;
}
/*
* Increment reference count for each used ip tuple part, and each dpi field.
* If reference count leaves zero, also modify hardware
*/
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_IPTUPLES; i++) {
if (isset(filter->used.srcaddr, i)) {
iptuple_group |= TOPAZ_EMAC_RX_DPI_IPT_GROUP_SRCADDR(i);
if (info->ipt_srcaddr[i].refcount++ == 0) {
topaz_dpi_iptuple_set_srcaddr(info, i, &req->srcaddr);
}
}
if (isset(filter->used.destaddr, i)) {
iptuple_group |= TOPAZ_EMAC_RX_DPI_IPT_GROUP_DESTADDR(i);
if (info->ipt_destaddr[i].refcount++ == 0) {
topaz_dpi_iptuple_set_destaddr(info, i, &req->destaddr);
}
}
if (isset(filter->used.srcport, i)) {
iptuple_group |= TOPAZ_EMAC_RX_DPI_IPT_GROUP_SRCPORT(i);
if (info->ipt_srcport[i].refcount++ == 0) {
topaz_dpi_iptuple_set_srcport(info, i, req->srcport);
}
}
if (isset(filter->used.destport, i)) {
iptuple_group |= TOPAZ_EMAC_RX_DPI_IPT_GROUP_DESTPORT(i);
if (info->ipt_destport[i].refcount++ == 0) {
topaz_dpi_iptuple_set_destport(info, i, req->destport);
}
}
}
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FIELDS; i++) {
if (isset(filter->used.fields, i)) {
field_group |= (1 << i);
if (info->fields[i].refcount++ == 0) {
writel(info->fields[i].def.ctrl.raw, info->base + TOPAZ_EMAC_RX_DPI_FIELD_CTRL(i));
writel(info->fields[i].def.val, info->base + TOPAZ_EMAC_RX_DPI_FIELD_VAL(i));
writel(info->fields[i].def.mask, info->base + TOPAZ_EMAC_RX_DPI_FIELD_MASK(i));
}
}
}
/*
* Enable DPI filter:
* - set dpi -> tid map
* - set filter field group
* - set filter iptuple group
* - set output port/node
* - enable dpi filter
*/
if (field_group)
out_combo |= TOPAZ_EMAC_RX_DPI_OUT_CTRL_DPI;
if (iptuple_group)
out_combo |= TOPAZ_EMAC_RX_DPI_OUT_CTRL_IPTUPLE;
out_ctrl = SM(req->out_node, TOPAZ_EMAC_RX_DPI_OUT_CTRL_NODE) |
SM(req->out_port, TOPAZ_EMAC_RX_DPI_OUT_CTRL_PORT) |
SM(out_combo, TOPAZ_EMAC_RX_DPI_OUT_CTRL_COMBO);
topaz_dpi_filter_add_set_tidmap(info, filter->index, req->tid);
writel(field_group, info->base + TOPAZ_EMAC_RX_DPI_FIELD_GROUP(filter->index));
writel(iptuple_group, info->base + TOPAZ_EMAC_RX_DPI_IPT_GROUP(filter->index));
writel(out_ctrl, info->base + TOPAZ_EMAC_RX_DPI_OUT_CTRL(filter->index));
topaz_dpi_unlock(info);
return filter->index;
}
EXPORT_SYMBOL(topaz_dpi_filter_add);
void topaz_dpi_filter_del(unsigned int emac, int filter_no)
{
struct topaz_dpi_info *info = topaz_dpi_info_get(emac);
struct topaz_dpi_filter *filter;
unsigned int i;
if (filter_no < 0 || filter_no >= TOPAZ_EMAC_NUM_DPI_FILTERS)
return;
topaz_dpi_lock(info);
filter = &info->filters[filter_no];
/* disable filters, field group & ip tuple group */
writel(readl(info->base + TOPAZ_EMAC_RXP_DPI_CTRL) & ~BIT(filter->index),
info->base + TOPAZ_EMAC_RXP_DPI_CTRL);
writel(0, info->base + TOPAZ_EMAC_RX_DPI_OUT_CTRL(filter->index));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_GROUP(filter->index));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_IPT_GROUP(filter->index));
/* decrement reference counts, clear hw if they hit zero */
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_IPTUPLES; i++) {
const struct in6_addr zero = IN6ADDR_ANY_INIT;
if (isset(filter->used.srcaddr, i) &&
--info->ipt_srcaddr[i].refcount == 0) {
topaz_dpi_iptuple_set_srcaddr(info, i, &zero);
}
if (isset(filter->used.destaddr, i) &&
--info->ipt_destaddr[i].refcount == 0) {
topaz_dpi_iptuple_set_destaddr(info, i, &zero);
}
if (isset(filter->used.srcport, i) &&
--info->ipt_srcport[i].refcount == 0) {
topaz_dpi_iptuple_set_srcport(info, i, 0);
}
if (isset(filter->used.destport, i) &&
--info->ipt_destport[i].refcount == 0) {
topaz_dpi_iptuple_set_destport(info, i, 0);
}
}
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FIELDS; i++) {
if (isset(filter->used.fields, i) &&
--info->fields[i].refcount == 0) {
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_CTRL(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_VAL(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_MASK(i));
}
}
memset(&filter->used, 0, sizeof(filter->used));
TAILQ_REMOVE(&info->used_filters_head, filter, next);
TAILQ_INSERT_TAIL(&info->unused_filters_head, filter, next);
topaz_dpi_unlock(info);
}
EXPORT_SYMBOL(topaz_dpi_filter_del);
static void topaz_dpi_hw_init(unsigned int emac)
{
struct topaz_dpi_info *info = topaz_dpi_info_get(emac);
int i;
/* clear dpi fields */
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FIELDS; i++) {
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_VAL(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_MASK(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_CTRL(i));
}
/* clear dpi filters and group registers */
for (i = 0; i < TOPAZ_EMAC_NUM_DPI_FILTERS; i++) {
writel(0, info->base + TOPAZ_EMAC_RX_DPI_OUT_CTRL(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_FIELD_GROUP(i));
writel(0, info->base + TOPAZ_EMAC_RX_DPI_IPT_GROUP(i));
}
/* clear ip tuple memory */
for (i = 0; i < TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA_MAX; i++) {
writel(0, info->base + TOPAZ_EMAC_RX_DPI_IPT_MEM_DATA(i));
}
for (i = 0; i < TOPAZ_EMAC_RX_DPI_IPT_ENTRIES; i++) {
topaz_dpi_iptuple_write_entry(info->base, i);
}
}
int topaz_dpi_init(unsigned int emac)
{
unsigned int base_addr;
if (emac == 0) {
base_addr = RUBY_ENET0_BASE_ADDR;
} else if (emac == 1) {
base_addr = RUBY_ENET1_BASE_ADDR;
} else {
return -EINVAL;
}
topaz_dpi_info_init(topaz_dpi_info_get(emac), base_addr);
topaz_dpi_hw_init(emac);
return 0;
}
EXPORT_SYMBOL(topaz_dpi_init);
MODULE_DESCRIPTION("Topaz EMAC DPI filters");
MODULE_AUTHOR("Quantenna");
MODULE_LICENSE("GPL");