summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2014-11-21 16:39:45 -0500
committerDavid S. Miller <davem@davemloft.net>2014-11-21 16:39:45 -0500
commit53b15ef3c2a6bac8e3d9bb58c5689d731ed9593b (patch)
tree6b3194af697d91ac4c2ab1c716bd30c972cf4949 /net
parentb48c5ec53ef9f0fe617aafa94a752f528fdad149 (diff)
parent9e6f3f472c8f95021ad048acc7cd3e40a827f8ce (diff)
Merge tag 'master-2014-11-20' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-next
John W. Linville says: ==================== pull request: wireless-next 2014-11-21 Please pull this batch of updates intended for the 3.19 stream... For the mac80211 bits, Johannes says: "It has been a while since my last pull request, so we accumulated another relatively large set of changes: * TDLS off-channel support set from Arik/Liad, with some support patches I did * custom regulatory fixes from Arik * minstrel VHT fix (and a small optimisation) from Felix * add back radiotap vendor namespace support (myself) * random MAC address scanning for cfg80211/mac80211/hwsim (myself) * CSA improvements (Luca) * WoWLAN Net Detect (wake on network found) support (Luca) * and lots of other smaller changes from many people" For the Bluetooth bits, Johan says: "Here's another set of patches for 3.19. Most of it is again fixes and cleanups to ieee802154 related code from Alexander Aring. We've also got better handling of hardware error events along with a proper API for HCI drivers to notify the HCI core of such situations. There's also a minor fix for mgmt events as well as a sparse warning fix. The code for sending HCI commands synchronously also gets a fix where we might loose the completion event in the case of very fast HW (particularly easily reproducible with an emulated HCI device)." And... "Here's another bluetooth-next pull request for 3.19. We've got: - Various fixes, cleanups and improvements to ieee802154/mac802154 - Support for a Broadcom BCM20702A1 variant - Lots of lockdep fixes - Fixed handling of LE CoC errors that should trigger SMP" For the Atheros bits, Kalle says: "One ath6kl patch and rest for ath10k, but nothing really major which stands out. Most notable: o fix resume (Bartosz) o firmware restart is now faster and more reliable (Michal) o it's now possible to test hardware restart functionality without crashing the firmware using hw-restart parameter with simulate_fw_crash debugfs file (Michal)" On top of that...both ath9k and mwifiex get their usual level of updates. Of note is the ath9k spectral scan work from Oleksij Rempel. I also pulled from the wireless tree in order to avoid some merge issues. Please let me know if there are problems! ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/6lowpan/iphc.c41
-rw-r--r--net/bluetooth/6lowpan.c36
-rw-r--r--net/bluetooth/Kconfig20
-rw-r--r--net/bluetooth/amp.c25
-rw-r--r--net/bluetooth/bnep/Kconfig2
-rw-r--r--net/bluetooth/cmtp/Kconfig2
-rw-r--r--net/bluetooth/hci_core.c184
-rw-r--r--net/bluetooth/hci_event.c91
-rw-r--r--net/bluetooth/hidp/Kconfig2
-rw-r--r--net/bluetooth/hidp/core.c10
-rw-r--r--net/bluetooth/l2cap_core.c35
-rw-r--r--net/bluetooth/l2cap_sock.c29
-rw-r--r--net/bluetooth/mgmt.c108
-rw-r--r--net/bluetooth/rfcomm/Kconfig2
-rw-r--r--net/bluetooth/rfcomm/core.c6
-rw-r--r--net/bluetooth/smp.c61
-rw-r--r--net/bluetooth/smp.h9
-rw-r--r--net/ieee802154/6lowpan_rtnl.c38
-rw-r--r--net/ieee802154/Makefile2
-rw-r--r--net/ieee802154/core.c220
-rw-r--r--net/ieee802154/core.h46
-rw-r--r--net/ieee802154/ieee802154.h2
-rw-r--r--net/ieee802154/netlink.c2
-rw-r--r--net/ieee802154/nl-mac.c226
-rw-r--r--net/ieee802154/nl-phy.c23
-rw-r--r--net/ieee802154/nl802154.c957
-rw-r--r--net/ieee802154/nl802154.h7
-rw-r--r--net/ieee802154/rdev-ops.h89
-rw-r--r--net/ieee802154/sysfs.c40
-rw-r--r--net/mac80211/aes_ccm.c3
-rw-r--r--net/mac80211/agg-tx.c7
-rw-r--r--net/mac80211/cfg.c27
-rw-r--r--net/mac80211/debugfs_sta.c11
-rw-r--r--net/mac80211/driver-ops.h83
-rw-r--r--net/mac80211/ieee80211_i.h42
-rw-r--r--net/mac80211/iface.c2
-rw-r--r--net/mac80211/main.c6
-rw-r--r--net/mac80211/mlme.c28
-rw-r--r--net/mac80211/rate.c5
-rw-r--r--net/mac80211/rc80211_minstrel_ht.c6
-rw-r--r--net/mac80211/rx.c146
-rw-r--r--net/mac80211/scan.c129
-rw-r--r--net/mac80211/sta_info.c15
-rw-r--r--net/mac80211/sta_info.h11
-rw-r--r--net/mac80211/status.c55
-rw-r--r--net/mac80211/tdls.c792
-rw-r--r--net/mac80211/trace.h142
-rw-r--r--net/mac80211/tx.c303
-rw-r--r--net/mac80211/util.c60
-rw-r--r--net/mac80211/vht.c2
-rw-r--r--net/mac80211/wme.c39
-rw-r--r--net/mac802154/Makefile2
-rw-r--r--net/mac802154/cfg.c210
-rw-r--r--net/mac802154/cfg.h9
-rw-r--r--net/mac802154/driver-ops.h46
-rw-r--r--net/mac802154/ieee802154_i.h47
-rw-r--r--net/mac802154/iface.c310
-rw-r--r--net/mac802154/mac_cmd.c78
-rw-r--r--net/mac802154/main.c150
-rw-r--r--net/mac802154/mib.c81
-rw-r--r--net/mac802154/rx.c14
-rw-r--r--net/mac802154/tx.c2
-rw-r--r--net/mac802154/util.c35
-rw-r--r--net/wireless/core.c4
-rw-r--r--net/wireless/core.h1
-rw-r--r--net/wireless/nl80211.c522
-rw-r--r--net/wireless/rdev-ops.h24
-rw-r--r--net/wireless/reg.c49
-rw-r--r--net/wireless/trace.h58
69 files changed, 4698 insertions, 1173 deletions
diff --git a/net/6lowpan/iphc.c b/net/6lowpan/iphc.c
index 73a7065f0c6b..aced97db62f0 100644
--- a/net/6lowpan/iphc.c
+++ b/net/6lowpan/iphc.c
@@ -319,7 +319,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
if (iphc1 & LOWPAN_IPHC_CID) {
pr_debug("CID flag is set, increase header with one\n");
if (lowpan_fetch_skb(skb, &num_context, sizeof(num_context)))
- goto drop;
+ return -EINVAL;
}
hdr.version = 6;
@@ -331,7 +331,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
*/
case 0: /* 00b */
if (lowpan_fetch_skb(skb, &tmp, sizeof(tmp)))
- goto drop;
+ return -EINVAL;
memcpy(&hdr.flow_lbl, &skb->data[0], 3);
skb_pull(skb, 3);
@@ -344,7 +344,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
*/
case 2: /* 10b */
if (lowpan_fetch_skb(skb, &tmp, sizeof(tmp)))
- goto drop;
+ return -EINVAL;
hdr.priority = ((tmp >> 2) & 0x0f);
hdr.flow_lbl[0] = ((tmp << 6) & 0xC0) | ((tmp >> 2) & 0x30);
@@ -354,7 +354,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
*/
case 1: /* 01b */
if (lowpan_fetch_skb(skb, &tmp, sizeof(tmp)))
- goto drop;
+ return -EINVAL;
hdr.flow_lbl[0] = (skb->data[0] & 0x0F) | ((tmp >> 2) & 0x30);
memcpy(&hdr.flow_lbl[1], &skb->data[0], 2);
@@ -371,7 +371,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
if ((iphc0 & LOWPAN_IPHC_NH_C) == 0) {
/* Next header is carried inline */
if (lowpan_fetch_skb(skb, &hdr.nexthdr, sizeof(hdr.nexthdr)))
- goto drop;
+ return -EINVAL;
pr_debug("NH flag is set, next header carried inline: %02x\n",
hdr.nexthdr);
@@ -383,7 +383,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
} else {
if (lowpan_fetch_skb(skb, &hdr.hop_limit,
sizeof(hdr.hop_limit)))
- goto drop;
+ return -EINVAL;
}
/* Extract SAM to the tmp variable */
@@ -402,7 +402,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
/* Check on error of previous branch */
if (err)
- goto drop;
+ return -EINVAL;
/* Extract DAM to the tmp variable */
tmp = ((iphc1 & LOWPAN_IPHC_DAM_11) >> LOWPAN_IPHC_DAM_BIT) & 0x03;
@@ -417,7 +417,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
tmp);
if (err)
- goto drop;
+ return -EINVAL;
}
} else {
err = uncompress_addr(skb, &hdr.daddr, tmp, daddr,
@@ -425,7 +425,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
pr_debug("dest: stateless compression mode %d dest %pI6c\n",
tmp, &hdr.daddr);
if (err)
- goto drop;
+ return -EINVAL;
}
/* UDP data uncompression */
@@ -434,16 +434,14 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
const int needed = sizeof(struct udphdr) + sizeof(hdr);
if (uncompress_udp_header(skb, &uh))
- goto drop;
+ return -EINVAL;
/* replace the compressed UDP head by the uncompressed UDP
* header
*/
err = skb_cow(skb, needed);
- if (unlikely(err)) {
- kfree_skb(skb);
+ if (unlikely(err))
return err;
- }
skb_push(skb, sizeof(struct udphdr));
skb_reset_transport_header(skb);
@@ -455,10 +453,8 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
hdr.nexthdr = UIP_PROTO_UDP;
} else {
err = skb_cow(skb, sizeof(hdr));
- if (unlikely(err)) {
- kfree_skb(skb);
+ if (unlikely(err))
return err;
- }
}
hdr.payload_len = htons(skb->len);
@@ -478,9 +474,6 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
raw_dump_table(__func__, "raw header dump", (u8 *)&hdr, sizeof(hdr));
return 0;
-drop:
- kfree_skb(skb);
- return -EINVAL;
}
EXPORT_SYMBOL_GPL(lowpan_header_decompress);
@@ -512,9 +505,17 @@ static u8 lowpan_compress_addr_64(u8 **hc_ptr, u8 shift,
static void compress_udp_header(u8 **hc_ptr, struct sk_buff *skb)
{
- struct udphdr *uh = udp_hdr(skb);
+ struct udphdr *uh;
u8 tmp;
+ /* In the case of RAW sockets the transport header is not set by
+ * the ip6 stack so we must set it ourselves
+ */
+ if (skb->transport_header == skb->network_header)
+ skb_set_transport_header(skb, sizeof(struct ipv6hdr));
+
+ uh = udp_hdr(skb);
+
if (((ntohs(uh->source) & LOWPAN_NHC_UDP_4BIT_MASK) ==
LOWPAN_NHC_UDP_4BIT_PORT) &&
((ntohs(uh->dest) & LOWPAN_NHC_UDP_4BIT_MASK) ==
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index eef298d17452..bdcaefd2db12 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -87,13 +87,6 @@ struct lowpan_dev {
struct delayed_work notify_peers;
};
-static inline void peer_free(struct rcu_head *head)
-{
- struct lowpan_peer *e = container_of(head, struct lowpan_peer, rcu);
-
- kfree(e);
-}
-
static inline struct lowpan_dev *lowpan_dev(const struct net_device *netdev)
{
return netdev_priv(netdev);
@@ -108,7 +101,7 @@ static inline void peer_add(struct lowpan_dev *dev, struct lowpan_peer *peer)
static inline bool peer_del(struct lowpan_dev *dev, struct lowpan_peer *peer)
{
list_del_rcu(&peer->list);
- call_rcu(&peer->rcu, peer_free);
+ kfree_rcu(peer, rcu);
module_put(THIS_MODULE);
@@ -294,20 +287,20 @@ static int iphc_decompress(struct sk_buff *skb, struct net_device *netdev,
peer = __peer_lookup_chan(dev, chan);
rcu_read_unlock();
if (!peer)
- goto drop;
+ return -EINVAL;
saddr = peer->eui64_addr;
daddr = dev->netdev->dev_addr;
/* at least two bytes will be used for the encoding */
if (skb->len < 2)
- goto drop;
+ return -EINVAL;
if (lowpan_fetch_skb_u8(skb, &iphc0))
- goto drop;
+ return -EINVAL;
if (lowpan_fetch_skb_u8(skb, &iphc1))
- goto drop;
+ return -EINVAL;
return lowpan_header_decompress(skb, netdev,
saddr, IEEE802154_ADDR_LONG,
@@ -315,9 +308,6 @@ static int iphc_decompress(struct sk_buff *skb, struct net_device *netdev,
IEEE802154_ADDR_LONG, EUI64_ADDR_LEN,
iphc0, iphc1);
-drop:
- kfree_skb(skb);
- return -EINVAL;
}
static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
@@ -370,8 +360,10 @@ static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
goto drop;
ret = iphc_decompress(local_skb, dev, chan);
- if (ret < 0)
+ if (ret < 0) {
+ kfree_skb(local_skb);
goto drop;
+ }
local_skb->protocol = htons(ETH_P_IPV6);
local_skb->pkt_type = PACKET_HOST;
@@ -615,17 +607,13 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
int err = 0;
bdaddr_t addr;
u8 addr_type;
- struct sk_buff *tmpskb;
/* We must take a copy of the skb before we modify/replace the ipv6
* header as the header could be used elsewhere
*/
- tmpskb = skb_unshare(skb, GFP_ATOMIC);
- if (!tmpskb) {
- kfree_skb(skb);
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if (!skb)
return NET_XMIT_DROP;
- }
- skb = tmpskb;
/* Return values from setup_header()
* <0 - error, packet is dropped
@@ -1142,6 +1130,8 @@ static struct l2cap_chan *bt_6lowpan_listen(void)
pchan->state = BT_LISTEN;
pchan->src_type = BDADDR_LE_PUBLIC;
+ atomic_set(&pchan->nesting, L2CAP_NESTING_PARENT);
+
BT_DBG("psm 0x%04x chan %p src type %d", psm_6lowpan, pchan,
pchan->src_type);
@@ -1224,7 +1214,7 @@ static void disconnect_all_peers(void)
l2cap_chan_close(peer->chan, ENOENT);
list_del_rcu(&peer->list);
- call_rcu(&peer->rcu, peer_free);
+ kfree_rcu(peer, rcu);
module_put(THIS_MODULE);
}
diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig
index 600fb29288f4..5e97a8ff850b 100644
--- a/net/bluetooth/Kconfig
+++ b/net/bluetooth/Kconfig
@@ -39,11 +39,10 @@ menuconfig BT
to Bluetooth kernel modules are provided in the BlueZ packages. For
more information, see <http://www.bluez.org/>.
-config BT_6LOWPAN
- tristate "Bluetooth 6LoWPAN support"
- depends on BT && 6LOWPAN
- help
- IPv6 compression over Bluetooth Low Energy.
+config BT_BREDR
+ bool "Bluetooth Classic (BR/EDR) features"
+ depends on BT
+ default y
source "net/bluetooth/rfcomm/Kconfig"
@@ -53,4 +52,15 @@ source "net/bluetooth/cmtp/Kconfig"
source "net/bluetooth/hidp/Kconfig"
+config BT_LE
+ bool "Bluetooth Low Energy (LE) features"
+ depends on BT
+ default y
+
+config BT_6LOWPAN
+ tristate "Bluetooth 6LoWPAN support"
+ depends on BT_LE && 6LOWPAN
+ help
+ IPv6 compression over Bluetooth Low Energy.
+
source "drivers/bluetooth/Kconfig"
diff --git a/net/bluetooth/amp.c b/net/bluetooth/amp.c
index 2640d78f30b8..ee016f039100 100644
--- a/net/bluetooth/amp.c
+++ b/net/bluetooth/amp.c
@@ -134,6 +134,7 @@ struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr,
static int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, u8 *output)
{
struct crypto_shash *tfm;
+ struct shash_desc *shash;
int ret;
if (!ksize)
@@ -148,18 +149,24 @@ static int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, u8 *output)
ret = crypto_shash_setkey(tfm, key, ksize);
if (ret) {
BT_DBG("crypto_ahash_setkey failed: err %d", ret);
- } else {
- char desc[sizeof(struct shash_desc) +
- crypto_shash_descsize(tfm)] CRYPTO_MINALIGN_ATTR;
- struct shash_desc *shash = (struct shash_desc *)desc;
-
- shash->tfm = tfm;
- shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+ goto failed;
+ }
- ret = crypto_shash_digest(shash, plaintext, psize,
- output);
+ shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
+ GFP_KERNEL);
+ if (!shash) {
+ ret = -ENOMEM;
+ goto failed;
}
+ shash->tfm = tfm;
+ shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+
+ ret = crypto_shash_digest(shash, plaintext, psize, output);
+
+ kfree(shash);
+
+failed:
crypto_free_shash(tfm);
return ret;
}
diff --git a/net/bluetooth/bnep/Kconfig b/net/bluetooth/bnep/Kconfig
index 71791fc9f6b1..9b70317c49dc 100644
--- a/net/bluetooth/bnep/Kconfig
+++ b/net/bluetooth/bnep/Kconfig
@@ -1,6 +1,6 @@
config BT_BNEP
tristate "BNEP protocol support"
- depends on BT
+ depends on BT_BREDR
select CRC32
help
BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet
diff --git a/net/bluetooth/cmtp/Kconfig b/net/bluetooth/cmtp/Kconfig
index 94cbf42ce155..939da0fbdd88 100644
--- a/net/bluetooth/cmtp/Kconfig
+++ b/net/bluetooth/cmtp/Kconfig
@@ -1,6 +1,6 @@
config BT_CMTP
tristate "CMTP protocol support"
- depends on BT && ISDN_CAPI
+ depends on BT_BREDR && ISDN_CAPI
help
CMTP (CAPI Message Transport Protocol) is a transport layer
for CAPI messages. CMTP is required for the Bluetooth Common
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 91995f8ab0a0..d786958a1dec 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -200,31 +200,6 @@ static const struct file_operations blacklist_fops = {
.release = single_release,
};
-static int whitelist_show(struct seq_file *f, void *p)
-{
- struct hci_dev *hdev = f->private;
- struct bdaddr_list *b;
-
- hci_dev_lock(hdev);
- list_for_each_entry(b, &hdev->whitelist, list)
- seq_printf(f, "%pMR (type %u)\n", &b->bdaddr, b->bdaddr_type);
- hci_dev_unlock(hdev);
-
- return 0;
-}
-
-static int whitelist_open(struct inode *inode, struct file *file)
-{
- return single_open(file, whitelist_show, inode->i_private);
-}
-
-static const struct file_operations whitelist_fops = {
- .open = whitelist_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
-
static int uuids_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private;
@@ -773,16 +748,15 @@ static const struct file_operations white_list_fops = {
static int identity_resolving_keys_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private;
- struct list_head *p, *n;
+ struct smp_irk *irk;
- hci_dev_lock(hdev);
- list_for_each_safe(p, n, &hdev->identity_resolving_keys) {
- struct smp_irk *irk = list_entry(p, struct smp_irk, list);
+ rcu_read_lock();
+ list_for_each_entry_rcu(irk, &hdev->identity_resolving_keys, list) {
seq_printf(f, "%pMR (type %u) %*phN %pMR\n",
&irk->bdaddr, irk->addr_type,
16, irk->val, &irk->rpa);
}
- hci_dev_unlock(hdev);
+ rcu_read_unlock();
return 0;
}
@@ -803,17 +777,15 @@ static const struct file_operations identity_resolving_keys_fops = {
static int long_term_keys_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private;
- struct list_head *p, *n;
+ struct smp_ltk *ltk;
- hci_dev_lock(hdev);
- list_for_each_safe(p, n, &hdev->long_term_keys) {
- struct smp_ltk *ltk = list_entry(p, struct smp_ltk, list);
+ rcu_read_lock();
+ list_for_each_entry_rcu(ltk, &hdev->long_term_keys, list)
seq_printf(f, "%pMR (type %u) %u 0x%02x %u %.4x %.16llx %*phN\n",
&ltk->bdaddr, ltk->bdaddr_type, ltk->authenticated,
ltk->type, ltk->enc_size, __le16_to_cpu(ltk->ediv),
__le64_to_cpu(ltk->rand), 16, ltk->val);
- }
- hci_dev_unlock(hdev);
+ rcu_read_unlock();
return 0;
}
@@ -1030,10 +1002,13 @@ static int device_list_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private;
struct hci_conn_params *p;
+ struct bdaddr_list *b;
hci_dev_lock(hdev);
+ list_for_each_entry(b, &hdev->whitelist, list)
+ seq_printf(f, "%pMR (type %u)\n", &b->bdaddr, b->bdaddr_type);
list_for_each_entry(p, &hdev->le_conn_params, list) {
- seq_printf(f, "%pMR %u %u\n", &p->addr, p->addr_type,
+ seq_printf(f, "%pMR (type %u) %u\n", &p->addr, p->addr_type,
p->auto_connect);
}
hci_dev_unlock(hdev);
@@ -1147,13 +1122,15 @@ struct sk_buff *__hci_cmd_sync_ev(struct hci_dev *hdev, u16 opcode, u32 plen,
hdev->req_status = HCI_REQ_PEND;
- err = hci_req_run(&req, hci_req_sync_complete);
- if (err < 0)
- return ERR_PTR(err);
-
add_wait_queue(&hdev->req_wait_q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
+ err = hci_req_run(&req, hci_req_sync_complete);
+ if (err < 0) {
+ remove_wait_queue(&hdev->req_wait_q, &wait);
+ return ERR_PTR(err);
+ }
+
schedule_timeout(timeout);
remove_wait_queue(&hdev->req_wait_q, &wait);
@@ -1211,10 +1188,15 @@ static int __hci_req_sync(struct hci_dev *hdev,
func(&req, opt);
+ add_wait_queue(&hdev->req_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
err = hci_req_run(&req, hci_req_sync_complete);
if (err < 0) {
hdev->req_status = 0;
+ remove_wait_queue(&hdev->req_wait_q, &wait);
+
/* ENODATA means the HCI request command queue is empty.
* This can happen when a request with conditionals doesn't
* trigger any commands to be sent. This is normal behavior
@@ -1226,9 +1208,6 @@ static int __hci_req_sync(struct hci_dev *hdev,
return err;
}
- add_wait_queue(&hdev->req_wait_q, &wait);
- set_current_state(TASK_INTERRUPTIBLE);
-
schedule_timeout(timeout);
remove_wait_queue(&hdev->req_wait_q, &wait);
@@ -1811,10 +1790,10 @@ static int __hci_init(struct hci_dev *hdev)
&hdev->manufacturer);
debugfs_create_u8("hci_version", 0444, hdev->debugfs, &hdev->hci_ver);
debugfs_create_u16("hci_revision", 0444, hdev->debugfs, &hdev->hci_rev);
+ debugfs_create_file("device_list", 0444, hdev->debugfs, hdev,
+ &device_list_fops);
debugfs_create_file("blacklist", 0444, hdev->debugfs, hdev,
&blacklist_fops);
- debugfs_create_file("whitelist", 0444, hdev->debugfs, hdev,
- &whitelist_fops);
debugfs_create_file("uuids", 0444, hdev->debugfs, hdev, &uuids_fops);
debugfs_create_file("conn_info_min_age", 0644, hdev->debugfs, hdev,
@@ -1893,8 +1872,6 @@ static int __hci_init(struct hci_dev *hdev)
hdev, &adv_min_interval_fops);
debugfs_create_file("adv_max_interval", 0644, hdev->debugfs,
hdev, &adv_max_interval_fops);
- debugfs_create_file("device_list", 0444, hdev->debugfs, hdev,
- &device_list_fops);
debugfs_create_u16("discov_interleaved_timeout", 0644,
hdev->debugfs,
&hdev->discov_interleaved_timeout);
@@ -2584,6 +2561,11 @@ static int hci_dev_do_close(struct hci_dev *hdev)
if (test_bit(HCI_MGMT, &hdev->dev_flags))
cancel_delayed_work_sync(&hdev->rpa_expired);
+ /* Avoid potential lockdep warnings from the *_flush() calls by
+ * ensuring the workqueue is empty up front.
+ */
+ drain_workqueue(hdev->workqueue);
+
hci_dev_lock(hdev);
hci_inquiry_cache_flush(hdev);
hci_pend_le_actions_clear(hdev);
@@ -2707,6 +2689,11 @@ int hci_dev_reset(__u16 dev)
skb_queue_purge(&hdev->rx_q);
skb_queue_purge(&hdev->cmd_q);
+ /* Avoid potential lockdep warnings from the *_flush() calls by
+ * ensuring the workqueue is empty up front.
+ */
+ drain_workqueue(hdev->workqueue);
+
hci_dev_lock(hdev);
hci_inquiry_cache_flush(hdev);
hci_conn_hash_flush(hdev);
@@ -3126,21 +3113,21 @@ void hci_link_keys_clear(struct hci_dev *hdev)
void hci_smp_ltks_clear(struct hci_dev *hdev)
{
- struct smp_ltk *k, *tmp;
+ struct smp_ltk *k;
- list_for_each_entry_safe(k, tmp, &hdev->long_term_keys, list) {
- list_del(&k->list);
- kfree(k);
+ list_for_each_entry_rcu(k, &hdev->long_term_keys, list) {
+ list_del_rcu(&k->list);
+ kfree_rcu(k, rcu);
}
}
void hci_smp_irks_clear(struct hci_dev *hdev)
{
- struct smp_irk *k, *tmp;
+ struct smp_irk *k;
- list_for_each_entry_safe(k, tmp, &hdev->identity_resolving_keys, list) {
- list_del(&k->list);
- kfree(k);
+ list_for_each_entry_rcu(k, &hdev->identity_resolving_keys, list) {
+ list_del_rcu(&k->list);
+ kfree_rcu(k, rcu);
}
}
@@ -3204,15 +3191,18 @@ struct smp_ltk *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, __le64 rand,
{
struct smp_ltk *k;
- list_for_each_entry(k, &hdev->long_term_keys, list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(k, &hdev->long_term_keys, list) {
if (k->ediv != ediv || k->rand != rand)
continue;
if (ltk_role(k->type) != role)
continue;
+ rcu_read_unlock();
return k;
}
+ rcu_read_unlock();
return NULL;
}
@@ -3222,11 +3212,16 @@ struct smp_ltk *hci_find_ltk_by_addr(struct hci_dev *hdev, bdaddr_t *bdaddr,
{
struct smp_ltk *k;
- list_for_each_entry(k, &hdev->long_term_keys, list)
+ rcu_read_lock();
+ list_for_each_entry_rcu(k, &hdev->long_term_keys, list) {
if (addr_type == k->bdaddr_type &&
bacmp(bdaddr, &k->bdaddr) == 0 &&
- ltk_role(k->type) == role)
+ ltk_role(k->type) == role) {
+ rcu_read_unlock();
return k;
+ }
+ }
+ rcu_read_unlock();
return NULL;
}
@@ -3235,17 +3230,22 @@ struct smp_irk *hci_find_irk_by_rpa(struct hci_dev *hdev, bdaddr_t *rpa)
{
struct smp_irk *irk;
- list_for_each_entry(irk, &hdev->identity_resolving_keys, list) {
- if (!bacmp(&irk->rpa, rpa))
+ rcu_read_lock();
+ list_for_each_entry_rcu(irk, &hdev->identity_resolving_keys, list) {
+ if (!bacmp(&irk->rpa, rpa)) {
+ rcu_read_unlock();
return irk;
+ }
}
- list_for_each_entry(irk, &hdev->identity_resolving_keys, list) {
+ list_for_each_entry_rcu(irk, &hdev->identity_resolving_keys, list) {
if (smp_irk_matches(hdev, irk->val, rpa)) {
bacpy(&irk->rpa, rpa);
+ rcu_read_unlock();
return irk;
}
}
+ rcu_read_unlock();
return NULL;
}
@@ -3259,11 +3259,15 @@ struct smp_irk *hci_find_irk_by_addr(struct hci_dev *hdev, bdaddr_t *bdaddr,
if (addr_type == ADDR_LE_DEV_RANDOM && (bdaddr->b[5] & 0xc0) != 0xc0)
return NULL;
- list_for_each_entry(irk, &hdev->identity_resolving_keys, list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(irk, &hdev->identity_resolving_keys, list) {
if (addr_type == irk->addr_type &&
- bacmp(bdaddr, &irk->bdaddr) == 0)
+ bacmp(bdaddr, &irk->bdaddr) == 0) {
+ rcu_read_unlock();
return irk;
+ }
}
+ rcu_read_unlock();
return NULL;
}
@@ -3329,7 +3333,7 @@ struct smp_ltk *hci_add_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr,
key = kzalloc(sizeof(*key), GFP_KERNEL);
if (!key)
return NULL;
- list_add(&key->list, &hdev->long_term_keys);
+ list_add_rcu(&key->list, &hdev->long_term_keys);
}
bacpy(&key->bdaddr, bdaddr);
@@ -3358,7 +3362,7 @@ struct smp_irk *hci_add_irk(struct hci_dev *hdev, bdaddr_t *bdaddr,
bacpy(&irk->bdaddr, bdaddr);
irk->addr_type = addr_type;
- list_add(&irk->list, &hdev->identity_resolving_keys);
+ list_add_rcu(&irk->list, &hdev->identity_resolving_keys);
}
memcpy(irk->val, val, 16);
@@ -3385,17 +3389,17 @@ int hci_remove_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr)
int hci_remove_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 bdaddr_type)
{
- struct smp_ltk *k, *tmp;
+ struct smp_ltk *k;
int removed = 0;
- list_for_each_entry_safe(k, tmp, &hdev->long_term_keys, list) {
+ list_for_each_entry_rcu(k, &hdev->long_term_keys, list) {
if (bacmp(bdaddr, &k->bdaddr) || k->bdaddr_type != bdaddr_type)
continue;
BT_DBG("%s removing %pMR", hdev->name, bdaddr);
- list_del(&k->list);
- kfree(k);
+ list_del_rcu(&k->list);
+ kfree_rcu(k, rcu);
removed++;
}
@@ -3404,16 +3408,16 @@ int hci_remove_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 bdaddr_type)
void hci_remove_irk(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 addr_type)
{
- struct smp_irk *k, *tmp;
+ struct smp_irk *k;
- list_for_each_entry_safe(k, tmp, &hdev->identity_resolving_keys, list) {
+ list_for_each_entry_rcu(k, &hdev->identity_resolving_keys, list) {
if (bacmp(bdaddr, &k->bdaddr) || k->addr_type != addr_type)
continue;
BT_DBG("%s removing %pMR", hdev->name, bdaddr);
- list_del(&k->list);
- kfree(k);
+ list_del_rcu(&k->list);
+ kfree_rcu(k, rcu);
}
}
@@ -3475,7 +3479,7 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev)
}
int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr,
- u8 *hash, u8 *randomizer)
+ u8 *hash, u8 *rand)
{
struct oob_data *data;
@@ -3490,10 +3494,10 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr,
}
memcpy(data->hash192, hash, sizeof(data->hash192));
- memcpy(data->randomizer192, randomizer, sizeof(data->randomizer192));
+ memcpy(data->rand192, rand, sizeof(data->rand192));
memset(data->hash256, 0, sizeof(data->hash256));
- memset(data->randomizer256, 0, sizeof(data->randomizer256));
+ memset(data->rand256, 0, sizeof(data->rand256));
BT_DBG("%s for %pMR", hdev->name, bdaddr);
@@ -3501,8 +3505,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr,
}
int hci_add_remote_oob_ext_data(struct hci_dev *hdev, bdaddr_t *bdaddr,
- u8 *hash192, u8 *randomizer192,
- u8 *hash256, u8 *randomizer256)
+ u8 *hash192, u8 *rand192,
+ u8 *hash256, u8 *rand256)
{
struct oob_data *data;
@@ -3517,10 +3521,10 @@ int hci_add_remote_oob_ext_data(struct hci_dev *hdev, bdaddr_t *bdaddr,
}
memcpy(data->hash192, hash192, sizeof(data->hash192));
- memcpy(data->randomizer192, randomizer192, sizeof(data->randomizer192));
+ memcpy(data->rand192, rand192, sizeof(data->rand192));
memcpy(data->hash256, hash256, sizeof(data->hash256));
- memcpy(data->randomizer256, randomizer256, sizeof(data->randomizer256));
+ memcpy(data->rand256, rand256, sizeof(data->rand256));
BT_DBG("%s for %pMR", hdev->name, bdaddr);
@@ -4244,6 +4248,24 @@ int hci_resume_dev(struct hci_dev *hdev)
}
EXPORT_SYMBOL(hci_resume_dev);
+/* Reset HCI device */
+int hci_reset_dev(struct hci_dev *hdev)
+{
+ const u8 hw_err[] = { HCI_EV_HARDWARE_ERROR, 0x01, 0x00 };
+ struct sk_buff *skb;
+
+ skb = bt_skb_alloc(3, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ bt_cb(skb)->pkt_type = HCI_EVENT_PKT;
+ memcpy(skb_put(skb, 3), hw_err, 3);
+
+ /* Send Hardware Error to upper stack */
+ return hci_recv_frame(hdev, skb);
+}
+EXPORT_SYMBOL(hci_reset_dev);
+
/* Receive frame from HCI drivers */
int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index aa152140c3e2..844f7d1ff1cd 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -189,6 +189,9 @@ static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb)
clear_bit(HCI_RESET, &hdev->flags);
+ if (status)
+ return;
+
/* Reset all non-persistent flags */
hdev->dev_flags &= ~HCI_PERSISTENT_MASK;
@@ -991,8 +994,8 @@ static void hci_cc_read_local_oob_data(struct hci_dev *hdev,
BT_DBG("%s status 0x%2.2x", hdev->name, rp->status);
hci_dev_lock(hdev);
- mgmt_read_local_oob_data_complete(hdev, rp->hash, rp->randomizer,
- NULL, NULL, rp->status);
+ mgmt_read_local_oob_data_complete(hdev, rp->hash, rp->rand, NULL, NULL,
+ rp->status);
hci_dev_unlock(hdev);
}
@@ -1004,8 +1007,8 @@ static void hci_cc_read_local_oob_ext_data(struct hci_dev *hdev,
BT_DBG("%s status 0x%2.2x", hdev->name, rp->status);
hci_dev_lock(hdev);
- mgmt_read_local_oob_data_complete(hdev, rp->hash192, rp->randomizer192,
- rp->hash256, rp->randomizer256,
+ mgmt_read_local_oob_data_complete(hdev, rp->hash192, rp->rand192,
+ rp->hash256, rp->rand256,
rp->status);
hci_dev_unlock(hdev);
}
@@ -1578,7 +1581,14 @@ static void hci_check_pending_name(struct hci_dev *hdev, struct hci_conn *conn,
struct discovery_state *discov = &hdev->discovery;
struct inquiry_entry *e;
- if (conn && !test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags))
+ /* Update the mgmt connected state if necessary. Be careful with
+ * conn objects that exist but are not (yet) connected however.
+ * Only those in BT_CONFIG or BT_CONNECTED states can be
+ * considered connected.
+ */
+ if (conn &&
+ (conn->state == BT_CONFIG || conn->state == BT_CONNECTED) &&
+ !test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags))
mgmt_device_connected(hdev, conn, 0, name, name_len);
if (discov->state == DISCOVERY_STOPPED)
@@ -1944,6 +1954,29 @@ unlock:
hci_dev_unlock(hdev);
}
+static void hci_cs_switch_role(struct hci_dev *hdev, u8 status)
+{
+ struct hci_cp_switch_role *cp;
+ struct hci_conn *conn;
+
+ BT_DBG("%s status 0x%2.2x", hdev->name, status);
+
+ if (!status)
+ return;
+
+ cp = hci_sent_cmd_data(hdev, HCI_OP_SWITCH_ROLE);
+ if (!cp)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr);
+ if (conn)
+ clear_bit(HCI_CONN_RSWITCH_PEND, &conn->flags);
+
+ hci_dev_unlock(hdev);
+}
+
static void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
__u8 status = *((__u8 *) skb->data);
@@ -2847,6 +2880,10 @@ static void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb)
hci_cs_create_conn(hdev, ev->status);
break;
+ case HCI_OP_DISCONNECT:
+ hci_cs_disconnect(hdev, ev->status);
+ break;
+
case HCI_OP_ADD_SCO:
hci_cs_add_sco(hdev, ev->status);
break;
@@ -2875,24 +2912,24 @@ static void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb)
hci_cs_setup_sync_conn(hdev, ev->status);
break;
- case HCI_OP_SNIFF_MODE:
- hci_cs_sniff_mode(hdev, ev->status);
+ case HCI_OP_CREATE_PHY_LINK:
+ hci_cs_create_phylink(hdev, ev->status);
break;
- case HCI_OP_EXIT_SNIFF_MODE:
- hci_cs_exit_sniff_mode(hdev, ev->status);
+ case HCI_OP_ACCEPT_PHY_LINK:
+ hci_cs_accept_phylink(hdev, ev->status);
break;
- case HCI_OP_DISCONNECT:
- hci_cs_disconnect(hdev, ev->status);
+ case HCI_OP_SNIFF_MODE:
+ hci_cs_sniff_mode(hdev, ev->status);
break;
- case HCI_OP_CREATE_PHY_LINK:
- hci_cs_create_phylink(hdev, ev->status);
+ case HCI_OP_EXIT_SNIFF_MODE:
+ hci_cs_exit_sniff_mode(hdev, ev->status);
break;
- case HCI_OP_ACCEPT_PHY_LINK:
- hci_cs_accept_phylink(hdev, ev->status);
+ case HCI_OP_SWITCH_ROLE:
+ hci_cs_switch_role(hdev, ev->status);
break;
case HCI_OP_LE_CREATE_CONN:
@@ -2922,6 +2959,13 @@ static void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb)
}
}
+static void hci_hardware_error_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct hci_ev_hardware_error *ev = (void *) skb->data;
+
+ BT_ERR("%s hardware error 0x%2.2x", hdev->name, ev->code);
+}
+
static void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
struct hci_ev_role_change *ev = (void *) skb->data;
@@ -3952,11 +3996,9 @@ static void hci_remote_oob_data_request_evt(struct hci_dev *hdev,
bacpy(&cp.bdaddr, &ev->bdaddr);
memcpy(cp.hash192, data->hash192, sizeof(cp.hash192));
- memcpy(cp.randomizer192, data->randomizer192,
- sizeof(cp.randomizer192));
+ memcpy(cp.rand192, data->rand192, sizeof(cp.rand192));
memcpy(cp.hash256, data->hash256, sizeof(cp.hash256));
- memcpy(cp.randomizer256, data->randomizer256,
- sizeof(cp.randomizer256));
+ memcpy(cp.rand256, data->rand256, sizeof(cp.rand256));
hci_send_cmd(hdev, HCI_OP_REMOTE_OOB_EXT_DATA_REPLY,
sizeof(cp), &cp);
@@ -3965,8 +4007,7 @@ static void hci_remote_oob_data_request_evt(struct hci_dev *hdev,
bacpy(&cp.bdaddr, &ev->bdaddr);
memcpy(cp.hash, data->hash192, sizeof(cp.hash));
- memcpy(cp.randomizer, data->randomizer192,
- sizeof(cp.randomizer));
+ memcpy(cp.rand, data->rand192, sizeof(cp.rand));
hci_send_cmd(hdev, HCI_OP_REMOTE_OOB_DATA_REPLY,
sizeof(cp), &cp);
@@ -4534,8 +4575,8 @@ static void hci_le_ltk_request_evt(struct hci_dev *hdev, struct sk_buff *skb)
*/
if (ltk->type == SMP_STK) {
set_bit(HCI_CONN_STK_ENCRYPT, &conn->flags);
- list_del(&ltk->list);
- kfree(ltk);
+ list_del_rcu(&ltk->list);
+ kfree_rcu(ltk, rcu);
} else {
clear_bit(HCI_CONN_STK_ENCRYPT, &conn->flags);
}
@@ -4743,6 +4784,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
hci_cmd_status_evt(hdev, skb);
break;
+ case HCI_EV_HARDWARE_ERROR:
+ hci_hardware_error_evt(hdev, skb);
+ break;
+
case HCI_EV_ROLE_CHANGE:
hci_role_change_evt(hdev, skb);
break;
diff --git a/net/bluetooth/hidp/Kconfig b/net/bluetooth/hidp/Kconfig
index 9332bc7aa851..bc8610b24077 100644
--- a/net/bluetooth/hidp/Kconfig
+++ b/net/bluetooth/hidp/Kconfig
@@ -1,6 +1,6 @@
config BT_HIDP
tristate "HIDP protocol support"
- depends on BT && INPUT
+ depends on BT_BREDR && INPUT
select HID
help
HIDP (Human Interface Device Protocol) is a transport layer
diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
index 1b7d605706aa..cc25d0b74b36 100644
--- a/net/bluetooth/hidp/core.c
+++ b/net/bluetooth/hidp/core.c
@@ -736,14 +736,10 @@ static int hidp_setup_hid(struct hidp_session *session,
struct hid_device *hid;
int err;
- session->rd_data = kzalloc(req->rd_size, GFP_KERNEL);
- if (!session->rd_data)
- return -ENOMEM;
+ session->rd_data = memdup_user(req->rd_data, req->rd_size);
+ if (IS_ERR(session->rd_data))
+ return PTR_ERR(session->rd_data);
- if (copy_from_user(session->rd_data, req->rd_data, req->rd_size)) {
- err = -EFAULT;
- goto fault;
- }
session->rd_size = req->rd_size;
hid = hid_allocate_device();
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index fc15174c612c..8e1273173020 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -424,6 +424,9 @@ struct l2cap_chan *l2cap_chan_create(void)
mutex_init(&chan->lock);
+ /* Set default lock nesting level */
+ atomic_set(&chan->nesting, L2CAP_NESTING_NORMAL);
+
write_lock(&chan_list_lock);
list_add(&chan->global_l, &chan_list);
write_unlock(&chan_list_lock);
@@ -567,7 +570,8 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
__clear_chan_timer(chan);
- BT_DBG("chan %p, conn %p, err %d", chan, conn, err);
+ BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err,
+ state_to_string(chan->state));
chan->ops->teardown(chan, err);
@@ -5215,9 +5219,10 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
u8 *data)
{
struct l2cap_le_conn_rsp *rsp = (struct l2cap_le_conn_rsp *) data;
+ struct hci_conn *hcon = conn->hcon;
u16 dcid, mtu, mps, credits, result;
struct l2cap_chan *chan;
- int err;
+ int err, sec_level;
if (cmd_len < sizeof(*rsp))
return -EPROTO;
@@ -5256,6 +5261,26 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
l2cap_chan_ready(chan);
break;
+ case L2CAP_CR_AUTHENTICATION:
+ case L2CAP_CR_ENCRYPTION:
+ /* If we already have MITM protection we can't do
+ * anything.
+ */
+ if (hcon->sec_level > BT_SECURITY_MEDIUM) {
+ l2cap_chan_del(chan, ECONNREFUSED);
+ break;
+ }
+
+ sec_level = hcon->sec_level + 1;
+ if (chan->sec_level < sec_level)
+ chan->sec_level = sec_level;
+
+ /* We'll need to send a new Connect Request */
+ clear_bit(FLAG_LE_CONN_REQ_SENT, &chan->flags);
+
+ smp_conn_security(hcon, chan->sec_level);
+ break;
+
default:
l2cap_chan_del(chan, ECONNREFUSED);
break;
@@ -5388,7 +5413,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
mutex_lock(&conn->chan_lock);
l2cap_chan_lock(pchan);
- if (!smp_sufficient_security(conn->hcon, pchan->sec_level)) {
+ if (!smp_sufficient_security(conn->hcon, pchan->sec_level,
+ SMP_ALLOW_STK)) {
result = L2CAP_CR_AUTHENTICATION;
chan = NULL;
goto response_unlock;
@@ -7329,7 +7355,8 @@ int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
l2cap_start_connection(chan);
else
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
- } else if (chan->state == BT_CONNECT2) {
+ } else if (chan->state == BT_CONNECT2 &&
+ chan->mode != L2CAP_MODE_LE_FLOWCTL) {
struct l2cap_conn_rsp rsp;
__u16 res, stat;
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 31f106e61ca2..b0efb7202957 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -285,6 +285,12 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
+ /* Listening channels need to use nested locking in order not to
+ * cause lockdep warnings when the created child channels end up
+ * being locked in the same thread as the parent channel.
+ */
+ atomic_set(&chan->nesting, L2CAP_NESTING_PARENT);
+
chan->state = BT_LISTEN;
sk->sk_state = BT_LISTEN;
@@ -301,7 +307,7 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock,
long timeo;
int err = 0;
- lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+ lock_sock_nested(sk, L2CAP_NESTING_PARENT);
timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
@@ -333,7 +339,7 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock,
release_sock(sk);
timeo = schedule_timeout(timeo);
- lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
+ lock_sock_nested(sk, L2CAP_NESTING_PARENT);
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(sk_sleep(sk), &wait);
@@ -1096,6 +1102,8 @@ static int l2cap_sock_shutdown(struct socket *sock, int how)
chan = l2cap_pi(sk)->chan;
conn = chan->conn;
+ BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
+
if (conn)
mutex_lock(&conn->chan_lock);
@@ -1153,12 +1161,16 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
{
struct sock *sk;
- BT_DBG("parent %p", parent);
+ BT_DBG("parent %p state %s", parent,
+ state_to_string(parent->sk_state));
/* Close not yet accepted channels */
while ((sk = bt_accept_dequeue(parent, NULL))) {
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+ BT_DBG("child chan %p state %s", chan,
+ state_to_string(chan->state));
+
l2cap_chan_lock(chan);
__clear_chan_timer(chan);
l2cap_chan_close(chan, ECONNRESET);
@@ -1246,7 +1258,16 @@ static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
struct sock *sk = chan->data;
struct sock *parent;
- lock_sock(sk);
+ BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
+
+ /* This callback can be called both for server (BT_LISTEN)
+ * sockets as well as "normal" ones. To avoid lockdep warnings
+ * with child socket locking (through l2cap_sock_cleanup_listen)
+ * we need separation into separate nesting levels. The simplest
+ * way to accomplish this is to inherit the nesting level used
+ * for the channel.
+ */
+ lock_sock_nested(sk, atomic_read(&chan->nesting));
parent = bt_sk(sk)->parent;
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 9c4daf715cf8..cbeef5f62f3b 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -3589,8 +3589,16 @@ static int add_remote_oob_data(struct sock *sk, struct hci_dev *hdev,
struct mgmt_cp_add_remote_oob_data *cp = data;
u8 status;
+ if (cp->addr.type != BDADDR_BREDR) {
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_ADD_REMOTE_OOB_DATA,
+ MGMT_STATUS_INVALID_PARAMS,
+ &cp->addr, sizeof(cp->addr));
+ goto unlock;
+ }
+
err = hci_add_remote_oob_data(hdev, &cp->addr.bdaddr,
- cp->hash, cp->randomizer);
+ cp->hash, cp->rand);
if (err < 0)
status = MGMT_STATUS_FAILED;
else
@@ -3602,11 +3610,17 @@ static int add_remote_oob_data(struct sock *sk, struct hci_dev *hdev,
struct mgmt_cp_add_remote_oob_ext_data *cp = data;
u8 status;
+ if (cp->addr.type != BDADDR_BREDR) {
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_ADD_REMOTE_OOB_DATA,
+ MGMT_STATUS_INVALID_PARAMS,
+ &cp->addr, sizeof(cp->addr));
+ goto unlock;
+ }
+
err = hci_add_remote_oob_ext_data(hdev, &cp->addr.bdaddr,
- cp->hash192,
- cp->randomizer192,
- cp->hash256,
- cp->randomizer256);
+ cp->hash192, cp->rand192,
+ cp->hash256, cp->rand256);
if (err < 0)
status = MGMT_STATUS_FAILED;
else
@@ -3620,6 +3634,7 @@ static int add_remote_oob_data(struct sock *sk, struct hci_dev *hdev,
MGMT_STATUS_INVALID_PARAMS);
}
+unlock:
hci_dev_unlock(hdev);
return err;
}
@@ -3633,14 +3648,26 @@ static int remove_remote_oob_data(struct sock *sk, struct hci_dev *hdev,
BT_DBG("%s", hdev->name);
+ if (cp->addr.type != BDADDR_BREDR)
+ return cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_REMOTE_OOB_DATA,
+ MGMT_STATUS_INVALID_PARAMS,
+ &cp->addr, sizeof(cp->addr));
+
hci_dev_lock(hdev);
+ if (!bacmp(&cp->addr.bdaddr, BDADDR_ANY)) {
+ hci_remote_oob_data_clear(hdev);
+ status = MGMT_STATUS_SUCCESS;
+ goto done;
+ }
+
err = hci_remove_remote_oob_data(hdev, &cp->addr.bdaddr);
if (err < 0)
status = MGMT_STATUS_INVALID_PARAMS;
else
status = MGMT_STATUS_SUCCESS;
+done:
err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_REMOTE_OOB_DATA,
status, &cp->addr, sizeof(cp->addr));
@@ -3727,20 +3754,23 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
hci_dev_lock(hdev);
if (!hdev_is_powered(hdev)) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_NOT_POWERED);
+ err = cmd_complete(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_NOT_POWERED,
+ &cp->type, sizeof(cp->type));
goto failed;
}
if (test_bit(HCI_PERIODIC_INQ, &hdev->dev_flags)) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_BUSY);
+ err = cmd_complete(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_BUSY, &cp->type,
+ sizeof(cp->type));
goto failed;
}
if (hdev->discovery.state != DISCOVERY_STOPPED) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_BUSY);
+ err = cmd_complete(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_BUSY, &cp->type,
+ sizeof(cp->type));
goto failed;
}
@@ -3758,15 +3788,18 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
case DISCOV_TYPE_BREDR:
status = mgmt_bredr_support(hdev);
if (status) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- status);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY, status,
+ &cp->type, sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
if (test_bit(HCI_INQUIRY, &hdev->flags)) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_BUSY);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_BUSY, &cp->type,
+ sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
@@ -3783,16 +3816,19 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
case DISCOV_TYPE_INTERLEAVED:
status = mgmt_le_support(hdev);
if (status) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- status);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY, status,
+ &cp->type, sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED &&
!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_NOT_SUPPORTED);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_NOT_SUPPORTED,
+ &cp->type, sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
@@ -3804,9 +3840,11 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
*/
if (hci_conn_hash_lookup_state(hdev, LE_LINK,
BT_CONNECT)) {
- err = cmd_status(sk, hdev->id,
- MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_REJECTED);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_REJECTED,
+ &cp->type,
+ sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
@@ -3829,8 +3867,10 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
*/
err = hci_update_random_address(&req, true, &own_addr_type);
if (err < 0) {
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_FAILED);
+ err = cmd_complete(sk, hdev->id,
+ MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_FAILED,
+ &cp->type, sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
@@ -3850,8 +3890,9 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
break;
default:
- err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
- MGMT_STATUS_INVALID_PARAMS);
+ err = cmd_complete(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+ MGMT_STATUS_INVALID_PARAMS,
+ &cp->type, sizeof(cp->type));
mgmt_pending_remove(cmd);
goto failed;
}
@@ -6728,8 +6769,8 @@ void mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status)
}
void mgmt_read_local_oob_data_complete(struct hci_dev *hdev, u8 *hash192,
- u8 *randomizer192, u8 *hash256,
- u8 *randomizer256, u8 status)
+ u8 *rand192, u8 *hash256, u8 *rand256,
+ u8 status)
{
struct pending_cmd *cmd;
@@ -6744,16 +6785,14 @@ void mgmt_read_local_oob_data_complete(struct hci_dev *hdev, u8 *hash192,
mgmt_status(status));
} else {
if (test_bit(HCI_SC_ENABLED, &hdev->dev_flags) &&
- hash256 && randomizer256) {
+ hash256 && rand256) {
struct mgmt_rp_read_local_oob_ext_data rp;
memcpy(rp.hash192, hash192, sizeof(rp.hash192));
- memcpy(rp.randomizer192, randomizer192,
- sizeof(rp.randomizer192));
+ memcpy(rp.rand192, rand192, sizeof(rp.rand192));
memcpy(rp.hash256, hash256, sizeof(rp.hash256));
- memcpy(rp.randomizer256, randomizer256,
- sizeof(rp.randomizer256));
+ memcpy(rp.rand256, rand256, sizeof(rp.rand256));
cmd_complete(cmd->sk, hdev->id,
MGMT_OP_READ_LOCAL_OOB_DATA, 0,
@@ -6762,8 +6801,7 @@ void mgmt_read_local_oob_data_complete(struct hci_dev *hdev, u8 *hash192,
struct mgmt_rp_read_local_oob_data rp;
memcpy(rp.hash, hash192, sizeof(rp.hash));
- memcpy(rp.randomizer, randomizer192,
- sizeof(rp.randomizer));
+ memcpy(rp.rand, rand192, sizeof(rp.rand));
cmd_complete(cmd->sk, hdev->id,
MGMT_OP_READ_LOCAL_OOB_DATA, 0,
diff --git a/net/bluetooth/rfcomm/Kconfig b/net/bluetooth/rfcomm/Kconfig
index 18d352ea2bc7..335df7515220 100644
--- a/net/bluetooth/rfcomm/Kconfig
+++ b/net/bluetooth/rfcomm/Kconfig
@@ -1,6 +1,6 @@
config BT_RFCOMM
tristate "RFCOMM protocol support"
- depends on BT
+ depends on BT_BREDR
help
RFCOMM provides connection oriented stream transport. RFCOMM
support is required for Dialup Networking, OBEX and other Bluetooth
diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
index bce9c3d39324..64e20dde4837 100644
--- a/net/bluetooth/rfcomm/core.c
+++ b/net/bluetooth/rfcomm/core.c
@@ -81,6 +81,8 @@ static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s);
#define __test_cr(b) (!!(b & 0x02))
#define __test_pf(b) (!!(b & 0x10))
+#define __session_dir(s) ((s)->initiator ? 0x00 : 0x01)
+
#define __addr(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01)
#define __ctrl(type, pf) (((type & 0xef) | (pf << 4)))
#define __dlci(dir, chn) (((chn & 0x1f) << 1) | dir)
@@ -388,7 +390,7 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst,
return err;
}
- dlci = __dlci(!s->initiator, channel);
+ dlci = __dlci(__session_dir(s), channel);
/* Check if DLCI already exists */
if (rfcomm_dlc_get(s, dlci))
@@ -543,7 +545,7 @@ struct rfcomm_dlc *rfcomm_dlc_exists(bdaddr_t *src, bdaddr_t *dst, u8 channel)
rfcomm_lock();
s = rfcomm_session_get(src, dst);
if (s) {
- dlci = __dlci(!s->initiator, channel);
+ dlci = __dlci(__session_dir(s), channel);
dlc = rfcomm_dlc_get(s, dlci);
}
rfcomm_unlock();
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 3ebf65b50881..069b76e03b57 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -383,18 +383,18 @@ static void smp_chan_destroy(struct l2cap_conn *conn)
/* If pairing failed clean up any keys we might have */
if (!complete) {
if (smp->ltk) {
- list_del(&smp->ltk->list);
- kfree(smp->ltk);
+ list_del_rcu(&smp->ltk->list);
+ kfree_rcu(smp->ltk, rcu);
}
if (smp->slave_ltk) {
- list_del(&smp->slave_ltk->list);
- kfree(smp->slave_ltk);
+ list_del_rcu(&smp->slave_ltk->list);
+ kfree_rcu(smp->slave_ltk, rcu);
}
if (smp->remote_irk) {
- list_del(&smp->remote_irk->list);
- kfree(smp->remote_irk);
+ list_del_rcu(&smp->remote_irk->list);
+ kfree_rcu(smp->remote_irk, rcu);
}
}
@@ -514,8 +514,6 @@ static int tk_request(struct l2cap_conn *conn, u8 remote_oob, u8 auth,
set_bit(SMP_FLAG_TK_VALID, &smp->flags);
}
- hci_dev_lock(hcon->hdev);
-
if (method == REQ_PASSKEY)
ret = mgmt_user_passkey_request(hcon->hdev, &hcon->dst,
hcon->type, hcon->dst_type);
@@ -528,8 +526,6 @@ static int tk_request(struct l2cap_conn *conn, u8 remote_oob, u8 auth,
hcon->type, hcon->dst_type,
passkey, 0);
- hci_dev_unlock(hcon->hdev);
-
return ret;
}
@@ -659,8 +655,8 @@ static void smp_notify_keys(struct l2cap_conn *conn)
* just remove it.
*/
if (!bacmp(&smp->remote_irk->rpa, BDADDR_ANY)) {
- list_del(&smp->remote_irk->list);
- kfree(smp->remote_irk);
+ list_del_rcu(&smp->remote_irk->list);
+ kfree_rcu(smp->remote_irk, rcu);
smp->remote_irk = NULL;
}
}
@@ -1126,18 +1122,20 @@ static bool smp_ltk_encrypt(struct l2cap_conn *conn, u8 sec_level)
return true;
}
-bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level)
+bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level,
+ enum smp_key_pref key_pref)
{
if (sec_level == BT_SECURITY_LOW)
return true;
- /* If we're encrypted with an STK always claim insufficient
- * security. This way we allow the connection to be re-encrypted
- * with an LTK, even if the LTK provides the same level of
- * security. Only exception is if we don't have an LTK (e.g.
- * because of key distribution bits).
+ /* If we're encrypted with an STK but the caller prefers using
+ * LTK claim insufficient security. This way we allow the
+ * connection to be re-encrypted with an LTK, even if the LTK
+ * provides the same level of security. Only exception is if we
+ * don't have an LTK (e.g. because of key distribution bits).
*/
- if (test_bit(HCI_CONN_STK_ENCRYPT, &hcon->flags) &&
+ if (key_pref == SMP_USE_LTK &&
+ test_bit(HCI_CONN_STK_ENCRYPT, &hcon->flags) &&
hci_find_ltk_by_addr(hcon->hdev, &hcon->dst, hcon->dst_type,
hcon->role))
return false;
@@ -1171,7 +1169,7 @@ static u8 smp_cmd_security_req(struct l2cap_conn *conn, struct sk_buff *skb)
else
sec_level = authreq_to_seclevel(auth);
- if (smp_sufficient_security(hcon, sec_level))
+ if (smp_sufficient_security(hcon, sec_level, SMP_USE_LTK))
return 0;
if (sec_level > hcon->pending_sec_level)
@@ -1221,7 +1219,7 @@ int smp_conn_security(struct hci_conn *hcon, __u8 sec_level)
if (!test_bit(HCI_LE_ENABLED, &hcon->hdev->dev_flags))
return 1;
- if (smp_sufficient_security(hcon, sec_level))
+ if (smp_sufficient_security(hcon, sec_level, SMP_USE_LTK))
return 1;
if (sec_level > hcon->pending_sec_level)
@@ -1323,7 +1321,6 @@ static int smp_cmd_master_ident(struct l2cap_conn *conn, struct sk_buff *skb)
skb_pull(skb, sizeof(*rp));
- hci_dev_lock(hdev);
authenticated = (hcon->sec_level == BT_SECURITY_HIGH);
ltk = hci_add_ltk(hdev, &hcon->dst, hcon->dst_type, SMP_LTK,
authenticated, smp->tk, smp->enc_key_size,
@@ -1331,7 +1328,6 @@ static int smp_cmd_master_ident(struct l2cap_conn *conn, struct sk_buff *skb)
smp->ltk = ltk;
if (!(smp->remote_key_dist & KEY_DIST_MASK))
smp_distribute_keys(smp);
- hci_dev_unlock(hdev);
return 0;
}
@@ -1378,8 +1374,6 @@ static int smp_cmd_ident_addr_info(struct l2cap_conn *conn,
skb_pull(skb, sizeof(*info));
- hci_dev_lock(hcon->hdev);
-
/* Strictly speaking the Core Specification (4.1) allows sending
* an empty address which would force us to rely on just the IRK
* as "identity information". However, since such
@@ -1407,8 +1401,6 @@ distribute:
if (!(smp->remote_key_dist & KEY_DIST_MASK))
smp_distribute_keys(smp);
- hci_dev_unlock(hcon->hdev);
-
return 0;
}
@@ -1417,7 +1409,6 @@ static int smp_cmd_sign_info(struct l2cap_conn *conn, struct sk_buff *skb)
struct smp_cmd_sign_info *rp = (void *) skb->data;
struct l2cap_chan *chan = conn->smp;
struct smp_chan *smp = chan->data;
- struct hci_dev *hdev = conn->hcon->hdev;
struct smp_csrk *csrk;
BT_DBG("conn %p", conn);
@@ -1430,7 +1421,6 @@ static int smp_cmd_sign_info(struct l2cap_conn *conn, struct sk_buff *skb)
skb_pull(skb, sizeof(*rp));
- hci_dev_lock(hdev);
csrk = kzalloc(sizeof(*csrk), GFP_KERNEL);
if (csrk) {
csrk->master = 0x01;
@@ -1438,7 +1428,6 @@ static int smp_cmd_sign_info(struct l2cap_conn *conn, struct sk_buff *skb)
}
smp->csrk = csrk;
smp_distribute_keys(smp);
- hci_dev_unlock(hdev);
return 0;
}
@@ -1662,6 +1651,13 @@ static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
chan->omtu = pchan->omtu;
chan->mode = pchan->mode;
+ /* Other L2CAP channels may request SMP routines in order to
+ * change the security level. This means that the SMP channel
+ * lock must be considered in its own category to avoid lockdep
+ * warnings.
+ */
+ atomic_set(&chan->nesting, L2CAP_NESTING_SMP);
+
BT_DBG("created chan %p", chan);
return chan;
@@ -1693,7 +1689,7 @@ int smp_register(struct hci_dev *hdev)
BT_DBG("%s", hdev->name);
- tfm_aes = crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC);
+ tfm_aes = crypto_alloc_blkcipher("ecb(aes)", 0, 0);
if (IS_ERR(tfm_aes)) {
int err = PTR_ERR(tfm_aes);
BT_ERR("Unable to create crypto context");
@@ -1719,6 +1715,9 @@ int smp_register(struct hci_dev *hdev)
chan->imtu = L2CAP_DEFAULT_MTU;
chan->ops = &smp_root_chan_ops;
+ /* Set correct nesting level for a parent/listening channel */
+ atomic_set(&chan->nesting, L2CAP_NESTING_PARENT);
+
hdev->smp_data = chan;
return 0;
diff --git a/net/bluetooth/smp.h b/net/bluetooth/smp.h
index 86a683a8b491..f76083b85005 100644
--- a/net/bluetooth/smp.h
+++ b/net/bluetooth/smp.h
@@ -133,8 +133,15 @@ static inline u8 smp_ltk_sec_level(struct smp_ltk *key)
return BT_SECURITY_MEDIUM;
}
+/* Key preferences for smp_sufficient security */
+enum smp_key_pref {
+ SMP_ALLOW_STK,
+ SMP_USE_LTK,
+};
+
/* SMP Commands */
-bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level);
+bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level,
+ enum smp_key_pref key_pref);
int smp_conn_security(struct hci_conn *hcon, __u8 sec_level);
int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey);
diff --git a/net/ieee802154/6lowpan_rtnl.c b/net/ieee802154/6lowpan_rtnl.c
index 519a65452d90..290e14f2e92e 100644
--- a/net/ieee802154/6lowpan_rtnl.c
+++ b/net/ieee802154/6lowpan_rtnl.c
@@ -176,13 +176,13 @@ iphc_decompress(struct sk_buff *skb, const struct ieee802154_hdr *hdr)
raw_dump_table(__func__, "raw skb data dump", skb->data, skb->len);
/* at least two bytes will be used for the encoding */
if (skb->len < 2)
- goto drop;
+ return -EINVAL;
if (lowpan_fetch_skb_u8(skb, &iphc0))
- goto drop;
+ return -EINVAL;
if (lowpan_fetch_skb_u8(skb, &iphc1))
- goto drop;
+ return -EINVAL;
ieee802154_addr_to_sa(&sa, &hdr->source);
ieee802154_addr_to_sa(&da, &hdr->dest);
@@ -200,23 +200,6 @@ iphc_decompress(struct sk_buff *skb, const struct ieee802154_hdr *hdr)
return lowpan_header_decompress(skb, skb->dev, sap, sa.addr_type,
IEEE802154_ADDR_LEN, dap, da.addr_type,
IEEE802154_ADDR_LEN, iphc0, iphc1);
-
-drop:
- kfree_skb(skb);
- return -EINVAL;
-}
-
-static int lowpan_set_address(struct net_device *dev, void *p)
-{
- struct sockaddr *sa = p;
-
- if (netif_running(dev))
- return -EBUSY;
-
- /* TODO: validate addr */
- memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
-
- return 0;
}
static struct sk_buff*
@@ -420,13 +403,6 @@ static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev)
}
}
-static struct wpan_phy *lowpan_get_phy(const struct net_device *dev)
-{
- struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
-
- return ieee802154_mlme_ops(real_dev)->get_phy(real_dev);
-}
-
static __le16 lowpan_get_pan_id(const struct net_device *dev)
{
struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
@@ -474,12 +450,10 @@ static int lowpan_dev_init(struct net_device *dev)
static const struct net_device_ops lowpan_netdev_ops = {
.ndo_init = lowpan_dev_init,
.ndo_start_xmit = lowpan_xmit,
- .ndo_set_mac_address = lowpan_set_address,
};
static struct ieee802154_mlme_ops lowpan_mlme = {
.get_pan_id = lowpan_get_pan_id,
- .get_phy = lowpan_get_phy,
.get_short_addr = lowpan_get_short_addr,
.get_dsn = lowpan_get_dsn,
};
@@ -544,7 +518,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */
ret = iphc_decompress(skb, &hdr);
if (ret < 0)
- goto drop;
+ goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
case LOWPAN_DISPATCH_FRAG1: /* first fragment header */
@@ -552,7 +526,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (ret == 1) {
ret = iphc_decompress(skb, &hdr);
if (ret < 0)
- goto drop;
+ goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
} else if (ret == -1) {
@@ -565,7 +539,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (ret == 1) {
ret = iphc_decompress(skb, &hdr);
if (ret < 0)
- goto drop;
+ goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
} else if (ret == -1) {
diff --git a/net/ieee802154/Makefile b/net/ieee802154/Makefile
index 38354d4a70cb..9f6970f2a28b 100644
--- a/net/ieee802154/Makefile
+++ b/net/ieee802154/Makefile
@@ -3,7 +3,7 @@ obj-$(CONFIG_IEEE802154_6LOWPAN) += ieee802154_6lowpan.o
ieee802154_6lowpan-y := 6lowpan_rtnl.o reassembly.o
ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o core.o \
- header_ops.o sysfs.o
+ header_ops.o sysfs.o nl802154.o
af_802154-y := af_ieee802154.o raw.o dgram.o
ccflags-y += -D__CHECK_ENDIAN__
diff --git a/net/ieee802154/core.c b/net/ieee802154/core.c
index 620abc2ba5fc..18bc7e738507 100644
--- a/net/ieee802154/core.c
+++ b/net/ieee802154/core.c
@@ -18,12 +18,16 @@
#include <linux/device.h>
#include <net/cfg802154.h>
+#include <net/rtnetlink.h>
#include "ieee802154.h"
+#include "nl802154.h"
#include "sysfs.h"
+#include "core.h"
-static DEFINE_MUTEX(wpan_phy_mutex);
-static int wpan_phy_idx;
+/* RCU-protected (and RTNL for writers) */
+LIST_HEAD(cfg802154_rdev_list);
+int cfg802154_rdev_list_generation;
static int wpan_phy_match(struct device *dev, const void *data)
{
@@ -71,54 +75,116 @@ int wpan_phy_for_each(int (*fn)(struct wpan_phy *phy, void *data),
}
EXPORT_SYMBOL(wpan_phy_for_each);
-static int wpan_phy_idx_valid(int idx)
+struct cfg802154_registered_device *
+cfg802154_rdev_by_wpan_phy_idx(int wpan_phy_idx)
{
- return idx >= 0;
+ struct cfg802154_registered_device *result = NULL, *rdev;
+
+ ASSERT_RTNL();
+
+ list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
+ if (rdev->wpan_phy_idx == wpan_phy_idx) {
+ result = rdev;
+ break;
+ }
+ }
+
+ return result;
}
-struct wpan_phy *wpan_phy_alloc(size_t priv_size)
+struct wpan_phy *
+wpan_phy_new(const struct cfg802154_ops *ops, size_t priv_size)
{
- struct wpan_phy *phy = kzalloc(sizeof(*phy) + priv_size,
- GFP_KERNEL);
-
- if (!phy)
- goto out;
- mutex_lock(&wpan_phy_mutex);
- phy->idx = wpan_phy_idx++;
- if (unlikely(!wpan_phy_idx_valid(phy->idx))) {
- wpan_phy_idx--;
- mutex_unlock(&wpan_phy_mutex);
- kfree(phy);
- goto out;
+ static atomic_t wpan_phy_counter = ATOMIC_INIT(0);
+ struct cfg802154_registered_device *rdev;
+ size_t alloc_size;
+
+ alloc_size = sizeof(*rdev) + priv_size;
+ rdev = kzalloc(alloc_size, GFP_KERNEL);
+ if (!rdev)
+ return NULL;
+
+ rdev->ops = ops;
+
+ rdev->wpan_phy_idx = atomic_inc_return(&wpan_phy_counter);
+
+ if (unlikely(rdev->wpan_phy_idx < 0)) {
+ /* ugh, wrapped! */
+ atomic_dec(&wpan_phy_counter);
+ kfree(rdev);
+ return NULL;
}
- mutex_unlock(&wpan_phy_mutex);
- mutex_init(&phy->pib_lock);
+ /* atomic_inc_return makes it start at 1, make it start at 0 */
+ rdev->wpan_phy_idx--;
- device_initialize(&phy->dev);
- dev_set_name(&phy->dev, "wpan-phy%d", phy->idx);
+ mutex_init(&rdev->wpan_phy.pib_lock);
- phy->dev.class = &wpan_phy_class;
+ INIT_LIST_HEAD(&rdev->wpan_dev_list);
+ device_initialize(&rdev->wpan_phy.dev);
+ dev_set_name(&rdev->wpan_phy.dev, "wpan-phy%d", rdev->wpan_phy_idx);
- phy->current_channel = -1; /* not initialised */
- phy->current_page = 0; /* for compatibility */
+ rdev->wpan_phy.dev.class = &wpan_phy_class;
+ rdev->wpan_phy.dev.platform_data = rdev;
- return phy;
+ init_waitqueue_head(&rdev->dev_wait);
-out:
- return NULL;
+ return &rdev->wpan_phy;
}
-EXPORT_SYMBOL(wpan_phy_alloc);
+EXPORT_SYMBOL(wpan_phy_new);
int wpan_phy_register(struct wpan_phy *phy)
{
- return device_add(&phy->dev);
+ struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(phy);
+ int ret;
+
+ rtnl_lock();
+ ret = device_add(&phy->dev);
+ if (ret) {
+ rtnl_unlock();
+ return ret;
+ }
+
+ list_add_rcu(&rdev->list, &cfg802154_rdev_list);
+ cfg802154_rdev_list_generation++;
+
+ /* TODO phy registered lock */
+ rtnl_unlock();
+
+ /* TODO nl802154 phy notify */
+
+ return 0;
}
EXPORT_SYMBOL(wpan_phy_register);
void wpan_phy_unregister(struct wpan_phy *phy)
{
+ struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(phy);
+
+ wait_event(rdev->dev_wait, ({
+ int __count;
+ rtnl_lock();
+ __count = rdev->opencount;
+ rtnl_unlock();
+ __count == 0; }));
+
+ rtnl_lock();
+ /* TODO nl802154 phy notify */
+ /* TODO phy registered lock */
+
+ WARN_ON(!list_empty(&rdev->wpan_dev_list));
+
+ /* First remove the hardware from everywhere, this makes
+ * it impossible to find from userspace.
+ */
+ list_del_rcu(&rdev->list);
+ synchronize_rcu();
+
+ cfg802154_rdev_list_generation++;
+
device_del(&phy->dev);
+
+ rtnl_unlock();
}
EXPORT_SYMBOL(wpan_phy_unregister);
@@ -128,6 +194,84 @@ void wpan_phy_free(struct wpan_phy *phy)
}
EXPORT_SYMBOL(wpan_phy_free);
+void cfg802154_dev_free(struct cfg802154_registered_device *rdev)
+{
+ kfree(rdev);
+}
+
+static void
+cfg802154_update_iface_num(struct cfg802154_registered_device *rdev,
+ int iftype, int num)
+{
+ ASSERT_RTNL();
+
+ rdev->num_running_ifaces += num;
+}
+
+static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
+ unsigned long state, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ struct cfg802154_registered_device *rdev;
+
+ if (!wpan_dev)
+ return NOTIFY_DONE;
+
+ rdev = wpan_phy_to_rdev(wpan_dev->wpan_phy);
+
+ /* TODO WARN_ON unspec type */
+
+ switch (state) {
+ /* TODO NETDEV_DEVTYPE */
+ case NETDEV_REGISTER:
+ wpan_dev->identifier = ++rdev->wpan_dev_id;
+ list_add_rcu(&wpan_dev->list, &rdev->wpan_dev_list);
+ rdev->devlist_generation++;
+
+ wpan_dev->netdev = dev;
+ break;
+ case NETDEV_DOWN:
+ cfg802154_update_iface_num(rdev, wpan_dev->iftype, -1);
+
+ rdev->opencount--;
+ wake_up(&rdev->dev_wait);
+ break;
+ case NETDEV_UP:
+ cfg802154_update_iface_num(rdev, wpan_dev->iftype, 1);
+
+ rdev->opencount++;
+ break;
+ case NETDEV_UNREGISTER:
+ /* It is possible to get NETDEV_UNREGISTER
+ * multiple times. To detect that, check
+ * that the interface is still on the list
+ * of registered interfaces, and only then
+ * remove and clean it up.
+ */
+ if (!list_empty(&wpan_dev->list)) {
+ list_del_rcu(&wpan_dev->list);
+ rdev->devlist_generation++;
+ }
+ /* synchronize (so that we won't find this netdev
+ * from other code any more) and then clear the list
+ * head so that the above code can safely check for
+ * !list_empty() to avoid double-cleanup.
+ */
+ synchronize_rcu();
+ INIT_LIST_HEAD(&wpan_dev->list);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block cfg802154_netdev_notifier = {
+ .notifier_call = cfg802154_netdev_notifier_call,
+};
+
static int __init wpan_phy_class_init(void)
{
int rc;
@@ -136,11 +280,25 @@ static int __init wpan_phy_class_init(void)
if (rc)
goto err;
- rc = ieee802154_nl_init();
+ rc = register_netdevice_notifier(&cfg802154_netdev_notifier);
if (rc)
goto err_nl;
+ rc = ieee802154_nl_init();
+ if (rc)
+ goto err_notifier;
+
+ rc = nl802154_init();
+ if (rc)
+ goto err_ieee802154_nl;
+
return 0;
+
+err_ieee802154_nl:
+ ieee802154_nl_exit();
+
+err_notifier:
+ unregister_netdevice_notifier(&cfg802154_netdev_notifier);
err_nl:
wpan_phy_sysfs_exit();
err:
@@ -150,7 +308,9 @@ subsys_initcall(wpan_phy_class_init);
static void __exit wpan_phy_class_exit(void)
{
+ nl802154_exit();
ieee802154_nl_exit();
+ unregister_netdevice_notifier(&cfg802154_netdev_notifier);
wpan_phy_sysfs_exit();
}
module_exit(wpan_phy_class_exit);
diff --git a/net/ieee802154/core.h b/net/ieee802154/core.h
new file mode 100644
index 000000000000..f3e95580caee
--- /dev/null
+++ b/net/ieee802154/core.h
@@ -0,0 +1,46 @@
+#ifndef __IEEE802154_CORE_H
+#define __IEEE802154_CORE_H
+
+#include <net/cfg802154.h>
+
+struct cfg802154_registered_device {
+ const struct cfg802154_ops *ops;
+ struct list_head list;
+
+ /* wpan_phy index, internal only */
+ int wpan_phy_idx;
+
+ /* also protected by devlist_mtx */
+ int opencount;
+ wait_queue_head_t dev_wait;
+
+ /* protected by RTNL only */
+ int num_running_ifaces;
+
+ /* associated wpan interfaces, protected by rtnl or RCU */
+ struct list_head wpan_dev_list;
+ int devlist_generation, wpan_dev_id;
+
+ /* must be last because of the way we do wpan_phy_priv(),
+ * and it should at least be aligned to NETDEV_ALIGN
+ */
+ struct wpan_phy wpan_phy __aligned(NETDEV_ALIGN);
+};
+
+static inline struct cfg802154_registered_device *
+wpan_phy_to_rdev(struct wpan_phy *wpan_phy)
+{
+ BUG_ON(!wpan_phy);
+ return container_of(wpan_phy, struct cfg802154_registered_device,
+ wpan_phy);
+}
+
+extern struct list_head cfg802154_rdev_list;
+extern int cfg802154_rdev_list_generation;
+
+/* free object */
+void cfg802154_dev_free(struct cfg802154_registered_device *rdev);
+struct cfg802154_registered_device *
+cfg802154_rdev_by_wpan_phy_idx(int wpan_phy_idx);
+
+#endif /* __IEEE802154_CORE_H */
diff --git a/net/ieee802154/ieee802154.h b/net/ieee802154/ieee802154.h
index 42ae63a345ab..a5d7515b7f62 100644
--- a/net/ieee802154/ieee802154.h
+++ b/net/ieee802154/ieee802154.h
@@ -15,7 +15,7 @@
#define IEEE_802154_LOCAL_H
int __init ieee802154_nl_init(void);
-void __exit ieee802154_nl_exit(void);
+void ieee802154_nl_exit(void);
#define IEEE802154_OP(_cmd, _func) \
{ \
diff --git a/net/ieee802154/netlink.c b/net/ieee802154/netlink.c
index 6c3c2595a201..63ee7d66950e 100644
--- a/net/ieee802154/netlink.c
+++ b/net/ieee802154/netlink.c
@@ -155,7 +155,7 @@ int __init ieee802154_nl_init(void)
ieee802154_mcgrps);
}
-void __exit ieee802154_nl_exit(void)
+void ieee802154_nl_exit(void)
{
genl_unregister_family(&nl802154_family);
}
diff --git a/net/ieee802154/nl-mac.c b/net/ieee802154/nl-mac.c
index abd0f31bdc66..fe77f0c770b8 100644
--- a/net/ieee802154/nl-mac.c
+++ b/net/ieee802154/nl-mac.c
@@ -29,7 +29,6 @@
#include <linux/nl802154.h>
#include <linux/export.h>
#include <net/af_ieee802154.h>
-#include <net/nl802154.h>
#include <net/ieee802154_netdev.h>
#include <net/cfg802154.h>
@@ -55,186 +54,7 @@ static __le16 nla_get_shortaddr(const struct nlattr *nla)
return cpu_to_le16(nla_get_u16(nla));
}
-int ieee802154_nl_assoc_indic(struct net_device *dev,
- struct ieee802154_addr *addr,
- u8 cap)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- if (addr->mode != IEEE802154_ADDR_LONG) {
- pr_err("%s: received non-long source address!\n", __func__);
- return -EINVAL;
- }
-
- msg = ieee802154_nl_create(0, IEEE802154_ASSOCIATE_INDIC);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr) ||
- nla_put_hwaddr(msg, IEEE802154_ATTR_SRC_HW_ADDR,
- addr->extended_addr) ||
- nla_put_u8(msg, IEEE802154_ATTR_CAPABILITY, cap))
- goto nla_put_failure;
-
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_assoc_indic);
-
-int ieee802154_nl_assoc_confirm(struct net_device *dev, __le16 short_addr,
- u8 status)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- msg = ieee802154_nl_create(0, IEEE802154_ASSOCIATE_CONF);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr) ||
- nla_put_shortaddr(msg, IEEE802154_ATTR_SHORT_ADDR, short_addr) ||
- nla_put_u8(msg, IEEE802154_ATTR_STATUS, status))
- goto nla_put_failure;
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_assoc_confirm);
-
-int ieee802154_nl_disassoc_indic(struct net_device *dev,
- struct ieee802154_addr *addr,
- u8 reason)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- msg = ieee802154_nl_create(0, IEEE802154_DISASSOCIATE_INDIC);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr))
- goto nla_put_failure;
- if (addr->mode == IEEE802154_ADDR_LONG) {
- if (nla_put_hwaddr(msg, IEEE802154_ATTR_SRC_HW_ADDR,
- addr->extended_addr))
- goto nla_put_failure;
- } else {
- if (nla_put_shortaddr(msg, IEEE802154_ATTR_SRC_SHORT_ADDR,
- addr->short_addr))
- goto nla_put_failure;
- }
- if (nla_put_u8(msg, IEEE802154_ATTR_REASON, reason))
- goto nla_put_failure;
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_disassoc_indic);
-
-int ieee802154_nl_disassoc_confirm(struct net_device *dev, u8 status)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- msg = ieee802154_nl_create(0, IEEE802154_DISASSOCIATE_CONF);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr) ||
- nla_put_u8(msg, IEEE802154_ATTR_STATUS, status))
- goto nla_put_failure;
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_disassoc_confirm);
-
-int ieee802154_nl_beacon_indic(struct net_device *dev, __le16 panid,
- __le16 coord_addr)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- msg = ieee802154_nl_create(0, IEEE802154_BEACON_NOTIFY_INDIC);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr) ||
- nla_put_shortaddr(msg, IEEE802154_ATTR_COORD_SHORT_ADDR,
- coord_addr) ||
- nla_put_shortaddr(msg, IEEE802154_ATTR_COORD_PAN_ID, panid))
- goto nla_put_failure;
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_beacon_indic);
-
-int ieee802154_nl_scan_confirm(struct net_device *dev,
- u8 status, u8 scan_type,
- u32 unscanned, u8 page,
- u8 *edl/* , struct list_head *pan_desc_list */)
-{
- struct sk_buff *msg;
-
- pr_debug("%s\n", __func__);
-
- msg = ieee802154_nl_create(0, IEEE802154_SCAN_CONF);
- if (!msg)
- return -ENOBUFS;
-
- if (nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name) ||
- nla_put_u32(msg, IEEE802154_ATTR_DEV_INDEX, dev->ifindex) ||
- nla_put(msg, IEEE802154_ATTR_HW_ADDR, IEEE802154_ADDR_LEN,
- dev->dev_addr) ||
- nla_put_u8(msg, IEEE802154_ATTR_STATUS, status) ||
- nla_put_u8(msg, IEEE802154_ATTR_SCAN_TYPE, scan_type) ||
- nla_put_u32(msg, IEEE802154_ATTR_CHANNELS, unscanned) ||
- nla_put_u8(msg, IEEE802154_ATTR_PAGE, page) ||
- (edl &&
- nla_put(msg, IEEE802154_ATTR_ED_LIST, 27, edl)))
- goto nla_put_failure;
- return ieee802154_nl_mcast(msg, IEEE802154_COORD_MCGRP);
-
-nla_put_failure:
- nlmsg_free(msg);
- return -ENOBUFS;
-}
-EXPORT_SYMBOL(ieee802154_nl_scan_confirm);
-
-int ieee802154_nl_start_confirm(struct net_device *dev, u8 status)
+static int ieee802154_nl_start_confirm(struct net_device *dev, u8 status)
{
struct sk_buff *msg;
@@ -274,8 +94,9 @@ static int ieee802154_nl_fill_iface(struct sk_buff *msg, u32 portid,
goto out;
ops = ieee802154_mlme_ops(dev);
- phy = ops->get_phy(dev);
+ phy = dev->ieee802154_ptr->wpan_phy;
BUG_ON(!phy);
+ get_device(&phy->dev);
short_addr = ops->get_short_addr(dev);
pan_id = ops->get_pan_id(dev);
@@ -292,7 +113,9 @@ static int ieee802154_nl_fill_iface(struct sk_buff *msg, u32 portid,
if (ops->get_mac_params) {
struct ieee802154_mac_params params;
+ rtnl_lock();
ops->get_mac_params(dev, &params);
+ rtnl_unlock();
if (nla_put_s8(msg, IEEE802154_ATTR_TXPOWER,
params.transmit_power) ||
@@ -343,7 +166,10 @@ static struct net_device *ieee802154_nl_get_dev(struct genl_info *info)
if (!dev)
return NULL;
- if (dev->type != ARPHRD_IEEE802154) {
+ /* Check on mtu is currently a hacked solution because lowpan
+ * and wpan have the same ARPHRD type.
+ */
+ if (dev->type != ARPHRD_IEEE802154 || dev->mtu != IEEE802154_MTU) {
dev_put(dev);
return NULL;
}
@@ -477,7 +303,7 @@ int ieee802154_start_req(struct sk_buff *skb, struct genl_info *info)
u8 channel, bcn_ord, sf_ord;
u8 page;
int pan_coord, blx, coord_realign;
- int ret = -EOPNOTSUPP;
+ int ret = -EBUSY;
if (!info->attrs[IEEE802154_ATTR_COORD_PAN_ID] ||
!info->attrs[IEEE802154_ATTR_COORD_SHORT_ADDR] ||
@@ -493,8 +319,14 @@ int ieee802154_start_req(struct sk_buff *skb, struct genl_info *info)
dev = ieee802154_nl_get_dev(info);
if (!dev)
return -ENODEV;
- if (!ieee802154_mlme_ops(dev)->start_req)
+
+ if (netif_running(dev))
+ goto out;
+
+ if (!ieee802154_mlme_ops(dev)->start_req) {
+ ret = -EOPNOTSUPP;
goto out;
+ }
addr.mode = IEEE802154_ADDR_SHORT;
addr.short_addr = nla_get_shortaddr(
@@ -521,8 +353,15 @@ int ieee802154_start_req(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
}
+ rtnl_lock();
ret = ieee802154_mlme_ops(dev)->start_req(dev, &addr, channel, page,
bcn_ord, sf_ord, pan_coord, blx, coord_realign);
+ rtnl_unlock();
+
+ /* FIXME: add validation for unused parameters to be sane
+ * for SoftMAC
+ */
+ ieee802154_nl_start_confirm(dev, IEEE802154_SUCCESS);
out:
dev_put(dev);
@@ -612,7 +451,11 @@ int ieee802154_dump_iface(struct sk_buff *skb, struct netlink_callback *cb)
idx = 0;
for_each_netdev(net, dev) {
- if (idx < s_idx || (dev->type != ARPHRD_IEEE802154))
+ /* Check on mtu is currently a hacked solution because lowpan
+ * and wpan have the same ARPHRD type.
+ */
+ if (idx < s_idx || dev->type != ARPHRD_IEEE802154 ||
+ dev->mtu != IEEE802154_MTU)
goto cont;
if (ieee802154_nl_fill_iface(skb, NETLINK_CB(cb->skb).portid,
@@ -662,8 +505,10 @@ int ieee802154_set_macparams(struct sk_buff *skb, struct genl_info *info)
!info->attrs[IEEE802154_ATTR_FRAME_RETRIES])
goto out;
- phy = ops->get_phy(dev);
+ phy = dev->ieee802154_ptr->wpan_phy;
+ get_device(&phy->dev);
+ rtnl_lock();
ops->get_mac_params(dev, &params);
if (info->attrs[IEEE802154_ATTR_TXPOWER])
@@ -691,6 +536,7 @@ int ieee802154_set_macparams(struct sk_buff *skb, struct genl_info *info)
params.frame_retries = nla_get_s8(info->attrs[IEEE802154_ATTR_FRAME_RETRIES]);
rc = ops->set_mac_params(dev, &params);
+ rtnl_unlock();
wpan_phy_put(phy);
dev_put(dev);
@@ -943,7 +789,11 @@ ieee802154_llsec_dump_table(struct sk_buff *skb, struct netlink_callback *cb,
int rc;
for_each_netdev(net, dev) {
- if (idx < first_dev || dev->type != ARPHRD_IEEE802154)
+ /* Check on mtu is currently a hacked solution because lowpan
+ * and wpan have the same ARPHRD type.
+ */
+ if (idx < first_dev || dev->type != ARPHRD_IEEE802154 ||
+ dev->mtu != IEEE802154_MTU)
goto skip;
data.ops = ieee802154_mlme_ops(dev);
diff --git a/net/ieee802154/nl-phy.c b/net/ieee802154/nl-phy.c
index 0afe760ff512..80a946dddd90 100644
--- a/net/ieee802154/nl-phy.c
+++ b/net/ieee802154/nl-phy.c
@@ -30,6 +30,8 @@
#include <linux/nl802154.h>
#include "ieee802154.h"
+#include "rdev-ops.h"
+#include "core.h"
static int ieee802154_nl_fill_phy(struct sk_buff *msg, u32 portid,
u32 seq, int flags, struct wpan_phy *phy)
@@ -203,11 +205,6 @@ int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info)
if (!msg)
goto out_dev;
- if (!phy->add_iface) {
- rc = -EINVAL;
- goto nla_put_failure;
- }
-
if (info->attrs[IEEE802154_ATTR_HW_ADDR] &&
nla_len(info->attrs[IEEE802154_ATTR_HW_ADDR]) !=
IEEE802154_ADDR_LEN) {
@@ -223,11 +220,13 @@ int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info)
}
}
- dev = phy->add_iface(phy, devname, type);
+ dev = rdev_add_virtual_intf_deprecated(wpan_phy_to_rdev(phy), devname,
+ type);
if (IS_ERR(dev)) {
rc = PTR_ERR(dev);
goto nla_put_failure;
}
+ dev_hold(dev);
if (info->attrs[IEEE802154_ATTR_HW_ADDR]) {
struct sockaddr addr;
@@ -257,7 +256,7 @@ int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info)
dev_unregister:
rtnl_lock(); /* del_iface must be called with RTNL lock */
- phy->del_iface(phy, dev);
+ rdev_del_virtual_intf_deprecated(wpan_phy_to_rdev(phy), dev);
dev_put(dev);
rtnl_unlock();
nla_put_failure:
@@ -288,8 +287,9 @@ int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info)
if (!dev)
return -ENODEV;
- phy = ieee802154_mlme_ops(dev)->get_phy(dev);
+ phy = dev->ieee802154_ptr->wpan_phy;
BUG_ON(!phy);
+ get_device(&phy->dev);
rc = -EINVAL;
/* phy name is optional, but should be checked if it's given */
@@ -319,13 +319,8 @@ int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info)
if (!msg)
goto out_dev;
- if (!phy->del_iface) {
- rc = -EINVAL;
- goto nla_put_failure;
- }
-
rtnl_lock();
- phy->del_iface(phy, dev);
+ rdev_del_virtual_intf_deprecated(wpan_phy_to_rdev(phy), dev);
/* We don't have device anymore */
dev_put(dev);
diff --git a/net/ieee802154/nl802154.c b/net/ieee802154/nl802154.c
new file mode 100644
index 000000000000..889647744697
--- /dev/null
+++ b/net/ieee802154/nl802154.c
@@ -0,0 +1,957 @@
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Authors:
+ * Alexander Aring <aar@pengutronix.de>
+ *
+ * Based on: net/wireless/nl80211.c
+ */
+
+#include <linux/rtnetlink.h>
+
+#include <net/cfg802154.h>
+#include <net/genetlink.h>
+#include <net/mac802154.h>
+#include <net/netlink.h>
+#include <net/nl802154.h>
+#include <net/sock.h>
+
+#include "nl802154.h"
+#include "rdev-ops.h"
+#include "core.h"
+
+static int nl802154_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
+ struct genl_info *info);
+
+static void nl802154_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
+ struct genl_info *info);
+
+/* the netlink family */
+static struct genl_family nl802154_fam = {
+ .id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
+ .name = NL802154_GENL_NAME, /* have users key off the name instead */
+ .hdrsize = 0, /* no private header */
+ .version = 1, /* no particular meaning now */
+ .maxattr = NL802154_ATTR_MAX,
+ .netnsok = true,
+ .pre_doit = nl802154_pre_doit,
+ .post_doit = nl802154_post_doit,
+};
+
+/* multicast groups */
+enum nl802154_multicast_groups {
+ NL802154_MCGRP_CONFIG,
+};
+
+static const struct genl_multicast_group nl802154_mcgrps[] = {
+ [NL802154_MCGRP_CONFIG] = { .name = "config", },
+};
+
+/* returns ERR_PTR values */
+static struct wpan_dev *
+__cfg802154_wpan_dev_from_attrs(struct net *netns, struct nlattr **attrs)
+{
+ struct cfg802154_registered_device *rdev;
+ struct wpan_dev *result = NULL;
+ bool have_ifidx = attrs[NL802154_ATTR_IFINDEX];
+ bool have_wpan_dev_id = attrs[NL802154_ATTR_WPAN_DEV];
+ u64 wpan_dev_id;
+ int wpan_phy_idx = -1;
+ int ifidx = -1;
+
+ ASSERT_RTNL();
+
+ if (!have_ifidx && !have_wpan_dev_id)
+ return ERR_PTR(-EINVAL);
+
+ if (have_ifidx)
+ ifidx = nla_get_u32(attrs[NL802154_ATTR_IFINDEX]);
+ if (have_wpan_dev_id) {
+ wpan_dev_id = nla_get_u64(attrs[NL802154_ATTR_WPAN_DEV]);
+ wpan_phy_idx = wpan_dev_id >> 32;
+ }
+
+ list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
+ struct wpan_dev *wpan_dev;
+
+ /* TODO netns compare */
+
+ if (have_wpan_dev_id && rdev->wpan_phy_idx != wpan_phy_idx)
+ continue;
+
+ list_for_each_entry(wpan_dev, &rdev->wpan_dev_list, list) {
+ if (have_ifidx && wpan_dev->netdev &&
+ wpan_dev->netdev->ifindex == ifidx) {
+ result = wpan_dev;
+ break;
+ }
+ if (have_wpan_dev_id &&
+ wpan_dev->identifier == (u32)wpan_dev_id) {
+ result = wpan_dev;
+ break;
+ }
+ }
+
+ if (result)
+ break;
+ }
+
+ if (result)
+ return result;
+
+ return ERR_PTR(-ENODEV);
+}
+
+static struct cfg802154_registered_device *
+__cfg802154_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
+{
+ struct cfg802154_registered_device *rdev = NULL, *tmp;
+ struct net_device *netdev;
+
+ ASSERT_RTNL();
+
+ if (!attrs[NL802154_ATTR_WPAN_PHY] &&
+ !attrs[NL802154_ATTR_IFINDEX] &&
+ !attrs[NL802154_ATTR_WPAN_DEV])
+ return ERR_PTR(-EINVAL);
+
+ if (attrs[NL802154_ATTR_WPAN_PHY])
+ rdev = cfg802154_rdev_by_wpan_phy_idx(
+ nla_get_u32(attrs[NL802154_ATTR_WPAN_PHY]));
+
+ if (attrs[NL802154_ATTR_WPAN_DEV]) {
+ u64 wpan_dev_id = nla_get_u64(attrs[NL802154_ATTR_WPAN_DEV]);
+ struct wpan_dev *wpan_dev;
+ bool found = false;
+
+ tmp = cfg802154_rdev_by_wpan_phy_idx(wpan_dev_id >> 32);
+ if (tmp) {
+ /* make sure wpan_dev exists */
+ list_for_each_entry(wpan_dev, &tmp->wpan_dev_list, list) {
+ if (wpan_dev->identifier != (u32)wpan_dev_id)
+ continue;
+ found = true;
+ break;
+ }
+
+ if (!found)
+ tmp = NULL;
+
+ if (rdev && tmp != rdev)
+ return ERR_PTR(-EINVAL);
+ rdev = tmp;
+ }
+ }
+
+ if (attrs[NL802154_ATTR_IFINDEX]) {
+ int ifindex = nla_get_u32(attrs[NL802154_ATTR_IFINDEX]);
+
+ netdev = __dev_get_by_index(netns, ifindex);
+ if (netdev) {
+ if (netdev->ieee802154_ptr)
+ tmp = wpan_phy_to_rdev(
+ netdev->ieee802154_ptr->wpan_phy);
+ else
+ tmp = NULL;
+
+ /* not wireless device -- return error */
+ if (!tmp)
+ return ERR_PTR(-EINVAL);
+
+ /* mismatch -- return error */
+ if (rdev && tmp != rdev)
+ return ERR_PTR(-EINVAL);
+
+ rdev = tmp;
+ }
+ }
+
+ if (!rdev)
+ return ERR_PTR(-ENODEV);
+
+ /* TODO netns compare */
+
+ return rdev;
+}
+
+/* This function returns a pointer to the driver
+ * that the genl_info item that is passed refers to.
+ *
+ * The result of this can be a PTR_ERR and hence must
+ * be checked with IS_ERR() for errors.
+ */
+static struct cfg802154_registered_device *
+cfg802154_get_dev_from_info(struct net *netns, struct genl_info *info)
+{
+ return __cfg802154_rdev_from_attrs(netns, info->attrs);
+}
+
+/* policy for the attributes */
+static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
+ [NL802154_ATTR_WPAN_PHY] = { .type = NLA_U32 },
+ [NL802154_ATTR_WPAN_PHY_NAME] = { .type = NLA_NUL_STRING,
+ .len = 20-1 },
+
+ [NL802154_ATTR_IFINDEX] = { .type = NLA_U32 },
+ [NL802154_ATTR_IFTYPE] = { .type = NLA_U32 },
+ [NL802154_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },
+
+ [NL802154_ATTR_WPAN_DEV] = { .type = NLA_U64 },
+
+ [NL802154_ATTR_PAGE] = { .type = NLA_U8, },
+ [NL802154_ATTR_CHANNEL] = { .type = NLA_U8, },
+
+ [NL802154_ATTR_TX_POWER] = { .type = NLA_S8, },
+
+ [NL802154_ATTR_CCA_MODE] = { .type = NLA_U8, },
+
+ [NL802154_ATTR_SUPPORTED_CHANNEL] = { .type = NLA_U32, },
+
+ [NL802154_ATTR_PAN_ID] = { .type = NLA_U16, },
+ [NL802154_ATTR_EXTENDED_ADDR] = { .type = NLA_U64 },
+ [NL802154_ATTR_SHORT_ADDR] = { .type = NLA_U16, },
+
+ [NL802154_ATTR_MIN_BE] = { .type = NLA_U8, },
+ [NL802154_ATTR_MAX_BE] = { .type = NLA_U8, },
+ [NL802154_ATTR_MAX_CSMA_BACKOFFS] = { .type = NLA_U8, },
+
+ [NL802154_ATTR_MAX_FRAME_RETRIES] = { .type = NLA_S8, },
+
+ [NL802154_ATTR_LBT_MODE] = { .type = NLA_U8, },
+};
+
+/* message building helper */
+static inline void *nl802154hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
+ int flags, u8 cmd)
+{
+ /* since there is no private header just add the generic one */
+ return genlmsg_put(skb, portid, seq, &nl802154_fam, flags, cmd);
+}
+
+static int
+nl802154_send_wpan_phy_channels(struct cfg802154_registered_device *rdev,
+ struct sk_buff *msg)
+{
+ struct nlattr *nl_page;
+ unsigned long page;
+
+ nl_page = nla_nest_start(msg, NL802154_ATTR_CHANNELS_SUPPORTED);
+ if (!nl_page)
+ return -ENOBUFS;
+
+ for (page = 0; page <= IEEE802154_MAX_PAGE; page++) {
+ if (nla_put_u32(msg, NL802154_ATTR_SUPPORTED_CHANNEL,
+ rdev->wpan_phy.channels_supported[page]))
+ return -ENOBUFS;
+ }
+ nla_nest_end(msg, nl_page);
+
+ return 0;
+}
+
+static int nl802154_send_wpan_phy(struct cfg802154_registered_device *rdev,
+ enum nl802154_commands cmd,
+ struct sk_buff *msg, u32 portid, u32 seq,
+ int flags)
+{
+ void *hdr;
+
+ hdr = nl802154hdr_put(msg, portid, seq, flags, cmd);
+ if (!hdr)
+ return -ENOBUFS;
+
+ if (nla_put_u32(msg, NL802154_ATTR_WPAN_PHY, rdev->wpan_phy_idx) ||
+ nla_put_string(msg, NL802154_ATTR_WPAN_PHY_NAME,
+ wpan_phy_name(&rdev->wpan_phy)) ||
+ nla_put_u32(msg, NL802154_ATTR_GENERATION,
+ cfg802154_rdev_list_generation))
+ goto nla_put_failure;
+
+ if (cmd != NL802154_CMD_NEW_WPAN_PHY)
+ goto finish;
+
+ /* DUMP PHY PIB */
+
+ /* current channel settings */
+ if (nla_put_u8(msg, NL802154_ATTR_PAGE,
+ rdev->wpan_phy.current_page) ||
+ nla_put_u8(msg, NL802154_ATTR_CHANNEL,
+ rdev->wpan_phy.current_channel))
+ goto nla_put_failure;
+
+ /* supported channels array */
+ if (nl802154_send_wpan_phy_channels(rdev, msg))
+ goto nla_put_failure;
+
+ /* cca mode */
+ if (nla_put_u8(msg, NL802154_ATTR_CCA_MODE,
+ rdev->wpan_phy.cca_mode))
+ goto nla_put_failure;
+
+ if (nla_put_s8(msg, NL802154_ATTR_TX_POWER,
+ rdev->wpan_phy.transmit_power))
+ goto nla_put_failure;
+
+finish:
+ return genlmsg_end(msg, hdr);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+}
+
+struct nl802154_dump_wpan_phy_state {
+ s64 filter_wpan_phy;
+ long start;
+
+};
+
+static int nl802154_dump_wpan_phy_parse(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ struct nl802154_dump_wpan_phy_state *state)
+{
+ struct nlattr **tb = nl802154_fam.attrbuf;
+ int ret = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl802154_fam.hdrsize,
+ tb, nl802154_fam.maxattr, nl802154_policy);
+
+ /* TODO check if we can handle error here,
+ * we have no backward compatibility
+ */
+ if (ret)
+ return 0;
+
+ if (tb[NL802154_ATTR_WPAN_PHY])
+ state->filter_wpan_phy = nla_get_u32(tb[NL802154_ATTR_WPAN_PHY]);
+ if (tb[NL802154_ATTR_WPAN_DEV])
+ state->filter_wpan_phy = nla_get_u64(tb[NL802154_ATTR_WPAN_DEV]) >> 32;
+ if (tb[NL802154_ATTR_IFINDEX]) {
+ struct net_device *netdev;
+ struct cfg802154_registered_device *rdev;
+ int ifidx = nla_get_u32(tb[NL802154_ATTR_IFINDEX]);
+
+ /* TODO netns */
+ netdev = __dev_get_by_index(&init_net, ifidx);
+ if (!netdev)
+ return -ENODEV;
+ if (netdev->ieee802154_ptr) {
+ rdev = wpan_phy_to_rdev(
+ netdev->ieee802154_ptr->wpan_phy);
+ state->filter_wpan_phy = rdev->wpan_phy_idx;
+ }
+ }
+
+ return 0;
+}
+
+static int
+nl802154_dump_wpan_phy(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx = 0, ret;
+ struct nl802154_dump_wpan_phy_state *state = (void *)cb->args[0];
+ struct cfg802154_registered_device *rdev;
+
+ rtnl_lock();
+ if (!state) {
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state) {
+ rtnl_unlock();
+ return -ENOMEM;
+ }
+ state->filter_wpan_phy = -1;
+ ret = nl802154_dump_wpan_phy_parse(skb, cb, state);
+ if (ret) {
+ kfree(state);
+ rtnl_unlock();
+ return ret;
+ }
+ cb->args[0] = (long)state;
+ }
+
+ list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
+ /* TODO net ns compare */
+ if (++idx <= state->start)
+ continue;
+ if (state->filter_wpan_phy != -1 &&
+ state->filter_wpan_phy != rdev->wpan_phy_idx)
+ continue;
+ /* attempt to fit multiple wpan_phy data chunks into the skb */
+ ret = nl802154_send_wpan_phy(rdev,
+ NL802154_CMD_NEW_WPAN_PHY,
+ skb,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI);
+ if (ret < 0) {
+ if ((ret == -ENOBUFS || ret == -EMSGSIZE) &&
+ !skb->len && cb->min_dump_alloc < 4096) {
+ cb->min_dump_alloc = 4096;
+ rtnl_unlock();
+ return 1;
+ }
+ idx--;
+ break;
+ }
+ break;
+ }
+ rtnl_unlock();
+
+ state->start = idx;
+
+ return skb->len;
+}
+
+static int nl802154_dump_wpan_phy_done(struct netlink_callback *cb)
+{
+ kfree((void *)cb->args[0]);
+ return 0;
+}
+
+static int nl802154_get_wpan_phy(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nl802154_send_wpan_phy(rdev, NL802154_CMD_NEW_WPAN_PHY, msg,
+ info->snd_portid, info->snd_seq, 0) < 0) {
+ nlmsg_free(msg);
+ return -ENOBUFS;
+ }
+
+ return genlmsg_reply(msg, info);
+}
+
+static inline u64 wpan_dev_id(struct wpan_dev *wpan_dev)
+{
+ return (u64)wpan_dev->identifier |
+ ((u64)wpan_phy_to_rdev(wpan_dev->wpan_phy)->wpan_phy_idx << 32);
+}
+
+static int
+nl802154_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags,
+ struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev)
+{
+ struct net_device *dev = wpan_dev->netdev;
+ void *hdr;
+
+ hdr = nl802154hdr_put(msg, portid, seq, flags,
+ NL802154_CMD_NEW_INTERFACE);
+ if (!hdr)
+ return -1;
+
+ if (dev &&
+ (nla_put_u32(msg, NL802154_ATTR_IFINDEX, dev->ifindex) ||
+ nla_put_string(msg, NL802154_ATTR_IFNAME, dev->name)))
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL802154_ATTR_WPAN_PHY, rdev->wpan_phy_idx) ||
+ nla_put_u32(msg, NL802154_ATTR_IFTYPE, wpan_dev->iftype) ||
+ nla_put_u64(msg, NL802154_ATTR_WPAN_DEV, wpan_dev_id(wpan_dev)) ||
+ nla_put_u32(msg, NL802154_ATTR_GENERATION,
+ rdev->devlist_generation ^
+ (cfg802154_rdev_list_generation << 2)))
+ goto nla_put_failure;
+
+ /* address settings */
+ if (nla_put_le64(msg, NL802154_ATTR_EXTENDED_ADDR,
+ wpan_dev->extended_addr) ||
+ nla_put_le16(msg, NL802154_ATTR_SHORT_ADDR,
+ wpan_dev->short_addr) ||
+ nla_put_le16(msg, NL802154_ATTR_PAN_ID, wpan_dev->pan_id))
+ goto nla_put_failure;
+
+ /* ARET handling */
+ if (nla_put_s8(msg, NL802154_ATTR_MAX_FRAME_RETRIES,
+ wpan_dev->frame_retries) ||
+ nla_put_u8(msg, NL802154_ATTR_MAX_BE, wpan_dev->max_be) ||
+ nla_put_u8(msg, NL802154_ATTR_MAX_CSMA_BACKOFFS,
+ wpan_dev->csma_retries) ||
+ nla_put_u8(msg, NL802154_ATTR_MIN_BE, wpan_dev->min_be))
+ goto nla_put_failure;
+
+ /* listen before transmit */
+ if (nla_put_u8(msg, NL802154_ATTR_LBT_MODE, wpan_dev->lbt))
+ goto nla_put_failure;
+
+ return genlmsg_end(msg, hdr);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+}
+
+static int
+nl802154_dump_interface(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int wp_idx = 0;
+ int if_idx = 0;
+ int wp_start = cb->args[0];
+ int if_start = cb->args[1];
+ struct cfg802154_registered_device *rdev;
+ struct wpan_dev *wpan_dev;
+
+ rtnl_lock();
+ list_for_each_entry(rdev, &cfg802154_rdev_list, list) {
+ /* TODO netns compare */
+ if (wp_idx < wp_start) {
+ wp_idx++;
+ continue;
+ }
+ if_idx = 0;
+
+ list_for_each_entry(wpan_dev, &rdev->wpan_dev_list, list) {
+ if (if_idx < if_start) {
+ if_idx++;
+ continue;
+ }
+ if (nl802154_send_iface(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ rdev, wpan_dev) < 0) {
+ goto out;
+ }
+ if_idx++;
+ }
+
+ wp_idx++;
+ }
+out:
+ rtnl_unlock();
+
+ cb->args[0] = wp_idx;
+ cb->args[1] = if_idx;
+
+ return skb->len;
+}
+
+static int nl802154_get_interface(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct wpan_dev *wdev = info->user_ptr[1];
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nl802154_send_iface(msg, info->snd_portid, info->snd_seq, 0,
+ rdev, wdev) < 0) {
+ nlmsg_free(msg);
+ return -ENOBUFS;
+ }
+
+ return genlmsg_reply(msg, info);
+}
+
+static int nl802154_new_interface(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ enum nl802154_iftype type = NL802154_IFTYPE_UNSPEC;
+ __le64 extended_addr = cpu_to_le64(0x0000000000000000ULL);
+
+ /* TODO avoid failing a new interface
+ * creation due to pending removal?
+ */
+
+ if (!info->attrs[NL802154_ATTR_IFNAME])
+ return -EINVAL;
+
+ if (info->attrs[NL802154_ATTR_IFTYPE]) {
+ type = nla_get_u32(info->attrs[NL802154_ATTR_IFTYPE]);
+ if (type > NL802154_IFTYPE_MAX)
+ return -EINVAL;
+ }
+
+ /* TODO add nla_get_le64 to netlink */
+ if (info->attrs[NL802154_ATTR_EXTENDED_ADDR])
+ extended_addr = (__force __le64)nla_get_u64(
+ info->attrs[NL802154_ATTR_EXTENDED_ADDR]);
+
+ if (!rdev->ops->add_virtual_intf)
+ return -EOPNOTSUPP;
+
+ return rdev_add_virtual_intf(rdev,
+ nla_data(info->attrs[NL802154_ATTR_IFNAME]),
+ type, extended_addr);
+}
+
+static int nl802154_del_interface(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct wpan_dev *wpan_dev = info->user_ptr[1];
+
+ if (!rdev->ops->del_virtual_intf)
+ return -EOPNOTSUPP;
+
+ /* If we remove a wpan device without a netdev then clear
+ * user_ptr[1] so that nl802154_post_doit won't dereference it
+ * to check if it needs to do dev_put(). Otherwise it crashes
+ * since the wpan_dev has been freed, unlike with a netdev where
+ * we need the dev_put() for the netdev to really be freed.
+ */
+ if (!wpan_dev->netdev)
+ info->user_ptr[1] = NULL;
+
+ return rdev_del_virtual_intf(rdev, wpan_dev);
+}
+
+static int nl802154_set_channel(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ u8 channel, page;
+
+ if (!info->attrs[NL802154_ATTR_PAGE] ||
+ !info->attrs[NL802154_ATTR_CHANNEL])
+ return -EINVAL;
+
+ page = nla_get_u8(info->attrs[NL802154_ATTR_PAGE]);
+ channel = nla_get_u8(info->attrs[NL802154_ATTR_CHANNEL]);
+
+ /* check 802.15.4 constraints */
+ if (page > IEEE802154_MAX_PAGE || channel > IEEE802154_MAX_CHANNEL)
+ return -EINVAL;
+
+ return rdev_set_channel(rdev, page, channel);
+}
+
+static int nl802154_set_pan_id(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ __le16 pan_id;
+
+ /* conflict here while tx/rx calls */
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* don't change address fields on monitor */
+ if (wpan_dev->iftype == NL802154_IFTYPE_MONITOR)
+ return -EINVAL;
+
+ if (!info->attrs[NL802154_ATTR_PAN_ID])
+ return -EINVAL;
+
+ pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
+
+ return rdev_set_pan_id(rdev, wpan_dev, pan_id);
+}
+
+static int nl802154_set_short_addr(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ __le16 short_addr;
+
+ /* conflict here while tx/rx calls */
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* don't change address fields on monitor */
+ if (wpan_dev->iftype == NL802154_IFTYPE_MONITOR)
+ return -EINVAL;
+
+ if (!info->attrs[NL802154_ATTR_SHORT_ADDR])
+ return -EINVAL;
+
+ short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
+
+ return rdev_set_short_addr(rdev, wpan_dev, short_addr);
+}
+
+static int
+nl802154_set_backoff_exponent(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ u8 min_be, max_be;
+
+ /* should be set on netif open inside phy settings */
+ if (netif_running(dev))
+ return -EBUSY;
+
+ if (!info->attrs[NL802154_ATTR_MIN_BE] ||
+ !info->attrs[NL802154_ATTR_MAX_BE])
+ return -EINVAL;
+
+ min_be = nla_get_u8(info->attrs[NL802154_ATTR_MIN_BE]);
+ max_be = nla_get_u8(info->attrs[NL802154_ATTR_MAX_BE]);
+
+ /* check 802.15.4 constraints */
+ if (max_be < 3 || max_be > 8 || min_be > max_be)
+ return -EINVAL;
+
+ return rdev_set_backoff_exponent(rdev, wpan_dev, min_be, max_be);
+}
+
+static int
+nl802154_set_max_csma_backoffs(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ u8 max_csma_backoffs;
+
+ /* conflict here while other running iface settings */
+ if (netif_running(dev))
+ return -EBUSY;
+
+ if (!info->attrs[NL802154_ATTR_MAX_CSMA_BACKOFFS])
+ return -EINVAL;
+
+ max_csma_backoffs = nla_get_u8(
+ info->attrs[NL802154_ATTR_MAX_CSMA_BACKOFFS]);
+
+ /* check 802.15.4 constraints */
+ if (max_csma_backoffs > 5)
+ return -EINVAL;
+
+ return rdev_set_max_csma_backoffs(rdev, wpan_dev, max_csma_backoffs);
+}
+
+static int
+nl802154_set_max_frame_retries(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ s8 max_frame_retries;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ if (!info->attrs[NL802154_ATTR_MAX_FRAME_RETRIES])
+ return -EINVAL;
+
+ max_frame_retries = nla_get_s8(
+ info->attrs[NL802154_ATTR_MAX_FRAME_RETRIES]);
+
+ /* check 802.15.4 constraints */
+ if (max_frame_retries < -1 || max_frame_retries > 7)
+ return -EINVAL;
+
+ return rdev_set_max_frame_retries(rdev, wpan_dev, max_frame_retries);
+}
+
+static int nl802154_set_lbt_mode(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ bool mode;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ if (!info->attrs[NL802154_ATTR_LBT_MODE])
+ return -EINVAL;
+
+ mode = !!nla_get_u8(info->attrs[NL802154_ATTR_LBT_MODE]);
+ return rdev_set_lbt_mode(rdev, wpan_dev, mode);
+}
+
+#define NL802154_FLAG_NEED_WPAN_PHY 0x01
+#define NL802154_FLAG_NEED_NETDEV 0x02
+#define NL802154_FLAG_NEED_RTNL 0x04
+#define NL802154_FLAG_CHECK_NETDEV_UP 0x08
+#define NL802154_FLAG_NEED_NETDEV_UP (NL802154_FLAG_NEED_NETDEV |\
+ NL802154_FLAG_CHECK_NETDEV_UP)
+#define NL802154_FLAG_NEED_WPAN_DEV 0x10
+#define NL802154_FLAG_NEED_WPAN_DEV_UP (NL802154_FLAG_NEED_WPAN_DEV |\
+ NL802154_FLAG_CHECK_NETDEV_UP)
+
+static int nl802154_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev;
+ struct wpan_dev *wpan_dev;
+ struct net_device *dev;
+ bool rtnl = ops->internal_flags & NL802154_FLAG_NEED_RTNL;
+
+ if (rtnl)
+ rtnl_lock();
+
+ if (ops->internal_flags & NL802154_FLAG_NEED_WPAN_PHY) {
+ rdev = cfg802154_get_dev_from_info(genl_info_net(info), info);
+ if (IS_ERR(rdev)) {
+ if (rtnl)
+ rtnl_unlock();
+ return PTR_ERR(rdev);
+ }
+ info->user_ptr[0] = rdev;
+ } else if (ops->internal_flags & NL802154_FLAG_NEED_NETDEV ||
+ ops->internal_flags & NL802154_FLAG_NEED_WPAN_DEV) {
+ ASSERT_RTNL();
+ wpan_dev = __cfg802154_wpan_dev_from_attrs(genl_info_net(info),
+ info->attrs);
+ if (IS_ERR(wpan_dev)) {
+ if (rtnl)
+ rtnl_unlock();
+ return PTR_ERR(wpan_dev);
+ }
+
+ dev = wpan_dev->netdev;
+ rdev = wpan_phy_to_rdev(wpan_dev->wpan_phy);
+
+ if (ops->internal_flags & NL802154_FLAG_NEED_NETDEV) {
+ if (!dev) {
+ if (rtnl)
+ rtnl_unlock();
+ return -EINVAL;
+ }
+
+ info->user_ptr[1] = dev;
+ } else {
+ info->user_ptr[1] = wpan_dev;
+ }
+
+ if (dev) {
+ if (ops->internal_flags & NL802154_FLAG_CHECK_NETDEV_UP &&
+ !netif_running(dev)) {
+ if (rtnl)
+ rtnl_unlock();
+ return -ENETDOWN;
+ }
+
+ dev_hold(dev);
+ }
+
+ info->user_ptr[0] = rdev;
+ }
+
+ return 0;
+}
+
+static void nl802154_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
+ struct genl_info *info)
+{
+ if (info->user_ptr[1]) {
+ if (ops->internal_flags & NL802154_FLAG_NEED_WPAN_DEV) {
+ struct wpan_dev *wpan_dev = info->user_ptr[1];
+
+ if (wpan_dev->netdev)
+ dev_put(wpan_dev->netdev);
+ } else {
+ dev_put(info->user_ptr[1]);
+ }
+ }
+
+ if (ops->internal_flags & NL802154_FLAG_NEED_RTNL)
+ rtnl_unlock();
+}
+
+static const struct genl_ops nl802154_ops[] = {
+ {
+ .cmd = NL802154_CMD_GET_WPAN_PHY,
+ .doit = nl802154_get_wpan_phy,
+ .dumpit = nl802154_dump_wpan_phy,
+ .done = nl802154_dump_wpan_phy_done,
+ .policy = nl802154_policy,
+ /* can be retrieved by unprivileged users */
+ .internal_flags = NL802154_FLAG_NEED_WPAN_PHY |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_GET_INTERFACE,
+ .doit = nl802154_get_interface,
+ .dumpit = nl802154_dump_interface,
+ .policy = nl802154_policy,
+ /* can be retrieved by unprivileged users */
+ .internal_flags = NL802154_FLAG_NEED_WPAN_DEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_NEW_INTERFACE,
+ .doit = nl802154_new_interface,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_WPAN_PHY |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_DEL_INTERFACE,
+ .doit = nl802154_del_interface,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_WPAN_DEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_CHANNEL,
+ .doit = nl802154_set_channel,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_WPAN_PHY |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_PAN_ID,
+ .doit = nl802154_set_pan_id,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_SHORT_ADDR,
+ .doit = nl802154_set_short_addr,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_BACKOFF_EXPONENT,
+ .doit = nl802154_set_backoff_exponent,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_MAX_CSMA_BACKOFFS,
+ .doit = nl802154_set_max_csma_backoffs,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_MAX_FRAME_RETRIES,
+ .doit = nl802154_set_max_frame_retries,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_LBT_MODE,
+ .doit = nl802154_set_lbt_mode,
+ .policy = nl802154_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+};
+
+/* initialisation/exit functions */
+int nl802154_init(void)
+{
+ return genl_register_family_with_ops_groups(&nl802154_fam, nl802154_ops,
+ nl802154_mcgrps);
+}
+
+void nl802154_exit(void)
+{
+ genl_unregister_family(&nl802154_fam);
+}
diff --git a/net/ieee802154/nl802154.h b/net/ieee802154/nl802154.h
new file mode 100644
index 000000000000..3846a89d0958
--- /dev/null
+++ b/net/ieee802154/nl802154.h
@@ -0,0 +1,7 @@
+#ifndef __IEEE802154_NL802154_H
+#define __IEEE802154_NL802154_H
+
+int nl802154_init(void);
+void nl802154_exit(void);
+
+#endif /* __IEEE802154_NL802154_H */
diff --git a/net/ieee802154/rdev-ops.h b/net/ieee802154/rdev-ops.h
new file mode 100644
index 000000000000..aff54fbd9264
--- /dev/null
+++ b/net/ieee802154/rdev-ops.h
@@ -0,0 +1,89 @@
+#ifndef __CFG802154_RDEV_OPS
+#define __CFG802154_RDEV_OPS
+
+#include <net/cfg802154.h>
+
+#include "core.h"
+
+static inline struct net_device *
+rdev_add_virtual_intf_deprecated(struct cfg802154_registered_device *rdev,
+ const char *name, int type)
+{
+ return rdev->ops->add_virtual_intf_deprecated(&rdev->wpan_phy, name,
+ type);
+}
+
+static inline void
+rdev_del_virtual_intf_deprecated(struct cfg802154_registered_device *rdev,
+ struct net_device *dev)
+{
+ rdev->ops->del_virtual_intf_deprecated(&rdev->wpan_phy, dev);
+}
+
+static inline int
+rdev_add_virtual_intf(struct cfg802154_registered_device *rdev, char *name,
+ enum nl802154_iftype type, __le64 extended_addr)
+{
+ return rdev->ops->add_virtual_intf(&rdev->wpan_phy, name, type,
+ extended_addr);
+}
+
+static inline int
+rdev_del_virtual_intf(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev)
+{
+ return rdev->ops->del_virtual_intf(&rdev->wpan_phy, wpan_dev);
+}
+
+static inline int
+rdev_set_channel(struct cfg802154_registered_device *rdev, u8 page, u8 channel)
+{
+ return rdev->ops->set_channel(&rdev->wpan_phy, page, channel);
+}
+
+static inline int
+rdev_set_pan_id(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, __le16 pan_id)
+{
+ return rdev->ops->set_pan_id(&rdev->wpan_phy, wpan_dev, pan_id);
+}
+
+static inline int
+rdev_set_short_addr(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, __le16 short_addr)
+{
+ return rdev->ops->set_short_addr(&rdev->wpan_phy, wpan_dev, short_addr);
+}
+
+static inline int
+rdev_set_backoff_exponent(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, u8 min_be, u8 max_be)
+{
+ return rdev->ops->set_backoff_exponent(&rdev->wpan_phy, wpan_dev,
+ min_be, max_be);
+}
+
+static inline int
+rdev_set_max_csma_backoffs(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, u8 max_csma_backoffs)
+{
+ return rdev->ops->set_max_csma_backoffs(&rdev->wpan_phy, wpan_dev,
+ max_csma_backoffs);
+}
+
+static inline int
+rdev_set_max_frame_retries(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, s8 max_frame_retries)
+{
+ return rdev->ops->set_max_frame_retries(&rdev->wpan_phy, wpan_dev,
+ max_frame_retries);
+}
+
+static inline int
+rdev_set_lbt_mode(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, bool mode)
+{
+ return rdev->ops->set_lbt_mode(&rdev->wpan_phy, wpan_dev, mode);
+}
+
+#endif /* __CFG802154_RDEV_OPS */
diff --git a/net/ieee802154/sysfs.c b/net/ieee802154/sysfs.c
index eb9ca6f99122..1613b9c65dfa 100644
--- a/net/ieee802154/sysfs.c
+++ b/net/ieee802154/sysfs.c
@@ -17,6 +17,37 @@
#include <net/cfg802154.h>
+#include "core.h"
+#include "sysfs.h"
+
+static inline struct cfg802154_registered_device *
+dev_to_rdev(struct device *dev)
+{
+ return container_of(dev, struct cfg802154_registered_device,
+ wpan_phy.dev);
+}
+
+#define SHOW_FMT(name, fmt, member) \
+static ssize_t name ## _show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return sprintf(buf, fmt "\n", dev_to_rdev(dev)->member); \
+} \
+static DEVICE_ATTR_RO(name)
+
+SHOW_FMT(index, "%d", wpan_phy_idx);
+
+static ssize_t name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct wpan_phy *wpan_phy = &dev_to_rdev(dev)->wpan_phy;
+
+ return sprintf(buf, "%s\n", dev_name(&wpan_phy->dev));
+}
+static DEVICE_ATTR_RO(name);
+
#define MASTER_SHOW_COMPLEX(name, format_string, args...) \
static ssize_t name ## _show(struct device *dev, \
struct device_attribute *attr, char *buf) \
@@ -60,14 +91,17 @@ static ssize_t channels_supported_show(struct device *dev,
}
static DEVICE_ATTR_RO(channels_supported);
-static void wpan_phy_release(struct device *d)
+static void wpan_phy_release(struct device *dev)
{
- struct wpan_phy *phy = container_of(d, struct wpan_phy, dev);
+ struct cfg802154_registered_device *rdev = dev_to_rdev(dev);
- kfree(phy);
+ cfg802154_dev_free(rdev);
}
static struct attribute *pmib_attrs[] = {
+ &dev_attr_index.attr,
+ &dev_attr_name.attr,
+ /* below will be removed soon */
&dev_attr_current_channel.attr,
&dev_attr_current_page.attr,
&dev_attr_channels_supported.attr,
diff --git a/net/mac80211/aes_ccm.c b/net/mac80211/aes_ccm.c
index ec24378caaaf..09d9caaec591 100644
--- a/net/mac80211/aes_ccm.c
+++ b/net/mac80211/aes_ccm.c
@@ -53,6 +53,9 @@ int ieee80211_aes_ccm_decrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad,
__aligned(__alignof__(struct aead_request));
struct aead_request *aead_req = (void *) aead_req_data;
+ if (data_len == 0)
+ return -EINVAL;
+
memset(aead_req, 0, sizeof(aead_req_data));
sg_init_one(&pt, data, data_len);
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 9242c60048cf..a360c15cc978 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -509,6 +509,10 @@ int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid,
struct tid_ampdu_tx *tid_tx;
int ret = 0;
+ if (WARN(sta->reserved_tid == tid,
+ "Requested to start BA session on reserved tid=%d", tid))
+ return -EINVAL;
+
trace_api_start_tx_ba_session(pubsta, tid);
if (WARN_ON_ONCE(!local->ops->ampdu_action))
@@ -765,6 +769,9 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid)
goto unlock;
}
+ WARN(sta->reserved_tid == tid,
+ "Requested to stop BA session on reserved tid=%d", tid);
+
if (test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) {
/* already in progress stopping it */
ret = 0;
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 06185940cbb6..e75d5c53e97b 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1042,6 +1042,13 @@ static int sta_apply_parameters(struct ieee80211_local *local,
clear_sta_flag(sta, WLAN_STA_TDLS_PEER);
}
+ /* mark TDLS channel switch support, if the AP allows it */
+ if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
+ !sdata->u.mgd.tdls_chan_switch_prohibited &&
+ params->ext_capab_len >= 4 &&
+ params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH)
+ set_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH);
+
if (params->sta_modify_mask & STATION_PARAM_APPLY_UAPSD) {
sta->sta.uapsd_queues = params->uapsd_queues;
sta->sta.max_sp = params->max_sp;
@@ -3158,6 +3165,12 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
goto out;
}
+ ch_switch.timestamp = 0;
+ ch_switch.device_timestamp = 0;
+ ch_switch.block_tx = params->block_tx;
+ ch_switch.chandef = params->chandef;
+ ch_switch.count = params->count;
+
err = drv_pre_channel_switch(sdata, &ch_switch);
if (err)
goto out;
@@ -3175,12 +3188,6 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
goto out;
}
- ch_switch.timestamp = 0;
- ch_switch.device_timestamp = 0;
- ch_switch.block_tx = params->block_tx;
- ch_switch.chandef = params->chandef;
- ch_switch.count = params->count;
-
err = ieee80211_set_csa_beacon(sdata, params, &changed);
if (err) {
ieee80211_vif_unreserve_chanctx(sdata);
@@ -3195,6 +3202,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
ieee80211_stop_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
+ cfg80211_ch_switch_started_notify(sdata->dev, &sdata->csa_chandef,
+ params->count);
+
if (changed) {
ieee80211_bss_info_change_notify(sdata, changed);
drv_channel_switch_beacon(sdata, &params->chandef);
@@ -3511,6 +3521,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS |
IEEE80211_TX_INTFL_NL80211_FRAME_TX;
+ info->band = band;
skb_set_queue_mapping(skb, IEEE80211_AC_VO);
skb->priority = 7;
@@ -3518,7 +3529,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
nullfunc->qos_ctrl = cpu_to_le16(7);
local_bh_disable();
- ieee80211_xmit(sdata, skb, band);
+ ieee80211_xmit(sdata, skb);
local_bh_enable();
rcu_read_unlock();
@@ -3741,6 +3752,8 @@ const struct cfg80211_ops mac80211_config_ops = {
.set_rekey_data = ieee80211_set_rekey_data,
.tdls_oper = ieee80211_tdls_oper,
.tdls_mgmt = ieee80211_tdls_mgmt,
+ .tdls_channel_switch = ieee80211_tdls_channel_switch,
+ .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch,
.probe_client = ieee80211_probe_client,
.set_noack_map = ieee80211_set_noack_map,
#ifdef CONFIG_PM
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c
index bafe48916229..94c70091bbd7 100644
--- a/net/mac80211/debugfs_sta.c
+++ b/net/mac80211/debugfs_sta.c
@@ -74,7 +74,7 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf,
test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : ""
int res = scnprintf(buf, sizeof(buf),
- "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
TEST(AUTH), TEST(ASSOC), TEST(PS_STA),
TEST(PS_DRIVER), TEST(AUTHORIZED),
TEST(SHORT_PREAMBLE),
@@ -82,10 +82,11 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf,
TEST(WDS), TEST(CLEAR_PS_FILT),
TEST(MFP), TEST(BLOCK_BA), TEST(PSPOLL),
TEST(UAPSD), TEST(SP), TEST(TDLS_PEER),
- TEST(TDLS_PEER_AUTH), TEST(4ADDR_EVENT),
- TEST(INSERTED), TEST(RATE_CONTROL),
- TEST(TOFFSET_KNOWN), TEST(MPSP_OWNER),
- TEST(MPSP_RECIPIENT));
+ TEST(TDLS_PEER_AUTH), TEST(TDLS_INITIATOR),
+ TEST(TDLS_CHAN_SWITCH), TEST(TDLS_OFF_CHANNEL),
+ TEST(4ADDR_EVENT), TEST(INSERTED),
+ TEST(RATE_CONTROL), TEST(TOFFSET_KNOWN),
+ TEST(MPSP_OWNER), TEST(MPSP_RECIPIENT));
#undef TEST
return simple_read_from_buffer(userbuf, count, ppos, buf, res);
}
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 9759dd1f0734..2ebc9ead9695 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -380,23 +380,26 @@ static inline int drv_sched_scan_stop(struct ieee80211_local *local,
return ret;
}
-static inline void drv_sw_scan_start(struct ieee80211_local *local)
+static inline void drv_sw_scan_start(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ const u8 *mac_addr)
{
might_sleep();
- trace_drv_sw_scan_start(local);
+ trace_drv_sw_scan_start(local, sdata, mac_addr);
if (local->ops->sw_scan_start)
- local->ops->sw_scan_start(&local->hw);
+ local->ops->sw_scan_start(&local->hw, &sdata->vif, mac_addr);
trace_drv_return_void(local);
}
-static inline void drv_sw_scan_complete(struct ieee80211_local *local)
+static inline void drv_sw_scan_complete(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata)
{
might_sleep();
- trace_drv_sw_scan_complete(local);
+ trace_drv_sw_scan_complete(local, sdata);
if (local->ops->sw_scan_complete)
- local->ops->sw_scan_complete(&local->hw);
+ local->ops->sw_scan_complete(&local->hw, &sdata->vif);
trace_drv_return_void(local);
}
@@ -621,6 +624,21 @@ static inline void drv_sta_rc_update(struct ieee80211_local *local,
trace_drv_return_void(local);
}
+static inline void drv_sta_rate_tbl_update(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta)
+{
+ sdata = get_bss_sdata(sdata);
+ if (!check_sdata_in_driver(sdata))
+ return;
+
+ trace_drv_sta_rate_tbl_update(local, sdata, sta);
+ if (local->ops->sta_rate_tbl_update)
+ local->ops->sta_rate_tbl_update(&local->hw, &sdata->vif, sta);
+
+ trace_drv_return_void(local);
+}
+
static inline int drv_conf_tx(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata, u16 ac,
const struct ieee80211_tx_queue_params *params)
@@ -1296,4 +1314,57 @@ static inline int drv_get_txpower(struct ieee80211_local *local,
return ret;
}
+static inline int
+drv_tdls_channel_switch(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta, u8 oper_class,
+ struct cfg80211_chan_def *chandef,
+ struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie)
+{
+ int ret;
+
+ might_sleep();
+ if (!check_sdata_in_driver(sdata))
+ return -EIO;
+
+ if (!local->ops->tdls_channel_switch)
+ return -EOPNOTSUPP;
+
+ trace_drv_tdls_channel_switch(local, sdata, sta, oper_class, chandef);
+ ret = local->ops->tdls_channel_switch(&local->hw, &sdata->vif, sta,
+ oper_class, chandef, tmpl_skb,
+ ch_sw_tm_ie);
+ trace_drv_return_int(local, ret);
+ return ret;
+}
+
+static inline void
+drv_tdls_cancel_channel_switch(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta)
+{
+ might_sleep();
+ if (!check_sdata_in_driver(sdata))
+ return;
+
+ if (!local->ops->tdls_cancel_channel_switch)
+ return;
+
+ trace_drv_tdls_cancel_channel_switch(local, sdata, sta);
+ local->ops->tdls_cancel_channel_switch(&local->hw, &sdata->vif, sta);
+ trace_drv_return_void(local);
+}
+
+static inline void
+drv_tdls_recv_channel_switch(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_tdls_ch_sw_params *params)
+{
+ trace_drv_tdls_recv_channel_switch(local, sdata, params);
+ if (local->ops->tdls_recv_channel_switch)
+ local->ops->tdls_recv_channel_switch(&local->hw, &sdata->vif,
+ params);
+ trace_drv_return_void(local);
+}
+
#endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 842e0661fb57..cc6e964d9837 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -525,8 +525,13 @@ struct ieee80211_if_managed {
struct ieee80211_vht_cap vht_capa; /* configured VHT overrides */
struct ieee80211_vht_cap vht_capa_mask; /* Valid parts of vht_capa */
+ /* TDLS support */
u8 tdls_peer[ETH_ALEN] __aligned(2);
struct delayed_work tdls_peer_del_work;
+ struct sk_buff *orig_teardown_skb; /* The original teardown skb */
+ struct sk_buff *teardown_skb; /* A copy to send through the AP */
+ spinlock_t teardown_lock; /* To lock changing teardown_skb */
+ bool tdls_chan_switch_prohibited;
/* WMM-AC TSPEC support */
struct ieee80211_sta_tx_tspec tx_tspec[IEEE80211_NUM_ACS];
@@ -988,6 +993,7 @@ enum sdata_queue_type {
IEEE80211_SDATA_QUEUE_AGG_STOP = 2,
IEEE80211_SDATA_QUEUE_RX_AGG_START = 3,
IEEE80211_SDATA_QUEUE_RX_AGG_STOP = 4,
+ IEEE80211_SDATA_QUEUE_TDLS_CHSW = 5,
};
enum {
@@ -1005,6 +1011,7 @@ enum queue_stop_reason {
IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL,
IEEE80211_QUEUE_STOP_REASON_FLUSH,
IEEE80211_QUEUE_STOP_REASON_TDLS_TEARDOWN,
+ IEEE80211_QUEUE_STOP_REASON_RESERVE_TID,
IEEE80211_QUEUE_STOP_REASONS,
};
@@ -1231,7 +1238,7 @@ struct ieee80211_local {
unsigned long scanning;
struct cfg80211_ssid scan_ssid;
struct cfg80211_scan_request *int_scan_req;
- struct cfg80211_scan_request *scan_req;
+ struct cfg80211_scan_request __rcu *scan_req;
struct ieee80211_scan_request *hw_scan_req;
struct cfg80211_chan_def scan_chandef;
enum ieee80211_band hw_scan_band;
@@ -1241,7 +1248,8 @@ struct ieee80211_local {
struct work_struct sched_scan_stopped_work;
struct ieee80211_sub_if_data __rcu *sched_scan_sdata;
- struct cfg80211_sched_scan_request *sched_scan_req;
+ struct cfg80211_sched_scan_request __rcu *sched_scan_req;
+ u8 scan_addr[ETH_ALEN];
unsigned long leave_oper_channel_time;
enum mac80211_scan_state next_scan_state;
@@ -1395,6 +1403,9 @@ struct ieee802_11_elems {
size_t total_len;
/* pointers to IEs */
+ const struct ieee80211_tdls_lnkie *lnk_id;
+ const struct ieee80211_ch_switch_timing *ch_sw_timing;
+ const u8 *ext_capab;
const u8 *ssid;
const u8 *supp_rates;
const u8 *ds_params;
@@ -1429,6 +1440,7 @@ struct ieee802_11_elems {
const struct ieee80211_mesh_chansw_params_ie *mesh_chansw_params_ie;
/* length of them, respectively */
+ u8 ext_capab_len;
u8 ssid_len;
u8 supp_rates_len;
u8 tim_len;
@@ -1625,8 +1637,14 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
struct net_device *dev);
netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
struct net_device *dev);
+void __ieee80211_subif_start_xmit(struct sk_buff *skb,
+ struct net_device *dev,
+ u32 info_flags);
void ieee80211_purge_tx_queue(struct ieee80211_hw *hw,
struct sk_buff_head *skbs);
+struct sk_buff *
+ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u32 info_flags);
/* HT */
void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
@@ -1753,8 +1771,7 @@ 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,
bool bss_notify);
-void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,
- enum ieee80211_band band);
+void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb);
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, int tid,
@@ -1865,6 +1882,9 @@ void ieee80211_add_pending_skbs(struct ieee80211_local *local,
struct sk_buff_head *skbs);
void ieee80211_flush_queues(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata);
+void __ieee80211_flush_queues(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ unsigned int queues);
void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
u16 transaction, u16 auth_alg, u16 status,
@@ -1881,12 +1901,14 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
u8 bands_used, u32 *rate_masks,
struct cfg80211_chan_def *chandef);
struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
- u8 *dst, u32 ratemask,
+ const u8 *src, const u8 *dst,
+ u32 ratemask,
struct ieee80211_channel *chan,
const u8 *ssid, size_t ssid_len,
const u8 *ie, size_t ie_len,
bool directed);
-void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
+void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata,
+ const u8 *src, const u8 *dst,
const u8 *ssid, size_t ssid_len,
const u8 *ie, size_t ie_len,
u32 ratemask, bool directed, u32 tx_flags,
@@ -1992,6 +2014,14 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
const u8 *peer, enum nl80211_tdls_operation oper);
void ieee80211_tdls_peer_del_work(struct work_struct *wk);
+int ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *addr, u8 oper_class,
+ struct cfg80211_chan_def *chandef);
+void ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *addr);
+void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
extern const struct ethtool_ops ieee80211_ethtool_ops;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 9df26adb864a..538fe4ef5c85 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1208,6 +1208,8 @@ static void ieee80211_iface_work(struct work_struct *work)
WLAN_BACK_RECIPIENT, 0,
false);
mutex_unlock(&local->sta_mtx);
+ } else if (skb->pkt_type == IEEE80211_SDATA_QUEUE_TDLS_CHSW) {
+ ieee80211_process_tdls_channel_switch(sdata, skb);
} else if (ieee80211_is_action(mgmt->frame_control) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK) {
int len = skb->len;
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 282a4f36eb92..6ab99da38db9 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -764,6 +764,12 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
local->hw.offchannel_tx_hw_queue >= local->hw.queues))
return -EINVAL;
+ if ((hw->wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) &&
+ (!local->ops->tdls_channel_switch ||
+ !local->ops->tdls_cancel_channel_switch ||
+ !local->ops->tdls_recv_channel_switch))
+ return -EOPNOTSUPP;
+
#ifdef CONFIG_PM
if (hw->wiphy->wowlan && (!local->ops->suspend || !local->ops->resume))
return -EINVAL;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 0d166e766dad..ba06cd003375 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1049,6 +1049,8 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_sub_if_data *sdata)
sdata->csa_block_tx = false;
}
+ cfg80211_ch_switch_notify(sdata->dev, &sdata->reserved_chandef);
+
sdata->vif.csa_active = false;
ifmgd->csa_waiting_bcn = false;
@@ -1205,6 +1207,9 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
mutex_unlock(&local->mtx);
+ cfg80211_ch_switch_started_notify(sdata->dev, &csa_ie.chandef,
+ csa_ie.count);
+
if (local->ops->channel_switch) {
/* use driver's channel switch callback */
drv_channel_switch(local, sdata, &ch_switch);
@@ -2221,7 +2226,8 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
else
ssid_len = ssid[1];
- ieee80211_send_probe_req(sdata, dst, ssid + 2, ssid_len, NULL,
+ ieee80211_send_probe_req(sdata, sdata->vif.addr, NULL,
+ ssid + 2, ssid_len, NULL,
0, (u32) -1, true, 0,
ifmgd->associated->channel, false);
rcu_read_unlock();
@@ -2324,7 +2330,7 @@ struct sk_buff *ieee80211_ap_probereq_get(struct ieee80211_hw *hw,
else
ssid_len = ssid[1];
- skb = ieee80211_build_probe_req(sdata, cbss->bssid,
+ skb = ieee80211_build_probe_req(sdata, sdata->vif.addr, cbss->bssid,
(u32) -1, cbss->channel,
ssid + 2, ssid_len,
NULL, 0, true);
@@ -2798,6 +2804,9 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
}
ifmgd->aid = aid;
+ ifmgd->tdls_chan_switch_prohibited =
+ elems.ext_capab && elems.ext_capab_len >= 5 &&
+ (elems.ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED);
/*
* Some APs are erroneously not including some information in their
@@ -3642,7 +3651,8 @@ static int ieee80211_probe_auth(struct ieee80211_sub_if_data *sdata)
* Direct probe is sent to broadcast address as some APs
* will not answer to direct packet in unassociated state.
*/
- ieee80211_send_probe_req(sdata, NULL, ssidie + 2, ssidie[1],
+ ieee80211_send_probe_req(sdata, sdata->vif.addr, NULL,
+ ssidie + 2, ssidie[1],
NULL, 0, (u32) -1, true, 0,
auth_data->bss->channel, false);
rcu_read_unlock();
@@ -3999,6 +4009,11 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
ifmgd->req_smps = IEEE80211_SMPS_AUTOMATIC;
else
ifmgd->req_smps = IEEE80211_SMPS_OFF;
+
+ /* Setup TDLS data */
+ spin_lock_init(&ifmgd->teardown_lock);
+ ifmgd->teardown_skb = NULL;
+ ifmgd->orig_teardown_skb = NULL;
}
/* scan finished notification */
@@ -4861,6 +4876,13 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata)
}
if (ifmgd->auth_data)
ieee80211_destroy_auth_data(sdata, false);
+ spin_lock_bh(&ifmgd->teardown_lock);
+ if (ifmgd->teardown_skb) {
+ kfree_skb(ifmgd->teardown_skb);
+ ifmgd->teardown_skb = NULL;
+ ifmgd->orig_teardown_skb = NULL;
+ }
+ spin_unlock_bh(&ifmgd->teardown_lock);
del_timer_sync(&ifmgd->timer);
sdata_unlock(sdata);
}
diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index 6081329784dd..08ab7d6d1517 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -385,7 +385,7 @@ static void rate_idx_match_mask(struct ieee80211_tx_rate *rate,
*rate = alt_rate;
return;
}
- } else {
+ } else if (!(rate->flags & IEEE80211_TX_RC_VHT_MCS)) {
/* handle legacy rates */
if (rate_idx_match_legacy_mask(rate, sband->n_bitrates, mask))
return;
@@ -696,6 +696,7 @@ int rate_control_set_rates(struct ieee80211_hw *hw,
struct ieee80211_sta *pubsta,
struct ieee80211_sta_rates *rates)
{
+ struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
struct ieee80211_sta_rates *old;
/*
@@ -709,6 +710,8 @@ int rate_control_set_rates(struct ieee80211_hw *hw,
if (old)
kfree_rcu(old, rcu_head);
+ drv_sta_rate_tbl_update(hw_to_local(hw), sta->sdata, pubsta);
+
return 0;
}
EXPORT_SYMBOL(rate_control_set_rates);
diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c
index c50fd94d2aef..62ff7cfb2723 100644
--- a/net/mac80211/rc80211_minstrel_ht.c
+++ b/net/mac80211/rc80211_minstrel_ht.c
@@ -690,6 +690,9 @@ minstrel_aggr_check(struct ieee80211_sta *pubsta, struct sk_buff *skb)
struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
u16 tid;
+ if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO)
+ return;
+
if (unlikely(!ieee80211_is_data_qos(hdr->frame_control)))
return;
@@ -700,9 +703,6 @@ minstrel_aggr_check(struct ieee80211_sta *pubsta, struct sk_buff *skb)
if (likely(sta->ampdu_mlme.tid_tx[tid]))
return;
- if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO)
- return;
-
ieee80211_start_tx_ba_session(pubsta, tid, 5000);
}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index a726bb169302..49c23bdf08bb 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -39,7 +39,8 @@
* only useful for monitoring.
*/
static struct sk_buff *remove_monitor_info(struct ieee80211_local *local,
- struct sk_buff *skb)
+ struct sk_buff *skb,
+ unsigned int rtap_vendor_space)
{
if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) {
if (likely(skb->len > FCS_LEN))
@@ -52,20 +53,25 @@ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local,
}
}
+ __pskb_pull(skb, rtap_vendor_space);
+
return skb;
}
-static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len)
+static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len,
+ unsigned int rtap_vendor_space)
{
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
- struct ieee80211_hdr *hdr = (void *)skb->data;
+ struct ieee80211_hdr *hdr;
+
+ hdr = (void *)(skb->data + rtap_vendor_space);
if (status->flag & (RX_FLAG_FAILED_FCS_CRC |
RX_FLAG_FAILED_PLCP_CRC |
RX_FLAG_AMPDU_IS_ZEROLEN))
return true;
- if (unlikely(skb->len < 16 + present_fcs_len))
+ if (unlikely(skb->len < 16 + present_fcs_len + rtap_vendor_space))
return true;
if (ieee80211_is_ctl(hdr->frame_control) &&
@@ -77,8 +83,9 @@ static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len)
}
static int
-ieee80211_rx_radiotap_space(struct ieee80211_local *local,
- struct ieee80211_rx_status *status)
+ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,
+ struct ieee80211_rx_status *status,
+ struct sk_buff *skb)
{
int len;
@@ -121,6 +128,21 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local,
len += 2 * hweight8(status->chains);
}
+ if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+ struct ieee80211_vendor_radiotap *rtap = (void *)skb->data;
+
+ /* vendor presence bitmap */
+ len += 4;
+ /* alignment for fixed 6-byte vendor data header */
+ len = ALIGN(len, 2);
+ /* vendor data header */
+ len += 6;
+ if (WARN_ON(rtap->align == 0))
+ rtap->align = 1;
+ len = ALIGN(len, rtap->align);
+ len += rtap->len + rtap->pad;
+ }
+
return len;
}
@@ -144,13 +166,20 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
u16 channel_flags = 0;
int mpdulen, chain;
unsigned long chains = status->chains;
+ struct ieee80211_vendor_radiotap rtap = {};
+
+ if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+ rtap = *(struct ieee80211_vendor_radiotap *)skb->data;
+ /* rtap.len and rtap.pad are undone immediately */
+ skb_pull(skb, sizeof(rtap) + rtap.len + rtap.pad);
+ }
mpdulen = skb->len;
if (!(has_fcs && (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS)))
mpdulen += FCS_LEN;
rthdr = (struct ieee80211_radiotap_header *)skb_push(skb, rtap_len);
- memset(rthdr, 0, rtap_len);
+ memset(rthdr, 0, rtap_len - rtap.len - rtap.pad);
it_present = &rthdr->it_present;
/* radiotap header, set always present flags */
@@ -172,6 +201,14 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
}
+ if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+ it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) |
+ BIT(IEEE80211_RADIOTAP_EXT);
+ put_unaligned_le32(it_present_val, it_present);
+ it_present++;
+ it_present_val = rtap.present;
+ }
+
put_unaligned_le32(it_present_val, it_present);
pos = (void *)(it_present + 1);
@@ -366,6 +403,22 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
*pos++ = status->chain_signal[chain];
*pos++ = chain;
}
+
+ if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+ /* ensure 2 byte alignment for the vendor field as required */
+ if ((pos - (u8 *)rthdr) & 1)
+ *pos++ = 0;
+ *pos++ = rtap.oui[0];
+ *pos++ = rtap.oui[1];
+ *pos++ = rtap.oui[2];
+ *pos++ = rtap.subns;
+ put_unaligned_le16(rtap.len, pos);
+ pos += 2;
+ /* align the actual payload as requested */
+ while ((pos - (u8 *)rthdr) & (rtap.align - 1))
+ *pos++ = 0;
+ /* data (and possible padding) already follows */
+ }
}
/*
@@ -379,10 +432,17 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
{
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(origskb);
struct ieee80211_sub_if_data *sdata;
- int needed_headroom;
+ int rt_hdrlen, needed_headroom;
struct sk_buff *skb, *skb2;
struct net_device *prev_dev = NULL;
int present_fcs_len = 0;
+ unsigned int rtap_vendor_space = 0;
+
+ if (unlikely(status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA)) {
+ struct ieee80211_vendor_radiotap *rtap = (void *)origskb->data;
+
+ rtap_vendor_space = sizeof(*rtap) + rtap->len + rtap->pad;
+ }
/*
* First, we may need to make a copy of the skb because
@@ -396,25 +456,27 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS)
present_fcs_len = FCS_LEN;
- /* ensure hdr->frame_control is in skb head */
- if (!pskb_may_pull(origskb, 2)) {
+ /* ensure hdr->frame_control and vendor radiotap data are in skb head */
+ if (!pskb_may_pull(origskb, 2 + rtap_vendor_space)) {
dev_kfree_skb(origskb);
return NULL;
}
if (!local->monitors) {
- if (should_drop_frame(origskb, present_fcs_len)) {
+ if (should_drop_frame(origskb, present_fcs_len,
+ rtap_vendor_space)) {
dev_kfree_skb(origskb);
return NULL;
}
- return remove_monitor_info(local, origskb);
+ return remove_monitor_info(local, origskb, rtap_vendor_space);
}
/* room for the radiotap header based on driver features */
- needed_headroom = ieee80211_rx_radiotap_space(local, status);
+ rt_hdrlen = ieee80211_rx_radiotap_hdrlen(local, status, origskb);
+ needed_headroom = rt_hdrlen - rtap_vendor_space;
- if (should_drop_frame(origskb, present_fcs_len)) {
+ if (should_drop_frame(origskb, present_fcs_len, rtap_vendor_space)) {
/* only need to expand headroom if necessary */
skb = origskb;
origskb = NULL;
@@ -438,15 +500,15 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
*/
skb = skb_copy_expand(origskb, needed_headroom, 0, GFP_ATOMIC);
- origskb = remove_monitor_info(local, origskb);
+ origskb = remove_monitor_info(local, origskb,
+ rtap_vendor_space);
if (!skb)
return origskb;
}
/* prepend radiotap information */
- ieee80211_add_rx_radiotap_header(local, skb, rate, needed_headroom,
- true);
+ ieee80211_add_rx_radiotap_header(local, skb, rate, rt_hdrlen, true);
skb_reset_mac_header(skb);
skb->ip_summed = CHECKSUM_UNNECESSARY;
@@ -985,7 +1047,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
}
static ieee80211_rx_result debug_noinline
-ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
+ieee80211_rx_h_check_dup(struct ieee80211_rx_data *rx)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
@@ -994,10 +1056,16 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
* Drop duplicate 802.11 retransmissions
* (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery")
*/
- if (rx->skb->len >= 24 && rx->sta &&
- !ieee80211_is_ctl(hdr->frame_control) &&
- !ieee80211_is_qos_nullfunc(hdr->frame_control) &&
- !is_multicast_ether_addr(hdr->addr1)) {
+
+ if (rx->skb->len < 24)
+ return RX_CONTINUE;
+
+ if (ieee80211_is_ctl(hdr->frame_control) ||
+ ieee80211_is_qos_nullfunc(hdr->frame_control) ||
+ is_multicast_ether_addr(hdr->addr1))
+ return RX_CONTINUE;
+
+ if (rx->sta) {
if (unlikely(ieee80211_has_retry(hdr->frame_control) &&
rx->sta->last_seq_ctrl[rx->seqno_idx] ==
hdr->seq_ctrl)) {
@@ -1011,6 +1079,14 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
}
}
+ return RX_CONTINUE;
+}
+
+static ieee80211_rx_result debug_noinline
+ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
+
if (unlikely(rx->skb->len < 16)) {
I802_DEBUG_INC(rx->local->rx_handlers_drop_short);
return RX_DROP_MONITOR;
@@ -2257,6 +2333,27 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
if (!ieee80211_frame_allowed(rx, fc))
return RX_DROP_MONITOR;
+ /* directly handle TDLS channel switch requests/responses */
+ if (unlikely(((struct ethhdr *)rx->skb->data)->h_proto ==
+ cpu_to_be16(ETH_P_TDLS))) {
+ struct ieee80211_tdls_data *tf = (void *)rx->skb->data;
+
+ if (pskb_may_pull(rx->skb,
+ offsetof(struct ieee80211_tdls_data, u)) &&
+ tf->payload_type == WLAN_TDLS_SNAP_RFTYPE &&
+ tf->category == WLAN_CATEGORY_TDLS &&
+ (tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
+ tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
+ rx->skb->pkt_type = IEEE80211_SDATA_QUEUE_TDLS_CHSW;
+ skb_queue_tail(&sdata->skb_queue, rx->skb);
+ ieee80211_queue_work(&rx->local->hw, &sdata->work);
+ if (rx->sta)
+ rx->sta->rx_packets++;
+
+ return RX_QUEUED;
+ }
+ }
+
if (rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
unlikely(port_control) && sdata->bss) {
sdata = container_of(sdata->bss, struct ieee80211_sub_if_data,
@@ -2892,8 +2989,10 @@ static void ieee80211_rx_cooked_monitor(struct ieee80211_rx_data *rx,
if (!local->cooked_mntrs)
goto out_free_skb;
+ /* vendor data is long removed here */
+ status->flag &= ~RX_FLAG_RADIOTAP_VENDOR_DATA;
/* room for the radiotap header based on driver features */
- needed_headroom = ieee80211_rx_radiotap_space(local, status);
+ needed_headroom = ieee80211_rx_radiotap_hdrlen(local, status, skb);
if (skb_headroom(skb) < needed_headroom &&
pskb_expand_head(skb, needed_headroom, 0, GFP_ATOMIC))
@@ -3046,6 +3145,7 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
goto rxh_next; \
} while (0);
+ CALL_RXH(ieee80211_rx_h_check_dup)
CALL_RXH(ieee80211_rx_h_check)
ieee80211_rx_reorder_ampdu(rx, &reorder_release);
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index af0d094b2f2f..ae842678b629 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -184,9 +184,21 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
return;
if (ieee80211_is_probe_resp(mgmt->frame_control)) {
- /* ignore ProbeResp to foreign address */
- if ((!sdata1 || !ether_addr_equal(mgmt->da, sdata1->vif.addr)) &&
- (!sdata2 || !ether_addr_equal(mgmt->da, sdata2->vif.addr)))
+ struct cfg80211_scan_request *scan_req;
+ struct cfg80211_sched_scan_request *sched_scan_req;
+
+ scan_req = rcu_dereference(local->scan_req);
+ sched_scan_req = rcu_dereference(local->sched_scan_req);
+
+ /* ignore ProbeResp to foreign address unless scanning
+ * with randomised address
+ */
+ if (!(sdata1 &&
+ (ether_addr_equal(mgmt->da, sdata1->vif.addr) ||
+ scan_req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR)) &&
+ !(sdata2 &&
+ (ether_addr_equal(mgmt->da, sdata2->vif.addr) ||
+ sched_scan_req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR)))
return;
elements = mgmt->u.probe_resp.variable;
@@ -234,11 +246,14 @@ ieee80211_prepare_scan_chandef(struct cfg80211_chan_def *chandef,
/* return false if no more work */
static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
{
- struct cfg80211_scan_request *req = local->scan_req;
+ struct cfg80211_scan_request *req;
struct cfg80211_chan_def chandef;
u8 bands_used = 0;
int i, ielen, n_chans;
+ req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
+
if (test_bit(SCAN_HW_CANCELLED, &local->scanning))
return false;
@@ -281,6 +296,9 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
bands_used, req->rates, &chandef);
local->hw_scan_req->req.ie_len = ielen;
local->hw_scan_req->req.no_cck = req->no_cck;
+ ether_addr_copy(local->hw_scan_req->req.mac_addr, req->mac_addr);
+ ether_addr_copy(local->hw_scan_req->req.mac_addr_mask,
+ req->mac_addr_mask);
return true;
}
@@ -290,6 +308,8 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
struct ieee80211_local *local = hw_to_local(hw);
bool hw_scan = local->ops->hw_scan;
bool was_scanning = local->scanning;
+ struct cfg80211_scan_request *scan_req;
+ struct ieee80211_sub_if_data *scan_sdata;
lockdep_assert_held(&local->mtx);
@@ -322,9 +342,15 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
kfree(local->hw_scan_req);
local->hw_scan_req = NULL;
- if (local->scan_req != local->int_scan_req)
- cfg80211_scan_done(local->scan_req, aborted);
- local->scan_req = NULL;
+ scan_req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
+
+ if (scan_req != local->int_scan_req)
+ cfg80211_scan_done(scan_req, aborted);
+ RCU_INIT_POINTER(local->scan_req, NULL);
+
+ scan_sdata = rcu_dereference_protected(local->scan_sdata,
+ lockdep_is_held(&local->mtx));
RCU_INIT_POINTER(local->scan_sdata, NULL);
local->scanning = 0;
@@ -335,7 +361,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
if (!hw_scan) {
ieee80211_configure_filter(local);
- drv_sw_scan_complete(local);
+ drv_sw_scan_complete(local, scan_sdata);
ieee80211_offchannel_return(local);
}
@@ -361,7 +387,8 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
}
EXPORT_SYMBOL(ieee80211_scan_completed);
-static int ieee80211_start_sw_scan(struct ieee80211_local *local)
+static int ieee80211_start_sw_scan(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata)
{
/* Software scan is not supported in multi-channel cases */
if (local->use_chanctx)
@@ -380,7 +407,7 @@ static int ieee80211_start_sw_scan(struct ieee80211_local *local)
* nullfunc frames and probe requests will be dropped in
* ieee80211_tx_h_check_assoc().
*/
- drv_sw_scan_start(local);
+ drv_sw_scan_start(local, sdata, local->scan_addr);
local->leave_oper_channel_time = jiffies;
local->next_scan_state = SCAN_DECISION;
@@ -440,23 +467,26 @@ static void ieee80211_scan_state_send_probe(struct ieee80211_local *local,
{
int i;
struct ieee80211_sub_if_data *sdata;
+ struct cfg80211_scan_request *scan_req;
enum ieee80211_band band = local->hw.conf.chandef.chan->band;
u32 tx_flags;
+ scan_req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
+
tx_flags = IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
- if (local->scan_req->no_cck)
+ if (scan_req->no_cck)
tx_flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
sdata = rcu_dereference_protected(local->scan_sdata,
lockdep_is_held(&local->mtx));
- for (i = 0; i < local->scan_req->n_ssids; i++)
+ for (i = 0; i < scan_req->n_ssids; i++)
ieee80211_send_probe_req(
- sdata, NULL,
- local->scan_req->ssids[i].ssid,
- local->scan_req->ssids[i].ssid_len,
- local->scan_req->ie, local->scan_req->ie_len,
- local->scan_req->rates[band], false,
+ sdata, local->scan_addr, NULL,
+ scan_req->ssids[i].ssid, scan_req->ssids[i].ssid_len,
+ scan_req->ie, scan_req->ie_len,
+ scan_req->rates[band], false,
tx_flags, local->hw.conf.chandef.chan, true);
/*
@@ -480,7 +510,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
if (!ieee80211_can_scan(local, sdata)) {
/* wait for the work to finish/time out */
- local->scan_req = req;
+ rcu_assign_pointer(local->scan_req, req);
rcu_assign_pointer(local->scan_sdata, sdata);
return 0;
}
@@ -530,9 +560,16 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
*/
}
- local->scan_req = req;
+ rcu_assign_pointer(local->scan_req, req);
rcu_assign_pointer(local->scan_sdata, sdata);
+ if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR)
+ get_random_mask_addr(local->scan_addr,
+ req->mac_addr,
+ req->mac_addr_mask);
+ else
+ memcpy(local->scan_addr, sdata->vif.addr, ETH_ALEN);
+
if (local->ops->hw_scan) {
__set_bit(SCAN_HW_SCANNING, &local->scanning);
} else if ((req->n_channels == 1) &&
@@ -549,7 +586,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
/* Notify driver scan is starting, keep order of operations
* same as normal software scan, in case that matters. */
- drv_sw_scan_start(local);
+ drv_sw_scan_start(local, sdata, local->scan_addr);
ieee80211_configure_filter(local); /* accept probe-responses */
@@ -558,7 +595,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
if ((req->channels[0]->flags &
IEEE80211_CHAN_NO_IR) ||
- !local->scan_req->n_ssids) {
+ !req->n_ssids) {
next_delay = IEEE80211_PASSIVE_CHANNEL_TIME;
} else {
ieee80211_scan_state_send_probe(local, &next_delay);
@@ -579,8 +616,9 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
if (local->ops->hw_scan) {
WARN_ON(!ieee80211_prep_hw_scan(local));
rc = drv_hw_scan(local, sdata, local->hw_scan_req);
- } else
- rc = ieee80211_start_sw_scan(local);
+ } else {
+ rc = ieee80211_start_sw_scan(local, sdata);
+ }
if (rc) {
kfree(local->hw_scan_req);
@@ -617,6 +655,7 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata;
struct ieee80211_channel *next_chan;
enum mac80211_scan_state next_scan_state;
+ struct cfg80211_scan_request *scan_req;
/*
* check if at least one STA interface is associated,
@@ -641,7 +680,10 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
}
mutex_unlock(&local->iflist_mtx);
- next_chan = local->scan_req->channels[local->scan_channel_idx];
+ scan_req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
+
+ next_chan = scan_req->channels[local->scan_channel_idx];
/*
* we're currently scanning a different channel, let's
@@ -656,7 +698,7 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
local->leave_oper_channel_time + HZ / 8);
if (associated && !tx_empty) {
- if (local->scan_req->flags & NL80211_SCAN_FLAG_LOW_PRIORITY)
+ if (scan_req->flags & NL80211_SCAN_FLAG_LOW_PRIORITY)
next_scan_state = SCAN_ABORT;
else
next_scan_state = SCAN_SUSPEND;
@@ -677,14 +719,18 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local,
int skip;
struct ieee80211_channel *chan;
enum nl80211_bss_scan_width oper_scan_width;
+ struct cfg80211_scan_request *scan_req;
+
+ scan_req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
skip = 0;
- chan = local->scan_req->channels[local->scan_channel_idx];
+ chan = scan_req->channels[local->scan_channel_idx];
local->scan_chandef.chan = chan;
local->scan_chandef.center_freq1 = chan->center_freq;
local->scan_chandef.center_freq2 = 0;
- switch (local->scan_req->scan_width) {
+ switch (scan_req->scan_width) {
case NL80211_BSS_CHAN_WIDTH_5:
local->scan_chandef.width = NL80211_CHAN_WIDTH_5;
break;
@@ -698,7 +744,7 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local,
oper_scan_width = cfg80211_chandef_to_scan_width(
&local->_oper_chandef);
if (chan == local->_oper_chandef.chan &&
- oper_scan_width == local->scan_req->scan_width)
+ oper_scan_width == scan_req->scan_width)
local->scan_chandef = local->_oper_chandef;
else
local->scan_chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
@@ -727,8 +773,7 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local,
*
* In any case, it is not necessary for a passive scan.
*/
- if (chan->flags & IEEE80211_CHAN_NO_IR ||
- !local->scan_req->n_ssids) {
+ if (chan->flags & IEEE80211_CHAN_NO_IR || !scan_req->n_ssids) {
*next_delay = IEEE80211_PASSIVE_CHANNEL_TIME;
local->next_scan_state = SCAN_DECISION;
return;
@@ -777,6 +822,7 @@ void ieee80211_scan_work(struct work_struct *work)
struct ieee80211_local *local =
container_of(work, struct ieee80211_local, scan_work.work);
struct ieee80211_sub_if_data *sdata;
+ struct cfg80211_scan_request *scan_req;
unsigned long next_delay = 0;
bool aborted;
@@ -784,6 +830,8 @@ void ieee80211_scan_work(struct work_struct *work)
sdata = rcu_dereference_protected(local->scan_sdata,
lockdep_is_held(&local->mtx));
+ scan_req = rcu_dereference_protected(local->scan_req,
+ lockdep_is_held(&local->mtx));
/* When scanning on-channel, the first-callback means completed. */
if (test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning)) {
@@ -796,20 +844,19 @@ void ieee80211_scan_work(struct work_struct *work)
goto out_complete;
}
- if (!sdata || !local->scan_req)
+ if (!sdata || !scan_req)
goto out;
- if (local->scan_req && !local->scanning) {
- struct cfg80211_scan_request *req = local->scan_req;
+ if (!local->scanning) {
int rc;
- local->scan_req = NULL;
+ RCU_INIT_POINTER(local->scan_req, NULL);
RCU_INIT_POINTER(local->scan_sdata, NULL);
- rc = __ieee80211_start_scan(sdata, req);
+ rc = __ieee80211_start_scan(sdata, scan_req);
if (rc) {
/* need to complete scan in cfg80211 */
- local->scan_req = req;
+ rcu_assign_pointer(local->scan_req, scan_req);
aborted = true;
goto out_complete;
} else
@@ -829,7 +876,7 @@ void ieee80211_scan_work(struct work_struct *work)
switch (local->next_scan_state) {
case SCAN_DECISION:
/* if no more bands/channels left, complete scan */
- if (local->scan_channel_idx >= local->scan_req->n_channels) {
+ if (local->scan_channel_idx >= scan_req->n_channels) {
aborted = false;
goto out_complete;
}
@@ -1043,7 +1090,7 @@ int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
ret = drv_sched_scan_start(local, sdata, req, &sched_scan_ies);
if (ret == 0) {
rcu_assign_pointer(local->sched_scan_sdata, sdata);
- local->sched_scan_req = req;
+ rcu_assign_pointer(local->sched_scan_req, req);
}
kfree(ie);
@@ -1052,7 +1099,7 @@ out:
if (ret) {
/* Clean in case of failure after HW restart or upon resume. */
RCU_INIT_POINTER(local->sched_scan_sdata, NULL);
- local->sched_scan_req = NULL;
+ RCU_INIT_POINTER(local->sched_scan_req, NULL);
}
return ret;
@@ -1090,7 +1137,7 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata)
}
/* We don't want to restart sched scan anymore. */
- local->sched_scan_req = NULL;
+ RCU_INIT_POINTER(local->sched_scan_req, NULL);
if (rcu_access_pointer(local->sched_scan_sdata)) {
ret = drv_sched_scan_stop(local, sdata);
@@ -1125,7 +1172,7 @@ void ieee80211_sched_scan_end(struct ieee80211_local *local)
RCU_INIT_POINTER(local->sched_scan_sdata, NULL);
/* If sched scan was aborted by the driver. */
- local->sched_scan_req = NULL;
+ RCU_INIT_POINTER(local->sched_scan_req, NULL);
mutex_unlock(&local->mtx);
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index adc25371b171..a42f5b2b024d 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -351,6 +351,9 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
sta->sta_state = IEEE80211_STA_NONE;
+ /* Mark TID as unreserved */
+ sta->reserved_tid = IEEE80211_TID_UNRESERVED;
+
ktime_get_ts(&uptime);
sta->last_connected = uptime.tv_sec;
ewma_init(&sta->avg_signal, 1024, 8);
@@ -847,6 +850,15 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
if (WARN_ON(ret))
return ret;
+ /*
+ * for TDLS peers, make sure to return to the base channel before
+ * removal.
+ */
+ if (test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) {
+ drv_tdls_cancel_channel_switch(local, sdata, &sta->sta);
+ clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL);
+ }
+
list_del_rcu(&sta->list);
drv_sta_pre_rcu_remove(local, sta->sdata, sta);
@@ -1249,7 +1261,8 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,
return;
}
- ieee80211_xmit(sdata, skb, chanctx_conf->def.chan->band);
+ info->band = chanctx_conf->def.chan->band;
+ ieee80211_xmit(sdata, skb);
rcu_read_unlock();
}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index bcda2ac7d844..4f052bb2a5ad 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -49,6 +49,9 @@
* packets. This means the link is enabled.
* @WLAN_STA_TDLS_INITIATOR: We are the initiator of the TDLS link with this
* station.
+ * @WLAN_STA_TDLS_CHAN_SWITCH: This TDLS peer supports TDLS channel-switching
+ * @WLAN_STA_TDLS_OFF_CHANNEL: The local STA is currently off-channel with this
+ * TDLS peer
* @WLAN_STA_UAPSD: Station requested unscheduled SP while driver was
* keeping station in power-save mode, reply when the driver
* unblocks the station.
@@ -78,6 +81,8 @@ enum ieee80211_sta_info_flags {
WLAN_STA_TDLS_PEER,
WLAN_STA_TDLS_PEER_AUTH,
WLAN_STA_TDLS_INITIATOR,
+ WLAN_STA_TDLS_CHAN_SWITCH,
+ WLAN_STA_TDLS_OFF_CHANNEL,
WLAN_STA_UAPSD,
WLAN_STA_SP,
WLAN_STA_4ADDR_EVENT,
@@ -249,6 +254,9 @@ struct ieee80211_tx_latency_stat {
u32 bin_count;
};
+/* Value to indicate no TID reservation */
+#define IEEE80211_TID_UNRESERVED 0xff
+
/**
* struct sta_info - STA information
*
@@ -337,6 +345,7 @@ struct ieee80211_tx_latency_stat {
* AP only.
* @cipher_scheme: optional cipher scheme for this station
* @last_tdls_pkt_time: holds the time in jiffies of last TDLS pkt ACKed
+ * @reserved_tid: reserved TID (if any, otherwise IEEE80211_TID_UNRESERVED)
*/
struct sta_info {
/* General information, mostly static */
@@ -454,6 +463,8 @@ struct sta_info {
/* TDLS timeout data */
unsigned long last_tdls_pkt_time;
+ u8 reserved_tid;
+
/* keep last! */
struct ieee80211_sta sta;
};
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 9612d89fad56..71de2d3866cc 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -390,6 +390,46 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_local *local,
}
}
+/*
+ * Handles the tx for TDLS teardown frames.
+ * If the frame wasn't ACKed by the peer - it will be re-sent through the AP
+ */
+static void ieee80211_tdls_td_tx_handle(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u32 flags)
+{
+ struct sk_buff *teardown_skb;
+ struct sk_buff *orig_teardown_skb;
+ bool is_teardown = false;
+
+ /* Get the teardown data we need and free the lock */
+ spin_lock(&sdata->u.mgd.teardown_lock);
+ teardown_skb = sdata->u.mgd.teardown_skb;
+ orig_teardown_skb = sdata->u.mgd.orig_teardown_skb;
+ if ((skb == orig_teardown_skb) && teardown_skb) {
+ sdata->u.mgd.teardown_skb = NULL;
+ sdata->u.mgd.orig_teardown_skb = NULL;
+ is_teardown = true;
+ }
+ spin_unlock(&sdata->u.mgd.teardown_lock);
+
+ if (is_teardown) {
+ /* This mechanism relies on being able to get ACKs */
+ WARN_ON(!(local->hw.flags &
+ IEEE80211_HW_REPORTS_TX_ACK_STATUS));
+
+ /* Check if peer has ACKed */
+ if (flags & IEEE80211_TX_STAT_ACK) {
+ dev_kfree_skb_any(teardown_skb);
+ } else {
+ tdls_dbg(sdata,
+ "TDLS Resending teardown through AP\n");
+
+ ieee80211_subif_start_xmit(teardown_skb, skb->dev);
+ }
+ }
+}
+
static void ieee80211_report_used_skb(struct ieee80211_local *local,
struct sk_buff *skb, bool dropped)
{
@@ -426,8 +466,19 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local,
if (!sdata) {
skb->dev = NULL;
} else if (info->flags & IEEE80211_TX_INTFL_MLME_CONN_TX) {
- ieee80211_mgd_conn_tx_status(sdata, hdr->frame_control,
- acked);
+ unsigned int hdr_size =
+ ieee80211_hdrlen(hdr->frame_control);
+
+ /* Check to see if packet is a TDLS teardown packet */
+ if (ieee80211_is_data(hdr->frame_control) &&
+ (ieee80211_get_tdls_action(skb, hdr_size) ==
+ WLAN_TDLS_TEARDOWN))
+ ieee80211_tdls_td_tx_handle(local, sdata, skb,
+ info->flags);
+ else
+ ieee80211_mgd_conn_tx_status(sdata,
+ hdr->frame_control,
+ acked);
} else if (ieee80211_is_nullfunc(hdr->frame_control) ||
ieee80211_is_qos_nullfunc(hdr->frame_control)) {
cfg80211_probe_status(sdata->dev, hdr->addr1,
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index b4f368e2cb3b..55ddd77b865d 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -35,19 +35,101 @@ void ieee80211_tdls_peer_del_work(struct work_struct *wk)
mutex_unlock(&local->mtx);
}
-static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb)
+static void ieee80211_tdls_add_ext_capab(struct ieee80211_local *local,
+ struct sk_buff *skb)
{
u8 *pos = (void *)skb_put(skb, 7);
+ bool chan_switch = local->hw.wiphy->features &
+ NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
*pos++ = WLAN_EID_EXT_CAPABILITY;
*pos++ = 5; /* len */
*pos++ = 0x0;
*pos++ = 0x0;
*pos++ = 0x0;
- *pos++ = 0x0;
+ *pos++ = chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0;
*pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
}
+static u8
+ieee80211_tdls_add_subband(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u16 start, u16 end,
+ u16 spacing)
+{
+ u8 subband_cnt = 0, ch_cnt = 0;
+ struct ieee80211_channel *ch;
+ struct cfg80211_chan_def chandef;
+ int i, subband_start;
+
+ for (i = start; i <= end; i += spacing) {
+ if (!ch_cnt)
+ subband_start = i;
+
+ ch = ieee80211_get_channel(sdata->local->hw.wiphy, i);
+ if (ch) {
+ /* we will be active on the channel */
+ u32 flags = IEEE80211_CHAN_DISABLED |
+ IEEE80211_CHAN_NO_IR;
+ cfg80211_chandef_create(&chandef, ch,
+ NL80211_CHAN_HT20);
+ if (cfg80211_chandef_usable(sdata->local->hw.wiphy,
+ &chandef, flags)) {
+ ch_cnt++;
+ continue;
+ }
+ }
+
+ if (ch_cnt) {
+ u8 *pos = skb_put(skb, 2);
+ *pos++ = ieee80211_frequency_to_channel(subband_start);
+ *pos++ = ch_cnt;
+
+ subband_cnt++;
+ ch_cnt = 0;
+ }
+ }
+
+ return subband_cnt;
+}
+
+static void
+ieee80211_tdls_add_supp_channels(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ /*
+ * Add possible channels for TDLS. These are channels that are allowed
+ * to be active.
+ */
+ u8 subband_cnt;
+ u8 *pos = skb_put(skb, 2);
+
+ *pos++ = WLAN_EID_SUPPORTED_CHANNELS;
+
+ /*
+ * 5GHz and 2GHz channels numbers can overlap. Ignore this for now, as
+ * this doesn't happen in real world scenarios.
+ */
+
+ /* 2GHz, with 5MHz spacing */
+ subband_cnt = ieee80211_tdls_add_subband(sdata, skb, 2412, 2472, 5);
+
+ /* 5GHz, with 20MHz spacing */
+ subband_cnt += ieee80211_tdls_add_subband(sdata, skb, 5000, 5825, 20);
+
+ /* length */
+ *pos = 2 * subband_cnt;
+}
+
+static void ieee80211_tdls_add_bss_coex_ie(struct sk_buff *skb)
+{
+ u8 *pos = (void *)skb_put(skb, 3);
+
+ *pos++ = WLAN_EID_BSS_COEX_2040;
+ *pos++ = 1; /* len */
+
+ *pos++ = WLAN_BSS_COEX_INFORMATION_REQUEST;
+}
+
static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata,
u16 status_code)
{
@@ -190,6 +272,7 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata,
ieee80211_add_srates_ie(sdata, skb, false, band);
ieee80211_add_ext_srates_ie(sdata, skb, false, band);
+ ieee80211_tdls_add_supp_channels(sdata, skb);
/* add any custom IEs that go before Extended Capabilities */
if (extra_ies_len) {
@@ -209,7 +292,7 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata,
offset = noffset;
}
- ieee80211_tdls_add_ext_capab(skb);
+ ieee80211_tdls_add_ext_capab(local, skb);
/* add the QoS element if we support it */
if (local->hw.queues >= IEEE80211_NUM_ACS &&
@@ -271,6 +354,10 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata,
rcu_read_unlock();
+ if (ht_cap.ht_supported &&
+ (ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40))
+ ieee80211_tdls_add_bss_coex_ie(skb);
+
/* add any remaining IEs */
if (extra_ies_len) {
noffset = extra_ies_len;
@@ -362,11 +449,68 @@ ieee80211_tdls_add_setup_cfm_ies(struct ieee80211_sub_if_data *sdata,
ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator);
}
+static void
+ieee80211_tdls_add_chan_switch_req_ies(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, const u8 *peer,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ieee80211_tdls_data *tf;
+ size_t offset = 0, noffset;
+ u8 *pos;
+
+ if (WARN_ON_ONCE(!chandef))
+ return;
+
+ tf = (void *)skb->data;
+ tf->u.chan_switch_req.target_channel =
+ ieee80211_frequency_to_channel(chandef->chan->center_freq);
+ tf->u.chan_switch_req.oper_class = oper_class;
+
+ if (extra_ies_len) {
+ static const u8 before_lnkie[] = {
+ WLAN_EID_SECONDARY_CHANNEL_OFFSET,
+ };
+ noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
+ before_lnkie,
+ ARRAY_SIZE(before_lnkie),
+ offset);
+ pos = skb_put(skb, noffset - offset);
+ memcpy(pos, extra_ies + offset, noffset - offset);
+ offset = noffset;
+ }
+
+ ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator);
+
+ /* add any remaining IEs */
+ if (extra_ies_len) {
+ noffset = extra_ies_len;
+ pos = skb_put(skb, noffset - offset);
+ memcpy(pos, extra_ies + offset, noffset - offset);
+ }
+}
+
+static void
+ieee80211_tdls_add_chan_switch_resp_ies(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, const u8 *peer,
+ u16 status_code, bool initiator,
+ const u8 *extra_ies,
+ size_t extra_ies_len)
+{
+ if (status_code == 0)
+ ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator);
+
+ if (extra_ies_len)
+ memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len);
+}
+
static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, const u8 *peer,
u8 action_code, u16 status_code,
bool initiator, const u8 *extra_ies,
- size_t extra_ies_len)
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
{
switch (action_code) {
case WLAN_TDLS_SETUP_REQUEST:
@@ -393,6 +537,18 @@ static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata,
if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN)
ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator);
break;
+ case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+ ieee80211_tdls_add_chan_switch_req_ies(sdata, skb, peer,
+ initiator, extra_ies,
+ extra_ies_len,
+ oper_class, chandef);
+ break;
+ case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+ ieee80211_tdls_add_chan_switch_resp_ies(sdata, skb, peer,
+ status_code,
+ initiator, extra_ies,
+ extra_ies_len);
+ break;
}
}
@@ -459,6 +615,19 @@ ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev,
skb_put(skb, sizeof(tf->u.discover_req));
tf->u.discover_req.dialog_token = dialog_token;
break;
+ case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+ tf->category = WLAN_CATEGORY_TDLS;
+ tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
+
+ skb_put(skb, sizeof(tf->u.chan_switch_req));
+ break;
+ case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+ tf->category = WLAN_CATEGORY_TDLS;
+ tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE;
+
+ skb_put(skb, sizeof(tf->u.chan_switch_resp));
+ tf->u.chan_switch_resp.status_code = cpu_to_le16(status_code);
+ break;
default:
return -EINVAL;
}
@@ -502,32 +671,33 @@ ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev,
return 0;
}
-static int
-ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
- const u8 *peer, u8 action_code,
- u8 dialog_token, u16 status_code,
- u32 peer_capability, bool initiator,
- const u8 *extra_ies, size_t extra_ies_len)
+static struct sk_buff *
+ieee80211_tdls_build_mgmt_packet_data(struct ieee80211_sub_if_data *sdata,
+ const u8 *peer, u8 action_code,
+ u8 dialog_token, u16 status_code,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
{
- struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local;
- struct sk_buff *skb = NULL;
- bool send_direct;
- struct sta_info *sta;
+ struct sk_buff *skb;
int ret;
- skb = dev_alloc_skb(local->hw.extra_tx_headroom +
- max(sizeof(struct ieee80211_mgmt),
- sizeof(struct ieee80211_tdls_data)) +
- 50 + /* supported rates */
- 7 + /* ext capab */
- 26 + /* max(WMM-info, WMM-param) */
- 2 + max(sizeof(struct ieee80211_ht_cap),
- sizeof(struct ieee80211_ht_operation)) +
- extra_ies_len +
- sizeof(struct ieee80211_tdls_lnkie));
+ skb = netdev_alloc_skb(sdata->dev,
+ local->hw.extra_tx_headroom +
+ max(sizeof(struct ieee80211_mgmt),
+ sizeof(struct ieee80211_tdls_data)) +
+ 50 + /* supported rates */
+ 7 + /* ext capab */
+ 26 + /* max(WMM-info, WMM-param) */
+ 2 + max(sizeof(struct ieee80211_ht_cap),
+ sizeof(struct ieee80211_ht_operation)) +
+ 50 + /* supported channels */
+ 3 + /* 40/20 BSS coex */
+ extra_ies_len +
+ sizeof(struct ieee80211_tdls_lnkie));
if (!skb)
- return -ENOMEM;
+ return NULL;
skb_reserve(skb, local->hw.extra_tx_headroom);
@@ -537,16 +707,18 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
case WLAN_TDLS_SETUP_CONFIRM:
case WLAN_TDLS_TEARDOWN:
case WLAN_TDLS_DISCOVERY_REQUEST:
- ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer,
+ case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+ case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+ ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy,
+ sdata->dev, peer,
action_code, dialog_token,
status_code, skb);
- send_direct = false;
break;
case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
- ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code,
+ ret = ieee80211_prep_tdls_direct(local->hw.wiphy, sdata->dev,
+ peer, action_code,
dialog_token, status_code,
skb);
- send_direct = true;
break;
default:
ret = -ENOTSUPP;
@@ -556,6 +728,30 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
if (ret < 0)
goto fail;
+ ieee80211_tdls_add_ies(sdata, skb, peer, action_code, status_code,
+ initiator, extra_ies, extra_ies_len, oper_class,
+ chandef);
+ return skb;
+
+fail:
+ dev_kfree_skb(skb);
+ return NULL;
+}
+
+static int
+ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *peer, u8 action_code, u8 dialog_token,
+ u16 status_code, u32 peer_capability,
+ bool initiator, const u8 *extra_ies,
+ size_t extra_ies_len, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ struct sk_buff *skb = NULL;
+ struct sta_info *sta;
+ u32 flags = 0;
+ int ret = 0;
+
rcu_read_lock();
sta = sta_info_get(sdata, peer);
@@ -586,6 +782,8 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
initiator = false;
break;
case WLAN_TDLS_TEARDOWN:
+ case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+ case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
/* any value is ok */
break;
default:
@@ -600,9 +798,17 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
if (ret < 0)
goto fail;
- ieee80211_tdls_add_ies(sdata, skb, peer, action_code, status_code,
- initiator, extra_ies, extra_ies_len);
- if (send_direct) {
+ skb = ieee80211_tdls_build_mgmt_packet_data(sdata, peer, action_code,
+ dialog_token, status_code,
+ initiator, extra_ies,
+ extra_ies_len, oper_class,
+ chandef);
+ if (!skb) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
ieee80211_tx_skb(sdata, skb);
return 0;
}
@@ -623,9 +829,44 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
break;
}
+ /*
+ * Set the WLAN_TDLS_TEARDOWN flag to indicate a teardown in progress.
+ * Later, if no ACK is returned from peer, we will re-send the teardown
+ * packet through the AP.
+ */
+ if ((action_code == WLAN_TDLS_TEARDOWN) &&
+ (sdata->local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) {
+ struct sta_info *sta = NULL;
+ bool try_resend; /* Should we keep skb for possible resend */
+
+ /* If not sending directly to peer - no point in keeping skb */
+ rcu_read_lock();
+ sta = sta_info_get(sdata, peer);
+ try_resend = sta && test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
+ rcu_read_unlock();
+
+ spin_lock_bh(&sdata->u.mgd.teardown_lock);
+ if (try_resend && !sdata->u.mgd.teardown_skb) {
+ /* Mark it as requiring TX status callback */
+ flags |= IEEE80211_TX_CTL_REQ_TX_STATUS |
+ IEEE80211_TX_INTFL_MLME_CONN_TX;
+
+ /*
+ * skb is copied since mac80211 will later set
+ * properties that might not be the same as the AP,
+ * such as encryption, QoS, addresses, etc.
+ *
+ * No problem if skb_copy() fails, so no need to check.
+ */
+ sdata->u.mgd.teardown_skb = skb_copy(skb, GFP_ATOMIC);
+ sdata->u.mgd.orig_teardown_skb = skb;
+ }
+ spin_unlock_bh(&sdata->u.mgd.teardown_lock);
+ }
+
/* disable bottom halves when entering the Tx path */
local_bh_disable();
- ret = ieee80211_subif_start_xmit(skb, dev);
+ __ieee80211_subif_start_xmit(skb, dev, flags);
local_bh_enable();
return ret;
@@ -676,7 +917,8 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev,
ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code,
dialog_token, status_code,
peer_capability, initiator,
- extra_ies, extra_ies_len);
+ extra_ies, extra_ies_len, 0,
+ NULL);
if (ret < 0)
goto exit;
@@ -715,7 +957,8 @@ ieee80211_tdls_mgmt_teardown(struct wiphy *wiphy, struct net_device *dev,
ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code,
dialog_token, status_code,
peer_capability, initiator,
- extra_ies, extra_ies_len);
+ extra_ies, extra_ies_len, 0,
+ NULL);
if (ret < 0)
sdata_err(sdata, "Failed sending TDLS teardown packet %d\n",
ret);
@@ -785,7 +1028,7 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
status_code,
peer_capability,
initiator, extra_ies,
- extra_ies_len);
+ extra_ies_len, 0, NULL);
break;
default:
ret = -EOPNOTSUPP;
@@ -888,3 +1131,480 @@ void ieee80211_tdls_oper_request(struct ieee80211_vif *vif, const u8 *peer,
cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp);
}
EXPORT_SYMBOL(ieee80211_tdls_oper_request);
+
+static void
+iee80211_tdls_add_ch_switch_timing(u8 *buf, u16 switch_time, u16 switch_timeout)
+{
+ struct ieee80211_ch_switch_timing *ch_sw;
+
+ *buf++ = WLAN_EID_CHAN_SWITCH_TIMING;
+ *buf++ = sizeof(struct ieee80211_ch_switch_timing);
+
+ ch_sw = (void *)buf;
+ ch_sw->switch_time = cpu_to_le16(switch_time);
+ ch_sw->switch_timeout = cpu_to_le16(switch_timeout);
+}
+
+/* find switch timing IE in SKB ready for Tx */
+static const u8 *ieee80211_tdls_find_sw_timing_ie(struct sk_buff *skb)
+{
+ struct ieee80211_tdls_data *tf;
+ const u8 *ie_start;
+
+ /*
+ * Get the offset for the new location of the switch timing IE.
+ * The SKB network header will now point to the "payload_type"
+ * element of the TDLS data frame struct.
+ */
+ tf = container_of(skb->data + skb_network_offset(skb),
+ struct ieee80211_tdls_data, payload_type);
+ ie_start = tf->u.chan_switch_req.variable;
+ return cfg80211_find_ie(WLAN_EID_CHAN_SWITCH_TIMING, ie_start,
+ skb->len - (ie_start - skb->data));
+}
+
+static struct sk_buff *
+ieee80211_tdls_ch_sw_tmpl_get(struct sta_info *sta, u8 oper_class,
+ struct cfg80211_chan_def *chandef,
+ u32 *ch_sw_tm_ie_offset)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ u8 extra_ies[2 + sizeof(struct ieee80211_sec_chan_offs_ie) +
+ 2 + sizeof(struct ieee80211_ch_switch_timing)];
+ int extra_ies_len = 2 + sizeof(struct ieee80211_ch_switch_timing);
+ u8 *pos = extra_ies;
+ struct sk_buff *skb;
+
+ /*
+ * if chandef points to a wide channel add a Secondary-Channel
+ * Offset information element
+ */
+ if (chandef->width == NL80211_CHAN_WIDTH_40) {
+ struct ieee80211_sec_chan_offs_ie *sec_chan_ie;
+ bool ht40plus;
+
+ *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET;
+ *pos++ = sizeof(*sec_chan_ie);
+ sec_chan_ie = (void *)pos;
+
+ ht40plus = cfg80211_get_chandef_type(chandef) ==
+ NL80211_CHAN_HT40PLUS;
+ sec_chan_ie->sec_chan_offs = ht40plus ?
+ IEEE80211_HT_PARAM_CHA_SEC_ABOVE :
+ IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+ pos += sizeof(*sec_chan_ie);
+
+ extra_ies_len += 2 + sizeof(struct ieee80211_sec_chan_offs_ie);
+ }
+
+ /* just set the values to 0, this is a template */
+ iee80211_tdls_add_ch_switch_timing(pos, 0, 0);
+
+ skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr,
+ WLAN_TDLS_CHANNEL_SWITCH_REQUEST,
+ 0, 0, !sta->sta.tdls_initiator,
+ extra_ies, extra_ies_len,
+ oper_class, chandef);
+ if (!skb)
+ return NULL;
+
+ skb = ieee80211_build_data_template(sdata, skb, 0);
+ if (IS_ERR(skb)) {
+ tdls_dbg(sdata, "Failed building TDLS channel switch frame\n");
+ return NULL;
+ }
+
+ if (ch_sw_tm_ie_offset) {
+ const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb);
+
+ if (!tm_ie) {
+ tdls_dbg(sdata, "No switch timing IE in TDLS switch\n");
+ dev_kfree_skb_any(skb);
+ return NULL;
+ }
+
+ *ch_sw_tm_ie_offset = tm_ie - skb->data;
+ }
+
+ tdls_dbg(sdata,
+ "TDLS channel switch request template for %pM ch %d width %d\n",
+ sta->sta.addr, chandef->chan->center_freq, chandef->width);
+ return skb;
+}
+
+int
+ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *addr, u8 oper_class,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+ struct sk_buff *skb = NULL;
+ u32 ch_sw_tm_ie;
+ int ret;
+
+ mutex_lock(&local->sta_mtx);
+ sta = sta_info_get(sdata, addr);
+ if (!sta) {
+ tdls_dbg(sdata,
+ "Invalid TDLS peer %pM for channel switch request\n",
+ addr);
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (!test_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH)) {
+ tdls_dbg(sdata, "TDLS channel switch unsupported by %pM\n",
+ addr);
+ ret = -ENOTSUPP;
+ goto out;
+ }
+
+ skb = ieee80211_tdls_ch_sw_tmpl_get(sta, oper_class, chandef,
+ &ch_sw_tm_ie);
+ if (!skb) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = drv_tdls_channel_switch(local, sdata, &sta->sta, oper_class,
+ chandef, skb, ch_sw_tm_ie);
+ if (!ret)
+ set_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL);
+
+out:
+ mutex_unlock(&local->sta_mtx);
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+void
+ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *addr)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+
+ mutex_lock(&local->sta_mtx);
+ sta = sta_info_get(sdata, addr);
+ if (!sta) {
+ tdls_dbg(sdata,
+ "Invalid TDLS peer %pM for channel switch cancel\n",
+ addr);
+ goto out;
+ }
+
+ if (!test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) {
+ tdls_dbg(sdata, "TDLS channel switch not initiated by %pM\n",
+ addr);
+ goto out;
+ }
+
+ drv_tdls_cancel_channel_switch(local, sdata, &sta->sta);
+ clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL);
+
+out:
+ mutex_unlock(&local->sta_mtx);
+}
+
+static struct sk_buff *
+ieee80211_tdls_ch_sw_resp_tmpl_get(struct sta_info *sta,
+ u32 *ch_sw_tm_ie_offset)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct sk_buff *skb;
+ u8 extra_ies[2 + sizeof(struct ieee80211_ch_switch_timing)];
+
+ /* initial timing are always zero in the template */
+ iee80211_tdls_add_ch_switch_timing(extra_ies, 0, 0);
+
+ skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr,
+ WLAN_TDLS_CHANNEL_SWITCH_RESPONSE,
+ 0, 0, !sta->sta.tdls_initiator,
+ extra_ies, sizeof(extra_ies), 0, NULL);
+ if (!skb)
+ return NULL;
+
+ skb = ieee80211_build_data_template(sdata, skb, 0);
+ if (IS_ERR(skb)) {
+ tdls_dbg(sdata,
+ "Failed building TDLS channel switch resp frame\n");
+ return NULL;
+ }
+
+ if (ch_sw_tm_ie_offset) {
+ const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb);
+
+ if (!tm_ie) {
+ tdls_dbg(sdata,
+ "No switch timing IE in TDLS switch resp\n");
+ dev_kfree_skb_any(skb);
+ return NULL;
+ }
+
+ *ch_sw_tm_ie_offset = tm_ie - skb->data;
+ }
+
+ tdls_dbg(sdata, "TDLS get channel switch response template for %pM\n",
+ sta->sta.addr);
+ return skb;
+}
+
+static int
+ieee80211_process_tdls_channel_switch_resp(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee802_11_elems elems;
+ struct sta_info *sta;
+ struct ieee80211_tdls_data *tf = (void *)skb->data;
+ bool local_initiator;
+ struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
+ int baselen = offsetof(typeof(*tf), u.chan_switch_resp.variable);
+ struct ieee80211_tdls_ch_sw_params params = {};
+ int ret;
+
+ params.action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE;
+ params.timestamp = rx_status->device_timestamp;
+
+ if (skb->len < baselen) {
+ tdls_dbg(sdata, "TDLS channel switch resp too short: %d\n",
+ skb->len);
+ return -EINVAL;
+ }
+
+ mutex_lock(&local->sta_mtx);
+ sta = sta_info_get(sdata, tf->sa);
+ if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
+ tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
+ tf->sa);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ params.sta = &sta->sta;
+ params.status = le16_to_cpu(tf->u.chan_switch_resp.status_code);
+ if (params.status != 0) {
+ ret = 0;
+ goto call_drv;
+ }
+
+ ieee802_11_parse_elems(tf->u.chan_switch_resp.variable,
+ skb->len - baselen, false, &elems);
+ if (elems.parse_error) {
+ tdls_dbg(sdata, "Invalid IEs in TDLS channel switch resp\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!elems.ch_sw_timing || !elems.lnk_id) {
+ tdls_dbg(sdata, "TDLS channel switch resp - missing IEs\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* validate the initiator is set correctly */
+ local_initiator =
+ !memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
+ if (local_initiator == sta->sta.tdls_initiator) {
+ tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time);
+ params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout);
+
+ params.tmpl_skb =
+ ieee80211_tdls_ch_sw_resp_tmpl_get(sta, &params.ch_sw_tm_ie);
+ if (!params.tmpl_skb) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+call_drv:
+ drv_tdls_recv_channel_switch(sdata->local, sdata, &params);
+
+ tdls_dbg(sdata,
+ "TDLS channel switch response received from %pM status %d\n",
+ tf->sa, params.status);
+
+out:
+ mutex_unlock(&local->sta_mtx);
+ dev_kfree_skb_any(params.tmpl_skb);
+ return ret;
+}
+
+static int
+ieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee802_11_elems elems;
+ struct cfg80211_chan_def chandef;
+ struct ieee80211_channel *chan;
+ enum nl80211_channel_type chan_type;
+ int freq;
+ u8 target_channel, oper_class;
+ bool local_initiator;
+ struct sta_info *sta;
+ enum ieee80211_band band;
+ struct ieee80211_tdls_data *tf = (void *)skb->data;
+ struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
+ int baselen = offsetof(typeof(*tf), u.chan_switch_req.variable);
+ struct ieee80211_tdls_ch_sw_params params = {};
+ int ret = 0;
+
+ params.action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
+ params.timestamp = rx_status->device_timestamp;
+
+ if (skb->len < baselen) {
+ tdls_dbg(sdata, "TDLS channel switch req too short: %d\n",
+ skb->len);
+ return -EINVAL;
+ }
+
+ target_channel = tf->u.chan_switch_req.target_channel;
+ oper_class = tf->u.chan_switch_req.oper_class;
+
+ /*
+ * We can't easily infer the channel band. The operating class is
+ * ambiguous - there are multiple tables (US/Europe/JP/Global). The
+ * solution here is to treat channels with number >14 as 5GHz ones,
+ * and specifically check for the (oper_class, channel) combinations
+ * where this doesn't hold. These are thankfully unique according to
+ * IEEE802.11-2012.
+ * We consider only the 2GHz and 5GHz bands and 20MHz+ channels as
+ * valid here.
+ */
+ if ((oper_class == 112 || oper_class == 2 || oper_class == 3 ||
+ oper_class == 4 || oper_class == 5 || oper_class == 6) &&
+ target_channel < 14)
+ band = IEEE80211_BAND_5GHZ;
+ else
+ band = target_channel < 14 ? IEEE80211_BAND_2GHZ :
+ IEEE80211_BAND_5GHZ;
+
+ freq = ieee80211_channel_to_frequency(target_channel, band);
+ if (freq == 0) {
+ tdls_dbg(sdata, "Invalid channel in TDLS chan switch: %d\n",
+ target_channel);
+ return -EINVAL;
+ }
+
+ chan = ieee80211_get_channel(sdata->local->hw.wiphy, freq);
+ if (!chan) {
+ tdls_dbg(sdata,
+ "Unsupported channel for TDLS chan switch: %d\n",
+ target_channel);
+ return -EINVAL;
+ }
+
+ ieee802_11_parse_elems(tf->u.chan_switch_req.variable,
+ skb->len - baselen, false, &elems);
+ if (elems.parse_error) {
+ tdls_dbg(sdata, "Invalid IEs in TDLS channel switch req\n");
+ return -EINVAL;
+ }
+
+ if (!elems.ch_sw_timing || !elems.lnk_id) {
+ tdls_dbg(sdata, "TDLS channel switch req - missing IEs\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&local->sta_mtx);
+ sta = sta_info_get(sdata, tf->sa);
+ if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
+ tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
+ tf->sa);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ params.sta = &sta->sta;
+
+ /* validate the initiator is set correctly */
+ local_initiator =
+ !memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
+ if (local_initiator == sta->sta.tdls_initiator) {
+ tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!sta->sta.ht_cap.ht_supported) {
+ chan_type = NL80211_CHAN_NO_HT;
+ } else if (!elems.sec_chan_offs) {
+ chan_type = NL80211_CHAN_HT20;
+ } else {
+ switch (elems.sec_chan_offs->sec_chan_offs) {
+ case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+ chan_type = NL80211_CHAN_HT40PLUS;
+ break;
+ case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+ chan_type = NL80211_CHAN_HT40MINUS;
+ break;
+ default:
+ chan_type = NL80211_CHAN_HT20;
+ break;
+ }
+ }
+
+ cfg80211_chandef_create(&chandef, chan, chan_type);
+ params.chandef = &chandef;
+
+ params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time);
+ params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout);
+
+ params.tmpl_skb =
+ ieee80211_tdls_ch_sw_resp_tmpl_get(sta,
+ &params.ch_sw_tm_ie);
+ if (!params.tmpl_skb) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ drv_tdls_recv_channel_switch(sdata->local, sdata, &params);
+
+ tdls_dbg(sdata,
+ "TDLS ch switch request received from %pM ch %d width %d\n",
+ tf->sa, params.chandef->chan->center_freq,
+ params.chandef->width);
+out:
+ mutex_unlock(&local->sta_mtx);
+ dev_kfree_skb_any(params.tmpl_skb);
+ return ret;
+}
+
+void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_tdls_data *tf = (void *)skb->data;
+ struct wiphy *wiphy = sdata->local->hw.wiphy;
+
+ /* make sure the driver supports it */
+ if (!(wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH))
+ return;
+
+ /* we want to access the entire packet */
+ if (skb_linearize(skb))
+ return;
+ /*
+ * The packet/size was already validated by mac80211 Rx path, only look
+ * at the action type.
+ */
+ switch (tf->action_code) {
+ case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
+ ieee80211_process_tdls_channel_switch_req(sdata, skb);
+ break;
+ case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
+ ieee80211_process_tdls_channel_switch_resp(sdata, skb);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return;
+ }
+}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 809a4983eb4a..85ccfbe863db 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -16,6 +16,7 @@
#define STA_ENTRY __array(char, sta_addr, ETH_ALEN)
#define STA_ASSIGN (sta ? memcpy(__entry->sta_addr, sta->addr, ETH_ALEN) : memset(__entry->sta_addr, 0, ETH_ALEN))
+#define STA_NAMED_ASSIGN(s) memcpy(__entry->sta_addr, (s)->addr, ETH_ALEN)
#define STA_PR_FMT " sta:%pM"
#define STA_PR_ARG __entry->sta_addr
@@ -595,14 +596,33 @@ DEFINE_EVENT(local_sdata_evt, drv_sched_scan_stop,
TP_ARGS(local, sdata)
);
-DEFINE_EVENT(local_only_evt, drv_sw_scan_start,
- TP_PROTO(struct ieee80211_local *local),
- TP_ARGS(local)
+TRACE_EVENT(drv_sw_scan_start,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ const u8 *mac_addr),
+
+ TP_ARGS(local, sdata, mac_addr),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ __array(char, mac_addr, ETH_ALEN)
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ memcpy(__entry->mac_addr, mac_addr, ETH_ALEN);
+ ),
+
+ TP_printk(LOCAL_PR_FMT ", " VIF_PR_FMT ", addr:%pM",
+ LOCAL_PR_ARG, VIF_PR_ARG, __entry->mac_addr)
);
-DEFINE_EVENT(local_only_evt, drv_sw_scan_complete,
- TP_PROTO(struct ieee80211_local *local),
- TP_ARGS(local)
+DEFINE_EVENT(local_sdata_evt, drv_sw_scan_complete,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata),
+ TP_ARGS(local, sdata)
);
TRACE_EVENT(drv_get_stats,
@@ -826,6 +846,13 @@ DEFINE_EVENT(sta_event, drv_sta_pre_rcu_remove,
TP_ARGS(local, sdata, sta)
);
+DEFINE_EVENT(sta_event, drv_sta_rate_tbl_update,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta),
+ TP_ARGS(local, sdata, sta)
+);
+
TRACE_EVENT(drv_conf_tx,
TP_PROTO(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
@@ -2140,6 +2167,7 @@ TRACE_EVENT(drv_pre_channel_switch,
VIF_ENTRY
CHANDEF_ENTRY
__field(u64, timestamp)
+ __field(u32, device_timestamp)
__field(bool, block_tx)
__field(u8, count)
),
@@ -2149,6 +2177,7 @@ TRACE_EVENT(drv_pre_channel_switch,
VIF_ASSIGN;
CHANDEF_ASSIGN(&ch_switch->chandef)
__entry->timestamp = ch_switch->timestamp;
+ __entry->device_timestamp = ch_switch->device_timestamp;
__entry->block_tx = ch_switch->block_tx;
__entry->count = ch_switch->count;
),
@@ -2194,6 +2223,107 @@ TRACE_EVENT(drv_get_txpower,
)
);
+TRACE_EVENT(drv_tdls_channel_switch,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta, u8 oper_class,
+ struct cfg80211_chan_def *chandef),
+
+ TP_ARGS(local, sdata, sta, oper_class, chandef),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ STA_ENTRY
+ __field(u8, oper_class)
+ CHANDEF_ENTRY
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_ASSIGN;
+ __entry->oper_class = oper_class;
+ CHANDEF_ASSIGN(chandef)
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT " tdls channel switch to"
+ CHANDEF_PR_FMT " oper_class:%d " STA_PR_FMT,
+ LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG, __entry->oper_class,
+ STA_PR_ARG
+ )
+);
+
+TRACE_EVENT(drv_tdls_cancel_channel_switch,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta),
+
+ TP_ARGS(local, sdata, sta),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ STA_ENTRY
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_ASSIGN;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT
+ " tdls cancel channel switch with " STA_PR_FMT,
+ LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG
+ )
+);
+
+TRACE_EVENT(drv_tdls_recv_channel_switch,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_tdls_ch_sw_params *params),
+
+ TP_ARGS(local, sdata, params),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ __field(u8, action_code)
+ STA_ENTRY
+ CHANDEF_ENTRY
+ __field(u32, status)
+ __field(bool, peer_initiator)
+ __field(u32, timestamp)
+ __field(u16, switch_time)
+ __field(u16, switch_timeout)
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_NAMED_ASSIGN(params->sta);
+ CHANDEF_ASSIGN(params->chandef)
+ __entry->peer_initiator = params->sta->tdls_initiator;
+ __entry->action_code = params->action_code;
+ __entry->status = params->status;
+ __entry->timestamp = params->timestamp;
+ __entry->switch_time = params->switch_time;
+ __entry->switch_timeout = params->switch_timeout;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT " received tdls channel switch packet"
+ " action:%d status:%d time:%d switch time:%d switch"
+ " timeout:%d initiator: %d chan:" CHANDEF_PR_FMT STA_PR_FMT,
+ LOCAL_PR_ARG, VIF_PR_ARG, __entry->action_code, __entry->status,
+ __entry->timestamp, __entry->switch_time,
+ __entry->switch_timeout, __entry->peer_initiator,
+ CHANDEF_PR_ARG, STA_PR_ARG
+ )
+);
#ifdef CONFIG_MAC80211_MESSAGE_TRACING
#undef TRACE_SYSTEM
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 3ffd91f295a6..66ddbbeccd20 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1426,8 +1426,7 @@ EXPORT_SYMBOL(ieee80211_tx_prepare_skb);
* Returns false if the frame couldn't be transmitted but was queued instead.
*/
static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, bool txpending,
- enum ieee80211_band band)
+ struct sk_buff *skb, bool txpending)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_tx_data tx;
@@ -1452,8 +1451,6 @@ static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata,
return true;
}
- info->band = band;
-
/* set up hw_queue value early */
if (!(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) ||
!(local->hw.flags & IEEE80211_HW_QUEUE_CONTROL))
@@ -1501,8 +1498,7 @@ static int ieee80211_skb_resize(struct ieee80211_sub_if_data *sdata,
return 0;
}
-void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,
- enum ieee80211_band band)
+void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
@@ -1537,7 +1533,7 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,
}
ieee80211_set_qos_hdr(sdata, skb);
- ieee80211_tx(sdata, skb, false, band);
+ ieee80211_tx(sdata, skb, false);
}
static bool ieee80211_parse_tx_radiotap(struct sk_buff *skb)
@@ -1757,7 +1753,8 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
sdata->vif.type))
goto fail_rcu;
- ieee80211_xmit(sdata, skb, chandef->chan->band);
+ info->band = chandef->chan->band;
+ ieee80211_xmit(sdata, skb);
rcu_read_unlock();
return NETDEV_TX_OK;
@@ -1787,23 +1784,26 @@ static void ieee80211_tx_latency_start_msrmnt(struct ieee80211_local *local,
}
/**
- * ieee80211_subif_start_xmit - netif start_xmit function for Ethernet-type
- * subinterfaces (wlan#, WDS, and VLAN interfaces)
- * @skb: packet to be sent
- * @dev: incoming interface
+ * ieee80211_build_hdr - build 802.11 header in the given frame
+ * @sdata: virtual interface to build the header for
+ * @skb: the skb to build the header in
+ * @info_flags: skb flags to set
+ *
+ * This function takes the skb with 802.3 header and reformats the header to
+ * the appropriate IEEE 802.11 header based on which interface the packet is
+ * being transmitted on.
*
- * Returns: NETDEV_TX_OK both on success and on failure. On failure skb will
- * be freed.
+ * Note that this function also takes care of the TX status request and
+ * potential unsharing of the SKB - this needs to be interleaved with the
+ * header building.
*
- * This function takes in an Ethernet header and encapsulates it with suitable
- * IEEE 802.11 header based on which interface the packet is coming in. The
- * encapsulated packet will then be passed to master interface, wlan#.11, for
- * transmission (through low-level driver).
+ * The function requires the read-side RCU lock held
+ *
+ * Returns: the (possibly reallocated) skb or an ERR_PTR() code
*/
-netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
- struct net_device *dev)
+static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u32 info_flags)
{
- struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local;
struct ieee80211_tx_info *info;
int head_need;
@@ -1819,25 +1819,17 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
bool wme_sta = false, authorized = false, tdls_auth = false;
bool tdls_peer = false, tdls_setup_frame = false;
bool multicast;
- u32 info_flags = 0;
u16 info_id = 0;
struct ieee80211_chanctx_conf *chanctx_conf;
struct ieee80211_sub_if_data *ap_sdata;
enum ieee80211_band band;
-
- if (unlikely(skb->len < ETH_HLEN))
- goto fail;
+ int ret;
/* convert Ethernet header to proper 802.11 header (based on
* operation mode) */
ethertype = (skb->data[12] << 8) | skb->data[13];
fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA);
- rcu_read_lock();
-
- /* Measure frame arrival for Tx latency statistics calculation */
- ieee80211_tx_latency_start_msrmnt(local, skb);
-
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP_VLAN:
sta = rcu_dereference(sdata->u.vlan.sta);
@@ -1855,8 +1847,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
ap_sdata = container_of(sdata->bss, struct ieee80211_sub_if_data,
u.ap);
chanctx_conf = rcu_dereference(ap_sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
band = chanctx_conf->def.chan->band;
if (sta)
break;
@@ -1864,8 +1858,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
case NL80211_IFTYPE_AP:
if (sdata->vif.type == NL80211_IFTYPE_AP)
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS);
/* DA BSSID SA */
memcpy(hdr.addr1, skb->data, ETH_ALEN);
@@ -1952,8 +1948,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
}
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
band = chanctx_conf->def.chan->band;
break;
#endif
@@ -1983,8 +1981,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
* of a link teardown after a TDLS sta is removed due to being
* unreachable.
*/
- if (tdls_peer && !tdls_auth && !tdls_setup_frame)
- goto fail_rcu;
+ if (tdls_peer && !tdls_auth && !tdls_setup_frame) {
+ ret = -EINVAL;
+ goto free;
+ }
/* send direct packets to authorized TDLS peers */
if (tdls_peer && tdls_auth) {
@@ -2012,8 +2012,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
hdrlen = 24;
}
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
band = chanctx_conf->def.chan->band;
break;
case NL80211_IFTYPE_OCB:
@@ -2023,8 +2025,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
eth_broadcast_addr(hdr.addr3);
hdrlen = 24;
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
band = chanctx_conf->def.chan->band;
break;
case NL80211_IFTYPE_ADHOC:
@@ -2034,12 +2038,15 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN);
hdrlen = 24;
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
- if (!chanctx_conf)
- goto fail_rcu;
+ if (!chanctx_conf) {
+ ret = -ENOTCONN;
+ goto free;
+ }
band = chanctx_conf->def.chan->band;
break;
default:
- goto fail_rcu;
+ ret = -EINVAL;
+ goto free;
}
/*
@@ -2077,12 +2084,13 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
!ether_addr_equal(sdata->vif.addr, skb->data + ETH_ALEN)))) {
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
net_info_ratelimited("%s: dropped frame to %pM (unauthorized port)\n",
- dev->name, hdr.addr1);
+ sdata->name, hdr.addr1);
#endif
I802_DEBUG_INC(local->tx_handlers_drop_unauth_port);
- goto fail_rcu;
+ ret = -EPERM;
+ goto free;
}
if (unlikely(!multicast && skb->sk &&
@@ -2119,8 +2127,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
skb = skb_clone(skb, GFP_ATOMIC);
kfree_skb(tmp_skb);
- if (!skb)
- goto fail_rcu;
+ if (!skb) {
+ ret = -ENOMEM;
+ goto free;
+ }
}
hdr.frame_control = fc;
@@ -2169,7 +2179,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
if (ieee80211_skb_resize(sdata, skb, head_need, true)) {
ieee80211_free_txskb(&local->hw, skb);
skb = NULL;
- goto fail_rcu;
+ return ERR_PTR(-ENOMEM);
}
}
@@ -2203,9 +2213,6 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
nh_pos += hdrlen;
h_pos += hdrlen;
- dev->stats.tx_packets++;
- dev->stats.tx_bytes += skb->len;
-
/* Update skb pointers to various headers since this modified frame
* is going to go through Linux networking code that may potentially
* need things like pointer to IP header. */
@@ -2216,23 +2223,90 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
info = IEEE80211_SKB_CB(skb);
memset(info, 0, sizeof(*info));
- dev->trans_start = jiffies;
-
info->flags = info_flags;
info->ack_frame_id = info_id;
+ info->band = band;
- ieee80211_xmit(sdata, skb, band);
- rcu_read_unlock();
+ return skb;
+ free:
+ kfree_skb(skb);
+ return ERR_PTR(ret);
+}
- return NETDEV_TX_OK;
+void __ieee80211_subif_start_xmit(struct sk_buff *skb,
+ struct net_device *dev,
+ u32 info_flags)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ struct ieee80211_local *local = sdata->local;
+
+ if (unlikely(skb->len < ETH_HLEN)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ rcu_read_lock();
+
+ /* Measure frame arrival for Tx latency statistics calculation */
+ ieee80211_tx_latency_start_msrmnt(local, skb);
+
+ skb = ieee80211_build_hdr(sdata, skb, info_flags);
+ if (IS_ERR(skb))
+ goto out;
+
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ dev->trans_start = jiffies;
- fail_rcu:
+ ieee80211_xmit(sdata, skb);
+ out:
rcu_read_unlock();
- fail:
- dev_kfree_skb(skb);
+}
+
+/**
+ * ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs
+ * @skb: packet to be sent
+ * @dev: incoming interface
+ *
+ * On failure skb will be freed.
+ */
+netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ __ieee80211_subif_start_xmit(skb, dev, 0);
return NETDEV_TX_OK;
}
+struct sk_buff *
+ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u32 info_flags)
+{
+ struct ieee80211_hdr *hdr;
+ struct ieee80211_tx_data tx = {
+ .local = sdata->local,
+ .sdata = sdata,
+ };
+
+ rcu_read_lock();
+
+ skb = ieee80211_build_hdr(sdata, skb, info_flags);
+ if (IS_ERR(skb))
+ goto out;
+
+ hdr = (void *)skb->data;
+ tx.sta = sta_info_get(sdata, hdr->addr1);
+ tx.skb = skb;
+
+ if (ieee80211_tx_h_select_key(&tx) != TX_CONTINUE) {
+ rcu_read_unlock();
+ kfree_skb(skb);
+ return ERR_PTR(-EINVAL);
+ }
+
+out:
+ rcu_read_unlock();
+ return skb;
+}
/*
* ieee80211_clear_tx_pending may not be called in a context where
@@ -2272,8 +2346,8 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local,
dev_kfree_skb(skb);
return true;
}
- result = ieee80211_tx(sdata, skb, true,
- chanctx_conf->def.chan->band);
+ info->band = chanctx_conf->def.chan->band;
+ result = ieee80211_tx(sdata, skb, true);
} else {
struct sk_buff_head skbs;
@@ -2887,19 +2961,16 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw,
EXPORT_SYMBOL(ieee80211_nullfunc_get);
struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
+ const u8 *src_addr,
const u8 *ssid, size_t ssid_len,
size_t tailroom)
{
- struct ieee80211_sub_if_data *sdata;
- struct ieee80211_local *local;
+ struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_hdr_3addr *hdr;
struct sk_buff *skb;
size_t ie_ssid_len;
u8 *pos;
- sdata = vif_to_sdata(vif);
- local = sdata->local;
ie_ssid_len = 2 + ssid_len;
skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*hdr) +
@@ -2914,7 +2985,7 @@ struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw,
hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
IEEE80211_STYPE_PROBE_REQ);
eth_broadcast_addr(hdr->addr1);
- memcpy(hdr->addr2, vif->addr, ETH_ALEN);
+ memcpy(hdr->addr2, src_addr, ETH_ALEN);
eth_broadcast_addr(hdr->addr3);
pos = skb_put(skb, ie_ssid_len);
@@ -3033,6 +3104,97 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
}
EXPORT_SYMBOL(ieee80211_get_buffered_bc);
+int ieee80211_reserve_tid(struct ieee80211_sta *pubsta, u8 tid)
+{
+ struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct ieee80211_local *local = sdata->local;
+ int ret;
+ u32 queues;
+
+ lockdep_assert_held(&local->sta_mtx);
+
+ /* only some cases are supported right now */
+ switch (sdata->vif.type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_AP_VLAN:
+ break;
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (WARN_ON(tid >= IEEE80211_NUM_UPS))
+ return -EINVAL;
+
+ if (sta->reserved_tid == tid) {
+ ret = 0;
+ goto out;
+ }
+
+ if (sta->reserved_tid != IEEE80211_TID_UNRESERVED) {
+ sdata_err(sdata, "TID reservation already active\n");
+ ret = -EALREADY;
+ goto out;
+ }
+
+ ieee80211_stop_vif_queues(sdata->local, sdata,
+ IEEE80211_QUEUE_STOP_REASON_RESERVE_TID);
+
+ synchronize_net();
+
+ /* Tear down BA sessions so we stop aggregating on this TID */
+ if (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION) {
+ set_sta_flag(sta, WLAN_STA_BLOCK_BA);
+ __ieee80211_stop_tx_ba_session(sta, tid,
+ AGG_STOP_LOCAL_REQUEST);
+ }
+
+ queues = BIT(sdata->vif.hw_queue[ieee802_1d_to_ac[tid]]);
+ __ieee80211_flush_queues(local, sdata, queues);
+
+ sta->reserved_tid = tid;
+
+ ieee80211_wake_vif_queues(local, sdata,
+ IEEE80211_QUEUE_STOP_REASON_RESERVE_TID);
+
+ if (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION)
+ clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
+
+ ret = 0;
+ out:
+ return ret;
+}
+EXPORT_SYMBOL(ieee80211_reserve_tid);
+
+void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid)
+{
+ struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+ lockdep_assert_held(&sdata->local->sta_mtx);
+
+ /* only some cases are supported right now */
+ switch (sdata->vif.type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_AP_VLAN:
+ break;
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ if (tid != sta->reserved_tid) {
+ sdata_err(sdata, "TID to unreserve (%d) isn't reserved\n", tid);
+ return;
+ }
+
+ sta->reserved_tid = IEEE80211_TID_UNRESERVED;
+}
+EXPORT_SYMBOL(ieee80211_unreserve_tid);
+
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, int tid,
enum ieee80211_band band)
@@ -3054,6 +3216,7 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
* requirements are that we do not come into tx with bhs on.
*/
local_bh_disable();
- ieee80211_xmit(sdata, skb, band);
+ IEEE80211_SKB_CB(skb)->band = band;
+ ieee80211_xmit(sdata, skb);
local_bh_enable();
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index f9319a5dca64..bb9664cb8831 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -576,15 +576,19 @@ ieee80211_get_vif_queues(struct ieee80211_local *local,
return queues;
}
-void ieee80211_flush_queues(struct ieee80211_local *local,
- struct ieee80211_sub_if_data *sdata)
+void __ieee80211_flush_queues(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ unsigned int queues)
{
- unsigned int queues;
-
if (!local->ops->flush)
return;
- queues = ieee80211_get_vif_queues(local, sdata);
+ /*
+ * If no queue was set, or if the HW doesn't support
+ * IEEE80211_HW_QUEUE_CONTROL - flush all queues
+ */
+ if (!queues || !(local->hw.flags & IEEE80211_HW_QUEUE_CONTROL))
+ queues = ieee80211_get_vif_queues(local, sdata);
ieee80211_stop_queues_by_reason(&local->hw, queues,
IEEE80211_QUEUE_STOP_REASON_FLUSH,
@@ -597,6 +601,12 @@ void ieee80211_flush_queues(struct ieee80211_local *local,
false);
}
+void ieee80211_flush_queues(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata)
+{
+ __ieee80211_flush_queues(local, sdata, 0);
+}
+
void ieee80211_stop_vif_queues(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
enum queue_stop_reason reason)
@@ -831,6 +841,9 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
case WLAN_EID_SECONDARY_CHANNEL_OFFSET:
case WLAN_EID_WIDE_BW_CHANNEL_SWITCH:
case WLAN_EID_CHAN_SWITCH_PARAM:
+ case WLAN_EID_EXT_CAPABILITY:
+ case WLAN_EID_CHAN_SWITCH_TIMING:
+ case WLAN_EID_LINK_ID:
/*
* not listing WLAN_EID_CHANNEL_SWITCH_WRAPPER -- it seems possible
* that if the content gets bigger it might be needed more than once
@@ -850,6 +863,24 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
elem_parse_failed = false;
switch (id) {
+ case WLAN_EID_LINK_ID:
+ if (elen + 2 != sizeof(struct ieee80211_tdls_lnkie)) {
+ elem_parse_failed = true;
+ break;
+ }
+ elems->lnk_id = (void *)(pos - 2);
+ break;
+ case WLAN_EID_CHAN_SWITCH_TIMING:
+ if (elen != sizeof(struct ieee80211_ch_switch_timing)) {
+ elem_parse_failed = true;
+ break;
+ }
+ elems->ch_sw_timing = (void *)pos;
+ break;
+ case WLAN_EID_EXT_CAPABILITY:
+ elems->ext_capab = pos;
+ elems->ext_capab_len = elen;
+ break;
case WLAN_EID_SSID:
elems->ssid = pos;
elems->ssid_len = elen;
@@ -1492,7 +1523,8 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
};
struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
- u8 *dst, u32 ratemask,
+ const u8 *src, const u8 *dst,
+ u32 ratemask,
struct ieee80211_channel *chan,
const u8 *ssid, size_t ssid_len,
const u8 *ie, size_t ie_len,
@@ -1517,8 +1549,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
else
chandef.chan = chan;
- skb = ieee80211_probereq_get(&local->hw, &sdata->vif,
- ssid, ssid_len, 100 + ie_len);
+ skb = ieee80211_probereq_get(&local->hw, src, ssid, ssid_len,
+ 100 + ie_len);
if (!skb)
return NULL;
@@ -1540,7 +1572,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
return skb;
}
-void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
+void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata,
+ const u8 *src, const u8 *dst,
const u8 *ssid, size_t ssid_len,
const u8 *ie, size_t ie_len,
u32 ratemask, bool directed, u32 tx_flags,
@@ -1548,7 +1581,7 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
{
struct sk_buff *skb;
- skb = ieee80211_build_probe_req(sdata, dst, ratemask, channel,
+ skb = ieee80211_build_probe_req(sdata, src, dst, ratemask, channel,
ssid, ssid_len,
ie, ie_len, directed);
if (skb) {
@@ -1690,6 +1723,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
int res, i;
bool reconfig_due_to_wowlan = false;
struct ieee80211_sub_if_data *sched_scan_sdata;
+ struct cfg80211_sched_scan_request *sched_scan_req;
bool sched_scan_stopped = false;
#ifdef CONFIG_PM
@@ -1980,13 +2014,15 @@ int ieee80211_reconfig(struct ieee80211_local *local)
mutex_lock(&local->mtx);
sched_scan_sdata = rcu_dereference_protected(local->sched_scan_sdata,
lockdep_is_held(&local->mtx));
- if (sched_scan_sdata && local->sched_scan_req)
+ sched_scan_req = rcu_dereference_protected(local->sched_scan_req,
+ lockdep_is_held(&local->mtx));
+ if (sched_scan_sdata && sched_scan_req)
/*
* Sched scan stopped, but we don't want to report it. Instead,
* we're trying to reschedule.
*/
if (__ieee80211_request_sched_scan_start(sched_scan_sdata,
- local->sched_scan_req))
+ sched_scan_req))
sched_scan_stopped = true;
mutex_unlock(&local->mtx);
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 671ce0d27a80..bc9e8fc48785 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -287,6 +287,8 @@ enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta)
/* fall through */
case NL80211_CHAN_WIDTH_20_NOHT:
case NL80211_CHAN_WIDTH_20:
+ bw = IEEE80211_STA_RX_BW_20;
+ break;
case NL80211_CHAN_WIDTH_40:
bw = sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c
index fdf52db95b33..9eb0aee9105b 100644
--- a/net/mac80211/wme.c
+++ b/net/mac80211/wme.c
@@ -53,6 +53,36 @@ static int wme_downgrade_ac(struct sk_buff *skb)
}
}
+/**
+ * ieee80211_fix_reserved_tid - return the TID to use if this one is reserved
+ * @tid: the assumed-reserved TID
+ *
+ * Returns: the alternative TID to use, or 0 on error
+ */
+static inline u8 ieee80211_fix_reserved_tid(u8 tid)
+{
+ switch (tid) {
+ case 0:
+ return 3;
+ case 1:
+ return 2;
+ case 2:
+ return 1;
+ case 3:
+ return 0;
+ case 4:
+ return 5;
+ case 5:
+ return 4;
+ case 6:
+ return 7;
+ case 7:
+ return 6;
+ }
+
+ return 0;
+}
+
static u16 ieee80211_downgrade_queue(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta, struct sk_buff *skb)
{
@@ -77,6 +107,10 @@ static u16 ieee80211_downgrade_queue(struct ieee80211_sub_if_data *sdata,
}
}
+ /* Check to see if this is a reserved TID */
+ if (sta && sta->reserved_tid == skb->priority)
+ skb->priority = ieee80211_fix_reserved_tid(skb->priority);
+
/* look up which queue to use for frames with this 1d tag */
return ieee802_1d_to_ac[skb->priority];
}
@@ -143,6 +177,11 @@ u16 ieee80211_select_queue(struct ieee80211_sub_if_data *sdata,
break;
#endif
case NL80211_IFTYPE_STATION:
+ /* might be a TDLS station */
+ sta = sta_info_get(sdata, skb->data);
+ if (sta)
+ qos = sta->sta.wme;
+
ra = sdata->u.mgd.bssid;
break;
case NL80211_IFTYPE_ADHOC:
diff --git a/net/mac802154/Makefile b/net/mac802154/Makefile
index 2e497d0c829a..702d8b466317 100644
--- a/net/mac802154/Makefile
+++ b/net/mac802154/Makefile
@@ -1,5 +1,5 @@
obj-$(CONFIG_MAC802154) += mac802154.o
mac802154-objs := main.o rx.o tx.o mac_cmd.o mib.o \
- iface.o llsec.o util.o
+ iface.o llsec.o util.o cfg.o
ccflags-y += -D__CHECK_ENDIAN__
diff --git a/net/mac802154/cfg.c b/net/mac802154/cfg.c
new file mode 100644
index 000000000000..c035708ada16
--- /dev/null
+++ b/net/mac802154/cfg.c
@@ -0,0 +1,210 @@
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Authors:
+ * Alexander Aring <aar@pengutronix.de>
+ *
+ * Based on: net/mac80211/cfg.c
+ */
+
+#include <net/rtnetlink.h>
+#include <net/cfg802154.h>
+
+#include "ieee802154_i.h"
+#include "driver-ops.h"
+#include "cfg.h"
+
+static struct net_device *
+ieee802154_add_iface_deprecated(struct wpan_phy *wpan_phy,
+ const char *name, int type)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ struct net_device *dev;
+
+ rtnl_lock();
+ dev = ieee802154_if_add(local, name, type,
+ cpu_to_le64(0x0000000000000000ULL));
+ rtnl_unlock();
+
+ return dev;
+}
+
+static void ieee802154_del_iface_deprecated(struct wpan_phy *wpan_phy,
+ struct net_device *dev)
+{
+ struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+
+ ieee802154_if_remove(sdata);
+}
+
+static int
+ieee802154_add_iface(struct wpan_phy *phy, const char *name,
+ enum nl802154_iftype type, __le64 extended_addr)
+{
+ struct ieee802154_local *local = wpan_phy_priv(phy);
+ struct net_device *err;
+
+ err = ieee802154_if_add(local, name, type, extended_addr);
+ if (IS_ERR(err))
+ return PTR_ERR(err);
+
+ return 0;
+}
+
+static int
+ieee802154_del_iface(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev)
+{
+ ieee802154_if_remove(IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev));
+
+ return 0;
+}
+
+static int
+ieee802154_set_channel(struct wpan_phy *wpan_phy, u8 page, u8 channel)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ int ret;
+
+ ASSERT_RTNL();
+
+ /* check if phy support this setting */
+ if (!(wpan_phy->channels_supported[page] & BIT(channel)))
+ return -EINVAL;
+
+ ret = drv_set_channel(local, page, channel);
+ if (!ret) {
+ wpan_phy->current_page = page;
+ wpan_phy->current_channel = channel;
+ }
+
+ return ret;
+}
+
+static int
+ieee802154_set_pan_id(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+ __le16 pan_id)
+{
+ ASSERT_RTNL();
+
+ /* TODO
+ * I am not sure about to check here on broadcast pan_id.
+ * Broadcast is a valid setting, comment from 802.15.4:
+ * If this value is 0xffff, the device is not associated.
+ *
+ * This could useful to simple deassociate an device.
+ */
+ if (pan_id == cpu_to_le16(IEEE802154_PAN_ID_BROADCAST))
+ return -EINVAL;
+
+ wpan_dev->pan_id = pan_id;
+ return 0;
+}
+
+static int
+ieee802154_set_backoff_exponent(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ u8 min_be, u8 max_be)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+
+ ASSERT_RTNL();
+
+ if (!(local->hw.flags & IEEE802154_HW_CSMA_PARAMS))
+ return -EOPNOTSUPP;
+
+ wpan_dev->min_be = min_be;
+ wpan_dev->max_be = max_be;
+ return 0;
+}
+
+static int
+ieee802154_set_short_addr(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+ __le16 short_addr)
+{
+ ASSERT_RTNL();
+
+ /* TODO
+ * I am not sure about to check here on broadcast short_addr.
+ * Broadcast is a valid setting, comment from 802.15.4:
+ * A value of 0xfffe indicates that the device has
+ * associated but has not been allocated an address. A
+ * value of 0xffff indicates that the device does not
+ * have a short address.
+ *
+ * I think we should allow to set these settings but
+ * don't allow to allow socket communication with it.
+ */
+ if (short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC) ||
+ short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST))
+ return -EINVAL;
+
+ wpan_dev->short_addr = short_addr;
+ return 0;
+}
+
+static int
+ieee802154_set_max_csma_backoffs(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ u8 max_csma_backoffs)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+
+ ASSERT_RTNL();
+
+ if (!(local->hw.flags & IEEE802154_HW_CSMA_PARAMS))
+ return -EOPNOTSUPP;
+
+ wpan_dev->csma_retries = max_csma_backoffs;
+ return 0;
+}
+
+static int
+ieee802154_set_max_frame_retries(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ s8 max_frame_retries)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+
+ ASSERT_RTNL();
+
+ if (!(local->hw.flags & IEEE802154_HW_FRAME_RETRIES))
+ return -EOPNOTSUPP;
+
+ wpan_dev->frame_retries = max_frame_retries;
+ return 0;
+}
+
+static int
+ieee802154_set_lbt_mode(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+ bool mode)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+
+ ASSERT_RTNL();
+
+ if (!(local->hw.flags & IEEE802154_HW_LBT))
+ return -EOPNOTSUPP;
+
+ wpan_dev->lbt = mode;
+ return 0;
+}
+
+const struct cfg802154_ops mac802154_config_ops = {
+ .add_virtual_intf_deprecated = ieee802154_add_iface_deprecated,
+ .del_virtual_intf_deprecated = ieee802154_del_iface_deprecated,
+ .add_virtual_intf = ieee802154_add_iface,
+ .del_virtual_intf = ieee802154_del_iface,
+ .set_channel = ieee802154_set_channel,
+ .set_pan_id = ieee802154_set_pan_id,
+ .set_short_addr = ieee802154_set_short_addr,
+ .set_backoff_exponent = ieee802154_set_backoff_exponent,
+ .set_max_csma_backoffs = ieee802154_set_max_csma_backoffs,
+ .set_max_frame_retries = ieee802154_set_max_frame_retries,
+ .set_lbt_mode = ieee802154_set_lbt_mode,
+};
diff --git a/net/mac802154/cfg.h b/net/mac802154/cfg.h
new file mode 100644
index 000000000000..e2718f981e82
--- /dev/null
+++ b/net/mac802154/cfg.h
@@ -0,0 +1,9 @@
+/* mac802154 configuration hooks for cfg802154
+ */
+
+#ifndef __CFG_H
+#define __CFG_H
+
+extern const struct cfg802154_ops mac802154_config_ops;
+
+#endif /* __CFG_H */
diff --git a/net/mac802154/driver-ops.h b/net/mac802154/driver-ops.h
index dfd29ffb8fee..f21e864613d0 100644
--- a/net/mac802154/driver-ops.h
+++ b/net/mac802154/driver-ops.h
@@ -50,16 +50,15 @@ static inline void drv_stop(struct ieee802154_local *local)
local->started = false;
}
-static inline int drv_set_channel(struct ieee802154_local *local,
- const u8 page, const u8 channel)
+static inline int
+drv_set_channel(struct ieee802154_local *local, u8 page, u8 channel)
{
might_sleep();
return local->ops->set_channel(&local->hw, page, channel);
}
-static inline int drv_set_tx_power(struct ieee802154_local *local,
- const s8 dbm)
+static inline int drv_set_tx_power(struct ieee802154_local *local, s8 dbm)
{
might_sleep();
@@ -71,8 +70,7 @@ static inline int drv_set_tx_power(struct ieee802154_local *local,
return local->ops->set_txpower(&local->hw, dbm);
}
-static inline int drv_set_cca_mode(struct ieee802154_local *local,
- const u8 cca_mode)
+static inline int drv_set_cca_mode(struct ieee802154_local *local, u8 cca_mode)
{
might_sleep();
@@ -84,8 +82,7 @@ static inline int drv_set_cca_mode(struct ieee802154_local *local,
return local->ops->set_cca_mode(&local->hw, cca_mode);
}
-static inline int drv_set_lbt_mode(struct ieee802154_local *local,
- const bool mode)
+static inline int drv_set_lbt_mode(struct ieee802154_local *local, bool mode)
{
might_sleep();
@@ -97,8 +94,8 @@ static inline int drv_set_lbt_mode(struct ieee802154_local *local,
return local->ops->set_lbt(&local->hw, mode);
}
-static inline int drv_set_cca_ed_level(struct ieee802154_local *local,
- const s32 ed_level)
+static inline int
+drv_set_cca_ed_level(struct ieee802154_local *local, s32 ed_level)
{
might_sleep();
@@ -110,8 +107,7 @@ static inline int drv_set_cca_ed_level(struct ieee802154_local *local,
return local->ops->set_cca_ed_level(&local->hw, ed_level);
}
-static inline int drv_set_pan_id(struct ieee802154_local *local,
- const __le16 pan_id)
+static inline int drv_set_pan_id(struct ieee802154_local *local, __le16 pan_id)
{
struct ieee802154_hw_addr_filt filt;
@@ -128,8 +124,8 @@ static inline int drv_set_pan_id(struct ieee802154_local *local,
IEEE802154_AFILT_PANID_CHANGED);
}
-static inline int drv_set_extended_addr(struct ieee802154_local *local,
- const __le64 extended_addr)
+static inline int
+drv_set_extended_addr(struct ieee802154_local *local, __le64 extended_addr)
{
struct ieee802154_hw_addr_filt filt;
@@ -146,8 +142,8 @@ static inline int drv_set_extended_addr(struct ieee802154_local *local,
IEEE802154_AFILT_IEEEADDR_CHANGED);
}
-static inline int drv_set_short_addr(struct ieee802154_local *local,
- const __le16 short_addr)
+static inline int
+drv_set_short_addr(struct ieee802154_local *local, __le16 short_addr)
{
struct ieee802154_hw_addr_filt filt;
@@ -164,8 +160,8 @@ static inline int drv_set_short_addr(struct ieee802154_local *local,
IEEE802154_AFILT_SADDR_CHANGED);
}
-static inline int drv_set_pan_coord(struct ieee802154_local *local,
- const bool is_coord)
+static inline int
+drv_set_pan_coord(struct ieee802154_local *local, bool is_coord)
{
struct ieee802154_hw_addr_filt filt;
@@ -182,9 +178,9 @@ static inline int drv_set_pan_coord(struct ieee802154_local *local,
IEEE802154_AFILT_PANC_CHANGED);
}
-static inline int drv_set_csma_params(struct ieee802154_local *local,
- u8 min_be, u8 max_be,
- u8 max_csma_backoffs)
+static inline int
+drv_set_csma_params(struct ieee802154_local *local, u8 min_be, u8 max_be,
+ u8 max_csma_backoffs)
{
might_sleep();
@@ -197,8 +193,8 @@ static inline int drv_set_csma_params(struct ieee802154_local *local,
max_csma_backoffs);
}
-static inline int drv_set_max_frame_retries(struct ieee802154_local *local,
- s8 max_frame_retries)
+static inline int
+drv_set_max_frame_retries(struct ieee802154_local *local, s8 max_frame_retries)
{
might_sleep();
@@ -210,8 +206,8 @@ static inline int drv_set_max_frame_retries(struct ieee802154_local *local,
return local->ops->set_frame_retries(&local->hw, max_frame_retries);
}
-static inline int drv_set_promiscuous_mode(struct ieee802154_local *local,
- const bool on)
+static inline int
+drv_set_promiscuous_mode(struct ieee802154_local *local, bool on)
{
might_sleep();
diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h
index 1086a9d96f8f..bebd70ffc7a3 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -20,7 +20,10 @@
#define __IEEE802154_I_H
#include <linux/mutex.h>
+#include <linux/hrtimer.h>
+#include <net/cfg802154.h>
#include <net/mac802154.h>
+#include <net/nl802154.h>
#include <net/ieee802154_netdev.h>
#include "llsec.h"
@@ -50,6 +53,8 @@ struct ieee802154_local {
*/
struct workqueue_struct *workqueue;
+ struct hrtimer ifs_timer;
+
bool started;
struct tasklet_struct tasklet;
@@ -73,36 +78,31 @@ enum ieee802154_sdata_state_bits {
struct ieee802154_sub_if_data {
struct list_head list; /* the ieee802154_priv->slaves list */
+ struct wpan_dev wpan_dev;
+
struct ieee802154_local *local;
struct net_device *dev;
- int type;
unsigned long state;
+ char name[IFNAMSIZ];
spinlock_t mib_lock;
- __le16 pan_id;
- __le16 short_addr;
- __le64 extended_addr;
- bool promisuous_mode;
-
- struct ieee802154_mac_params mac_params;
-
- /* MAC BSN field */
- u8 bsn;
- /* MAC DSN field */
- u8 dsn;
-
/* protects sec from concurrent access by netlink. access by
* encrypt/decrypt/header_create safe without additional protection.
*/
struct mutex sec_mtx;
struct mac802154_llsec sec;
+ /* must be last, dynamically sized area in this! */
+ struct ieee802154_vif vif;
};
#define MAC802154_CHAN_NONE 0xff /* No channel is assigned */
+/* utility functions/constants */
+extern const void *const mac802154_wpan_phy_privid; /* for wpan_phy privid */
+
static inline struct ieee802154_local *
hw_to_local(struct ieee802154_hw *hw)
{
@@ -115,27 +115,29 @@ IEEE802154_DEV_TO_SUB_IF(const struct net_device *dev)
return netdev_priv(dev);
}
+static inline struct ieee802154_sub_if_data *
+IEEE802154_WPAN_DEV_TO_SUB_IF(struct wpan_dev *wpan_dev)
+{
+ return container_of(wpan_dev, struct ieee802154_sub_if_data, wpan_dev);
+}
+
static inline bool
ieee802154_sdata_running(struct ieee802154_sub_if_data *sdata)
{
return test_bit(SDATA_STATE_RUNNING, &sdata->state);
}
-extern struct ieee802154_reduced_mlme_ops mac802154_mlme_reduced;
extern struct ieee802154_mlme_ops mac802154_mlme_wpan;
-void mac802154_monitor_setup(struct net_device *dev);
netdev_tx_t
ieee802154_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev);
-
-void mac802154_wpan_setup(struct net_device *dev);
netdev_tx_t
ieee802154_subif_start_xmit(struct sk_buff *skb, struct net_device *dev);
+enum hrtimer_restart ieee802154_xmit_ifs_timer(struct hrtimer *timer);
/* MIB callbacks */
void mac802154_dev_set_short_addr(struct net_device *dev, __le16 val);
__le16 mac802154_dev_get_short_addr(const struct net_device *dev);
-void mac802154_dev_set_ieee_addr(struct net_device *dev);
__le16 mac802154_dev_get_pan_id(const struct net_device *dev);
void mac802154_dev_set_pan_id(struct net_device *dev, __le16 val);
void mac802154_dev_set_page_channel(struct net_device *dev, u8 page, u8 chan);
@@ -174,4 +176,13 @@ void mac802154_get_table(struct net_device *dev,
struct ieee802154_llsec_table **t);
void mac802154_unlock_table(struct net_device *dev);
+/* interface handling */
+int ieee802154_iface_init(void);
+void ieee802154_iface_exit(void);
+void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata);
+struct net_device *
+ieee802154_if_add(struct ieee802154_local *local, const char *name,
+ enum nl802154_iftype type, __le64 extended_addr);
+void ieee802154_remove_interfaces(struct ieee802154_local *local);
+
#endif /* __IEEE802154_I_H */
diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c
index f7a6f83301e2..38dfc72d24b6 100644
--- a/net/mac802154/iface.c
+++ b/net/mac802154/iface.c
@@ -22,9 +22,7 @@
#include <linux/if_arp.h>
#include <linux/ieee802154.h>
-#include <net/rtnetlink.h>
-#include <linux/nl802154.h>
-#include <net/af_ieee802154.h>
+#include <net/nl802154.h>
#include <net/mac802154.h>
#include <net/ieee802154_netdev.h>
#include <net/cfg802154.h>
@@ -36,16 +34,17 @@ static int mac802154_wpan_update_llsec(struct net_device *dev)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
struct ieee802154_mlme_ops *ops = ieee802154_mlme_ops(dev);
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
int rc = 0;
if (ops->llsec) {
struct ieee802154_llsec_params params;
int changed = 0;
- params.pan_id = sdata->pan_id;
+ params.pan_id = wpan_dev->pan_id;
changed |= IEEE802154_LLSEC_PARAM_PAN_ID;
- params.hwaddr = sdata->extended_addr;
+ params.hwaddr = wpan_dev->extended_addr;
changed |= IEEE802154_LLSEC_PARAM_HWADDR;
rc = ops->llsec->set_params(dev, &params, changed);
@@ -58,10 +57,13 @@ static int
mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
struct sockaddr_ieee802154 *sa =
(struct sockaddr_ieee802154 *)&ifr->ifr_addr;
int err = -ENOIOCTLCMD;
+ ASSERT_RTNL();
+
spin_lock_bh(&sdata->mib_lock);
switch (cmd) {
@@ -69,8 +71,8 @@ mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
u16 pan_id, short_addr;
- pan_id = le16_to_cpu(sdata->pan_id);
- short_addr = le16_to_cpu(sdata->short_addr);
+ pan_id = le16_to_cpu(wpan_dev->pan_id);
+ short_addr = le16_to_cpu(wpan_dev->short_addr);
if (pan_id == IEEE802154_PANID_BROADCAST ||
short_addr == IEEE802154_ADDR_BROADCAST) {
err = -EADDRNOTAVAIL;
@@ -86,6 +88,11 @@ mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
break;
}
case SIOCSIFADDR:
+ if (netif_running(dev)) {
+ spin_unlock_bh(&sdata->mib_lock);
+ return -EBUSY;
+ }
+
dev_warn(&dev->dev,
"Using DEBUGing ioctl SIOCSIFADDR isn't recommended!\n");
if (sa->family != AF_IEEE802154 ||
@@ -97,8 +104,8 @@ mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
break;
}
- sdata->pan_id = cpu_to_le16(sa->addr.pan_id);
- sdata->short_addr = cpu_to_le16(sa->addr.short_addr);
+ wpan_dev->pan_id = cpu_to_le16(sa->addr.pan_id);
+ wpan_dev->short_addr = cpu_to_le16(sa->addr.short_addr);
err = mac802154_wpan_update_llsec(dev);
break;
@@ -110,37 +117,21 @@ mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
static int mac802154_wpan_mac_addr(struct net_device *dev, void *p)
{
+ struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
struct sockaddr *addr = p;
+ __le64 extended_addr;
if (netif_running(dev))
return -EBUSY;
- /* FIXME: validate addr */
- memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
- mac802154_dev_set_ieee_addr(dev);
- return mac802154_wpan_update_llsec(dev);
-}
-
-int mac802154_set_mac_params(struct net_device *dev,
- const struct ieee802154_mac_params *params)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
-
- mutex_lock(&sdata->local->iflist_mtx);
- sdata->mac_params = *params;
- mutex_unlock(&sdata->local->iflist_mtx);
-
- return 0;
-}
+ ieee802154_be64_to_le64(&extended_addr, addr->sa_data);
+ if (!ieee802154_is_valid_extended_addr(extended_addr))
+ return -EINVAL;
-void mac802154_get_mac_params(struct net_device *dev,
- struct ieee802154_mac_params *params)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+ memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+ sdata->wpan_dev.extended_addr = extended_addr;
- mutex_lock(&sdata->local->iflist_mtx);
- *params = sdata->mac_params;
- mutex_unlock(&sdata->local->iflist_mtx);
+ return mac802154_wpan_update_llsec(dev);
}
static int mac802154_slave_open(struct net_device *dev)
@@ -152,10 +143,11 @@ static int mac802154_slave_open(struct net_device *dev)
ASSERT_RTNL();
- if (sdata->type == IEEE802154_DEV_WPAN) {
+ if (sdata->vif.type == NL802154_IFTYPE_NODE) {
mutex_lock(&sdata->local->iflist_mtx);
list_for_each_entry(subif, &sdata->local->interfaces, list) {
- if (subif != sdata && subif->type == sdata->type &&
+ if (subif != sdata &&
+ subif->vif.type == sdata->vif.type &&
ieee802154_sdata_running(subif)) {
mutex_unlock(&sdata->local->iflist_mtx);
return -EBUSY;
@@ -188,6 +180,7 @@ static int mac802154_wpan_open(struct net_device *dev)
int rc;
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
struct wpan_phy *phy = sdata->local->phy;
rc = mac802154_slave_open(dev);
@@ -197,47 +190,42 @@ static int mac802154_wpan_open(struct net_device *dev)
mutex_lock(&phy->pib_lock);
if (local->hw.flags & IEEE802154_HW_PROMISCUOUS) {
- rc = drv_set_promiscuous_mode(local, sdata->promisuous_mode);
+ rc = drv_set_promiscuous_mode(local,
+ wpan_dev->promiscuous_mode);
if (rc < 0)
goto out;
}
- if (local->hw.flags & IEEE802154_HW_TXPOWER) {
- rc = drv_set_tx_power(local, sdata->mac_params.transmit_power);
+ if (local->hw.flags & IEEE802154_HW_AFILT) {
+ rc = drv_set_pan_id(local, wpan_dev->pan_id);
if (rc < 0)
goto out;
- }
- if (local->hw.flags & IEEE802154_HW_LBT) {
- rc = drv_set_lbt_mode(local, sdata->mac_params.lbt);
+ rc = drv_set_extended_addr(local, wpan_dev->extended_addr);
if (rc < 0)
goto out;
- }
- if (local->hw.flags & IEEE802154_HW_CCA_MODE) {
- rc = drv_set_cca_mode(local, sdata->mac_params.cca_mode);
+ rc = drv_set_short_addr(local, wpan_dev->short_addr);
if (rc < 0)
goto out;
}
- if (local->hw.flags & IEEE802154_HW_CCA_ED_LEVEL) {
- rc = drv_set_cca_ed_level(local,
- sdata->mac_params.cca_ed_level);
+ if (local->hw.flags & IEEE802154_HW_LBT) {
+ rc = drv_set_lbt_mode(local, wpan_dev->lbt);
if (rc < 0)
goto out;
}
if (local->hw.flags & IEEE802154_HW_CSMA_PARAMS) {
- rc = drv_set_csma_params(local, sdata->mac_params.min_be,
- sdata->mac_params.max_be,
- sdata->mac_params.csma_retries);
+ rc = drv_set_csma_params(local, wpan_dev->min_be,
+ wpan_dev->max_be,
+ wpan_dev->csma_retries);
if (rc < 0)
goto out;
}
if (local->hw.flags & IEEE802154_HW_FRAME_RETRIES) {
- rc = drv_set_max_frame_retries(local,
- sdata->mac_params.frame_retries);
+ rc = drv_set_max_frame_retries(local, wpan_dev->frame_retries);
if (rc < 0)
goto out;
}
@@ -257,6 +245,8 @@ static int mac802154_slave_close(struct net_device *dev)
ASSERT_RTNL();
+ hrtimer_cancel(&local->ifs_timer);
+
netif_stop_queue(dev);
local->open_count--;
@@ -309,6 +299,7 @@ static int mac802154_header_create(struct sk_buff *skb,
{
struct ieee802154_hdr hdr;
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
struct ieee802154_mac_cb *cb = mac_cb(skb);
int hlen;
@@ -327,17 +318,17 @@ static int mac802154_header_create(struct sk_buff *skb,
if (!saddr) {
spin_lock_bh(&sdata->mib_lock);
- if (sdata->short_addr == cpu_to_le16(IEEE802154_ADDR_BROADCAST) ||
- sdata->short_addr == cpu_to_le16(IEEE802154_ADDR_UNDEF) ||
- sdata->pan_id == cpu_to_le16(IEEE802154_PANID_BROADCAST)) {
+ if (wpan_dev->short_addr == cpu_to_le16(IEEE802154_ADDR_BROADCAST) ||
+ wpan_dev->short_addr == cpu_to_le16(IEEE802154_ADDR_UNDEF) ||
+ wpan_dev->pan_id == cpu_to_le16(IEEE802154_PANID_BROADCAST)) {
hdr.source.mode = IEEE802154_ADDR_LONG;
- hdr.source.extended_addr = sdata->extended_addr;
+ hdr.source.extended_addr = wpan_dev->extended_addr;
} else {
hdr.source.mode = IEEE802154_ADDR_SHORT;
- hdr.source.short_addr = sdata->short_addr;
+ hdr.source.short_addr = wpan_dev->short_addr;
}
- hdr.source.pan_id = sdata->pan_id;
+ hdr.source.pan_id = wpan_dev->pan_id;
spin_unlock_bh(&sdata->mib_lock);
} else {
@@ -402,65 +393,196 @@ static void mac802154_wpan_free(struct net_device *dev)
free_netdev(dev);
}
-void mac802154_wpan_setup(struct net_device *dev)
+static void ieee802154_if_setup(struct net_device *dev)
{
- struct ieee802154_sub_if_data *sdata;
-
- dev->addr_len = IEEE802154_ADDR_LEN;
- memset(dev->broadcast, 0xff, IEEE802154_ADDR_LEN);
+ dev->addr_len = IEEE802154_EXTENDED_ADDR_LEN;
+ memset(dev->broadcast, 0xff, IEEE802154_EXTENDED_ADDR_LEN);
dev->hard_header_len = MAC802154_FRAME_HARD_HEADER_LEN;
- dev->header_ops = &mac802154_header_ops;
dev->needed_tailroom = 2 + 16; /* FCS + MIC */
dev->mtu = IEEE802154_MTU;
dev->tx_queue_len = 300;
- dev->type = ARPHRD_IEEE802154;
dev->flags = IFF_NOARP | IFF_BROADCAST;
+}
- dev->destructor = mac802154_wpan_free;
- dev->netdev_ops = &mac802154_wpan_ops;
- dev->ml_priv = &mac802154_mlme_wpan;
-
- sdata = IEEE802154_DEV_TO_SUB_IF(dev);
- sdata->type = IEEE802154_DEV_WPAN;
+static int
+ieee802154_setup_sdata(struct ieee802154_sub_if_data *sdata,
+ enum nl802154_iftype type)
+{
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
- spin_lock_init(&sdata->mib_lock);
- mutex_init(&sdata->sec_mtx);
+ /* set some type-dependent values */
+ sdata->vif.type = type;
+ sdata->wpan_dev.iftype = type;
- get_random_bytes(&sdata->bsn, 1);
- get_random_bytes(&sdata->dsn, 1);
+ get_random_bytes(&wpan_dev->bsn, 1);
+ get_random_bytes(&wpan_dev->dsn, 1);
/* defaults per 802.15.4-2011 */
- sdata->mac_params.min_be = 3;
- sdata->mac_params.max_be = 5;
- sdata->mac_params.csma_retries = 4;
+ wpan_dev->min_be = 3;
+ wpan_dev->max_be = 5;
+ wpan_dev->csma_retries = 4;
/* for compatibility, actual default is 3 */
- sdata->mac_params.frame_retries = -1;
+ wpan_dev->frame_retries = -1;
+
+ wpan_dev->pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
+ wpan_dev->short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
+
+ switch (type) {
+ case NL802154_IFTYPE_NODE:
+ ieee802154_be64_to_le64(&wpan_dev->extended_addr,
+ sdata->dev->dev_addr);
+
+ sdata->dev->header_ops = &mac802154_header_ops;
+ sdata->dev->destructor = mac802154_wpan_free;
+ sdata->dev->netdev_ops = &mac802154_wpan_ops;
+ sdata->dev->ml_priv = &mac802154_mlme_wpan;
+ wpan_dev->promiscuous_mode = false;
+
+ spin_lock_init(&sdata->mib_lock);
+ mutex_init(&sdata->sec_mtx);
+
+ mac802154_llsec_init(&sdata->sec);
+ break;
+ case NL802154_IFTYPE_MONITOR:
+ sdata->dev->destructor = free_netdev;
+ sdata->dev->netdev_ops = &mac802154_monitor_ops;
+ wpan_dev->promiscuous_mode = true;
+ break;
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
+struct net_device *
+ieee802154_if_add(struct ieee802154_local *local, const char *name,
+ enum nl802154_iftype type, __le64 extended_addr)
+{
+ struct net_device *ndev = NULL;
+ struct ieee802154_sub_if_data *sdata = NULL;
+ int ret = -ENOMEM;
+
+ ASSERT_RTNL();
+
+ ndev = alloc_netdev(sizeof(*sdata) + local->hw.vif_data_size, name,
+ NET_NAME_UNKNOWN, ieee802154_if_setup);
+ if (!ndev)
+ return ERR_PTR(-ENOMEM);
+
+ ndev->needed_headroom = local->hw.extra_tx_headroom;
+
+ ret = dev_alloc_name(ndev, ndev->name);
+ if (ret < 0)
+ goto err;
+
+ ieee802154_le64_to_be64(ndev->perm_addr,
+ &local->hw.phy->perm_extended_addr);
+ switch (type) {
+ case NL802154_IFTYPE_NODE:
+ ndev->type = ARPHRD_IEEE802154;
+ if (ieee802154_is_valid_extended_addr(extended_addr))
+ ieee802154_le64_to_be64(ndev->dev_addr, &extended_addr);
+ else
+ memcpy(ndev->dev_addr, ndev->perm_addr,
+ IEEE802154_EXTENDED_ADDR_LEN);
+ break;
+ case NL802154_IFTYPE_MONITOR:
+ ndev->type = ARPHRD_IEEE802154_MONITOR;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* TODO check this */
+ SET_NETDEV_DEV(ndev, &local->phy->dev);
+ sdata = netdev_priv(ndev);
+ ndev->ieee802154_ptr = &sdata->wpan_dev;
+ memcpy(sdata->name, ndev->name, IFNAMSIZ);
+ sdata->dev = ndev;
+ sdata->wpan_dev.wpan_phy = local->hw.phy;
+ sdata->local = local;
+
+ /* setup type-dependent data */
+ ret = ieee802154_setup_sdata(sdata, type);
+ if (ret)
+ goto err;
+
+ if (ndev) {
+ ret = register_netdevice(ndev);
+ if (ret < 0)
+ goto err;
+ }
+
+ mutex_lock(&local->iflist_mtx);
+ list_add_tail_rcu(&sdata->list, &local->interfaces);
+ mutex_unlock(&local->iflist_mtx);
+
+ return ndev;
+
+err:
+ free_netdev(ndev);
+ return ERR_PTR(ret);
+}
- sdata->pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
- sdata->short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
+void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata)
+{
+ ASSERT_RTNL();
- sdata->promisuous_mode = false;
+ mutex_lock(&sdata->local->iflist_mtx);
+ list_del_rcu(&sdata->list);
+ mutex_unlock(&sdata->local->iflist_mtx);
- mac802154_llsec_init(&sdata->sec);
+ synchronize_rcu();
+ unregister_netdevice(sdata->dev);
}
-void mac802154_monitor_setup(struct net_device *dev)
+void ieee802154_remove_interfaces(struct ieee802154_local *local)
{
+ struct ieee802154_sub_if_data *sdata, *tmp;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry_safe(sdata, tmp, &local->interfaces, list) {
+ list_del(&sdata->list);
+
+ unregister_netdevice(sdata->dev);
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
+
+static int netdev_notify(struct notifier_block *nb,
+ unsigned long state, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct ieee802154_sub_if_data *sdata;
- dev->needed_tailroom = 2; /* room for FCS */
- dev->mtu = IEEE802154_MTU;
- dev->tx_queue_len = 10;
- dev->type = ARPHRD_IEEE802154_MONITOR;
- dev->flags = IFF_NOARP | IFF_BROADCAST;
+ if (state != NETDEV_CHANGENAME)
+ return NOTIFY_DONE;
+
+ if (!dev->ieee802154_ptr || !dev->ieee802154_ptr->wpan_phy)
+ return NOTIFY_DONE;
- dev->destructor = free_netdev;
- dev->netdev_ops = &mac802154_monitor_ops;
- dev->ml_priv = &mac802154_mlme_reduced;
+ if (dev->ieee802154_ptr->wpan_phy->privid != mac802154_wpan_phy_privid)
+ return NOTIFY_DONE;
sdata = IEEE802154_DEV_TO_SUB_IF(dev);
- sdata->type = IEEE802154_DEV_MONITOR;
+ memcpy(sdata->name, dev->name, IFNAMSIZ);
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block mac802154_netdev_notifier = {
+ .notifier_call = netdev_notify,
+};
- sdata->promisuous_mode = true;
+int ieee802154_iface_init(void)
+{
+ return register_netdevice_notifier(&mac802154_netdev_notifier);
+}
+
+void ieee802154_iface_exit(void)
+{
+ unregister_netdevice_notifier(&mac802154_netdev_notifier);
}
diff --git a/net/mac802154/mac_cmd.c b/net/mac802154/mac_cmd.c
index fc261ab33347..6aacb1816889 100644
--- a/net/mac802154/mac_cmd.c
+++ b/net/mac802154/mac_cmd.c
@@ -25,9 +25,9 @@
#include <net/ieee802154_netdev.h>
#include <net/cfg802154.h>
#include <net/mac802154.h>
-#include <net/nl802154.h>
#include "ieee802154_i.h"
+#include "driver-ops.h"
static int mac802154_mlme_start_req(struct net_device *dev,
struct ieee802154_addr *addr,
@@ -39,11 +39,12 @@ static int mac802154_mlme_start_req(struct net_device *dev,
struct ieee802154_mlme_ops *ops = ieee802154_mlme_ops(dev);
int rc = 0;
+ ASSERT_RTNL();
+
BUG_ON(addr->mode != IEEE802154_ADDR_SHORT);
mac802154_dev_set_pan_id(dev, addr->pan_id);
mac802154_dev_set_short_addr(dev, addr->short_addr);
- mac802154_dev_set_ieee_addr(dev);
mac802154_dev_set_page_channel(dev, page, channel);
if (ops->llsec) {
@@ -65,31 +66,48 @@ static int mac802154_mlme_start_req(struct net_device *dev,
rc = ops->llsec->set_params(dev, &params, changed);
}
- /* FIXME: add validation for unused parameters to be sane
- * for SoftMAC
- */
- ieee802154_nl_start_confirm(dev, IEEE802154_SUCCESS);
-
return rc;
}
-static struct wpan_phy *mac802154_get_phy(const struct net_device *dev)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
-
- BUG_ON(dev->type != ARPHRD_IEEE802154);
-
- return to_phy(get_device(&sdata->local->phy->dev));
-}
-
static int mac802154_set_mac_params(struct net_device *dev,
const struct ieee802154_mac_params *params)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+ struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ int ret;
+
+ ASSERT_RTNL();
+
+ /* PHY */
+ wpan_dev->wpan_phy->transmit_power = params->transmit_power;
+ wpan_dev->wpan_phy->cca_mode = params->cca_mode;
+ wpan_dev->wpan_phy->cca_ed_level = params->cca_ed_level;
+
+ /* MAC */
+ wpan_dev->min_be = params->min_be;
+ wpan_dev->max_be = params->max_be;
+ wpan_dev->csma_retries = params->csma_retries;
+ wpan_dev->frame_retries = params->frame_retries;
+ wpan_dev->lbt = params->lbt;
+
+ if (local->hw.flags & IEEE802154_HW_TXPOWER) {
+ ret = drv_set_tx_power(local, params->transmit_power);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (local->hw.flags & IEEE802154_HW_CCA_MODE) {
+ ret = drv_set_cca_mode(local, params->cca_mode);
+ if (ret < 0)
+ return ret;
+ }
- mutex_lock(&sdata->local->iflist_mtx);
- sdata->mac_params = *params;
- mutex_unlock(&sdata->local->iflist_mtx);
+ if (local->hw.flags & IEEE802154_HW_CCA_ED_LEVEL) {
+ ret = drv_set_cca_ed_level(local, params->cca_ed_level);
+ if (ret < 0)
+ return ret;
+ }
return 0;
}
@@ -98,10 +116,21 @@ static void mac802154_get_mac_params(struct net_device *dev,
struct ieee802154_mac_params *params)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
- mutex_lock(&sdata->local->iflist_mtx);
- *params = sdata->mac_params;
- mutex_unlock(&sdata->local->iflist_mtx);
+ ASSERT_RTNL();
+
+ /* PHY */
+ params->transmit_power = wpan_dev->wpan_phy->transmit_power;
+ params->cca_mode = wpan_dev->wpan_phy->cca_mode;
+ params->cca_ed_level = wpan_dev->wpan_phy->cca_ed_level;
+
+ /* MAC */
+ params->min_be = wpan_dev->min_be;
+ params->max_be = wpan_dev->max_be;
+ params->csma_retries = wpan_dev->csma_retries;
+ params->frame_retries = wpan_dev->frame_retries;
+ params->lbt = wpan_dev->lbt;
}
static struct ieee802154_llsec_ops mac802154_llsec_ops = {
@@ -120,12 +149,7 @@ static struct ieee802154_llsec_ops mac802154_llsec_ops = {
.unlock_table = mac802154_unlock_table,
};
-struct ieee802154_reduced_mlme_ops mac802154_mlme_reduced = {
- .get_phy = mac802154_get_phy,
-};
-
struct ieee802154_mlme_ops mac802154_mlme_wpan = {
- .get_phy = mac802154_get_phy,
.start_req = mac802154_mlme_start_req,
.get_pan_id = mac802154_dev_get_pan_id,
.get_short_addr = mac802154_dev_get_short_addr,
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
index 86e533ed3775..8500378c8318 100644
--- a/net/mac802154/main.c
+++ b/net/mac802154/main.c
@@ -4,8 +4,6 @@
* Written by:
* Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
*
- * Based on the code from 'linux-zigbee.sourceforge.net' project.
- *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
@@ -21,97 +19,14 @@
#include <linux/netdevice.h>
#include <net/netlink.h>
-#include <linux/nl802154.h>
+#include <net/nl802154.h>
#include <net/mac802154.h>
#include <net/ieee802154_netdev.h>
#include <net/route.h>
#include <net/cfg802154.h>
#include "ieee802154_i.h"
-
-static int
-mac802154_netdev_register(struct wpan_phy *phy, struct net_device *dev)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
- struct ieee802154_local *local;
- int err;
-
- local = wpan_phy_priv(phy);
-
- sdata->dev = dev;
- sdata->local = local;
-
- dev->needed_headroom = local->hw.extra_tx_headroom;
-
- SET_NETDEV_DEV(dev, &local->phy->dev);
-
- err = register_netdev(dev);
- if (err < 0)
- return err;
-
- rtnl_lock();
- mutex_lock(&local->iflist_mtx);
- list_add_tail_rcu(&sdata->list, &local->interfaces);
- mutex_unlock(&local->iflist_mtx);
- rtnl_unlock();
-
- return 0;
-}
-
-static void
-mac802154_del_iface(struct wpan_phy *phy, struct net_device *dev)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
-
- ASSERT_RTNL();
-
- BUG_ON(sdata->local->phy != phy);
-
- mutex_lock(&sdata->local->iflist_mtx);
- list_del_rcu(&sdata->list);
- mutex_unlock(&sdata->local->iflist_mtx);
-
- synchronize_rcu();
- unregister_netdevice(sdata->dev);
-}
-
-static struct net_device *
-mac802154_add_iface(struct wpan_phy *phy, const char *name, int type)
-{
- struct net_device *dev;
- int err = -ENOMEM;
-
- switch (type) {
- case IEEE802154_DEV_MONITOR:
- dev = alloc_netdev(sizeof(struct ieee802154_sub_if_data),
- name, NET_NAME_UNKNOWN,
- mac802154_monitor_setup);
- break;
- case IEEE802154_DEV_WPAN:
- dev = alloc_netdev(sizeof(struct ieee802154_sub_if_data),
- name, NET_NAME_UNKNOWN,
- mac802154_wpan_setup);
- break;
- default:
- dev = NULL;
- err = -EINVAL;
- break;
- }
- if (!dev)
- goto err;
-
- err = mac802154_netdev_register(phy, dev);
- if (err)
- goto err_free;
-
- dev_hold(dev); /* we return an incremented device refcount */
- return dev;
-
-err_free:
- free_netdev(dev);
-err:
- return ERR_PTR(err);
-}
+#include "cfg.h"
static void ieee802154_tasklet_handler(unsigned long data)
{
@@ -169,12 +84,14 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
priv_size = ALIGN(sizeof(*local), NETDEV_ALIGN) + priv_data_len;
- phy = wpan_phy_alloc(priv_size);
+ phy = wpan_phy_new(&mac802154_config_ops, priv_size);
if (!phy) {
pr_err("failure to allocate master IEEE802.15.4 device\n");
return NULL;
}
+ phy->privid = mac802154_wpan_phy_privid;
+
local = wpan_phy_priv(phy);
local->phy = phy;
local->hw.phy = local->phy;
@@ -206,9 +123,22 @@ void ieee802154_free_hw(struct ieee802154_hw *hw)
}
EXPORT_SYMBOL(ieee802154_free_hw);
+static void ieee802154_setup_wpan_phy_pib(struct wpan_phy *wpan_phy)
+{
+ /* TODO warn on empty symbol_duration
+ * Should be done when all drivers sets this value.
+ */
+
+ wpan_phy->lifs_period = IEEE802154_LIFS_PERIOD *
+ wpan_phy->symbol_duration;
+ wpan_phy->sifs_period = IEEE802154_SIFS_PERIOD *
+ wpan_phy->symbol_duration;
+}
+
int ieee802154_register_hw(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
+ struct net_device *dev;
int rc = -ENOSYS;
local->workqueue =
@@ -218,15 +148,29 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
goto out;
}
+ hrtimer_init(&local->ifs_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ local->ifs_timer.function = ieee802154_xmit_ifs_timer;
+
wpan_phy_set_dev(local->phy, local->hw.parent);
- local->phy->add_iface = mac802154_add_iface;
- local->phy->del_iface = mac802154_del_iface;
+ ieee802154_setup_wpan_phy_pib(local->phy);
rc = wpan_phy_register(local->phy);
if (rc < 0)
goto out_wq;
+ rtnl_lock();
+
+ dev = ieee802154_if_add(local, "wpan%d", NL802154_IFTYPE_NODE,
+ cpu_to_le64(0x0000000000000000ULL));
+ if (IS_ERR(dev)) {
+ rtnl_unlock();
+ rc = PTR_ERR(dev);
+ goto out_wq;
+ }
+
+ rtnl_unlock();
+
return 0;
out_wq:
@@ -239,7 +183,6 @@ EXPORT_SYMBOL(ieee802154_register_hw);
void ieee802154_unregister_hw(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
- struct ieee802154_sub_if_data *sdata, *next;
tasklet_kill(&local->tasklet);
flush_workqueue(local->workqueue);
@@ -247,13 +190,7 @@ void ieee802154_unregister_hw(struct ieee802154_hw *hw)
rtnl_lock();
- list_for_each_entry_safe(sdata, next, &local->interfaces, list) {
- mutex_lock(&sdata->local->iflist_mtx);
- list_del(&sdata->list);
- mutex_unlock(&sdata->local->iflist_mtx);
-
- unregister_netdevice(sdata->dev);
- }
+ ieee802154_remove_interfaces(local);
rtnl_unlock();
@@ -261,5 +198,20 @@ void ieee802154_unregister_hw(struct ieee802154_hw *hw)
}
EXPORT_SYMBOL(ieee802154_unregister_hw);
-MODULE_DESCRIPTION("IEEE 802.15.4 implementation");
+static int __init ieee802154_init(void)
+{
+ return ieee802154_iface_init();
+}
+
+static void __exit ieee802154_exit(void)
+{
+ ieee802154_iface_exit();
+
+ rcu_barrier();
+}
+
+subsys_initcall(ieee802154_init);
+module_exit(ieee802154_exit);
+
+MODULE_DESCRIPTION("IEEE 802.15.4 subsystem");
MODULE_LICENSE("GPL v2");
diff --git a/net/mac802154/mib.c b/net/mac802154/mib.c
index 0184fced2f62..3596b29ead6b 100644
--- a/net/mac802154/mib.c
+++ b/net/mac802154/mib.c
@@ -26,51 +26,6 @@
#include "ieee802154_i.h"
#include "driver-ops.h"
-struct hw_addr_filt_notify_work {
- struct work_struct work;
- struct net_device *dev;
- unsigned long changed;
-};
-
-static struct ieee802154_local *mac802154_slave_get_priv(struct net_device *dev)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
-
- BUG_ON(dev->type != ARPHRD_IEEE802154);
-
- return sdata->local;
-}
-
-static void hw_addr_notify(struct work_struct *work)
-{
- struct hw_addr_filt_notify_work *nw = container_of(work,
- struct hw_addr_filt_notify_work, work);
- struct ieee802154_local *local = mac802154_slave_get_priv(nw->dev);
- int res;
-
- res = local->ops->set_hw_addr_filt(&local->hw, &local->hw.hw_filt,
- nw->changed);
- if (res)
- pr_debug("failed changed mask %lx\n", nw->changed);
-
- kfree(nw);
-}
-
-static void set_hw_addr_filt(struct net_device *dev, unsigned long changed)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
- struct hw_addr_filt_notify_work *work;
-
- work = kzalloc(sizeof(*work), GFP_ATOMIC);
- if (!work)
- return;
-
- INIT_WORK(&work->work, hw_addr_notify);
- work->dev = dev;
- work->changed = changed;
- queue_work(sdata->local->workqueue, &work->work);
-}
-
void mac802154_dev_set_short_addr(struct net_device *dev, __le16 val)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
@@ -78,14 +33,8 @@ void mac802154_dev_set_short_addr(struct net_device *dev, __le16 val)
BUG_ON(dev->type != ARPHRD_IEEE802154);
spin_lock_bh(&sdata->mib_lock);
- sdata->short_addr = val;
+ sdata->wpan_dev.short_addr = val;
spin_unlock_bh(&sdata->mib_lock);
-
- if ((sdata->local->ops->set_hw_addr_filt) &&
- (sdata->local->hw.hw_filt.short_addr != sdata->short_addr)) {
- sdata->local->hw.hw_filt.short_addr = sdata->short_addr;
- set_hw_addr_filt(dev, IEEE802154_AFILT_SADDR_CHANGED);
- }
}
__le16 mac802154_dev_get_short_addr(const struct net_device *dev)
@@ -96,26 +45,12 @@ __le16 mac802154_dev_get_short_addr(const struct net_device *dev)
BUG_ON(dev->type != ARPHRD_IEEE802154);
spin_lock_bh(&sdata->mib_lock);
- ret = sdata->short_addr;
+ ret = sdata->wpan_dev.short_addr;
spin_unlock_bh(&sdata->mib_lock);
return ret;
}
-void mac802154_dev_set_ieee_addr(struct net_device *dev)
-{
- struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
- struct ieee802154_local *local = sdata->local;
-
- sdata->extended_addr = ieee802154_devaddr_from_raw(dev->dev_addr);
-
- if (local->ops->set_hw_addr_filt &&
- local->hw.hw_filt.ieee_addr != sdata->extended_addr) {
- local->hw.hw_filt.ieee_addr = sdata->extended_addr;
- set_hw_addr_filt(dev, IEEE802154_AFILT_IEEEADDR_CHANGED);
- }
-}
-
__le16 mac802154_dev_get_pan_id(const struct net_device *dev)
{
struct ieee802154_sub_if_data *sdata = IEEE802154_DEV_TO_SUB_IF(dev);
@@ -124,7 +59,7 @@ __le16 mac802154_dev_get_pan_id(const struct net_device *dev)
BUG_ON(dev->type != ARPHRD_IEEE802154);
spin_lock_bh(&sdata->mib_lock);
- ret = sdata->pan_id;
+ ret = sdata->wpan_dev.pan_id;
spin_unlock_bh(&sdata->mib_lock);
return ret;
@@ -137,14 +72,8 @@ void mac802154_dev_set_pan_id(struct net_device *dev, __le16 val)
BUG_ON(dev->type != ARPHRD_IEEE802154);
spin_lock_bh(&sdata->mib_lock);
- sdata->pan_id = val;
+ sdata->wpan_dev.pan_id = val;
spin_unlock_bh(&sdata->mib_lock);
-
- if ((sdata->local->ops->set_hw_addr_filt) &&
- (sdata->local->hw.hw_filt.pan_id != sdata->pan_id)) {
- sdata->local->hw.hw_filt.pan_id = sdata->pan_id;
- set_hw_addr_filt(dev, IEEE802154_AFILT_PANID_CHANGED);
- }
}
u8 mac802154_dev_get_dsn(const struct net_device *dev)
@@ -153,7 +82,7 @@ u8 mac802154_dev_get_dsn(const struct net_device *dev)
BUG_ON(dev->type != ARPHRD_IEEE802154);
- return sdata->dsn++;
+ return sdata->wpan_dev.dsn++;
}
void mac802154_dev_set_page_channel(struct net_device *dev, u8 page, u8 chan)
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index 95961cccc253..041dbd5958d4 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -25,8 +25,7 @@
#include <net/mac802154.h>
#include <net/ieee802154_netdev.h>
-#include <net/rtnetlink.h>
-#include <linux/nl802154.h>
+#include <net/nl802154.h>
#include "ieee802154_i.h"
@@ -42,6 +41,7 @@ static int
ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
struct sk_buff *skb, const struct ieee802154_hdr *hdr)
{
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
__le16 span, sshort;
int rc;
@@ -49,8 +49,8 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
spin_lock_bh(&sdata->mib_lock);
- span = sdata->pan_id;
- sshort = sdata->short_addr;
+ span = wpan_dev->pan_id;
+ sshort = wpan_dev->short_addr;
switch (mac_cb(skb)->dest.mode) {
case IEEE802154_ADDR_NONE:
@@ -65,7 +65,7 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
if (mac_cb(skb)->dest.pan_id != span &&
mac_cb(skb)->dest.pan_id != cpu_to_le16(IEEE802154_PANID_BROADCAST))
skb->pkt_type = PACKET_OTHERHOST;
- else if (mac_cb(skb)->dest.extended_addr == sdata->extended_addr)
+ else if (mac_cb(skb)->dest.extended_addr == wpan_dev->extended_addr)
skb->pkt_type = PACKET_HOST;
else
skb->pkt_type = PACKET_OTHERHOST;
@@ -208,7 +208,7 @@ __ieee802154_rx_handle_packet(struct ieee802154_local *local,
}
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
- if (sdata->type != IEEE802154_DEV_WPAN ||
+ if (sdata->vif.type != NL802154_IFTYPE_NODE ||
!netif_running(sdata->dev))
continue;
@@ -233,7 +233,7 @@ ieee802154_monitors_rx(struct ieee802154_local *local, struct sk_buff *skb)
skb->protocol = htons(ETH_P_IEEE802154);
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
- if (sdata->type != IEEE802154_DEV_MONITOR)
+ if (sdata->vif.type != NL802154_IFTYPE_MONITOR)
continue;
if (!ieee802154_sdata_running(sdata))
diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c
index cc37b77f2632..c62e95695c78 100644
--- a/net/mac802154/tx.c
+++ b/net/mac802154/tx.c
@@ -60,7 +60,7 @@ static void ieee802154_xmit_worker(struct work_struct *work)
if (res)
goto err_tx;
- ieee802154_xmit_complete(&local->hw, skb);
+ ieee802154_xmit_complete(&local->hw, skb, false);
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
diff --git a/net/mac802154/util.c b/net/mac802154/util.c
index 117e4eff4ca8..5fc979027919 100644
--- a/net/mac802154/util.c
+++ b/net/mac802154/util.c
@@ -15,6 +15,9 @@
#include "ieee802154_i.h"
+/* privid for wpan_phys to determine whether they belong to us or not */
+const void *const mac802154_wpan_phy_privid = &mac802154_wpan_phy_privid;
+
void ieee802154_wake_queue(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
@@ -47,9 +50,35 @@ void ieee802154_stop_queue(struct ieee802154_hw *hw)
}
EXPORT_SYMBOL(ieee802154_stop_queue);
-void ieee802154_xmit_complete(struct ieee802154_hw *hw, struct sk_buff *skb)
+enum hrtimer_restart ieee802154_xmit_ifs_timer(struct hrtimer *timer)
{
- ieee802154_wake_queue(hw);
- consume_skb(skb);
+ struct ieee802154_local *local =
+ container_of(timer, struct ieee802154_local, ifs_timer);
+
+ ieee802154_wake_queue(&local->hw);
+
+ return HRTIMER_NORESTART;
+}
+
+void ieee802154_xmit_complete(struct ieee802154_hw *hw, struct sk_buff *skb,
+ bool ifs_handling)
+{
+ if (ifs_handling) {
+ struct ieee802154_local *local = hw_to_local(hw);
+
+ if (skb->len > 18)
+ hrtimer_start(&local->ifs_timer,
+ ktime_set(0, hw->phy->lifs_period * NSEC_PER_USEC),
+ HRTIMER_MODE_REL);
+ else
+ hrtimer_start(&local->ifs_timer,
+ ktime_set(0, hw->phy->sifs_period * NSEC_PER_USEC),
+ HRTIMER_MODE_REL);
+
+ consume_skb(skb);
+ } else {
+ ieee802154_wake_queue(hw);
+ consume_skb(skb);
+ }
}
EXPORT_SYMBOL(ieee802154_xmit_complete);
diff --git a/net/wireless/core.c b/net/wireless/core.c
index a4d27927aba2..4c2e501203d1 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -541,6 +541,10 @@ int wiphy_register(struct wiphy *wiphy)
!wiphy->wowlan->tcp))
return -EINVAL;
#endif
+ if (WARN_ON((wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) &&
+ (!rdev->ops->tdls_channel_switch ||
+ !rdev->ops->tdls_cancel_channel_switch)))
+ return -EINVAL;
if (WARN_ON(wiphy->coalesce &&
(!wiphy->coalesce->n_rules ||
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 61ee664cf2bd..faa5b1609aae 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -111,6 +111,7 @@ cfg80211_rdev_free_wowlan(struct cfg80211_registered_device *rdev)
rdev->wiphy.wowlan_config->tcp->sock)
sock_release(rdev->wiphy.wowlan_config->tcp->sock);
kfree(rdev->wiphy.wowlan_config->tcp);
+ kfree(rdev->wiphy.wowlan_config->nd_config);
kfree(rdev->wiphy.wowlan_config);
#endif
}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 1a31736914e5..6e4177701d86 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -209,7 +209,7 @@ cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
}
/* policy for the attributes */
-static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
+static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
.len = 20-1 },
@@ -388,13 +388,14 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
[NL80211_ATTR_MAC_HINT] = { .len = ETH_ALEN },
[NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 },
[NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 },
- [NL80211_ATTR_IFACE_SOCKET_OWNER] = { .type = NLA_FLAG },
+ [NL80211_ATTR_SOCKET_OWNER] = { .type = NLA_FLAG },
[NL80211_ATTR_CSA_C_OFFSETS_TX] = { .type = NLA_BINARY },
[NL80211_ATTR_USE_RRM] = { .type = NLA_FLAG },
[NL80211_ATTR_TSID] = { .type = NLA_U8 },
[NL80211_ATTR_USER_PRIO] = { .type = NLA_U8 },
[NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 },
[NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 },
+ [NL80211_ATTR_MAC_MASK] = { .len = ETH_ALEN },
};
/* policy for the key attributes */
@@ -428,6 +429,7 @@ nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = {
[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED },
+ [NL80211_WOWLAN_TRIG_NET_DETECT] = { .type = NLA_NESTED },
};
static const struct nla_policy
@@ -1088,6 +1090,8 @@ static int nl80211_send_wowlan(struct sk_buff *msg,
if (large && nl80211_send_wowlan_tcp_caps(rdev, msg))
return -ENOBUFS;
+ /* TODO: send wowlan net detect */
+
nla_nest_end(msg, nl_wowlan);
return 0;
@@ -2341,12 +2345,16 @@ static int nl80211_send_chandef(struct sk_buff *msg,
static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags,
struct cfg80211_registered_device *rdev,
- struct wireless_dev *wdev)
+ struct wireless_dev *wdev, bool removal)
{
struct net_device *dev = wdev->netdev;
+ u8 cmd = NL80211_CMD_NEW_INTERFACE;
void *hdr;
- hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_INTERFACE);
+ if (removal)
+ cmd = NL80211_CMD_DEL_INTERFACE;
+
+ hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
if (!hdr)
return -1;
@@ -2413,7 +2421,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
}
if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
- rdev, wdev) < 0) {
+ rdev, wdev, false) < 0) {
goto out;
}
if_idx++;
@@ -2441,7 +2449,7 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
return -ENOMEM;
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
- rdev, wdev) < 0) {
+ rdev, wdev, false) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
@@ -2587,7 +2595,7 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct vif_params params;
struct wireless_dev *wdev;
- struct sk_buff *msg;
+ struct sk_buff *msg, *event;
int err;
enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
u32 flags;
@@ -2641,12 +2649,15 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
wdev = rdev_add_virtual_intf(rdev,
nla_data(info->attrs[NL80211_ATTR_IFNAME]),
type, err ? NULL : &flags, &params);
- if (IS_ERR(wdev)) {
+ if (WARN_ON(!wdev)) {
+ nlmsg_free(msg);
+ return -EPROTO;
+ } else if (IS_ERR(wdev)) {
nlmsg_free(msg);
return PTR_ERR(wdev);
}
- if (info->attrs[NL80211_ATTR_IFACE_SOCKET_OWNER])
+ if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
wdev->owner_nlportid = info->snd_portid;
switch (type) {
@@ -2682,11 +2693,25 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
}
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
- rdev, wdev) < 0) {
+ rdev, wdev, false) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
+ event = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (event) {
+ if (nl80211_send_iface(event, 0, 0, 0,
+ rdev, wdev, false) < 0) {
+ nlmsg_free(event);
+ goto out;
+ }
+
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
+ event, 0, NL80211_MCGRP_CONFIG,
+ GFP_KERNEL);
+ }
+
+out:
return genlmsg_reply(msg, info);
}
@@ -2694,10 +2719,18 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct wireless_dev *wdev = info->user_ptr[1];
+ struct sk_buff *msg;
+ int status;
if (!rdev->ops->del_virtual_intf)
return -EOPNOTSUPP;
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (msg && nl80211_send_iface(msg, 0, 0, 0, rdev, wdev, true) < 0) {
+ nlmsg_free(msg);
+ msg = NULL;
+ }
+
/*
* If we remove a wireless device without a netdev then clear
* user_ptr[1] so that nl80211_post_doit won't dereference it
@@ -2708,7 +2741,15 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
if (!wdev->netdev)
info->user_ptr[1] = NULL;
- return rdev_del_virtual_intf(rdev, wdev);
+ status = rdev_del_virtual_intf(rdev, wdev);
+ if (status >= 0 && msg)
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
+ msg, 0, NL80211_MCGRP_CONFIG,
+ GFP_KERNEL);
+ else
+ nlmsg_free(msg);
+
+ return status;
}
static int nl80211_set_noack_map(struct sk_buff *skb, struct genl_info *info)
@@ -4453,7 +4494,7 @@ static int nl80211_send_mpath(struct sk_buff *msg, u32 portid, u32 seq,
void *hdr;
struct nlattr *pinfoattr;
- hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_STATION);
+ hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_MPATH);
if (!hdr)
return -1;
@@ -5478,6 +5519,43 @@ static int validate_scan_freqs(struct nlattr *freqs)
return n_channels;
}
+static int nl80211_parse_random_mac(struct nlattr **attrs,
+ u8 *mac_addr, u8 *mac_addr_mask)
+{
+ int i;
+
+ if (!attrs[NL80211_ATTR_MAC] && !attrs[NL80211_ATTR_MAC_MASK]) {
+ memset(mac_addr, 0, ETH_ALEN);
+ memset(mac_addr_mask, 0, ETH_ALEN);
+ mac_addr[0] = 0x2;
+ mac_addr_mask[0] = 0x3;
+
+ return 0;
+ }
+
+ /* need both or none */
+ if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_MAC_MASK])
+ return -EINVAL;
+
+ memcpy(mac_addr, nla_data(attrs[NL80211_ATTR_MAC]), ETH_ALEN);
+ memcpy(mac_addr_mask, nla_data(attrs[NL80211_ATTR_MAC_MASK]), ETH_ALEN);
+
+ /* don't allow or configure an mcast address */
+ if (!is_multicast_ether_addr(mac_addr_mask) ||
+ is_multicast_ether_addr(mac_addr))
+ return -EINVAL;
+
+ /*
+ * allow users to pass a MAC address that has bits set outside
+ * of the mask, but don't bother drivers with having to deal
+ * with such bits
+ */
+ for (i = 0; i < ETH_ALEN; i++)
+ mac_addr[i] &= mac_addr_mask[i];
+
+ return 0;
+}
+
static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -5655,6 +5733,25 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
err = -EOPNOTSUPP;
goto out_free;
}
+
+ if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+ if (!(wiphy->features &
+ NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR)) {
+ err = -EOPNOTSUPP;
+ goto out_free;
+ }
+
+ if (wdev->current_bss) {
+ err = -EOPNOTSUPP;
+ goto out_free;
+ }
+
+ err = nl80211_parse_random_mac(info->attrs,
+ request->mac_addr,
+ request->mac_addr_mask);
+ if (err)
+ goto out_free;
+ }
}
request->no_cck =
@@ -5681,14 +5778,12 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
return err;
}
-static int nl80211_start_sched_scan(struct sk_buff *skb,
- struct genl_info *info)
+static struct cfg80211_sched_scan_request *
+nl80211_parse_sched_scan(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct nlattr **attrs)
{
struct cfg80211_sched_scan_request *request;
- struct cfg80211_registered_device *rdev = info->user_ptr[0];
- struct net_device *dev = info->user_ptr[1];
struct nlattr *attr;
- struct wiphy *wiphy;
int err, tmp, n_ssids = 0, n_match_sets = 0, n_channels, i;
u32 interval;
enum ieee80211_band band;
@@ -5696,38 +5791,32 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
struct nlattr *tb[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1];
s32 default_match_rssi = NL80211_SCAN_RSSI_THOLD_OFF;
- if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
- !rdev->ops->sched_scan_start)
- return -EOPNOTSUPP;
-
- if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE]))
- return -EINVAL;
+ if (!is_valid_ie_attr(attrs[NL80211_ATTR_IE]))
+ return ERR_PTR(-EINVAL);
- if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL])
- return -EINVAL;
+ if (!attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL])
+ return ERR_PTR(-EINVAL);
- interval = nla_get_u32(info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]);
+ interval = nla_get_u32(attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]);
if (interval == 0)
- return -EINVAL;
-
- wiphy = &rdev->wiphy;
+ return ERR_PTR(-EINVAL);
- if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+ if (attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
n_channels = validate_scan_freqs(
- info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]);
+ attrs[NL80211_ATTR_SCAN_FREQUENCIES]);
if (!n_channels)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
} else {
n_channels = ieee80211_get_num_supported_channels(wiphy);
}
- if (info->attrs[NL80211_ATTR_SCAN_SSIDS])
- nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS],
+ if (attrs[NL80211_ATTR_SCAN_SSIDS])
+ nla_for_each_nested(attr, attrs[NL80211_ATTR_SCAN_SSIDS],
tmp)
n_ssids++;
if (n_ssids > wiphy->max_sched_scan_ssids)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
/*
* First, count the number of 'real' matchsets. Due to an issue with
@@ -5738,9 +5827,9 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
* older userspace that treated a matchset with only the RSSI as the
* global RSSI for all other matchsets - if there are other matchsets.
*/
- if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) {
+ if (attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) {
nla_for_each_nested(attr,
- info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH],
+ attrs[NL80211_ATTR_SCHED_SCAN_MATCH],
tmp) {
struct nlattr *rssi;
@@ -5748,7 +5837,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
nla_data(attr), nla_len(attr),
nl80211_match_policy);
if (err)
- return err;
+ return ERR_PTR(err);
/* add other standalone attributes here */
if (tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]) {
n_match_sets++;
@@ -5765,30 +5854,23 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
n_match_sets = 1;
if (n_match_sets > wiphy->max_match_sets)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
- if (info->attrs[NL80211_ATTR_IE])
- ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
+ if (attrs[NL80211_ATTR_IE])
+ ie_len = nla_len(attrs[NL80211_ATTR_IE]);
else
ie_len = 0;
if (ie_len > wiphy->max_sched_scan_ie_len)
- return -EINVAL;
-
- if (rdev->sched_scan_req) {
- err = -EINPROGRESS;
- goto out;
- }
+ return ERR_PTR(-EINVAL);
request = kzalloc(sizeof(*request)
+ sizeof(*request->ssids) * n_ssids
+ sizeof(*request->match_sets) * n_match_sets
+ sizeof(*request->channels) * n_channels
+ ie_len, GFP_KERNEL);
- if (!request) {
- err = -ENOMEM;
- goto out;
- }
+ if (!request)
+ return ERR_PTR(-ENOMEM);
if (n_ssids)
request->ssids = (void *)&request->channels[n_channels];
@@ -5813,10 +5895,10 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
request->n_match_sets = n_match_sets;
i = 0;
- if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+ if (attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
/* user specified, bail out if channel not found */
nla_for_each_nested(attr,
- info->attrs[NL80211_ATTR_SCAN_FREQUENCIES],
+ attrs[NL80211_ATTR_SCAN_FREQUENCIES],
tmp) {
struct ieee80211_channel *chan;
@@ -5862,8 +5944,8 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
request->n_channels = i;
i = 0;
- if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) {
- nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS],
+ if (attrs[NL80211_ATTR_SCAN_SSIDS]) {
+ nla_for_each_nested(attr, attrs[NL80211_ATTR_SCAN_SSIDS],
tmp) {
if (nla_len(attr) > IEEE80211_MAX_SSID_LEN) {
err = -EINVAL;
@@ -5877,9 +5959,9 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
}
i = 0;
- if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) {
+ if (attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) {
nla_for_each_nested(attr,
- info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH],
+ attrs[NL80211_ATTR_SCHED_SCAN_MATCH],
tmp) {
struct nlattr *ssid, *rssi;
@@ -5934,36 +6016,88 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
if (ie_len) {
request->ie_len = ie_len;
memcpy((void *)request->ie,
- nla_data(info->attrs[NL80211_ATTR_IE]),
+ nla_data(attrs[NL80211_ATTR_IE]),
request->ie_len);
}
- if (info->attrs[NL80211_ATTR_SCAN_FLAGS]) {
+ if (attrs[NL80211_ATTR_SCAN_FLAGS]) {
request->flags = nla_get_u32(
- info->attrs[NL80211_ATTR_SCAN_FLAGS]);
+ attrs[NL80211_ATTR_SCAN_FLAGS]);
if ((request->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) &&
!(wiphy->features & NL80211_FEATURE_LOW_PRIORITY_SCAN)) {
err = -EOPNOTSUPP;
goto out_free;
}
+
+ if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+ u32 flg = NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR;
+
+ if (!wdev) /* must be net-detect */
+ flg = NL80211_FEATURE_ND_RANDOM_MAC_ADDR;
+
+ if (!(wiphy->features & flg)) {
+ err = -EOPNOTSUPP;
+ goto out_free;
+ }
+
+ if (wdev && wdev->current_bss) {
+ err = -EOPNOTSUPP;
+ goto out_free;
+ }
+
+ err = nl80211_parse_random_mac(attrs, request->mac_addr,
+ request->mac_addr_mask);
+ if (err)
+ goto out_free;
+ }
}
- request->dev = dev;
- request->wiphy = &rdev->wiphy;
request->interval = interval;
request->scan_start = jiffies;
- err = rdev_sched_scan_start(rdev, dev, request);
- if (!err) {
- rdev->sched_scan_req = request;
- nl80211_send_sched_scan(rdev, dev,
- NL80211_CMD_START_SCHED_SCAN);
- goto out;
- }
+ return request;
out_free:
kfree(request);
-out:
+ return ERR_PTR(err);
+}
+
+static int nl80211_start_sched_scan(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ int err;
+
+ if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
+ !rdev->ops->sched_scan_start)
+ return -EOPNOTSUPP;
+
+ if (rdev->sched_scan_req)
+ return -EINPROGRESS;
+
+ rdev->sched_scan_req = nl80211_parse_sched_scan(&rdev->wiphy, wdev,
+ info->attrs);
+ err = PTR_ERR_OR_ZERO(rdev->sched_scan_req);
+ if (err)
+ goto out_err;
+
+ err = rdev_sched_scan_start(rdev, dev, rdev->sched_scan_req);
+ if (err)
+ goto out_free;
+
+ rdev->sched_scan_req->dev = dev;
+ rdev->sched_scan_req->wiphy = &rdev->wiphy;
+
+ nl80211_send_sched_scan(rdev, dev,
+ NL80211_CMD_START_SCHED_SCAN);
+ return 0;
+
+out_free:
+ kfree(rdev->sched_scan_req);
+out_err:
+ rdev->sched_scan_req = NULL;
return err;
}
@@ -8681,6 +8815,39 @@ static int nl80211_parse_wowlan_tcp(struct cfg80211_registered_device *rdev,
return 0;
}
+static int nl80211_parse_wowlan_nd(struct cfg80211_registered_device *rdev,
+ const struct wiphy_wowlan_support *wowlan,
+ struct nlattr *attr,
+ struct cfg80211_wowlan *trig)
+{
+ struct nlattr **tb;
+ int err;
+
+ tb = kzalloc(NUM_NL80211_ATTR * sizeof(*tb), GFP_KERNEL);
+ if (!tb)
+ return -ENOMEM;
+
+ if (!(wowlan->flags & WIPHY_WOWLAN_NET_DETECT)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ err = nla_parse(tb, NL80211_ATTR_MAX,
+ nla_data(attr), nla_len(attr),
+ nl80211_policy);
+ if (err)
+ goto out;
+
+ trig->nd_config = nl80211_parse_sched_scan(&rdev->wiphy, NULL, tb);
+ err = PTR_ERR_OR_ZERO(trig->nd_config);
+ if (err)
+ trig->nd_config = NULL;
+
+out:
+ kfree(tb);
+ return err;
+}
+
static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -8826,6 +8993,14 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
goto error;
}
+ if (tb[NL80211_WOWLAN_TRIG_NET_DETECT]) {
+ err = nl80211_parse_wowlan_nd(
+ rdev, wowlan, tb[NL80211_WOWLAN_TRIG_NET_DETECT],
+ &new_triggers);
+ if (err)
+ goto error;
+ }
+
ntrig = kmemdup(&new_triggers, sizeof(new_triggers), GFP_KERNEL);
if (!ntrig) {
err = -ENOMEM;
@@ -9658,6 +9833,98 @@ static int nl80211_del_tx_ts(struct sk_buff *skb, struct genl_info *info)
return err;
}
+static int nl80211_tdls_channel_switch(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_chan_def chandef = {};
+ const u8 *addr;
+ u8 oper_class;
+ int err;
+
+ if (!rdev->ops->tdls_channel_switch ||
+ !(rdev->wiphy.features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH))
+ return -EOPNOTSUPP;
+
+ switch (dev->ieee80211_ptr->iftype) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (!info->attrs[NL80211_ATTR_MAC] ||
+ !info->attrs[NL80211_ATTR_OPER_CLASS])
+ return -EINVAL;
+
+ err = nl80211_parse_chandef(rdev, info, &chandef);
+ if (err)
+ return err;
+
+ /*
+ * Don't allow wide channels on the 2.4Ghz band, as per IEEE802.11-2012
+ * section 10.22.6.2.1. Disallow 5/10Mhz channels as well for now, the
+ * specification is not defined for them.
+ */
+ if (chandef.chan->band == IEEE80211_BAND_2GHZ &&
+ chandef.width != NL80211_CHAN_WIDTH_20_NOHT &&
+ chandef.width != NL80211_CHAN_WIDTH_20)
+ return -EINVAL;
+
+ /* we will be active on the TDLS link */
+ if (!cfg80211_reg_can_beacon(&rdev->wiphy, &chandef, wdev->iftype))
+ return -EINVAL;
+
+ /* don't allow switching to DFS channels */
+ if (cfg80211_chandef_dfs_required(wdev->wiphy, &chandef, wdev->iftype))
+ return -EINVAL;
+
+ addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+ oper_class = nla_get_u8(info->attrs[NL80211_ATTR_OPER_CLASS]);
+
+ wdev_lock(wdev);
+ err = rdev_tdls_channel_switch(rdev, dev, addr, oper_class, &chandef);
+ wdev_unlock(wdev);
+
+ return err;
+}
+
+static int nl80211_tdls_cancel_channel_switch(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ const u8 *addr;
+
+ if (!rdev->ops->tdls_channel_switch ||
+ !rdev->ops->tdls_cancel_channel_switch ||
+ !(rdev->wiphy.features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH))
+ return -EOPNOTSUPP;
+
+ switch (dev->ieee80211_ptr->iftype) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (!info->attrs[NL80211_ATTR_MAC])
+ return -EINVAL;
+
+ addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+ wdev_lock(wdev);
+ rdev_tdls_cancel_channel_switch(rdev, dev, addr);
+ wdev_unlock(wdev);
+
+ return 0;
+}
+
#define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04
@@ -10456,6 +10723,22 @@ static const struct genl_ops nl80211_ops[] = {
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL80211_CMD_TDLS_CHANNEL_SWITCH,
+ .doit = nl80211_tdls_channel_switch,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH,
+ .doit = nl80211_tdls_cancel_channel_switch,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
};
/* notification functions */
@@ -11653,7 +11936,9 @@ EXPORT_SYMBOL(cfg80211_pmksa_candidate_notify);
static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
struct net_device *netdev,
struct cfg80211_chan_def *chandef,
- gfp_t gfp)
+ gfp_t gfp,
+ enum nl80211_commands notif,
+ u8 count)
{
struct sk_buff *msg;
void *hdr;
@@ -11662,7 +11947,7 @@ static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
if (!msg)
return;
- hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_CH_SWITCH_NOTIFY);
+ hdr = nl80211hdr_put(msg, 0, 0, 0, notif);
if (!hdr) {
nlmsg_free(msg);
return;
@@ -11674,6 +11959,10 @@ static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
if (nl80211_send_chandef(msg, chandef))
goto nla_put_failure;
+ if ((notif == NL80211_CMD_CH_SWITCH_STARTED_NOTIFY) &&
+ (nla_put_u32(msg, NL80211_ATTR_CH_SWITCH_COUNT, count)))
+ goto nla_put_failure;
+
genlmsg_end(msg, hdr);
genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
@@ -11696,18 +11985,28 @@ void cfg80211_ch_switch_notify(struct net_device *dev,
trace_cfg80211_ch_switch_notify(dev, chandef);
- if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO &&
- wdev->iftype != NL80211_IFTYPE_ADHOC &&
- wdev->iftype != NL80211_IFTYPE_MESH_POINT))
- return;
-
wdev->chandef = *chandef;
wdev->preset_chandef = *chandef;
- nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL);
+ nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL,
+ NL80211_CMD_CH_SWITCH_NOTIFY, 0);
}
EXPORT_SYMBOL(cfg80211_ch_switch_notify);
+void cfg80211_ch_switch_started_notify(struct net_device *dev,
+ struct cfg80211_chan_def *chandef,
+ u8 count)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+
+ trace_cfg80211_ch_switch_started_notify(dev, chandef);
+
+ nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL,
+ NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, count);
+}
+EXPORT_SYMBOL(cfg80211_ch_switch_started_notify);
+
void cfg80211_cqm_txe_notify(struct net_device *dev,
const u8 *peer, u32 num_packets,
u32 rate, u32 intvl, gfp_t gfp)
@@ -11944,6 +12243,67 @@ void cfg80211_report_obss_beacon(struct wiphy *wiphy,
EXPORT_SYMBOL(cfg80211_report_obss_beacon);
#ifdef CONFIG_PM
+static int cfg80211_net_detect_results(struct sk_buff *msg,
+ struct cfg80211_wowlan_wakeup *wakeup)
+{
+ struct cfg80211_wowlan_nd_info *nd = wakeup->net_detect;
+ struct nlattr *nl_results, *nl_match, *nl_freqs;
+ int i, j;
+
+ nl_results = nla_nest_start(
+ msg, NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS);
+ if (!nl_results)
+ return -EMSGSIZE;
+
+ for (i = 0; i < nd->n_matches; i++) {
+ struct cfg80211_wowlan_nd_match *match = nd->matches[i];
+
+ nl_match = nla_nest_start(msg, i);
+ if (!nl_match)
+ break;
+
+ /* The SSID attribute is optional in nl80211, but for
+ * simplicity reasons it's always present in the
+ * cfg80211 structure. If a driver can't pass the
+ * SSID, that needs to be changed. A zero length SSID
+ * is still a valid SSID (wildcard), so it cannot be
+ * used for this purpose.
+ */
+ if (nla_put(msg, NL80211_ATTR_SSID, match->ssid.ssid_len,
+ match->ssid.ssid)) {
+ nla_nest_cancel(msg, nl_match);
+ goto out;
+ }
+
+ if (match->n_channels) {
+ nl_freqs = nla_nest_start(
+ msg, NL80211_ATTR_SCAN_FREQUENCIES);
+ if (!nl_freqs) {
+ nla_nest_cancel(msg, nl_match);
+ goto out;
+ }
+
+ for (j = 0; j < match->n_channels; j++) {
+ if (nla_put_u32(msg,
+ NL80211_ATTR_WIPHY_FREQ,
+ match->channels[j])) {
+ nla_nest_cancel(msg, nl_freqs);
+ nla_nest_cancel(msg, nl_match);
+ goto out;
+ }
+ }
+
+ nla_nest_end(msg, nl_freqs);
+ }
+
+ nla_nest_end(msg, nl_match);
+ }
+
+out:
+ nla_nest_end(msg, nl_results);
+ return 0;
+}
+
void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev,
struct cfg80211_wowlan_wakeup *wakeup,
gfp_t gfp)
@@ -12038,6 +12398,10 @@ void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev,
goto free_msg;
}
+ if (wakeup->net_detect &&
+ cfg80211_net_detect_results(msg, wakeup))
+ goto free_msg;
+
nla_nest_end(msg, reasons);
}
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 1b3864cd50ca..35cfb7134bdb 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -993,4 +993,28 @@ rdev_del_tx_ts(struct cfg80211_registered_device *rdev,
return ret;
}
+static inline int
+rdev_tdls_channel_switch(struct cfg80211_registered_device *rdev,
+ struct net_device *dev, const u8 *addr,
+ u8 oper_class, struct cfg80211_chan_def *chandef)
+{
+ int ret;
+
+ trace_rdev_tdls_channel_switch(&rdev->wiphy, dev, addr, oper_class,
+ chandef);
+ ret = rdev->ops->tdls_channel_switch(&rdev->wiphy, dev, addr,
+ oper_class, chandef);
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
+static inline void
+rdev_tdls_cancel_channel_switch(struct cfg80211_registered_device *rdev,
+ struct net_device *dev, const u8 *addr)
+{
+ trace_rdev_tdls_cancel_channel_switch(&rdev->wiphy, dev, addr);
+ rdev->ops->tdls_cancel_channel_switch(&rdev->wiphy, dev, addr);
+ trace_rdev_return_void(&rdev->wiphy);
+}
+
#endif /* __CFG80211_RDEV_OPS */
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index b725a31a4751..32d8310b0f85 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -573,8 +573,9 @@ static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy)
return get_cfg80211_regdom();
}
-unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
- const struct ieee80211_reg_rule *rule)
+static unsigned int
+reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd,
+ const struct ieee80211_reg_rule *rule)
{
const struct ieee80211_freq_range *freq_range = &rule->freq_range;
const struct ieee80211_freq_range *freq_range_tmp;
@@ -622,6 +623,27 @@ unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
return end_freq - start_freq;
}
+unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
+ const struct ieee80211_reg_rule *rule)
+{
+ unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule);
+
+ if (rule->flags & NL80211_RRF_NO_160MHZ)
+ bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80));
+ if (rule->flags & NL80211_RRF_NO_80MHZ)
+ bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40));
+
+ /*
+ * HT40+/HT40- limits are handled per-channel. Only limit BW if both
+ * are not allowed.
+ */
+ if (rule->flags & NL80211_RRF_NO_HT40MINUS &&
+ rule->flags & NL80211_RRF_NO_HT40PLUS)
+ bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20));
+
+ return bw;
+}
+
/* Sanity check on a regulatory rule */
static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
{
@@ -946,6 +968,16 @@ static u32 map_regdom_flags(u32 rd_flags)
channel_flags |= IEEE80211_CHAN_NO_OFDM;
if (rd_flags & NL80211_RRF_NO_OUTDOOR)
channel_flags |= IEEE80211_CHAN_INDOOR_ONLY;
+ if (rd_flags & NL80211_RRF_GO_CONCURRENT)
+ channel_flags |= IEEE80211_CHAN_GO_CONCURRENT;
+ if (rd_flags & NL80211_RRF_NO_HT40MINUS)
+ channel_flags |= IEEE80211_CHAN_NO_HT40MINUS;
+ if (rd_flags & NL80211_RRF_NO_HT40PLUS)
+ channel_flags |= IEEE80211_CHAN_NO_HT40PLUS;
+ if (rd_flags & NL80211_RRF_NO_80MHZ)
+ channel_flags |= IEEE80211_CHAN_NO_80MHZ;
+ if (rd_flags & NL80211_RRF_NO_160MHZ)
+ channel_flags |= IEEE80211_CHAN_NO_160MHZ;
return channel_flags;
}
@@ -1565,10 +1597,23 @@ static void handle_channel_custom(struct wiphy *wiphy,
if (max_bandwidth_khz < MHZ_TO_KHZ(160))
bw_flags |= IEEE80211_CHAN_NO_160MHZ;
+ chan->dfs_state_entered = jiffies;
+ chan->dfs_state = NL80211_DFS_USABLE;
+
+ chan->beacon_found = false;
chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
chan->max_reg_power = chan->max_power =
(int) MBM_TO_DBM(power_rule->max_eirp);
+
+ if (chan->flags & IEEE80211_CHAN_RADAR) {
+ if (reg_rule->dfs_cac_ms)
+ chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
+ else
+ chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
+ }
+
+ chan->max_power = chan->max_reg_power;
}
static void handle_band_custom(struct wiphy *wiphy,
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index 277a85df910e..ad38910f7036 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2032,6 +2032,48 @@ TRACE_EVENT(rdev_del_tx_ts,
WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(peer), __entry->tsid)
);
+TRACE_EVENT(rdev_tdls_channel_switch,
+ TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+ const u8 *addr, u8 oper_class,
+ struct cfg80211_chan_def *chandef),
+ TP_ARGS(wiphy, netdev, addr, oper_class, chandef),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ NETDEV_ENTRY
+ MAC_ENTRY(addr)
+ __field(u8, oper_class)
+ CHAN_DEF_ENTRY
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ NETDEV_ASSIGN;
+ MAC_ASSIGN(addr, addr);
+ CHAN_DEF_ASSIGN(chandef);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT
+ " oper class %d, " CHAN_DEF_PR_FMT,
+ WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr),
+ __entry->oper_class, CHAN_DEF_PR_ARG)
+);
+
+TRACE_EVENT(rdev_tdls_cancel_channel_switch,
+ TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+ const u8 *addr),
+ TP_ARGS(wiphy, netdev, addr),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ NETDEV_ENTRY
+ MAC_ENTRY(addr)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ NETDEV_ASSIGN;
+ MAC_ASSIGN(addr, addr);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT,
+ WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr))
+);
+
/*************************************************************
* cfg80211 exported functions traces *
*************************************************************/
@@ -2355,6 +2397,22 @@ TRACE_EVENT(cfg80211_ch_switch_notify,
NETDEV_PR_ARG, CHAN_DEF_PR_ARG)
);
+TRACE_EVENT(cfg80211_ch_switch_started_notify,
+ TP_PROTO(struct net_device *netdev,
+ struct cfg80211_chan_def *chandef),
+ TP_ARGS(netdev, chandef),
+ TP_STRUCT__entry(
+ NETDEV_ENTRY
+ CHAN_DEF_ENTRY
+ ),
+ TP_fast_assign(
+ NETDEV_ASSIGN;
+ CHAN_DEF_ASSIGN(chandef);
+ ),
+ TP_printk(NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT,
+ NETDEV_PR_ARG, CHAN_DEF_PR_ARG)
+);
+
TRACE_EVENT(cfg80211_radar_event,
TP_PROTO(struct wiphy *wiphy, struct cfg80211_chan_def *chandef),
TP_ARGS(wiphy, chandef),