Commit 3701e513 authored by Jesse Gross's avatar Jesse Gross Committed by David S. Miller

vlan: Centralize handling of hardware acceleration.

Currently each driver that is capable of vlan hardware acceleration
must be aware of the vlan groups that are configured and then pass
the stripped tag to a specialized receive function.  This is

different from other types of hardware offload in that it places a
significant amount of knowledge in the driver itself rather keeping
it in the networking core.

This makes vlan offloading function more similarly to other forms
of offloading (such as checksum offloading or TSO) by doing the
following:
* On receive, stripped vlans are passed directly to the network
core, without attempting to check for vlan groups or reconstructing
the header if no group
* vlans are made less special by folding the logic into the main
receive routines
* On transmit, the device layer will add the vlan header in software
if the hardware doesn't support it, instead of spreading that logic
out in upper layers, such as bonding.

There are a number of advantages to this:
* Fixes all bugs with drivers incorrectly dropping vlan headers at once.
* Avoids having to disable VLAN acceleration when in promiscuous mode
(good for bridging since it always puts devices in promiscuous mode).
* Keeps VLAN tag separate until given to ultimate consumer, which
avoids needing to do header reconstruction as in tg3 unless absolutely
necessary.
* Consolidates common code in core networking.
Signed-off-by: default avatarJesse Gross <jesse@nicira.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 65ac6a5f
...@@ -132,7 +132,7 @@ extern u16 vlan_dev_vlan_id(const struct net_device *dev); ...@@ -132,7 +132,7 @@ extern u16 vlan_dev_vlan_id(const struct net_device *dev);
extern int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp, extern int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp,
u16 vlan_tci, int polling); u16 vlan_tci, int polling);
extern void vlan_hwaccel_do_receive(struct sk_buff *skb); extern bool vlan_hwaccel_do_receive(struct sk_buff **skb);
extern gro_result_t extern gro_result_t
vlan_gro_receive(struct napi_struct *napi, struct vlan_group *grp, vlan_gro_receive(struct napi_struct *napi, struct vlan_group *grp,
unsigned int vlan_tci, struct sk_buff *skb); unsigned int vlan_tci, struct sk_buff *skb);
...@@ -166,8 +166,10 @@ static inline int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp, ...@@ -166,8 +166,10 @@ static inline int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp,
return NET_XMIT_SUCCESS; return NET_XMIT_SUCCESS;
} }
static inline void vlan_hwaccel_do_receive(struct sk_buff *skb) static inline bool vlan_hwaccel_do_receive(struct sk_buff **skb)
{ {
BUG();
return false;
} }
static inline gro_result_t static inline gro_result_t
......
...@@ -1768,7 +1768,6 @@ extern int netdev_rx_handler_register(struct net_device *dev, ...@@ -1768,7 +1768,6 @@ extern int netdev_rx_handler_register(struct net_device *dev,
void *rx_handler_data); void *rx_handler_data);
extern void netdev_rx_handler_unregister(struct net_device *dev); extern void netdev_rx_handler_unregister(struct net_device *dev);
extern void netif_nit_deliver(struct sk_buff *skb);
extern int dev_valid_name(const char *name); extern int dev_valid_name(const char *name);
extern int dev_ioctl(struct net *net, unsigned int cmd, void __user *); extern int dev_ioctl(struct net *net, unsigned int cmd, void __user *);
extern int dev_ethtool(struct net *net, struct ifreq *); extern int dev_ethtool(struct net *net, struct ifreq *);
......
...@@ -135,7 +135,7 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head) ...@@ -135,7 +135,7 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head)
vlan_gvrp_uninit_applicant(real_dev); vlan_gvrp_uninit_applicant(real_dev);
rcu_assign_pointer(real_dev->vlgrp, NULL); rcu_assign_pointer(real_dev->vlgrp, NULL);
if (real_dev->features & NETIF_F_HW_VLAN_RX) if (ops->ndo_vlan_rx_register)
ops->ndo_vlan_rx_register(real_dev, NULL); ops->ndo_vlan_rx_register(real_dev, NULL);
/* Free the group, after all cpu's are done. */ /* Free the group, after all cpu's are done. */
...@@ -156,11 +156,6 @@ int vlan_check_real_dev(struct net_device *real_dev, u16 vlan_id) ...@@ -156,11 +156,6 @@ int vlan_check_real_dev(struct net_device *real_dev, u16 vlan_id)
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
if ((real_dev->features & NETIF_F_HW_VLAN_RX) && !ops->ndo_vlan_rx_register) {
pr_info("8021q: device %s has buggy VLAN hw accel\n", name);
return -EOPNOTSUPP;
}
if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) && if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) &&
(!ops->ndo_vlan_rx_add_vid || !ops->ndo_vlan_rx_kill_vid)) { (!ops->ndo_vlan_rx_add_vid || !ops->ndo_vlan_rx_kill_vid)) {
pr_info("8021q: Device %s has buggy VLAN hw accel\n", name); pr_info("8021q: Device %s has buggy VLAN hw accel\n", name);
...@@ -213,7 +208,7 @@ int register_vlan_dev(struct net_device *dev) ...@@ -213,7 +208,7 @@ int register_vlan_dev(struct net_device *dev)
grp->nr_vlans++; grp->nr_vlans++;
if (ngrp) { if (ngrp) {
if (real_dev->features & NETIF_F_HW_VLAN_RX) if (ops->ndo_vlan_rx_register)
ops->ndo_vlan_rx_register(real_dev, ngrp); ops->ndo_vlan_rx_register(real_dev, ngrp);
rcu_assign_pointer(real_dev->vlgrp, ngrp); rcu_assign_pointer(real_dev->vlgrp, ngrp);
} }
......
...@@ -4,54 +4,29 @@ ...@@ -4,54 +4,29 @@
#include <linux/netpoll.h> #include <linux/netpoll.h>
#include "vlan.h" #include "vlan.h"
/* VLAN rx hw acceleration helper. This acts like netif_{rx,receive_skb}(). */ bool vlan_hwaccel_do_receive(struct sk_buff **skbp)
int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp,
u16 vlan_tci, int polling)
{ {
struct sk_buff *skb = *skbp;
u16 vlan_id = skb->vlan_tci & VLAN_VID_MASK;
struct net_device *vlan_dev; struct net_device *vlan_dev;
u16 vlan_id; struct vlan_rx_stats *rx_stats;
if (netpoll_rx(skb))
return NET_RX_DROP;
if (skb_bond_should_drop(skb, ACCESS_ONCE(skb->dev->master)))
skb->deliver_no_wcard = 1;
skb->skb_iif = skb->dev->ifindex; vlan_dev = vlan_find_dev(skb->dev, vlan_id);
__vlan_hwaccel_put_tag(skb, vlan_tci); if (!vlan_dev) {
vlan_id = vlan_tci & VLAN_VID_MASK; if (vlan_id)
vlan_dev = vlan_group_get_device(grp, vlan_id); skb->pkt_type = PACKET_OTHERHOST;
return false;
if (vlan_dev)
skb->dev = vlan_dev;
else if (vlan_id) {
if (!(skb->dev->flags & IFF_PROMISC))
goto drop;
skb->pkt_type = PACKET_OTHERHOST;
} }
return polling ? netif_receive_skb(skb) : netif_rx(skb); skb = *skbp = skb_share_check(skb, GFP_ATOMIC);
if (unlikely(!skb))
return false;
drop: skb->dev = vlan_dev;
atomic_long_inc(&skb->dev->rx_dropped); skb->priority = vlan_get_ingress_priority(vlan_dev, skb->vlan_tci);
dev_kfree_skb_any(skb);
return NET_RX_DROP;
}
EXPORT_SYMBOL(__vlan_hwaccel_rx);
void vlan_hwaccel_do_receive(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct vlan_rx_stats *rx_stats;
skb->dev = vlan_dev_real_dev(dev);
netif_nit_deliver(skb);
skb->dev = dev;
skb->priority = vlan_get_ingress_priority(dev, skb->vlan_tci);
skb->vlan_tci = 0; skb->vlan_tci = 0;
rx_stats = this_cpu_ptr(vlan_dev_info(dev)->vlan_rx_stats); rx_stats = this_cpu_ptr(vlan_dev_info(vlan_dev)->vlan_rx_stats);
u64_stats_update_begin(&rx_stats->syncp); u64_stats_update_begin(&rx_stats->syncp);
rx_stats->rx_packets++; rx_stats->rx_packets++;
...@@ -68,11 +43,13 @@ void vlan_hwaccel_do_receive(struct sk_buff *skb) ...@@ -68,11 +43,13 @@ void vlan_hwaccel_do_receive(struct sk_buff *skb)
* This allows the VLAN to have a different MAC than the * This allows the VLAN to have a different MAC than the
* underlying device, and still route correctly. */ * underlying device, and still route correctly. */
if (!compare_ether_addr(eth_hdr(skb)->h_dest, if (!compare_ether_addr(eth_hdr(skb)->h_dest,
dev->dev_addr)) vlan_dev->dev_addr))
skb->pkt_type = PACKET_HOST; skb->pkt_type = PACKET_HOST;
break; break;
} }
u64_stats_update_end(&rx_stats->syncp); u64_stats_update_end(&rx_stats->syncp);
return true;
} }
struct net_device *vlan_dev_real_dev(const struct net_device *dev) struct net_device *vlan_dev_real_dev(const struct net_device *dev)
...@@ -87,75 +64,27 @@ u16 vlan_dev_vlan_id(const struct net_device *dev) ...@@ -87,75 +64,27 @@ u16 vlan_dev_vlan_id(const struct net_device *dev)
} }
EXPORT_SYMBOL(vlan_dev_vlan_id); EXPORT_SYMBOL(vlan_dev_vlan_id);
static gro_result_t /* VLAN rx hw acceleration helper. This acts like netif_{rx,receive_skb}(). */
vlan_gro_common(struct napi_struct *napi, struct vlan_group *grp, int __vlan_hwaccel_rx(struct sk_buff *skb, struct vlan_group *grp,
unsigned int vlan_tci, struct sk_buff *skb) u16 vlan_tci, int polling)
{ {
struct sk_buff *p;
struct net_device *vlan_dev;
u16 vlan_id;
if (skb_bond_should_drop(skb, ACCESS_ONCE(skb->dev->master)))
skb->deliver_no_wcard = 1;
skb->skb_iif = skb->dev->ifindex;
__vlan_hwaccel_put_tag(skb, vlan_tci); __vlan_hwaccel_put_tag(skb, vlan_tci);
vlan_id = vlan_tci & VLAN_VID_MASK; return polling ? netif_receive_skb(skb) : netif_rx(skb);
vlan_dev = vlan_group_get_device(grp, vlan_id);
if (vlan_dev)
skb->dev = vlan_dev;
else if (vlan_id) {
if (!(skb->dev->flags & IFF_PROMISC))
goto drop;
skb->pkt_type = PACKET_OTHERHOST;
}
for (p = napi->gro_list; p; p = p->next) {
unsigned long diffs;
diffs = (unsigned long)p->dev ^ (unsigned long)skb->dev;
diffs |= compare_ether_header(skb_mac_header(p),
skb_gro_mac_header(skb));
NAPI_GRO_CB(p)->same_flow = !diffs;
NAPI_GRO_CB(p)->flush = 0;
}
return dev_gro_receive(napi, skb);
drop:
atomic_long_inc(&skb->dev->rx_dropped);
return GRO_DROP;
} }
EXPORT_SYMBOL(__vlan_hwaccel_rx);
gro_result_t vlan_gro_receive(struct napi_struct *napi, struct vlan_group *grp, gro_result_t vlan_gro_receive(struct napi_struct *napi, struct vlan_group *grp,
unsigned int vlan_tci, struct sk_buff *skb) unsigned int vlan_tci, struct sk_buff *skb)
{ {
if (netpoll_rx_on(skb)) __vlan_hwaccel_put_tag(skb, vlan_tci);
return vlan_hwaccel_receive_skb(skb, grp, vlan_tci) return napi_gro_receive(napi, skb);
? GRO_DROP : GRO_NORMAL;
skb_gro_reset_offset(skb);
return napi_skb_finish(vlan_gro_common(napi, grp, vlan_tci, skb), skb);
} }
EXPORT_SYMBOL(vlan_gro_receive); EXPORT_SYMBOL(vlan_gro_receive);
gro_result_t vlan_gro_frags(struct napi_struct *napi, struct vlan_group *grp, gro_result_t vlan_gro_frags(struct napi_struct *napi, struct vlan_group *grp,
unsigned int vlan_tci) unsigned int vlan_tci)
{ {
struct sk_buff *skb = napi_frags_skb(napi); __vlan_hwaccel_put_tag(napi->skb, vlan_tci);
return napi_gro_frags(napi);
if (!skb)
return GRO_DROP;
if (netpoll_rx_on(skb)) {
skb->protocol = eth_type_trans(skb, skb->dev);
return vlan_hwaccel_receive_skb(skb, grp, vlan_tci)
? GRO_DROP : GRO_NORMAL;
}
return napi_frags_finish(napi, skb,
vlan_gro_common(napi, grp, vlan_tci, skb));
} }
EXPORT_SYMBOL(vlan_gro_frags); EXPORT_SYMBOL(vlan_gro_frags);
...@@ -2789,33 +2789,6 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb, ...@@ -2789,33 +2789,6 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb,
} }
#endif #endif
/*
* netif_nit_deliver - deliver received packets to network taps
* @skb: buffer
*
* This function is used to deliver incoming packets to network
* taps. It should be used when the normal netif_receive_skb path
* is bypassed, for example because of VLAN acceleration.
*/
void netif_nit_deliver(struct sk_buff *skb)
{
struct packet_type *ptype;
if (list_empty(&ptype_all))
return;
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
rcu_read_lock();
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev)
deliver_skb(skb, ptype, skb->dev);
}
rcu_read_unlock();
}
/** /**
* netdev_rx_handler_register - register receive handler * netdev_rx_handler_register - register receive handler
* @dev: device to register a handler for * @dev: device to register a handler for
...@@ -2925,9 +2898,6 @@ static int __netif_receive_skb(struct sk_buff *skb) ...@@ -2925,9 +2898,6 @@ static int __netif_receive_skb(struct sk_buff *skb)
if (!netdev_tstamp_prequeue) if (!netdev_tstamp_prequeue)
net_timestamp_check(skb); net_timestamp_check(skb);
if (vlan_tx_tag_present(skb))
vlan_hwaccel_do_receive(skb);
/* if we've gotten here through NAPI, check netpoll */ /* if we've gotten here through NAPI, check netpoll */
if (netpoll_receive_skb(skb)) if (netpoll_receive_skb(skb))
return NET_RX_DROP; return NET_RX_DROP;
...@@ -2940,8 +2910,7 @@ static int __netif_receive_skb(struct sk_buff *skb) ...@@ -2940,8 +2910,7 @@ static int __netif_receive_skb(struct sk_buff *skb)
* be delivered to pkt handlers that are exact matches. Also * be delivered to pkt handlers that are exact matches. Also
* the deliver_no_wcard flag will be set. If packet handlers * the deliver_no_wcard flag will be set. If packet handlers
* are sensitive to duplicate packets these skbs will need to * are sensitive to duplicate packets these skbs will need to
* be dropped at the handler. The vlan accel path may have * be dropped at the handler.
* already set the deliver_no_wcard flag.
*/ */
null_or_orig = NULL; null_or_orig = NULL;
orig_dev = skb->dev; orig_dev = skb->dev;
...@@ -3000,6 +2969,18 @@ static int __netif_receive_skb(struct sk_buff *skb) ...@@ -3000,6 +2969,18 @@ static int __netif_receive_skb(struct sk_buff *skb)
goto out; goto out;
} }
if (vlan_tx_tag_present(skb)) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
if (vlan_hwaccel_do_receive(&skb)) {
ret = __netif_receive_skb(skb);
goto out;
} else if (unlikely(!skb))
goto out;
}
/* /*
* Make sure frames received on VLAN interfaces stacked on * Make sure frames received on VLAN interfaces stacked on
* bonding interfaces still make their way to any base bonding * bonding interfaces still make their way to any base bonding
...@@ -3264,6 +3245,7 @@ __napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) ...@@ -3264,6 +3245,7 @@ __napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
unsigned long diffs; unsigned long diffs;
diffs = (unsigned long)p->dev ^ (unsigned long)skb->dev; diffs = (unsigned long)p->dev ^ (unsigned long)skb->dev;
diffs |= p->vlan_tci ^ skb->vlan_tci;
diffs |= compare_ether_header(skb_mac_header(p), diffs |= compare_ether_header(skb_mac_header(p),
skb_gro_mac_header(skb)); skb_gro_mac_header(skb));
NAPI_GRO_CB(p)->same_flow = !diffs; NAPI_GRO_CB(p)->same_flow = !diffs;
...@@ -3323,6 +3305,7 @@ void napi_reuse_skb(struct napi_struct *napi, struct sk_buff *skb) ...@@ -3323,6 +3305,7 @@ void napi_reuse_skb(struct napi_struct *napi, struct sk_buff *skb)
{ {
__skb_pull(skb, skb_headlen(skb)); __skb_pull(skb, skb_headlen(skb));
skb_reserve(skb, NET_IP_ALIGN - skb_headroom(skb)); skb_reserve(skb, NET_IP_ALIGN - skb_headroom(skb));
skb->vlan_tci = 0;
napi->skb = skb; napi->skb = skb;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment