Merge branch 'master' of git://git.infradead.org/users/linville/wireless-next into...
[pandora-kernel.git] / net / mac80211 / cfg.c
index 3d1b091..ebd7fb1 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/slab.h>
 #include <net/net_namespace.h>
 #include <linux/rcupdate.h>
+#include <linux/if_ether.h>
 #include <net/cfg80211.h>
 #include "ieee80211_i.h"
 #include "driver-ops.h"
@@ -62,7 +63,7 @@ static int ieee80211_change_iface(struct wiphy *wiphy,
 
        if (type == NL80211_IFTYPE_AP_VLAN &&
            params && params->use_4addr == 0)
-               rcu_assign_pointer(sdata->u.vlan.sta, NULL);
+               RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
        else if (type == NL80211_IFTYPE_STATION &&
                 params && params->use_4addr >= 0)
                sdata->u.mgd.use_4addr = params->use_4addr;
@@ -343,7 +344,8 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
                        STATION_INFO_RX_BITRATE |
                        STATION_INFO_RX_DROP_MISC |
                        STATION_INFO_BSS_PARAM |
-                       STATION_INFO_CONNECTED_TIME;
+                       STATION_INFO_CONNECTED_TIME |
+                       STATION_INFO_STA_FLAGS;
 
        do_posix_clock_monotonic_gettime(&uptime);
        sinfo->connected_time = uptime.tv_sec - sta->last_connected;
@@ -403,6 +405,23 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
                sinfo->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
        sinfo->bss_param.dtim_period = sdata->local->hw.conf.ps_dtim_period;
        sinfo->bss_param.beacon_interval = sdata->vif.bss_conf.beacon_int;
+
+       sinfo->sta_flags.set = 0;
+       sinfo->sta_flags.mask = BIT(NL80211_STA_FLAG_AUTHORIZED) |
+                               BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) |
+                               BIT(NL80211_STA_FLAG_WME) |
+                               BIT(NL80211_STA_FLAG_MFP) |
+                               BIT(NL80211_STA_FLAG_AUTHENTICATED);
+       if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
+       if (test_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
+       if (test_sta_flag(sta, WLAN_STA_WME))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_WME);
+       if (test_sta_flag(sta, WLAN_STA_MFP))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_MFP);
+       if (test_sta_flag(sta, WLAN_STA_AUTH))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
 }
 
 
@@ -455,6 +474,20 @@ static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
        return ret;
 }
 
+static void ieee80211_config_ap_ssid(struct ieee80211_sub_if_data *sdata,
+                                    struct beacon_parameters *params)
+{
+       struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
+
+       bss_conf->ssid_len = params->ssid_len;
+
+       if (params->ssid_len)
+               memcpy(bss_conf->ssid, params->ssid, params->ssid_len);
+
+       bss_conf->hidden_ssid =
+               (params->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE);
+}
+
 /*
  * This handles both adding a beacon and setting new beacon info
  */
@@ -542,14 +575,17 @@ static int ieee80211_config_beacon(struct ieee80211_sub_if_data *sdata,
 
        sdata->vif.bss_conf.dtim_period = new->dtim_period;
 
-       rcu_assign_pointer(sdata->u.ap.beacon, new);
+       RCU_INIT_POINTER(sdata->u.ap.beacon, new);
 
        synchronize_rcu();
 
        kfree(old);
 
+       ieee80211_config_ap_ssid(sdata, params);
+
        ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED |
-                                               BSS_CHANGED_BEACON);
+                                               BSS_CHANGED_BEACON |
+                                               BSS_CHANGED_SSID);
        return 0;
 }
 
@@ -594,7 +630,7 @@ static int ieee80211_del_beacon(struct wiphy *wiphy, struct net_device *dev)
        if (!old)
                return -ENOENT;
 
-       rcu_assign_pointer(sdata->u.ap.beacon, NULL);
+       RCU_INIT_POINTER(sdata->u.ap.beacon, NULL);
        synchronize_rcu();
        kfree(old);
 
