]> bbs.cooldavid.org Git - net-next-2.6.git/commitdiff
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 27 Jul 2010 16:21:00 +0000 (09:21 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 27 Jul 2010 16:21:00 +0000 (09:21 -0700)
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6:
  s2io: fixing DBG_PRINT() macro
  ath9k: fix dma direction for map/unmap in ath_rx_tasklet
  net: dev_forward_skb should call nf_reset
  net sched: fix race in mirred device removal
  tun: avoid BUG, dump packet on GSO errors
  bonding: set device in RLB ARP packet handler
  wimax/i2400m: Add PID & VID for Intel WiMAX 6250
  ipv6: Don't add routes to ipv6 disabled interfaces.
  net: Fix skb_copy_expand() handling of ->csum_start
  net: Fix corruption of skb csum field in pskb_expand_head() of net/core/skbuff.c
  macvtap: Limit packet queue length
  ixgbe/igb: catch invalid VF settings
  bnx2x: Advance a module version
  bnx2x: Protect statistics ramrod and sequence number
  bnx2x: Protect a SM state change
  wireless: use netif_rx_ni in ieee80211_send_layer2_update

19 files changed:
drivers/net/bnx2x.h
drivers/net/bnx2x_main.c
drivers/net/bonding/bond_alb.c
drivers/net/igb/igb_main.c
drivers/net/ixgbe/ixgbe_main.c
drivers/net/macvlan.c
drivers/net/macvtap.c
drivers/net/s2io.h
drivers/net/tun.c
drivers/net/wimax/i2400m/i2400m-usb.h
drivers/net/wimax/i2400m/usb.c
drivers/net/wireless/ath/ath9k/recv.c
include/linux/if_macvlan.h
include/net/tc_act/tc_mirred.h
net/core/dev.c
net/core/skbuff.c
net/ipv6/addrconf.c
net/mac80211/cfg.c
net/sched/act_mirred.c

index 8bd23687c530f2e244c7050091d14c05b8fa7901..bb0872a633157157501b5c2312cc6a73d642ed1b 100644 (file)
@@ -1062,6 +1062,10 @@ struct bnx2x {
 
        /* used to synchronize stats collecting */
        int                     stats_state;
+
+       /* used for synchronization of concurrent threads statistics handling */
+       spinlock_t              stats_lock;
+
        /* used by dmae command loader */
        struct dmae_command     stats_dmae;
        int                     executer_idx;
index 57ff5b3bcce6fcd58edc438998ea1bf1ca08ee63..46167c0817274b793348039ea50db4754003b98e 100644 (file)
@@ -57,8 +57,8 @@
 #include "bnx2x_init_ops.h"
 #include "bnx2x_dump.h"
 
-#define DRV_MODULE_VERSION     "1.52.53-1"
-#define DRV_MODULE_RELDATE     "2010/18/04"
+#define DRV_MODULE_VERSION     "1.52.53-2"
+#define DRV_MODULE_RELDATE     "2010/21/07"
 #define BNX2X_BC_VER           0x040200
 
 #include <linux/firmware.h>
@@ -3789,6 +3789,8 @@ static void bnx2x_storm_stats_post(struct bnx2x *bp)
                struct eth_query_ramrod_data ramrod_data = {0};
                int i, rc;
 
+               spin_lock_bh(&bp->stats_lock);
+
                ramrod_data.drv_counter = bp->stats_counter++;
                ramrod_data.collect_port = bp->port.pmf ? 1 : 0;
                for_each_queue(bp, i)
@@ -3802,6 +3804,8 @@ static void bnx2x_storm_stats_post(struct bnx2x *bp)
                        bp->spq_left++;
                        bp->stats_pending = 1;
                }
+
+               spin_unlock_bh(&bp->stats_lock);
        }
 }
 
@@ -4367,6 +4371,14 @@ static int bnx2x_storm_stats_update(struct bnx2x *bp)
        struct host_func_stats *fstats = bnx2x_sp(bp, func_stats);
        struct bnx2x_eth_stats *estats = &bp->eth_stats;
        int i;
