cfg80211: Fix country IE parsing for single channel triplets
[safe/jmp/linux-2.6] / net / wireless / util.c
index 5aa39f7..23557c1 100644 (file)
@@ -285,7 +285,7 @@ static int ieee80211_get_mesh_hdrlen(struct ieee80211s_hdr *meshhdr)
        }
 }
 
-int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_to_8023(struct sk_buff *skb, const u8 *addr,
                           enum nl80211_iftype iftype)
 {
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
@@ -383,7 +383,7 @@ int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
 }
 EXPORT_SYMBOL(ieee80211_data_to_8023);
 
-int ieee80211_data_from_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_from_8023(struct sk_buff *skb, const u8 *addr,
                             enum nl80211_iftype iftype, u8 *bssid, bool qos)
 {
        struct ieee80211_hdr hdr;
@@ -497,6 +497,101 @@ int ieee80211_data_from_8023(struct sk_buff *skb, u8 *addr,
 }
 EXPORT_SYMBOL(ieee80211_data_from_8023);
 
+
+void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list,
+                             const u8 *addr, enum nl80211_iftype iftype,
+                             const unsigned int extra_headroom)
+{
+       struct sk_buff *frame = NULL;
+       u16 ethertype;
+       u8 *payload;
+       const struct ethhdr *eth;
+       int remaining, err;
+       u8 dst[ETH_ALEN], src[ETH_ALEN];
+
+       err = ieee80211_data_to_8023(skb, addr, iftype);
+       if (err)
+               goto out;
+
+       /* skip the wrapping header */
+       eth = (struct ethhdr *) skb_pull(skb, sizeof(struct ethhdr));
+       if (!eth)
+               goto out;
+
+       while (skb != frame) {
+               u8 padding;
+               __be16 len = eth->h_proto;
+               unsigned int subframe_len = sizeof(struct ethhdr) + ntohs(len);
+
+               remaining = skb->len;
+               memcpy(dst, eth->h_dest, ETH_ALEN);
+               memcpy(src, eth->h_source, ETH_ALEN);
+
+               padding = (4 - subframe_len) & 0x3;
+               /* the last MSDU has no padding */
+               if (subframe_len > remaining)
+                       goto purge;
+
+               skb_pull(skb, sizeof(struct ethhdr));
+               /* reuse skb for the last subframe */
+               if (remaining <= subframe_len + padding)
+                       frame = skb;
+               else {
+                       unsigned int hlen = ALIGN(extra_headroom, 4);
+                       /*
+                        * Allocate and reserve two bytes more for payload
+                        * alignment since sizeof(struct ethhdr) is 14.
+                        */
+                       frame = dev_alloc_skb(hlen + subframe_len + 2);
+                       if (!frame)
+                               goto purge;
+
+                       skb_reserve(frame, hlen + sizeof(struct ethhdr) + 2);
+                       memcpy(skb_put(frame, ntohs(len)), skb->data,
+                               ntohs(len));
+
+                       eth = (struct ethhdr *)skb_pull(skb, ntohs(len) +
+                                                       padding);
+                       if (!eth) {
+                               dev_kfree_skb(frame);
+                               goto purge;
+                       }
+               }
+
+               skb_reset_network_header(frame);
+               frame->dev = skb->dev;
+               frame->priority = skb->priority;
+
+               payload = frame->data;
+               ethertype = (payload[6] << 8) | payload[7];
+
+               if (likely((compare_ether_addr(payload, rfc1042_header) == 0 &&
+                           ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) ||
+                          compare_ether_addr(payload,
+                                             bridge_tunnel_header) == 0)) {
+                       /* remove RFC1042 or Bridge-Tunnel
+                        * encapsulation and replace EtherType */
+                       skb_pull(frame, 6);
+                       memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
+                       memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
+               } else {
+                       memcpy(skb_push(frame, sizeof(__be16)), &len,
+                               sizeof(__be16));
+                       memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
+                       memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
+               }
+               __skb_queue_tail(list, frame);
+       }
+
+       return;
+
+ purge:
+       __skb_queue_purge(list);
+ out:
+       dev_kfree_skb(skb);
+}
+EXPORT_SYMBOL(ieee80211_amsdu_to_8023s);
+
 /* Given a data frame determine the 802.1p/1d tag to use. */
 unsigned int cfg80211_classify8021d(struct sk_buff *skb)
 {
@@ -658,7 +753,14 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
            !(rdev->wiphy.interface_modes & (1 << ntype)))
                return -EOPNOTSUPP;
 
+       /* if it's part of a bridge, reject changing type to station/ibss */
+       if (dev->br_port && (ntype == NL80211_IFTYPE_ADHOC ||
+                            ntype == NL80211_IFTYPE_STATION))
+               return -EBUSY;
+
        if (ntype != otype) {
+               dev->ieee80211_ptr->use_4addr = false;
+
                switch (otype) {
                case NL80211_IFTYPE_ADHOC:
                        cfg80211_leave_ibss(rdev, dev, false);
@@ -682,5 +784,67 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
 
        WARN_ON(!err && dev->ieee80211_ptr->iftype != ntype);
 
+       if (!err && params && params->use_4addr != -1)
+               dev->ieee80211_ptr->use_4addr = params->use_4addr;
+
+       if (!err) {
+               dev->priv_flags &= ~IFF_DONT_BRIDGE;
+               switch (ntype) {
+               case NL80211_IFTYPE_STATION:
+                       if (dev->ieee80211_ptr->use_4addr)
+                               break;
+                       /* fall through */
+               case NL80211_IFTYPE_ADHOC:
+                       dev->priv_flags |= IFF_DONT_BRIDGE;
+                       break;
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_AP_VLAN:
+               case NL80211_IFTYPE_WDS:
+               case NL80211_IFTYPE_MESH_POINT:
+                       /* bridging OK */
+                       break;
+               case NL80211_IFTYPE_MONITOR:
+                       /* monitor can't bridge anyway */
+                       break;
+               case NL80211_IFTYPE_UNSPECIFIED:
+               case __NL80211_IFTYPE_AFTER_LAST:
+                       /* not happening */
+                       break;
+               }
+       }
+
        return err;
 }
+
+u16 cfg80211_calculate_bitrate(struct rate_info *rate)
+{
+       int modulation, streams, bitrate;
+
+       if (!(rate->flags & RATE_INFO_FLAGS_MCS))
+               return rate->legacy;
+
+       /* the formula below does only work for MCS values smaller than 32 */
+       if (rate->mcs >= 32)
+               return 0;
+
+       modulation = rate->mcs & 7;
+       streams = (rate->mcs >> 3) + 1;
+
+       bitrate = (rate->flags & RATE_INFO_FLAGS_40_MHZ_WIDTH) ?
+                       13500000 : 6500000;
+
+       if (modulation < 4)
+               bitrate *= (modulation + 1);
+       else if (modulation == 4)
+               bitrate *= (modulation + 2);
+       else
+               bitrate *= (modulation + 3);
+
+       bitrate *= streams;
+
+       if (rate->flags & RATE_INFO_FLAGS_SHORT_GI)
+               bitrate = (bitrate / 9) * 10;
+
+       /* do NOT round down here */
+       return (bitrate + 50000) / 100000;
+}