/*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
* Distributed under the terms of the GNU GPL version 2.
*
* Significant portions of this file may be copied from parts of the Linux
#include "checksum.h"
#include "datapath.h"
+#include "vlan.h"
#include "vport-internal_dev.h"
#include "vport-netdev.h"
-#include "compat.h"
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) && \
+ !defined(HAVE_VLAN_BUG_WORKAROUND)
+#include <linux/module.h>
+
+static int vlan_tso __read_mostly = 0;
+module_param(vlan_tso, int, 0644);
+MODULE_PARM_DESC(vlan_tso, "Enable TSO for VLAN packets");
+#else
+#define vlan_tso true
+#endif
/* If the native device stats aren't 64 bit use the vport stats tracking instead. */
#define USE_VPORT_STATS (sizeof(((struct net_device_stats *)0)->rx_bytes) < sizeof(u64))
static void netdev_port_receive(struct vport *vport, struct sk_buff *skb);
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+/* Called with rcu_read_lock and bottom-halves disabled. */
+static rx_handler_result_t netdev_frame_hook(struct sk_buff **pskb)
+{
+ struct sk_buff *skb = *pskb;
+ struct vport *vport;
+
+ if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
+ return RX_HANDLER_PASS;
+
+ vport = netdev_get_vport(skb->dev);
+
+ netdev_port_receive(vport, skb);
+
+ return RX_HANDLER_CONSUMED;
+}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
/* Called with rcu_read_lock and bottom-halves disabled. */
static struct sk_buff *netdev_frame_hook(struct sk_buff *skb)
{
skb_push(skb, ETH_HLEN);
compute_ip_summed(skb, false);
+ vlan_copy_skb_tci(skb);
vport_receive(vport, skb);
}
+static bool dev_supports_vlan_tx(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+ /* Software fallback means every device supports vlan_tci on TX. */
+ return true;
+#elif defined(HAVE_VLAN_BUG_WORKAROUND)
+ return dev->features & NETIF_F_HW_VLAN_TX;
+#else
+ /* Assume that the driver is buggy. */
+ return false;
+#endif
+}
+
static int netdev_send(struct vport *vport, struct sk_buff *skb)
{
struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
- int len = skb->len;
+ int len;
skb->dev = netdev_vport->dev;
forward_ip_summed(skb);
+
+ if (vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev)) {
+ int err;
+ int features = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+ features = skb->dev->features & skb->dev->vlan_features;
+#endif
+
+ err = vswitch_skb_checksum_setup(skb);
+ if (unlikely(err)) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ if (!vlan_tso)
+ features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
+ NETIF_F_UFO | NETIF_F_FSO);
+
+ if (skb_is_gso(skb) &&
+ (!skb_gso_ok(skb, features) ||
+ unlikely(skb->ip_summed != CHECKSUM_PARTIAL))) {
+ struct sk_buff *nskb;
+
+ nskb = skb_gso_segment(skb, features);
+ if (!nskb) {
+ if (unlikely(skb_cloned(skb) &&
+ pskb_expand_head(skb, 0, 0, GFP_ATOMIC))) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ skb_shinfo(skb)->gso_type &= ~SKB_GSO_DODGY;
+ goto tag;
+ }
+
+ kfree_skb(skb);
+ skb = nskb;
+ if (IS_ERR(skb))
+ return 0;
+
+ len = 0;
+ do {
+ nskb = skb->next;
+ skb->next = NULL;
+
+ skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
+ if (likely(skb)) {
+ len += skb->len;
+ vlan_set_tci(skb, 0);
+ dev_queue_xmit(skb);
+ }
+
+ skb = nskb;
+ } while (skb);
+
+ return len;
+ }
+
+tag:
+ skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
+ if (unlikely(!skb))
+ return 0;
+ vlan_set_tci(skb, 0);
+ }
+
+ len = skb->len;
dev_queue_xmit(skb);
return len;
/* Returns null if this device is not attached to a datapath. */
struct vport *netdev_get_vport(struct net_device *dev)
{
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
- /* XXX: The bridge code may have registered the data.
- * So check that the handler pointer is the datapath's.
- * Once the merge is done and IFF_OVS_DATAPATH stops
- * being the same value as IFF_BRIDGE_PORT the check can
- * simply be netdev_vport->dev->priv_flags & IFF_OVS_DATAPATH. */
- if (rcu_dereference(dev->rx_handler) != netdev_frame_hook)
+#ifdef IFF_BRIDGE_PORT
+#if IFF_BRIDGE_PORT != IFF_OVS_DATAPATH
+ if (likely(dev->priv_flags & IFF_OVS_DATAPATH))
+#else
+ if (likely(rcu_access_pointer(dev->rx_handler) == netdev_frame_hook))
+#endif
+ return (struct vport *)rcu_dereference_rtnl(dev->rx_handler_data);
+ else
return NULL;
- return (struct vport *)rcu_dereference(dev->rx_handler_data);
#else
- return (struct vport *)rcu_dereference(dev->br_port);
+ return (struct vport *)rcu_dereference_rtnl(dev->br_port);
#endif
}
const struct vport_ops netdev_vport_ops = {
- .type = "netdev",
+ .type = ODP_VPORT_TYPE_NETDEV,
.flags = (VPORT_F_REQUIRED |
(USE_VPORT_STATS ? VPORT_F_GEN_STATS : 0)),
.init = netdev_init,