+       u16 cur_stats_counter;
+
+       /* Make sure we use the value of the counter
+        * used for sending the last stats ramrod.
+        */
+       spin_lock_bh(&bp->stats_lock);
+       cur_stats_counter = bp->stats_counter - 1;
+       spin_unlock_bh(&bp->stats_lock);
 
        memcpy(&(fstats->total_bytes_received_hi),
               &(bnx2x_sp(bp, func_stats_base)->total_bytes_received_hi),
@@ -4394,25 +4406,22 @@ static int bnx2x_storm_stats_update(struct bnx2x *bp)
                u32 diff;
 
                /* are storm stats valid? */
-               if ((u16)(le16_to_cpu(xclient->stats_counter) + 1) !=
-                                                       bp->stats_counter) {
+               if (le16_to_cpu(xclient->stats_counter) != cur_stats_counter) {
                        DP(BNX2X_MSG_STATS, "[%d] stats not updated by xstorm"
                           "  xstorm counter (0x%x) != stats_counter (0x%x)\n",
-                          i, xclient->stats_counter, bp->stats_counter);
+                          i, xclient->stats_counter, cur_stats_counter + 1);
                        return -1;
                }
-               if ((u16)(le16_to_cpu(tclient->stats_counter) + 1) !=
-                                                       bp->stats_counter) {
+               if (le16_to_cpu(tclient->stats_counter) != cur_stats_counter) {
                        DP(BNX2X_MSG_STATS, "[%d] stats not updated by tstorm"
                           "  tstorm counter (0x%x) != stats_counter (0x%x)\n",
-                          i, tclient->stats_counter, bp->stats_counter);
+                          i, tclient->stats_counter, cur_stats_counter + 1);
                        return -2;
                }
-               if ((u16)(le16_to_cpu(uclient->stats_counter) + 1) !=
-                                                       bp->stats_counter) {
+               if (le16_to_cpu(uclient->stats_counter) != cur_stats_counter) {
                        DP(BNX2X_MSG_STATS, "[%d] stats not updated by ustorm"
                           "  ustorm counter (0x%x) != stats_counter (0x%x)\n",
-                          i, uclient->stats_counter, bp->stats_counter);
+                          i, uclient->stats_counter, cur_stats_counter + 1);
                        return -4;
                }
 
@@ -4849,16 +4858,18 @@ static const struct {
 
 static void bnx2x_stats_handle(struct bnx2x *bp, enum bnx2x_stats_event event)
 {
-       enum bnx2x_stats_state state = bp->stats_state;
+       enum bnx2x_stats_state state;
 
        if (unlikely(bp->panic))
                return;
 
-       bnx2x_stats_stm[state][event].action(bp);
+       /* Protect a state change flow */
+       spin_lock_bh(&bp->stats_lock);
+       state = bp->stats_state;
        bp->stats_state = bnx2x_stats_stm[state][event].next_state;
+       spin_unlock_bh(&bp->stats_lock);
 
-       /* Make sure the state has been "changed" */
-       smp_wmb();
+       bnx2x_stats_stm[state][event].action(bp);
 
        if ((event != STATS_EVENT_UPDATE) || netif_msg_timer(bp))
                DP(BNX2X_MSG_STATS, "state %d -> event %d -> state %d\n",
@@ -9908,6 +9919,7 @@ static int __devinit bnx2x_init_bp(struct bnx2x *bp)
 
        mutex_init(&bp->port.phy_mutex);
        mutex_init(&bp->fw_mb_mutex);
+       spin_lock_init(&bp->stats_lock);
 #ifdef BCM_CNIC
        mutex_init(&bp->cnic_mutex);
 #endif
index df483076eda6df4284e9e576fc522a0ff022af11..8d7dfd2f1e90cf1bd7d32fcc1a0aa1da87a61332 100644 (file)
@@ -822,7 +822,7 @@ static int rlb_initialize(struct bonding *bond)
 
        /*initialize packet type*/
        pk_type->type = cpu_to_be16(ETH_P_ARP);
-       pk_type->dev = NULL;
+       pk_type->dev = bond->dev;
        pk_type->func = rlb_arp_recv;
 
        /* register to receive ARPs */
index 3881918f5382c4e1305e4c5127bfbb2180bb85cd..cea37e0837ff64d3ed4ae16ccc78f85557be9700 100644 (file)
@@ -1722,6 +1722,15 @@ static int __devinit igb_probe(struct pci_dev *pdev,
        u16 eeprom_apme_mask = IGB_EEPROM_APME;
        u32 part_num;
 
+       /* Catch broken hardware that put the wrong VF device ID in
+        * the PCIe SR-IOV capability.
+        */
+       if (pdev->is_virtfn) {
+               WARN(1, KERN_ERR "%s (%hx:%hx) should not be a VF!\n",
+                    pci_name(pdev), pdev->vendor, pdev->device);
+               return -EINVAL;
+       }
+
        err = pci_enable_device_mem(pdev);
        if (err)
                return err;
index 7b5d9764f317c3c776712009dde301c27d9c3b99..74d9b6df3029f3c67e9e85ff5a95a14c5ef6ff0b 100644 (file)
@@ -6492,6 +6492,15 @@ static int __devinit ixgbe_probe(struct pci_dev *pdev,
 #endif
        u32 part_num, eec;
 
+       /* Catch broken hardware that put the wrong VF device ID in
+        * the PCIe SR-IOV capability.
+        */
+       if (pdev->is_virtfn) {
+               WARN(1, KERN_ERR "%s (%hx:%hx) should not be a VF!\n",
+                    pci_name(pdev), pdev->vendor, pdev->device);
+               return -EINVAL;
+       }
+
        err = pci_enable_device_mem(pdev);
        if (err)
                return err;
index 87e8d4cb40579a8947419446012b38896b29064c..f15fe2cf72ae1261fe6d0cde6ca263c2d0c54809 100644 (file)
@@ -499,7 +499,7 @@ static const struct net_device_ops macvlan_netdev_ops = {
        .ndo_validate_addr      = eth_validate_addr,
 };
 
-static void macvlan_setup(struct net_device *dev)
+void macvlan_common_setup(struct net_device *dev)
 {
        ether_setup(dev);
 
@@ -508,6 +508,12 @@ static void macvlan_setup(struct net_device *dev)
        dev->destructor         = free_netdev;
        dev->header_ops         = &macvlan_hard_header_ops,
        dev->ethtool_ops        = &macvlan_ethtool_ops;
+}
+EXPORT_SYMBOL_GPL(macvlan_common_setup);
+
+static void macvlan_setup(struct net_device *dev)
+{
+       macvlan_common_setup(dev);
        dev->tx_queue_len       = 0;
 }
 
@@ -705,7 +711,6 @@ int macvlan_link_register(struct rtnl_link_ops *ops)
        /* common fields */
        ops->priv_size          = sizeof(struct macvlan_dev);
        ops->get_tx_queues      = macvlan_get_tx_queues;
-       ops->setup              = macvlan_setup;
        ops->validate           = macvlan_validate;
        ops->maxtype            = IFLA_MACVLAN_MAX;
        ops->policy             = macvlan_policy;
@@ -719,6 +724,7 @@ EXPORT_SYMBOL_GPL(macvlan_link_register);
 
 static struct rtnl_link_ops macvlan_link_ops = {
        .kind           = "macvlan",
+       .setup          = macvlan_setup,
        .newlink        = macvlan_newlink,
        .dellink        = macvlan_dellink,
 };
index a8a94e2f6ddcfc04917ab2ec39305ac26e69ecf2..ff02b836c3c4a3d23c6b78fc707508b40732d9f8 100644 (file)
@@ -180,11 +180,18 @@ static int macvtap_forward(struct net_device *dev, struct sk_buff *skb)
 {
        struct macvtap_queue *q = macvtap_get_queue(dev, skb);
        if (!q)
-               return -ENOLINK;
+               goto drop;
+
+       if (skb_queue_len(&q->sk.sk_receive_queue) >= dev->tx_queue_len)
+               goto drop;
 
        skb_queue_tail(&q->sk.sk_receive_queue, skb);
        wake_up_interruptible_poll(sk_sleep(&q->sk), POLLIN | POLLRDNORM | POLLRDBAND);
-       return 0;
+       return NET_RX_SUCCESS;
+
+drop:
+       kfree_skb(skb);
+       return NET_RX_DROP;
 }
 
 /*
@@ -235,8 +242,15 @@ static void macvtap_dellink(struct net_device *dev,
        macvlan_dellink(dev, head);
 }
 
+static void macvtap_setup(struct net_device *dev)
+{
+       macvlan_common_setup(dev);
+       dev->tx_queue_len = TUN_READQ_SIZE;
+}
+
 static struct rtnl_link_ops macvtap_link_ops __read_mostly = {
        .kind           = "macvtap",
+       .setup          = macvtap_setup,
        .newlink        = macvtap_newlink,
        .dellink        = macvtap_dellink,
 };
index 5e52c75892dfdb5bd878fb58b1fa715e72be5db3..7f3a53dcc6ef303ca91fa506703074195f549d11 100644 (file)
@@ -65,7 +65,7 @@ static int debug_level = ERR_DBG;
 
 /* DEBUG message print. */
 #define DBG_PRINT(dbg_level, fmt, args...) do {                        \
-       if (dbg_level >= debug_level)                           \
+       if (dbg_level <= debug_level)                           \
                pr_info(fmt, ##args);                           \
        } while (0)
 
index 6ad6fe706312ff4c2cff31034b37428275e2f230..63042596f0cf34962afe9c25133e27b0aa746701 100644 (file)
@@ -736,8 +736,18 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun,
                                gso.gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
                        else if (sinfo->gso_type & SKB_GSO_UDP)
                                gso.gso_type = VIRTIO_NET_HDR_GSO_UDP;
-                       else
-                               BUG();
+                       else {
+                               printk(KERN_ERR "tun: unexpected GSO type: "
+                                      "0x%x, gso_size %d, hdr_len %d\n",
+                                      sinfo->gso_type, gso.gso_size,
+                                      gso.hdr_len);
+                               print_hex_dump(KERN_ERR, "tun: ",
+                                              DUMP_PREFIX_NONE,
+                                              16, 1, skb->head,
+                                              min((int)gso.hdr_len, 64), true);
+                               WARN_ON_ONCE(1);
+                               return -EINVAL;
+                       }
                        if (sinfo->gso_type & SKB_GSO_TCP_ECN)
                                gso.gso_type |= VIRTIO_NET_HDR_GSO_ECN;
                } else
index 2d7c96d7e865957df251046a7ba5193e94a0c423..eb80243e22df27315f7b271aa71593e4ebe534e9 100644 (file)
@@ -152,6 +152,7 @@ enum {
        /* Device IDs */
        USB_DEVICE_ID_I6050 = 0x0186,
        USB_DEVICE_ID_I6050_2 = 0x0188,
+       USB_DEVICE_ID_I6250 = 0x0187,
 };
 
 
index 0d5081d77dc045f01849676a54d1b13e39ae0b72..d3365ac85dde4639e455b35b65c805cdec1aa552 100644 (file)
@@ -491,6 +491,7 @@ int i2400mu_probe(struct usb_interface *iface,
        switch (id->idProduct) {
        case USB_DEVICE_ID_I6050:
        case USB_DEVICE_ID_I6050_2:
+       case USB_DEVICE_ID_I6250:
                i2400mu->i6050 = 1;
                break;
        default:
@@ -739,6 +740,7 @@ static
 struct usb_device_id i2400mu_id_table[] = {
        { USB_DEVICE(0x8086, USB_DEVICE_ID_I6050) },
        { USB_DEVICE(0x8086, USB_DEVICE_ID_I6050_2) },
+       { USB_DEVICE(0x8086, USB_DEVICE_ID_I6250) },
        { USB_DEVICE(0x8086, 0x0181) },
        { USB_DEVICE(0x8086, 0x1403) },
        { USB_DEVICE(0x8086, 0x1405) },
index ca6065b71b46fb463c914f61061304177f693d2c..e3e52913d83a49326e3319cd67e1a0275b302d39 100644 (file)
@@ -844,9 +844,9 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp)
        int dma_type;
 
        if (edma)
-               dma_type = DMA_FROM_DEVICE;
-       else
                dma_type = DMA_BIDIRECTIONAL;
+       else
+               dma_type = DMA_FROM_DEVICE;
 
        qtype = hp ? ATH9K_RX_QUEUE_HP : ATH9K_RX_QUEUE_LP;
        spin_lock_bh(&sc->rx.rxbuflock);
index 9ea047aca7955e7c654512b37648617d8c76c904..1ffaeffeff740f83e3e79b5d6e03b306410d6b3f 100644 (file)
@@ -67,6 +67,8 @@ static inline void macvlan_count_rx(const struct macvlan_dev *vlan,
        }
 }
 
+extern void macvlan_common_setup(struct net_device *dev);
+
 extern int macvlan_common_newlink(struct net *src_net, struct net_device *dev,
                                  struct nlattr *tb[], struct nlattr *data[],
                                  int (*receive)(struct sk_buff *skb),
index ceac661cdfd5fdddb29314389f1e3e4bbca320ba..cfe2943690ff298cd3e938ecef2ddb8d49dfc229 100644 (file)
@@ -9,6 +9,7 @@ struct tcf_mirred {
        int                     tcfm_ifindex;
        int                     tcfm_ok_push;
        struct net_device       *tcfm_dev;
+       struct list_head        tcfm_list;
 };
 #define to_mirred(pc) \
        container_of(pc, struct tcf_mirred, common)
index 0ea10f849be862fff3219bc69ed4c5db0cbd9253..1f466e82ac339359ffa0587f0e1e1f324c0f5dd7 100644 (file)
@@ -1488,6 +1488,7 @@ static inline void net_timestamp_check(struct sk_buff *skb)
 int dev_forward_skb(struct net_device *dev, struct sk_buff *skb)
 {
        skb_orphan(skb);
+       nf_reset(skb);
 
        if (!(dev->flags & IFF_UP) ||
            (skb->len > (dev->mtu + dev->hard_header_len))) {
index 34432b4e96bb288931e8b6ebf2d8d78b5c6589c5..ce88293a34e263000dd93c0df5690885a857d797 100644 (file)
@@ -843,7 +843,9 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
        skb->network_header   += off;
        if (skb_mac_header_was_set(skb))
                skb->mac_header += off;
-       skb->csum_start       += nhead;
+       /* Only adjust this if it actually is csum_start rather than csum */
+       if (skb->ip_summed == CHECKSUM_PARTIAL)
+               skb->csum_start += nhead;
        skb->cloned   = 0;
        skb->hdr_len  = 0;
        skb->nohdr    = 0;
@@ -930,7 +932,8 @@ struct sk_buff *skb_copy_expand(const struct sk_buff *skb,
        copy_skb_header(n, skb);
 
        off                  = newheadroom - oldheadroom;
-       n->csum_start       += off;
+       if (n->ip_summed == CHECKSUM_PARTIAL)
+               n->csum_start += off;
 #ifdef NET_SKBUFF_DATA_USES_OFFSET
        n->transport_header += off;
        n->network_header   += off;
index e1a698df5706a9bcff5889e4b734ad66d6dbfb55..784f34d11fdd2f5672949a8f31b302377e6e6fc7 100644 (file)
@@ -1760,7 +1760,10 @@ static struct inet6_dev *addrconf_add_dev(struct net_device *dev)
 
        idev = ipv6_find_idev(dev);
        if (!idev)
-               return NULL;
+               return ERR_PTR(-ENOBUFS);
+
+       if (idev->cnf.disable_ipv6)
+               return ERR_PTR(-EACCES);
 
        /* Add default multicast route */
        addrconf_add_mroute(dev);
@@ -2129,8 +2132,9 @@ static int inet6_addr_add(struct net *net, int ifindex, struct in6_addr *pfx,
        if (!dev)
                return -ENODEV;
 
-       if ((idev = addrconf_add_dev(dev)) == NULL)
-               return -ENOBUFS;
+       idev = addrconf_add_dev(dev);
+       if (IS_ERR(idev))
+               return PTR_ERR(idev);
 
        scope = ipv6_addr_scope(pfx);
 
@@ -2377,7 +2381,7 @@ static void addrconf_dev_config(struct net_device *dev)
        }
 
        idev = addrconf_add_dev(dev);
-       if (idev == NULL)
+       if (IS_ERR(idev))
                return;
 
        memset(&addr, 0, sizeof(struct in6_addr));
@@ -2468,7 +2472,7 @@ static void addrconf_ip6_tnl_config(struct net_device *dev)
        ASSERT_RTNL();
 
        idev = addrconf_add_dev(dev);
-       if (!idev) {
+       if (IS_ERR(idev)) {
                printk(KERN_DEBUG "init ip6-ip6: add_dev failed\n");
                return;
        }
index c7000a6ca379a2e206be38292dd550a39b396a17..67ee34f57df7b02cb06e5d0d4e44944cc95feca7 100644 (file)
@@ -632,7 +632,7 @@ static void ieee80211_send_layer2_update(struct sta_info *sta)
        skb->dev = sta->sdata->dev;
        skb->protocol = eth_type_trans(skb, sta->sdata->dev);
        memset(skb->cb, 0, sizeof(skb->cb));
-       netif_rx(skb);
+       netif_rx_ni(skb);
 }
 
 static void sta_apply_parameters(struct ieee80211_local *local,
index c0b6863e3b87dbe91679638b83a169f099ab887f..1980b71c283ffbe9e50ac4f6326f91d41a40a6cb 100644 (file)
@@ -33,6 +33,7 @@
 static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1];
 static u32 mirred_idx_gen;
 static DEFINE_RWLOCK(mirred_lock);
+static LIST_HEAD(mirred_list);
 
 static struct tcf_hashinfo mirred_hash_info = {
        .htab   =       tcf_mirred_ht,
@@ -47,7 +48,9 @@ static inline int tcf_mirred_release(struct tcf_mirred *m, int bind)
                        m->tcf_bindcnt--;
                m->tcf_refcnt--;
                if(!m->tcf_bindcnt && m->tcf_refcnt <= 0) {
-                       dev_put(m->tcfm_dev);
+                       list_del(&m->tcfm_list);
+                       if (m->tcfm_dev)
+                               dev_put(m->tcfm_dev);
                        tcf_hash_destroy(&m->common, &mirred_hash_info);
                        return 1;
                }
@@ -134,8 +137,10 @@ static int tcf_mirred_init(struct nlattr *nla, struct nlattr *est,
                m->tcfm_ok_push = ok_push;
        }
        spin_unlock_bh(&m->tcf_lock);
-       if (ret == ACT_P_CREATED)
+       if (ret == ACT_P_CREATED) {
+               list_add(&m->tcfm_list, &mirred_list);
                tcf_hash_insert(pc, &mirred_hash_info);
+       }
 
        return ret;
 }
@@ -162,9 +167,14 @@ static int tcf_mirred(struct sk_buff *skb, struct tc_action *a,
        m->tcf_tm.lastuse = jiffies;
 
        dev = m->tcfm_dev;
+       if (!dev) {
+               printk_once(KERN_NOTICE "tc mirred: target device is gone\n");
+               goto out;
+       }
+
        if (!(dev->flags & IFF_UP)) {
                if (net_ratelimit())
-                       pr_notice("tc mirred to Houston: device %s is gone!\n",
+                       pr_notice("tc mirred to Houston: device %s is down\n",
                                  dev->name);
                goto out;
        }
@@ -232,6 +242,28 @@ nla_put_failure:
        return -1;
 }
 
+static int mirred_device_event(struct notifier_block *unused,
+                              unsigned long event, void *ptr)
+{
+       struct net_device *dev = ptr;
+       struct tcf_mirred *m;
+
+       if (event == NETDEV_UNREGISTER)
+               list_for_each_entry(m, &mirred_list, tcfm_list) {
+                       if (m->tcfm_dev == dev) {
+                               dev_put(dev);
+                               m->tcfm_dev = NULL;
+                       }
+               }
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block mirred_device_notifier = {
+       .notifier_call = mirred_device_event,
+};
+
+
 static struct tc_action_ops act_mirred_ops = {
        .kind           =       "mirred",
        .hinfo          =       &mirred_hash_info,
@@ -252,12 +284,17 @@ MODULE_LICENSE("GPL");
 
 static int __init mirred_init_module(void)
 {
+       int err = register_netdevice_notifier(&mirred_device_notifier);
+       if (err)
+               return err;
+
        pr_info("Mirror/redirect action on\n");
        return tcf_register_action(&act_mirred_ops);
 }
 
 static void __exit mirred_cleanup_module(void)
 {
+       unregister_netdevice_notifier(&mirred_device_notifier);
        tcf_unregister_action(&act_mirred_ops);
 }