@@ -650,7 +686,6 @@ static void sta_apply_parameters(struct ieee80211_local *local,
                                 struct sta_info *sta,
                                 struct station_parameters *params)
 {
-       unsigned long flags;
        u32 rates;
        int i, j;
        struct ieee80211_supported_band *sband;
@@ -659,43 +694,58 @@ static void sta_apply_parameters(struct ieee80211_local *local,
 
        sband = local->hw.wiphy->bands[local->oper_channel->band];
 
-       spin_lock_irqsave(&sta->flaglock, flags);
        mask = params->sta_flags_mask;
        set = params->sta_flags_set;
 
        if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
-               sta->flags &= ~WLAN_STA_AUTHORIZED;
                if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
-                       sta->flags |= WLAN_STA_AUTHORIZED;
+                       set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+               else
+                       clear_sta_flag(sta, WLAN_STA_AUTHORIZED);
        }
 
        if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
-               sta->flags &= ~WLAN_STA_SHORT_PREAMBLE;
                if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
-                       sta->flags |= WLAN_STA_SHORT_PREAMBLE;
+                       set_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
+               else
+                       clear_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
        }
 
        if (mask & BIT(NL80211_STA_FLAG_WME)) {
-               sta->flags &= ~WLAN_STA_WME;
-               sta->sta.wme = false;
                if (set & BIT(NL80211_STA_FLAG_WME)) {
-                       sta->flags |= WLAN_STA_WME;
+                       set_sta_flag(sta, WLAN_STA_WME);
                        sta->sta.wme = true;
+               } else {
+                       clear_sta_flag(sta, WLAN_STA_WME);
+                       sta->sta.wme = false;
                }
        }
 
        if (mask & BIT(NL80211_STA_FLAG_MFP)) {
-               sta->flags &= ~WLAN_STA_MFP;
                if (set & BIT(NL80211_STA_FLAG_MFP))
-                       sta->flags |= WLAN_STA_MFP;
+                       set_sta_flag(sta, WLAN_STA_MFP);
+               else
+                       clear_sta_flag(sta, WLAN_STA_MFP);
        }
 
        if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
-               sta->flags &= ~WLAN_STA_AUTH;
                if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
-                       sta->flags |= WLAN_STA_AUTH;
+                       set_sta_flag(sta, WLAN_STA_AUTH);
+               else
+                       clear_sta_flag(sta, WLAN_STA_AUTH);
+       }
+
+       if (mask & BIT(NL80211_STA_FLAG_TDLS_PEER)) {
+               if (set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+                       set_sta_flag(sta, WLAN_STA_TDLS_PEER);
+               else
+                       clear_sta_flag(sta, WLAN_STA_TDLS_PEER);
+       }
+
+       if (params->sta_modify_mask & STATION_PARAM_APPLY_UAPSD) {
+               sta->sta.uapsd_queues = params->uapsd_queues;
+               sta->sta.max_sp = params->max_sp;
        }
-       spin_unlock_irqrestore(&sta->flaglock, flags);
 
        /*
         * cfg80211 validates this (1-2007) and allows setting the AID
@@ -786,10 +836,17 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
        if (!sta)
                return -ENOMEM;
 
-       sta->flags = WLAN_STA_AUTH | WLAN_STA_ASSOC;
+       set_sta_flag(sta, WLAN_STA_AUTH);
+       set_sta_flag(sta, WLAN_STA_ASSOC);
 
        sta_apply_parameters(local, sta, params);
 
+       /* Only TDLS-supporting stations can add TDLS peers */
+       if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
+           !((wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
+             sdata->vif.type == NL80211_IFTYPE_STATION))
+               return -ENOTSUPP;
+
        rate_control_rate_init(sta);
 
        layer2_update = sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
@@ -842,6 +899,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                return -ENOENT;
        }
 
+       /* The TDLS bit cannot be toggled after the STA was added */
+       if ((params->sta_flags_mask & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
+           !!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) !=
+           !!test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
+               rcu_read_unlock();
+               return -EINVAL;
+       }
+
        if (params->vlan && params->vlan != sta->sdata->dev) {
                vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 
@@ -857,7 +922,7 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                                return -EBUSY;
                        }
 
-                       rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
+                       RCU_INIT_POINTER(vlansdata->u.vlan.sta, sta);
                }
 
                sta->sdata = vlansdata;
@@ -918,7 +983,7 @@ static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev,
        if (dst)
                return mesh_path_del(dst, sdata);
 
-       mesh_path_flush(sdata);
+       mesh_path_flush_by_iface(sdata);
        return 0;
 }
 
@@ -1137,6 +1202,22 @@ static int ieee80211_update_mesh_config(struct wiphy *wiphy,
                conf->dot11MeshHWMPRootMode = nconf->dot11MeshHWMPRootMode;
                ieee80211_mesh_root_setup(ifmsh);
        }
+       if (_chg_mesh_attr(NL80211_MESHCONF_GATE_ANNOUNCEMENTS, mask)) {
+               /* our current gate announcement implementation rides on root
+                * announcements, so require this ifmsh to also be a root node
+                * */
+               if (nconf->dot11MeshGateAnnouncementProtocol &&
+                   !conf->dot11MeshHWMPRootMode) {
+                       conf->dot11MeshHWMPRootMode = 1;
+                       ieee80211_mesh_root_setup(ifmsh);
+               }
+               conf->dot11MeshGateAnnouncementProtocol =
+                       nconf->dot11MeshGateAnnouncementProtocol;
+       }
+       if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_RANN_INTERVAL, mask)) {
+               conf->dot11MeshHWMPRannInterval =
+                       nconf->dot11MeshHWMPRannInterval;
+       }
        return 0;
 }
 
@@ -1235,9 +1316,11 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
 }
 
 static int ieee80211_set_txq_params(struct wiphy *wiphy,
+                                   struct net_device *dev,
                                    struct ieee80211_txq_params *params)
 {
        struct ieee80211_local *local = wiphy_priv(wiphy);
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_tx_queue_params p;
 
        if (!local->ops->conf_tx)
@@ -1258,8 +1341,8 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy,
        if (params->queue >= local->hw.queues)
                return -EINVAL;
 
-       local->tx_conf[params->queue] = p;
-       if (drv_conf_tx(local, params->queue, &p)) {
+       sdata->tx_conf[params->queue] = p;
+       if (drv_conf_tx(local, sdata, params->queue, &p)) {
                wiphy_debug(local->hw.wiphy,
                            "failed to set TX queue parameters for queue %d\n",
                            params->queue);
@@ -1821,7 +1904,7 @@ ieee80211_offchan_tx_done(struct ieee80211_work *wk, struct sk_buff *skb)
         * so in that case userspace will have to deal with it.
         */
 
-       if (wk->offchan_tx.wait && wk->offchan_tx.frame)
+       if (wk->offchan_tx.wait && !wk->offchan_tx.status)
                cfg80211_mgmt_tx_status(wk->sdata->dev,
                                        (unsigned long) wk->offchan_tx.frame,
                                        wk->ie, wk->ie_len, false, GFP_KERNEL);
@@ -1833,7 +1916,8 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
                             struct ieee80211_channel *chan, bool offchan,
                             enum nl80211_channel_type channel_type,
                             bool channel_type_valid, unsigned int wait,
-                            const u8 *buf, size_t len, u64 *cookie)
+                            const u8 *buf, size_t len, bool no_cck,
+                            u64 *cookie)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = sdata->local;
@@ -1860,6 +1944,9 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
                flags |= IEEE80211_TX_CTL_TX_OFFCHAN;
        }
 
+       if (no_cck)
+               flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
+
        if (is_offchan && !offchan)
                return -EBUSY;
 
@@ -1898,33 +1985,6 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
 
        *cookie = (unsigned long) skb;
 
