Implement ethernet device support
authorAdrian-Ken Rueegsegger <ken@codelabs.ch>
Tue, 28 Feb 2017 22:38:11 +0000 (23:38 +0100)
committerAdrian-Ken Rueegsegger <ken@codelabs.ch>
Mon, 29 May 2017 15:48:21 +0000 (17:48 +0200)
The new module flag 'eth_dev' can be used to specify that the device
implements an ethernet device. This allows users to operate on layer 2
ethernet frames and gives them full control over the ethernet header.

The length of the ethernet packet including the 12-byte header is
appended to the data to enable readers to quickly determine the data
size. Note that the information is not prepended to simplify header
processing by the Linux kernel. The ARP code expects the device to have
a hard_header_length equal to ETH_HLEN.

internal.h
net.c
reader.c
writer.c

index b1269ae..3858b61 100644 (file)
@@ -75,6 +75,7 @@ struct dev_info {
  */
 enum muennet_flags {
        MUENNET_HDR = 1, /**< add network information needed for IPv4/IPv6 */
+       ETH_DEV     = 2, /**< treat interface as ethernet device */
 };
 
 /**
@@ -97,6 +98,8 @@ struct flag_name {
 static const struct flag_name flag_names[] = {
        { .name = "net_hdr",
          .value = MUENNET_HDR },
+       { .name = "eth_dev",
+         .value = ETH_DEV },
        {},
 };
 
@@ -114,6 +117,17 @@ struct net_hdr {
 /*@}*/
 
 /**
+ * @brief Header format for ethernet device flag.
+ *
+ * This header encodes additional information in the data transferred via
+ * the shared channel to avoid having to guess some of the packet properties.
+ */
+struct eth_hdr {
+       u16 length;  /**< length of the ethernet packet (header + payload) */
+} __packed;
+/*@}*/
+
+/**
  * @defgroup debug Debugging and tracing tools
  *
  * This module can be split into two parts. The functions are used to setup the
diff --git a/net.c b/net.c
index 70d7a5d..3a7a848 100644 (file)
--- a/net.c
+++ b/net.c
@@ -23,6 +23,7 @@
 
 #include <linux/module.h>
 #include <linux/if_arp.h>
+#include <linux/etherdevice.h>
 
 #include "internal.h"
 
@@ -185,6 +186,15 @@ static u32 muennet_get_link(struct net_device *dev)
                dev_info->reader_element_size != 0);
 }
 
+static void muennet_mclist(struct net_device *dev)
+{
+       /*
+        * This callback is supposed to deal with mc filter in
+        * _rx_ path and has nothing to do with the _tx_ path.
+        * In rx path we always accept everything userspace gives us.
+        */
+}
+
 /**
  * @brief ethtool operations
  *
@@ -223,6 +233,17 @@ static const struct net_device_ops muennet_device_ops = {
        .ndo_get_stats  = muennet_stats,
 };
 
+static const struct net_device_ops muennet_dev_eth_ops = {
+       .ndo_open       = muennet_open,
+       .ndo_stop       = muennet_close,
+       .ndo_start_xmit = muennet_xmit,
+       .ndo_get_stats  = muennet_stats,
+       .ndo_set_rx_mode    = muennet_mclist,
+       .ndo_set_mac_address = eth_mac_addr,
+       .ndo_validate_addr = eth_validate_addr,
+       .ndo_features_check = passthru_features_check,
+};
+
 /**
  * @brief Setup the network interface.
  *
@@ -233,7 +254,13 @@ static const struct net_device_ops muennet_device_ops = {
  */
 static void muennet_setup(struct net_device *dev)
 {
-       dev->netdev_ops  = &muennet_device_ops;
+       const struct dev_info *dev_info = netdev_priv(dev);
+
+       if (dev_info->flags & ETH_DEV)
+               dev->netdev_ops = &muennet_dev_eth_ops;
+       else
+               dev->netdev_ops = &muennet_device_ops;
+
        dev->ethtool_ops = &muennet_ethtool_ops;
        dev->destructor  = muennet_free;
 }
