6lowpan: rework fragment-deleting routine
[linux-2.6-block.git] / net / ieee802154 / 6lowpan.c
index 32eb4179e8fa6b04165dac2bb3a84ff788c59cff..6871ec1b30f88b724d499db4c307047ed7798869 100644 (file)
@@ -55,7 +55,6 @@
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/netdevice.h>
-#include <linux/etherdevice.h>
 #include <net/af_ieee802154.h>
 #include <net/ieee802154.h>
 #include <net/ieee802154_netdev.h>
@@ -114,7 +113,6 @@ struct lowpan_dev_record {
 
 struct lowpan_fragment {
        struct sk_buff          *skb;           /* skb to be assembled */
-       spinlock_t              lock;           /* concurency lock */
        u16                     length;         /* length to be assemled */
        u32                     bytes_rcv;      /* bytes received */
        u16                     tag;            /* current fragment tag */
@@ -124,7 +122,7 @@ struct lowpan_fragment {
 
 static unsigned short fragment_tag;
 static LIST_HEAD(lowpan_fragments);
-spinlock_t flist_lock;
+static DEFINE_SPINLOCK(flist_lock);
 
 static inline struct
 lowpan_dev_info *lowpan_dev_info(const struct net_device *dev)
@@ -240,8 +238,7 @@ lowpan_uncompress_addr(struct sk_buff *skb, struct in6_addr *ipaddr,
                lowpan_uip_ds6_set_addr_iid(ipaddr, lladdr);
        }
 
-       pr_debug("(%s): uncompressing %d + %d => ", __func__, prefcount,
-                                                               postcount);
+       pr_debug("uncompressing %d + %d => ", prefcount, postcount);
        lowpan_raw_dump_inline(NULL, NULL, ipaddr->s6_addr, 16);
 
        return 0;
@@ -252,13 +249,11 @@ lowpan_compress_udp_header(u8 **hc06_ptr, struct sk_buff *skb)
 {
        struct udphdr *uh = udp_hdr(skb);
 
-       pr_debug("(%s): UDP header compression\n", __func__);
-
        if (((uh->source & LOWPAN_NHC_UDP_4BIT_MASK) ==
                                LOWPAN_NHC_UDP_4BIT_PORT) &&
            ((uh->dest & LOWPAN_NHC_UDP_4BIT_MASK) ==
                                LOWPAN_NHC_UDP_4BIT_PORT)) {
-               pr_debug("(%s): both ports compression to 4 bits\n", __func__);
+               pr_debug("UDP header: both ports compression to 4 bits\n");
                **hc06_ptr = LOWPAN_NHC_UDP_CS_P_11;
                **(hc06_ptr + 1) = /* subtraction is faster */
                   (u8)((uh->dest - LOWPAN_NHC_UDP_4BIT_PORT) +
@@ -266,20 +261,20 @@ lowpan_compress_udp_header(u8 **hc06_ptr, struct sk_buff *skb)
                *hc06_ptr += 2;
        } else if ((uh->dest & LOWPAN_NHC_UDP_8BIT_MASK) ==
                        LOWPAN_NHC_UDP_8BIT_PORT) {
-               pr_debug("(%s): remove 8 bits of dest\n", __func__);
+               pr_debug("UDP header: remove 8 bits of dest\n");
                **hc06_ptr = LOWPAN_NHC_UDP_CS_P_01;
                memcpy(*hc06_ptr + 1, &uh->source, 2);
                **(hc06_ptr + 3) = (u8)(uh->dest - LOWPAN_NHC_UDP_8BIT_PORT);
                *hc06_ptr += 4;
        } else if ((uh->source & LOWPAN_NHC_UDP_8BIT_MASK) ==
                        LOWPAN_NHC_UDP_8BIT_PORT) {
-               pr_debug("(%s): remove 8 bits of source\n", __func__);
+               pr_debug("UDP header: remove 8 bits of source\n");
                **hc06_ptr = LOWPAN_NHC_UDP_CS_P_10;
                memcpy(*hc06_ptr + 1, &uh->dest, 2);
                **(hc06_ptr + 3) = (u8)(uh->source - LOWPAN_NHC_UDP_8BIT_PORT);
                *hc06_ptr += 4;
        } else {
-               pr_debug("(%s): can't compress header\n", __func__);
+               pr_debug("UDP header: can't compress\n");
                **hc06_ptr = LOWPAN_NHC_UDP_CS_P_00;
                memcpy(*hc06_ptr + 1, &uh->source, 2);
                memcpy(*hc06_ptr + 3, &uh->dest, 2);
@@ -291,25 +286,26 @@ lowpan_compress_udp_header(u8 **hc06_ptr, struct sk_buff *skb)
        *hc06_ptr += 2;
 }
 
-static u8 lowpan_fetch_skb_u8(struct sk_buff *skb)
+static inline int lowpan_fetch_skb_u8(struct sk_buff *skb, u8 *val)
 {
-       u8 ret;
+       if (unlikely(!pskb_may_pull(skb, 1)))
+               return -EINVAL;
 
-       ret = skb->data[0];
+       *val = skb->data[0];
        skb_pull(skb, 1);
 
-       return ret;
+       return 0;
 }
 
-static u16 lowpan_fetch_skb_u16(struct sk_buff *skb)
+static inline int lowpan_fetch_skb_u16(struct sk_buff *skb, u16 *val)
 {
-       u16 ret;
-
-       BUG_ON(!pskb_may_pull(skb, 2));
+       if (unlikely(!pskb_may_pull(skb, 2)))
+               return -EINVAL;
 
-       ret = skb->data[0] | (skb->data[1] << 8);
+       *val = skb->data[0] | (skb->data[1] << 8);
        skb_pull(skb, 2);
-       return ret;
+
+       return 0;
 }
 
 static int
@@ -318,10 +314,11 @@ lowpan_uncompress_udp_header(struct sk_buff *skb)
        struct udphdr *uh = udp_hdr(skb);
        u8 tmp;
 
-       tmp = lowpan_fetch_skb_u8(skb);
+       if (lowpan_fetch_skb_u8(skb, &tmp))
+               goto err;
 
        if ((tmp & LOWPAN_NHC_UDP_MASK) == LOWPAN_NHC_UDP_ID) {
-               pr_debug("(%s): UDP header uncompression\n", __func__);
+               pr_debug("UDP header uncompression\n");
                switch (tmp & LOWPAN_NHC_UDP_CS_P_11) {
                case LOWPAN_NHC_UDP_CS_P_00:
                        memcpy(&uh->source, &skb->data[0], 2);
@@ -347,19 +344,19 @@ lowpan_uncompress_udp_header(struct sk_buff *skb)
                        skb_pull(skb, 1);
                        break;
                default:
-                       pr_debug("(%s) ERROR: unknown UDP format\n", __func__);
+                       pr_debug("ERROR: unknown UDP format\n");
                        goto err;
                        break;
                }
 
-               pr_debug("(%s): uncompressed UDP ports: src = %d, dst = %d\n",
-                                       __func__, uh->source, uh->dest);
+               pr_debug("uncompressed UDP ports: src = %d, dst = %d\n",
+                        uh->source, uh->dest);
 
                /* copy checksum */
                memcpy(&uh->check, &skb->data[0], 2);
                skb_pull(skb, 2);
        } else {
-               pr_debug("(%s): ERROR: unsupported NH format\n", __func__);
+               pr_debug("ERROR: unsupported NH format\n");
                goto err;
        }
 
@@ -392,10 +389,9 @@ static int lowpan_header_create(struct sk_buff *skb,
        hdr = ipv6_hdr(skb);
        hc06_ptr = head + 2;
 
-       pr_debug("(%s): IPv6 header dump:\n\tversion = %d\n\tlength  = %d\n"
-                "\tnexthdr = 0x%02x\n\thop_lim = %d\n", __func__,
-               hdr->version, ntohs(hdr->payload_len), hdr->nexthdr,
-               hdr->hop_limit);
+       pr_debug("IPv6 header dump:\n\tversion = %d\n\tlength  = %d\n"
+                "\tnexthdr = 0x%02x\n\thop_lim = %d\n", hdr->version,
+                ntohs(hdr->payload_len), hdr->nexthdr, hdr->hop_limit);
 
        lowpan_raw_dump_table(__func__, "raw skb network header dump",
                skb_network_header(skb), sizeof(struct ipv6hdr));
@@ -490,28 +486,28 @@ static int lowpan_header_create(struct sk_buff *skb,
                break;
        default:
                *hc06_ptr = hdr->hop_limit;
+               hc06_ptr += 1;
                break;
        }
 
        /* source address compression */
        if (is_addr_unspecified(&hdr->saddr)) {
-               pr_debug("(%s): source address is unspecified, setting SAC\n",
-                                                               __func__);
+               pr_debug("source address is unspecified, setting SAC\n");
                iphc1 |= LOWPAN_IPHC_SAC;
        /* TODO: context lookup */
        } else if (is_addr_link_local(&hdr->saddr)) {
-               pr_debug("(%s): source address is link-local\n", __func__);
+               pr_debug("source address is link-local\n");
                iphc1 |= lowpan_compress_addr_64(&hc06_ptr,
                                LOWPAN_IPHC_SAM_BIT, &hdr->saddr, saddr);
        } else {
-               pr_debug("(%s): send the full source address\n", __func__);
+               pr_debug("send the full source address\n");
                memcpy(hc06_ptr, &hdr->saddr.s6_addr16[0], 16);
                hc06_ptr += 16;
        }
 
        /* destination address compression */
        if (is_addr_mcast(&hdr->daddr)) {
-               pr_debug("(%s): destination address is multicast", __func__);
+               pr_debug("destination address is multicast: ");
                iphc1 |= LOWPAN_IPHC_M;
                if (lowpan_is_mcast_addr_compressable8(&hdr->daddr)) {
                        pr_debug("compressed to 1 octet\n");
@@ -540,14 +536,13 @@ static int lowpan_header_create(struct sk_buff *skb,
                        hc06_ptr += 16;
                }
        } else {
-               pr_debug("(%s): destination address is unicast: ", __func__);
                /* TODO: context lookup */
                if (is_addr_link_local(&hdr->daddr)) {
-                       pr_debug("destination address is link-local\n");
+                       pr_debug("dest address is unicast and link-local\n");
                        iphc1 |= lowpan_compress_addr_64(&hc06_ptr,
                                LOWPAN_IPHC_DAM_BIT, &hdr->daddr, daddr);
                } else {
-                       pr_debug("using full address\n");
+                       pr_debug("dest address is unicast: using full one\n");
                        memcpy(hc06_ptr, &hdr->daddr.s6_addr16[0], 16);
                        hc06_ptr += 16;
                }
@@ -639,19 +634,15 @@ static void lowpan_fragment_timer_expired(unsigned long entry_addr)
 {
        struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr;
 
-       pr_debug("%s: timer expired for frame with tag %d\n", __func__,
-                                                               entry->tag);
+       pr_debug("timer expired for frame with tag %d\n", entry->tag);
 
-       spin_lock(&flist_lock);
        list_del(&entry->list);
-       spin_unlock(&flist_lock);
-
        dev_kfree_skb(entry->skb);
        kfree(entry);
 }
 
 static struct lowpan_fragment *
-lowpan_alloc_new_frame(struct sk_buff *skb, u8 iphc0, u8 len, u8 tag)
+lowpan_alloc_new_frame(struct sk_buff *skb, u8 iphc0, u8 len, u16 tag)
 {
        struct lowpan_fragment *frame;
 
@@ -666,8 +657,8 @@ lowpan_alloc_new_frame(struct sk_buff *skb, u8 iphc0, u8 len, u8 tag)
        frame->tag = tag;
 
        /* allocate buffer for frame assembling */
-       frame->skb = alloc_skb(frame->length +
-                              sizeof(struct ipv6hdr), GFP_ATOMIC);
+       frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length +
+                                              sizeof(struct ipv6hdr));
 
        if (!frame->skb)
                goto skb_err;
@@ -710,7 +701,9 @@ lowpan_process_data(struct sk_buff *skb)
        /* at least two bytes will be used for the encoding */
        if (skb->len < 2)
                goto drop;
-       iphc0 = lowpan_fetch_skb_u8(skb);
+
+       if (lowpan_fetch_skb_u8(skb, &iphc0))
+               goto drop;
 
        /* fragments assembling */
        switch (iphc0 & LOWPAN_DISPATCH_MASK) {
@@ -722,14 +715,15 @@ lowpan_process_data(struct sk_buff *skb)
                u16 tag;
                bool found = false;
 
-               len = lowpan_fetch_skb_u8(skb); /* frame length */
-               tag = lowpan_fetch_skb_u16(skb);
+               if (lowpan_fetch_skb_u8(skb, &len) || /* frame length */
+                   lowpan_fetch_skb_u16(skb, &tag))  /* fragment tag */
+                       goto drop;
 
                /*
                 * check if frame assembling with the same tag is
                 * already in progress
                 */
-               spin_lock(&flist_lock);
+               spin_lock_bh(&flist_lock);
 
                list_for_each_entry(frame, &lowpan_fragments, list)
                        if (frame->tag == tag) {
@@ -747,7 +741,8 @@ lowpan_process_data(struct sk_buff *skb)
                if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1)
                        goto unlock_and_drop;
 
-               offset = lowpan_fetch_skb_u8(skb); /* fetch offset */
+               if (lowpan_fetch_skb_u8(skb, &offset)) /* fetch offset */
+                       goto unlock_and_drop;
 
                /* if payload fits buffer, copy it */
                if (likely((offset * 8 + skb->len) <= frame->length))
@@ -762,17 +757,20 @@ lowpan_process_data(struct sk_buff *skb)
                if ((frame->bytes_rcv == frame->length) &&
                     frame->timer.expires > jiffies) {
                        /* if timer haven't expired - first of all delete it */
-                       del_timer(&frame->timer);
+                       del_timer_sync(&frame->timer);
                        list_del(&frame->list);
-                       spin_unlock(&flist_lock);
+                       spin_unlock_bh(&flist_lock);
 
                        dev_kfree_skb(skb);
                        skb = frame->skb;
                        kfree(frame);
-                       iphc0 = lowpan_fetch_skb_u8(skb);
+
+                       if (lowpan_fetch_skb_u8(skb, &iphc0))
+                               goto drop;
+
                        break;
                }
-               spin_unlock(&flist_lock);
+               spin_unlock_bh(&flist_lock);
 
                return kfree_skb(skb), 0;
        }
@@ -780,20 +778,19 @@ lowpan_process_data(struct sk_buff *skb)
                break;
        }
 
-       iphc1 = lowpan_fetch_skb_u8(skb);
+       if (lowpan_fetch_skb_u8(skb, &iphc1))
+               goto drop;
 
        _saddr = mac_cb(skb)->sa.hwaddr;
        _daddr = mac_cb(skb)->da.hwaddr;
 
-       pr_debug("(%s): iphc0 = %02x, iphc1 = %02x\n", __func__, iphc0, iphc1);
+       pr_debug("iphc0 = %02x, iphc1 = %02x\n", iphc0, iphc1);
 
        /* another if the CID flag is set */
        if (iphc1 & LOWPAN_IPHC_CID) {
-               pr_debug("(%s): CID flag is set, increase header with one\n",
-                                                               __func__);
-               if (!skb->len)
+               pr_debug("CID flag is set, increase header with one\n");
+               if (lowpan_fetch_skb_u8(skb, &num_context))
                        goto drop;
-               num_context = lowpan_fetch_skb_u8(skb);
        }
 
        hdr.version = 6;
@@ -805,9 +802,9 @@ lowpan_process_data(struct sk_buff *skb)
         * ECN + DSCP + 4-bit Pad + Flow Label (4 bytes)
         */
        case 0: /* 00b */
-               if (!skb->len)
+               if (lowpan_fetch_skb_u8(skb, &tmp))
                        goto drop;
-               tmp = lowpan_fetch_skb_u8(skb);
+
                memcpy(&hdr.flow_lbl, &skb->data[0], 3);
                skb_pull(skb, 3);
                hdr.priority = ((tmp >> 2) & 0x0f);
@@ -819,9 +816,9 @@ lowpan_process_data(struct sk_buff *skb)
         * ECN + DSCP (1 byte), Flow Label is elided
         */
        case 1: /* 10b */
-               if (!skb->len)
+               if (lowpan_fetch_skb_u8(skb, &tmp))
                        goto drop;
-               tmp = lowpan_fetch_skb_u8(skb);
+
                hdr.priority = ((tmp >> 2) & 0x0f);
                hdr.flow_lbl[0] = ((tmp << 6) & 0xC0) | ((tmp >> 2) & 0x30);
                hdr.flow_lbl[1] = 0;
@@ -832,9 +829,9 @@ lowpan_process_data(struct sk_buff *skb)
         * ECN + 2-bit Pad + Flow Label (3 bytes), DSCP is elided
         */
        case 2: /* 01b */
-               if (!skb->len)
+               if (lowpan_fetch_skb_u8(skb, &tmp))
                        goto drop;
-               tmp = lowpan_fetch_skb_u8(skb);
+
                hdr.flow_lbl[0] = (skb->data[0] & 0x0F) | ((tmp >> 2) & 0x30);
                memcpy(&hdr.flow_lbl[1], &skb->data[0], 2);
                skb_pull(skb, 2);
@@ -853,27 +850,26 @@ lowpan_process_data(struct sk_buff *skb)
        /* Next Header */
        if ((iphc0 & LOWPAN_IPHC_NH_C) == 0) {
                /* Next header is carried inline */
-               if (!skb->len)
+               if (lowpan_fetch_skb_u8(skb, &(hdr.nexthdr)))
                        goto drop;
-               hdr.nexthdr = lowpan_fetch_skb_u8(skb);
-               pr_debug("(%s): NH flag is set, next header is carried "
-                        "inline: %02x\n", __func__, hdr.nexthdr);
+
+               pr_debug("NH flag is set, next header carried inline: %02x\n",
+                        hdr.nexthdr);
        }
 
        /* Hop Limit */
        if ((iphc0 & 0x03) != LOWPAN_IPHC_TTL_I)
                hdr.hop_limit = lowpan_ttl_values[iphc0 & 0x03];
        else {
-               if (!skb->len)
+               if (lowpan_fetch_skb_u8(skb, &(hdr.hop_limit)))
                        goto drop;
-               hdr.hop_limit = lowpan_fetch_skb_u8(skb);
        }
 
        /* Extract SAM to the tmp variable */
        tmp = ((iphc1 & LOWPAN_IPHC_SAM) >> LOWPAN_IPHC_SAM_BIT) & 0x03;
 
        /* Source address uncompression */
-       pr_debug("(%s): source address stateless compression\n", __func__);
+       pr_debug("source address stateless compression\n");
        err = lowpan_uncompress_addr(skb, &hdr.saddr, lowpan_llprefix,
                                lowpan_unc_llconf[tmp], skb->data);
        if (err)
@@ -885,19 +881,15 @@ lowpan_process_data(struct sk_buff *skb)
        /* check for Multicast Compression */
        if (iphc1 & LOWPAN_IPHC_M) {
                if (iphc1 & LOWPAN_IPHC_DAC) {
-                       pr_debug("(%s): destination address context-based "
-                                "multicast compression\n", __func__);
+                       pr_debug("dest: context-based mcast compression\n");
                        /* TODO: implement this */
                } else {
                        u8 prefix[] = {0xff, 0x02};
 
-                       pr_debug("(%s): destination address non-context-based"
-                                " multicast compression\n", __func__);
+                       pr_debug("dest: non context-based mcast compression\n");
                        if (0 < tmp && tmp < 3) {
-                               if (!skb->len)
+                               if (lowpan_fetch_skb_u8(skb, &prefix[1]))
                                        goto drop;
-                               else
-                                       prefix[1] = lowpan_fetch_skb_u8(skb);
                        }
 
                        err = lowpan_uncompress_addr(skb, &hdr.daddr, prefix,
@@ -906,8 +898,7 @@ lowpan_process_data(struct sk_buff *skb)
                                goto drop;
                }
        } else {
-               pr_debug("(%s): destination address stateless compression\n",
-                                                               __func__);
+               pr_debug("dest: stateless compression\n");
                err = lowpan_uncompress_addr(skb, &hdr.daddr, lowpan_llprefix,
                                lowpan_unc_llconf[tmp], skb->data);
                if (err)
@@ -922,11 +913,11 @@ lowpan_process_data(struct sk_buff *skb)
        /* Not fragmented package */
        hdr.payload_len = htons(skb->len);
 
-       pr_debug("(%s): skb headroom size = %d, data length = %d\n", __func__,
-                                               skb_headroom(skb), skb->len);
+       pr_debug("skb headroom size = %d, data length = %d\n",
+                skb_headroom(skb), skb->len);
 
-       pr_debug("(%s): IPv6 header dump:\n\tversion = %d\n\tlength  = %d\n\t"
-                "nexthdr = 0x%02x\n\thop_lim = %d\n", __func__, hdr.version,
+       pr_debug("IPv6 header dump:\n\tversion = %d\n\tlength  = %d\n\t"
+                "nexthdr = 0x%02x\n\thop_lim = %d\n", hdr.version,
                 ntohs(hdr.payload_len), hdr.nexthdr, hdr.hop_limit);
 
        lowpan_raw_dump_table(__func__, "raw header dump", (u8 *)&hdr,
@@ -934,12 +925,25 @@ lowpan_process_data(struct sk_buff *skb)
        return lowpan_skb_deliver(skb, &hdr);
 
 unlock_and_drop:
-       spin_unlock(&flist_lock);
+       spin_unlock_bh(&flist_lock);
 drop:
        kfree_skb(skb);
        return -EINVAL;
 }
 
+static int lowpan_set_address(struct net_device *dev, void *p)
+{
+       struct sockaddr *sa = p;
+
+       if (netif_running(dev))
+               return -EBUSY;
+
+       /* TODO: validate addr */
+       memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+
+       return 0;
+}
+
 static int lowpan_get_mac_header_length(struct sk_buff *skb)
 {
        /*
@@ -1028,11 +1032,11 @@ static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        int err = -1;
 
-       pr_debug("(%s): package xmit\n", __func__);
+       pr_debug("package xmit\n");
 
        skb->dev = lowpan_dev_info(dev)->real_dev;
        if (skb->dev == NULL) {
-               pr_debug("(%s) ERROR: no real wpan device found\n", __func__);
+               pr_debug("ERROR: no real wpan device found\n");
                goto error;
        }
 
@@ -1041,14 +1045,13 @@ static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev)
                goto out;
        }
 
-       pr_debug("(%s): frame is too big, fragmentation is needed\n",
-                                                               __func__);
+       pr_debug("frame is too big, fragmentation is needed\n");
        err = lowpan_skb_fragmentation(skb);
 error:
        dev_kfree_skb(skb);
 out:
        if (err < 0)
-               pr_debug("(%s): ERROR: xmit failed\n", __func__);
+               pr_debug("ERROR: xmit failed\n");
 
        return (err < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK);
 }
@@ -1083,7 +1086,7 @@ static struct header_ops lowpan_header_ops = {
 
 static const struct net_device_ops lowpan_netdev_ops = {
        .ndo_start_xmit         = lowpan_xmit,
-       .ndo_set_mac_address    = eth_mac_addr,
+       .ndo_set_mac_address    = lowpan_set_address,
 };
 
 static struct ieee802154_mlme_ops lowpan_mlme = {
@@ -1094,8 +1097,6 @@ static struct ieee802154_mlme_ops lowpan_mlme = {
 
 static void lowpan_setup(struct net_device *dev)
 {
-       pr_debug("(%s)\n", __func__);
-
        dev->addr_len           = IEEE802154_ADDR_LEN;
        memset(dev->broadcast, 0xff, IEEE802154_ADDR_LEN);
        dev->type               = ARPHRD_IEEE802154;
@@ -1115,8 +1116,6 @@ static void lowpan_setup(struct net_device *dev)
 
 static int lowpan_validate(struct nlattr *tb[], struct nlattr *data[])
 {
-       pr_debug("(%s)\n", __func__);
-
        if (tb[IFLA_ADDRESS]) {
                if (nla_len(tb[IFLA_ADDRESS]) != IEEE802154_ADDR_LEN)
                        return -EINVAL;
@@ -1157,7 +1156,7 @@ static int lowpan_newlink(struct net *src_net, struct net_device *dev,
        struct net_device *real_dev;
        struct lowpan_dev_record *entry;
 
-       pr_debug("(%s)\n", __func__);
+       pr_debug("adding new link\n");
 
        if (!tb[IFLA_LINK])
                return -EINVAL;
@@ -1183,8 +1182,6 @@ static int lowpan_newlink(struct net *src_net, struct net_device *dev,
        list_add_tail(&entry->list, &lowpan_devices);
        mutex_unlock(&lowpan_dev_info(dev)->dev_list_mtx);
 
-       spin_lock_init(&flist_lock);
-
        register_netdevice(dev);
 
        return 0;
@@ -1195,19 +1192,9 @@ static void lowpan_dellink(struct net_device *dev, struct list_head *head)
        struct lowpan_dev_info *lowpan_dev = lowpan_dev_info(dev);
        struct net_device *real_dev = lowpan_dev->real_dev;
        struct lowpan_dev_record *entry, *tmp;
-       struct lowpan_fragment *frame, *tframe;
 
        ASSERT_RTNL();
 
-       spin_lock(&flist_lock);
-       list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) {
-               del_timer(&frame->timer);
-               list_del(&frame->list);
-               dev_kfree_skb(frame->skb);
-               kfree(frame);
-       }
-       spin_unlock(&flist_lock);
-
        mutex_lock(&lowpan_dev_info(dev)->dev_list_mtx);
        list_for_each_entry_safe(entry, tmp, &lowpan_devices, list) {
                if (entry->ldev == dev) {
@@ -1252,8 +1239,6 @@ static int __init lowpan_init_module(void)
 {
        int err = 0;
 
-       pr_debug("(%s)\n", __func__);
-
        err = lowpan_netlink_init();
        if (err < 0)
                goto out;
@@ -1265,11 +1250,24 @@ out:
 
 static void __exit lowpan_cleanup_module(void)
 {
-       pr_debug("(%s)\n", __func__);
+       struct lowpan_fragment *frame, *tframe;
 
        lowpan_netlink_fini();
 
        dev_remove_pack(&lowpan_packet_type);
+
+       /* Now 6lowpan packet_type is removed, so no new fragments are
+        * expected on RX, therefore that's the time to clean incomplete
+        * fragments.
+        */
+       spin_lock_bh(&flist_lock);
+       list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) {
+               del_timer_sync(&frame->timer);
+               list_del(&frame->list);
+               dev_kfree_skb(frame->skb);
+               kfree(frame);
+       }
+       spin_unlock_bh(&flist_lock);
 }
 
 module_init(lowpan_init_module);