net: macb: Added support for RX filtering

This patch allows filtering received packets to different
hardware queues (aka ntuple).

Signed-off-by: Rafal Ozieblo <rafalo@cadence.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Rafal Ozieblo 2017-11-30 18:20:44 +00:00 committed by David S. Miller
parent 512286bbd4
commit ae8223de3d
2 changed files with 444 additions and 1 deletions

View File

@ -164,10 +164,32 @@
#define GEM_DCFG5 0x0290 /* Design Config 5 */
#define GEM_DCFG6 0x0294 /* Design Config 6 */
#define GEM_DCFG7 0x0298 /* Design Config 7 */
#define GEM_DCFG8 0x029C /* Design Config 8 */
#define GEM_TXBDCTRL 0x04cc /* TX Buffer Descriptor control register */
#define GEM_RXBDCTRL 0x04d0 /* RX Buffer Descriptor control register */
/* Screener Type 2 match registers */
#define GEM_SCRT2 0x540
/* EtherType registers */
#define GEM_ETHT 0x06E0
/* Type 2 compare registers */
#define GEM_T2CMPW0 0x0700
#define GEM_T2CMPW1 0x0704
#define T2CMP_OFST(t2idx) (t2idx * 2)
/* type 2 compare registers
* each location requires 3 compare regs
*/
#define GEM_IP4SRC_CMP(idx) (idx * 3)
#define GEM_IP4DST_CMP(idx) (idx * 3 + 1)
#define GEM_PORT_CMP(idx) (idx * 3 + 2)
/* Which screening type 2 EtherType register will be used (0 - 7) */
#define SCRT2_ETHT 0
#define GEM_ISR(hw_q) (0x0400 + ((hw_q) << 2))
#define GEM_TBQP(hw_q) (0x0440 + ((hw_q) << 2))
#define GEM_TBQPH(hw_q) (0x04C8)
@ -457,6 +479,16 @@
#define GEM_DAW64_OFFSET 23
#define GEM_DAW64_SIZE 1
/* Bitfields in DCFG8. */
#define GEM_T1SCR_OFFSET 24
#define GEM_T1SCR_SIZE 8
#define GEM_T2SCR_OFFSET 16
#define GEM_T2SCR_SIZE 8
#define GEM_SCR2ETH_OFFSET 8
#define GEM_SCR2ETH_SIZE 8
#define GEM_SCR2CMP_OFFSET 0
#define GEM_SCR2CMP_SIZE 8
/* Bitfields in TISUBN */
#define GEM_SUBNSINCR_OFFSET 0
#define GEM_SUBNSINCR_SIZE 16
@ -485,6 +517,66 @@
#define GEM_RXTSMODE_OFFSET 4 /* RX Descriptor Timestamp Insertion mode */
#define GEM_RXTSMODE_SIZE 2
/* Bitfields in SCRT2 */
#define GEM_QUEUE_OFFSET 0 /* Queue Number */
#define GEM_QUEUE_SIZE 4
#define GEM_VLANPR_OFFSET 4 /* VLAN Priority */
#define GEM_VLANPR_SIZE 3
#define GEM_VLANEN_OFFSET 8 /* VLAN Enable */
#define GEM_VLANEN_SIZE 1
#define GEM_ETHT2IDX_OFFSET 9 /* Index to screener type 2 EtherType register */
#define GEM_ETHT2IDX_SIZE 3
#define GEM_ETHTEN_OFFSET 12 /* EtherType Enable */
#define GEM_ETHTEN_SIZE 1
#define GEM_CMPA_OFFSET 13 /* Compare A - Index to screener type 2 Compare register */
#define GEM_CMPA_SIZE 5
#define GEM_CMPAEN_OFFSET 18 /* Compare A Enable */
#define GEM_CMPAEN_SIZE 1
#define GEM_CMPB_OFFSET 19 /* Compare B - Index to screener type 2 Compare register */
#define GEM_CMPB_SIZE 5
#define GEM_CMPBEN_OFFSET 24 /* Compare B Enable */
#define GEM_CMPBEN_SIZE 1
#define GEM_CMPC_OFFSET 25 /* Compare C - Index to screener type 2 Compare register */
#define GEM_CMPC_SIZE 5
#define GEM_CMPCEN_OFFSET 30 /* Compare C Enable */
#define GEM_CMPCEN_SIZE 1
/* Bitfields in ETHT */
#define GEM_ETHTCMP_OFFSET 0 /* EtherType compare value */
#define GEM_ETHTCMP_SIZE 16
/* Bitfields in T2CMPW0 */
#define GEM_T2CMP_OFFSET 16 /* 0xFFFF0000 compare value */
#define GEM_T2CMP_SIZE 16
#define GEM_T2MASK_OFFSET 0 /* 0x0000FFFF compare value or mask */
#define GEM_T2MASK_SIZE 16
/* Bitfields in T2CMPW1 */
#define GEM_T2DISMSK_OFFSET 9 /* disable mask */
#define GEM_T2DISMSK_SIZE 1
#define GEM_T2CMPOFST_OFFSET 7 /* compare offset */
#define GEM_T2CMPOFST_SIZE 2
#define GEM_T2OFST_OFFSET 0 /* offset value */
#define GEM_T2OFST_SIZE 7
/* Offset for screener type 2 compare values (T2CMPOFST).
* Note the offset is applied after the specified point,
* e.g. GEM_T2COMPOFST_ETYPE denotes the EtherType field, so an offset
* of 12 bytes from this would be the source IP address in an IP header
*/
#define GEM_T2COMPOFST_SOF 0
#define GEM_T2COMPOFST_ETYPE 1
#define GEM_T2COMPOFST_IPHDR 2
#define GEM_T2COMPOFST_TCPUDP 3
/* offset from EtherType to IP address */
#define ETYPE_SRCIP_OFFSET 12
#define ETYPE_DSTIP_OFFSET 16
/* offset from IP header to port */
#define IPHDR_SRCPORT_OFFSET 0
#define IPHDR_DSTPORT_OFFSET 2
/* Transmit DMA buffer descriptor Word 1 */
#define GEM_DMA_TXVALID_OFFSET 23 /* timestamp has been captured in the Buffer Descriptor */
#define GEM_DMA_TXVALID_SIZE 1
@ -585,6 +677,8 @@
#define gem_writel(port, reg, value) (port)->macb_reg_writel((port), GEM_##reg, (value))
#define queue_readl(queue, reg) (queue)->bp->macb_reg_readl((queue)->bp, (queue)->reg)
#define queue_writel(queue, reg, value) (queue)->bp->macb_reg_writel((queue)->bp, (queue)->reg, (value))
#define gem_readl_n(port, reg, idx) (port)->macb_reg_readl((port), GEM_##reg + idx * 4)
#define gem_writel_n(port, reg, idx, value) (port)->macb_reg_writel((port), GEM_##reg + idx * 4, (value))
#define PTP_TS_BUFFER_SIZE 128 /* must be power of 2 */
@ -1026,6 +1120,16 @@ struct macb_queue {
#endif
};
struct ethtool_rx_fs_item {
struct ethtool_rx_flow_spec fs;
struct list_head list;
};
struct ethtool_rx_fs_list {
struct list_head list;
unsigned int count;
};
struct macb {
void __iomem *regs;
bool native_io;
@ -1092,6 +1196,11 @@ struct macb {
struct ptp_clock_info ptp_clock_info;
struct tsu_incr tsu_incr;
struct hwtstamp_config tstamp_config;
/* RX queue filer rule set*/
struct ethtool_rx_fs_list rx_fs_list;
spinlock_t rx_fs_lock;
unsigned int max_tuples;
};
#ifdef CONFIG_MACB_USE_HWSTAMP

View File

@ -2668,6 +2668,308 @@ static int macb_get_ts_info(struct net_device *netdev,
return ethtool_op_get_ts_info(netdev, info);
}
static void gem_enable_flow_filters(struct macb *bp, bool enable)
{
struct ethtool_rx_fs_item *item;
u32 t2_scr;
int num_t2_scr;
num_t2_scr = GEM_BFEXT(T2SCR, gem_readl(bp, DCFG8));
list_for_each_entry(item, &bp->rx_fs_list.list, list) {
struct ethtool_rx_flow_spec *fs = &item->fs;
struct ethtool_tcpip4_spec *tp4sp_m;
if (fs->location >= num_t2_scr)
continue;
t2_scr = gem_readl_n(bp, SCRT2, fs->location);
/* enable/disable screener regs for the flow entry */
t2_scr = GEM_BFINS(ETHTEN, enable, t2_scr);
/* only enable fields with no masking */
tp4sp_m = &(fs->m_u.tcp_ip4_spec);
if (enable && (tp4sp_m->ip4src == 0xFFFFFFFF))
t2_scr = GEM_BFINS(CMPAEN, 1, t2_scr);
else
t2_scr = GEM_BFINS(CMPAEN, 0, t2_scr);
if (enable && (tp4sp_m->ip4dst == 0xFFFFFFFF))
t2_scr = GEM_BFINS(CMPBEN, 1, t2_scr);
else
t2_scr = GEM_BFINS(CMPBEN, 0, t2_scr);
if (enable && ((tp4sp_m->psrc == 0xFFFF) || (tp4sp_m->pdst == 0xFFFF)))
t2_scr = GEM_BFINS(CMPCEN, 1, t2_scr);
else
t2_scr = GEM_BFINS(CMPCEN, 0, t2_scr);
gem_writel_n(bp, SCRT2, fs->location, t2_scr);
}
}
static void gem_prog_cmp_regs(struct macb *bp, struct ethtool_rx_flow_spec *fs)
{
struct ethtool_tcpip4_spec *tp4sp_v, *tp4sp_m;
uint16_t index = fs->location;
u32 w0, w1, t2_scr;
bool cmp_a = false;
bool cmp_b = false;
bool cmp_c = false;
tp4sp_v = &(fs->h_u.tcp_ip4_spec);
tp4sp_m = &(fs->m_u.tcp_ip4_spec);
/* ignore field if any masking set */
if (tp4sp_m->ip4src == 0xFFFFFFFF) {
/* 1st compare reg - IP source address */
w0 = 0;
w1 = 0;
w0 = tp4sp_v->ip4src;
w1 = GEM_BFINS(T2DISMSK, 1, w1); /* 32-bit compare */
w1 = GEM_BFINS(T2CMPOFST, GEM_T2COMPOFST_ETYPE, w1);
w1 = GEM_BFINS(T2OFST, ETYPE_SRCIP_OFFSET, w1);
gem_writel_n(bp, T2CMPW0, T2CMP_OFST(GEM_IP4SRC_CMP(index)), w0);
gem_writel_n(bp, T2CMPW1, T2CMP_OFST(GEM_IP4SRC_CMP(index)), w1);
cmp_a = true;
}
/* ignore field if any masking set */
if (tp4sp_m->ip4dst == 0xFFFFFFFF) {
/* 2nd compare reg - IP destination address */
w0 = 0;
w1 = 0;
w0 = tp4sp_v->ip4dst;
w1 = GEM_BFINS(T2DISMSK, 1, w1); /* 32-bit compare */
w1 = GEM_BFINS(T2CMPOFST, GEM_T2COMPOFST_ETYPE, w1);
w1 = GEM_BFINS(T2OFST, ETYPE_DSTIP_OFFSET, w1);
gem_writel_n(bp, T2CMPW0, T2CMP_OFST(GEM_IP4DST_CMP(index)), w0);
gem_writel_n(bp, T2CMPW1, T2CMP_OFST(GEM_IP4DST_CMP(index)), w1);
cmp_b = true;
}
/* ignore both port fields if masking set in both */
if ((tp4sp_m->psrc == 0xFFFF) || (tp4sp_m->pdst == 0xFFFF)) {
/* 3rd compare reg - source port, destination port */
w0 = 0;
w1 = 0;
w1 = GEM_BFINS(T2CMPOFST, GEM_T2COMPOFST_IPHDR, w1);
if (tp4sp_m->psrc == tp4sp_m->pdst) {
w0 = GEM_BFINS(T2MASK, tp4sp_v->psrc, w0);
w0 = GEM_BFINS(T2CMP, tp4sp_v->pdst, w0);
w1 = GEM_BFINS(T2DISMSK, 1, w1); /* 32-bit compare */
w1 = GEM_BFINS(T2OFST, IPHDR_SRCPORT_OFFSET, w1);
} else {
/* only one port definition */
w1 = GEM_BFINS(T2DISMSK, 0, w1); /* 16-bit compare */
w0 = GEM_BFINS(T2MASK, 0xFFFF, w0);
if (tp4sp_m->psrc == 0xFFFF) { /* src port */
w0 = GEM_BFINS(T2CMP, tp4sp_v->psrc, w0);
w1 = GEM_BFINS(T2OFST, IPHDR_SRCPORT_OFFSET, w1);
} else { /* dst port */
w0 = GEM_BFINS(T2CMP, tp4sp_v->pdst, w0);
w1 = GEM_BFINS(T2OFST, IPHDR_DSTPORT_OFFSET, w1);
}
}
gem_writel_n(bp, T2CMPW0, T2CMP_OFST(GEM_PORT_CMP(index)), w0);
gem_writel_n(bp, T2CMPW1, T2CMP_OFST(GEM_PORT_CMP(index)), w1);
cmp_c = true;
}
t2_scr = 0;
t2_scr = GEM_BFINS(QUEUE, (fs->ring_cookie) & 0xFF, t2_scr);
t2_scr = GEM_BFINS(ETHT2IDX, SCRT2_ETHT, t2_scr);
if (cmp_a)
t2_scr = GEM_BFINS(CMPA, GEM_IP4SRC_CMP(index), t2_scr);
if (cmp_b)
t2_scr = GEM_BFINS(CMPB, GEM_IP4DST_CMP(index), t2_scr);
if (cmp_c)
t2_scr = GEM_BFINS(CMPC, GEM_PORT_CMP(index), t2_scr);
gem_writel_n(bp, SCRT2, index, t2_scr);
}
static int gem_add_flow_filter(struct net_device *netdev,
struct ethtool_rxnfc *cmd)
{
struct macb *bp = netdev_priv(netdev);
struct ethtool_rx_flow_spec *fs = &cmd->fs;
struct ethtool_rx_fs_item *item, *newfs;
int ret = -EINVAL;
bool added = false;
newfs = kmalloc(sizeof(*newfs), GFP_KERNEL);
if (newfs == NULL)
return -ENOMEM;
memcpy(&newfs->fs, fs, sizeof(newfs->fs));
netdev_dbg(netdev,
"Adding flow filter entry,type=%u,queue=%u,loc=%u,src=%08X,dst=%08X,ps=%u,pd=%u\n",
fs->flow_type, (int)fs->ring_cookie, fs->location,
htonl(fs->h_u.tcp_ip4_spec.ip4src),
htonl(fs->h_u.tcp_ip4_spec.ip4dst),
htons(fs->h_u.tcp_ip4_spec.psrc), htons(fs->h_u.tcp_ip4_spec.pdst));
/* find correct place to add in list */
if (list_empty(&bp->rx_fs_list.list))
list_add(&newfs->list, &bp->rx_fs_list.list);
else {
list_for_each_entry(item, &bp->rx_fs_list.list, list) {
if (item->fs.location > newfs->fs.location) {
list_add_tail(&newfs->list, &item->list);
added = true;
break;
} else if (item->fs.location == fs->location) {
netdev_err(netdev, "Rule not added: location %d not free!\n",
fs->location);
ret = -EBUSY;
goto err;
}
}
if (!added)
list_add_tail(&newfs->list, &bp->rx_fs_list.list);
}
gem_prog_cmp_regs(bp, fs);
bp->rx_fs_list.count++;
/* enable filtering if NTUPLE on */
if (netdev->features & NETIF_F_NTUPLE)
gem_enable_flow_filters(bp, 1);
return 0;
err:
kfree(newfs);
return ret;
}
static int gem_del_flow_filter(struct net_device *netdev,
struct ethtool_rxnfc *cmd)
{
struct macb *bp = netdev_priv(netdev);
struct ethtool_rx_fs_item *item;
struct ethtool_rx_flow_spec *fs;
if (list_empty(&bp->rx_fs_list.list))
return -EINVAL;
list_for_each_entry(item, &bp->rx_fs_list.list, list) {
if (item->fs.location == cmd->fs.location) {
/* disable screener regs for the flow entry */
fs = &(item->fs);
netdev_dbg(netdev,
"Deleting flow filter entry,type=%u,queue=%u,loc=%u,src=%08X,dst=%08X,ps=%u,pd=%u\n",
fs->flow_type, (int)fs->ring_cookie, fs->location,
htonl(fs->h_u.tcp_ip4_spec.ip4src),
htonl(fs->h_u.tcp_ip4_spec.ip4dst),
htons(fs->h_u.tcp_ip4_spec.psrc),
htons(fs->h_u.tcp_ip4_spec.pdst));
gem_writel_n(bp, SCRT2, fs->location, 0);
list_del(&item->list);
kfree(item);
bp->rx_fs_list.count--;
return 0;
}
}
return -EINVAL;
}
static int gem_get_flow_entry(struct net_device *netdev,
struct ethtool_rxnfc *cmd)
{
struct macb *bp = netdev_priv(netdev);
struct ethtool_rx_fs_item *item;
list_for_each_entry(item, &bp->rx_fs_list.list, list) {
if (item->fs.location == cmd->fs.location) {
memcpy(&cmd->fs, &item->fs, sizeof(cmd->fs));
return 0;
}
}
return -EINVAL;
}
static int gem_get_all_flow_entries(struct net_device *netdev,
struct ethtool_rxnfc *cmd, u32 *rule_locs)
{
struct macb *bp = netdev_priv(netdev);
struct ethtool_rx_fs_item *item;
uint32_t cnt = 0;
list_for_each_entry(item, &bp->rx_fs_list.list, list) {
if (cnt == cmd->rule_cnt)
return -EMSGSIZE;
rule_locs[cnt] = item->fs.location;
cnt++;
}
cmd->data = bp->max_tuples;
cmd->rule_cnt = cnt;
return 0;
}
static int gem_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct macb *bp = netdev_priv(netdev);
int ret = 0;
switch (cmd->cmd) {
case ETHTOOL_GRXRINGS:
cmd->data = bp->num_queues;
break;
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = bp->rx_fs_list.count;
break;
case ETHTOOL_GRXCLSRULE:
ret = gem_get_flow_entry(netdev, cmd);
break;
case ETHTOOL_GRXCLSRLALL:
ret = gem_get_all_flow_entries(netdev, cmd, rule_locs);
break;
default:
netdev_err(netdev,
"Command parameter %d is not supported\n", cmd->cmd);
ret = -EOPNOTSUPP;
}
return ret;
}
static int gem_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
{
struct macb *bp = netdev_priv(netdev);
unsigned long flags;
int ret;
spin_lock_irqsave(&bp->rx_fs_lock, flags);
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
if ((cmd->fs.location >= bp->max_tuples)
|| (cmd->fs.ring_cookie >= bp->num_queues)) {
ret = -EINVAL;
break;
}
ret = gem_add_flow_filter(netdev, cmd);
break;
case ETHTOOL_SRXCLSRLDEL:
ret = gem_del_flow_filter(netdev, cmd);
break;
default:
netdev_err(netdev,
"Command parameter %d is not supported\n", cmd->cmd);
ret = -EOPNOTSUPP;
}
spin_unlock_irqrestore(&bp->rx_fs_lock, flags);
return ret;
}
static const struct ethtool_ops macb_ethtool_ops = {
.get_regs_len = macb_get_regs_len,
.get_regs = macb_get_regs,
@ -2693,6 +2995,8 @@ static const struct ethtool_ops gem_ethtool_ops = {
.set_link_ksettings = phy_ethtool_set_link_ksettings,
.get_ringparam = macb_get_ringparam,
.set_ringparam = macb_set_ringparam,
.get_rxnfc = gem_get_rxnfc,
.set_rxnfc = gem_set_rxnfc,
};
static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
@ -2750,6 +3054,12 @@ static int macb_set_features(struct net_device *netdev,
gem_writel(bp, NCFGR, netcfg);
}
/* RX Flow Filters */
if ((changed & NETIF_F_NTUPLE) && macb_is_gem(bp)) {
bool turn_on = features & NETIF_F_NTUPLE;
gem_enable_flow_filters(bp, turn_on);
}
return 0;
}
@ -2915,7 +3225,7 @@ static int macb_init(struct platform_device *pdev)
struct macb *bp = netdev_priv(dev);
struct macb_queue *queue;
int err;
u32 val;
u32 val, reg;
bp->tx_ring_size = DEFAULT_TX_RING_SIZE;
bp->rx_ring_size = DEFAULT_RX_RING_SIZE;
@ -3013,6 +3323,30 @@ static int macb_init(struct platform_device *pdev)
dev->hw_features &= ~NETIF_F_SG;
dev->features = dev->hw_features;
/* Check RX Flow Filters support.
* Max Rx flows set by availability of screeners & compare regs:
* each 4-tuple define requires 1 T2 screener reg + 3 compare regs
*/
reg = gem_readl(bp, DCFG8);
bp->max_tuples = min((GEM_BFEXT(SCR2CMP, reg) / 3),
GEM_BFEXT(T2SCR, reg));
if (bp->max_tuples > 0) {
/* also needs one ethtype match to check IPv4 */
if (GEM_BFEXT(SCR2ETH, reg) > 0) {
/* program this reg now */
reg = 0;
reg = GEM_BFINS(ETHTCMP, (uint16_t)ETH_P_IP, reg);
gem_writel_n(bp, ETHT, SCRT2_ETHT, reg);
/* Filtering is supported in hw but don't enable it in kernel now */
dev->hw_features |= NETIF_F_NTUPLE;
/* init Rx flow definitions */
INIT_LIST_HEAD(&bp->rx_fs_list.list);
bp->rx_fs_list.count = 0;
spin_lock_init(&bp->rx_fs_lock);
} else
bp->max_tuples = 0;
}
if (!(bp->caps & MACB_CAPS_USRIO_DISABLED)) {
val = 0;
if (bp->phy_interface == PHY_INTERFACE_MODE_RGMII)