@@ -285,15 +312,25 @@ static int add_device(const char *device_name,
                goto err;
 
        /* do further initialization of device */
-       dev->hard_header_len = 0;
-       if (flags & MUENNET_HDR)
-               dev->hard_header_len += sizeof(struct net_hdr);
 
-       dev->addr_len = 0;
-       dev->mtu = mtu;
+       if (flags & ETH_DEV) {
+               ether_setup(dev);
+               dev->priv_flags &= ~IFF_TX_SKB_SHARING;
+               dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+               eth_hw_addr_random(dev);
+
+               /* Additional information is appended to skb */
+               dev->needed_tailroom = sizeof(struct eth_hdr);
+       } else {
+               dev->type = ARPHRD_NONE;
+               dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+               dev->addr_len = 0;
+               dev->hard_header_len = 0;
+               dev->mtu = mtu;
+       }
 
-       dev->type = ARPHRD_NONE;
-       dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+       if (flags & MUENNET_HDR)
+               dev->hard_header_len += sizeof(struct net_hdr);
 
        dev_info = netdev_priv(dev);
        dev_info->dev = dev;
index 58bb6b5..7e6af2a 100644 (file)
--- a/reader.c
+++ b/reader.c
@@ -20,6 +20,7 @@
 #include <linux/ip.h>
 #include <linux/ipv6.h>
 #include <linux/workqueue.h>
+#include <linux/etherdevice.h>
 
 #include "internal.h"
 
@@ -68,6 +69,8 @@ static int get_skb(struct sk_buff **skb, struct dev_info *dev_info)
 
        if (dev_info->flags & MUENNET_HDR)
                size += sizeof(struct net_hdr);
+       else if (dev_info->flags & ETH_DEV)
+               size += sizeof(struct eth_hdr);
 
        new_skb = alloc_skb(size, GFP_KERNEL);
        if (new_skb == NULL)
@@ -160,6 +163,14 @@ static void muennet_reader_work(struct work_struct *work)
                                                + 40;
                                        break;
                                }
+                       } else if (dev_info->flags & ETH_DEV) {
+                               struct eth_hdr *hdr = (struct eth_hdr *)
+                                       (skb->data +
+                                        dev_info->reader_element_size -
+                                        sizeof(struct eth_hdr));
+                               skb_put(skb, hdr->length);
+                               protocol = eth_type_trans(skb, dev_info->dev);
+                               len = 0;
                        } else
                                len = dev_info->reader_element_size;
 
@@ -178,7 +189,8 @@ static void muennet_reader_work(struct work_struct *work)
                         * some data first
                         */
                        skb->dev = dev_info->dev;
-                       skb_put(skb, len);
+                       if (len > 0)
+                               skb_put(skb, len);
                        skb->protocol = protocol;
 
                        /* process and update stats */
index 6bee71b..137c2a9 100644 (file)
--- a/writer.c
+++ b/writer.c
@@ -39,6 +39,8 @@ static size_t gross_packet_size(size_t net_size, unsigned long flags)
        /* raw / annotated IP transfers? */
        if (flags & MUENNET_HDR)
                element_size += sizeof(struct net_hdr);
+       else if (flags & ETH_DEV)
+               element_size += ETH_HLEN + sizeof(struct eth_hdr);
 
        return element_size;
 }
@@ -234,7 +236,7 @@ int muennet_xmit(struct sk_buff *skb, struct net_device *dev)
                }
        }
 
-       if ((dev_info->flags & MUENNET_HDR)) {
+       if (dev_info->flags & MUENNET_HDR) {
                int len = skb->len;
                struct net_hdr *hdr;
 
@@ -266,6 +268,26 @@ int muennet_xmit(struct sk_buff *skb, struct net_device *dev)
                        dev_kfree_skb(skb);
                        return NET_XMIT_SUCCESS;
                }
+       } else if (dev_info->flags & ETH_DEV) {
+               int len = skb->len;
+               struct eth_hdr *hdr;
+
+               max_size = dev_info->writer_element_size -
+                       sizeof(struct eth_hdr);
+               if (len > max_size ||
+                   skb_tailroom(skb) < sizeof(struct eth_hdr)) {
+                       netdev_warn(dev_info->dev,
+                                   "Oversized packet dropped (size = %u, max = %lu, MTU = %u)\n",
+                                   len, max_size, dev_info->mtu);
+                       dev_info->stats.tx_dropped++;
+                       spin_unlock_irqrestore(&dev_info->writer_lock, flags);
+                       dev_kfree_skb(skb);
+                       return NET_XMIT_SUCCESS;
+               }
+
+               hdr = (struct eth_hdr *)(skb->data - sizeof(struct eth_hdr) +
+                                        dev_info->writer_element_size);
+               hdr->length = len;
        }
 
        muen_channel_write(dev_info->channel_out, skb->data);