]> Pileus Git - ~andy/linux/blobdiff - drivers/net/wireless/rt2x00/rt2x00usb.c
rt2x00: Preserve descriptor information after memmove()
[~andy/linux] / drivers / net / wireless / rt2x00 / rt2x00usb.c
index caee65e821986ce4caebec5a71d4fb067f9f73b3..f72b3d07a42d1037e25827ab5005d222fcbd8eae 100644 (file)
@@ -246,22 +246,35 @@ static struct sk_buff* rt2x00usb_alloc_rxskb(struct data_queue *queue)
 {
        struct sk_buff *skb;
        unsigned int frame_size;
+       unsigned int reserved_size;
 
        /*
-        * As alignment we use 2 and not NET_IP_ALIGN because we need
-        * to be sure we have 2 bytes room in the head. (NET_IP_ALIGN
-        * can be 0 on some hardware). We use these 2 bytes for frame
-        * alignment later, we assume that the chance that
-        * header_size % 4 == 2 is bigger then header_size % 2 == 0
-        * and thus optimize alignment by reserving the 2 bytes in
-        * advance.
+        * The frame size includes descriptor size, because the
+        * hardware directly receive the frame into the skbuffer.
         */
        frame_size = queue->data_size + queue->desc_size;
-       skb = dev_alloc_skb(queue->desc_size + frame_size + 2);
+
+       /*
+        * For the allocation we should keep a few things in mind:
+        * 1) 4byte alignment of 802.11 payload
+        *
+        * For (1) we need at most 4 bytes to guarentee the correct
+        * alignment. We are going to optimize the fact that the chance
+        * that the 802.11 header_size % 4 == 2 is much bigger then
+        * anything else. However since we need to move the frame up
+        * to 3 bytes to the front, which means we need to preallocate
+        * 6 bytes.
+        */
+       reserved_size = 6;
+
+       /*
+        * Allocate skbuffer.
+        */
+       skb = dev_alloc_skb(frame_size + reserved_size);
        if (!skb)
                return NULL;
 
-       skb_reserve(skb, queue->desc_size + 2);
+       skb_reserve(skb, reserved_size);
        skb_put(skb, frame_size);
 
        return skb;
@@ -274,7 +287,8 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
        struct sk_buff *skb;
        struct skb_frame_desc *skbdesc;
        struct rxdone_entry_desc rxdesc;
-       int header_size;
+       unsigned int header_size;
+       unsigned int align;
 
        if (!test_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags) ||
            !test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
@@ -298,19 +312,29 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
        memset(&rxdesc, 0, sizeof(rxdesc));
        rt2x00dev->ops->lib->fill_rxdone(entry, &rxdesc);
 
+       header_size = ieee80211_get_hdrlen_from_skb(entry->skb);
+
        /*
         * The data behind the ieee80211 header must be
-        * aligned on a 4 byte boundary.
+        * aligned on a 4 byte boundary. We already reserved
+        * 2 bytes for header_size % 4 == 2 optimization.
+        * To determine the number of bytes which the data
+        * should be moved to the left, we must add these
+        * 2 bytes to the header_size.
         */
-       header_size = ieee80211_get_hdrlen_from_skb(entry->skb);
-       if (header_size % 4 == 0) {
-               skb_push(entry->skb, 2);
-               memmove(entry->skb->data, entry->skb->data + 2,
-                       entry->skb->len - 2);
-               skbdesc->data = entry->skb->data;
-               skb_trim(entry->skb,entry->skb->len - 2);
+       align = (header_size + 2) % 4;
+
+       if (align) {
+               skb_push(entry->skb, align);
+               /* Move entire frame in 1 command */
+               memmove(entry->skb->data, entry->skb->data + align,
+                       rxdesc.size);
        }
 
+       /* Update data pointers, trim buffer to correct size */
+       skbdesc->data = entry->skb->data;
+       skb_trim(entry->skb, rxdesc.size);
+
        /*
         * Allocate a new sk buffer to replace the current one.
         * If allocation fails, we should drop the current frame