summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJohn W. Linville <linville@tuxdriver.com>2011-12-19 13:54:26 -0500
committerJohn W. Linville <linville@tuxdriver.com>2011-12-19 13:54:26 -0500
commit9f6e20cee6253c4ca5faacba8dbd09ebe70132ed (patch)
tree0dbcdabca91864daaf174bb014a5f46316880403 /net
parentd1d182e00d72300e05b18e28372fab003d8d4a58 (diff)
parent5bd5e9a6ae5137a61d0b5c277eac61892d89fc4f (diff)
downloadblackbird-op-linux-9f6e20cee6253c4ca5faacba8dbd09ebe70132ed.tar.gz
blackbird-op-linux-9f6e20cee6253c4ca5faacba8dbd09ebe70132ed.zip
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-next into for-davem
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/agg-tx.c6
-rw-r--r--net/mac80211/cfg.c102
-rw-r--r--net/mac80211/debugfs_netdev.c2
-rw-r--r--net/mac80211/ht.c2
-rw-r--r--net/mac80211/ibss.c156
-rw-r--r--net/mac80211/ieee80211_i.h31
-rw-r--r--net/mac80211/iface.c5
-rw-r--r--net/mac80211/main.c11
-rw-r--r--net/mac80211/mesh_plink.c7
-rw-r--r--net/mac80211/mlme.c9
-rw-r--r--net/mac80211/rx.c4
-rw-r--r--net/mac80211/sta_info.c356
-rw-r--r--net/mac80211/sta_info.h39
-rw-r--r--net/mac80211/status.c15
-rw-r--r--net/mac80211/tx.c32
-rw-r--r--net/mac80211/util.c3
-rw-r--r--net/nfc/Kconfig1
-rw-r--r--net/nfc/Makefile1
-rw-r--r--net/nfc/core.c143
-rw-r--r--net/nfc/llcp/Kconfig7
-rw-r--r--net/nfc/llcp/commands.c399
-rw-r--r--net/nfc/llcp/llcp.c973
-rw-r--r--net/nfc/llcp/llcp.h193
-rw-r--r--net/nfc/llcp/sock.c675
-rw-r--r--net/nfc/nci/core.c4
-rw-r--r--net/nfc/nci/data.c2
-rw-r--r--net/nfc/nci/ntf.c2
-rw-r--r--net/nfc/nci/rsp.c2
-rw-r--r--net/nfc/netlink.c148
-rw-r--r--net/nfc/nfc.h63
-rw-r--r--net/nfc/rawsock.c11
-rw-r--r--net/wireless/core.h6
-rw-r--r--net/wireless/nl80211.c250
-rw-r--r--net/wireless/reg.c62
-rw-r--r--net/wireless/sme.c61
-rw-r--r--net/wireless/util.c6
36 files changed, 3298 insertions, 491 deletions
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 7380287f010e..e92f98d32746 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -55,6 +55,8 @@
* @ampdu_action function will be called with the action
* %IEEE80211_AMPDU_TX_STOP. In this case, the call must not fail,
* and the driver must later call ieee80211_stop_tx_ba_cb_irqsafe().
+ * Note that the sta can get destroyed before the BA tear down is
+ * complete.
*/
static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata,
@@ -105,7 +107,7 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata,
mgmt->u.action.u.addba_req.start_seq_num =
cpu_to_le16(start_seq_num << 4);
- ieee80211_tx_skb(sdata, skb);
+ ieee80211_tx_skb_tid(sdata, skb, tid);
}
void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
@@ -134,7 +136,7 @@ void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
bar->start_seq_num = cpu_to_le16(ssn);
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
- ieee80211_tx_skb(sdata, skb);
+ ieee80211_tx_skb_tid(sdata, skb, tid);
}
EXPORT_SYMBOL(ieee80211_send_bar);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 393b2a4445b8..66ad9d9af87f 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -746,10 +746,11 @@ static void ieee80211_send_layer2_update(struct sta_info *sta)
netif_rx_ni(skb);
}
-static void sta_apply_parameters(struct ieee80211_local *local,
- struct sta_info *sta,
- struct station_parameters *params)
+static int sta_apply_parameters(struct ieee80211_local *local,
+ struct sta_info *sta,
+ struct station_parameters *params)
{
+ int ret = 0;
u32 rates;
int i, j;
struct ieee80211_supported_band *sband;
@@ -761,13 +762,59 @@ static void sta_apply_parameters(struct ieee80211_local *local,
mask = params->sta_flags_mask;
set = params->sta_flags_set;
+ /*
+ * In mesh mode, we can clear AUTHENTICATED flag but must
+ * also make ASSOCIATED follow appropriately for the driver
+ * API. See also below, after AUTHORIZED changes.
+ */
+ if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
+ /* cfg80211 should not allow this in non-mesh modes */
+ if (WARN_ON(!ieee80211_vif_is_mesh(&sdata->vif)))
+ return -EINVAL;
+
+ if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
+ !test_sta_flag(sta, WLAN_STA_AUTH)) {
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_AUTH);
+ if (ret)
+ return ret;
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_ASSOC);
+ if (ret)
+ return ret;
+ }
+ }
+
if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
- set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_AUTHORIZED);
else
- clear_sta_flag(sta, WLAN_STA_AUTHORIZED);
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_ASSOC);
+ if (ret)
+ return ret;
}
+ if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
+ /* cfg80211 should not allow this in non-mesh modes */
+ if (WARN_ON(!ieee80211_vif_is_mesh(&sdata->vif)))
+ return -EINVAL;
+
+ if (!(set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) &&
+ test_sta_flag(sta, WLAN_STA_AUTH)) {
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_AUTH);
+ if (ret)
+ return ret;
+ ret = sta_info_move_state_checked(sta,
+ IEEE80211_STA_NONE);
+ if (ret)
+ return ret;
+ }
+ }
+
+
if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
set_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
@@ -792,13 +839,6 @@ static void sta_apply_parameters(struct ieee80211_local *local,
clear_sta_flag(sta, WLAN_STA_MFP);
}
- if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
- if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
- 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);
@@ -870,6 +910,8 @@ static void sta_apply_parameters(struct ieee80211_local *local,
}
#endif
}
+
+ return 0;
}
static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
@@ -896,20 +938,18 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
if (is_multicast_ether_addr(mac))
return -EINVAL;
- /* Only TDLS-supporting stations can add TDLS peers */
- if ((params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
- !((wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
- sdata->vif.type == NL80211_IFTYPE_STATION))
- return -ENOTSUPP;
-
sta = sta_info_alloc(sdata, mac, GFP_KERNEL);
if (!sta)
return -ENOMEM;
- set_sta_flag(sta, WLAN_STA_AUTH);
- set_sta_flag(sta, WLAN_STA_ASSOC);
+ sta_info_move_state(sta, IEEE80211_STA_AUTH);
+ sta_info_move_state(sta, IEEE80211_STA_ASSOC);
- sta_apply_parameters(local, sta, params);
+ err = sta_apply_parameters(local, sta, params);
+ if (err) {
+ sta_info_free(local, sta);
+ return err;
+ }
/*
* for TDLS, rate control should be initialized only when supported
@@ -960,19 +1000,19 @@ static int ieee80211_change_station(struct wiphy *wiphy,
struct sta_info *sta;
struct ieee80211_sub_if_data *vlansdata;
- rcu_read_lock();
+ mutex_lock(&local->sta_mtx);
sta = sta_info_get_bss(sdata, mac);
if (!sta) {
- rcu_read_unlock();
+ mutex_unlock(&local->sta_mtx);
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();
+ /* in station mode, supported rates are only valid with TDLS */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+ params->supported_rates &&
+ !test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
+ mutex_unlock(&local->sta_mtx);
return -EINVAL;
}
@@ -981,13 +1021,13 @@ static int ieee80211_change_station(struct wiphy *wiphy,
if (vlansdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
vlansdata->vif.type != NL80211_IFTYPE_AP) {
- rcu_read_unlock();
+ mutex_unlock(&local->sta_mtx);
return -EINVAL;
}
if (params->vlan->ieee80211_ptr->use_4addr) {
if (vlansdata->u.vlan.sta) {
- rcu_read_unlock();
+ mutex_unlock(&local->sta_mtx);
return -EBUSY;
}
@@ -1003,7 +1043,7 @@ static int ieee80211_change_station(struct wiphy *wiphy,
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) && params->supported_rates)
rate_control_rate_init(sta);
- rcu_read_unlock();
+ mutex_unlock(&local->sta_mtx);
if (sdata->vif.type == NL80211_IFTYPE_STATION &&
params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))
diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c
index 8df28910b8ee..176c08ffb13c 100644
--- a/net/mac80211/debugfs_netdev.c
+++ b/net/mac80211/debugfs_netdev.c
@@ -321,6 +321,7 @@ static ssize_t ieee80211_if_parse_tkip_mic_test(
__IEEE80211_IF_FILE_W(tkip_mic_test);
/* AP attributes */
+IEEE80211_IF_FILE(num_sta_authorized, u.ap.num_sta_authorized, ATOMIC);
IEEE80211_IF_FILE(num_sta_ps, u.ap.num_sta_ps, ATOMIC);
IEEE80211_IF_FILE(dtim_count, u.ap.dtim_count, DEC);
@@ -458,6 +459,7 @@ static void add_ap_files(struct ieee80211_sub_if_data *sdata)
DEBUGFS_ADD(rc_rateidx_mask_2ghz);
DEBUGFS_ADD(rc_rateidx_mask_5ghz);
+ DEBUGFS_ADD(num_sta_authorized);
DEBUGFS_ADD(num_sta_ps);
DEBUGFS_ADD(dtim_count);
DEBUGFS_ADD(num_buffered_multicast);
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 0fd9c2a7f242..f25fff7607d8 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -300,7 +300,7 @@ void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
mgmt->u.action.u.delba.params = cpu_to_le16(params);
mgmt->u.action.u.delba.reason_code = cpu_to_le16(reason_code);
- ieee80211_tx_skb(sdata, skb);
+ ieee80211_tx_skb_tid(sdata, skb, tid);
}
void ieee80211_process_delba(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 3f830ac159e5..f8a32bf98216 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -275,6 +275,80 @@ static void ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata,
cbss->tsf);
}
+static struct sta_info *ieee80211_ibss_finish_sta(struct sta_info *sta)
+ __acquires(RCU)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ u8 addr[ETH_ALEN];
+
+ memcpy(addr, sta->sta.addr, ETH_ALEN);
+
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+ wiphy_debug(sdata->local->hw.wiphy,
+ "Adding new IBSS station %pM (dev=%s)\n",
+ addr, sdata->name);
+#endif
+
+ sta_info_move_state(sta, IEEE80211_STA_AUTH);
+ sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+ sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
+
+ rate_control_rate_init(sta);
+
+ /* If it fails, maybe we raced another insertion? */
+ if (sta_info_insert_rcu(sta))
+ return sta_info_get(sdata, addr);
+ return sta;
+}
+
+static struct sta_info *
+ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
+ const u8 *bssid, const u8 *addr,
+ u32 supp_rates)
+ __acquires(RCU)
+{
+ struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+ int band = local->hw.conf.channel->band;
+
+ /*
+ * XXX: Consider removing the least recently used entry and
+ * allow new one to be added.
+ */
+ if (local->num_sta >= IEEE80211_IBSS_MAX_STA_ENTRIES) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "%s: No room for a new IBSS STA entry %pM\n",
+ sdata->name, addr);
+ rcu_read_lock();
+ return NULL;
+ }
+
+ if (ifibss->state == IEEE80211_IBSS_MLME_SEARCH) {
+ rcu_read_lock();
+ return NULL;
+ }
+
+ if (compare_ether_addr(bssid, sdata->u.ibss.bssid)) {
+ rcu_read_lock();
+ return NULL;
+ }
+
+ sta = sta_info_alloc(sdata, addr, GFP_KERNEL);
+ if (!sta) {
+ rcu_read_lock();
+ return NULL;
+ }
+
+ sta->last_rx = jiffies;
+
+ /* make sure mandatory rates are always added */
+ sta->sta.supp_rates[band] = supp_rates |
+ ieee80211_mandatory_rates(local, band);
+
+ return ieee80211_ibss_finish_sta(sta);
+}
+
static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgmt *mgmt,
size_t len,
@@ -334,10 +408,11 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
#endif
rates_updated = true;
}
- } else
+ } else {
+ rcu_read_unlock();
sta = ieee80211_ibss_add_sta(sdata, mgmt->bssid,
- mgmt->sa, supp_rates,
- GFP_ATOMIC);
+ mgmt->sa, supp_rates);
+ }
}
if (sta && elems->wmm_info)
@@ -464,21 +539,17 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_join_ibss(sdata, bss);
supp_rates = ieee80211_sta_get_rates(local, elems, band);
ieee80211_ibss_add_sta(sdata, mgmt->bssid, mgmt->sa,
- supp_rates, GFP_KERNEL);
+ supp_rates);
+ rcu_read_unlock();
}
put_bss:
ieee80211_rx_bss_put(local, bss);
}
-/*
- * Add a new IBSS station, will also be called by the RX code when,
- * in IBSS mode, receiving a frame from a yet-unknown station, hence
- * must be callable in atomic context.
- */
-struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
- u8 *bssid, u8 *addr, u32 supp_rates,
- gfp_t gfp)
+void ieee80211_ibss_rx_no_sta(struct ieee80211_sub_if_data *sdata,
+ const u8 *bssid, const u8 *addr,
+ u32 supp_rates)
{
struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
struct ieee80211_local *local = sdata->local;
@@ -493,37 +564,29 @@ struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
if (net_ratelimit())
printk(KERN_DEBUG "%s: No room for a new IBSS STA entry %pM\n",
sdata->name, addr);
- return NULL;
+ return;
}
if (ifibss->state == IEEE80211_IBSS_MLME_SEARCH)
- return NULL;
+ return;
if (compare_ether_addr(bssid, sdata->u.ibss.bssid))
- return NULL;
-
-#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
- wiphy_debug(local->hw.wiphy, "Adding new IBSS station %pM (dev=%s)\n",
- addr, sdata->name);
-#endif
+ return;
- sta = sta_info_alloc(sdata, addr, gfp);
+ sta = sta_info_alloc(sdata, addr, GFP_ATOMIC);
if (!sta)
- return NULL;
+ return;
sta->last_rx = jiffies;
- set_sta_flag(sta, WLAN_STA_AUTHORIZED);
/* make sure mandatory rates are always added */
sta->sta.supp_rates[band] = supp_rates |
ieee80211_mandatory_rates(local, band);
- rate_control_rate_init(sta);
-
- /* If it fails, maybe we raced another insertion? */
- if (sta_info_insert(sta))
- return sta_info_get(sdata, addr);
- return sta;
+ spin_lock(&ifibss->incomplete_lock);
+ list_add(&sta->list, &ifibss->incomplete_stations);
+ spin_unlock(&ifibss->incomplete_lock);
+ ieee80211_queue_work(&local->hw, &sdata->work);
}
static int ieee80211_sta_active_ibss(struct ieee80211_sub_if_data *sdata)
@@ -862,6 +925,7 @@ void ieee80211_ibss_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
void ieee80211_ibss_work(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
+ struct sta_info *sta;
mutex_lock(&ifibss->mtx);
@@ -873,6 +937,19 @@ void ieee80211_ibss_work(struct ieee80211_sub_if_data *sdata)
if (!ifibss->ssid_len)
goto out;
+ spin_lock_bh(&ifibss->incomplete_lock);
+ while (!list_empty(&ifibss->incomplete_stations)) {
+ sta = list_first_entry(&ifibss->incomplete_stations,
+ struct sta_info, list);
+ list_del(&sta->list);
+ spin_unlock_bh(&ifibss->incomplete_lock);
+
+ ieee80211_ibss_finish_sta(sta);
+ rcu_read_unlock();
+ spin_lock_bh(&ifibss->incomplete_lock);
+ }
+ spin_unlock_bh(&ifibss->incomplete_lock);
+
switch (ifibss->state) {
case IEEE80211_IBSS_MLME_SEARCH:
ieee80211_sta_find_ibss(sdata);
@@ -931,6 +1008,8 @@ void ieee80211_ibss_setup_sdata(struct ieee80211_sub_if_data *sdata)
setup_timer(&ifibss->timer, ieee80211_ibss_timer,
(unsigned long) sdata);
mutex_init(&ifibss->mtx);
+ INIT_LIST_HEAD(&ifibss->incomplete_stations);
+ spin_lock_init(&ifibss->incomplete_lock);
}
/* scan finished notification */
@@ -993,8 +1072,11 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata,
if (params->channel_fixed) {
sdata->local->oper_channel = params->channel;
if (!ieee80211_set_channel_type(sdata->local, sdata,
- params->channel_type))
+ params->channel_type)) {
+ mutex_unlock(&sdata->u.ibss.mtx);
+ kfree_skb(skb);
return -EINVAL;
+ }
}
if (params->ie) {
@@ -1047,6 +1129,7 @@ int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata)
struct cfg80211_bss *cbss;
u16 capability;
int active_ibss;
+ struct sta_info *sta;
mutex_lock(&sdata->u.ibss.mtx);
@@ -1075,6 +1158,19 @@ int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata)
}
sta_info_flush(sdata->local, sdata);
+
+ spin_lock_bh(&ifibss->incomplete_lock);
+ while (!list_empty(&ifibss->incomplete_stations)) {
+ sta = list_first_entry(&ifibss->incomplete_stations,
+ struct sta_info, list);
+ list_del(&sta->list);
+ spin_unlock_bh(&ifibss->incomplete_lock);
+
+ sta_info_free(local, sta);
+ spin_lock_bh(&ifibss->incomplete_lock);
+ }
+ spin_unlock_bh(&ifibss->incomplete_lock);
+
netif_carrier_off(sdata->dev);
/* remove beacon */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 96fe75410bbe..c3f3e431a573 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -243,6 +243,7 @@ struct ieee80211_if_ap {
u8 tim[sizeof(unsigned long) * BITS_TO_LONGS(IEEE80211_MAX_AID + 1)];
struct sk_buff_head ps_bc_buf;
atomic_t num_sta_ps; /* number of stations in PS mode */
+ atomic_t num_sta_authorized; /* number of authorized stations */
int dtim_count;
bool dtim_bc_mc;
};
@@ -481,6 +482,9 @@ struct ieee80211_if_ibss {
struct sk_buff __rcu *presp;
struct sk_buff *skb;
+ spinlock_t incomplete_lock;
+ struct list_head incomplete_stations;
+
enum {
IEEE80211_IBSS_MLME_SEARCH,
IEEE80211_IBSS_MLME_JOINED,
@@ -851,18 +855,15 @@ struct ieee80211_local {
/* Station data */
/*
- * The mutex only protects the list and counter,
- * reads are done in RCU.
- * Additionally, the lock protects the hash table,
- * the pending list and each BSS's TIM bitmap.
+ * The mutex only protects the list, hash table and
+ * counter, reads are done with RCU.
*/
struct mutex sta_mtx;
- spinlock_t sta_lock;
+ spinlock_t tim_lock;
unsigned long num_sta;
- struct list_head sta_list, sta_pending_list;
+ struct list_head sta_list;
struct sta_info __rcu *sta_hash[STA_HASH_SIZE];
struct timer_list sta_cleanup;
- struct work_struct sta_finish_work;
int sta_generation;
struct sk_buff_head pending[IEEE80211_MAX_QUEUES];
@@ -1171,9 +1172,8 @@ void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata);
/* IBSS code */
void ieee80211_ibss_notify_scan_completed(struct ieee80211_local *local);
void ieee80211_ibss_setup_sdata(struct ieee80211_sub_if_data *sdata);
-struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
- u8 *bssid, u8 *addr, u32 supp_rates,
- gfp_t gfp);
+void ieee80211_ibss_rx_no_sta(struct ieee80211_sub_if_data *sdata,
+ const u8 *bssid, const u8 *addr, u32 supp_rates);
int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata,
struct cfg80211_ibss_params *params);
int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata);
@@ -1349,7 +1349,16 @@ void mac80211_ev_michael_mic_failure(struct ieee80211_sub_if_data *sdata, int ke
gfp_t gfp);
void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata);
void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb);
-void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb);
+
+void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, int tid);
+static void inline ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ /* Send all internal mgmt frames on VO. Accordingly set TID to 7. */
+ ieee80211_tx_skb_tid(sdata, skb, 7);
+}
+
void ieee802_11_parse_elems(u8 *start, size_t len,
struct ieee802_11_elems *elems);
u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 3d3bb5e9d8fa..e47768cb8cb3 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -318,8 +318,9 @@ static int ieee80211_do_open(struct net_device *dev, bool coming_up)
goto err_del_interface;
}
- /* no atomic bitop required since STA is not live yet */
- set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+ sta_info_move_state(sta, IEEE80211_STA_AUTH);
+ sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+ sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
res = sta_info_insert(sta);
if (res) {
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 60198ac664a0..0a0d94ad9b08 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -47,7 +47,7 @@ void ieee80211_configure_filter(struct ieee80211_local *local)
if (atomic_read(&local->iff_allmultis))
new_flags |= FIF_ALLMULTI;
- if (local->monitors || local->scanning)
+ if (local->monitors || test_bit(SCAN_SW_SCANNING, &local->scanning))
new_flags |= FIF_BCN_PRBRESP_PROMISC;
if (local->fif_probe_req || local->probe_req_reg)
@@ -150,8 +150,8 @@ int ieee80211_hw_config(struct ieee80211_local *local, u32 changed)
changed |= IEEE80211_CONF_CHANGE_SMPS;
}
- if ((local->scanning & SCAN_SW_SCANNING) ||
- (local->scanning & SCAN_HW_SCANNING))
+ if (test_bit(SCAN_SW_SCANNING, &local->scanning) ||
+ test_bit(SCAN_HW_SCANNING, &local->scanning))
power = chan->max_power;
else
power = local->power_constr_level ?
@@ -393,9 +393,6 @@ static int ieee80211_ifa_changed(struct notifier_block *nb,
sdata = IEEE80211_DEV_TO_SUB_IF(ndev);
bss_conf = &sdata->vif.bss_conf;
- if (!ieee80211_sdata_running(sdata))
- return NOTIFY_DONE;
-
/* ARP filtering is only supported in managed mode */
if (sdata->vif.type != NL80211_IFTYPE_STATION)
return NOTIFY_DONE;
@@ -424,7 +421,7 @@ static int ieee80211_ifa_changed(struct notifier_block *nb,
}
bss_conf->arp_addr_cnt = c;
- /* Configure driver only if associated */
+ /* Configure driver only if associated (which also implies it is up) */
if (ifmgd->associated) {
bss_conf->arp_filter_enabled = sdata->arp_filter_state;
ieee80211_bss_info_change_notify(sdata,
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 7314372b12ba..41ef1b476442 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -96,9 +96,12 @@ static struct sta_info *mesh_plink_alloc(struct ieee80211_sub_if_data *sdata,
if (!sta)
return NULL;
- set_sta_flag(sta, WLAN_STA_AUTH);
- set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+ sta_info_move_state(sta, IEEE80211_STA_AUTH);
+ sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+ sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
+
set_sta_flag(sta, WLAN_STA_WME);
+
sta->sta.supp_rates[local->hw.conf.channel->band] = rates;
if (elems->ht_cap_elem)
ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 09019d135942..a984f1f60ddb 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -819,7 +819,7 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
}
if ((local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) &&
- (!(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED))) {
+ !(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
netif_tx_stop_all_queues(sdata->dev);
if (drv_tx_frames_pending(local))
@@ -1577,10 +1577,10 @@ static bool ieee80211_assoc_success(struct ieee80211_work *wk,
return false;
}
- set_sta_flag(sta, WLAN_STA_AUTH);
- set_sta_flag(sta, WLAN_STA_ASSOC);
+ sta_info_move_state(sta, IEEE80211_STA_AUTH);
+ sta_info_move_state(sta, IEEE80211_STA_ASSOC);
if (!(ifmgd->flags & IEEE80211_STA_CONTROL_PORT))
- set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+ sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
rates = 0;
basic_rates = 0;
@@ -2371,6 +2371,7 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
(unsigned long) sdata);
ifmgd->flags = 0;
+ ifmgd->powersave = sdata->wdev.ps;
mutex_init(&ifmgd->mtx);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 7d226417ef46..2be5b7d69ad7 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2775,8 +2775,8 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx,
rate_idx = 0; /* TODO: HT rates */
else
rate_idx = status->rate_idx;
- rx->sta = ieee80211_ibss_add_sta(sdata, bssid,
- hdr->addr2, BIT(rate_idx), GFP_ATOMIC);
+ ieee80211_ibss_rx_no_sta(sdata, bssid, hdr->addr2,
+ BIT(rate_idx));
}
break;
case NL80211_IFTYPE_MESH_POINT:
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index f98235262006..2db01e9541e7 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -62,14 +62,14 @@
* freed before they are done using it.
*/
-/* Caller must hold local->sta_lock */
+/* Caller must hold local->sta_mtx */
static int sta_info_hash_del(struct ieee80211_local *local,
struct sta_info *sta)
{
struct sta_info *s;
s = rcu_dereference_protected(local->sta_hash[STA_HASH(sta->sta.addr)],
- lockdep_is_held(&local->sta_lock));
+ lockdep_is_held(&local->sta_mtx));
if (!s)
return -ENOENT;
if (s == sta) {
@@ -81,7 +81,7 @@ static int sta_info_hash_del(struct ieee80211_local *local,
while (rcu_access_pointer(s->hnext) &&
rcu_access_pointer(s->hnext) != sta)
s = rcu_dereference_protected(s->hnext,
- lockdep_is_held(&local->sta_lock));
+ lockdep_is_held(&local->sta_mtx));
if (rcu_access_pointer(s->hnext)) {
RCU_INIT_POINTER(s->hnext, sta->hnext);
return 0;
@@ -98,14 +98,12 @@ struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta;
sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
while (sta) {
if (sta->sdata == sdata && !sta->dummy &&
memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
break;
sta = rcu_dereference_check(sta->hnext,
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
}
return sta;
@@ -119,14 +117,12 @@ struct sta_info *sta_info_get_rx(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta;
sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
while (sta) {
if (sta->sdata == sdata &&
memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
break;
sta = rcu_dereference_check(sta->hnext,
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
}
return sta;
@@ -143,7 +139,6 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta;
sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
while (sta) {
if ((sta->sdata == sdata ||
@@ -152,7 +147,6 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
break;
sta = rcu_dereference_check(sta->hnext,
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
}
return sta;
@@ -169,7 +163,6 @@ struct sta_info *sta_info_get_bss_rx(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta;
sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
while (sta) {
if ((sta->sdata == sdata ||
@@ -177,7 +170,6 @@ struct sta_info *sta_info_get_bss_rx(struct ieee80211_sub_if_data *sdata,
memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
break;
sta = rcu_dereference_check(sta->hnext,
- lockdep_is_held(&local->sta_lock) ||
lockdep_is_held(&local->sta_mtx));
}
return sta;
@@ -204,16 +196,17 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
}
/**
- * __sta_info_free - internal STA free helper
+ * sta_info_free - free STA
*
* @local: pointer to the global information
* @sta: STA info to free
*
* This function must undo everything done by sta_info_alloc()
- * that may happen before sta_info_insert().
+ * that may happen before sta_info_insert(). It may only be
+ * called when sta_info_insert() has not been attempted (and
+ * if that fails, the station is freed anyway.)
*/
-static void __sta_info_free(struct ieee80211_local *local,
- struct sta_info *sta)
+void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
{
if (sta->rate_ctrl) {
rate_control_free_sta(sta);
@@ -227,10 +220,11 @@ static void __sta_info_free(struct ieee80211_local *local,
kfree(sta);
}
-/* Caller must hold local->sta_lock */
+/* Caller must hold local->sta_mtx */
static void sta_info_hash_add(struct ieee80211_local *local,
struct sta_info *sta)
{
+ lockdep_assert_held(&local->sta_mtx);
sta->hnext = local->sta_hash[STA_HASH(sta->sta.addr)];
RCU_INIT_POINTER(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
}
@@ -280,7 +274,7 @@ static int sta_prepare_rate_control(struct ieee80211_local *local,
}
struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
- u8 *addr, gfp_t gfp)
+ const u8 *addr, gfp_t gfp)
{
struct ieee80211_local *local = sdata->local;
struct sta_info *sta;
@@ -338,98 +332,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
return sta;
}
-static int sta_info_finish_insert(struct sta_info *sta,
- bool async, bool dummy_reinsert)
-{
- struct ieee80211_local *local = sta->local;
- struct ieee80211_sub_if_data *sdata = sta->sdata;
- struct station_info sinfo;
- unsigned long flags;
- int err = 0;
-
- lockdep_assert_held(&local->sta_mtx);
-
- if (!sta->dummy || dummy_reinsert) {
- /* notify driver */
- err = drv_sta_add(local, sdata, &sta->sta);
- if (err) {
- if (!async)
- return err;
- printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to "
- "driver (%d) - keeping it anyway.\n",
- sdata->name, sta->sta.addr, err);
- } else {
- sta->uploaded = true;
-#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
- if (async)
- wiphy_debug(local->hw.wiphy,
- "Finished adding IBSS STA %pM\n",
- sta->sta.addr);
-#endif
- }
-
- sdata = sta->sdata;
- }
-
- if (!dummy_reinsert) {
- if (!async) {
- local->num_sta++;
- local->sta_generation++;
- smp_mb();
-
- /* make the station visible */
- spin_lock_irqsave(&local->sta_lock, flags);
- sta_info_hash_add(local, sta);
- spin_unlock_irqrestore(&local->sta_lock, flags);
- }
-
- list_add(&sta->list, &local->sta_list);
- } else {
- sta->dummy = false;
- }
-
- if (!sta->dummy) {
- ieee80211_sta_debugfs_add(sta);
- rate_control_add_sta_debugfs(sta);
-
- memset(&sinfo, 0, sizeof(sinfo));
- sinfo.filled = 0;
- sinfo.generation = local->sta_generation;
- cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);
- }
-
- return 0;
-}
-
-static void sta_info_finish_pending(struct ieee80211_local *local)
-{
- struct sta_info *sta;
- unsigned long flags;
-
- spin_lock_irqsave(&local->sta_lock, flags);
- while (!list_empty(&local->sta_pending_list)) {
- sta = list_first_entry(&local->sta_pending_list,
- struct sta_info, list);
- list_del(&sta->list);
- spin_unlock_irqrestore(&local->sta_lock, flags);
-
- sta_info_finish_insert(sta, true, false);
-
- spin_lock_irqsave(&local->sta_lock, flags);
- }
- spin_unlock_irqrestore(&local->sta_lock, flags);
-}
-
-static void sta_info_finish_work(struct work_struct *work)
-{
- struct ieee80211_local *local =
- container_of(work, struct ieee80211_local, sta_finish_work);
-
- mutex_lock(&local->sta_mtx);
- sta_info_finish_pending(local);
- mutex_unlock(&local->sta_mtx);
-}
-
static int sta_info_insert_check(struct sta_info *sta)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
@@ -449,50 +351,15 @@ static int sta_info_insert_check(struct sta_info *sta)
return 0;
}
-static int sta_info_insert_ibss(struct sta_info *sta) __acquires(RCU)
-{
- struct ieee80211_local *local = sta->local;
- struct ieee80211_sub_if_data *sdata = sta->sdata;
- unsigned long flags;
-
- spin_lock_irqsave(&local->sta_lock, flags);
- /* check if STA exists already */
- if (sta_info_get_bss_rx(sdata, sta->sta.addr)) {
- spin_unlock_irqrestore(&local->sta_lock, flags);
- rcu_read_lock();
- return -EEXIST;
- }
-
- local->num_sta++;
- local->sta_generation++;
- smp_mb();
- sta_info_hash_add(local, sta);
-
- list_add_tail(&sta->list, &local->sta_pending_list);
-
- rcu_read_lock();
- spin_unlock_irqrestore(&local->sta_lock, flags);
-
-#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
- wiphy_debug(local->hw.wiphy, "Added IBSS STA %pM\n",
- sta->sta.addr);
-#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
-
- ieee80211_queue_work(&local->hw, &local->sta_finish_work);
-
- return 0;
-}
-
/*
* should be called with sta_mtx locked
* this function replaces the mutex lock
* with a RCU lock
*/
-static int sta_info_insert_non_ibss(struct sta_info *sta) __acquires(RCU)
+static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
{
struct ieee80211_local *local = sta->local;
struct ieee80211_sub_if_data *sdata = sta->sdata;
- unsigned long flags;
struct sta_info *exist_sta;
bool dummy_reinsert = false;
int err = 0;
@@ -500,19 +367,8 @@ static int sta_info_insert_non_ibss(struct sta_info *sta) __acquires(RCU)
lockdep_assert_held(&local->sta_mtx);
/*
- * On first glance, this will look racy, because the code
- * in this function, which inserts a station with sleeping,
- * unlocks the sta_lock between checking existence in the
- * hash table and inserting into it.
- *
- * However, it is not racy against itself because it keeps
- * the mutex locked.
- */
-
- spin_lock_irqsave(&local->sta_lock, flags);
- /*
* check if STA exists already.
- * only accept a scenario of a second call to sta_info_insert_non_ibss
+ * only accept a scenario of a second call to sta_info_insert_finish
* with a dummy station entry that was inserted earlier
* in that case - assume that the dummy station flag should
* be removed.
@@ -522,20 +378,47 @@ static int sta_info_insert_non_ibss(struct sta_info *sta) __acquires(RCU)
if (exist_sta == sta && sta->dummy) {
dummy_reinsert = true;
} else {
- spin_unlock_irqrestore(&local->sta_lock, flags);
- mutex_unlock(&local->sta_mtx);
- rcu_read_lock();
- return -EEXIST;
+ err = -EEXIST;
+ goto out_err;
}
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ if (!sta->dummy || dummy_reinsert) {
+ /* notify driver */
+ err = drv_sta_add(local, sdata, &sta->sta);
+ if (err) {
+ if (sdata->vif.type != NL80211_IFTYPE_ADHOC)
+ goto out_err;
+ printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to "
+ "driver (%d) - keeping it anyway.\n",
+ sdata->name, sta->sta.addr, err);
+ } else
+ sta->uploaded = true;
+ }
- err = sta_info_finish_insert(sta, false, dummy_reinsert);
- if (err) {
- mutex_unlock(&local->sta_mtx);
- rcu_read_lock();
- return err;
+ if (!dummy_reinsert) {
+ local->num_sta++;
+ local->sta_generation++;
+ smp_mb();
+
+ /* make the station visible */
+ sta_info_hash_add(local, sta);
+
+ list_add(&sta->list, &local->sta_list);
+ } else {
+ sta->dummy = false;
+ }
+
+ if (!sta->dummy) {
+ struct station_info sinfo;
+
+ ieee80211_sta_debugfs_add(sta);
+ rate_control_add_sta_debugfs(sta);
+
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.filled = 0;
+ sinfo.generation = local->sta_generation;
+ cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);
}
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
@@ -551,54 +434,35 @@ static int sta_info_insert_non_ibss(struct sta_info *sta) __acquires(RCU)
mesh_accept_plinks_update(sdata);
return 0;
+ out_err:
+ mutex_unlock(&local->sta_mtx);
+ rcu_read_lock();
+ return err;
}
int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
{
struct ieee80211_local *local = sta->local;
- struct ieee80211_sub_if_data *sdata = sta->sdata;
int err = 0;
+ might_sleep();
+
err = sta_info_insert_check(sta);
if (err) {
rcu_read_lock();
goto out_free;
}
- /*
- * In ad-hoc mode, we sometimes need to insert stations
- * from tasklet context from the RX path. To avoid races,
- * always do so in that case -- see the comment below.
- */
- if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
- err = sta_info_insert_ibss(sta);
- if (err)
- goto out_free;
-
- return 0;
- }
-
- /*
- * It might seem that the function called below is in race against
- * the function call above that atomically inserts the station... That,
- * however, is not true because the above code can only
- * be invoked for IBSS interfaces, and the below code will
- * not be -- and the two do not race against each other as
- * the hash table also keys off the interface.
- */
-
- might_sleep();
-
mutex_lock(&local->sta_mtx);
- err = sta_info_insert_non_ibss(sta);
+ err = sta_info_insert_finish(sta);
if (err)
goto out_free;
return 0;
out_free:
BUG_ON(!err);
- __sta_info_free(local, sta);
+ sta_info_free(local, sta);
return err;
}
@@ -625,7 +489,7 @@ int sta_info_reinsert(struct sta_info *sta)
might_sleep();
- err = sta_info_insert_non_ibss(sta);
+ err = sta_info_insert_finish(sta);
rcu_read_unlock();
return err;
}
@@ -712,7 +576,7 @@ void sta_info_recalc_tim(struct sta_info *sta)
}
done:
- spin_lock_irqsave(&local->sta_lock, flags);
+ spin_lock_irqsave(&local->tim_lock, flags);
if (indicate_tim)
__bss_tim_set(bss, sta->sta.aid);
@@ -725,7 +589,7 @@ void sta_info_recalc_tim(struct sta_info *sta)
local->tim_in_locked_section = false;
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ spin_unlock_irqrestore(&local->tim_lock, flags);
}
static bool sta_info_buffer_expired(struct sta_info *sta, struct sk_buff *skb)
@@ -849,8 +713,8 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
{
struct ieee80211_local *local;
struct ieee80211_sub_if_data *sdata;
- unsigned long flags;
int ret, i, ac;
+ struct tid_ampdu_tx *tid_tx;
might_sleep();
@@ -869,15 +733,12 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
set_sta_flag(sta, WLAN_STA_BLOCK_BA);
ieee80211_sta_tear_down_BA_sessions(sta, true);
- spin_lock_irqsave(&local->sta_lock, flags);
ret = sta_info_hash_del(local, sta);
- /* this might still be the pending list ... which is fine */
- if (!ret)
- list_del(&sta->list);
- spin_unlock_irqrestore(&local->sta_lock, flags);
if (ret)
return ret;
+ list_del(&sta->list);
+
mutex_lock(&local->key_mtx);
for (i = 0; i < NUM_DEFAULT_KEYS; i++)
__ieee80211_key_free(key_mtx_dereference(local, sta->gtk[i]));
@@ -904,6 +765,9 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
+ while (sta->sta_state > IEEE80211_STA_NONE)
+ sta_info_move_state(sta, sta->sta_state - 1);
+
if (sta->uploaded) {
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss,
@@ -949,7 +813,31 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
}
#endif
- __sta_info_free(local, sta);
+ /* There could be some memory leaks because of ampdu tx pending queue
+ * not being freed before destroying the station info.
+ *
+ * Make sure that such queues are purged before freeing the station
+ * info.
+ * TODO: We have to somehow postpone the full destruction
+ * until the aggregation stop completes. Refer
+ * http://thread.gmane.org/gmane.linux.kernel.wireless.general/81936
+ */
+ for (i = 0; i < STA_TID_NUM; i++) {
+ if (!sta->ampdu_mlme.tid_tx[i])
+ continue;
+ tid_tx = sta->ampdu_mlme.tid_tx[i];
+ if (skb_queue_len(&tid_tx->pending)) {
+#ifdef CONFIG_MAC80211_HT_DEBUG
+ wiphy_debug(local->hw.wiphy, "TX A-MPDU purging %d "
+ "packets for tid=%d\n",
+ skb_queue_len(&tid_tx->pending), i);
+#endif /* CONFIG_MAC80211_HT_DEBUG */
+ __skb_queue_purge(&tid_tx->pending);
+ }
+ kfree_rcu(tid_tx, rcu_head);
+ }
+
+ sta_info_free(local, sta);
return 0;
}
@@ -1005,11 +893,9 @@ static void sta_info_cleanup(unsigned long data)
void sta_info_init(struct ieee80211_local *local)
{
- spin_lock_init(&local->sta_lock);
+ spin_lock_init(&local->tim_lock);
mutex_init(&local->sta_mtx);
INIT_LIST_HEAD(&local->sta_list);
- INIT_LIST_HEAD(&local->sta_pending_list);
- INIT_WORK(&local->sta_finish_work, sta_info_finish_work);
setup_timer(&local->sta_cleanup, sta_info_cleanup,
(unsigned long)local);
@@ -1038,9 +924,6 @@ int sta_info_flush(struct ieee80211_local *local,
might_sleep();
mutex_lock(&local->sta_mtx);
-
- sta_info_finish_pending(local);
-
list_for_each_entry_safe(sta, tmp, &local->sta_list, list) {
if (!sdata || sdata == sta->sdata)
WARN_ON(__sta_info_destroy(sta));
@@ -1513,3 +1396,56 @@ void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
sta_info_recalc_tim(sta);
}
EXPORT_SYMBOL(ieee80211_sta_set_buffered);
+
+int sta_info_move_state_checked(struct sta_info *sta,
+ enum ieee80211_sta_state new_state)
+{
+ might_sleep();
+
+ if (sta->sta_state == new_state)
+ return 0;
+
+ switch (new_state) {
+ case IEEE80211_STA_NONE:
+ if (sta->sta_state == IEEE80211_STA_AUTH)
+ clear_bit(WLAN_STA_AUTH, &sta->_flags);
+ else
+ return -EINVAL;
+ break;
+ case IEEE80211_STA_AUTH:
+ if (sta->sta_state == IEEE80211_STA_NONE)
+ set_bit(WLAN_STA_AUTH, &sta->_flags);
+ else if (sta->sta_state == IEEE80211_STA_ASSOC)
+ clear_bit(WLAN_STA_ASSOC, &sta->_flags);
+ else
+ return -EINVAL;
+ break;
+ case IEEE80211_STA_ASSOC:
+ if (sta->sta_state == IEEE80211_STA_AUTH) {
+ set_bit(WLAN_STA_ASSOC, &sta->_flags);
+ } else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
+ if (sta->sdata->vif.type == NL80211_IFTYPE_AP)
+ atomic_dec(&sta->sdata->u.ap.num_sta_authorized);
+ clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+ } else
+ return -EINVAL;
+ break;
+ case IEEE80211_STA_AUTHORIZED:
+ if (sta->sta_state == IEEE80211_STA_ASSOC) {
+ if (sta->sdata->vif.type == NL80211_IFTYPE_AP)
+ atomic_inc(&sta->sdata->u.ap.num_sta_authorized);
+ set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+ } else
+ return -EINVAL;
+ break;
+ default:
+ WARN(1, "invalid state %d", new_state);
+ return -EINVAL;
+ }
+
+ printk(KERN_DEBUG "%s: moving STA %pM to state %d\n",
+ sta->sdata->name, sta->sta.addr, new_state);
+ sta->sta_state = new_state;
+
+ return 0;
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 1a14fab4bc9a..15b3bb7d8629 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -73,6 +73,14 @@ enum ieee80211_sta_info_flags {
WLAN_STA_4ADDR_EVENT,
};
+enum ieee80211_sta_state {
+ /* NOTE: These need to be ordered correctly! */
+ IEEE80211_STA_NONE,
+ IEEE80211_STA_AUTH,
+ IEEE80211_STA_ASSOC,
+ IEEE80211_STA_AUTHORIZED,
+};
+
#define STA_TID_NUM 16
#define ADDBA_RESP_INTERVAL HZ
#define HT_AGG_MAX_RETRIES 0x3
@@ -262,6 +270,7 @@ struct sta_ampdu_mlme {
* @dummy: indicate a dummy station created for receiving
* EAP frames before association
* @sta: station information we share with the driver
+ * @sta_state: duplicates information about station state (for debug)
*/
struct sta_info {
/* General information, mostly static */
@@ -283,6 +292,8 @@ struct sta_info {
bool uploaded;
+ enum ieee80211_sta_state sta_state;
+
/* use the accessors defined below */
unsigned long _flags;
@@ -371,12 +382,18 @@ static inline enum nl80211_plink_state sta_plink_state(struct sta_info *sta)
static inline void set_sta_flag(struct sta_info *sta,
enum ieee80211_sta_info_flags flag)
{
+ WARN_ON(flag == WLAN_STA_AUTH ||
+ flag == WLAN_STA_ASSOC ||
+ flag == WLAN_STA_AUTHORIZED);
set_bit(flag, &sta->_flags);
}
static inline void clear_sta_flag(struct sta_info *sta,
enum ieee80211_sta_info_flags flag)
{
+ WARN_ON(flag == WLAN_STA_AUTH ||
+ flag == WLAN_STA_ASSOC ||
+ flag == WLAN_STA_AUTHORIZED);
clear_bit(flag, &sta->_flags);
}
@@ -389,15 +406,32 @@ static inline int test_sta_flag(struct sta_info *sta,
static inline int test_and_clear_sta_flag(struct sta_info *sta,
enum ieee80211_sta_info_flags flag)
{
+ WARN_ON(flag == WLAN_STA_AUTH ||
+ flag == WLAN_STA_ASSOC ||
+ flag == WLAN_STA_AUTHORIZED);
return test_and_clear_bit(flag, &sta->_flags);
}
static inline int test_and_set_sta_flag(struct sta_info *sta,
enum ieee80211_sta_info_flags flag)
{
+ WARN_ON(flag == WLAN_STA_AUTH ||
+ flag == WLAN_STA_ASSOC ||
+ flag == WLAN_STA_AUTHORIZED);
return test_and_set_bit(flag, &sta->_flags);
}
+int sta_info_move_state_checked(struct sta_info *sta,
+ enum ieee80211_sta_state new_state);
+
+static inline void sta_info_move_state(struct sta_info *sta,
+ enum ieee80211_sta_state new_state)
+{
+ int ret = sta_info_move_state_checked(sta, new_state);
+ WARN_ON_ONCE(ret);
+}
+
+
void ieee80211_assign_tid_tx(struct sta_info *sta, int tid,
struct tid_ampdu_tx *tid_tx);
@@ -488,7 +522,10 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
* until sta_info_insert().
*/
struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
- u8 *addr, gfp_t gfp);
+ const u8 *addr, gfp_t gfp);
+
+void sta_info_free(struct ieee80211_local *local, struct sta_info *sta);
+
/*
* Insert STA info into hash table/list, returns zero or a
* -EEXIST if (if the same MAC address is already present).
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 46222ce0e5b1..30c265c98f73 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -340,7 +340,6 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- u16 frag, type;
__le16 fc;
struct ieee80211_supported_band *sband;
struct ieee80211_sub_if_data *sdata;
@@ -476,12 +475,8 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
* Fragments are passed to low-level drivers as separate skbs, so these
* are actually fragments, not frames. Update frame counters only for
* the first fragment of the frame. */
-
- frag = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG;
- type = le16_to_cpu(hdr->frame_control) & IEEE80211_FCTL_FTYPE;
-
if (info->flags & IEEE80211_TX_STAT_ACK) {
- if (frag == 0) {
+ if (ieee80211_is_first_frag(hdr->seq_ctrl)) {
local->dot11TransmittedFrameCount++;
if (is_multicast_ether_addr(hdr->addr1))
local->dot11MulticastTransmittedFrameCount++;
@@ -496,11 +491,11 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
* with a multicast address in the address 1 field of type Data
* or Management. */
if (!is_multicast_ether_addr(hdr->addr1) ||
- type == IEEE80211_FTYPE_DATA ||
- type == IEEE80211_FTYPE_MGMT)
+ ieee80211_is_data(fc) ||
+ ieee80211_is_mgmt(fc))
local->dot11TransmittedFragmentCount++;
} else {
- if (frag == 0)
+ if (ieee80211_is_first_frag(hdr->seq_ctrl))
local->dot11FailedCount++;
}
@@ -572,7 +567,7 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
/* Need to make a copy before skb->cb gets cleared */
send_to_cooked = !!(info->flags & IEEE80211_TX_CTL_INJECTED) ||
- (type != IEEE80211_FTYPE_DATA);
+ !(ieee80211_is_data(fc));
/*
* This is a bit racy but we can avoid a lot of work
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index e74652d38245..edcd1c7ab83f 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -295,7 +295,6 @@ ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx)
if (likely(tx->flags & IEEE80211_TX_UNICAST)) {
if (unlikely(!assoc &&
- tx->sdata->vif.type != NL80211_IFTYPE_ADHOC &&
ieee80211_is_data(hdr->frame_control))) {
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
printk(KERN_DEBUG "%s: dropped data frame to not "
@@ -305,17 +304,14 @@ ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx)
I802_DEBUG_INC(tx->local->tx_handlers_drop_not_assoc);
return TX_DROP;
}
- } else {
- if (unlikely(ieee80211_is_data(hdr->frame_control) &&
- tx->local->num_sta == 0 &&
- tx->sdata->vif.type != NL80211_IFTYPE_ADHOC)) {
- /*
- * No associated STAs - no need to send multicast
- * frames.
- */
- return TX_DROP;
- }
- return TX_CONTINUE;
+ } else if (unlikely(tx->sdata->vif.type == NL80211_IFTYPE_AP &&
+ ieee80211_is_data(hdr->frame_control) &&
+ !atomic_read(&tx->sdata->u.ap.num_sta_authorized))) {
+ /*
+ * No associated STAs - no need to send multicast
+ * frames.
+ */
+ return TX_DROP;
}
return TX_CONTINUE;
@@ -2337,9 +2333,9 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
} else {
unsigned long flags;
- spin_lock_irqsave(&local->sta_lock, flags);
+ spin_lock_irqsave(&local->tim_lock, flags);
ieee80211_beacon_add_tim(ap, skb, beacon);
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ spin_unlock_irqrestore(&local->tim_lock, flags);
}
if (tim_offset)
@@ -2700,15 +2696,15 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
}
EXPORT_SYMBOL(ieee80211_get_buffered_bc);
-void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)
+void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, int tid)
{
skb_set_mac_header(skb, 0);
skb_set_network_header(skb, 0);
skb_set_transport_header(skb, 0);
- /* Send all internal mgmt frames on VO. Accordingly set TID to 7. */
- skb_set_queue_mapping(skb, IEEE80211_AC_VO);
- skb->priority = 7;
+ skb_set_queue_mapping(skb, ieee802_1d_to_ac[tid]);
+ skb->priority = tid;
/*
* The other path calling ieee80211_xmit is from the tasklet,
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index ac7ea2949de0..eb1a5f737a9c 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1234,7 +1234,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
switch (sdata->vif.type) {
case NL80211_IFTYPE_STATION:
- changed |= BSS_CHANGED_ASSOC;
+ changed |= BSS_CHANGED_ASSOC |
+ BSS_CHANGED_ARP_FILTER;
mutex_lock(&sdata->u.mgd.mtx);
ieee80211_bss_info_change_notify(sdata, changed);
mutex_unlock(&sdata->u.mgd.mtx);
diff --git a/net/nfc/Kconfig b/net/nfc/Kconfig
index 58cddadf8e8e..44c865b86d6f 100644
--- a/net/nfc/Kconfig
+++ b/net/nfc/Kconfig
@@ -14,5 +14,6 @@ menuconfig NFC
be called nfc.
source "net/nfc/nci/Kconfig"
+source "net/nfc/llcp/Kconfig"
source "drivers/nfc/Kconfig"
diff --git a/net/nfc/Makefile b/net/nfc/Makefile
index fbb550f2377b..7b4a6dcfa566 100644
--- a/net/nfc/Makefile
+++ b/net/nfc/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_NFC) += nfc.o
obj-$(CONFIG_NFC_NCI) += nci/
nfc-objs := core.o netlink.o af_nfc.o rawsock.o
+nfc-$(CONFIG_NFC_LLCP) += llcp/llcp.o llcp/commands.o llcp/sock.o
diff --git a/net/nfc/core.c b/net/nfc/core.c
index 3ebc6b3aabac..3ddf6e698df0 100644
--- a/net/nfc/core.c
+++ b/net/nfc/core.c
@@ -21,12 +21,13 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/nfc.h>
#include "nfc.h"
@@ -180,6 +181,86 @@ error:
return rc;
}
+int nfc_dep_link_up(struct nfc_dev *dev, int target_index,
+ u8 comm_mode, u8 rf_mode)
+{
+ int rc = 0;
+
+ pr_debug("dev_name=%s comm:%d rf:%d\n",
+ dev_name(&dev->dev), comm_mode, rf_mode);
+
+ if (!dev->ops->dep_link_up)
+ return -EOPNOTSUPP;
+
+ device_lock(&dev->dev);
+
+ if (!device_is_registered(&dev->dev)) {
+ rc = -ENODEV;
+ goto error;
+ }
+
+ if (dev->dep_link_up == true) {
+ rc = -EALREADY;
+ goto error;
+ }
+
+ rc = dev->ops->dep_link_up(dev, target_index, comm_mode, rf_mode);
+
+error:
+ device_unlock(&dev->dev);
+ return rc;
+}
+
+int nfc_dep_link_down(struct nfc_dev *dev)
+{
+ int rc = 0;
+
+ pr_debug("dev_name=%s\n", dev_name(&dev->dev));
+
+ if (!dev->ops->dep_link_down)
+ return -EOPNOTSUPP;
+
+ device_lock(&dev->dev);
+
+ if (!device_is_registered(&dev->dev)) {
+ rc = -ENODEV;
+ goto error;
+ }
+
+ if (dev->dep_link_up == false) {
+ rc = -EALREADY;
+ goto error;
+ }
+
+ if (dev->dep_rf_mode == NFC_RF_TARGET) {
+ rc = -EOPNOTSUPP;
+ goto error;
+ }
+
+ rc = dev->ops->dep_link_down(dev);
+ if (!rc) {
+ dev->dep_link_up = false;
+ nfc_llcp_mac_is_down(dev);
+ nfc_genl_dep_link_down_event(dev);
+ }
+
+error:
+ device_unlock(&dev->dev);
+ return rc;
+}
+
+int nfc_dep_link_is_up(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode)
+{
+ dev->dep_link_up = true;
+ dev->dep_rf_mode = rf_mode;
+
+ nfc_llcp_mac_is_up(dev, target_idx, comm_mode, rf_mode);
+
+ return nfc_genl_dep_link_up_event(dev, target_idx, comm_mode, rf_mode);
+}
+EXPORT_SYMBOL(nfc_dep_link_is_up);
+
/**
* nfc_activate_target - prepare the target for data exchange
*
@@ -274,13 +355,54 @@ error:
return rc;
}
+int nfc_set_remote_general_bytes(struct nfc_dev *dev, u8 *gb, u8 gb_len)
+{
+ pr_debug("dev_name=%s gb_len=%d\n",
+ dev_name(&dev->dev), gb_len);
+
+ if (gb_len > NFC_MAX_GT_LEN)
+ return -EINVAL;
+
+ return nfc_llcp_set_remote_gb(dev, gb, gb_len);
+}
+EXPORT_SYMBOL(nfc_set_remote_general_bytes);
+
+u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *gt_len)
+{
+ return nfc_llcp_general_bytes(dev, gt_len);
+}
+EXPORT_SYMBOL(nfc_get_local_general_bytes);
+
/**
- * nfc_alloc_skb - allocate a skb for data exchange responses
+ * nfc_alloc_send_skb - allocate a skb for data exchange responses
*
* @size: size to allocate
* @gfp: gfp flags
*/
-struct sk_buff *nfc_alloc_skb(unsigned int size, gfp_t gfp)
+struct sk_buff *nfc_alloc_send_skb(struct nfc_dev *dev, struct sock *sk,
+ unsigned int flags, unsigned int size,
+ unsigned int *err)
+{
+ struct sk_buff *skb;
+ unsigned int total_size;
+
+ total_size = size +
+ dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+ skb = sock_alloc_send_skb(sk, total_size, flags & MSG_DONTWAIT, err);
+ if (skb)
+ skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+ return skb;
+}
+
+/**
+ * nfc_alloc_recv_skb - allocate a skb for data exchange responses
+ *
+ * @size: size to allocate
+ * @gfp: gfp flags
+ */
+struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp)
{
struct sk_buff *skb;
unsigned int total_size;
@@ -293,7 +415,7 @@ struct sk_buff *nfc_alloc_skb(unsigned int size, gfp_t gfp)
return skb;
}
-EXPORT_SYMBOL(nfc_alloc_skb);
+EXPORT_SYMBOL(nfc_alloc_recv_skb);
/**
* nfc_targets_found - inform that targets were found
@@ -441,6 +563,10 @@ int nfc_register_device(struct nfc_dev *dev)
if (rc < 0)
return rc;
+ rc = nfc_llcp_register_device(dev);
+ if (rc)
+ pr_err("Could not register llcp device\n");
+
rc = nfc_genl_device_added(dev);
if (rc)
pr_debug("The userspace won't be notified that the device %s was added\n",
@@ -472,6 +598,8 @@ void nfc_unregister_device(struct nfc_dev *dev)
mutex_unlock(&nfc_devlist_mutex);
+ nfc_llcp_unregister_device(dev);
+
rc = nfc_genl_device_removed(dev);
if (rc)
pr_debug("The userspace won't be notified that the device %s was removed\n",
@@ -501,6 +629,10 @@ static int __init nfc_init(void)
if (rc)
goto err_rawsock;
+ rc = nfc_llcp_init();
+ if (rc)
+ goto err_llcp_sock;
+
rc = af_nfc_init();
if (rc)
goto err_af_nfc;
@@ -508,6 +640,8 @@ static int __init nfc_init(void)
return 0;
err_af_nfc:
+ nfc_llcp_exit();
+err_llcp_sock:
rawsock_exit();
err_rawsock:
nfc_genl_exit();
@@ -519,6 +653,7 @@ err_genl:
static void __exit nfc_exit(void)
{
af_nfc_exit();
+ nfc_llcp_exit();
rawsock_exit();
nfc_genl_exit();
class_unregister(&nfc_class);
diff --git a/net/nfc/llcp/Kconfig b/net/nfc/llcp/Kconfig
new file mode 100644
index 000000000000..fbf5e8150908
--- /dev/null
+++ b/net/nfc/llcp/Kconfig
@@ -0,0 +1,7 @@
+config NFC_LLCP
+ depends on NFC && EXPERIMENTAL
+ bool "NFC LLCP support (EXPERIMENTAL)"
+ default n
+ help
+ Say Y here if you want to build support for a kernel NFC LLCP
+ implementation. \ No newline at end of file
diff --git a/net/nfc/llcp/commands.c b/net/nfc/llcp/commands.c
new file mode 100644
index 000000000000..151f2ef429c4
--- /dev/null
+++ b/net/nfc/llcp/commands.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+
+#include <net/nfc/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static u8 llcp_tlv_length[LLCP_TLV_MAX] = {
+ 0,
+ 1, /* VERSION */
+ 2, /* MIUX */
+ 2, /* WKS */
+ 1, /* LTO */
+ 1, /* RW */
+ 0, /* SN */
+ 1, /* OPT */
+ 0, /* SDREQ */
+ 2, /* SDRES */
+
+};
+
+static u8 llcp_tlv8(u8 *tlv, u8 type)
+{
+ if (tlv[0] != type || tlv[1] != llcp_tlv_length[tlv[0]])
+ return 0;
+
+ return tlv[2];
+}
+
+static u8 llcp_tlv16(u8 *tlv, u8 type)
+{
+ if (tlv[0] != type || tlv[1] != llcp_tlv_length[tlv[0]])
+ return 0;
+
+ return be16_to_cpu(*((__be16 *)(tlv + 2)));
+}
+
+
+static u8 llcp_tlv_version(u8 *tlv)
+{
+ return llcp_tlv8(tlv, LLCP_TLV_VERSION);
+}
+
+static u16 llcp_tlv_miux(u8 *tlv)
+{
+ return llcp_tlv16(tlv, LLCP_TLV_MIUX) & 0x7f;
+}
+
+static u16 llcp_tlv_wks(u8 *tlv)
+{
+ return llcp_tlv16(tlv, LLCP_TLV_WKS);
+}
+
+static u16 llcp_tlv_lto(u8 *tlv)
+{
+ return llcp_tlv8(tlv, LLCP_TLV_LTO);
+}
+
+static u8 llcp_tlv_opt(u8 *tlv)
+{
+ return llcp_tlv8(tlv, LLCP_TLV_OPT);
+}
+
+static u8 llcp_tlv_rw(u8 *tlv)
+{
+ return llcp_tlv8(tlv, LLCP_TLV_RW) & 0xf;
+}
+
+u8 *nfc_llcp_build_tlv(u8 type, u8 *value, u8 value_length, u8 *tlv_length)
+{
+ u8 *tlv, length;
+
+ pr_debug("type %d\n", type);
+
+ if (type >= LLCP_TLV_MAX)
+ return NULL;
+
+ length = llcp_tlv_length[type];
+ if (length == 0 && value_length == 0)
+ return NULL;
+ else
+ length = value_length;
+
+ *tlv_length = 2 + length;
+ tlv = kzalloc(2 + length, GFP_KERNEL);
+ if (tlv == NULL)
+ return tlv;
+
+ tlv[0] = type;
+ tlv[1] = length;
+ memcpy(tlv + 2, value, length);
+
+ return tlv;
+}
+
+int nfc_llcp_parse_tlv(struct nfc_llcp_local *local,
+ u8 *tlv_array, u16 tlv_array_len)
+{
+ u8 *tlv = tlv_array, type, length, offset = 0;
+
+ pr_debug("TLV array length %d\n", tlv_array_len);
+
+ if (local == NULL)
+ return -ENODEV;
+
+ while (offset < tlv_array_len) {
+ type = tlv[0];
+ length = tlv[1];
+
+ pr_debug("type 0x%x length %d\n", type, length);
+
+ switch (type) {
+ case LLCP_TLV_VERSION:
+ local->remote_version = llcp_tlv_version(tlv);
+ break;
+ case LLCP_TLV_MIUX:
+ local->remote_miu = llcp_tlv_miux(tlv) + 128;
+ break;
+ case LLCP_TLV_WKS:
+ local->remote_wks = llcp_tlv_wks(tlv);
+ break;
+ case LLCP_TLV_LTO:
+ local->remote_lto = llcp_tlv_lto(tlv) * 10;
+ break;
+ case LLCP_TLV_OPT:
+ local->remote_opt = llcp_tlv_opt(tlv);
+ break;
+ case LLCP_TLV_RW:
+ local->remote_rw = llcp_tlv_rw(tlv);
+ break;
+ default:
+ pr_err("Invalid gt tlv value 0x%x\n", type);
+ break;
+ }
+
+ offset += length + 2;
+ tlv += length + 2;
+ }
+
+ pr_debug("version 0x%x miu %d lto %d opt 0x%x wks 0x%x rw %d\n",
+ local->remote_version, local->remote_miu,
+ local->remote_lto, local->remote_opt,
+ local->remote_wks, local->remote_rw);
+
+ return 0;
+}
+
+static struct sk_buff *llcp_add_header(struct sk_buff *pdu,
+ u8 dsap, u8 ssap, u8 ptype)
+{
+ u8 header[2];
+
+ pr_debug("ptype 0x%x dsap 0x%x ssap 0x%x\n", ptype, dsap, ssap);
+
+ header[0] = (u8)((dsap << 2) | (ptype >> 2));
+ header[1] = (u8)((ptype << 6) | ssap);
+
+ pr_debug("header 0x%x 0x%x\n", header[0], header[1]);
+
+ memcpy(skb_put(pdu, LLCP_HEADER_SIZE), header, LLCP_HEADER_SIZE);
+
+ return pdu;
+}
+
+static struct sk_buff *llcp_add_tlv(struct sk_buff *pdu, u8 *tlv, u8 tlv_length)
+{
+ /* XXX Add an skb length check */
+
+ if (tlv == NULL)
+ return NULL;
+
+ memcpy(skb_put(pdu, tlv_length), tlv, tlv_length);
+
+ return pdu;
+}
+
+static struct sk_buff *llcp_allocate_pdu(struct nfc_llcp_sock *sock,
+ u8 cmd, u16 size)
+{
+ struct sk_buff *skb;
+ int err;
+
+ if (sock->ssap == 0)
+ return NULL;
+
+ skb = nfc_alloc_send_skb(sock->dev, &sock->sk, MSG_DONTWAIT,
+ size + LLCP_HEADER_SIZE, &err);
+ if (skb == NULL) {
+ pr_err("Could not allocate PDU\n");
+ return NULL;
+ }
+
+ skb = llcp_add_header(skb, sock->dsap, sock->ssap, cmd);
+
+ return skb;
+}
+
+int nfc_llcp_disconnect(struct nfc_llcp_sock *sock)
+{
+ struct sk_buff *skb;
+ struct nfc_dev *dev;
+ struct nfc_llcp_local *local;
+ u16 size = 0;
+
+ pr_debug("Sending DISC\n");
+
+ local = sock->local;
+ if (local == NULL)
+ return -ENODEV;
+
+ dev = sock->dev;
+ if (dev == NULL)
+ return -ENODEV;
+
+ size += LLCP_HEADER_SIZE;
+ size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+ skb = llcp_add_header(skb, sock->ssap, sock->dsap, LLCP_PDU_DISC);
+
+ skb_queue_tail(&local->tx_queue, skb);
+
+ return 0;
+}
+
+int nfc_llcp_send_symm(struct nfc_dev *dev)
+{
+ struct sk_buff *skb;
+ struct nfc_llcp_local *local;
+ u16 size = 0;
+
+ pr_debug("Sending SYMM\n");
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL)
+ return -ENODEV;
+
+ size += LLCP_HEADER_SIZE;
+ size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+ skb = llcp_add_header(skb, 0, 0, LLCP_PDU_SYMM);
+
+ return nfc_data_exchange(dev, local->target_idx, skb,
+ nfc_llcp_recv, local);
+}
+
+int nfc_llcp_send_connect(struct nfc_llcp_sock *sock)
+{
+ struct nfc_llcp_local *local;
+ struct sk_buff *skb;
+ u8 *service_name_tlv = NULL, service_name_tlv_length;
+ int err;
+ u16 size = 0;
+
+ pr_debug("Sending CONNECT\n");
+
+ local = sock->local;
+ if (local == NULL)
+ return -ENODEV;
+
+ if (sock->service_name != NULL) {
+ service_name_tlv = nfc_llcp_build_tlv(LLCP_TLV_SN,
+ sock->service_name,
+ sock->service_name_len,
+ &service_name_tlv_length);
+ size += service_name_tlv_length;
+ }
+
+ pr_debug("SKB size %d SN length %zu\n", size, sock->service_name_len);
+
+ skb = llcp_allocate_pdu(sock, LLCP_PDU_CONNECT, size);
+ if (skb == NULL) {
+ err = -ENOMEM;
+ goto error_tlv;
+ }
+
+ if (service_name_tlv != NULL)
+ skb = llcp_add_tlv(skb, service_name_tlv,
+ service_name_tlv_length);
+
+ skb_queue_tail(&local->tx_queue, skb);
+
+ return 0;
+
+error_tlv:
+ pr_err("error %d\n", err);
+
+ kfree(service_name_tlv);
+
+ return err;
+}
+
+int nfc_llcp_send_cc(struct nfc_llcp_sock *sock)
+{
+ struct nfc_llcp_local *local;
+ struct sk_buff *skb;
+
+ pr_debug("Sending CC\n");
+
+ local = sock->local;
+ if (local == NULL)
+ return -ENODEV;
+
+ skb = llcp_allocate_pdu(sock, LLCP_PDU_CC, 0);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ skb_queue_tail(&local->tx_queue, skb);
+
+ return 0;
+}
+
+int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason)
+{
+ struct sk_buff *skb;
+ struct nfc_dev *dev;
+ u16 size = 1; /* Reason code */
+
+ pr_debug("Sending DM reason 0x%x\n", reason);
+
+ if (local == NULL)
+ return -ENODEV;
+
+ dev = local->dev;
+ if (dev == NULL)
+ return -ENODEV;
+
+ size += LLCP_HEADER_SIZE;
+ size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
+
+ skb = llcp_add_header(skb, ssap, dsap, LLCP_PDU_DM);
+
+ memcpy(skb_put(skb, 1), &reason, 1);
+
+ skb_queue_head(&local->tx_queue, skb);
+
+ return 0;
+}
+
+int nfc_llcp_send_disconnect(struct nfc_llcp_sock *sock)
+{
+ struct sk_buff *skb;
+ struct nfc_llcp_local *local;
+
+ pr_debug("Send DISC\n");
+
+ local = sock->local;
+ if (local == NULL)
+ return -ENODEV;
+
+ skb = llcp_allocate_pdu(sock, LLCP_PDU_DISC, 0);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ skb_queue_head(&local->tx_queue, skb);
+
+ return 0;
+}
diff --git a/net/nfc/llcp/llcp.c b/net/nfc/llcp/llcp.c
new file mode 100644
index 000000000000..67756b23eac5
--- /dev/null
+++ b/net/nfc/llcp/llcp.c
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static u8 llcp_magic[3] = {0x46, 0x66, 0x6d};
+
+static struct list_head llcp_devices;
+
+static void nfc_llcp_socket_release(struct nfc_llcp_local *local)
+{
+ struct nfc_llcp_sock *parent, *s, *n;
+ struct sock *sk, *parent_sk;
+ int i;
+
+
+ mutex_lock(&local->socket_lock);
+
+ for (i = 0; i < LLCP_MAX_SAP; i++) {
+ parent = local->sockets[i];
+ if (parent == NULL)
+ continue;
+
+ /* Release all child sockets */
+ list_for_each_entry_safe(s, n, &parent->list, list) {
+ list_del(&s->list);
+ sk = &s->sk;
+
+ lock_sock(sk);
+
+ if (sk->sk_state == LLCP_CONNECTED)
+ nfc_put_device(s->dev);
+
+ sk->sk_state = LLCP_CLOSED;
+ sock_set_flag(sk, SOCK_DEAD);
+
+ release_sock(sk);
+ }
+
+ parent_sk = &parent->sk;
+
+ lock_sock(parent_sk);
+
+ if (parent_sk->sk_state == LLCP_LISTEN) {
+ struct nfc_llcp_sock *lsk, *n;
+ struct sock *accept_sk;
+
+ list_for_each_entry_safe(lsk, n, &parent->accept_queue,
+ accept_queue) {
+ accept_sk = &lsk->sk;
+ lock_sock(accept_sk);
+
+ nfc_llcp_accept_unlink(accept_sk);
+
+ accept_sk->sk_state = LLCP_CLOSED;
+ sock_set_flag(accept_sk, SOCK_DEAD);
+
+ release_sock(accept_sk);
+
+ sock_orphan(accept_sk);
+ }
+ }
+
+ if (parent_sk->sk_state == LLCP_CONNECTED)
+ nfc_put_device(parent->dev);
+
+ parent_sk->sk_state = LLCP_CLOSED;
+ sock_set_flag(parent_sk, SOCK_DEAD);
+
+ release_sock(parent_sk);
+ }
+
+ mutex_unlock(&local->socket_lock);
+}
+
+static void nfc_llcp_timeout_work(struct work_struct *work)
+{
+ struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+ timeout_work);
+
+ nfc_dep_link_down(local->dev);
+}
+
+static void nfc_llcp_symm_timer(unsigned long data)
+{
+ struct nfc_llcp_local *local = (struct nfc_llcp_local *) data;
+
+ pr_err("SYMM timeout\n");
+
+ queue_work(local->timeout_wq, &local->timeout_work);
+}
+
+struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev)
+{
+ struct nfc_llcp_local *local, *n;
+
+ list_for_each_entry_safe(local, n, &llcp_devices, list)
+ if (local->dev == dev)
+ return local;
+
+ pr_debug("No device found\n");
+
+ return NULL;
+}
+
+static char *wks[] = {
+ NULL,
+ NULL, /* SDP */
+ "urn:nfc:sn:ip",
+ "urn:nfc:sn:obex",
+ "urn:nfc:sn:snep",
+};
+
+static int nfc_llcp_wks_sap(char *service_name, size_t service_name_len)
+{
+ int sap, num_wks;
+
+ pr_debug("%s\n", service_name);
+
+ if (service_name == NULL)
+ return -EINVAL;
+
+ num_wks = ARRAY_SIZE(wks);
+
+ for (sap = 0 ; sap < num_wks; sap++) {
+ if (wks[sap] == NULL)
+ continue;
+
+ if (strncmp(wks[sap], service_name, service_name_len) == 0)
+ return sap;
+ }
+
+ return -EINVAL;
+}
+
+u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
+ struct nfc_llcp_sock *sock)
+{
+ mutex_lock(&local->sdp_lock);
+
+ if (sock->service_name != NULL && sock->service_name_len > 0) {
+ int ssap = nfc_llcp_wks_sap(sock->service_name,
+ sock->service_name_len);
+
+ if (ssap > 0) {
+ pr_debug("WKS %d\n", ssap);
+
+ /* This is a WKS, let's check if it's free */
+ if (local->local_wks & BIT(ssap)) {
+ mutex_unlock(&local->sdp_lock);
+
+ return LLCP_SAP_MAX;
+ }
+
+ set_bit(BIT(ssap), &local->local_wks);
+ mutex_unlock(&local->sdp_lock);
+
+ return ssap;
+ }
+
+ /*
+ * This is not a well known service,
+ * we should try to find a local SDP free spot
+ */
+ ssap = find_first_zero_bit(&local->local_sdp, LLCP_SDP_NUM_SAP);
+ if (ssap == LLCP_SDP_NUM_SAP) {
+ mutex_unlock(&local->sdp_lock);
+
+ return LLCP_SAP_MAX;
+ }
+
+ pr_debug("SDP ssap %d\n", LLCP_WKS_NUM_SAP + ssap);
+
+ set_bit(BIT(ssap), &local->local_sdp);
+ mutex_unlock(&local->sdp_lock);
+
+ return LLCP_WKS_NUM_SAP + ssap;
+
+ } else if (sock->ssap != 0) {
+ if (sock->ssap < LLCP_WKS_NUM_SAP) {
+ if (!(local->local_wks & BIT(sock->ssap))) {
+ set_bit(BIT(sock->ssap), &local->local_wks);
+ mutex_unlock(&local->sdp_lock);
+
+ return sock->ssap;
+ }
+
+ } else if (sock->ssap < LLCP_SDP_NUM_SAP) {
+ if (!(local->local_sdp &
+ BIT(sock->ssap - LLCP_WKS_NUM_SAP))) {
+ set_bit(BIT(sock->ssap - LLCP_WKS_NUM_SAP),
+ &local->local_sdp);
+ mutex_unlock(&local->sdp_lock);
+
+ return sock->ssap;
+ }
+ }
+ }
+
+ mutex_unlock(&local->sdp_lock);
+
+ return LLCP_SAP_MAX;
+}
+
+u8 nfc_llcp_get_local_ssap(struct nfc_llcp_local *local)
+{
+ u8 local_ssap;
+
+ mutex_lock(&local->sdp_lock);
+
+ local_ssap = find_first_zero_bit(&local->local_sap, LLCP_LOCAL_NUM_SAP);
+ if (local_ssap == LLCP_LOCAL_NUM_SAP) {
+ mutex_unlock(&local->sdp_lock);
+ return LLCP_SAP_MAX;
+ }
+
+ set_bit(BIT(local_ssap), &local->local_sap);
+
+ mutex_unlock(&local->sdp_lock);
+
+ return local_ssap + LLCP_LOCAL_SAP_OFFSET;
+}
+
+void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap)
+{
+ u8 local_ssap;
+ unsigned long *sdp;
+
+ if (ssap < LLCP_WKS_NUM_SAP) {
+ local_ssap = ssap;
+ sdp = &local->local_wks;
+ } else if (ssap < LLCP_LOCAL_NUM_SAP) {
+ local_ssap = ssap - LLCP_WKS_NUM_SAP;
+ sdp = &local->local_sdp;
+ } else if (ssap < LLCP_MAX_SAP) {
+ local_ssap = ssap - LLCP_LOCAL_NUM_SAP;
+ sdp = &local->local_sap;
+ } else {
+ return;
+ }
+
+ mutex_lock(&local->sdp_lock);
+
+ clear_bit(1 << local_ssap, sdp);
+
+ mutex_unlock(&local->sdp_lock);
+}
+
+u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *general_bytes_len)
+{
+ struct nfc_llcp_local *local;
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL) {
+ *general_bytes_len = 0;
+ return NULL;
+ }
+
+ *general_bytes_len = local->gb_len;
+
+ return local->gb;
+}
+
+static int nfc_llcp_build_gb(struct nfc_llcp_local *local)
+{
+ u8 *gb_cur, *version_tlv, version, version_length;
+ u8 *lto_tlv, lto, lto_length;
+ u8 *wks_tlv, wks_length;
+ u8 gb_len = 0;
+
+ version = LLCP_VERSION_11;
+ version_tlv = nfc_llcp_build_tlv(LLCP_TLV_VERSION, &version,
+ 1, &version_length);
+ gb_len += version_length;
+
+ /* 1500 ms */
+ lto = 150;
+ lto_tlv = nfc_llcp_build_tlv(LLCP_TLV_VERSION, &lto, 1, &lto_length);
+ gb_len += lto_length;
+
+ pr_debug("Local wks 0x%lx\n", local->local_wks);
+ wks_tlv = nfc_llcp_build_tlv(LLCP_TLV_WKS, (u8 *)&local->local_wks, 2,
+ &wks_length);
+ gb_len += wks_length;
+
+ gb_len += ARRAY_SIZE(llcp_magic);
+
+ if (gb_len > NFC_MAX_GT_LEN) {
+ kfree(version_tlv);
+ return -EINVAL;
+ }
+
+ gb_cur = local->gb;
+
+ memcpy(gb_cur, llcp_magic, ARRAY_SIZE(llcp_magic));
+ gb_cur += ARRAY_SIZE(llcp_magic);
+
+ memcpy(gb_cur, version_tlv, version_length);
+ gb_cur += version_length;
+
+ memcpy(gb_cur, lto_tlv, lto_length);
+ gb_cur += lto_length;
+
+ memcpy(gb_cur, wks_tlv, wks_length);
+ gb_cur += wks_length;
+
+ kfree(version_tlv);
+ kfree(lto_tlv);
+
+ local->gb_len = gb_len;
+
+ return 0;
+}
+
+int nfc_llcp_set_remote_gb(struct nfc_dev *dev, u8 *gb, u8 gb_len)
+{
+ struct nfc_llcp_local *local = nfc_llcp_find_local(dev);
+
+ if (local == NULL) {
+ pr_err("No LLCP device\n");
+ return -ENODEV;
+ }
+
+ memset(local->remote_gb, 0, NFC_MAX_GT_LEN);
+ memcpy(local->remote_gb, gb, gb_len);
+ local->remote_gb_len = gb_len;
+
+ if (local->remote_gb == NULL ||
+ local->remote_gb_len == 0)
+ return -ENODEV;
+
+ if (memcmp(local->remote_gb, llcp_magic, 3)) {
+ pr_err("MAC does not support LLCP\n");
+ return -EINVAL;
+ }
+
+ return nfc_llcp_parse_tlv(local,
+ &local->remote_gb[3], local->remote_gb_len - 3);
+}
+
+static void nfc_llcp_tx_work(struct work_struct *work)
+{
+ struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+ tx_work);
+ struct sk_buff *skb;
+
+ skb = skb_dequeue(&local->tx_queue);
+ if (skb != NULL) {
+ pr_debug("Sending pending skb\n");
+ nfc_data_exchange(local->dev, local->target_idx,
+ skb, nfc_llcp_recv, local);
+ } else {
+ nfc_llcp_send_symm(local->dev);
+ }
+
+ mod_timer(&local->link_timer,
+ jiffies + msecs_to_jiffies(local->remote_lto));
+}
+
+static u8 nfc_llcp_dsap(struct sk_buff *pdu)
+{
+ return (pdu->data[0] & 0xfc) >> 2;
+}
+
+static u8 nfc_llcp_ptype(struct sk_buff *pdu)
+{
+ return ((pdu->data[0] & 0x03) << 2) | ((pdu->data[1] & 0xc0) >> 6);
+}
+
+static u8 nfc_llcp_ssap(struct sk_buff *pdu)
+{
+ return pdu->data[1] & 0x3f;
+}
+
+static u8 nfc_llcp_ns(struct sk_buff *pdu)
+{
+ return pdu->data[2] >> 4;
+}
+
+static u8 nfc_llcp_nr(struct sk_buff *pdu)
+{
+ return pdu->data[2] & 0xf;
+}
+
+static void nfc_llcp_set_nrns(struct nfc_llcp_sock *sock, struct sk_buff *pdu)
+{
+ pdu->data[2] = (sock->send_n << 4) | ((sock->recv_n - 1) % 16);
+ sock->send_n = (sock->send_n + 1) % 16;
+ sock->recv_ack_n = (sock->recv_n - 1) % 16;
+}
+
+static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local,
+ u8 ssap, u8 dsap)
+{
+ struct nfc_llcp_sock *sock, *llcp_sock, *n;
+
+ if (ssap == 0 && dsap == 0)
+ return NULL;
+
+ mutex_lock(&local->socket_lock);
+ sock = local->sockets[ssap];
+ if (sock == NULL) {
+ mutex_unlock(&local->socket_lock);
+ return NULL;
+ }
+
+ pr_debug("root dsap %d (%d)\n", sock->dsap, dsap);
+
+ if (sock->dsap == dsap) {
+ sock_hold(&sock->sk);
+ mutex_unlock(&local->socket_lock);
+ return sock;
+ }
+
+ list_for_each_entry_safe(llcp_sock, n, &sock->list, list) {
+ pr_debug("llcp_sock %p sk %p dsap %d\n", llcp_sock,
+ &llcp_sock->sk, llcp_sock->dsap);
+ if (llcp_sock->dsap == dsap) {
+ sock_hold(&llcp_sock->sk);
+ mutex_unlock(&local->socket_lock);
+ return llcp_sock;
+ }
+ }
+
+ pr_err("Could not find socket for %d %d\n", ssap, dsap);
+
+ mutex_unlock(&local->socket_lock);
+
+ return NULL;
+}
+
+static void nfc_llcp_sock_put(struct nfc_llcp_sock *sock)
+{
+ sock_put(&sock->sk);
+}
+
+static u8 *nfc_llcp_connect_sn(struct sk_buff *skb, size_t *sn_len)
+{
+ u8 *tlv = &skb->data[2], type, length;
+ size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0;
+
+ while (offset < tlv_array_len) {
+ type = tlv[0];
+ length = tlv[1];
+
+ pr_debug("type 0x%x length %d\n", type, length);
+
+ if (type == LLCP_TLV_SN) {
+ *sn_len = length;
+ return &tlv[2];
+ }
+
+ offset += length + 2;
+ tlv += length + 2;
+ }
+
+ return NULL;
+}
+
+static void nfc_llcp_recv_connect(struct nfc_llcp_local *local,
+ struct sk_buff *skb)
+{
+ struct sock *new_sk, *parent;
+ struct nfc_llcp_sock *sock, *new_sock;
+ u8 dsap, ssap, bound_sap, reason;
+
+ dsap = nfc_llcp_dsap(skb);
+ ssap = nfc_llcp_ssap(skb);
+
+ pr_debug("%d %d\n", dsap, ssap);
+
+ nfc_llcp_parse_tlv(local, &skb->data[LLCP_HEADER_SIZE],
+ skb->len - LLCP_HEADER_SIZE);
+
+ if (dsap != LLCP_SAP_SDP) {
+ bound_sap = dsap;
+
+ mutex_lock(&local->socket_lock);
+ sock = local->sockets[dsap];
+ if (sock == NULL) {
+ mutex_unlock(&local->socket_lock);
+ reason = LLCP_DM_NOBOUND;
+ goto fail;
+ }
+
+ sock_hold(&sock->sk);
+ mutex_unlock(&local->socket_lock);
+
+ lock_sock(&sock->sk);
+
+ if (sock->dsap == LLCP_SAP_SDP &&
+ sock->sk.sk_state == LLCP_LISTEN)
+ goto enqueue;
+ } else {
+ u8 *sn;
+ size_t sn_len;
+
+ sn = nfc_llcp_connect_sn(skb, &sn_len);
+ if (sn == NULL) {
+ reason = LLCP_DM_NOBOUND;
+ goto fail;
+ }
+
+ pr_debug("Service name length %zu\n", sn_len);
+
+ mutex_lock(&local->socket_lock);
+ for (bound_sap = 0; bound_sap < LLCP_LOCAL_SAP_OFFSET;
+ bound_sap++) {
+ sock = local->sockets[bound_sap];
+ if (sock == NULL)
+ continue;
+
+ if (sock->service_name == NULL ||
+ sock->service_name_len == 0)
+ continue;
+
+ if (sock->service_name_len != sn_len)
+ continue;
+
+ if (sock->dsap == LLCP_SAP_SDP &&
+ sock->sk.sk_state == LLCP_LISTEN &&
+ !memcmp(sn, sock->service_name, sn_len)) {
+ pr_debug("Found service name at SAP %d\n",
+ bound_sap);
+ sock_hold(&sock->sk);
+ mutex_unlock(&local->socket_lock);
+
+ lock_sock(&sock->sk);
+
+ goto enqueue;
+ }
+ }
+
+ }
+
+ mutex_unlock(&local->socket_lock);
+
+ reason = LLCP_DM_NOBOUND;
+ goto fail;
+
+enqueue:
+ parent = &sock->sk;
+
+ if (sk_acceptq_is_full(parent)) {
+ reason = LLCP_DM_REJ;
+ release_sock(&sock->sk);
+ sock_put(&sock->sk);
+ goto fail;
+ }
+
+ new_sk = nfc_llcp_sock_alloc(NULL, parent->sk_type,
+ GFP_ATOMIC);
+ if (new_sk == NULL) {
+ reason = LLCP_DM_REJ;
+ release_sock(&sock->sk);
+ sock_put(&sock->sk);
+ goto fail;
+ }
+
+ new_sock = nfc_llcp_sock(new_sk);
+ new_sock->dev = local->dev;
+ new_sock->local = local;
+ new_sock->nfc_protocol = sock->nfc_protocol;
+ new_sock->ssap = bound_sap;
+ new_sock->dsap = ssap;
+ new_sock->parent = parent;
+
+ pr_debug("new sock %p sk %p\n", new_sock, &new_sock->sk);
+
+ list_add_tail(&new_sock->list, &sock->list);
+
+ nfc_llcp_accept_enqueue(&sock->sk, new_sk);
+
+ nfc_get_device(local->dev->idx);
+
+ new_sk->sk_state = LLCP_CONNECTED;
+
+ /* Wake the listening processes */
+ parent->sk_data_ready(parent, 0);
+
+ /* Send CC */
+ nfc_llcp_send_cc(new_sock);
+
+ release_sock(&sock->sk);
+ sock_put(&sock->sk);
+
+ return;
+
+fail:
+ /* Send DM */
+ nfc_llcp_send_dm(local, dsap, ssap, reason);
+
+ return;
+
+}
+
+static void nfc_llcp_recv_hdlc(struct nfc_llcp_local *local,
+ struct sk_buff *skb)
+{
+ struct nfc_llcp_sock *llcp_sock;
+ struct sock *sk;
+ u8 dsap, ssap, ptype, ns, nr;
+
+ ptype = nfc_llcp_ptype(skb);
+ dsap = nfc_llcp_dsap(skb);
+ ssap = nfc_llcp_ssap(skb);
+ ns = nfc_llcp_ns(skb);
+ nr = nfc_llcp_nr(skb);
+
+ pr_debug("%d %d R %d S %d\n", dsap, ssap, nr, ns);
+
+ llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+ if (llcp_sock == NULL) {
+ nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+ return;
+ }
+
+ sk = &llcp_sock->sk;
+ lock_sock(sk);
+ if (sk->sk_state == LLCP_CLOSED) {
+ release_sock(sk);
+ nfc_llcp_sock_put(llcp_sock);
+ }
+
+ if (ns == llcp_sock->recv_n)
+ llcp_sock->recv_n = (llcp_sock->recv_n + 1) % 16;
+ else
+ pr_err("Received out of sequence I PDU\n");
+
+ /* Pass the payload upstream */
+ if (ptype == LLCP_PDU_I) {
+ pr_debug("I frame, queueing on %p\n", &llcp_sock->sk);
+
+ skb_pull(skb, LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE);
+ if (sock_queue_rcv_skb(&llcp_sock->sk, skb)) {
+ pr_err("receive queue is full\n");
+ skb_queue_head(&llcp_sock->tx_backlog_queue, skb);
+ }
+ }
+
+ /* Remove skbs from the pending queue */
+ if (llcp_sock->send_ack_n != nr) {
+ struct sk_buff *s, *tmp;
+
+ llcp_sock->send_ack_n = nr;
+
+ skb_queue_walk_safe(&llcp_sock->tx_pending_queue, s, tmp)
+ if (nfc_llcp_ns(s) <= nr) {
+ skb_unlink(s, &llcp_sock->tx_pending_queue);
+ kfree_skb(s);
+ }
+ }
+
+ /* Queue some I frames for transmission */
+ while (llcp_sock->remote_ready &&
+ skb_queue_len(&llcp_sock->tx_pending_queue) <= local->remote_rw) {
+ struct sk_buff *pdu, *pending_pdu;
+
+ pdu = skb_dequeue(&llcp_sock->tx_queue);
+ if (pdu == NULL)
+ break;
+
+ /* Update N(S)/N(R) */
+ nfc_llcp_set_nrns(llcp_sock, pdu);
+
+ pending_pdu = skb_clone(pdu, GFP_KERNEL);
+
+ skb_queue_tail(&local->tx_queue, pdu);
+ skb_queue_tail(&llcp_sock->tx_pending_queue, pending_pdu);
+ }
+
+ release_sock(sk);
+ nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_recv_disc(struct nfc_llcp_local *local,
+ struct sk_buff *skb)
+{
+ struct nfc_llcp_sock *llcp_sock;
+ struct sock *sk;
+ u8 dsap, ssap;
+
+ dsap = nfc_llcp_dsap(skb);
+ ssap = nfc_llcp_ssap(skb);
+
+ llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+ if (llcp_sock == NULL) {
+ nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+ return;
+ }
+
+ sk = &llcp_sock->sk;
+ lock_sock(sk);
+ if (sk->sk_state == LLCP_CLOSED) {
+ release_sock(sk);
+ nfc_llcp_sock_put(llcp_sock);
+ }
+
+
+ if (sk->sk_state == LLCP_CONNECTED) {
+ nfc_put_device(local->dev);
+ sk->sk_state = LLCP_CLOSED;
+ sk->sk_state_change(sk);
+ }
+
+ nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_DISC);
+
+ release_sock(sk);
+ nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_recv_cc(struct nfc_llcp_local *local,
+ struct sk_buff *skb)
+{
+ struct nfc_llcp_sock *llcp_sock;
+ u8 dsap, ssap;
+
+
+ dsap = nfc_llcp_dsap(skb);
+ ssap = nfc_llcp_ssap(skb);
+
+ llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+
+ if (llcp_sock == NULL)
+ llcp_sock = nfc_llcp_sock_get(local, dsap, LLCP_SAP_SDP);
+
+ if (llcp_sock == NULL) {
+ pr_err("Invalid CC\n");
+ nfc_llcp_send_dm(local, dsap, ssap, LLCP_DM_NOCONN);
+
+ return;
+ }
+
+ llcp_sock->dsap = ssap;
+
+ nfc_llcp_parse_tlv(local, &skb->data[LLCP_HEADER_SIZE],
+ skb->len - LLCP_HEADER_SIZE);
+
+ nfc_llcp_sock_put(llcp_sock);
+}
+
+static void nfc_llcp_rx_work(struct work_struct *work)
+{
+ struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
+ rx_work);
+ u8 dsap, ssap, ptype;
+ struct sk_buff *skb;
+
+ skb = local->rx_pending;
+ if (skb == NULL) {
+ pr_debug("No pending SKB\n");
+ return;
+ }
+
+ ptype = nfc_llcp_ptype(skb);
+ dsap = nfc_llcp_dsap(skb);
+ ssap = nfc_llcp_ssap(skb);
+
+ pr_debug("ptype 0x%x dsap 0x%x ssap 0x%x\n", ptype, dsap, ssap);
+
+ switch (ptype) {
+ case LLCP_PDU_SYMM:
+ pr_debug("SYMM\n");
+ break;
+
+ case LLCP_PDU_CONNECT:
+ pr_debug("CONNECT\n");
+ nfc_llcp_recv_connect(local, skb);
+ break;
+
+ case LLCP_PDU_DISC:
+ pr_debug("DISC\n");
+ nfc_llcp_recv_disc(local, skb);
+ break;
+
+ case LLCP_PDU_CC:
+ pr_debug("CC\n");
+ nfc_llcp_recv_cc(local, skb);
+ break;
+
+ case LLCP_PDU_I:
+ case LLCP_PDU_RR:
+ pr_debug("I frame\n");
+ nfc_llcp_recv_hdlc(local, skb);
+ break;
+
+ }
+
+ queue_work(local->tx_wq, &local->tx_work);
+ kfree_skb(local->rx_pending);
+ local->rx_pending = NULL;
+
+ return;
+}
+
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err)
+{
+ struct nfc_llcp_local *local = (struct nfc_llcp_local *) data;
+
+ pr_debug("Received an LLCP PDU\n");
+ if (err < 0) {
+ pr_err("err %d", err);
+ return;
+ }
+
+ local->rx_pending = skb_get(skb);
+ del_timer(&local->link_timer);
+ queue_work(local->rx_wq, &local->rx_work);
+
+ return;
+}
+
+void nfc_llcp_mac_is_down(struct nfc_dev *dev)
+{
+ struct nfc_llcp_local *local;
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL)
+ return;
+
+ /* Close and purge all existing sockets */
+ nfc_llcp_socket_release(local);
+}
+
+void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode)
+{
+ struct nfc_llcp_local *local;
+
+ pr_debug("rf mode %d\n", rf_mode);
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL)
+ return;
+
+ local->target_idx = target_idx;
+ local->comm_mode = comm_mode;
+ local->rf_mode = rf_mode;
+
+ if (rf_mode == NFC_RF_INITIATOR) {
+ pr_debug("Queueing Tx work\n");
+
+ queue_work(local->tx_wq, &local->tx_work);
+ } else {
+ mod_timer(&local->link_timer,
+ jiffies + msecs_to_jiffies(local->remote_lto));
+ }
+}
+
+int nfc_llcp_register_device(struct nfc_dev *ndev)
+{
+ struct device *dev = &ndev->dev;
+ struct nfc_llcp_local *local;
+ char name[32];
+ int err;
+
+ local = kzalloc(sizeof(struct nfc_llcp_local), GFP_KERNEL);
+ if (local == NULL)
+ return -ENOMEM;
+
+ local->dev = ndev;
+ INIT_LIST_HEAD(&local->list);
+ mutex_init(&local->sdp_lock);
+ mutex_init(&local->socket_lock);
+ init_timer(&local->link_timer);
+ local->link_timer.data = (unsigned long) local;
+ local->link_timer.function = nfc_llcp_symm_timer;
+
+ skb_queue_head_init(&local->tx_queue);
+ INIT_WORK(&local->tx_work, nfc_llcp_tx_work);
+ snprintf(name, sizeof(name), "%s_llcp_tx_wq", dev_name(dev));
+ local->tx_wq = alloc_workqueue(name,
+ WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (local->tx_wq == NULL) {
+ err = -ENOMEM;
+ goto err_local;
+ }
+
+ local->rx_pending = NULL;
+ INIT_WORK(&local->rx_work, nfc_llcp_rx_work);
+ snprintf(name, sizeof(name), "%s_llcp_rx_wq", dev_name(dev));
+ local->rx_wq = alloc_workqueue(name,
+ WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (local->rx_wq == NULL) {
+ err = -ENOMEM;
+ goto err_tx_wq;
+ }
+
+ INIT_WORK(&local->timeout_work, nfc_llcp_timeout_work);
+ snprintf(name, sizeof(name), "%s_llcp_timeout_wq", dev_name(dev));
+ local->timeout_wq = alloc_workqueue(name,
+ WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (local->timeout_wq == NULL) {
+ err = -ENOMEM;
+ goto err_rx_wq;
+ }
+
+ nfc_llcp_build_gb(local);
+
+ local->remote_miu = LLCP_DEFAULT_MIU;
+ local->remote_lto = LLCP_DEFAULT_LTO;
+ local->remote_rw = LLCP_DEFAULT_RW;
+
+ list_add(&llcp_devices, &local->list);
+
+ return 0;
+
+err_rx_wq:
+ destroy_workqueue(local->rx_wq);
+
+err_tx_wq:
+ destroy_workqueue(local->tx_wq);
+
+err_local:
+ kfree(local);
+
+ return 0;
+}
+
+void nfc_llcp_unregister_device(struct nfc_dev *dev)
+{
+ struct nfc_llcp_local *local = nfc_llcp_find_local(dev);
+
+ if (local == NULL) {
+ pr_debug("No such device\n");
+ return;
+ }
+
+ list_del(&local->list);
+ nfc_llcp_socket_release(local);
+ del_timer_sync(&local->link_timer);
+ skb_queue_purge(&local->tx_queue);
+ destroy_workqueue(local->tx_wq);
+ destroy_workqueue(local->rx_wq);
+ kfree(local->rx_pending);
+ kfree(local);
+}
+
+int __init nfc_llcp_init(void)
+{
+ INIT_LIST_HEAD(&llcp_devices);
+
+ return nfc_llcp_sock_init();
+}
+
+void nfc_llcp_exit(void)
+{
+ nfc_llcp_sock_exit();
+}
diff --git a/net/nfc/llcp/llcp.h b/net/nfc/llcp/llcp.h
new file mode 100644
index 000000000000..0ad2e3361584
--- /dev/null
+++ b/net/nfc/llcp/llcp.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+enum llcp_state {
+ LLCP_CONNECTED = 1, /* wait_for_packet() wants that */
+ LLCP_CLOSED,
+ LLCP_BOUND,
+ LLCP_LISTEN,
+};
+
+#define LLCP_DEFAULT_LTO 100
+#define LLCP_DEFAULT_RW 1
+#define LLCP_DEFAULT_MIU 128
+
+#define LLCP_WKS_NUM_SAP 16
+#define LLCP_SDP_NUM_SAP 16
+#define LLCP_LOCAL_NUM_SAP 32
+#define LLCP_LOCAL_SAP_OFFSET (LLCP_WKS_NUM_SAP + LLCP_SDP_NUM_SAP)
+#define LLCP_MAX_SAP (LLCP_WKS_NUM_SAP + LLCP_SDP_NUM_SAP + LLCP_LOCAL_NUM_SAP)
+
+struct nfc_llcp_sock;
+
+struct nfc_llcp_local {
+ struct list_head list;
+ struct nfc_dev *dev;
+
+ struct mutex sdp_lock;
+ struct mutex socket_lock;
+
+ struct timer_list link_timer;
+ struct sk_buff_head tx_queue;
+ struct workqueue_struct *tx_wq;
+ struct work_struct tx_work;
+ struct workqueue_struct *rx_wq;
+ struct work_struct rx_work;
+ struct sk_buff *rx_pending;
+ struct workqueue_struct *timeout_wq;
+ struct work_struct timeout_work;
+
+ u32 target_idx;
+ u8 rf_mode;
+ u8 comm_mode;
+ unsigned long local_wks; /* Well known services */
+ unsigned long local_sdp; /* Local services */
+ unsigned long local_sap; /* Local SAPs, not available for discovery */
+
+ /* local */
+ u8 gb[NFC_MAX_GT_LEN];
+ u8 gb_len;
+
+ /* remote */
+ u8 remote_gb[NFC_MAX_GT_LEN];
+ u8 remote_gb_len;
+
+ u8 remote_version;
+ u16 remote_miu;
+ u16 remote_lto;
+ u8 remote_opt;
+ u16 remote_wks;
+ u8 remote_rw;
+
+ /* sockets array */
+ struct nfc_llcp_sock *sockets[LLCP_MAX_SAP];
+};
+
+struct nfc_llcp_sock {
+ struct sock sk;
+ struct list_head list;
+ struct nfc_dev *dev;
+ struct nfc_llcp_local *local;
+ u32 target_idx;
+ u32 nfc_protocol;
+
+ u8 ssap;
+ u8 dsap;
+ char *service_name;
+ size_t service_name_len;
+
+ /* Link variables */
+ u8 send_n;
+ u8 send_ack_n;
+ u8 recv_n;
+ u8 recv_ack_n;
+
+ /* Is the remote peer ready to receive */
+ u8 remote_ready;
+
+ struct sk_buff_head tx_queue;
+ struct sk_buff_head tx_pending_queue;
+ struct sk_buff_head tx_backlog_queue;
+
+ struct list_head accept_queue;
+ struct sock *parent;
+};
+
+#define nfc_llcp_sock(sk) ((struct nfc_llcp_sock *) (sk))
+#define nfc_llcp_dev(sk) (nfc_llcp_sock((sk))->dev)
+
+#define LLCP_HEADER_SIZE 2
+#define LLCP_SEQUENCE_SIZE 1
+
+/* LLCP versions: 1.1 is 1.0 plus SDP */
+#define LLCP_VERSION_10 0x10
+#define LLCP_VERSION_11 0x11
+
+/* LLCP PDU types */
+#define LLCP_PDU_SYMM 0x0
+#define LLCP_PDU_PAX 0x1
+#define LLCP_PDU_AGF 0x2
+#define LLCP_PDU_UI 0x3
+#define LLCP_PDU_CONNECT 0x4
+#define LLCP_PDU_DISC 0x5
+#define LLCP_PDU_CC 0x6
+#define LLCP_PDU_DM 0x7
+#define LLCP_PDU_FRMR 0x8
+#define LLCP_PDU_SNL 0x9
+#define LLCP_PDU_I 0xc
+#define LLCP_PDU_RR 0xd
+#define LLCP_PDU_RNR 0xe
+
+/* Parameters TLV types */
+#define LLCP_TLV_VERSION 0x1
+#define LLCP_TLV_MIUX 0x2
+#define LLCP_TLV_WKS 0x3
+#define LLCP_TLV_LTO 0x4
+#define LLCP_TLV_RW 0x5
+#define LLCP_TLV_SN 0x6
+#define LLCP_TLV_OPT 0x7
+#define LLCP_TLV_SDREQ 0x8
+#define LLCP_TLV_SDRES 0x9
+#define LLCP_TLV_MAX 0xa
+
+/* Well known LLCP SAP */
+#define LLCP_SAP_SDP 0x1
+#define LLCP_SAP_IP 0x2
+#define LLCP_SAP_OBEX 0x3
+#define LLCP_SAP_SNEP 0x4
+#define LLCP_SAP_MAX 0xff
+
+/* Disconnection reason code */
+#define LLCP_DM_DISC 0x00
+#define LLCP_DM_NOCONN 0x01
+#define LLCP_DM_NOBOUND 0x02
+#define LLCP_DM_REJ 0x03
+
+
+struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev);
+u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
+ struct nfc_llcp_sock *sock);
+u8 nfc_llcp_get_local_ssap(struct nfc_llcp_local *local);
+void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap);
+
+/* Sock API */
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp);
+void nfc_llcp_sock_free(struct nfc_llcp_sock *sock);
+void nfc_llcp_accept_unlink(struct sock *sk);
+void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk);
+struct sock *nfc_llcp_accept_dequeue(struct sock *sk, struct socket *newsock);
+
+/* TLV API */
+int nfc_llcp_parse_tlv(struct nfc_llcp_local *local,
+ u8 *tlv_array, u16 tlv_array_len);
+
+/* Commands API */
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err);
+u8 *nfc_llcp_build_tlv(u8 type, u8 *value, u8 value_length, u8 *tlv_length);
+void nfc_llcp_recv(void *data, struct sk_buff *skb, int err);
+int nfc_llcp_disconnect(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_symm(struct nfc_dev *dev);
+int nfc_llcp_send_connect(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_cc(struct nfc_llcp_sock *sock);
+int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason);
+int nfc_llcp_send_disconnect(struct nfc_llcp_sock *sock);
+
+/* Socket API */
+int __init nfc_llcp_sock_init(void);
+void nfc_llcp_sock_exit(void);
diff --git a/net/nfc/llcp/sock.c b/net/nfc/llcp/sock.c
new file mode 100644
index 000000000000..f738ccd535f1
--- /dev/null
+++ b/net/nfc/llcp/sock.c
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) "llcp: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+
+#include "../nfc.h"
+#include "llcp.h"
+
+static struct proto llcp_sock_proto = {
+ .name = "NFC_LLCP",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct nfc_llcp_sock),
+};
+
+static int llcp_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
+{
+ struct sock *sk = sock->sk;
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+ struct nfc_llcp_local *local;
+ struct nfc_dev *dev;
+ struct sockaddr_nfc_llcp llcp_addr;
+ int len, ret = 0;
+
+ pr_debug("sk %p addr %p family %d\n", sk, addr, addr->sa_family);
+
+ if (!addr || addr->sa_family != AF_NFC)
+ return -EINVAL;
+
+ memset(&llcp_addr, 0, sizeof(llcp_addr));
+ len = min_t(unsigned int, sizeof(llcp_addr), alen);
+ memcpy(&llcp_addr, addr, len);
+
+ /* This is going to be a listening socket, dsap must be 0 */
+ if (llcp_addr.dsap != 0)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->sk_state != LLCP_CLOSED) {
+ ret = -EBADFD;
+ goto error;
+ }
+
+ dev = nfc_get_device(llcp_addr.dev_idx);
+ if (dev == NULL) {
+ ret = -ENODEV;
+ goto error;
+ }
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL) {
+ ret = -ENODEV;
+ goto put_dev;
+ }
+
+ llcp_sock->dev = dev;
+ llcp_sock->local = local;
+ llcp_sock->nfc_protocol = llcp_addr.nfc_protocol;
+ llcp_sock->service_name_len = min_t(unsigned int,
+ llcp_addr.service_name_len, NFC_LLCP_MAX_SERVICE_NAME);
+ llcp_sock->service_name = kmemdup(llcp_addr.service_name,
+ llcp_sock->service_name_len, GFP_KERNEL);
+
+ llcp_sock->ssap = nfc_llcp_get_sdp_ssap(local, llcp_sock);
+ if (llcp_sock->ssap == LLCP_MAX_SAP)
+ goto put_dev;
+
+ local->sockets[llcp_sock->ssap] = llcp_sock;
+
+ pr_debug("Socket bound to SAP %d\n", llcp_sock->ssap);
+
+ sk->sk_state = LLCP_BOUND;
+
+put_dev:
+ nfc_put_device(dev);
+
+error:
+ release_sock(sk);
+ return ret;
+}
+
+static int llcp_sock_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ int ret = 0;
+
+ pr_debug("sk %p backlog %d\n", sk, backlog);
+
+ lock_sock(sk);
+
+ if ((sock->type != SOCK_SEQPACKET && sock->type != SOCK_STREAM)
+ || sk->sk_state != LLCP_BOUND) {
+ ret = -EBADFD;
+ goto error;
+ }
+
+ sk->sk_max_ack_backlog = backlog;
+ sk->sk_ack_backlog = 0;
+
+ pr_debug("Socket listening\n");
+ sk->sk_state = LLCP_LISTEN;
+
+error:
+ release_sock(sk);
+
+ return ret;
+}
+
+void nfc_llcp_accept_unlink(struct sock *sk)
+{
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+ pr_debug("state %d\n", sk->sk_state);
+
+ list_del_init(&llcp_sock->accept_queue);
+ sk_acceptq_removed(llcp_sock->parent);
+ llcp_sock->parent = NULL;
+
+ sock_put(sk);
+}
+
+void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk)
+{
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+ struct nfc_llcp_sock *llcp_sock_parent = nfc_llcp_sock(parent);
+
+ /* Lock will be free from unlink */
+ sock_hold(sk);
+
+ list_add_tail(&llcp_sock->accept_queue,
+ &llcp_sock_parent->accept_queue);
+ llcp_sock->parent = parent;
+ sk_acceptq_added(parent);
+}
+
+struct sock *nfc_llcp_accept_dequeue(struct sock *parent,
+ struct socket *newsock)
+{
+ struct nfc_llcp_sock *lsk, *n, *llcp_parent;
+ struct sock *sk;
+
+ llcp_parent = nfc_llcp_sock(parent);
+
+ list_for_each_entry_safe(lsk, n, &llcp_parent->accept_queue,
+ accept_queue) {
+ sk = &lsk->sk;
+ lock_sock(sk);
+
+ if (sk->sk_state == LLCP_CLOSED) {
+ release_sock(sk);
+ nfc_llcp_accept_unlink(sk);
+ continue;
+ }
+
+ if (sk->sk_state == LLCP_CONNECTED || !newsock) {
+ nfc_llcp_accept_unlink(sk);
+ if (newsock)
+ sock_graft(sk, newsock);
+
+ release_sock(sk);
+
+ pr_debug("Returning sk state %d\n", sk->sk_state);
+
+ return sk;
+ }
+
+ release_sock(sk);
+ }
+
+ return NULL;
+}
+
+static int llcp_sock_accept(struct socket *sock, struct socket *newsock,
+ int flags)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct sock *sk = sock->sk, *new_sk;
+ long timeo;
+ int ret = 0;
+
+ pr_debug("parent %p\n", sk);
+
+ lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+
+ if (sk->sk_state != LLCP_LISTEN) {
+ ret = -EBADFD;
+ goto error;
+ }
+
+ timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+ /* Wait for an incoming connection. */
+ add_wait_queue_exclusive(sk_sleep(sk), &wait);
+ while (!(new_sk = nfc_llcp_accept_dequeue(sk, newsock))) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (!timeo) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ ret = sock_intr_errno(timeo);
+ break;
+ }
+
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+ }
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk_sleep(sk), &wait);
+
+ if (ret)
+ goto error;
+
+ newsock->state = SS_CONNECTED;
+
+ pr_debug("new socket %p\n", new_sk);
+
+error:
+ release_sock(sk);
+
+ return ret;
+}
+
+static int llcp_sock_getname(struct socket *sock, struct sockaddr *addr,
+ int *len, int peer)
+{
+ struct sockaddr_nfc_llcp *llcp_addr = (struct sockaddr_nfc_llcp *) addr;
+ struct sock *sk = sock->sk;
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+ pr_debug("%p\n", sk);
+
+ addr->sa_family = AF_NFC;
+ *len = sizeof(struct sockaddr_nfc_llcp);
+
+ llcp_addr->dev_idx = llcp_sock->dev->idx;
+ llcp_addr->dsap = llcp_sock->dsap;
+ llcp_addr->ssap = llcp_sock->ssap;
+ llcp_addr->service_name_len = llcp_sock->service_name_len;
+ memcpy(llcp_addr->service_name, llcp_sock->service_name,
+ llcp_addr->service_name_len);
+
+ return 0;
+}
+
+static inline unsigned int llcp_accept_poll(struct sock *parent)
+{
+ struct nfc_llcp_sock *llcp_sock, *n, *parent_sock;
+ struct sock *sk;
+
+ parent_sock = nfc_llcp_sock(parent);
+
+ list_for_each_entry_safe(llcp_sock, n, &parent_sock->accept_queue,
+ accept_queue) {
+ sk = &llcp_sock->sk;
+
+ if (sk->sk_state == LLCP_CONNECTED)
+ return POLLIN | POLLRDNORM;
+ }
+
+ return 0;
+}
+
+static unsigned int llcp_sock_poll(struct file *file, struct socket *sock,
+ poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask = 0;
+
+ pr_debug("%p\n", sk);
+
+ sock_poll_wait(file, sk_sleep(sk), wait);
+
+ if (sk->sk_state == LLCP_LISTEN)
+ return llcp_accept_poll(sk);
+
+ if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
+ mask |= POLLERR;
+
+ if (!skb_queue_empty(&sk->sk_receive_queue))
+ mask |= POLLIN;
+
+ if (sk->sk_state == LLCP_CLOSED)
+ mask |= POLLHUP;
+
+ return mask;
+}
+
+static int llcp_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct nfc_llcp_local *local;
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+ if (!sk)
+ return 0;
+
+ pr_debug("%p\n", sk);
+
+ local = llcp_sock->local;
+ if (local == NULL)
+ return -ENODEV;
+
+ mutex_lock(&local->socket_lock);
+
+ if (llcp_sock == local->sockets[llcp_sock->ssap]) {
+ local->sockets[llcp_sock->ssap] = NULL;
+ } else {
+ struct nfc_llcp_sock *parent, *s, *n;
+
+ parent = local->sockets[llcp_sock->ssap];
+
+ list_for_each_entry_safe(s, n, &parent->list, list)
+ if (llcp_sock == s) {
+ list_del(&s->list);
+ break;
+ }
+
+ }
+
+ mutex_unlock(&local->socket_lock);
+
+ lock_sock(sk);
+
+ /* Send a DISC */
+ if (sk->sk_state == LLCP_CONNECTED)
+ nfc_llcp_disconnect(llcp_sock);
+
+ if (sk->sk_state == LLCP_LISTEN) {
+ struct nfc_llcp_sock *lsk, *n;
+ struct sock *accept_sk;
+
+ list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue,
+ accept_queue) {
+ accept_sk = &lsk->sk;
+ lock_sock(accept_sk);
+
+ nfc_llcp_disconnect(lsk);
+ nfc_llcp_accept_unlink(accept_sk);
+
+ release_sock(accept_sk);
+
+ sock_set_flag(sk, SOCK_DEAD);
+ sock_orphan(accept_sk);
+ sock_put(accept_sk);
+ }
+ }
+
+ /* Freeing the SAP */
+ if ((sk->sk_state == LLCP_CONNECTED
+ && llcp_sock->ssap > LLCP_LOCAL_SAP_OFFSET) ||
+ sk->sk_state == LLCP_BOUND ||
+ sk->sk_state == LLCP_LISTEN)
+ nfc_llcp_put_ssap(llcp_sock->local, llcp_sock->ssap);
+
+ sock_set_flag(sk, SOCK_DEAD);
+
+ release_sock(sk);
+
+ sock_orphan(sk);
+ sock_put(sk);
+
+ return 0;
+}
+
+static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr,
+ int len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+ struct sockaddr_nfc_llcp *addr = (struct sockaddr_nfc_llcp *)_addr;
+ struct nfc_dev *dev;
+ struct nfc_llcp_local *local;
+ int ret = 0;
+
+ pr_debug("sock %p sk %p flags 0x%x\n", sock, sk, flags);
+
+ if (!addr || len < sizeof(struct sockaddr_nfc) ||
+ addr->sa_family != AF_NFC) {
+ pr_err("Invalid socket\n");
+ return -EINVAL;
+ }
+
+ if (addr->service_name_len == 0 && addr->dsap == 0) {
+ pr_err("Missing service name or dsap\n");
+ return -EINVAL;
+ }
+
+ pr_debug("addr dev_idx=%u target_idx=%u protocol=%u\n", addr->dev_idx,
+ addr->target_idx, addr->nfc_protocol);
+
+ lock_sock(sk);
+
+ if (sk->sk_state == LLCP_CONNECTED) {
+ ret = -EISCONN;
+ goto error;
+ }
+
+ dev = nfc_get_device(addr->dev_idx);
+ if (dev == NULL) {
+ ret = -ENODEV;
+ goto error;
+ }
+
+ local = nfc_llcp_find_local(dev);
+ if (local == NULL) {
+ ret = -ENODEV;
+ goto put_dev;
+ }
+
+ device_lock(&dev->dev);
+ if (dev->dep_link_up == false) {
+ ret = -ENOLINK;
+ device_unlock(&dev->dev);
+ goto put_dev;
+ }
+ device_unlock(&dev->dev);
+
+ if (local->rf_mode == NFC_RF_INITIATOR &&
+ addr->target_idx != local->target_idx) {
+ ret = -ENOLINK;
+ goto put_dev;
+ }
+
+ llcp_sock->dev = dev;
+ llcp_sock->local = local;
+ llcp_sock->ssap = nfc_llcp_get_local_ssap(local);
+ if (llcp_sock->ssap == LLCP_SAP_MAX) {
+ ret = -ENOMEM;
+ goto put_dev;
+ }
+ if (addr->service_name_len == 0)
+ llcp_sock->dsap = addr->dsap;
+ else
+ llcp_sock->dsap = LLCP_SAP_SDP;
+ llcp_sock->nfc_protocol = addr->nfc_protocol;
+ llcp_sock->service_name_len = min_t(unsigned int,
+ addr->service_name_len, NFC_LLCP_MAX_SERVICE_NAME);
+ llcp_sock->service_name = kmemdup(addr->service_name,
+ llcp_sock->service_name_len, GFP_KERNEL);
+
+ local->sockets[llcp_sock->ssap] = llcp_sock;
+
+ ret = nfc_llcp_send_connect(llcp_sock);
+ if (ret)
+ goto put_dev;
+
+ sk->sk_state = LLCP_CONNECTED;
+
+ release_sock(sk);
+ return 0;
+
+put_dev:
+ nfc_put_device(dev);
+
+error:
+ release_sock(sk);
+ return ret;
+}
+
+static int llcp_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t len, int flags)
+{
+ int noblock = flags & MSG_DONTWAIT;
+ struct sock *sk = sock->sk;
+ unsigned int copied, rlen;
+ struct sk_buff *skb, *cskb;
+ int err = 0;
+
+ pr_debug("%p %zu\n", sk, len);
+
+ lock_sock(sk);
+
+ if (sk->sk_state == LLCP_CLOSED &&
+ skb_queue_empty(&sk->sk_receive_queue)) {
+ release_sock(sk);
+ return 0;
+ }
+
+ release_sock(sk);
+
+ if (flags & (MSG_OOB))
+ return -EOPNOTSUPP;
+
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb) {
+ pr_err("Recv datagram failed state %d %d %d",
+ sk->sk_state, err, sock_error(sk));
+
+ if (sk->sk_shutdown & RCV_SHUTDOWN)
+ return 0;
+
+ return err;
+ }
+
+ rlen = skb->len; /* real length of skb */
+ copied = min_t(unsigned int, rlen, len);
+
+ cskb = skb;
+ if (memcpy_toiovec(msg->msg_iov, cskb->data, copied)) {
+ if (!(flags & MSG_PEEK))
+ skb_queue_head(&sk->sk_receive_queue, skb);
+ return -EFAULT;
+ }
+
+ /* Mark read part of skb as used */
+ if (!(flags & MSG_PEEK)) {
+
+ /* SOCK_STREAM: re-queue skb if it contains unreceived data */
+ if (sk->sk_type == SOCK_STREAM) {
+ skb_pull(skb, copied);
+ if (skb->len) {
+ skb_queue_head(&sk->sk_receive_queue, skb);
+ goto done;
+ }
+ }
+
+ kfree_skb(skb);
+ }
+
+ /* XXX Queue backlogged skbs */
+
+done:
+ /* SOCK_SEQPACKET: return real length if MSG_TRUNC is set */
+ if (sk->sk_type == SOCK_SEQPACKET && (flags & MSG_TRUNC))
+ copied = rlen;
+
+ return copied;
+}
+
+static const struct proto_ops llcp_sock_ops = {
+ .family = PF_NFC,
+ .owner = THIS_MODULE,
+ .bind = llcp_sock_bind,
+ .connect = llcp_sock_connect,
+ .release = llcp_sock_release,
+ .socketpair = sock_no_socketpair,
+ .accept = llcp_sock_accept,
+ .getname = llcp_sock_getname,
+ .poll = llcp_sock_poll,
+ .ioctl = sock_no_ioctl,
+ .listen = llcp_sock_listen,
+ .shutdown = sock_no_shutdown,
+ .setsockopt = sock_no_setsockopt,
+ .getsockopt = sock_no_getsockopt,
+ .sendmsg = sock_no_sendmsg,
+ .recvmsg = llcp_sock_recvmsg,
+ .mmap = sock_no_mmap,
+};
+
+static void llcp_sock_destruct(struct sock *sk)
+{
+ struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
+
+ pr_debug("%p\n", sk);
+
+ if (sk->sk_state == LLCP_CONNECTED)
+ nfc_put_device(llcp_sock->dev);
+
+ skb_queue_purge(&sk->sk_receive_queue);
+
+ nfc_llcp_sock_free(llcp_sock);
+
+ if (!sock_flag(sk, SOCK_DEAD)) {
+ pr_err("Freeing alive NFC LLCP socket %p\n", sk);
+ return;
+ }
+}
+
+struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp)
+{
+ struct sock *sk;
+ struct nfc_llcp_sock *llcp_sock;
+
+ sk = sk_alloc(&init_net, PF_NFC, gfp, &llcp_sock_proto);
+ if (!sk)
+ return NULL;
+
+ llcp_sock = nfc_llcp_sock(sk);
+
+ sock_init_data(sock, sk);
+ sk->sk_state = LLCP_CLOSED;
+ sk->sk_protocol = NFC_SOCKPROTO_LLCP;
+ sk->sk_type = type;
+ sk->sk_destruct = llcp_sock_destruct;
+
+ llcp_sock->ssap = 0;
+ llcp_sock->dsap = LLCP_SAP_SDP;
+ llcp_sock->send_n = llcp_sock->send_ack_n = 0;
+ llcp_sock->recv_n = llcp_sock->recv_ack_n = 0;
+ llcp_sock->remote_ready = 1;
+ skb_queue_head_init(&llcp_sock->tx_queue);
+ skb_queue_head_init(&llcp_sock->tx_pending_queue);
+ skb_queue_head_init(&llcp_sock->tx_backlog_queue);
+ INIT_LIST_HEAD(&llcp_sock->list);
+ INIT_LIST_HEAD(&llcp_sock->accept_queue);
+
+ if (sock != NULL)
+ sock->state = SS_UNCONNECTED;
+
+ return sk;
+}
+
+void nfc_llcp_sock_free(struct nfc_llcp_sock *sock)
+{
+ kfree(sock->service_name);
+
+ skb_queue_purge(&sock->tx_queue);
+ skb_queue_purge(&sock->tx_pending_queue);
+ skb_queue_purge(&sock->tx_backlog_queue);
+
+ list_del_init(&sock->accept_queue);
+
+ sock->parent = NULL;
+}
+
+static int llcp_sock_create(struct net *net, struct socket *sock,
+ const struct nfc_protocol *nfc_proto)
+{
+ struct sock *sk;
+
+ pr_debug("%p\n", sock);
+
+ if (sock->type != SOCK_STREAM && sock->type != SOCK_DGRAM)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &llcp_sock_ops;
+
+ sk = nfc_llcp_sock_alloc(sock, sock->type, GFP_ATOMIC);
+ if (sk == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static const struct nfc_protocol llcp_nfc_proto = {
+ .id = NFC_SOCKPROTO_LLCP,
+ .proto = &llcp_sock_proto,
+ .owner = THIS_MODULE,
+ .create = llcp_sock_create
+};
+
+int __init nfc_llcp_sock_init(void)
+{
+ return nfc_proto_register(&llcp_nfc_proto);
+}
+
+void nfc_llcp_sock_exit(void)
+{
+ nfc_proto_unregister(&llcp_nfc_proto);
+}
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 37de28e97330..2deb4aebf568 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -25,7 +25,7 @@
*
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/types.h>
#include <linux/workqueue.h>
@@ -71,7 +71,7 @@ static int __nci_request(struct nci_dev *ndev,
__u32 timeout)
{
int rc = 0;
- unsigned long completion_rc;
+ long completion_rc;
ndev->req_status = NCI_REQ_PEND;
diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c
index 1e040feb2f84..e5756b30e602 100644
--- a/net/nfc/nci/data.c
+++ b/net/nfc/nci/data.c
@@ -21,7 +21,7 @@
*
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/types.h>
#include <linux/interrupt.h>
diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c
index c36bd4a09abb..003846b2c326 100644
--- a/net/nfc/nci/ntf.c
+++ b/net/nfc/nci/ntf.c
@@ -25,7 +25,7 @@
*
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/types.h>
#include <linux/interrupt.h>
diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
index ca611c52aaaa..3f444c8a66e9 100644
--- a/net/nfc/nci/rsp.c
+++ b/net/nfc/nci/rsp.c
@@ -25,7 +25,7 @@
*
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <linux/types.h>
#include <linux/interrupt.h>
diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c
index c10e9b8c5ad9..43a1c47756a7 100644
--- a/net/nfc/netlink.c
+++ b/net/nfc/netlink.c
@@ -21,7 +21,7 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <net/genetlink.h>
#include <linux/nfc.h>
@@ -46,6 +46,8 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = {
[NFC_ATTR_DEVICE_NAME] = { .type = NLA_STRING,
.len = NFC_DEVICE_NAME_MAXSIZE },
[NFC_ATTR_PROTOCOLS] = { .type = NLA_U32 },
+ [NFC_ATTR_COMM_MODE] = { .type = NLA_U8 },
+ [NFC_ATTR_RF_MODE] = { .type = NLA_U8 },
};
static int nfc_genl_send_target(struct sk_buff *msg, struct nfc_target *target,
@@ -311,6 +313,75 @@ static int nfc_genl_dump_devices_done(struct netlink_callback *cb)
return 0;
}
+int nfc_genl_dep_link_up_event(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ pr_debug("DEP link is up\n");
+
+ msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+ NFC_CMD_DEP_LINK_UP);
+ if (!hdr)
+ goto free_msg;
+
+ NLA_PUT_U32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx);
+ if (rf_mode == NFC_RF_INITIATOR)
+ NLA_PUT_U32(msg, NFC_ATTR_TARGET_INDEX, target_idx);
+ NLA_PUT_U8(msg, NFC_ATTR_COMM_MODE, comm_mode);
+ NLA_PUT_U8(msg, NFC_ATTR_RF_MODE, rf_mode);
+
+ genlmsg_end(msg, hdr);
+
+ dev->dep_link_up = true;
+
+ genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC);
+
+ return 0;
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+free_msg:
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+}
+
+int nfc_genl_dep_link_down_event(struct nfc_dev *dev)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ pr_debug("DEP link is down\n");
+
+ msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0,
+ NFC_CMD_DEP_LINK_DOWN);
+ if (!hdr)
+ goto free_msg;
+
+ NLA_PUT_U32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx);
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC);
+
+ return 0;
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+free_msg:
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+}
+
static int nfc_genl_get_device(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
@@ -398,6 +469,8 @@ static int nfc_genl_start_poll(struct sk_buff *skb, struct genl_info *info)
u32 idx;
u32 protocols;
+ pr_debug("Poll start\n");
+
if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
!info->attrs[NFC_ATTR_PROTOCOLS])
return -EINVAL;
@@ -452,6 +525,67 @@ out:
return rc;
}
+static int nfc_genl_dep_link_up(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nfc_dev *dev;
+ int rc, tgt_idx;
+ u32 idx;
+ u8 comm, rf;
+
+ pr_debug("DEP link up\n");
+
+ if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
+ !info->attrs[NFC_ATTR_COMM_MODE] ||
+ !info->attrs[NFC_ATTR_RF_MODE])
+ return -EINVAL;
+
+ idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+ if (!info->attrs[NFC_ATTR_TARGET_INDEX])
+ tgt_idx = NFC_TARGET_IDX_ANY;
+ else
+ tgt_idx = nla_get_u32(info->attrs[NFC_ATTR_TARGET_INDEX]);
+
+ comm = nla_get_u8(info->attrs[NFC_ATTR_COMM_MODE]);
+ rf = nla_get_u8(info->attrs[NFC_ATTR_RF_MODE]);
+
+ if (comm != NFC_COMM_ACTIVE && comm != NFC_COMM_PASSIVE)
+ return -EINVAL;
+
+ if (rf != NFC_RF_INITIATOR && comm != NFC_RF_TARGET)
+ return -EINVAL;
+
+ dev = nfc_get_device(idx);
+ if (!dev)
+ return -ENODEV;
+
+ rc = nfc_dep_link_up(dev, tgt_idx, comm, rf);
+
+ nfc_put_device(dev);
+
+ return rc;
+}
+
+static int nfc_genl_dep_link_down(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nfc_dev *dev;
+ int rc;
+ u32 idx;
+
+ if (!info->attrs[NFC_ATTR_DEVICE_INDEX])
+ return -EINVAL;
+
+ idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
+
+ dev = nfc_get_device(idx);
+ if (!dev)
+ return -ENODEV;
+
+ rc = nfc_dep_link_down(dev);
+
+ nfc_put_device(dev);
+ return rc;
+}
+
static struct genl_ops nfc_genl_ops[] = {
{
.cmd = NFC_CMD_GET_DEVICE,
@@ -481,6 +615,16 @@ static struct genl_ops nfc_genl_ops[] = {
.policy = nfc_genl_policy,
},
{
+ .cmd = NFC_CMD_DEP_LINK_UP,
+ .doit = nfc_genl_dep_link_up,
+ .policy = nfc_genl_policy,
+ },
+ {
+ .cmd = NFC_CMD_DEP_LINK_DOWN,
+ .doit = nfc_genl_dep_link_down,
+ .policy = nfc_genl_policy,
+ },
+ {
.cmd = NFC_CMD_GET_TARGET,
.dumpit = nfc_genl_dump_targets,
.done = nfc_genl_dump_targets_done,
@@ -504,12 +648,10 @@ static int nfc_genl_rcv_nl_event(struct notifier_block *this,
dev = nfc_device_iter_next(&iter);
while (dev) {
- mutex_lock(&dev->genl_data.genl_data_mutex);
if (dev->genl_data.poll_req_pid == n->pid) {
nfc_stop_poll(dev);
dev->genl_data.poll_req_pid = 0;
}
- mutex_unlock(&dev->genl_data.genl_data_mutex);
dev = nfc_device_iter_next(&iter);
}
diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h
index 67d605015304..2c2c4015c68b 100644
--- a/net/nfc/nfc.h
+++ b/net/nfc/nfc.h
@@ -46,6 +46,60 @@ struct nfc_rawsock {
#define to_rawsock_sk(_tx_work) \
((struct sock *) container_of(_tx_work, struct nfc_rawsock, tx_work))
+#ifdef CONFIG_NFC_LLCP
+
+void nfc_llcp_mac_is_down(struct nfc_dev *dev);
+void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode);
+int nfc_llcp_register_device(struct nfc_dev *dev);
+void nfc_llcp_unregister_device(struct nfc_dev *dev);
+int nfc_llcp_set_remote_gb(struct nfc_dev *dev, u8 *gb, u8 gb_len);
+u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *general_bytes_len);
+int __init nfc_llcp_init(void);
+void nfc_llcp_exit(void);
+
+#else
+
+void nfc_llcp_mac_is_down(struct nfc_dev *dev)
+{
+}
+
+void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode)
+{
+}
+
+static inline int nfc_llcp_register_device(struct nfc_dev *dev)
+{
+ return 0;
+}
+
+static inline void nfc_llcp_unregister_device(struct nfc_dev *dev)
+{
+}
+
+static inline int nfc_llcp_set_remote_gb(struct nfc_dev *dev, u8 *gb, u8 gb_len)
+{
+ return 0;
+}
+
+static inline u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *gb_len)
+{
+ *gb_len = 0;
+ return NULL;
+}
+
+static inline int nfc_llcp_init(void)
+{
+ return 0;
+}
+
+static inline void nfc_llcp_exit(void)
+{
+}
+
+#endif
+
int __init rawsock_init(void);
void rawsock_exit(void);
@@ -68,6 +122,10 @@ int nfc_genl_targets_found(struct nfc_dev *dev);
int nfc_genl_device_added(struct nfc_dev *dev);
int nfc_genl_device_removed(struct nfc_dev *dev);
+int nfc_genl_dep_link_up_event(struct nfc_dev *dev, u32 target_idx,
+ u8 comm_mode, u8 rf_mode);
+int nfc_genl_dep_link_down_event(struct nfc_dev *dev);
+
struct nfc_dev *nfc_get_device(unsigned idx);
static inline void nfc_put_device(struct nfc_dev *dev)
@@ -102,6 +160,11 @@ int nfc_start_poll(struct nfc_dev *dev, u32 protocols);
int nfc_stop_poll(struct nfc_dev *dev);
+int nfc_dep_link_up(struct nfc_dev *dev, int target_idx,
+ u8 comm_mode, u8 rf_mode);
+
+int nfc_dep_link_down(struct nfc_dev *dev);
+
int nfc_activate_target(struct nfc_dev *dev, u32 target_idx, u32 protocol);
int nfc_deactivate_target(struct nfc_dev *dev, u32 target_idx);
diff --git a/net/nfc/rawsock.c b/net/nfc/rawsock.c
index 68ecf3fa94e0..2e2f8c6a61fe 100644
--- a/net/nfc/rawsock.c
+++ b/net/nfc/rawsock.c
@@ -21,7 +21,7 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
#include <net/tcp_states.h>
#include <linux/nfc.h>
@@ -208,13 +208,10 @@ static int rawsock_sendmsg(struct kiocb *iocb, struct socket *sock,
if (sock->state != SS_CONNECTED)
return -ENOTCONN;
- skb = sock_alloc_send_skb(sk, len + dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE,
- msg->msg_flags & MSG_DONTWAIT, &rc);
- if (!skb)
+ skb = nfc_alloc_send_skb(dev, sk, msg->msg_flags, len, &rc);
+ if (skb == NULL)
return rc;
- skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);
-
rc = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
if (rc < 0) {
kfree_skb(skb);
@@ -313,7 +310,7 @@ static int rawsock_create(struct net *net, struct socket *sock,
sock->ops = &rawsock_ops;
- sk = sk_alloc(net, PF_NFC, GFP_KERNEL, nfc_proto->proto);
+ sk = sk_alloc(net, PF_NFC, GFP_ATOMIC, nfc_proto->proto);
if (!sk)
return -ENOMEM;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index fb08c28fc90a..43ad9c81efcf 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -249,12 +249,11 @@ struct cfg80211_event {
u16 status;
} cr;
struct {
- struct ieee80211_channel *channel;
- u8 bssid[ETH_ALEN];
const u8 *req_ie;
const u8 *resp_ie;
size_t req_ie_len;
size_t resp_ie_len;
+ struct cfg80211_bss *bss;
} rm;
struct {
const u8 *ie;
@@ -403,8 +402,7 @@ int cfg80211_disconnect(struct cfg80211_registered_device *rdev,
struct net_device *dev, u16 reason,
bool wextev);
void __cfg80211_roamed(struct wireless_dev *wdev,
- struct ieee80211_channel *channel,
- const u8 *bssid,
+ struct cfg80211_bss *bss,
const u8 *req_ie, size_t req_ie_len,
const u8 *resp_ie, size_t resp_ie_len);
int cfg80211_mgd_wext_connect(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ba439664c2e0..b07c4fc4ae22 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -47,22 +47,21 @@ static struct genl_family nl80211_fam = {
};
/* internal helper: get rdev and dev */
-static int get_rdev_dev_by_info_ifindex(struct genl_info *info,
- struct cfg80211_registered_device **rdev,
- struct net_device **dev)
+static int get_rdev_dev_by_ifindex(struct net *netns, struct nlattr **attrs,
+ struct cfg80211_registered_device **rdev,
+ struct net_device **dev)
{
- struct nlattr **attrs = info->attrs;
int ifindex;
if (!attrs[NL80211_ATTR_IFINDEX])
return -EINVAL;
ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
- *dev = dev_get_by_index(genl_info_net(info), ifindex);
+ *dev = dev_get_by_index(netns, ifindex);
if (!*dev)
return -ENODEV;
- *rdev = cfg80211_get_dev_from_ifindex(genl_info_net(info), ifindex);
+ *rdev = cfg80211_get_dev_from_ifindex(netns, ifindex);
if (IS_ERR(*rdev)) {
dev_put(*dev);
return PTR_ERR(*rdev);
@@ -2247,6 +2246,7 @@ static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
[NL80211_STA_FLAG_WME] = { .type = NLA_FLAG },
[NL80211_STA_FLAG_MFP] = { .type = NLA_FLAG },
[NL80211_STA_FLAG_AUTHENTICATED] = { .type = NLA_FLAG },
+ [NL80211_STA_FLAG_TDLS_PEER] = { .type = NLA_FLAG },
};
static int parse_station_flags(struct genl_info *info,
@@ -2579,6 +2579,9 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
params.ht_capa =
nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
+ if (!rdev->ops->change_station)
+ return -EOPNOTSUPP;
+
if (parse_station_flags(info, &params))
return -EINVAL;
@@ -2590,73 +2593,84 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
params.plink_state =
nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_STATE]);
- params.vlan = get_vlan(info, rdev);
- if (IS_ERR(params.vlan))
- return PTR_ERR(params.vlan);
-
- /* validate settings */
- err = 0;
-
switch (dev->ieee80211_ptr->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_GO:
/* disallow mesh-specific things */
if (params.plink_action)
- err = -EINVAL;
+ return -EINVAL;
+
+ /* TDLS can't be set, ... */
+ if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+ return -EINVAL;
+ /*
+ * ... but don't bother the driver with it. This works around
+ * a hostapd/wpa_supplicant issue -- it always includes the
+ * TLDS_PEER flag in the mask even for AP mode.
+ */
+ params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+
+ /* accept only the listed bits */
+ if (params.sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) |
+ BIT(NL80211_STA_FLAG_WME) |
+ BIT(NL80211_STA_FLAG_MFP)))
+ return -EINVAL;
+
+ /* must be last in here for error handling */
+ params.vlan = get_vlan(info, rdev);
+ if (IS_ERR(params.vlan))
+ return PTR_ERR(params.vlan);
break;
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_STATION:
/* disallow things sta doesn't support */
if (params.plink_action)
- err = -EINVAL;
- if (params.vlan)
- err = -EINVAL;
- if (params.supported_rates &&
- !(params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
- err = -EINVAL;
+ return -EINVAL;
if (params.ht_capa)
- err = -EINVAL;
+ return -EINVAL;
if (params.listen_interval >= 0)
- err = -EINVAL;
- if (params.sta_flags_mask &
- ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
- BIT(NL80211_STA_FLAG_TDLS_PEER)))
- err = -EINVAL;
- /* can't change the TDLS bit */
- if (!(params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
- (params.sta_flags_mask & BIT(NL80211_STA_FLAG_TDLS_PEER)))
- err = -EINVAL;
+ return -EINVAL;
+ /*
+ * Don't allow userspace to change the TDLS_PEER flag,
+ * but silently ignore attempts to change it since we
+ * don't have state here to verify that it doesn't try
+ * to change the flag.
+ */
+ params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+
+ /* reject any changes other than AUTHORIZED */
+ if (params.sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
+ return -EINVAL;
break;
case NL80211_IFTYPE_MESH_POINT:
/* disallow things mesh doesn't support */
if (params.vlan)
- err = -EINVAL;
+ return -EINVAL;
if (params.ht_capa)
- err = -EINVAL;
+ return -EINVAL;
if (params.listen_interval >= 0)
- err = -EINVAL;
+ return -EINVAL;
+ /*
+ * No special handling for TDLS here -- the userspace
+ * mesh code doesn't have this bug.
+ */
if (params.sta_flags_mask &
~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
BIT(NL80211_STA_FLAG_MFP) |
BIT(NL80211_STA_FLAG_AUTHORIZED)))
- err = -EINVAL;
+ return -EINVAL;
break;
default:
- err = -EINVAL;
+ return -EOPNOTSUPP;
}
- if (err)
- goto out;
-
- if (!rdev->ops->change_station) {
- err = -EOPNOTSUPP;
- goto out;
- }
+ /* be aware of params.vlan when changing code here */
err = rdev->ops->change_station(&rdev->wiphy, dev, mac_addr, &params);
- out:
if (params.vlan)
dev_put(params.vlan);
@@ -2711,70 +2725,81 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
params.plink_action =
nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_ACTION]);
+ if (!rdev->ops->add_station)
+ return -EOPNOTSUPP;
+
if (parse_station_flags(info, &params))
return -EINVAL;
- /* parse WME attributes if sta is WME capable */
- if ((rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) &&
- (params.sta_flags_set & BIT(NL80211_STA_FLAG_WME)) &&
- info->attrs[NL80211_ATTR_STA_WME]) {
- struct nlattr *tb[NL80211_STA_WME_MAX + 1];
- struct nlattr *nla;
+ switch (dev->ieee80211_ptr->iftype) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_AP_VLAN:
+ case NL80211_IFTYPE_P2P_GO:
+ /* parse WME attributes if sta is WME capable */
+ if ((rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) &&
+ (params.sta_flags_set & BIT(NL80211_STA_FLAG_WME)) &&
+ info->attrs[NL80211_ATTR_STA_WME]) {
+ struct nlattr *tb[NL80211_STA_WME_MAX + 1];
+ struct nlattr *nla;
+
+ nla = info->attrs[NL80211_ATTR_STA_WME];
+ err = nla_parse_nested(tb, NL80211_STA_WME_MAX, nla,
+ nl80211_sta_wme_policy);
+ if (err)
+ return err;
- nla = info->attrs[NL80211_ATTR_STA_WME];
- err = nla_parse_nested(tb, NL80211_STA_WME_MAX, nla,
- nl80211_sta_wme_policy);
- if (err)
- return err;
+ if (tb[NL80211_STA_WME_UAPSD_QUEUES])
+ params.uapsd_queues =
+ nla_get_u8(tb[NL80211_STA_WME_UAPSD_QUEUES]);
+ if (params.uapsd_queues &
+ ~IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
+ return -EINVAL;
- if (tb[NL80211_STA_WME_UAPSD_QUEUES])
- params.uapsd_queues =
- nla_get_u8(tb[NL80211_STA_WME_UAPSD_QUEUES]);
- if (params.uapsd_queues & ~IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
- return -EINVAL;
+ if (tb[NL80211_STA_WME_MAX_SP])
+ params.max_sp =
+ nla_get_u8(tb[NL80211_STA_WME_MAX_SP]);
- if (tb[NL80211_STA_WME_MAX_SP])
- params.max_sp =
- nla_get_u8(tb[NL80211_STA_WME_MAX_SP]);
+ if (params.max_sp &
+ ~IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK)
+ return -EINVAL;
- if (params.max_sp & ~IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK)
+ params.sta_modify_mask |= STATION_PARAM_APPLY_UAPSD;
+ }
+ /* TDLS peers cannot be added */
+ if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
return -EINVAL;
+ /* but don't bother the driver with it */
+ params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
- params.sta_modify_mask |= STATION_PARAM_APPLY_UAPSD;
+ /* must be last in here for error handling */
+ params.vlan = get_vlan(info, rdev);
+ if (IS_ERR(params.vlan))
+ return PTR_ERR(params.vlan);
+ break;
+ case NL80211_IFTYPE_MESH_POINT:
+ /* TDLS peers cannot be added */
+ if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+ return -EINVAL;
+ break;
+ case NL80211_IFTYPE_STATION:
+ /* Only TDLS peers can be added */
+ if (!(params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
+ return -EINVAL;
+ /* Can only add if TDLS ... */
+ if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS))
+ return -EOPNOTSUPP;
+ /* ... with external setup is supported */
+ if (!(rdev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP))
+ return -EOPNOTSUPP;
+ break;
+ default:
+ return -EOPNOTSUPP;
}
- if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
- dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN &&
- dev->ieee80211_ptr->iftype != NL80211_IFTYPE_MESH_POINT &&
- dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO &&
- dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION)
- return -EINVAL;
-
- /*
- * Only managed stations can add TDLS peers, and only when the
- * wiphy supports external TDLS setup.
- */
- if (dev->ieee80211_ptr->iftype == NL80211_IFTYPE_STATION &&
- !((params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
- (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
- (rdev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP)))
- return -EINVAL;
-
- params.vlan = get_vlan(info, rdev);
- if (IS_ERR(params.vlan))
- return PTR_ERR(params.vlan);
-
- /* validate settings */
- err = 0;
-
- if (!rdev->ops->add_station) {
- err = -EOPNOTSUPP;
- goto out;
- }
+ /* be aware of params.vlan when changing code here */
err = rdev->ops->add_station(&rdev->wiphy, dev, mac_addr, &params);
- out:
if (params.vlan)
dev_put(params.vlan);
return err;
@@ -4795,7 +4820,7 @@ static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info)
static int nl80211_testmode_dump(struct sk_buff *skb,
struct netlink_callback *cb)
{
- struct cfg80211_registered_device *dev;
+ struct cfg80211_registered_device *rdev;
int err;
long phy_idx;
void *data = NULL;
@@ -4813,9 +4838,21 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
nl80211_policy);
if (err)
return err;
- if (!nl80211_fam.attrbuf[NL80211_ATTR_WIPHY])
- return -EINVAL;
- phy_idx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]);
+ if (nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]) {
+ phy_idx = nla_get_u32(
+ nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]);
+ } else {
+ struct net_device *netdev;
+
+ err = get_rdev_dev_by_ifindex(sock_net(skb->sk),
+ nl80211_fam.attrbuf,
+ &rdev, &netdev);
+ if (err)
+ return err;
+ dev_put(netdev);
+ phy_idx = rdev->wiphy_idx;
+ cfg80211_unlock_rdev(rdev);
+ }
if (nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA])
cb->args[1] =
(long)nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA];
@@ -4827,15 +4864,15 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
}
mutex_lock(&cfg80211_mutex);
- dev = cfg80211_rdev_by_wiphy_idx(phy_idx);
- if (!dev) {
+ rdev = cfg80211_rdev_by_wiphy_idx(phy_idx);
+ if (!rdev) {
mutex_unlock(&cfg80211_mutex);
return -ENOENT;
}
- cfg80211_lock_rdev(dev);
+ cfg80211_lock_rdev(rdev);
mutex_unlock(&cfg80211_mutex);
- if (!dev->ops->testmode_dump) {
+ if (!rdev->ops->testmode_dump) {
err = -EOPNOTSUPP;
goto out_err;
}
@@ -4846,7 +4883,7 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
NL80211_CMD_TESTMODE);
struct nlattr *tmdata;
- if (nla_put_u32(skb, NL80211_ATTR_WIPHY, dev->wiphy_idx) < 0) {
+ if (nla_put_u32(skb, NL80211_ATTR_WIPHY, phy_idx) < 0) {
genlmsg_cancel(skb, hdr);
break;
}
@@ -4856,8 +4893,8 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
genlmsg_cancel(skb, hdr);
break;
}
- err = dev->ops->testmode_dump(&dev->wiphy, skb, cb,
- data, data_len);
+ err = rdev->ops->testmode_dump(&rdev->wiphy, skb, cb,
+ data, data_len);
nla_nest_end(skb, tmdata);
if (err == -ENOBUFS || err == -ENOENT) {
@@ -4875,7 +4912,7 @@ static int nl80211_testmode_dump(struct sk_buff *skb,
/* see above */
cb->args[0] = phy_idx + 1;
out_err:
- cfg80211_unlock_rdev(dev);
+ cfg80211_unlock_rdev(rdev);
return err;
}
@@ -6110,7 +6147,8 @@ static int nl80211_pre_doit(struct genl_ops *ops, struct sk_buff *skb,
}
info->user_ptr[0] = rdev;
} else if (ops->internal_flags & NL80211_FLAG_NEED_NETDEV) {
- err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+ err = get_rdev_dev_by_ifindex(genl_info_net(info), info->attrs,
+ &rdev, &dev);
if (err) {
if (rtnl)
rtnl_unlock();
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 70b171a52aea..c45c8b772ddd 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -873,10 +873,22 @@ static void handle_channel(struct wiphy *wiphy,
chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
chan->max_antenna_gain = min(chan->orig_mag,
(int) MBI_TO_DBI(power_rule->max_antenna_gain));
- if (chan->orig_mpwr)
- chan->max_power = min(chan->orig_mpwr,
- (int) MBM_TO_DBM(power_rule->max_eirp));
- else
+ if (chan->orig_mpwr) {
+ /*
+ * Devices that have their own custom regulatory domain
+ * but also use WIPHY_FLAG_STRICT_REGULATORY will follow the
+ * passed country IE power settings.
+ */
+ if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
+ wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY &&
+ wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) {
+ chan->max_power =
+ MBM_TO_DBM(power_rule->max_eirp);
+ } else {
+ chan->max_power = min(chan->orig_mpwr,
+ (int) MBM_TO_DBM(power_rule->max_eirp));
+ }
+ } else
chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
}
@@ -1163,9 +1175,21 @@ void regulatory_update(struct wiphy *wiphy,
static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
{
struct cfg80211_registered_device *rdev;
+ struct wiphy *wiphy;
- list_for_each_entry(rdev, &cfg80211_rdev_list, list)
- wiphy_update_regulatory(&rdev->wiphy, initiator);
+ list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+ wiphy = &rdev->wiphy;
+ wiphy_update_regulatory(wiphy, initiator);
+ /*
+ * Regulatory updates set by CORE are ignored for custom
+ * regulatory cards. Let us notify the changes to the driver,
+ * as some drivers used this to restore its orig_* reg domain.
+ */
+ if (initiator == NL80211_REGDOM_SET_BY_CORE &&
+ wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY &&
+ wiphy->reg_notifier)
+ wiphy->reg_notifier(wiphy, last_request);
+ }
}
static void handle_channel_custom(struct wiphy *wiphy,
@@ -1768,6 +1792,26 @@ static void restore_alpha2(char *alpha2, bool reset_user)
REG_DBG_PRINT("Restoring regulatory settings\n");
}
+static void restore_custom_reg_settings(struct wiphy *wiphy)
+{
+ struct ieee80211_supported_band *sband;
+ enum ieee80211_band band;
+ struct ieee80211_channel *chan;
+ int i;
+
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ sband = wiphy->bands[band];
+ if (!sband)
+ continue;
+ for (i = 0; i < sband->n_channels; i++) {
+ chan = &sband->channels[i];
+ chan->flags = chan->orig_flags;
+ chan->max_antenna_gain = chan->orig_mag;
+ chan->max_power = chan->orig_mpwr;
+ }
+ }
+}
+
/*
* Restoring regulatory settings involves ingoring any
* possibly stale country IE information and user regulatory
@@ -1789,6 +1833,7 @@ static void restore_regulatory_settings(bool reset_user)
struct reg_beacon *reg_beacon, *btmp;
struct regulatory_request *reg_request, *tmp;
LIST_HEAD(tmp_reg_req_list);
+ struct cfg80211_registered_device *rdev;
mutex_lock(&cfg80211_mutex);
mutex_lock(&reg_mutex);
@@ -1837,6 +1882,11 @@ static void restore_regulatory_settings(bool reset_user)
/* First restore to the basic regulatory settings */
cfg80211_regdomain = cfg80211_world_regdom;
+ list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+ if (rdev->wiphy.flags & WIPHY_FLAG_CUSTOM_REGULATORY)
+ restore_custom_reg_settings(&rdev->wiphy);
+ }
+
mutex_unlock(&reg_mutex);
mutex_unlock(&cfg80211_mutex);
diff --git a/net/wireless/sme.c b/net/wireless/sme.c
index f0c900ce2fb9..7b9ecaed96be 100644
--- a/net/wireless/sme.c
+++ b/net/wireless/sme.c
@@ -553,45 +553,35 @@ void cfg80211_connect_result(struct net_device *dev, const u8 *bssid,
EXPORT_SYMBOL(cfg80211_connect_result);
void __cfg80211_roamed(struct wireless_dev *wdev,
- struct ieee80211_channel *channel,
- const u8 *bssid,
+ struct cfg80211_bss *bss,
const u8 *req_ie, size_t req_ie_len,
const u8 *resp_ie, size_t resp_ie_len)
{
- struct cfg80211_bss *bss;
#ifdef CONFIG_CFG80211_WEXT
union iwreq_data wrqu;
#endif
-
ASSERT_WDEV_LOCK(wdev);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
- return;
+ goto out;
if (wdev->sme_state != CFG80211_SME_CONNECTED)
- return;
+ goto out;
/* internal error -- how did we get to CONNECTED w/o BSS? */
if (WARN_ON(!wdev->current_bss)) {
- return;
+ goto out;
}
cfg80211_unhold_bss(wdev->current_bss);
cfg80211_put_bss(&wdev->current_bss->pub);
wdev->current_bss = NULL;
- bss = cfg80211_get_bss(wdev->wiphy, channel, bssid,
- wdev->ssid, wdev->ssid_len,
- WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS);
-
- if (WARN_ON(!bss))
- return;
-
cfg80211_hold_bss(bss_from_pub(bss));
wdev->current_bss = bss_from_pub(bss);
- nl80211_send_roamed(wiphy_to_dev(wdev->wiphy), wdev->netdev, bssid,
+ nl80211_send_roamed(wiphy_to_dev(wdev->wiphy), wdev->netdev, bss->bssid,
req_ie, req_ie_len, resp_ie, resp_ie_len,
GFP_KERNEL);
@@ -612,11 +602,15 @@ void __cfg80211_roamed(struct wireless_dev *wdev,
memset(&wrqu, 0, sizeof(wrqu));
wrqu.ap_addr.sa_family = ARPHRD_ETHER;
- memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN);
- memcpy(wdev->wext.prev_bssid, bssid, ETH_ALEN);
+ memcpy(wrqu.ap_addr.sa_data, bss->bssid, ETH_ALEN);
+ memcpy(wdev->wext.prev_bssid, bss->bssid, ETH_ALEN);
wdev->wext.prev_bssid_valid = true;
wireless_send_event(wdev->netdev, SIOCGIWAP, &wrqu, NULL);
#endif
+
+ return;
+out:
+ cfg80211_put_bss(bss);
}
void cfg80211_roamed(struct net_device *dev,
@@ -626,32 +620,57 @@ void cfg80211_roamed(struct net_device *dev,
const u8 *resp_ie, size_t resp_ie_len, gfp_t gfp)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_bss *bss;
+
+ CFG80211_DEV_WARN_ON(wdev->sme_state != CFG80211_SME_CONNECTED);
+
+ bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, wdev->ssid,
+ wdev->ssid_len, WLAN_CAPABILITY_ESS,
+ WLAN_CAPABILITY_ESS);
+ if (WARN_ON(!bss))
+ return;
+
+ cfg80211_roamed_bss(dev, bss, req_ie, req_ie_len, resp_ie,
+ resp_ie_len, gfp);
+}
+EXPORT_SYMBOL(cfg80211_roamed);
+
+void cfg80211_roamed_bss(struct net_device *dev,
+ struct cfg80211_bss *bss, const u8 *req_ie,
+ size_t req_ie_len, const u8 *resp_ie,
+ size_t resp_ie_len, gfp_t gfp)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
struct cfg80211_event *ev;
unsigned long flags;
CFG80211_DEV_WARN_ON(wdev->sme_state != CFG80211_SME_CONNECTED);
+ if (WARN_ON(!bss))
+ return;
+
ev = kzalloc(sizeof(*ev) + req_ie_len + resp_ie_len, gfp);
- if (!ev)
+ if (!ev) {
+ cfg80211_put_bss(bss);
return;
+ }
ev->type = EVENT_ROAMED;
- ev->rm.channel = channel;
- memcpy(ev->rm.bssid, bssid, ETH_ALEN);
ev->rm.req_ie = ((u8 *)ev) + sizeof(*ev);
ev->rm.req_ie_len = req_ie_len;
memcpy((void *)ev->rm.req_ie, req_ie, req_ie_len);
ev->rm.resp_ie = ((u8 *)ev) + sizeof(*ev) + req_ie_len;
ev->rm.resp_ie_len = resp_ie_len;
memcpy((void *)ev->rm.resp_ie, resp_ie, resp_ie_len);
+ ev->rm.bss = bss;
spin_lock_irqsave(&wdev->event_lock, flags);
list_add_tail(&ev->list, &wdev->event_list);
spin_unlock_irqrestore(&wdev->event_lock, flags);
queue_work(cfg80211_wq, &rdev->event_work);
}
-EXPORT_SYMBOL(cfg80211_roamed);
+EXPORT_SYMBOL(cfg80211_roamed_bss);
void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
size_t ie_len, u16 reason, bool from_ap)
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 9c601d59b77a..e77df7585004 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -740,9 +740,9 @@ static void cfg80211_process_wdev_events(struct wireless_dev *wdev)
NULL);
break;
case EVENT_ROAMED:
- __cfg80211_roamed(wdev, ev->rm.channel, ev->rm.bssid,
- ev->rm.req_ie, ev->rm.req_ie_len,
- ev->rm.resp_ie, ev->rm.resp_ie_len);
+ __cfg80211_roamed(wdev, ev->rm.bss, ev->rm.req_ie,
+ ev->rm.req_ie_len, ev->rm.resp_ie,
+ ev->rm.resp_ie_len);
break;
case EVENT_DISCONNECTED:
__cfg80211_disconnected(wdev->netdev,
OpenPOWER on IntegriCloud