-       if (is_offchan && local->ops->offchannel_tx) {
-               int ret;
-
-               IEEE80211_SKB_CB(skb)->band = chan->band;
-
-               mutex_lock(&local->mtx);
-
-               if (local->hw_offchan_tx_cookie) {
-                       mutex_unlock(&local->mtx);
-                       return -EBUSY;
-               }
-
-               /* TODO: bitrate control, TX processing? */
-               ret = drv_offchannel_tx(local, skb, chan, channel_type, wait);
-
-               if (ret == 0)
-                       local->hw_offchan_tx_cookie = *cookie;
-               mutex_unlock(&local->mtx);
-
-               /*
-                * Allow driver to return 1 to indicate it wants to have the
-                * frame transmitted with a remain_on_channel + regular TX.
-                */
-               if (ret != 1)
-                       return ret;
-       }
-
        if (is_offchan && local->ops->remain_on_channel) {
                unsigned int duration;
                int ret;
@@ -2011,18 +2071,6 @@ static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
 
        mutex_lock(&local->mtx);
 
-       if (local->ops->offchannel_tx_cancel_wait &&
-           local->hw_offchan_tx_cookie == cookie) {
-               ret = drv_offchannel_tx_cancel_wait(local);
-
-               if (!ret)
-                       local->hw_offchan_tx_cookie = 0;
-
-               mutex_unlock(&local->mtx);
-
-               return ret;
-       }
-
        if (local->ops->cancel_remain_on_channel) {
                cookie ^= 2;
                ret = ieee80211_cancel_remain_on_channel_hw(local, cookie);
@@ -2123,6 +2171,323 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy,
        return 0;
 }
 
+static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb)
+{
+       u8 *pos = (void *)skb_put(skb, 7);
+
+       *pos++ = WLAN_EID_EXT_CAPABILITY;
+       *pos++ = 5; /* len */
+       *pos++ = 0x0;
+       *pos++ = 0x0;
+       *pos++ = 0x0;
+       *pos++ = 0x0;
+       *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
+}
+
+static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_local *local = sdata->local;
+       u16 capab;
+
+       capab = 0;
+       if (local->oper_channel->band != IEEE80211_BAND_2GHZ)
+               return capab;
+
+       if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE))
+               capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
+       if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE))
+               capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
+
+       return capab;
+}
+
+static void ieee80211_tdls_add_link_ie(struct sk_buff *skb, u8 *src_addr,
+                                      u8 *peer, u8 *bssid)
+{
+       struct ieee80211_tdls_lnkie *lnkid;
+
+       lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie));
+
+       lnkid->ie_type = WLAN_EID_LINK_ID;
+       lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2;
+
+       memcpy(lnkid->bssid, bssid, ETH_ALEN);
+       memcpy(lnkid->init_sta, src_addr, ETH_ALEN);
+       memcpy(lnkid->resp_sta, peer, ETH_ALEN);
+}
+
+static int
+ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev,
+                              u8 *peer, u8 action_code, u8 dialog_token,
+                              u16 status_code, struct sk_buff *skb)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_tdls_data *tf;
+
+       tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u));
+
+       memcpy(tf->da, peer, ETH_ALEN);
+       memcpy(tf->sa, sdata->vif.addr, ETH_ALEN);
+       tf->ether_type = cpu_to_be16(ETH_P_TDLS);
+       tf->payload_type = WLAN_TDLS_SNAP_RFTYPE;
+
+       switch (action_code) {
+       case WLAN_TDLS_SETUP_REQUEST:
+               tf->category = WLAN_CATEGORY_TDLS;
+               tf->action_code = WLAN_TDLS_SETUP_REQUEST;
+
+               skb_put(skb, sizeof(tf->u.setup_req));
+               tf->u.setup_req.dialog_token = dialog_token;
+               tf->u.setup_req.capability =
+                       cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata));
+
+               ieee80211_add_srates_ie(&sdata->vif, skb);
+               ieee80211_add_ext_srates_ie(&sdata->vif, skb);
+               ieee80211_tdls_add_ext_capab(skb);
+               break;
+       case WLAN_TDLS_SETUP_RESPONSE:
+               tf->category = WLAN_CATEGORY_TDLS;
+               tf->action_code = WLAN_TDLS_SETUP_RESPONSE;
+
+               skb_put(skb, sizeof(tf->u.setup_resp));
+               tf->u.setup_resp.status_code = cpu_to_le16(status_code);
+               tf->u.setup_resp.dialog_token = dialog_token;
+               tf->u.setup_resp.capability =
+                       cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata));
+
+               ieee80211_add_srates_ie(&sdata->vif, skb);
+               ieee80211_add_ext_srates_ie(&sdata->vif, skb);
+               ieee80211_tdls_add_ext_capab(skb);
+               break;
+       case WLAN_TDLS_SETUP_CONFIRM:
+               tf->category = WLAN_CATEGORY_TDLS;
+               tf->action_code = WLAN_TDLS_SETUP_CONFIRM;
+
+               skb_put(skb, sizeof(tf->u.setup_cfm));
+               tf->u.setup_cfm.status_code = cpu_to_le16(status_code);
+               tf->u.setup_cfm.dialog_token = dialog_token;
+               break;
+       case WLAN_TDLS_TEARDOWN:
+               tf->category = WLAN_CATEGORY_TDLS;
+               tf->action_code = WLAN_TDLS_TEARDOWN;
+
+               skb_put(skb, sizeof(tf->u.teardown));
+               tf->u.teardown.reason_code = cpu_to_le16(status_code);
+               break;
+       case WLAN_TDLS_DISCOVERY_REQUEST:
+               tf->category = WLAN_CATEGORY_TDLS;
+               tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST;
+
+               skb_put(skb, sizeof(tf->u.discover_req));
+               tf->u.discover_req.dialog_token = dialog_token;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int
+ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev,
+                          u8 *peer, u8 action_code, u8 dialog_token,
+                          u16 status_code, struct sk_buff *skb)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_mgmt *mgmt;
+
+       mgmt = (void *)skb_put(skb, 24);
+       memset(mgmt, 0, 24);
+       memcpy(mgmt->da, peer, ETH_ALEN);
+       memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+       memcpy(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN);
+
+       mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+                                         IEEE80211_STYPE_ACTION);
+
+       switch (action_code) {
+       case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+               skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp));
+               mgmt->u.action.category = WLAN_CATEGORY_PUBLIC;
+               mgmt->u.action.u.tdls_discover_resp.action_code =
+                       WLAN_PUB_ACTION_TDLS_DISCOVER_RES;
+               mgmt->u.action.u.tdls_discover_resp.dialog_token =
+                       dialog_token;
+               mgmt->u.action.u.tdls_discover_resp.capability =
+                       cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata));
+
+               ieee80211_add_srates_ie(&sdata->vif, skb);
+               ieee80211_add_ext_srates_ie(&sdata->vif, skb);
+               ieee80211_tdls_add_ext_capab(skb);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
+                              u8 *peer, u8 action_code, u8 dialog_token,
+                              u16 status_code, const u8 *extra_ies,
+                              size_t extra_ies_len)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_tx_info *info;
+       struct sk_buff *skb = NULL;
+       bool send_direct;
+       int ret;
+
+       if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+               return -ENOTSUPP;
+
+       /* make sure we are in managed mode, and associated */
+       if (sdata->vif.type != NL80211_IFTYPE_STATION ||
+           !sdata->u.mgd.associated)
+               return -EINVAL;
+
+#ifdef CONFIG_MAC80211_VERBOSE_TDLS_DEBUG
+       printk(KERN_DEBUG "TDLS mgmt action %d peer %pM\n", action_code, peer);
+#endif
+
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom +
+                           max(sizeof(struct ieee80211_mgmt),
+                               sizeof(struct ieee80211_tdls_data)) +
+                           50 + /* supported rates */
+                           7 + /* ext capab */
+                           extra_ies_len +
+                           sizeof(struct ieee80211_tdls_lnkie));
+       if (!skb)
+               return -ENOMEM;
+
+       info = IEEE80211_SKB_CB(skb);
+       skb_reserve(skb, local->hw.extra_tx_headroom);
+
+       switch (action_code) {
+       case WLAN_TDLS_SETUP_REQUEST:
+       case WLAN_TDLS_SETUP_RESPONSE:
+       case WLAN_TDLS_SETUP_CONFIRM:
+       case WLAN_TDLS_TEARDOWN:
+       case WLAN_TDLS_DISCOVERY_REQUEST:
+               ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer,
+                                                    action_code, dialog_token,
+                                                    status_code, skb);
+               send_direct = false;
+               break;
+       case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+               ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code,
+                                                dialog_token, status_code,
+                                                skb);
+               send_direct = true;
+               break;
+       default:
+               ret = -ENOTSUPP;
+               break;
+       }
+
+       if (ret < 0)
+               goto fail;
+
+       if (extra_ies_len)
+               memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len);
+
+       /* the TDLS link IE is always added last */
+       switch (action_code) {
+       case WLAN_TDLS_SETUP_REQUEST:
+       case WLAN_TDLS_SETUP_CONFIRM:
+       case WLAN_TDLS_TEARDOWN:
+       case WLAN_TDLS_DISCOVERY_REQUEST:
+               /* we are the initiator */
+               ieee80211_tdls_add_link_ie(skb, sdata->vif.addr, peer,
+                                          sdata->u.mgd.bssid);
+               break;
+       case WLAN_TDLS_SETUP_RESPONSE:
+       case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
+               /* we are the responder */
+               ieee80211_tdls_add_link_ie(skb, peer, sdata->vif.addr,
+                                          sdata->u.mgd.bssid);
+               break;
+       default:
+               ret = -ENOTSUPP;
+               goto fail;
+       }
+
+       if (send_direct) {
+               ieee80211_tx_skb(sdata, skb);
+               return 0;
+       }
+
+       /*
+        * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise
+        * we should default to AC_VI.
+        */
+       switch (action_code) {
+       case WLAN_TDLS_SETUP_REQUEST:
+       case WLAN_TDLS_SETUP_RESPONSE:
+               skb_set_queue_mapping(skb, IEEE80211_AC_BK);
+               skb->priority = 2;
+               break;
+       default:
+               skb_set_queue_mapping(skb, IEEE80211_AC_VI);
+               skb->priority = 5;
+               break;
+       }
+
+       /* disable bottom halves when entering the Tx path */
+       local_bh_disable();
+       ret = ieee80211_subif_start_xmit(skb, dev);
+       local_bh_enable();
+
+       return ret;
+
+fail:
+       dev_kfree_skb(skb);
+       return ret;
+}
+
+static int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
+                              u8 *peer, enum nl80211_tdls_operation oper)
+{
+       struct sta_info *sta;
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+               return -ENOTSUPP;
+
+       if (sdata->vif.type != NL80211_IFTYPE_STATION)
+               return -EINVAL;
+
+#ifdef CONFIG_MAC80211_VERBOSE_TDLS_DEBUG
+       printk(KERN_DEBUG "TDLS oper %d peer %pM\n", oper, peer);
+#endif
+
+       switch (oper) {
+       case NL80211_TDLS_ENABLE_LINK:
+               rcu_read_lock();
+               sta = sta_info_get(sdata, peer);
+               if (!sta) {
+                       rcu_read_unlock();
+                       return -ENOLINK;
+               }
+
+               set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
+               rcu_read_unlock();
+               break;
+       case NL80211_TDLS_DISABLE_LINK:
+               return sta_info_destroy_addr(sdata, peer);
+       case NL80211_TDLS_TEARDOWN:
+       case NL80211_TDLS_SETUP:
+       case NL80211_TDLS_DISCOVERY_REQ:
+               /* We don't support in-driver setup/teardown/discovery */
+               return -ENOTSUPP;
+       default:
+               return -ENOTSUPP;
+       }
+
+       return 0;
+}
+
 struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -2186,4 +2551,6 @@ struct cfg80211_ops mac80211_config_ops = {
        .set_ringparam = ieee80211_set_ringparam,
        .get_ringparam = ieee80211_get_ringparam,
        .set_rekey_data = ieee80211_set_rekey_data,
+       .tdls_oper = ieee80211_tdls_oper,
+       .tdls_mgmt = ieee80211_tdls_mgmt,
 };