enic: Add Accelerated RFS support
This patch adds supports for Accelerated Receive Flow Steering. When the desired rx is different from current rq, for a flow, kernel calls the driver function enic_rx_flow_steer(). enic_rx_flow_steer adds a IP-TCP/UDP hardware filter. Driver registers a timer function enic_flow_may_expire. This function is called every HZ/4 seconds. In this function we check if the added filter has expired by calling rps_may_expire_flow(). If the flow has expired, it removes the hw filter. As of now adaptor supports only IPv4 - TCP/UDP filters. Signed-off-by: Govindarajulu Varadarajan <_govind@gmx.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
b6e97c132b
commit
a145df23ef
|
@ -99,6 +99,44 @@ struct enic_port_profile {
|
|||
u8 mac_addr[ETH_ALEN];
|
||||
};
|
||||
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
/* enic_rfs_fltr_node - rfs filter node in hash table
|
||||
* @@keys: IPv4 5 tuple
|
||||
* @flow_id: flow_id of clsf filter provided by kernel
|
||||
* @fltr_id: filter id of clsf filter returned by adaptor
|
||||
* @rq_id: desired rq index
|
||||
* @node: hlist_node
|
||||
*/
|
||||
struct enic_rfs_fltr_node {
|
||||
struct flow_keys keys;
|
||||
u32 flow_id;
|
||||
u16 fltr_id;
|
||||
u16 rq_id;
|
||||
struct hlist_node node;
|
||||
};
|
||||
|
||||
/* enic_rfs_flw_tbl - rfs flow table
|
||||
* @max: Maximum number of filters vNIC supports
|
||||
* @free: Number of free filters available
|
||||
* @toclean: hash table index to clean next
|
||||
* @ht_head: hash table list head
|
||||
* @lock: spin lock
|
||||
* @rfs_may_expire: timer function for enic_rps_may_expire_flow
|
||||
*/
|
||||
struct enic_rfs_flw_tbl {
|
||||
u16 max;
|
||||
int free;
|
||||
|
||||
#define ENIC_RFS_FLW_BITSHIFT (10)
|
||||
#define ENIC_RFS_FLW_MASK ((1 << ENIC_RFS_FLW_BITSHIFT) - 1)
|
||||
u16 toclean:ENIC_RFS_FLW_BITSHIFT;
|
||||
struct hlist_head ht_head[1 << ENIC_RFS_FLW_BITSHIFT];
|
||||
spinlock_t lock;
|
||||
struct timer_list rfs_may_expire;
|
||||
};
|
||||
|
||||
#endif /* CONFIG_RFS_ACCEL */
|
||||
|
||||
/* Per-instance private data structure */
|
||||
struct enic {
|
||||
struct net_device *netdev;
|
||||
|
@ -150,6 +188,9 @@ struct enic {
|
|||
/* completion queue cache line section */
|
||||
____cacheline_aligned struct vnic_cq cq[ENIC_CQ_MAX];
|
||||
unsigned int cq_count;
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
struct enic_rfs_flw_tbl rfs_h;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct device *enic_get_dev(struct enic *enic)
|
||||
|
|
|
@ -64,3 +64,216 @@ int enic_delfltr(struct enic *enic, u16 filter_id)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
void enic_flow_may_expire(unsigned long data)
|
||||
{
|
||||
struct enic *enic = (struct enic *)data;
|
||||
bool res;
|
||||
int j;
|
||||
|
||||
spin_lock(&enic->rfs_h.lock);
|
||||
for (j = 0; j < ENIC_CLSF_EXPIRE_COUNT; j++) {
|
||||
struct hlist_head *hhead;
|
||||
struct hlist_node *tmp;
|
||||
struct enic_rfs_fltr_node *n;
|
||||
|
||||
hhead = &enic->rfs_h.ht_head[enic->rfs_h.toclean++];
|
||||
hlist_for_each_entry_safe(n, tmp, hhead, node) {
|
||||
res = rps_may_expire_flow(enic->netdev, n->rq_id,
|
||||
n->flow_id, n->fltr_id);
|
||||
if (res) {
|
||||
res = enic_delfltr(enic, n->fltr_id);
|
||||
if (unlikely(res))
|
||||
continue;
|
||||
hlist_del(&n->node);
|
||||
kfree(n);
|
||||
enic->rfs_h.free++;
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock(&enic->rfs_h.lock);
|
||||
mod_timer(&enic->rfs_h.rfs_may_expire, jiffies + HZ/4);
|
||||
}
|
||||
|
||||
/* enic_rfs_flw_tbl_init - initialize enic->rfs_h members
|
||||
* @enic: enic data
|
||||
*/
|
||||
void enic_rfs_flw_tbl_init(struct enic *enic)
|
||||
{
|
||||
int i;
|
||||
|
||||
spin_lock_init(&enic->rfs_h.lock);
|
||||
for (i = 0; i <= ENIC_RFS_FLW_MASK; i++)
|
||||
INIT_HLIST_HEAD(&enic->rfs_h.ht_head[i]);
|
||||
enic->rfs_h.max = enic->config.num_arfs;
|
||||
enic->rfs_h.free = enic->rfs_h.max;
|
||||
enic->rfs_h.toclean = 0;
|
||||
init_timer(&enic->rfs_h.rfs_may_expire);
|
||||
enic->rfs_h.rfs_may_expire.function = enic_flow_may_expire;
|
||||
enic->rfs_h.rfs_may_expire.data = (unsigned long)enic;
|
||||
mod_timer(&enic->rfs_h.rfs_may_expire, jiffies + HZ/4);
|
||||
}
|
||||
|
||||
void enic_rfs_flw_tbl_free(struct enic *enic)
|
||||
{
|
||||
int i, res;
|
||||
|
||||
del_timer_sync(&enic->rfs_h.rfs_may_expire);
|
||||
spin_lock(&enic->rfs_h.lock);
|
||||
enic->rfs_h.free = 0;
|
||||
for (i = 0; i < (1 << ENIC_RFS_FLW_BITSHIFT); i++) {
|
||||
struct hlist_head *hhead;
|
||||
struct hlist_node *tmp;
|
||||
struct enic_rfs_fltr_node *n;
|
||||
|
||||
hhead = &enic->rfs_h.ht_head[i];
|
||||
hlist_for_each_entry_safe(n, tmp, hhead, node) {
|
||||
enic_delfltr(enic, n->fltr_id);
|
||||
hlist_del(&n->node);
|
||||
kfree(n);
|
||||
}
|
||||
}
|
||||
spin_unlock(&enic->rfs_h.lock);
|
||||
}
|
||||
|
||||
static struct enic_rfs_fltr_node *htbl_key_search(struct hlist_head *h,
|
||||
struct flow_keys *k)
|
||||
{
|
||||
struct enic_rfs_fltr_node *tpos;
|
||||
|
||||
hlist_for_each_entry(tpos, h, node)
|
||||
if (tpos->keys.src == k->src &&
|
||||
tpos->keys.dst == k->dst &&
|
||||
tpos->keys.ports == k->ports &&
|
||||
tpos->keys.ip_proto == k->ip_proto &&
|
||||
tpos->keys.n_proto == k->n_proto)
|
||||
return tpos;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int enic_rx_flow_steer(struct net_device *dev, const struct sk_buff *skb,
|
||||
u16 rxq_index, u32 flow_id)
|
||||
{
|
||||
struct flow_keys keys;
|
||||
struct enic_rfs_fltr_node *n;
|
||||
struct enic *enic;
|
||||
u16 tbl_idx;
|
||||
int res, i;
|
||||
|
||||
enic = netdev_priv(dev);
|
||||
res = skb_flow_dissect(skb, &keys);
|
||||
if (!res || keys.n_proto != htons(ETH_P_IP) ||
|
||||
(keys.ip_proto != IPPROTO_TCP && keys.ip_proto != IPPROTO_UDP))
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
tbl_idx = skb_get_hash_raw(skb) & ENIC_RFS_FLW_MASK;
|
||||
spin_lock(&enic->rfs_h.lock);
|
||||
n = htbl_key_search(&enic->rfs_h.ht_head[tbl_idx], &keys);
|
||||
|
||||
if (n) { /* entry already present */
|
||||
if (rxq_index == n->rq_id) {
|
||||
res = -EEXIST;
|
||||
goto ret_unlock;
|
||||
}
|
||||
|
||||
/* desired rq changed for the flow, we need to delete
|
||||
* old fltr and add new one
|
||||
*
|
||||
* The moment we delete the fltr, the upcoming pkts
|
||||
* are put it default rq based on rss. When we add
|
||||
* new filter, upcoming pkts are put in desired queue.
|
||||
* This could cause ooo pkts.
|
||||
*
|
||||
* Lets 1st try adding new fltr and then del old one.
|
||||
*/
|
||||
i = --enic->rfs_h.free;
|
||||
/* clsf tbl is full, we have to del old fltr first*/
|
||||
if (unlikely(i < 0)) {
|
||||
enic->rfs_h.free++;
|
||||
res = enic_delfltr(enic, n->fltr_id);
|
||||
if (unlikely(res < 0))
|
||||
goto ret_unlock;
|
||||
res = enic_addfltr_5t(enic, &keys, rxq_index);
|
||||
if (res < 0) {
|
||||
hlist_del(&n->node);
|
||||
enic->rfs_h.free++;
|
||||
goto ret_unlock;
|
||||
}
|
||||
/* add new fltr 1st then del old fltr */
|
||||
} else {
|
||||
int ret;
|
||||
|
||||
res = enic_addfltr_5t(enic, &keys, rxq_index);
|
||||
if (res < 0) {
|
||||
enic->rfs_h.free++;
|
||||
goto ret_unlock;
|
||||
}
|
||||
ret = enic_delfltr(enic, n->fltr_id);
|
||||
/* deleting old fltr failed. Add old fltr to list.
|
||||
* enic_flow_may_expire() will try to delete it later.
|
||||
*/
|
||||
if (unlikely(ret < 0)) {
|
||||
struct enic_rfs_fltr_node *d;
|
||||
struct hlist_head *head;
|
||||
|
||||
head = &enic->rfs_h.ht_head[tbl_idx];
|
||||
d = kmalloc(sizeof(*d), GFP_ATOMIC);
|
||||
if (d) {
|
||||
d->fltr_id = n->fltr_id;
|
||||
INIT_HLIST_NODE(&d->node);
|
||||
hlist_add_head(&d->node, head);
|
||||
}
|
||||
} else {
|
||||
enic->rfs_h.free++;
|
||||
}
|
||||
}
|
||||
n->rq_id = rxq_index;
|
||||
n->fltr_id = res;
|
||||
n->flow_id = flow_id;
|
||||
/* entry not present */
|
||||
} else {
|
||||
i = --enic->rfs_h.free;
|
||||
if (i <= 0) {
|
||||
enic->rfs_h.free++;
|
||||
res = -EBUSY;
|
||||
goto ret_unlock;
|
||||
}
|
||||
|
||||
n = kmalloc(sizeof(*n), GFP_ATOMIC);
|
||||
if (!n) {
|
||||
res = -ENOMEM;
|
||||
enic->rfs_h.free++;
|
||||
goto ret_unlock;
|
||||
}
|
||||
|
||||
res = enic_addfltr_5t(enic, &keys, rxq_index);
|
||||
if (res < 0) {
|
||||
kfree(n);
|
||||
enic->rfs_h.free++;
|
||||
goto ret_unlock;
|
||||
}
|
||||
n->rq_id = rxq_index;
|
||||
n->fltr_id = res;
|
||||
n->flow_id = flow_id;
|
||||
n->keys = keys;
|
||||
INIT_HLIST_NODE(&n->node);
|
||||
hlist_add_head(&n->node, &enic->rfs_h.ht_head[tbl_idx]);
|
||||
}
|
||||
|
||||
ret_unlock:
|
||||
spin_unlock(&enic->rfs_h.lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void enic_rfs_flw_tbl_init(struct enic *enic)
|
||||
{
|
||||
}
|
||||
|
||||
void enic_rfs_flw_tbl_free(struct enic *enic)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_RFS_ACCEL */
|
||||
|
|
|
@ -4,7 +4,16 @@
|
|||
#include "vnic_dev.h"
|
||||
#include "enic.h"
|
||||
|
||||
#define ENIC_CLSF_EXPIRE_COUNT 128
|
||||
|
||||
int enic_addfltr_5t(struct enic *enic, struct flow_keys *keys, u16 rq);
|
||||
int enic_delfltr(struct enic *enic, u16 filter_id);
|
||||
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
void enic_rfs_flw_tbl_init(struct enic *enic);
|
||||
void enic_rfs_flw_tbl_free(struct enic *enic);
|
||||
int enic_rx_flow_steer(struct net_device *dev, const struct sk_buff *skb,
|
||||
u16 rxq_index, u32 flow_id);
|
||||
#endif /* CONFIG_RFS_ACCEL */
|
||||
|
||||
#endif /* _ENIC_CLSF_H_ */
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "enic.h"
|
||||
#include "enic_dev.h"
|
||||
#include "enic_pp.h"
|
||||
#include "enic_clsf.h"
|
||||
|
||||
#define ENIC_NOTIFY_TIMER_PERIOD (2 * HZ)
|
||||
#define WQ_ENET_MAX_DESC_LEN (1 << WQ_ENET_LEN_BITS)
|
||||
|
@ -1546,6 +1547,7 @@ static int enic_open(struct net_device *netdev)
|
|||
vnic_intr_unmask(&enic->intr[i]);
|
||||
|
||||
enic_notify_timer_start(enic);
|
||||
enic_rfs_flw_tbl_init(enic);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -1572,6 +1574,7 @@ static int enic_stop(struct net_device *netdev)
|
|||
enic_synchronize_irqs(enic);
|
||||
|
||||
del_timer_sync(&enic->notify_timer);
|
||||
enic_rfs_flw_tbl_free(enic);
|
||||
|
||||
enic_dev_disable(enic);
|
||||
|
||||
|
@ -2064,6 +2067,9 @@ static const struct net_device_ops enic_netdev_dynamic_ops = {
|
|||
#ifdef CONFIG_NET_POLL_CONTROLLER
|
||||
.ndo_poll_controller = enic_poll_controller,
|
||||
#endif
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
.ndo_rx_flow_steer = enic_rx_flow_steer,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct net_device_ops enic_netdev_ops = {
|
||||
|
@ -2084,6 +2090,9 @@ static const struct net_device_ops enic_netdev_ops = {
|
|||
#ifdef CONFIG_NET_POLL_CONTROLLER
|
||||
.ndo_poll_controller = enic_poll_controller,
|
||||
#endif
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
.ndo_rx_flow_steer = enic_rx_flow_steer,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void enic_dev_deinit(struct enic *enic)
|
||||
|
@ -2429,6 +2438,10 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
|
||||
netdev->features |= netdev->hw_features;
|
||||
|
||||
#ifdef CONFIG_RFS_ACCEL
|
||||
netdev->hw_features |= NETIF_F_NTUPLE;
|
||||
#endif
|
||||
|
||||
if (using_dac)
|
||||
netdev->features |= NETIF_F_HIGHDMA;
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ int enic_get_vnic_config(struct enic *enic)
|
|||
GET_CONFIG(intr_mode);
|
||||
GET_CONFIG(intr_timer_usec);
|
||||
GET_CONFIG(loop_tag);
|
||||
GET_CONFIG(num_arfs);
|
||||
|
||||
c->wq_desc_count =
|
||||
min_t(u32, ENIC_MAX_WQ_DESCS,
|
||||
|
|
|
@ -32,6 +32,8 @@ struct vnic_enet_config {
|
|||
char devname[16];
|
||||
u32 intr_timer_usec;
|
||||
u16 loop_tag;
|
||||
u16 vf_rq_count;
|
||||
u16 num_arfs;
|
||||
};
|
||||
|
||||
#define VENETF_TSO 0x1 /* TSO enabled */
|
||||
|
|
Loading…
Reference in New Issue